Git --distributed-is-the-new-centralized
Chapters ▾ 2nd Edition

5.2 Gedistribueerd Git - Bijdragen aan een project

Bijdragen aan een project

De grote moeilijkheid bij het beschrijven van dit proces is dat er een enorm aantal variaties mogelijk zijn in hoe het gebeurt. Om dat Git erg flexibel is, kunnen en zullen mensen op vele manieren samenwerken, en het is lastig om te beschrijven hoe je zou moeten bijdragen aan een project - ieder project is net weer een beetje anders. Een aantal van de betrokken variabelen zijn het aantal actieve bijdragers, gekozen workflow, je commit toegang, en mogelijk de manier waarop externe bijdragen worden gedaan.

De eerste variabele is aantal actieve bijdragers. Hoeveel gebruikers dragen actief code bij aan dit project, en hoe vaak? In veel gevallen zal je twee of drie ontwikkelaars met een paar commits per dag hebben, of misschien minder voor wat meer slapende projecten. Voor zeer grote bedrijven of projecten kan het aantal ontwikkelaars in de duizenden lopen, met tientallen of zelfs honderden patches die iedere dag binnenkomen. Dit is belangrijk omdat met meer en meer ontwikkelaars, je meer en meer problemen tegenkomt bij het je verzekeren dat code netjes gepatched of eenvoudig gemerged kan worden. Wijzigingen die je indient kunnen verouderd of zwaar beschadigd raken door werk dat gemerged is terwijl je ermee aan het werken was, of terwijl je wijzigingen in de wacht stonden voor goedkeuring of toepassing. Hoe kun je jouw code consequent up-to-date en je patches geldig houden?

De volgende variabele is de gebruikte workflow in het project. Is het gecentraliseerd, waarbij iedere ontwikkelaar gelijkwaardige schrijftoegang heeft tot de hoofd codebasis? Heeft het project een eigenaar of integrator die alle patches controleert? Worden alle patches ge(peer)reviewed en goedgekeurd? Ben jij betrokken bij dat proces? Is er een luitenanten systeem neergezet, en moet je je werk eerst bij hen inleveren?

Het volgende probleem is je commit toegang. De benodigde workflow om bij te dragen aan een project is heel anders als je schrijftoegang hebt tot het project dan wanneer je dat niet hebt. Als je geen schrijftoegang hebt, wat is de voorkeur van het project om bijdragen te ontvangen? Is er überhaupt een beleid? Hoeveel werk draag je per keer bij? Hoe vaak draag je bij?

Al deze vragen kunnen van invloed zijn op hoe je effectief bijdraagt aan een project en welke workflows de voorkeur hebben of die beschikbaar zijn voor je. We zullen een aantal van deze aspecten behandelen in een aantal voorbeelden, waarbij we van eenvoudig tot complex zullen gaan. Je zou in staat moeten zijn om de specifieke workflows die je in jouw praktijk nodig hebt te kunnen herleiden naar deze voorbeelden.

Commit richtlijnen

Voordat we gaan kijken naar de specifieke gebruiksscenario’s, volgt hier een kort stukje over commit berichten. Het hebben van een goede richtlijn voor het maken commits en je daar aan houden maakt het werken met Git en samenwerken met anderen een stuk makkelijker. Het Git project levert een document waarin een aantal goede tips staan voor het maken van commits waaruit je patches kunt indienen - je kunt het lezen in de Git broncode in het Documentation/SubmittingPatches bestand.

Als eerste wil je geen witruimte fouten indienen. Git geeft je een eenvoudige manier om hierop te controleren - voordat je commit, voer git diff --check uit, wat mogelijke witruimte fouten identificeert en ze voor je afdrukt.

Uitvoer van `git diff --check`.
Uitvoer van git diff --check.

Als je dat commando uitvoert alvorens te committen, kun je al zien of je op het punt staat witruimte problemen te committen waaraan andere ontwikkelaars zich zouden kunnen ergeren.

Probeer vervolgens om van elke commit een logische set wijzigingen te maken. Probeer, als het je lukt, om je wijzigingen verteerbaar te houden - ga niet het hele weekend zitten coderen op vijf verschillende problemen om dat vervolgens op maandag als één gigantische commit in te dienen. Zelfs als je gedurende het weekend niet commit, gebruik dan het staging gebied op maandag om je werk in ten minste één commit per probleem op te splitsen, met een bruikbaar bericht per commit. Als een paar van de wijzigingen hetzelfde bestand betreffen, probeer dan git add --patch te gebruiken om bestanden gedeeltelijk te stagen (in detail behandeld in “Interactief stagen”). De snapshot aan de kop van het project is gelijk of je nu één commit doet of vijf, zolang alle wijzigingen op een gegeven moment maar toegevoegd zijn, probeer dus om het je mede-ontwikkelaars makkelijk te maken als ze je wijzigingen moeten bekijken. Deze aanpak maakt het ook makkelijker om één wijziging eruit te selecteren of terug te draaien, mocht dat later nodig zijn. “Geschiedenis herschrijven” beschrijft een aantal handige Git trucs om geschiedenis te herschrijven en bestanden interactief te stagen - gebruik deze instrumenten als hulp om een schone en begrijpelijke historie op te bouwen voordat deze naar iemand anders wordt gestuurd.

Het laatste om in gedachten te houden is het commit bericht. Als je er een gewoonte van maakt om een goede kwaliteit commit berichten aan te maken, dan maakt dat het gebruik van en samenwerken in Git een stuk eenvoudiger. In het algemeen zouden je berichten moeten beginnen met een enkele regel die niet langer is dan 50 karakters en die de wijzigingen beknopt omschrijft, gevolgd door een lege regel en daarna een meer gedetailleerde uitleg. Het Git project vereist dat de meer gedetailleerde omschrijving ook je motivatie voor de verandering bevat, en de nieuwe implementatie tegen het oude gedrag afzet. Dit is een goede richtlijn om te volgen. Het is ook een goed idee om de gebiedende wijs te gebruiken in deze berichten. Met andere woorden, gebruik commando’s. In plaats van “Ik heb testen toegevoegd voor” of “Testen toegevoegd voor” gebruik je “Voeg testen toe voor”. Hier is een sjabloon dat origineel geschreven is door Tim Pope:

Short (50 chars or less) summary of changes

More detailed explanatory text, if necessary.  Wrap it to
about 72 characters or so.  In some contexts, the first
line is treated as the subject of an email and the rest of
the text as the body.  The blank line separating the
summary from the body is critical (unless you omit the body
entirely); tools like rebase can get confused if you run
the two together.

Further paragraphs come after blank lines.

  - Bullet points are okay, too

  - Typically a hyphen or asterisk is used for the bullet,
    preceded by a single space, with blank lines in
    between, but conventions vary here

Als al je commit berichten er zo uit zien, dan zullen de dingen een stuk eenvoudiger zijn voor jou en de ontwikkelaars waar je mee samenwerkt. Het Git project heeft goed geformatteerde commit berichten - ik raad je aan om git log --no-merges uit te voeren om te zien hoe een goed geformatteerde project-commit historie eruit ziet.

In de volgende voorbeelden en verder door de rest van dit boek, zijn omwille van bondigheid, de berichten niet zo netjes geformateerd als dit; in plaats daarvan gebruiken we de -m optie voor git commit. Doe wat wij zeggen, niet wat wij doen.

Besloten klein team

De eenvoudigste opzet die je waarschijnlijk zult tegenkomen is een besloten project met één of twee andere ontwikkelaars. Met “besloten” bedoel ik gesloten broncode - zonder leestoegang voor de buitenwereld. Jij en de andere ontwikkelaars hebben allemaal push toegang op de repository.

In deze omgeving kan je een workflow aanhouden die vergelijkbaar is met wat je zou doen als je Subversion of een andere gecentraliseerd systeem zou gebruiken. Je hebt nog steeds de voordelen van zaken als offline committen en veel eenvoudiger branchen en mergen, maar de workflow kan erg vergelijkbaar zijn. Het grootste verschil is dat het mergen aan de client-kant gebeurt en niet tijdens het committen aan de server-kant. Laten we eens kijken hoe het er uit zou kunnen zien als twee ontwikkelaars samen beginnen te werken met een gedeelde repository. De eerste ontwikkelaar, John, cloned de repository, maakt een wijziging, en commit lokaal. (De protocol berichten zijn met ... vervangen in deze voorbeelden om ze iets in te korten.)

# Machine van John
$ git clone john@githost:simplegit.git
Initialized empty Git repository in /home/john/simplegit/.git/
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'removed invalid default value'
[master 738ee87] removed invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

De tweede ontwikkelaar, Jessica, doet hetzelfde - cloned de repository en commit een wijziging:

# Machine van Jessica
$ git clone jessica@githost:simplegit.git
Initialized empty Git repository in /home/jessica/simplegit/.git/
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

Nu pusht Jessica haar werk naar de server:

# Machine van Jessica
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

John probeert ook zijn werk te pushen:

# Machine van John
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

John mag niet pushen omdat Jessica in de tussentijd gepusht heeft. Dit is belangrijk om te begrijpen als je gewend bent aan Subversion, omdat het je zal opvallen dat de twee ontwikkelaars niet hetzelfde bestand hebben aangepast. Waar Subversion automatisch zo’n merge op de server doet als verschillende bestanden zijn aangepast, moet je in Git de commits lokaal mergen. John moet Jessica’s wijzigingen ophalen (fetch) en ze mergen voor hij mag pushen:

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

Hierna ziet de lokale repository van John er ongeveer zo uit:

De afwijkende historie van John.
De afwijkende historie van John.

John heeft een referentie naar de wijzigingen die Jessica gepusht heeft, maar hij moet ze mergen met zijn eigen werk voordat hij het mag pushen:

$ git merge origin/master
Merge made by recursive.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Het mergen gaat soepeltjes - de commit historie van John ziet er nu zo uit:

De repository van John na `origin/master` te hebben gemerged.
De repository van John na origin/master te hebben gemerged.

Nu kan John zijn code testen om er zeker van te zijn dat alles nog steeds goed werkt, en dan kan hij zijn nieuwe gemergede werk pushen naar de server:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

Tenslotte ziet de commit historie van John er zo uit:

De historie van John na te hebben gepushed naar de `origin` server.
De historie van John na te hebben gepushed naar de origin server.

In de tussentijd heeft Jessica gewerkt op een topic branch. Ze heeft een topic branch genaamd issue54 aangemaakt en daar drie commits op gedaan. Ze heeft John’s wijzigingen nog niet opgehaald, dus haar commit historie ziet er zo uit:

Topic branch van Jessica.
Topic branch van Jessica.

Jessica wil met John synchroniseren, dus ze haalt de wijzigingen op:

# Machine van Jessica
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

Dit haalt het werk op dat John in de tussentijd gepusht heeft. De historie van Jessica ziet er nu zo uit:

Historie van Jessica na het fetchen van de wijzigingen van John.
Historie van Jessica na het fetchen van de wijzigingen van John.

Jessica denkt dat haar topic branch nu klaar is, maar ze wil weten wat ze in haar werk moet mergen zodat ze kan pushen. Ze voert git log uit om dat uit te zoeken:

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   removed invalid default value

De issue54..origin/master syntax is een log filter dat Git vraagt om alleen de lijst van commits te tonen die op de laatstgenoemde branch (in dit geval origin/master) staan die niet in de eerstegenoemde (in dit geval issue54) staan. We zullen deze syntax in detail bespreken in “Commit reeksen”.

Nu zien we in de uitvoer dat er een commit is die John gemaakt heeft die Jessica nog niet gemerged heeft. Als ze origin/master merged, is dat de enkele commit die haar lokale werk zal wijzigen.

Nu kan Jessica het werk van haar topic branch mergen in haar master branch, het werk van John (origin/master) in haar master branch mergen, en dan naar de server pushen. Eerst schakelt ze terug naar haar master branch om al dit werk te integreren:

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Ze kan origin/master of issue54 als eerste mergen - ze zijn beide stroomopwaarts dus de volgorde maakt niet uit. Uiteindelijk zou de snapshot gelijk moeten zijn ongeacht welke volgorde ze kiest; alleen de geschiedenis zal iets verschillen. Ze kiest ervoor om issue54 eerst te mergen:

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

Er doen zich geen problemen voor, zoals je kunt zien was het een eenvoudige fast-forward. Nu merged Jessica het werk van John (origin/master):

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

Alles merged netjes, en de historie van Jessica ziet er nu zo uit:

Historie van Jessica na het mergen van de wijzigingen van John.
Historie van Jessica na het mergen van de wijzigingen van John.

