简体   繁体   English

将 Hotfix 分支合并到两者后,功能分支是 Master 后面的 1 个提交

[英]Feature branch is 1 commit behind Master after merging Hotfix branch to both

I am new to git branches, so this question must be silly:我是 git 分支的新手,所以这个问题一定很傻:

I have a master branch and a feature branch.我有一个master分支和一个feature分支。 I've created a third hot-fix branch, made some changes, and merged it to master .我创建了第三个hot-fix分支,进行了一些更改,并将其合并到master Afterwards, I also merged it to feature , because I am doing a lot of work there and I want the bugs fixed there as well.之后,我还将它合并到feature ,因为我在那里做了很多工作,我也希望在那里修复错误。

Now, GitHub says that my feature branch is 1 commit behind the master , where this one commit is the pull request hot-fix -> master , even though in reality both branches are identical in terms of the hot-fix changes because I also pulled them into feature现在,GitHub 说我的feature分支是master后面的 1 个提交,其中这个提交是 pull request hot-fix -> master ,尽管实际上两个分支在 hot-fix 更改方面是相同的,因为我也拉他们成为feature

How should I be merging hot-fix branches to both master and feature without the latter being falsely behind?我应该如何将hot-fix分支合并到masterfeature而后者不会被错误地落后?

You're one commit behind because there's one commit that's "on" master that is not on your branch.你落后了一个提交,因为有一个提交是“on” master ,它不在你的分支上。 That's no big deal.这没什么大不了的。 Well, you'll find lots of opinions about what is or is not a big deal, how you should work, and so on.好吧,你会发现很多关于什么是大事,什么不是大事,你应该如何工作等等的意见。 Some will contradict others.有些人会反驳其他人。 You can pick any plan that works and stick with it, or hopscotch around with various things that work: whatever works, works.你可以选择任何可行的计划并坚持下去,或者跳房子来做各种可行的事情:任何可行的,都行得通。 It might be confusing to others though, which might make it stop working in a different sense.不过,这可能会让其他人感到困惑,这可能会使其在不同的意义上停止工作。

Instead of getting all caught up in right-flow / wrong-flow arguments, let's just look at what you did and why you see the result you see.与其陷入正确流/错误流 arguments 中,不如让我们看看你做了什么以及为什么你会看到你看到的结果。 You can choose your eventual course later, once you understand the building blocks.一旦您了解了构建模块,您就可以稍后选择最终的课程。

I have a master branch and a feature branch.我有一个master分支和一个feature分支。 I've created a third hot-fix branch, made some changes, and merged it to master .我创建了第三个hot-fix分支,进行了一些更改,并将其合并到master Afterwards, I also merged it to feature , because I am doing a lot of work there and I want the bugs fixed there as well.之后,我还将它合并到feature ,因为我在那里做了很多工作,我也希望在那里修复错误。

Git isn't really about branches, and in a way, branch is a bad word—not in the sense of being explicit or foul language or something, but rather, in the sense that it fails to communicate the right meaning . Git 并不是关于分支的,在某种程度上,分支是一个坏词——不是在明确或粗俗的语言之类的意义上,而是在它无法传达正确含义的意义上。 The problem is that the word branch in Git has at least two different meanings , and probably more: exactly how many will depend on what and how you go about counting.问题是 Git 中的分支一词至少有两种不同的含义,而且可能更多:到底有多少将取决于您 go 关于计数的内容和方式。 (See also What exactly do we mean by "branch"? ) (另请参阅“分支”到底是什么意思?

What Git is really about, in the end, is the commit .最后,Git 真正的意义在于commit A repository is a collection of commits—all stored in a big database, indexed by hash IDs or more formally object IDs or OIDs—plus some other stuff, which is important, but let's concentrate on the commits first.存储库是提交的集合——全部存储在一个大数据库中,由hash ID或更正式的object ID或 OID 索引——加上一些其他的东西,这很重要,但让我们首先关注提交。 Each commit is numbered: it gets a unique, but random-looking and very large and unwieldy, number, which Git insists on spelling in hexadecimal , as, eg, 4af7188bc97f70277d0f10d56d5373022b1fa385 .每个提交都有编号:它有一个唯一的,但看起来随机且非常大且笨拙的数字,Git 坚持以十六进制拼写,例如4af7188bc97f70277d0f10d56d5373022b1fa385 In theory, the number alone suffices to find the commit, because the number is totally unique .理论上,单独的数字就足以找到提交,因为数字是完全唯一的 Any Git repository that has a commit with that number has exactly that commit .任何具有该编号的提交的 Git 存储库都具有该提交 1 In practice, we have to have the repository already, and then we can ask it for that commit, by the hash ID: if it isn't in there, we pick some other repository that does have it and use git fetch to get it from the other repository. 1在实践中,我们必须已经拥有存储库,然后我们可以通过 hash ID 向它请求该提交:如果它不在那里,我们选择一些其他存储库使用git fetch来获取它来自另一个存储库。

So, in short, we use the commits in the repository, as that's what the repository is about.因此,简而言之,我们使用存储库中的提交,因为这就是存储库的意义所在。 We ask for them by raw hash ID;我们通过原始 hash ID 要求他们; Git pulls them out. Git 将它们拉出。 Each commit holds two things:每个提交包含两件事:

  • A commit holds a full snapshot of every file as of the state it had when it got frozen into that commit, as an archive.提交保存了从 state 开始的每个文件的完整快照,当它被冻结到该提交时,作为存档。 These are the files we want to use / work-with.这些是我们想要使用/处理的文件。 They're in a special, read-only, Git-only format, compressed and—importantly— de-duplicated , so that the repository doesn't fatten up grotesquely as we add commit after commit.它们采用特殊的、只读的、仅 Git 的格式,经过压缩,并且——重要的是——去重,因此当我们在提交后添加提交时,存储库不会变得怪诞地变胖。 Only changed files need to be stored, and even then they can be stored very efficiently (with further tricks happening eventually, long after the initial de-duplication).只需要存储更改的文件,即使这样它们也可以非常有效地存储(最终会发生进一步的技巧,在最初的重复数据删除之后很久)。

  • A commit also holds some metadata , or information about the commit itself.提交还包含一些元数据,或有关提交本身的信息。 This includes things like the name and email address of the person who made the commit, and date-and-time stamps, and so on.这包括提交人的姓名和 email 地址,日期和时间戳等。


1 Some simple math, specifically the pigeonhole principle , tells us that this cannot work forever. 1一些简单的数学运算,特别是鸽笼原理,告诉我们这不可能永远有效。 The sheer size of the hash space is meant to put off the inevitable Git failure for billions of years, though the birthday problem or birthday paradox means it can't work even as long as that. hash 空间的绝对大小旨在将不可避免的 Git 故障推迟数十亿年,尽管生日问题或生日悖论意味着它不能工作那么久。 But if it works until after we're all dead and gone, that's probably long enough.但如果它一直有效到我们都死去之后,那可能就足够长了。 That's someone else's problem!那是别人的问题!


Commits form branches提交表单分支

Crucially for Git itself, Git stores the raw hash IDs for a list of previous commits in any new commit anyone ever makes, in the metadata for the new commit.对于 Git 本身至关重要,Git 在新提交的元数据中存储原始 hash ID 以获取任何新提交中先前提交的列表。 The commits themselves are entirely read-only—this is required by the magic hash ID numbering system—so no part of any commit can ever change, and each commit records one—well, zero or more, but usually just one— previous commit hash ID.提交本身是完全只读的——这是神奇的 hash ID 编号系统所要求的——因此任何提交的任何部分都不能更改,并且每个提交记录一个——嗯,零个或更多,但通常只有一个——之前的提交 hash ID。 This forms a backwards-looking chain, where, if we just know the hash ID of the latest commit, we can have Git find all the earlier commits.这个 forms 是一个向后看的链,如果我们只知道最新提交的 hash ID,我们可以让Git找到所有早期提交。

Let's call the latest commit hash ID H (for "hash"), and draw the commit:让我们调用最新的提交 hash ID H (代表“哈希”),并绘制提交:

            <-H

We draw the commit with an arrow sticking out of it, pointing backwards, to represent the stored hash ID in H .我们用一个箭头指向提交来绘制提交,以表示存储H中的 hash ID。 Let's call that hash ID G for short and draw commit G too:让我们简称为 hash ID G并绘制提交G

        <-G <-H

Of course, G has another arrow sticking out of it: G 's metadata stores the raw hash ID of some earlier commit.当然, G还有另一个箭头: G的元数据存储了一些较早提交的原始 hash ID。 Let's call that one F and draw it in too:让我们称它为F并把它也画进去:

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

The result is a chain that goes back all the way through history, until it reaches the very first commit ever.结果是一条链一直追溯到历史,直到它到达有史以来的第一次提交。 If we call that commit A , and draw it in, commit A has no backwards arrow: its list of previous commits is empty .如果我们调用该提交A并将其绘制,则提交A没有向后箭头:它的先前提交列表是的。 This is kind of a special case, but it simply means "there's no previous commit", which allows Git to stop going backwards:这是一种特殊情况,但它只是意味着“没有先前的提交”,它允许 Git 停止倒退:

A--B--...--G--H

We'll get lazy (for various reasons good and bad) and stop drawing the internal arrows here as arrows.我们会变得懒惰(由于各种原因好坏)并停止在这里将内部箭头绘制为箭头。 Inside Git, they point backwards—and it's important to remember that now and then—but humans really like to go forwards a lot, so a diagram like this is OK, and easier to draw (ie, I'm a little lazy ).在 Git 内部,它们指向后方——重要的是不时记住这一点——但人类真的很喜欢 go 向前很多,所以这样的图表是可以的,而且更容易绘制(即,我有点懒)。 The point here is to note that all we need is to somehow (magically?) find commit H .这里的重点是要注意,我们所需要的只是以某种方式(神奇地?)找到提交H From there, Git can find every earlier commit .从那里, Git 可以找到每个较早的提交

Branch names find branches分支名称查找分支

We could memorize the actual latest hash ID.我们可以记住实际最新的 hash ID。 We'd have to memorize a new, big ugly hash ID every time we make a new commit, though.不过,每次我们进行新的提交时,我们都必须记住一个新的、又大又丑的 hash ID。 We could write them down on paper, and carefully type them in to Git commands, but that seems stupid... and, wait!我们可以将它们写在纸上,然后小心地将它们输入到 Git 命令中,但这似乎很愚蠢……等等! We have a computer!我们有电脑! Let's make it remember these hash IDs for us.让我们为我们记住这些 hash ID。 What if we dropped the latest hash ID into a file.., no, better, a database of branch names?如果我们将最新的 hash ID 放入文件中会怎样……不,更好的是分支名称数据库?

And that's exactly what we do: a repository has, besides the database of commit objects and other internal Git objects that's indexed by raw hash IDs, a database—usually much smaller—containing branch and tag and other such names.这正是我们所做的:除了提交对象的数据库和其他由原始 hash ID 索引的内部 Git 对象的数据库之外,存储库还具有一个数据库(通常要小得多)包含分支和标签以及其他此类名称。 Each name holds just one hash ID, but that's all it takes:每个名称仅包含一个hash ID,但仅此而已:

...--G--H   <-- master

The name master literally holds the hash ID for commit H .名称master字面上包含提交H的 hash ID。 So now we can just run:所以现在我们可以运行:

git log master

and view commits from H on backwards, as Git follows H back to G , then to F , and so on, backwards.并从H向后查看提交,因为 Git 跟随H回到G ,然后到F ,依此类推,向后。

We can, at any time , tell Git to add new names .我们可以随时告诉 Git添加新名称 We can also tell Git to delete names.我们还可以告诉 Git 删除名称。 The names simply point to a commit: there is one arrow coming out of each name, pointing to any commit we like.这些名称只是指向一个提交:每个名称都有一个箭头,指向我们喜欢的任何提交。

If we create a new branch name feature right now, we have to pick one of our commits, to make the name point to that commit.如果我们现在创建一个新的分支名称feature ,我们必须选择一个提交,以使名称指向该提交。 We can pick H , or G , or anything earlier.我们可以选择HG或任何更早的东西。 But let's pick H —the latest commit—because why would we pick an earlier one?但是让我们选择H最新的提交——因为我们为什么要选择更早的提交呢? We get this:我们得到这个:

...--G--H   <-- feature, master

Both names point to commit H .两个名称都指向提交H That's perfectly fine.这完全没问题。 But that leaves us with a couple of questions:但这给我们留下了几个问题:

  • How do we know which name we're actually using?我们如何知道我们实际使用的是哪个名称 To answer that, Git attaches a special name, HEAD , written in all uppercase like this, to one and only one branch name.为了回答这个问题,Git 将一个特殊的名称HEAD以这样的全大写形式附加到一个且只有一个分支名称。

  • Which branch are these commits on?这些提交在哪个分支上?

The second question is a trick question, because these commits are actually on both branches .第二个问题是一个技巧问题,因为这些提交实际上是在两个分支上。 What it means, in Git, for a commit to be "on" some branch, is that we can find that commit by starting with the commit to which the branch name points and working backwards.这意味着,在 Git 中,对于“在”某个分支上的提交,我们可以通过从分支名称指向的提交开始并向后工作来找到该提交。

So, with:所以,有:

...--F--G--H   <-- feature (HEAD), master

we know that we're "on" branch feature , and that this branch contains commits up through and including H .我们知道我们正在“开启”分支feature ,并且该分支包含提交,包括H Branch master also contains the same commits.分支master包含相同的提交。

Making new commits drags the current branch name forward进行新提交会将当前分支名称向前拖动

If we now make a new commit, in the usual way—we'll skip right over what that is since you already know it—this new commit will get a new unique hash ID.如果我们现在以通常的方式进行的提交——我们将直接跳过它,因为你已经知道它——这个新的提交将获得一个新的唯一 hash ID。 Let's call that hash ID I .我们称之为 hash ID I Commit I will point back to existing commit H , like this:提交I将指向现有的提交H ,如下所示:

...--G--H
         \
          I

I drew new commit I on a new line, because Git's last trick, as it creates new commit I and thereby obtains the new unique hash ID, is to store that ID into the current branch name .我在新行上绘制了新的提交I ,因为 Git 的最后一个技巧,当它创建新的提交I并因此获得新的唯一 hash ID 时,将该 ID存储当前分支名称中。 The current branch name is the one that has HEAD attached to it.当前分支名称是附加了HEAD的分支名称。 The other branch names—no matter how many there are—don't change at all during this update, so now we have:其他分支名称——不管有多少——在这次更新期间根本不会改变,所以现在我们有了:

...--G--H   <-- master
         \
          I   <-- feature (HEAD)

Putting some pieces together把一些碎片放在一起

Let's go back to your original statement:让我们 go 回到你原来的语句:

I have a master branch and a feature branch.我有一个master分支和一个feature分支。 I've created a third hot-fix branch, made some changes, and merged it to master .我创建了第三个hot-fix分支,进行了一些更改,并将其合并到master Afterwards, I also merged it to feature , because I am doing a lot of work there and I want the bugs fixed there as well.之后,我还将它合并到feature ,因为我在那里做了很多工作,我也希望在那里修复错误。

Each of these names must, necessarily, point to one single commit .这些名称中的每一个都必须指向一个单一的提交 Which one?哪一个? I have no idea: that's in your repository, which I don't have.我不知道:那在您的存储库中,而我没有。 I'll make something up, with the caveat that my made-up example obviously won't match your own repository exactly.我会编造一些东西,但需要注意的是,我编造的示例显然与您自己的存储库不完全匹配。 Here's my made-up example:这是我编造的例子:

          E  <-- release/1.0
         /
...--C--D--F--G   <-- master
               \
                H   <-- feature (HEAD)

Now, to fix the bug, which was in commit D , we've checked out commit D and attached a branch name there, hot-fix :现在,为了修复提交D中的错误,我们检查了提交D并在此处附加了一个分支名称hot-fix

          E  <-- release/1.0
         /
...--C--D   <-- hot-fix (HEAD)
         \
          F--G   <-- master
              \
               H   <-- feature

and then we fix the bug, making a new commit I :然后我们修复错误,进行新的提交I

          E  <-- release/1.0
         /
...--C--D--I   <-- hot-fix (HEAD)
         \
          F--G   <-- master
              \
               H   <-- feature

We can now merge commit I , the hot-fix for the bug in D , into any of these various branches.我们现在可以将提交I (针对D中的错误的热修复)合并到任何这些不同的分支中。 (That's the advantage to going back in time and fixing the bug as soon as it got introduced: it's always possible to "forward merge" the fix. Not everyone uses this method, as it brings a certain amount of pain as well, but without any better guide to drawing your situation, it's what I'm drawing.) (这是回到过去并在引入错误后立即修复它的优势:总是可以“向前合并”修复。不是每个人都使用这种方法,因为它也会带来一定的痛苦,但没有绘制您的情况的任何更好的指南,这就是我正在绘制的内容。)

Merging in Git并入 Git

Note: I'm moving fast here, skipping over a lot of helpful introductory material.注意:我在这里快速移动,跳过了很多有用的介绍性材料。 For instance, I've skipped how Git transforms a commit, which is a snapshot, into a diff or patch or changeset, which isn't.例如,我跳过了 Git 如何将作为快照的提交转换为差异或补丁或变更集,但事实并非如此。 For a gentler introduction, consider a longer tutorial, or one of my other answers.对于更温和的介绍,请考虑更长的教程,或我的其他答案之一。

Merging in Git is about combining changes .合并 Git 是关于合并更改 Sometimes there's nothing to combine, which makes things easier, but I've carefully made sure that there's always something to combine in these examples, so that we'll go through only the most general case.有时没有什么可以组合,这使事情变得更容易,但我已经仔细确保在这些示例中总有一些东西可以组合,因此我们将仅通过最一般的情况来组合 go。

Ignoring the other diagrams so far, let's look just at this particular diagram:忽略到目前为止的其他图表,让我们看一下这个特定的图表:

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

We'll pick one of the two branches and use git switch or git checkout to make it the current branch and hence make J or L the current commit .我们将选择两个分支之一并使用git switchgit checkout使其成为当前分支,从而使JL成为当前提交 For simplicity (and because the merge result is symmetric in most practical cases anyway) let's just pick br1 .为简单起见(并且因为在大多数实际情况下合并结果是对称的),我们只选择br1 Then we'll run git merge and give it the other branch name, which Git will use to find the other of the two commits—ie, commit L .然后我们将运行git merge并给它另一个分支名称,Git 将使用它来查找两个提交中的另一个,即提交L

To perform the actual work of merging, Git must now find the common starting point .要执行合并的实际工作,Git 现在必须找到共同的起点 Git does this on its own, using the graph implied by the parent links. Git 使用父链接隐含的图形自行执行此操作。 That is, Git works backwards from the current commit J to find I and H and so on.即 Git 从当前提交J向后工作以找到IH等等。 At the same time, Git works backwards from the other commit we named: L leads back to K , which leads back to H .同时,Git 从我们命名的另一个提交向后工作: L导致回到K ,这导致回到H

It's obvious from this drawing that the first shared commit, on both branches , is commit H .从这张图中很明显,两个分支上的第一个共享提交是提交H (Or is that the last shared commit? Depends on whether you think backwards, like Git does, doesn't it?) Anyway, skipping over lots more potential complexities that don't occur in carefully chosen examples like this one, commit H is our merge base , or common starting point here, for git merge . (或者这是最后一次共享提交?取决于你是否向后思考,就像 Git 所做的那样,不是吗?)无论如何,跳过更多潜在的复杂性,这些复杂性在精心挑选的示例中不会出现,提交Hgit merge合并基础或此处的共同起点。

To combine work , what Git does now is to run git diff on (ie, compare the snapshots in) commits H and J , to see what we did on branch br1 , and then, separately, diff commits H and L , to see what they (whoever "they" are) did on branch br2 .为了结合工作,Git 现在做的是在提交HJ上运行git diff (即比较快照),看看我们在分支br1上做了什么,然后分别 diff 提交HL ,看看什么他们(无论“他们”是谁)在分支br2上做了。 For each file, this gives us "what changed", and the changes apply to the same starting point because there's just the one copy of that file in H , and the one copy in each of the two targets.对于每个文件,这为我们提供了“更改了什么”,并且更改适用于相同的起点,因为H中只有该文件的一个副本,两个目标中的每个都有一个副本。 That makes it easy for Git to combine the two sets of changes.这使得 Git 可以轻松组合两组更改。 Each change is either adding or deleting some lines, because that's what comes out of git diff internally.每个更改都是添加或删除一些行,因为这是git diff内部差异的结果。

Having combined the changes—taken the "ours" (H-to-J) changes and the "theirs" (H-to-L) changes—for any one particular file, Git then applies both changes to the copy of the file from the base commit, H .对于任何一个特定文件,合并了更改(采用“我们的”(H-to-J)更改“他们的”(H-to-L)更改)后,Git 然后将这两项更改应用于文件副本基本提交H Where (if) two changes overlap , Git requires that they be exactly the same change, otherwise Git declares a merge conflict and forces you—the programmer—to fix things up.如果(如果)两个更改重叠,Git 要求它们是完全相同的更改,否则 Git 会声明合并冲突并迫使您(程序员)修复问题。 (This description omits a number of special cases, but covers the majority of what Git does for you.) (此描述省略了一些特殊情况,但涵盖了 Git 为您所做的大部分工作。)

