Moje biuro próbuje dowiedzieć się, jak radzimy sobie z podziałem i łączeniem oddziałów, i napotkaliśmy duży problem.
Nasz problem dotyczy długoterminowych odgałęzień - w przypadku, gdy kilka osób pracuje nad odgałęzieniem, które dzieli się od mistrza, rozwijamy się przez kilka miesięcy, a kiedy osiągamy kamień milowy, synchronizujemy oba.
Teraz, IMHO, naturalnym sposobem na poradzenie sobie z tym, jest zgniecenie bocznego oddziału w jednym zatwierdzeniu. master
postępuje naprzód; tak jak powinno - nie zrzucamy wstecz wstecz miesięcy historii równoległego rozwoju master
. A jeśli ktoś potrzebuje lepszej rozdzielczości dla historii sidebranch, cóż, oczywiście, że wciąż tam jest - po prostu go nie ma master
, jest w sidebranch.
Oto problem: pracuję wyłącznie z linii poleceń, ale reszta mojego zespołu używa GUIS. Odkryłem, że GUIS nie ma rozsądnej opcji wyświetlania historii z innych gałęzi. Więc jeśli dojdziesz do zatwierdzenia squasha, mówiąc: „ten rozwój zgnieciony z gałęzi XYZ
”, ogromnie trudno jest zobaczyć, co jest w środku XYZ
.
Na SourceTree, o ile jestem w stanie znaleźć, to ogromny ból głowy: jeśli jesteś włączony master
i chcesz zobaczyć historię master+devFeature
, musisz albo sprawdzić master+devFeature
(dotykając każdego pojedynczego pliku, który jest inny), albo przewiń dziennik wyświetlający WSZYSTKIE gałęzie repozytorium równolegle, aż znajdziesz właściwe miejsce. Powodzenia w ustaleniu, gdzie jesteś.
Moi koledzy z drużyny, całkiem słusznie, nie chcą, aby historia rozwoju była tak niedostępna. Dlatego chcą, aby te duże, długie gałęzie rozwoju zostały połączone, zawsze z zatwierdzeniem scalania. Nie chcą żadnej historii, która nie byłaby natychmiast dostępna z gałęzi master.
I nienawidzę tego pomysłu; oznacza niekończącą się, nieuchronną plątaninę równoległej historii rozwoju. Ale nie widzę, jaką mamy alternatywę. I jestem bardzo zaskoczona; wydaje się to blokować większość wszystkiego, co wiem o dobrym zarządzaniu oddziałami, i będzie to dla mnie ciągłą frustracją, jeśli nie będę w stanie znaleźć rozwiązania.
Czy mamy tutaj jakąś opcję oprócz ciągłego łączenia oddziałów bocznych w master z scalaniem-zatwierdzeniami? A może istnieje powód, dla którego ciągłe używanie zatwierdzeń scalania nie jest tak złe, jak się obawiam?
źródło
git log master+bugfixDec10th
się, że będzie to punktmerge --no-ff
chcesz Onmaster
sam masz jeden zobowiązują się, że opisuje to, co zmieniło się w branży, ale wszystkich zatwierdzeń nadal istnieją i są wychowywane do HEAD.Odpowiedzi:
Mimo że używam Gita w wierszu poleceń - muszę się zgodzić z twoimi kolegami. Rozsyłanie dużych zmian w jednym zatwierdzeniu nie jest rozsądne. W ten sposób tracisz historię, nie tylko czyniąc ją mniej widoczną.
Celem kontroli źródła jest śledzenie historii wszystkich zmian. Kiedy co zmieniło się dlaczego? W tym celu każde zatwierdzenie zawiera wskaźniki do zatwierdzeń nadrzędnych, różnicę i metadane, takie jak komunikat zatwierdzenia. Każde zatwierdzenie opisuje stan kodu źródłowego i pełną historię wszystkich zmian, które doprowadziły do tego stanu. Moduł odśmiecający może usuwać zatwierdzenia, które są nieosiągalne.
Czynności takie jak zmiana bazy danych, wybieranie czereśni lub zgniatanie usuwają lub przepisują historię. W szczególności wynikowe zatwierdzenia nie odwołują się już do oryginalnych zatwierdzeń. Rozważ to:
[1]: Niektóre serwery Git umożliwiają ochronę gałęzi przed przypadkowym usunięciem, ale wątpię, czy chcesz zachować wszystkie gałęzie funkcji na wieczność.
Teraz nie możesz już szukać tego zatwierdzenia - po prostu nie istnieje.
Odwoływanie się do nazwy gałęzi w komunikacie zatwierdzenia jest jeszcze gorsze, ponieważ nazwy gałęzi są lokalne dla repozytorium. To, co jest
master+devFeature
w twojej lokalnej kasie, może byćdoodlediduh
moje. Gałęzie to tylko ruchome etykiety wskazujące na jakiś obiekt zatwierdzenia.Ze wszystkich technik przepisywania historii zmiana jest najbardziej łagodna, ponieważ powiela kompletne zatwierdzenia z całą ich historią i po prostu zastępuje zatwierdzenie nadrzędne.
To, że
master
historia obejmuje pełną historię wszystkich połączonych z nią gałęzi, jest dobrą rzeczą, ponieważ reprezentuje rzeczywistość. [2] Jeśli równoległy rozwój był widoczny, powinno to być widoczne w dzienniku.[2]: Z tego powodu wolę także wyraźne zatwierdzenia scalania niż zlinearyzowaną, ale ostatecznie fałszywą historię wynikającą z ponownego bazowania.
W wierszu poleceń
git log
usilnie stara się uprościć wyświetlaną historię i zachować wszystkie wyświetlane zatwierdzenia odpowiednie. Możesz dostosować uproszczenie historii do swoich potrzeb. Możesz mieć ochotę napisać własne narzędzie git log, które prowadzi wykres zatwierdzania, ale generalnie nie można odpowiedzieć „czy to zatwierdzenie pierwotnie zostało popełnione w tej czy innej gałęzi?”. Pierwszym rodzicem zatwierdzenia scalania jest poprzednieHEAD
, tj. Zatwierdzenie w gałęzi, do której się scalasz. Zakłada się jednak, że nie wykonano scalenia wstecznego z elementu głównego do gałęzi funkcji, a następnie elementu głównego z szybkim przekazaniem do elementu scalającego.Najlepszym rozwiązaniem dla oddziałów długoterminowych, jakie napotkałem, jest zapobieganie połączeniom oddziałów dopiero po kilku miesiącach. Scalanie jest najłatwiejsze, gdy zmiany są najnowsze i niewielkie. Najlepiej byłoby połączyć co najmniej raz w tygodniu. Ciągła integracja (jak w Extreme Programming, nie jak w „skonfigurujmy serwer Jenkins”), sugeruje nawet wielokrotne połączenia dziennie, tj. Nie utrzymywanie oddzielnych gałęzi funkcji, ale dzielenie gałęzi programistycznej jako zespołu. Scalenie przed poddaniem funkcji kontroli jakości wymaga, aby funkcja była ukryta za flagą funkcji.
W zamian częsta integracja umożliwia wykrycie potencjalnych problemów znacznie wcześniej i pomaga zachować spójną architekturę: możliwe są daleko idące zmiany, ponieważ zmiany te są szybko uwzględniane we wszystkich gałęziach. Jeśli zmiana złamie jakiś kod, zepsuje to tylko kilka dni pracy, a nie kilka miesięcy.
Przepisywanie historii może mieć sens w naprawdę dużych projektach, gdy istnieje wiele milionów linii kodu i setki lub tysiące aktywnych programistów. Wątpliwe jest, dlaczego tak duży projekt musiałby być pojedynczym repozytorium git, zamiast być podzielonym na osobne biblioteki, ale na taką skalę wygodniej jest, jeśli centralne repozytorium zawiera tylko „wydania” poszczególnych komponentów. Np. Jądro Linuksa stosuje squashing, aby utrzymać główną historię zarządzania. Niektóre projekty open source wymagają przesyłania poprawek pocztą e-mail zamiast scalania na poziomie git.
źródło
git bisect
, tylko po to, aby dotrzeć do 10.000 linii uber-commit z gałęzi bocznej.Podoba mi się odpowiedź Amona , ale czułem, że jedna mała część wymaga o wiele większego nacisku: możesz łatwo uprościć historię podczas przeglądania dzienników, aby spełnić twoje potrzeby, ale inni nie mogą dodawać historii podczas przeglądania dzienników, aby spełnić ich potrzeby. Dlatego lepiej jest zachować historię, jaka się pojawiła.
Oto przykład z jednego z naszych repozytoriów. Korzystamy z modelu pull-request, więc każda funkcja wygląda jak twoje długo działające gałęzie w historii, nawet jeśli zwykle działają tylko tydzień lub krócej. Poszczególni programiści czasami decydują się na zmarnowanie swojej historii przed połączeniem, ale często łączymy funkcje, więc jest to stosunkowo niezwykłe. Oto kilka najważniejszych zatwierdzeń
gitk
, GUI dołączone do git:Tak, trochę plątaniny, ale nam się też podoba, ponieważ możemy dokładnie zobaczyć, kto miał zmiany w danym momencie. Dokładnie odzwierciedla naszą historię rozwoju. Jeśli chcemy zobaczyć widok wyższego poziomu, jedno żądanie ściągania scalane na raz, możemy spojrzeć na następujący widok, który jest równoważny
git log --first-parent
poleceniu:git log
ma wiele innych opcji zaprojektowanych tak, aby zapewnić dokładnie te widoki, które chcesz.gitk
może wziąć dowolny dowolnygit log
argument, aby zbudować widok graficzny. Jestem pewien, że inne GUI mają podobne możliwości. Przeczytaj dokumentację i naucz się go prawidłowo używać, zamiast wymuszać swój preferowanygit log
pogląd na wszystkich w czasie łączenia.źródło
merge --no-ff
- nie używaj scalania do przodu, zamiast tego zawsze twórz zatwierdzenie--first-parent
Moją pierwszą myślą jest - nawet tego nie rób, chyba że jest to absolutnie konieczne. Twoje fuzje muszą czasem stanowić wyzwanie. Zachowaj niezależność oddziałów i możliwie jak najkrótszy czas. To znak, że musisz podzielić swoje historie na mniejsze fragmenty implementacji.
W przypadku, gdy musisz to zrobić, możliwe jest połączenie w git z opcją --no-ff, dzięki czemu historie są odrębne dla ich własnej gałęzi. Zatwierdzenia będą nadal pojawiać się w połączonej historii, ale można je również zobaczyć osobno w gałęzi funkcji, aby przynajmniej można było określić, w której linii rozwoju byli częścią.
Muszę przyznać, że kiedy zacząłem używać git, wydawało mi się trochę dziwne, że zatwierdzenie gałęzi pojawiło się w tej samej historii co gałąź główna po scaleniu. Było to trochę niepokojące, ponieważ nie wydawało się, aby te zobowiązania należały do tej historii. Ale w praktyce nie jest to wcale tak bolesne, jeśli weźmie się pod uwagę, że gałąź integracji jest właśnie taka - jej jedynym celem jest połączenie gałęzi funkcji. W naszym zespole nie zgniatamy i często dokonujemy fuzji. Używamy --no-ff przez cały czas, aby upewnić się, że łatwo jest zobaczyć dokładną historię dowolnej funkcji, jeśli chcemy ją zbadać.
źródło
git merge --no-ff
git merge --no-ff
. Jeśli używasz gitflow, staje się to domyślny.Pozwól, że odpowiem bezpośrednio na twoje pytania:
Zwykle nie chcesz, aby Twoje oddziały były niezsynchronizowane przez miesiące.
Twoja gałąź funkcji rozgałęziła się na czymś w zależności od przepływu pracy; nazwijmy to
master
dla uproszczenia. Teraz, ilekroć zobowiązujesz się do opanowania, możesz i powinieneśgit checkout long_running_feature ; git rebase master
. Oznacza to, że Twoje gałęzie są z założenia zawsze zsynchronizowane.git rebase
jest również właściwe do zrobienia tutaj. To nie jest hack ani coś dziwnego lub niebezpiecznego, ale całkowicie naturalne. Tracisz jeden kawałek informacji, który jest „urodziny” gałęzi funkcji, ale to wszystko. Jeśli ktoś uważa, że jest to ważne, można to zrobić, zapisując go gdzie indziej (w systemie biletów lub, jeśli potrzeba jest duża, wgit tag
...).Nie, absolutnie tego nie chcesz, chcesz zatwierdzić scalenie. Scal zatwierdzenie jest również „pojedynczym zatwierdzeniem”. W jakiś sposób nie wstawia on wszystkich pojedynczych zatwierdzeń gałęzi „do” wzorca. Jest to pojedyncze zatwierdzenie z dwojgiem rodziców -
master
szefem i szefem oddziału w momencie scalenia.Oczywiście należy podać
--no-ff
opcję; łączenie bez--no-ff
powinno być w twoim scenariuszu surowo zabronione. Niestety--no-ff
nie jest domyślny; ale wierzę, że istnieje możliwość ustawienia tej opcji. Zobacz,git help merge
co--no-ff
robi (krótko: aktywuje zachowanie, które opisałem w poprzednim akapicie), jest kluczowe.Absolutnie nie - nigdy nie wrzucasz czegoś „do historii” jakiegoś oddziału, szczególnie nie z zatwierdzeniem scalania.
Z zatwierdzeniem scalania nadal tam jest. Nie w mistrzu, ale w bocznym odgałęzieniu, wyraźnie widoczne jako jedno z rodziców fuzji popełnienia i zachowane na wieczność, jak powinno być.
Widzisz co zrobiłem? Wszystkie rzeczy, które opisujesz dla swojego zatwierdzenia do squasha, znajdują się tutaj wraz z
--no-ff
zatwierdzeniem przez scalenie .(Uwaga boczna: prawie wyłącznie pracuję również z linią poleceń (cóż, to kłamstwo, zwykle używam emacs magit, ale to inna historia - jeśli nie jestem w dogodnym miejscu z moją indywidualną konfiguracją emacsa, wolę polecenie linii)). Ale wyświadcz sobie przysługę i spróbuj przynajmniej
git gui
raz. Jest o wiele bardziej wydajny w przypadku wybierania linii, kawałków itp. w celu dodawania / cofania dodawania.)Jest tak, ponieważ to, co próbujesz zrobić, jest całkowicie sprzeczne z duchem
git
.git
opiera się na rdzeniu na „ukierunkowanym wykresie acyklicznym”, co oznacza, że wiele informacji dotyczy relacji między rodzicem a dzieckiem. A w przypadku fuzji oznacza to, że prawdziwa fuzja jest zgodna z dwojgiem rodziców i jednym dzieckiem. GUI twoich współpracowników będą w porządku, jak tylko użyjeszno-ff
zatwierdzeń scalania.Tak, ale to nie jest problem z GUI, ale z zatwierdzaniem squasha. Użycie squasha oznacza, że pozostawiasz zwisającą głowę gałęzi funkcji i tworzysz zupełnie nowe zatwierdzenie
master
. To łamie strukturę na dwóch poziomach, tworząc wielki bałagan.I mają absolutną rację. Ale oni nie są „połączone w ”, są po prostu połączone. Scalanie jest naprawdę zrównoważoną rzeczą, nie ma preferowanej strony, która byłaby scalona „w” drugą stronę (
git checkout A ; git merge B
jest dokładnie taka sama, jakgit checkout B ; git merge A
z wyjątkiem drobnych różnic wizualnych, takich jak zamiana gałęzigit log
itp.).Co jest całkowicie poprawne. W czasie, gdy nie ma żadnych połączonych funkcji, miałbyś jedną gałąź
master
z bogatą historią, obejmującą wszystkie linie zatwierdzania cech, jakie kiedykolwiek istniały, wracając dogit init
zatwierdzenia od samego początku (zauważ, że specjalnie unikałem używania terminu „ gałęzie ”w drugiej części tego akapitu, ponieważ historia w tym czasie nie jest już„ gałęziami ”, chociaż wykres zatwierdzania byłby dość rozgałęziony).Masz trochę bólu, ponieważ pracujesz przeciwko używanemu narzędziu.
git
Podejście jest bardzo elegancki i wydajny, zwłaszcza w obszarze / łączących rozgałęzienia; jeśli zrobisz to dobrze (jak wspomniano powyżej, szczególnie z--no-ff
), to przeskakuje i ogranicza się do innych podejść (np. bałagan wywrotowy posiadania równoległych struktur katalogów dla gałęzi).Niekończące się, równoległe - tak.
Niemożliwe, plątanina - nie.
Dlaczego nie działa tak jak wynalazca
git
, twoi koledzy i reszta świata, każdego dnia?Brak innych opcji; nie tak źle.
źródło
Zmiażdżenie długoterminowego odgałęzienia spowoduje utratę dużej ilości informacji.
To, co bym zrobił, to spróbuje przekształcić master w długoterminowy sidebranch przed scaleniem sidebranch w master . W ten sposób utrzymujesz każde zatwierdzenie w master, jednocześnie czyniąc historię zatwierdzeń liniową i łatwiejszą do zrozumienia.
Gdybym nie mógł zrobić tego łatwo przy każdym zatwierdzeniu, pozwoliłbym, aby był nieliniowy , aby zachować przejrzystość kontekstu programowania. Moim zdaniem, jeśli mam problematyczne połączenie podczas podziału mistrza na boczny oddział, oznacza to, że nieliniowość miała znaczenie w świecie rzeczywistym. Oznacza to, że łatwiej będzie zrozumieć, co się stało, na wypadek, gdy będę musiał zagłębić się w historię. Czerpię też natychmiastową korzyść z tego, że nie muszę robić bazy.
źródło
rebase
. Bez względu na to, czy jest to najlepsze podejście (osobiście nie miałem z nim wielkich doświadczeń, chociaż nie korzystałem z niego zbyt często), zdecydowanie wydaje się, że jest to najbliższe temu, czego OP naprawdę chce - a konkretnie kompromis między zrzutem historii poza kolejnością a całkowitym ukryciem tej historii.Osobiście wolę rozwijać się w rozwidleniu, a następnie pobierać żądania scalenia do głównego repozytorium.
Oznacza to, że jeśli chcę ponownie wprowadzić zmiany w systemie master lub zmiażdżyć niektóre zatwierdzenia WIP, mogę to zrobić całkowicie. Mogę też poprosić o połączenie całej mojej historii.
To, co lubię robić, to robić mój rozwój na gałęzi, ale często bazuję na master / dev. W ten sposób otrzymuję najnowsze zmiany od master bez konieczności wiązań zatwierdzeń scalania w mojej gałęzi lub konieczności radzenia sobie z całym mnóstwem konfliktów scalania, gdy nadszedł czas scalenia z master.
Aby jednoznacznie odpowiedzieć na twoje pytanie:
Tak - możesz scalić je raz na gałąź (gdy funkcja lub poprawka jest „kompletna”) lub jeśli nie lubisz zatwierdzania scalania w swojej historii, możesz po prostu wykonać szybkie scalanie do przodu na masterie po wykonaniu ostatecznej zmiany bazy.
źródło
Kontrola wersji polega na wyrzucaniu elementów bezużytecznych.
Problem polega na tym, że praca w toku w gałęzi funkcji może zawierać wiele „spróbujmy tego ... nie, to nie działało, zastąpmy to tym” i wszystkie zatwierdzenia oprócz końcowego „to” właśnie się kończy bezużyteczne zanieczyszczanie historii.
Ostatecznie należy zachować historię (niektóre z nich mogą się przydać w przyszłości), ale tylko „czystą kopię” należy scalić.
W Git można to zrobić, rozgałęziając najpierw gałąź funkcji (aby zachować całą historię), a następnie (interaktywnie) zwalniając gałąź gałęzi funkcji z master, a następnie scalając gałąź ponownie.
źródło
git
i są lokalne. To, co nazywa sięfoo
w jednym repozytorium, może być nazwanebar
w innym. I mają one być tymczasowe: nie chcesz zaśmiecać swojego repozytorium tysiącami gałęzi funkcji, które muszą być utrzymywane przy życiu, aby uniknąć utraty historii. I musiałbyś zachować te gałęzie, aby historia była referencją, ponieważgit
ostatecznie usunie wszystkie zatwierdzenia, które nie są już dostępne dla gałęzi.