Nu is origin/master bereikbaar vanuit Jessica’s master branch, dus ze zou in staat moeten zijn om succesvol te pushen (even aangenomen dat John in de tussentijd niet weer iets gepusht heeft):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

Iedere ontwikkelaar heeft een paar keer gecommit en elkaars werk succesvol gemerged.

Historie van Jessica na al haar wijzigingen naar de server te hebben gepusht.
Historie van Jessica na al haar wijzigingen naar de server te hebben gepusht.

Dit is één van de eenvoudigste workflows. Je werkt een tijdje, over het algemeen in een topic branch, en merged dit in je master branch als het klaar is om te worden geïntegreerd. Als je dat werk wilt delen, dan merge je het in je eigen master branch, en vervolgens fetch je origin/master en merge je deze als het gewijzigd is, en als laatste push je deze naar de master branch op de server. De algemene volgorde is als volgt:

Algemene volgorde van gebeurtenissen voor een eenvoudige multi-ontwikkelaar Git workflow.
Algemene volgorde van gebeurtenissen voor een eenvoudige multi-ontwikkelaar Git workflow.

Besloten aangestuurd team

In het volgende scenario zul je kijken naar de rol van de bijdragers in een grotere besloten groep. Je zult leren hoe te werken in een omgeving waar kleine groepen samenwerken aan functies, waarna die team-gebaseerde bijdragen worden geïntegreerd door een andere partij.

Stel dat John en Jessica samen werken aan een functie, terwijl Jessica en Josie aan een tweede aan het werken zijn. In dit geval gebruikt het bedrijf een integratie-manager achtige workflow, waarbij het werk van de individuele groepen alleen wordt geïntegreerd door bepaalde ontwikkelaars, en de master branch van het hoofd repo alleen kan worden vernieuwd door die ontwikkelaars. In dit scenario wordt al het werk gedaan in team-gebaseerde branches en later door de integrators samengevoegd.

Laten we Jessica’s workflow volgen terwijl ze aan haar twee features werkt, in parallel met twee verschillende ontwikkelaars in deze omgeving. We nemen even aan dat ze haar repository al gecloned heeft, en dat ze besloten heeft als eerste te werken aan featureA. Ze maakt een nieuwe branch aan voor de functie en doet daar wat werk:

# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

Op dit punt, moet ze haar werk delen met John, dus ze pusht haar commits naar de featureA branch op de server. Jessica heeft geen push toegang op de master branch - alleen de integratoren hebben dat - dus ze moet naar een andere branch pushen om samen te kunnen werken met John:

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Jessica mailt John om hem te zeggen dat ze wat werk gepusht heeft in een branch genaamd featureA en dat hij er nu naar kan kijken. Terwijl ze op terugkoppeling van John wacht, besluit Jessica te beginnen met het werken aan featureB met Josie. Om te beginnen start ze een nieuwe feature branch, gebaseerd op de master branch van de server:

# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

Nu doet Jessica een paar commits op de featureB branch:

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

Jessica’s repository ziet eruit als volgt:

Initiële commit historie van Jessica.
Initiële commit historie van Jessica.

Ze is klaar om haar werk te pushen, maar ze krijgt een mail van Josie dat een branch met wat initieel werk erin al gepusht is naar de server in de featureBee branch. Jessica moet die wijzigingen eerst mergen met die van haar voordat ze kan pushen naar de server. Ze kan dan Josie’s wijzigingen ophalen met git fetch:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Jessica kan dit nu mergen in het werk wat zij gedaan heeft met git merge:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

Er is wel een klein probleempje - ze moet het gemergde werk in haar featureB branch naar de featureBee branch op de server zetten. Ze kan dat doen door de lokale branch door te geven aan het git push commando, gevolgd door een dubbele punt (:), gevolgd door de remote branch:

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

Dit wordt een refspec genoemd. Zie “De Refspec” voor een meer gedetailleerde behandeling van Git refspecs en de verschillende dingen die je daarmee kan doen. Meer ook de -u vlag op; dit is een verkorte notatie voor -set-upstream, wat de branches voor eenvoudigere pushen en pullen op een later moment inricht.

Vervolgens mailt John naar Jessica om te zeggen dat hij wat wijzigingen naar de featureA branch gepusht heeft, en om haar te vragen die te verifiëren. Ze voert een git fetch uit om die wijzigingen op te halen:

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

