简体   繁体   English

我如何 git 将单个提交重新设置为主并推送为新分支

[英]How can I git rebase a single commit onto master and push as new branch

I made a wrong commit commit_c on branch branch_a .我在分支commit_c上提交了错误的branch_a
How can I git rebase a single commit ( commit_c ) onto master and push as a new branch branch_b .我如何 git 将单个提交( commit_c )重新设置为 master 并作为新分支branch_b
So that i have the same scenario as if i would have created a branch from master, did the same changes like in commit_c and pushed the branch_b .这样我就有了相同的场景,就好像我会从 master 创建一个分支一样,在commit_c中进行了相同的更改并推送了branch_b
Note: I want to rebase the commit, I don't want to cherry-pick, since that would create a new commit_d right?注意:我想重新提交提交,我不想挑选,因为那会创建一个新的commit_d对吗?

No commit, once made, can ever be changed.任何提交,一旦做出,就永远无法改变。 So you must make a new commit regardless of everything else.因此,无论其他任何事情,您都必须进行新的提交。

It's important to understand this about Git: commits are what matter, and commits are forever.了解 Git 的这一点很重要:提交是最重要的,而提交是永远的。 You have a commit you don't like.你有一个你不喜欢的承诺。 You will need to make a new commit—"new and improved", as it were—to use instead.需要进行新的提交——“新的和改进的”,就像它一样——来使用。

Another thing to know about commits is that every commit has a unique hash ID.关于提交的另一件事是,每个提交都有一个唯一的 hash ID。 That hash ID is now (and forever) reserved for that commit. hash ID 现在(并且永远)为该提交保留。 Every Git repository everywhere will use that hash ID for that commit, if the repository has that commit.如果存储库具有提交,则任何地方的每个Git 存储库都将为该提交使用hash ID。 If the repository doesn't have that commit it won't have anything with that hash ID.如果存储库没有该提交,则它不会有任何具有该 hash ID 的内容。 1 1

Saying that commits are forever overstates things slightly, though.不过,说提交永远是夸大了。 These hash IDs are big, and ugly, and impossible for humans to get right and to remember.这些 hash ID 又大又丑,人类不可能正确记忆。 So we don't use them.所以我们不使用它们。 Git uses them internally. Git在内部使用它们。 We use names .我们使用名称 A name—a branch name like master or a tag name like v2.1 —holds a hash ID.一个名称——像master这样的分支名称或像v2.1这样的标签名称——拥有一个 hash ID。 That's most of what a name does: hold a hash ID.这就是名称的大部分作用:持有 hash ID。 This lets us—or Git— find one particular commit by name.这让我们——或 Git——可以通过名称找到一个特定的提交


1 Technically, as long as two Gits never "meet", it's OK for them to have different objects that have the same hash ID. 1从技术上讲,只要两个 Git 从未“相遇”,它们就可以拥有具有相同 hash ID 的不同对象。 If they do ever meet, they'll think they have each other's objects already, and if you're trying to get them to have repository-sex so that they acquire each other's objects, it will go wrong.如果他们真的相遇,他们会认为他们已经拥有彼此的对象,如果你试图让他们拥有存储性以便他们获得彼此的对象,那将是错误的 go。 In practice, this kind of duplication never happens.在实践中,这种重复从未发生过。 You could in theory make it happen on purpose, but see How does the newly found SHA-1 collision affect Git?理论上你可以故意让它发生,但请参阅新发现的 SHA-1 冲突如何影响 Git?


What good does finding one commit do?找到一个提交有什么好处?

Every commit contains a full snapshot of all of your files.每个提交都包含所有文件的完整快照。 Commits are not changes , they're snapshots.提交不是更改,它们是快照。 This snapshot is typically the main bulk of any one commit: its data.这个快照通常是任何一次提交的主要部分:它的数据。 But each commit also has some metadata , or information about the data.但是每个提交也有一些元数据,或者关于数据的信息。 This includes stuff like who made the commit, when, and why (the log message).这包括诸如谁提交、何时提交以及为什么提交(日志消息)之类的内容。 There's one other key item—or list—in the metadata, though.不过,元数据中还有另一个关键项目或列表。 Each commit contains the raw hash ID of its immediately- previous , or parent commit.每个提交都包含其前一个提交或提交的原始 hash ID。 (Merge commits have two or more parents, so this is a list of hash IDs, rather than a single hash ID.) (合并提交有两个或多个父级,因此这是一个 hash ID 列表,而不是单个 hash ID。)

