Jak mogę pogodzić odłączone HEAD z master/origin?

Jestem nowy w zawiłościach rozgałęziania Git. Zawsze pracuję na pojedynczej gałęzi i wprowadzam zmiany, a następnie okresowo popycham do mojego zdalnego źródła.

Gdzieś ostatnio zrobiłem reset niektórych plików, aby usunąć je z commit staging, a później wykonałem rebase -i, aby pozbyć się kilku ostatnich lokalnych commitów. Teraz jestem w stanie, którego nie do końca rozumiem.

W moim obszarze roboczym, git log pokazuje dokładnie to, czego bym się spodziewał - jestem na właściwym torze z commitami, których nie chciałem, a nowe są tam, itd.

Ale właśnie pchnąłem do zdalnego repozytorium, a to co tam jest jest inne - kilka commitów, które zabiłem w rebase zostało pchniętych, a nowych commitów popełnionych lokalnie tam nie ma.

Myślę, że "master/origin" jest odłączony od HEAD, ale nie jestem w 100% pewien, co to oznacza, jak to zobrazować za pomocą narzędzi wiersza poleceń i jak to naprawić.

Rozwiązanie

Po pierwsze, wyjaśnijmy czym jest HEAD i co oznacza, gdy jest odłączony.

HEAD jest symboliczną nazwą aktualnie sprawdzanego commitu. Kiedy HEAD nie jest odłączony (normalna 1 sytuacja: masz sprawdzoną gałąź), HEAD faktycznie wskazuje na "ref" gałęzi, a gałąź wskazuje na commit. HEAD jest więc "przywiązany" do gałęzi. Kiedy robisz nowy commit, gałąź, na którą wskazuje HEAD jest aktualizowana, aby wskazywać na nowy commit. HEAD podąża za tym automatycznie, ponieważ po prostu wskazuje na gałąź.

  • git symbolic-ref HEAD daje refs/heads/master. Gałąź o nazwie "master" jest sprawdzana.
  • git rev-parse refs/heads/master yield 17a02998078923f2d62811326d130de991d1a95a. Ten commit jest aktualną końcówką lub "głową" gałęzi master.
  • git rev-parse HEAD również daje 17a02998078923f2d62811326d130de991d1a95a. To jest właśnie to, co oznacza bycie "symbolicznym refem". Wskazuje na obiekt poprzez jakieś inne odniesienie.
    (Symboliczne referencje były pierwotnie zaimplementowane jako dowiązania symboliczne, ale później zostały zmienione na zwykłe pliki z dodatkową interpretacją, aby mogły być używane na platformach, które nie mają symlinków).

Mamy HEADrefs/heads/master17a02998078923f2d62811326d130de991d1a95a.

Kiedy HEAD jest odłączony, wskazuje bezpośrednio na commit - zamiast pośrednio wskazywać na niego poprzez gałąź. Możesz myśleć o odłączonym HEAD jak o gałęzi bez nazwy.

  • git symbolic-ref HEAD kończy się niepowodzeniem z fatal: ref HEAD is not a symbolic ref.
  • git rev-parse HEAD daje 17a02998078923f2d62811326d130de991d1a95a. Ponieważ nie jest to symboliczny ref, musi on wskazywać bezpośrednio na sam commit.

Mamy HEAD17a02998078923f2d62811326d130de991d1a95a.

Ważną rzeczą do zapamiętania z odłączonym HEAD jest to, że jeśli commit, na który wskazuje jest w inny sposób nieodwołany (żaden inny ref nie może do niego dotrzeć), to będzie on "dyndał" kiedy sprawdzisz inny commit. Ostatecznie, takie "miotające się" commit'y zostaną usunięte w procesie odśmiecania (domyślnie są przechowywane przez co najmniej 2 tygodnie, a mogą być przechowywane dłużej przez reflog HEAD'a).

1 Jest to całkowicie w porządku, aby wykonywać "normalną" pracę z odłączonym HEAD, musisz tylko śledzić, co robisz, aby uniknąć konieczności wyławiania zrzuconej historii z reflogu.


Pośrednie kroki interaktywnego rebase'u są wykonywane z odłączonym HEAD (częściowo po to, aby uniknąć zanieczyszczania reflogu aktywnej gałęzi). Jeśli zakończysz pełną operację rebase, zaktualizuje ona twoją oryginalną gałąź z łącznym wynikiem operacji rebase i ponownie dołączy HEAD do oryginalnej gałęzi. Zgaduję, że nigdy nie zakończyłeś w pełni procesu rebase; to pozostawi cię z odłączonym HEAD wskazującym na commit, który został ostatnio przetworzony przez operację rebase.

Aby wyjść z tej sytuacji, powinieneś utworzyć gałąź, która wskazuje na commit aktualnie wskazywany przez odłączony HEAD:

git branch temp
git checkout temp

(te dwie komendy mogą być skrócone jako git checkout -b temp)

Spowoduje to ponowne dołączenie twojego HEAD do nowej gałęzi temp.

Następnie, powinieneś porównać aktualny commit (i jego historię) z normalną gałęzią, na której spodziewałeś się pracować:

git log --graph --decorate --pretty=oneline --abbrev-commit master origin/master temp
git diff master temp
git diff origin/master temp

(Prawdopodobnie będziesz chciał poeksperymentować z opcjami logów: dodać -p, opuścić --pretty=... aby zobaczyć cały komunikat logu, itp.)

Jeśli twój nowy oddział temp wygląda dobrze, możesz chcieć zaktualizować (np.) master aby wskazywał na niego:

git branch -f master temp
git checkout master

(te dwie komendy mogą być skrócone jako git checkout -B master temp)

Następnie możesz usunąć tymczasowy oddział:

git branch -d temp

Na koniec, prawdopodobnie będziesz chciał popchnąć przywróconą historię:

git push origin master

Możesz potrzebować dodać --force na końcu tej komendy do push, jeśli zdalna gałąź nie może być "fast-forwarded" do nowego commitu (tj. porzuciłeś, lub przepisałeś jakiś istniejący commit, lub w inny sposób przepisałeś jakiś fragment historii).

Jeśli byłeś w trakcie operacji rebase, prawdopodobnie powinieneś to wyczyścić. Możesz sprawdzić, czy rebase był w toku, szukając katalogu .git/rebase-merge/. Możesz ręcznie wyczyścić trwający rebase przez usunięcie tego katalogu (np. jeśli nie pamiętasz już celu i kontekstu operacji rebase). Zwykle użyłbyś git rebase --abort, ale to powoduje dodatkowe resetowanie, którego prawdopodobnie chcesz uniknąć (przenosi HEAD z powrotem do oryginalnej gałęzi i resetuje go z powrotem do oryginalnego commitu, co cofnie część pracy, którą wykonaliśmy powyżej).

Komentarze (22)

Zobacz tutaj podstawowe wyjaśnienia dotyczące oderwanej głowy:

http://git-scm.com/docs/git-checkout

Linia poleceń, aby to zwizualizować:

git branch

lub

git branch -a

otrzymasz dane wyjściowe jak poniżej:

* (no branch)
master
branch1

Znak * (brak gałęzi) pokazuje, że jesteś w odłączonej głowie.

Mogłeś dojść do tego stanu wykonując git checkout somecommit itp. i system ostrzegłby cię następująco:

Znajdujesz się w 'detached HEAD' stanie. You Możesz się rozejrzeć, dokonać eksperymentalnych zmiany i popełnić je, i możesz odrzucić wszystkie commit'y, które zrobisz w tym stanie stanie bez wpływu na jakiekolwiek gałęzie wykonując kolejny checkout.

Jeśli chcesz utworzyć nową gałąź, aby zachować commity, które utworzyłeś, możesz to zrobić to (teraz lub później) przez użycie -b z poleceniem poleceniem checkout ponownie. Przykład:

git checkout -b new_branch_name

Teraz, aby przenieść je na master:.

Zrób git reflog lub nawet po prostu git log i zanotuj swoje polecenia. Teraz git checkout master i git merge z commitami.

git merge HEAD@{1}

Edytuj:

Aby dodać, użyj git rebase -i nie tylko do usuwania / zabijania commitów, których nie'potrzebujesz, ale także do ich edycji. Wystarczy wspomnieć "edit" na liście commitów i będziesz mógł zmienić swój commit, a następnie wydać git rebase --continue, aby przejść dalej. To zapewniłoby, że nigdy nie wszedłeś do odłączonego HEAD.

Komentarze (2)

Get your detached commit into its own branch

Po prostu uruchom git checkout -b mynewbranch.

Następnie uruchom git log, a zobaczysz, że commit jest teraz HEAD na nowej gałęzi.

Komentarze (2)