2010-05-09 15 views
17

To, czego chcę, jest podobne do this question. Jednakże chcę katalog, który jest podzielony na oddzielnym repo pozostanie podkatalogu w tym repo:Jak podzielić repozytorium git przy zachowaniu podkatalogów?

mam to:

foo/ 
    .git/ 
    bar/ 
    baz/ 
    qux/ 

I chcę, aby podzielić je na dwie całkowicie niezależne repozytoria:

foo/ 
    .git/ 
    bar/ 
    baz/ 

quux/ 
    .git/ 
    qux/ # Note: still a subdirectory 

Jak to zrobić w git?

Mogę skorzystać z metody z this answer, jeśli istnieje sposób przeniesienia całej zawartości nowego repo do podkatalogu, w historii.

Odpowiedz

16

Można rzeczywiście użyć filtra podkatalogu, po którym następuje filtr indeksu, aby umieścić zawartość z powrotem w podkatalogu, ale po co zawracać sobie głowę, gdy można po prostu użyć samego filtra indeksu?

Oto przykład ze strony człowieka:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD 

To tylko usuwa jednego pliku; to, co chcesz zrobić, to usunąć wszystko oprócz podanego podkatalogu. Jeśli chcesz być ostrożnym, można jawnie wymienić każdą ścieżkę do usuwania, ale jeśli chcesz po prostu iść all-in, można po prostu zrobić coś takiego:

git filter-branch --index-filter 'git ls-tree -z --name-only --full-tree $GIT_COMMIT | grep -zv "^directory-to-keep$" | xargs -0 git rm --cached -r' -- --all 

Spodziewam się tam pewnie bardziej elegancki sposób ; jeśli ktokolwiek coś ma, sugeruj to!

Kilka uwag na temat tego polecenia:

  • filtra oddział wewnętrznie wyznacza GIT_COMMIT do prądu popełnić SHA1
  • bym nie oczekiwano --full-tree być konieczne, ale widocznie filtrować-gałąź biegnie indeks -filter z katalogu .git-rewrite/t zamiast najwyższego poziomu repozytorium.
  • grep to prawdopodobnie przesada, ale nie sądzę, że jest to problem z szybkością.
  • --all stosuje się do wszystkich informacji; Myślę, że naprawdę tego chcesz. (-- oddziela go od opcji gałęzi filtru)
  • -z i -0 powiedz ls-tree, grep i xargs, aby używały terminacji NUL do obsługi spacji w nazwach plików.

Edytuj, znacznie później: Thomas zasugerował sposób usunięcia pustych już zatwierdzeń, ale jest już nieaktualny. Spójrz na historii edycji jeśli masz starą wersję git, ale z nowoczesnym git, wszystko co musisz zrobić, to przyczepność na tej opcji:

--prune-empty 

To będzie usunąć wszystkie rewizje, które są puste po zastosowanie filtra indeksu.

+0

Oprócz zagnieżdżonych pojedynczych cytatów (które mogłem zastąpić), działało prawie idealnie. Jedynym problemem było to, że puste wpisy do nieistniejących katalogów pozostały w dzienniku. Usunąłem je za pomocą 'git filter-branch -f --commit-filter ', jeśli [z $ 1 = z \' git rev-parse $ 3^{tree} \ ']; następnie skip_commit "$ @"; else git commit-tree "$ @"; fi '"$ @" ', które znalazłem na http://github.com/jwiegley/git-scripts/blob/master/git-remove-empty-commits – Thomas

+0

@Thomas: Dzięki za naprawienie mojego nieostrożnego błędu! Ponadto powinieneś być w stanie użyć filtra zatwierdzania w tej samej komendzie, co filtr indeksu. Filtry są uruchamiane w kolejności pokazanej w dokumentacji; commit-filter jest naturalnie po filtrach, które modyfikują zawartość commit. Prawdopodobnie chcesz również użyć '--remap-to-ancestor', co spowoduje, że referencje, które wskazują pominięte zatwierdzenia, zostaną przeniesione do najbliższego przodka zamiast ich wykluczenia. – Cascabel

+0

@Jefromi: argument 'index-filter' powinien być łatwiej wyrażalny jako' git rm -r -f --cached --ignore-unmatch $ (ls! (Directory-to-keep)) ', zobacz moje odpowiedzi http : //stackoverflow.com/a/8079852/396967 i http://stackoverflow.com/a/7849648/396967 – kynan

