Bir Git klasörünü geriye dönük olarak alt modüle dönüştür?


115

Çoğu zaman, bir tür proje yazıyorsunuzdur ve bir süre sonra, projenin bazı bileşenlerinin aslında bağımsız bir bileşen (belki bir kütüphane) olarak faydalı olduğu anlaşılır. Bu fikri erken yaşlarda yaşadıysanız, o zaman bu kodun çoğunun kendi klasöründe olma ihtimali oldukça yüksektir.

Git projesindeki alt dizinlerden birini alt modüle dönüştürmenin bir yolu var mı?

İdeal olarak bu, o dizindeki tüm kodun ana projeden kaldırılması ve alt modül projesinin tüm uygun geçmişle yerine eklenmesi ve tüm ana projenin taahhütlerinin doğru alt modül taahhütlerini göstereceği şekilde gerçekleşir. .


stackoverflow.com/questions/1365541/… bazılarına yardımcı olabilir :)
Rob Parker

Bu, orijinal sorunun bir parçası değil, ancak klasörün dışında başlayan ve klasörün içine taşınan dosyaların geçmişini tutmanın bir yolu daha da güzel olurdu. Şu anda, tüm cevaplar taşınmadan önceki tüm geçmişi kaybediyor.
naught101

2
@ ggll bağlantısı kesildi. İşte arşivlenmiş bir kopya.
s3cur3

Yanıtlar:


84

Bir alt dizini kendi deposunda izole etmek için filter-branch, orijinal deponun bir klonunda kullanın :

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

Bu durumda, orijinal dizininizi silmekten ve alt modülü ana projenize eklemekten başka bir şey değildir.


18
Muhtemelen git remote rm <name>filtre dalından sonra da yapmak ve sonra belki yeni bir uzaktan kumanda eklemek istersiniz . Ayrıca, yok sayılan dosyalar varsa, a git clean -xd -fyararlı olabilir
naught101

-- --allAlt modülün yalnızca bu daldan çıkarılması gerekiyorsa, bir dalın adı ile değiştirilebilir.
adius

git clone <your_project> <your_submodule>Yalnızca your_submodule için dosyalar mı indirilir?
Dominic

@DominicTobias: git clone source destinationGit'e klonlanan dosyalarınızı nereye koyacağınızı söyler. Alt modülünüzün dosyalarını filtrelemek için gerçek sihir daha sonra filter-branchadımda gerçekleşir.
knittl

filter-brancholduğu kaldırılmış bugünlerde. Kullanabilirsiniz git clone --filter, ancak Git sunucunuz filtrelemeye izin verecek şekilde yapılandırılmalıdır, aksi takdirde alırsınız warning: filtering not recognized by server, ignoring.
Matthias Braun

24

Önce, bir alt modül olacak klasöre değiştirin. Sonra:

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
Bu, o klasörün tüm geçmişini kaybedecek.
naught101

6
klasörün geçmişi ana depoya kaydedilecek ve yeni kayıtlar geçmişi alt modülde kaydedecek
zednight

11

Bunun eski bir ileti dizisi olduğunu biliyorum, ancak buradaki yanıtlar diğer dallardaki ilgili taahhütleri sıkıştırıyor.

Tüm bu ekstra dalları ve taahhütleri klonlamanın ve saklamanın basit bir yolu:

1 - Bu git takma adına sahip olduğunuzdan emin olun

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

2 - Uzaktan kumandayı klonlayın, tüm dalları çekin, uzaktan kumandayı değiştirin, dizininizi filtreleyin,

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
Orijinalimde kodu buraya
SO'ya

1

Yapılabilir, ancak basit değil. Eğer ararsanız git filter-branch, subdirectoryve submodule, süreçle ilgili bazı iyi yazma da olur. Esasen, projenizin iki klonunu oluşturmayı git filter-branch, bir alt dizin dışındaki her şeyi kaldırmayı ve diğerinde yalnızca bu alt dizini kaldırmayı gerektirir. Daha sonra ikinci depoyu ilkinin bir alt modülü olarak kurabilirsiniz.


0

Statüko

