Git系统总结学习(三)

Git 分支

我们知道,Git 保存的不是文件差异或者变化量,而只是一系列文件快照。 在 Git 中提交时,会保存一个提交(commit)对象,它包含一个指向暂存内容快照的指针,作者和相关附 属信息,以及一定数量(也可能没有)指向该提交对象直接祖先的指针:第一次提交是没有直接祖先的,普通 提交有一个祖先,由两个或多个分支合并产生的提交则有多个祖先。

当使用 git commit 新建一个提交对象前,Git 会先计算每一个子目录(本例中就是项目根目录)的校验和, 然后在 Git 仓库中将这些目录保存为树(tree)对象。之后 Git 创建的提交对象,除了包含相关提交信息以 外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容 了。

Git 中的分支,其实本质上仅仅是个指向 commit 对象的可变指针。Git 会使用 master 作 为分支的默认名字。在若干次提交后,你其实已经有了一个指向最后一次提交对象的 master 分支,它在每次 提交的时候都会自动向前移动。

那么,Git 又是如何创建一个新的分支的呢?答案很简单,创建一个新的分支指针。比如新建一个 testing 分支,可以使用 git branch 命令:

git branch testing

那么,Git 是如何知道你当前在哪个分支上工作的呢?其实答案也很简单,它保存着一个名为 HEAD 的特别 指针。 Git 中,它是一个指向你正在工作中的本地分支的指针。运行 git branch 命令,仅仅是建立了一个新的分支, 但不会自动切换到这个分支中去,所以 要切换到其他分支,可以执行 git checkout 命令。我们现在转换到新建的 testing 分支:

git checkout testing

如果,此时我们执行命令git checkout master,它会把 HEAD 指针移回到 master 分支,并把工作目录中的文件换成了 master 分支 所指向的快照内容。也就是说,现在开始所做的改动,将始于本项目中一个较老的版本。它的主要作用是将 testing 分支里作出的修改暂时取消,这样你就可以向另一个方向进行开发。

基本的分支与合并

新建并切换到此分支

git checkout -b dev_bug_1

不过在此之前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生 冲突从而阻止 Git 为你转换分支。转换分支的时候最好保持一个清洁的工作区域。稍后会介绍几个绕过这种 问题的办法(分别叫做 stashing 和 amending)。

如果此时把dev_bug_1合并到 master 分支并发布到生产服务器。用 git merge 命 令来进行合并:

git checkout master
git merge dev_bug_1

请注意,合并时出现了 “Fast forward”(快进)提示。由于当前 master 分支所在的 commit 是要并入 的 hotfix 分支的直接上游(所谓上游,可以理解为随着时间流逝,我们进行的提交记录中,越早的记录,就相当于该分支的上游),我们把dev_bug_1合并到master,master的指针原先在”过去”的上游, Git 在合并两者后,master相当于”快速向前”(快速向距离最后一次提交的时间靠近了)推进了,又因为没有什么分歧需要解决,所以这个过程叫做快 进(Fast forward)。

~/Desktop/gitDemo(dev_bug_1) » git checkout master                                                          liuxingxing@liuxingxingdeMacBook-Pro
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
-------------------------------------------------------------------------------------------------------------------------------------------------
~/Desktop/gitDemo(master) » git merge dev_bug_1                                                             liuxingxing@liuxingxingdeMacBook-Pro
Updating 0d78f5a..3e3fc9c
Fast-forward
 README  | 1 +
 Text.md | 5 ++++-
 git     | 0
 3 files changed, 5 insertions(+), 1 deletion(-)
 create mode 100644 git

快 进(Fast forward)可以理解为视频的播放进度中快进的操作.

删除 分支

合并后,dev_bug_1和master指向相同的提交,现在假设dev_bug_1没什么用了,可以先删掉它:

~/Desktop/gitDemo(master) » git branch -d dev_bug_1                                                         
Deleted branch dev_bug_1 (was 3e3fc9c).