If Git is able to combine all the changes to all the various changed files, and apply those combined changes to all the files from the merge base commit, Git will go on to make a new commit.如果 Git 能够将所有更改合并到所有各种更改的文件,并将这些合并更改应用于合并基础提交中的所有文件,则 Git 将 go 进行新的提交。 This new commit is special in exactly one way: instead of listing one previous commit, it lists two .这个新的提交有一个特别之处:它没有列出一个先前的提交,而是列出了两个. That's all, Like every other commit: this new commit has a snapshot and metadata: the snapshot stores all the files , in their merged form, and the metadata shows that you made this commit, just now.就是这样,就像其他所有提交一样:这个新的提交有一个快照和元数据:快照以合并的形式存储所有文件,并且元数据显示刚刚进行了这个提交。 The only thing special about this new commit is that it links back to both commits J and L , like this:这个新提交的唯一特别之处是它链接回提交JL 如下所示:

          I--J
         /    \
...--G--H      M
         \    /
          K--L

As usual, Git writes the new commit's hash ID in the current branch name .像往常一样, Git 将新提交的 hash ID 写入当前分支名称中。 Since we said we made br1 the current branch, that means we finish off our drawing this way:由于我们说我们将br1当前分支,这意味着我们以这种方式完成绘制:

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

Note how, at this point, the commits "on" br1 include commit M , and then—because M points back to both J and L —commits J and L , and I and K , and then H and G and so forth.请注意,此时“on” br1的提交如何包括提交M ,然后——因为M指向JL提交JL ,以及IK ,然后是HG等等。 But when we use the name br2 , the commits that are "on" this branch start at L , and go back to K , then H , then G and so on.但是当我们使用名称br2时,这个分支上的提交从L开始,go 回到K ,然后是H ,然后是G等等。 Git literally can't go forwards: commit H does not connect to either I or K . Git 字面上不能 go转发:提交H不连接到IK All the connections are strictly backwards.所有的连接都是严格向后的。

So commit M is not on br2 , it's only on br1 .所以提交M不在br2上,它只在br1上。 The same goes for IJ . IJ也是如此。 But merging like this has caused commits KL , which were only on br2 before, to be on both branches now.但是像这样的合并导致之前只在br2上的提交KL现在在两个分支上。

This is in fact the answer to your question, but to finish things off, let's look at the rest of the details.这实际上是您的问题的答案,但为了完成事情,让我们看看 rest 的详细信息。 There are often a bunch of complications in the way of seeing it.看待它的方式往往有一堆复杂性。

Ahead and/or behind领先和/或落后

Given any two branch names, Git can count how many commits one name is "ahead of" the other, and how many it is "behind", simply by counting the commits that we can only "see" on one of the two names:给定任意两个分支名称,Git 可以计算一个名称“领先”另一个名称的提交数量,以及它“落后”的数量,只需计算我们只能在两个名称之一上“看到”的提交:

...--F   <-- br1
      \
       G--H   <-- br2

Here, branch br2 is "two commits ahead of" br1 because commits up through F are on both branches but GH are only on br2 .在这里,分支br2是“提前两次提交” br1因为通过F的提交在两个分支上,但GH仅在br2上。 This in turn means that br1 is two commits behind br2 .这反过来意味着br1br2后面的两个提交。 When we have a "forking" or "branching" structure like this one:当我们有这样的“分叉”或“分支”结构时:

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

we find that br1 is "ahead 2" and "behind 1" of br2 , and br2 is "ahead 1 and behind 2" of br1 .我们发现br1br2 br2 br1的“领先 1 和落后 2”。 These are precisely commits I -J (the two) and K` (the one).这些正是提交I -J (the two) and K`(一个)。

Merging causes commits that weren't "on" some branch to be "on" the branch.合并会导致未“在”某个分支上的提交“在”该分支上。 That branch is now "less behind", maybe not at all behind.该分支现在“不那么落后”,也许根本不落后。 But making a new merge commit causes the branch that just acquired the new commit to be "ahead".但是进行新的合并提交会导致刚刚获得新提交的分支“领先”。

Note that if you draw the graph , a lot of these things get clearer.请注意,如果您绘制图表,其中很多内容会变得更加清晰。 You can do this by hand, or have Git and/or other programs do it.您可以手动执行此操作,或让 Git 和/或其他程序执行此操作。 See also Pretty Git branch graphs .另请参阅漂亮的 Git 分支图

More questions更多问题

This is the whole answer to your question as asked, but there are a few more questions we should ask.这是对您所问问题的全部答案,但我们还应该问几个问题。 Let's look now at these.现在让我们看看这些。

You have a repository, local to you, where you do your work.您有一个本地存储库,您可以在其中进行工作。 Let's call this "on your laptop" (whether or not your computer is a laptop computer) as this gives us a good name for it.让我们称之为“在您的笔记本电脑上”(无论您的计算机是否是笔记本电脑),因为这给了我们一个好名字。

Meanwhile, GitLab has a different repository , stored on their server.同时,GitLab 有一个不同的存储库,存储在他们的服务器上。 This other repository has its own databases .这个其他存储库有自己的数据库 The commits that are in their objects database, and the commits that are in your laptop Git's objects database, get shared: their unique hash IDs tell them apart, and when you and they have the same commit , you and they have a database entry whose key is that particular shared hash ID and the contents match.在他们的对象数据库中的提交,以及在你的笔记本电脑 Git 的对象数据库中的提交,被共享:它们唯一的 hash ID 将它们区分开来,当你和他们有相同的提交时,你和他们有一个数据库条目,其关键是特定的共享 hash ID 和内容匹配。 (This database is a simple key-value store , with hash IDs as the keys.) But—this is importantthe branch names are not shared . (这个数据库是一个简单的键值存储,以 hash ID 作为键。)但是——这很重要——分支名称不是共享的 They have their names, and you have yours;他们有他们的名字,你也有你的; each of their branch names holds one hash ID, and each of your branch names holds one hash ID.他们的每个分支名称都拥有一个 hash ID,而您的每个分支名称都拥有一个 hash ID。

