ブランチを保持しながら、特定のコミットポイントでgitリポジトリを最近のコミットと古いコミットに分割するにはどうすればよいですか?

metamattj

マスターでのコミットに適切にリンクされた各ブランチを保持しながら、特定のコミットSHAでgitリポジトリを2つのリポジトリ(最近と履歴)に分割するにはどうすればよいですか?

問題の説明

多くのSOの質問は、サブディレクトリ(たとえば、The Easy Wayを分割する方法を尋ねて答えますが、それは私がする必要があることではありません。むしろ、特定のコミットの前にリポジトリのコミットをすべてに分割し、その後にすべてのコミットを分割する必要があります。私のリポジトリは大きく、10年の歴史の中で数千のコミットと数百のブランチがありますが、問題は8つのコミット(1〜8)と3つのブランチ(マスター、A、B)を持つ単純なリポジトリに要約できます。

1 - 2 - 3 - 4 - 5 - 6 - master
     \           \
      7           8
       \           \
        A           B

変換後、私が欲しいのは2つのリポジトリです。最初の(project-history)には、ブランチAの履歴コミット1、2、3、および4と関連するコミット7が含まれている必要があります。2番目の(project-recent)には、ブランチBのコミット4、5、6および関連するコミット8が含まれている必要があります。 。これらは次のようになります。

project-history                     project-recent
1 - 2 - 3 - 4 -master               4 - 5 - 6 - master
     \                                   \
      7                                   8
       \                                   \
        A                                   B

Gitリポジトリを2つ分割する」で説明されている同様の問題がありますが、どちらの回答も受け入れられず、テストスクリプトとともに必要な結果を生成しません。

考えられるアプローチ:分岐してから、孤立したコミットを使用してリベースします

ProGitの本第7.13章Git-Tools-Replaceは、非常に近いアプローチを提供します。このアプローチでは、最初に履歴を作成してから、最近のコミットを新しい孤立したコミットにリベースします。

履歴を作成する

  1. リポジトリが分割されるコミットのSHAを見つけます
  2. historyその時点でブランチを作成します
  3. 履歴ブランチとそれに接続されたブランチを新しいプロジェクトにプッシュします-履歴リポジトリ

これはすべてうまく機能します。

最近のコミットをリベースする

しかし、この次の部分は完全には機能しません。

  1. 孤立したコミットを作成します。これにより、コミットが生成されます。 aaf5c36
    • git commit-tree 8e3dbc5^{tree}
  2. aaf5c36分割コミットの親から開始 するようにマスターをリベースします
    • git rebase --preserve-merges --onto aaf5c36 8e3dbc5
  3. この新しいマスターとブランチBを新しいプロジェクトにプッシュします-最近のリポジトリ

問題:新しいプロジェクトの最近のリポジトリで、ブランチBがマスターから切断されています。結果のリポジトリは次のようになります。

project-history                     project-recent
1 - 2 - 3 - 4 -master               4 - 5 - 6 - master
     \                                   
      7                             1 - 2 - 3 - 4 - 5 - 8- B
       \                            
        A                                   

問題を説明するためのスクリプト

repo-split-example.shスクリプトは、(例えば、リポジトリを作成しrepo-split-example)、その後にこの技術を使用して、それを分割repo-split-historyし、repo-split-recentリポジトリが、ブランチBは後者に取り付けられていません。さらに、ブランチBを最近のリポジトリにプッシュすることにより、履歴コミットもリポジトリにプッシュされ(コミット1、2、3)、コミット4と5の複製があります(元のコミットに加えて、リベース)。プロジェクトの最終状態は次のとおりです-最近のリポジトリ:

$ git log --graph --all --oneline --decorate
* c29649c (HEAD -> master) sixth
* e8545fd fifth
* 8e3dbc5 fourth
* aaf5c36 Get history from historical repository at file:///Users/jones/development/git-svn-migrate/repo-split-history
* 7a98d11 (B) branchB
* 1f620ac fifth
* 1853778 fourth
* 14ab901 third
* 8dd0189 second
* bb1fc8d first

私が欲しいのは:

$ git log --graph --all --oneline --decorate
* c29649c (HEAD -> master) sixth
| * 7a98d11 (B) branchB
|/
* e8545fd fifth
* 8e3dbc5 fourth
* aaf5c36 Get history from historical repository at file:///Users/jones/development/git-svn-migrate/repo-split-history

repo-split-example.shスクリプトは、問題を再現する簡単な方法です。プロジェクトの最近のリポジトリに、マスターからの最近のコミットとブランチBからのコミットを含め、リベースされたコミット5(fifth)に適切にリンクさせるにはどうすればよいですか?

アドバイスありがとうございます!

更新

さらに調べた結果、最近のブランチを手動でリベースして、新しく書き直したツリーに戻すことができると判断しました。これを行うには、最近のツリーの各ブランチに対して、次のようにします。

# Rebase branch B onto the newly rewritten fifth commit
git branch temp e8545fd # the SHA of the rewritten fifth commit
git checkout B
git rebase temp # This works, but will (always?) have conflicts because it starts 
                # from the beginning because there is no common merge base for the commit
git branch -d temp

したがって、これは機能し、目的の結果を生成します。git rebase temp書き直された5番目のコミットは元のブランチBと履歴を共有しないため、ビットは多数のマージ競合を生成します(履歴の開始以降のコミットごとに1つ)。したがって、ここには多くの手動の競合解決があります。私の実際のリポジトリには時間がかかりすぎるでしょう。そのため、マージの競合なしにリベースが機能する実行可能なソリューションを探しています。

metamattj

私はついにこれを理解したので、それが役立つことを願ってここに手順を文書化します。リベースの代わりに、グラフトを使用してリポジトリを分割し、フィルターブランチを使用してツリーを書き換えてグラフトを永続的にすることができます。だから、その与えられたTRUNCPOINTリポジトリを分割した上でコミットのSHAは、TRUNCPARENTその親のSHAあり、そして両方project-historyproject-recent、新たに半分にリポジトリを分割するために、最終的な手続き歴史コミットと最近のコミットを、受信する準備ができリポジトリを初期化されました次のように:

まず、履歴コミットのブランチを作成します

これは、$ TRUNCPOINTでブランチを作成し、そのブランチとそれに由来するすべてのブランチをにプッシュすることで簡単に実行できproject-historyます。

git branch history $TRUNCPOINT
git push project-history history:master
git push project-history A

それはローカルに歴史のコミットをプッシュhisotryへの分岐masterの枝project-historyレポ、その後に分岐Aを押してproject-history、リポジトリにも。結果は次のようになります。

git log --graph --oneline --decorate --all
* fdc8f84 (A) branchA a1
| * 7237a3e (HEAD -> master) fourth
| * 55be55d third
|/  
* 26555d8 second
* 5a68ca2 first

履歴内の最新のコミットは4番目のコミットであるため、これまでのところ問題ありません。

次に、リポジトリを分割して、TRUNCPOINTからマスターのHEADへの最近のコミットを取得する必要があります。

最近のコミットの親として機能する基本コミットを作成します

これらの次のコマンドは、最近のコミットツリーの新しいルートになる空のコミットを作成します。

MESSAGE="Get history from historical repository"
BASECOMMIT=`echo $MESSAGE | git commit-tree ${TRUNCPARENT}^{tree}`

TRUNCPARENTをBASECOMMITに移植して、リポジトリを分割します

最後に、リポジトリ移植して、$ TRUNCPOINTの親が元の親ではなく$ BASECOMMITになったことを伝えます。これにより、$ TRUNCPOINTで履歴が効果的に切り捨てられます。次にfilter-branch、履歴を書き換えてグラフトを永続的にし、マスターとそれに関連するブランチBをproject-recentリポジトリにプッシュします。

echo "${TRUNCPOINT} ${BASECOMMIT}" > .git/info/grafts
git filter-branch -- --all
git push project-recent master
git push project-recent B

project-recentリポジトリの結果の分割コンテンツは次のとおりです。

git log --graph --oneline --decorate --all
* 2335aeb (B) branchB b2
* 2bb7ea3 branchB b1
| * 83c3ae9 (HEAD -> master) sixth
|/  
* 25931c5 fifth
* 1e1e201 fourth
* a7f3373 Get history from historical repository

ルートコミットa7f3373は人為的に作成したBASECOMMITであり、そのコミットログには、プロジェクト履歴を含むリポジトリの場所をユーザーに示すメッセージを含めることができるため、将来のユーザーはgit replace必要に応じて2つのリポジトリに再参加できます。 。再現可能なスクリプトとしての完全なプロセスをダウンロードできますが、参照用に以下に含まれています。

私たちが抱えている他の唯一の大きな問題は、実際のケースでは、どのブランチを過去のレポにプッシュする必要があり、どのブランチを最近のレポにプッシュする必要があるかを判断しようとすることです。しかし、この回答は、2つのリポジトリを作成するために分割自体がどのように完了したかを示しています。

完全に再現されたbashスクリプトの例

#!/bin/bash
WORKDIR=${PWD}

create_repos () {
    rm -rf repo-split-example repo-split-recent repo-split-history
    # Create the repo to be split
    example_repo

    # Create the repo to contain the historical commits
    HISTREPO="file://${WORKDIR}/repo-split-history"
    mkdir ../repo-split-history
    cd ../repo-split-history/
    git init --bare
    cd ../repo-split-example
    git remote add project-history $HISTREPO

    # Create the repo to contain the recent commits
    RECEREPO="file://${WORKDIR}/repo-split-recent"
    mkdir ../repo-split-recent
    cd ../repo-split-recent/
    git init --bare
    cd ../repo-split-example
    git remote add project-recent $RECEREPO
}

example_repo () {
    # Part I: set up a test repo with our example commits
    mkdir repo-split-example
    cd repo-split-example/
    git init
    echo "We want to split the repository into project-recent and project-history portions, following the instructions at https://git-scm.com/book/en/v2/Git-Tools-Replace., but also including branches." > README.md
    echo " "
    echo "First commit." >> README.md
    git add README.md
    git commit -m "first"
    echo "Second commit." >> README.md
    git add README.md
    git commit -m "second"

    git checkout -b A HEAD
    echo "Add Branch A change." >> README.md
    git add README.md
    git commit -m "branchA a1"

    git checkout master
    echo "Third commit." >> README.md
    git add README.md
    git commit -m "third"
    TRUNCPARENT=`git rev-parse HEAD`

    echo "Fourth commit." >> README.md 
    git add README.md
    git commit -m "fourth"
    TRUNCPOINT=`git rev-parse HEAD`

    echo "Fifth commit." >> README.md
    git add README.md
    git commit -m "fifth"
    FIFTH=`git rev-parse HEAD`

    git checkout -b B HEAD
    echo "Add Branch B change. b1" >> README.md
    git add README.md
    git commit -m "branchB b1"
    B1=`git rev-parse HEAD`

    echo "Add Branch B change. b2" >> README.md
    git add README.md
    git commit -m "branchB b2"
    B2=`git rev-parse HEAD`

    git checkout master
    echo "Sixth commit." >> README.md
    git add README.md
    git commit -m "sixth"

    # Now we have a repo with the requisite structure, ready to be split
    git log --graph --all --oneline --decorate
}


split_repo () {
    # Part II: Split the git repo into historical and current halves at $TRUNCPOINT
    # Following guidelines at https://git-scm.com/book/en/v2/Git-Tools-Replace

    # First create a branch for the historical commits
    echo "Branching history at $TRUNCPOINT"
    git branch history $TRUNCPOINT
    git log --graph --oneline --decorate history A

    # Now copy the history repo to the remote HISTREPO repository
    git push project-history history:master
    git push project-history A

    # Now to split the repo to get the recent history from TRUNCPOINT to HEAD of master
    # Create a base commit for the new new recent history
    MESSAGE="Get history from historical repository at $HISTREPO"
    BASECOMMIT=`echo $MESSAGE | git commit-tree ${TRUNCPARENT}^{tree}`

    # Split the repository by grafting the TRUNCPARENT onto BASECOMMIT
    echo "${TRUNCPOINT} ${BASECOMMIT}" > .git/info/grafts
    git filter-branch -- --all

    # Finally, push the current rewritten master and associated branches to a new repository
    git push project-recent master
    git push project-recent B
}

create_repos
split_repo 

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