1.分支常用操作
分支新建和合并的例子
1.1 分支创建
Git 创建了一个可以移动的新的指针。 比如,创建一个 testing 分支, 你需要使用 git branch 命令:
$ git branch testing
注意git branch并不会自动切换到新分支中去,HEAD 指向当前所在的分支,可以使用 git log 命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate
$ git log --oneline --decorate
1.2 分支切换
要切换到一个已存在的分支,你需要使用 git checkout 命令。 我们现在切换到新创建的 testing 分支,让HEAD 就指向 testing 分支:
$ git checkout testing
这样的实现方式会给我们带来什么好处呢? 现在不妨再提交一次:
$ vim test.rb
$ git commit -a -m 'made a change'
testing 分支向前移动了,但是 master 分支却没有,它仍然指向运行 git checkout 时所指的对象。 现在我们切换回 master 分支看看:
$ git checkout master
这条命令做了两件事。 一是使 HEAD 指回 master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。 即恢复老版本。 本质上来讲,这就是忽略 testing 分支所做的修改,以便于向另一个方向进行开发。
再稍微做些修改并提交:
$ vim test.rb
$ git commit -a -m 'made other changes'
现在,这个项目的提交历史已经产生了分叉 你可以使用 git log 命令查看分叉历史。 运行 git log --oneline --decorate --graph --all ,它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况。
$ git log --oneline --decorate --graph --all
如果要创建新分支的同时切换过去,则
git checkout -b <newbranchname>
相当于branch命令和checkout命令合到一起
1.3 分支合并及删除
Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase,rebase见下文4 变基rebase
1.3.1 合并原理
例如,开发任务分叉到两个不同分支,又各自提交了更新。 merge 命令。 它会把两个分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。
1.3.2 操作
将 hotfix 分支合并回master 分支来部署到线上
$ git checkout master
$ git merge hotfix
此时merge和checkout指向同一个分支,可以使用带 -d 选项的 git branch 命令来删除分支:
$ git branch -d hotfix
1.4 遇到冲突时的分支合并
1.4.1 常用方法
在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,在合并它们的时候就会产生合并冲突
$ git merge iss53
你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件:
$ git status
Git 会在有冲突的文件中加入标准的冲突解决标记,可以打开这些包含冲突的文件然后手动解决冲突。 出现冲突的文件会包含一些特殊区段:
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
这表示 HEAD 所指示的版本(也就是你的 master 分支所在的位置,因为你在运行 merge 命令的时候已经检出到了这个分支)在这个区段的上半部分(======= 的上半部分),而 iss53 分支所指示的版本在 ======= 的下半部分。 为了解决冲突,你必须选择使用由 ======= 分割的两部分中的一个,或者你也可以自行合并这些内容。 可以通过把这段内容换成下面的样子来解决冲突:
<div id="footer">
please contact us at email.support@github.com
</div>
上述的冲突解决方案仅保留了其中一个分支的修改,并且 <<<<<<< , ======= , 和 >>>>>>> 这些行被完全删除了。 在你解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。
1.4.2 mergetool
使用图形化工具来解决冲突,你可以运行 git mergetool,该命令会为你启动一个合适的可视化合并工具,并带领你一步一步解决这些冲突:
$ git mergetool
如果想使用除默认工具外的其他合并工具,可以在 “下列工具中(one of the following tools)” 这句后面看到所有支持的合并工具,输入喜欢的工具名字。
退出合并工具之后,Git 会询问刚才的合并是否成功。 如果你回答是,Git 会暂存那些文件以表明冲突已解决: 你可以再次运行 git status 来确认所有的合并冲突都已被解决:
$ git status
如果对结果感到满意,并且确定之前有冲突的的文件都已经暂存了,这时你可以输入 git commit 来完成合并提交。
2 分支管理
2.1 查看所有分支
git branch 命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一个列表:
$ git branch
iss53
* master
testing
如果需要查看每一个分支的最后一次提交,可以运行 git branch -v 命令:
$ git branch -v
iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes
2.2 已合并及未合并分支查看
–merged 与 --no-merged 这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。 如果要查看哪些分支已经合并到当前分支,可以运行 git branch --merged :
$ git branch --no-merged
尝试使用 git branch -d 命令删除未合并的分支时会失败 可以使用 -D 选项强制删除它
可以提供一个附加的参数来查看其它分支的合并状态而不必检出它们。 例如,尚未合并到 master 分支的有哪些?
$ git branch --no-merged master
3 远程分支
3.1 深入理解git基本操作(clone等)
它们以 <remote>/<branch> 方式命名 远程引用是对远程仓库的引用(指针),包括分支、标签等等。 你可以通过 git ls-remote <remote> 来显式地获得远程引用的完整列表, 或者通过 git remote show <remote> 获得远程分支的更多信息。一个更常见的做法是利用远程跟踪分支。
3.1.1 clone
假设网络里有一个在 git.ourcompany.com 的 Git 服务器。 clone 命令会为你自动将其命名为 origin,拉取它的所有数据, 创建一个指向它的 master 分支的指针,并且在本地将其命名为 origin/master 。 Git 也会给你一个与 origin 的 master 分支在指向同一个地方的本地 master 分支。 如果你运行
git clone -o booyah
默认的远程分支名字将会是 booyah/master。
3.1.2 fetch
如果要与给定的远程仓库同步数据,运行 git fetch <remote> 命令,这个命令查找 “origin” 是哪一个服务器(例如 git.ourcompany.com), 从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master 指针到更新之后的位置。
3.1.3 remote add
有多个远程仓库与远程分支的情况,我们假定你有另一个内部 Git 服务器,仅服务于你的某个敏捷开发团队。 这个服务器位于 git.team1.ourcompany.com。 你可以运行
$ git remote add teamone git.team1.ourcompany.com
命令添加一个新的远程仓库引用到当前的项目,这个命令我们会在 Git 基础 中详细说明。 将这个远程仓库命名为 teamone,将其作为完整 URL 的缩写。
3.1.4 push
希望和别人一起在名为 serverfix 的分支上工作,你可以像推送第一个分支那样推送它。 运行 git push <remote> <branch>
$ git push origin serverfix
Git 自动将 serverfix 分支名字展开为 refs/heads/serverfix:refs/heads/serverfix , 表示“推送本地的 serverfix 分支来更新远程仓库上的 serverfix 分支。” 因此也可以是
$ git push origin serverfix:serverfix
不想让远程仓库上的分支叫做 serverfix,可以运行
$ git push origin serverfix:awesomebranch
来将本地的 serverfix 分支推送到远程仓库上的 awesomebranch 分支。
当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 换句话说,这种情况下,不会有一个新的 serverfix 分支——只有一个不可以修改的 origin/serverfix 指针。 可以运行 git merge origin/serverfix 将这些工作合并到当前所在的分支。 如果想要在自己的 serverfix 分支上工作,可以将其建立在远程跟踪分支之上:
$ git checkout -b serverfix origin/serverfix
一个用于工作的本地分支,并且起点位于 origin/serverfix。
3.1.5 pull 拉取
当 git fetch 命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。 git pull 在大多数情况下它的含义是一个 git fetch 紧接着一个 git merge 命令。 如果有一个设置好的跟踪分支,不管它是显式地设置还是通过 clone 或 checkout 命令为你创建的,git pull 都会查找当前分支所跟踪的服务器与分支, 从服务器上抓取数据然后尝试合并入那个远程分支。
通常单独显式地使用 fetch 与 merge 命令会更好一些。
3.1.6 delete
假设你已经通过远程分支做完所有的工作了——也就是说你和你的协作者已经完成了一个特性, 并且将其合并到了远程仓库的 master 分支(或任何其他稳定代码分支)。 可以运行带有 --delete 选项的 git push 命令来删除一个远程分支。 如果想要从服务器上删除 serverfix 分支,运行下面的命令:
$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
- [deleted] serverfix
基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。
3.2 跟踪分支
从一个远程跟踪分支检出一个本地分支会自动创建所谓的“跟踪分支”(它跟踪的分支叫做“上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
3.2.1 创建跟踪分支
当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支 git checkout -b <branch> <remote>/<branch> 。 这是一个十分常用的操作所以 Git 提供了 --track 快捷方式:
$ git checkout --track origin/serverfix
如果你尝试检出的分支满足条件:
那么 Git 就会为你创建一个跟踪分支:
$ git checkout serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
如果想要将本地分支与远程分支设置为不同的名字,使用上一个命令增加一个不同名字的本地分支:
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'
现在,本地分支 sf 会自动从 origin/serverfix 拉取。
3.2.2 用已有本地分支跟踪远程分支
设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支, 你可以在任意时间使用 -u 或 --set-upstream-to 选项运行 git branch 来显式地设置。
$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
上游快捷方式: 当设置好跟踪分支后,可以通过简写 @{upstream} 或 @{u} 来引用它的上游分支。 所以在 master 分支时并且它正在跟踪 origin/master 时,如果愿意的话可以使用 git merge @{u} 来取代 git merge origin/master 。
3.2.3 查看跟踪分支
查看设置的所有跟踪分支,可以使用 git branch 的 -vv 选项。 这会将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。
$ git branch -vv
iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets
master 1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
testing 5ea463a trying something new
看到 iss53 分支正在跟踪 origin/iss53 并且 “ahead” 是 2,意味着本地有两个提交还没有推送到服务器上。 也能看到 master 分支正在跟踪 origin/master 分支并且是最新的。 接下来可以看到 serverfix 分支正在跟踪 teamone 服务器上的 server-fix-good 分支并且领先 3 落后 1, 意味着服务器上有一次提交还没有合并入同时本地有三次提交还没有推送。 最后看到 testing 分支并没有跟踪任何远程分支。
需要重点注意的一点是这些数字的值来自于你从每个服务器上最后一次抓取的数据。 这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据。 如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。 这样做:
$ git fetch --all; git branch -vv
4 变基 rebase
4.1 基本原理和操作
提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。 使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上
检出 experiment 分支,然后将它变基到 master 分支上:
$ git checkout experiment
$ git rebase master
原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master) 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。 现在回到 master 分支,进行一次快进合并。
$ git checkout master
$ git merge experiment
这两种整合方法的最终结果没有任何区别,变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的, 但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。
一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个其他人维护的项目贡献代码时。 在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到 origin/master 上,然后再向主项目提交修改。 这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。
4.2 一个主题分支里再分出一个主题分支的提交变基
![一个主题分支里再分出一个主题分支的提交历史](https://img-blog.csdnimg.cn/img_convert/4ef5818f 假设你希望将 client 中的修改合并到主分支并发布,但暂时并不想合并 server 中的修改, 因为它们还需要经过更全面的测试。这时,你就可以使用 git rebase 命令的 --onto 选项, 选中在 client 分支里但不在 server 分支里的修改(即 C8 和 C9),将它们在 master 分支上重放:
$ git rebase --onto master server client
意思是:“取出 client 分支,找出它从 server 分支分歧之后的补丁, 然后把这些补丁在 master 分支上重放一遍,让 client 看起来像直接基于 master 修改一样”。 达到的效果: 现在可以快进合并 master 分支了。(如图 快进合并 master 分支,使之包含来自 client 分支的修改):
$ git checkout master
$ git merge client
然后再将 server 分支中的修改也整合进来。 使用 git rebase <basebranch> <topicbranch> 命令可以直接将主题分支 (即本例中的 server)变基到目标分支(即 master)上。 这样做能省去你先切换到 server 分支,再对其执行变基命令的多个步骤。
$ git rebase master server
然后就可以快进合并主分支 master 了:
$ git checkout master
$ git merge server
至此,client 和 server 分支中的修改都已经整合到主分支里了, 你可以删除这两个分支,最终提交历史会变成图 最终的提交历史 中的样子:
$ git branch -d client
$ git branch -d server
4.3 变基的风险
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,不要执行变基。 如果习惯使用 git pull ,同时又希望默认使用选项 --rebase,你可以执行这条语句 git config --global pull.rebase true 来更改 pull.rebase 的默认配置。
如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。
如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 git pull --rebase 命令,这样尽管不能避免伤痛,但能有所缓解。
|