
在本文中,我将讨论使用git的一个有用但鲜为人知的技巧-如何使用另一个提交中的树轻松创建一个提交。 简而言之,如何在分支上获取项目的期望状态(如果该状态之前已经存在于存储库中的某个位置)。 将给出几个示例,说明如何优雅地解决一些实际问题。 特别是,我将讨论我发现的方法,该方法可以大大简化在重新设置基准期间纠正多个冲突的方法。 另外,本文是在实践中理解git提交内容的好方法。
目录内容
理论部分。 关于提交和树
git提交树
实践部分
1.与另一个分支同步
2.两个分支的比较
3.分支反向
4.部分反转
5.人工合并
6a。 通过合并重新设置方法-描述
6b。 通过合并重新设置方法-脚本
7.别名
结论
理论部分。 关于提交和树
提交可能是git中最基本的概念,让我们看看它的组成。 每个提交都有其自己的唯一标识符,该标识符以哈希形式表示,例如5e45ecb 。 并使用以下命令,了解了哈希,就可以看到其内容。
git cat-file -p 5e45ecb
tree 8640790949c12690fc71f9abadd7b57ec0539376 parent 930741d1f5fd2a78258aa1999bb4be897ba3d015 author Mark Tareshawty <tareby...@github.com> 1542718283 -0500 committer Mark Tareshawty <tareby...@github.com> 1542718283 -0500 gpgsig -----BEGIN PGP SIGNATURE----- ... -----END PGP SIGNATURE----- Fix scoping so that we don't break the naming convention
以下几行是提交的全部内容:
- 树-指向特定项目状态的链接
- parent-链接到父提交
- 作者-最初提交的作者+日期
- committer-此提交的创建者+日期
- gpgsig-数字签名(如果有)
- 消息-提交文本
让我提醒您,git中的提交(与其他VCS不同)没有描述所做的更改。 恰恰相反:每次提交都描述了整个项目的特定状态,而我们所做的更改实际上是与先前状态相比的动态计算差异。 还值得注意的是,所有提交都是不可变的(例如,不可变的),例如,使用rebase / cherry-pick / amend可以创建绝对的新提交。
树(树)-实际上,它只是一个具有特定不变内容的文件夹。 树类型的对象包含具有特定内容(斑点)和子文件夹(树)的文件列表。 每个提交所指向的树是项目的根文件夹,或者是其特定状态。
查看树内容对于其他任何对象(提交/树/ blob),都可以采用完全相同的方式进行操作,并且足以采用前几个唯一的哈希字符:8640790949c12690fc71f9abadd7b57ec0539376-> 8640790。
git cat-file -p 8640790
100644 blob 7ab08294a46f158c51460be3e7df6a190e15023b .env.example 100644 blob 0a1a4d1ad9ff3f35b67678ca893811e91b423af5 .gemset 040000 tree 033aa38ce0eab11fe229067c14ccce95e2b8b601 .github 100644 blob ca49bb7ffa6273b0be4ce7ba1accba456032fb11 .gitignore 100644 blob c99d2e7396e14ac072c63ec8419d9b8fede28d86 .rspec 100644 blob 65e77a2f59f635a8f24eb4714e8e43745c5c0eb9 .rubocop.yml 100644 blob 8e8299dcc068356889b365e23c948b92c6dfcd78 .ruby-version 100644 blob 19028f9885948aca2ba61f9d062e9dc21c21ad03 .stylelintrc.json 100644 blob 2f7a032fbc3f4f7195bfd91cb33889a684b572b9 .travis.yml 100644 blob 121615722a6c206a9fe24b9a1c9b647662a460d2 ARCHITECTURE.md 100644 blob 898195daeea0bbf8c5930deeaf1020ba8abab34a Gemfile 100644 blob de7ca707f9fe9172db941b65cdacaba7e024fc06 Gemfile.lock 100644 blob e6ff62fefd071b1a8ca279bae94ddbc4dd17b7a3 Gruntfile.js 100644 blob 0cac5b30fb32d36cce2aeb7d936be7b6207d68c7 MIT-LICENSE.txt 100644 blob c2c566e8cc3d440d3ee8041b79cded416db28136 Procfile 100644 blob d1fb2f575380e1e093a4d82e3f19e51f0b99a0a1 Procfile.dev 100644 blob 3a88e138f10fa65bd2cfe1a1d3292348205508b5 README.md 100644 blob 5366e6e073cc426518894cc379d3a07cf3c9cfb3 Rakefile 100644 blob e6d3d2d3e9d5122c5f75bbeee8ed0917ad38c131 app.json 040000 tree 94f83cf03bd6f1cf14672034877b14604744b7a2 app 040000 tree d4d859e82564250b4c4f2047de21e089e7555475 bin 100644 blob 1f71007621f17334fd6f2dd71c87b7a16867119c config.ru 040000 tree 9e8e4bf5ec44541aefff544672b94ca8a9d07bbf config 040000 tree 31b8d0e1fa2bb789dbd6319e04fc9f115952cf2a db 040000 tree 38e7a13e0e772c2a13e46d2007e239f679045bee doc 040000 tree a6e35ded8b35837660cf786e637912377f845515 lib 040000 tree d564d0bc3dd917926892c55e3706cc116d5b165e log 100644 blob 843523565ddee5e00f580d9c4e37fc2478fdaecc package-lock.json 100644 blob 791ee833ad316d75b1d2c83a64a3053fc952d254 package.json 040000 tree 4645317c52675d9889f89b26f4dd4d2ae1d8cbad public 040000 tree 31d3f8ae4a4ffe62787134642743ed32a35dbae2 resources 040000 tree 807ffa29868ef9c25ddb4b4126a4bb7f1b041bf0 script 040000 tree 4c3bf9a7f3679ba059b0f1c214a500d197546462 spec 040000 tree 136c8174412345531a9542cafef25ce558d2664f test 040000 tree e6524eafe066819e4181bc56c503320548d8009b vendor
这实际上是git如何工作的最重要特征,提交标识符实际上是其内容的哈希。 就像嵌入其中的对象(树,斑点)的哈希一样。
现在让我们看看当我们这样做时会发生什么
git commit -m "Fixed bug"
此命令创建一个新提交,捕获以下内容:
- 暂存项目的状态(另存为新的树对象并采用其哈希值)
- 链接到当前(父)提交
- 作者+提交者+两个日期
- 提交文字
全部保存,散列,并获得新的提交对象。 然后,团队会自动将当前分支指针引发到该分支。
关于术语的一点点众所周知,tree是一个对象,它包含过去某个时刻的项目状态-使用此树创建提交时。
工作文件夹称为工作树/工作副本/工作目录,这很合逻辑。
我们还具有-暂存区域/索引-准备好的更改区域。 但从逻辑上讲,这也是一棵树 ,或者更确切地说,是提交为树时保存的状态。 因此,在我看来,调用分级树会更合乎逻辑。
git提交树
最后,我们可以继续描述所需的git commit-tree命令。 形式上,这是低级命令之一,因此很少提及和使用。 我们不会考虑与其关联的其他低级命令(例如git write-tree,git-update-index,它们也称为管道命令)。 我们只对一个特定的结果感兴趣:使用此命令,我们可以轻松地从任何其他提交中复制(重用)项目状态树。
看她的挑战
git commit-tree 4c835c2 -m "Fixed bug" -p a8fc5e3
d9aded78bf57ca906322e26883644f5f36cfdca5
git commit-tree命令也可以进行提交,但是以低级方式进行。 在这里,您必须显式指定已经存在的树(树)4c835c2以及到父提交a8fc5e3的链接。 并且它返回新提交d9aded7的哈希,并且分支的位置没有更改(因此,此提交似乎已冻结)。
实践部分
以下简单的存储库中演示了使用此命令的示例。

它包含三个分支:
主-主分支
alpha-我们工作所在的分支
beta-先前停在master中的分支
所有操作都易于在本地重复,因此只需克隆存储库,进入alpha分支,然后执行示例中的命令就足够了。 此初始状态是所有示例共有的。
git clone https://github.com/capslocky/git-commit-tree-example.git cd ./git-commit-tree-example/ git checkout alpha
在窗户下所有命令(包括脚本)也在Windows下运行。 您只需要在项目文件夹中打开bash终端,例如这样
"C:\Program Files\Git\git-bash.exe" --cd="D:\path\project"
1.与另一个分支同步
挑战:
将alpha分支上的项目状态与beta分支同步。 也就是说,您需要在alpha分支上创建这样的新提交,以便项目状态变得与beta分支上的完全相同。
具体而言,这种任务不太可能出现,但这是最合适的方法来演示该方法。
最简单的解决方案是获取beta分支指向的现有树,然后从alpha分支的新提交中仅指向它。 由于这是第一个示例,因此将充分详细地考虑其所有逻辑。
首先,找到beta分支指向的提交哈希:
git rev-parse origin/beta
280c30ff81a574f8dd41721726cf60b22fb2eced
280c30f-仅需输入前几个字符
现在通过通过git cat-file显示提交的内容来找到其树的哈希:
git cat-file -p 280c30f
tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a <--- parent 560b449675513bc8f8f4d6cda56a922d4e36917a author Baur <atanov...@gmail.com> 1540619512 +0600 committer Baur <atanov...@gmail.com> 1540619512 +0600 Added info about windows
3c1afe7-这是我们需要的树
现在,我们将创建一个指向该树的提交,并使用父提交来指示当前提交:
git commit-tree 3c1afe7 -m "Synced with branch 'beta'" -p HEAD
eb804d403d4ec0dbeee36aa09da706052a7cc687
就是这样,提交已创建,团队获得了哈希值。 而且,此值将始终是唯一的,因为它不仅是根据树计算的,而且还根据作者和时间来计算的。 只要提交不进入任何分支,提交本身就会被冻结。 对于我们来说,只需输入前几个字符就足够了: eb804d4 ,这个值对于每种情况都是唯一的,我将其指定为xxxxxxx 。 让我们看一下它的内容:
git cat-file -p xxxxxxx
tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a <--- parent 64fafc79e8f6d22f5226490daa5023062299fd6c author Peter <peter...@gmail.com> 1545230299 +0600 committer Peter <peter...@gmail.com> 1545230299 +0600 Synced with branch 'beta'
太好了,它具有与origin / beta分支上的提交相同的树。 并且由于此提交是当前分支的直接后代,因此要将其包括在分支中,只需进行快进合并
git merge --ff xxxxxxx
Updating 64fafc7..xxxxxxx Fast-forward Azure.txt | 3 --- Bill.txt | 6 +----- Linus.txt | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 8 deletions(-)
做完了 现在,alpha分支上的项目状态与beta分支上的状态完全相同。 [更新]如果您查看此提交毕竟发生了什么变化,我们将看到:它颠倒了alpha分支的所有自身提交(更改),并添加了beta分支相对于其共同祖先提交的所有唯一提交(更改)。

2.两个分支的比较
挑战:
比较alpha分支和beta分支。
第一个示例显示创建的提交显示所有更改,这是两个分支之间的实际差异。 通过此属性,可以轻松地将一个分支与另一个分支进行比较。 创建第三个临时分支并在其中进行类似的提交就足够了。
因此,首先,将Alpha分支返回其原始状态
git reset --hard origin/alpha
让我们在当前分支上创建一个临时分支并将其站立
git checkout -b temp
剩下的工作与前面的示例相同。 但是这次我们将碰面。 为此,我们使用特殊语法来访问提交树origin / beta ^ {tree}或相同的280c30f ^ {tree} 。
git merge --ff $(git commit-tree origin/beta^{tree} -m "Diff with branch 'beta'" -p HEAD)
完成,本质上我们实现为两个分支之间的提交差异
git show
git diff alpha origin/beta
当然,我们可以为存储库中的任何两个提交(状态)创建这样的“比较”提交。

3.分支反向
挑战:
回滚最近的几次提交。
让我们回到alpha分支并删除temp分支
git checkout alpha git branch -D temp
假设我们需要回滚alpha分支上的最后两次提交。 有两种经典的方法可以做到这一点:
- 运行git revert两次-每次提交
- git reset,即重置分支位置
但是您可以通过第三种方式来做到这一点:
git merge --ff $(git commit-tree 7a714bf^{tree} -m "Reverted to commit 7a714bf" -p HEAD)
这将添加一个新提交,该新提交将回滚前两个提交的更改。 与第一种方法不同,即使您需要回滚最后十次提交,也只会创建一个提交。 git reset方法的区别在于我们不从分支本身抛出这些提交。
此外,如果您需要返回分支的原始状态,则可以用相同的方式完成
git merge --ff $(git commit-tree 64fafc7^{tree} -m "Reverted back to commit 64fafc7" -p HEAD)
同时,这两个提交将保留在分支的历史记录中,根据该历史记录,可以看到该分支已回滚并返回。

4.部分反转
挑战:
在最近的几次提交中将更改回滚到某些文件。
再次,将Alpha分支返回其原始状态
git reset --hard origin/alpha
alpha分支包含3个提交,每个提交都对Bill.txt文件进行了更改,在最后一次提交中,还添加了Azure.txt文件。 假设我们需要将对最后2次提交的更改回滚到Bill.txt文件,而不涉及任何其他文件。
首先,回滚所有文件2提交
git merge --ff $(git commit-tree 7a714bf^{tree} -m "any text" -p HEAD)
接下来,将分支返回到上一个提交,但不影响磁盘上项目的状态
git reset HEAD~1
现在,只需足够必要的文件并提交,就可以丢弃其他更改。
git add Bill.txt git commit -m "Reverted file Bill.txt to 7a714bf" git reset --hard HEAD

5.人工合并
挑战:
将一个分支增加到另一个分支,以获得预定结果。
想象一下这种情况。 在生产中已检测到一个严重的错误,并且与往常一样,需要紧急修复。 但是,尚不清楚需要花费多少时间来研究它并做出正确的修补程序,因此迅速制作了一个临时修补程序,该修补程序隔离了与问题模块的交互。 因此,在master分支中,将出现带有此临时补丁的提交,并且一段时间后,而不是在master中提交,您需要处理一个已经成熟的修补程序。
因此,我们需要合并master中的alpha分支,但是当从alpha分支的所有唯一更改都从上方添加到master中并且存在冲突时,这不应该是传统的合并,并且我们必须用alpha分支完全覆盖master。
首先,让我们回想一下合并提交的全部含义-实际上是相同的普通提交,但是它只有两个父提交,如果您查看其内容(其树的形成方式是一个单独的问题),就可以清楚地看到。 。 由谁决定谁是简单的-第一次父提交被认为是主要提交。
git cat-file -p 7229df8
tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a parent fd54ab7dde87593b9892b6d1ffbf1afd39ba6f9e parent 280c30ff81a574f8dd41721726cf60b22fb2eced author Baur <atanov...@gmail.com> 1540619579 +0600 committer Baur <atanov...@gmail.com> 1540619592 +0600 Merge branch 'beta' into 'master'
重置当前的Alpha分支并切换到母版
git reset --hard origin/alpha git checkout master
现在是同一个团队,但是有两个父提交
git merge --ff $(git commit-tree alpha^{tree} -m "Merge 'alpha' into 'master', but take 'alpha' tree" -p HEAD -p alpha)
完成后,我们在master中暂存了alpha分支,并且不必删除临时代码并解决冲突,因为在这种情况下,我们只需要重写最新的更改即可。

实际上,与从另一次提交复制的树创建合并并不奇怪。 之所以会出现这种情况,是因为git是一种非常灵活的工具,它使您可以实现用于处理分支和存储库的多种方法。 但是,最常见的示例还是从一开始就在我们的存储库中-尝试自己解析它或打开破坏器以阅读说明。
扰流板让我们注意一下beta分支是如何被扼杀回master的。 在其中出现两次提交期间,master分支本身中没有新的提交。 这意味着在主版中使用合并beta时,由于同时发生更改,因此排除了任何冲突。


如果我们进行了git merge beta,则将发生快进合并( 默认行为),也就是说,master分支将与beta分支处于同一提交,并且不会进行合并提交。 就像这样:

但是这里没有使用git merge beta --no-ff命令进行快速合并。 也就是说,尽管没有必要,我们还是强制创建了合并提交。 而且由于知道了将来合并所需的项目最终状态-这是一个beta树,git只需将指向该树的链接复制到新提交中:
git cat-file -p origin/beta
tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a <--- parent 560b449675513bc8f8f4d6cda56a922d4e36917a author Baur <atanov...@gmail.com> 1540619512 +0600 committer Baur <atanov...@gmail.com> 1540619512 +0600 Added info about windows
git cat-file -p 7229df8
tree 3c1afe75f54518dbd82ea7a4e3c4ff50389a573a <--- parent fd54ab7dde87593b9892b6d1ffbf1afd39ba6f9e parent 280c30ff81a574f8dd41721726cf60b22fb2eced author Baur <atanov...@gmail.com> 1540619579 +0600 committer Baur <atanov...@gmail.com> 1540619592 +0600 Merge branch 'beta' into 'master'
6a。 通过合并重新设置方法-描述
挑战:
有必要建立一个“大量”的rebase分支(在不同的提交上有很多冲突)。
git中有一个经典的holivar主题-rebase vs merge。 但是我不会胡扯。 相反,我将谈谈如何在此任务范围内结识他们。
通常,git是经过专门设计的,因此我们可以有效地进行合并。 当我来到工作流基于rebase的项目时,我第一次感到不舒服和不寻常,直到我开发出可以简化git日常工作的技术。 其中之一是我进行大量基准调整的原始方法。
因此,我们需要在开发时重新设置alpha分支,以便以后将其与beta分支一样精美地进行染色。 如果我们像往常一样开始变基,则第一次提交和最后一次提交将在两个不同的地方产生两个不同的冲突。 但是,如果不进行基础调整而只是进行合并,则一次合并提交中的一个位置只会有一个冲突。
如果您想确保这一点,我会在扰流板下方提供现成的团队。
隐藏文字使分支恢复到原始状态
git checkout master git reset --hard origin/master git checkout alpha git reset --hard origin/alpha
创建并站在alpha-rebase-conflicts分支上,并以master为基础
git checkout -b alpha-rebase-conflicts git rebase master
各种提交都会有冲突,包括幻影冲突。
现在,让我们尝试合并,回到alpha分支并删除该分支以进行变基。
git checkout alpha git branch -D alpha-rebase-conflicts
切换到母版并进行合并
git checkout master git merge alpha
只有一个简单的冲突,我们将其纠正并执行
git add Bill.txt git commit -m "Merge branch 'alpha' into 'master'"
Marge成功完成。
Git冲突是我们生活中很自然的一部分,这个简单的例子表明,就此而言,合并绝对比重新设置更为方便。 在实际项目中,这种差异是通过花费大量时间和精力来衡量的。 因此,例如,存在一个模糊的建议,即在合并之前将功能分支的所有提交压缩为一个提交。
这种方法的想法是进行一个临时的隐藏合并,在该合并中我们将立即解决所有冲突。 记住结果(树)。 接下来,运行常规的变基,但是选择“自动解决冲突,选择我们的更改”。 最后,向分支添加一个额外的提交,这将还原正确的树。
让我们开始吧。 同样,将两个分支都恢复到其原始状态。
git checkout master git reset --hard origin/master git checkout alpha git reset --hard origin/alpha
让我们创建一个临时分支临时文件,在其中进行合并。
git checkout -b temp git merge origin/master
冲突。
让我们照常解决Bill.txt文件中的一个简单冲突(在任何编辑器中)。
请注意,与rebase一样,只有一个冲突,而不是两个冲突。
git add Bill.txt git commit -m "Merge branch 'origin/master' into 'temp'"
我们返回到alpha分支,为自动解决所有冲突做好基础,并将temp分支带入状态,并删除temp分支本身。
git checkout alpha git rebase origin/master -X theirs git merge --ff $(git commit-tree temp^{tree} -m "Fix after rebase" -p HEAD) git branch -D temp

最后,大师中精美的mergim alpha。
git checkout master git merge alpha --no-ff --no-edit

