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
#!/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 containing the URLs to the repositories
## which are to be merged on separate lines.
##
## Author: Robert von Burg
## eitch@eitchnet.ch
##
## Version: 0.2.0
## Created: 2015-06-17
##
################################################################################
#
# Disallow using undefined variables
shopt -s -o nounset
# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
# Detect proper usage
if [ "$#" -ne "2" ] ; then
echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
exit 1
fi
# Script functions
function failed() {
echo -e "ERROR: Merging of projects failed:"
echo -e "$1"
exit 1
}
function commit_merge() {
current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
CHANGES=$(git status | grep "working directory clean")
MERGING=$(git status | grep "merging")
if [[ "$CHANGES" != "" ]] && [[ "$MERGING" == "" ]] ; then
echo -e "INFO: No commit required."
else
echo -e "INFO: Committing ${sub_project}..."
if ! git commit --quiet -m "[Project] Merged branch '$1' of ${sub_project}" ; then
failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
fi
fi
}
## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"
# 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: Creating new git repository ${PROJECT_NAME}..."
echo -e "===================================================="
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 th branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "===================================================="
for url in $(cat ${REPO_URL_FILE}) ; do
# Extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Project ${sub_project}"
echo -e "----------------------------------------------------"
# Fetch the project
echo -e "INFO: Fetching ${sub_project}..."
git remote add "${sub_project}" "${url}"
if ! git fetch --no-tags --quiet ${sub_project} 2>/dev/null ; then
failed "Failed to fetch project ${sub_project}"
fi
# Add remote branches
echo -e "INFO: Creating local branches for ${sub_project}..."
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}..."
# Create and checkout new merge branch off of master
git checkout --quiet -b "${sub_project}/${branch_name}" master
git reset --hard --quiet
git clean -d --force --quiet
# Merge the project
echo -e "INFO: Merging ${sub_project}..."
if ! git merge --quiet --no-commit "remotes/${sub_project}/${branch_name}" 2>/dev/null ; 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."
else
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
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" 2>/dev/null ; then
failed "sub_project ${sub_project} is missing master branch!"
fi
# Copy remote tags
echo -e "INFO: Copying tags for ${sub_project}..."
while read tag ; do
tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
tag_name=$(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%%^*}"
tag_new_name="${sub_project}/${tag_name}"
echo -e "INFO: Copying tag ${tag_name} to ${tag_new_name} for ref ${tag_ref}..."
if ! git tag "${tag_new_name}" "${tag_ref}" 2>/dev/null ; then
echo -e "WARN: Could not copy tag ${tag_name} to ${tag_new_name} for ref ${tag_ref}"
fi
done < <(git ls-remote --tags ${sub_project})
# Remove the remote to the old project
echo -e "INFO: Removing remote ${sub_project}..."
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 "===================================================="
for url in $(cat ${REPO_URL_FILE}) ; do
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Merging ${sub_project}..."
if ! git merge --quiet --no-commit "${sub_project}/master" 2>/dev/null ; 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
exit 0
21 回答
可以将另一个存储库的单个分支轻松放置在保留其历史记录的子目录下 . 例如:
这将显示为单个提交,其中Rails主分支的所有文件都添加到“rails”目录中 . 但是,commit的 Headers 包含对旧历史树的引用:
其中
<rev>
是SHA-1提交哈希 . 你仍然可以看到历史,归咎于一些变化 .请注意,您无法从此处看到目录前缀,因为这是一个完整的旧分支 . 您应该将此视为通常的文件移动提交:到达时需要额外的跳转 .
有更复杂的解决方案,例如手动执行此操作或重写历史记录,如其他答案中所述 .
git-subtree命令是官方git-contrib的一部分,一些数据包管理器默认安装它(OS X Homebrew) . 但除了git之外,您可能需要自己安装它 .
如果要将
project-a
合并到project-b
:取自:git merge different repositories?
这种方法对我来说效果很好,它更短,在我看来更清洁 .
Note:
--allow-unrelated-histories
参数仅在git> = 2.9时存在 . 见Git - git merge Documentation / --allow-unrelated-historiesUpdate :按照@jstadler的建议添加
--tags
以保留标记 .这有两种可能的解决方案:
子模块
将存储库A复制到较大项目B中的单独目录中,或者(可能更好)将存储库A克隆到项目B中的子目录中 . 然后使用git submodule使此存储库成为存储库B的 submodule .
对于松散耦合的存储库来说,这是一个很好的解决方案,其中存储库A中的开发仍在继续,并且开发的主要部分是A中的单独独立开发 . 另请参阅Git Wiki上的SubmoduleSupport和GitSubmoduleTutorial页面 .
子树合并
您可以使用 subtree merge 策略将存储库A合并到项目B的子目录中 . 这由Markus Prinz在Subtree Merging and You中描述 .
(Git> = 2.9.0需要选项
--allow-unrelated-histories
. )或者你可以使用apenwarr(Avery Pennarun)的 git subtree 工具(repository on GitHub),例如在他的博客文章_834601中宣布 .
我认为在你的情况下(A是大项目B的一部分)正确的解决方案是使用 subtree merge .
如果要单独维护项目,子模块方法很好 . 但是,如果您真的想将两个项目合并到同一个存储库中,那么您还需要做更多的工作 .
第一件事是使用
git filter-branch
将第二个存储库中所有内容的名称重写到您希望它们结束的子目录中 . 所以不是foo.c
,bar.html
,而是projb/foo.c
和projb/bar.html
.然后,您应该能够执行以下操作:
git pull
将执行git fetch
,然后执行git merge
. 如果您要提取的存储库还没有projb/
目录,则不应存在冲突 .进一步搜索表明已将类似的东西合并
gitk
到git
. Junio C Hamano在这里写道:http://www.mail-archive.com/git@vger.kernel.org/msg03395.htmlgit-subtree is nice, but it is probably not the one you want.
例如,如果
projectA
是在B中创建的目录,则在git subtree
之后,列表 only one commit:合并 . 合并项目的提交是针对不同的路径,因此它们不会显示 .
Greg Hewgill的答案最接近,尽管它实际上没有说明如何改写路径 .
解决方案非常简单 .
(1)在A中,
注意:这会重写历史记录,因此如果您打算继续使用此回购A,您可能希望首先克隆(复制)它的一次性副本 .
(2)然后在B中,运行
瞧! B中有一个
projectA
目录 . 如果运行git log projectA
,您将看到来自A的所有提交 .就我而言,我想要两个子目录,
projectA
和projectB
. 在那种情况下,我也做了步骤(1)到B.如果两个存储库都具有相同类型的文件(例如,针对不同项目的两个Rails存储库),则可以将辅助存储库的数据提取到当前存储库:
然后将其合并到当前存储库:
如果您的Git版本小于2.9,请删除
--allow-unrelated-histories
.在此之后,可能会发生冲突 . 您可以使用
git mergetool
解决它们 .kdiff3
可以单独使用键盘,因此只需几分钟读取代码即可获得5个冲突文件 .记得完成合并:
在使用merge时我一直在丢失历史记录,所以我最终使用了rebase,因为在我的情况下,这两个存储库是不同的,不会最终合并到每次提交:
=>解决冲突,然后根据需要继续多次......
这样做会导致一个项目具有projA的所有提交,然后是projB的提交
就我而言,我有一个
my-plugin
存储库和一个main-project
存储库,我想假装my-plugin
始终是在main-project
的plugins
子目录中开发的 .基本上,我重写了
my-plugin
存储库的历史记录,以便所有开发都发生在plugins/my-plugin
子目录中 . 然后,我将my-plugin
的开发历史添加到main-project
历史中,并将两棵树合并在一起 . 由于main-project
存储库中不存在plugins/my-plugin
目录,因此这是一个简单的无冲突合并 . 生成的存储库包含两个原始项目的所有历史记录,并且有两个根 .TL; DR
长版
首先,创建
my-plugin
存储库的副本,因为我们将重写此存储库的历史记录 .现在,导航到
my-plugin
存储库的根目录,检查主分支(可能是master
),然后运行以下命令 . 当然,无论您的实际名称是什么,都应该替换my-plugin
和plugins
.现在来解释一下 .
git filter-branch --tree-filter (...) HEAD
在每个可从HEAD
到达的提交上运行(...)
命令 . 请注意,这直接对每个提交存储的数据进行操作,因此我们不必担心"working directory","index","staging"等的概念 .如果您运行失败的
filter-branch
命令,它将在.git
目录中留下一些文件,下次您尝试filter-branch
时它会抱怨此情况,除非您将-f
选项提供给filter-branch
.至于实际命令,我没有太多运气让
bash
做我想要的事情,所以我使用zsh -c
来使zsh
执行一个命令 . 首先,我设置extended_glob
选项,这是启用mv
命令中^(...)
语法的选项,以及glob_dots
选项,它允许我选择带有glob(^(...)
)的点文件(例如.gitignore
) .接下来,我使用
mkdir -p
命令同时创建plugins
和plugins/my-plugin
.最后,我使用
zsh
"negative glob"功能^(.git|plugins)
来匹配存储库根目录中的所有文件,但.git
和新创建的my-plugin
文件夹除外 . (这里可能不需要排除.git
,但尝试将目录移动到自身是一个错误 . )在我的存储库中,初始提交不包含任何文件,因此
mv
命令在初始提交时返回错误(因为没有可用的移动) . 因此,我添加了|| true
,以便git filter-branch
不会中止 .--all
选项告诉filter-branch
重写存储库中所有分支的历史记录,并且需要额外的--
来告诉git
将其解释为要重写的分支的选项列表的一部分,而不是作为filter-branch
本身的选项 .现在,导航到
main-project
存储库并检查要合并到的任何分支 . 将my-plugin
存储库的本地副本(其历史记录已修改)添加为main-project
的远程:您现在将在提交历史记录中有两个不相关的树,您可以使用以下方法很好地可视化:
要合并它们,请使用:
请注意,在2.9.0之前的Git中,
--allow-unrelated-histories
选项不存在 . 如果您使用的是其中一个版本,请忽略该选项:在2.9.0中也添加了--allow-unrelated-histories
防止的错误消息 .您不应该有任何合并冲突 . 如果这样做,可能意味着
filter-branch
命令无法正常工作或main-project
中已存在plugins/my-plugin
目录 .确保为任何未来的贡献者输入一个解释性提交消息,想知道hackery正在进行什么样的生成有两个根的存储库 .
您可以使用上面的
git log
命令可视化新的提交图,该图应该有两个根提交 . 注意 only the master branch will be merged . 这意味着如果您想要合并到main-project
树中的其他my-plugin
分支上有重要的工作,那么在完成这些合并之前,应该避免删除my-plugin
远程 . 如果不这样做,那么来自这些分支的提交仍将在main-project
存储库中,但有些将无法访问并且容易受到最终垃圾回收的影响 . (此外,您必须通过SHA引用它们,因为删除远程会删除其远程跟踪分支 . )(可选)在将所有要保留的内容从
my-plugin
合并后,可以使用以下命令删除my-plugin
远程:您现在可以安全地删除已更改其历史记录的
my-plugin
存储库的副本 . 在我的情况下,我还在合并完成并推送后向真实的my-plugin
存储库添加了弃用通知 .在Mac OS X El Capitan上测试
git --version 2.9.0
和zsh --version 5.2
. 您的里程可能有所不同参考文献:
https://git-scm.com/docs/git-filter-branch
https://unix.stackexchange.com/questions/6393/how-do-you-move-all-files-including-hidden-from-one-directory-to-another
http://www.refining-linux.org/archives/37/ZSH-Gem-2-Extended-globbing-and-expansion/
Purging file from Git repo failed, unable to create new backup
git, filter-branch on all branches
我一直试图做同样的事情好几天,我正在使用git 2.7.2 . 子树不保留历史记录 .
如果您不再使用旧项目,则可以使用此方法 .
我建议你先分支B并在分支机构工作 .
以下是没有分支的步骤:
如果您现在记录子目录A中的任何文件,您将获得完整的历史记录
这是帮助我这样做的帖子:
http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/
我知道事情已经很久了,但我对这里找到的其他答案并不满意,所以我写了这个:
如果您保留所有文件历史记录(正如人们在其他答案中注明的那样) . 请参阅此答案here以获得简单而正确的方法 .
如果你想把repo B中的一个分支中的文件放在一个 subtree 的repo中 and 也保留历史记录,继续阅读 . (在下面的例子中,我假设我们想要回购B 's master branch merged into repo A'的主分支 . )
在回购A中,首先执行以下操作以使回购B可用:
现在我们在repo A中创建一个全新的分支(只有一个提交),我们称之为
new_b_root
. 生成的提交将具有在repo B的主分支的第一次提交中提交但放在名为path/to/b-files/
的子目录中的文件 .说明:checkout命令的
--orphan
选项检出A 's master branch but doesn'中的文件创建任何提交 . 我们可以选择任何提交,因为接下来我们会清除所有文件 . 然后,在没有提交的情况下(-n
),我们从B 's master branch. (The cherry-pick preserves the original commit message which a straight checkout doesn' t中选择第一个提交 . 然后我们创建子树,我们要将所有文件放在repo B中 . 然后我们必须移动所有文件 . 在樱桃挑选中引入了子树 . 在上面的示例中,只有一个README
文件要移动 . 然后我们提交B-repo根提交,同时,我们还保留原始提交的时间戳 .现在,我们将在新创建的
new_b_root
之上创建一个新的B/master
分支 . 我们称之为新分支b
:现在,我们将
b
分支合并到A/master
:最后,您可以删除
B
远程和临时分支:最终的图形将具有如下结构:
我已经在Stack OverFlow上收集了很多信息,并且设法将脚本放在一起,这为我解决了问题 .
需要注意的是,它只考虑每个存储库的“develop”分支,并将其合并到一个全新的存储库中的单独目录中 .
标签和其他分支被忽略 - 这可能不是你想要的 .
该脚本甚至处理功能分支和标记 - 在新项目中重命名它们,以便您知道它们来自何处 .
你也可以从http://paste.ubuntu.com/11732805获得它
首先使用每个存储库的URL创建一个文件,例如:
然后调用脚本,给出项目名称和脚本路径:
脚本本身有很多注释,可以解释它的作用 .
我遇到了类似的挑战,但就我而言,我们在repo A中开发了一个代码库版本,然后将其克隆到一个新的repo,repo B中,用于新版本的产品 . 在修复回购A中的一些错误后,我们需要将更改归入回购B.结束执行以下操作:
添加一个指向repo A的回购B的遥控器(git remote add ...)
拉出当前分支(我们没有使用master来修复bug)(git pull remoteForRepoA bugFixBranch)
将合并推送到github
工作了:)
与@Smar类似但使用文件系统路径,在PRIMARY和SECONDARY中设置:
然后你手动合并 .
(改编自post by Anar Manafov)
如果要在 single 提交中合并三个或更多项目,请执行其他答案(
remote add -f
,merge
)中所述的步骤 . 然后,(软)将索引重置为旧头(未发生合并) . 添加所有文件(git add -A
)并提交它们(消息“将项目A,B,C和D合并到一个项目中) . 这现在是master的commit-id .现在,使用以下内容创建
.git/info/grafts
:运行
git filter-branch -- head^..head head^2..head head^3..head
. 如果你有三个以上的分支,只需添加head^n..head
,因为你有分支 . 要更新标签,请附加--tag-name-filter cat
. 不要总是添加它,因为这可能会导致重写某些提交 . 有关详细信息,请参阅man page of filter-branch,搜索"grafts" .现在,您的上一次提交有正确的父母关联 .
要合并B中的A:
1)在项目A中
2)在项目B中
在此分支中,执行您需要执行的所有操作并提交它们 .
C)然后回到主和两个分支之间的经典合并:
合并2个回购
鉴于命令是我建议的最好的解决方案 .
这个函数将克隆远程repo转换为本地repo dir,合并后所有提交都将被保存,
git log
将显示原始提交和正确的路径:如何使用:
如果进行一些更改,您甚至可以将合并仓库的文件/目录移动到不同的路径中,例如:
Notices
路径通过
sed
替换,因此请确保在合并后将其移动到正确的路径中 .--allow-unrelated-histories
参数仅在git> = 2.9时存在 .我手动合并项目,这使我可以避免需要处理合并冲突 .
首先,根据您的需要复制其他项目中的文件 .
接下来的历史
告诉git合并上次提取的东西的历史
现在提交,但你通常会提交