冲突的合并

有时候合并操作并不会如此顺利。如果你修改了两个待合并分支里同一个文件的同一部分,Git 就无法干净 地把两者合到一起(逻辑上说,这种问题只能由人来解决)。 Git 仍然会合并,但不会提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,可以用 git status 查阅 .任何包含未解决冲突的文件都会以未合并(unmerged)状态列出。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 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。 在 解决了所有文件里的所有冲突后,运行 git add 将把它们标记为已解决(resolved)。因为一旦暂存,就表示 冲突已经解决。 然后就可以用 git commit 来完成这次合并 提交。

分支管理

查看当前所有分支的清单 :

git branch

若要查看各个分支最后一次 commit 信息,运行 git branch -v:

git branch -v

要从该清单中筛选出你已经(或尚未)与当前分支合并的分支,可以用 –merge 和 –no-merged 选项 :

git branch --merge
git branch --no-merge

跟踪分支

从远程分支检出的本地分支,称为跟踪分支(tracking branch)。跟踪分支是一种和远程分支有直接联系的 本地分支。在跟踪分支里输入 git push,Git 会自行推断应该向哪个服务器的哪个分支推送数据。反过来,在 这些分支里运行 git pull 会获取所有远程索引,并把它们的数据都合并到本地分支中来。

推送

要想和其他人分享某个分支,你需要把它推送到一个你拥有写权限的远程仓库。你的本地分支不会被自动同 步到你引入的远程分支中,除非你明确执行推送操作 .如果你有个叫 dev_test 的分支需要和他人一起开发, 可以运行 git push (远程仓库名) (分支名):

~/Desktop/DLL/MyDesk/hubSource/blog(dev_test) » git push origin dev_test                                    liuxingxing@liuxingxingdeMacBook-Pro
Enumerating objects: 30, done.
Counting objects: 100% (30/30), done.
Delta compression using up to 12 threads
Compressing objects: 100% (15/15), done.
Writing objects: 100% (18/18), 5.02 KiB | 5.02 MiB/s, done.
Total 18 (delta 11), reused 0 (delta 0)
remote: Resolving deltas: 100% (11/11), completed with 4 local objects.
To github.com:smileasy/hubSource.git
   3e0867a..31dfae8  dev_test -> dev_test

删除远程分支

如果不再需要某个远程分支了 ,可以用这个非常无厘头的语法来删除它:git push [远程名] :[分支名]。如果想在服务器上删 除 dev_liuxingxing 分支 :

~/Desktop/DLL/MyDesk/hubSource/blog(dev_test*) » git push origin :dev_liuxingxing                           liuxingxing@liuxingxingdeMacBook-Pro
To github.com:smileasy/hubSource.git
 - [deleted]         dev_liuxingxing

有种方便记忆这条命令的方法:记住我们不久前见过的 git push [远程名] [本地分支]:[远程分支] 语法,如 果省略 [本地分支],那就等于是在说“在这里提取空白然后把它变成[远程分支]”。

当然,你也可以直接使用delete来删除分支,上面的命令如果不小心记错了,就会发生误删的情况:

git branch -d dev_leg_0923
git push origin --delete dev_leg_0923

更改本地和远程分支名称

如果你想将dev_leg分支改名为dev_liuxingxing,可以使用下面2条命令来完成操作:

git branch -m dev_leg dev_liuxingxing
git push origin dev_liuxingxing

储藏(Stashing)

储藏(Stashing) 可以将你的更改保存到一个未完结变更的堆栈中,随时可以重新应用:

~/Desktop/gitDemo(master*) » git stash
Saved working directory and index state WIP on master: 3e3fc9c 修改文件

要查看现有的储藏,你可以使用 git stash list:

git stash list

应用储藏:

git stash apply

git stash apply会使用栈顶的存储.

如果你想应用更 早的储藏,你可以通过名字指定它,像这样:git stash apply stash@{1} :

git stash apply stash@{1}

apply 选项只尝试应用储藏的工作——储藏的内容仍然在栈上。要移除它,你可以运行 git stash drop,加上 你希望移除的储藏的名字:

~/Desktop/gitDemo(master*) » git stash drop stash@{1}
Dropped stash@{1} (2e9346692e3aa4f58b0f0e868f9381af209f6b62)

你也可以运行 git stash pop 来重新应用储藏,同时立刻将其从堆栈中移走。

从储藏中创建分支

如果你储藏了一些工作,暂时不去理会,然后继续在你储藏工作的分支上工作,你在重新应用工作时可能会 碰到一些问题。如果尝试应用的变更是针对一个你那之后修改过的文件,你会碰到一个归并冲突并且必须去化 解它。如果你想用更方便的方法来重新检验你储藏的变更,你可以运行 git stash branch,这会创建一个新的分 支,检出你储藏工作时的所处的提交,重新应用你的工作,如果成功,将会丢弃储藏。

~/Desktop/gitDemo(master) » git stash branch dev_branch
Switched to a new branch 'dev_branch'
On branch dev_branch
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   Text.md

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (abaf4f5a6e22d8fa51d3c9f93652133279d7b946)

重写历史

很多时候,在 Git 上工作的时候,你也许会由于某种原因想要修订你的提交历史。

改变最近一次提交

改变最近一次提交也许是最常见的重写历史的行为。对于你的最近一次提交,你经常想做两件基本事情:改 变提交说明,或者改变你刚刚通过增加,改变,删除而记录的快照。

如果你只想修改最近一次提交说明,这非常简单:

git commit --amend

这会把你带入文本编辑器,里面包含了你最近一次提交说明,供你修改。当你保存并退出编辑器,这个编辑 器会写入一个新的提交,里面包含了那个说明,并且让它成为你的新的最近一次提交。 如果你完成提交后又想修改被提交的快照,增加或者修改其中的文件,可能因为你最初提交时,忘了添加一 个新建的文件,这个过程基本上一样。你通过修改文件然后对其运行git add或对一个已被记录的文件运行git rm,随后的git commit –amend会获取你当前的暂存区并将它作为新提交对应的快照。 使用这项技术的时候你必须小心,因为修正会改变提交的SHA-1值。这个很像是一次非常小的rebase——不 要在你最近一次提交被推送后还去修正它。

修改多个提交说明

要修改历史中更早的提交, 例如,你想修改最近三次的提交说明,或者其中任意一次,你必须给git rebase -i提供一个参数,指明你想 要修改的提交的父提交,例如HEAD2或者HEAD3。可能记住~3更加容易,因为你想修改最近三次提交;但是请记 住你事实上所指的是四次提交之前,即你想修改的提交的父提交。

~/Desktop/gitDemo(devtest) » git rebase -i HEAD~3
Successfully rebased and updated refs/heads/devtest.

再次提醒这是一个衍合命令——HEAD~3..HEAD范围内的每一次提交都会被重写,无论你是否修改说明。不要涵 盖你已经推送到中心服务器的提交——这么做会使其他开发者产生混乱,因为你提供了同样变更的不同版本。


   转载规则


《Git系统总结学习(三)》 刘星星 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Java学习笔记(一) Java学习笔记(一)
Java开发环境 编译期:Java经过编译,生成.class字节码文件。 运行期:JVM加载.class文件并运行。 JVM:Java虚拟机,加载并运行.class JRE: Java运行时环境,除了包含JVM以外还包含了运行Jav
2017-07-01
下一篇 
Git系统总结学习(二) Git系统总结学习(二)
远程分支查看当前的远程库查看当前配置有哪些远程仓库,可以用 git remote 命令,它会列出每个远程库的简短名字。在克隆完某个项目后,至少可以看到一个名为 origin 的远程库,Git 默认使用这个名字来标识你所克隆的原始仓库
2017-06-03
  目录