履歴を保持しながら、ファイルを1つのGitリポジトリから別のGitリポジトリ(クローンではない)に移動する方法


484

私たちのGitリポジトリは、個々のプロジェクトがそれぞれ独自のツリーを持っている単一のモンスターSVNリポジトリの一部として開始されました。

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

もちろん、を使用してファイルを別のファイルに移動するのは非常に簡単svn mvでした。しかし、Gitでは、各プロジェクトは独自のリポジトリにあり、今日、サブディレクトリをからproject2に移動するように求められましたproject1。私はこのようなことをしました:

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

しかし、それはかなり複雑なようです。この種のことを一般的に行うより良い方法はありますか?それとも正しいアプローチを採用していますか?

これには、単に別のリポジトリの一部から新しいスタンドアロンリポジトリを作成するのではなく、履歴を既存のリポジトリにマージすることに注意してください(前の質問のように)。


1
それは私には合理的なアプローチのように思えます。あなたの方法を大幅に改善する明白な方法は考えられません。Gitが実際にこれを簡単にすることは素晴らしいことです(たとえば、Subversionの異なるリポジトリ間でファイルのディレクトリを移動したくありません)。
グレッグヒューギル

1
@ebneter-シェルスクリプトを使用して、手動でこれを実行しました(履歴をsvnリポジトリから別のリポジトリに移動しました)。基本的に、特定のファイル/ディレクトリから2番目のリポジトリに履歴(diff、コミットログメッセージ)を再生しました。
Adam Monsen

1
なぜgit fetch p2 && git merge p2代わりにしないのかしらgit fetch p2 && git branch .. && git merge p2?編集:申し分なく、現在のブランチではなく、p2という名前の新しいブランチで変更を取得したいようです。
Lekensteyn 2011

1
--filter-branchがディレクトリ構造を破壊するのを防ぐ方法はありませんか?この「git mv」ステップにより、ファイルの削除と作成でいっぱいの大規模なコミットが行われます。
エドワードフォーク

1
git 2.9以降、無関係な履歴のマージはデフォルトでは許可されていないことに注意してください。機能さ--allow-unrelated-historiesせるには、最後にgit mergeを追加して機能させます。
Scott Berrevoets

回答:


55

うん、で打つ--subdirectory-filterのは、filter-branch鍵となりました。それを使用したという事実は基本的に簡単な方法がないことを証明します-ファイルの(名前が変更された)サブセットのみで終了するため、履歴を書き換える以外に選択肢はなく、これは定義によりハッシュを変更します。標準コマンド(などpull)は履歴を書き換えないため、これらを使用してこれを実行する方法はありません。

もちろん、細部を調整することもできます。一部のクローン作成や分岐は厳密には必要ありませんでしたが、全体的なアプローチは優れています。複雑であるのは残念ですが、もちろん、gitの目的は履歴を簡単に書き直すことではありません。


1
ファイルが複数のディレクトリを移動し、現在1つに存在している場合はどうなりますか?サブディレクトリフィルターは引き続き機能しますか?(つまり、1つのファイルを移動したいだけの場合は、それを独自のサブディレクトリに移動できます。これは機能しますか?)
rogerdpack

1
@rogerdpack:いいえ、これは名前の変更を通じてファイルを追跡しません。選択したサブディレクトリに移動した時点で作成されたように見えると思います。ファイルを1つだけ選択する場合--index-filterは、filter-branchマンページを参照してください。
Cascabel 2012

8
名前を変更する方法についてのレシピはありますか?
ナイトウォリアー2015

歴史の維持と管理はgitの主要なポイントの1つだと思います。
artburkart

288

履歴に問題がない場合は、コミットをパッチとして取り出し、新しいリポジトリに適用できます。

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

または一行で

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

Exherboのドキュメントから引用


21
3つまたは4つのファイルの場合、これを移動するために必要なのは、受け入れられた回答よりもはるかに単純なソリューションでした。最終的に、パッチファイルのパスをfind-replaceでトリミングして、新しいリポジトリのディレクトリ構造に合わせるようにしました。
リアンサンダーソン

8
バイナリファイル(画像など)も適切に移行されるようにオプションを追加しましたgit log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch。問題なく動作しますAFAICT。
Emmanuel Touzery 2013

35
適用ステップ--committer-date-is-author-dateでは、ファイルを移動した日付ではなく、元のコミット日付を保持するオプションを使用しました。
darrenmc 2014年

6
履歴のコミットをマージすると、「am」コマンドが壊れます。上記のgit logコマンドに「-m --first-parent」を追加すると、うまくいきました。
ガーボルLipták

6
@Daniel Golden移動されたファイルに関する問題をなんとか修正しました(これはのバグの結果であり、git log両方--follow--reverse正しく機能しません)。私はこの回答を使用しました。ここに、ファイルを移動するために現在使用して
tsayen

75

ファイルまたはフォルダーを1つのGitリポジトリーから別のGitリポジトリーに移動するためのさまざまなアプローチを試みた後、確実に機能していると思われる唯一のものを以下に概説します。

これには、ファイルまたはフォルダーの移動元のリポジトリーの複製、そのファイルまたはフォルダーのルートへの移動、Git履歴の書き換え、ターゲット・リポジトリーの複製、および履歴のあるファイルまたはフォルダーをこのターゲット・リポジトリーに直接プルすることが含まれます。

第一段階

  1. リポジトリAのコピーを作成します。次の手順でこのコピーに大きな変更を加えますが、プッシュしないでください。

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. それにCD

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. 元のリポジトリへのリンクを削除して、誤ってリモートの変更(プッシュなど)を行わないようにします。

    git remote rm origin
    
  4. 履歴とファイルを調べ、ディレクトリ1にないものをすべて削除します。その結果、ディレクトリ1の内容がリポジトリAのベースに吐き出されます。

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. 単一ファイルの移動のみの場合:残っているものを調べ、目的のファイル以外をすべて削除します。(同じ名前を付けたくないファイルを削除してコミットする必要があるかもしれません。)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

ステージ2

  1. クリーンアップ手順

    git reset --hard
    
  2. クリーンアップ手順

    git gc --aggressive
    
  3. クリーンアップ手順

    git prune
    

これらのファイルを、ルートではなくディレクトリ内のリポジトリBにインポートすることができます。

  1. そのディレクトリを作る

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. ファイルをそのディレクトリに移動します

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. そのディレクトリにファイルを追加する

    git add .
    
  4. 変更をコミットすると、これらのファイルを新しいリポジトリにマージする準備が整います。

    git commit
    

ステージ3

  1. まだ持っていない場合は、リポジトリBのコピーを作成します

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (FOLDER_TO_KEEPがコピー先の新しいリポジトリの名前であると想定)

  2. それにCD

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. リポジトリBのブランチとしてリポジトリAへのリモート接続を作成する

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. このブランチ(移動するディレクトリのみを含む)からリポジトリBにプルします。

    git pull repo-A-branch master --allow-unrelated-histories
    

    プルはファイルと履歴の両方をコピーします。注:プルの代わりにマージを使用できますが、プルの方がうまく機能します。

  5. 最後に、リポジトリAへのリモート接続を削除して、少しクリーンアップしたいと思うでしょう。

    git remote rm repo-A-branch
    
  6. 押すと、準備が整います。

    git push
    

ここで説明したほとんどの手順を実行しましたが、マスター(および他のブランチから)からのファイルまたはディレクトリのコミット履歴のみをコピーしているようです。そうですか?
Bao-Long Nguyen-Trong 2015年

私はそれが正しいと思います、そしてあなたがファイルやフォルダを移動したいあらゆるブランチに対して同様のステップを踏まなければならないでしょう、すなわち。ブランチに切り替えます。リポジトリA、フィルタ分岐でMyBranchなどあなたが考えリポジトリBにし、「gitのプルレポ-A-分岐MyBranchは、」
mcarans

返信いただきありがとうございます。ブランチのタグも移行されるかどうか知っていますか?
Bao-Long Nguyen-Trong 2015

よくわかりませんが、そうなると思います。
mcarans 2015

1
@mcarans残念ながら、信頼できる方法ではありませんが、信頼できる方法です。他のすべてのソリューションと同じ問題が発生します-名前を変更した後の履歴は保持されません。私の場合、最初のコミットはディレクトリ/ファイルの名前を変更したときです。それを超えるものはすべて失われます。
xZero

20

これを見つけ非常に便利。これは、新しいリポジトリに適用されるパッチを作成する非常に単純なアプローチです。詳細はリンク先のページをご覧ください。

3つのステップのみが含まれています(ブログからコピー)。

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

私が持っていた唯一の問題は、すべてのパッチを一度に適用できないことでした

git am --3way <patch-directory>/*.patch

Windowsでは、InvalidArgumentエラーが発生しました。そのため、すべてのパッチを次々と適用する必要がありました。


ある時点でsha-hashsがなくなっていたので私のために働いていませんでした。これは、私を助け:stackoverflow.com/questions/17371150/...
dr0i

「git log」アプローチとは異なり、このオプションは私にとって完璧に機能しました!ありがとう!
AlejandroVD

1
プロジェクトを新しいリポジトリに移動するためのさまざまなアプローチを試しました。これは私のために働いた唯一のものです。そのような一般的なタスクがそれほど複雑である必要があるとは信じられません。
Chris_D_Turk

Ross Hendricksonのブログを共有していただきありがとうございます。このアプローチは私にとってうまくいきました。
Kaushik Acharya、2018年

1
これは非常に洗練されたソリューションですが、他のすべてのソリューションと同じ問題が発生します-名前変更の履歴は保持されません。
xZero

6

ディレクトリ名を維持する

subdirectory-filter(またはより短いコマンドgit subtree)は適切に機能しますが、コミット情報からディレクトリ名が削除されるため、機能しませんでした。私のシナリオでは、1つのリポジトリの一部を別のリポジトリにマージし、フルパス名で履歴を保持したいだけです。

私の解決策は、ツリーフィルターを使用して、ソースリポジトリの一時的なクローンから不要なファイルとディレクトリを削除し、5つの簡単な手順でそのクローンからターゲットリポジトリにプルすることでした。

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

このスクリプトは、元のリポジトリに変更を加えません。マップファイルで指定されたdestリポジトリが存在しない場合、このスクリプトはそれを作成しようとします。
チェタバハナ2015

1
また、ディレクトリ名をそのまま維持することは非常に重要だと思います。それ以外の場合は、ターゲットリポジトリへの追加の名前変更コミットを取得します。
ipuustin 2015

6

私がいつも使用しているのは、ここhttp://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/です。シンプルで高速。

Stackoverflow標準に準拠するための手順は次のとおりです。

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

5

この回答はgit am、例に基づいて、例を使用して提示された興味深いコマンドを段階的に提供します。

目的

  • 一部またはすべてのファイルを1つのリポジトリから別のリポジトリに移動したい。
  • あなたは彼らの歴史を守りたいのです。
  • ただし、タグやブランチを保持する必要はありません。
  • 名前を変更したファイル(および名前を変更したディレクトリ内のファイル)の限られた履歴を受け入れます。

手順

  1. を使用して履歴を電子メール形式で抽出する
    git log --pretty=email -p --reverse --full-index --binary
  2. ファイルツリーを再編成し、履歴のファイル名の変更を更新する(オプション)
  3. を使用して新しい履歴を適用する git am

1.履歴をメール形式で抽出する

例:file3file4およびの履歴を抽出するfile5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

一時ディレクトリの宛先をクリーンアップします

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

あなたのレポきれいにソースを

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

各ファイルの履歴をメール形式で抽出する

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

残念ながらオプション--followまたは--find-copies-harderと組み合わせることはできません--reverse。これが、ファイルの名前が変更されたとき(または親ディレクトリの名前が変更されたとき)に履歴が削除される理由です。

後:電子メール形式の一時的な履歴

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

2.ファイルツリーを再編成し、履歴のファイル名の変更を更新します(オプション)。

これら3つのファイルをこの別のリポジトリ(同じリポジトリにすることもできます)に移動するとします。

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

したがって、ファイルを再編成します。

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

一時的な履歴は次のとおりです:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

履歴内のファイル名も変更します。

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

注:これにより、パスとファイル名の変更を反映するように履歴が書き換えられます。
      (つまり、新しいリポジトリ内の新しい場所/名前の変更)


3.新しい履歴を適用する

あなたの他のリポジトリは:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

一時履歴ファイルからコミットを適用します。

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

あなたの他のリポジトリは今です:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

git statusプッシュする準備ができているコミットの量を確認するために使用します:-)

注:パスとファイル名の変更を反映するように履歴が書き直されたため
      (つまり、前のリポジトリ内の場所/名前と比較されます)

  • git mv場所/ファイル名を変更する必要はありません。
  • git log --follow完全な履歴にアクセスする必要はありません。

追加のトリック:リポ内で名前が変更されたファイルや移動されたファイルを検出する

名前が変更されたファイルを一覧表示するには:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

その他のカスタマイズ:git logオプション--find-copies-harderまたはを使用してコマンドを完了することができます--reverse。また、cut -f3-完全なパターン '{。* =>。*}' を使用して最初の2つの列を削除することもできます。

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

3

(特定のリポジトリの一部のファイルについてのみですが)スクラッチするのと同様のかゆみがあったので、このスクリプトは本当に役に立ちました:git-import

短いバージョンでは$object、既存のリポジトリから指定されたファイルまたはディレクトリ()のパッチファイルを作成します。

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

次に、新しいリポジトリに適用されます。

cd new_repo
git am "$temp"/*.patch 

詳細については、以下を参照してください。


2

これを試して

cd repo1

これにより、上記以外のすべてのディレクトリが削除され、これらのディレクトリの履歴のみが保持されます

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

これで、gitリモートに新しいリポジトリを追加して、それにプッシュできます。

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

追加-fして上書き


警告:git-filter-branchには、破損した履歴の書き換えを生成する大量の問題があります。中止する前にCtrl-Cを押してから、代わりに'git filter-repo'(github.com/newren/git-filter-repo)などの別のフィルタリングツールを使用します。詳細については、filter-branchのマニュアルページを参照してください。この警告を抑制するには、FILTER_BRANCH_SQUELCH_WARNING = 1を設定します。
コリン

1

http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/からのインスピレーションを使用して、同じことを行うためにこのPowershell関数を作成しましたが、これまでのところ私にとってはうまくいきました:

# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
    # The file or directory within the current Git repo to migrate.
    param([string] $fileOrDir)
    # Path to the destination repo
    param([string] $destRepoDir)
    # A temp directory to use for storing the patch file (optional)
    param([string] $tempDir = "\temp\migrateGit")

    mkdir $tempDir

    # git log $fileOrDir -- to list commits that will be migrated
    Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
    git format-patch -o $tempDir --root -- $fileOrDir

    cd $destRepoDir
    Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
    ls $tempDir -Filter *.patch  `
        | foreach { git am $_.FullName }
}

この例の使用法:

git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"

これを実行した後、migrate-from-project2マージする前にブランチ上のファイルを再編成できます。


1

堅牢で再利用可能なもの(1つのコマンドで実行+元に戻す機能)が欲しかったので、次のbashスクリプトを作成しました。何度か私のために働いたので、ここでそれを共有したいと思いました。

任意のフォルダを移動させることができる/path/to/fooからrepo1/some/other/folder/barしますrepo2(ルートフォルダからの距離が異なっていてもよく、同一または異なることができフォルダパス)。

入力フォルダー内のファイルに触れるコミットのみを対象とするため(ソースリポジトリのすべてのコミットを対象とするのではない)、大きなソースリポジトリであっても、すべてで触れられていない深くネストされたサブフォルダーを抽出するだけであれば、かなり高速になるはずです。コミット。

これは、古いリポジトリのすべての履歴を持つ孤立したブランチを作成し、それをHEADにマージすることであるため、ファイル名が衝突した場合でも機能します(その後、マージを解決する必要があります)。 。

ファイル名の衝突がない場合は、 git commitは、最後にマージを完了するがあります。

欠点はREWRITE_FROM、ソースリポジトリの(フォルダー外の)ファイルの名前変更には従わない可能性が高いことです。これに対応するために、GitHubでプルリクエストを歓迎します。

GitHubリンク:git-move-folder-between-repos-keep-history

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

0

私の場合、移行元のリポジトリを保存したり、以前の履歴を保存したりする必要はありませんでした。同じブランチのパッチを別のリモートから持っていました

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

これらの2つの手順で、他のリポジトリのブランチを同じリポジトリに表示することができました。

最後に、このブランチ(他のリポジトリからインポートしたもの)をターゲットリポジトリのメインラインに従うように設定しました(そのため、それらを正確に比較できます)

git br --set-upstream-to=origin/mainline

今度は、同じレポに対してプッシュした別のブランチのように動作しました。


0

問題のファイルのパスが2つのリポジトリで同じであり、1つのファイルまたは関連するファイルの小さなセットだけを持ち込みたい場合、これを行う簡単な方法の1つは、を使用することgit cherry-pickです。

最初のステップは、を使用して、他のリポジトリからのコミットを独自のローカルリポジトリに取り込むことgit fetch <remote-url>です。これはFETCH_HEAD、他のリポジトリからのヘッドコミットをポイントしたままになります。他のフェッチを行った後、そのコミットへの参照を保持したい場合は、それにタグ付けすることができますgit tag other-head FETCH_HEADます。

次に、そのファイル(存在しない場合)の初期コミットを作成するか、ファイルを、他のリポジトリからの最初のコミットでパッチを適用できる状態にするためのコミットを作成する必要があります。必要なファイルが導入されたgit cherry-pick <commit-0>場合にこれを実行できるcommit-0か、または「手動」でコミットを構築する必要がある場合があります。-n最初のコミットを変更する必要がある場合は、チェリーピックオ​​プションに追加します。たとえば、取り込んだくないコミットからファイルをドロップします。

その後、必要に応じてgit cherry-pick再度使用して、後続のコミットを続行できます-n。最も単純なケース(すべてのコミットがまさにあなたが望んでいるものであり、きれいに適用されます)で、cherry-pickコマンドラインでコミットの完全なリストを指定できますgit cherry-pick <commit-1> <commit-2> <commit-3> ...


0

これはgit-filter-repoを使用すると簡単になります。

に移動project2/sub/dirするにはproject1/sub/dir

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

単にツールをインストールするには:pip3 install git-filter-repo詳細とオプションのREADMEに

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2

-2

すべてのブランチを維持し、履歴を保持することにより、GIT StashをGitLabに移行する以下の方法。

古いリポジトリのクローンをローカルに作成します。

git clone --bare <STASH-URL>

GitLabで空のリポジトリを作成します。

git push --mirror <GitLab-URL>

上記のコードは、stashからGitLabにコードを移行したときに実行したもので、非常にうまく機能しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.