Development:Git Workflow

Aus TuxBoxWIKI
Version vom 4. Februar 2013, 17:35 Uhr von Dbt (Diskussion | Beiträge) (Formulierung korrigiert)
Wechseln zu: Navigation, Suche


Review-KandidatDieser Artikel befindet sich derzeit im Reviewprozess. Hilf mit, ihn zu verbessern! Falls du bei weiteren Artikeln helfen willst, findest du hier eine Auswahl offener Artikel.

Mit Git haben Entwickler ein sehr mächtiges und umfangreiches Tool zur Softwareentwicklung an der Hand. Damit man Git wirklich effektiv zur Entwicklung einsetzt, haben sich folgende beschriebene Modelle bei der Softwareentwicklung von großen, aber auch kleinen Projekten etabliert.

Alles beruht auf Branches

Der wohl mächtigste Unterschied zwischen Git und den anderen nicht verteilten Versionsverwaltungssystemen liegt in der lokalen Arbeitsweise und der Möglichkeit sehr einfach und dazu sehr variabel mit Branches zu arbeiten. Mit Branches können andere Systeme sicher auch umgehen, aber wer mit Git arbeitet, wird sehr schnell feststellen, wie vielseitig diese Mittel sind, die Git bietet.

Zunächst sollte man sich bewusst sein, dass man mit Git in erster Linie nur lokal arbeitet, was bedeutet, dass man sich von dem Gedanken trennen muss, dass mein Sourcecode irgendwo auf einem (entfernten) Server liegt. Das ist zwar in gewisser Hinsicht auch der Fall, aber dabei spricht man von einem Remote Repository, wo auch wieder Branches vorhanden sein können und demzufolge diese "entfernten" Branches auch wie lokale Branches behandelt werden. Diese sind unter der Bezeichnung origin erkennbar. Nur bei Bedarf holt man sich den letzten Stand aus so einem entfernten Repository und gleicht seine lokalen Zweige mit den entfernten Zweigen ab. In der Regel wird dies vornehmlich von origin/master auf den lokalen master passieren.

Hat man nun von solch einem Remote-Repository einen Klon auf seinen lokalen Rechner geholt erstellt Git immer erst den master-Branch. Mehr erst mal nicht. Dazu sind zusätzliche Angaben beim Klonen nötig.

Um sich das Prinzip etwas zu veranschaulichen, stelle man sich vor, dass der "master", ein Repository auf CVS- oder SVN-Servern ist, worauf man sein eigenes Verzeichnis immer wieder abgleicht. Im Falle von Git wäre das ein lokaler "master"-Branch. Bei CVS oder SVN hätte man zudem als anonymer Nutzer auf den Server niemals Schreibzugriff. Und so sollte man es auch mit dem "master" seines Git-Repos handhaben. Daher gibt es eine fast goldene Regel beim Arbeiten mit Git, die besagt "Entwickle nie im master Branch!" Ergo muss bzw. sollte man zusätzliche Branches erstellen. Aber wie soll man nun die einzelnen Branches benennen?

Dazu muss man sich Gedanken machen wie der Arbeitsablauf (Workflow) sich darstellt. Man braucht aber das Rad nicht versuchen neu zu erfinden. Vor dieser Frage der Namensgebung standen schon viele andere Projekte. Also schaut man einfach mal an wie man es dort macht. Es sein noch erwähnt, dass man lokal sicher wesentlich freier mit der Namensgebung umgeht und sicher auch wesentlich mehrere kleine Zweige für verschiedenste eigene Aufgaben anlegen wird. Das liegt aber in der Natur der Dinge und sollte man auch so tun.

Das Modell "master/release/feature/hotfix" Branches

Dieses Branchingmodell findet man so oder ähnlich sehr oft in großen Projekten mit vielen Unterkategorien.
Folgendes ist dort im origin Repository übliche Praxis. origin ist der Git Server der als "zentraler" Hauptserver fungiert. Man bedenke, das Git ein Distributed Concurrent Versions System (DCVS), also ein verteiltes Revisions-Kontroll-System ist und somit jeder Git Benutzer auch eine komplette Kopie einschließlich der Quellcode-Historie vom jeweiligen Server, inklusive der eigenen Änderungen besitzt und anbieten könnte. Der Server der origin darstellt kann also auch recht schnell geändert werden.