Kendi deposu repo-oldolan bir alt modüle dönüştürmek istediğimiz bir alt dizini içeren bir depomuz olduğunu varsayalım .subrepo-sub

Ayrıca, orijinal deponun , önceden var olan alt dizine dokunan tüm taahhütlerin artık çıkarılan alt modül depomuzun karşılık gelen taahhütlerini göstereceği repo-olddeğiştirilmiş bir depoya dönüştürülmesi amaçlanmaktadır .repo-newsubrepo-sub

Hadi değiştirelim

Bunu git filter-branchiki aşamalı bir süreçte gerçekleştirmek mümkündür:

  1. Den Alt Dizin çıkarma repo-oldiçin repo-sub(zaten kabul belirtilen cevap )
  2. Den Alt Dizin yedek repo-oldiçin repo-new(işlemek uygun haritalama ile)

Not : Bu sorunun eski olduğunu biliyorum ve bundan daha önce de belirtildiği gibi git filter-branch, kullanımdan kaldırılmış ve tehlikeli olabilir. Ancak diğer yandan, dönüştürmeden sonra doğrulanması kolay kişisel havuzlarla başkalarına yardımcı olabilir. Öyleyse uyarılmalıdır ! Ayrıca, kullanımdan kaldırılmadan aynı şeyi yapan ve kullanımı güvenli olan başka bir araç varsa lütfen bana bildirin!

Linux'ta her iki adımı da aşağıdaki 2.26.2 sürümüyle nasıl gerçekleştirdiğimi açıklayacağım. Eski sürümler bir miktar çalışabilir, ancak bunun test edilmesi gerekir.

Basitlik adına, orijinal depoda sadece bir masterşube ve bir originuzaktan kumanda olduğu durumla sınırlı kalacağım repo-old. Ayrıca temp_, işlem sırasında kaldırılacak ön ekli geçici git etiketlerine güvendiğim konusunda da uyarıda bulunun . Dolayısıyla, benzer şekilde adlandırılmış etiketler varsa, aşağıdaki öneki ayarlamak isteyebilirsiniz. Ve son olarak, bunu kapsamlı bir şekilde test etmediğimi ve tarifin başarısız olduğu bazı köşe durumları olabileceğini lütfen unutmayın. Lütfen devam etmeden önce her şeyi yedekleyin !

Aşağıdaki bash parçacıkları, daha sonra deponun repo-orgyaşadığı aynı klasörde çalıştırılması gereken büyük bir komut dosyası halinde birleştirilebilir . Her şeyi doğrudan bir komut penceresine kopyalayıp yapıştırmanız önerilmez (bunu başarıyla test etmiş olmama rağmen)!

0. Hazırlık

Değişkenler

# 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"

Komut dosyasını filtrele

# 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. Alt dizin çıkarma

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. Alt dizin değiştirme

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"

Not: Yeni oluşturulan depo repo-newsırasında takılırsa git submodule update --init, bunun yerine depoyu bir kez yinelemeli olarak yeniden klonlamayı deneyin:

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

Bu, dönüşümü yerinde yapar, herhangi bir filtre dalını yapacağınız gibi onu geri çekebilirsiniz (kullanıyorum git fetch . +refs/original/*:*).

utilsDiğer projelerde yararlı olmaya başlayan ve geçmişini alt modüllere ayırmak isteyen bir kütüphaneli bir projem var . İlk önce SO'ya bakmayı düşünmedim, bu yüzden kendi yazımı yazdım, geçmişi yerel olarak oluşturuyor, bu yüzden biraz daha hızlı, ardından isterseniz yardımcı komutun .gitmodulesdosyasını ve benzerlerini kurabilir ve alt modül geçmişlerini herhangi bir yere itebilirsiniz. İstediğiniz.

Soyulmuş komutun kendisi burada, dokümanlar yorumlarda, izleyen şeritlenmemiş komutta. Dizini bölüyormuşsunuz subdirgibi set ile kendi komutu olarak çalıştırın . Çatlak çünkü tek seferlik ama bunu Git geçmişindeki Documentation alt dizininde test ettim.subdir=utils git split-submoduleutils

#!/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}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.