git reset --soft的实际用途?


135

我已经与git合作了一个多月。确实,我只是昨天才第一次使用了重置,但是软重置对我来说仍然没有多大意义。

我了解我可以像使用一样使用软重置来编辑提交,而无需更改索引或工作目录git commit --amend

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

Answers:


109

git reset与移动有关HEAD通常是分支ref
问题:工作树和索引如何?
当与使用--soft移动HEAD,最常更新分支ref,将只HEAD
这不同于commit --amend

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

刚刚发现以下结合示例:

  • 经典合并
  • 子树合并

全部合并为一个(章鱼,因为合并了两个以上的分支),提交合并。

Tomas“ wereHamster” Carnecky在他的“ Subtree Octopus merge”文章中解释道

  • 如果要将一个项目合并到另一个项目的子目录中,则可以使用子树合并策略,并随后使该子项目保持最新状态。它是git子模块的替代。
  • 章鱼合并策略可用于合并三个或更多分支。普通策略只能合并两个分支,如果您尝试合并多个分支,git会自动退回到章鱼策略。

问题是您只能选择一种策略。但是我想将两者结合起来以得到一个干净的历史记录,在该历史记录中,整个存储库都被自动更新为新版本。

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

(这是子树合并部分)

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

当我决定更新两个项目时,我不会简单地退出projectAprojectB 因为那样会为整个项目的原子更新创建两个提交
相反,我创建一个单一的合并提交它结合了projectAprojectB和我的本地提交
这里最棘手的部分是这是一个章鱼合并(三个头),但是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 就是答案。


8
自我注意:压榨的简单示例git reset --softstackoverflow.com/questions/6869705/…–
VonC

3
如果您选择了错误的分支,它也很有用。所有更改都返回到暂存区域,并在您检出右分支时与您一起移动。
smoebody 2015年

44

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

“糟糕。这三个承诺可能只是其中之一。”

因此,撤消最后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 --softvs commit --amend)?

  • 没有。

有什么理由在实际中使用其中一个?

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

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

  • 查看其他答案:)

2
参见其他答案,“ reset --soft除了修改提交还有其他用途-否”
Antony Hatchkins,2016年

针对最后一条评论-部分同意,但是我想说修改单个提交太具体了。例如,您可能希望在编写功能后压缩所有内容并制定出对审核者更有用的新提交。我想说,git reset当您(a)想要重写历史记录,(b)不在乎旧的提交(这样可以避免交互式变基的麻烦),并且(c)具有多个更改承诺(否则,commit --amend更简单)。
johncip,

18

我用它来修改的不仅仅是上一次提交。

假设我在提交A时犯了一个错误,然后在提交B中犯了错误。现在我只能修改git reset --soft HEAD^^B。

当然,对于大型提交不是很方便……但是无论如何您都不应该进行大型提交;-)


3
git commit --fixup HEAD^^ git rebase --autosquash HEAD~X效果也不错。
尼古拉斯

3
git rebase --interactive HEAD^^您可以在其中选择编辑提交A和B。以这种方式保留A和B的提交消息,如果需要,您仍然可以对其进行修改。
Paul Pladijs 2011年

2
重置为A后如何重新提交B?
northben

1
另一种可能工作量较少的方法:检查您的git日志,获取B的提交哈希,然后git reset A进行并添加更改git commit --amendgit cherry-pick <B-commit-hash>
naught101

15

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

例如,如果我在分支上工作,并且需要在master上紧急修复某些问题,我可以这样做:

git commit -am "In progress."

然后结帐大师并进行修复。完成后,我返回我的分支并执行

git reset --soft HEAD~1

在我离开的地方继续工作。


3
更新(现在我对git有了更好的了解):--soft在这里实际上是不必要的,除非您真的关心立即进行更改。我现在只git reset HEAD~在执行此操作时使用。如果我有一些变化上演,当我需要切换分支机构,并希望继续保持这种方式,那么我这样做git commit -m "staged changes",然后git commit -am "unstaged changes",再后来git reset HEAD~接着git reset --soft HEAD~完全恢复工作状态。虽然,老实说,我现在所知道的都很少做这两件事git-worktree:)
deltacrux

7

您可以git reset --soft用来更改要作为索引和工作树中的更改的父版本。有用的情况很少见。有时,您可能会决定您在工作树中所做的更改应属于另一个分支。或者,您可以使用此方法作为将多个提交折叠为一个的简单方法(类似于压缩/折叠)。

参见VonC的答案,了解一个实际示例: 在Git中压入前两次提交?


这是我之前从未发现的一个好问题。但是我不确定我是否可以看到如何使用reset soft将更改放入另一个分支。所以我有checkout分支,然后使用git reset --soft anotherBranch然后在那提交?但是您实际上并没有更改ckeckout分支,因此您将提交到Branch还是anotherBranch吗?
2011年

执行此操作时,请不要使用git checkout,因为这会更改树,这一点很重要。将git reset --soft视为仅处理HEAD指向的修订版的一种方式。
约翰内斯·鲁道夫

7

一种可能的用法是当您想在另一台机器上继续工作时。它将像这样工作:

  1. 签出一个具有类似隐藏名称的新分支,

    git checkout -b <branchname>_stash
    
  2. 向上推动藏匿处,

    git push -u origin <branchname>_stash
    
  3. 切换到另一台机器。

  4. 拉下您的藏匿处和现有分支机构,

    git checkout <branchname>_stash; git checkout <branchname>
    
  5. 您现在应该在现有分支中。合并存储分支中的更改,

    git merge <branchname>_stash
    
  6. 在合并之前将现有分支软重置为1,

    git reset --soft HEAD^
    
  7. 删除您的存储分支,

    git branch -d <branchname>_stash
    
  8. 同时从原始位置删除您的隐藏分支,

    git push origin :<branchname>_stash
    
  9. 继续进行更改,就好像您已将它们妥善保存一样。

我认为,将来GitHub和co。应该以更少的步骤提供此“远程隐藏”功能。


2
我想指出的是,完全不需要在第一台计算机上进行第一个存储和弹出操作,您可以直接从脏的工作副本中创建一个新分支,提交,然后将更改推送到远程。

7

一种实际用途是,如果您已经提交了本地存储库(即git commit -m),则可以通过执行git reset --soft HEAD〜1来撤销最后一次提交

同样据您所知,如果您已经上演了更改(即使用git add),则可以通过执行git reset --mixed HEAD来逆转该阶段,或者我也经常使用过git reset

最后,git reset --hard清除所有内容,包括本地更改。〜后头告诉您从顶部开始有多少次提交。


1
git reset --soft HEAD ~1给了我fatal: Cannot do soft reset with paths.我认为我们需要在HEAD之后删除空格,而是git reset --soft HEAD~1
安德鲁·洛尔

6

使用' git reset --soft <sha1>'的一个重要原因是进入HEAD裸仓库。

如果尝试使用--mixedor --hard选项,则将由于尝试修改和工作不存在的树和/或索引而收到错误消息。

注意:您将需要直接从裸仓库执行此操作。

再次注意:您将需要确保要在裸仓库中重置的分支是活动分支。如果没有,请直接访问仓库,按照VonC的答案在裸仓库中更新活动分支。


1
如您先前的答案(stackoverflow.com/questions/4624881/…)所述,当您可以直接访问裸仓库(在此处提到)时,这是正确的。+1。
VonC

@VonC是的,绝对正确,感谢您添加注释!我一直忘记添加,因为我假设重置是直接从仓库中完成的。另外,我假设此人要在裸仓库中重置的分支是其活动分支。如果分支不是其活动分支,则需要根据您的答案(stackoverflow.com/questions/3301956/…)更新有关如何为裸存储库更新活动分支的信息。我还将使用活动的分支信息来更新答案。再次感谢!!!
哈佐

2

SourceTree是一个git GUI,它具有一个非常方便的界面,用于仅暂存所需的位。它没有任何类似的东西可以修改适当的修订版。

因此git reset --soft HEAD~1commit --amend这种情况下有用得多。我可以撤消提交,将所有更改返回到暂存区域,然后继续使用SourceTree调整已暂存的位。

确实,在我看来,这commit --amend是两者中更冗余的命令,但是git是git,并且不会回避执行稍有不同的事情的类似命令。


1

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

我使用IDE进行开发,该IDE具有良好的diff工具,用于显示上次提交后的更改(暂存和未暂存)。现在,我的大部分任务都涉及多个提交。例如,假设我进行了5次提交以完成特定任务。在从1-5进行的每次增量提交期间,我都使用IDE中的diff工具查看从上一次提交所做的更改。我发现这是一种非常有用的方法,可以在提交之前检查我的更改。

但是在我的任务结束时,当我想一起查看所有更改(从第一次提交之前)时,在发出拉取请求之前进行自我代码审查,我只会看到前一次提交(提交之后)的更改。 4)并不会因当前任务的所有提交而改变。

所以我git reset --soft HEAD~4经常返回4次提交。这使我可以一起查看所有更改。当我对自己的更改充满信心后,便可以git reset HEAD@{1}放心地将其推送到远程。


1
......那么做git add --patch,并git commit多次向建承诺,如果你知道你在这样做所有你已经构建系列。您的第一批提交就像您桌上的便笺或备忘录的初稿一样,它们是在组织您的想法,而不是用于发布。
jthill

嘿,我不太明白您的建议。
Swanky Coder

1
只是不必代替git reset @{1}还原第一个草稿系列,而是可以通过git add -p和构建一个for-publication系列git commit
jthill

是,对的。另一种方法是(我通常遵循的一种方法)基于上游/主服务器并将提交压缩为一个。
Swanky Coder

1

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

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

  • 删除了功能B

  • 新增功能D

在此过程中,为功能B开发刚刚添加的修补程序。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.