Continuous Delivery in der Praxis – Bitbucket effizient nutzen
Bitbucket als Versionsverwaltungs-Server
Dies ist der zweite Teil der Serie von Blog-Einträgen mit Fokus auf Continuous Delivery (CD). Im ersten Teil der Serie haben wir uns die verschiedenen Branching- und Entwicklungsmodelle im Zusammenhang mit Git angeschaut. Nun werden wir uns Bitbucket als Versionsverwaltungs-Server widmen. Bitbucket ist nach meiner Erfahrung häufig das Tool der Wahl insofern auch andere Produkte von Atlassian im Projekt verwendet werden. Ich persönlich empfinde die Konfigurationsverwaltung in Bitbucket etwas gewöhnungsbedürftig. Vor allem sind manche Übersetzungen ins Deutsche meines Erachtens ziemlich unglücklich. Damit wir uns jedoch in Zukunft besser zurechtfinden, werden wir im Folgenden die einzelnen Konfigurationen und ihre Auswirkungen genauer beleuchten.
Ein neues Repository
In Bitbucket ein neues Repository (Repo) zu erstellen ist ziemlich einfach. Repos liegen in Bitbucket immer in sogenannten Projekten. Ein Projekt ist dabei nicht viel mehr als ein Ordner für mehrere Repos, in dem man gewisse Konfigurationen und Berechtigungen für alle enthaltenen Repos übergreifend definieren kann.
Falls also noch kein Projekt existieren sollte, legen wir ein neues an. In dem Projekt erstellen wir dann ein Repo. Bitbucket bietet nun direkt auf der Startseite des Repos eine nützliche Hilfe zum Starten des Git-Projekts. Welches der Verfahren man wählt, ist dabei nicht von großer Bedeutung und hat keine Auswirkungen auf die spätere Entwicklung.
Hat man einen ersten Commit gepusht, wird dieser in der Regel in dem „master“-Branch liegen. Nun gilt es zu entscheiden, welches Branching-Modell man verwenden möchte. Natürlich ist dies keine permanente Entscheidung, jedoch wäre jetzt die Zeit, weitere erforderliche Branches anzulegen. Für das git-flow- und dev-master-Modell macht es zum Beispiel Sinn, bereits jetzt einen zweiten Branch „develop“ anzulegen. Die verschiedenen Branching-Modelle haben wir bereits im ersten Teil dieser Serie kennengelernt. Im Folgenden werde ich beispielhaft das Repo für das git-flow-Modell konfigurieren. Für die anderen Modelle sind dann nur einzelne entsprechende Teile der Konfiguration notwendig.
Konfiguration des Repositories
In den Repository-Einstellungen in Bitbucket kann man viele Parameter konfigurieren. Wir werden nun die einzelnen relevanten Menü-Punkte Schritt für Schritt durchgehen.
Repository-Details
Hier ist für uns nur die Auswahl des „Standardbranch“ interessant. Wenn bereits ein „develop“-Branch angelegt wurde, kann man diesen hier direkt auswählen. So erhalten Entwickler beim Klonen des Repos direkt den „develop“-Branch, auf dem sie auch arbeiten (dev-master-Modell) oder abzweigen können (git-flow-Modell).
Branch-Berechtigungen
Die Branch-Berechtigungen sind meiner Meinung nach einer der Teile, die nicht sofort verständlich sind. In der Regel erstelle ich hier nur Berechtigungs-Konfigurationen für „master“ und „develop“. Für die anderen Branches im git-flow-Modell sind in den meisten Fällen keine Einschränkungen erforderlich.
Mit Bitbucket kann man die Berechtigungen in Form von Einschränkungen mit Ausnahmen festlegen. Dafür klickt man auf einen der Buttons „Rechte hinzufügen“. Ein Modal öffnet sich, in dem wir zum einen den Branch bzw. ein Branch-Schema als Ziel auswählen und zum anderen diverse Einschränkungen vornehmen können. Leider sind die Beschreibungen bzw. Übersetzungen an dieser Stelle, wie ich finde, wenig hilfreich. Die genauen Auswirkungen versteht man erst nach einigen Tests, deswegen habe ich im Folgenden meine Beobachtungen festgehalten.
-
„Alle Veränderungen verhindern“
-
Diese Einschränkung gilt hauptsächlich für Pull-Requests. Ist diese Einschränkung aktiv, kann niemand einen Pull Request über Bitbucket mergen. Der entsprechende Button ist dann deaktiviert. Selbst wenn „Veränderungen ohne Pull-Request verhindern“ deaktiviert oder die Person als Ausnahme eingetragen ist. Fazit: Hier müssen die Personen als Ausnahme eingetragen werden, die später Pull-Requests mergen dürfen.
-
-
„Löschen verhindern“
-
Diese Einschränkung verhindert, dass eine Person den kompletten Branch löschen kann. Diese Einstellung ist für langlebige Branches (wie z.B. „master“ und „develop“) sinnvoll.
-
-
„Ändern des Verlaufs verhindern“
-
Diese Einschränkung verbietet es, die git history zu ändern. Auch diese Einstellung ist sinnvoll für „develop“ und „master“.
-
-
„Änderungen ohne Pull-Anfrage verhindern“
-
Diese Einschränkung verbietet es, direkt auf den Branch zu pushen. Nur Personen, die hier als Ausnahme eingetragen sind, dürfen auch ohne Pull-Request Änderungen an dem Branch vornehmen. Das heißt aber nicht, dass diese auch Pull-Requests mergen dürfen. Dafür ist die Einschränkung „Alle Änderungen verhindern“ anzupassen.
-
Abb. 1: Beispielhafte Branchberechtigungen für master und develop. Pro Einschränkung lassen sich mehrere Ausnahmen hinzufügen.
Zuerst wählen wir den „master“-Branch aus. Für diesen können wir alle Einschränkungen aktivieren. Lediglich bei „Alle Veränderungen verhindern“ müssen die Personen eingetragen werden, die später die Pull-Requests mergen dürfen. Unter Umständen macht es auch Sinn eine oder zwei Personen bei „Ändern des Verlaufs verhindern“ und „Änderungen ohne Pull-Anfrage verhindern“ als Ausnahmen einzutragen. Diese können dann direkt Probleme im oder am Branch beheben, ohne den Umweg über Pull-Requests gehen zu müssen. Dies ist vor allem erforderlich, falls es bei Pull-Requests zu der Situation kommen sollte, dass Bitbucket wegen Konflikten o. ä. den Merge nicht von selbst durchführen kann. Das kann bei manchen Merge-Strategien passieren. (In der Regel empfiehlt es sich jedoch, den Konflikt auf dem zu mergenden Branch zu lösen.) Dazu jedoch weiter unten mehr.
Nachdem wir die Berechtigungs-Konfigurationen für „master“ gespeichert haben, können wir als Nächstes die Rechte für „develop“ konfigurieren. Hier empfiehlt es sich nur die Einschränkungen „Löschen verhindern“ und „Ändern des Verlaufs verhindern“ zu aktivieren.
Branchmodell
Hier lässt sich das Branching-Modell auswählen und konfigurieren. Als Entwicklungs-Branch können wir den Standardbranch verwenden (welchen wir zuvor auf „develop“ gesetzt haben). Als Produktionsbranch können wir folglich „master“ auswählen. Das hat meiner Erfahrung nach nur Auswirkungen für den Fall, dass man weiter unten „Automatisches Mergen“ aktiviert. In der Praxis habe ich dies jedoch noch nicht verwendet, weshalb ich dazu keine weitere Empfehlung äußern kann.
Außerdem kann man hier die Branch-Präfixe definieren. Diese sind dem git-flow-Modell nachempfunden. Beim Anlegen eines neuen Branches über Bitbucket (oder indirekt über Jira), werden die aktivierten Branches als Auswahloptionen dargestellt.
Falls man sowieso nur nach dem dev-master- oder trunk-based-Modell arbeiten will, kann man hier alle Branch-Typen deaktivieren. Es hat jedoch keine negativen Auswirkungen, sie einfach aktiviert zu lassen.
Abb. 2: Die Branch-Präfixe mit Binde- statt Schrägstrich führen zu weniger Verwirrung im Zusammenhang mit Bamboo.
Ich empfehle jedoch die Präfixe selbst anzupassen. Aus mir nicht verständlichen Gründen gehen Bitbucket und Bamboo unterschiedlich mit diesen Präfixen um. In Bamboo werden nämlich alle Schrägstriche in Bindestriche umgewandelt. Das kann zu Verwirrung bei der Anzeige, aber auch vor allem in eigenen Skripten führen. Also ruhig hier bei den Präfixen die Schrägstriche gegen Bindestriche austauschen. Dann ist es in allen Tools einheitlich.
Hooks
An dieser Stelle möchte ich nur kurz auf den Pre-receive-Hook „Reject Force Push“ mit dem passenden Darth Vader Symbol eingehen. Grundsätzlich aktiviere ich diesen Hook immer, denn ein force-push deutet in der Regel auf ein ganz anderes Problem hin und kann sogar noch mehr Probleme verursachen, wenn der- oder diejenige nicht genau weiß, was er/sie tut. Falls doch mal kein Weg drumherum führt, kann man diesen Hook einfach kurzzeitig deaktivieren.
Merge-Checks
Hier kann man Bedingungen definieren, die erfüllt sein müssen, bevor ein Pull-Request über Bitbucket gemerged werden darf. Meine Standard-Konfiguration sieht wie folgt aus:
Abb. 3: Empfehlung zur Einstellung der Merge-Checks.
-
Alle Bewertenden stimmen zu: Deaktiviert
-
Meistens ist es so, dass mehrere Personen als Prüfer eingetragen sind. Davon ist eine dann im Urlaub und die andere hat gerade keine Zeit. Dementsprechend setze ich persönlich lieber die „Mindestzahl an Zustimmungen“.
-
-
Keine unerledigten Aufgaben: Aktiviert
-
Erfahrungsgemäß wird dieses Feature zur Definition von Checklisten in Pull-Requests sehr selten verwendet. Trotzdem stört es nicht, diesen Check zu aktivieren. Es wäre ein Traum, wenn Bitbucket die Möglichkeit bieten würde, Default-Checklisten für jeden Pull-Request zu definieren. Dann würde dieses Feature nach meinem Empfinden auch viel mehr Sinn machen.
-
-
Mindestzahl an Zustimmungen: Aktiviert (1)
-
Eine Person als Prüfer ist zwar das Mindestmaß, jedoch nach eigener Erfahrung auch absolut ausreichend. Vorausgesetzt natürlich, der oder die Prüfer/in weiß, was er/sie tut.
-
-
Mindestzahl erfolgreicher Builds: Aktiviert (1)
-
Diese Option funktioniert nur im Zusammenhang mit Bamboo und ist in diesem Fall wirklich sinnvoll. Niemand möchte einen Stand mergen, bei dem die Tests z. B. nicht mehr funktionieren. Als Anzahl reicht hier aus meiner Sicht auch 1. Damit ist dann gemeint, dass der letzte Build erfolgreich gewesen sein muss.
-
Abb. 4: Hinweise in einem Pull-Request bevor der Merge mit Bitbucket möglich ist.
Ist übrigens einer der Merge-Checks nicht erfüllt, kann der Merge über Bitbucket nicht durchgeführt werden. Der entsprechende Button ist dann deaktiviert. Zum Glück bietet Bitbucket jedoch auch Hinweise zu den Gründen an.
Zusammenführungsstrategien
Was Bitbucket als „Strategien“ bezeichnet sind für mich eher „Rätsel“. Ich habe viel Zeit damit verbracht nachzuvollziehen, was genau Bitbucket bei den einzelnen Strategien macht, welche Strategien sich am besten für welche Branches eignen und womit die commit Historien am strukturiertesten aussehen. Am Ende entscheide ich mich doch immer wieder für den „einfachen“ „Merge-Commit“.
An dieser Stelle möchte ich gar nicht weiter auf die einzelnen Merge-Strategien eingehen. Es gibt bereits genug Erklärungen was ein Rebase, Squash und fast-forward ist (ein guter Startpunkt ist die Dokumentation von Git). Und wenn man das Ganze manuell macht, hat man wahrscheinlich auch genug Kontrolle darüber, um ein vernünftiges Ergebnis zu erhalten. Sobald man das Ganze jedoch automatisch von Bitbucket erledigen lässt, steigt die Gefahr von unbeabsichtigten Folgen.
Das Problem der anderen Zusammenführungsstrategien ist, dass sie die Historie stark beeinträchtigen. Zwar kann man damit eventuell eine schön strukturierte Historie oder übersichtliche Commits erreichen, jedoch muss dafür auch jeder im Projekt wissen, was genau da passiert. Aus meiner Erfahrung heraus kennen sich nur die wenigsten sehr gut mit dem Umgang mit der Git-Historie aus.
Erfahrungsgemäß endet fast alles – außer die „Merge-Commit“-Strategie – in irgendwelchen kruden Konflikten oder Merge-Schleifen. Es mag sein, dass ein „Rebase + Merge“ bzw. sogar „Squash“ für Feature-Branches recht sinnvoll sein kann. Die Historie ist dann unter Umständen tatsächlich viel aufgeräumter. Jedoch überwiegen hier aus meiner Sicht die Nachteile und Probleme in der Praxis ganz klar dem Vorteil einer aufgeräumten Historie.
Abb. 5: Dropdown zur Auswahl einer Zusammenführungsstrategie beim Mergen in Bitbucket.
Es ist natürlich möglich neben „Merge-Commits“ auch eine der anderen Methoden zu aktivieren, damit man die eine z. B. für Merges in „master“ und die andere für Merges von Feature-Branches verwenden kann. Beim Mergen in Bitbucket erscheint dann tatsächlich im entsprechenden Dialog – halb unsichtbar – ein Dropdown, in dem man die Merge-Strategie auswählen kann. Aber denkt vorher auch wirklich immer jeder daran das Korrekte auszuwählen? Der Button ist leider schnell übersehen. Und was ein Mal gemerged ist, kann nicht so einfach wieder rückgängig gemacht werden.
Dementsprechend belasse ich es hier in der Regel bei „Merge-Commits“. Dann hat man zwar keine besonders schöne Historie und vermeidet keine Merge-Konflikte (das geht übrigens mit keiner Zusammenführungsstrategie), dafür handelt man sich aber auch die wenigsten Probleme ein.
Standardprüfer
Zu guter Letzt kann man noch, falls gewünscht, einen oder mehrere Standardprüfer für Pull-Requests festlegen. Dies sogar je nach Branch-Typ.
Am besten trägt man hier eine(n) Projekt- bzw. Repository-Verantwortliche(n) für jeden Branch ein. Diese Einstellung verhindert nicht, dass man bei der Erstellung eines neuen Pull-Requests auch noch weitere Prüfer hinzufügen kann. Aber so ist der- oder diejenige zumindest immer informiert.
Fazit
Bitbucket stellt viele Konfigurationen für Branches und Pull-Requests zur Verfügung. Leider sind diese nicht immer ganz verständlich und es gibt hier und da noch Verbesserungspotential. Trotzdem ist Bitbucket bei der Software-Entwicklung ein sehr hilfreiches Tool.
Außerdem werden im Zusammenhang mit Bamboo einige Prozesse, wie bereits angedeutet, automatisiert bzw. „magisch“ verknüpft. Auf Bamboo werden wir dann im nächsten Teil dieser Serie noch genauer eingehen.
content by Marvin Becker