W moim przypadku miałem my-plugin
repozytorium i main-project
repozytorium i chciałem udawać, że my-plugin
zawsze było rozwijane w plugins
podkatalogu main-project
.
Zasadniczo przepisałem historię my-plugin
repozytorium, aby wyglądało na to, że cały rozwój odbywał się w plugins/my-plugin
podkatalogu. Następnie dodałem historię rozwoju my-plugin
do main-project
historii i połączyłem oba drzewa. Ponieważ plugins/my-plugin
w main-project
repozytorium nie było już katalogu , było to trywialne połączenie bez konfliktów. Powstałe repozytorium zawierało całą historię obu oryginalnych projektów i miało dwa źródła.
TL; DR
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
Długa wersja
Najpierw utwórz kopię my-plugin
repozytorium, ponieważ zamierzamy przepisać historię tego repozytorium.
Teraz przejdź do katalogu głównego my-plugin
repozytorium, sprawdź swoją główną gałąź (prawdopodobnie master
) i uruchom następującą komendę. Oczywiście powinieneś zastąpić my-plugin
i plugins
jakikolwiek jest twój rzeczywisty imion.
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
Teraz wyjaśnienie. git filter-branch --tree-filter (...) HEAD
uruchamia (...)
polecenie przy każdym dostępnym zatwierdzeniu HEAD
. Zauważ, że działa to bezpośrednio na dane przechowywane dla każdego zatwierdzenia, więc nie musimy się martwić o pojęcia „katalogu roboczego”, „indeksu”, „przemieszczania” i tak dalej.
Jeśli uruchomisz filter-branch
polecenie, które się nie powiedzie, pozostawi on niektóre pliki w .git
katalogu, a przy następnej próbie filter-branch
będzie narzekał na to, chyba że podasz -f
opcję filter-branch
.
Jak dla rzeczywistego polecenia, nie mam dużo szczęścia z dostaniem bash
się do tego, co chciałem, więc zamiast tego użyć zsh -c
, aby zsh
wykonać polecenie. Najpierw ustawiam extended_glob
opcję, która umożliwia ^(...)
składnię mv
polecenia, a także glob_dots
opcję, która pozwala mi wybierać pliki dot (np. .gitignore
) Za pomocą glob ( ^(...)
).
Następnie używam mkdir -p
polecenia do tworzenia zarówno plugins
i plugins/my-plugin
w tym samym czasie.
Na koniec używam funkcji zsh
„negatywny glob”, ^(.git|plugins)
aby dopasować wszystkie pliki w katalogu głównym repozytorium oprócz .git
i nowo utworzonego my-plugin
folderu. (Wykluczenie .git
może nie być tutaj konieczne, ale próba przeniesienia katalogu do siebie jest błędem).
W moim repozytorium początkowe zatwierdzenie nie zawierało żadnych plików, więc mv
polecenie zwróciło błąd przy początkowym zatwierdzeniu (ponieważ nic nie było dostępne do przeniesienia). Dlatego dodałem || true
tak, git filter-branch
żeby się nie przerywało.
--all
Opcja mówi filter-branch
przepisać historię dla wszystkich oddziałów w repozytorium, a dodatkowo --
trzeba powiedzieć git
interpretować go jako części listy opcji dla oddziałów przepisać, zamiast jako opcja do filter-branch
siebie.
Teraz przejdź do swojego main-project
repozytorium i sprawdź gałąź, z którą chcesz się połączyć. Dodaj lokalną kopię my-plugin
repozytorium (ze zmodyfikowaną historią) jako zdalną main-project
z:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Będziesz teraz mieć dwa niezwiązane drzewa w swojej historii zmian, które możesz ładnie wizualizować za pomocą:
$ git log --color --graph --decorate --all
Aby je połączyć, użyj:
$ git merge my-plugin/master --allow-unrelated-histories
Zauważ, że w wersji wcześniejszej niż 2.9.0 --allow-unrelated-histories
opcja nie istnieje. Jeśli używasz jednej z tych wersji, po prostu pomiń tę opcję: komunikat o błędzie, który --allow-unrelated-histories
zapobiega, został również dodany w wersji 2.9.0.
Nie powinieneś mieć żadnych konfliktów scalania. Jeśli to zrobisz, prawdopodobnie oznacza to, że albo filter-branch
polecenie nie działało poprawnie, albo w plugins/my-plugin
katalogu już był main-project
.
Upewnij się, że wprowadzasz wyjaśniający komunikat zatwierdzenia dla wszystkich przyszłych autorów, którzy zastanawiają się, co się dzieje, aby stworzyć repozytorium z dwoma źródłami.
Za pomocą powyższego git log
polecenia możesz wizualizować nowy wykres zatwierdzania, który powinien mieć dwa główne zatwierdzenia . Pamiętaj, że tylko master
gałąź zostanie scalona . Oznacza to, że jeśli masz ważną pracę nad innymi my-plugin
gałęziami, które chcesz scalić z main-project
drzewem, powinieneś powstrzymać się od usuwania my-plugin
pilota, dopóki nie wykonasz tych scaleń. Jeśli tego nie zrobisz, zatwierdzenia z tych gałęzi nadal będą w main-project
repozytorium, ale niektóre będą nieosiągalne i podatne na ewentualne wyrzucanie elementów bezużytecznych. (Ponadto będziesz musiał odwoływać się do nich przez SHA, ponieważ usunięcie pilota usuwa jego gałęzie zdalnego śledzenia.)
Opcjonalnie po scaleniu wszystkiego, co chcesz zachować my-plugin
, możesz usunąć my-plugin
pilota za pomocą:
$ git remote remove my-plugin
Możesz teraz bezpiecznie usunąć kopię my-plugin
repozytorium, którego historię zmieniłeś. W moim przypadku dodałem również informację o wycofaniu do prawdziwego my-plugin
repozytorium po zakończeniu scalania i wypchnięciu.
Testowane na Mac OS X El Capitan z git --version 2.9.0
i zsh --version 5.2
. Twój przebieg może się różnić.
Bibliografia: