Git基础学习
基本都参考自 Git-book。
什么是版本控制
本地版本控制系统:采用某种简单的数据库来记录文件的历次更新差异。
缺点:无法让不同系统上的开发者协同工作。
集中化的版本控制系统:一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
优点:每个人都可以在一定程度上看到项目中的其他人正在做些什么。 而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。
缺点:中央服务器的单点故障,有丢失所有历史更新记录的风险。
分布式版本控制系统:客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。
Git 的特点
直接记录快照,而非差异比较。
基于差异的版本控制:
Git 对待数据更像是一个快照流:
近乎所有操作都是本地执行
速度快,离线的时候也能操作。
Git 保持完整性
Git 所有数据在存储前都计算校验和,然后以校验和来引用。这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。
Git 计算校验和的机制是 SHA-1 Hash。
Git 一般只添加数据
Git 操作几乎只往 Git 数据库中添加数据。 你很难使用 Git 从数据库中删除数据,也就是说 Git 几乎不会执行任何可能导致文件不可恢复的操作。
三种状态
文件有三种状态:
- 已修改(modified):已修改表示修改了文件,但还没保存到数据库中。
- 已暂存(staged):已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
- 已提交(committed):已提交表示数据已经安全地保存在本地数据库中。
因而,Git 项目拥有三个阶段:工作区、暂存区和(本地)Git 仓库。
基本的 Git 工作流程如下:
- 在工作区中修改文件。
- 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。
- 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录。
Git 配置
可以使用 git config
命令来设置一些配置变量,这些变量存储在:
/etc/gitconfig
文件:系统上每一个用户的通用配置。执行git config
时带上--system
选项,需要管理员或超级用户权限。~/.gitconfig
或~/.config/git/config
文件:只针对当前用户。传递--global
选项,对系统上所有的仓库生效。- 当前使用仓库的 Git 目录中的
config
文件(即.git/config
):针对该仓库。 传递--local
选项(其实默认就是它)。
每一个级别的设置会覆盖上一级别的设置。
以下命令查看所有的配置以及它们所在的文件:
1 |
|
安装完 Git 之后要设置用户名和邮箱,它们会写入到每一次提交中,不可更改:
1 |
|
这里使用了
--global
选项,因此对系统上所有仓库有效。
获取 Git 仓库
有两种获取 Git 项目的方式:
将尚未进行版本控制的本地目录转换为 Git 仓库:
1
$ git init
该命令创建一个名为
.git
的子目录,含有初始化的 Git 仓库中所有的必须文件。从其他服务器上 clone 一个已存在的 Git 仓库:
1
$ git clone https://github.com/libgit2/libgit2
可以通过额外的参数在 clone 的时候自定义本地仓库的名字:
1
$ git clone https://github.com/libgit2/libgit2 mylibgit
上面的例子使用的是
https://
协议,也可以使用git://
协议或者 SSH 传输协议。
记录每次更新到仓库
工作目录下的每一个文件都不外乎这两种状态:已跟踪或未跟踪。已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后, 它们的状态可能是未修改,已修改或已放入暂存区。简而言之,已跟踪的文件就是 Git 已经知道的文件。
检查当前文件状态
使用 git status
命令查看哪些文件处于什么状态,git status -s
或 git status --short
输出更紧凑的格式。
跟踪新文件
使用 git add
命令跟踪新文件,参数是文件或目录的路径,如果参数是目录的路径,则该命令将递归地跟踪该目录下的所有文件。
暂存已修改文件
git add
还可以用来把已修改的文件放入暂存区。事实上它是一个多功能命令,理解为“精确地将内容添加到下一次提交中”。
忽略文件
文件 .gitignore
列出无需纳入 Git 管理的文件,格式规范如下:
- 所有空行或者以
#
开头的行都会被 Git 忽略。 - 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
- 匹配模式可以以(
/
)开头防止递归。 - 匹配模式可以以(
/
)结尾指定目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(
!
)取反。
glob 模式:shell 所使用的简化了的正则表达式
- 星号(
*
)匹配零个或多个任意字符;[abc]
匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);- 问号(
?
)只匹配一个任意字符;- 如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如
[0-9]
表示匹配所有 0 到 9 的数字);- 使用两个星号(
**
)表示匹配任意中间目录,比如a/**/z
可以匹配a/z
、a/b/z
或a/b/c/z
等。
查看修改
使用 git diff
命令(不带任何参数)比较工作目录中当前文件和暂存区快照之间的差异,也即修改之后还为暂存的变化内容。
使用 git diff --staged
或者 git diff --cached
(--staged
和 --cached
是同义词)比较已暂存文件与上一次提交的文件的差异。
提交更新
使用 git commit
命令会启动文本编辑器来输入提交说明。默认的提交信息包含最后一次运行 git status
的输出(在注释行中)。
也可以使用 -m
选项将提交信息和命令放在同一行。
提交后 Git 会输出,当前是在哪个分支提交的,本次提交的完整 SHA-1 校验和是什么,以及在本次提交中,有多少文件修订过,多少行添加和删改过。
跳过暂存区
给 git commit
加上 -a
选项,Git 就会自动把所有已跟踪的文件暂存起来一并提交,从而跳过 git add
步骤。
移除文件
使用 git rm
命令可以从工作区和暂存区中一并删除指定的文件。
如果只是从工作区手动删除文件,则运行 git status
会在 “Changes not staged for commit” 部分看到相应信息。这时再运行 git rm
可以记录下此次删除文件的操作。如果要删除之前修改过或已经放入暂存区的文件,则必须使用强制删除选项 -f
。这是一种安全特性,用于防止误删尚未添加到快照的数据,这样的数据不能被 Git 恢复。
使用 git rm --cached
命令只从暂存区删除指定文件,文件依然保留在工作区。
移动文件
git mv file_from file_to
查看提交历史
git log
命令按时间先后顺序列出所有的提交(SHA-1 校验和、作者名字和邮箱、提交时间以及提交说明)。
git log -p
或 git log --patch
会显示每次提交所引入的差异,也可以限制显示的日志条目数量,例如使用 -2
选项来只显示最近的两次提交。
git log --stat
可以附带上每次提交的简略统计信息(列出所有被修改的文件、有多少文件被修改了以及被修改的文件的哪些行被移除或是添加了)。
--pretty
选项使用不同于默认格式的方式展示提交历史。它有一些内置的子选项,例如 oneline
、short
、full
、fuller
、format
等。
--graph
选项能形象地展示分支、合并的历史,与 oneline
或 format
结合使用更佳。
撤销操作
如果提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了,可以用 git commit --amend
重新提交。这个命令会将暂存区中的文件提交。 如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令), 那么快照会保持不变,而你所修改的只是提交信息。
取消暂存的文件:使用 git reset HEAD <file>...
来取消暂存(执行 git status
后有提示)。
撤销对文件的修改:使用 git checkout -- <file>...
恢复工作区文件到上一次提交的样子(执行 git status
后有提示)。
远程仓库的使用
查看远程仓库
使用 git remote
命令查看已经配置的远程仓库服务器,它会列出指定的每一个远程服务器的简写。clone 下来的仓库服务器默认名字为 origin。
使用 git remote -v
可以显示简写和对应的 URL。
添加远程仓库
使用 git remote add <shortname> <url>
添加一个新的远程仓库,同时指定一个简写。
从远程仓库 fetch 与 pull
使用 git fetch <remote>
拉取所有你还没有的数据。执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。git fetch
命令只会将数据下载到你的本地仓库——它并不会自动合并或修改你当前的工作。
如果当前分支设置了跟踪远程分支,那么可以用 git pull
命令来自动抓取后合并该远程分支到当前分支。
推送到远程仓库
git push <remote> <branch>
查看某个远程仓库
使用 git remote show <remote>
查看某一个远程仓库的更多信息。
远程仓库的重命名和移除
使用 git remote rename
修改某一个远程仓库的简写名。
使用 git remote remove
或 git remote rm
移除一个远程仓库。
Git 分支
新建分支
为了形象地说明,假设我们暂存了三个文件,则 Git 会为每一个文件计算校验和,然后将当前版本的文件快照用 blob 对象保存下来;commit 时,Git 会计算每一个子目录的校验和,然后将这些校验和保存为 tree 对象。随后 Git 创建一个 commit 对象,包含指向上述 tree 对象的指针。
做一些修改后再次提交,则这次产生的 commit 对象包含指向上一次的 commit 对象(父对象)的指针。
Git 分支的本质是指向提交对象的可变指针。Git 默认分支名字是 master
,在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master
分支。 master
分支会在每次提交时自动向前移动。
使用 git branch <newbranchname>
命令创建一个分支,本质是创建了一个可移动的指针。为了知道当前在哪一个分支上,Git 维护一个特殊的 HEAD
指针,指向当前所在的本地分支。
使用 git log --decorate
可以查看各个分支当前所指的对象。
使用 git checkout
切换到一个已存在的分支,本质是将 HEAD
指针切换到分支指针上。这时再提交内容时,HEAD
指针指向的那个分支指针向前移动,而其他分支指针不移动:
再使用 git checkout
回到 master
分支时,除了 HEAD
指针指回 master
分支,工作区也会恢复成 master
分支指向的快照内容。这时再次修改,则提交历史会产生分叉。
git log --oneline --decorate --graph --all
会输出你的提交历史、各个分支的指向以及项目的分支分叉情况。
注意,git branch
只创建分支而不切换,需要再执行 git checkout
。如果想创建之后立即切换,可以使用 git checkout -b <newbranchname>
命令。
使用 git branch -d
删除分支。
分支的合并
使用 git merge
命令合并分支,假设当前在 A 分支,则 git merge B
会将 B 分支的内容合并到 A 中。如果 B 是 A 的直接后继,则 Git 要做的事情仅仅是将 A 指针移动到 B 指针相同的地方,这被称做 fast-forward;如果合并的两个分支有分叉,则 Git 会对待合并的两个分支以及它们的公共祖先做一个简单的三方合并:
Git 会对三方合并的结果做一个快照并创建一个新的提交指向它,这被称做一个合并提交,它不只有一个父提交。
有冲突的分支合并
如果两个不同的分支对同一个文件的同一个部分进行了不同的修改,合并时就会产生冲突。此时 Git 做了合并,但是没有自动地创建一个新的合并提交。Git 会暂停下来,等待你去解决合并产生的冲突。你可以在合并冲突后的任意时刻使用 git status
命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件。
解决冲突后可以输入 git commit
来完成合并提交。
分支管理
不带任何参数的 git branch
会给出当前所有分支的列表,*
标识当前分支(HEAD
指针指向的分支)。
git branch -v
命令可以查看每一个分支的最后一次提交。
git branch --merge
查看哪些分支已经合并到当前分支,git branch --no-merged
查看尚未合并到当前分支的分支。
远程分支
远程跟踪分支以 <remote>/<branch>
的形式命名。正如前文所说,clone 的远程服务器默认取名为 origin
。
假设你在本地的 master
分支上做了一些工作,而其他人 push 到 origin
并更新了它的 master
分支。但只要你保持不与 origin
服务器连接(并拉取数据),你的 origin/master
指针就不会移动。
git fetch <remote>
命令抓取 remote 服务器上本地没有的数据并更新本地数据库,移动 origin/master
指针到更新之后的位置:
推送
git push <remote> <branch>
git push <remote> <localbranchname>:<remotebranchname>
删除远程分支
git push <remote> --delete <branch>
变基
在 Git 中整合来自不同分支的修改主要有两种方法:merge
以及 rebase
。
假设有这样的分支历史:
变基的操作是:首先 checkout
到 experiment
分支,然后 git rebase master
。
它的原理是首先找到这两个分支(即当前分支 experiment
、变基操作的目标基底分支 master
) 的最近共同祖先 C2
,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3
,最后以此将之前另存为临时文件的修改依序应用。