自动存储git rebase上的保存/弹出更改?

时间:2014-11-25 00:17:13

标签: git git-stash

我的git工作流程经常使用rebase。我总是获取上游更改(我分叉的主要仓库)然后合并到我的分支,然后重新绑定以删除无用(对我来说:D)合并提交和树分割。

这个工作流程中有一件让我烦恼的事情是:

$ git rebase upstream/master
Cannot rebase: You have unstaged changes.
Please commit or stash them.

$ git stash
Saved working directory and index state WIP on cc: abc1234 Merge remote-tracking branch 'upstream/master' into local_branch
HEAD is now at abc1234 Merge remote-tracking branch 'upstream/master' into local_branch

$ git rebase upstream/master
First, rewinding head to replay your work on top of it...
Applying: awesome code change

$ git stash pop

所以这里我们有4个命令,1 =失败的rebase,2 = stash,3 = rebase,4 = stash pop。除了3之外的任何东西都是无意识的工作。

所以,问题是:最推荐的自动化方法是什么?每次都运行git stash / rebase / pop的别名?一些git配置强制rebase隐藏或将其视为另一个提交后再重新应用?别的什么?

5 个答案:

答案 0 :(得分:36)

编辑:从Git版本1.8.4开始,但在Git版本2.0.1中修复了一个重要的漏洞,git rebase现在有--autostash。您也可以将git rebase配置为默认使用--autostashgit config --global rebase.autoStash true。请注意the documentation中的以下句子:

  

然而,谨慎使用:最后藏匿    成功改组后的申请可能会导致非平凡    冲突。

(我仍然喜欢提交。)

TL; DR回答:只提交一个提交(然后再取消它)

它可能会帮助您意识到git stash实际上只是git commit(以更复杂的形式,首先提交索引,然后是工作树 - 当您应用存储时,您可以维护索引和工作树的分离,或者将它们组合成工作树的变化。)

什么使存储特别之处在于它所做的提交 - 两个或者-u-a,甚至三个提交 - 以不寻常的形式制作(作为合并提交,#39) ; s不是真正的合并)并且没有放在任何分支上(相反,特殊的refs/stash引用用于保留和查找它们。)

由于他们不在分支机构,rebase不会触及它们,而在您的工作流程中,git stash pop将工作树更改带入您的新工作区工作树。但是,如果您在分支上进行自己的(正常)提交,并且使用rebase并包含该提交,则此正常提交将与其他提交一起进行重新设置。我们马上就会遇到最后一个问题;现在,让我们来做这件事,因为一系列承诺(或者不会)得到重新定位:

... do some work ...
... make some commits ...
... more work ...
... do something that causes upstream/master to update, such as git fetch upstream
$ git stash

此时,您拥有的是:

... - o - * - A - B - C     <-- HEAD=master
           \          |\
            \         i-w   <-- stash
             \
              @-@-@         <-- upstream/master

此处,ABC是您的提交(我假设您已成为3),所有这些都在分支master上。 i-w挂起提交C是您的存储,它不在分支上,但仍然是双提交"git stash bag"并且实际上附加到您的最新提交(C })。 @提交(可能只有一个)是新的上游提交。

(如果您已提交 no 提交,则您的存储包会挂起提交*,并且您当前的分支指向提交*,以便{{1}除了向前移动你当前的分支指针之外没有任何工作要做。在这种情况下,一切都是一样的,但我会假设有一些提交。)

现在你运行git rebase。这会将您的提交复制到新的提交,使用新的ID和新的父ID,以便它们位于最后git rebase upstream/master的顶部。存储袋不会移动,因此结果如下:

@

现在使用... - o - * - A - B - C [abandoned, except for the stash] \ |\ \ i-w <-- stash \ @-@-@ <-- upstream/master \ A'-B'-C' <-- HEAD=master ,它将i / w内容恢复为工作树更改,删除git stash pop标签(更准确地说,弹出它以便stash,如果它存在,现在是stash@{1},依此类推)。这会释放对原始stash链的最后引用,这意味着我们也不需要A - B - C位,这样我们就可以将其重绘为更简单的方法:

i-w

现在让我们了解一下,如果您... - @ <-- upstream/master \ A'-B'-C' <-- HEAD=master plus work tree changes (或git stash savegit commit -a没有-a)创建git add,而不是git commit,那么会发生什么?实际提交D。你从:

开始
... - o-*-A-B-C-D   <-- HEAD=master
         \
          @-@-@     <-- upstream/master

现在你git rebase upstream/master复制了AD,将它们放在最后@的末尾,你有了这个:

... - o-*-@-@-@     <-- upstream/master
               \
                A'-B'-C'-D'   <-- HEAD=master

唯一的问题是你有一个不需要的额外提交D(现在,D'),而不是未提交的工作树更改。但是这很简单,git reset撤回一个提交。我们可以使用--mixed重置 - 默认 - 来重新设置索引(暂存区域),以便&#34;取消添加&#34;所有文件,或者如果您希望它们保持git add - ed,--soft重置。 (两者都不影响生成的提交图,只有索引状态不同。)

git reset --mixed HEAD^   # or leave out `--mixed` since it's the default

以下是这样的:

... - o-*-@-@-@     <-- upstream/master
               \
                A'-B'-C'      <-- HEAD=master
                        \
                         D'   [abandoned]

您可能认为这样做效率很低,但是当您使用git stash时,您实际上至少会进行两次提交,稍后当您git stash pop时放弃这些提交他们。真正的区别在于,通过临时,非发布提交,您可以自动重新生成。

不要害怕临时提交

git的一般规则是:make lot 临时提交,以便随时保存您的工作。你可以随时将它们改掉。也就是说,而不是:

... - * - A - B - C   <-- mybranch

其中ABC是提交*(来自其他人或之前发布的内容)的最佳提交,请执行以下操作:

... - * - a1 - a2 - b1 - a3 - b2 - a4 - b3 - c1 - b4 - c2 - c3

其中a1A的初始攻击,a2修复a1中的错误,b1是初次尝试b } {},a3意识到b1要求A完全不同,b2修复了b1中的错误,a4修正了a3中的错误更改为a2,而b3b1应该做的错误;然后c1C的初始尝试,b4b1的另一个修正,c2是一个改进,依此类推。

让我们说c3之后你认为它已经准备好了。现在您运行git rebase -i origin/master或其他任何内容,随机播放pick行,以便a1通过a4订购,b1b4按顺序排列, c1c3按顺序排列,让rebase运行。然后,您修复任何冲突并确保内容仍然正确,然后再运行另一个git rebase -i将所有四个a版本折叠为A,依此类推。

当你完成所有操作后,看起来就像你第一次创建一个完美的A一样(或者可能是a4或其他一个,具体取决于哪个承诺你保留,你丢下哪些,以及你是否重新设置任何时间戳记。其他人可能不希望或不需要看到您的中间工作 - 尽管您可以保留它,组合提交,如果它有用。与此同时,你永远不需要有必须重新定义的未提交的东西,因为你只是提交部分内容。

在单行提交文本中提供这些提交名称有助于指导您以后的rebase工作:

git commit -m 'temp commit: work to enable frabulator, incomplete'

等等。

答案 1 :(得分:16)

一个简单的答案: git rebase -i --autosquash --autostash <tree-ish>

-i = interactively rebase

https://devdocs.io/git/git-rebase

这将...

  • 自动存储您的更改
  • <tree-ish> 进行互动式转换
    • 自动定位南瓜和固定装置
  • 在rebase
  • 之后自动弹出存储在工作目录中

tree-ish 可以是提交哈希分支名称标记或{ {3}}

答案 2 :(得分:2)

您可以使用名为git-up的外部工具,它完全按照您对所有分支的说法执行操作。这也有助于您保持干净的历史记录图表。

我已经使用了几年而且效果很好,特别是如果你不是git专家的话。如果您添加自动重新定位功能,您应该知道如何从失败的rebase中正确恢复(继续,中止,...)

安装

打开一个shell并运行:

sudo gem install git-up

配置

打开您的全局配置文件(~/.gitconfig),然后添加以下内容:

[git-up "fetch"]
    all = true    # up all branches at once, default false
    prune = true  # prune deleted remote branches, default false
[git-up "rebase"]
    # recreate merges while rebasing, default: unset
    arguments = --preserve-merges
    # uncomment the following to show changed commit on rebase
    # log-hook = "git --no-pager log --oneline --color --decorate $1..$2"

有关更多选项,请参阅official documentation

调用

如果配置完好,只需运行:

git up

这(大致)等同于执行以下内容:

git stash
git fetch --all
[foreach branch]
    git rebase --preserve-merges <branch> <remote>/<branch>
    git merge --ff-only <branch>
[end foreach]
git checkout <prev_branch>
git stash pop

答案 3 :(得分:2)

一个命令中的整个工作流程,包括提取:

git pull --rebase --autostash [...]

答案 4 :(得分:1)

来自tjsingleton blogg

的回答

命名别名:

  

git stash&amp;&amp; git pull --rebase&amp;&amp; git stash pop

<强>更新

如果您正在使用想法,使用脏工作目录推送,它将提示一个对话框,选择rebase / merge,它将进行存储,rebase / merge并自动弹出。