我可以重新分配Git分支而不修改我的工作副本吗?

假设我有我的“主”分支签出。 我已经对“主人”进行了一些生产更改,现在我想把我的“实验”分支重新分配给最新的主人。 但是,我想这样做,而不修改我的工作副本中的任何文件。 实质上,我希望所有的魔法都发生在.git目录中,而不需要触摸工作副本。

如果不是“不要修改我的工作副本”的要求,这只是一个问题:

# current branch is master git checkout experimental git rebase master git checkout master 

我真正的问题是,这会修改我的工作副本中的时间戳,即使通过检查我开始的完全相同的内容来结束。 只要我运行“git checkout experimental”,任何包含实验分支变化的文件都会将其mtime设置为当前时间 – 自从上次重新设置实验以来,在master中更改的任何文件也一样。 因为这些时间已经改变了,所以像构build工具这样的东西会得到他们需要再做的工作的想法,即使在我完成的时候,这些文件的内容并没有真正改变。 (就我而言,如果项目文件的时间戳发生变化,Visual Studio认为需要花费大量的时间来卸载和重新加载项目。)我想避免这种情况。

是否有办法一步完成上述所有操作,而不需要修改工作副本中的任何内容 (假设在重新分配期间没有冲突)

(如果有冲突的话,我的偏好是显示错误,然后中止整个操作,而不修改任何时间戳,但这只是我的偏好,不是一个硬性要求 – 我不知道什么是可能的。

当然我可以写一个脚本来捕捉m次,运行git,然后重新设置m次; 但似乎Git可能已经有办法像rebase这样的事情而不打扰工作副本,因为rebase实际上是关于增量,而不是文件的实际内容。

是否有办法一步完成上述所有操作,而不需要修改工作副本中的任何内容?

这是不可能的(因为git会在工作树上执行所有merge-y操作(真正的合并,樱桃挑选,rebase,补丁应用程序),而不会创build工作副本的可修改副本 – 另请参见Petr的答案 )。 这在之前被提及了几次,例如在知识JakubNarębski的答复之一 :

如果没有触及工作目录(和索引),合并(或重新分配)就无法工作,因为可能存在需要使用工作目录(和/或索引)解决的合并冲突。

是的,这是一个devise决定,但这是一个非常容易理解的问题 – build立所有必要的结构来尝试在内存中合并将是一件难事,然后一旦发生冲突,将所有内容转储到工作树,而是可以简单地在工作树中完成。 (我不是一个git开发者,不要把它当成绝对完整的事实,可能还有其他的原因。)

我的build议是,不要编写一个脚本来完成所有这些操作,而只需克隆存储库,在克隆中执行rebase,然后将其推回原始存储库:

 git clone project project-for-rebase cd project-for-rebase git branch experimental origin/experimental git rebase master experimental git push origin experimental 

当然,假设您的原始回购中没有签出实验。 如果是这样,而不是推,你会做一些像git fetch ../project-for-rebase experimental; git reset --hard FETCH_HEAD git fetch ../project-for-rebase experimental; git reset --hard FETCH_HEAD或更可读, git remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental git remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental git remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental 。 这自然会触及原始和重新devise的实验分支之间的任何文件,但这绝对是正确的行为。 (当然,这不是你给出的例子,但是我希望这些指令是一般的!)

既然git 2.5 ,更好的解决办法是使用第二个工作树 。

一个git仓库可以支持多个工作树,允许你一次签出多个分支。

 $ git worktree add ../second-copy experimental $ cd ../second-copy/ $ git rebase master experimental 

就是这样。 之后,如果你愿意的话,你可以使用rm -rf second-copy ,或者在将来继续使用更多的rebase。

 $ git rebase master experimental 

我已经创build了一个小脚本在Linux上做到这一点。 这是基于Jefromi的回答和一些补充(主要是设置替代,所以对象数据库不被复制,只拉动所需的分支)。 你们中的一些人可能会觉得很有用: https : //github.com/encukou/bin/blob/master/oot-rebase

如果它没有做你喜欢的,拉请求是欢迎:)

类似于创build你的仓库的克隆,我发现利用多个工作者来完成这样的事情更为简单。 另外一个克隆将使用大量的磁盘空间,这将几乎没有使用。

https://github.com/git/git/blob/master/contrib/workdir/git-new-workdir

你创build一个新的workdir像这样:

 git-new-workdir project-dir new-workdir branch 

那么你可以把它看作是一个克隆,除了原始工作目录中的提交和提交将会在这里被反映出来(尽pipe没有没有重新工作的工作分支)。 唯一的例外是,如果你有子模块,那么默认情况下每个工作目录都是分别完成的。 坦率地说,我从来没有看过,因为我试图避免子模块。

所以基本上只是:

 cd new-workdir git checkout experimental git rebase master 

不完全是一个单一的命令,但很简单。

这种方法(如克隆方法)的一个优点就是,如果您的代码正在从工作目录执行(或者被某些进程使用),则不会中断。


这里没有提到的另一个选项是在你当前的工作目录中执行它,但是保存你的修改,这样你可以立即恢复你的工作目录状态。

 # current branch is master (with changes to working state) git stash -u git checkout experimental git rebase master git checkout master git stash pop 

