Git Workflows

Aus Thomas-Krenn-Wiki
Zur Navigation springen Zur Suche springen

Mittels Git sind verschiedene verteilte Architekturen von Repositories möglich, die mit anderen Versionierungs-Systemen schwieriger zu modellieren sind. Berühmtestes Beispiel von verteilten Repos ist wohl der Aufbau beim Linux-Kernel. Dieser Artikel erklärt, wie mehrere Repos für Entwickler zum Einsatz kommen können und wie diese damit arbeiten. Dabei geht es um das Einspielen der eigenen Entwicklungs-Fortschritte in sein Repo am Server sowie die Zurverfügungstellung für die anderen.

Zentralisierte Architektur

In einem zentralen Punkt werden alle Arbeiten der Entwickler gesammelt. Daher synchronisieren sich auch alle Entwickler mit diesem Repo, um ihre eigenen Systeme mit den Fortschritten der anderen zu aktualisieren. Für kleinere Gruppen von Entwicklern ergeben sich bei zentralen Systemen nur selten Probleme. Unter Umständen schwierig wird es dann, wenn ein Entwickler Änderungen ins gemeinsame Repo pusht und ein zweiter daraufhin ebenfalls ein Push durchführen will. Der zweite Entwickler muss dann zuerst die Änderungen des Ersten einarbeiten und etwaige Konflikte zwischen seiner Version und der des Repos beheben[1]. Diese Art eines zentralisierten Repos ist für viele Entwickler eine intuitive Möglichkeit mit einem Repo zu arbeiten, da dieses Konzept für [subversion.tigris.org SVN] als auch für Git gilt. Auch Git wird nicht zulassen, dass ältere Informationen mit neuen Versionen überschrieben werden. Die früheren Version müssen zuerst geholt und eingearbeitet werden, um die aktuellen Dateien ins Repo spielen zu können. Aus dieser Vorgehensweise lässt sich bereits erkennen, dass der Arbeits- bzw. Einarbeitungs-Aufwand mit jedem im Repo arbeitenden Entwickler steigt. Für Git sind daher noch weitere, flexible Architekturen möglich, die die Zusammenarbeit von vielen Entwicklern koordinieren.

Verteilte Architektur

Aufbau von verteilten Repos für zwei Entwickler.

Die nebenstehende Abbildung zeigt ein Beispiel, wie mit mehreren Repos Entwickler zusammenarbeiten können. Jeder Entwickler besitzt seine lokalen Repos auf seiner Client-Maschine. Am Server befindet sich für jeden von Ihnen ein weiteres Repo, in das jeder Entwickler seine Änderungen pusht. Diese Repos am Server (dev-1, dev-2) dienen jedoch auch dazu, um den zweiten Entwickler Aktualisierungen zur Verfügung zu stellen.[2] Entwickler 2 synchronisiert sich daher mit Repo "dev-1" und umgekehrt. Somit erhält jeweils der anderer Entwickler jene Arbeiten, die aus den local-Repos in die dev-Repos gepusht wurden (dies gleicht einer Veröffentlichung für die anderen Entwickler). Der Vorteil ist, dass die Versions-Historie eines Entwicklers erhalten bleibt und sich am Server nicht mit der des anderen vermischt. Außerdem kann der Zeitpunkt der Einarbeitung von Änderungen von einem Entwickler selbst bestimmt werden, auch müssen nicht alle Änderungen des anderen übernommen werden. Einigen sich beide Entwickler über Features die ins öffentliche Repo gelangen sollen (Repo "public"), kann einer der beiden die aktuelle Version dorthin pushen, oder beide pushen separat jene Änderungen, die öffentlich werden sollen.

Git Remote Repositories und Branches

Bei einer verteilten Architektur, bei der aktuelle Versionen von Repos anderer Entwickler ins eigene geholt werden, können Remote Repositories und Remote Branches zum Einsatz kommen. Um zu demonstrieren wie mit diesen umgegangen wird, folgt im folgenden Abschnitt ein Beispiel:

Im ersten Schritt werden am Server zwei Repos initialisiert:

tktest@debian-server:~/repos/dev-1$ git init --bare
Initialized empty Git repository in /home/tktest/repos/dev-1/
tktest@debian-server:~/repos/dev-1$ cd ..
tktest@debian-server:~/repos$ cd dev-2
tktest@debian-server:~/repos/dev-2$ git init --bare
Initialized empty Git repository in /home/tktest/repos/dev-2/

Auf den beiden Clients werden die Repos geklont:

  • Client 1:
:~/Repos$ git clone ssh://tktest@192.168.56.101/home/tktest/repos/dev-1 dev-1
Cloning into dev-1...
tktest@192.168.56.101's password: 
warning: You appear to have cloned an empty repository.
:~/Repos/dev-2$ git remote
origin
:~/Repos/dev-1$ git remote -v
origin	ssh://tktest@192.168.56.101/home/tktest/repos/dev-1 (fetch)
origin	ssh://tktest@192.168.56.101/home/tktest/repos/dev-1 (push)
  • Client 2:
:~/Repos$ git clone ssh://tktest@192.168.56.101/home/tktest/repos/dev-2 dev-2
Cloning into dev-2...
tktest@192.168.56.101's password: 
warning: You appear to have cloned an empty repository.
:~/Repos/dev-2$ git remote
origin
:~/Repos/dev-2$ git remote -v
origin	ssh://tktest@192.168.56.101/home/tktest/repos/dev-2 (fetch)
origin	ssh://tktest@192.168.56.101/home/tktest/repos/dev-2 (push)

Nach dem Klonen zeigt sich bereits, dass beide Repos eine Remote-Origin besitzen.

Developer 1

Developer 1 (Dev-1) startet mit der Entwicklung und fügt eine Datei hinzu:

:~/Repos/dev-1$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	function1.c
:~/Repos/dev-1$ git add function1.c
:~/Repos/dev-1$ git commit -m "added function dev-1"
[master (root-commit) f535530] added function dev-1
 1 files changed, 10 insertions(+), 0 deletions(-)
 create mode 100644 function1.c

Dev-1 beschließt, dass auch Dev-2 diese Funktion benötigt und spielt sie in sein Repo am Server. Da er sein Repo am Server geklont hat, braucht er nur ein push auf seine "Origin" (Herkunft, Quelle) durchführen:

:~/Repos/dev-1$ git push origin master
tktest@192.168.56.101's password: 
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 304 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To ssh://tktest@192.168.56.101/home/tktest/repos/dev-1
 * [new branch]      master -> master

Developer 2

Developer 2 (Dev-2) will sich nun den aktuellen Entwicklungsstand von Dev-1 ansehen und evtl. in seinen Stand einarbeiten. Dazu fügt er das Repo von Dev-1 am Server als Remote-Ziel bei sich hinzu:

:~/Repos/dev-2$ git remote add dev-1 ssh://tktest@192.168.56.101/home/tktest/repos/dev-1
:~/Repos/dev-2$ git remote
dev-1
origin

Er erstellt einen eigenen branch und holt sich den aktuellen Stand von Dev-1. Dabei wird ein sogenannter "Remote Tracking Branch" benutzt. Diese Branches werden anschaulich und ausführlich unter Tracking branches and remote tracking branches (gitguys.com) erklärt.

Einige Vorteile sind beispielsweise:

  • Verknüpfung des lokalen Branches - Informationen darüber, in welchem Verhältnis der aktuelle zum Remote Branch steht (vgl. nachfolgendes Beispiel)
  • Die Befehle "push, fetch, pull" werden automatisch im lokalen tracking Branch mit dem Remote-Branch durchgeführt.[3]
:~/Repos/dev-2$ git checkout --track -b dev-1-tmp dev-1/master
Branch dev-1-tmp set up to track remote branch master from dev-1.
Switched to a new branch 'dev-1-tmp'
:~/Repos/dev-2$ git fetch
tktest@192.168.56.101's password: 
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ssh://192.168.56.101/home/tktest/repos/dev-1
   66496da..511cbea  master     -> dev-1/master
:~/Repos/dev-2$ git status
# On branch dev-1-tmp
# Your branch is behind 'dev-1/master' by 1 commit, and can be fast-forwarded.
#
nothing to commit (working directory clean)
:~/Repos/dev-2$ git diff dev-1-tmp dev-1/master 
diff --git a/function1.c b/function1.c
index 37246ca..0ed6d88 100644
--- a/function1.c
+++ b/function1.c
@@ -1,10 +1,11 @@
 #include <stdio.h>
+#include "function2.h"
 
 void function1(void){
        printf("function from dev-1\n");
 }
 
 int main(void){
-       
+       function2();    
        return 0;
 }

Das Diff zeigt, welche Änderungen "dev-1/master" ("dev-1" ist das zu Beginn eingestellte Remote-Repository bereitstellt). Diese Änderung werden in den lokalen Branch übernommen:

:~/Repos/dev-2$ git pull
tktest@192.168.56.101's password: 
Updating 5e1273e..511cbea
Fast-forward
 function1.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
:~/Repos/dev-2$ git branch
* dev-1-tmp
  master
:~/Repos/dev-2$ git log
commit 511cbeabd165a747dd1b018c854557218463c04e
Author: dev-1 <dev-1@exmaple.com>
Date:   Mon Jan 23 14:56:47 2012 +0100

    calling function2

Entwickler-2 hat nun auch die Änderung von Entwickler-1 in seinem lokalen Branch und kann diesen bei Bedarf in seinen Master-Branch übernehmen:

:~/Repos/dev-2$ git checkout master
Switched to branch 'master'
:~/Repos/dev-2$ git merge dev-1-tmp 
Updating 5e1273e..511cbea
Fast-forward
 function1.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
:~/Repos/dev-2$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
nothing to commit (working directory clean)

Einzelnachweise

  1. Pro Git Book (progit.org)
  2. Git Community Book (book.git-scm.com)
  3. Remote Branches (www-cs-students.stanford.edu)

Das könnte Sie auch interessieren

Git Branches
Git Server-Konfiguration
Git-annex Repository auf externer Festplatte