3

To, co skończyło się robi, aby rozwiązać ten problem, gdy miałem to sam:

git filter-branch --index-filter \ 
'git ls-tree --name-only --full-tree $GIT_COMMIT | \ 
grep -v "^directory-to-keep$" | \ 
sed -e "s/^/\"/g" -e "s/$/\"/g" | \ 
xargs git rm --cached -r -f --ignore-unmatch \ 
' \ 
--prune-empty -- --all 

Rozwiązanie bazuje na odpowiedź Jefromi i na Detach (move) subdirectory into separate Git repository oraz wielu komentarzach tutaj na SO.

Powodem, dla którego rozwiązanie Jefromi nie zadziałało, było to, że w repozytorium miałem pliki i foldery, których nazwy zawierały specjalne znaki (głównie spacje). Dodatkowo git rm skarżył się na niedopasowane pliki (rozwiązane z --ignore-unmatch).

Możesz zachować filtrowanie agnostykiem do katalogu, nie będąc w korzeniu repo lub przemieszczane są wokół:

grep --invert-match "^.*directory-to-keep$" 

I wreszcie, można to wykorzystać, aby odfiltrować stałą podzbiór plików lub katalogów:

egrep --invert-match "^(.*file-or-directory-to-keep-1$|.*file-or-directory-to-keep-2$|…)" 

Aby oczyścić potem można użyć następujących poleceń:

$ git reset --hard 
$ git show-ref refs/original/* --hash | xargs -n 1 git update-ref -d 
$ git reflog expire --expire=now --all 
$ git gc --aggressive --prune=now 
3

Chciałem zrobić coś podobnego, ale ponieważ lista plików, które chciałem zachować, była dość długa, nie było sensu robić tego za pomocą niezliczonej liczby grepsów. Napisałem skrypt, który czyta listę plików z pliku:

#!/bin/bash 

# usage: 
# git filter-branch --prune-empty --index-filter \ 
# 'this-script file-with-list-of-files-to-be-kept' -- --all 

if [ -z $1 ]; then 
    echo "Too few arguments." 
    echo "Please specify an absolute path to the file" 
    echo "which contains the list of files that should" 
    echo "remain in the repository after filtering." 
    exit 1 
fi 

# save a list of files present in the commit 
# which is currently being modified. 
git ls-tree -r --name-only --full-tree $GIT_COMMIT > files.txt 

# delete all files that shouldn't be removed 
while read string; do 
    grep -v "$string" files.txt > files.txt.temp 
    mv -f files.txt.temp files.txt 
done < $1 

# remove unwanted files (i.e. everything that remained in the list). 
# warning: 'git rm' will exit with non-zero status if it gets 
# an invalid (non-existent) filename OR if it gets no arguments. 
# If something exits with non-zero status, filter-branch will abort. 
# That's why we have to check carefully what is passed to git rm. 
if [ "$(cat files.txt)" != "" ]; then 
    cat files.txt | \ 
    # enclose filenames in "" in case they contain spaces 
    sed -e 's/^/"/g' -e 's/$/"/g' | \ 
    xargs git rm --cached --quiet 
fi 

Dość zaskakująco, okazało się to być o wiele więcej pracy, niż początkowo oczekiwano, więc postanowiłem opublikować go tutaj.

+1

Wielkie dzięki za udostępnienie! To zadziałało dla mnie podczas testu repo. Dodałem także "if [" $ (cat $ 1) "==" "]; następnie echo "Brak treści w pliku wykluczonym" exit 1 fi', aby sprawdzić, czy podany plik istnieje. Również wydaje się, że trzeba podać pełną ścieżkę do pliku wykluczającego. – Denis

+0

p.s. również, wyklucz plik powinien mieć ostatni wiersz pusty/śmieci. – Denis

1

Czystsze metoda:

git filter-branch --index-filter ' 
       git read-tree --empty 
       git reset $GIT_COMMIT path/to/dir 
     ' \ 
     -- --all -- path/to/dir 

lub trzymać się tylko podstawowych komend, SUB w git read-tree --prefix=path/to/dir/ $GIT_COMMIT:path/to/dir do resetu.

Podanie path/to/dir na liście argumentów na początku listy powoduje, że przycinanie wcześnie, z filtrem tak taniego, nie ma większego znaczenia, ale i tak dobrze jest uniknąć zmarnowanego wysiłku.

Powiązane problemy