This means that if we can find the last commit in a chain, we can find the whole chain:这意味着如果我们可以找到链中的最后一个提交,我们就可以找到整个链:

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

The name master holds the hash ID of the last commit.名称master持有最后一次提交的 hash ID。 In this case, master holds the hash ID of some commit with a big ugly hash that we're just going to call H .在这种情况下, master持有一些提交的 hash ID 和一个丑陋的 hash ,我们将称之为H We say that the name master points to H .我们说名称master指向H

Meanwhile, the actual commit itself, as retrieved by Git from its big database of all-commits-and-other-internal-objects, contains the hash ID of some earlier commit.同时,由 Git 从其所有提交和其他内部对象的大型数据库中检索到的实际提交本身包含一些较早提交的 hash ID。 We'll call it G , rather than guessing some hash ID, so we say that H points to G .我们称之为G ,而不是猜测一些 hash ID,所以我们说H指向G

Commit G is an ordinary commit too, so it points to—contains the hash ID of—its parent F .提交G也是一个普通的提交,所以它指向——包含它的父F的 hash ID。 F of course also points back to some earlier commit, which continues to point back. F当然也指向一些更早的提交,它继续指向回来。 The process only stops when we reach the very first commit ever, which—since it can't point back—has no parent at all.这个过程只有在我们到达第一个提交时才会停止,因为它不能指向回来,根本没有父提交。

Hence, finding one commit is as good as finding every commit from here backwards .因此,找到一个提交与从这里向后找到每个提交一样好。 Merge commits add a bit of a wrinkle as each merge has two (or more) parents, so suddenly you're able to find two (or more) previous chains and work backwards along both.合并提交会增加一些麻烦,因为每个合并都有两个(或更多)父级,所以突然之间你可以找到两个(或更多)先前的链并沿着两者向后工作。 We don't need to look any further than that here, though, so we won't.不过,我们不需要再看这里了,所以我们不会。

Draw your commits绘制你的提交

Whenever you have a series of commits and some branch names, draw them .每当您有一系列提交和一些分支名称时,请绘制它们 Get into practice, doing this on a whiteboard or on paper or whatever, because you'll need it as a general skill when working with Git.开始实践,在白板或纸上或其他任何东西上执行此操作,因为在使用 Git 时,您将需要它作为一般技能。 (You can have Git do it for you, using git log --graph —which draws a rather crude ASCII-graphics graph—or have a graphical interface do it, but it's a good idea to do it yourself a few times too.) (你可以让 Git 为你做这件事,使用git log --graph - 它绘制了一个相当粗糙的 ASCII 图形图 - 或者有一个图形界面来做,但最好自己做几次。)

Let's watch the addition of a single commit on master in a tiny repository with just three commits.让我们看看在一个只有三个提交的小型存储库中在master上添加一个提交。 We start with:我们从:

A <-B <-C   <--master

We git checkout master , which selects commit C as the current commit .我们git checkout master选择了 commit C作为当前 commit That extracts all of its frozen-forever files into a work-area (our working tree or work-tree ), and also into Git's index (from which Git makes new commits; we won't go into the details here, but knowing about the index is important).这会将其所有永久冻结的文件提取到工作区(我们的工作树工作树)中,也提取到 Git 的索引中(Git 从中进行新的提交;我们不会在这里详细介绍 go,但知道索引很重要)。

We'll now make some changes to the work-tree files, use git add to copy them back into the index, and run git commit to make a new snapshot.我们现在将对工作树文件进行一些更改,使用git add将它们复制回索引,然后运行git commit以创建新快照。 This new snapshot will get some big ugly hash ID, but we'll keep things simple and just call it D .这个新快照会得到一些大而丑陋的 hash ID,但我们会保持简单,就叫它D D needs to point back to C . D需要指向C Because my crude-ASCII-graph-drawing talents are limited, I'll draw it like this:因为我粗略的 ASCII 图形绘制能力有限,所以我会画成这样:

A--B--C   <-- master
       \
        D

Now, the name master must point to the latest commit, so Git now writes D 's hash ID into the name master :现在,名称master必须指向最新的提交,因此 Git 现在将D的 hash ID 写入名称master

A--B--C
       \
        D   <-- master

Draw your branch names画出你的分支名称

Let's rewind a bit to:让我们回顾一下:

A--B--C   <-- master

If you have more than one branch name, you can draw in the additional names.如果您有多个分支名称,则可以绘制其他名称。 Let's make a new branch, dev for development.让我们创建一个新的分支, dev用于开发。 A name has to point to a commit.名称必须指向提交。 Which commit should we use?我们应该使用哪个提交? Let's use the latest, which is C :让我们使用最新的C

A--B--C   <-- master, dev

Note that all three commits are now on both branches.请注意,所有三个提交现在都在两个分支上。 The latest master commit is C , and the latest dev commit is also C .最新的master提交是C ,最新的dev提交也是C

We need to know which branch name we're using.我们需要知道我们正在使用哪个分支名称 Either way, we'll use commit C now, but to remember which name we are using, let's add the name HEAD .无论哪种方式,我们现在都将使用提交C ,但要记住我们使用的名称,让我们添加名称HEAD You can draw this as another pointer:您可以将其绘制为另一个指针:

                      HEAD
                       |
                       v
A--B--C   <-- master, dev

or use less space like this:或者像这样使用更少的空间:

A--B--C   <-- master, dev (HEAD)

Basically, we attach the special name HEAD to one of the branch names.基本上,我们将特殊名称HEAD附加到分支名称之一。 The name that HEAD is attached-to is the branch we used with our git checkout . HEAD所附加的名称是我们与git checkout一起使用的分支。

Let's make commit D now, on dev :现在让我们在dev上提交D

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

So what Git does, when we make a new commit, is to make the new commit point back to the current commit—as found by our current branch namem as found by HEAD —and then write the new commit's hash ID into the current branch name, as found by HEAD .因此,当我们进行新提交时,Git 所做的是将新提交点返回到当前提交(由HEAD找到的当前分支 namem 找到),然后将新提交的 hash ID 写入当前分支名称,由HEAD发现。

If we go back to master , with git checkout master , we get this:如果我们 go 回到master ,用git checkout master ,我们得到这个:

A--B--C   <-- master (HEAD)
       \
        D   <-- dev

No commits have changed at all, but now we are once again using commit C .根本没有任何提交发生变化,但现在我们再次使用提交C If we make a new commit E , we need to draw it in, either on a new line:如果我们进行新的提交E ,我们需要将其绘制在新行上:

        E   <-- master (HEAD)
       /
A--B--C
       \
        D   <-- dev

or on the same line:或在同一行:

A--B--C--E   <-- master (HEAD)
       \
        D   <-- dev

Both drawings represent the same thing: in both cases, commits ABC are on both branches .两张图代表相同的东西:在这两种情况下,提交ABC都在两个分支上。 Commit D is only on dev and commit E is only on master .提交D仅在dev上,提交E仅在master上。

Which way should you draw them?您应该以哪种方式绘制它们? Well, any way you like.嗯,随你喜欢。 You can put newer commits towards the top or bottom, instead of towards the right.您可以将较新的提交放在顶部或底部,而不是向右。 Just remember that each commit points backwards , and each branch name points to the last commit.请记住,每个提交都指向backs ,每个分支名称都指向最后一个提交。 Git calls that last commit the tip commit. Git 调用最后一次提交的提示提交。

This is the normal way branches work in Git: we add new commits to them .这是分支在 Git 中的正常工作方式:我们向它们添加新的提交 The commits that were on the branch are still there, on the branch;分支上的提交仍然存在,在分支上; the new commit is the new tip of the branch.新提交是分支的新提示。

Forgetting a commit忘记提交

Suppose we decide commit E is bad and we should not have made it.假设我们认为提交E是错误的,我们不应该这样做。

We can tell Git to move a branch name.我们可以告诉 Git移动一个分支名称。 When we do this, we can move it backwards from the normal direction.当我们这样做时,我们可以将它从法线方向向后移动。 That is, suppose we have:也就是说,假设我们有:

        E   <-- master (HEAD)
       /
A--B--C
       \
        D   <-- dev

and we tell Git: move the name master back one step, to C .我们告诉 Git:将名称master向后移动一步,到C The result is:结果是:

        E
       /
A--B--C   <-- master (HEAD)
       \
        D   <-- dev

Commit E still exists, but we can't find it.提交E仍然存在,但我们找不到它。 It's been abandoned!它被遗弃了! There's a way to find A , by starting at C and going back to B and then A , or by starting at D and going back—but commits only let you go back, not forward, so you can't get from C to E .有一种方法可以找到A ,从C开始然后返回B然后A ,或者从D开始然后返回 - 但提交只会让你E向后,而不是向前,所以你不能从C得到. Eventually, Git will see that we can't find it and have not used it for a long time (at least 30 days, by default) and will remove it after all.最终,Git 会看到我们找不到它并且很长时间没有使用它(默认情况下至少 30 天)并将其删除。

Hence, we can get our Git to forget this bad commit E that we made.因此,我们可以让我们的 Git忘记我们所做的错误提交E It will stick around for a while, but eventually it will go away.它会持续一段时间,但最终它go 消失。 This assumes we haven't copied commit E into some other Git repository, of course.当然,这假设我们没有将提交E复制到其他一些 Git 存储库中。 If we used git push to send E to some other Git, that other Git now has a copy of E , and having our Git forget ours won't do anything about theirs . If we used git push to send E to some other Git, that other Git now has a copy of E , and having our Git forget ours won't do anything about theirs .

Back to your problem回到你的问题

Now that we have this structure, we can draw your problem and see, clearly, what we should do:现在我们有了这个结构,我们可以画出你的问题并清楚地看到我们应该做什么:

          I--J   <-- branch-a (HEAD)
         /
...--G--H   <-- master

Let's say that it's commit J that's wrong.假设提交J是错误的。 We'd like a different commit K to come from H , and to make the name branch-b point to J .我们希望来自H的不同提交K ,并使名称branch-b指向J Here is how we can do that with git cherry-pick , which copies a commit to a new (and perhaps improved) one.下面是我们如何使用git cherry-pick来做到这一点,它将提交复制到一个新的(也许是改进的)一个。 First we'll git checkout master to select commit H :首先我们将git checkout master到 select commit H

          I--J   <-- branch-a
         /
...--G--H   <-- master (HEAD)

Then we'll create a new branch name, branch-b , here, and git checkout branch-b :然后我们将在这里创建一个新的分支名称branch-bgit checkout branch-b

          I--J   <-- branch-a
         /
...--G--H   <-- master, branch-b (HEAD)

Then we'll run git cherry-pick branch-a .然后我们将运行git cherry-pick branch-a The name branch-a selects commit J , so that's the commit that Git will copy:名称branch-a选择提交J ,因此这是 Git 将复制的提交:

          I--J   <-- branch-a
         /
...--G--H   <-- master
         \
          K   <-- branch-b (HEAD)

If we want Git to forget commit J , we can force the name branch-a to point to I now.如果我们希望 Git 忘记提交J ,我们现在可以强制名称branch-a指向I There are multiple ways to do that—I'll use a short one in a moment—but we want:有多种方法可以做到这一点——我稍后会使用一个简短的方法——但我们想要:

            J   [abandoned]
           /
          I   <-- branch-a
         /
...--G--H   <-- master
         \
          K   <-- branch-b (HEAD)

A very short sequence of Git commands to go from the starting graph, to this one, is:从起始图到此图,Git 命令到 go 的一个非常短的序列是:

git checkout -b branch-b master
git cherry-pick branch-a
git branch -f branch-a branch-a~1   # a little bit magic

But: what if it's commit I that we want to copy to K ?但是:如果我们想要复制到K是提交I怎么办? Well, we can start out with the same git checkout -b branch-b master to get:好吧,我们可以从同样的git checkout -b branch-b master开始得到:

          I--J   <-- branch-a
         /
...--G--H   <-- master, branch-b (HEAD)

Now we would run git cherry-pick branch-a~1 .现在我们将运行git cherry-pick branch-a~1 This ~1 suffix means count back one commit , ie, start at J , where branch-a points, then move back one step to commit I .这个~1后缀表示倒数一次 commit ,即从branch-a指向的J开始,然后向后退一步提交I So we'll now have:所以我们现在有:

          I--J   <-- branch-a
         /
...--G--H   <-- master
         \
          K   <-- branch-b (HEAD)

where K is a copy of I .其中KI的副本。

More alternatives更多选择

In fact, since I and K both have the same parent and the same snapshot , we could have done this instead, which might be cleverer:事实上,由于IK都有相同的 parent相同的 snapshot ,我们可以这样做,这可能更聪明:

            J   <-- branch-a
           /
          I   <-- branch-b
         /
...--G--H   <-- master

You need to define what result you want你需要定义你想要的结果

The real question here is what you want done with commit J .这里真正的问题是你想用提交J做什么。 Whatever else we do, commit J still has I as its parent.无论我们做什么,提交J仍然有I作为它的父级。 That's literally impossible to change: nothing about any commit can ever be changed.这实际上是不可能改变的:任何提交无法改变。

Suppose the result you want , in the end, looks more like this:假设你想要的结果最终看起来更像这样:

        I--J   [abandoned]
        |
        | L   <-- branch-a
        |/
...--G--H   <-- master
         \
          K   <-- branch-b

In this case, you'll need to copy I to K , and J to L .在这种情况下,您需要将I复制到K ,并将J复制到L But perhaps you can use instead:但也许您可以改用:

          L   <-- branch-a
         /
...--G--H   <-- master
         \
          I   <-- branch-b
           \
            J   [abandoned]

The key is always:关键永远是:

  • Draw what you have.画出你所拥有的。
  • Think about what you want instead (and maybe draw that too).想想你想要什么(也许也画出来)。
  • Pick Git commands that do the thing you want.选择 Git 命令来做你想做的事。

The history in your Git repository consists of all the commits that you can find, by starting at branch names (and other names) to find one specific commit, and working backwards from there. Git 存储库中的历史记录包含您可以找到的所有提交,方法是从分支名称(和其他名称)开始找到一个特定的提交,然后从那里向后工作。

The branch names in your Git repository are under your control. Git 存储库中的分支名称由您控制。 You can do anything you want with them, at any time.您可以随时对它们做任何您想做的事情。 You can rename them, move them around, or delete them.您可以重命名它们、移动它们或删除它们。 You can make new ones.你可以制作新的。 Each of your branch names locates one tip commit , and from there, Git will work backwards.您的每个分支名称都找到一个提示 commit ,从那里, Git 将向后工作。

You're only really stuck with a commit once you copy it into another Git repository .仅当您将提交复制到另一个 Git 存储库时,您才会真正被提交。 That other Git repository has its own branch names, and if it can find the commit—which will have that same hash ID, as hash IDs can never change—then it will continue to find that commit.其他 Git 存储库有自己的分支名称,如果它可以找到提交——它将具有相同的 hash ID,因为 hash ID 将永远不会改变——然后它会继续找到那个提交 ID。 If you let a bad commit get out, you have to get every Git repository that has it, to give it up.如果你让一个错误的提交退出,你必须让每个拥有它的 Git 存储库放弃它。 It's usually much easier to just let it be out there, bad, and add a new commit to fix it, because Git repositories are greedy for new commits: give them a new commit and they'll usually take it.将它放在那里通常要容易得多,糟糕,然后添加一个新的提交来修复它,因为 Git 存储库对新的提交很贪婪:给他们一个新的提交,他们通常会接受它。 Try to take a commit away, and you need to use a lot of force ( git branch --force or git reset ).尝试取消提交,您需要使用很大的力量( git branch --forcegit reset )。

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

相关问题 如何将2个主题分支重新绑定到新分支? - How can I rebase 2 topic branches onto a new branch? 我怎样才能安全地 git rebase master 中的提交历史 - How can I safely git rebase commit history in master 如何在Git上将svn提取的分支重新建立到主节点上? - How to rebase a svn fetched branch onto the master on Git? Git rebase 分支到 master 失败,如何解决? - Git rebase a branch onto master failed, how to resolve? 我如何从特定的develop分支提交转换为特定的master分支提交 - How can I rebase from specific develop branch commit to specific master branch commit 如何从提交树的底部创建一个新的 git 分支,它与 master 分支没有共同的提交? 对于 gh-pages - How can I create a new git branch from the base of the commit tree , which has no commit in common with master branch ? for gh-pages git rebase --onto单次提交的结果 - git rebase --onto results on single commit 在 git 中,如何将分支变基为 master - In git, how to rebase branch to master 如何将本地分支变基到远程主机 - How to rebase local branch onto remote master Git的。 在本地主服务器上重新启动本地分支。 如何忽略单个文件更改? - Git. Rebase local branch atop local master. How do I ignore a single files changes?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM