3

关于处理重命名还有其他答案merge,但我的情况很复杂,我认为它需要一个单独的问题。

我们有一个 git 项目,最初由 20 多个存储库组成。我们使用包装脚本来处理许多标准的 git 操作。因为我们现在正在迁移到 GitHub,所以我们无法以这种方式处理项目。

因此,我们将所有存储库移动到一个存储库中,基本上使用了saintgimp中描述的方法。当然,这意味着所有文件现在都已重命名,但 SHA 在历史上是相同的。

好的,所以现在我想将 branch 合并source到 branch target,注意我确保两者在最终切换之前同步。我的第一次尝试,使用git merge <source>引起了成千上万的冲突,抱怨在一侧或另一侧更改/删除的文件等。

然后我在Advanced Merging 页面上找到了一个 gem :

如果你想做这样的事情,但没有让 Git 尝试合并来自另一端的更改,有一个更严格的选项,即“我们的”合并策略。这与“我们的”递归合并选项不同。

啊,这听起来像我需要的。好的,我执行了以下操作:

$ git merge -s ours SHA

SHA统一的最后一次提交在哪里。换句话说,我希望所有历史,包括SHA,都被认为已经合并到target. 我希望这将是一次合并,并会修复所有未来的合并。

现在,当我尝试从 合并第一个新提交时source,效果是正确的,但我继续收到以下警告:

[user@host src] git merge --no-commit next_unmerged_commit
Auto-merging /path/to/file/changed/foo.c
warning: inexact rename detection was skipped due to too many files.
warning: you may want to set your merge.renamelimit variable to at least 5384 and retry the command.
Automatic merge went well; stopped before committing as requested

而且,事实上,如果我设置renamelimit为 10000,下一次合并(调用它B)会在没有警告的情况下执行,但代价是性能要慢得多。再一次,一次性成本是可以接受的,如果我的后续合并再次正常,我将支付该成本。

下一次合并,C我使用默认值的地方renamelimit,再次给出警告。

所以,最后,我的问题是:我怎样才能让 git 相信target分支是同步的,source这样它就不会在统一之前停止尝试回到历史?renamelimit由于性能下降,我希望能够在不增加的情况下合并。

4

1 回答 1

2

这确实不是一个很好的答案,因为它更多的是关于您使用的脚本 - 或者我应该说,您没有使用的脚本,因为您的评论说您使用了一个基于 您链接的脚本的脚本- 但是我将展示我在下面的一些原始存储库的假设脚本转换中得到的相当复杂的图表。请注意,此特定脚本将所有转换都保留为合并基础提交,本质上是 commit B,并且 commitB本身是empty

你的问题说:

现在我想将 branch 合并source到 branch target,注意我确保两者在最终切换之前同步。

正如您将在下面看到的,所有新分支都以它们来自的项目命名 - 没有明确的方法来映射sourcetarget到,例如,P 或 Q。但是如果您要运行:

git checkout P/master
git merge Q/master

在下面说明的过程之后,此的合并基础git merge将为空B提交,并且合并将顺利进行:Git 将查看我分别绘制为D和的提交H,跟踪它们的祖先,找到提交B作为它们的合并基础,然后运行两个git diff年代:

git diff <hash-of-B> <hash-of-D>   # what we did on P/master
git diff <hash-of-B> <hash-of-H>   # what they did on H/master

这些git diffs 的输出会说每个文件都是从头开始创建的,并且它们的所有名称都不同:其中的所有内容都已P/master命名,而其中的P/*所有内容都已H/master命名Q/*。不会有名称冲突,合并将自行完成。

显然,那不是你正在做的事情。但是你做什么,以及哪个提交是合并基础,仍然是神秘的。看起来您正在挑选两个提示提交,以便两个提示提交的合并基础是一个确实具有文件的提交,并且这些文件尚未从基础重命名为提示。

您链接的脚本的重点是进行设置,以便每个不相关项目的合并基础都是一个提交。可能,在该脚本之后要做的事情 - 或者代替该脚本,真的 - 是对所有最终提交进行一次大规模的章鱼合并(注意:这是未经测试的,可能很明显):

git checkout P/master                 # just to be somewhere that's not master
git branch -d master                  # discard existing master branch name
git checkout --orphan master          # arrange to create new master
git merge P/master Q/master R/master  # make big octopus merge to marry all projects

此章鱼合并的合并基础将再次为 commit B,结果将是一次合并,将所有项目都以新project/*名称命名。原始存储库现在几乎都没有用了,但如果其中有新的提交,您可以从中获取,添加重命名提交,然后从重命名提交中合并(如果导入脚本没有删除添加的遥控器)。

对链接脚本工作的观察

我从未遇到过这个特殊问题,但脚本中的方法似乎是一个不合理的起点。我可能会做一些不同的事情,而不是为空的合并基础而烦恼,而是使用git read-treeandgit commit-tree来构建和创建最终章鱼合并。P/*主要的关键是在下面的草图中的每个传入项目分支( 、Q/*等)的末尾添加一个重命名提交。

该脚本似乎以这种方式工作。它具有项目 P、Q、R(其最后一个组件被视为项目名称的 URL)作为输入。

  1. 做空回购。
  2. 进行两次初始提交:

    A--B   <-- master
    

提交 A 有一个文件,提交 B 没有文件(为什么不将空树提交为 B?但没关系)。

  1. 循环,适用于所有三个项目。在这里,我扩展了循环以查看正在发生的事情。

  2. (循环迭代 1)git remote add P <url>git fetch P(使用--tags!?)。我们在这里假设 P 有 master 和 dev。

    A--B   <-- master
    
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    
  3. 使用 git ls-remote --heads 在 P 中查找提交的名称,即与我们在refs/remotes/P/*. (假设在 fetch 期间远程 hsa 没有改变——不明智但可能没问题。)

    遍历这些名称。结果再次扩展以进行说明...

  4. 运行git checkout -b P/master master。影响:

    A--B   <-- master, P/master
    
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    
  5. git reset --hard无明显原因运行:没有效果。也许这可能会对后面的某些步骤产生一些影响。

  6. git clean -d --force无明显原因运行:没有效果。

  7. 运行git merge --allow-unrelated-histories --no-commit remotes/P/master" (does merge, but does not commit yet) and then rungit commit -m ...`。影响:

    A--B   <-- master
        \
         \-------C   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    
  8. 也许重命名文件,代码有点松散(第 160-180 行):如果项目 P 有一个名为 P 的顶级目录,则什么都不做,否则创建名为 P 的目录(不检查是否失败),然后生效:

    git mv all-but-P P/
    git commit -m "[Project] Move ${sub_project} files into sub directory"
    

    给予:

    A--B   <-- master
        \
         \-------C--D   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    

    请注意,git mv如果其中一项操作失败-k,它不会执行任何操作。git mv但是,除了子目录 P 和.git它本身之外,工作树顶层中的所有文件都应该在索引中并且git mv应该成功,除非其中一个被命名P(在这种情况下,哎呀!)。

    我在这里假设我们做了 mv,否则提交D不存在。

  9. 重复循环(参见步骤 5)dev。运行git checkout -b P/dev master

    A--B   <-- master, P/dev
        \
         \-------C--D   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
            Pd   <-- origin/P/dev
    
  10. 大概是无效的git reset,并且git clean在第 7 步和第 8 步中再次出现。(如果第 10 步真的很糟糕,这可能会有所作为git mv ?)像第 9 步那样做一个时髦的两步合并,给出:

    A--B   <-- master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
         \  Pd   <-- origin/P/dev
          \   \
           \---E   <-- P/dev
    

    从 B 向下的线连接到从 E 向上的线。此时,图表已经变得相当失控。

  11. 重命名并按照步骤 10 提交。我在这里假设项目尚未在子目录中master,如已经假设的那样,和dev.

    A--B   <-- master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm   <-- origin/P/master
           \
         \  Pd   <-- origin/P/dev
          \   \
           \---E--F   <-- P/dev
    
  12. 在第 190-207 行重命名标签真的很丑陋。这应该在获取时使用聪明的 refspec 完成。写这篇文章的人可能不知道带注释的标签和轻量级标签。我不清楚这是否正常工作,我没有仔细看。让我们暂时假设没有标签。

  13. 删除远程 P。这origin/P/*也会删除名称,但当然提交会保留,因为它们被新P/*分支保留:

    A--B   <-- master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
  14. 使用远程 Q 重复外部循环(步骤 3)。我们将添加 Q 并获取(再次使用--tags,这不是第 14 步中提到的好计划,但我们假设没有标签)。origin/Q/*所以现在我们得到另一个带有名称的不相交子图。为简单起见,我们只假设这次仅origin/Q/master存在:

    A--B   <-- master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
    Q1-Q2-...-Qm   <-- origin/Q/master
    
  15. 运行git checkout -b Q/master master

    A--B   <-- master, Q/master
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
    Q1-Q2-...-Qm   <-- origin/Q/master
    
  16. 运行(可能无效且仍然神秘) git reset --hardgit clean步骤。

  17. 使用时髦的两步合并--allow-unrelated-histories 来创建新的提交G,如下所示:

         ---------------G   <-- Q/master
        /               |
    A--B   <-- master   | (down to Qm)
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
                 / (up to G)
                /
    Q1-Q2-...-Qm   <-- origin/Q/master
    
  18. 同样,可选:将 G 中的所有文件重命名为位于 Q/ 中并提交。再次假设这确实发生了:

         ---------------G--H   <-- Q/master
        /               |
    A--B   <-- master   | (down to Qm)
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
                 / (up to G)
                /
    Q1-Q2-...-Qm   <-- origin/Q/master
    
  19. 丑陋的尝试重命名标签;我们将忽略这一点。

  20. 删除遥控器Qorigin/Q/*名称。(不需要画这个。)

  21. 对存储库 R 重复外部循环。假设它只有自己的master,我们将得到一个像这样的纠结图:

         --------------------I--J   <-- R/master
        /                    | (down to Rm)
       /
       | ---------------G--H   <-- Q/master
       |/               |
    A--B   <-- master   | (down to Qm)
       |\
       | \-------C--D   <-- P/master
                /
    P1-P2-...-Pm
           \
         \  Pd
          \   \
           \---E--F   <-- P/dev
    
                 / (up to G)
                /
    Q1-Q2-...-Qm
                    / (up to I)
                   /
    R1-R2-...----Rm
    

(分析结束)

于 2019-03-23T18:21:07.530 回答