Nazwane gałęzie a wiele repozytoriów

130

Obecnie używamy Subversion na stosunkowo dużej bazie kodu. Każde wydanie otrzymuje własną gałąź, a poprawki są wykonywane względem linii głównej i migrowane do gałęzi wydania przy użyciusvnmerge.py

Uważam, że nadszedł czas, aby przejść do lepszej kontroli źródła i od jakiegoś czasu bawię się z Mercurialem.

Wydaje się, że istnieją dwie szkoły zarządzania taką strukturą wydania przy użyciu Mercurial. Każde wydanie otrzymuje własne repozytorium, a poprawki są wprowadzane w gałęzi wydania i wypychane do gałęzi głównej (i wszelkich innych nowszych gałęzi wydania). LUB przy użyciu nazwanych gałęzi w ramach jednego repozytorium (lub wielu pasujących kopii).

W obu przypadkach wygląda na to, że mogę używać czegoś takiego jak przeszczep, aby cherrypick zmiany do włączenia do gałęzi wydania.

Proszę cię; jakie są względne zalety każdego podejścia?

James Emerton
źródło

Odpowiedzi:

129

Największą różnicą jest sposób zapisywania nazw oddziałów w historii. W przypadku gałęzi nazwanych nazwa gałęzi jest osadzona w każdym zestawie zmian i tym samym stanie się niezmienną częścią historii. W przypadku klonów nie będzie trwałego zapisu pochodzenia określonego zestawu zmian.

Oznacza to, że klony świetnie nadają się do szybkich eksperymentów, w których nie chcesz zapisywać nazwy gałęzi, a nazwane gałęzie są dobre dla gałęzi długoterminowych („1.x”, „2.x” i podobne).

Należy również zauważyć, że pojedyncze repozytorium może z łatwością pomieścić wiele lekkich gałęzi w Mercurial. Takie gałęzie w repozytorium można dodać do zakładek, aby można je było łatwo znaleźć ponownie. Powiedzmy, że sklonowałeś repozytorium firmy, które wyglądało tak:

[a] --- [b]

Wycinasz, robisz [x]i [y]:

[a] --- [b] --- [x] --- [y]

Oznacza to, że ktoś umieszcza [c]i [d]w repozytorium, więc kiedy wyciągniesz, otrzymasz wykres historii, taki jak ten:

            [x] --- [y]
           /
[a] --- [b] --- [c] --- [d]

Tutaj są dwie głowy w jednym repozytorium. Twoja kopia robocza zawsze będzie odzwierciedlać pojedynczy zestaw zmian, tak zwany nadrzędny zbiór zmian kopii roboczej. Sprawdź to za pomocą:

% hg parents

Powiedzmy, że zgłasza [y]. Możesz zobaczyć głowy z

% hg heads

a to zgłosi [y]i [d]. Jeśli chcesz zaktualizować swoje repozytorium do czystego pobrania [d], po prostu wykonaj ( [d]zastąp numerem wersji [d]):

% hg update --clean [d]

Wtedy zobaczysz ten hg parentsraport [d]. Oznacza to, że twój następny commit będzie miał [d]jako rodzic. W ten sposób możesz naprawić błąd, który zauważyłeś w głównej gałęzi i utworzyć zestaw zmian [e]:

            [x] --- [y]
           /
[a] --- [b] --- [c] --- [d] --- [e]

Aby przekazać [e]tylko zestaw zmian , musisz to zrobić

% hg push -r [e]

gdzie [e]jest skrót zestawu zmian. Domyślnie hg pushpo prostu porówna repozytoria i zobaczy to [x], [y]i [e]brakuje, ale możesz nie chcieć udostępniać, [x]a [y]jeszcze.

Jeśli poprawka dotyczy również Ciebie, chcesz ją połączyć z gałęzią funkcji:

% hg update [y]
% hg merge

W ten sposób wykres repozytorium będzie wyglądał następująco:

            [x] --- [y] ----------- [z]
           / /
[a] --- [b] --- [c] --- [d] --- [e]

gdzie [z]jest połączenie między [y]i [e]. Mogłeś również zdecydować się na wyrzucenie gałęzi:

% hg strip [x]

Mój główny punkt tej historii jest następujący: pojedynczy klon może z łatwością reprezentować kilka ścieżek rozwoju. Tak było zawsze dla „zwykłego hg” bez użycia jakichkolwiek rozszerzeń. Jednak rozszerzenie zakładek jest bardzo pomocne. Umożliwi to przypisanie nazw (zakładek) do zestawów zmian. W powyższym przypadku będziesz chciał mieć zakładkę na głowie programisty i jedną na głowie upstream. Zakładki można wypychać i wyciągać za pomocą Mercurial 1.6 i stały się one funkcją wbudowaną w Mercurial 1.8.

Gdybyś zdecydował się na utworzenie dwóch klonów, twój klon programistyczny wyglądałby tak po utworzeniu [x]i [y]:

[a] --- [b] --- [x] --- [y]

Twój klon wyższego szczebla będzie zawierał:

[a] --- [b] --- [c] --- [d]

Teraz zauważysz błąd i napraw go. Tutaj nie musisz tego robić, hg updateponieważ klon nadrzędny jest gotowy do użycia. Zobowiązujesz się i tworzysz [e]:

[a] --- [b] --- [c] --- [d] --- [e]

Aby dołączyć poprawkę do swojego klonu programistycznego, ściągasz ją tam:

[a] --- [b] --- [x] --- [y]
           \
            [c] --- [d] --- [e]

i scal:

[a] --- [b] --- [x] --- [y] --- [z]
           \ /
            [c] --- [d] --- [e]

Wykres może wyglądać inaczej, ale ma taką samą strukturę, a efekt końcowy jest taki sam. Używając klonów, trzeba było trochę mniej umysłowo księgować.

Nazwane gałęzie tak naprawdę nie pojawiły się tutaj na zdjęciu, ponieważ są dość opcjonalne. Sam Mercurial został opracowany przy użyciu dwóch klonów przez lata, zanim przestawiliśmy się na używanie nazwanych gałęzi. Oprócz gałęzi domyślnej utrzymujemy gałąź o nazwie „stabilna” i tworzymy nasze wydania w oparciu o gałąź „stabilną”. Zobacz standardową stronę rozgałęzień na wiki, aby uzyskać opis zalecanego przepływu pracy.

Martin Geisler
źródło
1
jeśli zestaw zmian pochodzi od innego użytkownika, zostałby zarejestrowany, więc używanie klonów nie jest niczym złym. Podczas wypychania nowej funkcji często nie jest interesujące wiedzieć, że zrobiłeś to z osobnego repozytorium. Istnieje również rozszerzenie gałęzi lokalnej, które daje tylko lokalny oddział. Przydatne, gdy klonowanie repozytorium wiąże się z wysokimi kosztami (czas / przestrzeń).
Johannes Rudolph
2
w odniesieniu do: „klony są świetne do szybkich eksperymentów” - nie, nie są! Co jeśli masz kilka tysięcy plików w repozytorium? Klonowanie zajmie całe wieki (zawsze powyżej 1 minuty), a przełączanie gałęzi tylko chwilę (<1 sekunda). Nadal używanie nazwanych gałęzi będzie zanieczyszczać dziennik zmian. Czy to nie ślepy zaułek? Albo coś mi brakuje?
seler
Dobra seler; Brzmi jak modyfikacja jego oryginalnego argumentu; Klony są dobre, gdy obciążenie związane z wieloma kompletnymi kopiami nie jest dla ciebie ważne lub gdy możesz użyć linków symbolicznych / twardych hg, aby zmniejszyć koszt oddzielnych lokalnych kopii roboczych na oddział.
Warren P,
@seler: masz rację, że klony są niepraktyczne, jeśli żądany kod jest duży. Zakładki są więc rozwiązaniem.
Martin Geisler,
29

Myślę, że chcesz mieć całą historię w jednym repozytorium. Tworzenie krótkoterminowego repozytorium służy do krótkoterminowych eksperymentów, a nie do ważnych wydarzeń, takich jak wydania.

Jednym z rozczarowań Mercurial jest to, że wydaje się, że nie ma łatwego sposobu na stworzenie krótkotrwałej gałęzi, bawienie się nią, porzucenie jej i zbieranie śmieci. Gałęzie są wieczne. Współczuję, że nigdy nie chciałem porzucić historii, ale super tanie, jednorazowe gałęzie to gitcecha, którą naprawdę chciałbym zobaczyć hg.

Norman Ramsey
źródło
20
Możesz bardzo łatwo utworzyć taką gałąź funkcji: "aktualizuj hg" do punktu gałęzi, edytuj z dala i "hg commit". Utworzyłeś nową, rozbieżną linię rozwoju - nowe zatwierdzenia rozszerzą tę gałąź. Użyj "hg clone -r", aby się go pozbyć, lub usuń go w linii przez "hg strip". Dlatego nie bądź rozczarowany i nie przychodź na listy mailingowe Mercurial z prośbami o dodanie funkcji.
Martin Geisler
8
Wygląda na hg stripto, że tego chcę. Dlaczego nie można usunąć oddziałów wniosków z dokumentacji online?
Norman Ramsey
11
Zobacz także ten wpis na blogu, aby wyjaśnić, w jaki sposób Mercurial ma, w pewnym sensie, tańsze niż git oddziały: stevelosh.com/blog/entry/2009/8/30/…
Martin Geisler
9
Możesz zamknąć nazwaną gałąź za pomocą hg ci --close-branch .
Andrey Vlasovskikh
3
@Norman Ramsey: kiedy ludzie mówią, że gałęzi nie można usunąć, oznacza to, że nie można zmienić nazwy gałęzi osadzonej w zestawach zmian. A changeet us no on a branch, określa gałąź. Będziesz musiał usunąć zestaw zmian i utworzyć go ponownie z inną nazwą gałęzi, jeśli chcesz „przenieść” go do innej gałęzi.
Martin Geisler
14

Powinieneś zrobić jedno i drugie .

Zacznij od zaakceptowanej odpowiedzi od @Norman: użyj jednego repozytorium z jedną nazwaną gałęzią na wydanie.

Następnie przygotuj jeden klon na gałąź wydania do kompilacji i testowania.

Jedną kluczową uwagą jest to, że nawet jeśli używasz wielu repozytoriów, powinieneś unikać używania transplantdo przenoszenia zestawów zmian między nimi, ponieważ 1) zmienia to hash i 2) może wprowadzać błędy, które są bardzo trudne do wykrycia, gdy istnieją sprzeczne zmiany między zestawem zmian, przeszczep i gałąź docelową. Zamiast tego chcesz wykonać zwykłe scalanie (i bez premerge: zawsze wizualnie sprawdź scalanie), co spowoduje, że @mg powiedział na końcu swojej odpowiedzi:

Wykres może wyglądać inaczej, ale ma taką samą strukturę, a efekt końcowy jest taki sam.

Mówiąc bardziej szczegółowo, jeśli używasz wielu repozytoriów, repozytorium „główne” (lub domyślne, główne, programistyczne, cokolwiek) zawiera WSZYSTKIE zestawy zmian we WSZYSTKICH repozytoriach. Każde repozytorium wydania / gałęzi jest po prostu jedną gałęzią w linii głównej, wszystkie połączone z powrotem w jedną lub drugą stronę z powrotem do linii głównej, dopóki nie zechcesz zostawić starej wersji w tyle. Dlatego jedyną rzeczywistą różnicą między tym głównym repozytorium a pojedynczym repozytorium w schemacie nazwanych gałęzi jest po prostu to, czy gałęzie są nazwane, czy nie.

To powinno wyjaśnić, dlaczego powiedziałem „zacznij od jednego repozytorium”. To pojedyncze repozytorium jest jedynym miejscem, w którym będziesz kiedykolwiek potrzebować szukać zestawu zmian w dowolnej wersji . Możesz dalej oznaczać zestawy zmian w gałęziach wydania w celu przechowywania wersji. Jest koncepcyjnie przejrzysty i prosty oraz upraszcza administrowanie systemem, ponieważ jest to jedyna rzecz, która absolutnie musi być dostępna i możliwa do odzyskania przez cały czas.

Ale nadal musisz utrzymywać jeden klon na gałąź / wydanie, który musisz zbudować i przetestować. To trywialne, jak tylko możeszhg clone <main repo>#<branch> <branch repo> , a następnie hg pullw repozytorium gałęzi pobierze tylko nowe zestawy zmian z tej gałęzi (plus zestawy zmian przodków z wcześniejszych gałęzi, które zostały scalone).

Ta konfiguracja najlepiej pasuje do modelu zatwierdzania jądra Linuksa pojedynczego pullera (czy nie jest dobrze działać jak Lord Linus. W naszej firmie nazywamy integratorem ról ), ponieważ główne repozytorium jest jedyną rzeczą, którą programiści muszą sklonować, a ściągacz musi wciągnąć. Utrzymanie repozytoriów branżowych służy wyłącznie do zarządzania wersjami i może być całkowicie zautomatyzowane. Deweloperzy nigdy nie muszą wyciągać z repozytorium / wypychać do niego.


Oto przykład @ mg przekształcony dla tej konfiguracji. Punkt wyjścia:

[a] - [b]

Utwórz nazwaną gałąź dla wydania, powiedz „1.0”, kiedy dojdziesz do wydania alfa. Zatwierdź poprawki błędów:

[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

(1.0)nie jest prawdziwym zestawem zmian, ponieważ nazwana gałąź nie istnieje, dopóki nie zatwierdzisz. (Możesz wykonać trywialne zatwierdzenie, takie jak dodanie tagu, aby upewnić się, że nazwane gałęzie są poprawnie utworzone.)

Połączenie [m1] jest kluczem do tej konfiguracji. W przeciwieństwie do repozytorium programistów, w którym może być nieograniczona liczba głowic, NIE chcesz mieć wielu głowic w swoim głównym repozytorium (z wyjątkiem starej, martwej gałęzi wydania, jak wspomniano wcześniej). Więc za każdym razem, gdy masz nowe zestawy zmian w gałęziach wydania, musisz natychmiast scalić je z powrotem do gałęzi domyślnej (lub późniejszej gałęzi wydania). Gwarantuje to, że wszelkie poprawki błędów w jednym wydaniu są również uwzględnione we wszystkich późniejszych wersjach.

W międzyczasie rozwój domyślnej gałęzi jest kontynuowany w kierunku następnej wersji:

          ------- [c] - [d]
         /
[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

I jak zwykle musisz scalić dwie głowice na domyślnej gałęzi:

          ------- [c] - [d] -------
         /                         \
[a] - [b] ------------------ [m1] - [m2]
         \                 /
          (1.0) - [x] - [y]

A to jest klon gałęzi 1.0:

[a] - [b] - (1.0) - [x] - [y]

Teraz jest ćwiczeniem, aby dodać następną gałąź wydania. Jeśli jest to 2.0, to na pewno odłączy się domyślnie. Jeśli jest to 1.1, możesz wybrać rozgałęzienie 1.0 lub domyślne. Niezależnie od tego, każdy nowy zestaw zmian w wersji 1.0 powinien być najpierw scalony do następnej gałęzi, a następnie do domyślnego. Można to zrobić automatycznie, jeśli nie ma konfliktu, co skutkuje jedynie pustym scaleniem.


Mam nadzieję, że ten przykład wyjaśnia moje wcześniejsze uwagi. Podsumowując, zalety tego podejścia to:

  1. Jedno autorytatywne repozytorium zawierające pełny zestaw zmian i historię wersji.
  2. Przejrzyste i uproszczone zarządzanie wersjami.
  3. Przejrzysty i uproszczony przepływ pracy dla programistów i integratorów.
  4. Ułatw iteracje przepływu pracy (przeglądy kodu) i automatyzację (automatyczne puste scalanie).

UPDATE hg robi to samo : główne repozytorium zawiera domyślne i stabilne gałęzie, a stabilne repozytorium to stabilny klon gałęzi. Nie używa jednak gałęzi wersjonowanej, ponieważ znaczniki wersji wzdłuż gałęzi stabilnej są wystarczająco dobre do celów zarządzania wydaniami.

Geoffrey Zheng
źródło
5

O ile wiem, główna różnica polega na tym, co już powiedziałeś: nazwane rozgałęzione znajdują się w jednym repozytorium. Nazwane gałęzie mają wszystko pod ręką w jednym miejscu. Oddzielne repozytoria są mniejsze i łatwe do przenoszenia. Powodem, dla którego istnieją dwie szkoły, jest to, że nie ma wyraźnego zwycięzcy. Argumenty, z którejkolwiek strony mają dla Ciebie największy sens, są prawdopodobnie tymi, z którymi powinieneś się zdecydować, ponieważ prawdopodobnie ich otoczenie jest najbardziej podobne do Twojego.

dwc
źródło
2

Myślę, że to zdecydowanie pragmatyczna decyzja w zależności od aktualnej sytuacji, np. Rozmiaru funkcji / przeprojektowania. Myślę, że rozwidlenia są naprawdę dobre dla współpracowników z rolami, które jeszcze nie są zaangażowane, aby dołączyć do zespołu programistów, udowadniając swoje umiejętności z pomijalnymi kosztami technicznymi.

thSoft
źródło
0

Naprawdę odradzałbym używanie nazwanych gałęzi dla wersji. Naprawdę do tego służą tagi. Nazwane gałęzie są przeznaczone do długotrwałych rozrywek, takich jakstable gałąź.

Dlaczego więc nie używać po prostu tagów? Podstawowy przykład:

  • Rozwój odbywa się na jednej gałęzi
  • Za każdym razem, gdy tworzone jest wydanie, należy je odpowiednio oznaczyć
  • Od tego momentu rozwój jest kontynuowany
  • Jeśli masz jakieś błędy do naprawienia (lub cokolwiek) w określonej wersji, po prostu zaktualizuj do jego tagu, wprowadź zmiany i zatwierdź

To stworzy nową, nienazwaną głowę na defaultgałęzi, aka. anonimowa gałąź, która jest w porządku w hg. Możesz wtedy w dowolnym momencie scalić poprawki błędów z powrotem do głównej ścieżki rozwoju. Nie ma potrzeby używania nazwanych gałęzi.

DanMan
źródło
To zależy w dużej mierze od twojego procesu. Na przykład aplikacja internetowa dobrze działa z hierarchią gałęzi stabilnej / testowej / rozwojowej. Tworząc oprogramowanie desktopowe, zazwyczaj mamy gałąź programistyczną (domyślną) oraz jedną do trzech (!) Różnych gałęzi utrzymania. Trudno jest przewidzieć, kiedy będziemy musieli ponownie odwiedzić gałąź, a posiadanie ścieżki odgałęzienia w wersji major.minor ma pewną elegancję.
James Emerton,