{"id":1487,"date":"2013-08-22T23:38:16","date_gmt":"2013-08-22T21:38:16","guid":{"rendered":"https:\/\/blog.benny-baumann.de\/?p=1487"},"modified":"2013-08-23T13:47:56","modified_gmt":"2013-08-23T11:47:56","slug":"mehr-farbe-fuer-git","status":"publish","type":"post","link":"https:\/\/blog.benny-baumann.de\/?p=1487","title":{"rendered":"Mehr Farbe f\u00fcr Git"},"content":{"rendered":"<p>So eine Kommandozeile ist in der Regel Gr\u00fcn auf Schwarz, zumindest, wenn man sein Terminal ganz klassisch eingerichtet hat.<!--more--><\/p>\n<p>Klassisch sieht dies meist so aus:<\/p>\n<pre lang=\"bash\">\r\nif [ \"$color_prompt\" = yes ]; then\r\n    PS1='${debian_chroot:+($debian_chroot)}\\[\\033[01;32m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[00m\\]\\$ '\r\nelse\r\n    PS1='${debian_chroot:+($debian_chroot)}\\u@\\h:\\w\\$ '\r\nfi\r\n<\/pre>\n<p>Insbesondere wenn man mit einem Versionsverwaltungstool wie Git arbeitet, ist diese doch sehr kurze Ausgabe meist sehr unpraktisch, da mindestens der aktuelle Branch noch durchaus interessant gewesen w\u00e4re. Dieser ist ohne Weiteres hinzugef\u00fcgt, aber so richtig spannend ist das alles noch nicht:<\/p>\n<pre lang=\"bash\">\r\nif [ \"$color_prompt\" = yes ]; then\r\n    PS1='${debian_chroot:+($debian_chroot)}\\[\\033[01;32m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[01;31m\\]$(__git_ps1 \" (%s)\")\\[\\033[00m\\]\\$ '\r\nelse\r\n    PS1='${debian_chroot:+($debian_chroot)}\\u@\\h:\\w$(__git_ps1 \" (%s)\")\\$ '\r\nfi\r\n<\/pre>\n<p>Wo wir nun den aktuellen Branch schon einmal im Prompt enthalten haben, k\u00f6nnen wir das Ganze auch noch etwas ausbauen und uns eine Anzeige des Repository-Status erg\u00e4nzen. Da diese Auswertung etwas aufw\u00e4ndiger ist, bauen wir den Code durch Auslagern in eine Funktion leicht um:<\/p>\n<pre lang=\"bash\">\r\n__git_ps2() {\r\n    if __gitdir >\/dev\/null; then\r\n        if ! git diff --no-ext-diff --quiet --cached --exit-code; then\r\n            # staged changes\r\n            echo -ne '\\033[01;31m'\r\n        elif ! git diff --no-ext-diff --quiet --exit-code; then\r\n            # unstaged changes\r\n            echo -ne '\\033[01;33m'\r\n        else\r\n            # no changes\r\n            echo -ne '\\033[01;32m'\r\n        fi\r\n        __git_ps1 \" (%s)\"\r\n    fi\r\n}\r\n\r\nif [ \"$color_prompt\" = yes ]; then\r\n    PS1='${debian_chroot:+($debian_chroot)}\\[\\033[01;32m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[01;31m\\]$(__git_ps2)\\[\\033[00m\\]\\$ '\r\nelse\r\n    PS1='${debian_chroot:+($debian_chroot)}\\u@\\h:\\w$(__git_ps2)\\$ '\r\nfi\r\n<\/pre>\n<p>Okay, das sieht doch schon mal gut aus. Wenn wir aber eh schon auswerten, ob es \u00c4nderungen im Repository gibt, k\u00f6nnen wir auch gleich mit einblenden, wie viel sich ge\u00e4ndert hat:<\/p>\n<pre lang=\"bash\">\r\n__git_ps2() {\r\n    if __gitdir >\/dev\/null; then\r\n        if ! git diff --no-ext-diff --quiet --cached --exit-code; then\r\n            # staged changes\r\n            echo -ne '\\033[01;31m'\r\n        elif ! git diff --no-ext-diff --quiet --exit-code; then\r\n            # unstaged changes\r\n            echo -ne '\\033[01;33m'\r\n        else\r\n            # no changes\r\n            echo -ne '\\033[01;32m'\r\n        fi\r\n        STAT_S=`git diff --cached --stat|tail -1|awk '{print $1\" \"$4\" \"$6}'`\r\n        STAT_U=`git diff --stat|tail -1|awk '{print $1\" \"$4\" \"$6}'`\r\n        __git_ps1\r\n        if [ \"$STAT_U\" != \"\" -o \"$STAT_S\" != \"\" ]; then\r\n            echo -ne '\\033[01;37m@['\r\n            printf \"T:\\033[01;33m%d\\033[01;37m{\\033[01;32m+%d\\033[01;37m\/\\033[01;31m-%d\\033[01;37m}\" $STAT_U\r\n            if [ \"$STAT_S\" != \"\" ]; then\r\n                echo -ne ' '\r\n                printf \"S:\\033[01;33m%d\\033[01;37m{\\033[01;32m+%d\\033[01;37m\/\\033[01;31m-%d\\033[01;37m}\" $STAT_S\r\n            fi\r\n            echo -ne ']'\r\n        fi\r\n    fi\r\n}\r\n<\/pre>\n<p>Auch wenn wir theoretisch mit diesem Prompt bereits am Ziel sind, gibt es doch ein sehr subtiles Problem, welches sich zeigt, wenn man l\u00e4ngere Befehle eingibt. Unter umst\u00e4nden f\u00e4ngt die Shell dann n\u00e4mlich bereits vorzeitig an umzubrechen, obwohl die Befehlszeile noch bei Weitem nicht vollst\u00e4ndig gef\u00fcllt ist. Insbesondere das Cursor-Handling ist unter diesen Umst\u00e4nden &#8222;kaputt&#8220;.<\/p>\n<p>Wer jetzt annimmt, dass es hilft, in unserer Funktion einfach auch die <a href=\"http:\/\/www.cyberciti.biz\/tips\/howto-linux-unix-bash-shell-setup-prompt.html\">Escape-Sequenzen f\u00fcr die Shell<\/a> zu erg\u00e4nzen und aus<\/p>\n<pre lang=\"bash\">\r\n            echo -ne '\\033[01;37m@['\r\n<\/pre>\n<p>kurzerhand<\/p>\n<pre lang=\"bash\">\r\n            echo -ne '\\[\\033[01;37m\\]@['\r\n<\/pre>\n<p>macht, wird feststellen, dass pl\u00f6tzlich \\[ im Prompt angezeigt wird. Hier scheint die Shell alle Ausgaben, die durch eine Subshell im Prompt erzeugt werden, als literalen Text zu behandeln: Hierdurch werden alle Escape-Sequenzen in der Cursor-Position mitgez\u00e4hlt. M\u00f6chte man dies vermeiden, so ist ein wenig Arbeit n\u00f6tig. Bevor wir uns aber darum k\u00fcmmern, erg\u00e4nzen wir noch kurz eine Info, ob wir gerade Root sind:<\/p>\n<pre lang=\"bash\">\r\nUIDCOLOR=32\r\n[[ 0 == $UID ]] && UIDCOLOR=31\r\nPS1='${debian_chroot:+($debian_chroot)}\\[\\033[01;${UIDCOLOR}m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w$(__git_ps2)\\033[00m\\$ '\r\n<\/pre>\n<p>Aber zur\u00fcck zu unserem Problem: Da die Shell die via Subshell ausgegebenen Zeichen in jedem Fall z\u00e4hlt und dabei auch Escape-Sequenzen einbezieht, m\u00fcssten wir ein wenig das Problem umgehen: Mittels Escape-Sequenz merken wir uns die Position zuerst, geben dann eine Klartext-Version aus und drucken anschlie\u00dfend die farbige Version des Strings an der vorher gemerkten Position:<\/p>\n<pre lang=\"bash\">\r\nif [ \"$color_prompt\" = yes ]; then\r\n    UIDCOLOR=32\r\n    [[ 0 == $UID ]] && UIDCOLOR=31\r\n    PS1='${debian_chroot:+($debian_chroot)}\\[\\033[01;'${UIDCOLOR}'m\\]\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[01;37m\\]\\[\\0337\\]$(__git_ps2 0)\\[\\0338$(__git_ps2 1)\\]\\[\\033[00m\\]\\$ '\r\nelse\r\n    PS1='${debian_chroot:+($debian_chroot)}\\u@\\h:\\w$(__git_ps2 0)\\$ '\r\nfi\r\n<\/pre>\n<p>Ein kleines Detail ist in diesem Code-Abschnitt bereits angedeutet: Um zu Unterscheiden, ob wir die Klartext-Variante oder die farbige Version haben wollen. Hierzu m\u00fcssen wir auch in unserer Funktion noch eine Kleinigkeit anpassen:<\/p>\n<pre lang=\"bash\">\r\n__git_ps2() {\r\n    if __gitdir >\/dev\/null; then\r\n        if ! git diff --no-ext-diff --quiet --cached --exit-code; then\r\n            # staged changes\r\n            [ $1 -ne 0 ] && echo -ne '\\033[01;31m'\r\n        elif ! git diff --no-ext-diff --quiet --exit-code; then\r\n            # unstaged changes\r\n            [ $1 -ne 0 ] && echo -ne '\\033[01;33m'\r\n        else\r\n            # no changes\r\n            [ $1 -ne 0 ] && echo -ne '\\033[01;32m'\r\n        fi\r\n        STAT_S=`git diff --cached --stat|tail -1|awk '{print $1\" \"$4\" \"$6}'`\r\n        STAT_U=`git diff --stat|tail -1|awk '{print $1\" \"$4\" \"$6}'`\r\n        __git_ps1\r\n        if [ $1 -ne 0 ]; then\r\n            if [ \"$STAT_U\" != \"\" -o \"$STAT_S\" != \"\" ]; then\r\n                echo -ne '\\033[01;37m@['\r\n                printf 'T:\\033[01;33m%d\\033[01;37m{\\033[01;32m+%d\\033[01;37m\/\\033[01;31m-%d\\033[01;37m}' $STAT_U\r\n                if [ \"$STAT_S\" != \"\" ]; then\r\n                    echo -ne ' '\r\n                    printf 'S:\\033[01;33m%d\\033[01;37m{\\033[01;32m+%d\\033[01;37m\/\\033[01;31m-%d\\033[01;37m}' $STAT_S\r\n                fi\r\n                echo -ne ']'\r\n            fi\r\n        else\r\n            if [ \"$STAT_U\" != \"\" -o \"$STAT_S\" != \"\" ]; then\r\n                echo -ne '@['\r\n                printf 'T:%d{+%d\/-%d}' $STAT_U\r\n                if [ \"$STAT_S\" != \"\" ]; then\r\n                    echo -ne ' '\r\n                    printf 'S:%d{+%d\/-%d}' $STAT_S\r\n                fi\r\n                echo -ne ']'\r\n            fi\r\n        fi\r\n    fi\r\n}\r\n<\/pre>\n<p>Und fertig sind wir. Wem dies alles zu bunt ist, kann mit dem nachfolgenden Prompt wieder <a href=\"https:\/\/twitter.com\/climagic\/status\/368113017835118592\">ganz klassisch arbeiten<\/a>:<\/p>\n<pre lang=\"bash\" escaped=\"true\">\r\nPS1='C:$(pwd | sed s\/\\\\\\\\\/\/\\\\\\\\\\\\\\\\\/g)&gt; '\r\n<\/pre>\n<p class=\"wp-flattr-button\"><a href=\"https:\/\/blog.benny-baumann.de\/?flattrss_redirect&amp;id=1487&amp;md5=1325274f3939ba8c10542fedc3295c88\" title=\"Flattr\" target=\"_blank\"><img src=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" srcset=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" alt=\"Flattr this!\"\/><\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>So eine Kommandozeile ist in der Regel Gr\u00fcn auf Schwarz, zumindest, wenn man sein Terminal ganz klassisch eingerichtet hat.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[29],"tags":[324,10,98,334],"class_list":["post-1487","post","type-post","status-publish","format-standard","hentry","category-software","tag-bash","tag-debian","tag-developement","tag-git"],"_links":{"self":[{"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/1487","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1487"}],"version-history":[{"count":6,"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/1487\/revisions"}],"predecessor-version":[{"id":1494,"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/1487\/revisions\/1494"}],"wp:attachment":[{"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1487"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1487"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1487"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}