When you ran git clone to make your laptop repository, your Git read, from their Git, all the commits and stuffed them into your new objects database, so that you had copies of all their commits.当您运行git clone以创建笔记本电脑存储库时,您的 Git 从他们的 Git 中读取所有提交并将它们填充到您的新对象数据库中,这样您就拥有了所有提交副本。 But your Git took each of their branch names, like master , and changed them into remote-tracking names like origin/master .但是您的 Git 采用了它们的每个分支名称,例如master ,并将它们更改为远程跟踪名称,例如origin/master Your Git software will copy their branch names into your repository by making this change, turning branch names into remote-tracking names.您的 Git 软件将通过进行此更改将其分支名称复制到您的存储库中,将分支名称转换为远程跟踪名称。 This keeps your branch names yours .这样可以保留您的分支名称yours

The git fetch command has your Git reach out to their software, using the URL stored under the name origin . git fetch命令让您的 Git 使用存储在名称origin下的 URL 访问他们的软件。 Your Git has their Git list out their branch names and commit hash IDs.您的 Git 有他们的 Git 列出他们的分支名称并提交 hash ID。 Your Git can immediately tell if you have all their commits or not, just by checking those hash IDs.您的 Git 可以通过检查那些 hash ID 立即判断您是否有所有提交。 For any commits you are missing, your Git will ask their software to package up those commits and send them over, and it will do that.对于您丢失的任何提交,您的 Git 将要求他们的软件 package 提交这些提交并将它们发送过来,它会这样做。 Those commits come with the parent commits, up until the point where your Git already has the commit(s) needed, so that you get all their new commits that they have that you don't.这些提交与父提交一起提供,直到您的 Git 已经具有所需的提交,以便您获得他们拥有的所有新提交而您没有。 Then your Git will update all your remote-tracking names to remember their new most-recent-commits for each branch (what Git calls a tip commit , whether it's a branch tip or a remote-tracking-name tip).然后您的 Git 将更新您的所有远程跟踪名称,以记住每个分支的最新最新提交(Git 称为提示提交,无论是分支提示还是远程跟踪名称提示)。

If you run:如果你运行:

git status

you will see an "ahead" and/or "behind" count for the upstream of your (local) branch, which is typically the corresponding remote-tracking name in their repository.您将看到(本地)分支上游的“领先”和/或“落后”计数,这通常是其存储库中相应的远程跟踪名称 So you run:所以你运行:

git fetch

(or git fetch origin ) to update your repository with any new commits they have that you don't, and update all your origin/* names to remember the tip commits of their branches, and then git status tells you about your branch foo with respect to their origin/foo , assuming the upstream of your foo is origin/foo . (或git fetch origin )以使用您没有的任何新提交更新您的存储库,并更新您的所有origin/*名称以记住其分支的提示提交,然后git status告诉您有关您的分支foo的信息关于他们的origin/foo ,假设您的foo的上游是origin/foo

To send commits to them you use git push .要将提交发送给他们,请使用git push This is as close as Git gets to the opposite of git fetch , but it has some key differences.这与 Git 与git fetch截然相反,但它有一些关键区别。 As before, your Git calls up their software at the stored URL.和以前一样,您的 Git 在存储的 URL 中调用他们的软件。 You (or your Git) then offer to them a commit hash ID: usually, the tip commit of one of your branches.然后,您(或您的 Git)向他们提供提交 hash ID:通常是您的一个分支的提示提交 If they don't have that commit yet, your Git software has to offer the commit's parent(s), just like for git fetch , until you and they find the shared commits that you don't need to re-send;如果他们还没有提交,您的 Git 软件必须提供提交的父级,就像git fetch一样,直到您和他们找到您不需要重新发送的共享提交; you then send all these commits.然后你发送所有这些提交。 But then, instead of having them set a "remote-tracking name" in their names database, your Git will ask—politely, by default—that they change one of their branch names to remember this as their new tip commit for that branch.但是,随后,您的 Git 将默认情况下礼貌地询问他们更改其中一个分支名称以将其记住为该分支的新提示提交,而不是让他们在其名称数据库中设置“远程跟踪名称”。

That is, you don't say to them: "Here's some new commits, remember them under grant/master " but rather: "Here's some new commits, now please set your own master` to remember the tip commit as its latest tip commit."也就是说,您不会对他们说:“这里有一些新的提交,请记住它们在 grant/master 下" but rather: "Here's some new commits, now please set your own master` 以将提示提交记住为其最新的提示提交。” In other words, you'd like them to add commits to their branch .换句话说,您希望他们将提交添加到他们的分支

GitHub, GitLab, and other hosting providers all add various protection features so that not anybody can add commits—base Git has no permissions checking!—but assuming you have permission and you're just adding new commits , they generally do as you ask. GitHub、GitLab 和其他托管服务提供商都添加了各种保护功能,因此任何人都无法添加提交——基础 Git 没有权限只是问你添加新提交 In any case, whether or not they permit this update, they send back a response: OK, I set my branch or no, because ____ (fill in the blank).无论如何,无论他们是否允许此更新,他们都会发回一个响应:好的,我设置我的分支否,因为____ (填空)。 The OK response makes your Git update your remote-tracking name, because they accepted the update. OK 响应使您的 Git 更新您的远程跟踪名称,因为他们接受了更新。 The "no" makes your Git produce a "rejected" error, using the filled-in-blank. “否”使您的 Git 使用填充空白产生“拒绝”错误。

The most common error (besides the permission-denied type errors that hosting sites add) is "non-fast-forward", which is Git's peculiar way of saying: I can only remember one hash ID in a name, and if I make this change, some commits that I can access right now via that name, will no longer be find-able via that name.最常见的错误(除了托管站点添加的权限被拒绝类型错误之外)是“非快进”,这是 Git 特有的说法:我只能记住一个名称中的 hash ID,如果我这样做更改,我现在可以通过该名称访问的一些提交,将不再可以通过该名称找到。

Since you'll generally view ahead/behind through the lens of "these pushes worked and updated my remote-tracking names" or "I just ran git fetch and updated my remote-tracking names", what you see here is often a single or double level reflection of what you've done in your own repository.由于您通常会通过“这些推送有效并更新了我的远程跟踪名称”或“我刚刚运行git fetch并更新了我的远程跟踪名称”的镜头来查看前后,因此您在这里看到的通常是单个或您在自己的存储库中所做的工作的双重反映。 This makes for a bit of confusion sometimes.这有时会造成一些混乱。 But if we take the repository I drew early on above:但是,如果我们采用我在上面早期绘制的存储库:

          E  <-- release/1.0
         /
...--C--D--I   <-- hot-fix (HEAD)
         \
          F--G   <-- master
              \
               H   <-- feature

and then add merge commits as appropriate:然后根据需要添加合并提交

git switch master && git merge hot-fix

          E  <-- release/1.0
         /
...--C--D--I___  <-- hot-fix
         \     \
          F--G--J   <-- master (HEAD)
              \
               H   <-- feature

(new commit J on master ) and: (新提交Jmaster )和:

git switch feature && git merge hot-fix

          E  <-- release/1.0
         /
...--C--D--I______  <-- hot-fix
         \     \  \
          F--G--J  \  <-- master
              \     \
               H-----K   <-- feature (HEAD)

(the diagrams get messy, but here's new merge K on feature ) we can see that feature is now "ahead of" master because commit K isn't on master . (图表变得混乱,但这里是新的合并K on feature )我们可以看到该feature现在“领先于” master因为提交K不在master上。 That's true even if we just merge feature into master instead:即使我们只是将feature合并到master中也是如此:

git switch feature && git merge master

produces:产生:

          E  <-- release/1.0
         /
...--C--D--I___  <-- hot-fix
         \     \
          F--G--J   <-- master (HEAD)
              \  \
               H--K   <-- feature

It's the new merge on feature , commit K , that pushes us "ahead of" master every time.这是新feature合并,提交K ,每次都推动我们“领先于” master

(Git has a non-merge-y kind of operation that Git calls—misleadingly—a fast-forward merge , that git merge can perform, that doesn't make a new merge commit. It can only be used in particular circumstances and Git Hub in particular will never do this, no matter how much you might want them to; Git Lab may differ here. But presumably this is not what you're doing, or you would not have had this question. I skipped over it in the merge section for space reasons.) (Git 有一种非合并类型的操作,Git 调用——误导性地——快进合并git merge可以执行,不会进行新的合并提交。它只能用于特定情况和 Z0066EZ105AD27957 Hub永远不会这样做,无论您多么希望他们这样做;Git Lab可能在此处有所不同。但大概这不是您正在做的,或者您不会有这个问题。我在出于空间原因合并部分。)

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

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