更改当前分支并保留本地更改

时间:2019-03-29 00:30:54

标签: git

我在分支a上。我想在分支b上进行提交,以便克隆分支b的人具有与我现在相同的工作目录。

存储当前更改无效,因为在某些情况下这会导致冲突。

我正在寻找相当于创建工作目录的临时副本,调用git checkout -f b,删除所有文件,将临时目录复制到项目目录并进行提交的等效操作。

3 个答案:

答案 0 :(得分:1)

基本上任何提交 都是“工作目录的临时副本”。

例如,当您git show <commit>时,快照所指的就是您所指的“之间的变化”。

为什么直接回答您的问题有点困难。让我们尝试以下两种可能的路径:


具有分支b的历史记录重写

如果您此时想在分支b上进行一次提交,以反映分支a上的确切状态,为什么不直接将分支b指向正确位置。像这样:

# get the uncommited changes on the branch
git commit -am "Useful message"

# point b where a is now
git branch -f b a

# instead of forcing the branch we could merge with a strategy ours
# but it's hard to tell what you need from your description alone

# reset a to its previous state
git reset --hard HEAD^

初始状态:

C1---C2 <<< a <<< HEAD # with a dirty tree, uncommited changes

      ? <<< b # (you didn't mention where b was pointing)

之后的结果

C1---C2 <<< a <<< HEAD
     \
      C3 <<< b # where your last (previously uncommited) changes are

在重写分支的历史记录时,应仔细考虑它,并可能排除是否共享了该分支。尽管如此,它仍然可以满足您的要求:“克隆分支b的人与我现在拥有的工作目录相同。”


没有分支b的重写历史记录

为避免重写分支b的历史记录,一种替代方法是使用一种策略将a合并为b,该策略将从a的角度考虑所有内容,而不仅仅是冲突的部分。它会像这样:

# we work on a copy of branch a
git checkout -b a-copy a

# get the uncommited changes on the branch
git commit -am "Useful message"

# proceed with the merge, taking nothing from b
git merge -s ours b

# we now reflect the merge on b, and this is a fast-forward
git checkout b
git merge a-copy

# no further need for the working copy of a
git branch -D a-copy

a不需要重置,因为它在任何时候都没有移动。

第一次提交后:

C1---C2 <<< a
     \
      C3 <<< a-copy <<< HEAD

      o <<< b (anywhere in the tree)

第一次合并后:

C1---C2 <<< a
     \
      C3 (same state as a, plus the previously uncommited changes)
       \
        C4 <<< a-copy <<< HEAD (the merge taking only C3 side)
       /
      o <<< b (anywhere in the tree)

结束状态:

C1---C2 <<< a
     \
      C3 (same state as a, plus the previously uncommited changes)
       \
        C4 <<< b <<< HEAD
       /
      o

答案 1 :(得分:1)

实际上,git stash 确实保存当前的工作树。问题是保存它的 way 并不直接适合您的需求。还有一个次要问题,可能是一个非常棘手的问题,但反而可能很小。请参阅下面的警告。但是最后,您 也许可以使用git stash做您想要的事情。

知道什么

首先,请记住,Git根本不从您的工作树(您的工作目录)中进行提交,并且提交是所有文件的完整快照。它们是根据 index 而不是工作树创建的快照。新提交中的文件是现在索引为 的文件。

(还要记住,索引是Git的其他部分称为暂存区或有时称为 cache 的东西。它保存了每个文件的一个副本,该副本将进入 next 提交。该副本最初是从 current 提交获取的副本,除非Checkout another branch when there are uncommitted changes on the current branch中提到的某些极端情况。)

如果您的工作树与索引不同,并且您想对您的工作树进行快照,则需要git add每个文件,然后覆盖索引中的 副本。可以提交。当然,这会破坏您在索引中 所做的任何仔细的分阶段。

但这就是git stash真正使两次提交的原因:

  • 一次提交会将您当前的索引状态保存为不在任何分支上的新提交。现在可以安全地破坏索引状态。
  • 第二个提交将您当前的工作树保存为具有两个父级的提交:索引提交和当前提交。为了获取所做的提交,Git使用工作树变体替换索引中的所有文件(因为Git从索引而不是从工作树进行提交)。 1

(实际上存在第三个可选提交,用于保存未跟踪或未跟踪加忽略的文件。如果存在该提交,则它是工作树提交的第三个父级。但是,通常它不存在。)

已经进行了这两次提交,git stash更新了refs/stash以记住工作树提交w的哈希ID。该提交会记住索引提交i的哈希ID以及当前提交的哈希ID:

...--o--o--T   <-- your-branch (HEAD)
 \         |\
  \        i-w   <-- refs/stash
   \
    o--A   <-- b

然后git stash运行git reset --hard,以便您的索引和工作树返回到匹配的提交T。正如其他分支A所指出的,我突出了另一个提交b


1 从技术上讲,git stash使用第二个辅助/临时索引进行提交w,以防万一出错。在这种情况下,它可以放弃临时索引。不过,使索引提交i非常容易,因为管道命令git write-tree完成了所有工作。


利用Git的隐藏提交

请记住这里的警告:git stash实际上只是对索引中已经的所有文件执行git add。任何未跟踪的文件(包括所有未跟踪和忽略的文件)根本不在提交w中。他们只是坐在您的工作树上。确实是即使,如果您做了git checkout A来提交A,其中有些文件将被复制到您的索引中。 (当然,在这种情况下,您通常会抱怨Git首先需要覆盖一些未跟踪的文件。)

无论如何,除了这一重大警告外,隐藏提交w在其快照中具有您刚刚要提交的快照{{1 }}。

现在,此快照已存在,您可以告诉Git进行新的提交A,该提交以B作为父节点,并以A tree 作为其快照。这需要一个Git管道命令:

w

也就是说,我们使用名称git commit-tree -p refs/heads/b refs/stash^{tree} (分支refs/heads/b,指向提交b)来告诉Git parent 哈希ID应该是什么我们的新承诺。我们使用A来告诉Git新提交的 tree (快照)应该是什么。 Git读取标准输入以收集日志消息-如果愿意,可以添加refs/stash^{tree}-m <message>来提供消息,或向标准输入发送一条消息:

-F <file>

结果是:

echo some message | git commit-tree -p refs/heads/b refs/stash^{tree}

其中新提交...--o--o--T <-- your-branch (HEAD) \ |\ \ i-w <-- refs/stash \ o--A <-- b \ B 与隐藏提交B具有相同的快照。

w命令打印新提交的哈希ID。您需要抓住它(也许将其捕获到一个shell变量中),然后很可能设置一些名称(例如git commit-tree)来记住此提交。例如:

refs/heads/b

给予:

hash=$(git commit-tree -p refs/heads/b refs/stash^{tree})
git update-ref -m "add stashed work-tree commit" refs/heads/b $hash

也就是说,新提交...--o--o--T <-- your-branch (HEAD) \ |\ \ i-w <-- refs/stash \ o--A--B <-- b 现在是现有分支b的技巧。 b中的快照B中的快照;它们是自动共享的。 w中的 log消息与您给B的内容相同。 git commit-tree哈希ID 现在存储在B中,并且b的父代是B,因此此新提交在分支上A,随心所欲。

现在所有 操作都已完成,您将想要恢复b删除的索引和工作树,但首先将它们保存在这两个提交中。为此,请使用git stashgit stash pop --index很重要:它将当前索引与--index进行比较,并使用差异来还原索引。 2 然后将当前工作树与{{1} },并使用差异从i恢复您的工作树。然后,其中的w部分将丢弃w个提交,如果还有其他隐藏的提交,则使pop记住正确的提交。

因此,忽略所有可能发生错误的地方并进行所有适当的错误检查,以下命令序列可能会根据您的实际情况执行所需的操作你想要吗?

i-w

这是完全未经测试的(并且有一些错误的失败模式,特别是refs/stash表示没有任何可保存的内容,并且拒绝执行任何操作)。


2 这是直接将git stash push # and make sure it does something hash=$(echo automatic commit of work-tree | git commit-tree -p refs/heads/b refs/stash^{tree}) git update-ref -m "add stashed work-tree commit" refs/heads/b $hash git stash pop --index 读入索引的一种低效方式,但它实现了相同的目标。 git stash push步骤也是如此。

答案 2 :(得分:1)

git reset --soft(如果您的朋友)。如果希望B之后的修订成为您在工作树上的方式(到目前为止尚未提交),可以执行以下操作:

git checkout --detach # disconnect from A
git reset --soft b # set the branch pointer to whatever revision B is pointing to.... Your working tree will be unaffected
git add . # add everything to index
git commit -m "revision after B that made it look the way I had it where I was working"
# if you like everything, move b branch to this revision and push
git branch -f b
git checkout b
git push some-remote b

那应该做。