Odpowiedzi:
Korzystanie git rebase. Jest to ogólne polecenie „weź zatwierdzenie (-a) i umieść je / plop na innym nadrzędnym (podstawowym)” poleceniu w Git.
Niektóre rzeczy należy jednak wiedzieć:
Ponieważ zatwierdzenia SHA dotyczą ich rodziców, po zmianie elementu nadrzędnego danego zatwierdzenia zmieni się jego SHA - podobnie jak SHA wszystkich zatwierdzeń, które następują po nim (nowsze od niego) w linii rozwoju.
Jeśli pracujesz z innymi ludźmi i już opublikowałeś omawiane zatwierdzenie publicznie tam, gdzie je pobrali, modyfikacja zatwierdzenia jest prawdopodobnie złym pomysłem. Wynika to z nr 1, a tym samym wynikającego z tego zamieszania w repozytoriach innych użytkowników, gdy próbuje się dowiedzieć, co się stało, ponieważ Twoje SHA nie pasują już do nich dla „tych samych” zobowiązań. (Szczegółowe informacje znajdują się w sekcji „ODZYSKIWANIE Z REBASY UPSTREAM” na połączonej stronie podręcznika).
To powiedziawszy, jeśli obecnie jesteś w oddziale z pewnymi zatwierdzeniami, które chcesz przenieść do nowego rodzica, wyglądałoby to mniej więcej tak:
git rebase --onto <new-parent> <old-parent>
To przeniesie wszystko po <old-parent> w bieżącej gałęzi, aby usiąść <new-parent>zamiast niej.
--ontosłuży! Dokumentacja zawsze była dla mnie całkowicie niejasna, nawet po przeczytaniu tej odpowiedzi!
git rebase --onto <new-parent> <old-parent>Powiedziała mi wszystko o tym, jak wykorzystać rebase --ontoto każde inne pytanie i dokument, które czytałem do tej pory, czego nie udało się.
rebase this commit on that onelub git rebase --on <new-parent> <commit>. Korzystanie ze starego rodzica tutaj nie ma dla mnie sensu.
git rebase <new-parent>.
Jeśli okaże się, że musisz unikać ponownego wprowadzania kolejnych zatwierdzeń (np. Ponieważ zapisywanie historii byłoby niemożliwe do wykonania), możesz użyć git replace (dostępny w Git 1.6.5 i nowszych).
# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.
replace_first_parent() {
old_parent=$(git rev-parse --verify "${1}^1") || return 1
new_parent=$(git rev-parse --verify "${2}^0") || return 2
new_commit=$(
git cat-file commit "$1" |
sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
git hash-object -t commit -w --stdin
) || return 3
git replace "$1" "$new_commit"
}
replace_first_parent B A
# …---o---A---o---o---…
# \
# C---b---b---…
#
# C is the replacement for B.
Po ustaleniu powyższej zamiany wszelkie żądania dotyczące obiektu B faktycznie zwrócą obiekt C. Zawartość C jest dokładnie taka sama jak zawartość B, z wyjątkiem pierwszego rodzica (ci sami rodzice (z wyjątkiem pierwszego), to samo drzewo, ten sam komunikat zatwierdzenia).
Zastąpienia są domyślnie aktywne, ale można je wyłączyć, używając --no-replace-objectsopcji git (przed nazwą polecenia) lub ustawiając GIT_NO_REPLACE_OBJECTSzmienną środowiskową. Zamienniki można udostępniać, naciskając refs/replace/*(oprócz zwykłego refs/heads/*).
Jeśli nie podoba ci się zatwierdzanie zmian (wykonane przy pomocy sed powyżej), możesz utworzyć zamianę zamienną za pomocą poleceń wyższego poziomu:
git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
Duża różnica polega na tym, że ta sekwencja nie propaguje dodatkowych rodziców, jeśli B jest zatwierdzeniem scalania.
refs/replace/hierarchii odwołań. Jeśli musisz to zrobić tylko raz, możesz to zrobić git push yourremote 'refs/replace/*'w repozytorium źródłowym i git fetch yourremote 'refs/replace/*:refs/replace/*'repozytoriach docelowych. Jeśli musisz to zrobić wiele razy, możesz zamiast tego dodać te refspecs do remote.yourremote.pushzmiennej config (w źródłowym repozytorium) i remote.yourremote.fetchzmiennej config (w docelowych repozytoriach).
git replace --grafts. Nie jestem pewien, kiedy to zostało dodane, ale jego jedynym celem jest utworzenie zamiennika, który jest taki sam jak zatwierdzenie B, ale z określonymi rodzicami.
Zauważ, że zmiana zatwierdzenia w Git wymaga zmiany wszystkich zatwierdzeń następujących po nim. Odradza to, jeśli opublikowałeś tę część historii, a ktoś mógł oprzeć swoją pracę na historii sprzed zmiany.
Alternatywnym rozwiązaniem git rebasewymienionym w odpowiedzi Amber jest użycie mechanizmu przeszczepów (patrz definicja przeszczepów Git w Słowniku Git i dokumentacja .git/info/graftspliku w dokumentacji układu repozytorium Git ) w celu zmiany elementu nadrzędnego zatwierdzenia, sprawdź, czy działało poprawnie w niektórych przeglądarkach historii ( gitk, git log --graph, itp.), a następnie użyj git filter-branch(zgodnie z opisem w sekcji „Przykłady” strony podręcznika), aby uczynić go trwałym (a następnie usuń przeszczep i opcjonalnie usuń oryginalne git filter-branchreferencje, których kopie zapasowe utworzono w kopii zapasowej , lub ponownie skonfiguruj repozytorium):
echo "$ commit-id $ graft-id" >> .git / info / grafts git filter-branch $ graft-id..HEAD
UWAGA !!! Rozwiązanie to różni się od rozwiązania rebase że git rebasebyłoby rebase / przeszczepie zmiany , natomiast rozwiązanie oparte na przeszczepy po prostu reparent commity jak jest , nie biorąc pod uwagę różnice między starym i nowym rodzica rodzica!
git replacezastąpił przeszczepy git (zakładając, że masz git 1.6.5 lub nowszy).
git-replace+ git-filter-branch? W moim teście filter-branchzdawałem się szanować zastępstwo, wypuszczając całe drzewo.
git filter-branchprzepisuje historię, szanuje przeszczepy i zamienniki (ale w przerobionej historii zastąpienia będą dyskusyjne); push spowoduje zmianę non-faf-forward. git replacetworzy w miejscu zbywalne zamienniki; push będzie przewijać do przodu, ale będziesz musiał pchnąć, refs/replaceaby przenieść zamienniki, aby poprawić historię. HTH
Aby wyjaśnić powyższe odpowiedzi i bezwstydnie podłączyć własny skrypt:
Zależy to od tego, czy chcesz „rebase” czy „reparent”. Rebase , jak sugeruje Amber , porusza dyferencjału . Reparent , jak sugeruje Jakuba i Chris , porusza migawek całego drzewa. Jeśli chcesz się zregenerować, sugeruję używanie git reparentzamiast wykonywania pracy ręcznie.
Załóżmy, że masz zdjęcie po lewej stronie i chcesz, aby wyglądało jak zdjęcie po prawej:
C'
/
A---B---C A---B---C
Zarówno ponowne bazowanie, jak i ponowne rodzenie utworzy ten sam obraz, ale definicja C'różni się. Dzięki git rebase --onto A B, C'nie będzie zawierać żadnych zmian wprowadzonych B. Dzięki git reparent -p A, C'będzie identyczny C(poza tym, że Bnie będzie w historii).
reparentskryptem; działa pięknie; wysoce zalecane . Poleciłbym także utworzenie nowej branchetykiety i do checkouttej gałęzi przed wywołaniem (ponieważ etykieta gałęzi się przeniesie).
Zdecydowanie odpowiedź @ Jakuba pomogła mi kilka godzin temu, kiedy próbowałem dokładnie tego samego co OP.
Jest to jednak git replace --graftteraz łatwiejsze rozwiązanie dotyczące przeszczepów. Poważnym problemem związanym z tym rozwiązaniem było to, że gałąź filtrów spowodowała, że straciłem każdą gałąź, która nie została scalona z gałęzią HEAD. Następnie git filter-repowykonał pracę idealnie i bezbłędnie.
$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force
Fore więcej informacji: zapoznaj się z sekcją „Przeszczepianie historii” w dokumentacji