自动隐藏保存/流行更改在git rebase上? [英] automatically stash save/pop changes on git rebase?

查看:139
本文介绍了自动隐藏保存/流行更改在git rebase上?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的git工作流很大程度上使用了rebase。我总是获取上游变更(我从中分出的主要回购),然后合并到我的分支,然后重新分配以除去无用的(对我来说:D)合并提交和树分裂。





  $ git rebase upstream / master 
无法重新分配:你有未分离的变化。
请提交或储存它们。

$ git stash
保存的工作目录和索引状态WIP on cc:abc1234将远程跟踪分支'upstream / master'合并到local_branch
HEAD现在位于abc1234合并remote-跟踪分支'上游/主'到local_branch

$ git rebase upstream / master
首先,倒带头重播你的作品...
应用:真棒代码改变

$ git stash pop

所以在这里我们有4个命令,1 =失败的rebase,2 =隐藏,3 = rebase,4 =隐藏pop。除了3以外的任何东西都是没有意义的工作。



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

编辑:自Git版本1.8.4开始,但在Git版本2.0中修复了一个重要的侧面错误。 1, git rebase 现在有 - autostash 。你可以配置 git rebase 来默认使用 - autostash ,同时使用 git config --global rebase.autoStash true 。请注意文档中的以下句子:
$ b


然而,谨慎使用:成功重新绑定后最终隐藏的
应用程序可能会导致不重要的
冲突。


(我仍然只是提交提交。)

TL; DR答案:只是做一个提交(然后取消它)



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