如果您有任何新文件,请确保使用stash -u ,否则它们将不会被隐藏。 再次,不是一个步骤,而是相当干净和简单。

我也喜欢它,但是这对我没有希望:

如果指定了<branch> ,那么git rebase将在执行任何其他操作之前执行自动git checkout。 否则,它保持在当前分支上。

http://git-scm.com/docs/git-rebase

正如其他人所说,不可能在没有触及工作目录的情况下重build分支(甚至build议的替代方法,例如创build新的克隆或工作树也不能改变这个事实;这些替代方法确实不会触及您当前的工作目录,而只能通过创造一个新的工作树)。

对于要更新的​​分支要在当前工作树(或其父项)上进行重新组织的特殊情况,可以对另一个分支进行“重新绑定”而不会不必要地触及文件。
这种特殊的情况经常发生,如果你有一个git工作stream,你正在从主“master”分支(定期更新到远程主分支)分支的许多分支上工作。

为了说明,假设一个具有以下结构的Git仓库:

 repo - commitA - commitB - commitC <-- master <-- OtherBranch based on master - commitD <-- First commit in otherBranch - commitE <-- Second commit in OtherBranch - commitD <-- Unrelated commit in current working tree 

为了举例,假设“OtherBranch”分支为“master”,并且您当前的工作树也是基于“master”。 您的工作stream程通常以更新远程版本的本地主分支开始。

 # Fetch commits from remote "origin" and update the master branch: # If your current branch is identical to master git pull origin master # If your current branch has extra commits on top of master git pull --rebase origin master # If you don't want to touch your current branch git fetch origin master:master 

…然后你摆弄当前的分支,并做一些耗时的编译。 最后,你决定要在OtherBranch上工作。 这个OtherBranch应该重新设置在master (最好用最less的文件系统操作)。 以下部分将显示如何。

重新启动其他分支(参考示例 – 不要这样做)

下面的解决scheme是git的方式来做到这一点:

 git checkout OtherBranch git rebase master # or git rebase origin/master 

这样做的缺点是,即使第二个命令要恢复文件,第一个命令也会更改当前工作树的date。

以最小的变化重新分配其他分支

为了减less被触摸的文件的数量,你需要签出新的基本分支,然后使用git cherry-pick在基本分支之上的OtherBranch应用所有额外的提交。

在做任何事情之前,您需要确定OtherBranch的提交。

  • git log OtherBranch显示了git log OtherBranch上的提交(主要用于如果还没有更改OtherBranch
  • git reflog显示了对本地存储库中分支的更改(如果你已经更新了分支并且出错了,那么这个function很有用)。

在当前的例子中,你会发现OtherBranch上的最后一个提交是commitE 。 你可以在git log commitE之前看到提交列表(或者你想要一个较短的列表, git log --oneline commitE )。 如果您查看列表,您将看到基本提交是commitC

现在你知道基提交是commitC ,最后一个提交是commitE ,你可以按如下方式将OtherBranch(从它的前一个“主”变成新的“主”)重新绑定:

 # Replace the old OtherBranch with "master" and switch to it. git checkout -B OtherBranch master # Cherry-pick commits starting from commitC and ending at commitE. cherry-pick commitC^..commitE 

或者(如果您想在更换OtherBranch之前成功完成“rebase”):

 # Create new branch NewOtherBranch based off "master" and switch to it. git checkout -b NewOtherBranch master # Cherry-pick commits starting from commitC and ending at commitE. cherry-pick commitC^..commitE # Replace the old branch with the current branch (-M = --move --force) git branch -M OtherBranch 

为什么这个工作?

在git中重新分支分支需要将当前分支切换到你想要更新的分支( OtherBranch )。

使用git rebase工作stream程,会发生以下情况:

  1. 切换到OtherBranch (可能分支了一个非常老的基地分支)。
  2. Rebase(内部步骤1):保存不在上游分支中的提交。
  3. 重置(内部步骤2):将当前分支重置到(新)基本分支。
  4. Rebase(内部步骤3):恢复步骤2中的提交。

步骤1和步骤3触摸很多文件,但最终很多被触摸的文件并没有真正改变。

我的方法将第1步和第3步合并到第3步,结果所触及的文件数量最小。 唯一感兴趣的文件是:

  • 在基本分支和当前工作树中的当前提交之间更改的文件。
  • OtherBranch提交更改的文件。

所以你需要在分支之前为分支做一个rebase? 我真的不明白这个原因,因为如果你不检查那个分支,你就无法工作。 你为什么要重组一个你不工作的分支? 做结账,它会改变你的时间,然后做rebase。 rebase会触及被改变的文件,当然你需要重build它们。

然而,解决这个问题的一个简单的方法是使用其他工作树来进行rebase。 只需将环境variablesGIT_WORK_TREE设置为其他工作树。 只是不要忘了让你的头匹配你的工作树。

根据他在哪个分支和推动什么,推到非裸回购可能是危险的。 更好的解决办法是用宝贵的工作树取代回购。 例:

`orgbranch = $(git rev-parse HEAD)

mkdir / tmp / tmp_wd

cp -r!(.git)/ tmp / tmp_wd

导出GIT_WORK_TREE = / tmp / tmp_wd

git checkout branch1

git rebase master

git checkout $ orgbranch

导出GIT_WORK_TREE =

rm -rf / tmp / tmp_wd“