1932

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

私は独自のGitリポジトリで小さな実験プロジェクトAを開発しました。今では成熟しているので、Aは独自の大きなリポジトリを持つより大きなプロジェクトBの一部になりたいと思います。ここで、AをBのサブディレクトリとして追加します。

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

4

26 に答える 26

2374

にマージ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?

この方法は私にとってはかなりうまくいきました。それはより短く、私の意見ではずっときれいです。

project-aサブディレクトリに入れたい場合は、 git-filter-repo( filter-branchis discouraged ) を使用できます。上記のコマンドの前に、次のコマンドを実行します。

cd path/to/project-a
git filter-repo --to-subdirectory-filter project-a

2 つの大きなリポジトリをマージし、そのうちの 1 つをサブディレクトリに配置する例: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

注:この--allow-unrelated-historiesパラメーターは git >= 2.9 以降でのみ存在します。Gitを参照してください- git merge Documentation / --allow-unrelated-history

更新:--tagsタグを保持するために、@jstadler の提案に従って追加されました。

于 2012-05-11T09:37:09.730 に答える
638

考えられる解決策は次の 2 つです。

サブモジュール

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

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

サブツリーのマージ

サブツリー マージ戦略を使用して、リポジトリ 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

--allow-unrelated-histories( Git >= 2.9.0 ではオプションが必要です。)

または、apenwarr (Avery Pennarun) によるgitサブツリーツール ( GitHub のリポジトリ)を使用することもできます。


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

于 2009-09-15T09:38:42.673 に答える
501

別のリポジトリの単一のブランチを、その履歴を保持するサブディレクトリの下に簡単に配置できます。例えば:

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

これは、Rails マスター ブランチのすべてのファイルが "rails" ディレクトリに追加される単一のコミットとして表示されます。ただし、コミットのタイトルには古い履歴ツリーへの参照が含まれています。

コミットから「rails/」を追加<rev>

<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 に加えて、自分でインストールする必要がある場合があります。

于 2013-02-20T23:44:27.257 に答える
206

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

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

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

git remote add projb [wherever]
git pull projb

git pulla のgit fetch後に aを実行しgit mergeます。projb/プル先のリポジトリにまだディレクトリがない場合、競合は発生しません。

gitkさらに検索すると、 にマージするために同様のことが行われたことがわかりgitます。Junio C Hamano がここに書いています: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html

于 2009-09-15T08:38:49.507 に答える
77

git-subtreeいいですが、おそらくあなたが望むものではありません。

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

git log projectA

1 つのコミットのみをリストします: マージです。マージされたプロジェクトからのコミットは別のパスに対するものであるため、表示されません。

Greg Hewgill's answer が最も近いですが、パスを書き換える方法については実際には述べていません。


解決策は驚くほど簡単です。

(1) で、

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 のバックアップを作成することをお勧めします。

注: ファイル名またはパスに非 ASCII 文字 (または白文字) を使用する場合は、sed コマンド内の代替スクリプトを変更する必要があります。その場合、「ls-files -s」によって生成されるレコード内のファイルの場所は引用符で始まります。

(2) 次に、B で実行します。

git pull path/to/A

出来上がり!BにprojectAディレクトリがあります。 を実行するgit log projectAと、A からのすべてのコミットが表示されます。


私の場合、2 つのサブディレクトリが必要projectAでしprojectBた。その場合は、手順 (1) を B にも行いました。

于 2014-02-01T08:10:09.617 に答える
60

両方のリポジトリに同じ種類のファイルがある場合 (異なるプロジェクト用の 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 つの競合ファイルが数分しかかかりません。

マージを完了することを忘れないでください:

git commit
于 2011-06-11T14:31:33.217 に答える
31

マージを使用すると履歴が失われ続けたので、リベースを使用することになりました。私の場合、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

これを行うと、projA からのすべてのコミットに続いて projB からのコミットを持つ 1 つのプロジェクトが作成されます。

于 2014-02-27T03:09:17.523 に答える
9

ここで Stack OverFlow などに関する多くの情報を収集し、問題を解決するスクリプトをまとめることができました。

注意点は、各リポジトリの「開発」ブランチのみを考慮し、完全に新しいリポジトリの別のディレクトリにマージすることです。

タグや他のブランチは無視されます - これはあなたが望むものではないかもしれません.

このスクリプトは機能ブランチとタグも処理します。新しいプロジェクトでそれらの名前を変更して、それらがどこから来たのかがわかるようにします。

#!/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

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

于 2015-06-11T12:59:50.353 に答える
9

私は何日も同じことをしようとしてきました.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/

于 2016-03-10T12:51:08.847 に答える
7

2 つのリポジトリを単純に接着しようとしている場合、サブモジュールとサブツリーのマージは、すべてのファイル履歴を保持しないため、使用するツールとしては不適切です (人々が他の回答で指摘したように)。これを行う簡単で正しい方法については、こちらの回答を参照してください。

于 2013-01-23T00:08:25.180 に答える
7

かなり後になってからだと思いますが、ここで見つけた他の回答に満足できなかったので、次のように書きました。

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
于 2012-05-07T13:38:36.190 に答える
6

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

  1. レポ A を指すレポ B にリモートを追加する (git remote add...)
  2. 現在のブランチをプルします (バグ修正のためにマスターを使用していませんでした) (git pull remoteForRepoA bugFixBranch)
  3. マージを github にプッシュする

おやつを食べました:)

于 2011-11-18T19:36:17.760 に答える
5

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

于 2011-08-12T15:15:48.213 に答える
4

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) 次に、マスターに戻り、2 つのブランチ間の従来のマージを行います。

git checkout master
git merge projectA
于 2015-07-28T16:47:33.723 に答える
1

与えられたコマンドは、私が提案する最善の解決策です。

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
于 2015-11-24T05:39:42.777 に答える
1

より複雑なユースケースのために Google が使用する Copybara ツールがあります - https://github.com/google/copybara

于 2021-12-15T16:18:55.380 に答える