web-dev-qa-db-ja.com

2つのGitリポジトリをどのようにマージしますか?

次のシナリオを検討してください。

私はそれ自身のGitリポジトリで小さな実験プロジェクトAを開発しました。それは今や成熟しており、そして私はAがそれ自身の大きなリポジトリを持っているより大きなプロジェクトBの一部であることを望みます。 AをBのサブディレクトリとして追加します。

どちらの側でも履歴を失うことなく、どうすればAをBにマージできますか?

1403
static_rtti

他のリポジトリの単一のブランチは、その履歴を保持しているサブディレクトリの下に簡単に置くことができます。例えば:

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に加えてあなた自身でそれをインストールしなければならないかもしれません。

373

project-aproject-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> = 2.9以降にのみ存在します。 Git - git merge Documentation/--allow-related-histories を参照してください。

更新 :タグを保持するために@jstadlerが提案しているように--tagsを追加.

1575
Andresch Serj

次の2つの解決策があります。

サブモジュール

リポジトリAをより大きなプロジェクトBの別のディレクトリにコピーするか、(おそらくは)リポジトリAをプロジェクトBのサブディレクトリに複製します。次に、 git submodule を使用して、このリポジトリをsubmoduleリポジトリBの.

これは、リポジトリAでの開発が継続し、開発の大部分がAでの独立したスタンドアロン開発である疎結合リポジトリに適したソリューションです。 SubmoduleSupport および GitSubmoduleTutorial Git Wikiのページ。

サブツリーのマージ

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が必要です。)

または、git subtreeツール( GitHubのリポジトリ )を使用できます。たとえば、彼のブログで発表されたapenwarr(Avery Pennarun) post Gitサブモジュールの新しい代替:gitサブツリー


あなたの場合(Aはより大きなプロジェクトBの一部である)、正しい解決策はsubtree mergeを使用することだと思います

599
Jakub Narębski

サブモジュールアプローチは、プロジェクトを個別に管理したい場合に適しています。ただし、両方のプロジェクトを同じリポジトリに本当に統合する場合は、もう少し作業が必要です。

最初のことは、git filter-branchを使用して、2番目のリポジトリ内のすべての名前を、最終的に格納したいサブディレクトリに書き換えることです。 foo.cbar.htmlの代わりに、projb/foo.cprojb/bar.htmlがあります。

そうすれば、次のようなことができるはずです。

git remote add projb [wherever]
git pull projb

git pullgit fetchに続いてgit mergeを実行します。あなたが引っ張っているリポジトリがまだprojb/ディレクトリを持っていなければ、衝突はないはずです。

さらに検索すると、gitkgitにマージするために同様のことが行われたことがわかります。 Junio C Hamanoがこれについて書いています: http://www.mail-archive.com/[email protected]/msg03395.html

192
Greg Hewgill

git-subtreeはいいですが、おそらくあなたが望むものではないでしょう。

たとえば、projectAがBで作成されたディレクトリで、git subtreeの後にある場合、

git log projectA

lists 1つだけ 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からのすべてのコミットが表示されます。


私の場合は、2つのサブディレクトリprojectAprojectBが必要でした。その場合、私はBに対してもステップ(1)を行いました。

64
Paul Draper

両方のリポジトリに同じ種類のファイルがある場合(異なるプロジェクト用の2つの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 mergetoolkdiff3はキーボードでのみ使用できるので、コードを読むときにわずか5分の競合ファイルが5つかかります。

必ずマージを終了してください。

git commit
44
Smar

私はマージを使用しているときに履歴を失い続けていたので、私の場合は2つのリポジトリがコミットごとにマージされないように十分に異なっているので、リベースを使用しました。

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

こうすると、1つのプロジェクトでprojAからのコミットがすべて行われ、その後projBからのコミットが行われることになります。

22
Calahad

私の場合は、my-pluginリポジトリとmain-projectリポジトリがあり、my-pluginは常にmain-projectpluginsサブディレクトリで開発されていると思います。

基本的に、すべての開発がmy-pluginサブディレクトリで行われるように、plugins/my-pluginリポジトリの履歴を書き直しました。次に、my-pluginの開発履歴をmain-project履歴に追加し、2つのツリーをマージしました。 plugins/my-pluginリポジトリにはmain-projectディレクトリがまだ存在していなかったので、これは些細な衝突のないマージでした。結果のリポジトリには、両方のオリジナルプロジェクトからのすべての履歴が含まれ、2つのルーツがありました。

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-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にコマンドを実行させるためにzsh -cを使用します。最初に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オプションは、リポジトリ内のallbranches)の履歴を書き換えるようにfilter-branchに指示します。追加の--は、書き換えるブランチとしてのオプションとしてではなく、gitにオプションリストの一部として解釈させるために必要です。 filter-branchそのもの.

さて、あなたのmain-projectリポジトリに移動して、マージしたいブランチをチェックしてください。 my-pluginリポジトリのローカルコピー(履歴を変更したもの)をmain-projectのリモートとして追加します。

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

コミット履歴に2つの無関係なツリーが表示されます。これらを使用して、視覚的にわかりやすく表示できます。

$ 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ディレクトリがあったことを意味します。