Der "master" Branch

Jede Software wird irgendwann einmal als produktiv angesehen. Wenn ein User sich ein Git Repository clont, benutzt Git den master Branch als Default Branch, wie schon erwähnt. Damit nun der User sich unsere Software auch bauen und auch benutzen kann muss "master" aber auf jeden Fall den letzten als produktiven benannten Stand wieder spiegeln! "master" ist also immer auch das letzte getaggte Release! Oder anders gesagt, im master findet keine Entwicklung statt, diese wird normalerweise in einem oder auch mehreren Branches getätigt!

Damit nun nicht unbeabsichtigt ein Developer in den Branch origin/master pusht haben auf den master Branch in origin nur wenige Personen ( Maintainer) die benötigten Rechte, um in diesen Branch zu pushen bzw. zu mergen.

Es sei allerdings noch bemerkt, dass ein Remote-Repository gewissen Regeln des bzw. der betreibenden Maintainer(s) unterliegt diese naturgemäß daran interessiert sind, den Wartungsaufwand möglichst klein zu halten und daher unter Umständen nur einen master im Remote-Repository aber keine weiteren Branches pflegen. Insbesondere bei kleinen Projekten hat dies unter Umständen gewisse Vorteile, da die Historie kurz gehalten wird und linear bleibt. Auch für Contributors, die sich an so einem Projekt beteiligen oder nur hin und wieder einen Patch abliefern, bleibt es überschaubarer. Auf lokale Vorgehensweisen muss das natürlich nicht zutreffen und sollte beherzigen, den master generell nicht anfassen und nur als Referenz für seine lokalen Branches betrachten.

Die Release Tags

Wie findet man nun den vorherigen releasten Stand?
Im Prinzip ganz einfach, in dem man jedes Release basierend auf dem master Branch taggt! An Hand des Tagnamen kann man diesen Stand in Git einfach auschecken. Damit kann man jederzeit auch jedes Release wieder bauen, da Git sich um die nötigen Dinge kümmert. Ein Releasetag ist kein eigener Branch, es ist eigentlich ein zusätzlicher (einfach zu lesender) Vermerk zu einem Commit. Das Tagging kann man durch Hook Scripte sogar automatisieren, da ja jedes Pushen in origin/master automatisch ein Tagging verlangt.

Der Devel- oder Entwicklungsbranch

Noch sind wir nur bei einem Branch, eben den Standardbranch den Git von selbst anlegt, den schon bekannten origin/master Branch. Nun soll und muss ja irgendwo auch entwickelt werden und dies geschieht natürlich in einem extra Branch. In den meisten Projekten wird dieser devel oder develop benannt. Dieser Branch ist also Bleeding Edge und kann auch mal durch einen fehlerhaften Commit zu einem Buildfehler oder fehlerhaften Verhalten der Software führen. Dies fällt aber sehr schnell auf und wird durch den letzten Committer dann hoffentlich schnell gefixt.
Hat man einen Entwicklungsstand erreicht in dem es keine Fehler mehr gibt und die Software ausreichend getestet hat wird man nun den develop Branch in den master Branch mergen und final dort dann noch das Tagging erstellen. Damit hat man dann ein neues Release erschaffen, der Kreis schließt sich.

Erstes Fazit

  • In der Minimalvariante hat man zwei Branches.
  • Im master findet man immer die letzte stabile Variante als Release als auch alle anderen veröffentlichten Releases, im develop findet man statt dessen den aktuellsten Entwicklungsstand.

Git-workflow-two branches.png

Release Branches

Bisher war der Workflow noch sehr einfach, es gibt nur zwei Branches.
In einem gutem Projekt gibt es üblicher Weise eine Roadmap in der Features für ein bestimmtes Releases festgelegt sind. In den Projekten gibt es Entwickler die an unterschiedlichen Features, die z.B. in unterschiedlichen Releases verfügbar sein sollen, arbeiten. Damit diese sich nicht gegenseitig behindern, trotzdem aber entsprechender Quellcode nicht verloren geht werden in solchen Fällen Release Branches eröffnet. Diese Branches werden dann nach dem Releasetag benannt. Zum Beispiel origin/v1.0 und origin/v1.1.
Wenn man mit mehr wie einem ReleaseBranch arbeitet muss man beim Mergen aufpassen. Der/die höherwertige(n) ReleaseBranch(es) sollte(n) nicht mehr nach origin/develop gemerged werden.
Wenn einzelne Funktionalitäten trotzdem schon in jüngeren Releases zur Verfügung stehen sollen werden selektiv einzelne Commits eingepflegt (per cherry-pick).

ReleaseBranches sind temporäre Branches, das heißt das diese nach dem erfolgreichen Mergen in den HauptentwicklungsBranch develop wieder gelöscht werden. Nach dem Mergen haben diese Branches keinen tieferen Sinn mehr und stören mehr wie das sie eine Hilfe sind. Nach dem Mergen wird dann im Branch develop das Release in Form von finalen Codecleaning und Beseitigen von noch auftretenden Bugfixes betrieben.

Zweites Fazit

  • Ein Projekt mit verschiedenen Releasezielen erfordert mehrere EntwicklungsBranches.
  • Die Ausarbeitung zukünftiger Releasestände erfolgt in eigenständigen Branches.
  • Nach dem Erreichen eines Softwarezieles wird der entsprechende Branch nach develop gemerged und gelöscht. Dies ist nicht mit dem Tagging zu verwechseln!

Git-workflow-two-branches-release-branches.png

Features Branches

Ab einer gewissen Größe von einem Projekt lassen sich neue Features innerhalb des obigen aufgezeigten Ablaufes nicht effektiv einbringen, da die Entwicklung nicht mit wenigen Commits getan ist. Oder dies ist von den Projektverantwortlichen nicht erwünscht. Dann muss die Entwicklung eines neuen Features außerhalb der Releasebranches erfolgen. Dies hat den Vorteil das keine Reverts im Releasebranch (oder gar im develop Branch) durchgeführt werden müssen wenn sich ein Feature als obsolet oder überflüssig erweist. Auch kann der eventuell einzige Entwickler des neuen Features seine Entwicklung aus verschiedensten Gründen einstellen.

Deshalb kann es in einem Projekt einen oder mehrere sogenannte Featurebranches geben. Diese haben in der Regel einen sehr experimentellen Charakter, zu mindestens in der Anfangsphase. Featurebranches arbeiten fast immer auf ein Release hin. Featurebranches können auf dem Server liegen der origin/... darstellt, dann kann jeder interessierte sich diese Branches auschecken und testen. Diese können aber auch auf anderen Servern liegen. In dem Fall muss ein weiterer Remote hinzugefügt werden.

Ist ein Featurebranch "fertig" zur Integration in den aktuellen Releasebranch und liegt auf dem Server der origin/ ist kann ein Entwickler diesen Branch in den jeweiligen Releasebranch mergen.
Liegt der Branch auf einem entfernten Server so wird durch den Betreiber des Featurebranch ein "Pull" Request oder auch ein Patchset gestellt. Die Releasebranchverantwortlichen prüfen dann gfs. die Pull- Commitfähigkeit sowie die Codequalität des Featurebranches. Sollte es keine Probleme oder Einsprüche seitens der Releaseverantwortlichen geben dann wird der Featurebranch ins Projekt gemerged.

Drittes Fazit

  • Komplexere und große Projekte erfordern in der Regel weitere Branches. Diese zusätzlichen Branches dienen der speziellen Entwicklung von einzelnen Features. Diese Branches können im Projekt direkt integriert sein aber auch bei einzelnen Personen lokal oder in anderen Repositorys liegen.
  • Nach Fertigstellung neuer Funktionen werden diese Branches in den Releasebranch gemerged, können auch per Patchset committet werden um danach durch weitere QS in den develop Branch bis hin zum Release zu wandern.
  • Mit Git ist dieses Arbeiten kein Problem, im Gegenteil, dies ist eine der Stärken von Git. Diese Art des Workflows wird zum Beispiel sehr stark im Kernelumfeld praktiziert.

Git-workflow-two-branches-release-feature-branches.png

Der Hotfix Branch, das I-Tüpfelchen

Dieser Branch wird nur eingerichtet um Hotfixes einzupflegen. Dieser Branch ist kein Muss und wird vermutlich nur bei sehr großen Projekten nötig sein. Sinn ist es einen oder hoffentlich nicht mehrere nach dem Release kritische Bugs zu fixen. Da diese natürlich auch in spätere Releases einfließen sollen, aber wegen möglicher aktueller Entwicklungsarbeit nicht sofort integriert werden können, werden diese später gemerged, der Code an den entsprechenden Stellen kann sich ja wegen der laufenden Entwicklungsarbeit schon insoweit geändert haben das ein Mergen nicht mehr so einfach möglich ist. Nach dem Hotfix wird wieder in den master Branch gemerged und ein neues Release samt Tagging erstellt.

Das Modell "master/maint/next/pu/*" Branches

Ein weiteres Modell welches man sehr oft antrifft. Im Gegensatz zum oben beschriebenen Modell findet hier die Entwicklung des aktuellen Releases bewusst im origin/master Branch statt. Aber insgesamt ist das Grundprinzip das selbe wie im obigen Modell, die Namen der Branches sind aber etwas anders. Bei der Kernelentwicklung, sowie fast allen GNU Projekten aus dessen Umfeld wird dieses Modell benutzt.

Der "master" Branch

Eines haben alle Modelle gemeinsam, den origin/master Branch. In diesem Modell spielt dieser master Branch aber eine zentrale Rolle, er ist der Branch der Quelle für alle anderen Branches ist, aber auch die Sammelstelle für alle Merge Vorgänge die zu einem Release führen sollen. Im Gegensatz zum oben beschriebenen Modell findet im master Branch die Entwicklung für das laufende Release statt. Dies allerdings nur um das Release stabil und fehlerfrei releasen zu können, es wird Codestabilisierung und Bereinigung durchgeführt. Alles was gegen diese Vorgabe verstößt gehört nicht in diesen Branch sondern in den next oder einen der pu/* Branches.

Der "next" Branch

Der next Branch ist die Sammelstelle für alle zukünftiges Features nach den aktuellen Releasevorbereitungen. Somit gehört Code der entweder noch sehr neu ist oder bestimmte neu Abhängigkeiten mit sich bringt in diesen Branch. Trotzdem ist der next Branch aber kein experimenteller Branch, vielmehr ist er Sammelstelle für die neuen Ideen und Weiterentwicklungen die über das Stadium "Proof of Concept" hinaus sind.

Der "maint" Branch

Der Name maint steht für maintenance, also in etwas übersetzt für "Wartung" oder "Pflege". Und das passt sehr gut, da dieser Branch die einzelnen Releases aufnimmt. Wenn also ein Softwarestand erreicht ist der veröffentlicht werden kann, wird in diesen Branch gemerged und anschließend getaggt. Das entspricht dem was im obigen Modell der master Branch macht.

Der oder die "pu/*" Branches

In dem oder den pu/* Branches erfolgt die Entwicklung von neuen Features (entspricht den Featurebranches oben). Wenn ein neues Feature funktionsfähig ist und keine offensichtlichen Bugs mehr enthält wird dieser Branch in den next Branch gemerged. Dort erfolgt die Weiterentwicklung bis es in den master gemerged werden kann.

Viertes Fazit

  • Dieses Modell zeigt ein anderes Herangehen an das Problem der Namensräume, benutzt aber auch mindestens drei Branches. Es ist aber auch sehr flexibel da es keinen Zwang gibt das eine oder das andere Modell zu benutzen.
  • Der Vorteil dieses Modell liegt in der flachen Struktur, die sich besonders bei einfacheren Projekten als praktikabel erweisen sollte.

Git-workflow-four-branches.png

Versionierung

Git selber kennt keine lineare Versionierung, dies liegt im Modell begründet auf dem Git aufgebaut ist. Lineare Versionierung ist nur mit einem Client/Server Prinzip möglich. Da Git dezentral arbeitet funktioniert die Versionierung anders.
Git selber bietet als einfachste Möglichkeit nur die SHA1 ID. Da aber jeder Commit eine eigene ID bekommt, dies auch Branch übergreifend, reicht diese ID eigentlich zur eindeutigen Versionierung aus. Allerdings ist selbst die Kurzform einer SHA1 ID nicht besonders leserlich, und es kann ebenfalls vorkommen das es zwei Commits gibt die die selbe Kurzform ID haben.
Da es im Verlauf eines Projektes immer wieder zu Releases kommt, dies unabhängig von RCs oder Final Releases, gibt es noch die Möglichkeit den Zustand des Repositorys zu diesem Zeitpunkt zu taggen. Dabei gelten die selben Namenskonventionen wie bei der Erstellung von Branches. Einen Tag kann man genauso auschecken wie einen Branch, er hat eine bestimmte SHA1 ID, aber eigentlich ist ein Tag nichts anderes wie ein Verweis auf eine bestimmte SHA1 ID. Dadurch kann man einen Stand des Repository aus der Vergangenheit wiederherstellen. Dies ist ähnlich der Benutzung von Branches nur das diese in der Regel immer den aktuellen Stand einer Entwicklung darstellen. In großen Projekten wird dies ausgiebig genutzt muss aber auch dokumentiert werden.

Da beim Taggen die Länge des Tags recht kurz sein sollte gibt es zusätzlich noch die Möglichkeit eine Art Bemerkung dem Tag hinzufügen (git annontate), diese kann man aber nicht als Basis zum auschecken verwenden.

Berechtigungen und Namespaces

Je größer ein Projekt wird desto eher wird man an dem Punkt ankommen an dem man sich Gedanken machen muss welcher Entwickler auf welche Bereiche Schreibzugriff benötigt. Am Anfang wird es vermutlich ein globales Schreibrecht auf alle Bereiche des Repositorys geben.
Mit dem Entstehen von diversen Teilbereichen und Maintainern für diese Bereiche wird man dieses globale Recht einschränken wollen oder auch müssen. Dies sind Schutzmaßnahmen die ergriffen werden um ungewollte Löschung oder Veränderung von Codebereichen zu verhindern die sonst zeitintensive Rekonstruktion verursachen würden. Hat ein Benutzer trotzdem das Verlangen in so einem Bereich Code zu verändern wird er sich an den Maintainer des Bereiches wenden in dem er diesem Seinen Patch schickt oder auf einer ML zur Diskussion stellt.

Git selbst kennt keine graduierten Berechtigungen, da der Git Prozess unter dem User "git" oder ähnlich läuft. Allerdings können Wrapper den Connect eines Benutzer auf Grund seines SSH Key identifizieren und dann in der internen Berechtigungsverwaltung zu prüfen ob der Benutzer in/aus einen bestimmten Bereich des Repos committen oder auch pullen darf. Wenn ja wird der Commitprozess an den gitdaemon weiter geleitet.
Typische Tools für eine Benutzerverwaltung von Git Repositorys sind das schon etwas ältere gitosis und das aus diesem hervorgegangene gitolite. Die Administration der Git Repositorys auf kernel.org wird z.B. auch mit gitolite durchgeführt.

Ebenfalls wird man abhängig von der Größe eines Projektes irgendwann an den Punkt gelangen wo man sich Gedanken über die Namensgebung der jeweiligen Branches machen muss. Git unterstützt das Konzept von Namespaces. Dies bedeutet das man in der Benennung von Branches das Zeichen "/" verwenden darf. Dieses Zeichen die im sonstigen Gebrauch auf dem System in der Regel nicht möglich. Damit kann man logische Unterbranche zusammen fassen, im Ansatz bei den oben genannten pu/ Branches zu sehen.
Also zum Beispiel sind pu/gui_extra1 und pu/gui_extra2 zwei vollkommen von einander unabhängige Branches die aber als Proposed Update zusammen gehören. Oder auch rel_v1.0/fileparser und rel_v1.0/webapi sind eigenständige Branches die aber zusammen in das Release 1.0 einfließen sollen.

Quellen