Versie beheer met Git onder windows

Software ontwikkelaars gebruiken versie beheersystemen om de verschillende versies van een programma beheerhaar te maken. Daarmee bedoelen we dat we

  1. kunnen terugkeren naar een vorige versie;
  2. kunnen zien wie bepaalde wijzigingen heeft aangebracht
  3. verschillende versies kunnen samenvoegen

In dit artikel bekijken we Git. Het is een versie beheersysteem dat ontwikkeld is door Linus Torvalds en gebruikt wordt voor onder meer de Linux Kernel en Drupal.

Inleiding

Wanneer we sofware ontwikkelen maken we verschillende versies van een programma. Met verschillende versies bedoelen we niet alleen versie 1.1, versie 2.0 of versie 7.0-beta. Dat zijn de officiële versies. Maar elke keer wanneer we een bestand bewaren, maken we een nieuwe “versie” van dat bestand. En tenzij we een backup hebben van de vorige versie, zijn we dat onherroepelijk kwijt.

In de praktijk wordt er natuurlijk een backup gemaakt van bestanden. Maar voor software ontwikkeling, waar we per dag misschien enkele tientallen “versies” hebben van een programma, is die, meestal nachtelijke, backup niet voldoende.

Ontwikkelaars proberen dit soms op te lossen via commentaar. Ze zetten de oude regel in commentaar en zetten de gewijzigde er onder:

  //$content['#theme_wrappers'] = array('theming_example_content_array');
  $content['#theme'] = array('theming_example_content_array');

Wanneer de nieuwe code niet blijkt te werken, kan men de oude gemakkelijk uit commentaar halen.

Wanneer ze nieuwe code toevoegen, gebruiken ze commentaar om het begin en het einde van het nieuwe stuk aan te duiden:

  //Added by Joske 2011-08-12
  $body_instance['label'] = $t('Example Description');
  //End added by Joske 2011-08-12

Deze manier van werken is gevoelig voor fouten (vergeten code in commentaar te zetten) en maakt code onleesbaar (achteraf vergeet men de oude code weg te halen).

Daarom zijn versie beheersystemen een stuk handiger. Een ontwikkelaar kan beslissen om een versie “opzij te zetten” wanneer hij of zij vindt dat die versie de moeite waard is. En wanneer een bestand bewaard wordt, wordt de versie die opzij is gezet niet overschreven.

Versie beheersystemen hebben een Repository. Dat is een databank waar de verschillende versies worden bijgehouden. In de onderstaande tekening zien we dat een ontwikkelaar start met een oorspronkelijke versie van een bestand (Bestand). Het bestand wordt aangepast en bewaard. Op dat moment bestaat alleen de laatste versie (Bestand’).

Wanneer de ontwikkelaar beslist dat hij of zij Bestand’ belangrijk genoeg vindt om te bewaren, moet er een “commit” worden uitgevoerd. Dat wil zeggen dat het bestand wordt gekopieerd naar de Repository.

image

Wanneer de ontwikkelaar vervolgens Bestand’ wijzigt en bewaart, zijn er twee versies van het bestand: Bestand’’ (lokaal) en Bestand’ (repository). Na nog een wijziging (Bestand’’’) gebeurt er weer een commit. Deze commit overschrijft het bestand in de repository niet, maar bewaart Bestand’’’ als een bijkomende versie. Na een laatste wijziging (Bestand’’’’) hebben dus drie versies; Bestand’’’’(lokaal), Bestand’’’(repository) en Bestand’(repository).

Wanneer de wijzigingen in Bestand’’’’ niet juist blijken te zijn, kan de ontwikkelaar terugkeren naar Bestand’’’.

Installatie van Git onder Windows

