Scal dwa repozytoria Git bez przerywania historii plików

226

Muszę połączyć dwa repozytoria Git z nowym, trzecim repozytorium. Znalazłem wiele opisów tego, jak to zrobić za pomocą scalania poddrzewa (na przykład odpowiedź Jakuba Narębskiego na temat Jak scalić dwa repozytoria Git? ) I przestrzeganie tych instrukcji w większości działa, z tym wyjątkiem, że kiedy zatwierdzam poddrzewo, scalam wszystkie pliki ze starych repozytoriów są zapisywane jako nowo dodane pliki. Widzę historię zatwierdzeń ze starych repozytoriów, kiedy to robię git log, ale jeśli to zrobię git log <file>, pokazuje tylko jedno zatwierdzenie dla tego pliku - scalanie poddrzewa. Sądząc po komentarzach do powyższej odpowiedzi, nie jestem sam widząc ten problem, ale nie znalazłem opublikowanych rozwiązań tego problemu.

Czy istnieje sposób scalenia repozytoriów i pozostawienia nienaruszonej historii poszczególnych plików?

Eric Lee
źródło
Nie używam Git, ale w Mercurial najpierw wykonam konwersję, jeśli to konieczne, aby naprawić ścieżki plików repozytoriów, które mają zostać scalone, a następnie zmuszam jedno repozytorium do celu, aby uzyskać zestawy zmian, a następnie wykonaj połączenie różnych gałęzi. Jest to przetestowane i działa;) Może to pomaga znaleźć rozwiązanie również dla Gita ... w porównaniu z metodą scalania poddrzewa Podejrzewam, że krok konwersji jest inny, gdy historia jest przepisywana zamiast po prostu mapować ścieżkę (jeśli rozumiem prawidłowo). Zapewnia to płynne scalanie bez specjalnej obsługi ścieżek plików.
Lucero
Znalazłem też to pytanie pomocne stackoverflow.com/questions/1683531/...
nacross
Stworzyłem pytanie uzupełniające. Może to być interesujące: Scal dwa repozytoria Git i zachowaj główną historię: stackoverflow.com/questions/42161910/…
Dimitri Dewaele
Zautomatyzowane rozwiązanie, które działało dla mnie, to stackoverflow.com/a/30781527/239408
xverges

Odpowiedzi:

269

Okazuje się, że odpowiedź jest znacznie prostsza, jeśli po prostu próbujesz skleić dwa repozytoria razem i sprawić, by wyglądało to tak od samego początku, zamiast zarządzać zewnętrzną zależnością. Musisz po prostu dodać piloty do swoich starych repozytoriów, połączyć je z nowym masterem, przenieść pliki i foldery do podkatalogu, zatwierdzić przeniesienie i powtórzyć dla wszystkich dodatkowych repozytoriów. Podmoduły, scalanie poddrzewa i fantazyjne rebazy mają na celu rozwiązanie nieco innego problemu i nie są odpowiednie do tego, co próbowałem zrobić.

Oto przykładowy skrypt Powershell do sklejenia dwóch repozytoriów:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

Oczywiście możesz zamiast tego połączyć old_b w old_a (który staje się nowym połączonym repozytorium), jeśli wolisz to zrobić - zmodyfikuj skrypt, aby pasował.

Jeśli chcesz również przenieść gałęzie funkcji w toku, użyj tego:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

To jedyna nieoczywista część procesu - nie jest to scalanie poddrzewa, ale raczej argument na rzecz normalnego scalania rekurencyjnego, które mówi Gitowi, że zmieniliśmy nazwę celu i pomaga Gitowi poprawnie wszystko wyrównać.

Tutaj napisałem nieco bardziej szczegółowe wyjaśnienie .

Eric Lee
źródło
16
użycie tego rozwiązania git mvnie działa tak dobrze. kiedy później użyjesz git logjednego z przeniesionych plików, otrzymasz tylko zatwierdzenie z przeniesienia. cała poprzednia historia została utracona. to dlatego, że git mvtak naprawdę jest git rm; git addtylko jeden krok .
mholm815
15
Jest to to samo, co każda inna operacja przenoszenia / zmiany nazwy w Git: z wiersza poleceń możesz uzyskać całą historię, wykonując tę ​​czynność git log --follow, lub wszystkie narzędzia GUI robią to automatycznie. O ile wiem, dzięki scaleniu poddrzewa nie można uzyskać historii poszczególnych plików, więc ta metoda jest lepsza.
Eric Lee
3
@EricLee Po połączeniu repozytorium old_b pojawia się wiele konfliktów scalania. Czy to jest oczekiwane? Dostaję KONFLIKT (zmiana nazwy / usunięcie)
Jon
9
Kiedy próbuję „dir -exclude old_a |% {git mv $ _. Name old_a}”, otrzymuję sh.exe ": katalog: nie znaleziono polecenia i sh.exe": git: polecenie nie znaleziono. Korzystanie z tego działa: ls -I old_a | xargs -I '{}' git mv '{}' old_a /
George
5
To jest 1(numer jeden) lsi wielkie „oko” xargs. Dziękuję za tę wskazówkę!
Dominique Vial
149

Oto sposób, który nie przepisuje żadnej historii, więc wszystkie identyfikatory zatwierdzeń pozostaną ważne. W rezultacie pliki drugiego repozytorium znajdą się w podkatalogu.

  1. Dodaj drugie repozytorium jako zdalne:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. Upewnij się, że pobrałeś wszystkie zatwierdzenia Secondrepo:

    git fetch secondrepo
    
  3. Utwórz oddział lokalny z drugiego oddziału repozytorium:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Przenieś wszystkie swoje pliki do podkatalogu:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. Scal drugą gałąź z gałęzią główną pierwszego repozytorium:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

Twoje repozytorium będzie miało więcej niż jedno zatwierdzenie główne, ale to nie powinno stanowić problemu.

Flimm
źródło
1
Krok 2 nie działa dla mnie: fatal: Niepoprawna nazwa obiektu: „secondrepo / master”.
Keith
@Keith: upewnij się, że dodałeś drugie repozytorium jako zdalne o nazwie „secondrepo” i że to repozytorium ma gałąź o nazwie „master” (możesz przeglądać gałęzie na zdalnym repozytorium za pomocą polecenia git remote show secondrepo)
Flimm
Musiałem też zrobić pobranie, żeby to obniżyć. Pomiędzy 1 a 2 zrobiłem fare secondrepo
sksamuel
@monkjack: Zredagowałem swoją odpowiedź, aby uwzględnić krok pobierania git. W przyszłości możesz samodzielnie edytować odpowiedź.
Flimm
4
@MartijnHeemels W przypadku starszej wersji Git po prostu pomiń --allow-unrelated-histories. Zobacz historię tego posta z odpowiedzią.
Flimm,
8

Minęło kilka lat i są dobrze oparte na dobrze ocenianych rozwiązaniach, ale chcę udostępnić moje, ponieważ było trochę inaczej, ponieważ chciałem połączyć 2 zdalne repozytoria w nowe bez usuwania historii z poprzednich repozytoriów.

  1. Utwórz nowe repozytorium w Github.

    wprowadź opis zdjęcia tutaj

  2. Pobierz nowo utworzone repozytorium i dodaj stare zdalne repozytorium.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. Pobierz wszystkie pliki ze starego repozytorium, aby utworzyć nowy oddział.

    git fetch OldRepo
    git branch -a
    

    wprowadź opis zdjęcia tutaj

  4. W gałęzi master wykonaj scalenie, aby połączyć stare repozytorium z nowo utworzonym.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    wprowadź opis zdjęcia tutaj

  5. Utwórz nowy folder do przechowywania całej nowo utworzonej zawartości, która została dodana z OldRepo i przenieś swoje pliki do tego nowego folderu.

  6. Na koniec możesz przesłać pliki z połączonych repozytoriów i bezpiecznie usunąć OldRepo z GitHub.

Mam nadzieję, że może to być przydatne dla każdego, kto zajmuje się łączeniem zdalnych repozytoriów.

abautista
źródło
1
To jedyne rozwiązanie, które działało dla mnie, aby zachować historię git. Nie zapomnij usunąć zdalnego łącza do starego repozytorium za pomocą git remote rm OldRepo.
Harubiyori
7

proszę spojrzeć na korzystanie

git rebase --root --preserve-merges --onto

połączyć dwie historie na wczesnym etapie życia.

Jeśli masz ścieżki, które się pokrywają, napraw je

git filter-branch --index-filter

podczas korzystania z dziennika upewnij się, że „trudniej znaleźć kopie”

git log -CC

w ten sposób znajdziesz wszelkie ruchy plików na ścieżce.

Adam Dymitruk
źródło
Dokumentacja Git zaleca, aby nie dokonywać ponownej
Stephen Turner
7

I okazało się, że rozwiązanie z @Flimm to do git aliastak (Dodane do moich ~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"
Fredrik Erlandsson
źródło
12
Ciekawe: czy naprawdę robisz to wystarczająco często, aby potrzebować aliasu?
Parker Coates
1
Nie, ale nigdy nie pamiętam, jak to zrobić, więc alias jest dla mnie tylko sposobem na zapamiętanie tego.
Fredrik Erlandsson,
1
Tak .. ale spróbuj zmienić komputery i zapomnij przenieść aliasy;)
quetzalcoatl
1
Jaka jest wartość $GIT_PREFIX?
neowulf33
github.com/git/git/blob/… „GIT_PREFIX” jest ustawiany jako zwracany przez uruchomienie „git rev-parse --show-prefix” z oryginalnego katalogu bieżącego. Zobacz linkgit: git-rev-parse [1].
Fredrik Erlandsson
3

Ta funkcja sklonuje zdalne repozytorium do lokalnego katalogu repo:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Jak używać:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Ogłoszenie. Ten skrypt może przepisać zatwierdzenia, ale zapisze wszystkich autorów i daty, oznacza to, że nowe zatwierdzenia będą miały kolejne skróty, a jeśli spróbujesz wypchnąć zmiany na zdalny serwer, będzie mógł to zrobić tylko za pomocą klucza wymuszenia, a także przepisać zatwierdzenia na serwerze. Zrób więc kopie zapasowe przed uruchomieniem.

Zysk!

Andrey Izman
źródło
Używam zsh zamiast bash i v2.13.0 z git. Bez względu na to, czego próbowałem, nie byłem w stanie zabrać się git filter-branch --index-filterdo pracy. Zazwyczaj pojawia się komunikat o błędzie, że plik indeksu .new nie istnieje. Czy to dzwoni?
Patrick Beard,
@PatrickBeard Nie znam zsh, możesz utworzyć oddzielny plik git-add-repo.shz funkcją powyżej, na końcu pliku wstaw tę linię git-add-repo "$@". Następnie możesz go używać z Zsh jak cd current/git/packageibash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
Andrey Izman
Problem został omówiony tutaj: stackoverflow.com/questions/7798142/... mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" czasami się nie udaje, więc musisz dodać if test.
Patrick Beard
1
Nie użyłbym tej metody! Spróbowałem skryptu, naiwnie i dosłownie (mogę winić się tylko za tę część), a to zablokowało moje lokalne repozytorium git. Historia wyglądała w większości dobrze, ale wykonanie git push z powrotem do Github spowodowało przerażające „RPC nie powiodło się; curl 55 SSL_write () zwrócił błąd SYSCALL, errno = 32”. Próbowałem go naprawić, ale został nieodwracalnie zepsuty. Skończyło się na tym, że musiałem zrekonstruować rzeczy w nowym lokalnym repozytorium.
Mason Freed
@MasonFreed ten skrypt tworzy nową historię gitów z kombinacją obu repozytoriów, więc nie można go wypchnąć na stare repozytorium, wymaga utworzenia nowej lub wypchnięcia klawiszem siły, co oznacza, że ​​przepisuje repozytorium na serwerze
Andrey Izman
2

Postępuj zgodnie z instrukcjami, aby osadzić jedno repo w innym repo, mając jedną historię git, łącząc obie historie git.

  1. Sklonuj oba repozytoria, które chcesz scalić.

git clone [email protected]: użytkownik / rodzic-repo.git

git clone [email protected]: user / child-repo.git

  1. Przejdź do repozytorium dzieci

cd child-repo /

  1. uruchom poniższe polecenie, zamień ścieżkę my/new/subdir(3 wystąpienia) na strukturę katalogów, w której chcesz mieć repozytorium potomne.

git filter-branch --prune-empty --tree-filter 'if [! -e mój / nowy / podkatalog]; następnie mkdir -p mój / nowy / subdir git ls-tree - onlyname $ GIT_COMMIT | xargs -I pliki mv pliki mój / nowy / subdir fi '

  1. Przejdź do repozytorium nadrzędnego

cd ../parent-repo/

  1. Dodaj zdalne repozytorium nadrzędne, wskazując ścieżkę do repozytorium podrzędnego

git remote dodaj child-remote ../child-repo/

  1. Pobierz repozytorium potomne

git fetch-child-remote

  1. Połącz historie

git merge --allow-nonrelated-histories child-remote / master

Jeśli sprawdzisz teraz dziennik git w repozytorium nadrzędnym, powinien on zostać scalony. Możesz także zobaczyć znacznik wskazujący ze źródła zatwierdzenia.

Poniższy artykuł pomógł mi Osadzić jedno repo w innym repo, mając jedną historię git, łącząc obie historie git.

http://ericlathrop.com/2014/01/combining-git-repositories/

Mam nadzieję że to pomoże. Happy Coding!

AnoopGoudar
źródło
Krok 3 nie powiódł się z błędem składni. Brakuje średników. Poprawkagit filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
Yuri L
1

Powiedzmy, że chcesz scalić repozytorium ado b(jestem zakładając się znajdują obok siebie):

cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

Jeśli chcesz umieścić aw podkatalogu, wykonaj następujące czynności przed powyższymi poleceniami:

cd a
git filter-repo --to-subdirectory-filter a
cd ..

Do tego trzeba git-filter-repozainstalować ( filter-branchjest odradzane ).

Przykład połączenia 2 dużych repozytoriów i umieszczenia jednego z nich w podkatalogu: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Więcej na ten temat tutaj .

x-yuri
źródło