Git Branch 的操作与基本工作流程

Git Branch 的操作与基本工作流程

branch (分支)应该是Git 最重要的技能了,在一个多人专案的开发过程中我们有时候要开发新功能,有时候是要修正某个Bug,有时候想要测试某个特异功能能不能work ,这时候我们通常都会从主branch 再开出一条新的branch 来做,这支新开的branch 会带着你的主branch 目前的最新状态,当你完成你所要开发的新功能/ Bug 修正后确认没问题就再把它merge(合并)回主Branch ,如此便完成了新功能的开发或是Bug 的修正,因此每个人都可以从主branch 拉一条新的branch 来做自己想做的事,再来我们好好了解一下branch 的使用。

了解branch 最好的方法就是有图像可以看,你可以用任何你已安装的GUI来查看,但在你安装Git 的时候其实同时也安装了最基本的Git GUI 叫做gitk,你可以使用gitk – -all 这个指令来呼叫他,此时你应该能看到以下的画面:

gitk

点击左上方区块的commit 节点你可以看到当次commit 的详细资料,例如作者以及他的更新记录,但你会发现这时候终端机是无法输入的,因此请你先关掉gitk ,改打指令gitk –all & 来让gitk 在背景执行。

git branch 这个指令可以列出所有的 branch 并告诉你目前正在哪个 branch:

$ git branch
* master
develop
feature/test
上面的讯息告诉我们在这个 Git repository里有3支 branch ,而你目前正在 master branch 上。假设我们现在要开一支新的 branch 叫做 cat ,使用 git branch 来帮助你开一支新的 branch

$ git branch cat
$ git branch
cat
* master
上面我们开了一支新的branch 叫做cat ,使用git branch 再查看一次发现已经多了这支新的branch了,这时候你去查看你的gitk 的图像状态会发现像下图一样,新的branch cat 与master 在同一条水平线上,表示目前他们的状态是一模一样的。

git branch

你应该也有发现,虽然我们建立了一个 cat 的 branch ,但其实我们所在的 branch 还是在 master branch,因此我们现在还需要切换过去,因此我们使用 git checkout 来切换:

$ git checkout cat
Switched to branch ‘cat’
这样就会从原本的 mater branch 切换到 cat branch 了。

接下来假设我正在 cat 这支 branch 做开发,因此新增一个档案,加上一些内容,将它 add 到 stage 后再 commit 它。

$ vim lib/cat.rb
$ git status
# On branch cat
# Untracked files:
# (use “git add …” to include in what will be committed)
#
# lib/cat.rb
nothing added to commit but untracked files present (use “git add” to track)
$ git add lib/cat.rb
$ git commit -m “Add Cat.rb”
[cat ea7d309] Add Cat.rb
1 files changed, 3 insertions(+), 0 deletions(-)
create mode 100644 lib/cat.rb
cat branch

上面的流程你已经很熟悉了,接下来我们再切换到原本的 master branch ,这时候你会发现刚刚在 cat branch 新增的 cat.rb 档案已经不见了。

$ git checkout master
Switched to branch ‘master’
Your branch is ahead of ‘origin/master’ by 1 commit.
master branch

Git 在我们切换branch 的同时就会很聪明的会把我们的工作目录更动成那个branch 该有的状态,如果你这时候切换到GUI 去看,你会发现到与刚刚cat 和master branch 在同一条水平线上不同,cat branch现在已经比master branch 再多出了一个commit 的内容。

gitk

现在我在切换到 cat branch 去增加更多的内容,一样再将它 add 到 stage 后,再 commit 它。

$ git checkout cat
Switched to branch ‘cat’
$ mvim lib/cat.rb
$ git add lib/cat.rb
$ git commit -m “Add initializer”
[cat a3bce42] Add initializer
1 files changed, 3 insertions(+), 1 deletions(-)
add initializer

切到 GUI 来看的话你会发现现在 cat 这支 branch 比 master branch 要在多上两个 commit 的内容。

gitk

如果这时候我们在 master 上继续开发会发生什么事呢?我们现在切换到 master branch 并增加一个档案及内容,照惯例 add 后 commit。

$ git co master
Switched to branch ‘master’
Your branch is ahead of ‘origin/master’ by 1 commit.
$ git add spec/animal_spec.rb
$ git commit -m “Another spec”
[master 7d72927] Another spec
1 files changed, 2 insertions(+), 0 deletions(-)
master another spec

