Git 复位的实际用途——软?

我和 Git 一起工作才一个多月。事实上我昨天才第一次使用重置,但是软重置对我来说仍然没有多大意义。

我知道我可以使用软重置来编辑提交而不改变索引或工作目录,就像我使用 git commit --amend一样。

这两个命令是否真的相同(reset --softcommit --amend) ?有什么理由在实际操作中使用其中一种方法吗?更重要的是,除了修改提交之外,reset --soft还有其他用途吗?

345737 次浏览

可以使用 git reset --soft更改索引和工作树中的更改的父版本。这种情况很少见。有时,您可能会决定您的工作树中的更改应该属于不同的分支。或者您可以使用这种简单的方法将多个提交压缩为一个(类似于 squash/fold)。

VonC 给出了一个实际的例子: 在 Git 中压缩前两个提交?

git reset就是移动 HEAD一般而言,「税务易」分行
问: 工作树和索引怎么样?
--soft移动 HEAD,最常见的是更新分支参考,而且只更新 HEAD一起使用时。
这与 commit --amend的不同之处在于:

  • 它不会创建新的提交。
  • 它实际上可以将 HEAD 移动到任何提交(因为 commit --amend只是关于 没有移动 HEAD,同时允许重做当前提交)

刚刚发现了这个结合的例子:

  • 典型的合并
  • 子树合并

所有成为一个(章鱼,因为有两个以上的分支合并)提交合并。

Tomas“ were Hamster”Carnecky 在他的 “子树八达通合并”文章中解释道:

  • 如果希望将一个项目合并到另一个项目的子目录中,并随后使子项目保持最新,则可以使用子树合并策略。它是 git 子模块的一种替代方案。
  • 章鱼合并策略可以用来合并三个或三个以上的分支。通常的策略只能合并两个分支,如果你试图合并更多的分支,git 就会自动退回到章鱼策略。

问题是你只能选择一种策略。但是我想把这两者结合起来,以获得一个清晰的历史记录,在这个历史记录中,整个存储库被原子地更新为一个新版本。

我有一个超级项目,我们称之为 projectA,还有一个子项目,projectB,我将它合并到 projectA的一个子目录中。

(这是子树合并部分)

我还维护了一些本地提交。
ProjectA是定期更新,projectB有一个新的版本每两天或几周,通常取决于一个特定的版本的 projectA

当我决定更新这两个项目,我不只是从 projectAprojectB 因为这将为整个项目的原子级更新创建两个提交拉。
取而代之的是 我创建了一个单独的合并提交,它结合了 ABC0、 projectB和我的本地提交
棘手的部分是这是一个章鱼合并(三个头) ,但是 projectB需要与子树策略合并。所以这是我要做的:

# Merge projectA with the default strategy:
git merge projectA/master


# Merge projectB with the subtree strategy:
git merge -s subtree projectB/master

在这里,作者使用 reset --hard,然后使用 read-tree来恢复前两次合并对工作树和索引所做的操作,但这正是 reset --soft可以提供帮助的地方:
如何重做这两个已经工作的合并 ,也就是说,我的工作树和索引都很好,但是不需要记录这两个提交?

# Move the HEAD, and just the HEAD, two commits back!
git reset --soft HEAD@{2}

现在,我们可以继续 Tomas 的解决方案:

# Pretend that we just did an octopus merge with three heads:
echo $(git rev-parse projectA/master) > .git/MERGE_HEAD
echo $(git rev-parse projectB/master) >> .git/MERGE_HEAD


# And finally do the commit:
git commit

因此,每一次:

  • 你对你最终得到的东西感到满意(就工作树和索引而言)
  • 你对所有带你到达目的地的提交都感到满意:

git reset --soft就是答案。

我用它来修正的不仅仅是 最后提交。

假设我在 A 中犯了一个错误,然后又犯了 B。现在我只能修改 B。 所以我做 git reset --soft HEAD^^,我更正并重新提交 A,然后重新提交 B。

当然,对于大型提交来说并不是很方便... ... 但是你通常不应该进行大型提交。

使用“ git reset --soft <sha1>”的一个很好的理由是在一个裸回购中移动 HEAD

如果您尝试使用 --mixed--hard选项,您将得到一个错误,因为您试图修改和工作树和/或索引不存在。

注意: 您将需要直接从回购。

再次注意: 您需要确保您想要重置的空回购分支是活动分支。如果没有,请遵循 VonC 的 回答,了解当您可以直接访问回购时,如何更新裸回购中的活动分支。

一种可能的用法是,当您想在不同的机器上继续工作时。工作原理是这样的:

  1. 检查一个新的分支机构,其名称类似于“隐藏”,

    git checkout -b <branchname>_stash
    
  2. Push your stash branch up,

    git push -u origin <branchname>_stash
    
  3. Switch to your other machine.

  4. Pull down both your stash and existing branches,

    git checkout <branchname>_stash; git checkout <branchname>
    
  5. You should be on your existing branch now. Merge in the changes from the stash branch,

    git merge <branchname>_stash
    
  6. Soft reset your existing branch to 1 before your merge,

    git reset --soft HEAD^
    
  7. Remove your stash branch,

    git branch -d <branchname>_stash
    
  8. Also remove your stash branch from origin,

    git push origin :<branchname>_stash
    
  9. Continue working with your changes as if you had stashed them normally.

I think, in the future, GitHub and co. should offer this "remote stash" functionality in fewer steps.

SourceTree 是一个 git GUI,它有一个非常方便的界面,可以按照您的需要进行分段。它没有任何远程类似的修改适当的修订。

所以在这个场景中,git reset --soft HEAD~1commit --amend有用得多。我可以撤消提交,将所有更改返回到暂存区域,并使用 SourceTree 继续调整暂存位。

实际上,在我看来,commit --amend是这两个命令中较为冗余的命令,但 git 是 git,并且不会回避执行略有不同的类似命令。

用例-组合一系列本地提交

“哎呀,这三次提交可能只有一次”

因此,撤消最后3次(或其他)提交(不影响索引和工作目录)。然后一起提交所有更改。

例如。

> git add -A; git commit -m "Start here."
> git add -A; git commit -m "One"
> git add -A; git commit -m "Two"
> git add -A' git commit -m "Three"
> git log --oneline --graph -4 --decorate


> * da883dc (HEAD, master) Three
> * 92d3eb7 Two
> * c6e82d3 One
> * e1e8042 Start here.


> git reset --soft HEAD~3
> git log --oneline --graph -1 --decorate


> * e1e8042 Start here.

现在,您的所有更改都被保留并准备作为一个整体提交。

简短回答你的问题

这两个命令是否真的相同(reset --softcommit --amend) ?

  • 没有。

有什么理由在实际操作中使用其中一种方法吗?

  • 从最后一次提交中添加/rm 文件或更改其消息。
  • reset --soft <commit>将多个连续提交合并为一个新提交。

更重要的是,除了修改提交之外,reset --soft还有其他用途吗?

  • 查看其他答案:)

另一个潜在的用途是作为一种替代存储(有些人不喜欢,见例如 https://codingkilledthecat.wordpress.com/2012/04/27/git-stash-pop-considered-harmful/)。

例如,如果我正在处理一个分支,并且需要在 master 上紧急修复一些东西,我可以这样做:

git commit -am "In progress."

然后结帐主人和做的修复。当我完成后,我回到我的分行和做

git reset --soft HEAD~1

继续我的工作。

一个实际的用途是,如果您已经承诺您的本地回购(即。Git commit-m)然后可以通过执行 基特复位-软头部 ~ 1来逆转最后一次提交

如果您已经分阶段进行了更改(即使用 git add) ,也要告诉您然后你可以通过做 基特复位-混合头逆转分期,或者我通常也只是使用 git reset

最后,基特复位,很难删除所有内容,包括本地更改。后面的头部告诉你从顶部提交了多少。

虽然我非常喜欢这个线程中的答案,但是我使用 git reset --soft作为一个稍微不同的,但是非常实际的场景。

我使用一个 IDE 进行开发,它有一个很好的 diff 工具来显示我上次提交后的更改(分阶段和非分阶段)。现在,我的大多数任务都涉及到多次提交。例如,假设我提交了5次来完成一个特定的任务。我使用 IDE 中的 diff 工具在从1到5的每次增量提交期间查看上次提交后的更改。我发现在提交之前检查我的更改是一种非常有用的方法。

但是在我的任务结束时,当我想看到我所有的变化(从第一次提交之前) ,做一个自我代码审查之前提请请求,我只会看到从我以前的提交(提交4后)的变化,而不是从我当前任务的所有提交的变化。

所以我使用 git reset --soft HEAD~4返回4次提交。这样我就可以一起看到所有的更改。当我对我的改变有信心时,我就可以自信地做 git reset HEAD@{1}并把它推到远程。

另一个用例是,当您想在一个拉请求中用您的分支替换另一个分支时,例如,假设您有一个在开发中具有特性 A、 B、 C 的软件。

您正在使用下一个版本进行开发,并且您:

  • 删除特征 B

  • 增加了特性 D

在这个过程中,为特性 B 开发刚刚添加的修补程序。

您可以合并开发到下一个,但是有时可能会很混乱,但是您也可以使用 git reset --soft origin/develop并创建一个包含您的更改的提交,而且分支可以合并,没有冲突并保持您的更改。

事实证明,git reset --soft是一个方便的命令。我个人经常使用它来压缩那些没有“完成工作”的提交,比如“ WIP”,所以当我打开拉请求时,我所有的提交都是可以理解的。

我通常同时使用 git reset --softgit commit --amend来组合提交。 例如,如果我有一个提交历史记录,比如:

master: A--B--C

此时,我只想保留提交 A并删除提交 BC,但仍然保留更改。第一:

git reset --soft <commit A's hash>

因为选项 --soft只是将您带回到一个特定的提交,并且仍然保留更改。现在可以将这些更改应用于提交 A:

git commit --amend

或者也可以重命名提交:

git commit --amend -m 'your new commit message'

之后,如果您想将其推送到远程存储库,则必须使用:

git push -f origin master

请注意,看起来好像你“回到”了你的旧提交,但实际上,修改后的提交是一个新的提交,并且已经取代了旧的提交 A(你可以使用 git log检查它的 hash id) ,但是最后,你仍然有一个很好的提交历史,就像你想的那样。