Mittwoch, 7. August 2013

Na super() - Freude mit Python

Neulich in PyCharm: Da hacke ich so froh vor mich hin, bastel ein Skriptchen mit zwei Klässelchen, und stelle fest, dass man Teile davon in eine gemeinsame Superklasse ziehen kann. Nichts besonderes, und weil Python ja objektorientiert konzeptioniert ist, geht das auch total einfach:

class Common(object):
  def __init__(self, some):
    self._some = some


class A(Common):
  def __init__(self):
    super(A, self).__init__('some soll hier A sein')


class B(Common):
  def __init__(self):
    super(B, self).__init__('hier soll es B sein')

Ok, ist schon klar, das ist synthetischer Code, tut aber nichts zu Sache. Nach allem, was ich so weiß, funktioniert das theoretisch. In der Praxis funktioniert das auch, beispielsweise auf einem 2.7.x-Python auf Linux.

Bis ich das in einem virtualenv auf meinem Mac laufen lasse (Python 2.7.2). Dann werde ich mit folgender Meldung beglückt:

TypeError: super(type, obj): obj must be an instance or subtype of type

Nicht, was ich erwartet hatte. Google spuckt dazu einen netten Link namens Another super() wrinkle – raising TypeError aus, und wer Freude dran hat, darf sich ruhig in den Artikel vertiefen. Allein: Ich benutze keinen Plugin-Loader, der Fehler tritt bei mir beim ganz normalen Unittest auf (mit einem ganz normalen unittest2, muss ich wohl dazu sagen). Ich überspringe hier mal lange Debugging-Sessions und komme gleich zum Kern: super(self.__class__, self).__init__(...) tut's:

class Common(object):
  def __init__(self, some):
    self._some = some


class A(Common):
  def __init__(self):
    super(self.__class__, self).__init__('some soll hier A sein')


class B(Common):
  def __init__(self):
    super(self.__class__, self).__init__('hier soll es B sein')

Das sieht mir zwar irgendwie wirklich zu schräg aus, und ich kann mir gut vorstellen, dass hier super irgendwie falsch behandelt wird, aber auch, dass man hier noch basteln kann, damit es hübscher aussieht. Andererseits reicht es mir wirklich vollkommen, die Vererbung im Kopf der Klassendefinition anzugeben, also ist diese Schreibweise vielleicht sogar wert, ins Muskelgedächtnis der Finger aufgenommen zu werden.

Ausserdem funktioniert's.

Montag, 5. August 2013

Git Branches auf dem Terminal

Es passiert ja immer wieder mal, dass man auf irgendeinem Rechner, der keine grafische Oberfläche angeschlossen hat, in die History eines Repositories reingucken muss. Das Werkzeug dazu ist natürlich git log:

> git log

commit 123...789

Author: John Doe <john.doe@inter.net>
Date:  Fri Feb 1 13:23:03 2013 +0100

    Add nice stuff

    Today I decided to add nice stuff to this repository.
...

Auf die Art und Weise bekommt man natürlich keinen Überblick über das Repository, das ist ja viel zu ausführlich. Aber git kennt einen Einzeiler-Modus, der praktischerweise gleich so heißt:

> git log --oneline
1234567 Add nice stuff
...

Als History kreuz und quer über alle Branches ist das dann aber etwas unübersichtlich. ASCII-Art sei Dank gibt's auch auch dem Terminal Grafiken:

> git log --graph --oneline
* 1234567 Add nice stuff
...

Schon besser. Wenn wir alle Branches sehen wollen, nicht nur den aktuellen Branch, gibt's dazu noch ein "--all":

> git log --graph --oneline --all
* 1234567 Add nice stuff
...

Ok, der Unterschied ist hier nicht gerade deutlich, aber das sollte man einfach mal mit einem echten Repository probieren. Wenn man das tut, stellt man aber fest: Welcher Commit auf welchen Branch ist, kann man einfach nicht sehen. Das kann schon mal wichtig sein, also lassen wir uns das einfach noch mit anzeigen:

> git log --graph --oneline --decorate=short --all
* 1234567 (HEAD, origin/master, master) Add nice stuff
...

Das ist schon fast perfekt. Damit wir das aber nicht jedes Mal tippen müssen, legen wir dafür einfach noch einen Alias an.

> git config --global alias.lg 'log --graph --oneline --decorate=short'
> git lg --all
* 1234567 (HEAD, origin/master, master) Add nice stuff
...

Den Alias haben wir global für den Nutzer angelegt, damit funktioniert er in allen Repos des Nutzers. Das letzte '--all' habe ich im Beispiel weggelassen, damit mal auch mal einfach nur die Historie des aktuellen Branches sehen kann. Noch schöner wird's übrigens, wenn man die Farbe einschaltet.

So einfach können die kleinen Freuden sein.

Dienstag, 19. Februar 2013

Was ist auf diesem Branch?

Manchmal sitzt man so vor einem Terminal, hat fröhlich-chaotisch vor sich hin gehackt, natürlich alles brav auf einem eigenen chaotisch-vor-sich-hin-hack-Branch, und fühlt, dass die Zeit reif ist, die Früchte seiner Arbeit in den master-Branch zurückfließen zu lassen. Nur: Was sind denn die Früchte besagter Arbeit?

Im grafischen Display von Gitk ist das schnell erkannt: Gitk öffnen, die Commits ansehen, die man auf dem Branch eingebracht hat, und schon kann man entscheiden, ob man wirklich auf den master mergen will oder nicht. Nur was ist, wenn's auf dem Terminal keine grafische Darstellung gibt?

Kein Problem. Gitk ist ja im Grunde nur ein glorifiziertes git log (wenn auch recht gut glorifiziert), also kann man alles, was man mit gitk machen kann, auf mit git log machen, ganz ohne grafische Ausgabe.

In diesem Falle interessieren uns alle Commits auf dem Branch hacking, der von master abgezweigt. Das bedeutet, dass alle Commits, die auf hacking liegen, angezeigt werden sollen, aber keine Commit, die auf master liegen. Und so geht's:

  git log hacking ^master

Eigentlich ganz einfach. Natürlich gibt's noch jede Menge Optionen für git log, mit dem man anpassen kann, was genau wie ausgegeben wird. Beliebt sind in diesem Zusammenhang --oneline und --graph. Aber wenn man wirklich will geht da noch viel mehr...

Dienstag, 12. Februar 2013

Git Status in der Bash, Teil 2

Vor ein paar Jahren hatte habe ich unter Git Status in der Bash meine PS1-Definition abgetippert. Das war eine Kombination aus Bash-Funktionen und einem Prompt-String, die auch lange Zeit ganz gut funktioniert hat. Mit einem der aktuellen Git-Updates (aus der 1.8x-Reihe, wenn ich mich nicht irre) ist das dann umgefallen: Weil Git eine Ausgabezeile geringfügig geändert hat, zeigte das Skript grundsätzlich alle Working Directories als "modified" an. Ärgerlich.

Aber ein Grund, etwas zu verbessern. Die Skripte, die den Status des aktuellen Working Directory anzeigen, gibt's nämlich im Rahmen der normalen Git-Installation unter contrib. (Wer das in seinem Ubuntu nicht findet: In den Git-Sourcen ist das vollständige contrib-Verzeichnis enthalten, einfach einmal clonen und dann contrib/completion/git-prompt.sh einbinden).

Jetzt kommen die folgenden Zeilen in die .profile/.bashrc:

. /contrib/completion/git-prompt.sh
PS1='\[\e]0;\w $(__git_ps1 " %s")\a\]\n\[\e[32m\]\u@\h \
    \[\e[33m\]\w $(__git_ps1 " %s")\[\e[0m\]\n\$ '

Noch ein wenig an den eigenen Geschmack anpassen, und schon ist es fertig. Wer es ein wenig hübscher will, findet in git-prompt.sh noch Hinweise, was man noch so alles tweaken kann.

Das Beste zum Schluss: Da das contrib-Verzeichnis von den Git-Contributors gepflegt wird, bekommt man immer funktionierende für die gerade verwendete Git-Version.

Donnerstag, 24. Januar 2013

Kennt mein Git-Repo das schon?

Hin und wieder muss man tatsächlich nachsehen, ob eine bestimmte Datei schon im Repo ist oder nicht. In der Regel ist ja ein

git status

ausreichend, aber was, wenn man nichts verändert hat? Dann taucht die Datei auch in keiner Liste auf. Und wenn man in ein unbekanntes Repo guckt, hat man ohnehin schlechte Karten: Vielleicht sind die Dateien, die man meint, ja generiert worden und stehen schon in der .gitignore?

Schnelle Antwort liefert diese Kommandozeilenungetüm:

git ls-files --error-unmatch Dateiname

Wenn die Datei bereits im Repo ist, wird sie gelistet, ansonsten gibt's eine Fehlermeldung und, skriptfreundlich, einen Exitcode ungleich Null.

Tip: Kann man auch als Alias einrichten.

Mittwoch, 16. Januar 2013

Riesenrepos

Hier mal ein Fall für Extrem-Repos: Angenommen, im Repository tummeln sich mehrere Gigabyte Daten, beispielsweise weil man ganze Filmschnittprojekte dort ablegt (vermutlich ohnehin eine schlechte Idee), dann steht man vor zwei Problemen: Erstens dauert ein Clonen ewig, und zweitens ist irgendwann auch die größte Festplate voll. Git hat dafür eine einfache Lösung: Referenz-Repos.

Bevor ich weitermache, eine kurze Warnung: Referenz-Repos sind nicht ohne Risiko. Das ist möglicherweise nichts für Leute, die gerne an der History rumbasteln.

mkdir -p /mirror
git clone --bare git@server:/myrepo.git /mirror/myrepo.git
git clone --reference /mirror/myrepo.git git@server:/myrepo.git myrepo

Das erste Clonen erzeugt ein (lokales) Bare-Repo, das zweite verwendet das als Referenz. Den besten Effekt erzielt man, wenn beide Repos auf dem gleichen Filesystem liegen, und es lohnt sich natürlich nur, wenn man mehr als eine lokale Kopie braucht. Das Referenz-Repo muss jetzt wie ein public repo verwendet werden: Verschwinden von dort Commits, sind auch die darauf aufbauenden Repos kaputt. Also Vorsicht.

Findet man sich mit den Beschränkungen ab, bekommt man schnelle und kleine lokale Arbeitskopien. Das Referenz-Repo kann man leicht mit Cron-Jobs aktuell halten: je aktueller das Referenz-Repo, desto weniger Daten müssen die Arbeitskopien separat übers Netz schaufeln. Ich habe allerdings nicht ausprobiert, was passiert, wenn man die History im remote Repo umschreibt oder dort einen Commit löscht. Hausaufgabe: Ausprobieren, ob die Arbeitsrepos dabei beschädigt werden.

Das Ergebnis kann es aber durchaus wert sein: In meinem Falle ist ein 8GB Arbeitsrepo auf 3GB geschrumpft -- 5GB müssen beim Clonen also nicht mehr über die Leitung.

(Nein, es war kein Filmschnittprojekt.)