你如何合并两个Git存储库?

时间:2009-09-15 08:31:55

标签: git merge repository git-subtree

考虑以下情况:

我在自己的Git仓库中开发了一个小型实验项目A.它现在已经成熟,我希望A成为更大的项目B的一部分,它有自己的大型存储库。我现在想添加A作为B的子目录。

如何将A合并到B中,而不会丢失任何一方的历史记录?

25 个答案:

答案 0 :(得分:1593)

如果您想将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 merge different repositories?

这种方法对我来说效果很好,它更短,在我看来更清洁。

注意: {gid> = 2.9只存在--allow-unrelated-histories参数。见Git - git merge Documentation / --allow-unrelated-histories

更新:根据@jstadler的建议添加--tags以保留代码。

答案 1 :(得分:593)

以下是两种可能的解决方案:

子模

将存储库A复制到较大项目B中的单独目录中,或者(可能更好)将存储库A克隆到项目B中的子目录中。然后使用git submodule使此存储库成为 子模块 的存储库B。

对于松散耦合的存储库,这是一个很好的解决方案,其中存储库A中的开发仍在继续,并且开发的主要部分是A中的单独独立开发。另请参阅SubmoduleSupportGitSubmoduleTutorial Git Wiki上的页面。

子树合并

您可以使用 子树合并 策略将存储库A合并到项目B的子目录中。 Markus Prinz在Subtree Merging and You中对此进行了描述。

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> = 2.9.0需要选项--allow-unrelated-histories。)

或者你可以使用apenwarr(Avery Pennarun)的 git子树工具(repository on GitHub),例如在他的博文A new alternative to Git submodules: git subtree中宣布。


我认为在您的情况下(A将成为较大项目B的一部分)正确的解决方案是使用子树合并

答案 2 :(得分:374)

另一个存储库的单个分支可以轻松放置在保留其历史记录的子目录下。例如:

git subtree add --prefix=rails git://github.com/rails/rails.git master

这将显示为单个提交,其中Rails主分支的所有文件都添加到“rails”目录中。 但是,commit的标题包含对旧历史树的引用:

  

从提交<rev>

添加'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之外,你可能还必须自己安装它。

答案 3 :(得分:192)

如果要单独维护项目,子模块方法很好。但是,如果您真的想将两个项目合并到同一个存储库中,那么您还需要做更多的工作。

首先要使用git filter-branch将第二个存储库中所有内容的名称重写到您希望它们结束的子目录中。因此,您需要foo.cbar.html而不是projb/foo.cprojb/bar.html

然后,您应该可以执行以下操作:

git remote add projb [wherever]
git pull projb

git pullgit fetch后跟git merge。如果您要提取的存储库还没有projb/目录,则不应该发生冲突。

进一步搜索表明已将类似gitk合并到git中。 Junio C Hamano在这里写道:http://www.mail-archive.com/git@vger.kernel.org/msg03395.html

答案 4 :(得分:65)

git-subtree很不错,但可能不是您想要的那个。

例如,如果projectA是在git subtree之后在B中创建的目录,

git log projectA

列出只有一个提交:合并。合并项目的提交是针对不同的路径,因此它们不会显示出来。

格雷格·休吉尔的答案最接近,尽管它实际上并没有说明如何改写这些路径。


解决方案非常简单。

