Git提交,如何将一个提交分成两部分 [英] Git commit, how to break one commit into two

查看:129
本文介绍了Git提交,如何将一个提交分成两部分的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在分支 temp 中,我创建了一个新分支 temp2 .我重新整理了一个文件,然后对其进行了一些清理.我承诺.

后来我发现,如果我进行了重新整理,提交,然后进行清理,然后提交,那么我的历史将更加清晰.

换句话说,我只想将此提交分为两个阶段,两个提交.

好吧,我记得重新安排的部分.因此,我切换到了 temp 分支,并再次进行了重新排列,而没有进行清理.我的想法是我可以将提交从 temp2 提交到temp.

我所拥有的:

  ---------- -----重排之前<-temp\\\-------重新排列加上清理<-temp2 

我想要什么:

  ------ -----重排之前-----重排加上清除<-temp 

首先,我尝试将 temp2 重新设置为 temp ,但是遇到了合并冲突.这让我感到惊讶,因为它没有要求合并任何内容.我以为重新定基只是更改了附加提交的位置.所以我放弃了.

然后,我尝试将重新安排+清理"从 temp2 引入到 temp .但是我 still 发生合并冲突.真的使我感到惊讶,因为我认为这是完全不可能的.所以我也放弃了.

我被困住了.我不明白这里发生了什么.git不是基于差异的.提交只是快照.那么为什么会有冲突呢?我要做的只是按一定顺序排列这些快照.为什么这么难,我该怎么办?

解决方案

TL; DR

使用:

  git签出温度hash = $(git log --no-walk --pretty = format:%B temp2 | git commit-tree -p temp temp2 ^ {tree})git merge --ff-only $哈希 

将提交 C 复制到新的提交 C'中,该提交重新使用日志消息和 C 的树,然后调整分支名称 temp 指向新的提交.

我以为重新定位只是在附加提交的地方发生了变化...

那是关键错误!

如果我拿起您的图表并缩小名称,我们将从以下内容开始:

  A--B<-临时\温度<-temp2 

什么 git checkout temp2;git rebase temp 确实(或试图做到)是将提交 C 复制到新的提交 C',在某种程度上类似于C",但具有 B 作为其父级.如果成功,最终结果将是:

  C'<-temp2/A--B<-温度\C [废弃] 

我想你已经知道了;您错在哪里是如何复制的想法.

本质上, git rebase 的作用是运行 git cherry-pick .从逻辑的角度来看, git cherry-pick 的作用是;潜在的机制是使用Git的合并机制-将提交转换为,然后将其应用于其他地方.因此,Git将比较(diff)提交 C 与提交 A ,然后比较diff commit B A ,并尝试结合这两个变更集以在 B 之上产生commit C'.当然,这两个变更集重叠,这就是为什么您会发生合并冲突的原因.

您没有没有使用 git rebase 来实现这一目标.由于 C 已经是您想要的最终结果,因此您可以简单地将C的快照以及可能的提交消息复制到新的提交 C'中.没有面向用户的Git命令可以执行此操作,但是有一个底层命令可以执行此操作.这些低级命令就是Git所说的 plumbing 命令.

尤其是 git commit-tree 会写入一个提交对象.为此,它需要一个现有的 tree 对象(快照)以及一些父哈希集.通常,我们可以使用管道命令 git write-tree 创建一棵树,该命令会写出当前索引,但是在已经存在的提交 C 中,我们已经有了一个非常合适的最终结果.我们只需要获取它的树哈希ID. gitrevisions语法为此,是 temp2^ {tree} ,因为名称 temp2 指向提交 C .

我们想要提交提交 C 的副本的父级当然是提交 B .名称 temp 就足够了,因为它现在指向提交 B .我们还必须在标准输入或作为参数或文件的情况下,向 git commit-tree 提供提交日志消息. git log --no-walk --pretty = format:%B 将从给定的提交中获取日志消息.

一旦创建了新的提交对象,就必须在宽限期内(对于 git gc /git prune ),默认情况下为14天.为此,我们可以使用 git merge --ff-only 推进当前分支名称(即 temp ).

(请注意,只要您确定 git log ... | git commit-tree 都可以正常工作,就可以将整个操作作为一个单行表达式来完成.)

Working in branch temp, I created a new branch, temp2. I rearranged a file heavily and then did some cleanup on it. I committed.

Later it occurred to me that I'd have a clearer history if I had done the rearrangement, committed, then did the cleanup, then committed.

In other words, I just want to turn this commit into two stages, two commits.

Well, I remembered the rearrangement part. So I switched to branch temp and did the rearrangement again, without the cleanup. My idea was that I could then bring the commit from temp2 onto temp.

What I have:

------ before ----- rearrangement <-- temp
              \
               \ 
                \
                 ------- rearrangement plus cleanup <-- temp2

What I want:

------ before ----- rearrangement ----- rearrangement plus cleanup <-- temp

First I tried rebasing temp2 onto temp, but I got a merge conflict. This surprised me because wasn't asking to merge anything. I thought rebasing just changed where a commit was attached. So I aborted that.

Then I tried cherrypicking "rearrangement plus cleanup" from temp2 into temp. But I still got a merge conflict. That really surprised me because I thought it was completely impossible. So I aborted that too.

I'm stuck. I don't understand what's going on here. git is not based on diffs. Commits are just snapshots. So why is there a conflict? All I'm asking to do is arrange these snapshots in a certain order. Why is that so difficult, and how can I do it?

解决方案

TL;DR

Use:

git checkout temp
hash=$(git log --no-walk --pretty=format:%B temp2 | git commit-tree -p temp temp2^{tree})
git merge --ff-only $hash

to copy commit C to a new commit C' that re-uses the log message and tree of C, and then adjust the branch name temp to point to the new commit.

Long

I thought rebasing just changed where a commit was attached ...

That's the key error!

If I take your diagram and shrink the names down, we start with:

A--B  <-- temp
 \
  C   <-- temp2

What git checkout temp2; git rebase temp does—or tries to do—is to copy commit C to new commit C' that is "like C" in some ways, but has B as its parent. The final result, if it had been successful, would be:

     C'  <-- temp2
    /
A--B   <-- temp
 \
  C   [abandoned]

You already knew that, I think; where you went wrong is the idea of how the copying takes place.

What git rebase does is, in essence, to run git cherry-pick. What git cherry-pick does is—well, from a logical point of view; the underlying mechanism is to use Git's merge machinery—to turn a commit into a change, and then apply that change elsewhere. So Git will compare (diff) commit C vs commit A, and then also diff commit B vs A, and attempt to combine the two change-sets to produce commit C' atop B. These two change-sets overlap, of course, and that's why you get a merge conflict.

You don't have to use git rebase to achieve this. Since C is already the final result you want, you can simply copy C's snapshot—and perhaps its commit message as well—to a new commit C'. There is no user-oriented Git command to do this, but there is a low-level command that does exactly this. These low level commands are what Git calls plumbing commands.

In particular, git commit-tree writes a commit object. To do so, it needs an existing tree object (snapshot) plus some set of parent hashes. Normally we might make a tree with the plumbing command git write-tree, which writes out the current index, but we already have a perfectly suitable final result in existing commit C. We just need to obtain its tree hash ID. The gitrevisions syntax for this is temp2^{tree}, since the name temp2 points to commit C.

The parent we want for our copy of commit C is of course commit B. The name temp suffices since it points to commit B at the moment. We also must supply to git commit-tree the commit log message, on standard input, or as an argument or a file. git log --no-walk --pretty=format:%B will obtain the log message from the given commit.

Once we have made the new commit object, we must hurry up and make some name remember that object, within our grace period (for git gc / git prune), which is 14 days by default. To do that, we can use git merge --ff-only to advance the current branch name (i.e., temp).

(Note that you can do this whole thing as a one line expression, provided you're sure the git log ... | git commit-tree is all going to work.)

这篇关于Git提交,如何将一个提交分成两部分的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