请注意,尽管master,alpha和remote temp分支都是三个不同的提交,但它们都指向同一棵树。
这种方法的缺点:
- 没有每个冲突提交的手动更正-冲突会自动解决。 这样的中间提交可能无法编译。
- 在每个基础上添加(但不总是)额外提交
优点:
- 我们只解决实际冲突(没有幻影冲突)
- 所有冲突仅修复一次。
- 前两点节省时间
- 保存了提交和所有更改的完整历史记录(例如,您可以进行选择)
- 该方法以脚本的形式实现,并且在必要时始终可以用于变基(它不需要任何有关树的知识,等等)。
6b。 通过合并重新设置方法-脚本
该脚本发布在这里: https : //github.com/capslocky/git-rebase-via-merge
让我们在示例中检查其工作。 同样,将两个分支都恢复到原始状态
git checkout master git reset --hard origin/master git checkout alpha git reset --hard origin/alpha
下载脚本并使其可执行
curl -L https://git.io/rebase-via-merge -o ~/git-rebase-via-merge.sh chmod +x ~/git-rebase-via-merge.sh
窗户该文件将显示在这里:C:\ Users \用户名\ git-rebase-via-merge.sh
更改您需要为其重新设置基础的默认分支,在本例中,我们需要origin / master
nano ~/git-rebase-via-merge.sh
default_base_branch='origin/master'
我们还创建并站在一个临时分支上,以免碰到alpha分支本身
git checkout -b alpha-rebase-test
现在,您可以运行脚本(而不是传统的git rebase起源/主服务器)
~/git-rebase-via-merge.sh

脚本结果 $ ~/git-rebase-via-merge.sh This script will perform rebase via merge. Current branch: alpha-rebase-test (64fafc7) Base branch: origin/master (9c6b60a) Continue (c) / Abort (a) c Auto-merging Bill.txt CONFLICT (content): Merge conflict in Bill.txt Automatic merge failed; fix conflicts and then commit the result. You have at least one merge conflict. Fix all conflicts in the following files, stage them up and type 'c': Bill.txt Continue (c) / Abort (a) c [detached HEAD 785d49e] Hidden temp commit to save result of merging 'origin/master' into 'alpha-rebase-test' as detached head. Merge succeeded on hidden commit: 785d49e Starting rebase automatically resolving any conflicts in favor of current branch. First, rewinding head to replay your work on top of it... Auto-merging Bill.txt [detached HEAD a680316] Added history of windows Author: Baur <atanov...@gmail.com> Date: Sat Oct 27 11:45:50 2018 +0600 1 file changed, 6 insertions(+), 3 deletions(-) Committed: 0001 Added history of windows Auto-merging Bill.txt [detached HEAD dcd34a8] Replaced history of windows Author: Baur <atanov...@gmail.com> Date: Sat Oct 27 11:55:42 2018 +0600 1 file changed, 4 insertions(+), 5 deletions(-) Committed: 0002 Replaced history of windows Auto-merging Bill.txt [detached HEAD 8d6d82c] Added file about Azure and info about Windows 10 Author: Baur <atanov...@gmail.com> Date: Sat Oct 27 12:06:27 2018 +0600 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 Azure.txt Committed: 0003 Added file about Azure and info about Windows 10 All done. Restoring project state from hidden merge with single additional commit. Updating 8d6d82c..268b320 Fast-forward Bill.txt | 4 ++++ 1 file changed, 4 insertions(+) Done.
~/git-rebase-via-merge.sh origin/develop
, ours / theirs :
ours — (HEAD)
theirs — (, origin/develop)
rebase — .
7.
git .
.
git config alias.copy '!git merge --ff $(git commit-tree ${1}^{tree} -p HEAD -m "Tree copy from ${1}")'
git copy xxx, xxx — .
git copy xxx
git merge --ff $(git commit-tree xxx^{tree} -p HEAD -m "Tree copy from xxx")
git copy a8fc5e3
git copy origin/beta
git copy HEAD~3
git amend.
git commit --amend -m "Just for test"
:
git commit --amend
, , . , . — . , , . . " " " " . ( , devops), , .