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,最后以此将之前另存为临时文件的修改依序应用。
