如何合并两个 Git 存储库?
-
07-07-2019 - |
题
考虑以下场景:
我在自己的 Git 存储库中开发了一个小型实验项目 A。它现在已经成熟了,我希望 A 成为更大的项目 B 的一部分,该项目有自己的大存储库。我现在想将 A 添加为 B 的子目录。
如何将 A 合并到 B 中,而不丢失任何一方的历史记录?
解决方案
另一个存储库的单个分支可以轻松放置在保留其历史记录的子目录下。例如:
git subtree add --prefix=rails git://github.com/rails/rails.git master
这将显示为单个提交,其中Rails master分支的所有文件都被添加到<!> quot; rails <!> quot;目录。 但是,commit的标题包含对旧历史树的引用:
从commit
添加'rails /'<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-subtree命令是官方git-contrib的一部分,一些数据包管理器默认安装它(OS X Homebrew)。 但除了git之外,你可能还必须自己安装它。
其他提示
如果要将project-a
合并到project-b
:
cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a
摘自: git合并不同的存储库?
这种方法对我来说效果很好,它更短,在我看来更清洁。
注意: --allow-unrelated-histories
参数仅存在,因为git <!> gt; = 2.9。请参阅 Git - git merge文档/ - 允许 - 无关-历史
更新:根据@jstadler的建议添加--tags
以保留标记。
以下是两种可能的解决方案:
子模
将存储库A复制到较大项目B中的单独目录中,或者(可能更好)将存储库A克隆到项目B中的子目录中。然后使用 git子模块以使此存储库成为<存储库B的strong> 子模块 。
对于松散耦合的存储库,这是一个很好的解决方案,其中存储库A中的开发仍在继续,并且开发的主要部分是A中的单独独立开发。另请参阅 SubmoduleSupport 和 GitSubmoduleTutorial Git Wiki上的页面。
子树合并
您可以使用 子树合并 策略将存储库A合并到项目B的子目录中。这在 子树合并和你 中有所描述。 Markus Prinz。
git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master
(Git <!> gt; = 2.9.0需要选项--allow-unrelated-histories
。)
或者您可以使用 git subtree 工具( GitHub上的存储库)由apenwarr(Avery Pennarun),例如在他的博客文章中宣布 Git子模块的新替代方案:git子树。
我认为在您的情况下(A将成为较大项目B的一部分)正确的解决方案是使用子树合并 。
如果要单独维护项目,子模块方法很好。但是,如果您真的想将两个项目合并到同一个存储库中,那么您还需要做更多的工作。
首先要使用 git filter-branch
来重写第二个存储库中所有内容的名称,使其位于您希望它们结束的子目录中。因此,不是 foo.c
, bar.html
,而是 projb / foo.c
和 projb / bar.html 代码>
然后,您应该可以执行以下操作:
git remote add projb [wherever]
git pull projb
git pull
将执行 git fetch
,然后执行 git merge
。如果您要提取的存储库还没有 projb /
目录,则应该没有冲突。
进一步搜索表明已将类似 gitk
合并到 git
中。 Junio C Hamano在此处写道: http://www.mail -archive.com/git@vger.kernel.org/msg03395.html
git-subtree
很不错,但可能不是你想要的那个。
例如,如果 projectA
是在B中创建的目录,则在 git subtree
之后,
git log projectA
列出只有一个提交:合并。合并项目的提交是针对不同的路径,因此它们不会显示出来。
Greg Hewgill的答案最接近,虽然它实际上没有说明如何改写路径。
解决方案非常简单。
(1)在A中,
PREFIX=projectA #adjust this
git filter-branch --index-filter '
git ls-files -s |
sed "s,\t,&'"$PREFIX"'/," |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD
注意:这会重写历史记录,因此如果您打算继续使用此回购A,您可能希望首先克隆(复制)它的一次性副本。
(2)然后在B中,运行
git pull path/to/A
瞧!您在B中有一个 projectA
目录。如果您运行 git log projectA
,您将看到来自A的所有提交。
就我而言,我想要两个子目录, projectA
和 projectB
。在那种情况下,我也做了步骤(1)到B.
如果两个存储库都有相同类型的文件(比如两个不同项目的Rails存储库),则可以将辅助存储库的数据提取到当前存储库:
git fetch git://repository.url/repo.git master:branch_name
然后将其合并到当前存储库:
git merge --allow-unrelated-histories branch_name
如果你的Git版本小于2.9,请删除 - allow-unrelated-histories
。
此后,可能会发生冲突。您可以使用 git mergetool
来解决它们。 kdiff3
只能与键盘一起使用,因此只需几分钟读取代码即可获得5个冲突文件。
请记住完成合并:
git commit
在使用merge时我一直在丢失历史记录,所以我最终使用了rebase,因为在我的情况下,这两个存储库是不同的,不会在每次提交时最终合并:
git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB
cd projB
git remote add projA ../projA/
git fetch projA
git rebase projA/master HEAD
=&GT;解决冲突,然后根据需要继续多次......
git rebase --continue
执行此操作会导致一个项目具有projA的所有提交,然后是projB的提交
就我而言,我有一个 my-plugin
存储库和一个 main-project
存储库,我想假装 my-plugin
一直被开发于 plugins
的子目录 main-project
.
基本上,我改写了历史 my-plugin
存储库,以便看起来所有开发都发生在 plugins/my-plugin
子目录。然后,我添加了发展历史 my-plugin
进入 main-project
历史,并将两棵树合并在一起。由于没有 plugins/my-plugin
目录已存在于 main-project
存储库,这是一个微不足道的无冲突合并。生成的存储库包含两个原始项目的所有历史记录,并且有两个根。
长话短说
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
长版
首先,创建一个副本 my-plugin
存储库,因为我们将重写该存储库的历史。
现在,导航到根目录 my-plugin
存储库,检查您的主分支(可能 master
),然后运行以下命令。当然,你应该替代 my-plugin
和 plugins
无论你的真实姓名是什么。
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
现在进行解释。 git filter-branch --tree-filter (...) HEAD
运行 (...)
对可从以下位置访问的每个提交执行命令 HEAD
. 。请注意,这直接对每次提交存储的数据进行操作,因此我们不必担心“工作目录”、“索引”、“暂存”等概念。
如果你运行一个 filter-branch
命令失败,它会在目录中留下一些文件 .git
目录以及下次尝试时 filter-branch
它会抱怨这一点,除非你提供 -f
选项 filter-branch
.
至于实际的命令,我没有太多运气 bash
做我想做的事,所以我使用 zsh -c
使 zsh
执行命令。首先我设置了 extended_glob
选项,这使得 ^(...)
语法中的 mv
命令,以及 glob_dots
选项,它允许我选择点文件(例如 .gitignore
)与一个球(^(...)
).
接下来,我使用 mkdir -p
命令来创建两者 plugins
和 plugins/my-plugin
同时。
最后,我使用 zsh
“负全局”功能 ^(.git|plugins)
匹配存储库根目录中的所有文件,除了 .git
和新创建的 my-plugin
文件夹。(不包括 .git
这里可能没有必要,但尝试将目录移动到其自身中是一个错误。)
在我的存储库中,初始提交不包含任何文件,因此 mv
命令在初始提交时返回错误(因为没有任何内容可以移动)。因此,我添加了一个 || true
以便 git filter-branch
不会中止。
这 --all
选项告诉 filter-branch
改写历史 全部 存储库中的分支,以及额外的 --
有必要告诉 git
将其解释为要重写的分支的选项列表的一部分,而不是作为 filter-branch
本身。
现在,导航到您的 main-project
存储库并检查您想要合并到的任何分支。添加您的本地副本 my-plugin
存储库(其历史记录已修改)作为远程 main-project
和:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
现在,您的提交历史记录中将有两个不相关的树,您可以使用以下命令很好地可视化它们:
$ git log --color --graph --decorate --all
要合并它们,请使用:
$ git merge my-plugin/master --allow-unrelated-histories
请注意,在 2.9.0 之前的 Git 中, --allow-unrelated-histories
选项不存在。如果您使用的是这些版本之一,只需省略该选项:错误消息 --allow-unrelated-histories
防止是 还 2.9.0 中添加。
您不应该有任何合并冲突。如果您这样做,则可能意味着 filter-branch
命令无法正常工作或已经存在 plugins/my-plugin
目录在 main-project
.
确保为任何想知道黑客行为如何创建具有两个根的存储库的未来贡献者输入解释性提交消息。
您可以使用上面的内容可视化新的提交图,它应该有两个根提交 git log
命令。注意 只有 master
分支将被合并. 。这意味着如果您在其他方面有重要工作 my-plugin
您想要合并到的分支 main-project
树,你应该避免删除 my-plugin
远程直到您完成这些合并。如果不这样做,那么这些分支的提交仍将位于 main-project
存储库,但有些将无法访问并且容易受到最终垃圾收集的影响。(此外,您必须通过 SHA 引用它们,因为删除远程会删除其远程跟踪分支。)
或者,在合并您想要阻止的所有内容后 my-plugin
, ,您可以删除 my-plugin
远程使用:
$ git remote remove my-plugin
您现在可以安全地删除该副本 my-plugin
您更改了历史记录的存储库。就我而言,我还在实际中添加了弃用通知 my-plugin
合并完成并推送后的存储库。
在 Mac OS X El Capitan 上进行测试 git --version 2.9.0
和 zsh --version 5.2
. 。你的旅费可能会改变。
参考:
我一直试图做同样的事情好几天,我正在使用git 2.7.2。子树不保留历史记录。
如果您不再使用旧项目,可以使用此方法。
我建议您先分支B并在分支机构工作。
以下是没有分支的步骤:
cd B
# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B
# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B
git add .
git commit -m "Moving content of project B in preparation for merge from A"
# Now merge A into B
git remote add -f A <A repo url>
git merge A/<branch>
mkdir A
# move all the files into subdir A, excluding .git
git mv <files> A
git commit -m "Moved A into subdir"
# Move B's files back to root
git mv B/* ./
rm -rf B
git commit -m "Reset B to original state"
git push
如果您现在记录子目录A中的任何文件,您将获得完整的历史记录
git log --follow A/<file>
这是帮助我这样做的帖子:
如果您想将repo B中分支的文件放在repo A 和 子树 中>还保留历史,继续阅读。 (在下面的例子中,我假设我们希望将rep的B主分支合并到repo A的主分支中。)
在回购A中,首先执行以下操作以使回购B可用:
git remote add B ../B # Add repo B as a new remote.
git fetch B
现在我们在repo A中创建一个全新的分支(只有一个提交),我们称之为 new_b_root
。生成的提交将包含在repo B的主分支的第一次提交中提交的文件,但放在名为 path / to / b-files /
的子目录中。
git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"
说明:checkout命令的 - orphan
选项从A的主分支中检出文件,但不创建任何提交。我们可以选择任何提交,因为接下来我们清除了所有文件。然后,在没有提交( -n
)的情况下,我们从B的主分支中挑选第一个提交。 (cherry-pick保留了原始的提交消息,直接签出似乎没有。)然后我们创建子树,我们要放置来自repo B的所有文件。然后我们必须移动所有文件樱桃挑选子树。在上面的示例中,只有一个 README
文件要移动。然后我们提交B-repo根提交,同时,我们还保留原始提交的时间戳。
现在,我们将在新创建的 new_b_root
之上创建一个新的 B / master
分支。我们称之为新分支 b
:
git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root
现在,我们将 b
分支合并到 A / master
中:
git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'
最后,您可以删除 B
远程和临时分支:
git remote remove B
git branch -D new_b_root b
最终图表的结构如下:
我知道事情已经很久了,但我对这里找到的其他答案并不满意,所以我写了这个:
me=$(basename <*>)
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
我在Stack&nbsp; OverFlow等处收集了大量信息,并设法将脚本放在一起,为我解决问题。
需要注意的是,它只考虑每个存储库的“develop”分支,并将其合并到一个全新的存储库中的单独目录中。
标签和其他分支被忽略 - 这可能不是你想要的。
该脚本甚至可以处理功能分支和标记 - 在新项目中重命名它们,以便您知道它们来自何处。
#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
## and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
## which are to be merged on separate lines.
##
## Author: Robert von Burg
## eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#
# disallow using undefined variables
shopt -s -o nounset
# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=
您也可以从 http://paste.ubuntu.com/11732805 获取
首先使用每个存储库的URL创建一个文件,例如:
git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git
然后调用脚本,给出项目名称和脚本路径:
./mergeGitRepositories.sh eitchnet_test eitchnet.lst
脚本本身有很多注释,可以解释它的作用。
\n'
# Detect proper usage
if [ "$#" -ne "2" ] ; then
echo -e "ERROR: Usage: <*> <new_project> <my_repo_urls.lst>"
exit 1
fi
## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"
# Script functions
function failed() {
echo -e "ERROR: Merging of projects failed:"
echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
echo -e "$1"
exit 1
}
function commit_merge() {
current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
if [[ ! -f ".git/MERGE_HEAD" ]] ; then
echo -e "INFO: No commit required."
echo -e "INFO: No commit required." >>${LOG_FILE} 2>&1
else
echo -e "INFO: Committing ${sub_project}..."
echo -e "INFO: Committing ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
fi
fi
}
# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
exit 1
fi
# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
exit 1
fi
# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo
# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do
if [[ "${url:0:1}" == '#' ]] ; then
continue
fi
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Project ${sub_project}"
echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
echo -e "----------------------------------------------------"
echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1
# Fetch the project
echo -e "INFO: Fetching ${sub_project}..."
echo -e "INFO: Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
git remote add "${sub_project}" "${url}"
if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
failed "Failed to fetch project ${sub_project}"
fi
# add remote branches
echo -e "INFO: Creating local branches for ${sub_project}..."
echo -e "INFO: Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
while read branch ; do
branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)
echo -e "INFO: Creating branch ${branch_name}..."
echo -e "INFO: Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1
# create and checkout new merge branch off of master
if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
# Merge the project
echo -e "INFO: Merging ${sub_project}..."
echo -e "INFO: Merging ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
fi
# And now see if we need to commit (maybe there was a merge)
commit_merge "${sub_project}/${branch_name}"
# relocate projects files into own directory
if [ "$(ls)" == "${sub_project}" ] ; then
echo -e "WARN: Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
echo -e "WARN: Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
else
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
mkdir ${sub_project}
for f in $(ls -a) ; do
if [[ "$f" == "${sub_project}" ]] ||
[[ "$f" == "." ]] ||
[[ "$f" == ".." ]] ; then
continue
fi
git mv -k "$f" "${sub_project}/"
done
# commit the moving
if ! git commit --quiet -m "[Project] Move ${sub_project} files into sub directory" ; then
failed "Failed to commit moving of ${sub_project} files into sub directory"
fi
fi
echo
done < <(git ls-remote --heads ${sub_project})
# checkout master of sub probject
if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
failed "sub_project ${sub_project} is missing master branch!"
fi
# copy remote tags
echo -e "INFO: Copying tags for ${sub_project}..."
echo -e "INFO: Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
while read tag ; do
tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)
# hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
tag_name="${tag_name_unfixed%%^*}"
tag_new_name="${sub_project}/${tag_name}"
echo -e "INFO: Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
echo -e "INFO: Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
echo -e "WARN: Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
echo -e "WARN: Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
fi
done < <(git ls-remote --tags --refs ${sub_project})
# Remove the remote to the old project
echo -e "INFO: Removing remote ${sub_project}..."
echo -e "INFO: Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
git remote rm ${sub_project}
echo
done
# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do
if [[ ${url:0:1} == '#' ]] ; then
continue
fi
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Merging ${sub_project}..."
echo -e "INFO: Merging ${sub_project}..." >>${LOG_FILE} 2>&1
if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
failed "Failed to merge branch ${sub_project}/master into master"
fi
# And now see if we need to commit (maybe there was a merge)
commit_merge "${sub_project}/master"
echo
done
# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo
exit 0
您也可以从 http://paste.ubuntu.com/11732805 获取
首先使用每个存储库的URL创建一个文件,例如:
<*>然后调用脚本,给出项目名称和脚本路径:
<*>脚本本身有很多注释,可以解释它的作用。
如果您只是想将两个存储库粘合在一起,则子模块和子树合并是错误的工具,因为它们不会保留所有文件历史记录(正如人们在其他答案中所指出的那样)。请参阅此处,以获得简单而正确的方法。
与@Smar类似,但使用文件系统路径,在PRIMARY和SECONDARY中设置:
PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master
然后你手动合并。
(改编自 Anar Manafov的帖子)
我遇到了类似的挑战,但就我而言,我们在repo A中开发了一个代码库版本,然后将其克隆到新版本的repo B中,用于新版本的产品。在修复回购A中的一些错误之后,我们需要将更改转换为回购B.结束执行以下操作:
- 将遥控器添加到指向repo A(git remote add ...) 的repo B中
- 拉出当前分支(我们没有使用master修复错误)(git pull remoteForRepoA bugFixBranch)
- 将合并推送到github 醇>
工作了一个款待:)
如果要在单个提交中合并三个或更多项目,请执行其他答案中描述的步骤( remote add -f
, merge 代码>)。然后,(软)将索引重置为旧头(未发生合并)。添加所有文件(
git add -A
)并提交它们(消息“将项目A,B,C和D合并到一个项目中)。现在这是master的commit-id。
现在,使用以下内容创建 .git / info / grafts
:
<commit-id of master> <list of commit ids of all parents>
运行 git filter-branch - head ^ .. head head ^ 2..head head ^ 3..head
。如果你有三个以上的分支,只需添加 head ^ n..head
,因为你有分支。要更新标签,请附加 - tag-name-filter cat
。不要总是添加它,因为这可能会导致重写一些提交。有关详细信息,请参阅 filter-branch的手册页,搜索“移植物”。
现在,您的上一次提交与正确的父级相关联。
在B中合并A:
1)在项目A中
git fast-export --all --date-order > /tmp/ProjectAExport
2)在项目B中
git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport
在此分支中执行您需要执行的所有操作并提交它们。
C)然后回到主人和两个分支之间的经典合并:
git checkout master
git merge projectA
合并2个回购
git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2
delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master
此函数将远程repo克隆到本地repo dir,合并后所有提交将被保存, git log
将显示原始提交和正确的路径:
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="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-unrelated-histories
参数仅存在,因为git&gt; = 2.9。
我手动合并项目,这使我可以避免需要处理合并冲突。
首先,根据您的需要复制其他项目中的文件。
cp -R myotherproject newdirectory
git add newdirectory
接下来的历史
git fetch path_or_url_to_other_repo
告诉git合并上次提取的东西的历史
echo 'FETCH_HEAD' > .git/MERGE_HEAD
现在提交你通常会提交
git commit
鉴于命令是我建议的最佳解决方案。
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
我想将一个小项目移动到一个较大项目的子目录中。由于我的小项目没有很多提交,我使用 git format-patch --output-directory / path / to / patch-dir
。然后在较大的项目中,我使用 git am --directory = dir / in / project / path / to / patch-dir / *
。
这感觉方式不那么可怕,而且比过滤器分支更清洁。当然,它可能并不适用于所有情况。