【问题标题】:How to rebase after git-subtree add?如何在 git-subtree 添加后变基?
【发布时间】:2012-10-03 05:13:43
【问题描述】:

我正在尝试学习在 Git 1.7.11 中添加的新 git-subtree 命令。添加子树后,我似乎失去了变基的能力。我有一个带有 README 文件的主存储库和一个也有一个 README 文件的库存储库。我用subtree add将它添加到lib目录:

$ git subtree add -P lib/mylib myliborigin master

这工作正常,但现在历史看起来像这样:

*   22c1fe6 (HEAD, master) Merge commit 'b6e698d9f4985825efa06dfdd7bba8d2930cd40e' as 'lib/mylib' - 
|\                                                                                                                
| * b6e698d Squashed 'lib/mylib/' content from commit d7dbd3d
* b99d55b Add readme
* 020e372 Initial

现在,当我想针对 origin/master 重新设置我的 repo 时,它失败了,因为 squash 提交直接应用于其不适用的父提交,因为它应用于 repo 的根目录而不是我给的前缀添加子树时将其添加到它。

如果我查看 squash 提交,原因就很清楚了。没有关于前缀的信息。它只是压缩在一起的原始 mylib 提交。只有下一次合并提交对此一无所知,但 rebase 不会在这里考虑。

是否有任何解决方法(除了从不基于子树提交变基)?

【问题讨论】:

  • 你试过git rebase --preserve-merges吗?
  • @TechliveZheng 效果很好,谢谢! Read more here

标签: git git-rebase git-subtree


【解决方案1】:

你需要使用

git rebase --preserve-merges --preserve-committer --onto new_place start end

【讨论】:

  • 这没有帮助。我仍然丢失了前缀。
  • 当我想将功能分支重新定位到 master 时,new_placestartend 的典型值是什么
【解决方案2】:

显然这是预期行为(对于“预期行为”的一些不正当定义。)请参阅:http://git.661346.n2.nabble.com/subtree-merges-lose-prefix-after-rebase-td7332850.html

并不是说这对任何人都有很大帮助。我也很想为此找到解决方法。

【讨论】:

    【解决方案3】:

    我遇到了类似的问题:我想在添加子树后进行 rebase,但使用 --preserve-merges 仍然给我留下了合并冲突(由于 .gitignore 文件和其他文件冲突)。

    就我而言,我并不一定打算使用任何子树功能:我只是拉入了一个原本应该是超级项目一部分的存储库。如果它对其他人有帮助,这就是我最终所做的,基于我发现的其他 related answers

    假设我在同一个目录中有两个项目,main_project 和 sub_project。我想将 sub_project 拉到 main_project 中名为 sub_project 的目录中,假设两个 repo 都没有名为 sub_project 的目录:

    cd main_project
    git fetch ../sub_project
    git checkout -b sub_project FETCH_HEAD
    git filter-branch --prune-empty --tree-filter '
        if [[ ! -e sub_project ]]; then
            mkdir -p sub_project
            git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files sub_project
        fi'
    git checkout branch-to-merge-within
    git merge sub_project
    git branch -d sub_project
    

    如果我发现这种方法有任何问题,我会更新。

    【讨论】:

      【解决方案4】:

      这不是一个解决方案,但它是我当前使用的解决方法......

      使用您的初始示例:

      *   22c1fe6 (HEAD, master) Merge commit 'b6e698d9f4985825efa06dfdd7bba8d2930cd40e' as 'lib/mylib' - 
      |\                                                                                                                
      | * b6e698d Squashed 'lib/mylib/' content from commit d7dbd3d
      * b99d55b Add readme
      * 020e372 Initial
      

      在子树添加之前以交互方式变基到第二次提交:

      $ git rebase -i 020e372
      

      删除两个子树条目并标记先前提交的编辑:

      e b99d55b Add readme
      

      保存文件/关闭,然后当它到达“添加自述文件”提交时,运行修改命令:

      $ git commit --amend
      

      然后重新添加你的新子树:

      $ git subtree add -P lib/mylib myliborigin master
      

      继续变基:

      $ git rebase --continue
      

      然后你的分支应该从 master 重新建立,并且子树将是“正常”的,Squash + Merge 完好无损:

      *   22c1fe6 (HEAD, master) Merge commit 'b6e698d9f4985825efa06dfdd7bba8d2930cd40e' as 'lib/mylib' - 
      |\                                                                                                                
      | * b6e698d Squashed 'lib/mylib/' content from commit d7dbd3d
      

      【讨论】:

      • 如果我有这样的场景,我想压缩以后对子项目所做的提交,这些提交出现在超级项目的历史记录中。例如: commit 597df58 (HEAD, master) 对超级项目的提交 commit 9cd82bd 对子项目的提交 commit 059ffbe 对子项目的提交 af5209e 将提交“da151a ...”合并为“子项目”提交 da151a3 从提交中压缩“子项目/”内容4d455fb 提交 b10f7c9 对超级项目的初始提交我想用 af5209e 压缩 9cd82bd 059ffbe 因为子项目的历史已经使用 git subtree push 推送到它自己的仓库...
      • 不确定,我想我需要更好地了解项目布局,确保术语保持一致,有子树和子模块,但没有子项目:)
      • 子项目只是子树添加 --prefix=subproject repoB 分支的结果
      • 希望您没有破坏其他存储库,因为您认为以后一切都会好起来的:(
      • 这当然有效,但它有点hacky。请参阅我的回答 below 以获得更简单的方法来实现相同的结果。
      【解决方案5】:

      这是一个老问题,但我的 repo 也遇到了同样的问题,我终于找到了一个完整的解决方案,它(希望)保留了所有子树元数据。

      假设我们有这个提交树:

      B   (master) Add README.md
      |     
      A            Initial commit
      

      我们分叉了一个feature 分支,其中一个子树位于lib/

      git remote add -f githublib https://github.com/lib/lib.git
      git subtree add --prefix lib/ githublib master --squash
      

      它创建了一个包含两个父级的合并提交 D:我们当前的 master (B),以及一个不相关的提交 F,其中包含外部 repo 的压缩历史记录。此提交还在其提交消息中包含一些git subtree 元数据(即git-subtree-dirgit-subtree-split)。

         D     (feature) Merged commit 'F' as 'lib/'
        / \    
       /   F             Squashed 'lib/' content from GGGGGG
      B        (master)  Add README.md
      |     
      A                  Initial commit
      

      稍后,我们分别向两个分支添加一些提交。

         E     (feature) Remove .gitignore from lib/
      C  |     (master)  Add LICENSE.md
      |  D               Merged commit 'F' as 'lib/'
      | / \    
      |/   F             Squashed 'lib/' content from GGGGGG
      B                  Add README.md
      |     
      A                  Initial commit
      

      现在我们要将feature 重新定位为master。方法如下:

      1。从feature 中逐一挑选提交,在master 之上创建feature 分支的新副本。

      git branch -f feature C
      git checkout feature
      git cherry-pick D E
      
      E'       (feature) Remove .gitignore from lib/
      |
      D'                 Merged commit 'F' as 'lib/'
      |
      |  E               Remove .gitignore from lib/
      C  |     (master)  Add LICENSE.md
      |  D               Merged commit 'F' as 'lib/'
      | / \    
      |/   F             Squashed 'lib/' content from GGGGGG
      B                  Add README.md
      |     
      A                  Initial commit
      

      现在我们有了一个 rebase 的等价物,但是我们已经丢失了 git subtree 所需的有关外部 repo 的所有信息。恢复它:

      2。 Add the missing parent link as a graft,并重写feature的历史,使其永久化。

      git checkout feature
      git replace --graft D' C F
      git filter-branch --tag-name-filter cat -- master..
      

      现在我们得到了一张与开头完全相同的图片。旧的提交 D 和 E 仍然存在,但它们可以在以后被垃圾收集。

      E'       (feature) Remove .gitignore from lib/
      |
      D'                 Merged commit 'F' as 'lib/'
      |\
      | \                
      C  \     (master)  Add LICENSE.md
      |   \              
      |    \   
      |     F            Squashed 'lib/' content from GGGGGG
      B                  Add README.md
      |     
      A                  Initial commit
      

      警告:这会改写feature 的历史,所以如果有其他人在这个分支上与你合作,请小心发布它。但是,由于您一开始就想进行变基,因此您可能已经意识到了 :-)

      【讨论】:

        【解决方案6】:

        这适用于简单的情况:

        git rebase --preserve-merges master
        

        感谢 cmets 中的@Techlive Zheng。


        你可能会看到

        fatal: refusing to merge unrelated histories
        Error redoing merge a95986e...
        

        这意味着 git 未能自动应用您的子树。这使您处于@ericpeters 在他的回答中描述的情况。解决方案:

        重新添加您的子树(使用您最初使用的相同命令):

        git subtree add -P lib lib-origin master
        

        继续变基:

        git rebase --continue
        

        一切就绪!


        如果您想知道它是否成功,您可以在变基后与您的原始版本进行比较,以确保您没有更改任何内容:

        git diff <ref-before-rebase> <ref-after-rebase> -- .
        

        (末尾的-- . 指示git 仅diff 当前目录中的文件)。


        如果所有其他方法都失败并且您不关心保留提交本身,您可以简单地 git cherry-pick 原始子树提交。

        提交消息将类似于Add 'lib/' from commit '9767e6...'——这就是你想要的。

        【讨论】:

        • 当您说“重新添加”时,是否意味着也要先删除?执行上述git subtree add 命令时,我收到prefix already exists 消息。
        • @Mike-EEE 在所描述的情况下,我相信 git 无法自动添加它,所以“重新添加”是指手动添加它。由于变基,它在那个时候不应该存在。
        • 感谢凯文的上下文和回复。我花了一天的时间被这个话题和我自己的个人困境所迷惑,这源于remote commit comprised of a single file change 上看似无害的git subtree pull,导致父回购/分支中出现数十个冲突和错误,仅与所述提交不同.无论如何,我都能在这里找到我想要的东西:git-memo.readthedocs.io/en/latest/subtree.html 也许它会帮助其他人。
        • 对于“我在添加子树之前忘记拉一些更改并想要变基”的相对简单的情况,这可以正常工作 - 谢谢!
        • 聚会有点晚了,但这个解决方案效果很好,谢谢凯文!我还收到了prefix already exists 消息,这是因为它包含一个不受版本控制的生成文件 (*.pyc) 并阻止删除该文件夹。在执行 git subtree add ... 之前手动删除文件夹就可以了。另请注意,如果您原来的 git subtree add ... 命令有一个 --squash,请记住在此处也包含它,以便重新定位的版本看起来完全相同。
        【解决方案7】:

        Git 2.24.0(2019 年 11 月 4 日发布)增加了对 git rebase --rebase-merges --strategy [strategy] 的支持。所以现在如果你在当前分支包含子树合并时运行git rebase --rebase-merges --strategy subtree [branch],它将立即工作。

        对于我的项目,我决定我可能不使用git subtree add,而是使用git replace --edit 丢弃合并提交的第二个父级。我也用过the Git book v1's obsolete "subtree" tutorial,它做同样的事情但很乏味。

        【讨论】:

          【解决方案8】:

          另一种方法是git rebase -i master,然后将merged squashed 提交替换为b(在此处中断),因此 git rebase 将在原始子树添加提交时停止。此时再次运行git subtree add ...,然后运行git rebase --continue。您将获得相同的历史记录,但会重新基于最新的 master。

          【讨论】:

            猜你喜欢
            • 2016-09-13
            • 1970-01-01
            • 2017-03-09
            • 1970-01-01
            • 2012-07-29
            • 2014-12-01
            • 2015-04-20
            • 2014-07-19
            • 2013-07-27
            相关资源
            最近更新 更多