【问题标题】:Can I rebase a Git branch without modifying my working copy?我可以在不修改工作副本的情况下重新设置 Git 分支吗?
【发布时间】:2011-02-06 12:44:36
【问题描述】:

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

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

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

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

有没有一种方法可以一步完成上述所有操作,而无需修改工作副本中的任何内容 (假设在变基期间没有冲突)强>?

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

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

【问题讨论】:

    标签: git rebase


    【解决方案1】:

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

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

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

    就是这样。之后,如果需要,您可以rm -rf second-copy,或者保留它以供将来进行更多 rebase。

    $ git rebase master experimental
    

    【讨论】:

      【解决方案2】:

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

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

      如果不触及工作目录(和索引),合并(或变基)就无法工作,因为可能存在必须使用工作目录(和/或索引)解决的合并冲突。

      是的,这是一个设计决策,但这是一个很容易理解的决策 - 构建尝试在内存中合并所需的所有结构有点麻烦,然后一旦遇到冲突,就转储一切都放入工作树中,而您可以首先在工作树中简单地进行操作。 (我不是 git 开发人员;不要将此视为绝对完整的事实。可能还有其他原因。)

      我的建议,而不是编写脚本来执行所有 mtime 操作,而是简单地克隆存储库,在克隆中执行变基,然后将其推回原始存储库:

      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 来代替推送。这自然会触及原始和重新建立的实验分支之间的任何文件差异,但这绝对是正确的行为。 (当然,这不是您给出的示例,但我希望这些说明是通用的!)

      【讨论】:

      • 对原因的出色解释和出色的替代方案。谢谢!
      • 投了反对票,因为stackoverflow.com/a/12481546/99057 表明这显然是可能的
      • @DaveAbrahams 我认为这很清楚,我的意思是 没有 Petr 的回答创建的工作树的副本 是不可能的。但请按您喜欢的方式投票。
      • 哦,如果不是这个答案,我们永远不会得到 Petr 发布的脚本!也许答案应该从更肯定的东西开始。 (现在投票)
      • 但是最后一个解决方案建议推送到非裸仓库,默认情况下被拒绝...
      【解决方案3】:

      我创建了一个小脚本来在 linux 上执行此操作。它基于 Jefromi 的回答和一些补充(主要是设置替代项,以便不复制对象数据库,只提取所需的分支)。你们中的一些人可能会发现它很有用: https://github.com/encukou/bin/blob/master/oot-rebase

      如果它不符合您的喜好,欢迎提出拉取请求 :)

      【讨论】:

      • 请注意,如果重新定位的分支实际上已在原始存储库中签出,则这是危险的。
      • P.S.避免这种情况的最简单方法是首先检查您是否真的在分支上,在这种情况下,请改为在本地执行 rebase。
      • 默认情况下,Git 不允许推送到当前分支,即使使用 -f
      【解决方案4】:

      更新,因为 git 2.5 这个答案被内置的 机制“工作树”基本相同。见楼上回答: https://stackoverflow.com/a/12481546/1499102

      与创建存储库的克隆类似,我发现使用多个工作目录来做这样的事情要整洁得多。克隆也会占用大量磁盘空间,这几乎不会占用任何空间。

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

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

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

      然后您可以将其视为克隆,只是原始工作目录中的获取和提交将反映在此处(尽管在没有重新签出的情况下不会在工作分支上)。唯一的例外是如果您有子模块,因为默认情况下这些子模块是为每个 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,否则它们将不会被隐藏。同样,不是一步,而是非常干净和简单。

      【讨论】:

        【解决方案5】:

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

        对于要更新的​​分支基于当前工作树(或其父级)的特殊情况,可以“重新设置”另一个分支而无需不必要地接触文件。
        如果您有一个 git 工作流程,其中您正在处理所有从主“主”分支(定期更新到远程主分支)分支的许多分支,这种特殊情况通常会发生。

        为了说明,假设 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”。 您的工作流程通常从使用远程版本更新本地主分支开始...

        # 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,您可以将OtherBranch(从其以前的“master”到新的“master”)rebase 如下:

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

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

        第 1 步和第 3 步涉及很多文件,但最终很多涉及的文件实际上并没有改变。

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

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

        【讨论】:

        • 我喜欢这种方法,因为不必创建另一个工作树。而不是手动寻找基本提交,你可以做git merge-base master OtherBranch我还需要从樱桃挑选中摘下帽子。
        【解决方案6】:

        我也会喜欢它,但这给我留下了希望:

        如果指定&lt;branch&gt;,git rebase 将执行自动 git checkout 在做任何其他事情之前。 否则它保持在当前 分支。

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

        【讨论】:

          【解决方案7】:

          因此,您希望在签出该分支之前为该分支完成变基?我真的看不出这样做的原因,因为如果您不签出该分支,您将无法处理它。为什么要对一个你不工作的分支进行变基?进行结帐,它将更改您的 mtime,然后进行变基。 rebase 会触及已更改的文件,当然您需要重新构建它们。

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

          根据他所在的分支和推送的内容,推送到非裸仓库可能很危险。一个更好的解决方案是使用宝贵的工作树从 repo 中获取。 示例:

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

          【讨论】:

          • -1:有很多很好的理由想要这样做,例如,想要在更新的主分支之上重新设置您正在处理的多个分支(但目前不是) .关于 HEAD 匹配工作树的警告是不平凡的地雷。
          • 这正是我所说的情况。如果您(目前)不处理它们,那么不要变基。在处理它们时重新设置基准。 (对不起,我不够清楚)。是的,它是地雷,这就是我提到它的原因......
          • 也许你想与某人共享 rebase 版本,也许你想(如我所说)现在一次做几个,这样你就不必每次切换任务时都要做 rebase,也许是其他一些原因。我当然有工作流程,在拉到 master 后我会一次重新设置 10 个分支; OP 显然也需要它。
          • 这就是为什么我给出了不会改变他的 mtime 并且不需要其他存储库的问题的解决方案......推送到非裸存储库也会有同样的问题HEAD 与工作树不匹配(或分支与工作树不匹配),因此情况更糟。
          • 推送是为了将更改恢复到原始仓库;二级存储库只是克隆变基,没有恶作剧,完全没有危险。如果您可以准确地阐明如何设置以执行您正在谈论的事情(您如何制作第二个工作树,您必须如何/在哪里确保 HEAD 是正确的,等等)我很乐意删除我的反对票。
          猜你喜欢
          • 2015-07-03
          • 2012-07-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-12-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多