Daarna kan ze zien wat er veranderd is met git log:

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    changed log output to 30 from 25

Uiteindelijk merged ze het werk van John in haar eigen featureA branch:

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Jessica wil iets kleins bijstellen, dus doet ze nog een commit en pusht dit naar de server:

$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

De commit historie van Jessica ziet er nu zo uit:

De historie van Jessica na het committen op een feature branch.
De historie van Jessica na het committen op een feature branch.

Jessica, Josie en John informeren de integrators nu dat de featureA en featureBee branches op de server klaar zijn voor integratie in de hoofdlijn. Nadat zij die branches in de hoofdlijn geïntegreerd hebben, zal een fetch de nieuwe merge commits ophalen, waardoor de commit historie er zo uit ziet:

De historie van Jessica na het mergen van haar beide topic branches.
De historie van Jessica na het mergen van haar beide topic branches.

Veel groepen schakelen om naar Git juist vanwege de mogelijkheid om meerdere teams in parallel te kunnen laten werken, waarbij de verschillende lijnen van werk laat in het proces gemerged worden. De mogelijkheid van kleinere subgroepen of een team om samen te werken via remote branches zonder het hele team erin te betrekken of te hinderen is een enorm voordeel van Git. De volgorde van de workflow die je hier zag is ongeveer dit:

Eenvoudige volgorde in de workflow van dit aangestuurde team.
Eenvoudige volgorde in de workflow van dit aangestuurde team.

Klein openbaar project

Het bijdragen aan openbare, of publieke, projecten gaat op een iets andere manier. Omdat je niet de toestemming hebt om de branches van het project rechtstreeks te updaten, moet je het werk op een andere manier naar de beheerders krijgen. Dit eerste voorbeeld beschrijft het bijdragen via afsplitsen (forken) op Git hosts die het eenvoudig aanmaken van forks ondersteunen. Vele hosting sites ondersteunen dit (waaronder GitHub, BitBucket, Google Code, repo.or.cz, en andere), en veel project beheerders verwachten deze manier van bijdragen. De volgende paragraaf behandelt projecten die de voorkeur hebben om bijdragen in de vorm van patches via e-mail te ontvangen.

Eerst zal je waarschijnlijk de hoofdrepository clonen, een topic branch maken voor de patch of reeks patches die je van plan bent bij te dragen, en je werk daarop doen. De te volgen stappen zien er in principe zo uit:

$ git clone (url)
$ cd project
$ git checkout -b featureA
# (work)
$ git commit
# (work)
$ git commit

Je kunt eventueel besluiten rebase -i te gebruiken om je werk in één enkele commit samen te persen (squash), of het werk in de commits te herschikken om de patch eenvoudiger te kunnen laten reviewen door de beheerders - zie “Geschiedenis herschrijven” voor meer informatie over het interactief rebasen.

Als je werk op de branch af is, en je klaar bent om het over te dragen aan de beheerders, ga je naar de originele project pagina en klik op de “Fork” knop. Hiermee maak je een eigen overschrijfbare fork van het project. Je moet de URL van deze nieuwe repository URL toevoegen als een tweede remote, in dit geval myfork genaamd:

$ git remote add myfork (url)

Dam moet je je werk daar naartoe pushen. Het is het makkelijkst om de topic branch waar je op zit te werken te pushen naar je repository, in plaats van het te mergen in je master branch en die te pushen. De reden hiervan is, dat als het werk niet wordt geaccepteerd of alleen ge-cherry picked (deels overgenomen), je jouw master branch niet hoeft terug te draaien. Als de beheerders je werk mergen, rebasen of cherry picken, dan krijg je het uiteindelijk toch binnen door hun repository te pullen:

$ git push -u myfork featureA

Als jouw werk gepusht is naar jouw fork, dan moet je de beheerder inlichten. Dit wordt een pull request (haal-binnen-verzoek) genoemd, en je kunt deze via de website genereren - GitHub heeft een eigen Pull Request mechanisme die we verder zullen behandelen in Chapter 6 of je roept het git request-pull commando aan en stuurt een mail met de uitvoer handmatig naar de projectbeheerder mailen.

