Freitag, 25. November 2011

Liferay

Ich stelle in einem kurzen Blogeintrag das CMS Liferay vor.

Liferay ist eine Enterprise Plattform, welche als Portal genutzt werden kann. Es können mehrere Webseiten - hier Organisation genannt - und Communities angelegt werden. Die Installation inklusive SDK (Software Development Kit) ist simpel und lokales Arbeiten schnell möglich.

Eine Webseite kann ohne Anpassungen im Default-Layout angelegt werden. Für jede Seite kann ein Seitenlayout ausgewählt werden, welches das grundsätzliche Raster vorgibt. Man kann Portlets auf einer Seite hinzufügen und per Drag&Drop positionieren, wie z. B. das Web Content Display, um Inhalte anzuzeigen. Weitere bestehende Portlets sind, um nur ein paar zu nennen, Formular, Navigation, Site Map, Tag-Wolke, Google Maps, RSS. Außerdem können Seitenvorlagen erstellen werden, in denen man Seitenlayout und einzelne Portlets mit deren Positionen vorgibt.

Inhalte werden im Kontrollbereich gesondert gelistet und können mehrfach in verschiedenen Web Content Displays angezeigt werden.

Weiterhin besteht die Möglichkeit Templates anzulegen. Es wird kein Template für eine ganze Seite benötigt, doch es können einzelne Module erstellt werden. Dafür wird eine Struktur und damit die Inhaltselemente vorgeben. Dazu passend wird mit Velocity ein Template geschrieben. Die Templates können auf ein Web Content Display angewendet werden.

Über das SDK kann ein eigenes Theme erstellt werden, welches das Layout definiert. Dabei wird das CSS überschrieben und bestehende Klassen genutzt, so dass das Theme auf das bisherige Seitenlayout angewendet werden kann. Basierend auf Java können im SDK Portlets angepasst oder neu erstellt werden.

Ich finde Liferay ist ein benutzerfreundliches CMS. Aus meinen derzeitigen Erfahrungen stelle ich mir es eher für kleinere Webseiten vor. Derzeit arbeite ich jedoch erst seit ein paar Wochen damit, es könnten sich noch weitere Türen öffnen.

Mir ist Liferay sympathisch und es macht Spaß damit zu arbeiten.

http://www.liferay.com/de/

Freitag, 26. August 2011

Navigation Manager in RedDot

Der Navigation Manager in RedDot erstellt dynamisch eine Navigation. Änderungen in der Navigationsstrukur wirken sich direkt auf die Projektstruktur und die Seiten aus. Das bedeutet, wenn man neue Seiten im Navigation Manager anlegt, wird auf allen Seiten die Navigation angepasst.

Folgende Schritte müssen durchgeführt werden, um eine Navigation mit dem Navigation Manager zu erstellen:
  1. Navigation Manager aktivieren
  2. Mastertemplate mit einem Listenelement erstellen
  3. Content-Klassen für die Navigationsebenen erstellen
  4. Navigationsbereiche erstellen und die Content-Klassen zuordnen
  5. Navigationsbereiche im Mastertemplate angeben
  6. Navigationsstruktur aufbauen
1. Navigation Manager aktivieren
Damit der Navigation Manager genutzt werden kann, muss er in den allgemeinen Einstellungen des Projektes aktiviert werden.

2. Mastertemplate mit einem Listenelement erstellen
Nur an einem Mastertemplate können mit dem Navigation Manager Seiten angehängt werden. Eine Seite, die eine Masterpage als Template hat, wird automatisch in die Navigationsstruktur aufgenommen. Dazu erstellt man eine neue Content-Klasse. Diese kann zum Beispiel das Grundgerüst für alle Seiten sein. In dieser Content-Klasse muss ein Listenelement angelegt werden. Diese Liste dient als direktes Strukturelement zur Verknüpfung von darauf folgenden Ebenen. Die Einstellung für das Listenelement ist „Nur Datei- und Pfadnamen angeben". Dieses Element muss nicht im Template verwendet werden. In den Masterpage Einstellungen wird definiert, dass diese Content-Klasse als Masterpage verwendet wird. Weiterhin gibt man an welches Listenelement als Navigationsstruktur dient.

3. Contentklassen für die Navigationsebenen erstellen
Es muss für jede Navigationsebene ein Template erstellt werden. Wenn es einen „selektiert“ und „nicht selektiert“ Zustand gibt, benötigt jeder Zustand ein eigenes Template. Für eine Navigation mit zwei Ebenen ohne unterschiedliche Zustände, benötigt man also zwei Templates.
Der Navigation Manager bietet Render Tags. Die Render Tags sind Code für die Navigationsfunktionalität. Sie ermöglichen den Zugriff auf Objekte im Management Server, wie Navigationsindex, Inhalte aus den Seiten oder Navigationsbereiche. Komplexere Render Tags Funktionen werden in einem <reddot:cms /> Block zusammengefasst.

Ebene Eins
<ul>
<li>
<a href=" <%!! Context:CurrentIndex.GetUrl() !!%>">
<%!! Context:CurrentIndex.Headline !!%>
</a>
<reddot:cms>
<if>
<query valuea="Context:CurrentIndex.HasChildren()" operator="=="
valueb="Bool:True">
<htmltext>
<ul>
<navigation:nextlevel>
</ul>
</htmltext>
</query>
</if>
</reddot:cms>
</li>
</ul>

Context:CurrentIndex.GetUrl = setzt den Link der aktuellen Seite Context:CurrentIndex.Page.Headline = liest den Seitennamen der aktuellen Seite aus und ergibt den Linktext .
Die Abfrage ist die Grundlage um Seiten aus der zweiten Navigationsebene einzufügen. Wenn die aktuelle Seite (CurrentIndex) Kindelemente besitzt, wird eine Liste für das nächste Level generiert.
<if> <qeury> = Abfrage; alle Werte werden in Strings angegeben, selbst der Vergleichsoperator; es werden valuea und valueb vergleichen.
<htmltext /> = Es kann HTML Code im <reddot:cms /> Block geschrieben werden
<navigation:nextlevel> = fügt nächstes Navigationstemplate ein

Ebene Zwei
<li>
<a href=" <%!! Context:CurrentIndex.GetUrl() !!%>">
<%!! Context:CurrentIndex.Headline !!%>
</a>
</li>

Es wird nur das Listenelement der aktuellen Seite generiert. Das umgebende Listenelement (<ul />) wird im Navigationstemplate Ebene Eins erstellt.

4. Navigationsbereiche erstellen und die Content-Klassen zuordnen
Es müssen die erstellten Navigationtemplates Navigationsbereichen zugeteilt werden. Unter "Navigation ansehen - Projekt - Navigationsbereiche" werden Navigationsbereiche erstellt und die jeweilige Content-Klasse zugeordnet.Im linken Dialogfenster werden die erstellten Navigationsbereiche angezeigt, rechts die Content-Klassen. In diesem Beispiel muss ein Bereich für die Hauptnavigation erstellt werden. Diesem Bereich werden die Navigationtemplates Ebene Eins und Ebene Zwei zugeordnet.In den Einstellungen der Zuordnung gibt man an für welche Ebene das jeweilige Template wirken soll.

5. Navigationsbereiche im Mastertemplate angeben
Die Zusammenführung des Mastertemplates und der Navigationtemplates erfolgt über die Navigationsbereiche. Im Mastertemplate werden über Render Tags die Navigationsbereiche eingefügt. Diese basieren auf den Navigationtemplates, so dass sich das Bild eines kompletten HTML-Gerüstes ergibt.

<%!! Navigation:OutputArea(Str:Hauptnavigation) !!%>

6. Navigationsstruktur aufbauen
Unter "Navigation einsehen - Projekt - Navigationsstruktur" erstellt man die Navigationsstruktur. Das Dialogfenster hat zwei Seiten:
Links: navigationsrelevante Seiten
Rechts: Hilfsbaum, der entweder alle freien Seiten, oder bestehende Navigation anzeigt

Jedes Projekt kann nur eine Hauptseite haben, eine zweite Seite landet unter den freien Seiten. Weitere Seiten können an der Hauptseite erstellt werden.

Es gibt zwei Unterschiede beim Löschen von Seiten:
Seite löschen - Seite wird aus Navigation und Projekt gelöscht
Seitenindex löschen - Seite wird aus Navigation gelöscht, bleibt im Projekt bestehen

Donnerstag, 25. August 2011

JavaScript richtig integrieren

Eine Webapplikation ohne den Einsatz von JavaScript, ist mittlerweile kaum noch vorstellbar. JavaScript wird unter anderem für das Nachladen und Ersetzen von DOM-Elementen, sowie für Event-Verarbeitung und Animationen genutzt.

Es gibt unterschiedliche Möglichkeiten JavaScript zu integrieren, wobei sich einige im Bezug auf die resultierende Performance der Website besser eignen als andere. Dieser Artikel fasst einige Empfehlungen aus dem Buch High Performance Web Sites von Steve Souders zusammen und soll darüber hinaus noch Tipps für die JavaScript-Entwicklung geben.

Externe JavaScript-Dateien

Für die Einbindung von JavaScript gibt es zwei Wege. Zum einen direkt in der HTML-Datei innerhalb eines Script-Blocks und zum anderen in einer externen JavaScript-Datei referenziert durch ein Script-Tag. Da bei der Lösung mit externer Datei ein zusätzlicher HTTP-Request durchgeführt werden muss, lädt die Website mit integriertem Script-Block beim ersten Aufruf schneller. Der große Nachteil an Script-Blöcken ist jedoch, dass diese bei jedem Request mit übertragen werden müssen. Externe JavaScript-Dateien können hingegen vom Browser gecached werden.

Eine minifizierte JavaScript-Datei

Zusätzlich sollte beachtet werden, dass nur eine JavaScript-Datei eingebunden wird, da jede Datei durch einen eigenen Request geladen werden muss. Das Skript sollte außerdem minifiziert vorliegen, um die zu übertragenen Bytes zu minimieren. Gute Ergebnisse können beispielsweise mit UglifyJS, Google Closure Compiler oder YUI Compressor erreicht werden.

JavaScript ans Ende und erst nach dem Rendern ausführen

Die Browser rendern eine Seite von oben nach unten und laden dabei alle benötigten externen Ressourcen wie Stylesheets, Bilder und JavaScripts. Da nur eine begrenzte Anzahl an Requests parallel durchgeführt werden kann (RFC2616 empfiehlt zwei parallele Requests pro Domain, moderne Browser unterstützen jedoch mehr), sollten Scripts erst am Ende der Seite eingebettet werden, damit das initiale Rendern von sichtbaren Elementen nicht gebremst wird.

Außerdem werden JavaScripts direkt nach dem Laden evaluiert, wodurch der Rendervorgang pausiert wird. Daher empfiehlt es sich, die Ausführung des JavaScripts an ein Browser-Event zu binden, welches erst nach dem vollständigen Rendern ausgelöst wird. Im folgenden Beispiel vereinheitlicht jQuery die Events der unterschiedlichen Browser und ruft den Event-Handler auf, nachdem die Seite vollständig geladen wurde:

$(document).ready(function() {

// do stuff

});


In diesem Abschnitt wird ein eine anonyme Funktion als Event-Handler an das Ready-Event des HTML-Dokuments gebunden. Innerhalb dieser Funktion sollten alle weiteren JavaScript Funktionen gekapselt werden.

Entwicklungs- und Produktionsmodus

Eine einzelne, und obendrein noch minifizierte JavaScript-Datei, ist selbstverständlich durch keinen Entwickler wartbar und im Fehlerfall kann die Zeilennummer in der Browserkonsole keinen Aufschluss über die Fehlerquelle geben. Daher wird ein Mechanismus benötigt, welcher dafür Sorge trägt, dass im Produktionsmodus die minifizierte und im Entwicklungsmodus die normale JavaScript-Datei eingebunden werden. Die Zusammenfassung der einzelnen JavaScript-Dateien und die anschließende Minifizierung erfolgen dabei in einem Postprozess während des Deployments.

Je nach Servertechnologie und Template-Engine, könnte dies wie folgt aussehen: Eine Variable, deren Wert in der Serverkonfiguration oder durch eine Umgebungsvariable definiert wird, bestimmt, ob die basic.js oder die basic-min.js eingebunden wird.

<% if env === "development" %>

<script type="text/javascript"

src="/extensions/js/basic.js"></script>

<% else %>

<script type="text/javascript"

src="/extensions/js/basic-min.js"></script>

<% end %>


Dadurch wird im Entwicklungsmodus die nicht-minifizierte basic.js eingebunden. Somit hat der Entwickler bessere Möglichkeiten für das Debuggen des JavaScripts.

Aufteilung in mehrere Module

In einem Projekt, in dem viel JavaScript eingesetzt wird, wird eine Datei sehr schnell unübersichtlich und unwartbar. Erschwerend kommt hinzu, dass oftmals mehrere Personen an den Entwicklungsarbeiten beteiligt sind, wodurch Konflikte entstehen können.

Aus diesem Grund ist es sinnvoll, das gesamte JavaScript in mehrere Dateien zu modularisieren. Templateänderungen führen bei einigen Systemen dazu, dass HTML-Seiten neu publiziert werden müssen. Daher sollte das Hinzufügen von neuen JavaScript-Modulen nicht durch ergänzen von Script-Tags im Template erfolgen. Vielmehr sollte es möglich sein, dass Anpassungen zentral in einer JavaScript-Datei vorgenommen werden können.

Dies gelingt, indem die einzelnen Script-Tags für die Module durch ein JavaScript (basic.js) in die Seite geschrieben werden:

(function() {

var files = [

"/extensions/js/file1.js",

"/extensions/js/file2.js"

];


(function(scripts) {

for (var i = 0; i <scripts.length; ++i) {

scripts[i] = "<script type=\"text/javascript\"" +

"src=\"" + scripts[i] + "\"></script>";

}

document.write(scripts.join("\n"));

})(files);

})();


Dieses JavaScript iteriert über eine Liste der angegebenen URIs der JavaScript-Dateien und erstellt für jede ein Script-Tag. Diese werden anschließend gemeinsam in das HTML-Dokument geschrieben, bevor der Browser das Rendern des Dokuments beendet hat. Soll ein Modul hinzugefügt oder entfernt werden, muss lediglich die Liste files angepasst werden.

Ausliefern der JavaScript-Dateien

Einige Websites liefern JavaScript-Dateien wie andere statische Ressourcen (z. B. Bilder) mit einem Content Management System aus. Dies ist jedoch mit Sicherheit nicht die beste Möglichkeit, da JavaScript keinen Seiteninhalt, sondern vielmehr einen Bestandteil der Software darstellt. Außerdem müsste für jede Anpassung des JavaScripts jeweils eine Publikation angestoßen werden. Stattdessen sollte JavaScript direkt von einem Webserver ausgeliefert werden. nginx und andere Webserver bieten für diesen Fall den Einsatz eines Rewrites an, wodurch ein Pfadmuster gesondert behandelt werden kann. Anfragen, welche mit http://meineseite.de/extensions/js/ beginnen werden abgefangen und können mit Ressourcen unterhalb von /var/www/meineseite.de/extensions/js beantwortet werden. Alle anderen Requests werden wie gewohnt vom verwendeten System beantwortet.

server {

server_name meineseite.de;

listen 80;


location / {

rewrite /extensions/js/(.*)

/var/www/meineseite.de/extensions/js/$1;

}

}


Kleiner Aufwand für große Auswirkungen

Die in diesem Artikel beschriebenen Tipps können zum größten Teil mit wenig Mühe umgesetzt werden. Dennoch können diese erheblich auf die Performance Ihrer Website einzahlen. Weiterhin kann dadurch auch der Komfort für die Entwicklungsarbeiten erhöht werden.

Freitag, 12. August 2011

RubyGems selber bauen

RubyGems
ist das offizielle Software-Paketsystem für die Programmiersprache Ruby.

Ein RubyGem kann nicht nur Bibliotheken, sondern auch (Kommandozeilen-) Programme, Rails-Plugins etc. pp. enthalten. RubyGems erlaubt dem Benutzer die gleichzeitige Installation eines Gems in verschiedenen Versionen.

Ein Gem-Autor kann für sein Gem Abhängigkeiten zu anderen Gems definieren, die von RubyGems bei der Installation automatisch aufgelöst werden.

Ein RubyGem selber zu bauen und veröffentlichen ist gar nicht schwer. In diesem Artikel werde ich zeigen, wie man ein RubyGem von Grund auf selbst baut und sowohl im Quellcode als auch als Paket veröffentlicht.

Im Inneren eines Gems

Ein Gem besteht aus mindestens zwei Teilen

  1. dem eigentlichen Programmcode und
  2. einer Gemspec-Datei

Zweckmäßigerweise beinhaltet ein Gem darüber hinaus noch:

  • Tests
  • Dokumentation
  • Automatisierung für die Tests und das Bauen des Pakets

Hier ein Beispiel für die Datei-Struktur eines Gems aus dem RubyGems
Guide
:

$ tree freewill
freewill/
|-- bin/
| `-- freewill
|-- lib/
| `-- freewill.rb
|-- test/
| `-- test_freewill.rb
|-- README
|-- Rakefile
`-- freewill.gemspec
Das Gem-Kommando

Die RubyGems-Installation bring das Kommandozeilen-Programm gem mit. Mit diesem Programm werden Gems verwaltet (installiert, deinstalliert, aufgelistet usw.). Das gem-Programm dient auch dazu Gems aus dem Quellcode zu bauen:

   $ gem build hola.gemspec

Successfully built RubyGem
Name: hola
Version: 0.0.0
File: hola-0.0.0.gem

Mit dem Gem-Kommando build wird die Gem-Datei erzeugt. Hierfür liest das Gem-Programm die erforderlichen Angaben aus der Gemspec-Datei.

In dem Beispiel finden wir das fertige Gem in der Datei hola-0.0.0.gem. Mit

   gem install hola-0.0.0.gem

kann man das Gem lokal installieren.

RubyGems können auf im Netz veröffentlicht und mit dem Gem-Kommando
von remote installiert werden. Näheres dazu den Abschnitt RubyGems
Dot Org
unten.


Die Gemspec-Datei

Das Herz eines Gems ist die Gemspec-Datei.

Hier wieder ein Beispiel aus dem RubyGems Guide:

Gem::Specification.new do |s|
s.name = 'hola'
s.version = '0.0.0'
s.date = '2010-04-28'
s.summary = "Hola!"
s.description = "A simple hello world gem"
s.authors = ["Nick Quaranto"]
s.email = 'nick@quaran.to'
s.files = ["lib/hola.rb"]
s.homepage = 'http://rubygems.org/gems/hola'
end
Die Gemspec-Datei enthält allgemeine Informationen über das Gem und beschreibt dessen Inhalt. Eine vollständige Liste aller Attribute findet man hier.
Die meisten Attribute sind optional. Erforderlich sind date, name, summary und version.

Optional aber dennoch wichtig ist das Attribut files. Es entscheidet, welche Dateien ins Gem kommen -- und welche nicht. Das ist deshalb wichtig, weil wir sonst Dateien mit ins Gem packen (zum Beispiel Repository-Dateien .git, .svn etc.), die da nicht hineingehören. Wer seinen Gem-Quellcode in git verwaltet, kann einfach

    s.files = `git ls-files`.split($/)

schreiben. Das packt dann alle Dateien ins Gem, die im Repository gehalten werden.

Komplexere Gems basieren möglicherweise auf anderen Gems. Um bei der Installation des Gem automatisch dafür zu sorgen, dass Abhängigkeiten aufgelöst und benötigte Gems gleich mit installiert werden, muss man nur mit z.B.

   s.add_dependency('log4r', '>= 1.0.5')

entsprechende Abhängigkeiten definieren.

RubyGems Dot Org

Wir haben ein RubyGem gebaut, die Gemspec-Datei geschrieben und können nun das Gem bauen und lokal installieren. Jetzt möchten wir das Gem veröffentlichen, so dass jeder im Netz es mit

    $ gem list -r

finden und mit

    $ gem install 

installieren kann.

Alles was man dafür braucht ist ein kostenloses Konto auf rubygems.org. Dann kann man dort sein Gem veröffentlichen:

$ gem push <gem-datei>

Don't have an account yet? Create one at http://rubygems.org/sign_up
Email: ...
Password:
Pushing gem to https://rubygems.org...
Successfully registered gem: ...

Montag, 30. Mai 2011

PreExecute in RedDot

In dem Content Managment System RedDot ist es möglich Skriptcode vor dem Publizieren auszuführen. Dadurch wird z. B. je nach Elementeintrag der Inhalt des entsprechenden Containers gerendert. Es wird eine statische Seite mit ausgeführen Ergebnis publiziert.

Einstellungen für PreExecute in RedDot
Um auszuführenden Skriptcode vor dem Publizieren in ein Template zu schreiben, müssen ein paar Einstellungen in RedDot vorgenommen werden.
Unter SmartTree - Projekteinstellungen bearbeiten - Projekt - Allgemeine Einstellungen muss ein Haken gesetzt werden an "Active Template zulassen". Bei den "wählbaren Suffixe" können alle serverseitigen Sprachen wie PHP, JSP, ASP, mit Semikolon getrennt, angegeben werden.
Weiterhin müssen die Projektvarianten eingestellt werden. Unter "SmartTree - Projekteinstellungen bearbeiten - Projekt - Projektvarianten" kann eingestellt werden welcher Skriptcode in der Projektvariante verarbeitet werden kann. Unter dem DropDown bei "Active Template ausführen als" sind alle Skriptsprachen gelistet, die man bei den Projekteinstellungen angegeben hatte.

Beispiel
Ich möchte, dass je nach Loginstatus ein anderer Container gerendert wird.
Es besteht ein Optionsfeld "opt_websiteRole", in dem der Redakteuer angeben kann, ob diese Seite als eingeloggt oder ausgeloggt angezeigt werden soll. Außerdem sind im Template Inhaltscontainer "cont_loggedIn" und "cont_loggedOut", die je nach Optionseintrag ausgewählt werden sollen. In meiner Projektvariante habe ich ASP als auszuführendes Skript angegeben.

<!IoRangePreExecute>
<% role = <%opt:_webseiteRole%>
<% if role = "loggedIn" then %>
<%cont_loggedIn%>
<% elseif role = "loggedOut" then %>
<%cont_loggedOut%>
<%end if%>
<!/IoRangePreExecute>

<!IoRangePreExecute> <!/IoRangePreExecute>
markiert im Template den PreExecute-Bereich. Ohne diesen Tag wird der ASP Code nicht ausgeführt, sondern als HTML in die Seite gerendert. Mit
<% role = <%opt:_webseiteRole%>
wird die Variable "role" gesetzt mit dem Inhalt des Optionsfeldes "opt_websiteRole", dass der Redaketeur gepflegt hat. Durch die If-Abfrage wird ermittelt welcher Container genutzt werden soll.

Probleme
Beim Umgang mit PreEcxecute in RedDot muss die korrekte Ausführung von Sonderzeichen in den Skriptseiten sichergestellt sein. Dies kann der Dokumentation der jeweiligen Skriptsprache entommen werden.
Das Debuggen ist nur möglich, wenn das Löschen der temporär erstellten Datei für die Ausführung der PreExecute-Seite unterdrückt wird. Mehr Informationen zum Debuggen.
Weiterhin ist es nicht möglich RedDot Query Language (RQL) in PreExecute Blockmakierungen zu schreiben. Warum wird hier erklärt.
Abschließend ist zu sagen, dass umfangreiche Skripts das Ausliefern der Seite verzögern.

Montag, 11. April 2011

git - commits mit rebase zusammenfassen

Moin Moin,

das Versionskontrollsystem git bietet einen großen Umfang von Features um den Source Code eines Projektes professionell und sicher zu verwalten. Dieser Post behandelt einen Aspekt von git. Er richtet sich an Benutzer von git die mehr oder weniger täglich damit arbeiten und die Grundlagen bereits verinnerlicht haben.


Ziel dieses Posts


Bei der täglichen Arbeit, z.B. auf einem branch, kann es nützlich sein, mehrere commits zusammen zu fassen. Dafür bietet git den Befehl

$ git rebase -i

Wie git rebase funktioniert und angewendet wird, soll der Inhalt dieses Posts sein.


Testumgebung schaffen


Um die Funktionsweise von git rebase anschaulich erklären zu können, wird zuerst eine Testumgebung erstellt. Sprich, ein neuese git Repository wird erstellt.

$ mkdir gitrebase
$ cd gitrebase/
$ git init
Initialized empty Git repository in /tmp/gitrebase/.git/
$ touch file_with_content
$ git add .
$ git commit -m 'create testfile'
[master (root-commit) d1beb13] create testfile
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file_with_content

Die Schritte sind ziemlich straight forward. Es wird ein Verzeichnis erstellt, in welchem eine Datei für Testzwecke erstellt wird. Danach wird diese Datei dem Repository Index hinzugefügt und mit dem commit Befehl wird die Änderung im Repository aufgezeichnet. Der Befehl git log zeigt die Änderungen am Repository auf:

$ git log
commit d1beb13cfd94c134544a1e068fac24b2b6bd56b5
Author: Andreas Wenk <andreas.wenk@sinnerschrader.de>
Date:   Tue Apr 5 17:17:57 2011

    create testfile

Im nächsten Schritt wird die Datei einige Male geändert und die Änderungen werden im Log aufgezeichnet. Das Vorgehen folgt dabei diesem Muster:

$ vi file_with_content
$ git commit -a -m 'first change'
[master 7da887b] first change
 1 files changed, 1 insertions(+), 0 deletions(-)

Für unsere Betrachtungen genügen ein paar commits. Das Log sieht dann in etwa so aus:

$ git log
commit 847f8c7bf98c1e907b5c8ab1f08f3a9b38b2b40b
Author: Andreas Wenk <andreas.wenk@sinnerschrader.de>
Date:   Tue Apr 5 17:29:07 2011

    third change

commit 240da4170ad23f736c365d1c25435654e31a54c3
Author: Andreas Wenk <andreas.wenk@sinnerschrader.de>
Date:   Tue Apr 5 17:28:49 2011

    second change

commit 7da887ba6fb01ca3ac3f6a1928a45bef3c49e7d4
Author: Andreas Wenk <andreas.wenk@sinnerschrader.de>
Date:   Tue Apr 5 17:26:19 2011

    first change

commit d1beb13cfd94c134544a1e068fac24b2b6bd56b5
Author: Andreas Wenk <andreas.wenk@sinnerschrader.de>
Date:   Tue Apr 5 17:17:57 2011

    create testfile

Damit ist die Testumgebung erstellt.


Aus drei mach eins

In einem großen Projekt mit mehreren Entwicklern sollten unterschiedliche Aufgaben und Bereiche in branches bearbeitet und später, z.B. im master branch, zusammengeführt werden. Von diesem können dann Software Versionen erstellt werden und das Deployment, basierend auf diesen Versionen, gestartet werden.

Eine rule of thumb bei der Entwicklungsarbeit ist, viele kleine commits zu erstellen. Das erleichtert  das Nachvollziehen des Entwicklungsprozess, da der Entwickler meistens intuitiv zusammengehörige Schritte als commits erstellt. Ausserdem ist diese Vorgehensweise bei der Fehlersuche und einem notwendigen "zurückrollen" extrem hilfreich.

Viele commits bedeuten allerdings auch eine sehr große commit history. Um einer möglichen Flut vorzubeugen, kommt also git rebase ins Spiel um mehrere commits zusammen zu fassen. Die Änderungen am Dokument im hier besprochenen Beispiel sind alle ziemlich ähnlich und könnten somit in einen einzigen commit zusammengefasst werden.

Bei der konkreten Anwendung von git rebase wird ein Startpunkt erwartet, nach (!) welchem die commits zusammengefasst werden sollen. Die Option -i wird genutzt, um git rebase im interaktiven Modus auszuführen. In unserem Beispiel sollen alle commits nach dem initialen commit mit dem SHA-1 Hash d1beb13cfd94 zusammengefasst werden. Deshalb ist der Aufruf folgender:

$ git rebase -i d1beb13cfd94c13

Daraufhin erhält man folgende Ausgabe:

pick 7da887b first change
pick 240da41 second change
pick 847f8c7 third change

# Rebase d1beb13..847f8c7 onto d1beb13
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Es besteht auch die Möglichkeit einen Bereich anzugeben:

$ git rebase -i d1beb13cfd94c13 240da4170ad23f7

pick 7da887b first change
pick 240da41 second change

# Rebase d1beb13..240da41 onto d1beb13
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Hier würden also nur die beiden nächsten Commits betrachtet werden.

git gibt dem Benutzer im interaktiven Modus eine Erklärung, welche Commands genutzt werden können frei Haus mit. Übrigens wird diese Ansicht in dem für git definierten Editor geöffnet. Die Einstellung hierfür wird in $HOME/.gitconfig getroffen:

[core]
    editor = vim

Standardmäßig ist der Command für die einzelnen commits bei der Ausführung von rebase pick. Das bedeutet, die so markierten commits werden ohne Änderung übernommen. Die getroffenen Einstellungen sind also Anweisungen, was während der Ausführung von git rebase mit den einzelnen commits geschehen soll. Das Ziel hier soll sein, alle commit messages beizubehalten, aber alle commits in einen einzigen zusammen zu fassen, nämlich im commit 7da887ba6fb0. Die Anweisungen sehen also so aus:

p 7da887b first change
s 240da41 second change
s 847f8c7 third change

Bei einer großen Liste von commits kann festgelegt werden, dass irgend ein commit unberührt bleiben soll (p oder pick) oder einfach mit dem vorherigen verschmolzen werden soll ohne commit message (f oder fixup) oder die commit message während des rebase Vorgangs geändert werden soll (e oder edit) usw. . Allerdings muss in der Liste zumindest ein commit angegeben sein, mit welchem die anderen commits verschmolzen werden sollen. Ist dies nicht der Fall beschwert sich git mit:

Cannot 'squash' without a previous commit

Nach dem Speichern der Änderungen im Editor, wird der rebase Vorgang automatisch angestossen und wartet mit einer neuen Ausgabe auf:

# This is a combination of 3 commits.
# The first commit's message is:
first change

# This is the 2nd commit message:

second change

# This is the 3rd commit message:

third change

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   file_with_content

Sinnvoll ist es an dieser Stelle, die einzelnen commit messages einfach bei zu behalten - ggf. mit Änderungen - um später im Log besser sehen zu können, was genau getan wurde. Wenn die Ausgabe im Editior gespeichert bzw. geschlossen wird, bestätigt git die erfolgreiche rebase Operation:

[detached HEAD e7edfb6] first change
 1 files changed, 5 insertions(+), 0 deletions(-)
Successfully rebased and updated refs/heads/master.

Schliesslich zeigt uns git log das Ergebnis:

$ git log
commit e7edfb61e1feed468a352207cfff48feb7f49903
Author: Andreas Wenk <andreas.wenk@sinnerschrader.de>
Date:   Tue Apr 5 17:26:19 2011

    first change
   
    second change
   
    third change

commit d1beb13cfd94c134544a1e068fac24b2b6bd56b5
Author: Andreas Wenk <andreas.wenk@sinnerschrader.de>
Date:   Tue Apr 5 17:17:57 2011

    create testfile

Interessant ist der zusammengefasste commit: der SHA-1 Wert des commit hat sich (natürlich) geändert aber die EInträge für Datum und Autor sind immer noch die gleichen wie beim ursprünglichen commit 7da887ba6fb01ca3a.

Ziel erreicht

Die hier gezeigte Nutzung von git rebase ist eine mögliche Nutzung von rebase. Es gibt noch einige weitere Nutzungsmöglichgkeiten. Dem Leser sei an dieser Stelle das übliche man git-reabse ans Herz gelegt - getreu dem Motto: RTFM.

Viel Spass und Erfolg bei der Nutzung von git.

Andy Wenk

Montag, 4. April 2011

Initial Commit Message

Hallo und willkommen,

Du bist auf dem Blog des Technik Team 7 gelandet. Wir sind eines der Technik Teams bei SinnerSchrader, das verantwortlich für die Realisierung von unterschiedlichen Projekten zeichnet.

In diesem Blog werden wir von Zeit zu Zeit über tägliche Aufgaben, Probleme und Lösungsansätze schreiben. Dabei werden Themen aus den Bereichen Ruby / Rails, OpenText (aka RedDot), JavaScript, GIT, HTML, CSS und anderen behandelt.

Lange Rede und viel Sinn - es wird technisch. Viel Spass!

Team 7