(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的所有提交。


就我而言,我想要两个子目录projectAprojectB。在那种情况下,我也做了步骤(1)到B.

答案 5 :(得分:44)

如果两个存储库都有相同类型的文件(比如两个不同项目的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

答案 6 :(得分:22)

在使用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的提交

答案 7 :(得分:17)

在我的情况下,我有一个my-plugin存储库和一个main-project存储库,我想假装my-plugin始终在plugins子目录中开发main-project {1}}。

基本上,我重写了my-plugin存储库的历史记录,以便所有开发都发生在plugins/my-plugin子目录中。然后,我将my-plugin的开发历史记录添加到main-project历史记录中,并将两棵树合并在一起。由于plugins/my-plugin存储库中不存在main-project目录,因此这是一个简单的无冲突合并。生成的存储库包含两个原始项目的所有历史记录,并且有两个根。

TL; DR

$ 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-pluginplugins

$ 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命令。请注意,这直接对每个提交存储的数据进行操作,因此我们不必担心&#34;工作目录&#34;,&#34; index&#34;,&#34; staging& #34;等等。

如果您运行失败的filter-branch命令,它将在.git目录中留下一些文件,下次您尝试filter-branch时,它会抱怨此情况,除非您提供-f的{​​{1}}选项。

至于实际命令,我没有太多运气让filter-branch做我想做的事情,所以我使用bashzsh -c执行命令。首先,我设置zsh选项,这是extended_glob命令中启用^(...)语法的选项,以及mv选项,它允许我选择点文件(例如作为glob_dots)与glob(.gitignore)。

接下来,我使用^(...)命令同时创建mkdir -pplugins

最后,我使用plugins/my-plugin&#34;负面的#&#34; feature 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

请注意,在2.9.0之前的Git中,$ git merge my-plugin/master --allow-unrelated-histories 选项不存在。如果您使用的是其中一个版本,请忽略该选项:--allow-unrelated-histories阻止的错误消息也在<。em>中添加

您不应该有任何合并冲突。如果这样做,可能意味着--allow-unrelated-histories命令无法正常工作或filter-branch中已存在plugins/my-plugin目录。

确保为任何未来的贡献者输入一个解释性提交消息,想知道什么是hackery正在创建一个有两个根的存储库。

您可以使用上面的main-project命令可视化新的提交图,该图应该有两个根提交。请注意,仅合并git log分支。这意味着如果您要在要合并到master树中的其他my-plugin分支上执行重要工作,则在完成这些合并之前,应避免删除main-project远程。如果你不这样做,那么来自这些分支的提交仍将在my-plugin存储库中,但有些将无法访问,并且容易受到最终垃圾收集的影响。 (此外,您必须通过SHA引用它们,因为删除远程会删除其远程跟踪分支。)

或者,在您将要保留的所有内容与main-project合并后,您可以使用以下命令删除my-plugin遥控器:

my-plugin

您现在可以安全地删除已更改其历史记录的$ git remote remove my-plugin 存储库的副本。就我而言,在合并完成并推送之后,我还向真实的my-plugin存储库添加了弃用通知。

使用my-plugingit --version 2.9.0在Mac OS X El Capitan上测试。您的里程可能会有所不同。

参考文献:

答案 8 :(得分:8)

我一直试图做同样的事情好几天,我正在使用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>

这是帮助我这样做的帖子:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/

答案 9 :(得分:7)

如果您想将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根提交,同时,我们还保留原始提交的时间戳。

现在,我们将在新创建的B/master之上创建一个新的new_b_root分支。我们称之为新分支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

最终图表的结构如下:

enter image description here

答案 10 :(得分:6)

我已经在Stack 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=$'\n'

# Detect proper usage
if [ "$#" -ne "2" ] ; then
  echo -e "ERROR: Usage: $0 <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创建一个文件,例如:

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

脚本本身有很多注释,可以解释它的作用。

答案 11 :(得分:6)

我知道事情已经很久了,但我对这里找到的其他答案并不满意,所以我写了这个:

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

答案 12 :(得分:5)

如果您只是想将两个存储库粘合在一起,则子模块和子树合并是错误的工具,因为它们不会保留所有文件历史记录(正如人们在其他答案中所指出的那样)。请参阅此答案here,了解执行此操作的简单方法。

答案 13 :(得分:4)

我遇到了类似的挑战,但就我而言,我们在repo A中开发了一个代码库版本,然后将其克隆到新版本的repo B中,用于新版本的产品。在修复回购A中的一些错误之后,我们需要将更改转换为回购B.结束执行以下操作:

  1. 将遥控器添加到指向repo A(git remote add ...)
  2. 的repo B中
  3. 拉出当前分支(我们没有使用master修复错误)(git pull remoteForRepoA bugFixBranch)
  4. 将合并推送到github
  5. 工作了一个款待:)

