git 合并多个副本保留历史

git merge multiple copies preserving history

我有一个项目在不同的地方有一些文件的多个副本。 例如:

src/location1/foobar.h
src/location1/foobar.cpp
src/location2/foobar.h
src/location2/foobar.cpp

我正在将这些提取到自己的库中。 所以我希望结束:

src/location3/foobar.h        combining multiple versions of foobar.h
src/location3/foobar.cpp      combining multiple versions of foobar.cpp

我已经通过了删除所有不需要的文件的第一个障碍:

git filter-repo --path-glob \*foobar\*

在此过程中发现 filter-branch 最近已被高级 filter-repo 取代(值得重复,因为 filter-branch 仍然出现在此处的许多热门答案中)。

我现在想将这些副本合二为一,保留它们的所有历史记录。 这两个候选者是 mergemerge-file

merge-file 需要识别每个文件的共同祖先,这可能很痛苦:

src/location3/foobar.h

这在提交历史中是未知的。 我们有 git merge-base 来寻找最佳共同祖先。

我不清楚如何为 git 合并文件指定文件版本 我想做:

git mv src/location1/foobar.h src/newlocation/foobar.h
git commit
git merge-file src/newlocation/foobar.h src/location3/foobar@<commitid> src/location2/foobar.h
...
git merge-file src/newlocation/foobar.h src/location3/foobar@<commitid> src/location3/foobar.h

这非常费力,必须对每个文件重复一次。 另一种方法是创建多个临时分支:

git checkout -b newlibbranch
git mv src/location1/foobar.h src/newlocation/foobar.h
git mv src/location1/foobar.cpp src/newlocation/foobar.cpp
git commit
git checkout oldversion
git checkout -b v2
git mv src/location2/foobar.h src/newlocation/foobar.h
git mv src/location2/foobar.cpp src/newlocation/foobar.cpp
git commit
git checkout newlibbranch
git merge --allow-unrelated-histories v2

这个也是蛮辛苦的。尽管它可能是可编写脚本的。 还有一个实际问题,因为合并是“rename/rename”冲突而不是实际文件的合并。 这似乎可以通过添加 --allow-unrelated-histories

来解决

所以我的问题是:

关于任务:

  1. 有没有更好的方法?也许是一个我不知道的合并工具,就像我不知道 filter-repo
  2. 我认为多合并分支方式比 git 合并文件更好?

关于合并文件:

  1. 如何为 git 合并文件
  2. 指定文件的特定版本
  3. 有没有自动找到共同祖先的命令或脚本。 类似于:
      git merge-file-wrapper location1 location2   -->

      base = `git merge-base location1 location2`
      git merge-file location1 $base location2

难道这个不存在是不是有什么隐患?

我还没有找到任何自动化工具来执行此操作,因此生态系统中可能存在缺口。

在我的例子中,我有多个文件要移动,其中一些文件比其他文件有更多的副本,这增加了一些有趣的复杂性,但在重构以删除重复时并不少见。

我最后做的是:

  • 编写一个脚本来创建一个新分支,其中每个变体都被移动到它的新位置。

  • 我的脚本首先识别要移动的文件。

  • 找到副本最多的文件并创建那么多分支。

  • 对于每个分支,它会尝试将每个文件的一个副本移动到其新位置

  • 然后我手动合并了每个分支。

    这些合并中的大多数都是微不足道的事情,例如为每个合并更改名称空间 sub-project。

结果是一组文件,其中包含我想要的所有更改以及每个文件的所有更改历史记录。

为了更具体一点:

  • 第 1 步:使用 filter-repo 创建一个仅包含感兴趣文件的项目

    (注意这应该在项目的新克隆上完成)

     git filter-repo --path-glob \*ThingIWant1\* --path-glob \*AnotherThingIWant\* 
     git filter-repo --invert --path-glob \*ThingIDontWant\*
  • 第 2 步:创建分支
    #!/bin/bash
    
    # find unique filenames
    MAXLOCS=0
    FILES=`find . -not -path '*/.*' -type f | grep -v makebranch | xargs -ifile basename file | sort -u`
    for FILE in $FILES; do
        echo FILE=$FILE
        # find number of locations for each filename
        NUMLOCS=`find . -not -path '*/.*' -name $FILE | wc -l`
        if [ $NUMLOCS -gt $MAXLOCS ]; then
        MAXLOCS=$NUMLOCS
        fi
    done
    echo "$MAXLOCS branches required"
    
    # for each branch
    #  move one location of each file to its final destination
    L=0
    while [ $L -lt $MAXLOCS ]; do
        git checkout develop
        git checkout -b ps$L
        for FILE in $FILES; do
        echo FILE=$FILE
        LOCS=( $(find . -not -path '*/.*' -name $FILE) )
        NUMLOCS=${#LOCS[@]}
        if [ $L -lt $NUMLOCS ]; then
            LOC=${LOCS[$L]}
            echo "mv $LOC"
            # Move source files to one place and test files to another
            # In my case we have src and test
            echo $LOC | grep -q /src/
            if [ $? ]; then
                mkdir -p FinalDestinationForSource
                git mv $LOC FinalDestinationForSource/$FILE
                if [ $? -ne 0 ];then
                   echo "BAD: git mv $LOC FinalDestinationForSource/$FILE"
                fi
            else
                mkdir -p FinalDestinationForTests
                git mv $LOC FinalDestinationForTests/$FILE
                if [ $? -ne 0 ];then
                   echo "BAD: git mv $LOC FinalDestinationForTests/$FILE"
                fi
            fi
        fi 
        done
        git add -u
        git status
        git commit -m "#Ticket: move Things to new location $L"
        ((L = L + 1))
    done
  • 第 3 步:合并每个分支
    git checkout ps0
    git merge ps1 -X rename-threshold=5%
    # resolve manually... then
    git commit
    git merge ps1 -X rename-threshold=5%
    # resolve manually... then
    git commit

rename-threshold 有助于说服 git 这些文件具有相同的来源。 否则,一个版本可能会简单地替换另一个版本,而不会保留链接它们的更改历史记录。 我认为结果相当于使用 git commit-tree 链接多个提交 这将是解决此问题的另一种方法。

您可以使用 git blame 验证历史记录以查看每个文件中每一行的来源,并使用 git log 查看实际提交。

Raymond Chen 对此有一个 series of blogs 可能感兴趣。他使用 commit-tree 来完成这项任务。我认为这会奏效,但我认为它有点太 low-level 一种适合我的情况的方法。

  • 第 4 步:将您的库合并到它所属的项目中

    这是为了完整起见,因为您可能会将文件移动到另一个项目。 有关详细信息,请参阅“How do you merge two Git repositories?

    cd targetProject
    git remote add sourceProject /path/to/sourceProject
    git fetch sourceProject
    git merge --allow-unrelated-histories sourceProject/ps0

我认为这个领域已经成熟,可以贡献一个脚本来向 git 添加一个新的合并工具。