Git is oorspronkelijk ontwikkeld voor een Linux omgeving. Om Git te gebruiken onder Windows, kunnen we kiezen tussen twee verschillende types:

  1. Cygwin: dit is een Linux shell die onder Windows werkt. Verschillende Linux commando’s zijn gecompileerd voor een Windows omgeving en Git kan in die omgeving worden uitgevoerd.
  2. msysGit: een “native” installatie waarbij we alleen Git installeren en enkele andere Linux commando’s die belangrijk zijn voor Git.

In dit artikel gebruiken we msysGit. Je kunt de installer downloaden van http://msysgit.github.com/. Wanneer we de installatie starten, krijgen we eerst een welkomscherm te zien:

image

Wanneer we op Next klikken, zien we de GNU General Public License verschijnen waaronder git is uitgebracht:

image

Wanneer we nogmaals op Next klikken, kunnen we kiezen in welke directory msysGit moet geïnstalleerd worden:

image

In het volgende scherm kunnen we kiezen welke opties we willen installeren: icoontjes, integratie met Windows Explorer en associatie van bestandsextensies met programma’s:

image

Men raadt aan om de git-cheetah plugin niet te kiezen. Deze plugin verkeert op dit moment nog in het beginstadium.

Daarna kun je kiezen of je een aparte groep wil aanmaken in het Startmenu:

image

Vervolgens kun je kiezen hoe het Windows PATH moet worden aangepast:

  1. Er gebeurt geen aanpassing van het path. Je kunt Git alleen gebruiken wanneer je specifiek de Git bash shell opstart.
  2. Alleen het Git commando wordt toegevoegd aan het path. Je kunt Git uitvoeren via een gewone Windows command prompt
  3. Het Git commando en alle andere hulpprogramma’s voor Git worden toegevoegd aan het path. Je kunt de volledige Git omgeving gebruiken aan de command prompt.

image

In een voorlaatste scherm kun je kiezen welk programma je gebruikt als secure shell:

image

In het laatste scherm moet je bepalen wat er moet gebeuren met end-of-line karakters. Windows maakt gebruik van Carriage Return/Linefeed (CR/LF)terwijl Linux alleen Linefeed(LF) gebruikt:

  1. Bij het ophalen van de bestanden wordt LF omgezet naar CR/LF. Bij het wegschrijven naar de repository moet CR/LF worden omgezet naar LF.
  2. Bij het ophalen van de bestanden gebeurt er niets. Bij het wegschrijven wordt CR/LF omgezet naar LF
  3. Er gebeurt niets tijdens het ophalen en bij het wegschrijven

image

Vervolgens worden de bestanden gekopieerd naar de juiste directories en worden eventueel het path en de registry aangepast.

Eerste kennismaking

Git onder Windows kunnen we op twee manieren gebruiken: via de (bash) command prompt of via een GUI (grafische gebruikersinterface). Wij bekijken Git hier via de command prompt. We kunnen de Git bash prompt opstarten via Start menu-Git-Git bash. In de command prompt die geopend wordt, kunnen we het git commando uitvoeren:

$ git
usage: git [--version] [--exec-path[=<path>]] [--html-path] [--man-path] [--info
-path]
           [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           [-c name=value] [--help]
           <command> [<args>]

The most commonly used git commands are:
   add        Add file contents to the index
   bisect     Find by binary search the change that introduced a bug
   branch     List, create, or delete branches
   checkout   Checkout a branch or paths to the working tree
   clone      Clone a repository into a new directory
   commit     Record changes to the repository
   diff       Show changes between commits, commit and working tree, etc
   fetch      Download objects and refs from another repository
   grep       Print lines matching a pattern
   init       Create an empty git repository or reinitialize an existing one
   log        Show commit logs
   merge      Join two or more development histories together
   mv         Move or rename a file, a directory, or a symlink
   pull       Fetch from and merge with another repository or a local branch
   push       Update remote refs along with associated objects
   rebase     Forward-port local commits to the updated upstream head
   reset      Reset current HEAD to the specified state
   rm         Remove files from the working tree and from the index
   show       Show various types of objects
   status     Show the working tree status
   tag        Create, list, delete or verify a tag object signed with GPG

See 'git help <command>' for more information on a specific command.

We zullen beginnen met een repository aan te maken in een directory c:/xampp/ontwikkeling:

$ git init
Initialized empty Git repository in c:/xampp/ontwikkeling/.git/

We zien hier meteen hoe Git werkt. De Git-gegevens worden bijgehouden in een directory met de naam “.git”. Onder Linux zijn bestanden en directories die met een punt beginnen verborgen. De directory c:/xampp/ontwikkeling is de working directory. Daarin (en in eventuele subdirectories) zet je eigen bestanden. Uit de .git directory blijf je in principe uit. Die wordt beheerd door Git.

We zullen beginnen met een HTML-bestand aan te maken in de working directory met als naam “index.html”:

$ echo "<html><body>Dag allemaal</body></html>" > index.html

Op dit moment weet Git nog niet dat “index.html” bestaat. We zullen het bestand bekend moeten maken bij Git via het het “git add” commando:

$ git add index.html
warning: LF will be replaced by CRLF in index.html.
The file will have its original line endings in your working directory.

Een bestand dat wordt aangemaakt in een working directory valt niet automatisch onder het versiebeheer. We moeten dat bestand zelf toevoegen. Een gemakkelijke manier om alle bestanden (dus ook in subdirectories) toe te voegen aan git is door gebruik te maken van de punt. Een punt verwijst naar de huidige directory. Via het commando

$ git add .

worden alle bestanden die nog niet zijn toegevoegd, toegevoegd aan Git.

Een bestand toevoegen aan Git, zorgt echter nog niet dat we al een kopie aanmaken in de repository. We kunnen dat zien wanneer we de status opvragen:

$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   index.html
#

De eerstvolgende maal dat er een commit wordt uitgevoerd, zal het nieuwe bestand “index.html” bewaard worden in de repository.

Bij een commit moeten we een bericht meegeven. We moeten ook vertellen wie de commit uitvoert:

$ git commit -m"Eerste versie van index.html" \
> --author="Joske Vermeulen <Joske.Vermeulen@schoten.be>"
[master (root-commit) a15f446] Eerste versie van index.html
 Author: Joske Vermeulen <Joske.Vermeulen@schoten.be>
warning: LF will be replaced by CRLF in index.html.
The file will have its original line endings in your working directory.
 1 file changed, 1 insertion(+)
 create mode 100644 index.html

In de praktijk is het vervelend om telkens dezelfde identificatie mee te geven. We kunnen dit ook bewaren in de configuratie van Git. We kiezen er hier voor om de lokale configuratie aan te passen. In de praktijk zullen we de globale configuratie hiervoor gebruiken (–global):

$ git config --local --add user.name "Joske Vermeulen"
$ git config --local --add user.email Joske.Vermeulen@schoten.be

We krijgen niet veel feedback. Om te zien of het gelukt is, kunnen we het volgende commando uitvoeren (de –l optie staat voor ‘list’):

$ git config --local -l
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
core.hidedotfiles=dotGitOnly
user.name=Joske Vermeulen
user.email=Joske.Vermeulen@schoten.be

We kunnen er ook voor zorgen dat het bericht dat we meegeven (via –m) wat uitgebreider ingevuld kan worden met behulp van een editor. Standaard wordt daar ‘vi’ voor bereikt. Wanneer we dat willen wijzigen kunnen we de omgevingsvariabele GIT_EDITOR wijzigen. Stel dat notepad++ in c:/xampp/notepad++ staat.


$ export GIT_EDITOR=/c/xampp/notepad++/notepad++.exe

Wanneer we nu commit –a uitvoeren, zal automatisch notepad++ worden opgestart om een bericht in te geven. (de optie –a is nodig om automatisch alle bestanden die zijn gewijzigd toe te voegen aan de commit. Een alternatief is dat we eerst weer git add gebruiken)

Om te zien wat er in de repository zit (en wat er allemaal gewijzigd is), kunnen we git log gebruiken. Stel dat we nog een bijkomende wijziging (en commit –a) hebben gedaan:

$ git log
commit a1a94b24f2f234de8163b28c2f9c5a287214e8d5
Author: Joske Vermeulen <Joske.Vermeulen@schoten.be>
Date:   Sat Sep 15 10:44:19 2012 +0200

    Head tags toegevoegd

commit a15f44643fe0d3849231b62417f193a76e88618a
Author: Joske Vermeulen <Joske.Vermeulen@schoten.be>
Date:   Sat Sep 15 09:45:14 2012 +0200

    Eerste versie van index.html

Elke wijziging krijgt een uniek nummer. We kunnen de details van een bepaalde commit opvragen met behulp van dat nummer:

$ git show a15f44643fe0d3849231b62417f193a76e88618a
commit a15f44643fe0d3849231b62417f193a76e88618a
Author: Joske Vermeulen <Joske.Vermeulen@schoten.be>
Date:   Sat Sep 15 09:45:14 2012 +0200

    Eerste versie van index.html

diff --git a/index.html b/index.html
new file mode 100644
index 0000000..854f65f
--- /dev/null
+++ b/index.html
@@ -0,0 +1 @@
+<html><body>Dag allemaal</body></html>

Wanneer we git show uitvoeren zonder nummer, krijgen we de details van de laatste commit te zien:

$ git show
commit a1a94b24f2f234de8163b28c2f9c5a287214e8d5
Author: Joske Vermeulen <Joske.Vermeulen@schoten.be>
Date:   Sat Sep 15 10:44:19 2012 +0200

    Head tags toegevoegd

diff --git a/index.html b/index.html
index 854f65f..0aae6b6 100644
--- a/index.html
+++ b/index.html
@@ -1 +1 @@
-<html><body>Dag allemaal</body></html>
+<html><head></head><body>Dag allemaal</body></html>

Bij het opvragen van de details wordt er een diff (verschil) getoond. We zien dat de lijn <html><body>Dag allemaal</body></html> verwijderd is en vervangen door een lijn met een head tag in. Bij de eerste commit is er niets verwijderd (want het bestand was leeg) en is er één regel toegevoegd.

Terugkeren naar een vorige versie

Tot nu toe hebben we gezien dat we wijzigingen kunnen aanbrengen in de working directory en op bepaalde momenten een commit kunnen uitvoeren om die wijzigingen te bewaren in de repository. Maar we hebben natuurlijk niet veel aan de repository wanneer we niet kunnen terugkeren naar een toestand in de repository.

Stel dat we een bestand (per ongeluk) verwijderd hebben. We kunnen het checkout commando gebruiken om een bestand van de repository terug te halen(laatste commit):

$ rm index.html

$ ls index.html
ls: index.html: No such file or directory

$ git checkout index.html

$ ls index.html
index.html

Wanneer we meerdere bestanden willen uitchecken kunnen we ook wildcards gebruiken: git checkout *.

We kunnen een overzicht krijgen van wat er veranderd is door git checkout uit te voeren.

Stel dat we niet de laatste commit willen terugzetten, maar één van de vorige. Dat is een situatie die hopelijk niet zoveel zal voorkomen. Maar stel dat het toch gebeurt, dan moeten we de pointer de verwijst naar de laatste commit verplaatsen naar een vorige. Dat is op zich niet zo moeilijk. Veel moeilijker is het waarschijnlijk te weten hoeveel commits we moeten teruggaan. Daarbij is een overzicht van alle commits handig. We kunnen dat overzicht via een windows programma tonen of via de command prompt.

Om een grafische voorstelling te krijgen van de commit “tree”, kunnen we gebruik maken van gitk.

image

Bovenaan krijgen we een overzicht van alle commits. Rechts onderaan zien we de bestanden. Door een bestand te selecteren, kunnen we de inhoud opvragen.

Wanneer we in command mode willen blijven, kunnen we git log gebruiken:

$ git log --graph --oneline
* 16dd1af Een titel van gemaakt
* a77cf20 Titel toegevoegd
* a1a94b2 Head tags toegevoegd
* a15f446 Eerste versie van index.html

De laatste commit is de master. We kunnen naar een vorige commit gaan met behulp van het ^-teken. We kunnen een aantal ^-tekens achter elkaar zetten. Stel dat we willen terugkeren naar “Head tags toegevoegd”. Dat zijn twee commits terug, vandaar dat we master^^ gebruiken.

$ git checkout master^^
Note: checking out 'master^^'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at a1a94b2... Head tags toegevoegd

De bestanden van “Head tag toegevoegd” zijn nu ineens gekopieerd naar de de working directory. Door het feit dat we van commit veranderd zijn, gaat git ervan uit dat we die bestanden ook wel hadden willen uitchecken.

Om terug te keren naar de laatste commit, gebruiken we git checkout master.

Commits benoemen(tagging)

De notatie “master^^” werkt wel, maar het zou toch handiger zijn wanneer we een ietwat meer betekenisvolle naam konden geven aan een commit. Hiervoor kunnen we gebruik maken van tags. Stel dat we we de huidige commit (de master) interessant vinden. We zouden die als tag “v1.0” kunnen geven:

$ git tag "v1.0"

Vanaf nu  kunnen we naar deze commit verwijzen met de naam v1.0. Wanneer we weer iets wijzigen en een commit uitvoeren, kunnen we in plaats van git checkout master^ het volgende commando gebruiken:

$ git checkout v1.0
Note: checking out 'v1.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 16dd1af... Een titel van gemaakt

Branching

Tot nu toe vormden de commits één lijn. Elke commit (behalve de eerste) had exact één parent. Stel dat ik aan en bijkomende feature wil werken van het programma. Ik zou dat in de master kunnen doen. Maar dat is niet altijd de beste optie:

  • Ik begin met versie 2.0 van een applicatie, maar klanten werken nog met versie 1.0 en af en toe moeten er bugfixes gebeuren. Die moeten natuurlijk gebeuren op versie 1.0. Een branch laat toe om wijzigingen aan te brengen in versie 1.0 en daarna terug verder te werken met 2.0
  • Tijdens de ontwikkeling komt er een bug naar voor die niet direct is opgelost. Terwijl er verder wordt gewerkt naar een release versie, kunnen we in een aparte branch zaken uitproberen om de bug op te lossen.
  • Wanneer we met meerdere werken aan een product, kan elke ontwikkelaar verder werken in een eigen branch

Belangrijk in al deze gevallen is dat het werk van de branch achteraf kan samengevoegd worden met de ‘master’ branch. Een bug die voorkomt in versie 1.0, zit waarschijnlijk ook in versie 2.0. We lossen de bug op in de branch 1.0 en daarna voegen we de wijzigingen in de bug branch samen met de master branch. Ook wanneer we met meerdere ontwikkelaars werken, zal het nodig zijn om regelmatig hun branches samen te voegen met de master branch (of integration branch zoals we hem zouden kunnen noemen).

Een branch maken we aan met het git branch commando:

$ git branch bugs/jv-001

$ git branch
  bugs/jv-001
* master

We hebben hier een branch toegevoegd met als naam bugs/jv–1. Met het git branch commando zonder argumenten, kunnen we lijst van alle branches opvragen. De huidige branch(*) is nog steeds ‘master’. Om de huidige branch te wijzigen, gebruiken we weer git checkout. (we zullen een extra bestand toevoegen aan de branch bugs/jv-001)

$ git checkout bugs/jv-001
Switched to branch 'bugs/jv-001'

$ vi tweede.html

$ git add tweede.html

$ git commit
[bugs/jv-001 207c020] Een tweede bestand toegevoegd (oplossing bug jv-001)
 1 file changed, 1 insertion(+)
 create mode 100644 tweede.html

Wanneer we willen verder werken aan de master branch, kunnen we weer een checkout gebruiken (we wijzigen index.html in de  master)

$ git checkout master
Switched to branch 'master'

$ ls
index.html

$ vi index.html

$ git commit -a
[master 29cb984] Kennismaking toegevoegd
 1 file changed, 3 insertions(+), 1 deletion(-)

Via het ls-commando zien we dat het bestand tweede.html verdwenen is. Wanneer we de twee willen samenvoegen, kunnen we git merge gebruiken:

$ git merge bugs/jv-001
Merge made by the 'recursive' strategy.
 tweede.html | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 tweede.html

$ ls
index.html  tweede.html

Het nieuwe bestand (tweede.html) is nu toegevoegd aan de huidige branch. Er is meteen ook een nieuwe commit gemaakt. We kunnen dat zien via het git log –graph commando:

*   3a3f8f5 Merge branch 'bugs/jv-001'
|\
| * 207c020 Een tweede bestand toegevoegd (oplossing bug jv-001)
* | 29cb984 Kennismaking toegevoegd
|/
* 2046b2d Volgende versie begonnen
* 16dd1af Een titel van gemaakt
* a77cf20 Titel toegevoegd
* a1a94b2 Head tags toegevoegd
* a15f446 Eerste versie van index.html

Dit was echter wel een gemakkelijke merge. Er moest alleen een nieuw bestand toegevoegd worden aan de commit. Stel echter dat we in de branch bugs/jv-001 ook index.html hadden gewijzigd. In sommige gevallen (wanneer de aanpassing op twee verschillende plaatsen is gebeurd) kan git dit nog oplossen. Maar wanneer de aanpassing op dezelfde regel is gebeurd, zal de merge niet lukken:

$ git merge bugs/jv-001
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

$ git status
# On branch master
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      index.html
#
no changes added to commit (use "git add" and/or "git commit -a")

Aangezien git onmogelijk kan weten hoe beide bestanden moeten worden samengevoegd, zullen we dat zelf moeten doen.

Het bestand index.html heeft nu een speciale inhoud gekregen:

<<<<<<< HEAD
<html><head><title>Een titel</title></head><body>Dag allemaal.
Prettige kennismaking met jou
</body></html>
=======
<html><head><title>Een titel</title></head><body>Dag allemaal.</body></html>
<!--commentaar -->
>>>>>>> bugs/jv-001

Het stuk van <<<<<<< HEAD tot ======= bevat het bestand in de master. Het stuk van ======= tot >>>>>>> bugs/jv-001 bevat het bestand van de branch bugs/jv001. We zullen het bestand zelf aanpassen en het de volgende inhoud geven:

<html><head><title>Een titel</title></head><body>Dag allemaal.
Prettige kennismaking met jou
</body></html>
<!--commentaar -->

(want dat is wat de eigenlijk wilden). Vervolgens committen we dit bestand in de huidige (master) branch. We tonen de volledige tree van branches met git log:

$ git commit -a
[master ed2c927] Merge branch 'bugs/jv-001'

$ git log --graph --oneline
*   ed2c927 Merge branch 'bugs/jv-001'
|\
| * ae50e72 Commentaar toegevoegd
* | 0df5807 uitbreiding kennismaking
* |   3a3f8f5 Merge branch 'bugs/jv-001'
|\ \
| |/
| * 207c020 Een tweede bestand toegevoegd (oplossing bug jv-001)
* | 29cb984 Kennismaking toegevoegd
|/
* 2046b2d Volgende versie begonnen
* 16dd1af Een titel van gemaakt
* a77cf20 Titel toegevoegd
* a1a94b2 Head tags toegevoegd
* a15f446 Eerste versie van index.html

Plaats een reactie

Deze site gebruikt Akismet om spam te bestrijden. Ontdek hoe de data van je reactie verwerkt wordt.