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 仍然出现在此处的许多热门答案中)。
我现在想将这些副本合二为一,保留它们的所有历史记录。
这两个候选者是 merge
和 merge-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
来解决
所以我的问题是:
关于任务:
- 有没有更好的方法?也许是一个我不知道的合并工具,就像我不知道 filter-repo
- 我认为多合并分支方式比 git 合并文件更好?
关于合并文件:
- 如何为 git 合并文件
指定文件的特定版本
- 有没有自动找到共同祖先的命令或脚本。
类似于:
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 添加一个新的合并工具。
我有一个项目在不同的地方有一些文件的多个副本。 例如:
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 仍然出现在此处的许多热门答案中)。
我现在想将这些副本合二为一,保留它们的所有历史记录。
这两个候选者是 merge
和 merge-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
来解决所以我的问题是:
关于任务:
- 有没有更好的方法?也许是一个我不知道的合并工具,就像我不知道 filter-repo
- 我认为多合并分支方式比 git 合并文件更好?
关于合并文件:
- 如何为 git 合并文件 指定文件的特定版本
- 有没有自动找到共同祖先的命令或脚本。 类似于:
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 添加一个新的合并工具。