Gitフォルダーを遡及的にサブモジュールに変換しますか?


115

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

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

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


stackoverflow.com/questions/1365541/…役立つかもしれません:)
Rob Parker

これは元の質問の一部ではありませんが、さらにクールなのは、フォルダーの外で開始され、フォルダーに移動されたファイルの履歴を保持する方法です。現時点では、すべての回答が移動前のすべての履歴を失っています。
naught101

2
@ggllのリンクがダウンしています。これがアーカイブされたコピーです。
s3cur3

回答:


84

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

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

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


18
あなたはおそらくもしたいgit remote rm <name>フィルタ分岐した後、その後、おそらく新しいリモートを追加します。また、無視されたファイルがあるgit clean -xd -f場合は、a が役立つ場合があります
naught101

-- --allサブモジュールをこのブランチからのみ抽出する必要がある場合は、ブランチの名前に置き換えることができます。
adius、2015年

git clone <your_project> <your_submodule>your_submodule用のファイルのみをダウンロードしてください
ドミニク

@DominicTobias:git clone source destinationクローンしたファイルを置く場所の場所をGitに伝えます。次に、サブモジュールのファイルをフィルタリングする実際の魔法がfilter-branchステップで発生します。
knittl 2017年

filter-branchは現在廃止されています。を使用できますがgit clone --filter、フィルタリングを許可するようにGitサーバーを構成する必要があります。そうしないと、次のようになります。warning: filtering not recognized by server, ignoring。ます。
マティアスブラウン

24

最初に、dirをサブモジュールになるフォルダーに変更します。次に:

git init
git remote add origin repourl
git add .
git commit -am'first commit in submodule'
git push -u origin master
cd ..
rm -rf folder wich will be a submodule
git commit -am'deleting folder'
git submodule add repourl folder wich will be a submodule
git commit -am'adding submodule'

9
これにより、そのフォルダの履歴がすべて失われます。
naught101

6
フォルダーの履歴はメインリポジトリに保存され、新しいコミットは履歴をサブモジュールに保存します
zednight

11

私はこれが古いスレッドであることを知っていますが、ここでの答えは他のブランチの関連するコミットを押しつぶします。

これらすべての余分なブランチとコミットを複製して保持する簡単な方法:

1-このgitエイリアスがあることを確認してください

git config --global alias.clone-branches '! git branch -a | sed -n "/\/HEAD /d; /\/master$/d; /remotes/p;" | xargs -L1 git checkout -t'

2-リモートの複製、すべてのブランチのプル、リモートの変更、ディレクトリのフィルタリング、プッシュ

git clone git@github.com:user/existing-repo.git new-repo
cd new-repo
git clone-branches
git remote rm origin
git remote add origin git@github.com:user/new-repo.git
git remote -v
git filter-branch --subdirectory-filter my_directory/ -- --all
git push --all
git push --tags

1
私のオリジナルには、SOにコードを埋め込む代わりに要旨へのリンクがありました
oodavid

1

それは可能ですが、簡単ではありません。あなたが検索した場合git filter-branchsubdirectoryおよびsubmodule、プロセス上のいくつかのまともな書き込みアップがあります。基本的に、プロジェクトの2つのクローンを作成しgit filter-branch、1つのサブディレクトリ以外のすべてを削除し、もう1つのサブディレクトリのみを削除することを伴います。次に、2番目のリポジトリを最初のリポジトリのサブモジュールとして確立できます。


0

現状

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

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

さあ変えよう

これはgit filter-branch、次の2つの手順で実現できます。

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

備考:この質問は古く、すでにgit filter-branch非推奨になっていて危険な可能性があることは既に述べられています。しかし、その一方で、変換後の検証が容易な個人用リポジトリで他の人を助けるかもしれません。ですから、警告してください!また、非推奨ではなく同じことを行う、安全に使用できる他のツールがあるかどうかをお知らせください。

以下のgitバージョン2.26.2を使用してLinuxで両方の手順を実現した方法を説明します。古いバージョンはある程度機能するかもしれませんが、テストする必要があります。

簡単にするために、元のリポジトリに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代わりにリポジトリを再帰的に1回再クローンしてみてください。

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"

0

