git-svn kan kurere git-envy

Er du en av dem som skuler litt misunnelig på de kule gutta med friprog-prosjekter på GitHub?
De som brancher og forker og merger i ett sett. De som skryter av at de sitter på flyet og commiter mens de er offline.
Mens du er stuck med subversion som bare fungerer når du er på nett og som er klar til å bite deg bare du tenker på å lage en aldri så liten branch.

git-svn

git-svn to the resque!

Med git-svn kan du ha så mange lokale git-branch’er du vil. Hoppe frem og tilbake, slå sammen alle de små commitene du gjorde mens du satt på t-banen og hente frem den ene lille endringen du gjorde i en eksperimentell branch i forrige uke (vha. funksjonen med det velklingende navnet cherry-pick). Jeg skal ikke misjonere mere for gits kvaliteter i denne posten, det er beskrevet veldig mye bedre enn det jeg klarer på bl.a. disse nettstedene:

Clone

For å komme i gang trenger man bare å kjøre denne kommandoen:

$ git svn clone <subversion-url> ?optional-directory-name?

Smør deg med tålmodighet, for den laster ned hele svns commit-historie. Det finnes ekstra options for å laste ned alle eksisterende branch’er og tags også. Nå har du en git-klone av svn-repoet.

Rebase

Når man bruker svn update hentes eventuelle endringer ned fra det sentrale repo’et og disse merges sammen med din lokale kopi.
git svn rebase prøver å etterligne det update gjør og da kan det se sånn ut:

$ git svn rebase
M Makefile
r3 = ae409c2f5fe0831f22d6dc891652b5f9159f35de (git-svn)
First, rewinding head to replay your work on top of it…
Applying: Local git commit.

På linje 4 ser du at det står noe om rewinding og replay your work on top of it . Dette er noe git-svn må gjøre fordi svn ikke har noe konsept om lokale commits, noe git selv har. For at ting ikke skal krølle seg når man skal commite tilbake til svn må man først rulle tilbake alle de lokale git-commitene, hente ned endringene som finnes i svn og så legge de lokale commitene på toppen av disse igjen. Historien stokkes litt om på, men det er nødvendig for at svn skal henge med på det som skjer.

Når du har kjørt git svn rebase er du klar til å commite dine lokale endringer tilbake til svn repo’et med kommandoen git svn dcommit.

Det er viktig å huske på at en del av det som lett kan gjøres i git ikke fungerer i det hele tatt i svn. Derfor bør man følge disse reglene:

  1. Ikke dcommit endringer som er en følge av en merge til subversion. Svn håndterer merge-opperasjoner på en annen måte enn git og derfor må man holde git-historien linjær (ingen merging fra andre brancher, bare rebasing).
  2. Aldri gjør endringer (amend, squash, sletting) på commits som er dcommitet til svn. Samme regel gjelder forøvrig for git-commits som er lagt ut i et offentlig repo.

Workflow

Til daglig bruker jeg en workflow som innebærer å ha en master-branch som bare brukes til å synke fra og til svn-repoet og separate topic-brancher for hver feature jeg skal utvikle.For å forenkle synkingen mellom topic-branchen, master-branchen og svn-repoet bruker jeg to bash-script som heter hack og ship.

#!/bin/bash
#
# HACK
#
CURRENT=`git branch | grep ‘\*’ | awk ‘{print $2}’`
status=`git checkout master | awk ‘{print $1}’`
if [[ ! "${status}" =~ error ]]; then
git svn rebase
git checkout ${CURRENT}
git rebase master
fi

Det som skjer her er:

  1. Topic-branchens navn lagres i CURRENT
  2. Forsøker å bytte over til master-branchen og sjekker at det går bra.
  3. Henter oppdateringer fra svn repoet med svn rebase.
  4. Bytter tilbake til CURRENT og kjører rebase mot master

Dette scriptet kjøres for å holde topic-branchen oppdatert med både master-branchen og svn-repo.

Når oppgaven er utført og jeg skal sjekke inn alle endringene bruker jeg ship-scriptet.


#!/bin/bash
#
# SHIP
#
CURRENT=`git branch | grep ‘\*’ | awk ‘{print $2}’`
git rebase -i master
status=`git checkout master | awk ‘{print $1}’`
if [[ ! "${status}" =~ error ]]; then
git merge ${CURRENT}
git svn dcommit
git checkout ${CURRENT}
fi

Her gjør vi det motsatte av i hack:

  1. Interactive rebase (-i) gjør det mulig å slå sammen (squash) alle små commits gjort underveis i utviklinga
  2. Endringene merges over i master-branchen. (den observante leser vil se at dette strider mot regel 1. over, men pga. den første rebasen går det bra)
  3. Alt commites til svn og man er tilbake i topic-branchen
  4. Man kan nå fortsette utviklingnen i topic-branchen eller gå tilbake i master, slette topic-branchen og man har blanke ark til å starte på neste oppgave

Et steg i riktig retning

Det var det, ingen grunn til å la seg sinke av svn når man kan kjøre med en slik hybridløsning. Etterhvert kan du kanskje overbevise hele teamet, og produkteier, om gits fortreffelighet og bruke det som et ekte distribuert versjonkontrollsystem. Inntil du klarer dette kan du nå enkelt bruke topic-branches, lage hotfix-patcher og spore endringer (og hvem som gjorde dem) ned på linjenivå.

Når det blir så lett å rulle tilbake endringer blir man mindre redd for å gjøre dem!
  • Erlend Halvorsen

    Hva blir egentlig forskjellen på å kjøre “git svn rebase” direkte fra topic branch’en og å gjøre det via master?

  • Torbjørn Vatn

    Hvis man kjører “git svn rebase” i topic-branch’en vil man praksis oppnå det samme som “git svn rebase” i master med en etterfølgende “git svn rebase master” i topic-branchen.

    Fordelen med å gjøre all sync’ing mot SVN via master er at man da alltid har et oppdatert og “rent” utgangspunkt å starte nye topic-branch’er fra.

    F.eks. hvis man jobber på en feature, men må snike inn en bugfix i all hast, kan det være bra å ha en “ren” master å hoppe tilbake til for å starte hotfix’ingen.

  • Ole Chr. Rynning

    Kjempefin oppsummering!

    Jeg bruker litt enklere git-aliaser. For eksempel (~/.gitconfig):
    [alias]
    sup = svn fetch
    sci = svn dcommit
    sreb = svn rebase
    srebt = svn rebase remotes/trunk

  • Finn Johnsen

    Takk for fin intro Torbjørn! Skal teste dette ut. Eneste jeg er redd for, er å mangle godt verktøy for å takle konflikter v/dcommit. Har blitt veldig glad i eclipse på akkurat dette.

  • http://twitter.com/ovstetun Trond Marius Øvstetun

    Når jeg bruker skriptene over så klarer ikke git å oppdatere min commit med svn-id og det faktum at jeg har merget min branch inn i trunk forsvinner etter dcommit.
    Vært borti det? Sånn det er hos meg nå så kan jeg ikke jobbe videre med branchen min og må slette den (med -D for den er ikke registrert som merget..) og evt opprette ny om jeg vil jobbe videre i samme stil. Litt av poenget blir liksom borte da :S

    Sitter på windows med git i cygwin BTW om det har noe å si..

  • http://vatn.org torbjornvatn

    @Finn Johnsen: Jeg bruker av og til opendiff (på OSX) for å løse konflikter, og hvis man bruker Linux er meld veldig bra. Som regel synes jeg det er like greit å løse konflikter rett i editoren.

  • http://vatn.org torbjornvatn

    @Trond Marius: Har ikke vært ute for akkurat dette problemet nei. Kan kanskje være en idé å kjøre hver enkelt kommando for seg og se om output’en i konsollet kan gi noen pointers.

    Du kan jo teste msygit (http://code.google.com/p/msysgit/) og se om du får samme resultat da?