itemis Blog

MAGIT – A TEXTUAL GIT INTERFACE

Written by Gunther Bachmann | Jun 21, 2019

This is not about the merits of Git. This is about working with Git in a text-based environment, to which programmers are (usually) used to. Having said that, we can now dive into the world of Magit, a text-based UI for Git.

Are you still reading this? Cool, keep going and it might give you a new perspective on how programmers use these archaic interfaces quite efficiently in their daily routine. And, hey, it’s a lot of fun, too! Still reading? Very cool!

First repo

Let’s say, you start your life in Git with a simple clone command, for example:

git clone git@github.com/test-editor/test-editor-web

Since I don’t want to talk about the merits of Git, I will keep my comments about Git to a minimum. I will, however, mention things a seasoned gitter will already know or will believe to be quite obvious, so that anyone interested will feel comfortable following this article, even if not yet familiar with every caveat of Git. At least that is my intention.

Now back to our initial clone. This will leave us with a local repository that is cloned from a remote repository. Now, what does Magit have to offer here? Starting Magit1 on this repository will give you the following screen:

Let’s go through the different sections of this screen:

  • Head … is your current branch head.
  • Merge … is the remote this branch is connected to.
  • Tag … is the latest tag in this repo.
  • Recent commits … is a list of the most recent commits.
  • TODOs … is a list of locations that mention TODO.

This initial screen is somewhat related to the output of the git status command. It offers a bit more, though, of which I will talk about in a minute. For those of you who will immediately start Magit and try things out, I will quickly introduce some basics on how to interact with Magit (don’t worry, this won’t take long).

Magit popup

The primary user interface to Magit is provided through screens (information about the repository) and so-called popups (details about commands, appearing in the lower half of the screen). To get a list of all command keys available, use [?], and you will see a popup like this:

Each Magit command will start its respective popup, giving you the chance to add options and switches to the command. Each popup can be customized to use certain options or switches by default (options are persisted). We will look into some of those possibilities later on in the context of certain commands, e.g., commit.

Basic navigation

To make use of our textual interface, I want to introduce some basic keyboard commands that will be used throughout Magit and throughout this introduction.

  • [Up/Down] … to move the point or cursor (that was obvious, I know).
  • [Tab] … to fold/unfold details of the element or rather section under the cursor.
  • [Enter] … open (or rather enter) the element under the point. This action is heavily dependent on the context.
  • [q] … to quit the current screen and return to the previous one.

Resulting Git commands

Each interaction with the repository will also print the respective Git command-line equivalent to another screen that can be reached by pressing [$]:

Looking at these commands is especially helpful when starting to use Magit. Even for those familiar with Magit, a look at the actual Git commands that are executed is informative more often than we like to admit.

Commit

Given we have a change on a file already part of the index, the overview will show unstaged changes.

These can be staged by simply pressing [s] if the point is over the respective file. [S] (note that this is a capital S) will stage all unstaged files after asking for approval for that action. If you want to stage certain chunks within a file, unfold its details by pressing [Tab], which opens the relevant chunks within this file, marking changes with -/+, color, and displaying the context of the change (3 preceding/following lines).

Each chunk can again be folded/unfolded with [Tab] and can be individually staged with [s]. You can even mark lines within a chunk that you want to state using [Shift] and the cursor keys2.

Unstaging via [u] behaves respectively, given that you are working on a file listed under “Staged changes”.

Before actually committing any changes on the master branch, it is a good idea to open a feature branch, hitting [b] (branch) then [s] (spin-off), naming it, e.g., “feature/added_tutorial_ref_to_readme”.

Given a list of staged changes, pressing [c] will open the commit popup, giving you the chance to use additional switches or options. You may now select a simple commit via [c], amend the previous commit with [a], or reword the previous commit via [w]. There are a lot more subcommands available that you may explore at your discretion.

Let’s say, you stick with a regular commit. You are then presented with a screen to write the commit message. The commit message holds reminders of what will be committed, on which branch you currently are, and the like. When done, hit [Ctrl+c] twice ([Ctrl+c][Ctrl+k] aborts the commit). Given you committed your changes, the status screen is shown again, which presents you with a new, (unpushed) commit in the recent commit list.

Now let’s take a look at the whole command sequence (assuming that you accept the changes as they are):

  • [S][y] … stage all files (and [y]es, I mean that).
  • [b][s] … spin off a new branch from master (which you are currently on).
  • <enter new branch name>
  • [c][c] … commit my changes (second [c] to accept the popup without modification).
  • <enter the commit message>
  • [Ctrl+c][Ctrl+c] … to actually commit

Only 8 keystrokes plus branch name and commit message! And hey, you didn’t even need to touch your mouse, did you?

Log history

On the status screen, hit [l] to get to the log popup, which allows you to use additional switches and options for the log. Pressing [l] again will open the log for the current branch you are on and display the log history. Your new commit should be at the head of this list.

This list is basically the output of the git log command with the additional benefit of being able to

  • navigate within this list,
  • copy the hash of a commit (now what might that be useful for?),
  • open a commit and look at the changes that were introduced by it,
  • search for a certain commit message,

and much, much more.

You might as well want to look at the tree of another branch, just hit [l] followed by [o] for other, select the branch you want to see the log of and there you are. Cherry picking from a commit is as easy as navigating to that commit, pressing [A] twice (first [A] opens the cherry pick popup, remember?).

Commit detail

Using [Enter] on a commit will provide a detailed view of this commit as seen in this screenshot:

[Tab] will fold/unfold changes listed by this commit. To leave this screen simply use [q].

TDD session

Now that you are familiar with the basics of Magit, let’s dive into a TDD session supported by Magit. We will, however, focus on the repository actions and will not actually write any code. The actions done via Magit are bold, whereas the other actions completing the TDD sessions are kept in italics.

We will:

  • create a new branch
  • write a test
  • run the test
  • commit
  • write some code
  • run the test again
  • commit
  • refactor the code
  • run the test
  • commit
  • squash the commits
  • push the branch
  • make a pull request, see that we have to rebase the master (since someone just merged his new feature)
  • rebase to origin/master
  • push again

Finally CI is green and the pull request is accepted. Now let’s get into the detail of (just) the repository actions involved here:

  • create new branch … given that you are on the master branch (and up to date), hit [b] (branch popup) [s] (spin off), give the branch a name, e.g., “feature/rock”, [Enter]
  • commit … (we know that one already) [c][c], commit message, e.g., “TEST: test-editor will rock hereafter”, [Ctrl+c][Ctrl+c]
  • commit … [c][c], commit message, e.g., “CODE: test-editor will rock hereafter”, [Ctrl+c][Ctrl+c]
  • commit … [c][c], commit message, [Ctrl+c][Ctrl+c]
  • squash commits … navigate to first commit, [r] (rebase) [i] (interactive), mark commits to squash with [s][Ctrl+c][Ctrl+c] to accept, done (I’ll will talk about rebase in the next section)
  • push … [P] (push, note it’s a capital P), [p] (no further options)
  • rebase to origin/master … [r] (rebase) [e] (elsewhere), select origin/master, done
  • push … [P][p]

I will not start counting the keystrokes again, but it should be obvious that Magit tries to reduce the amount of keystrokes to a minimum, while retaining some conceptual consistency and similarity to the command-line interface of Git. Reducing the overhead of committing (or interacting with the repository) might be arguably a needless venture, compared to the overall effort that goes into writing tests and code passing the tests.

I’m convinced that programmers should be relieved as much as possible of chores that will slow down their TDD cycle. Programmers must use version control for several reasons (hopefully Git, so that Magit can be used). Keeping friction to a minimum is a goal absolutely worth pursuing. Do your dojos! Use Magit! Be efficient! ← Oops, the quote sneaked in ;-)

Rebase

Those of you familiar with Git have definitely used rebase to bring some order to your commits. Keeping your Git history clean is a benefit to all within your project. Rebasing will therefore constantly accompany you on every non-trivial Git project. Resolving conflicts will hopefully be at a minimum (given that your features are small enough to not stir up too much dust, and large enough to provide a useful feature).

Let’s start with a rebase without conflicts. Magit offers an interface again very similar to the Git command line. Everyone who has used git rebase --interactive will feel at home. The main benefit here is probably only the ease of selecting the commit onto which to rebase.

Once you selected the commits to squash, reordered the commits as you see fit, start rebasing by pressing [ctrl-c][ctrl-c] (this is no typo, you have to press [Ctrl+c] twice). If there is no conflict, the rebase is through.

In the case of conflicts Magit lists all files for which a manual conflict resolution is necessary. Depending on the tooling that is configured for merging, you can start the conflict resolution right from here. [E][m] will bring up a three-way merge screen (theirs, mine, common parent) for conflict resolution.

Once the conflict is resolved, update your status screen [g], and the conflict should disappear. Once all conflicts are gone, continue the once-started rebase with [r][r]. If you want to abort this rebase, hit [r][a] to abort, and everything will be undone up to the point where you started to rebase.

Lost a commit?

Commits are hard to get rid off. If you ever found yourself in the position that this change you definitely made, somehow got lost (usually after you rewrote your history through rebases, force pushes and the like), you are well advised to take a look at the ref logs (only the garbage collector of Git will remove them, if called). E.g., [y][r] brings up the (local) ref log of the current branch. You can inspect the commits or even cherry pick from them, if need be. You might never need to look here (I needed to do so only once, up to now), but it is very comforting to know that Git lets you still access them.

Feature complete?

Since all features of Git are accessible on the command line and Magit does well in keeping pace with all new features Git has to provide. There might be the time when even Magit users make use of the command line. This absolutely is encouraged! Since Magit does not have its own view on the repository, but utilizes Git for each interaction, issuing commands on the command line will never disrupt Magit or your use of it (don’t forget to refresh your screens though).

So, using Magit is not an all-in decision. It can as well be a helpful addition to your CLI. You might notice, however, that dropping back to the CLI will become less often be needed the more you get to know Magit.

Wrapping up

Git is a wonderful tool on its own. It lets you collaborate and organize your changes in a very flexible way. Git integration within editors and IDEs is useful, but often too restrictive to leverage the power of Git, making many a programmer return to the command line. Using zsh, aliases and shortcuts defined with Git itself, programmers try to reduce the friction necessarily felt, when interacting with the repository.

Magit reduces the friction of interacting with Git repositories even further and allows nearly friction-less TDD cycles, while retaining the full power of Git.

 

Footnotes

1 If you want to follow the examples, please clone this repository: https://github.com/gunther-bachmann/magit-blog. It holds a readme file with all prerequisites you need plus a couple of scripts that will make your life easier.

2 More emacs-like are [Ctrl+Space] and then movement commands as [Ctrl+p] or [Ctrl+n] (previous/next line).