请记住,git 自称是“愚蠢的内容跟踪器”。它不想变得比它需要的更聪明,因为这使它保持高度灵活,因此非常强大。提交本身只是文本文件,仅此而已。它们实际上看起来像这样:
tree 68cd5b298858425fd8b5c2c0adfa62249b4eb650
parent c9ac0bb0d74d59b1ccb5aa8c498c33314b16948e
author aperson <aperson@somecompany.com> 1368010332 -0700
committer aperson <aperson@somecompany.com> 1368010332 -0700
Add test module for widget.py
tree
指向列出项目文件夹内容的散列文本文件,并且此列表可以包含其他树,递归和blob
s,它们是文件的散列内容。parent
只是另一个提交的哈希 - 这又是另一个像这样的文本文件。作者和提交者行也有带有 GMT 偏移量的 Unix 时间戳。文件的其余部分是提交消息。提交 - 文本文件 - 回答关于自身的 5 个元问题:谁(作者/提交者)、什么(指向树的指针)、何时(时间戳)、何处(父提交)以及为什么(提交消息) . tree
和parent
(父级可以是多行父级来表示合并提交)是指针。他们说“那棵树描述了这个提交的快照”和“那个提交/那些提交是/是它的来源。”
理论上您可以保留分支名称,但分支也只是指针。如果您在主分支上,则有一个名为master
in的文本文件,.git/refs/heads
其中包含命名您当前正在进行的提交的哈希(即 40 位数字)。您在该分支(也称为“头”)上的事实只是.git/HEAD
文本文件中的一行 - 也是一个指针 - 读取ref: refs/heads/master
,它仅指向 . 下master
文件heads
夹中的文件名refs
。这一切都只是指针,但更重要的是,这一切都非常流畅。
Git 想变得太愚蠢以至于分支无关紧要。最初在哪个分支上创建提交并没有真正的用处——或者更好的说法是,它太不稳定而无法让它变得有用——因为您可以在闲暇时移动提交。我经常这样做是为了得到我想要的东西。
我意识到我在一个项目分支上来回进行一般性和非常特定于项目的提交——让我们称之为mixedbranch
——项目的东西应该是它自己的分支,所以我交互式地重新定位以解压缩它们。即我确实git branch projectname
添加了第二个分支头,指向指向相同的提交mixedbranch
(但没有切换到它),然后做了git rebase -i <commit before I started mixing things>
. 然后在弹出的文本编辑器中,我删除了所有项目特定的提交行,保存并退出。这以当前名称重建了分支,但没有基于项目的提交(最好保持提交足够细化以轻松实现这一点,除此之外还有几个原因)。
然后我确实git checkout projectname
切换到那个新的分支头,它仍然在所有这些混合提交的尖端保持位置,我反向做了同样的事情 - git rebase -i <that same old commit before the mixing occurred>
,但这次删除了非项目特定的行。这使用原始分支提交重建了新分支,没有非项目提交,并指向projectname
新重新定位的头。现在两个分支都没有指向原始的交错提交集,它们都从视图中消失了,看起来就像a-a-b-b-a-b-b-a-b-a<--mixed
简单地变成了a-a-a-a<--nonmixed
and b-b-b-b-b<--project
。存储最初在哪个分支上创建的东西现在会变得一团糟。它太“聪明”了,无法信任,所以 git - “愚蠢的内容跟踪器” - 甚至都没有尝试。
在跟踪分支上的提交跟踪方面,所有这些都是向后完成的,因为提交 - 那些文本文件 - 只是存储对其父级的引用。你只能找出过去的血统。合并时,您会创建一个新提交,它只是整个合并树的快照。您当前所在的(或合并到的)分支被认为是第一个父分支,而合并的分支是第二个。如果还有其他分支也被合并,它们将是第三个、第四个等。通过这种方式,git 可以轻松地跟踪主要提交以跟踪 - 并直观地指示 - 哪些提交是,比如说,主分支。当你切换到一个分支并进行提交时,然后执行 a git log --oneline --graph --decorate --all
,你会看到类似这样的内容(来自 Vim easymotion 插件):
* 4fb1af8 (origin/develop, develop) Update jumplist when moving cursor
* fe9f404 Merge branch 'master' into develop
|\
| * 667a668 (HEAD, tag: 1.3, origin/master, origin/HEAD, master) Merge pull request #34 from Layzie/master
| |\
| | * 06826d7 fix EasyMotion#InitHL arguments
| |/
| * afd0e42 Merge branch 'release/1.3'
| |\
* | \ 44c6bfd Merge branch 'release/1.3' into develop
|\ \ \
| | |/
| |/|
| * | c4863f8 Update vim docs
| * | 8dc93e6 Update README
|/ /
* | c1f1926 Update default leader key to <Leader><Leader>
* | 6f0c9b9 Move most of the code to autoload/
即使没有颜色的好处,也很容易看到发生了什么。开发分支是最近提交的,所以它显示在顶部。如果您按照图表左侧的线,这些是develop
和origin/develop
分支的主要提交。所有“合并”的分支都从右侧进入。该develop
分支有一个合并提交master
。您可以沿着该合并的右侧到主分支(667a668
),这也是一个合并提交,它拉入Layzie/master
(06826d7
),这显然是在afd0e42
(这是清楚地在master
分支上(沿着直线向上看到 master 的第一个父提交包含它) - 所有必要的上下文都在图中)。该合并提交(667a668
) 告诉你那是什么分支——它是 Lazie/master,来自远程仓库06826d7
的分支也是如此。master
Layzie
这是我建议一个工作流习惯的地方 - 留下关于合并时单独合并的内容的提交消息。这就是你可以 - 并且应该,IMO - 知道哪些提交在哪些分支上(当前负责人是告诉当前哪些提交在哪些分支上的机制)。合并说“在我的左边是我正在合并的分支的目标分支。在右边是我正在合并的分支。在我的提交消息中是我正在合并的分支的名称被调用在我合并它的时间”(这里暗示的重点:您可以在工作时随意替换分支名称,我经常这样做)。
通过不将分支名称作为提交的一部分,您可以cherry-pick
并且rebase
周围的事情没有任何试图推理不断变化的提交位置,这太难了,而且没有帮助。提交只是一个完整的树快照,其中包含一些关于创建它的人、时间和地点的元数据,它适合相互连接的提交的 dag。这使您可以从其他分支甚至其他人的存储库中提取提交。我已经使用这种能力将完全不同的 repos 合并在一起,将提交交错在一起(与我在第一个示例中提到的相反)。我已经使用这种能力将特定文件的历史从另一个 repo 拉到一个新的分支上,然后将其合并,这样我就可以在更明智的地方跟踪它的发展。然后我扔掉了我不再想要的原始的、充满垃圾的 repo。你可以在这里看到一些关于液态 git 提交的例子,
如果我cherry-pick
从同事的“开发”分支提交,我不在乎它是否开启coworker/dev
. 我只想在我当前的分支中进行这项工作。提交元数据将显示我将它提交到现在的位置,我的同事创作了它,以及我们每个人何时做了这些事情。现在只是我的分支上的更改并没有试图弄清楚它们在我的分支上,而是曾经在他们的分支上。试图在如此灵活的系统中跟踪它会是一团糟,而且你可能最终会得到不正确的信息,而系统试图变得那么聪明(也许不是,但这似乎是一个难题)。我说让合并告诉你想要调用分支,让 git 为你将第一父级历史排序到左边。我可以说,我一次跟踪甚至几十个分支的历史都没有问题(而且我们在工作中遇到了可怕的存储库,其中有这么多并行分支,它们不适合宽屏显示器)。