Het request-pull commando neemt de basis branch waarin je de topic branch gepulled wil hebben, en de URL van de Git repository waar je ze uit wil laten pullen, en maakt een samenvatting van alle wijzigingen die je gepulled wenst te hebben. Bijvoorbeeld, als Jessica John een pull request wil sturen, en ze heeft twee commits gedaan op de topic branch die ze zojuist gepusht heeft, dan kan ze dit uitvoeren:

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
  John Smith (1):
        added a new function

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      add limit to log function
      change log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

De uitvoer kan naar de beheerders gestuurd worden: het vertelt ze waar het werk vanaf gebranched is, vat de commits samen en vertelt waar vandaan ze dit werk kunnen pullen.

Bij een project waarvan je niet de beheerder bent, is het over het algemeen eenvoudiger om een branch zoals master altijd de origin/master te laten tracken, en je werk te doen in topic branches die je eenvoudig weg kunt gooien als ze geweigerd worden. Als je je werkthema’s gescheiden houdt in topic branches maakt dat het ook eenvoudiger voor jou om je werk te rebasen als de punt van de hoofd-repository in de tussentijd verschoven is en je commits niet langer netjes toegepast kunnen worden. Bijvoorbeeld, als je een tweede onderwerp wilt bijdragen aan een project, ga dan niet verder werken op de topic branch die je zojuist gepusht hebt - begin opnieuw vanaf de master branch van het hoofd repository:

$ git checkout -b featureB origin/master
# (work)
$ git commit
$ git push myfork featureB
# (email maintainer)
$ git fetch origin

Nu zijn al je onderwerpen opgeslagen in een silo - vergelijkbaar met een patch reeks (queue) - die je kunt herschrijven, rebasen en wijzigen zonder dat de onderwerpen elkaar beïnvloeden of van elkaar afhankelijk zijn zoals hier:

Initiële commit historie met werk van `featureB`.
Initiële commit historie met werk van featureB.

Stel dat de project-beheerder een verzameling andere patches binnengehaald heeft en jouw eerste branch geprobeerd heeft, maar dat die niet meer netjes merged. In dat geval kun je proberen die branch te rebasen op origin/master, de conflicten op te lossen voor de beheerder, en dan je wijzigingen opnieuw aanbieden:

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

Dit herschrijft je historie zodat die eruit ziet als in Figure 5-18.

Commit historie na werk van `featureA`.
Commit historie na werk van featureA.

Omdat je de branch gerebased hebt, moet je de -f specificeren met je push commando om in staat te zijn de featureA branch op de server te vervangen met een commit die er geen afstammeling van is. Een alternatief zou zijn dit nieuwe werk naar een andere branch op de server te pushen (misschien featureAv2 genaamd).

Laten we eens kijken naar nog een mogelijk scenario: de beheerder heeft je werk bekeken in je tweede branch en vind het concept goed, maar zou willen dat je een implementatie detail verandert. Je gebruikt deze gelegenheid meteen om het werk te baseren op de huidige master branch van het project. Je begint een nieuwe branch gebaseerd op de huidige origin/master branch, squashed de featureB wijzigingen er naartoe, lost eventuele conflicten op, doet de implementatie wijziging en pusht deze terug als een nieuwe branch:

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
# (change implementation)
$ git commit
$ git push myfork featureBv2

De --squash optie pakt al het werk op de gemergde branch en perst dat samen in één wijzigingsset (changeset) die de staat van de repository geeft alsof er een echte merge zou hebben plaatsgevonden, zonder feitelijk een merge commit te doen. Dit betekent dat je toekomstige commit maar één ouder heeft en geeft je de ruimte om alle wijzigingen te introduceren uit een andere branch en daarna meer wijzigingen te maken voordat de nieuwe commit wordt vastgelegd. Daarnaast kan de --no-commit handig zijn door de merge commit uit te stellen in het geval van een standaard merge proces.

Je kunt de beheerder nu een bericht sturen dat je de gevraagde wijzigingen gemaakt hebt en dat ze die wijzigingen kunnen vinden in je featureBv2 branch.

Commit historie na het `featureBv2` werk.
Commit historie na het featureBv2 werk.

Openbaar project per e-mail

Veel projecten hebben vastgestelde procedures voor het accepteren van patches - je zult de specifieke regels voor elk project moeten controleren, ze zullen namelijk verschillen. Omdat er verscheidene oudere, grotere projecten zijn die patches accepteren via ontwikkelaar-maillijsten, zullen we nu een voorbeeld hiervan behandelen.

