首页 文章

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

提问于
浏览
1317

请考虑以下情形:

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

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

21 回答

  • 587

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

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

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

    从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之外,您可能需要自己安装它 .

  • 5

    如果要将 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?

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

    Note: --allow-unrelated-histories 参数仅在git> = 2.9时存在 . 见Git - git merge Documentation / --allow-unrelated-histories

    Update :按照@jstadler的建议添加 --tags 以保留标记 .

  • 63

    这有两种可能的解决方案:

    子模块

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

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

    子树合并

    您可以使用 subtree merge 策略将存储库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 subtree 工具(repository on GitHub),例如在他的博客文章_834601中宣布 .


    我认为在你的情况下(A是大项目B的一部分)正确的解决方案是使用 subtree merge .

  • 4

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

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

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

    git remote add projb [wherever]
    git pull projb
    

    git pull 将执行 git fetch ,然后执行 git merge . 如果您要提取的存储库还没有 projb/ 目录,则不应存在冲突 .

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

  • 8

    git-subtree is nice, but it is probably not the one you want.

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

    git log projectA
    

    列表 only one commit:合并 . 合并项目的提交是针对不同的路径,因此它们不会显示 .

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


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

  • 22

    如果两个存储库都具有相同类型的文件(例如,针对不同项目的两个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
    
  • 0

    在使用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
    

    =>解决冲突,然后根据需要继续多次......

    git rebase --continue
    

    这样做会导致一个项目具有projA的所有提交,然后是projB的提交

  • 3

    就我而言,我有一个 my-plugin 存储库和一个 main-project 存储库,我想假装 my-plugin 始终是在 main-projectplugins 子目录中开发的 .

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

    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 到达的提交上运行 (...) 命令 . 请注意,这直接对每个提交存储的数据进行操作,因此我们不必担心"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 命令同时创建 pluginsplugins/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 的远程:

    $ 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 选项不存在 . 如果您使用的是其中一个版本,请忽略该选项:在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 远程:

    $ git remote remove my-plugin
    

    您现在可以安全地删除已更改其历史记录的 my-plugin 存储库的副本 . 在我的情况下,我还在合并完成并推送后向真实的 my-plugin 存储库添加了弃用通知 .


    在Mac OS X El Capitan上测试 git --version 2.9.0zsh --version 5.2 . 您的里程可能有所不同

    参考文献:

  • 5

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

  • 0

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

    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
    
  • 1418

    如果您保留所有文件历史记录(正如人们在其他答案中注明的那样) . 请参阅此答案here以获得简单而正确的方法 .

  • 346

    如果你想把repo B中的一个分支中的文件放在一个 subtree 的repo中 and 也保留历史记录,继续阅读 . (在下面的例子中,我假设我们想要回购B 's master branch merged into 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 '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

    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

  • 43

    我已经在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 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
    

    你也可以从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
    

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

  • 15

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

    • 添加一个指向repo A的回购B的遥控器(git remote add ...)

    • 拉出当前分支(我们没有使用master来修复bug)(git pull remoteForRepoA bugFixBranch)

    • 将合并推送到github

    工作了:)

  • 0

    与@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

  • 6

    如果要在 single 提交中合并三个或更多项目,请执行其他答案( 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,搜索"grafts" .

    现在,您的上一次提交有正确的父母关联 .

  • 3

    要合并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
    
  • 3

    合并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
    
  • 5

    鉴于命令是我建议的最好的解决方案 .

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

    这个函数将克隆远程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"
    

    Notices
    路径通过 sed 替换,因此请确保在合并后将其移动到正确的路径中 .
    --allow-unrelated-histories 参数仅在git> = 2.9时存在 .

  • 190

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

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

    cp -R myotherproject newdirectory
    git add newdirectory
    

    接下来的历史

    git fetch path_or_url_to_other_repo
    

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

    echo 'FETCH_HEAD' > .git/MERGE_HEAD
    

    现在提交,但你通常会提交

    git commit
    

相关问题