これは変換をインプレースで行い、フィルターブランチと同じようにバックアウトできます(私はを使用していますgit fetch . +refs/original/*:*)。

utils他のプロジェクトで有用になり始めたライブラリーのあるプロジェクトがあり、その履歴をサブモジュールに分割したいと考えていました。最初にSOを見るとは思わなかったので、私は自分で独自に作成しました。履歴をローカルに構築するので、かなり高速です。その後、ヘルパーコマンドを設定できます。.gitmodulesファイルなどを、サブモジュールの履歴をどこにでもプッシュできます。あなたが欲しい。

ストリップされたコマンド自体はここにあり、ドキュメントはコメントにあり、その後のストリップされていないコマンドにあります。ディレクトリを分割する場合のsubdirように、それを独自のコマンドとして実行しsubdir=utils git split-submoduleますutils。1回限りなのでハッキーですが、Git履歴のDocumentationサブディレクトリでテストしました。

#!/bin/bash
# put this or the commented version below in e.g. ~/bin/git-split-submodule
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}
${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
    | git cat-file --batch-check='%(objectname)' | uniq`)
[[ $pathcheck = *:* ]] || {
    subfam=($( set -- ${fam[@]}; shift;
        for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
            git rev-parse -q --verify $tpar:"$subdir"
        done
    ))
    git rm -rq --cached --ignore-unmatch  "$subdir"
    if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
        git update-index --add --cacheinfo 160000,$subfam,"$subdir"
    else
        subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
            | git commit-tree $GIT_COMMIT:"$subdir" $(
                ${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
            ` &&
        git update-index --add --cacheinfo 160000,$subnew,"$subdir"
    fi
}
${debug+set +x}

#!/bin/bash
# Git filter-branch to split a subdirectory into a submodule history.

# In each commit, the subdirectory tree is replaced in the index with an
# appropriate submodule commit.
# * If the subdirectory tree has changed from any parent, or there are
#   no parents, a new submodule commit is made for the subdirectory (with
#   the current commit's message, which should presumably say something
#   about the change). The new submodule commit's parents are the
#   submodule commits in any rewrites of the current commit's parents.
# * Otherwise, the submodule commit is copied from a parent.

# Since the new history includes references to the new submodule
# history, the new submodule history isn't dangling, it's incorporated.
# Branches for any part of it can be made casually and pushed into any
# other repo as desired, so hooking up the `git submodule` helper
# command's conveniences is easy, e.g.
#     subdir=utils git split-submodule master
#     git branch utils $(git rev-parse master:utils)
#     git clone -sb utils . ../utilsrepo
# and you can then submodule add from there in other repos, but really,
# for small utility libraries and such, just fetching the submodule
# histories into your own repo is easiest. Setup on cloning a
# project using "incorporated" submodules like this is:
#   setup:  utils/.git
#
#   utils/.git:
#       @if _=`git rev-parse -q --verify utils`; then \
#           git config submodule.utils.active true \
#           && git config submodule.utils.url "`pwd -P`" \
#           && git clone -s . utils -nb utils \
#           && git submodule absorbgitdirs utils \
#           && git -C utils checkout $$(git rev-parse :utils); \
#       fi
# with `git config -f .gitmodules submodule.utils.path utils` and
# `git config -f .gitmodules submodule.utils.url ./`; cloners don't
# have to do anything but `make setup`, and `setup` should be a prereq
# on most things anyway.

# You can test that a commit and its rewrite put the same tree in the
# same place with this function:
# testit ()
# {
#     tree=($(git rev-parse `git rev-parse $1`: refs/original/refs/heads/$1));
#     echo $tree `test $tree != ${tree[1]} && echo ${tree[1]}`
# }
# so e.g. `testit make~95^2:t` will print the `t` tree there and if
# the `t` tree at ~95^2 from the original differs it'll print that too.

# To run it, say `subdir=path/to/it git split-submodule` with whatever
# filter-branch args you want.

# $GIT_COMMIT is set if we're already in filter-branch, if not, get there:
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}

${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
    | git cat-file --batch-check='%(objectname)' | uniq`)

[[ $pathcheck = *:* ]] || {
    subfam=($( set -- ${fam[@]}; shift;
        for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
            git rev-parse -q --verify $tpar:"$subdir"
        done
    ))

    git rm -rq --cached --ignore-unmatch  "$subdir"
    if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
        # one id same for all entries, copy mapped mom's submod commit
        git update-index --add --cacheinfo 160000,$subfam,"$subdir"
    else
        # no mapped parents or something changed somewhere, make new
        # submod commit for current subdir content.  The new submod
        # commit has all mapped parents' submodule commits as parents:
        subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
            | git commit-tree $GIT_COMMIT:"$subdir" $(
                ${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
            ` &&
        git update-index --add --cacheinfo 160000,$subnew,"$subdir"
    fi
}
${debug+set +x}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.