De workflow is vergelijkbaar met het vorige geval - je maakt topic branches voor iedere patch waar je aan werkt. Het verschil is hoe je die aanlevert bij het project. In plaats van het project te forken en naar je eigen schrijfbare versie te pushen, genereer je e-mail versies van iedere reeks commits en mailt die naar de ontwikkelaar-maillijst:

$ git checkout -b topicA
# (work)
$ git commit
# (work)
$ git commit

Nu heb je twee commits die je wil sturen naar de maillijst. Je gebruikt git format-patch om de mbox-geformatteerde bestanden te genereren die je kunt mailen naar de lijst. Dit vormt iedere commit om naar een e-mail bericht met de eerste regel van het commit bericht als het onderwerp, en de rest van het bericht plus de patch die door de commit wordt geïntroduceerd als de inhoud. Het prettige hieraan is dat met het toepassen van een patch uit een mail die gegenereerd is met format-patch alle commit informatie blijft behouden.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

Het format-patch commando drukt de namen af van de patch bestanden die het maakt. De -M optie vertelt Git te kijken naar hernoemingen. De bestanden komen er uiteindelijk zo uit te zien:

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

Je kunt deze patch bestanden ook aanpassen om meer informatie, die je niet in het commit bericht wilt laten verschijnen, voor de maillijst toe te voegen. Als je tekst toevoegt tussen de --- regel en het begin van de patch (de diff --git regel), dan kunnen ontwikkelaars dit lezen, maar tijdens het toepassen van de patch wordt dit genegeerd.

Om dit te mailen naar een maillijst, kan je het bestand in je mail-applicatie plakken of het sturen via een commandoregel programma. Het plakken van de tekst veroorzaakt vaak formaterings problemen, in het bijzonder bij “slimmere” clients die de newlines en andere witruimte niet juist behouden. Gelukkig levert Git een gereedschap die je helpt om juist geformatteerde patches via IMAP te versturen, wat het alweer een stuk makkelijker voor je maakt. We zullen je laten zien hoe je een patch via Gmail stuurt, wat de mail-applicatie is waar we het meest bekend mee zijn. Je kunt gedetailleerde instructies voor een aantal mail programma’s vinden aan het eind van het eerder genoemde Documentation/SubmittingPatches bestand in de Git broncode.

Eerst moet je de imap sectie in je ~/.gitconfig bestand instellen. Je kunt iedere waarde apart instellen met een serie git config commando’s, of je kunt ze handmatig toevoegen, maar uiteindelijk moet je config bestand er ongeveer zo uitzien:

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = p4ssw0rd
  port = 993
  sslverify = false

Als je IMAP server geen SSL gebruikt, zijn de laatste twee regels waarschijnlijk niet nodig, en de waarde voor host zal imap:// zijn in plaats van imaps://. Als dat ingesteld is, kun je git imap-send gebruiken om de patch reeks in de Drafts map van de gespecificeerde IMAP server te zetten:

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

Nu zou je in staat moeten zijn om naar je Drafts folder te gaan, het To veld te veranderen in het adres van de mail lijst waar je de patch naar to stuurt, en mogelijk de onderhouder of de persoon verantwoordelijk voor dat deel te CC-en, en het te versturen.

Je kunt de patches ook via een SMTP server sturen. Net als hiervoor, kan je elke waarde apart zetten met een reeks van git config commando’s, of je kunt ze handmatig in de sendemail sectie in je ~/.gitconfig bestand toevoegen:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

Als dit gedaan is, kan je git send-email gebruiken om je patches te sturen:

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

Dan zal Git een berg log-informatie oplepelen die er ongeveer zo uitziet voor elke patch die je aan het versturen bent:

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK

Samenvatting

In dit hoofdstuk is een aantal veel voorkomende workflows behandeld, die je kunt gebruiken om te kunnen werken in een aantal zeer verschillende typen Git projecten die je misschien zult tegenkomen. En er zijn een aantal nieuwe gereedschappen geïntroduceerd die je helpen om dit proces te beheren. Wat hierna volgt zal je laten zien hoe je aan de andere kant van de tafel werkt: een Git project beheren. Je zult leren hoe een welwillende dictator of integratie manager te zijn.