2つのルーツを持つリポジトリを作成するためにどのようなハッカーが発生しているのか疑問に思う、今後の貢献者のための説明的なコミットメッセージを必ず入力してください。

上記のgit logコマンドを使用して、2つのルートコミットがあるはずの新しいコミットグラフを視覚化できます。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を使用してテスト済み。あなたのマイレージは異なる場合があります。

参考文献:

17

私は何日も同じことをやろうとしていました、私は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/ /

8
Rian

私はここで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
##            [email protected]
##
## 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を含むファイルを作成します。

[email protected]:eitchnet/ch.eitchnet.parent.git
[email protected]:eitchnet/ch.eitchnet.utils.git
[email protected]:eitchnet/ch.eitchnet.privilege.git

次に、プロジェクトの名前とスクリプトへのパスを指定してスクリプトを呼び出します。

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

スクリプト自体には、それが何をするのかを説明するためのたくさんのコメントがあります。

6
eitch

私はそれが事実には程遠いことを知っていますが、私がここで見つけた他の答えに満足していなかったので、私はこれを書きました:

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
6
jettero

リポジトリBのブランチからのファイルをリポジトリAのサブツリーに保存したい場合はおよび)履歴も保存してください(以下の例では、レポBのマスターブランチをレポAのマスターブランチにマージしたいと思います。)

リポジトリAで、まずリポジトリBを使用可能にするために次の手順を実行します。

git remote add B ../B # Add repo B as a new remote.
git fetch B

今度はレポジトリAにnew_b_rootと呼ぶ真新しいブランチを作成します(コミットは1つだけです)。結果として得られるコミットは、リポジトリ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のマスターブランチから最初のコミットを選択します。 (チェリーピックは、まっすぐなチェックアウトでは行われないような元のコミットメッセージを保存します。)その後、リポジトリBからすべてのファイルを配置するサブツリーを作成します。サブツリーへのチェリーピック。上記の例では、移動するREADMEファイルだけがあります。それから、B-repo rootコミットをコミットし、同時に元のコミットのタイムスタンプも保存します。

それでは、新しく作成した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

6
Finn Haakansson

2つのリポジトリを単純に結合しようとしているのであれば、サブモジュールとサブツリーのマージは間違ったツールです(他の回答で指摘されているように)。これを行う簡単で正しい方法については、この回答 here を参照してください。

5
Eric Lee

@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による投稿 から変更)

4
Turadg

私は同様の課題を抱えていましたが、私の場合は、リポジトリAで1つのバージョンのコードベースを開発し、それを新しいバージョンの製品用に新しいリポジトリrepo Bに複製しました。レポAのいくつかのバグを修正した後、私たちはレポBへの変更をFIする必要がありました。

  1. リポジトリAを指すリポジトリBへのリモートの追加(git remote add ...)
  2. 現在のブランチを引っ張ります(バグ修正にmasterを使っていません)(git pull remoteForRepoA bugFixBranch)
  3. Githubへのマージを推進

御馳走を働いた:)

4
David Lemphers

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

AをB内でマージするには:

1)プロジェクトA

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

2)プロジェクトB

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

このブランチで、あなたがする必要があるすべての操作を行い、それらをコミットします。

C)それからmasterに戻り、2つのブランチ間の古典的なマージを行います。

git checkout master
git merge projectA
3

single commitで3つ以上のプロジェクトをマージする場合は、他の回答(remote add -fmerge)で説明されている手順を実行してください。次に、(ソフト)インデックスを古いヘッド(マージが発生しなかった場所)にリセットします。すべてのファイルを追加し(git add -A)、それらをコミットします(「プロジェクトA、B、C、Dを1つのプロジェクトにマージする」というメッセージ)。これが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を実行してください。 3つ以上のブランチがある場合は、ブランチと同じ数のhead^n..headを追加するだけです。タグを更新するには、--tag-name-filter catを追加します。これを常に追加しないでください。コミットが書き換えられる可能性があるためです。詳細については、 filter-branchのマンページ を参照して、 "grafts"を検索してください。

さて、あなたの最後のコミットには正しい親が関連付けられています。

3
koppor

プロジェクトをわずかに手動でマージするので、マージの競合に対処する必要がなくなります。

まず、他のプロジェクトのファイルをコピーしてください。

cp -R myotherproject newdirectory
git add newdirectory

次に歴史を引き出す

git fetch path_or_url_to_other_repo

最後に取得したものの履歴にマージするようにgitに指示する

echo 'FETCH_HEAD' > .git/MERGE_HEAD

今コミットするが、通常はコミットする

git commit
1
Collin Anderson

小さなプロジェクトを大きなプロジェクトのサブディレクトリに移動したいと思いました。私の小さなプロジェクトはあまりコミットされていなかったので、私はgit format-patch --output-directory /path/to/patch-dirを使いました。大規模プロジェクトでは、git am --directory=dir/in/project /path/to/patch-dir/*を使用しました。

これは way filter-branchよりも怖くないしきれいに感じます。もちろん、すべてのケースに当てはまるわけではありません。

0
Mike

この関数はすべてのコミットをマージした後にリモートリポジトリをローカルリポジトリディレクトリに複製し、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> = 2.9以降に存在します。

0
Andrey Izman

与えられた命令は私が提案する最も可能な解決策です。

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