Lustige Sachen mit git


In der Tat schreibe ich hier, als bekennender Mercurial-Fan auch mal über git 😉

Ich bin nämlich gerade gezwungne git zu nutzen, da ich heroku für mich entdeckt habe. Und als ebenfalls bekennender Web-Design-Analphabet bin ich ein Freund von fertigen CSS-Dateien, die man für seine Applikationen benutzen kann. Ein solches, ganzes Framework stellt blueprint da. Dahinter steckt die Open Source Gemeinde, und deshalb gibt es den gesamten Code auch auf github – offiziell unter dem Repository von Joshua Clayton.

Jetzt hab ich gleich zwei Probleme:

  1. Es wäre schön, das Repository einfach in dem Verzeichnisbaum meines Projekts unter zu bekommen. Dieses steht aber ebenfalls bei git unter Versionskontrolle.
  2. Das Repository beinhaltet allerlei Files. Mich interessieren tun jedoch nur die Sachen im Unterverzeichnis “blueprint” – ich bin weder interessiert an Tests, noch an Mockup-Templates, o.Ä., lediglich die CSS Dateien möchte ich haben.

Klar, ich kann mir das Projekt herunter laden, die drei CSS-Dateien in meinen stylesheets-Verzeichnis kopieren und fertig. Aber es ist schon irgendwie doof, wenn man die Möglichkeit hat, per “git pull” immer auf den aktuellsten Stand zu sein, und dann darauf verzichten zu müssen, oder nicht?

Aber git ist ja in vielen Belangen sehr umfangreich und flexibel einsetzbar – und so geht auch das.

Als erstes erzeugen wir einfach ein Repository in unserem Repository. Klingt vielleicht komisch, git macht das aber mit – es ist ja auch nichts weiter dran, das Repository wird, wie auch bei Mercurial über versteckte Verzeichnisse definiert, und sobald wir in einem Ordner mit einem solche Verzeichnis sind, denkt git, wir sind in einem neuen Repository und weiß nichts mehr von dem alten.

pygospa@lalaith stylesheets (git)-[pages-controller] % mkdir bp
pygospa@lalaith stylesheets (git)-[pages-controller] % cd bp
pygospa@lalaith stylesheets/bp (git)-[pages-controller] % git init
pygospa@lalaith stylesheets/bp (git)-[master] %

Damit man nun nur einzelne Verzeichnisse aus einem anderen Repository pullen kann, benötigt man ein git-Feature names sparse-checkout. Dieses muss vorher aktiviert werden

pygospa@lalaith stylesheets/bp (git)-[master] % git config core.sparsecheckout true

Mit “git remote add ” kann man Remote Repositories (d.h. entfernte Repositories) im eigenen Workspace hinzufügen.

git remote add github git://github.com/jdoe/coolapp.git

würde beispielsweise das Remote Repository git://github.com/johndoe/someApp.git unter dem Kürzel github dem eigenen Workspace hinzufügen, und man könnte dann mithilfe von

git pull github

aus diesem Repository pullen. Neben dem Pullen, was unter git die Hintereinander Ausführung zweier Befehle (nämlich dem Fetchen – das holen aller Änderungen, und dem Mergen, das einspielen der Änderungen in das eigene Workspace), kann man auch die einzelnen Befehle ausführen. Hier dann ein Grund, warum ich kein großer Fan von git bin: während bei einigen Befehlen -f für –force steht, also das willentliche erzwingen, auch wenn git es besser meint (sollte man nur äußerst vorsichtig anwenden!), heißt es bei der Kombination mit git remote dann –fetch. Wer nicht höllisch aufpassen will vermeidet die Kurzformen also ganz und sieht git als nette Tipp-Übung an:

pygospa@lalaith stylesheets/bp (git)-[master] % git remote add --fetch blueprint https://github.com/joshuaclayton/blueprint-css.git
Updating blueprint
remote: Counting objects: 3463, done.
remote: Compressing objects: 100% (1380/1380), done.
remote: Total 3463 (delta 2102), reused 3392 (delta 2058)
Receiving objects: 100% (3463/3463), 4.82 MiB | 1.41 MiB/s, done.
Resolving deltas: 100% (2102/2102), done.
From https://github.com/joshuaclayton/blueprint-css
 * [new branch]      master     -> blueprint/master
 * [new branch]      owengalenjones-fonts -> blueprint/owengalenjones-fonts
 * [new branch]      remove-plugins -> blueprint/remove-plugins
 * [new branch]      testing-css -> blueprint/testing-css
From https://github.com/joshuaclayton/blueprint-css
 * [new tag]         v0.5       -> v0.5
 * [new tag]         v0.6       -> v0.6
 * [new tag]         v0.6.1     -> v0.6.1
 * [new tag]         v0.7.1     -> v0.7.1
 * [new tag]         v0.8.0     -> v0.8.0
 * [new tag]         v0.9       -> v0.9
 * [new tag]         v0.9.1     -> v0.9.1
 * [new tag]         v1.0       -> v1.0
 * [new tag]         v1.0.1     -> v1.0.1
pygospa@lalaith stylesheets/bp (git)-[master] % 

Jetzt haben wir also das komplette Changeset aller Branches (hier gibt es nach meiner Recherche scheinbar keinen Weg drum herum – wobei ich sicher bin, dass es auch dafür irgendwie einen Weg bei git gibt). Allerdings befindet sich noch keine Datei in unserem Workspace.

Das wollten wir ja auch noch nicht – erst einmal möchten wir nun definieren, woran wir überhaupt interessiert sind. Dazu wird im Unterverzeichnis .git/info/ die Datei sparse-checkout angelegt, welche pro Zeile die Pfade enthält, die wir Auschecken wollen.

pygospa@lalaith stylesheets/bp (git)-[master] % echo blueprint/ >> .git/info/sparse-checkout

Geschafft. Leider erkennt die sparse-checkout-Datei nur relative Pfade als gültig an, die mit einem Ordnernamen beginnen. So etwas wie

/blueprint/
./blueprint/
blueprint/*.css

wurde bei mir leider nicht anerkannt. Warum leider, sehen wir gleich.

Mit git pull kann man nun ausschließlich die ausgewählten Pfade einspielen. Beim ersten Pull möchte git natürlich wieder wissen, von wo es pullen soll, und in welchem Branch das ganze landen soll. Da ich nichts größeres vorhabe, bleiben wir beim standardmäßig angelegten Master-Branch, und für das Remote-Repository haben wir ja jetzt das Kürzel “blueprint” zur Verfügung:

pygospa@lalaith stylesheets/bp (git)-[master] % git pull blueprint master
From https://github.com/joshuaclayton/blueprint-css
 * branch            master     -> FETCH_HEAD
pygospa@lalaith stylesheets/bp (git)-[master] % ls
blueprint/ lib/

Warum jetzt das lib-Verzeichnis ebenfalls kopiert wurde? Nun, weil sich in diesem Verzeichnis ebenfalls ein Unterverzeichnis namens blueprint befindet. Scheinbar ist es also nicht eine Pfadangabe, sondern eine Verzeichnisstruktur, die man angibt. git sucht nach dieser, und überall wo er fündig wird, wird alles, was unter dieser Verzeichnisstruktur gefunden wird, übertragen.

Da oben dargestellte Lösungen nicht funktionieren, hab ich noch ein wenig herumexperimentiert, und kam dann darauf, dass das Negieren aus den .gitignore Dateien auch in der sprase-checkout funktioniert.

Ich möchte also alle Verzeichnisse in dessen Pfad in der sich die Verzeichnisstruktur blueprint/ befindet – nicht aber jene, die lib/blueprint beinhalten. Die Fertige sparse-checkout sollte dann so aussehen:

pygospa@lalaith stylesheets/bp (git)-[master] % cat .git/info/sparse-checkout
blueprint/
!lib/blueprint/
pygospa@lalaith stylesheets/bp (git)-[master] %

Mit read-tree kann der Index erneuert werden. Der Index ist eines dieser Mysterien, die zu den Interna gehören, von denen ein Mercurial-User nie etwas mitbekommt. Der Index ist in git eine Schicht die quasi zwischen dem Working Directory und dem Git-Repository liegt. Das Git-Repository ist der Stand, seit dem letzten Commit. Das Working-Directory wiederum der aktuelle Stand, mit dem man arbeitet. Jede veränderte Datei, jede Datei die hinzugefügt, gelöscht, oder bewegt wird, landet im Index, einer Byte-Datei unter .git/index.

Diesen kann man sich übrigens über den Befehl git ls-files Anzeigen lassen.

Der Index wird scheinbar nur beim ersten Pullen so erzeugt, dass er die sparse-checkout berücksichtigt – daher ist es auch notwendig, dass das Remote-Repository zunächst nur gefetched, nicht aber gemerged wird. Ein nachträglicher pull ignoriert alles, was in der Datei steht, die Dateien bleiben weiterhin im Workspace ererhalten. Will man das nicht, muss also der Index neu erzeugt werden, was mit read-tree passiert. Wir wollen aber nicht nur den Index neu erstellen, sondern gleichzeitig auch alle Änderungen mergen und das Working-Directory auf den aktuellen Stand bringen. Das geschieht mit den Flags -m und -u:

pygospa@lalaith stylesheets/bp (git)-[master] % git read-tree -mu HEAD
pygospa@lalaith stylesheets/bp (git)-[master] % ls
blueprint/

Bleibt also noch das “Problem” mit dem Repository im Repository. Zurück in unserem Oberrepository bemerkt git natürlich sehr wohl, dass da ein neues Verzeichnis hinzu gekommen ist. Der übliche Weg wäre das Benutzen von Submodules. Normalerweise kann man diese über

git submodule add <remote> <local_subdirectory>

direkt in ein Repository clonen. Alternativ dazu erkennt git auch automatisch, dass es sich um eine Sub-Repository handelt, wenn man einfach nur

git add .

eingibt. Allerdings, wenn man ein wenig bei Google stäbert, kommt man über Kurz oder Lang immerzu auf folgende Aussagen: “Git submodules kinda suck.” oder “Git submodules are a pain to use, difficult to explain and you cannot check out partial trees.”

Besser ist es daher, das Verzeichnis einfach als Verzeichnis hinzu zu fügen

pygospa@lalaith stylesheets (git)-[pages-controller] % git add bp/

Da git relativ schlau ist, ist der letzte Slash entscheidend! Fehlt dieser, so erkennt git, dass dort ein weiteres Repository existiert, und legt das Submodule alleine an. Erst der Slash sorgt dafür, dass git gezwungen wird, das Verzeichnis bp auch tatsächlich als Verzeichnis zu behandeln, ganz gleich, was dadrunter liegt.

Dessen, dass nun ein Repository im Repository liegt, kann man sich auch dadurch klar machen, dass man nun wieder in den Ordner wechselt. Der aktuelle Branch sollte dann wieder automatisch zum Master wechseln.

Advertisements

2 thoughts on “Lustige Sachen mit git

  1. Der Index ist eigentlich garnicht so mysteriös (die Benennung wird allerdings kritisiert) und mit Absicht für den Nutzer da. It’s not a bug, it’s a feature 😀

    Ich bin da nicht so der power-user, aber der Index wird z.B. gerne genutzt, um ein commit aufzuteilen. Das wird u.a. hier erklärt http://newartisans.com/2008/04/git-from-the-bottom-up/ ist auch ansonsten ein schöner Artikel darüber, wie git funktioniert.

    • Also ich hab schon 2-3 male nach Erklärungen für den Index gesucht, und dabei ist mir das Gefühl aufgekommen, dass ihn sehr wohl sehr viele Menschen sehr mysteriös finden. Man bekommt ja in der Regel auch nie etwas mit ihm zu tun – bis auf die einigen Male, wo man ihn dann braucht.

      Das es ihn nicht mit Absicht gibt, bzw. es ein Bug sei, wollte ich auch nie unterstellen. Es ändert aber nicht an der Tatsache, dass er nur ganz selten mal benötigt wird, daher für die meisten komplett unsichtbar ist, und deshalb auch gerne in allen (Anfänger-)Beschreibungen und Tutorials zu git außen vor gelassen wird – was ihn noch mysteriöser macht 😉

Please comment. I really enjoy your thoughts!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s