Samstag, 17. April 2010

Git-Mode für die KSH

Ich habe gerade mal meine Git Erweiterung für die Kornshell in meinen Bitbucket gestellt. Das ganze liegt zwar noch unter "Mercurial-Integration" aber sollte zu finden sein ;). Wie schon beim Mercurial mode wechselt man mit mode git in den entsprechenden Modus. Die Farbe der Commit ID wechselt von Rot (uncommited changes) zu Gelb (nicht die aktuellste Revision) zu Grün (aktuellste Revision und nichts zu commiten). Außerdem wird der aktuelle Branch angezeigt und im Falle eines "detached HEAD" Zustands visuell gewarnt. Viel Spass damit!

Git happens

Ich bin kein besonders großer Fan von Git, allerdings kommt man hin und wieder einfach nicht drum herum. Da mein Standard-Workflow derzeit einfach darin besteht Commits im Nirvana verschwinden zu lassen gibt es eigentlich nur 2 Alternativen für mich:
  1. Eine Selbsthilfegruppe für Git-Geschädigte aufsuchen

  2. Ein paar Skripten schreiben die mir das Leben leichter machen

Zweiteres klang bei näherer Betrachtung einfach attraktiver.

Da ich im privaten Umfeld mehr Mercurial nutze platzt meine .gitconfig derzeit durch Aliase auseinander die meinen Workflow erhalten, 2 davon replizieren in etwa das Verhalten von incoming und outgoing, oder anders gesagt: sie zeigen mir eingehende bzw. ausstehende Commits an.

Beispiel:

// commits die noch nicht in origin/master
// eingeflossen sind
git out origin/master

// commit aus origin/master die noch nicht
// in meinem lokalen Branch liegen
git in origin/master

Die beiden Aliase sind nicht besonders spektakulär aber nützlich:

out = "log --stat HEAD --not"
in = "!f() { git log --stat $1 --not HEAD; }; f"

Ein weiteres Alias reproduziert das verhalten von hg log -G und zeigt mit die History in einem halbwegs lesbaren Graphen an (bei komplexeren Bäumen greife ich aber lieber zu einem Tool wie GitX)

graph = "log --graph --pretty=oneline --abbrev-commit --decorate --all"

Das alles hilft aber noch nicht bei meinem eigentlichen Problem: Commits schrotten.
Hierfür habe ich mir eine kleine Erweiterung geschrieben… Also nicht zum schrotten, sondern zum wiederherstellen ^^.
Mit git orphans werden mir sogenannte "dangling commits" angezeigt, das sind Commits die auf normalem Wege nicht mehr erreichbar sind. Da git ohne Garbage Collection (git gc oder git prune) nicht so ohne weiteres Commits wegwirft sondern als "dangling commits" im Nirvana vorhält, kann ich mit git reset --hard SHA1 (oder in meinem Fall mit dem Alias git rollback SHA1) meinen HEAD auf gerade verlorene Commits umbiegen und alles ist wieder beim Alten. git orphans verifiziert Commitobjekte und zeigt mir den dazugehören History Eintrag an. Das Ganze ist ein simples Shellskript das man einfach zu den anderen git Kommandos (welche bei mir in libexec/git-core liegen) unter dem Namen git-orphans abglegt.

#!/bin/sh

USAGE=''
OPTIONS_SPEC=
. git-sh-setup

if [ "$#" != "0" ]
then
usage
fi

git fsck --lost-found |
while read dangling type sha1
do
case "$type" in
commit)
if git rev-parse -q --verify "$sha1" >/dev/null
then
git log --color -n 1 "$sha1"
echo ""
fi
;;
esac
done | git_pager

Für mich sind das ein paar sehr brauchbare Anpassungen, ich hoffe der ein oder andere findet sie ebenso brauchbar :)

Mittwoch, 14. April 2010

C++ "Lazy Evaluation" für Streams

Manchmal will man nicht das ein Stream überhaupt Sachen ausgibt (z.B. für Debugstreams die sich bei Bedarf zuschalten kann) und dann will man natürlich auch nicht das die Parameter die durch den Streamoperator gehen abgearbeitet werden da sich dort eventuell komplexe Parsereien verstecken könnten.

Hierfür gibt es einen denkbar einfachen Trick mit C-Macros (jaja ich weiß, aber hin und wieder bin sogar ich da pragmatisch :P ).

#include <iostream>
#define DEBUGSTREAM if (!debug); else std::cerr

int main() {
bool debug = false;
DEBUGSTREAM << "Test\n";
return 0;
}

In diesem Code wird nichts ausgegeben und auch nichts ausgewertet, das Verhalten lässt sich einfach zur Laufzeit ändern. Tada! "Lazy Evaluation" on demand :)

C++ Steams dressieren

Manchmal möchte man gerne bestimmte Aktionen auslösen die sich nach dem Abwickeln einer Streambefüllung zutragen sollen (z.B. das ein Lock freigeben). Hier ist ein kleiner Trick der genau das macht und in bestimmten Situationen durchaus hilfreich sein kann: wenn z.B. man durch widrige Umstände nicht in der Lage ist den Scope zu begrenzen.

#include <iostream>
#include <tr1/memory>

class Tester {
public:

Tester& operator<<(std::string s) {
std::cout << s;
return *this;
}

~Tester() {
std::cout << "End\n";
}
};

std::tr1::shared_ptr<Tester> wrapper() {
std::cout << "Begin\n";
return std::tr1::shared_ptr<Tester>(new Tester());
}

int main() {
*(wrapper()) << "A "<< "test!\n";
std::cout << "Ending scope\n";
return 0;
}

Deadlocks jagen mit DTrace

Deadlocks gehören so ziemlich zu den widerlichsten Phänomenen die man sich so beim Programmieren vorstellen kann. Vor allem wenn sie nur sporadisch auftreten. Irgendein Thread weigert sich einfach ein Lock wieder freizugeben und wir hängen. Mit dem gleichen Problem durfte ich mich neulich auch rumschlagen, leider tappten wir total im Dunkeln da nicht lokalisieren war wer das Lock eigentlich in Beschlag genommen hat. Hier musste wieder mal meine Lieblingswunderwaffe ran und das Problem mit einem D Skript eingekreist werden.

Was dieses Skript macht ist nichts anderes als sich zu jedem Lock der geschlossen wird den Stacktrace zu merken und sobald wir an den Punkt kommen an dem das Deadlock auftritt uns zu zeigen wer gerade das Lock hält das uns am weitergehen hindert. Das ganze ist ein unglaublicher Speicherfresser (ich muss dazu sagen das die jeweiligen Stacktraces einfach nur abnorm waren) da ich hier Speculations verwende (siehe letzter Eintrag ;) ) und DTrace bereinigt den Speicher nur in bestimmten Intervallen, was in bei der Menge an Locks einfach zu langsam war. Außerdem hat man nur eine begrenzte Anzahl an Speculations zur Verfügung. Aber DTrace wär nicht das was es ist wenn man das nicht auch "aushebeln" könnte ;)

nspec dient dazu die Anzahl der möglichen Speculations hochzusetzen und cleanrate bestimmt die Frequenz der Garbagecollection, mit -b schrauben wir den Gesamtpuffer auf 128MB hoch.

Also, ohne weitere Umschweife: der Star des gestrigen Tages!


#!/usr/sbin/dtrace -b 128m -x nspec=20 -x cleanrate=101 -s

pid$target::deadlockgoeshere:entry
{
self->traceme = 1;
}

pid$target::deadlockgoeshere:return
/self->traceme/
{
self->traceme = 0;
}

pid$target::pthread_mutex_lock:entry
/spec[arg0] == 0/
{
spec[arg0] = speculation();
speculate(spec[arg0]);
ustack();
}

pid$target::pthread_mutex_lock:entry
/spec[arg0] && self->traceme/
{
commit(spec[arg0]);
spec[arg0] = 0;
}

pid$target::pthread_mutex_lock:entry
/spec[arg0] && self->traceme == 0/
{
discard(spec[arg0]);
spec[arg0] = 0;
}

pid$target::pthread_mutex_unlock:entry
{
discard(spec[arg0]);
spec[arg0] = 0;
}