我可以在不修改我的工作副本的情况下重新绑定Git分支吗?

时间:2011-02-06 12:44:37

标签: git rebase

假设我检查了我的“主”分支。我已经对“master”进行了一些生产更改,现在我想将我的“实验”分支改为最新的master。但是,我想在不修改工作副本中的任何文件的情况下执行此操作。从本质上讲,我希望所有的魔法都发生在.git目录中,而不会触及工作副本。

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

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

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

有没有办法一步完成上述所有操作,而无需修改工作副本中的任何内容 (假设在rebase期间没有冲突)

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

当然我可以编写一个脚本来捕获mtimes,运行git,然后重置mtimes;但似乎Git已经有办法在没有打扰工作副本的情况下执行rebase这样的事情,因为rebase实际上是关于增量,而不是文件的实际内容。

7 个答案:

答案 0 :(得分:32)

  

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

遗憾的是,这是不可能的(没有创建工作副本的可修改副本 - see also Petr's answer),因为git在工作树上执行所有merge-y操作(真正的合并,樱桃挑选,rebase,补丁应用程序) 。之前已多次提及此问题,例如one of the knowledgeable Jakub Narębski's answers

  

合并(或rebase)无法在不触及工作目录(和索引)的情况下工作,因为可能存在必须使用工作目录(和/或索引)解决的合并冲突。

是的,这是一个设计决定,但这是一个非常容易理解的问题 - 构建在内存中尝试合并所需的所有结构是一件苦差事,然后一旦遇到冲突,就转储一切都进入工作树,相反,你可以简单地在工作树中做到这一点。 (我不是一个git开发人员;不要把它当作绝对完整的事实。可能有其他原因。)

我的建议是,不是编写脚本来执行所有mtime操作,而是简单地克隆存储库,在克隆中执行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 remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimental,而不是推送。这将自然地触及原始和重新定位的实验分支之间的任何文件,但这绝对是正确的行为。 (当然,这不是你给出的例子,但我希望这些指示是通用的!)

答案 1 :(得分:28)

git 2.5开始,更好的解决方案是使用第二个worktree

  

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

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

就是这样。之后,如果您愿意,可以rm -rf second-copy,或者将来保留更多的折扣。

$ git rebase master experimental

答案 2 :(得分:11)

我已经在linux上创建了一个小脚本。它基于Jefromi的答案和一些补充(主要是设置替代,因此不复制对象数据库,只拉动所需的分支)。有些人可能觉得它很有用: https://github.com/encukou/bin/blob/master/oot-rebase

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

答案 3 :(得分:5)

与创建存储库的克隆类似,我发现使用多个工作来完成这样的事情会更加整洁。另外一个克隆将使用大量的磁盘空间,这几乎不会使用。

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

你创建一个像这样的新工作区:

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

然后你可以把它看作是克隆,除了你原来的workdir中的提取和提交将在这里反映出来(虽然不在没有重新检查的工作分支上)。唯一的例外是,如果您有子模块,则默认情况下为每个workdir单独完成这些子模块。坦率地说,我从未参与其中,因为我试图避免使用子模块。

所以基本上只是:

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,否则它们将不会被隐藏。再一次,不是一步,而是非常干净和简单。

答案 4 :(得分:1)

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

  

如果指定了<branch>,则为git rebase   将执行自动git结帐    在做任何事之前。   否则它仍然是当前的   分支。

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

答案 5 :(得分:1)

正如其他人所说的那样,如果不触及工作目录就无法重新分支分支(即使是建议的替代方法,如创建新的克隆或工作树也无法改变这一事实;这些替代方案确实没有触及您当前的工作目录,但只能通过创建一个新的工作树。)

对于您要更新的分支要在当前工作树(或其父代)上进行重新定位的特殊情况,可以&#34; rebase&#34;另一个分支没有不必要地触摸文件 这种特殊情况经常发生在你正在使用git工作流程的地方,你正在处理许多分支,这些分支都是从主要的&#34; master&#34;分支(定期更新到远程主分支)。

为了说明,假设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

为了举例,我们假设&#34; OtherBranch&#34;已分支&#34; master&#34;,并且您当前的工作树也基于&#34; master&#34;。 您的工作流程通常从使用远程版本更新本地主分支开始......

# 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上重新定义(最好是文件系统操作最少)。以下部分将说明如何。

重新定位其他分支(参考示例 - 不要这样做)

以下解决方案是git方式:

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

缺点是第一个命令会更改当前工作树的日期,即使第二个命令将恢复文件。

以最小的更改重新定位其他分支

要最小化触摸文件的数量,您需要签出新的基本分支,然后使用git cherry-pick在基本分支顶部的OtherBranch中应用所有额外提交。

在执行任何操作之前,您需要在OtherBranch中识别提交。

  • git log OtherBranch显示了对OtherBranch的提交(如果您尚未更改OtherBranch,则主要有用)
  • git reflog显示对本地存储库中分支的更改(如果您已更新分支并出错,则非常有用)。

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

现在您知道基本提交是commitC而最后一次提交是commitE,您可以将其从之前的#34; master&#34;到#34;以及#34; master&#34;)如下:

# 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之前成功完成&#34; rebase&#34;)

# 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工作流程,会发生以下情况:

  1. 切换到OtherBranch(可能分支出一个非常古老的基础分支)。
  2. Rebase(内部步骤1):保存不在上游分支中的提交。
  3. Rebase(内部步骤2):将当前分支重置为(新)基本分支。
  4. Rebase(内部步骤3):从步骤2恢复提交。
  5. 步骤1和步骤3触摸许多文件,但最终许多触摸的文件实际上没有改变。

    我的方法将步骤1和3合并到步骤3中,因此触摸的文件数量最少。唯一触及的文件是:

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

答案 6 :(得分:-1)

所以你想要在结账之前为一个分支完成一个rebase?我真的看不出原因,因为如果你不检查那个分支你就无法解决它。为什么要修改一个你不工作的分支?结帐时,它会改变你的mtime,然后进行rebase。 rebase将触摸已更改的文件,当然您需要重建它们。

但是,解决此问题的一种简单方法是使用其他工作树进行rebase。只需将环境变量GIT_WORK_TREE设置为其他工作树即可。只是不要忘记让你的HEAD与你的工作树相匹配。

根据他所在的分支以及推动的内容,推送到非裸仓可能是危险的。一个更好的解决方案是从珍贵的工作树中获取回购。 例如:

` 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`