我在 master branch 的 animal_spec.rb 增加了一些内容,把它 commit 之后我们切换到 GUI 来看。

master spec

我们发现现在 master branch 与 cat branch 已经产生分歧了,因为两支 branch 都有了各自往后开发的 commit ,而且由于 master branch 最后一次的 commit 时间较新因此排列在最前面。

Git rebase 整理现在的 branch

假设我们现在在cat branch 的开发动作已经完毕,通常我们现在要做的事情会是将cat branch 合并回master branch,在开发流程上, master branch 就像是一个主要的branch ,每个开发人员都是从master branch checkout 出去一支新的branch 做开发,在开发完毕后就再将开发完的branch 合并回master branch,因此master branch 都会保有最新的开发好的状态,一般在Git 教学中会教你现在使用git merge 这个指令来将两个branch 合并,但这边我要先教你的是git rebase 这个指令。

与 git merge 不同的是, git rebase 不单单只是将两个不同的 branch 合并起来,而是将某一支 branch 基于另一支 branch 的内容合并起来,这是什么意思?以我们的例子来说,我们在cat branch 开发完了以后,这时候我们的master branch 也有了其他开发者所合并回去的内容,换句话说现在的master branch 与我们当初checkout 出去的时候的状态已经不同了,但我们会希望我们现在这支cat branch 的内容就像是刚刚从master branch checkout出来一样干净,也就是说让cat branch 中保有master branch 最新的状态, git rebase 会基于master branch 目前最后一次的commit 内容再往后把你在cat branch 上commit 的内容加上去,我们现在在cat branch 输入git rebase master 来将cat branch 基于master branch 做rebase。

$ git rebase master
First, rewinding head to replay your work on top of it…
Applying: Add Cat.rb
Applying: Add initializer
过程中没有发生冲突,这时候我们到 GUI 看看现在的结果。

git rebase

原先cat branch 上的两个commit (Add Cat.rb 和Add initializer) 已经合并到master branch 最新的commit (Another spec),换句话说目前cat branch 的内容就像是刚从master branch 所checkout 出来然后再加上自己的commit,因此不同于git merge 的线图会把cat branch 合并到master branch , 而是把原本的cat branch 接到master branch 因此只有一条线,当一个专案有很多的branch 再做开发的时候会避免很多branch 的线接来接去难以辨认。

开发过程中,若你在开发的 branch 功能比较多, commit 的量也比较多时,建议使用rebase将你现在的 branch 整理过再合并回主干,这样会产生较漂亮的线图

若你想要看看目前的 branch 与其他 branch 有哪些差异,你可以使用 git diff 的指令去观察,例如我现在想要看 master 跟 cat 这两个 branch 的差异,我只要下:

$ git diff cat master
diff –git a/lib/cat.rb b/lib/cat.rb
deleted file mode 100644
index 1227d26..0000000
— a/lib/cat.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class Cat < Animal
– def initialize
– super
– end
-end
你就可以看到现在的 cat branch 跟 master branch 的差异在哪了。

如果我们开发完毕时,我们会把开发好的东西合并回 master 很自然的我们通常都会使用 git merge 这个指令来合并两个branch

$ git merge cat
Merge made by recursive.
lib/cat.rb | 3 +++
1 files changed, 3 insertions(+), 0 deletions(-)
create mode 100644 lib/cat.rb
这时候我们看图的话会是这样:

git merge

可以看到我刚刚在master branch 下了git merge cat 这个指令来告诉git 要merge cat 到现在所在的branch ,因此在图上就看到了cat branch 拉一条线回来合并到了master 这个branch 了,解释这张图的意思就是, cat branch 从master branch 的Another spec 这一次的commit 分支出来后,自己产生了三次的commit (Add Cat.rb、Add initializer、Rever “Add initializer”) 然后合并到master。

Confict: 处理 Git 合并时的冲突:

很常发生的情况是再merge 或是rebase 的图中产生了convict (冲突),这时候Git 会停下来请你去处理,例如我们在cat 和master 的branch 都对lib/cat.rb 这支档案做编辑,然后我们将他们merge:

$ git merge cat
Auto-merging lib/cat.rb
CONFLICT (content): Merge conflict in lib/cat.rb
Automatic merge failed; fix conflicts and then commit the result.
你会看到Git 告诉你在你合并的过程中在lib/cat.rb 这支档案发生了冲突,Git 不知道该怎么处理因此要你去处理它,这时候我们打开这支档案会看到这样的情况:

git confict

<<<<<<<<<< HEAD 到========== 的中间区域是目前你所在branch 的commit 内容,而从=========== 到>> >>>>>>>>> cat 则是你要合并的cat branch 的内容,你必须做出决定看是要两个都留下,或是选一个,或是改成你想要的内容,改好后记得要将Git 自动产生的<<< 、 =====、 <<< 的内容都删除,修改完毕后存档,将刚刚修改过的档案再使用git add 加入stage ,将所有的冲突都修正完毕后就使用git commit 提交一次commit,由于这次的commit 是在处理merge 时的冲突,因此Git 很聪明的已经帮我们加上了一些预设的讯息“Merge branch ‘cat’”, commit 提交后就会看到合并成功的讯息了。

$ git add lib/cat.rb
$ git commit
[master c37c9e3] Merge branch ‘cat’
发生 confict 时的处理步骤

将发生 confict 的档案打开,处理内容( 别忘了删除<<<、===、>>> )。
使用 git add 将处理好的档案加入 stage。
反覆步骤 1~2 直到所有 confict 处理完毕。
git commit 提交合并讯息。
完成
讲到这里我们再来整理一下工作流程,让大家再复习一下 git 的使用:

在专案中会有一条主 branch 是大家将开发好或是修好的东西合并回去的对象,所有要开发的新功能或是修 bug 都是从主 branch 拉出一条新的 branch 去工作。
当你完成一个阶段性的任务时,将你刚刚 所新增的内容使用 git add 加入到 stage 的状态,并且使用 git commit 加上 commit 的讯息来提交一次的 commit。
反覆动作2 直到你完成这支branch 的主要目的(新功能/修bug ),若这时你离主branch 已经有一段时间,或是确定主branch 上已经有新的commit ,使用git rebase 将自己的分支整理然后使用git merge 合并回主branch,反之则是直接使用git merge 将自己branch 的内容合并回去。
Git reset 取消上一次的操作

取消 merge

版本控制最大的好处之一就是让你永远可以后悔,因此我们常会希望把已暂存的档案、已提交的commit 或是已合并的branch 取消修改,这时候我们可以使用git reset 这个指令来帮助我们,像现在我若是想要取消刚刚的merge 动作,我只要下:

$ git reset –hard ORIG_HEAD
HEAD is now at c126ff9 Config initialze
这时候再回去看图你会发现已经回到合并前的样子了:

git reset

取消已暂存的档案

有时候手残不小心将还没修改完的档案使用 git add 加入了 stage ,这时候可以使用 git reset HEAD <file> 来将这支档案取消 stage:

git reset

你可以看到我使用git add 将档案加入stage 后,在我的status 状态显示lib/cat.rb 这支档案现在已经准备好被commit ,但这时我使用了git reset HEAD 将这支档案取消stage,再使用status 查看时它就变回一支还没加入stage 的档案了。

取消修改过的档案

连续刚刚的情况,若是我想完全放弃这次修改 (将档案状态回复到最新的一次 commit 时的状态),我可以使用 git checkout — <file> 来回复这支档案:

$ git checkout — lib/cat.rb
取消变更不会有任何讯息,但这时你去看档案会发现他已经回复成没修改过时的模样了。

修改上一次的commit

手误打太快, commit 讯息打错时,我们可以使用 git commit –amend 来帮助我们重新修改:

git reset git reset

在上面我想要修改打错字的 commit 讯息 “Cat initiae”,因此我使用 git commit –amend 来修改成正确的讯息。

强制回复到上一次 commit 的版本

有时候我们想要放弃所有修改回到commit 时的状态,这时候我们可以下git reset –hard HEAD 来回复,HEAD 参数可以加上一些变化,例如HEAD^ 表示目前版本的上一个版本HEAD~2则是再上一个,因此你可以自由的跳回去之前的状态。

git reset, hard 与 soft 的差异

你可能会在这边感到疑惑,在使用 git reset 的时候都会看到一个 soft 或是 hard 的参数,这代表什么样的意义?基本上在使用git reset 的时候,都会把目前状态回复到你想回复的版本,但若是不加参数的情况,会把你做过的修改仍然保留,但是,若是加上—soft 参数,则会把做过的修改加入stage ,若是加上hard 参数的话则是把做过的修改完全删除,回到那个版本原本的样子。

0 Comments
Leave a Reply