Jade Dungeon

分支与远程同步

操作本地分支

查看分支

显示所有的分支:

git branch

查看当前所在的分支及提交记录:

git-show-branch

新建分支

想要建一个新的叫experimenal的分支:

git branch experimenal

切换分支

在切换分支之前要保证当前分支处于commit状态。

切换到experimenal分支:

git checkout experimenal

使用checkout可以在多个分支上随意切换。在一个分支上的操作不会影响到另一个分支。

可以把分支的创建与切换合并为一步:

git checkout -b experimenal

删除分支

# 删除本地分支
git branch -d localBranchName

# 删除远程分支
git push origin --delete remoteBranchName

合并操作:merge

合并分支

两个分支必须都已经commit才能合并。

要把experimenal合并到master,要先切到master

git checkout master

然后把experimenal合并过来:

git merge experimenal

如果有冲突,会把冲突中的文件修改为diff格式的状态。要手动修正了以后再提交:

git commit -a

可以通过图型界面查看分支过程:

gitk

合并成功了以后可以删除原来的experimenal

git branch -d experimenal

如果分支还没有合并,可以用大写-D表示强制删除,通常在分支失败时用:

git branch -D experimenal

冲突处理

合作开发与合并分支都有可能发生冲突,看到如下错误信息:

Automatic merge failed; fix conflicts and then commit the result.

这个时候要手动排除冲突,源代码被会git修改。以====分隔冲突的两边。修改完成了 以后可以再次提交:

git commit -a

有一点要注意,git会自动在merge完成以后添加一个原始冲突的备份.orig文件。如果嫌 这个文件碍眼的话可以设置不生成这个东西:

用命令:

git config --global mergetool.keepBackup false

或直接改配置:

[mergetool]
	keepBackup = false
使用vimdiff

设置合并工具为vimdiff,修改~/.gitconfig

[diff]
        tool = vimdiff
[merge]
        tool = vimdiff
[difftool]
        prompt = false

以后merge会调用vimdiff:

git mergetool

在vimdiff窗口中会上面一半会分别显示本地版本、原始版本、过程版本,下半部分是冲突 的版本:

提交、比较、回退

窗口切换:CONTROL+w w

跳到上一个、下一个冲突点:[c]c

取得某窗口中的内容::diffget window

放置内容到某窗口::diffput window

diffgetdiffput都要指定窗口,怎么指定窗口呢?有两个方法:

一个方法是用:buffers查出来Buffer编号。一般来说下半编号是1,上面三个是从2到4。

另一个方法是用关键字匹配。vimdiff一般把上面三个窗口中的文件名为XXXX.REMOTE.yyyXXXX.BASE.yyyXXXX.LOCAL.yyy。用diffget REMOTE就是取远程的。

经过合并以后的版面会有点乱,可以用:diffupdate重新生成diff格式。

保存当前窗口用w,关闭全部窗口用a

使用meld

下载http://meldmerge.org/

git config --global merge.tool      meld

如果是windows可能还要加一下程序的位置:

git config --global merge.tool.path "d:\Meld\meld.exe"

撤消合并分支

git 到底是个多么奇葩的管理工具,一个问题能有这么多解释,一眼还不出来那个是对的。 http://git-scm.com/blog/2010/03/02/undoing-merges.html 这是官方的解答,待我看完,在此翻译一下。

======================简单翻译开始====================

由于太多人问怎么撤销 merge 了,于是 git 官方出了这份教程,表示在 git 现有的思想体系下怎么达到撤销 merge 的目标。

方法一

reset 到 merge 前的版本,然后再重做接下来的操作,要求每个合作者都晓得怎么将本地的 HEAD 都回滚回去:

$ git checkout 【行merge操作时所在的分支】
$ git reset --hard 【merge前的版本号】
方法二

当 merge 以后还有别的操作和改动时,git 正好也有办法能撤销 merge,用 git revert:

$ git revert -m 【要撤销的那条merge线的编号,从1开始计算(怎么看哪条线是几啊?)】 【merge前的版本号】
Finished one revert.
[master 88edd6d] Revert "Merge branch 'jk/post-checkout'"
 1 files changed, 0 insertions(+), 2 deletions(-)

这样会创建新的 commit 来抵消对应的 merge 操作,而且以后 git merge 【那个编号所代表的分支】 会提示:

Already up-to-date.

因为使用方法二会让 git 误以为这个分支的东西都是咱们不想要的。

方法三

怎么撤销方法二:

$ git revert 【方法二撤销merge时提交的commit的版本号,这里是88edd6d】
Finished one revert.
[master 268e243] Revert "Revert "Merge branch 'jk/post-checkout'""
 1 files changed, 2 insertions(+), 0 deletions(-)

这样就行了,可以正常 merge 了,不过可能会有很多冲突噢!!

$ git merge jk/post-checkout
Auto-merging test.txt
Merge made by recursive.
 test.txt |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

=====================简单翻译结束====================

方法四:使用IDE的功能

最后的最后,还是觉得顶上那些都麻烦爆了。前端天天用 WebStorm 写代码的看过来: WebStorm 右键点击项目文件或文件夹,有一个: Local History -> Show History

点开后出现一个窗口,可以看到所有的本地改动。找到合适的那份, 按左上角那个Revert按钮,也就是个紫色的弯曲的小箭头就回去了。 在文件量不大的时候十分管用,强烈推荐。

基变操作:rebase

修改分支的基础

重建基准会改变历史记录,所以推荐只能在个人专用的分支上做。

例如,我们在master建立一个新分支feature-a开发新的功能, 但是还没有做完的时修,已经有别人在master上合并了一个已经完成的 分支feature-b

* a95dac3 - feature-b finished # 别人已经开发完了,合并进来了
|\
| * a844f01 - B.3
| * b4ee001 - B.2
| * 89feacb - B.1
|/
| * 31c6571 - A.2  # 我们的分支还没能开发完,不能合并
| * 1307648 - A.1  # 新的分支 feature-a 是从这里分叉开的
|/
* ee6dbe9 - master-2 
* f60e9c7 - master-1 
* 63b7169 - first blood 

为了让代码的提交历史清晰,我们要进行基变操作,把我们分支的分叉点改为 别人已经合并完以后的位置。

rebase前要先把master上最新的提交同步到:

git checkout master
git pull

再切回自己的分支,使用rebase进行操作,很有可能会提示代码有冲突, 所以要把冲突的部分解决掉:

$ git checkout feature-a
$ git rebase master
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
error: could not apply 1307648... A.1
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 1307648... A.1

等冲突解决以后,再继续合并操作:

$ git rebase --continue
[detached HEAD 4167078] A.2
 1 file changed, 3 insertions(+), 3 deletions(-)
Successfully rebased and updated refs/heads/feature-a

完成以后,我们的分支分叉的起点就变了:

| * 4167078 - A.2
| * 0080b28 - A.1 # 分叉的起点变了
|/
* a95dac3 - feature-b finished # 别人已经开发完了,合并进来了
|\
| * a844f01 - B.3
| * b4ee001 - B.2
| * 89feacb - B.1
|/
* ee6dbe9 - master-2 
* f60e9c7 - master-1 
* 63b7169 - first blood 

等功能完成以后,再用merge把我们的分支合并到主线:

git checkout master
git merge --no-ff feature-a

这样看提交的历史就非常清楚:

* 77cfa05 - feature-a finished # 我们开发完了,合并进来
|\
| * 1550d9c - A.3
| * 4167078 - A.2
| * 0080b28 - A.1
|/
* a95dac3 - feature-b finished # 别人已经开发完了,合并进来了
|\
| * a844f01 - B.3
| * b4ee001 - B.2
| * 89feacb - B.1
|/
* ee6dbe9 - master-2 
* f60e9c7 - master-1 
* 63b7169 - first blood 

还有一种方式,是以交互方式rebase:

git rebase -i <提交ID>

修复rebase造成的损坏

# 切到 本地与远程不一致的分支
git reset --soft <最后一次分叉前的提交>
git commit -m 'your message'
git push -f

rebase与merge的区别

rebase 和 merge 根本不是二选一的关系,要协同使用。 如果你听到任何人说我只用 rebase/merge 那一定是他不懂。

一个最简单的模型,从 master 分支 checkout 出几个本地 feature 分支, 你或者你的团队在协同开发某个feature-a时,可能别人已把feature-b的代码 merge 回 master 了。

所以应该及时将 master 的改动 rebase 到你的本地分支,顺便fix conflicts。 即:

$ git switch feature-a
$ git rebase master
fix conflicts...
$ git rebase --continue

当你开发完成feature-a时,应该将改动 merge 回 master。即:

$ git switch master
$ git merge --no-ff feature-a -m "Merge branch 'feature-a'"

在本地分支中使用 rebase 来合并主分支的改动,是为了让你的本地提交记录清晰可读。 当然, rebase 不只用来合并 master 的改动,还可以在协同开发时 rebase 队友的改动。

在主分支中使用 merge 来把 feature 分支的改动合并进来,是为了保留分支信息。 如果全使用 merge 就会导致提交历史繁复交叉,错综复杂。 如果全使用 rebase 就会让你的 commits history 变成一条光秃秃的直线。

一个好的 commits history,应该是这样的:

*   e2e6451 (HEAD -> master) feture-c finished
|\
| * 516fc18 C.2
| * 09112f5 C.1
|/
*   c6667ab feture-a finished
|\
| * e64c4b6 A.2
| * 6058323 A.1
|/
*   2b24281 feture-b finished
|\
| * c354401 B.4
| * 4bfefb8 B.3
| * eb13f72 B.2
| * c2c62b9 B.1
|/
* bbbba82 init

而不是这样的:

*   9f0c13b (HEAD -> master) feture-c finished
|\
| * 55be61c C.2
| *   e18b5c5 merge master
| |\
| |/
|/|
* |   ee549c2 feture-a finished
|\ \
| * | 51f2126 A.3
| * |   72118e2 merge master
| |\ \
| |/ /
|/| |
* | |   6cb16a0 feture-b finished
|\ \ \
| * | | 7b27b77 B.3
| * | | 3aac8a2 B.2
| * | | 2259a21 B.1
|/ / /
| * | 785fab7 A.2
| * | 2b2b664 A.1
|/ /
| * bf9e77f C.1
|/
* 188abf9 init

也不是这样的:

* b8902ed (HEAD -> master) C.2
* a4d4e33 C.1
* 7e63b80 A.3
* 760224c A.2
* 84b2500 A.1
* cb4c4cb B.3
* 2ea8f0d B.2
* df97f39 B.1
* 838f514 init

就这么简单。

同步远程与本地分支

关联本地分支与远程分支

新建立了分支以后,要和过程同步一下,让远程也有一个对应的分支:

git branch experimenal
git push --set-upstream origin experimenal
git push

推送本地分支的修改到远程

git push

如果上一步说的创建新分支后,没有关联远程分支:

git branch experimenal
# 如果这一条命令漏了git push --set-upstream origin experimenal
# 直接就push了
git push

那么可以设定一下本地分支与远程分支的关联:

git branch --set-upstream-to=origin/experimenal experimenal

把远程传动同步到本地

把远程的内容拉取过来,有git fetchgit pull两种方式,二者的区别有:

1、目的不同:

  • git fetch:从远程获取最新版本到本地,但不会自动 merge, 用于从远程跟踪分支下载和查看其他人完成的最新提交, 但不将这些提交合并到本地存储库中。 它从远程存储库中获取更改并将其存储在本地存储库中。
  • git pull:从远程获取最新版本并 merge 到本地, 它会自动将提交合并到您的本地存储库中,而无需查看提交。

2、用途不同:

  • git fetch:只是通过将提交从远程存储库传输到本地存储库来使远程存储库的 本地副本保持最新。将提交导入到本地分支将允许您跟上其他人所做的更改。
  • git pull:Pull 将更改引入本地代码存储库,以使用远程存储库更新本地存储库。

3、用法不同

  • git fetch:当您想要查看其他人正在处理的内容, 可以在将更改与本地存储库集成之前轻松查看其他开发人员推送的提交。 使用命令git fetch从远程存储库中获取所有分支。
  • git pull:您可以使用命令git pull来执行拉取, 该命令检索分支的远程副本并将其与本地副本合并。 这与使用命令git fetch后跟git merge完全相同。

4、远端跟踪分支不同

  • git fetch:能够直接更改远端跟踪分支。
  • git pull:无法直接对远程跟踪分支操作, 我们必须先切回本地分支然后创建一个新的commit提交。

5、拉取不同

  • git fetch:Git fetch会将数据拉取到本地仓库, 它并不会自动合并或修改当前的工作。
  • git pull:git pull是从远程获取最新版本并merge到本地, 会自动合并或修改当前的工作。

6、commitID不同

  • git fetch:更新代码后本地的库中master的commitID不变,还是等于1。
  • git pull:更新代码后本地的库中master的commitID发生改变,变成了2。

7、安全性不同

  • git fetch:更安全,从远程仓库拉入所有的提交,但不会本地文件做任何修改。 这给了你足够时间去发现远程仓库自从你上次拉取后到现在为止发生的变化。 你可以在合并前检查哪些文件有变化,哪些文件可能导致冲突。
  • git pull:相当于运行git fetch,然后立即将你的改动合并到本地仓库。 这样的确少了一个步骤,但是也会带来一些风险。

常见问答:

  • 什么情况下应该使用Git Fetch而不是Git Pull? 当你需要更细粒度的控制远程更改时,或者当你想先审查代码之后再进行合并时, 应该使用Git Fetch。
  • Git Fetch和Git Pull哪个更安全? Git Fetch相对更安全,因为它不会自动合并更改。 这样可以给你一个机会去手动审查和测试更改。
  • 如何将Git Fetch下来的更改合并到本地分支? 使用git merge命令或者git rebase命令,你可以将Fetch下来的更改合并到你的本地分支。