3.4 使用 rerere
合并有冲突的 Git 版本
如果每天都需要合并分支,或者在一个长期维护的特性分支上需要一直相同的代码冲突,那么可以试试 git rerere
( reuse recorded resolution
)。该命令默认不生效,需要手动配置生效:(可设为用户级配置,添加 --global
标记)
bash
$ git config rerere.enabled true
下面以 jgit
为例,演示该命令的用法:
bash
# Checkout a branch
$ git checkout -b rerereExample --track origin/stable-2.2
# Do some modification: 2.5.1 --> 2.5.2, then check by git diff
$ git diff
diff --git a/pom.xml b/pom.xml
index 085e00fef..d5aec1777 100644
--- a/pom.xml
+++ b/pom.xml
@@ -208,7 +208,7 @@
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
- <version>2.5.1</version>
+ <version>2.5.2</version>
</plugin>
<plugin>
# Add a new commit for the modification
$ git add pom.xml
$ git commit
[rerereExample 37ef9f194] Recorded resolution for 'pom.xml'.
1 file changed, 1 insertion(+), 1 deletion(-)
# Notice the first output from git
# Checkout another branch
$ git checkout -b rerereExample2
Switched to a new branch 'rerereExample2'
# rebase to stable-3.2 branch
$ git rebase origin/stable-3.2
# resolve conflicted file (pom.xml) as follows:
bash
# Notice the code in L233 (from git rerere)
# Remain 2.5.2 line and continue rebase
$ git add pom.xml
$ git rebase --continue
# Add commit message 'Update maven-compiler-plugin to 2.5.2.' in editor
用 gitk
验证合并效果:
发散练习:当需要确定某个版本归属哪个分支时,可以轻松使用 git branch
命令的 --contains
参数实现,后跟 commit
ID
即可:
bash
# list some commit ID and select one
git log -5 --oneline --format='%h %s'
7384fac94 Update maven-compiler-plugin to 2.5.2.
f839d383e Prepare post 3.2.0 builds
699900c30 JGit v3.2.0.201312181205-r
0ff691cdb Revert "Fix for core.autocrlf=input resulting in modified file..."
1def0a125 Fix for core.autocrlf=input resulting in modified file and unsmudge
# select the 3rd one
$ git branch --contains 699900c30
master
* rerereExample2
# If specific commit ID omitted, HEAD is used:
$ git branch -a --contains
* rerereExample2
contains
的值除了 SHA-1
外,还可以是标签(tags
)、分支名(branch names
):
bash
# Use tag
$ git branch -a --contains v2.3.0.201302130906
master
* rerereExample2
remotes/origin/HEAD -> origin/master
remotes/origin/master
remotes/origin/next
remotes/origin/stable-2.3
remotes/origin/stable-3.0
# ... (omitted)
3.5 计算分之间的差异
合并分支前比较分支之间的差异可以得到很多有价值的信息。默认的 git diff
命令会输出所有差异,这往往不全是最需要的信息;更多的情况是想了解某个文件或路径在两个分支间的差异。这就需要用到 --name-only
标记:
bash
# on master branch
$ git checkout master
# Diff origin/stable-3.1 with the origin/stable-3.2 branch
$ git diff --name-only origin/stable-3.1 origin/stable-3.2 org.eclipse.jgit/src/org/eclipse/jgit/transport/
org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/ServiceMayNotContinueException.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
这里出现笔误:指定的路径为 org.eclipse.jgit/src/org/eclipse/jgit/transport/
,书中写成了 org.eclipse.jgit/src/org/eclipse/jgit/transport/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetch
。且 Git
会提示在版本引用与文件列表之间,用 --
进行分隔。因此,更正后的保险写法是:
bash
git diff --name-only origin/stable-3.1 origin/stable-3.2 -- org.eclipse.jgit/src/org/eclipse/jgit/transport/
该类命令的语法结构为:
git diff [options] <srcCommit> <desCommit> <path>
或
git diff [options] <srcCommit> <desCommit> -- <path>
这对于仅比较指定路径的需求而言,是十分方便的。
如果还要查看各个变更文件的状态,使用 --name-status
标记;若还想列出只有新增或删除过若干行的文件的列表,则可以使用筛选条件 --diff-filter=DA
:
bash
$ git diff --name-status --diff-filter=DA origin/stable-3.1 origin/stable-3.2
结果如下:
倘若交换两个分支的顺序,则得到相反的结果:
bash
$ git diff --name-status --diff-filter=DA origin/stable-3.2 origin/stable-3.1
注意,git diff
中的第一个版本引用为起点(source
),第二个为终点(destination
)。
3.6 孤立分支(Orphan branches
)
根据 DAG
有向无环图的设计,通常一个 Git
对象都有一个父级引用,比如创建的新分支,其 commit
对象就是其父级对象。有时也会遇到没有父级引用的对象,即孤立分支(orphan branches
)。
孤立分支的一个应用场景,就是合并两个单独的 git
库。使用简单复制粘贴文件的方法无疑会丢失历史提交记录。但利用孤立分支的相关特性,就能实现在一个 git
库中 fetch
到另一个 git
库的数据。
bash
# clone demo repo
$ git clone https://github.com/PacktPublishing/Git-Version-Control-Cookbook-Second-Edition.git demo
$ cd demo
# checkout an orphan branch
$ git checkout --orphan fresh-start
# Check git log
$ git log
fatal: your current branch 'fresh-start' does not have any commits yet
# check ls and git status
$ ls
$ git status
# ... (omitted)
# unstage files in working directory
$ git rm --cached README.md a_sub_directory/readme another-file.txt cat-me.txt hello_world.c
# then remove them
# on Linux:
$ rm -rf README.md a_sub_directory another-file.txt cat-me.txt hello_world.c
# on Windows:
$ rm -Recurse -Force README.md,a_sub_directory,another-file.txt,cat-me.txt,hello_world.c
# Check status
$ git status
On branch fresh-start
No commits yet
nothing to commit (create/copy files and use "git add" to track)
这样,fresh-start
分支既没有任何文件,也没有任何提交记录。此时可以用 git add remote
关联远程分支,再用 git fetch
获取到本地,这样两个库的提交历史都不受影响。为简单起见,这里只演示从本地新增提交记录,再将孤立分支并入 master
分支:
bash
# Create a commit on orphan branch
$ echo "This is from an orphan branch." > orphan.txt
$ git add orphan.txt
$ git commit -m "Orphan"
[fresh-start (root-commit) babe63f] Orphan
1 file changed, 1 insertion(+)
create mode 100644 orphan.txt
# Toggle to master branch
$ git checkout master
$ git merge fresh-start
fatal: refusing to merge unrelated histories
默认情况下,Git 拒绝合并孤立分支。通过 gitk
命令可以看到两个版本树:
这时可以使用 --allow-unrelated-histories
强制并入孤立分支:
bash
# Force to merge orphan branch with --allow-unrelated-histories
$ git merge fresh-start --allow-unrelated-histories
Merge made by the 'ort' strategy.
orphan.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 orphan.txt
再次查看 gitk
,孤立分支已被并入 master
分支:
孤立分支虽然用得不多,但需要重新组织代码库的时候就可以大显身手了。
拓展
孤立分支的另一个场景,是需要将本地代码共享到线上
git
服务器托管。比如本地维护了一段时间的git
代码库,需要和Github
或Gitlab
上新建的远程仓库(通常是空的)进行合并。这就需要孤立分支的相关知识,步骤如下:
- 在本地仓库创建一个孤立分支 A;
- 利用 A 关联并拉取远程仓库内容;
- 将本地代码并入 A 分支(使用
--allow-unrelatived-histories
)