Łączenie wielu repozytoriów git

207

Powiedzmy, że mam konfigurację, która wygląda mniej więcej tak

phd/code/
phd/figures/
phd/thesis/

Ze względów historycznych wszystkie one mają swoje własne repozytoria git. Ale chciałbym połączyć je w jeden, aby trochę uprościć. Na przykład teraz mogę wprowadzić dwa zestawy zmian i muszę coś zrobić

cd phd/code
git commit 
cd ../figures
git commit

Byłoby (teraz) miło po prostu występować

cd phd
git commit

Wydaje się, że można to zrobić na kilka sposobów za pomocą submodułów lub pobierania z moich repozytoriów, ale jest to trochę bardziej skomplikowane niż szukam. Przynajmniej byłbym szczęśliwy

cd phd
git init
git add [[everything that's already in my other repositories]]

ale to nie wygląda na jedno-liniowe. Czy coś w gittym może mi pomóc?

Will Robertson
źródło
Weź również pod uwagę to świetne podejście: stackoverflow.com/questions/1425892/...
Johan Sjöberg
Weź
ptim
Join-git-repos.py skrypt robi dobrą robotę, jeśli mają oddzielne repozytoria, każdy z oddziałów mistrzowskich, które chcesz połączyć.
Mark

Odpowiedzi:

149

Oto rozwiązanie, które tutaj podałem :

  1. Najpierw wykonaj pełną kopię zapasową katalogu phd: nie chcę ponosić odpowiedzialności za utratę lat ciężkiej pracy! ;-)

    $ cp -r phd phd-backup
    
  2. Przenieś zawartość phd/codedo phd/code/codei napraw historię, aby wyglądała, jakby zawsze tam była (używa komendy git filter-branch ):

    $ cd phd/code
    $ git filter-branch --index-filter \
        'git ls-files -s | sed "s#\t#&code/#" |
         GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
         git update-index --index-info &&
         mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
    
  3. Sam za treść phd/figuresi phd/thesis(wystarczy wymienić codez figuresa thesis).

    Teraz struktura katalogów powinna wyglądać następująco:

    phd
      |_code
      |    |_.git
      |    |_code
      |         |_(your code...)
      |_figures
      |    |_.git
      |    |_figures
      |         |_(your figures...)
      |_thesis
           |_.git
           |_thesis
                |_(your thesis...)
    
  4. Następnie utwórz repozytorium git w katalogu głównym, przeciągnij do niego wszystko i usuń stare repozytoria:

    $ cd phd
    $ git init
    
    $ git pull code
    $ rm -rf code/code
    $ rm -rf code/.git
    
    $ git pull figures --allow-unrelated-histories
    $ rm -rf figures/figures
    $ rm -rf figures/.git
    
    $ git pull thesis --allow-unrelated-histories
    $ rm -rf thesis/thesis
    $ rm -rf thesis/.git
    

    Wreszcie powinieneś mieć teraz to, czego chciałeś:

    phd
      |_.git
      |_code
      |    |_(your code...)
      |_figures
      |    |_(your figures...)
      |_thesis
           |_(your thesis...)
    

Jedną fajną stroną tej procedury jest to, że pozostawi ona nie wersjonowane pliki i katalogi na miejscu.

Mam nadzieję że to pomoże.


Wystarczy jedno słowo ostrzeżenia: jeśli codekatalog ma już codepodkatalog lub plik, rzeczy może pójść bardzo źle (taki sam dla figuresi thesisoczywiście). W takim przypadku po prostu zmień nazwę tego katalogu lub pliku przed wykonaniem całej procedury:

$ cd phd/code
$ git mv code code-repository-migration
$ git commit -m "preparing the code directory for migration"

A po zakończeniu procedury dodaj ten ostatni krok:

$ cd phd
$ git mv code/code-repository-migration code/code
$ git commit -m "final step for code directory migration"

Oczywiście, jeśli codepodkatalog lub plik nie jest wersjonowany, użyj mvzamiast niego git mvi zapomnij o nim git commit.

MiniQuark
źródło
13
Dziękuję za ten fragment - zrobił dokładnie to, czego potrzebowałem (kiedyś rozliczyłem Mac OS X, nie przetwarzając „\ t” (zamiast tego musiałem użyć ^ V ^ I).
Craig Trader
6
Na początku nie mogłem tego uruchomić i ostatecznie znalazłem rozwiązanie problemu na innym starym forum. W ostatnim wierszu musiałem wstawić cudzysłowy wokół nazw plików w ten sposób: mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEADa potem zadziałało świetnie!
Jorin,
3
Funky polecenie filter-branch pochodzi ze stron man git z filter-branch. Powinieneś powiedzieć, że: a) należy go przypisać poprawnie b) Nie uruchomię takiego polecenia tylko dlatego, że ktoś, nawet o wysokiej reputacji, opublikował go na StackOverflow. Wiedząc, że pochodzi ze stron podręcznika.
tymtam
5
UWAŻAJ! MacOS X nie korzysta z rozszerzenia GNU sed, więc nie zna sekwencji \ t. Rezultatem jest popsuta historia! Moim rozwiązaniem było wklejenie kodu do pliku skryptu i napisanie w nim prawdziwego znaku <TAB>. W terminalu można wprowadzić zakładkę, naciskając ctrl + v, a następnie wpisując <TAB>. Nie próbowałem rozwiązania Craiga
Gil Vegliach
4
OBEJRZYJ (2)! Zauważ również, że jeśli niektóre pliki lub katalogi zawierają łączniki („-”), polecenie sed zakończy się niepowodzeniem. W takim przypadku możesz zastąpić go czymś w rodzaju „s ~ \ t ~ & code / ~”. Stosując tę ​​samą logikę, uważaj na „~” w nazwach
Gil Vegliach
75

git-stitch-repoprzetworzy dane wyjściowe git-fast-export --all --date-orderrepozytoriów git podane w wierszu poleceń i utworzy odpowiedni strumień git-fast-import, który utworzy nowe repozytorium zawierające wszystkie zatwierdzenia w nowym drzewie zatwierdzeń, które szanuje historię wszystkich repozytoriów źródłowych.

Arystoteles Pagaltzis
źródło
33
Uh, to narzędzie innej firmy, nie jest częścią git… :-)
Aristotle Pagaltzis,
1
Rzeczywiście, teraz mi mówisz :) No cóż, chyba musiałem nauczyć się instalować pakiety CPAN pewnego dnia…
Will Robertson,
1
Dzięki za wskazanie tego polecenia. Właśnie go użyłem, aby pomóc w przeniesieniu kilku repozytoriów z SVN do Git.
podpisano
1
OSTRZEŻENIE może nie działać, jeśli masz oddziały / fuzje! Ze strony git-stich-repo : „git-stich-repo działa idealnie z repozytoriami, które mają historię liniową (bez scalania). .. Udoskonalenia algorytmu szycia dodane w wersji 0.06 powinny sprawić, że nadaje się do pracy z repozytoriami posiadającymi oddziały i scala ”.
Bryan P
6
To jest zewnętrzny skrypt, odpowiedź jest za krótka i niezbyt pomocna, ten skrypt ma problemy z zatwierdzaniem scalania, niewiele osób poradziłoby sobie z Perlem lub CPAN i nie jest to dobrze wyjaśnione w odpowiedzi. Więc ... -1 przepraszam.
Haralan Dobrev
20

Być może po prostu (podobnie jak w poprzedniej odpowiedzi, ale przy użyciu prostszych poleceń) dokonanie w każdym z oddzielnych starych repozytoriów zatwierdzenia, które przenosi zawartość do odpowiednio nazwanego podkatalogu, np .:

$ cd phd/code
$ mkdir code
# This won't work literally, because * would also match the new code/ subdir, but you understand what I mean:
$ git mv * code/
$ git commit -m "preparing the code directory for migration"

a następnie łącząc trzy oddzielne repo w jeden nowy, wykonując coś takiego:

$ cd ../..
$ mkdir phd.all
$ cd phd.all
$ git init
$ git pull ../phd/code
...

Następnie zapiszesz swoje historie, ale przejdzie do jednego repozytorium.

imz - Ivan Zakharyaschev
źródło
Jest to w porządku, ale jeśli scalasz jedno repozytorium w inne (tj. Phd było już istniejącym repozytorium), to jeśli phd miał foldery o takich samych nazwach jak podfoldery w katalogu kodu, napotkasz problemy jako „git pull .. / phd / code 'pobiera wszystkie commity ze ścieżkami orignalnymi i dopiero na końcu stosuje mv commit.
tymtam
1
@Tymek: ale nadal będzie działać bez problemów. Rzeczą, która nie będzie miła, jest to, że ścieżki w historii nie będą „poprawne” (odpowiadają nowym ścieżkom).
imz - Ivan Zakharyaschev
19

Możesz wypróbować strategię scalania poddrzewa . Pozwoli ci to połączyć repozytorium B w repozytorium A. Przewaga git-filter-branchpolega na tym, że nie wymaga przepisania historii repozytorium A (zerwanie sum SHA1).

Leif Gruenwoldt
źródło
Link nie działa, a to nie zachowałoby historii, prawda?
tymtam
3
@Tymek (Niestety niektóre części kernel.org są nadal niedostępne po naruszeniu bezpieczeństwa). Łamie SHA1 nadchodzącego repozytorium B. Ale A pozostaje nienaruszony.
Leif Gruenwoldt,
2
Oto lustro tego dokumentu na razie ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/howto/…
Leif Gruenwoldt
1
@LeifGruenwoldt Pierwszy link już działa. Link lustrzany zniknął, jak sądzę, powinieneś go usunąć.
Vadim Kotov
9

Rozwiązanie git-filter-branch działa dobrze, ale pamiętaj, że jeśli twoje repozytorium git pochodzi z importu SVN, może się nie powieść z komunikatem:

Rewrite 422a38a0e9d2c61098b98e6c56213ac83b7bacc2 (1/42)mv: cannot stat `/home/.../wikis/nodows/.git-rewrite/t/../index.new': No such file or directory

W takim przypadku należy wykluczyć wstępną wersję z gałęzi filter - tj. Zmienić HEADna końcu na [SHA of 2nd revision]..HEAD- patrz:

http://www.git.code-experiments.com/blog/2010/03/merging-git-repositories.html

Gareth
źródło
2
Dziękuję Ci! Drapałem się po głowie, dlaczego to nie działa! Repozytorium rzeczywiście pochodziło od SVN.
Arthur Maltson
1
Ten sam błąd, kiedy to robię. Mam nadzieję. Link jest teraz zepsuty.
Ryan
Czy mógłbyś wyjaśnić, co miałeś na myśli mówiąc „zmiana głowy na ...”, moje repozytorium pochodzi z importu SVN i mam do czynienia właśnie z tym problemem, bardzo doceniłbym pomoc!
5

Rozwiązanie @MiniQuark bardzo mi pomogło, ale niestety nie uwzględnia tagów znajdujących się w repozytoriach źródłowych (przynajmniej w moim przypadku). Poniżej moja poprawa do odpowiedzi @MiniQuark.

  1. Najpierw utwórz katalog, który będzie zawierał złożone repozytorium i połączone repozytorium, utwórz katalog dla każdego scalonego repozytorium.

    $ mkdir nowy_phd
    $ mkdir nowy_phd / kod
    $ mkdir nowy_phd / dane
    $ mkdir nowy_phd / praca

  2. Wykonaj wyciąg z każdego repozytorium i pobierz wszystkie tagi. (Prezentowanie instrukcji tylko dla codepodkatalogu)

    $ cd new_phd / code
    $ git init
    $ git pull ../../original_phd/code master
    $ git fetch ../../original_phd/code refs / tags / *: refs / tags / *

  3. (Jest to poprawka do punktu 2 w odpowiedzi MiniQuark) Przenieś zawartość new_phd/codedonew_phd/code/code i dodaj code_prefiks przed każdym tagiem

    $ git filter-branch --index-filter 'git ls-files -s | sed "s- \ t \" * - & code / - "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv $ GIT_INDEX_FILE.new $ GIT_INDEX_FILE '--tag-name-filter' sed" s -. * - kod _ i - „” GŁOWA

  4. Po wykonaniu tej czynności będzie dwa razy więcej tagów niż przed wykonaniem odgałęzienia filtru. Stare tagi pozostają w repozytorium i code_dodawane są nowe tagi z prefiksem.

    $ git tag
    mytag1
    code_mytag1

    Usuń stare tagi ręcznie:

    $ ls .git / refs / tags / * | grep -v "/ code_" | xargs rm

    Powtórz pkt 2,3,4 dla innych podkatalogów

  5. Teraz mamy strukturę katalogów jak w @MiniQuark w punkcie 3.

  6. Postępuj jak w punkcie 4 anwsera MiniQuark, ale po pociągnięciu i przed usunięciem .gitreż, pobierz tagi:

    $ git fetch katalog refs / tags / *: refs / tags / *

    Kontyntynuj..

To tylko kolejne rozwiązanie. Mam nadzieję, że to komuś pomaga, pomogło mi :)

MichK
źródło
5

git-stitch-repo z odpowiedzi Arystotelesa Pagaltzisa działa tylko w przypadku repozytoriów o prostej, liniowej historii.

Odpowiedź MiniQuark działa dla wszystkich repozytoriów, ale nie obsługuje tagów i gałęzi.

Stworzyłem program, który działa w taki sam sposób, jak opisuje MiniQuark, ale używa jednego zatwierdzenia scalania (z N rodzicami), a także odtwarza wszystkie znaczniki i gałęzie, aby wskazać te zatwierdzenia scalania.

Zobacz repozytorium git-merge-repos, aby dowiedzieć się, jak z niego korzystać.

robinst
źródło
3

Stworzyłem narzędzie, które sprawia, że ​​to zadanie. Zastosowana metoda jest podobna (wewnętrznie tworzą niektóre rzeczy, takie jak --filter-branch), ale jest bardziej przyjazna. Jest GPL 2.0

http://github.com/geppo12/GitCombineRepo

Giuseppe Monteleone
źródło
3

W rzeczywistości git-stitch-repo obsługuje teraz gałęzie i tagi, w tym tagi z adnotacjami (znalazłem błąd, który zgłosiłem i został naprawiony). Za przydatne uważałem tagi. Ponieważ tagi są dołączane do zatwierdzeń, a niektóre rozwiązania (takie jak podejście Erica Lee) nie radzą sobie z tagami. Próbujesz utworzyć gałąź z zaimportowanego znacznika, a ona cofnie wszelkie połączenia / przeniesienia git i odeśle cię tak, jakby skonsolidowane repozytorium było prawie identyczne z repozytorium, z którego pochodzi znacznik. Istnieją również problemy, jeśli używasz tego samego tagu w wielu repozytoriach, które „scaliłeś / skonsolidowałeś”. Na przykład, jeśli masz repozytorium A reklama B, oba mają tag rel_1.0. Scalasz repozytorium A i repozytorium B w repozytorium AB. Ponieważ znaczniki rel_1.0 dotyczą dwóch różnych zatwierdzeń (jeden dla A i jeden dla B), który tag będzie widoczny w AB? Znacznik z zaimportowanego repozytorium A lub z importowanego repozytorium B, ale nie oba.

git-stitch-repo pomaga rozwiązać ten problem, tworząc tagi rel_1.0-A i rel_1.0-B. Możesz nie być w stanie wyewidencjonować tag rel_1.0 i oczekiwać obu, ale przynajmniej możesz zobaczyć oba i teoretycznie możesz scalić je we wspólny oddział lokalny, a następnie utworzyć tag rel_1.0 w połączonym oddziale (zakładając, że po prostu Scal i nie zmieniaj kodu źródłowego). Lepiej jest pracować z oddziałami, ponieważ można łączyć jak oddziały z każdego repozytorium w oddziały lokalne. (dev-a i dev-b można połączyć w lokalną gałąź programistyczną, którą następnie można zepchnąć do źródła).

użytkownik3622356
źródło
2

Sekwencja, którą zasugerowałeś

git init
git add *
git commit -a -m "import everything"

zadziała, ale stracisz swoją historię zmian.

Patrick_O
źródło
Utrata historii nie jest taka zła, ale ponieważ repozytorium jest dla mojej własnej pracy (tj. Jest prywatne), jest tam wiele rzeczy, których nie chcę wersji lub które jeszcze nie są wersjonowane.
Will Robertson,
1

Aby scalić drugi projekt w ramach głównego projektu:

A) W drugim projekcie

git fast-export --all --date-order > /tmp/secondProjectExport

B) W głównym projekcie:

git checkout -b secondProject
git fast-import --force < /tmp/secondProjectExport

W tej gałęzi dokonaj wszystkich ciężkich transformacji, które musisz zrobić, i dokonaj ich.

C) Następnie z powrotem do mistrza i klasyczne połączenie dwóch gałęzi:

git checkout master
git merge secondProject
użytkownik 123568943685
źródło
Spowodowałoby to scalenie wszystkich plików i folderów w katalogu głównym obu projektów git w jeden projekt. Wątpię, czy ktokolwiek chciałby, aby tak się stało.
Clintm,
0

Wrzucę tu również moje rozwiązanie. Zasadniczo jest to dość proste opakowanie skryptu bashgit filter-branch . Podobnie jak inne rozwiązania, migruje tylko główne gałęzie i nie migruje tagów. Ale pełne historie zatwierdzeń głównych są migrowane i jest to krótki skrypt bash, więc użytkownicy powinni względnie łatwo przejrzeć lub poprawić.

https://github.com/Oakleon/git-join-repos

chrishiestand
źródło
0

Ten skrypt bash działa wokół problemu znaków tabulacji sed (na przykład w systemie MacOS) i problemu brakujących plików.

export SUBREPO="subrepo"; # <= your subrepository name here
export TABULATOR=`printf '\t'`;
FILTER='git ls-files -s | sed "s#${TABULATOR}#&${SUBREPO}/#" |
  GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
  git update-index --index-info &&
  if [ -f "$GIT_INDEX_FILE.new" ]; then mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE; else echo "git filter skipped missing file: $GIT_INXEX_FILE.new"; fi'

git filter-branch --index-filter "$FILTER" HEAD

Jest to kombinacja postów miniquark , marius-butuc i ryan . Pozdrawiam ich!

bue
źródło