135

ある種のプロジェクトを作成していて、しばらくすると、プロジェクトの一部のコンポーネントが実際にはスタンドアロン コンポーネント (おそらくライブラリ) として有用であることが明らかになる場合がよくあります。早い段階からその考えを持っていた場合、そのコードのほとんどが独自のフォルダーにある可能性がかなりあります。

Git プロジェクトのサブディレクトリの 1 つをサブモジュールに変換する方法はありますか?

理想的には、そのディレクトリ内のすべてのコードが親プロジェクトから削除され、サブモジュール プロジェクトがその場所に追加され、すべての適切な履歴が追加され、すべての親プロジェクトのコミットが正しいサブモジュールのコミットを指すようになります。 .

4

7 に答える 7

98

サブディレクトリを独自のリポジトリに分離するにfilter-branchは、元のリポジトリのクローンで使用します。

git clone <your_project> <your_submodule>
cd <your_submodule>
git filter-branch --subdirectory-filter 'path/to/your/submodule' --prune-empty -- --all

元のディレクトリを削除し、サブモジュールを親プロジェクトに追加するだけです。

于 2012-09-20T15:13:38.777 に答える
3

現状

独自の repo を持つサブモジュールに変換したいサブディレクトリrepo-oldを含むというリポジトリがあると仮定しましょう。 subrepo-sub

repo-oldさらに、元のリポジトリを変更されたリポジトリに変換しrepo-new、以前に存在したサブディレクトリに触れるすべてのコミットsubが、抽出されたサブモジュール repo の対応するコミットを指すようにすることを意図していrepo-subます。

さあ変えよう

これはgit filter-branch、次の 2 段階のプロセスを使用して実現できます。

  1. repo-oldからへのサブディレクトリの抽出repo-sub(受け入れられた回答で既に言及されています)
  2. repo-oldからへのサブディレクトリの置換repo-new(適切なコミット マッピングを使用)

備考git filter-branch: この質問は古いものであり、非推奨であり、危険である可能性があることは既に言及されています。しかし一方で、変換後に簡単に検証できる個人用リポジトリを使用すると、他の人に役立つかもしれません. ですから、注意してください!また、廃止されずに安全に使用できる他のツールがあれば教えてください。

Linux で git バージョン 2.26.2 を使用して両方のステップをどのように実現したかを以下で説明します。古いバージョンはある程度機能する可能性がありますが、テストする必要があります。

master簡単にするために、元のリポジトリにブランチとoriginリモートだけがある場合に限定しrepo-oldます。temp_また、プロセスで削除されるプレフィックス付きの一時的な git タグに依存していることにも注意してください。したがって、同様の名前のタグが既にある場合は、以下のプレフィックスを調整することをお勧めします。そして最後に、私はこれを広範囲にテストしておらず、レシピが失敗するまれなケースがあるかもしれないことに注意してください. したがって、続行する前にすべてをバックアップしてください。

次の bash スニペットは、1 つの大きなスクリプトに連結できます。このスクリプトは、リポジトリが存在する同じフォルダーで実行する必要がありrepo-orgます。すべてをコマンド ウィンドウに直接コピー アンド ペーストすることはお勧めしません (テストは成功しましたが)。

0.準備

変数

# Root directory where repo-org lives
# and a temporary location for git filter-branch
root="$PWD"
temp='/dev/shm/tmp'

# The old repository and the subdirectory we'd like to extract
repo_old="$root/repo-old"
repo_old_directory='sub'

# The new submodule repository, its url
# and a hash map folder which will be populated
# and later used in the filter script below
repo_sub="$root/repo-sub"
repo_sub_url='https://github.com/somewhere/repo-sub.git'
repo_sub_hashmap="$root/repo-sub.map"

# The new modified repository, its url
# and a filter script which is created as heredoc below
repo_new="$root/repo-new"
repo_new_url='https://github.com/somewhere/repo-new.git'
repo_new_filter="$root/repo-new.sh"

フィルタ スクリプト

# The index filter script which converts our subdirectory into a submodule
cat << EOF > "$repo_new_filter"
#!/bin/bash

# Submodule hash map function
sub ()
{
    local old_commit=\$(git rev-list -1 \$1 -- '$repo_old_directory')

    if [ ! -z "\$old_commit" ]
    then
        echo \$(cat "$repo_sub_hashmap/\$old_commit")
    fi
}

# Submodule config
SUB_COMMIT=\$(sub \$GIT_COMMIT)
SUB_DIR='$repo_old_directory'
SUB_URL='$repo_sub_url'

# Submodule replacement
if [ ! -z "\$SUB_COMMIT" ]
then
    touch '.gitmodules'
    git config --file='.gitmodules' "submodule.\$SUB_DIR.path" "\$SUB_DIR"
    git config --file='.gitmodules' "submodule.\$SUB_DIR.url" "\$SUB_URL"
    git config --file='.gitmodules' "submodule.\$SUB_DIR.branch" 'master'
    git add '.gitmodules'

    git rm --cached -qrf "\$SUB_DIR"
    git update-index --add --cacheinfo 160000 \$SUB_COMMIT "\$SUB_DIR"
fi
EOF
chmod +x "$repo_new_filter"

1.サブディレクトリの抽出

cd "$root"

# Create a new clone for our new submodule repo
git clone "$repo_old" "$repo_sub"

# Enter the new submodule repo
cd "$repo_sub"

# Remove the old origin remote
git remote remove origin

# Loop over all commits and create temporary tags
for commit in $(git rev-list --all)
do
    git tag "temp_$commit" $commit
done

# Extract the subdirectory and slice commits
mkdir -p "$temp"
git filter-branch --subdirectory-filter "$repo_old_directory" \
                  --tag-name-filter 'cat' \
                  --prune-empty --force -d "$temp" -- --all

# Populate hash map folder from our previously created tag names
mkdir -p "$repo_sub_hashmap"
for tag in $(git tag | grep "^temp_")
do
    old_commit=${tag#'temp_'}
    sub_commit=$(git rev-list -1 $tag)

    echo $sub_commit > "$repo_sub_hashmap/$old_commit"
done
git tag | grep "^temp_" | xargs -d '\n' git tag -d 2>&1 > /dev/null

# Add the new url for this repository (and e.g. push)
git remote add origin "$repo_sub_url"
# git push -u origin master

2. サブディレクトリの置き換え

cd "$root"

# Create a clone for our modified repo
git clone "$repo_old" "$repo_new"

# Enter the new modified repo
cd "$repo_new"

# Remove the old origin remote
git remote remove origin

# Replace the subdirectory and map all sliced submodule commits using
# the filter script from above
mkdir -p "$temp"
git filter-branch --index-filter "$repo_new_filter" \
                  --tag-name-filter 'cat' --force -d "$temp" -- --all

# Add the new url for this repository (and e.g. push)
git remote add origin "$repo_new_url"
# git push -u origin master

# Cleanup (commented for safety reasons)
# rm -rf "$repo_sub_hashmap"
# rm -f "$repo_new_filter"

注意:新しく作成されたレポrepo-newが途中でハングする場合は、git submodule update --init代わりに再帰的に一度レポジトリを再クローンしてみてください:

cd "$root"

# Clone the new modified repo recursively
git clone --recursive "$repo_new" "$repo_new-tmp"

# Now use the newly cloned one
mv "$repo_new" "$repo_new-bak"
mv "$repo_new-tmp" "$repo_new"

# Cleanup (commented for safety reasons)
# rm -rf "$repo_new-bak"
于 2020-05-26T10:08:03.610 に答える
1

それはできますが、簡単ではありません。git filter-branchsubdirectoryおよびを検索するとsubmodule、プロセスに関するいくつかのまともな記事があります。基本的に、プロジェクトの 2 つのクローンを作成しgit filter-branch、1 つのサブディレクトリを除くすべてを 1 つから削除し、もう 1 つのサブディレクトリのみを削除する必要があります。次に、2 番目のリポジトリを最初のサブモジュールとして確立できます。

于 2012-09-20T15:08:29.733 に答える
0

@knittl による現在の回答でfilter-branchは、目的の効果にかなり近づけることができますが、試してみると、Git から警告が表示されました。

WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.

この質問が最初に尋ねられ、回答されてから 9 年が経過した現在、filter-branchは推奨されなくなりましたgit filter-repo。実際、 を使用して git の履歴を確認したgit log --all --oneline --graphところ、関係のないコミットでいっぱいでした。

じゃあどうやって使うのgit filter-repo?Github には、その概要を説明した非常に優れた記事があります。(gitとは別にインストールする必要があることに注意してください。私はpythonバージョンを使用しましたpip3 install git-filter-repo

彼らが記事を移動/削除することを決定した場合に備えて、以下の手順を要約して一般化します。

git clone <your_old_project_remote> <your_submodule>
cd <your_submodule>
git filter-repo --path path/to/your/submodule
git remote set-url origin <your_new_submodule_remote>
git push -u origin <branch_name>

そこから、新しいリポジトリを必要な場所にサブモジュールとして登録するだけです。

cd <path/to/your/parent/module>
git submodule add <your_new_submodule_remote>
git submodule update
git commit
于 2021-12-02T10:24:54.213 に答える