【问题标题】:automatically stash save/pop changes on git rebase?在 git rebase 上自动存储保存/弹出更改?
【发布时间】:2015-01-22 20:43:50
【问题描述】:

我的 git 工作流程经常使用 rebase。我总是获取上游更改(我从中分叉的主仓库),然后合并到我的分支,然后 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 config 强制 rebase 存储或将其视为另一个提交以在之后重新应用?还有什么?

【问题讨论】:

  • 为什么要合并然后变基而不是从一开始就变基?
  • @AndrewC 我在工作流程中提到了它,只是因为大多数时候合并会“变基”,因为我只强制 ff ......我可能会删除它,因为它不重要。在示例中,我将其省略了。
  • 在这种情况下,我回应了 Torek 的回应。提交,然后根据需要在事后进行 rebase 和清理。
  • 一个非常相似的问题stackoverflow.com/questions/30208928/…

标签: git git-stash


【解决方案1】:

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

但是,请谨慎使用:最后的存储 成功变基后的应用程序可能会导致不平凡 冲突。

(我还是更喜欢只提交。)

TL;DR 回答:只需提交(稍后取消提交)

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

stash 的特别之处在于它所做的两次提交,或者-u-a,甚至是三个提交,都是以一种不寻常的形式进行的(作为不是真正合并的合并提交),并且没有放在任何分支上(相反,特殊的refs/stash 引用用于保留和查找它们)。

由于它们不在分支上,rebase 不会触及它们,在您的工作流程中,是 git stash pop 将工作树更改带入您的新工作树。但是,如果您在分支上进行自己的(正常)提交,并重新设置并包含该提交,则此正常提交将与任何其他提交一起重新设置。我们稍后会解决最后一个问题;现在,让我们将其绘制为一系列可以(或不)重新定位的提交:

... 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 提交,您的 stash-bag 将挂起提交 *,并且您当前的分支指向提交 *,因此 git rebase 没有工作要做除了向前移动当前的分支指针。在这种情况下,一切都一样,但我假设有一些提交。)

现在你运行git rebase upstream/master。这会将您的提交复制到具有新 ID 和新父 ID 的新提交,以便它们位于最后一个 @ 之上。 stash-bag 没有移动,所以结果如下所示:

... - o - * - A - B - C     [abandoned, except for the stash]
           \          |\
            \         i-w   <-- stash
             \
              @-@-@         <-- upstream/master
                   \
                    A'-B'-C'   <-- HEAD=master

您现在使用git stash pop,它会在工作树更改时恢复 i/w 内容,删除stash 标签(更准确地说,弹出它以便stash@{1},如果它存在,现在是stash , 等等)。这释放了对原始A - B - C 链的最后引用,这意味着我们也不需要i-w 位,这让我们可以更简单地重新绘制它:

... - @            <-- upstream/master
       \
        A'-B'-C'   <-- HEAD=master plus work tree changes

现在让我们画出如果您只执行git commit -a(或不带-a 的git addgit commit)而不是git stash save 来创建实际提交D 会发生什么。你开始:

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

现在你 git rebase upstream/master,将 A 复制到 D 以将它们放在最后一个 @ 的末尾,你有这个:

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

唯一的问题是你有这个不需要的额外提交D(嗯,现在是D'),而不是未提交的工作树更改。但这可以通过git reset 轻松撤消以退回一次提交。我们可以使用--mixed 重置(默认设置)来重新设置索引(暂存区),以便“取消添加”所有文件,或者如果您希望它们保留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 有一个通用规则:进行 lots 的临时提交,以便随时保存您的工作。您以后可以随时重新调整它们。也就是说,而不是这个:

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

如果ABC 是提交* 之上的完美和最终提交(来自其他人或更早发布的内容),请执行以下操作:

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

其中a1A 的初始刺,a2 修复了a1 中的错误,b1 是使b 工作的初步尝试,a3 是从意识到@987654392 @毕竟需要A不同,b2修复了b1中的错误,a4修复了a3更改为a2的错误,b3b1应该的完成了;那么c1C 的初始尝试,b4b1 的另一个修复,c2 是一种改进,等等。

假设在c3 之后,您认为它基本上已经准备好了。现在你运行git rebase -i origin/master 或其他任何东西,将pick 行打乱以使a1a4 有序,b1b4 有序,c1c3 有序,然后让变基运行。然后你修复所有冲突并确保一切仍然正确,然后运行另一个git rebase -i 将所有四个a 版本折叠成A,等等。

当你全部完成后,看起来就像你第一次创建了一个完美的 A(或者可能使用 a4 或其他一些,这取决于你保留哪些提交以及哪些提交你放弃以及你是否重新设置了任何时间戳)。其他人可能不想或不需要看到你的中间工作——尽管你可以保留它,组合提交,如果这有用的话。同时,您永远不需要必须重新设置未提交的内容,因为您只提交了部分内容。

在单行提交文本中为这些提交命名确实有帮助,这将指导您以后的 rebase 工作:

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

等等。

【讨论】:

  • 这是一个很好的答案。但是,如果当我按照您所说的那样进行“肮脏”提交时,我想git push 其中一个并留下其余部分。这可能吗?
  • 推送一个提交的方法是将它放在自己的分支上(复制它,使用git cherry-pick)。假设您已将所有内容临时提交,git checkout -b pushme upstream/master 创建一个命名分支pushme,然后git cherry-pick &lt;commit-identifier&gt; 复制它,然后git push upstream pushme:master 将一个提交从本地分支pushme 推送到上游master,对于实例。推送完成后,您可以将临时链重新定位到新更新的 upstream/master 上(并省略您复制的那个,但如果可以的话,重新定位会自动执行此操作)。
  • @torek 这实际上是一个完美的工作流程,当你宁愿“选择你的提交来推送”时......我肯定会在某个时候使用它。并且一定要指出我为svn 哭泣的朋友对您的评论!这是一个非常好的工作流程,我总是忽略一个太复杂的,但你是对的,它很实用。
【解决方案2】:

一个简单的答案: git rebase -i --autosquash --autostash &lt;tree-ish&gt;

-i = interactively rebase

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


这会……

  • 自动存储您的更改
  • &lt;tree-ish&gt; 交互式地变基
    • 自动定位您的壁球和修正
  • 变基后在工作目录中自动弹出存储

tree-ish 可以是提交哈希分支名称标签any identifier .

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      您可以使用名为git-up 的外部工具,它完全按照您对所有分支的要求执行。这也将帮助您保持干净的历史图表。

      我已经使用了几年,效果很好,尤其是如果您不是 git 专家。如果您添加自动变基功能,您应该知道如何从失败的变基中正确恢复(继续,中止,...)

      安装

      打开一个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
      

      【讨论】:

      • Git 本身使用了大量的魔法,这就是为什么命令被分成瓷器和管道的原因。 git-up 仅在一系列“安全”瓷器命令中执行。如果发生任何“坏事”,您最终会陷入变基冲突合并场景:一个简单的git rebase --abort 解决所有问题。随着您对 git 的信心和控制力逐渐增强,您会逐渐发现该脚本毫无用处。
      【解决方案5】:

      tjsingleton blogg的回答

      创建一个命令别名:

      git stash && git pull --rebase && git stash pop

      更新

      如果你使用idea,用一个脏的工作目录推送,它会提示一个对话框,选择rebase / merge,它会自动做stash、rebase / merge和pop。

      【讨论】:

        猜你喜欢
        • 2021-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-18
        • 2016-04-05
        • 1970-01-01
        • 2022-12-23
        • 2020-09-23
        相关资源
        最近更新 更多