Word processors are overrated

Word processors are overrated

(This is the transcript of my second submission for Hacker Public Radio.)

Word processors are overrated. Too often they are used instead of better alternatives. For example: to write a report, to describe a workflow or a vision, a lot of people just grab Microsoft Word. Which is a bad idea. Should you use LibreOffice Writer then? OpenOffice? Maybe Google docs? They are not much better.

If the focus of your text is on its content, if the structure of your text is important, if the way the text is laid out is less important than the consistency of the lay-out, or if you want to collaborate with other people, you should not use a typical mainstream word processor.

Problems with mainstream word processors

Page layout errors

There are some major issues, and the first one is that you will probably end up with page layout errors.

If the way your text is laid out is unimportant, you should focus on the actual information, not on making your text looking good. With a mainstream word processor, you often end up with formatting inconsistencies: incorrectly indented list bullets, wrong fonts in a text after a copy-paste-operation, or inconsistencies in the formatting of section titles. Especially when you have to collaborate with other people, the result will be ugly. And if some contributors use a different word processor than you are using, all bets are loose.

When multiple people work together on a document using a mainstream word processor, and if those people don't really care about page layout, they will probably create an ugly document. And that's a shame. It does not have to be this way

The underestimated learning curve

Another problem with typical word processors, is that the learning curve is underestimated. Experienced users of a word processor will argue that you can avoid all those layout problems, if you use the software the right way. But that means that all the people working with you on the same document, should know how to use your word processor. Some of them might have to invest in training. And even then, it is easy to make mistakes.

A typical word processor has a WYSYWYG interface, which seems to be very easy to use. Even a 3 year old child can produce a text. But any advanced user of a certain word processor, will agree that there are many ways to use it in a wrong way.

It's not beatiful

The last problem I want to cover might be personal, but many texts that are created with software like Word are not to say beautiful. It is not difficult to produce ugly texts with Word alike systems, and it happens a lot. Some users need to be protected against the Comic Sanses of this world.

Expect more

If you don't care about your page layout, you should not spend time in laying out your pages.

You have a computer. Your computer should take care on the looks of your document, so that you can concentrate on what actually matters: the content.

So we have to look for are alternatives for Word. Not LibreOffice, not Google Docs not Abiword; they have the same problems. We should be looking for something completely different. Like for exampel LaTeX.

LaTeX

LaTeX is very good if you need mathematical formulas in your text. If you have to write a mathematical text (on your own or together with someone else), you obviously choose LaTeX anyway, because there is just nothing else. But if your text is not about mathematics, and you have to work with someone else, LaTeX is usually not an option. The majority of people are easily scared, because a LaTeX source document is rather hard to read.

Plain text

What else can we use? Plain text? It is an option as well, but the possibilities to format text are really limited. Plain text is good for quickly sending an e-mail, but as soon as you need some formatting, it just won't work.

HTML

Maybe HTML is an option. HTML is a whole lot richer than plain text. But also it comes with some disadvantages. The source code is still quite difficult to read. It requires some work to get a nice printout (without e.g. headers and footers from a browsers). And I personally find the HTML tags annoying to type.

Markdown [1]

So now I come to the point I want to make: Markdown is a great alternative for writing texts. I won't pretend that it is the perfect solution, but it has some nice features, it has a decent user base, and the learning curve is quite low.

A Markdown file is a plain text document. Meta-information about the structure is added using symbols like the asterisk or the hash symbol. This way, the source text stays very readable, and you can easily see the structure.

Text documents can be opened by virtually everyone.

And because the possibilities to structure the text are limited, the possibilities to make mistakes are limited as well.

A Markdown document is a text file, but there are a lot of tools that render Markdown to a formatted text. A lot of blogs and forums accept Markdown as input format. And so does Github. If you work on a text with someone who understands the workings of git, Github renders your text, and you can easily look up the history of your files.

Next to those web applications, there are also some native applications, which show a live preview of the text you are typing, like e.g. ReText for Linux, and Markdownpad for Windows.

If you are comfortable using the command line, then you can use pandoc to convert your markdown documents to LaTeX (for pretty output), to Word (for conservative readers), to HTML and some Wiki formats. There is also a command line tool (which is called mcider) that converts a Markdown document to a html slideshow, but at this moment you will probably have to do some hacking to finetune the layout of your resulting slides.

Limitations of markdown

Markdown is not ideal, it has some limitations. Like for example: There is no clear Markdown standard. Putting tables or images in your document, is not always supported. Support for footnotes is often non-existent. And so on.

On that level, I think that dokuwiki does a better job. But unfortunately: the dokuwiki syntax is less used than Markdown. In fact, I don't think it is used anywhere except on dokuwiki itself.

Another disadvantage is that most people do not know Markdown. And even worse: Windows doesn't know Markdown (or doesn't want to). As said, markdown documents just plain text files, but they typically get the .md-extension. And if you try to open such a document in Windows, then you get the message that the file format is not recognized. So if you work on a document with a Windows user who does not know the difference between plain text and binary file formats, you probably better use the .txt-extension for your file name. And if you do not use Windows yourself, make sure that your Windows colleagues get a text file with Windows line endings, otherwise notepad is confused :-)

Comments

This text is also available on github. In markdown format, of course. :-) You can post comments (issues, or even pull requests) over there.

[1] (update 2014-10-22) the new cool replacement for Markdown is called CommonMark

Cyanogenmod on the Samsung Galaxy S wifi 5 (Samsung Galaxy Player 5)

Cyanogenmod on the Samsung Galaxy S wifi 5 (Samsung Galaxy Player 5)

I had the chance to buy a Samsung Galaxy S wifi 5: It's not a phone, it 's not a tablet, it's something in between. I got a special price ;-) so I could buy it just to mess with it. If I break it, so be it.

Of course, I wanted to replace the default firmware by a clean system: CyanogenMod. Unfortunately, there is no official CyanogenMod version for this device. I thought there was, but I was confused. There are a lot of devices called 'Samsung Galaxy S', and they all use incompatible hardware. So the installation was a little harder than I expected.

Replace the recovery image

I found this very useful walkthrough to replace the recovery image:

I did install the Samsung USB drivers for mobile phones on beforehand, I am not sure this was necessary, but it is likely it is. (Remember: I am clueless, as always.)

If you went throught the steps, you can boot into the ClockWorkMod recovery by pressing the volume-up button while turning on the device.

Download and flash the images for CM9

I found a lot of ROMS for the Samsung Galaxy Player on theunlockr.com. I picked 'CM9 for Galaxy Player 5.0 ROM' (because I am not in the USA), and I followed the links to

Download the stable version. Which I did not notice, because the link was in red. Probably to draw the attention. Which did not work for me :-). I first installed an old version, so I lost a lot of time.

The extra's seem to be useful as well. I installed all of them. For the GApps, I took the most recent for ics (gapps-ics-20120429-signed.zip), and for the 'home fix for INTL users', I took version 2.

I put all the zips on the sd-card. I rebooted into recovery, created a backup, and installed all images. (Cyanogenmod first, then all the rest.) Factory reset, clear cache, reboot, done.

What does not work:

  • Camera
  • Transferring files via USB from/to the sd-card.
  • The hardware button in the middle under the screen doesn't seem to do anything.
  • Installing apps on the SD-card.

Other versions?

There is a thread about CM10 on the xda-developers forum, but this does not work, and development on this version seems to have stopped.

There is a ROM for CM7 as well. Maybe with a working camera. I still have to try this out.

About the SD-card problems

There are some issues with the SD-card, but I im unsure which ones.

Some apps (like e.g. Mustard) want to install on the SD-card. So the installation from the market fails.

If you remount / as rw, using the terminal: `` mount -o rw,remount /``

installation works, and the apps run. That is, until you reboot the device. Then the app is 'not found' when you try to run it.

Another problem: if you now open the 'app management' screen, the system crashes after a few seconds.

To install Mustard, I did the following:

  • remount / as rw
  • install mustard
  • open app management, show the apps on SD-card, and uncheck mustard. I had to be very quick, before the crash.

Tekstverwerkers zijn overrated

Tekstverwerkers zijn overrated

Tekstverwerkers zijn overrated. Ze worden te vaak gebruikt voor zaken waarvoor betere alternatieven bestaan. Typisch voorbeeld: om een verslag te typen, om een manier van werken te documenteren, of om een visie op een bepaald onderwerp uit de doeken te doen, grijpen veel mensen naar Microsoft Word. Slecht idee. LibreOffice Writer dan maar? Al even erg.

Als je een tekst wilt maken waarbij

  • de inhoud belangrijk is
  • de structuur belangrijk is
  • de manier waarop hij opgemaakt minder belangrijk is dan dat hij consequent opgemaakt is
  • je makkelijk moet kunnen samenwerken met andere personen

dan gebruik je beter iets anders dan een typische tekstverwerker.

Waar slaan de tekstverwerkers de bal mis?

Ik zie drie grote problemen bij de meest gebruikte tekstverwerkers.

Fouten in de opmaak

Als de manier waarop je tekst opgemaakt is niet belangrijk is, dan wil je weinig tijd spenderen aan het opmaken van je tekst. Met een typische tekstverwerker resulteert zoiets in een lelijke tekst. 'Bullets' in een geordende lijst die verkeerd geïndenteerd zijn. Lettertypes die niet meer kloppen nadat je iets uit een andere tekst hebt gekopieerd. De manier waarop titeltjes worden weergegeven is inconsequent, omdat contributor b dat op een andere manier doet dan contributor a. En de ene dan nog eens een andere tekstverwerker gebruiken dan de andere, dan is het hek vaak helemaal van de dam.

Als je met meerdere personen aan een document werkt waarvan de opmaak niet belangrijk is, en je gebruikt hiervoor een klassieke tekstverwerker, dan resulteert dat vaak in een lelijk document. En dat is jammer.

De onderschatte leercurve

Ervaren gebruikers van een tekstverwerker zullen argumenteren dat je bovenstaande problemen kunt vermijden, als je de tekstverwerker maar op de juiste manier gebruikt. Maar dat wil zeggen dat de mensen waarmee je samenwerkt, de tekstverwerker ook moeten beheersen. Een fout is immers snel gemaakt. Als je met iemand moet samenwerken aan een tekst, dan is het jammer dat je eerst tijd moet investeren in het aanleren van het correct gebruik van een tekstverwerker. Of dat je achteraf opmaakfouten moet verbeteren. Het schrijven van de tekst moet mogelijk zijn met supereenvoudige technologie, zodat je zonder veel opleiding aan de slag kunt.

Met hun WYSYWYG-interfaces geven moderne tekstverwerkers de indruk om makkelijk in gebruik te zijn. Een kind van 3 kan een tekst maken. Maar eender wie die zich een geavanceerde gebruiker van een bepaalde tekstverwerker noemt, zal beamen dat er erg veel manieren zijn om die tekstverwerker verkeerd te gebruiken.

Mooi is het niet

Het is misschien persoonlijk, maar heel wat teksten die in Word gemaakt zijn, zijn niet mooi. Het is niet moeilijk om lelijke teksten te maken met Word-achtigen, en het gebeurt massaal. Sommige gebruikers moet je beschermen tegen de comic-sansen van deze wereld.

Alternatieven

Er zijn alternatieven. Waarmee ik niet LibreOffice, Google Docs of Abiword bedoel, want die zijn in hetzelfde bedje ziek als Microsoft Word. We gaan op zoek naar iets volledig anders.

LaTeX

LaTeX is erg goed als je wiskunde nodig hebt in je tekst. Als je met iemand anders samenwerkt aan een wiskundige tekst, dan kiezen jullie sowieso voor LaTeX, want er is gewoon niets anders. Maar als je tekst niet over wiskunde gaat, en je moet samenwerken met iemand anders, dan is LaTeX meestal geen optie. De meerderheid van de mensen wordt erdoor afgeschrikt, omdat de broncode er erg ingewikkeld uitziet.

Plain text

Plain text is ook een optie, maar de mogelijkheden om tekst op te maken zijn echt te beperkt. Plain text is goed om gauw een mailtje te sturen, maar van zodra je iets van opmaak nodig hebt, volstaat het niet meer.

HTML

HTML is een heel pak rijker dan plain text. Maar er blijven wel wat nadelen aan gekoppeld. De broncode is nog altijd vrij moeilijk leesbaar. Het is ook niet zo vanzelfsprekend om als mooi document af te drukken. En ik vind de HTML-tags verveldend om in te tikken. Al zal dat persoonlijk zijn.

Markdown [1]

En nu kom ik bij de kern van mijn betoog: Markdown. Je zult me niet horen vertellen dat markdown perfect is, maar het is tamelijk bekend (alles is relatief uiteraard), en de leercurve is laag.

Een markdownbestand bestaat uit platte tekst, waarbij de meta-informatie over de structuur aangeduid wordt met symbolen zoals de asterisk of het hekje. Op die manier blijft de brontekst erg leesbaar, en zie je er gemakkelijk de structuur in. Een tekstdocument aanpassen kan vrijwel iedereen, en omdat de mogelijkheden om structuur te brengen beperkt zijn, kun je er ook weinig fouten mee maken.

Tekst is natuurlijk maar tekst, maar markdown kan op veel manieren als bron gebruikt worden van een echte afgewerkte tekst.

Zo zijn er een aantal blogs en webforums die markdown aanvaarden als invoerformaat. En ook github kent markdown: als de mensen waarmee je aan een tekst samenwerkt met git overweg kunnen, dan laat github het resultaat mooi geformatteerd zien. Bovendien zijn markdownteksten erg interessant voor versiecontrolesystemen, net omdat ze plain text zijn.

Dat gebeurt allemaal op het web, maar je kunt ook ooffline teksteditors installeren, die je een live preview geven van de markdown die je aan het tikken bent (ReText, Markdownpad).

En kun je overweg met de command line, dan is er pandoc om je markdowndocumenten om te zetten naar LaTeX (als je mooie output wilt), Word (voor conservatieve lezers), HTML en sommige wikiformaten.

Beperkingen van markdown

Is markdown het ideale alternatief voor office? Nee, want markdown heeft beperkingen. En er is geen duidelijke standaard. Afbeeldingen in je tekst zetten, is vaak niet ondersteund. Net als tabellen. Bovendien mist het op dit moment nog wel wat features, zoals voetnoten.

Op dat vlak scoort dokuwiki mijns inziens een pak beter. Maar helaas: dokuwiki wordt minder gebruikt dan markdown. Je hebt er geen standalone editors voor, en github herkent het ook niet. Dus toch maar markdown. Of misschien wordt het tijd om dokuwiki-support toe te voegen aan pandoc of retext. :)

Een ander nadeel is dat de meeste mensen markdown nog niet kennen. En erger nog: Windows kent het ook niet (of wil het niet kennen). Zoals gezegd zijn markdowndocumenten gewoon plain text files, typisch met extensie .md. Maar als als je probeert zo'n document te openen onder Windows, dan krijg je het bericht dat het bestandsformaat niet herkend wordt. Als je samenwerkt aan een document met een Windowsuser die het verschil niet kent tussen plain text en binary, geef je je markdownbestanden best een .txt-extensie. En als je zelf geen Windows gebruikt, zorg er dan voor dat je Windows-collega een versie krijgt met Windows-line-endings, want anders weet notepad niet wat er gebeurt :-)

Demo

Deze tekst was oorsrponkelijk ook geschreven in markdown. Ter illustratie geef ik je de brontekst mee, en de pdf die ik maakte via een conversie naar LaTeX.

[1] Markdown is op dit moment een beetje passé. De opvolger heet commonmark.

HPR episode: About git

HPR episode: About git

This is more or less the transcript of my HPR submission about git, a version control system.

I have used other version control systems in the past: cvs (long ago) and subversion, but today git is by far my favourite. This is why:

  • You can commit new revisions when your internet connection is down.
  • You can easily prevent that just any developer can commit to the master branch or to release branches.
  • You can try out experimental things locally, committing changes, without having to create a branch in a central repository.
  • Creating feature branches, and switching between branches is easy, even without a working internet connection.
  • Git has way more features than subversion or cvs.
  • Linus says: 'If you are using subversion, you are stupid and ugly.' ;-)

Yet there are problems when moving from subversion to git

  • Git works in a very different way than subversion. It requires some effort to fully understand what it does.
  • The easiest way to work with git (imo) is with the command line. For some users, this is a drawback.

I moved a project from subversion and trac to git and redmine, about half a year ago. I could use an existing server with git, gitolite and redmine, so I didn't have to bother about setting those things up. The conversion from subversion to git went pretty easy. We started using git, and it kind of worked, although we were not quite sure why it did. I guess it is a common problem for new git users.

I aim to explain the things that I wanted to know when starting with git. Now there are some oddities in our repository, which could probably be avoided if I only knew what I was doing.

Two disclaimers:

  • I only discuss the concepts of using git. You will not find specific git commands here. This is intentional. Possibly I will publish a more practical follow-up later on.
  • I make abstraction of some of the technical stuff, just to restrict the length of this introduction.

Git is distributed

Git is distributed. Theoretically there is no central code repository, and every developer has his own local copy of the entire repository. If you want a copy for yourself, you can just clone an existing one.

Your own repository typically contains references to remote repositories. At the moment you clone a repository, a reference to the original is kept, which is usually called origin.

Commits

A git repository is basically nothing more than a targeted acyclic graph of commits. A commit represents a specific revision of your source code. Each commit is determined by a SHA-1 hash, which is a unique checksum.

The hash is 40 characters long, which is tedious to type. If there is no ambiguity, you can refer to a commit using just the first few characters of the hash. Usually 5 or 6 characters are sufficient.

Each commit (except for the initial one) has a reference to one or two parents. If you pick a random commit, you can find its entire history following the parent links to the very beginning.

In the simplest case, a git repository is just a sequence of commits, where each commit has at most one parent and most one child. The history is so to speak one straight line:

C1 <- C2 <- C3 <- C4

``C1`` is the initial commit. In order not to overload the diagrams, I will from now on omit the "arrows" of the parent relation (``<-``). By convention I put the parent on the left, and the children on the right.

Generally, it is not the case that all the revisions in a git repository nicely follow one after the other. In the case of branching a commit typically has several children. In a merge operation a commit can have two parents. But more on that later.

Example of a more complex repository:

C1 -- C2 -- C3 -- C4 -- C5 -- D6 -- D7
       |                       |
       +--- D3 -- D4 -- D5 ----+
       |
       |           +--- F5 -- F6
       |           |
       +--- E3 -- E4--- E5

While programming, there is always one commit checked out. This commit is the HEAD of your repository. The current source code (called working copy) corresponds to the code of HEAD, with a certain modifications you made. Files can be added, deleted, or changed.

If you want to commit a new revision of your code, you need to inform git about the changes in the working copy that should be included in the new commit. Git knows how you version of the code differs from the code in the last commit, but it does not include by default all changes in a new commit. If you want a change to be included in the next commit, you should explicitly add it to the 'index': this is called 'staging'. All staged changes will be part of the next commit. Changes you did not stage, stay as they are in your working copy, but they are kept out of the commit.

               HEAD
                ↓
C1 - C2 - C3 - C4 - staged changes - working copy

... after committing:

                       HEAD
                         ↓
C1 -- C2 -- C3 -- C4 -- C5 -- working copy

When a new revision of your code is committed, this commit becomes the new HEAD of the repository.

When git repositories talk to each other, commits are moved from one repository to the other. After some time you end up with a lot of commits, and it becomes difficult to find your way. Branches are the answer to this problem.

Branches

You might know the concept of branches from other source control systems. In the easiest case, your code history is one straight line, one commit after the other, from the initial commit to HEAD. This way, there is only one branch in your source repository.

It is possible however, that at some point, development is done in parallel. After a certain commit, say C developer A adds new commits A1, A2, A3, while developer B ignores these commits, and adds other comitts B1, B2 and B3 after C. Now there are two branches, in which the code is diverging. These branches could or could not be merged again, at some point in the future, but more about this later.

X1 -- X2 -- X3 -- C -- A1 -- A2 -- A3
                  |
                  +--- B1 -- B2 -- B3

Technically, a git branch is nothing more than a pointer to a particular commit in your repository. Just like HEAD, as a matter of fact. A branch is pointing to its most recent commit.

If you take two random branches in your repository, you can always find a commit where they diverged. You start from the commits the branches are pointing to, and then keep follwing the parent links. At some point, you will find a common ancester, and this is the commit you are looking for.

Just as with any other version control system, there is typically one branch 'checked out'. This is the branch you are working on. HEAD is pointing to the same commit as the checked out branch, and when commiting a new revision, the branch pointer will move along with HEAD to this new commit.

Adding new branches is very easy, you just add a new pointer to the repository.

You can name branches as you like, but typically there is one branch called master. master is pointing to the 'mainline', the most up-to-date development revision.

Example:

                master
                   ↓     HEAD
C1 -- C2 -- C3 -- C4     branch2
       |                 ↓
       +--- D3 -- D4 -- D5
       |
       |           +--- F5 -- F6
       |           |           ↑
       +--- E3 -- E4         branch4
                   ↑
                 branch3

Branches in your own copy of the repository, are called local branches. Git is also aware of branches in remote repositories: remote branches. When you 'fetch' a remote branch, git downloads all necessary commits to your repository, and puts a pointer to the commit corresponding with the remote branch.

For example:

(remote repo: origin)

                master
                   ↓
C1 -- C2 -- C3 -- C4          branch2
       |                       ↓
       +--- D3 -- D4 -- D5 -- D6
       |
       |           +--- F5 -- F6
       |           |           ↑
       +--- E3 -- E4         branch4
                   ↑
                 branch3

(local repo)

                       HEAD
                       master
                         ↓
C1 -- C2 -- C3 -- C4 -- C5

After fetching ``origin/branch4``:

(local repo)

                      HEAD
                      master
                         ↓
C1 -- C2 -- C3 -- C4 -- C5
       |
       +--- E3 -- F4 -- F5 -- F6
                               ↑
                            origin/branch4

You can not directly add commits to a remote branch. Typically you first fetch the remote branch, you link it to a local branch, and you commit new revisions to the local branch. Such a local branch that is linked to a remote branch, is called a '(remote) tracking branch'.

If you are working in a tracking branch, git knows where the original is. This makes it easy to download the latest commits in the remote branch, and git will inform you about the differences between the remote branch and your associated tracking branch.

Back to the previous example. If you have a local branch ``branch4`` checked out, which is set it up to track ``origin/branch4``, the situation is as follows:

                     master
                         ↓
C1 -- C2 -- C3 -- C4 -- C5
       |
       +--- E3 -- F4 -- F5 -- F6
                              ↑
                           origin/branch4
                           branch4
                           HEAD

A tracking branch behaves just like an ordinary local branches. If it is checked out, and you create a new commit, the branch will move along with HEAD.

                      master
                         ↓                 HEAD
C1 -- C2 -- C3 -- C4 -- C5                 branch4
       |                                   ↓
       +--- E3 -- F4 -- F5 -- F6 -- F7 -- F8
                             ↑
                          origin/branch4

Merging

Suppose you have 2 branches, let's say A and B, which originate from a common ancester commit C.

Merging branch B into branch A means incorporating into A all changes between C and B.

In the simplest case, branch A itself is an ancestor of branch B. So when working on branch A, you created a new branch B, to which you added some commits. (The common ancester C is just the last commit of branch A.)

In this case, git will just move the pointer A, so that it points to the same commit as B. This kind of merge is called a "fast forward merge"; an important concept in the world of git. A fast forward merge is a merge operation which comes down to moving the pointer of the branch into whom you are merging.

                HEAD
                   A
                   ↓
C1 -- C2 -- C3 -- C4
                   |
                   +--- D5 -- D6
                               ↑
                               B

After merge of ``B`` into ``A``:

C1 -- C2 -- C3 -- C4
                   |
                   +--- D5 -- D6
                               ↑
                               A
                               B
                            HEAD

(Note that this graph is isomorph to a straight line)

A fast forward merge is not always possible. If A and B diverged from their common ancester C, simply moving a pointer does not work.

                      HEAD
                         A
                         ↓
C1 -- C2 -- C3 -- C4 -- C5
                   |
                   +--- D5 -- D6
                               ↑
                               B

In this case, when merging B into A, the changes between the common ancestor and branch to B are applied to branch A. If this doesn't cause any trouble (lucky you), git will create a new commit on A, containing the changes in B.

The example below shows how ``B`` will be merged merged into ``A``.

                                HEAD
                                   A
                                   ↓
C1 -- C2 -- C3 -- C4 -- C5 ------ C6
                   |               |
                   +--- D5 -- D6 --+
                               ↑
                               B

If both branches modify the same part of your code, you cannot just apply the changes from one branch to the other. If this happens, git marks the conflicts, and does not commit the result of the merge operation. You first have to resolve the conflicts, before you commit.

That's it about merging. Merging comes down to integrating changes frome one branch into another branch in the same repository. Now we will consider push and pull operations, which is about moving changes between repositories.

Pull and push

Suppose you have checked out a remote tracking branch, and you want to apply the latest commits of the remote branch to your tracking branch locally. This is called a pull operation. Git fetches the current state of the remote branch, together with all necessary commits, and merges it into the tracking branch in your repository.

For example: When ``remote/branch1`` pointed to ``C3``, you made a remote tracking branch. Since then, commit ``C4`` was added to the remote repository, while you added ``C4'``, ``C5'`` and ``C6'`` to your local repository.

(origin)
                 branch1
                   ↓
C1 -- C2 -- C3 -- C4

(Local)

                               HEAD
                               branch1 (trackt remote/branch1)
                                ↓
C1 -- C2 -- C3 -- C4' -- C5' -- C6'
             ↑
          remote/branch1

After fetching of ``remote/branch1``, see the local repo looks as follows:

                               HEAD
                               branch1
                                ↓
C1 -- C2 -- C3 -- C4' -- C5' -- C6 '
             |
             + --- C4
                   ↑
              remote/branch1

After merging:

                                     HEAD
                                   branch1
                                       ↓
C1 -- C2 -- C3 -- C4' -- C5' -- C6' -- C7'
             |                          |
             +--- C4 -------------------+
                   ↑
              remote/branch1

As with any other merge, it could be that this causes conflicts, which you'll have to resolve.

Conversely you can push the commits in a local branch to a branch in a remote repository. This can be either to a new remote branch as to an existing remote branch. Git will upload the most recent commit of the local branch, together with all necessary ancestor commits to link it to the existing remote commits. This way you create a new remote branch; if there was no existing branch, you are done.

If the remote branch you were pushing to already existed, the newly created branch will be merged into the existing branch. But in most configurations this only works if this merge operation is a fast forward merge, which is the case if no commits were added to the remote branch after your last pull. If a fast forward merge is not possible, you will get an error message.

To resolve this, you first fetch the remote branch, and merge it locally with your local tracking branch. (Which is in fact a pull operation.) This operation results in a new local commit with the latest commit from the remote repository as one of its parents. So if you push your branch again, it will be fast forward merged into the remote repository without a problem.

Rebasing

When branches diverge, merging is one way to get them together again. A typical use case of merging, is the resynchronisation of the same branches in different repositories, as we've encountered in the discussion of push and pull operations.

There is however another way to integrate changes from one branch into another: rebasing.

Suppose you have two branches, let's say A and B, with a common ancester C. Rebasing B onto A can be seen as taking branch B from the point where it diverged from A, tearing it off, taking it away, and reattaching it to the current commit of branch A.

Let's look at this into more detail. You created a branch B which diverged from branch A. New commits were added to B, but to A as well.

When you rebase your B onto A, git searches for the commit where the branches diverged, which is C.

Now git will iterate over the commits from C to B, and determine the changes that have been applied to the source code between each commit. Then git starts a new branch on A, and creates similar commits on there by replaying the same changes.

It is possible that conflicts occur, in particular if the same code was changed in branches A and B. If so, you will have to resolve these conflicts before the rebase process can continue. When all commits from C to B are recreated on top of A, the new branch will take the place of the original B-branch.

The overall result will be that the changes which where developed in parallel on branches A and B now appear to be serial changes: A first, then B.

Visually: after commit ``C4`` in the ``A``-branch, you created a new branch ``B``. You added commits ``D5`` and ``D6`` to this new branch.

                                     A
                                     ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6 -- C7
                   |
                   +--- D5 -- D6
                               ↑
                               B
                             HEAD

Meanwhile, new commis were added to the ``A``-branch. Now you want the changes from the ``B``-branch to be applied to the current state of ``A`` (``C7``): rebasing branch ``B`` onto ``A``.

Git searches for the point where both branches diverged, in this example, ``C4``. Now, the changes needed to transform ``C4`` to ``D5``, will be applied to ``C7``, and committed (``D5'``). Next, the changes for the transition from ``D5`` to ``D6`` are applied, to create the next commit (``D6'``). The result is as follows:

                                     A
                                     ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6 -- C7
                                     |
                                      --- D5' -- D6'
                                                  ↑
                                                  B
                                                 HEAD

One should be careful with rebasing. You should only rebase branches that nobody else is supposed to be tracking. Rebasing changes the history of a branch. So if a collegue wants to push/pull commits to/from a branch you rebased, you probably end up in a lot of trouble.

Workflow

There are many ways to organise your work with git. At the moment, I usually work as follows:

Master branch

The master branch contains the latest relevant code. It may contain experimental features, but the idea is that the code in master compiles and works.

Feature branches

Every time you want to implement a new feature, you create a feature branch.

For example:

                 master
                   ↓
C1 -- C2 -- C3 -- C4
                   |
                   + --- D5 -- D6 -- D7
                                     ↑
                                feature1
                                   HEAD

In a feature branch, you can commit non-functional or even broken code. This is not a problem; only the code in master is expected to work.

Now suppose you are working on a new feature, but meanwhile a bug had been reported, which urgently needs a fix. In that case you can rather easily switch back to master, and a create a new bugfix branch. The changes you made in your half-finished feature branch will cause no troubles.

                 master
                   ↓
C1 -- C2 -- C3 -- C4
                   |
                   +--- D5 -- D6 -- D7
                   |                 ↑
                   +--- E5      feature1
                         ↑
                       bugfix
                        HEAD

When your bugfix is ready, and nothing changed to master you can easily fast-forward merge the bugfix branch to the master branch.

                       HEAD
                      bugfix
                      master
                         ↓
C1 -- C2 -- C3 -- C4 -- E5
                   |
                   +--- D5 -- D6 -- D7
                                     ↑
                                 feature1

After merging, the bugfix branch is of no more interest; this pointer can be removed. You can check out your feature branch again, and continue to work on the feature.

At some point, hopefully, your feature implementation is ready, and has to be merged it into master. A fast forward merge is impossible now, because the bugfix created new commits in the master branch. To avoid clutter in the history of your code, it is useful to rebase your feature branch onto master before merging.

Rebase ``feature1`` onto ``master``:

                      master
                         ↓
C1 -- C2 -- C3 -- C4 -- E5
                         |
                         +--- D5' -- D6' -- D7'
                                             ↑
                                         feature1
                                           HEAD

After that you can fast forward merge:

                                          HEAD
                                         feature1
                                          master
                                             ↓
C1 -- C2 -- C3 -- C4 -- E5 -- D5' -- D6' -- D7'

A feature branch is typically a branch on which you work alone; chances are high that no one else is tracking it. So rebasing is no problem. Because of the rebase operation, your feature seems to be completely developed after the bugfix, which results in a cleaner history of your project's code.

If you had just merged your feature branch without rebasing, you would end up with a commit with two parents, which would just make things more complicated than they should be.

Release branches

If a new release of your project is approaching, you typically create a release branch from master.

                 release-1
                 master
                   ↓
C1 -- C2 -- C3 -- C4

There are probably a number of bugs that still need to be fixed before release. Meanwhile, the normal development of new features can continue in master.

                 release-1   master
                   ↓           ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6

Suppose you have a release-critical bug to fix. Then you fix that bug in the release branch.

                             master
                               ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6
                   |
                   +--- D5
                         ↑
                      release-1

However, you probably also want to apply the bugfix on the master branch. At this point rebasing the release branch onto master is not an option, because this would make the new features you committed to master part of the release branch. Which is not what you want, because these new features could be experimental or untested. So in this particular case merging the release branch into the master branch is the way to go.

                                 master
                                    ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6 -- C7
                   |                |
                   +----D5 ---------+
                         ↑
                      release-1

After the merge, you must not remove the release branch since you will need it afterwards for other release critical bugs to be committed.

Major refactoring

A final use case that I want to discuss is a major refactoring. If you want to refactor your code in such a way that a lot has to be rewritten, you also create a branch.

This kind of refactoring usually takes some time, and you typically want feedback from other developers during the process. If you're lucky, other people are even willing to help you with the refactoring. So it is a good idea to make the refactoring branch publicly available.

Now suppose you want the new fixes from master to be incorporated in your refactoring branch. Rebasing your refactoring branch onto master is usually not a good idea: other developers have probably pulled it; they might even be working on it. So in this case, merging the master branch into your feature branch will do.

That's all

There you go. A modest introduction to git. I made abstraction of some details, because I wanted to keep it (relatively) short. And of course also because there are still things I don't understand completely myself :-)

The workflow as I describe it here, seems to work for me. I'm not sure whether it is really best practice. If you have any feedback, I am certainly interested.

This text is also available on github. You can comment over there (just submit an issue), or send me pull requests if you want to improve it. :)

De tekst die ik zocht toen ik startte met git

De tekst die ik zocht toen ik startte met git

Toen de dieren nog spraken, heb ik aan projecten gewerkt die CVS gebruikten als versiebeheersysteem. Daarna werkte ik verschillende jaren met subversion. Maar sinds enkele maanden heb ik ook subversion gedumpt, en gebruik ik alleen nog git. Als ik kan kiezen tenminste. Dit zijn de redenen:

  • Je kunt committen als je internetconnectie het laat afweten.
  • Je kunt vrij gemakkelijk configureren dat niet iedereen schrijfrechten heeft op de master- en de releasebranches.
  • Je kunt al eens lokaal iets uitproberen en wijzigingen committen, zonder dat je daarvoor op de centrale repository een branch moet maken.
  • Feature branches maken en switchen tussen branches gaat veel makkelijker, en ook zonder internetconnectie.
  • Git heeft veel meer features dan subversion.
  • Linus zegt: 'If you are using subversion, you are stupid and ugly.' ;-)

Er zijn ook wel wat problemen met een migratie naar git:

  • Git werkt op een heel andere manier dan subversion, en het vraagt tijd om goed te begrijpen wat het doet
  • Git werkt het gemakkelijkst via de command line, en dat is voor sommige users een hoge drempel.

Ik mocht een bestaande server gebruiken met git en gitolite. En ook de conversie van subversion naar git ging vrij gemakkelijk. We konden git gebruiken, het werkte, al wist ik niet helemaal hoe dat kwam. Dat lijkt overigens een meer gehoord probleem te zijn. Vandaar dat ik nu de tekst schrijf die ik graag had gelezen toen ik met git begon. Want hier en daar zitten er wel wat rariteiten in onze repo, die waarschijnlijk vermeden hadden kunnen worden, als ik beter wist waar ik mee bezig was.

Twee disclaimers:

  • Ik bespreek hier enkel de concepten, je vindt hier geen concrete git-commando's. Dat is bewust :) Mogelijk schrijf ik ooit nog een praktisch vervolg op deze tekst.
  • Van sommige technische zaken maak ik abstractie, om de lengte van de tekst te kunnen beperken.

Git is gedistribueerd

Git is gedistribueerd. Er is in principe geen centrale repository, en iedere developer heeft een kopie van de volledige repository staan. Wil je een nieuwe kopie, dan kloon je gewoon een bestaande.

In je eigen repository kun je referenties bewaren naar andere remote reposity's. Als je een repository kloont, dan wordt sowieso een referentie gelegd naar het orginineel, die dan de naam 'origin' krijgt.

Commits

Een git repository is in grote lijnen niets meer dan een gerichte acyclische graph met commits, waar een commit een specifieke revisie van je source code is. Iedere commit wordt bepaald door een SHA-1-hash, niets meer dan een (in praktijk) unieke checksum. Vaak spreekt men kortweg over een 'SHA'.

Een SHA is 40 karakters lang, dat is erg veel om te typen. Als er geen dubbelzinnigheid is, kun je naar een commit verwijzen met de eerste karakters van de SHA. Meestal kom je met 5 à 6 karakters toe.

Iedere commit (behalve de initiële) heeft een verwijzing naar één of twee ouders. Die verwijzing gebeurt via de SHA. Door van een willekeurige commit die verwijzigingen te blijven volgen tot de initiële commit, kun je heel de geschiedenis van die revisie van de source code terugvinden.

In het meest eenvoudige geval, is een git repository gewoon een opeenvolging van commits, waarbij iedere commit hoogstens één ouder en hoogstens één kind heeft. De geschiedenis is dan bij wijze van spreke een rechte lijn:

C1 <- C2 <- C3 <- C4

``C1`` is de initiële commit. Om de schema's niet te overladen, ga ik verderop in de tekst de 'pijltjes' voor de ouderrelatie (``<-``) niet meer als dusdanig afbeelden. De conventie is dat de parent links staat, en de children rechts.

In de meeste gevallen is het echter niet zo dat alle revisies in een git-repository elkaar mooi opvolgen. In geval van branchen heeft een commit typisch meerdere kinderen. Bij een merge-operatie heeft een commit precies twee ouders. Maar daarover later meer.

Voorbeeld van een ingewikkelder repository:

C1 -- C2 -- C3 -- C4 -- C5 -- D6 -- D7
       |                       |
       +--- D3 -- D4 -- D5 ----+
       |
       |           +--- F5 -- F6
       |           |
       +--- E3 -- E4--- E5

Bij het programmeren is er altijd één commit uitgecheckt. Deze commit is de HEAD van je repository. De huidige source code (working copy) komt overeen de code van HEAD, met daarop een een aantal wijzigingen die je maakte. Er kunnen bestanden toegevoegd, verwijderd, of aangepast zijn.

Als je nu een nieuwe revisie van je code wilt committen, moet je aan git laten weten welke van de wijzigingen in de working copy meegenomen moeten worden. Als je wilt dat een wijziging mee gecommit wordt, dan moet je die toevoegen aan de 'index': de wijziging 'stagen'. Een nieuwe commit bevat alle wijzigingen van de index. Zaken die je aan de source veranderde, maar niet stagede, blijven weliswaar gewijzigd in jouw versie van de source code, maar worden niet mee opgenomen in de commit.

                  HEAD
                   ↓
C1 -- C2 -- C3 -- C4  -- staged changes -- working copy

... wordt na committen:

                        HEAD
                         ↓
C1 -- C2 -- C3 -- C4 -- C5 -- working copy

Na een commit schuift HEAD een plaatsje op, zodat die opnieuw wijst naar de commit waarop je working copy gebaseerd is.

Als git-repository's met elkaar praten, dan wisselen ze typisch commits uit. Op de duur krijg je er zo heel wat bij elkaar, en de vraag is dan hoe je daar je weg nog in vindt. Hier is een oplossing voor: branches.

Branches

Branches zijn in se niets meer dan pointers naar een bepaalde commit in je repository. Net zoals HEAD er eentje is, eigenlijk. Een branch uitchecken, komt neer op het uitchecken van de commit waar de branch naar wijst. Git weet in welke branch je aan het werken bent, en als je een nieuwe revisie commit, dan schuift niet alleen HEAD op, maar ook de pointer van de huidige branch.

Branches kun je namen geven zoals je wilt, maar typisch is er één die master heet. De master-branch is de 'mainline', die bevat de meest up-to-date developmentversie.

Voorbeeld:

                master
                    ↓    HEAD
C1 -- C2 -- C3 -- C4     branch2
       |                 ↓
       +--- D3 -- D4 -- D5
       |
       |           +--- F5 -- F6
       |           |           ↑
       +--- E3 -- E4         branch4
                   ↑
                 branch3

Branches in je eigen kopie van de repository, zijn lokale branches. De branches in een remote repository kun je ook zien, dat zijn dan remote branches. Het is mogelijk om remote branches te 'fetchen'; dan worden alle relevante commits overgehaald naar je eigen repository, alsook de pointer van de remote branch.

(origin)

                master
                    ↓
C1 -- C2 -- C3 -- C4          branch2
       |                       ↓
       +--- D3 -- D4 -- D5 -- D6
       |
       |           +--- F5 -- F6
       |           |           ↑
       +--- E3 -- E4         branch4
                   ↑
                 branch3

(lokaal)

                       HEAD
                       master
                         ↓
C1 -- C2 -- C3 -- C4 -- C5

Fetchen van ``origin/branch4`` geeft in dit voorbeeld

(lokaal)

                      HEAD
                      master
                         ↓
C1 -- C2 -- C3 -- C4 -- C5
       |
       +--- E3 -- F4 -- F5 -- F6
                               ↑
                            origin/branch4

Je kunt niet rechtstreeks committen een branch in een remote repository. De geijkte manier van werken is dat je eerst de remote branch fetcht, dat je die koppelt aan een lokale branch, en dat je dan lokaal je nieuwe commits maakt. Een dergelijke lokale branch die gekoppeld is aan een remote branch, heet een '(remote) tracking branch'.

Van een tracking branch weet git waar het origineel zit, zodat je de recentste wijzigingen in de remote branch makkelijk kunt downloaden. Git zal je ook informeren over de verschillen tussen je de remote branch en je gekoppelde lokale tracking branch.

Terug naar het voorbeeld van daarnet. Als je een lokale branch ``branch4`` maakt, als remote tracking branch voor ``origin/branch4``, en je checkt die uit, dan is de situatie als volgt:

                      master
                         ↓
C1 -- C2 -- C3 -- C4 -- C5
       |
       +--- E3 -- F4 -- F5 -- F6
                               ↑
                            origin/branch4
                            branch4
                            HEAD

Maar voor de rest gedraagt een tracking branch zich net hetzelfde als een gewone lokale branch. Als hij uitgechekt is, en je commit, dan schuift de pointer van je tracking branch gewoon mee op met HEAD.

                      master
                         ↓                HEAD
C1 -- C2 -- C3 -- C4 -- C5                branch4
       |                                   ↓
       +--- E3 -- F4 -- F5 -- F6 -- F7 -- F8
                               ↑
                            origin/branch4

Mergen

Als een commit 2 parents heeft, dan spreken we over een 'merge-operatie'. Het idee achter mergen is dat je de wijzigingen van een andere branch toepast op je uitgecheckte branch. Die wijzigingen worden bepaald op basis van de recentste gemeenschappelijke voorouder van de uitgecheckte branch en de te mergen branch.

In het eenvoudigste geval, is je uitgecheckte branch zelf een voorouder van de branch die je wilt mergen. (Dit komt vaker voor dan je initieel zou denken). Git zal dan gewoon de pointer van je uitgecheckte branch verleggen naar de commit waar de te mergen branch naar wijst. De verlegde branch wordt dan opnieuw uitgecheckt, zodat HEAD ook opschuift. We spreken van een 'fast forward merge'. Goed onthouden, want dat is een belangrijk concept: een fast forward merge is een merge die enkel neerkomt op het verleggen van de pointer van een branch.

                HEAD
                branch1
                   ↓
C1 -- C2 -- C3 -- C4
                   |
                   +--- D5 -- D6
                               ↑
                            branch2

Na merge van branch2 op branch1:

C1 -- C2 -- C3 -- C4
                   |
                   +--- D5 -- D6
                               ↓
                            branch1
                            branch2
                            HEAD

(Ik heb de knik in de tekening laten zitten, maar uiteraard is die niet van belang)

Een fast forward merge is niet altijd mogelijk. Als je uitgecheckte branch geen voorouder is van de te mergen branch, dan volstaat het verleggen van een pointer niet.

                      HEAD
                      branch1
                         ↓
C1 -- C2 -- C3 -- C4 -- C5
                   |
                   +--- D5 -- D6
                               ↑
                            branch2

In dit geval gaat git op zoek naar de recentste gemeenschappelijke voorouder. Op die voorouder worden dan de wijzigingen van daar tot de uitgecheckte branch toegepast, en de wijzigingen van daar tot de te mergen branch.

Onderstaand voorbeeld laat zien hoe ``branch2`` gemerged wordt in ``branch1``.

                                HEAD
                                branch1
                                   ↓
C1 -- C2 -- C3 -- C4 -- C5------- C6
                   |               |
                   +--- D5 -- D6 --+
                               ↑
                            branch2

In het beste geval gaat dat probleemloos. Git maakt dan een nieuwe commit in de uitgecheckte branch.

Als er conflicten zijn tussen de 2 sets wijzigingen, dan zal git je files wel aanpassen, maar zal het resultaat nog niet gecommit zijn. Je zult dan manueel de conflicten moeten oplossen (git zal je vertellen over welke files het gaat), alvorens het resultaat te committen.

Pull en push

Stel dat je in een remote tracking branch aan het werken bent, en dat je de laatste commits van die remote branch wilt toepassen op jouw branch. Dan kun je een 'pull'-operatie uitvoeren. Git zal dan de remote branch opnieuw fetchen, en die mergen in jouw tracking branch.

Bijvoorbeeld: Toen in onderstaand voorbeeld ``remote/branch1`` naar ``C3`` wees, maakte je een remote tracking branch. Sindsdien werd remote een commit ``C4`` bijgemaakt, terwijl jij lokaal ``C4'``, ``C5'`` en ``C6'`` committe.

(origin)
                 branch1
                   ↓
C1 -- C2 -- C3 -- C4

(lokaal)

                               HEAD
                               branch1 (trackt remote/branch1)
                                ↓
C1 -- C2 -- C3 -- C4' -- C5'-- C6'
             ↑
          remote/branch1

Na een fetch-operatie van ``remote/branch1``, ziet de lokale repo er als volgt uit:

                               HEAD
                               branch1
                                ↓
C1 -- C2 -- C3 -- C4' -- C5'-- C6'
             |
             +--- C4
                   ↑
              remote/branch1

Tenslotte wordt gemerged

                                     HEAD
                                   branch1
                                       ↓
C1 -- C2 -- C3 -- C4' -- C5'-- C6' -- C7'
             |                         |
             +--- C4 ------------------+
                   ↑
              remote/branch1

Net zoals bij iedere andere merge, zou het kunnen dat dit conflicten veroorzaakt, die je dan zult moeten oplossen.

Omgekeerd kun je de commits in een uitgecheckte branch 'pushen' naar een branch in een remote repository. Dat kan zowel naar een nieuwe als naar een bestaande remote branch zijn. Git uploadt dan de commit waar HEAD naar wijst, samen met de nodige voorouders om jouw HEAD te koppelen aan de remote commits.

Als de remote branch al bestond, worden jouw lokale commits gemerged in de remote branch. Maar in de meeste configuraties gebeurt dat enkel als die merge-operatie een fast forward merge is. In de andere gevallen krijg je een foutmelding.

In zo'n geval pull je eerst de remote branch, zodat de merge-operatie in jouw lokale repository afgehandeld kan worden. Die merge-operatie resulteert dan lokaal in een nieuwe commit, met als ouders jouw recentste commit en de recentste commit uit de remote repository. Als je dan opnieuw pusht, zal aan de remote kant wel fast forward gemerged kunnen worden.

Rebasen

'Rebasen' komt min of meer neer op het 'verleggen' van een branch. Stel dat je een branch hebt gemaakt, op basis van master. Intussen zijn er aan die branch nieuwe commits toegevoegd, en ook master heeft nieuwe commits.

Je kunt nu je branch rebasen op master. Git gaat op zoek naar de gemeenschappelijke voorouder van master en jouw branch. Vertrekkende van dat punt, zal git alle commits in jouw branch aflopen, en bekijken wat er precies gewijzigd is bij deze commits. Dan maakt git een nieuwe branch vertrekkende van de huidige master, en worden in die nieuwe branch nieuwe commits gemaakt, door dezelfde wijzigingen door te voeren in de nieuwe branch. Mogelijk treden er onderweg conflicten op, omdat wijzigingen in master de wijzigingen uit de branch in de weg staan. Die moet je dan onderweg oplossen. Als alle commits zijn overgedaan, wordt de pointer van je branch verlegd naar de nieuw aangemaakte branch, en wordt die uitgecheckt.

Bijvoorbeeld: na een commit ``C4`` in de ``master`` branch, maakte je een nieuwe branch. Daarin committe je de wijzigingen ``D5`` en ``D6``.

                                  master
                                     ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6 -- C7
                   |
                   +--- D5 -- D6
                               ↑
                            branch2
                             HEAD

Intussen zijn er ook nieuwe commits in de master branch. Nu wil je graag dat ``branch2`` verlegd wordt naar de huidige toestand van de master branch (``C7``). In dat geval spreekt men van een 'rebase' van ``branch2`` op ``master``.

Git gaat op zoek naar de recentste gemeenschappelijke 'voorouder' van ``branch2`` en ``master``. In dit voorbeeld is dat ``C4``. Nu worden de wijzigingen die nodig waren om van ``C4`` naar ``D5`` te gaan toegepast op ``C7``. Dit wordt gecommit, en op die nieuwe commit worden dan de wijzigingen voor de overgang van ``D5`` naar ``D6`` toegepast. Met het volgende resultaat:

                                  master
                                     ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6 -- C7
                                     |
                                     +--- D5' -- D6'
                                                  ↑
                                                branch2
                                                 HEAD

Let op! Rebasen doe je enkel met branches die niemand anders wordt geacht te tracken. Bij het rebasen verander je namelijk de geschiedenis van een branch. Als een collega commits toevoegt aan een branch die jij hebt verlegd, en hij probeert te pushen of te pullen, dan eindigt dat ongetwijfeld met miserie.

Workflow

Je kunt met git veel kanten uit. Op dit moment is mijn manier van werken als volgt:

Master-branch

De master-branch bevat de recentste werkbare code. Dat mag met experimentele features zijn, maar de bedoeling is wel dat de code compileert, en geacht wordt goed te werken.

Feature branches

Iedere keer als je aan een nieuwe feature begint, maak je een feature-branch.

Bijvoorbeeld:

                 master
                   ↓
C1 -- C2 -- C3 -- C4
                   |
                   +--- D5 -- D6 -- D7
                                     ↑
                                feature1
                                   HEAD

Als je in een feature branch code commit die niet helemaal werkt, of half broken is, is dat geen probleem. Enkel master wordt geacht in orde te zijn.

Stel dat er nu plots een bug wordt gevonden, die dringend gefixt moet worden. In dat geval kun je makkelijk master opnieuwe uitchecken, en een nieuwe branch maken voor je bugfix. Je zult dan geen last hebben van mogelijke problemen of onvolkomenheden van je half-afgewerkte feature.

                 master
                   ↓
C1 -- C2 -- C3 -- C4
                   |
                   +--- D5 -- D6 -- D7
                   |                 ↑
                   +--- E5         feature1
                         ↑
                       bugfix
                        HEAD

Is je bugfix afgewerkt, en er is ondertussen niets aan master veranderd, dan kun je die bugfix makkelijk fast forward mergen.

                       HEAD
                      bugfix
                      master
                         ↓
C1 -- C2 -- C3 -- C4 -- E5
                   |
                   +--- D5 -- D6 -- D7
                                     ↑
                                 feature1

De bugfixbranch is na de merge van geen belang meer, en kan verwijderd worden. Je checkt je feature branch opnieuw uit, en kunt dan zonder problemen verder werken aan waar je mee bezig was.

Na een tijdje is die feature af, en wil je ook mergen. Dat kan niet meer via een fast forward merge, want ondertussen is de master branch opgeschoven. (Omwille van de bugfix van daarnet.) Om de geschiedenis van je code dan overzichtelijk te houden, is het dan interessanter om de feature-branch te rebasen alvorens hem te mergen naar master.

Rebase dus eerst ``feature1`` op ``master``:

                      master
                         ↓
C1 -- C2 -- C3 -- C4 -- E5
                         |
                         +--- D5' -- D6' -- D7'
                                             ↑
                                         feature1
                                           HEAD

Hierna kun je fast forward mergen:

                                          HEAD
                                         feature1
                                          master
                                             ↓
C1 -- C2 -- C3 -- C4 -- E5 -- D5' -- D6' -- D7'

Een feature-branch is typisch een branch waar jij alleen aan werkt; er is niemand anders die die trackt. Rebasen is dus geen probleem, en op die manier heeft elke commit slechts 1 parent, wat overzichtelijker is als je de geschiedenis van je project wilt bekijken. Bij een gewone merge die niet fast-forward is, heb je een commit met 2 parents, en dat maakt het ingewikkelder. Als je dat kunt vermijden, moet je dat doen.

Release-branches

Stel dat er een release dichtbij komt. Dan splits je een release-branch af van master. In het begin bevat die uiteraard nog niets speciaals.

                 release-1
                 master
                   ↓
C1 -- C2 -- C3 -- C4

Typisch zijn er een aantal bugs die nog gefixt moeten worden voor de release. Maar de gewone ontwikkeling gaat verder in master.

                 release-1    master
                   ↓           ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6

Stel nu dat je een release-critical bug wilt fixen. Dan doe je die fix in de release branch.

                             master
                               ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6
                   |
                   +--- D5
                         ↑
                      release-1

Maar je wilt deze fix natuurlijk ook toepassen op de master branch. Hier is het uiteraard geen optie om eerst de release-branch te rebasen op master, want dan zouden de nieuwe features die intussen naar master gecommit zijn, ook in de releasebranch zitten. En dat is uiteraard niet de bedoeling. In dit geval merge je de release-branch gewoon in master.

                                 master
                                    ↓
C1 -- C2 -- C3 -- C4 -- C5 -- C6 -- C7
                   |                |
                   +--- D5 ---------+
                         ↑
                      release-1

Na zo'n merge mag de release-branch uiteraard ook niet verwijderd worden, want die heb je achteraf nog nodig om verdere release-critical-bugs te committen.

Ingrijpende refactoring

Een laatste use case die ik wil bespreken, is een ingrijpende refactoring. Hiervoor maak je ook een branch.

Omdat zo'n refactoring wel wat tijd in beslag zal nemen, en omdat je tijdens het refactoren graag feedback hebt, wil je je refactoring branch ook publiek beschikbaar maken.

Publieke branches rebasen is meestal niet zo'n goed idee. Want zoals gezegd geeft dat problemen als iemand anders jouw branch trackt. Vermijden dus. Als je de laatste zaken uit master ook in je refactoring branch wilt trekken, dan kun je ook beter direct mergen.

That's all

Voilà. Een bescheiden introductie tot git. Hier en daar heb ik dingen weggeabstraheerd, om de tekst niet te lang te maken, en natuurlijk ook omdat ik zelf nog niet alles beheers :-)

De manier van werken die ik beschrijf, werkt voor mij. Ik ben niet zeker of het echt volgens de best practices is. Als je feedback hebt, dan ben ik daar zeker in geïnteresseerd.

Deze tekst is ook beschikbaar op github. Daar kun je commentaar geven (post gerust een issue), of zelfs pull requests sturen, als je hem wilt verbeteren :)

SFD 2012: Linux mint vanop een USB-stick

Deze zaterdag, 15 september, is het weer software freedom day! Dit jaar installeren we geen Linuxdistributie op je harde schijf, want dat deden we vorig jaar al :-)

Dit jaar maken we een draagbare linuxdistributie op een USB-stick, die je dan kunt gebruiken op zo goed als iedere conventionele computer of laptop. We kiezen voor Linux Mint, en installeren dat op een usb-stick met behulp van Unetbootin.

De video is naar mooie traditie minder professioneel, met hier en daar onhandig knipwerk, een plots einde, en commentaar in lelijk verkavelingsvlaams. Maar als tegenprestatie mag je de stick die ik maakte komen lenen om uit te proberen. Op voorwaarden dat je hem zelf komt halen, en dat ik hem nog niet aan iemand anders gegeven heb :-)

Update: 1 GB schrijfbare ruimte op de USB-stick is te weinig om de updates te kunnen installeren. (Mint 13 is al een aantal maanden uit, en er staan wel wat updates te wachten.) Onderaan zal ik een howto voorzien om je systeem opnieuw stabiel te krijgen, als je het probeerde te upgraden met te weinig schrijfbare ruimte. Maar beter is dat je bij het opzetten van je systeem 2 GB (2048 MB) vrije ruimte voorziet, in plaats van de 1 GB in de video.

Als aardigheidje heb ik ook nog een fotootje van ‘the making of’, dat ik jullie niet wou onthouden:

the making of

Upgrading the Xperia Mini Pro from CM 7.2 to CM 9

Recently I upgraded my phone (Sony Ericsson Xperia Mini Pro) from Cyanogenmod 7.2 to Cyanogenmod 9. And you should upgrade too, if you are running Cyanogenmod 7.2. This is why:

  • The hardware keyboard layout can be configured from the settings menu.
  • The ‘symbols’ key on the hardware keyboard now actually works, so you don't have to switch to the software keyboard anymore when you have to enter an exotic symbol.
  • The dictionary on the software keyboard now works. Not that I ever use it, but hey, it might be useful for some of you.
  • The ‘dock’ at the bottom is more stable: it doesn't disappear for no reason, and the icons in it stay where they are.
  • Dragging icons from the app drawer to the main screen is a lot smoother.
  • CM 9 (based on Android 4) looks way cooler than CM 7.2 (based on Android 2.3).

But I went to some pain while upgrading the thing. So if you want to perform a similar upgrade, here are two useful tips.

The first one: Be sure to put not only the roms of CM 9 and the Google apps on your SD-card, but the ROM of the old CM 7.2 as well. If anything goes wrong, you can quickly flash the old image for having access to your phone again.

Second thing: before flashing CM 9 to your phone, you'll probably have to upgrade your boot loader. I didn't do that, and after flashing the ROM, the phone booted into a black screen; removing the battery was the only option. Maybe you can upgrade the boot loader with ROM manager, but I didn't try that. I just used fastboot (a tool that can be found in the platform-tools folder of the Android SDK), as described in steps 5 and 6 of the excellent documentation on the cyanogenmod wiki.

The new bootloader looks much slicker than the previous one, which was plain ugly :-). And when it's installed, you should be able to flash the roms for CyanogenMod 9 and the Google Apps for Ice Cream Sandwich. (But don't come after me if things break, because I'm clueless as always.)

Installatie van cyanogenmod 7.1 op de Xperia Mini Pro

Ik kocht me een nieuwe telefoon. Een Sony Ericsson Mini Pro met Android. Een fijn toestel met hardwarekeyboard, maar helaas ook met een hele hoop crap voorgeïnstalleerd, die ik allemaal niet nodig heb. Gelukkig bestaat er een oplossing, en die heet cyanogenmod.

Cyanogenmod is alternatieve firmware voor Androidtelefoons, en het toeval wil dat er ondersteuning is voor de Xperie Mini Pro. ('t Is te zeggen, zo toevallig is dat niet. Een belangrijke vereiste voor mijn nieuwe telefoon was dat cyanogenmod erop moest kunnen draaien.)

Een aantal opmerkingen vooraf:

  1. Je moet je stock-os niet rooten om cyanogenmod te kunnen installeren: de Xperia Mini Pro laat toe om de bootloader te unlocken, en dan kun je eigenlijk doen wat je wilt.
  2. Op de cyanogenmod-wiki staat als recentste stable versie voor de Xperia Mini Pro cyanogenmod 7.1.0.2. Maar op de downloadpagina vind je ook de 7.2-versie. Kies die laatste, want die is recenter, en bovendien zorgt een bug in 7.1.0.2 dat het systeem niet boot na de installatie.
  3. Cyanogenmod 7.2 is gebaseerd op Android 2.3. Dat is natuurlijk nog niet zo hip als Android 4. Cyanogenmod 9 is ook beschikbaar maar dat is blijkbaar nog een release candidate. Met cyanogenmod 9 heb je wel Android 4, maar ik dacht dat het geen slecht idee zou zijn om in eerste instantie gewoon de stable rom te installeren.
  4. Op de cyanogenmod-wiki staat een erg goede howto voor de installatie van cyanogenmod op de xperia mini pro. Daar kom je al heel ver mee. De bedoeling van dit artikeltje is een howto in het Nederlands te zijn, met wat extra info over hoe je het hardwarekeyboard configureert.

Backup

Wat ik niet gedaan heb, is vooraf een backup maken van mijn systeem. Het was immers een nieuwe telefoon, en ik had er nog niet veel mee gedaan. Als alles om zeep zou zijn, kon ik tijdelijk mijn oude telefoon nog gebruiken, en ik was er behoorlijk zeker van dat ik er wel in zou slagen om mijn telefoon terug werkende te krijgen. Maar uiteraard beveel ik voor de rest iedereen aan om een backup te maken van het systeem op je telefoon, vooraleer je nieuwe roms begint te flashen. Vermoedelijk lukt dat met de PC companion, die je op een Windows-PC kunt installeren door je telefoon in te pluggen.

Bestanden downloaden

Download het volgende, en plaats de zip-bestanden in de root-folder van je SD-kaart:

De zip van cyanogenmod bevat een bestand ‘boot.img’. Dat moet je extraheren, en ergens op je de harde schijf van je computer zetten.

Bootloader unlocken

Om met je telefoon te kunnen prutsen, heb je de android-sdk nodig. Gelukkig had ik die ooit geïnstalleerd op een oude Linuxinstallatie die ik nog ergens had, want de installatie van de sdk's heeft heel wat 32-bit-pakketten nodig, en ik vind het altijd zo stom om heel die boterham op mijn ‘main system’ te moeten installeren.

Verder moet je de IMEI van je telefoon opvragen, die krijg je te zien als je het ‘nummer’ *#06# draait. Noteer deze IMEI, want dat heb je zodadelijk nodig.

Er is nog zo'n exotisch nummer dat je eens kunt vormen: *#*#7378423#*#*. Hiermee tover je het ‘service menu’ te voorschijn. Kies achtereenvolgens ‘Service info’, ‘Configuration’ en ‘Rooting Status’, en hopelijk staat er dan ergens ‘Bootloader unlock allowed:Yes’.

Als dat het geval is, kun je eraan beginnen. Schakel de telefoon uit. Houd de knop om het volume te verhogen ingeduwd, en connecteer tegelijkertijd de telefoon aan de computer. Normaalgezien gaat er een ledlampje aan op je telefoon, maar gebeurt er voor de rest niets.

Op de computer open je een terminalvenster, zorg ervoor dat de map ‘platform-tools’ van de Android-SDK in je zoekpad staat (of geef het pad mee in onderstaand commando), en tik in:

fastboot -i 0x0fce getvar version

Je krijgt hopelijk iets in deze aard:

version: 0.3
finished. total time: 0.001s

Als je in de plaats daarvan ‘waiting for device’ krijgt, is er iets mis, en zul je moeten troubleshooten.

Surf nu naar http://unlockbootloader.sonyericsson.com/instructions. Klik achtereenvolgens ‘Start unlocking the boot loader’, ‘Continue’ en ‘Yes, I'm sure’. Klik aan dat je je garantie verliest (bla bla yada yada) en dat het extra veel zal kosten om de telefoon te repareren (precies of het flashen van de stock image is moeilijk). Vervolgens klik je ‘I accept ’. Op het volgende formulier geef je je naam in, je IMEI-code zonder het laatste cijfer, en je e-mailadres. Klik op ‘Submit’, en je krijgt je unlock-code te zien.

Tik nu het volgende in het terminalvenster

fastboot -i 0x0fce oem unlock 0xKEY

waarbij je ‘KEY’ vervangt door je unlock-code. (Vergeet de ‘0x’ niet te typen voor je code, anders werkt het niet. 't Is hex hé.) Je krijgt wat angstaanjagende tekst te zien (‘Erasing block...’), die eindigt met ‘OKAY’. Je bootloader is unlocked. Nu kun je hem vervangen.

Een nieuwe bootloader flashen

Geef je het volgende commando in:

fastboot -i 0xfce flash boot boot.img

Ik weet niet precies wat dit doet. Het vervangt volgens mij de bootloader, en installeert ook de ClockworkMod recovery image. Of de recovery image al dan niet onderdeel is van de bootloader, dat is me niet helemaal duidelijk. Het voornaamste is dat je de recovery image kunt oproepen, en wel als volgt:

Het nieuwe OS flashen

Reboot de telefoon. Dat kan nog met fastboot:

fastboot -i 0xfce reboot

Je krijgt de nieuwe bootloader te zien. Op dat moment moet je wat op de volumecontroleknoppen drukken, dan wordt de recovery image geboot. Als je dat niet doet, of je wacht te lang, dan loopt je systeem vast, en moet je de batterij er uit nemen (en even wachten) om opnieuw te proberen.

Als alles goed gelopen is, ben je nu in recovery mode geboot, op slechts een paar stappen verwijderd van cyanogenmod. De recoverymode is menugebaseerd, en je kunt het menu aansturen met de volumecontroleknoppen en de home button van je telefoon.

Kies 'Wipe data/factory reset,' en bevestig.

Idem voor 'Wipe cache partition.'

Vervolgens kies je 'Install zip from sd-card', selecteer je de zip voor cyanogenmod, en bevestig je.

Hetzelfde doe je voor de Google-appszip...

... en de keyboard-layoutzip.

Kies dan ‘back,’ en ‘reboot.’

Fingers crossed.

Met wat geluk heb je nu een nieuw en cool OS op je telefoon, zodat je kunt uitpakken bij al je vrienden :)

Raspberry Pi as print server without server side driver

I bought myself a Raspberry Pi and I am using it as print server right now. It required some hacking to make it work, so I might as well blog about it.

I started off with the Arch based image from the official download page. Arch Linux might not be the best distribution for a server, but I didn't want to use the Debian image since it had X installed. And I didn't search for other downloadable images. I guess Arch will be fine for my specific home situation.

Setting up a print server on Arch is easy enoguh:

pacman -S cups

You have to modify /etc/cups/cupsd.conf in order to make the cups web interface remote accessible. This is what I changed:

  • Replaced Listen localhost:631 by Port 631.
  • Added Allow from 192.168.0.0/255.255.0.0 in the /, /admin and /admin/conf locations. (You will have to adapt the network ranges to your own situation.)

To make cups start at boot time, you have to add @cupsd to the DAEMONS line in /etc/rc.conf. (To start the cups daemon right now, issue /etc/rc.d/cupsd start.)

Using the web interface, in my case https://192.168.3.200:631 (replace the IP address as appropriate) I could add my printer. But, a problem: it uses the foo2zjs driver, which has no ARM version, so it could not be installed on the ARM-based Raspberry Pi. We can work around this, by defining the printer driver client side. This implies of course that it will be impossible to send print jobs originating from the Raspberry Pi itself. Printing a test page from the CUPS management web interface will not work either. But since print jobs typically come from client computers, this won't be an issue.

Because there is no driver available for the server, I just set up the printer to use no driver (selecting RAW as model in the CUPS printer configuration page). This is sufficient to make Windows clients work, provided that you selected the appropriate driver client side.

(Note: you don't have to install samba on your print server to use your printer from Windows. To define the printer on a Windows client, just use the url http://192.168.3.3:631/printers/myprinter.)

I would have thought that the same would work with Linux: I tried to use CUPS to connect to the printer on the server, using a locally installed driver. It didn't work.

I hacked around the problem by emulating a HP-jetdirect printer. For this I installed xinetd on the Raspberry Pi (pacman -S xinetd), and created a file /etc/xinetd.d/hp-pdl-datastr with the following content:

service hp-pdl-datastr
{
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/bin/lp
server_args = -d myprinter -o raw
groups = yes
disable = no
}

(the name hp-pdl-datastr is just a guess. I grepped port 9100 in /etc/services, and I found hp-pdl-datastr and pdl-datastream. I just took hp-pdl-datastream because I have a HP printer, but I guess it doesn't really matter.)

When you now start the xinetd service:

/etc/rc.d/xinetd start

you can tell your Linux client that you have a HP-JetDirect printer on port 9100 of your print server. (Remember to adapt /etc/rc.conf to make xinetd start at boot time.)

References: