Czy git-svn dcommit po połączeniu w git jest niebezpieczny?

133

Moją motywacją do wypróbowania git-svn jest łatwe łączenie i rozgałęzianie. Wtedy zauważyłem, że człowiek git-svn (1) mówi:

Uruchamianie git-merge lub git-pull NIE jest zalecane na gałęzi, z której planujesz dcommitować. Subversion nie reprezentuje fuzji w żaden rozsądny lub użyteczny sposób; więc użytkownicy korzystający z Subversion nie widzą żadnych wykonanych przez ciebie połączeń. Ponadto, jeśli scalisz lub ściągniesz z gałęzi git, która jest lustrzanym odbiciem gałęzi SVN, dcommit może zatwierdzić niewłaściwą gałąź.

Czy to oznacza, że ​​nie mogę utworzyć lokalnego oddziału z svn / trunk (lub oddziału), zhakować, połączyć się z powrotem w svn / trunk, a następnie wykonać dcommit? Rozumiem, że użytkownicy svn zobaczą ten sam bałagan, jaki zawsze występował w svn pre 1.5.x, ale czy są jakieś inne wady? To ostatnie zdanie też mnie martwi. Czy ludzie rutynowo robią takie rzeczy?

Knut Eldhuset
źródło
14
To jest po prostu błędne: Subversion nie reprezentuje fuzji w żaden rozsądny lub użyteczny sposób; więc użytkownicy używający Subversion nie widzą żadnych wykonanych przez ciebie scaleń. Ponadto, jeśli scalisz lub ściągniesz z gałęzi git, która jest lustrzanym odbiciem gałęzi SVN, dcommit może zatwierdzić niewłaściwą gałąź. Subversion może przedstawiać nie tylko informacje o śledzeniu git merge, ale także znacznie bardziej szczegółowe informacje. To git-svn nie tworzy poprawnego svn: mergeinfo lub nie ustawia dcommit w odpowiedniej gałęzi.
Alexander Kitaev,
2
@AlexanderKitaev: dokumentacja git-svn zmieniła się od tamtego czasu, a dodatkowo istnieje --mergeinfo=<mergeinfo>opcja, która może być w stanie przekazać informacje o scalaniu do SVN. Nie jestem pewien, jak należy go używać.
Mr_and_Mrs_D

Odpowiedzi:

174

Właściwie znalazłem jeszcze lepszy sposób dzięki --no-ffopcji na git merge. Cała ta technika squasha, której używałem wcześniej, nie jest już potrzebna.

Mój nowy przepływ pracy wygląda teraz następująco:

  • Mam „master” oddział, który jest jedyną gałąź że dcommit zi że klon repozytorium SVN ( -sZakładam, że masz standardowy układ SVN w repozytorium trunk/, branches/i tags/):

    git svn clone [-s] <svn-url>
    
  • Pracuję w lokalnym oddziale „praca” ( -btworzy oddział „praca”)

    git checkout -b work
    
  • zatwierdzić lokalnie do gałęzi "work" ( -saby podpisać komunikat o zatwierdzeniu). Zakładam, że w sequelu wykonałeś 3 lokalne zatwierdzenia

    ...
    (work)$> git commit -s -m "msg 1"
    ...
    (work)$> git commit -s -m "msg 2"
    ...
    (work)$> git commit -s -m "msg 3"
    

Teraz chcesz zatwierdzić na serwerze SVN

  • [Ostatecznie] przechowuj modyfikacje, których nie chcesz widzieć na serwerze SVN (często komentowałeś jakiś kod w pliku głównym tylko dlatego, że chcesz przyspieszyć kompilację i skupić się na danej funkcji)

    (work)$> git stash
    
  • przebuduj gałąź główną za pomocą repozytorium SVN (aby zaktualizować z serwera SVN)

    (work)$> git checkout master
    (master)$> git svn rebase
    
  • Wróć do gałęzi pracy i przeprowadź rebase z mistrzem

    (master)$> git checkout work
    (work)$> git rebase master
    
  • Upewnij się, że wszystko jest w porządku, na przykład:

    (work)$> git log --graph --oneline --decorate
    
  • Teraz nadszedł czas, aby połączyć wszystkie trzy zatwierdzenia z gałęzi „work” w „master”, używając tej wspaniałej --no-ffopcji

    (work)$> git checkout master
    (master)$> git merge --no-ff work
    
  • Możesz zauważyć stan dzienników:

    (master)$> git log --graph --oneline --decorate
    * 56a779b (work, master) Merge branch 'work'
    |\  
    | * af6f7ae msg 3
    | * 8750643 msg 2
    | * 08464ae msg 1
    |/  
    * 21e20fa (git-svn) last svn commit
    
  • Teraz prawdopodobnie chcesz edytować ( amend) ostatnie zatwierdzenie dla twoich kolesi z SVN (w przeciwnym razie zobaczą tylko pojedyncze zatwierdzenie z komunikatem "Merge branch 'work'"

    (master)$> git commit --amend
    
  • Na koniec zatwierdź na serwerze SVN

    (master)$> git svn dcommit
    
  • Wróć do pracy i ostatecznie odzyskaj przechowywane pliki:

    (master)$> git checkout work
    (work)$> git stash pop
    
Sebastien Varrette
źródło
4
To jest DOKŁADNIE przepływ pracy, którego szukał ten git n00b. Utwórz gałąź lokalną, aby naprawić błąd, zrób cokolwiek, a następnie wyślij do svn z JEDNYM zatwierdzeniem. Korzystanie z najwyżej ocenionej odpowiedzi tutaj zepsuło to, co robiłem - nie mogłem git svn rebase bez błędów.
João Bragança
19
Czy to nie jest dokładnie to, przed czym ostrzegają cytowane w opcjach dokumenty git-svn? Uruchamiając scalanie z --no-ffopcją, jawnie tworzysz zatwierdzenie scalające (zatwierdzenie z dwoma rodzicami) zamiast szybkiego przewijania do przodu. Aby upewnić się, że wszystkie zatwierdzenia w gałęzi śledzenia svn są zatwierdzeniami pojedynczego rodzica, wszystkie połączenia muszą być albo szybkie do przodu ( --ff-onlymoże w tym pomóc), albo, jeśli trunk zmienił się za twoimi plecami, a --squash, prawda?
jemmons
4
Działa to dla jednorazowej gałęzi, którą natychmiast usuwasz, ale git svn dcommitprzepisuje zatwierdzenie git, które jej dajesz. Oznacza to, że tracisz drugiego rodzica, a teraz twoje repozytorium git nie ma informacji, że kiedykolwiek scaliłeś tę gałąź master. Może to również spowodować niespójność katalogu roboczego.
rescdsk
9
używaj git merge --no-ff work -m "commit message"zamiast dodatkowego git commit --amendkroku
tekumara
3
dla mnie wolałbym używać „git merge - off-only work”, ponieważ chcę zachować wszystkie moje zobowiązania, a nie tylko ostatnie
Moataz Elmasry
49

Tworzenie lokalnych oddziałów jest zdecydowanie możliwe dzięki git-svn. Tak długo, jak używasz lokalnych oddziałów dla siebie i nie próbujesz używać git do łączenia się między gałęziami svn wyższego rzędu, powinno być dobrze.

Mam gałąź "master", której używam do śledzenia serwera svn. To jedyna gałąź, z której się zaangażowałem. Jeśli wykonuję jakąś pracę, tworzę gałąź tematyczną i pracuję nad nią. Kiedy chcę to zrobić, wykonuję następujące czynności:

  1. Zaangażuj wszystko w gałąź tematyczną
  2. git svn rebase (rozwiąż wszelkie konflikty między twoją pracą a svn)
  3. git checkout master
  4. git svn rebase (dzięki temu następny krok będzie szybkim łączeniem do przodu, zobacz komentarze Aarona poniżej)
  5. git merge topic_branch
  6. rozwiąż wszelkie konflikty przy scalaniu (w tym momencie nie powinno ich być)
  7. git svn dcommit

Mam również inną sytuację, w której muszę zachować pewne lokalne zmiany (do debugowania), które nigdy nie powinny być przenoszone do svn. W tym celu mam powyższą gałąź główną, ale także gałąź zwaną „pracą”, w której normalnie pracuję. Gałęzie tematyczne są rozgałęzione od pracy. Kiedy chcę tam zatwierdzić pracę, wyewidencjonowuję mastera i używam cherry-pick, aby wybrać commity z gałęzi work, które chcę przekazać do svn. Dzieje się tak, ponieważ chcę uniknąć wykonywania trzech lokalnych zatwierdzeń zmian. Następnie dcommend z gałęzi głównej i przebudowuję wszystko.

Warto git svn dcommit -nnajpierw uruchomić, aby upewnić się, że masz zamiar popełnić dokładnie to, co zamierzasz. W przeciwieństwie do gita, przepisywanie historii w svn jest trudne!

Uważam, że musi istnieć lepszy sposób na scalenie zmiany w gałęzi tematycznej, pomijając te lokalne zmiany, niż przy użyciu selektora, więc jeśli ktoś ma jakieś pomysły, byłby mile widziany.

Greg Hewgill
źródło
Jeśli dobrze to rozumiem, w git scalenie gita z svn jest w porządku, ale nie svn ze svn? Więc nie mogę połączyć mojego svn / branch z svn / trunk w git?
Knut Eldhuset
1
Przepisanie historii w SVN jest trudne, jeśli chcesz zrobić coś trywialnego. W nietrywialnych przypadkach jest to praktycznie niemożliwe.
Arystoteles Pagaltzis
19
Odpowiedź Grega Hewgilla ma wiele głosów, ale uważam, że jest błędna. Scalanie z topic_branch do mastera jest bezpieczne tylko wtedy, gdy jest to tylko szybkie scalanie do przodu. Jeśli jest to prawdziwe scalanie wymagające zatwierdzenia przez scalanie, to zatwierdzenie przez scalanie zostanie utracone, gdy wykonasz dcommit. Następnym razem, gdy spróbujesz połączyć topic_branch z master, git myśli, że pierwsze połączenie nigdy się nie wydarzyło, a piekło się rozpada. Zobacz pytanie Dlaczego git svn dcommit traci historię zatwierdzeń połączeń dla lokalnych oddziałów?
Aaron,
1
@Greg Hewgill, zmiana nie jest jasna. Napisałeś, że rebase "sprawia, że ​​następne zatwierdzenie jest szybkim łączeniem do przodu". Nie wiem, co to znaczy, ponieważ szybkie scalanie do przodu nie obejmuje zatwierdzenia, ale być może miałeś na myśli „sprawia, że ​​następne scalanie jest szybkim łączeniem do przodu”. Ale jeśli to miałeś na myśli, to nieprawda. To, czy scalenie z topic_branch do mastera jest połączeniem z przyspieszeniem do przodu, czy nie, zależy od tego, czy jakiekolwiek zatwierdzenia zostały wykonane na serwerze głównym od momentu rozgałęzienia. Kiedy ponownie bazujesz na wzorcu, tworzy to nowe zatwierdzenia na wzorcu, więc późniejsze scalanie niekoniecznie musi być szybkim łączeniem do przodu.
Aaron
1
@Aaron: Tak, nie wiem, o czym myślałem. Naprawiłem to ponownie, mam nadzieję, że to ma sens. Jednym z problemów jest to, że w Gicie jest tak wiele sposobów robienia rzeczy, że opisanie określonej sekwencji działań wymaga pewnych wyjaśnień .
Greg Hewgill
33

Proste rozwiązanie: Usuń gałąź 'work' po scaleniu

Krótka odpowiedź: Możesz używać git, jak chcesz (zobacz poniżej prosty przepływ pracy), w tym scalanie. Po prostu upewnij się, że podążaj za każdym poleceniem „ git merge work ” z „ git branch -d work ”, aby usunąć tymczasową gałąź pracy.

Wyjaśnienie w tle: Problem merge / dcommit polega na tym, że za każdym razem, gdy 'git svn dcommit' gałąź, historia scalania tej gałęzi jest `` spłaszczona '': git zapomina o wszystkich operacjach scalania, które przeszły do ​​tej gałęzi: tylko zawartość pliku jest zachowywana, ale fakt, że ta treść (częściowo) pochodzi z innej konkretnej branży, jest stracony. Zobacz: Dlaczego git svn dcommit traci historię zatwierdzeń połączeń dla lokalnych oddziałów?

(Uwaga: git-svn nie może wiele na to poradzić: svn po prostu nie rozumie o wiele potężniejszych połączeń git. Tak więc w repozytorium svn te informacje o scalaniu nie mogą być w żaden sposób reprezentowane.)

Ale to jest cały problem. Jeśli usuniesz gałąź 'work' po jej scaleniu z 'gałęzią główną', twoje repozytorium git jest w 100% czyste i wygląda dokładnie tak, jak repozytorium svn.

Mój przepływ pracy: Oczywiście najpierw sklonowałem zdalne repozytorium svn do lokalnego repozytorium git (może to zająć trochę czasu):

$> git svn clone <svn-repository-url> <local-directory>

Cała praca odbywa się wtedy w „katalogu lokalnym”. Za każdym razem, gdy potrzebuję aktualizacji z serwera (np. „Aktualizacja svn”), robię:

$> git checkout master
$> git svn rebase

Wszystkie prace programistyczne wykonuję w osobnej gałęzi „praca”, która jest tworzona w następujący sposób:

$> git checkout -b work

Oczywiście możesz utworzyć dowolną liczbę gałęzi dla swojej pracy, a także scalać i ponownie bazować między nimi, jak chcesz (po prostu usuń je, gdy skończysz - jak omówiono poniżej). W mojej normalnej pracy bardzo często popełniam:

$> git commit -am '-- finished a little piece of work'

Kolejny krok (git rebase -i) jest opcjonalny - po prostu wyczyści historię przed zarchiwizowaniem jej na svn: Kiedy osiągnąłem stabilny kamień milowy, którym chcę się podzielić z innymi, przepisuję historię tej „pracy” rozgałęziaj i wyczyść komunikaty zmian (inni programiści nie muszą widzieć wszystkich małych kroków i błędów, które popełniłem po drodze - tylko wynik). W tym celu robię

$> git log

i skopiuj hash sha-1 ostatniego zatwierdzenia, który znajduje się w repozytorium svn (zgodnie z identyfikatorem git-svn-id). Wtedy dzwonię

$> git rebase -i 74e4068360e34b2ccf0c5869703af458cde0cdcb

Po prostu wklej hash sha-1 naszego ostatniego zatwierdzenia svn zamiast mojego. Aby uzyskać szczegółowe informacje, możesz przeczytać dokumentację za pomocą polecenia „git help rebase”. W skrócie: to polecenie najpierw otwiera edytor prezentujący twoje zatwierdzenia - po prostu zmień 'pick' na 'squash' dla wszystkich tych zatwierdzeń, które chcesz zgnieść z poprzednimi zatwierdzeniami. Oczywiście pierwsza linia powinna pozostać jako „wybór”. W ten sposób możesz skondensować swoje liczne małe zmiany w jedną lub więcej znaczących jednostek. Zapisz i wyjdź z edytora. Otrzymasz inny edytor z prośbą o przepisanie komunikatów dziennika zmian.

W skrócie: po zakończeniu „hakowania kodu” masuję gałąź „praca”, aż wygląda, jak chcę ją zaprezentować innym programistom (lub jak chcę zobaczyć pracę za kilka tygodni, kiedy przeglądam historię) .

Aby wprowadzić zmiany do repozytorium svn, wykonuję:

$> git checkout master
$> git svn rebase

Teraz wracamy do starej gałęzi „master”, zaktualizowanej o wszystkie zmiany, które zaszły w międzyczasie w repozytorium svn (Twoje nowe zmiany są ukryte w gałęzi „praca”).

Jeśli istnieją zmiany, które mogą kolidować z nowymi zmianami „pracy”, musisz je rozwiązać lokalnie, zanim będziesz mógł przesłać nową pracę (zobacz szczegóły poniżej). Następnie możemy przesłać nasze zmiany do svn:

$> git checkout master
$> git merge work        # (1) merge your 'work' into 'master'
$> git branch -d work    # (2) remove the work branch immediately after merging
$> git svn dcommit       # (3) push your changes to the svn repository

Uwaga 1: Polecenie „git branch -d work” jest całkiem bezpieczne: pozwala tylko na usuwanie gałęzi, których już nie potrzebujesz (ponieważ są już włączone do twojej bieżącej gałęzi). Jeśli wykonasz to polecenie przez pomyłkę przed połączeniem pracy z gałęzią „master”, pojawi się komunikat o błędzie.

Uwaga 2: Upewnij się, że usunąłeś swoją gałąź za pomocą 'git branch -d work' między scalaniem a dcommit: Jeśli spróbujesz usunąć gałąź po dcommit, pojawi się komunikat o błędzie: Kiedy wykonujesz 'git svn dcommit', git zapomina o tym twoja gałąź została połączona z 'master'. Musisz go usunąć za pomocą 'git branch -D work', które nie wykonuje testu bezpieczeństwa.

Teraz natychmiast tworzę nową gałąź „work”, aby uniknąć przypadkowego włamania się do gałęzi „master”:

$> git checkout -b work
$> git branch            # show my branches:
  master
* work

Integrowanie twojej 'pracy' ze zmianami na svn: Oto co robię, gdy 'git svn rebase' ujawnia, że ​​inni zmienili repozytorium svn, gdy pracowałem nad moją gałęzią 'work':

$> git checkout master
$> git svn rebase              # 'svn pull' changes
$> git checkout work           # go to my work
$> git checkout -b integration # make a copy of the branch
$> git merge master            # integrate my changes with theirs
$> ... check/fix/debug ...
$> ... rewrite history with rebase -i if needed

$> git checkout master         # try again to push my changes
$> git svn rebase              # hopefully no further changes to merge
$> git merge integration       # (1) merge your work with theirs
$> git branch -d work          # (2) remove branches that are merged
$> git branch -d integration   # (2) remove branches that are merged
$> git svn dcommit             # (3) push your changes to the svn repository

Istnieją mocniejsze rozwiązania: przedstawiony przepływ pracy jest uproszczony: wykorzystuje uprawnienia git tylko w każdej rundzie „update / hack / dcommit” - ale pozostawia długoterminową historię projektu tak samo liniową, jak repozytorium svn. Jest to w porządku, jeśli chcesz po prostu zacząć używać scalania git w małych pierwszych krokach w starszym projekcie svn.

Kiedy stajesz się bardziej zaznajomieni z git scalania, nie krępuj się badać inne przepływy pracy: Jeśli wiesz, co robisz, ty może mieszać git łączy się z scala svn ( Używanie git-svn (lub podobne), żeby pomóc z svn seryjnej? )

Yaakov Belch
źródło
Wydaje się to niepotrzebnie skomplikowane. Słyszałem, że ludzie robią git merge --squash work, dlaczego tego nie robią? Widzę, jak robisz squash w gałęzi przed scaleniem, jeśli tworzysz więcej niż jeden „wybór” (powiedzmy, że masz 8 zatwierdzeń i zamieniasz każde z 4 zatwierdzeń w 1 i łączenie 2 zatwierdzeń w master). Także aktualizując gałąź 'work', robię to rebase, jest to prostsze niż tworzenie innej gałęzi dla mojego oddziału i robienie
połączeń
8

Odpowiedź Grega Hewgilla na górze nie jest bezpieczna! Jeśli jakieś nowe zatwierdzenia pojawiły się na linii głównej między dwoma "git svn rebase", scalanie nie będzie szybkie do przodu.

Można to zapewnić używając flagi "--ff-only" do git-merge, ale zwykle nie uruchamiam "git svn rebase" w gałęzi, tylko "git rebase master" (zakładając, że jest to tylko lokalna gałąź). Następnie gwarantuje się szybkie przejście do przodu „git merge thebranch”.

Marius K.
źródło
6

Bezpiecznym sposobem łączenia gałęzi svn w git jest użycie git merge --squash. Spowoduje to utworzenie pojedynczego zatwierdzenia i zatrzymanie dodawania wiadomości.

Powiedzmy, że masz gałąź tematyczną svn o nazwie svn-branch.

git svn fetch
git checkout remotes/trunk -b big-merge
git merge --squash svn-branch

w tym momencie wszystkie zmiany z gałęzi svn są zgniecione w jednym zatwierdzeniu oczekującym w indeksie

git commit
luntain
źródło
Jak jednak zauważyli inni, powoduje to utratę szczegółowości zatwierdzeń.
Kzqai
@Tchalvak: Czasami tego chcesz. Ja często popełnia w oddziale fabularnego ala „stałej bałagan z poprzedniego popełnić” i tych ślepych zaułków lubię się ukrywać, bo o dobrej reputacji :-)
schoetbi
1
Tak, chociaż uważam to za spacer po linie, ponieważ za każdym razem, gdy się zgniatasz, tracisz również możliwość późniejszego rozłączania poszczególnych zatwierdzeń. To tylko kwestia różnych wyborów standardów zaangażowania.
Kzqai
1
@schoetbi tak, czasami trzeba wyczyścić niechlujną historię przed publikacją. W tym miejscu bardzo pomocny jest 'git rebase -i <trunk>': możesz dołączać / zmieniać kolejność / usuwać, a nawet dzielić (!) Zatwierdzenia, naprawiać wiadomości selektywnie.
inger
5

Przebuduj lokalną gałąź git do głównej gałęzi git, a następnie dcommit i wygląda na to, że wykonałeś wszystkie zatwierdzenia po kolei, aby ludzie svn mogli zobaczyć to liniowo, tak jak są przyzwyczajeni. Zakładając, że masz lokalny oddział o nazwie Temat, który możesz zrobić

git rebase master topic

który następnie odegra Twoje zatwierdzenia na gałęzi głównej, gotowy do wykonania polecenia

JoeyJ
źródło