考虑以下场景:
我在自己的Git repo中开发了一个小型实验项目a。它现在已经成熟,我希望A成为更大的项目B的一部分,该项目有自己的大仓库。现在我想将A添加为B的子目录。
我如何将A合并为B,而不丢失任何方面的历史?
考虑以下场景:
我在自己的Git repo中开发了一个小型实验项目a。它现在已经成熟,我希望A成为更大的项目B的一部分,该项目有自己的大仓库。现在我想将A添加为B的子目录。
我如何将A合并为B,而不丢失任何方面的历史?
当前回答
除了使用远程add->fetch->merge策略的所有答案之外:如果您想保留其他存储库中的标记,但不想将它们全部泄漏到一个公共命名空间中(可能会发生冲突),您可能需要稍微更改fetch命令:
git fetch --no-tags other_repo
git fetch --no-tags other_repo 'refs/tags/*:refs/tags/other_repo/*'
第一个命令像往常一样获取所有分支,但省略了附加到提交的标记,第二个命令也省略了通常的标记获取机制(git help fetch了解更多信息),并使用git的refspec功能获取将它们从X映射到other_repo/X的所有标记。
引用(分支、标记)只是git中的文件,您可以使用目录来命名。上面的两个命令将保留第一个存储库中的标记,而另一个存储库的标记将以other_repo作为前缀/
操作完成后,最好移除另一个遥控器,这样您就不会意外地以正常方式获取标签并造成混乱。
其他回答
当您希望在一次提交中合并三个或更多项目时,请执行其他答案中所述的步骤(远程添加-f,合并)。然后,(软)将索引重置为旧头(没有合并)。添加所有文件(git-Add-A)并提交它们(消息“将项目A、B、C和D合并到一个项目中”)。这现在是master的提交id。
现在,使用以下内容创建.git/info/places:
<commit-id of master> <list of commit ids of all parents>
运行gitfilter分支--head^。。头头^2.头头^3.头。如果你有三个以上的分支,就加上同样多的头。。当你有树枝的时候,就把头伸过去。要更新标记,请追加--tag-namefilter cat。不要总是添加,因为这可能会导致某些提交的重写。有关详细信息,请参阅过滤器分支的手册页,搜索“移植物”。
现在,你的最后一次承诺与正确的父母相关。
我知道事情已经过去很久了,但我对我在这里找到的其他答案并不满意,所以我写了这样一篇文章:
me=$(basename $0)
TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo
echo "building new repo in $TMP"
echo
sleep 1
set -e
cd $TMP
mkdir new-repo
cd new-repo
git init
cd ..
x=0
while [ -n "$1" ]; do
repo="$1"; shift
git clone "$repo"
dirname=$(basename $repo | sed -e 's/\s/-/g')
if [[ $dirname =~ ^git:.*\.git$ ]]; then
dirname=$(echo $dirname | sed s/.git$//)
fi
cd $dirname
git remote rm origin
git filter-branch --tree-filter \
"(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
cd ..
cd new-repo
git pull --no-commit ../$dirname
[ $x -gt 0 ] && git commit -m "merge made by $me"
cd ..
x=$(( x + 1 ))
done
此函数将远程repo克隆到本地repo目录中,合并后将保存所有提交,git日志将显示原始提交和正确路径:
function git-add-repo
{
repo="$1"
dir="$(echo "$2" | sed 's/\/$//')"
path="$(pwd)"
tmp="$(mktemp -d)"
remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"
git clone "$repo" "$tmp"
cd "$tmp"
git filter-branch --index-filter '
git ls-files -s |
sed "s,\t,&'"$dir"'/," |
GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
cd "$path"
git remote add -f "$remote" "file://$tmp/.git"
git pull "$remote/master"
git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
git remote remove "$remote"
rm -rf "$tmp"
}
如何使用:
cd current/package
git-add-repo https://github.com/example/example dir/to/save
如果进行一些更改,您甚至可以将合并的repo的文件/目录移动到不同的路径中,例如:
repo="https://github.com/example/example"
path="$(pwd)"
tmp="$(mktemp -d)"
remote="$(echo "$tmp" | sed 's/\///g' | sed 's/\./_/g')"
git clone "$repo" "$tmp"
cd "$tmp"
GIT_ADD_STORED=""
function git-mv-store
{
from="$(echo "$1" | sed 's/\./\\./')"
to="$(echo "$2" | sed 's/\./\\./')"
GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}
# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'
git filter-branch --index-filter '
git ls-files -s |
sed "'"$GIT_ADD_STORED"'" |
GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD
GIT_ADD_STORED=""
cd "$path"
git remote add -f "$remote" "file://$tmp/.git"
git pull "$remote/master"
git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
git remote remove "$remote"
rm -rf "$tmp"
通知路径将通过sed替换,因此请确保在合并后将其移动到正确的路径中。--allow非相关历史参数仅在git>=2.9之后存在。
如果您试图简单地将两个存储库粘合在一起,那么子模块和子树合并是错误的工具,因为它们不能保留所有的文件历史记录(正如人们在其他答案中所指出的)。请参阅此处的答案,了解简单而正确的方法。
另一个存储库的单个分支可以很容易地放在保留其历史的子目录下。例如:
git subtree add --prefix=rails git://github.com/rails/rails.git master
这将显示为一次提交,其中Rails主分支的所有文件都添加到“Rails”目录中。然而,提交的标题包含对旧历史树的引用:
从提交添加“rails/”<rev>
其中<rev>是SHA-1提交哈希。你仍然可以看到历史,责怪一些变化。
git log <rev>
git blame <rev> -- README.md
注意,从这里看不到目录前缀,因为这是一个完整的旧分支。您应该像通常的文件移动提交一样对待它:当到达它时,您需要额外的跳转。
# finishes with all files added at once commit
git log rails/README.md
# then continue from original tree
git log <rev> -- README.md
还有一些更复杂的解决方案,如手动执行此操作或如其他答案所述重写历史。
git子树命令是git contrib的一部分,一些数据包管理器默认安装它(OS X Homebrew)。但除了git之外,您可能还需要自己安装它。