答案 14 :(得分:4)

与@Smar类似,但使用文件系统路径,在PRIMARY和SECONDARY中设置:

PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master

然后你手动合并。

(改编自post by Anar Manafov

答案 15 :(得分:3)

如果要在单个提交中合并三个或更多项目,请执行其他答案(remote add -fmerge)中所述的步骤。然后,(软)将索引重置为旧头(未发生合并)。添加所有文件(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。不要总是添加它,因为这可能会导致重写一些提交。有关详细信息,请参阅man page of filter-branch,搜索“移植”。

现在,您的上一次提交与正确的父级相关联。

答案 16 :(得分:3)

To merge a A within B:

1) In the project A

git fast-export --all --date-order > /tmp/ProjectAExport

2) In the project B

git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport

In this branch do all operations you need to do and commit them.

C) Then back to the master and a classical merge between the two branches:

git checkout master
git merge projectA

答案 17 :(得分:3)

合并2个回购

/p:Configuration=Debug
/p:DeployOnBuild=true 
/p:WebPublishMethod=Package 
/p:PackageAsSingleFile=true 
/p:SkipInvalidConfigurations=true
/p:VisualStudioVersion=14.0 
/p:PackageLocation="$(build.artifactstagingdirectory)\\"

答案 18 :(得分:1)

我手动合并项目,这使我可以避免需要处理合并冲突。

首先,根据您的需要复制其他项目中的文件。

cp -R myotherproject newdirectory
git add newdirectory

接下来的历史

git fetch path_or_url_to_other_repo

告诉git合并上次提取的东西的历史

echo 'FETCH_HEAD' > .git/MERGE_HEAD

现在提交你通常会提交

git commit

答案 19 :(得分:1)

我今天必须解决它如下: 项目 A 在 bitbucket 中,项目 B 在代码提交中......两者都是相同的项目,但必须合并从 A 到 B 的更改。(诀窍是在项目 A 中创建相同名称的分支,与项目 B 中相同)< /p>

  • git checkout 项目 A
  • git 远程删除原点
  • git remote add origin Project B
  • git checkout 分支
  • git add *
  • git commit -m "我们已经移动了代码"
  • git push

答案 20 :(得分:0)

鉴于命令是我建议的最佳解决方案。

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master

答案 21 :(得分:0)

此函数将远程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替换,因此请确保在合并后将其移动到正确的路径中 由于git&gt; = 2.9。

--allow-unrelated-histories参数仅存在

答案 22 :(得分:0)

我想将一个小项目移到一个大项目的子目录中。由于我的小型项目没有很多提交,因此我使用了git format-patch --output-directory /path/to/patch-dir。然后在更大的项目中,我使用了git am --directory=dir/in/project /path/to/patch-dir/*

与过滤器分支相比,这感觉方式不那么恐怖,而且更加干净。当然,它可能并不适用于所有情况。

答案 23 :(得分:0)

https://github.com/hraban/tomono是另一种基于脚本的解决方案。

我不是作者,但使用了它,就可以完成工作。

一个积极的方面是,您可以将所有分支机构和所有历史记录都放入最终回购中。对于我的存储库(存储库中没有重复的文件夹-实际上,它们是从tfs2git迁移出来的),没有冲突,并且一切都自动化了。

主要用于(参见名称)创建monorepos。

对于Windows用户:git bash可以执行.sh文件。它带有标准的git安装。

答案 24 :(得分:0)

除了使用 remote 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),并从 {{1} 获取映射它们的所有标签}} 到 X 使用 git 的 refspec 功能。

引用(分支、标签)只是 git 中的文件,您可以使用目录进行命名空间。上面的两个命令将按原样保留第一个存储库中的标签,另一个存储库中的标签将以 other_repo/X

为前缀

操作后最好把另一个遥控器拿掉,以免不小心按正常方式取到标签弄得一团糟。