どんな混ざり合Gitリポジトリ?
-
07-07-2019 - |
質問
考え、以下のシナリオ:
の開発を行っている小さな実験プロジェクトに独自のGitレポ-レート.まで成熟していただきたいと思いますようにする一大プロジェクトBは、大きなリポジトリ今現在のように追加するとサブディレクトリの
い合、B失うことなく、歴史ずれかの方法
解決
別のリポジトリの単一のブランチは、その履歴を保持するサブディレクトリの下に簡単に配置できます。例:
git subtree add --prefix=rails git://github.com/rails/rails.git master
これは、Rails masterブランチのすべてのファイルが<!> quot; rails <!> quot;に追加される単一のコミットとして表示されます。ディレクトリ。 ただし、コミットのタイトルには古い履歴ツリーへの参照が含まれています:
コミットから「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に加えて自分でインストールする必要がある場合があります。
他のヒント
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 <!> gt; = 2.9以降、--allow-unrelated-histories
パラメーターのみが存在します。 Git-git mergeドキュメント/ --allow-をご覧ください。無関係な履歴
更新:タグを保持するために、@ jstadlerによって提案された--tags
を追加しました。
次の2つの解決策があります。
サブモジュール
リポジトリAをより大きなプロジェクトBの別のディレクトリにコピーするか、(おそらくは)リポジトリAをプロジェクトBのサブディレクトリに複製します。次に、 gitサブモジュールにより、このリポジトリを<リポジトリBのstrong> サブモジュール 。
これは、リポジトリAでの開発が継続しており、開発の大部分がAの独立したスタンドアロン開発である疎結合リポジトリに適したソリューションです。 SubmoduleSupport および GitSubmoduleTutorial Git Wikiのページ。
サブツリーのマージ
サブツリーマージ 戦略を使用して、リポジトリAをプロジェクトBのサブディレクトリにマージできます。これは、 サブツリーのマージとあなた で説明されていますマーカス・プリンツ。
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 <!> gt; = 2.9.0にはオプション--allow-unrelated-histories
が必要です。)
または gitサブツリーツールを使用できます( GitHubのリポジトリ)、たとえばapenwarr(Avery Pennarun)によるブログ投稿 Gitサブモジュールの新しい代替手段:gitサブツリー。
あなたの場合(Aはより大きなプロジェクトBの一部である)、正しい解決策はサブツリーマージ を使用することだと思います。
サブモジュールアプローチは、プロジェクトを個別に保守する場合に適しています。ただし、両方のプロジェクトを同じリポジトリに本当にマージする場合は、もう少し作業が必要です。
最初のことは、git filter-branch
を使用して、2番目のリポジトリ内のすべての名前を書き換えて、サブディレクトリに配置することです。したがって、foo.c
、bar.html
の代わりに、projb/foo.c
およびprojb/bar.html
があります。
その後、次のようなことができるはずです:
git remote add projb [wherever]
git pull projb
git pull
は、git fetch
に続いてgit merge
を実行します。プルしようとしているリポジトリにまだprojb/
ディレクトリがない場合、競合は発生しません。
さらに検索すると、gitk
をgit
にマージするために同様のことが行われたことを示します。 Junio C Hamanoはそれについてここに書いています: http://www.mail -archive.com/git@vger.kernel.org/msg03395.html
git-subtree
が、それはどのように変動するというものです。
例えば、 projectA
のディレクトリに作成されたB、 git subtree
,
git log projectA
リスト 一つだけ コミット:にする必要がありません。の犯からの統合プロジェクトのためのさまざまな経路を通っています。
Greg Hewgillの答えに最も近いものなんいどのように書き換えを行った。
解決には驚くほどシンプルです。
(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
注意:この書き換え回歴史をただ引き続きこのレポ-レートしたいということはクローン(コピー)、使い捨てのコピーです。
(2)次にB、
git pull path/to/A
ほら!お持ちの projectA
ディレクトリBご git log projectA
, と、すべての犯からA.
私の場合、たいと思った二つのサブディレクトリ, projectA
や projectB
.その場合は、いったステップ(1)Bです。
両方のリポジトリに同じ種類のファイルがある場合(異なるプロジェクトの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 mergetool
で解決できます。 kdiff3
はキーボードでのみ使用できるため、わずか数分でコードを読み取るときに5つの競合ファイルがかかります。
マージの完了を忘れないでください:
git commit
マージの使用時に履歴が失われ続けたため、リベースを使用することになりました。私の場合、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
= <!> gt;競合を解決して、必要な回数だけ続行します...
git rebase --continue
これを行うと、projAからのすべてのコミットに続いてprojBからのコミットを持つ1つのプロジェクトが作成されます
私の場合、my-plugin
リポジトリとmain-project
リポジトリがあり、plugins
は常にplugins/my-plugin
のmaster
サブディレクトリで開発されたふりをしたかったです。
基本的に、すべての開発がgit filter-branch --tree-filter (...) HEAD
サブディレクトリで行われるように、(...)
リポジトリの履歴を書き直しました。次に、HEAD
の開発履歴をfilter-branch
履歴に追加し、2つのツリーをマージしました。 .git
リポジトリーに-f
ディレクトリーがすでに存在していないため、これは些細な競合のないマージでした。結果のリポジトリには、両方の元のプロジェクトからのすべての履歴が含まれ、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
ロングバージョン
まず、bash
リポジトリのコピーを作成します。これは、このリポジトリの履歴を書き換えるからです。
今、zsh -c
リポジトリのルートに移動し、メインブランチ(おそらくzsh
)をチェックアウトして、次のコマンドを実行します。もちろん、実際の名前が何であれ、extended_glob
と^(...)
を置き換える必要があります。
$ 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
説明が終わりました。 mv
は、glob_dots
から到達可能なすべてのコミットで.gitignore
コマンドを実行します。これはコミットごとに保存されたデータに直接作用するため、<!> quot; working directory <!> quot ;, <!> quot; index <!> quot ;, <の概念を心配する必要はありません。 !> quot; staging <!> quot;など。
失敗したmkdir -p
コマンドを実行すると、いくつかのファイルが^(.git|plugins)
ディレクトリーに残され、次に|| true
を実行しようとすると、git filter-branch
オプションを指定しない限り、これについて文句を言います。 --all
。
実際のコマンドについては、--
を使用して望みどおりの結果を得ることができなかったため、代わりにgit
を使用して--allow-unrelated-histories
にコマンドを実行させます。最初にgit log
オプションを設定します。これは、git --version 2.9.0
コマンドでzsh --version 5.2
構文を有効にするものです。また、<=>オプションを使用すると、グロブ付きのドットファイル(<=>など)を選択できます(<=>)。
次に、<=>コマンドを使用して、<=>と<=>の両方を同時に作成します。
最後に、<=> <!> quot; negative glob <!> quot;を使用します。 <=>および新しく作成された<=>フォルダーを除く、リポジトリーのルート・ディレクトリー内のすべてのファイルと一致する機能<=>。 (ここでは<=>を除外する必要はないかもしれませんが、ディレクトリをそれ自体に移動しようとするとエラーになります。)
私のリポジトリでは、最初のコミットにファイルが含まれていなかったため、<=>コマンドは最初のコミットでエラーを返しました(移動できるものがないため)。したがって、<=>が追加されないように、<=>を追加しました。
<=>オプションは、リポジトリ内の all ブランチの履歴を書き換えるように<=>に指示し、<=>にそれを一部として解釈するように追加の<=>が必要です<=>自体のオプションとしてではなく、書き換えるブランチのオプションリスト。
今、<=>リポジトリに移動し、マージしたいブランチをチェックアウトします。 <=>リポジトリのローカルコピー(履歴を変更)を<=>のリモートとして追加します。
$ 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では、<=>オプションは存在しないことに注意してください。これらのバージョンのいずれかを使用している場合は、オプションを省略します。<=>が防止するエラーメッセージは、 2.9.0で追加された
マージの競合はありません。もしそうなら、それはおそらく<=>コマンドが正しく動作しなかったか、既に<=>に<=>ディレクトリがあったことを意味します。
2つのルートを持つリポジトリを作成するために、どのようなハッカーが起こっているのか疑問に思う将来の貢献者のために、説明的なコミットメッセージを必ず入力してください。
上記の<=>コマンドを使用して、2つのルートコミットが必要な新しいコミットグラフを視覚化できます。 <=>ブランチのみがマージされることに注意してください。これは、<=>ツリーにマージする他の<=>ブランチで重要な作業がある場合、これらのマージが完了するまで<=>リモートを削除しないことを意味します。 yの場合そうしないと、それらのブランチからのコミットは<=>リポジトリに残りますが、一部は到達不能で、最終的にガベージコレクションの影響を受けやすくなります。 (また、リモートを削除するとそのリモート追跡ブランチが削除されるため、SHAでそれらを参照する必要があります。)
オプションで、保持するすべてのものを<=>からマージした後、次を使用して<=>リモートを削除できます。
$ git remote remove my-plugin
履歴を変更した<=>リポジトリのコピーを安全に削除できます。私の場合、マージが完了してプッシュされた後に、実際の<=>リポジトリに廃止通知も追加しました。
Mac OS X El Capitanで<=>および<=>を使用してテスト済み。走行距離は異なる場合があります。
参照:
- https://git-scm.com/docs/git-filter-branch
- https ://unix.stackexchange.com/questions/6393/how-do-you-move-all-files-include-hidden-from-one-directory-to-another
- http:// www.refining-linux.org/archives/37/ZSH-Gem-2-Extended-globbing-and-expansion/
- Gitリポジトリからのファイルのパージに失敗しました。新しいバックアップを作成できません
- git、すべてのブランチのフィルターブランチ
私は何日も同じことをしようとしていますが、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>
これは私がこれを行うのに役立つ投稿でした:
リポジトリAの サブツリー のリポジトリBのブランチからファイルを配置する場合は、 履歴も保存し、読み続けます。 (以下の例では、レポ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のmasterブランチの最初のコミットでコミットされたファイルがありますが、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のmasterブランチからファイルをチェックアウトしますが、コミットを作成しません。次はとにかくすべてのファイルを消去するため、コミットを選択できます。次に、まだコミットせずに(-n
)、Bのマスターブランチから最初のコミットを選択します。 (チェリーピックは、元のコミットメッセージを保存しますが、これは単純なチェックアウトでは実行されないようです。)次に、リポジトリBからすべてのファイルを配置するサブツリーを作成します。サブツリーへのチェリーピック。上記の例では、移動するファイルはREADME
のみです。次に、B-repoルートコミットをコミットすると同時に、元のコミットのタイムスタンプも保持します。
今、新しく作成したB/master
の上に新しいb
ブランチを作成します。新しいブランチをA/master
:
git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root
今、B
ブランチを<=>にマージします:
git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'
最後に、<=>リモートおよび一時ブランチを削除できます:
git remote remove B
git branch -D new_b_root b
最終的なグラフは次のような構造になります。
それは事実からかなり後のことですが、ここで見つけた他の答えに満足できなかったので、これを書きました:
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
ここでStack <!> nbsp; 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 からも入手できます。 p>
まず、各リポジトリへの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
スクリプト自体には、スクリプトの機能を説明する多くのコメントがあります。
2つのリポジトリを単に接着しようとする場合、サブモジュールとサブツリーのマージは、ファイル履歴のすべてを保持しないため、使用するのに間違ったツールです(他の回答で指摘されているように)。これを行う簡単で正しい方法については、この回答こちらをご覧ください。
@Smarに似ていますが、PRIMARYおよびSECONDARYで設定されたファイルシステムパスを使用します。
PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master
その後、手動でマージします。
同様の課題がありましたが、私の場合、レポAでコードベースの1つのバージョンを開発し、それを製品の新しいバージョンの新しいレポ、レポBにクローンしました。レポジトリAのいくつかのバグを修正した後、レポジトリBへの変更をFIする必要がありました。
- リポジトリAを指すリポジトリBにリモートを追加する(git remote add ...)
- 現在のブランチのプル(バグ修正にmasterを使用していませんでした)(git pull remoteForRepoA bugFixBranch)
- プッシュはGitHubにマージします
御treat走をした:)
3つ以上のプロジェクトを単一コミットにマージする場合は、他の回答(remote add -f
、merge
)で説明されている手順を実行します。次に、(ソフト)インデックスを古いヘッド(マージが発生しなかった場所)にリセットします。すべてのファイル(git add -A
)を追加してコミットします(メッセージ<!> quot;プロジェクト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のマニュアルページ、<!> quot; grafts <!> quot;を検索します。
今、最後のコミットには適切な親が関連付けられています。
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)次にマスターに戻り、2つのブランチ間の古典的なマージ:
git checkout master
git merge projectA
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
この関数は、リモートリポジトリをローカルリポジトリ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
を介して置換されるため、マージ後に適切なパスに移動したことを確認してください。
--allow-unrelated-histories
パラメーターは、git <!> gt; = 2.9以降にのみ存在します。
プロジェクトをわずかに手動でマージします。これにより、マージの競合に対処する必要がなくなります。
まず、他のプロジェクトのファイルを必要に応じてコピーします。
cp -R myotherproject newdirectory
git add newdirectory
履歴の次のプル
git fetch path_or_url_to_other_repo
最後に取得したものの履歴にマージするようにgitに伝える
echo 'FETCH_HEAD' > .git/MERGE_HEAD
今すぐコミットしますが、通常はコミットします
git commit
指定されたコマンドは、私が提案する最善の解決策です。
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
小さなプロジェクトを大きなプロジェクトのサブディレクトリに移動したかった。私の小さなプロジェクトには多くのコミットがなかったため、git format-patch --output-directory /path/to/patch-dir
を使用しました。次に、より大きなプロジェクトで、git am --directory=dir/in/project /path/to/patch-dir/*
を使用しました。
これは、フィルタブランチより way 怖くなく、ずっときれいです。確かに、すべてのケースに適用できるわけではありません。