什么使得存储特别的是它所做的提交 - 使用 -u -a ,即使是三次提交,都会以一种不寻常的形式出现(作为合并提交并不是放在任何分支上(而是使用特殊的 refs / stash 引用来保留并找到它们)。

由于它们不在分支上,因此 rebase 不会触及它们,而在您的工作流程中,它是 git隐藏弹出窗口,将工作树更改导入您的新工作树。但是,如果您在分支上进行自己的(正常)提交并重新绑定并包含该提交,则此正常提交将与其他任何提交一起重新分配。我们马上就会遇到最后一个问题;现在,让我们来画一下,作为一系列提交(或不提交)的提交:

。做一些工作...
...做一些提交...
...更多工作...
...做一些导致上游/主更新的事情,例如作为git fetch上游
$ git存储

此时,以下是您拥有的内容:

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

这里, A B C 是你的提交(我假设你已经创建了3),所有分支 master iw 挂起提交 C 是您的存储,它不在分支上,但仍然是双提交git stash bag,实际上已附加到您的最新提交( C )。 @ 提交(可能只有一个)是新的上游提交。



(如果您制作了 no 提交,你的存储包挂起提交 * ,并且您当前的分支指向提交 * ,所以 git rebase 除了向前移动当前分支指针之外没有其他工作要做,在这种情况下,所有的工作都是一样的,但我会假设)



现在运行 git rebase upstream / master 。这会将您的提交复制到新的提交中,包含新的ID和新的父ID,以便它们位于最后的 @ 之上。藏匿袋不会移动,所以结果如下所示:

  ...  -  o  -  *  -  A  -  B -  C [放弃,除了藏匿处] 
\ | \
\ iw < - 藏匿
\
@ - @ - @< - 上游/主
$
A'-B'-C'< - HEAD =主

您现在使用 git stash pop ,它将I / W资料恢复为工作树更改,擦除存储 label(更确切地说,弹出它以使 stash @ {1} ,如果存在的话,现在是 stash 等等)。这将释放对原始 A - B - C 链的最后一个引用,并且意味着我们不需要 iw 这可以让我们重新绘制这个简单得多的:

  ...  -  @<  -  upstream / master 

A'-B'-C'< - HEAD =主加工作树变更

现在让我们画出如果发生了什么,而不是 git stash save ,那么您只需执行 git commit -a (或 git add git commit 不带-a)来创建一个实际的提交 d 。您从以下开始:

  ...  -  o  -  *  -  ABCD < -  HEAD = master 
\
@ - @ - @< - upstream / master

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

  ...  -   -   -   - 上游/主
\\ $
A'-B'-C'-D'< - HEAD =主

唯一的问题是您有这个不必要的额外提交 D (现在, D'),而不是未提交的工作树更改。但是用 git reset 来简单地撤销一个提交。我们可以使用 - mixed reset-default来重新设置索引(暂存区域),以便解除所有文件,或者如果你希望他们留在 git add -ed,a - soft reset。 (不会影响生成的提交图,只有索引状态是不同的。) $ b

  git reset  - -mixed HEAD ^#或者省略`--mixed`,因为它是默认的

这是看起来的样子例如:

  ...  -  o  -  *  -  @  -  @  -  @<  - 上游/主
\
A'-B'-C'< - HEAD =主
$
D'[弃用]

您可能认为这样做效率低下,但是当您使用 git stash 时,您实际上至少使两个提交,稍后当您 git stash pop 它们时,您将放弃这些提交。真正的不同之处在于,通过临时的非发布提交,你可以自动重新发布。



不要担心临时提交



git有一个通用规则:make lot 临时提交,以随时保存工作。你以后可以随时将它们重新绑定。也就是说,不是这样:

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

其中 A B C 是完美且最终提交的顶层提交 * (来自其他人或以前发布的你可以这样做:

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

其中 a1 是在 A a2 中的一个初始戳记,修复了 a1 b1 是最初尝试使 b 工作, a3 意识到 b1 需要 A 毕竟是不同的, b2 修正了 b1 a4 中的错误修正了 a3 的更改为 a2 b3 b1 应该完成;那么 c1 是初始尝试 C b4 是另一个修正为 b1 c2 是一种改进,依此类推。



假设在 c3 之后,您认为它已经基本就绪。现在你运行 git rebase -i origin / master 或其他任何东西,将 pick 行转换为 a1 a4 顺序, b1 b4 进入顺序,并将 c1 c3 按顺序排列,然后让rebase运行。然后你解决任何冲突并确保东西仍然正确,然后运行另一个 git rebase -i 来折叠所有四个 a 版本转换为 A 等等。



当你完成所有工作后, 就像你第一次创建了完美的 A (或者也可以用 a4 或者其他的依赖于哪些提交你保留,哪些丢弃以及是否重新设置任何时间戳)。其他人可能不想或不需要看到你的中间工作 - 虽然你可以保留它,但如果这是有用的,那么不是组合提交。与此同时,你永远不需要有未提交的东西,你必须rebase,因为你只是提交部分东西。



它有助于给这些提交名称,在一个-line commit text,它会指导你后来的工作:


$ b

git commit -m'temp提交:启用frabulator的工作,不完整的'

等等。


my git workflow uses rebase a lot. I always fetch upstream changes (the main repo i forked from) and then merge to my branches, and then rebase to remove useless (to me :D) merge commits and tree splits.

one thing on this workflow that annoys me is:

$ 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

so here we have 4 commands, 1=failed rebase, 2=stash, 3=rebase, 4=stash pop. anything but 3 is just mindless work.

So, the question is: What is the most recommended way of automating it? an alias to run git stash/rebase/pop everytime? some git config that forces rebase to stash or treat it as another commit to reapply afterwards? something else?

解决方案

Edit: As of Git version 1.8.4, but with an important side bug fixed in Git version 2.0.1, git rebase now has --autostash. You can configure git rebase to use --autostash by default as well, with git config --global rebase.autoStash true. Please note the following sentence from the documentation:

However, use with care: the final stash application after a successful rebase might result in non-trivial conflicts.

(I still prefer to just make commits.)

TL;DR answer: just make a commit (then unmake it later)

It may help you to realize that git stash is really just git commit (in a more complicated form, which commits the index first, then the work-tree—when you apply a stash, you can maintain the separation of index and work-tree, or combine them into just a work-tree change).

What makes a stash special is that the commits it makes—the two or, with -u or -a, even three commits—are made in an unusual form (as a merge commit that's not really a merge) and not placed on any branch (instead, the special refs/stash reference is used to retain and find them).

Since they're not on a branch, rebase does not touch them, and in your workflow, it's the git stash pop that brings the work-tree changes into your new work-tree. However, if you make your own (normal) commit, on a branch, and rebase and include that commit, this normal commit will be rebased along with any others. We'll get to one last problem in a moment; for now, let's draw this up, as a series of commits that do (or don't) get rebased:

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

At this point, here's what you have:

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

Here, A, B, and C are your commits (I'll assume you've made 3), all on branch master. The i-w hanging off commit C is your stash, which is not on the branch, but is still a two-commit "git stash bag" and is actually attached to your latest commit (C). The @ commits (there might be just one) are the new upstream commits.

(If you have made no commits, your stash-bag hangs off commit *, and your current branch points to commit *, so that git rebase has no work to do other than move your current branch pointer forward. Everything works out the same, in this case, but I'll assume there are some commits.)

Now you run git rebase upstream/master. This copies your commits to new commits, with new IDs and new parent IDs, so that they sit atop the last @. The stash-bag does not move, so the result looks like this:

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

You now use git stash pop, which restores the i/w stuff as work-tree changes, erasing the stash label (more precisely, popping it so that stash@{1}, if it exists, is now stash, and so on). That releases the last references to the original A - B - C chain, and means we don't need the i-w bit either, which lets us redraw this as the much simpler:

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

Now let's draw what happens if, instead of git stash save, you just do a git commit -a (or git add and git commit without -a) to create an actual commit D. You start with:

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

Now you git rebase upstream/master, which copies A through D to place them at the end of the last @, and you have this:

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

The only problem is that you have this one unwanted extra commit D (well, D' now), instead of uncommitted work-tree changes. But this is trivially undone with git reset to step back one commit. We can use a --mixed reset—the default—to get the index (staging area) re-set too, so as to "un-add" all the files, or if you want them to stay git add-ed, a --soft reset. (Neither affects the resulting commit graph, only the index state is different.)

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

Here's what that looks like:

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

You might think this is inefficient, but when you use git stash you're actually making at least two commits, which you then abandon later when you git stash pop them. The real difference is that by making temporary, not-for-publication commits, you get those automatically rebased.

Don't be afraid of temporary commits

There's a general rule with git: make lots of temporary commits, to save your work as you go. You can always rebase them away later. That is, instead of this:

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

where A, B, and C are perfect and final commits atop commit * (from someone else or earlier published stuff), make this:

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

where a1 is an initial stab at A, a2 fixes a bug in a1, b1 is an initial attempt to make b work, a3 is from realizing that b1 requires A to be different after all, b2 fixes a bug in b1, a4 fixes a bug in a3's change to a2, and b3 is what b1 should have done; then c1 is an initial attempt at C, b4 is another fix to b1, c2 is a refinement, and so on.

Let's say that after c3 you think it's mostly ready. Now you run git rebase -i origin/master or whatever, shuffle the pick lines to get a1 through a4 into order, b1 through b4 into order, and c1 through c3 into order, and let the rebase run. Then you fix any conflicts and make sure stuff is still right, then you run another git rebase -i to collapse all four a versions into A, and so on.

When you're all done, it looks like you created a perfect A the first time (or maybe with a4 or some other one depending on which commits you keep and which ones you drop and whether you re-set any time stamps in things). Other people may not want or need to see your intermediate work—though you can retain it, not combining commits, if that's useful. Meanwhile you never need to have uncommitted stuff that you have to rebase, because you just have commits of partial stuff.

It does help to give these commits names, in the one-line commit text, that will guide your later rebase work:

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

and so on.

这篇关于自动隐藏保存/流行更改在git rebase上?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