polecenie git, aby uczynić jedną gałąź inną

81

Próbuję wziąć gałąź ze zmianami i przywrócić ją, aby była identyczna z wyższą wersją, od której się odbiegła. Zmiany są zarówno lokalne, jak i zostały zepchnięte na github, więc żadne z nich git resetlub nie git rebasesą naprawdę wykonalne, ponieważ zmieniają historię, co jest złe w przypadku gałęzi, która została już wypchnięta.

Próbowałem również git mergez różnymi strategiami, ale żadna z nich nie cofnęła lokalnych zmian, tj. Gdybym dodał plik, scalenie może przywrócić inne pliki, ale nadal będę mieć ten plik, którego nie ma nadrzędny mieć.

Mógłbym po prostu utworzyć nową gałąź poza upstreamem, ale naprawdę chciałbym scalić, które pod względem historii zmian zastosuje wszystkie zmiany, aby wziąć moją gałąź i uczynić ją identyczną z nadrzędną, aby móc bezpiecznie wprowadzić tę zmianę bez bijącej historii. Czy jest takie polecenie lub seria poleceń?

Arne Claassen
źródło
9
Jeśli nie zależy Ci na zachowaniu zmian, dlaczego nie usunąć i ponownie utworzyć gałęzi? „Historia projektu” nie musi być święta. Git to narzędzie ułatwiające komunikację programistom. Jeśli te zmiany nie pomogą, wyrzuć je.
mądry
+100 @wnoise - zwłaszcza jeśli zmiany zostały już włączone.
wuputah
7
Zależy mi na zachowaniu historii zarówno dlatego, że została opublikowana do współpracy i może chciałbym do niej wrócić. Po co zawracać sobie głowę kontrolą wersji, jeśli zachowujesz tylko najnowsze?
Arne Claassen
1
To jest subiektywny argument, ale dla mnie celem VCS nie jest rejestrowanie wszystkich najdrobniejszych szczegółów historii projektu, a jedynie rejestrowanie zmian w treści (zatwierdzenia), aby umożliwić manipulowanie drzewem / historią w oparciu o na tych zatwierdzeniach (rozgałęzianie, łączenie, zmiana bazy, resetowanie itp.) i pozwala na przeglądanie raportów opartych na historii (różnice, logi, winy itp.). git to „głupi tracker zawartości” - postrzegam go jako narzędzie do zarządzania kodem źródłowym, a nie maszynę czasu.
wuputah
5
Jak powiedziałeś, jest to subiektywne. Zależy mi na tym, aby móc przeglądać porzucone podejścia i móc zobaczyć, jakie decyzje zostały podjęte w przeszłości. I zależy mi na tym, żeby moja decyzja o porzuceniu czegoś nie zniszczyła punktów łączenia, na które inni mogą wskazywać.
Arne Claassen

Odpowiedzi:

107

Mógłbyś połączyć swoją gałąź nadrzędną ze swoją devgałęzią, z niestandardowym sterownikiem scalającym „keepTheirs” :
Zobacz „ git merge -s theirs”potrzebny - ale wiem, że nie istnieje ”.
W twoim przypadku .gitattributespotrzebny byłby tylko jeden i keepTheirsskrypt:

mv -f $3 $2
exit 0

git merge --strategy=theirs Symulacja nr 1

Pokazuje jako scalenie, z nadrzędnym jako pierwszym rodzicem.

Jefromi wspomina (w komentarzach) o merge -s ours, scalając twoją pracę na nadrzędnym (lub tymczasowej gałęzi zaczynającej się od wyższego szczebla), a następnie przewijając twoją gałąź do wyniku tego scalenia:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

Ma to tę zaletę, że rejestruje górnego przodka jako pierwszego rodzica, więc scalanie oznacza „wchłonąć tę nieaktualną gałąź tematu” zamiast „zniszczyć tę gałąź tematu i zastąpić ją upstream” .

(Edycja 2011):

Ten przepływ pracy został zgłoszony w tym poście na blogu przez OP :

Dlaczego chcę tego ponownie?

O ile moje repozytorium nie miało nic wspólnego z wersją publiczną, wszystko było w porządku, ale ponieważ teraz chciałbym mieć możliwość porównywania WIP z innymi członkami zespołu i współpracownikami zewnętrznymi, chcę się upewnić, że moje publiczne oddziały są niezawodny dla innych do rozgałęziania się i ściągania, tj. koniec z ponownym bazowaniem i resetowaniem rzeczy, które wypchnąłem do zdalnej kopii zapasowej, ponieważ jest teraz na GitHub i publicznie.

Więc zostaję z tym, jak mam postępować.
W 99% przypadków moja kopia trafia do nadrzędnego mastera, więc chcę pracować z moim masterem i przez większość czasu przesyłać do nadrzędnego.
Ale od czasu do czasu to, co mam w wipsobie, zostanie unieważnione przez to, co trafi do górnego strumienia i porzucę jakąś część mojego wip.
W tym momencie chcę zsynchronizować mojego mastera z nadrzędnym, ale nie niszczyć żadnych punktów commita na moim publicznie wypchniętym masterze. To znaczy, chcę scalić z nadrzędnym, który kończy się zestawem zmian, który sprawi, że moja kopia będzie identyczna z nadrzędną .
I to właśnie git merge --strategy=theirspowinno zrobić.


git merge --strategy=theirs Symulacja nr 2

Pokazuje jako połączenie, z naszym jako pierwszym rodzicem.

(zaproponowane przez jcwenger )

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs Symulacja nr 3

W tym poście na blogu wspomniano :

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

czasami chcesz to zrobić i nie dlatego, że masz „bzdury” w swojej historii, ale być może dlatego, że chcesz zmienić podstawę rozwoju w repozytorium publicznym, w którym należy unikać ponownego bazowania .


git merge --strategy=theirs Symulacja nr 4

(ten sam post na blogu)

Alternatywnie, jeśli chcesz, aby lokalne gałęzie upstream były szybkie do przodu, potencjalnym kompromisem jest praca ze zrozumieniem, że w przypadku sid / unstable gałąź nadrzędna może od czasu do czasu zostać zresetowana / zmieniona (na podstawie zdarzeń, które ostatecznie nie istnieją kontroli po stronie projektu).
To nie jest wielka sprawa, a praca z tym założeniem oznacza, że ​​łatwo jest utrzymać lokalną gałąź nadrzędną w stanie, w którym pobiera tylko szybkie aktualizacje.

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs Symulacja nr 5

(zaproponowane przez Baraka A. Pearlmuttera ):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs Symulacja # 6

(zaproponowane przez tego samego Michaela Gebetsroithera ):

Michael Gebetsroither włączył się, twierdząc, że „oszukuję”;) i podał inne rozwiązanie z komendami niższego poziomu:

(nie byłoby to git, gdyby nie było to możliwe za pomocą poleceń tylko git, wszystko w git z diff / patch / apply nie jest prawdziwym rozwiązaniem;).

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs Symulacja # 7

Niezbędne kroki można opisać jako:

  1. Zastąp swoje drzewo robocze usługą upstream
  2. Zastosuj zmiany do indeksu
  3. Dodaj powyżej jako drugiego rodzica
  4. Popełnić

Polecenie git read-treezastępuje indeks innym drzewem, wykonując drugi krok , i zawiera flagi do aktualizacji drzewa roboczego, wykonując pierwszy krok . Podczas zatwierdzania git używa SHA1 w .git / MERGE_HEAD jako drugiego rodzica, więc możemy go wypełnić, aby utworzyć zatwierdzenie scalające. Dlatego można to osiągnąć za pomocą:

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit
VonC
źródło
7
Zawsze możesz po prostu użyć naszego zamiast ich: sprawdź inną gałąź, połącz z nią swoją, a następnie przewiń swoją do scalania. git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream. (W razie potrzeby użyj tymczasowej gałęzi u góry strumienia). Ma to tę zaletę, że zapisuje przodka z wyższego poziomu jako pierwszego rodzica, dzięki czemu scalanie oznacza „wchłonąć tę nieaktualną gałąź tematu” zamiast „zniszczyć tę gałąź tematu i zastąpić z upstreamem ”.
Cascabel
@Jefromi: jak zwykle doskonała uwaga. Zawarłem to w mojej odpowiedzi.
VonC,
Inna opcja - jak git merge --strategy = theirs Simulation # 1 - z tą różnicą, że ta zachowuje brange jako pierwszy element nadrzędny merge: git checkout -b tmp origin / upstream git merge -s ours downstream # ignorowanie wszystkich zmian z niższego git checkout downstream git merge --squash tmp # zastosuj zmiany z tmp, ale nie jako merge. git rev-parse upstream> .git / MERGE_HEAD #record upstream jako druga głowica scalająca git commit -m "rebaselined ours from upstream" # wykonaj zatwierdzenie. git branch -D tmp #
deleting
2
Wow, kto by pomyślał, że --strategia = ich może być wdrożona na wiele sposobów. Gdyby to mogło być w następnej wersji gita
Arne Claassen,
VonC i jego wiedza są zdumiewające. Jest jak JonSkeet gita. :)
sjas
13

Wydaje mi się, że po prostu musisz to zrobić:

$ git reset --hard origin/master

Jeśli nie ma żadnej zmiany do wypychania w górę i po prostu chcesz, aby gałąź upstream była twoją bieżącą gałęzią, zrobi to. Robienie tego lokalnie nie jest szkodliwe, ale utracisz wszelkie lokalne zmiany **, które nie zostały wprowadzone do perfekcji.

** Właściwie zmiany są nadal dostępne, jeśli zatwierdziłeś je lokalnie, ponieważ zatwierdzenia będą nadal dostępne w Twoim git reflog, zwykle przez co najmniej 30 dni.

wuputah
źródło
to działa, jeśli potrzebujesz tej zmiany za wszelką cenę, ponieważ może to zmienić historię gałęzi (musisz naciskać z -f). które można skonfigurować tak, aby były blokowane, więc w praktyce będzie działać tylko dla Ciebie, jako prywatne repozytoria.
ribamar
12

Możesz to zrobić teraz dość łatwo:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

Dzięki temu lokalne repozytorium jest zsynchronizowane z pochodzeniem i zachowuje historię.


źródło
5
git merge -s recursive -Xtheirsnie łączy automatycznie plików binarnych, więc znajdujesz się w sytuacji konfliktu, którą musisz rozwiązać ręcznie. Przepływy pracy oparte na git merge -s ourstym nie cierpią.
Stefaan
Wydaje się, że tworzy to puste zatwierdzenie.
Robin Green,
Przepraszam - to faktycznie działa, ale git showprzy zatwierdzaniu scalającym pokazuje tylko rozwiązania konfliktów i -Xtheirsoczywiście nie ma rozwiązań konfliktów, jeśli są używane.
Robin Green,
5

Kolejna symulacja dla git merge -s theirs ref-to-be-merged:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

Alternatywą dla podwójnego resetu byłoby zastosowanie odwróconej łatki:

git diff --binary ref-to-be-merged | git apply -R --index
michas
źródło
Ciekawe wykorzystanie odwrotnej naszywki. +1
VonC
Reset nie zadziałał dla mnie, otrzymałem „fatalny: niejednoznaczny argument 'HEAD2': nieznana wersja lub ścieżka nie znajduje się w drzewie roboczym”. . (Tak, wpisałem HEAD^2) Metoda łatki zadziałała.
user247702
@Stijn: Najprawdopodobniej faktycznie nie wpisałeś ^poprawnie. Czasami inne naciśnięcia klawiszy, takie jak „ctrl-c”, są wyświetlane jako „^ C”. - Jeśli naprawdę wpisałeś poprawne "^", znalazłeś poważny błąd w swojej wersji git.
michas
@michas W systemie Windows znak ^ jest używany do ucieczki, więc aby użyć go jako literału, musisz uciec z nim samym. git reset --hard HEAD^^2; git reset --soft HEAD@{1}
Joshua Walsh
4

Jest też sposób z niewielką pomocą polecenia hydraulicznego - IMHO najprostszy. Załóżmy, że chcesz emulować „ich” w przypadku dwóch gałęzi:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

Łączy to dowolną liczbę głowic (2 w powyższym przykładzie) przy użyciu drzewa jednej z nich (pasek w powyższym przykładzie, dostarczający drzewo `` ich ''), pomijając wszelkie problemy z różnicami / plikami (drzewo zatwierdzeń jest poleceniem niskiego poziomu, więc nie dba o to). Zauważ, że głowa może mieć tylko 1 (czyli odpowiednik „cherry-pick” z „ich”).

Zauważ, że to, która głowa rodzica jest określona jako pierwsza, może wpłynąć na niektóre rzeczy (zobacz np. --First-parent polecenia git-log) - więc miej to na uwadze.

Zamiast git-show można użyć wszystkiego, co jest w stanie wyprowadzić skróty drzewa i zatwierdzenia - cokolwiek jest używane do analizowania (plik-cat, lista-obrotów, ...). Możesz śledzić wszystko za pomocą git commit --amend do interaktywnego upiększania wiadomości o zatwierdzeniu.

Michał Sołtys
źródło
To jest najprostsze w moich oczach. Używasz poleceń hydraulicznych, aby powiedzieć „Utwórz nowy obiekt zatwierdzenia z tym drzewem, tym pierwszym rodzicem, drugim rodzicem. Następnie skieruj głowę na to nowe zatwierdzenie.”, Co jest dokładnie tym, czego chcemy, „git merge -s theirs” do zrobienia. Wadą jest to, że aby ta operacja działała, musisz zapisać 4 różne skróty.
Michael R
2

Ciężka ręka, ale do diabła, co może się nie udać?

  • Sprawdź gałąź X, w której chcesz wyglądać jak Y
  • cp -r .git /tmp
  • Sprawdź oddział Y git checkout y
  • rm -rf .git && cp -r /tmp/.git .
  • Zatwierdź i przesuń każdą różnicę
  • GOTOWE.
sscarduzio
źródło
Jest to najprostszy, brutalny sposób na stworzenie identycznych dwóch gałęzi, zakładając, że nie zależy ci na utrzymywaniu historii scalania.
Damon D
1

przejdź do zdalnej gałęzi nadrzędnej i wykonaj git mergeze strategią scalania ustawioną na ours.

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

Cała historia będzie nadal obecna, ale będziesz mieć dodatkowe zatwierdzenie scalenia. Ważne jest, aby zacząć od wersji, w której chcesz być, i połączyć się oursz gałęzią, w której aktualnie znajduje się github.

kelloti
źródło
1
Potrzebuję czegoś przeciwnego. To zajmie moją gałąź i zintegruje ją z górnym strumieniem, ale pozostawi górną głowicę niezmienioną. Ale muszę wziąć górny strumień i zintegrować go z moją gałęzią, pozostawiając głowę, by wyglądać jak górny strumień. Zasadniczo coś w rodzaju --strategy=theirs, ale najbliżsi --strategy=recursive -X=theirstego nie robią.
Arne Claassen
--strategy=theirsjest przeciwieństwem --strategy=ours. Zaczynasz od przeciwnego końca (więc zacznij od github i połącz w drugą stronę).
kelloti,
nie ma --strategy=theirs, co jest problemem. Najbliższe jest to, --strategy=recursive -X theirsco nie jest zupełnie odwrotne, ponieważ nie usunie obcych zmian lokalnych, jeśli nie będą kolidować.
Arne Claassen
Te dwa są przeciwieństwami: git checkout dev; git merge origin/master --strategy=oursigit checkout origin/master; git merge dev --strategy=ours
wuputah
2
@Arne: Zobacz mój komentarz do odpowiedzi VonC. Obecność oursstrategii całkowicie umożliwia wykonanie theirsstrategii.
Cascabel
1

Użyj git reset BACKWARDS!

Możesz sprawić, że gałąź będzie wyglądać jak każdy inny commit z git reset , ale musisz to zrobić w sposób okrężny.

Aby gałąź po zatwierdzeniu <old>wyglądała jak zatwierdzenie <new>, możesz to zrobić

git reset --hard <new>

żeby zrobić <new> zawartości drzewa roboczego.

Więc zrób

git reset --mixed <old> 

aby zmienić gałąź z powrotem na oryginalne zatwierdzenie, ale pozostawiając drzewo robocze w <new> stanie .

Następnie możesz dodać i zatwierdzić zmiany, aby Twoja gałąź dokładnie pasowała do zawartości pliku <new> zatwierdzenia.

To sprzeczne z intuicją, że aby przejść ze <old>stanu do stanu <new>, należy wykonać git reset od <new> do <old> . Jednak w przypadku opcji --mixeddrzewo robocze jest pozostawione na, <new>a wskaźnik gałęzi ustawiony na<old> , więc po zatwierdzeniu zmian gałąź wygląda tak, jak chcemy.

Ostrzeżenie

Nie trać z oczu swoich zatwierdzeń, np. Zapomnij o tym, co <old>robisz git reset --hard <new>.

Ivan
źródło
0

Podążałem za tymi rolami:

Pobierając źródło, resetuj mocno z gałęzi, a następnie rekurencyjnie z ich gałęzi, a następnie wymuszaj wypychanie na gałąź

NA WŁASNE RYZYKO

git fetch origin
git reset --hard origin/<branch>
git merge origin/<branch> -s recursive -Xtheirs
git push -f <remote> <branch>
esso
źródło