简体   繁体   English

Git 多个合并分支-合并到主分支时如何避免多次提交

[英]Git multiple merge branches- how to avoid multiple commit while merging to master branch

My Git repository looks like below:我的 Git 存储库如下所示:

I have created 2 branches- Branch_1 & Branch_2.我创建了 2 个分支 - Branch_1 和 Branch_2。 Now finally I am ready to merge this Branch_2 into Master branch.现在终于准备好将这个 Branch_2 合并到 Master 分支中。 But when I did merge it showed all the commits for Branch_1 & Branch_2 because of multiple merge in between.但是当我合并时,它显示了 Branch_1 和 Branch_2 的所有提交,因为它们之间有多次合并。 Can anyone suggest how to proceed in this case to have a single commit before merging my code to master branch?谁能建议在这种情况下如何在将我的代码合并到主分支之前进行一次提交?

git log --oneline --graph --color --all --decorate

* 36dbb26 (origin/Branch_2) changed abc
* 1a7bf25 changed T
* 110095a changed Z
*   1087d5d Merge remote-tracking branch 'origin/Branch_1' into Branch_2
|\
| * 8c9d02a (origin/Branch_1) sleep added between each processing to discover partitions
| * ca401cb changed S
| * 20a4edd changed R
* 3f472ef install package
*   1087d5d Merge remote-tracking branch 'origin/Branch_1' into Branch_2
|\
| * 8c9d02a (origin/Branch_1) adding y
| * ca401cb changed g 
| * 97c326d changed f 
* | fd543bf changed c
* | 7b24330 (HEAD -> master, origin/master, origin/HEAD) fix D
* | 53aecb4 adding x
|/
* 49d3bda changed e
| * 213ea18 (origin/Feature_branch) changed d
| * 0b3b675 changed c
|/
* df6ac90 Adding c 
* 96699ff Adding b 
* 99f165f Adding a 

I want end result like below:(all the commits from fd543bf to be merged into 1 commit)我希望最终结果如下:(来自 fd543bf 的所有提交都合并到 1 个提交中)

 * 36dbb26 (HEAD -> master, origin/Branch_2) changed R-All consolidated
 * 7b24330 (origin/master) Fix D

TL;DR TL;博士

You probably just want git log --first-parent .您可能只想要git log --first-parent

Long

... But when I did merge it showed all the commits for Branch_1 & Branch_2 because of multiple merge in between. ...但是当我合并时,它显示了 Branch_1 和 Branch_2 的所有提交,因为它们之间有多次合并。

No, that's not why.不,这不是为什么。 The reason you see all these commits is because you do in fact have all these commits.您看到所有这些提交的原因是因为您实际上拥有所有这些提交。

The thing to understand here is that, in the end, Git is all about commits .这里要理解的是,最后,Git都是关于 commits的。 Commits are the unit of storage in Git.提交是 Git 中的存储单元。 1 Commits are what you have, and they are what you want. 1提交是您所拥有的,也是您想要的。 If you don't want these commits, what you want must be some other commits.如果您不想要这些提交,那么您想要的必须是其他一些提交。 Commits are all you get, so you'd better want commits.提交就是你得到的全部,所以你最好想要提交。 (If you want something else, don't use Git. But many other version control systems are commit-oriented as well, so you may find that you still get commits anyway, so you might as well stick with Git, unless... well, read the next paragraph.) (如果你想要别的东西,不要使用 Git。但是许多其他版本控制系统也是面向提交的,所以你可能会发现你仍然得到提交,所以你最好坚持使用 Git,除非......好吧,阅读下一段。)

Branch names, in Git, exist for one primary reason: to find commits. Git 中的分支名称存在一个主要原因:查找提交。 This is where Git differs from other version control systems.这是 Git 与其他版本控制系统不同的地方。 In many version control systems, a branch is a container for commits, and you can inspect commits by inspecting branches: the set of commits contained in the branch is the set of commits you'll see, if that's how you ask.在许多版本控制系统中,分支是提交的容器,您可以通过检查分支来检查提交:分支中包含的提交集就是您将看到的提交集,如果您这样问的话。 But that's not how branch names work in Git.但这不是 Git 中分支名称的工作方式。

In Git, a commit can be—and often is—on many, or perhaps even all , branches at the same time .在 Git 中,提交可以——而且经常是——同时出现在许多甚至所有分支 That's because Git's branch names are not containers.那是因为 Git 的分支名称不是容器。 They do not hold commits.他们不持有提交。 They simply let you find commits.他们只是让你找到提交。 Each name finds one commit.每个名称都会找到一个提交。 It's the commits themselves that find the rest of the commits.提交本身找到提交的rest

Each Git commit is made up of two parts, which we'll describe later.每个 Git 提交由两部分组成,我们将在后面介绍。 Each commit is found by its unique hash ID.每个提交都通过其唯一的 hash ID找到 Each commit has one of these hash IDs;每个提交都有这些 hash ID 之一; that hash ID is the "true name", as it were, of the commit. hash ID 是提交的“真实姓名”。 Without the hash ID, Git literally can't find the commit.如果没有 hash ID,Git 根本找不到提交。 2 So a branch name holds one hash ID, which is, by definition, the last commit that is contained within that branch. 2因此,分支名称包含一个 hash ID,根据定义,它是该分支中包含的最后一次提交。 That commit in turn holds some set of hash IDs—usually just one—of earlier commits that are also part of the branch .该提交又包含一组 hash ID(通常只是一个),这些 ID 也是早期提交的一部分,它们也是分支的一部分

When we have a branch name, like main or feature , that holds some hash ID, we say that the branch name points to the last, or tip , commit of the branch:当我们有一个分支名称,如mainfeature ,它包含一些 hash ID,我们说分支名称指向分支的最后一个或tip提交:

            <-H   <--feature

But commit HH here stands in for the real hash ID, whatever it is—has the hash ID of some earlier commit G .但是这里的提交H - H代表真正的 hash ID,不管它是什么——具有一些早期提交G的 hash ID。 So we say that H points to G :所以我们说H指向G

        <-G <-H   <--feature

But commit G also points backwards to a still-earlier commit:但是提交G还指向更早的提交:

... <-F <-G <-H   <-- feature

and so on, all the way back to the very first commit ever.依此类推,一直回到有史以来的第一次提交。 This one literally can't point backwards to an earlier commit, so it just doesn't, and that's where Git stops working backwards.这个实际上不能指向更早的提交,所以它只是没有,这就是 Git 停止向后工作的地方。

So, this is what it means for a commit to be on a branch: we start with the branch name, which automatically determines the last commit on that branch, and then work backwards.所以,这就是提交在分支上的含义:我们从分支名称开始,它会自动确定该分支上的最后一次提交,然后向后工作。 But if that's the case... well, suppose we have something like this, where commit I points back to H , and commit K also points back to H :但如果是这样的话......好吧,假设我们有这样的事情,其中提交I指向H ,并且提交K也指向H

          I--J   <-- br1
         /
...--G--H
         \
          K--L   <-- br2

Which branch holds commit H ?哪个分支持有提交H

Git's answer is that commit H is now on both branches at the same time. Git 的回答是提交H现在同时在两个分支上。 So are all earlier commits.所有早期的提交也是如此。 Furthermore, even if H is the last commit on some third branch:此外,即使H第三个分支上的最后一次提交:

          I--J   <-- br1
         /
...--G--H   <-- main
         \
          K--L   <-- br2

this is still the case.现在仍然如此。 Commit H is now on all three branches .提交H现在在所有三个分支上。

Hence, in Git, the set of branches that contain some commit is dynamic and fluid.因此,在 Git中,包含一些提交的分支集是动态和流动的。 What matters is not the branch names, but the connections from commit to commit.重要的不是分支名称,而是从提交到提交的连接。 The branch names are useful, but only to get you started .分支名称很有用,但只是为了让您入门 Everything else is all about the commits .其他一切都与提交有关


1 Because commits are made up of smaller parts, it is possible to work at a lower level. 1由于提交由较小的部分组成,因此可以在较低级别上工作。 But this is roughly analogous to breaking molecules, like salt, into atoms—sodium metal and chlorine—or even subatomic particles like protons, neutrons, and electrons.但这大致类似于将盐等分子分解成原子——钠金属和氯——甚至是质子、中子和电子等亚原子粒子。 Once you break them up like this, they're not useful any more, not in the way that the salt is anyway.一旦你像这样分解它们,它们就不再有用了,无论如何都不是盐的方式。 You can't season your food with sodium metal, nor with chlorine, and especially not with neutrons.你不能用金属钠或氯来调味你的食物,尤其是不能用中子。

2 There are some maintenance commands— git fsck and git gc in particular—that simply look over every commit in the repository and figure out which ones connect to which other ones and so on. 2有一些维护命令——特别是git fsckgit gc只需查看存储库中的每个提交,并找出哪些提交连接到其他提交等等。 This is very slow, so it's not the way you use Git in day-to-day operation.这很慢,所以这不是您在日常操作中使用 Git 的方式。 In a bigger repository like the Linux kernel, a git checkout or git log will take up to a few seconds sometimes, but a git fsck or git gc could take many minutes. In a bigger repository like the Linux kernel, a git checkout or git log will take up to a few seconds sometimes, but a git fsck or git gc could take many minutes. Some of this depends on the speed of your computer and its file systems and so on, but the contrast is pretty clear: finding a commit by hash ID is fast , but finding it any other way is usually excruciatingly slow.其中一些取决于您的计算机及其文件系统的速度等,但对比非常明显:通过 hash ID 查找提交很快,但以任何其他方式查找它通常非常慢。


The two parts of a commit are the snapshot and the metadata提交的两个部分是快照和元数据

We mentioned above that each commit has two parts.我们在上面提到每个提交都有两个部分。 These are:这些是:

  • the main data, a snapshot .主要数据,快照 Here, Git saves, for all time, 3 a read-only snapshot of every file's name and content as of the time you, or whoever, made the commit.在这里,Git 一直保存3每个文件名称和内容的只读快照,该快照是您或任何人进行提交时的时间。 This allows you—or anyone else—to get back all those files as of that snapshot.这使您(或其他任何人)可以取回该快照的所有这些文件。

  • the metadata .元数据 Here, Git saves the name and email address of the person who made the commit.在这里,Git 保存了提交人的姓名和 email 地址。 Git saves a date-and-time-stamp for when they made the commit. Git 保存了他们提交的日期和时间戳。 (Git actually has two name-and-address-and-time fields per commit, here, though most people normally only look at one.) Git lets you add a description—a log message —explaining why you made this commit, if you like. Git实际上每次提交都有两个名称和地址和时间字段,尽管大多数人通常只看一个。)喜欢。 And, key for Git itself, this is also where Git stores those earlier-commit hash IDs.而且,Git 本身的关键,这也是 Git 存储那些早先提交的 hash ID 的地方。 Git keeps a list of such hash IDs. Git 保留了此类 hash ID 的列表。 Most commits just have that one entry, which tells Git what the parent of the commit is.大多数提交只有一个条目,它告诉 Git 提交的级是什么。

It's the parent in the metadata that lets Git show you a commit—which is a snapshot, not a set of changes— as a set of changes.它是元数据中的父项,它让 Git 向您显示一个提交——它是一个快照,而不是一组更改——作为一组更改。 If we have two commits in a row:如果我们连续两次提交:

... <-F <-G ...

and we take the snapshots out of both F (the parent) and G (the child) and compare them, whatever is the same is not changed, and whatever isn't the same... well, comparing those will tell you what changed.我们F (父)和G (子)中取出快照并比较它们,相同的部分没有改变,而不同的部分......嗯,比较它们会告诉你发生了什么变化. So that's what Git shows: the changes .这就是 Git 显示的内容:更改 But to get those changes, Git needed two commits, to get the two snapshots.但要获得这些更改,Git 需要两次提交,以获得两个快照。


3 While no part of any commit can ever change , not all commits have to last forever, so saying for all time is an overstatement. 3虽然任何提交的任何部分都不能改变,但并非所有提交都必须永远持续下去,所以说永远都是夸大其词。 Given a commit's hash ID, if Git can find that commit, that commit is that commit .给定提交的 hash ID,如果 Git 可以找到该提交,则该提交就是该提交 It's not any other commit.这不是任何其他提交。 It must be the commit that had that hash ID the last time you looked.它必须是您上次查看时具有该 hash ID 的提交。 In other words, the commit is still there, so it's unchanged, and its files are still the same way they were then.换句话说,提交仍然存在,所以它没有改变,它的文件仍然是它们当时的样子。

You can, however, get Git to delete a commit.但是,您可以让 Git删除提交。 It's not easy: Git is built to add new commits while keeping existing commits, and most of the normal everyday commands you use work this way.这并不容易:Git 旨在在保留现有提交的同时添加新提交,您使用的大多数日常命令都以这种方式工作。 But you can, with some effort, make some commits hard to find .但是您可以通过一些努力使一些提交变得难以找到 Once you do this, and leave them un-find-able (except by maintenance commands) long enough, Git will eventually decide that they must be unwanted trash, and throw them out for real.一旦你这样做了,并且让它们无法找到(维护命令除外)足够长的时间,Git 最终将决定它们一定是不需要的垃圾,并将它们真正扔掉。 The git gc maintenance command in particular does this. git gc维护命令特别执行此操作。 Once that happens, if you have saved the hash ID somewhere else—written it on a whiteboard for instance—and type it in correctly, Git will say I don't have anything with that ID .一旦发生这种情况,如果您将 hash ID 保存在其他地方(例如,将其写在白板上)并正确输入,Git 会说我没有该 ID 的任何内容

Because Git is built to add commits, and when two Gits connect and have Git-sex, the receiving Git is usually very willing to add all the sending Git's new commits to itself, new commits spread like viruses.因为 Git 是为添加提交而构建的,并且当两个 Git 连接并具有 Git-sex 时,接收 Git 通常非常愿意将所有发送 Git 的新提交添加到自己,新提交像病毒一样传播。 So just because you added, but then retracted, a commit doesn't mean it didn't get out to some other Git.因此,仅仅因为您添加但随后撤回,提交并不意味着它没有发送到其他 Git。 It may come back to you later:稍后可能会回复您:

  • Don't be afraid to make temporary commits, but do remember that if you let other Gits talk to your Git, they might copy your temporary commits, and present them back to you later—so either be careful about which repositories you let your repository have Git-sex with, or be careful about letting sensitive data get into your temporary commits, or both.不要害怕进行临时提交,但记住,如果您让其他 Git 与您的 Git 交谈,他们可能会复制您的临时提交,并在稍后将它们呈现给您——所以要么小心您让哪些仓库让您的仓库使用 Git-sex,或者小心让敏感数据进入您的临时提交,或者两者兼而有之。

  • Note, too, that when you use git push , you choose which commits your Git sends to some other Git, so git push is safer for you —you choose which commits, including temporary ones, you send—than if you allow all users everywhere to read your repository (and hence read all your temporary commits). Note, too, that when you use git push , you choose which commits your Git sends to some other Git, so git push is safer for you —you choose which commits, including temporary ones, you send—than if you allow all users everywhere读取您的存储库(并因此读取所有临时提交)。

Receiving Gits, of course, have to be pretty careful.当然,接收 Gits 必须非常小心。 That's why hosting sites like GitHub offer access control (which is not something built directly into Git itself, but rather, an add-on).这就是为什么像 GitHub 这样的托管网站提供访问控制(这不是 Git 本身直接内置的东西,而是一个附加组件)。


Merges are commits with more than one parent合并是与多个父级的提交

When we have divergent work, such as:当我们有不同的工作时,例如:

          I--J   <-- br1
         /
...--G--H
         \
          K--L   <-- br2

we might want to combine the two divergent lines of work.我们可能希望两条不同的工作线结合起来。 That way, we can get a commit that adds the feature that someone added in br1 and the feature that someone added in br2 .这样,我们可以得到一个提交,添加某人在br1中添加的特性某人在br2中添加的特性。 That's what git merge is meant for.这就是git merge的目的。

Now, git merge , as a command, does not always make a merge commit .现在, git merge作为命令,并不总是进行合并提交 We need to distinguish carefully between the verb form, to merge , meaning to combine work , and the noun or adjective form, a merge or a merge commit , meaning a commit resulting from doing the work-combining:我们需要仔细区分动词形式to merge ,意思是组合 work ,以及名词或形容词形式a mergemerge commit ,意思是完成工作组合的提交:

  • The verb form, to merge , is what git merge usually (or at least often) does.动词形式,即 merge ,是git merge通常(或至少经常)所做的。

  • The noun form, a merge , or its adjective equivalent, a merge commit , is what Git usually (or at least often) makes after doing the to merge work.名词形式, a merge ,或其对应的形容词, a merge commit ,是 Git 通常(或至少经常)在完成合并工作后所做的。

So you can see that these are closely related, but not the same thing.所以你可以看到这些是密切相关的,但不是一回事。 One is a process;一是过程; the other is a result.另一个是结果。

We won't go into details about how the process works, but when the result of a merge is a merge commit , that merge commit is just like any other commit, except instead of having a single parent, it has two or more .我们不会go详细介绍该过程是如何工作的,但是当合并的结果是一个合并提交时,该合并提交就像任何其他提交一样,除了它有两个或多个. (Most merge commits have exactly two parents; I'll go into the or more part in a later section.) Remember, all commits have their two parts: snapshot, and list-of-parents. (大多数合并提交恰好有两个父级;我将在后面的部分中将 go 放入或更多部分。)请记住,所有提交都有两个部分:快照和父级列表。 What's special about a merge commit is that its list has two or more parents .合并提交的特别之处在于它的列表有两个或多个 parent

Now, the first parent of any new commit is simply the commit you started with.现在,任何新提交的第一个父级只是您开始的提交。 You run:你跑:

git checkout br1

Then you do some stuff to make a new commit, and eventually, you run git commit .然后你做一些事情来做一个新的提交,最终你运行git commit Git builds a new commit, with a new and unique hash ID, by: Git 使用新的唯一 hash ID 构建的提交,通过:

  • saving a snapshot of every file as of the form it has right now;以现在的形式保存每个文件的快照; 4 4
  • gathering metadata: your name, your email address, the current date-and-time, your log message, and so on;收集元数据:您的姓名、您的 email 地址、当前日期和时间、您的日志消息等;
  • writing these all out, using the current commit's hash ID as the parent of the new commit;将这些全部写出来,使用当前提交的 hash ID作为新提交的父级; and finally最后
  • writing the new commit's hash ID into the current branch name .将新提交的 hash ID 写入当前分支名称

That's probably how you got commit J , for instance: you ran git checkout br1 , which extracted commit I .这可能就是您获得提交J的方式,例如:您运行了git checkout br1 ,它提取了提交I You then made a new commit with git commit .然后,您使用git commit进行了新的提交。 The new commit's parent was commit I , so that J pointed back to I , and now the name br1 selected commit J instead of selecting commit I .新提交的父级是提交I ,因此J指向I ,现在名称br1选择了提交J而不是选择提交I

When you use git merge to make the new commit, however, 5 Git doesn't write out a single -parent commit and advance the branch name.但是,当您使用git merge进行新提交时, 5 Git 不会写出父提交并推进分支名称。 This time, Git writes out a multi -parent commit.这一次,Git 写出一个父提交。 The first parent in the new commit's list of parents is the same as usual, but at least one additional parent goes into the list.新提交的父级列表中的第一个父级与往常一样,但至少有一个额外的父级进入列表。

The additional parent, in this case, is the commit you selected when you ran git merge :在这种情况下,附加父级是您在运行git merge时选择的提交:

git checkout br1
git merge br2

This causes Git to use commit L as the other commit.这会导致 Git 使用提交L作为另一个提交。 So, after merging the work on the two branches and coming up with an appropriate snapshot, Git now makes new merge commit M like this:因此,在合并两个分支上的工作并提出适当的快照后,Git 现在像这样进行新的合并提交M

          I--J
         /    \₁
...--G--H      M   <-- br1 (HEAD)
         \    /²
          K--L   <-- br2

The (HEAD) here signifies that we're "on" branch br1 , so that new commit M is the new tip of branch br1 .这里的(HEAD)表示我们“在”分支br1 ,因此新提交M是分支br1的新提示。 Commit M has two parents instead of the usual one: the first parent is commit J , where branch br1 used to point a moment ago.提交M两个父节点,而不是通常的父节点:第一个父节点是提交J ,其中分支br1曾经指向刚才。 The second parent is commit L .第二个父母是提交L The branch name br2 has not changed, so it still points to commit L .分支名称br2没有改变,所以它仍然指向提交L

Because M points to L as well as to J , commits KL are now on branch br1 .因为M指向L以及J ,所以提交KL现在在分支br1上。 This is why your git log shows them: they exist and are on the branch.这就是您的git log显示它们的原因:它们存在并且在分支上。 Git finds them by going to commit M , then going backwards to both commits J and L , and from those two, to both commits I and K , and from those two, to commit H . Git 通过提交M找到它们,然后返回提交JL ,然后从这两个提交到提交IK ,然后从这两个提交H (Of course, Git has to be careful to visit commit H exactly once, even though there are now two ways to get there. But that's easy enough for Git to do.) (当然,Git 必须小心访问提交H一次,即使现在有两种方法可以到达那里。但这对 Git 来说很容易做到。)


4 The snapshot is made from the copies of files that are in Git's index , not from the files you can see and work with. 4快照是由 Git索引中的文件副本制作的,而不是您可以查看和使用的文件。 This is why Git makes you run git add so often.这就是为什么 Git 让你经常运行git add

5 If the merge has a merge conflict, the to-merge process will stop in the middle and make you fix the conflict. 5如果merge有合并冲突,to-merge过程会在中间停止,让你修复冲突。 An eventual git commit or git merge --continue will finish the merge and make a merge commit.最终的git commitgit merge --continue将完成合并并进行合并提交。 To achieve that, before stopping in the middle, git merge writes out this special in the middle of a conflicted merge state.为了实现这一点,在中间停止之前, git merge在冲突的合并 state 的中间写出这个特殊的。 The git commit command checks for this state and finishes the merge, rather than making an ordinary single-parent commit. git commit命令检查此 state 并完成合并,而不是进行普通的单亲提交。


Octopus merges章鱼合并

Since you're griping, to some extent, about having to make multiple merge commits to merge more than one branch, it's time to mention Git's octopus merge .由于您在某种程度上抱怨必须进行多次合并提交才能合并多个分支,因此是时候提及 Git 的octopus merge了。 Suppose we have a "mainline branch" and two or more features that spring from it, perhaps from a single starting point commit or perhaps from multiple starting points:假设我们有一个“主线分支”和两个或多个 spring 的功能,可能来自单个起点提交,也可能来自多个起点:

       o--o--o   <-- feature1
      /
...--o--o--o   <-- main (HEAD)
         \
          o--o   <-- feature2

We can merge the two feature branches one at a time:我们可以一次合并两个特征分支:

       o--o--o   <-- feature1
      /       \
...--o--o---o--M   <-- main (HEAD)
         \
          o--o   <-- feature2

and then:接着:

       o--o--o   <-- feature1
      /       \
...--o--o---o--M--N   <-- main (HEAD)
         \       /
          o-----o   <-- feature2

There is nothing wrong with this method.这种方法没有任何问题。 It works fine.它工作正常。 The mainline branch, main here, now has two two-parent merge commits M and N .主线分支,这里的main ,现在有两个双父合并提交MN The first parent of N is M ; N的第一个父节点是M the first parent of M is the commit directly to its left, on the main line. M的第一个父级是直接在其左侧的提交,在主线上。 The second parent of N shows how feature2 got merged and the second parent of M shows how feature1 got merged. N第二个父节点显示了feature2是如何合并的, M第二个父节点显示了feature1是如何合并的。

Git offers the ability—in some cases, because when doing this kind of merge, there's no good way to do merge conflict resolution, so an octopus merge must be conflict-free—to use a single merge commit to get this result: Git 提供了这样的能力——在某些情况下,因为在进行这种合并时,没有很好的方法来解决合并冲突,所以章鱼合并必须是无冲突的——使用单个合并提交来获得这个结果:

       o--o--o   <-- feature1
      /       \
...--o--o--o---M   <-- main (HEAD)
         \    /
          o--o   <-- feature2

Commit M here has three parents instead of just two.这里的提交M三个父母,而不是只有两个。 The first parent is directly behind it on the left as usual.像往常一样,第一个父母就在它的左边。 The second and third parents are the remaining two branch-tip commits from feature1 and feature2 .第二个和第三个父母是来自feature1feature2的剩余两个分支提示提交。

We get this by running:我们通过运行得到这个:

git checkout main
git merge feature1 feature2

The fact that we named two commits makes git merge use the -s octopus merge strategy , which tries to merge all these commits (using an octopus style merge base algorithm) and which does the merge only if it can do so without conflicts.我们命名了两个提交的事实使得git merge使用-s octopus合并策略,它尝试合并所有这些提交(使用 octopus 风格的合并基础算法)并且只有在没有冲突的情况下才进行合并。 This means there are some merges you could do with two regular two-parent merges that you cannot do with a three-parent octopus;这意味着您可以使用两个常规的双亲合并进行一些合并,而您无法使用三亲章鱼进行合并; but some people like the octopus merges as they tie all the features in at once, and indicate that there were no conflicts (well, probably).但是有些人喜欢章鱼合并,因为它们一次将所有功能联系在一起,表明没有冲突(嗯,可能)。 6 6

Note that an octopus merge still results in putting all the commits on the merged-into branch (in this case main ).请注意,章鱼合并仍然会导致将所有提交放在合并到分支上(在本例中为main )。 Git simply follows all parents of the merge, when you run git log , so that you see all the commits that are part of the branch . Git 只是跟随合并的所有父级,当您运行git log时,您可以看到属于分支的所有提交


6 Because Git is a set of tools, rather than a complete solution, it's possible to construct an octopus merge that doesn't actually use git merge at all, or that went through two regular merges. 6因为 Git 是一组工具,而不是一个完整的解决方案,所以可以构建一个实际上不使用git merge的章鱼合并,或者经过两次常规合并。 But don't do that.但不要那样做。 We won't even look at how you could do that.我们甚至不会看你如何做到这一点。


Viewing fewer commits查看更少的提交

The git log walks through commits, one at a time, moving backwards from commits to their parents. git log遍历提交,一次一个,从提交向后移动到其父级。 Whenever it encounters a merge commit, it has a choice of which commit(s) to move backwards to.每当遇到合并提交时,它都可以选择向后移动到哪个提交。 But it does not insist on showing you every commit, or even moving to every commit reachable in this way.但它并不坚持向您展示每个提交,甚至不坚持以这种方式移动到每个可访问的提交。 It just defaults to showing every commit.它只是默认显示每个提交。

You can limit which commits you see, and you can limit which commits git log will visit in the first place .你可以限制你看到的提交你可以限制git log首先访问的提交。 If you limit the set of commits visited, you automatically limit the commits seen, so this is pretty powerful.如果你限制访问的提交集,你会自动限制看到的提交,所以这是非常强大的。 We won't look at all the gory details here, but rather only at one very useful and important option: --first-parent .我们不会在这里查看所有血淋淋的细节,而只会查看一个非常有用且重要的选项: --first-parent

When we use --first-parent , we are telling Git: Whenever you reach a merge commit, pretend that this merge commit has only a single parent, namely, its first parent.当我们使用--first-parent时,我们是在告诉 Git:每当你到达一个合并提交时,假设这个合并提交只有一个父级,即它的第一个父级。 In other words, ignore the merged-in commits entirely, and don't even walk down those paths.换句话说,完全忽略合并的提交,甚至不要沿着这些路径走。 7 If we have: 7如果我们有:

          I--J
         /    \₁
...--G--H      M--N--O--P   <-- main (HEAD)
         \    /²
          K--L

where some merge occurred at point M , and we run git log , we'll see commits P , O , N , M , J , L , K , I , H , and so on (with the ones between M and H happening in some order).M点发生了一些合并,我们运行git log ,我们将看到提交PONMJLKIH等等( MH之间的那些发生在一些命令)。 8 But if we run: 8但是如果我们运行:

git log --first-parent

the walk will pretend that commit M has only one parent, J , and we'll visit commits P , O , N , M , J , I , H , and so on, in that order. walk 会假装提交M只有一个父节点J ,我们将按顺序访问提交PONMJIH等等。 We never even look at commits KL , so we never see them.我们甚至从不查看提交KL ,因此我们从未见过它们。


7 Note that, just like a fork in a road that rejoins later, if you reverse your path—going down the road from your original destination back to your original starting point—what was a join is now a fork, and what was a fork is now a join. 7请注意,就像在稍后重新连接的道路上的分叉一样,如果您反转您的路径 - 从原来的目的地沿着道路回到原来的起点 - 什么连接现在是一个分叉,而什么是一个分叉现在是加入。 So, since Git works backwards, merges are actually where things branch, and branch points are where things come together.因此,由于 Git 向后工作,合并实际上是事物分支的地方,而分支点是事物聚集的地方。 It's really all in how you look at it.这真的取决于你如何看待它。

8 When a merge offers git log a fork in the graph walk, the actual order in which the commits come out comes from the sorting options you give. 8当合并提供git log一个 fork,提交的实际顺序来自您提供的排序选项。 The default sort is to show the highest commit date first.默认排序是首先显示最高提交日期。 If all the computer clocks were accurate when all the commits were made, this shows the commits in the right order, but sometimes one computer's clock is off, and the commits can get weirdly mixed.如果在进行所有提交时所有计算机时钟都是准确的,则这会以正确的顺序显示提交,但有时一台计算机的时钟是关闭的,并且提交可能会奇怪地混合。 Consider using git log --graph to help view the actual commit graph structure, in difficult cases.在困难的情况下,考虑使用git log --graph来帮助查看实际的提交图结构。


Other options其他选项

As I mentioned at the top of this answer, if you don't want these commits, you must want some other commits.正如我在此答案顶部提到的那样,如果您不想要这些提交,则必须要进行其他一些提交。 When I said these commits I was both speaking in general—Git stores commits, so that's all you get—but also in specific.当我说这些提交时,我既是泛泛地说——Git 存储提交,这就是你得到的全部——但也是具体的。 If you don't want merge commits, don't make merge commits in the first place.如果您不想要合并提交,请不要首先进行合并提交。 ("Don't start none, won't be none", as they say.) (正如他们所说,“不要开始,就不会没有”。)

Now, there are some huge disadvantages to this.现在,这有一些巨大的缺点 If you don't make merge commits, you can't preserve the actual original work you did.如果您不进行合并提交,则无法保留您所做的实际原始工作。 You do have that choice though.不过你确实有这个选择。 When you run git merge you can use git merge --squash , for instance.例如,当您运行git merge时,您可以使用git merge --squash This tells Git to go through the merging process , but to make an ordinary, non-merge single parent commit at the end.这通过合并过程告诉 Git 到 go ,但要在最后进行普通的非合并单亲提交 (It also turns on --no-commit , for no good reason. 9 ) (它也打开--no-commit ,没有充分的理由。 9

If you do use this method, remember to delete the branch names that find the commits from before the merge action since those commits are now redundant with the (single) squash-merge that does them.如果您确实使用此方法,请记住删除从合并操作之前找到提交的分支名称,因为这些提交现在与执行它们的(单个)squash-merge 是多余的。 If you allow those commits to come back into view later, they are likely to cause trouble.如果您允许这些提交稍后重新出现,它们可能会造成麻烦。 This is in many ways the same problem as that sort of viral effect of letting temporary or incorrect commits escape to some other Git repository: Git is built to add commits, not to discard them.这在许多方面与让临时或不正确的提交逃逸到其他一些 Git 存储库的病毒效应相同的问题:Git 的构建是为了添加提交,而不是丢弃它们。 But by doing a squash-merge, which does not leave a merge trace, you set a trap for yourself in the future, unless those now-unwanted commits really disappear forever.但是通过做一个不会留下合并痕迹的 squash-merge,你在未来为自己设置了一个陷阱,除非那些现在不需要的提交真的永远消失了。

If you have multiple merges to do, and each will have some conflicts to resolve, you can do them as normal (non-squash) merges or squash merges.如果您有多个合并要做,并且每个合并都会有一些冲突需要解决,您可以将它们作为正常(非壁球)合并或壁球合并进行。 The result will be multiple commits: either multiple merge commits, or multiple ordinary single-parent commits.结果将是多个提交:多个合并提交,或多个普通的单父提交。 You can, after doing either of these, then use git reset --soft to make the new merge-or-not-merge commits hard to find , and then use a plain git commit to make a new, single, ordinary commit that has the same snapshot as the final merge.您可以在完成其中任何一项之后,然后使用git reset --soft使新的合并或不合并提交难以找到,然后使用普通的git commit进行新的单一普通提交与最终合并相同的快照 As with git merge --squash , you should in general now consider the merged branches "dead" and you should get rid of those commits and pretend they never existed and hope they never come back to haunt you.git merge --squash ,您现在通常应该认为合并的分支“已死”,您应该摆脱这些提交并假装它们从未存在并希望它们永远不会回来困扰您。

This is not a wrong thing to do, but it requires understanding of what you're doing.这不是一件错误的事情,但它需要了解你在做什么。 Do it only if you understand the consequences.只有在您了解后果的情况下才这样做。


9 The implied -n is almost certainly just a leftover from the original shell script implementation, carefully preserved for all time in Git's behavior. 9隐含的-n几乎可以肯定只是原始 shell 脚本实现的遗留物,在 Git 的行为中一直小心保存。 It's annoying since if you want this behavior, you can use git merge -n --squash .这很烦人,因为如果你想要这种行为,你可以使用git merge -n --squash Right now that's redundant, though.不过,现在这是多余的。

To squash everything in one commit: call git reset --soft followed by git commit :要在一次提交中压缩所有内容:调用git reset --soft后跟git commit

# from Branch_2 :
git reset --soft master
git commit

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM