2

情况:在提出拉取请求时,我希望接收者能够理解它所做的更改。我发现将它们压缩成一个提交可能会令人困惑,尤其是在以下情况下:

  1. 对代码的编辑也被移动了 - diff 将其呈现为批量删除和添加,而不是突出显示编辑。

  2. 代码被添加到一系列相似的部分,例如 case 语句、级联 ifs、yacc 产生式 - diff 通常将更改重建为重叠部分(例如,它使用前一部分的开头而不是添加部分,添加新的结尾和另一个开始,然后使用上一节的结尾);添加新的代码结尾,在某些情况下,挑选出一些小的相似之处,然后删除并插入大量相同的代码。(我意识到 diff 使用 LCS 并且速度非常快 - 但有时它的结果很难理解,即使考虑到 diff 不具备语法意识并且无法识别您看到的代码“部分”)。

顺便说一句:我使用git diff --color-words --ignore-space-change,这很好,但也错误重构,可以隐藏细节 - 我担心接收者可能会使用 plaingit diff并看到完全不同的东西(他们可以以不同的方式重构)。

TASK:好的,因此显而易见的解决方案是将 Pull Request 分成单独的提交。有时,这些可能是我开始时的实际提交,所以我需要做的就是首先不要 rebase/squash。但我发现即使那样,差异也可能不清楚(尤其是上面的原因(2)),我需要进一步分开它们。

  1. 显而易见的方法是使用git add --patch/-p. 但是,补丁很难用于重叠更改 - 您可以d划分甚至删除 hunks e但是当您想要的更改结合了添加、删除和公共代码时,考虑反转差异有点令人费解。

  2. 我实际上所做的是直接编辑文件:删除我不想要的部分并提交;然后撤消删除(使用我的编辑器)并提交。根据实际来源工作比根据差异工作更清晰和直观 - 但感觉就像我在与 git 作斗争并且做错了(而且,依赖编辑器撤消似乎很容易发生意外)。

  3. 我想到首先git stash是文件,然后通过删除我不想要的部分来准备第一次提交;然后git stash apply“撤消”该删除以准备第二次提交。但是我不确定您是否可以在 a 中间执行此操作rebase(尚未尝试过)。

问题:我要花好几个小时才能做到这一点……我想我会通过练习有所提高,但是……我在正确的轨道上吗?有没有更好的办法?你能从一开始就防止错误重构的差异吗?我是否为了清晰而努力工作?

(公平地说,这是不久前对微妙而复杂的代码进行的许多编辑——花费这些时间揭示了更深入的见解。)

4

2 回答 2

2

基于这些答案,在启动交互式 rebase ( get rebase -i ...) 并e修改一个提交后:

git reset HEAD^     # reverts index to previous commit (not change files)
                    # so it's as if you are just about to add and commit
git stash           # save
git stash apply     # get it back
...edit the file, deleting the changes you don't want in the first commit
git add .
git commit -m "...first changes..."

git stash apply     # get it back again (ie undo the above delete)
...(I needed to resolve a merge conflict)
git add .
git commit -m "...second changes..."

git rebase --continue

可惜没有一个git stash copy可以保存您的更改而不恢复。可能有更顺畅的方法来做到这一点。

令我惊讶的是,您可以在交互式 rebase 中间使用 git 的全部功能。您可以忽略您“应该”正在编辑的旧提交,而是添加两个提交;您可以存储并申请。我可能需要研究实际是如何rebase实现的,并停止将其视为抽象。实际上,rebase 手册页有一个拆分提交的标题。

于 2013-06-27T19:04:40.793 回答
1

为什么不使用提交来撤消更改(而不是存储),因为我们已经有了它们?有两个问题:引用提交,以及让文件(工作树)和索引处于正确的状态。

  1. 引用提交 我们可以剪切并粘贴提交的哈希。或者,使用git tag tmp(删除git tag -d tmp)创建一个临时标签。n或者,计算来自分支的提交并使用branch~n. 或者,对于 rebase 现在正在修改的提交,使用它存储的哈希,cat .git/rebase-merge/amend(但尴尬和未记录的实现细节 - 我在这里得到了信息)。

  2. 文件和索引 我目前的理解:resetcheckout不会HEAD在指定文件(路径)时改变。像这样使用时,reset仅更改索引;checkout更改索引和文件。要仅更改文件,您可以使用git show <commit>:file > file(注意文件的奇怪:语法而不是--)来破坏它。

把它放在一起:

git checkout -b newbranch  # I'm on a dev branch already; make a new one
git rebase -i master       # only the commits not part of master
...mark one with `edit` or `e`...

git tag tmp    
git reset HEAD^            # changes index only, as if we had just edited
...edit myfile, deleting what is to be split into another commit...
git add .
git commit -m "first commit"

git tag tmp2
git checkout tmp -- myfile # get file and index before above edit
git reset tmp2             # ...so need to reset *index* to first commit
                           # 1. index is same as "first commit"
                           # 2. file is same as commit we wanted to split
                           # (the diff is what we deleted above)
git add .
git commit -m "second commit"

git rebase --continue
git tag -d tmp tmp2        # clean up

如果我们使用“git show”,第二次提交会稍微简单一些,因为我们不需要git reset tmp2

git show tmp:myfile > myfile   # clobber file, but not index
git add .
git commit -m "second commit"

很难说这一切发生了什么!检查当前状态的一些方法:

git log -1                 # see HEAD
git diff                   # between files and index
git diff --cached HEAD     # between index and HEAD
git show-ref tmp           # see tag

无论如何,这一切似乎远比在我的编辑器中 *undo*ing 复杂得多,我一开始就这样做了。但我敢打赌,这种更好的理解reset并且checkout会派上用场......

于 2013-06-29T19:49:23.620 回答