Tag Archives: Git

Recovering from git push fuckup

For some of my projects the central Git server is hosted with gitolite for a long time now. Some time ago I forked a repo bss (example name) to a new repo gee, where both should keep the history of bss, but head in a different direction, including different tags. While doing changes on both repos in different branches I still need to merge an old branch of bss to the recent branch of gee, but usually not in the other direction.

To not accidentally break things, my working copies of both projects only have one remote, the one designated for the original or the fork. For merge or cherry-pick operations in one or the other directions I have a special working copy with both remotes configured. Today after a cherry-pick from gee to bss I messed up pushing, and ended up with gee/master pushed to bss/master, which was wrong as hell, and cold sweat appeared on my head. Damn.

So I had to rewind bss/master to a known good state, but on the server.
WARNING: Make 100% sure you know what you do here to prevent DATA LOSS or your colleagues being mad at you or both.

First try was with the obvious git push --force which was denied by gitolite right away. If you do not know why force pushing usually is a bad thing, stop reading here and learn why it is a bad thing.

Did I warn you about possible data loss?

Second try usually works with branches which are not the default branch, like this (assuming I messed up origin/mybranch and want to push a different version):

$ git push --delete origin mybranch
$ git push --set-upstream origin mybranch

This won’t work with the default branch however with this error message:

$ git push --delete origin master
remote: error: By default, deleting the current branch is denied, because the next
remote: 'git clone' won't result in any file checked out, causing confusion.
remote:
remote: You can set 'receive.denyDeleteCurrent' configuration variable to
remote: 'warn' or 'ignore' in the remote repository to allow deleting the
remote: current branch, with or without a warning message.
remote:
remote: To squelch this message, you can set it to 'refuse'.
remote: error: refusing to delete the current branch: refs/heads/master
To ssh://git.example.com/bss
 ! [remote rejected]   master (deletion of the current branch prohibited)
Fehler: Fehler beim Versenden einiger Referenzen nach 'ssh://git.example.com/bss'

This is not a gitolite problem, but Git itself complains here. Why? For bare repositories Git has a concept of a default branch. This is the branch you get when cloning a repo without specifying a branch. It is marked by the ref named HEAD. Now the idea to recover is like this:

  1. create a new temporary branch on the same changeset you want to have as master on the server
  2. push that branch to the server
  3. change the default branch on the server
  4. do the push –delete / push dance from above (with master now not being the default branch anymore)
  5. change the server’s default branch back
  6. delete the temporary branch both local and remote

In recent Git changing the default branch is done with git symbolic-ref and that would have to be executed on the gitolite server.

Thankfully on recent gitolite V3 you can execute that through SSH, and I learned this from stackoverflow. These were the commands I used (including output), with main being my temporary branch (see above):

$ ssh git@git.example.com symbolic-ref bss HEAD
refs/heads/master
$ ssh git@git.example.com symbolic-ref bss HEAD refs/heads/main
$ ssh git@git.example.com symbolic-ref bss HEAD                
refs/heads/main

Now this is the time to delete master on the remote and push it again from a known good state. Then switch back the default branch.

$ ssh git@git.example.com symbolic-ref bss HEAD refs/heads/master
$ ssh git@git.example.com symbolic-ref bss HEAD                  
refs/heads/master

Everything fine again now. Wrote this for my future self.

dropwatch moved

While debugging some network packet loss I stumbled over a blog post from 2013: Dropwatch to see where the packets are dropped in kernel stack. And another one: Using netstat and dropwatch to observe packet loss on Linux servers. I wanted to try it, but unfortunately fedorahosted.org where it was hosted previously is history since March 1st, 2017.

My favorite distributions and build systems didn’t have dropwatch as package or still used the old addresses so I looked for some clones at GitHub, got the E-Mail address of what appeared to be the maintainer and got in contact. I got a friendly reply and – good news everyone – the project is still maintained and moved to: git.infradead.org/users/nhorman/dropwatch.git

Hope this helps someone, maybe when this page hits the search engines. ;-)

HowTo: gitolite auf Debian Wheezy

Das Netz ist natürlich voll von HowTos zum Thema gitolite, in diesem hier nutze ich aber nicht die bleeding edge Version von upstream sondern das Debian-Paket. Eine gewisse Hilfe für’s Verständnis und bei der Installation war die Dokumentation für gitolite 2.x, denn in Wheezy ist Version 2.3 verpackt.

Installation

Geht los mit Installation des Pakets gitolite. Wenn der bei der Installation dpkg nichts konfiguriert haben will, dann gibt man danach nochmal ein:

dpkg-reconfigure gitolite

Dort wird dann ein SSH public key abgefragt für den Admin-Nutzer. In meinem Fall hab ich hier für die Testinstallation auf dem selben Rechner den folgenden Pfad angegeben, ansonsten kopiert man seinen public key ins Eingabefeld:

/home/adahl/.ssh/id_rsa.pub

Damit liegt jetzt hier unterhalb von /var/lib/gitolite1 folgendes:

% ls -la /var/lib/gitolite
insgesamt 28
drwxr-xr-x  5 gitolite gitolite 4096 Jun 17 13:51 ./
drwxr-xr-x 58 root     root     4096 Jun 17 13:47 ../
drwx------  8 gitolite gitolite 4096 Jun 17 13:47 .gitolite/
-rw-r--r--  1 gitolite gitolite 4217 Jun 17 13:47 .gitolite.rc
-rw-------  1 gitolite gitolite    0 Jun 17 13:47 projects.list
drwx------  4 gitolite gitolite 4096 Jun 17 13:47 repositories/
drwx------  2 gitolite gitolite 4096 Jun 17 13:47 .ssh/

Da mein public key bereits hinterlegt ist, kann ich direkt zum nächsten Schritt übergehen und das Admin-Repo clonen, welches bei der Installation angelegt wurde:

git clone gitolite@localhost:gitolite-admin

Wie man sieht, hatte ich den dedizierten Nutzer gitolite genannt, für Produktionsumgebung ist vielleicht git eine bessere Wahl.

Ganz wichtig: beim Ändern der Config muss man stets drauf achten, dass man sich nicht selbst aussperrt. Für das Repository gitolite-admin muss man Schreibrechte für irgendeinen Key behalten, auf den man auch Zugriff hat und der unterhalb von keydir im Repo liegt, sonst hat man keine Möglichkeit mehr ohne große Schmerzen diese Rechte wiederzuerlangen.

Konfiguration

Als nächstes habe ich die Datei /var/lib/gitolite/.gitolite.rc angepasst. Berücksichtigt sind hier bereits, dass ich sowohl git-daemon als auch gitweb einsetzen und die Repos auf einen anderen Rechner spiegeln will. D.h. ich habe folgende Einträge geändert:

$REPO_UMASK = 0027;
$GL_GITCONFIG_KEYS = "gitolite.mirror.*";
$REPO_BASE="/srv/repos/git";

Der letzten Zeile ist erhöhte Beachtung zu schenken. Die bereits angelegten Repositories gitolite-admin und testing aus /var/lib/gitolite/repositories müssen nämlich in den geänderten Pfad verschoben werden.2 Damit der Zugriff über gitweb funktioniert, habe ich den Nutzer www-data der Gruppe git hinzugefügt und die Rechte des Repository-Verzeichnisses gelockert:3

sudo addgroup www-data git
sudo chmod g+rx /var/lib/gitolite/repositories

Die eigentliche Konfiguration erfolgt dann wie bei gitolite üblich über das admin-Repository nachdem man es geklont hat:

git clone gitolite@myfancyserver:gitolite-admin

Wie man das im Einzelnen macht, kann man in der Doku zu gitolite v2 nachlesen. Das ist nicht Debian-spezifisch.

gitweb mit lighttpd

Für einen schnellen Überblick mit dem Webbrowser ist das Paket gitweb installiert. Als Webserver kann vermutlich irgendein Webserver dienen. Für lighttpd habe ich eine Datei /etc/lighttpd/conf-available/80-gitweb.conf angelegt und mit den für diesen Browser üblichen Mechanismen aktiviert. Der Inhalt sieht so aus:4

# -*- depends: cgi -*-
server.modules += ("mod_setenv")
setenv.add-environment = ("GITWEB_CONFIG" => "/etc/gitweb.conf")
alias.url += ( "/gitweb" => "/usr/share/gitweb" )

$HTTP["url"] =~ "^/gitweb/" {
        server.indexfiles = ("gitweb.cgi")
        cgi.assign = ( ".cgi" => "" )
}

Es ist außerdem das Modul 10-cgi.conf zu aktivieren:

sudo lighty-enable-mod cgi
sudo lighty-enable-mod gitweb

In der /etc/gitweb.conf sind auch noch Anpassungen zu tätigen. Hier meine geänderten Zeilen:

$projectroot = "/var/lib/gitolite/repositories";
$projects_list = "/var/lib/gitolite/projects.list";
@diff_opts = ('--find_renames', '--minimal');

Urspünglich war jetzt hier noch eine Beschreibung zum Mirroring, ich verschiebe das auf später und tu es in einen separaten Artikel … O:-)

  1. auch den Pfad kann man beim dpkg-reconfigure angeben. []
  2. Dieser Schritt kann natürlich entfallen, wenn man die Repositories im Standard-Pfad belässt. []
  3. Die Rechte der bei der Installation angelegten Repositories sind allerdings immernoch so restriktiv, dass sie nicht über gitweb angezeigt werden können. Bei <code>gitolite-admin</code> will man das sowieso nicht und statt <code>testing</code> legt man sich vielleicht ein weiteres Testrepository an, um zu sehen, ob das mit den Rechten beim neu anlegen richtig klappt. []
  4. Siehe auch gitweb im ArchWiki. []

Git unter Windows

Ganz ehrlich, ich hab die Schnauze voll von Windows. Es begann damit, dass ich mehrere Tage gebraucht habe, um Qt 4.8.x selbst zu kompilieren, weil die pre-built Binaries mit einem alten MinGW mit GCC 4.4.x gebaut wurden, dieses aber nicht mit Qt mitgeliefert wird. MinGW selbst stellt aber keine älteren Releases bereit und wegen ABI-Änderungen bei GCC lässt sich Qt (GCC 4.4) nicht mit aktuellem MinGW (GCC 4.8) zusammen nutzen. Das allein schon war frustrierend genug, aber es tauchen dann ja noch so Schwierigkeiten auf, wie dass der Snakeoil Virenscanner selbst kompilierte Software hart löscht. Jetzt habe ich endlich ein Qt und dann beschwert sich CMake noch lange vor dem Build der Anwendung, dass es ein “sh” im PATH gefunden hat. Git für Windows bringt das mit, wozu auch immer. Dachte ich, schauste mal, ob das unbedingt nötig ist, aber die Git-GUIs für Windows brauchen das und auch sonst ist das mit den GUIs für Git unter Windows höchst schmerzhaft. Eine Liste gibt es zwar, aber das ist alles unbrauchbar:

GitHub for Windows
Verbirgt große Teile der Komplexität von Git, ist damit für gewisse Workflows unbrauchbar und arbeitet, oh Wunder, nur mit GitHub.
Git Extensions
Ist nicht plattformunabhängig und installiert mir Dinge mit, die ich schon habe.
git-cola
Braucht erstmal noch Python und PyQt und letzteres wiederum bringt im Binary-Installer gleich wieder ein ganzes Qt mit. Was hatte ich noch gleich selbst kompiliert?
GitEye
Ist ein komplettes Eclipse, für Git, also riesengroß, nur für ein Versionsverwaltungssystem. Irre.
SmartGit
Nicht frei.
SourceTree
Braucht mindestens Windows 7.

Dann wäre da noch TortoiseGit, aber das ist nur ein Fork von dem für Subversion super geeigneten TortoiseSVN und dementsprechend ungeeignet für einen Git-Workflow. Git ist halt kein zentralisiertes Versionsverwaltungssystem wie CVS, wie soll ich dafür dann das selbe Interface gebrauchen können?

Bei Mercurial hat man wenigstens ein schönes plattformunabhängiges GUI in Qt geschrieben. Das heißt zwar auch TortoiseHg, hat aber von der Bedienung her kaum was mit den zuvor genannten gemein und spült mir auch keine zusätzliche kaputte Shell auf meinen Rechner.

Hölle, ist das alles broken. Ich will mein Linux zurück. :-/

Ach so ja, das Problem mit CMake lässt sich übrigens lösen, indem man das CMake-GUI mit einem sauberen PATH startet, bspw. mit einer Batch-Datei cmake-gui.bat:

cd "C:\Programme\CMake 2.8"
PATH=C:\MinGW\bin;C:\Qt\Qt4.8.5-x86\bin
"C:\Programme\CMake 2.8\bin\cmake-gui.exe"

Farbige, seitenweise Diffs mit Mercurial

Bei der Mitarbeit an verschiedenen Projekten ist man heutzutage beinahe schon gezwungen sich mit verschiedenen Versionsverwaltungssystemen auseinanderzusetzen. Ich selbst benutze am liebsten Mercurial, aber es gibt auch Projekte, wo Git oder Subversion benutzt werden. Alle haben ihre Vor- und Nachteile aber so gewisse Dinge, die das eine per default macht, hätte man gern auch im anderen und bei einigen Dingen bekommt man das sogar hin.

Ein wirklich praktisches Feature von Git ist beispielsweise, dass es bei einem `git diff` den Output farbig und im Pager darstellt. Um dieses Verhalten in Subversion nutzen zu können, kann man ein externes Skript nutzen, das ich letztens im Netz gefunden habe: Color highlighted diffs with git, svn and cvs. Für Mercurial hab ich mich gerade nochmal selbst auf die Suche gemacht und folgende Lösung gefunden:

Im Grunde muss man nur die beiden Erweiterungen color und pager aktivieren und konfigurieren. Das sieht bei mir in der ~/.hgrc jetzt wie folgt aus:

[extensions]
color =
pager =

[pager]
pager = LESS='FSRX' less

Die Aktivierung der Extensions ist selbsterklärend, die Optionen habe ich so von der Wikiseite zur pager-Extension übernommen bzw. über `man less` nochmal nachgelesen. Wichtig hier ist auf jeden Fall das ‘R’, damit die Farbcodes von less auch interpretriert werden.