简体   繁体   English

Git将不同分支中的旧提交推送到新分支

[英]Git pushes old commit in different branch to new branch

I've been struggling with GIT for a while now since it does some weird things. 我一直在与GIT挣扎一段时间,因为它做了一些奇怪的事情。

I have a base commit with the initial code. 我有一个初始代码的基本提交。

Ok next I make a branch do my edits and commit and push: 好的,接下来我做一个分支做我的编辑并提交和推送:

git checkout -b feature/feature1
git commit -m "added feature1"
git push origin feature/feature1

Now I do a pull request on that feature. 现在我对该功能进行拉取请求。

After that I change branch and update for example: 之后我改变分支并更新例如:

git checkout -b chore/update-framework
git commit -m "updated framework"
git push origin chore/update-framework

At this point when I try a pull request on the 2nd push, it contains the first commit with all the feature stuff in it. 此时,当我在第二次推送时尝试拉取请求时,它包含第一次提交,其中包含所有功能内容。 So all the changed files from that commit are in the new pull request, while the old pull request only contains the feature files. 因此,来自该提交的所有已更改文件都在新的pull请求中,而旧的pull请求仅包含要素文件。

What am I doing wrong? 我究竟做错了什么?

When you did: 你这样做的时候:

git checkout -b chore/update-framework

You did not checkout on master before . 你没有在主结帐。

To start a new branch from the commit you want (example with master): 从您想要的提交开始一个新分支(例如master):

git checkout -b chore/update-framework master

or: 要么:

git checkout master
git checkout -b chore/update-framework

or: 要么:

git branch chore/update-framework master
git checkout chore/update-framework

To correct your mistake, you can do: 要纠正错误,您可以:

git rebase --onto master feature/feature1 chore/update-framework

Git will pick all your commits of chore/update-framework which are not pointed by feature/feature1, then it will put them onto master and finally update your chore/update-framework branch. Git将选择所有chore / update-framework的提交,这些提交未被feature / feature1指向,然后它将把它们放到master上,最后更新你的chore / update-framework分支。

I think your issue here comes down to understanding what it means for a commit to be on (or it may be better to say contained in ) a branch. 我认为你的问题归结为理解对一个分支的提交意味着什么(或者可能更好地说包含在内 )。 This is different in Git than in many traditional version control systems. 这在Git中与许多传统版本控制系统不同。

In Git, each commit is on (or contained in) many branches, at least potentially. 在Git中,每个提交都在(或包含在) 许多分支中,至少是潜在的。 It can be on no branches, 1 branch, 2 branches, and so on. 它可以在没有分支,1个分支,2个分支等等。 This is because, in Git, a branch name is just a moveable pointer to one specific commit . 这是因为,在Git中,分支名称只是指向一个特定提交的可移动指针。 Think of it more as a yellow sticky note. 把它想象成一个黄色的粘滞便笺。 Creating a new branch name means grabbing a blank sticky note and writing the name on it, feature/feature1 . 创建新的分支名称意味着抓取空白便笺并在其上写下名称feature/feature1

But where is the note attached? 但附注在哪里? Well, here it helps a lot to draw (part of) the commit graph . 好吧,这里绘制(部分)提交有很大帮助。

Drawing (part of) a commit graph 绘制(部分)提交图

In Git, each commit "points to" its predecessor(s) (or parent commits). 在Git中,每个提交“指向”其前任(或父提交)。 For a linear chain of commits, this is basically a series of arrows pointing backwards in time: 对于线性提交链,这基本上是一系列向后指向的箭头:

... <- o <- o <- o <- ... <- o

Each o here represents a commit and each commit points back to its (one) parent. 这里的每个o代表一个提交,每个提交指向它的(一个)父代。

A merge commit points back to two parents, which is in fact what makes it a merge commit. 合并提交指向两个父项,这实际上是使它成为合并提交的原因。 These are much harder to draw with arrows when restricted to plain text on StackOverflow: 当限制为StackOverflow上的纯文本时,使用箭头绘制这些更难:

           o--o
          /    \
... --o--*      M--o
          \    /
           o--o

Imagine arrowheads on all the lines, so that they all point left-ish (up-and-left, or down-and-left, if needed). 想象一下所有线上的箭头,以便它们都指向左边(上下左右,或者左右,如果需要的话)。 M here is the merge commit, with its two parents. 这里的M是合并提交,有两个父母。 I marked one more commit here, commit * . 我在这里标记了一个提交,commit * It is not a merge commit, but it does have two children (two commits that point back to it). 不是合并提交,但它确实有两个节点(两个提交指向它)。 This sometimes makes it more interesting. 这有时会使它更有趣。

In fact, commit * is especially interesting before we make the merge commit. 事实上, 我们进行合并提交之前 ,commit *特别有趣。 Let's draw that (by erasing M and some o s): 让我们画出(通过删除M和一些o ):

           o
          /
... --o--*
          \
           o--o

Adding branch names to the drawing 将分支名称添加到图形中

This is where branches really come into proper focus. 这是分支机构真正得到适当关注的地方。 Let's add names and longer <-- arrows: 让我们添加名称和更长的<--箭头:

           o   <-- bra1
          /
... --o--*
          \
           o--o   <-- bra2

Branch bra1 points to (or, equivalently, is a yellow sticky note pasted onto) the upper tip-most o commit, and bra2 points to the lower tip-most o . 分支bra1点(或等效地,是粘贴到黄色便条)上部尖端最o提交和bra2指向下尖端最o

That commit we called out as * is on both branches (as is every commit to its left). 我们称之为*提交在两个分支上 (就像它左边的每个提交一样)。

There are two more pieces to this particular puzzle. 这个特殊难题还有两个部分。 One is the name HEAD , and the other has to do with what happens when we add new commits and write up new branch names. 一个是HEAD的名称,另一个是与我们添加新提交和编写新分支名称时发生的情况有关。

HEAD

The name HEAD , in Git, tells us about our current commit and current branch. Git中的名称HEAD告诉我们当前的提交和当前分支。 The way Git does this is almost laughably simple: HEAD normally just contains the name of the branch. Git这样做的方式几乎是可笑的简单: HEAD通常只包含分支的名称。 Think of it as another sticky note (maybe bright pink, or green, or purple, just to make it obviously a bit different). 把它想象成另一个粘滞便笺(可能是亮粉色,或绿色或紫色,只是为了使它显然有点不同)。 It can point directly to a commit—that's the "detached HEAD" thing you have no doubt seen—but normally it just has a branch name written on it: 可以直接指向一个提交 - 这是你毫无疑问看到的“分离的HEAD” - 但通常它只有一个分支名称写在它上面:

           o   <-- HEAD->bra1
          /
... --o--*
          \
           o--o   <-- bra2

This means we are on branch bra1 . 这意味着我们在分支bra1

Growing a branch 成长一个分支

Let's make a new commit while we're on bra1 . 当我们在bra1上时,让我们做一个新的提交。

When making a new commit, Git: 在进行新提交时,Git:

  1. Reads HEAD (it says branch bra1 ). 读取HEAD (它表示branch bra1 )。
  2. Reads the commit ID from branch bra1 (that's some big ugly SHA-1 a1c397f... or whatever). 从分支bra1读取提交ID(这是一些很难看的SHA-1 a1c397f...或者其他什么)。
  3. Makes a new commit using whatever you've git add ed, with your log message, pointing to that parent ID. 使用您git add ,使用您的日志消息指向该父ID,进行新的提交。 The new commit gets a new unique SHA-1 (such as 0bc3112... ). 新提交获得一个新的唯一SHA-1(例如0bc3112... )。
  4. Writes this new number into the current branch, bra1 . 将此新数字写入当前分支bra1

Step 4 causes bra1 to point to the new commit, and now we have: 第4步导致bra1指向新提交,现在我们有:

           o--o   <-- HEAD->bra1
          /
... --o--*
          \
           o--o   <-- bra2

Adding a merge commit 添加合并提交

Just for completeness, let's look at making a merge commit. 为了完整起见,我们来看看合并提交。

The process of doing the merge itself can be messy, but actually making the merge commit is easy: it's just a commit with two parents. 执行合并本身的过程可能很混乱,但实际上使合并提交很容易:它只是一个有两个父项的提交。 Note that we still are on bra1 . 请注意,我们仍然在bra1 We run git merge bra2 . 我们运行git merge bra2 Git fires up the merge machinery to do the work in the work-tree. Git启动合​​并机制来完成工作树中的工作。 If there are conflicts, it leaves a mess behind and we have to fix that up manually, but if not, it automatically starts a new commit. 如果存在冲突,则会造成混乱,我们必须手动修复它,但如果没有,它会自动启动新的提交。

The new commit happens just as before, with one small change. 新提交就像以前一样,只做了一个小改动。 In step 3, instead of writing one parent ID (the one from the existing bra1 ), it writes two parent IDs: the first one is the usual one, and the second one is the ID obtained from reading bra2 . 在步骤3中,它不是写一个父ID(来自现有bra1 ),而是写入两个父ID:第一个是通常的ID,第二个是从读取bra2获得的ID。

Step 4 works as usual, writing the new commit's ID into bra1 (because HEAD still says "use branch bra1 "): 第4步像往常一样工作,将新提交的ID写入bra1 (因为HEAD仍然说“使用分支bra1 ”):

           o--o
          /    \
... --o--*      M   <-- HEAD->bra1
          \    /
           o--o   <-- bra2

Because M is a merge (has two parents), this means that all the commits that used to be exclusively on bra2 are now on bra1 as well! 由于M是合并(有两个父母),这意味着使用的所有提交到独家的bra2现在对bra1以及!

Commit * used to be on both branches, and still is. Commit *曾经在两个分支上,但仍然是。 And, there are still those two post- * commits we can get to by starting from bra2 and working leftwards. 并且,仍然有两个post- *提交我们可以从bra2开始向左工作。

We're only allowed to move left(ish), so starting from bra2 , we are not allowed to move to M , which means we cannot get to the top row of commits. 我们只能向左移动(ISH),所以从开始bra2 ,我们是不允许移动到M ,这意味着我们不能去提交的顶行。 We can only get there by starting there, or starting from M . 我们只能从那里开始,或从M开始。 We are, however, not just allowed, but actually required , to follow all the parents of a merge like M . 然而,我们不仅被允许,而且实际上需要跟随像M这样的合并的所有父母。 So starting from bra1 , we get M , then we get both sides of the branch structure all the way back to commit * , and keep going leftward from there. 所以从bra1开始,我们得到M ,然后我们得到分支结构的两边一直回到提交* ,并从那里继续向左。

If you count the nodes, you will see that there are now three commits contained in bra1 that are not contained in bra2 ... but all commits contained in bra2 are contained in bra1 . 如果计算节点,您将看到bra1中包含的三个提交未包含在bra2 ...但bra2中包含的所有提交都包含在bra1

OK, so what about creating new branches? 好的,那么创建新的分支怎么样?

Here HEAD comes back into play. 在这里, HEAD重新回归。

You can use git branch to just create a branch: 您可以使用git branch 创建一个分支:

$ git branch bra3

By default, this reads HEAD to figure out where we are now. 默认情况下,这会读取HEAD以确定我们现在的位置。 HEAD says bra1 and bra1 has M 's ID in it. HEADbra1bra1M的ID。 So this makes bra3 point to M : 所以这使得bra3指向M

           o--o
          /    \
... --o--*      M   <-- HEAD->bra1, bra3
          \    /
           o--o   <-- bra2

Note that HEAD still points where it used to, and no other branch is disturbed either, we just added a new moveable label bra3 . 请注意, HEAD仍指向以前的位置,也没有其他分支受到干扰,我们只添加了一个新的可移动标签bra3 If we now move bra1 or bra2 , bra3 continues to point to M . 如果我们现在移动bra1bra2bra3继续指向M

Since HEAD still points to bra1 , new commits will make bra1 change. 由于HEAD仍指向bra1 ,因此新的提交将使bra1发生变化。

If we use git checkout -b to create the new branch, though, we get this instead: 但是,如果我们使用git checkout -b创建新分支,我们会改为:

           o--o
          /    \
... --o--*      M   <-- bra1, HEAD->bra3
          \    /
           o--o   <-- bra2

This looks almost exactly the same. 这看起来几乎完全一样。 The difference is that, besides adding the new branch name, we also change HEAD . 不同之处在于,除了添加新的分支名称外,我们更改了HEAD Now it points to bra3 . 现在它指向bra3

Making a new branch that points to something other than the current commit 创建一个指向当前提交之外的新分支

Let's go back to git branch , which just makes the branch and does not change HEAD . 让我们回到git branch ,它只是创建分支并且不会改变HEAD The git branch command takes one more optional argument: git branch命令还有一个可选参数:

$ git branch bra4 bra2

Instead of making bra4 point to the same commit as HEAD , this says "make bra4 point to the same commit as bra2 ". 相反使得bra4指向同犯的HEAD ,这个说“让bra4指向同犯的bra2 ”。 So now we have: 所以现在我们有:

           o--o
          /    \
... --o--*      M   <-- bra1, HEAD->bra3
          \    /
           o--o   <-- bra2, bra4

Now let's git checkout bra4 , just to see what that does (in terms of the graph—it also checks out the files, of course): 现在让我们看一下git checkout bra4 ,只是为了看看它做了什么(就图形而言 - 它当然也检查了文件):

$ git checkout bra4

           o--o
          /    \
... --o--*      M   <-- bra1, bra3
          \    /
           o--o   <-- bra2, HEAD->bra4

Again, nothing happens to any of the branch labels themselves. 同样,任何分支标签本身都没有任何反应。 In the graph, we just change where HEAD points. 在图中,我们只是改变HEAD指向的位置。

(We could have used the combined form—we already have bra4 now so it's too late now, but it would have been one command instead of two—by doing git checkout -b bra4 bra2 .) (我们本来可以使用组合形式 - 我们现在已经有了bra4所以现在已经太晚了,但它可能是一个命令而不是两个 - 通过git checkout -b bra4 bra2 。)

The bottom line for new branch pointers 新分支指针的底线

This means we get to choose which commit a new branch points to whenever we create that new branch. 这意味着我们可以在创建新分支时选择新分支指向哪个提交 The default is "wherever HEAD points", which usually means reading another branch name. 默认值为“ HEAD指向的任何位置”,这通常意味着读取另一个分支名称。 We can make HEAD point somewhere useful first, or we can just add that to the branch-creator and make it all happen together. 我们可以先将HEAD指向某个有用的地方,或者我们可以将它添加到branch-creator中并使它们一起发生。

That leaves one question, and it does not always have a single right answer: where should we point that new branch? 这留下了一个问题,它并不总是有一个正确的答案:我们应该在哪里指出新的分支?

Probably we want to point it to something like origin/master or origin/feature1 . 可能我们想把它指向像origin/masterorigin/feature1 Sometimes it makes more sense to pick some other starting point, maybe even one with no label pointing to it. 有时选择其他起点更有意义,甚至可能没有标签指向它。

Wait, origin/master is a label? 等等, origin/master是一个标签?

Yes, these things are branch labels—moveable pointers to commits—too. 是的,这些东西都是分支标签 - 也是提交的可移动指针。 They're just not labels that you move. 它们不是移动的标签。 You let them move when you run git fetch (or pull, but pull is just fetch followed by one other step). 当你运行git fetch (或者pull,但是pull只是fetch,然后是另一个步骤)时,你让它们移动。 In other words, they "track the remote" (you git fetch origin and origin/* move if needed); 换句话说,他们“跟踪遥控器”(如果需要,你可以git fetch originorigin/*移动); so they are remote-tracking branches . 所以他们是远程跟踪分支机构

When we drew up the graphs above, we probably should have included these labels ... maybe something like this: 当我们绘制上面的图表时,我们可能应该包含这些标签......可能是这样的:

           o      <-- HEAD->feature1
          /
... --o--*        <-- origin/feature1
          \
           o--o   <-- feature2

If you want to make a new feature3 that grows out from * as well, git checkout -b feature3 origin/feature1 would do that. 如果你想制作一个从*中长大的新feature3git checkout -b feature3 origin/feature1也会这样做。 Commit * would then be contained in four branches: feature1 , origin/feature1 , feature2 , and feature3 . 然后,Commit *将包含在四个分支中: feature1origin/feature1feature2feature3

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

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