Skip to content
Zurück zum Blog
· 5 Min. Lesezeit

Versionsverwaltung in Embedded-Projekten: was nach zehn Jahren noch zählt

Git in langlebigen Embedded-Projekten: Branches je Hardwarerevision, Umgang mit Vendor-BSPs, Binärdateien und Toolchains, reproduzierbare Stände über Tags und Manifeste – und warum eine lesbare Historie erst nach Jahren ihren Wert zeigt.

Diagramm zur Versionsverwaltung in Embedded-Projekten: oben die Zeitachse eines Produkts mit Hauptzweig, Release-Branches je ausgelieferter Gerätegeneration und Backports von Sicherheitskorrekturen; unten die drei Bestandteile eines reproduzierbaren Standes – Anwendungscode, Yocto-Layer mit Manifest und die festgeschriebene Toolchain.

Kurz gesagt: Versionsverwaltung in Embedded-Projekten unterscheidet sich weniger in den Befehlen als im Zeithorizont. Ein Webdienst wird kontinuierlich ausgerollt und kennt nur den aktuellen Stand; ein Gerät wird gebaut, ausgeliefert und Jahre später noch mit Korrekturen versorgt – parallel zu drei anderen Hardwarerevisionen. Die Entscheidungen, die das erträglich machen, trifft man am Anfang, spürt sie aber erst nach Jahren.

Was anders ist als bei Software, die man ausrollt

Drei Eigenheiten prägen die Arbeit:

Es gibt nicht einen aktuellen Stand, sondern mehrere gleichzeitig. Wenn Revision C in Produktion geht, laufen Revision A und B weiter im Feld und bekommen weiterhin Sicherheitskorrekturen. Alle drei müssen sich jederzeit bauen lassen.

Ein erheblicher Teil des Codes stammt nicht von Ihnen. Kernel, Bootloader, BSP vom SoC-Hersteller, Layer aus dem Yocto-Ökosystem. Sie pflegen ihn nicht, aber Sie müssen ihn ausliefern können – auch wenn der Hersteller das Repository irgendwann abschaltet.

Der Bauvorgang ist Teil des Produkts. Ob eine Firmware bitidentisch reproduzierbar ist, entscheidet nicht der Anwendungscode allein, sondern die Kombination aus Quellen, Layern, Konfiguration und Toolchain. Was davon nicht versioniert ist, fehlt später.

Branches, die zur Produktlebensdauer passen

Die Zweigstruktur folgt der Hardware, nicht dem Kalender. Bewährt hat sich ein schlankes Modell: Der Hauptzweig trägt die laufende Entwicklung. Jede ausgelieferte Gerätegeneration bekommt beim Produktionsstart einen Release-Branch, der ab dann nur noch Korrekturen aufnimmt.

Wichtig ist die Richtung des Datenflusses. Ein Fehler wird im Hauptzweig behoben und von dort in die betroffenen Release-Branches übernommen – per Cherry-Pick oder Backport-Merge. Der umgekehrte Weg, direkt im Release-Branch zu reparieren und später hochzumergen, funktioniert genau so lange, bis jemand den zweiten Schritt vergisst. Dann existiert die Korrektur nur auf einem Zweig, der beim nächsten Produkt niemanden mehr interessiert, und der Fehler kommt in der nächsten Generation zurück.

Tags markieren, was tatsächlich ausgeliefert wurde. Ein Tag ohne den zugehörigen Build-Stand ist allerdings wenig wert – dazu gleich mehr.

Fremdcode: Submodul, Kopie oder Layer

Für Vendor-BSPs und externe Quellen gibt es drei gangbare Wege, und die Wahl hängt daran, wem Sie zutrauen, das Repository in zehn Jahren noch bereitzustellen:

  • Submodul. Sauber getrennt, gezielt aktualisierbar, kein Rauschen in der eigenen Historie. Der Preis ist die Abhängigkeit von einer fremden URL – und SoC-Hersteller räumen ihre Git-Server ohne Vorwarnung auf.
  • Eigene Kopie im Repository. Unelegant und aufgebläht, dafür vollständig unabhängig. Für BSPs, die man ohnehin patcht und die nur einmal im Jahr angefasst werden, ist das oft die ehrlichere Lösung.
  • Yocto-Layer mit fixierter Revision. Für alles, was ohnehin über Yocto gebaut wird, der natürliche Ort: Das Layer bleibt extern, die exakte Revision steht im eigenen Repository. Ergänzt um einen internen Mirror hat man beides – Trennung und Verfügbarkeit.

Ein Sonderfall sind Kernel-Anpassungen. Ein eigener Treiber oder ein Board-Device-Tree gehört als Patch oder eigene Quelldatei in den Produkt-Layer, nicht als lokale Änderung in einen Kernel-Baum. Sonst ist er beim nächsten Versionssprung entweder verschwunden oder muss mühsam wieder aufgetragen werden.

Reproduzierbarkeit: Tag allein genügt nicht

Die Frage, an der sich alles entscheidet, lautet: Lässt sich der Stand, der vor drei Jahren ausgeliefert wurde, heute noch einmal bauen? Dafür braucht es drei Dinge im Zugriff:

  1. Den Anwendungscode – der einfache Teil, der Tag reicht.
  2. Die Build-Beschreibung samt aller Layer-Revisionen. Bei Yocto leistet das ein Manifest oder eine Kombination aus fixierten SRCREV-Angaben; ohne das zeigt derselbe Layer-Zweig heute auf einen anderen Commit als damals.
  3. Die Toolchain. Sie gehört nicht ins Repository, aber sie muss festgeschrieben sein – über das Yocto-Rezept oder ein Container-Image, das per Digest referenziert wird, nicht per latest.

Fehlt einer der drei Punkte, ist der Tag ein Etikett ohne Inhalt. Diese Lücke fällt selten beim Anlegen auf, sondern in dem Moment, in dem ein Kunde nach drei Jahren eine Korrektur für ein Gerät braucht, das nie ein Update bekommen hat.

Binärdateien, mit Maß

Firmware-Blobs für Funkmodule, Kalibriertabellen, kleine Referenzbilder: Solche Artefakte gehören zum reproduzierbaren Stand und damit ins Repository, sinnvollerweise über Git LFS. Was nicht hineingehört, sind Build-Ergebnisse und komplette SDKs – sie lassen sich aus den Quellen wiederherstellen, blähen aber jeden Clone dauerhaft auf. Der Prüfstein ist einfach: Kann ich es aus dem, was ohnehin versioniert ist, exakt wiederherstellen? Dann gehört es nicht hinein.

Warum die Historie lesbar sein sollte

Der Nutzen einer aufgeräumten Historie zeigt sich nicht beim Schreiben, sondern beim Suchen. Ein Gerät im Feld verhält sich seit einem Update anders, niemand weiß warum, und git bisect soll den Commit finden, der es verursacht hat. Das Verfahren ist mechanisch und zuverlässig – unter einer Bedingung: Jeder Commit muss für sich baubar sein und eine erkennbare Absicht haben.

Genau daran scheitert es in gewachsenen Projekten. Sammel-Commits mit der Nachricht „fixes”, Zwischenstände, die nicht kompilieren, vermischte Umformatierungen und Sachänderungen – jedes davon macht die Suche in dem Moment unmöglich, in dem sie am meisten wert wäre. Für Software, die in zwei Jahren ersetzt wird, ist das verschmerzbar. Für ein Gerät mit zehn Jahren Feldeinsatz ist es teuer.

Dazu gehört auch, dass die CI jeden Commit baut und nicht nur den Stand nach einem Merge. Sonst entstehen Löcher in der Historie, auf denen bisect später strandet.

Womit anfangen

Wenn ein bestehendes Projekt aufgeräumt werden soll, lohnt die Reihenfolge: zuerst die Reproduzierbarkeit sicherstellen – Layer-Revisionen und Toolchain festschreiben –, dann die Branch-Struktur an die tatsächlich unterstützten Gerätegenerationen anpassen, und erst danach an Konventionen für Commits und Reviews arbeiten. Die ersten beiden Punkte verhindern konkrete Ausfälle, der dritte macht die Arbeit dauerhaft leichter.

Wir gehen diese Fragen in unserer Git-Schulung direkt an Ihrem Repository durch – mit Ihrer Branch-Strategie und den Konflikten, die bei Ihnen tatsächlich auftreten. Wo die Versionsverwaltung Teil eines größeren Umbaus ist, begleiten wir das im Rahmen unserer Embedded-Linux-Entwicklung.

Häufige Fragen

Gehören Vendor-BSPs als Submodul ins Repository oder kopiert man sie hinein?
Beides ist verbreitet, und beides hat einen klaren Anwendungsfall. Ein Submodul bleibt sauber getrennt und lässt sich gezielt aktualisieren, setzt aber voraus, dass die Quelle in zehn Jahren noch erreichbar ist – bei SoC-Herstellern eine gewagte Annahme. Ein hineinkopierter, im eigenen Repository versionierter Stand ist unschön, aber unabhängig. Für alles, dessen Verfügbarkeit Sie nicht kontrollieren, ist die eigene Kopie die sicherere Wahl; für aktiv gepflegte Open-Source-Layer das Submodul.
Wie viele Branches braucht ein Produkt, das über Jahre ausgeliefert wird?
So wenige wie möglich, aber einen je Gerätegeneration, die noch Updates bekommt. Der Hauptzweig trägt die Entwicklung, jede ausgelieferte Generation einen Release-Branch, in den ausschließlich Korrekturen zurückfließen. Entscheidend ist die Richtung: Fehler werden im Hauptzweig behoben und in die Release-Branches übernommen, nie umgekehrt – sonst existiert eine Korrektur irgendwann nur noch auf einem Zweig, den niemand mehr im Blick hat.
Gehören Binärdateien und Toolchains ins Git-Repository?
Firmware-Blobs, Kalibrierdaten und ähnliche kleine Artefakte ja – sie gehören zum reproduzierbaren Stand, und Git LFS hält das Repository handlich. Eine komplette Toolchain gehört nicht hinein: Sie wird stattdessen exakt festgeschrieben, über das Yocto-Rezept oder ein Container-Image mit fixiertem Digest. Der Maßstab ist immer derselbe: Lässt sich der Stand von vor drei Jahren aus dem, was im Repository steht, wieder bauen?
Lohnt sich der Aufwand für eine aufgeräumte Historie wirklich?
Bei kurzlebiger Software ist es Geschmackssache, bei langlebigen Geräten nicht. Der Moment, in dem sich das entscheidet, kommt Jahre später: Ein Gerät im Feld zeigt einen Fehler, der vor zwei Jahren noch nicht auftrat, und `git bisect` soll die Ursache eingrenzen. Das funktioniert nur, wenn jeder Commit für sich baut und eine erkennbare Absicht hat. Eine Historie aus Sammel-Commits mit der Nachricht "fixes" macht dieses Werkzeug wertlos, genau dann, wenn man es am dringendsten braucht.

Alexander Nassian

Geschäftsführer, bitshift dynamics

Entwickelt mit seinem Team hardwarenahe Software für Embedded-Produkte – C++, Qt/QML, Embedded Linux und das Yocto Project. bitshift dynamics arbeitet seit 2005 in diesem Feld.

Verwandte Beiträge

· 7 Min. Lesezeit

Hardware-in-the-Loop-Testing: CI/CD mit echter Embedded-Hardware

Warum ein Hardware-in-the-Loop-Prüfstand das fehlende Stück für echtes CI/CD auf Embedded-Geräten ist: Treiber-, Timing- und Integrationsfehler auf echter Hardware finden.

  • Testing
  • CI/CD
  • Hardware-in-the-Loop
Weiterlesen
· 5 Min. Lesezeit

Linux-Treiber entwickeln: wann der Kernel nötig ist – und wann nicht

Bevor Sie einen Kernel-Treiber schreiben: Welche Geräte sich sauber aus dem Userspace bedienen lassen, wann ein Kernel-Treiber unvermeidlich wird, wie ein Platform-Treiber aufgebaut ist und welche Fehler beim Bring-up am meisten Zeit kosten.

  • Embedded Linux
  • Kernel
  • Treiber
Weiterlesen
· 4 Min. Lesezeit

systemd auf Embedded-Systemen: was sich lohnt und was schadet

systemd im Embedded-Einsatz: Units und Abhängigkeiten richtig schneiden, Boot-Zeit statt Reihenfolge optimieren, Dienste per Watchdog überwachen, das Journal vom Flash fernhalten und mit read-only Root-Dateisystem arbeiten.

  • Embedded Linux
  • systemd
  • Boot-Zeit
Weiterlesen

Steht bei Ihnen eine ähnliche Aufgabe an?

Wir begleiten Embedded-Teams genau bei solchen Fragestellungen – von der Architekturentscheidung bis zur Serienreife. Schildern Sie uns kurz Ihr Vorhaben.

Projekt anfragen