Version control with Git

I've recently started using Git for version control of all my personal projects. It works so smoothly that I don't have any reservations about using version control. That means I commit small changes very often; as a result, I'm never afraid of leaving my projects in a wedged state, even if I'm making big changes.

This post only discusses Git basic usage. I'll write about its distributed features in future posts.

To start managing a directory with Git:

  1. Do the following to initialize the directory:

    git init

  2. If you have existing files you want to start managing, do

    git add .

    to add all the files in the directory, or

    git add FILE ...

    to add only particular files.

  3. Then do

    git commit

    Git prompts you for a log message and records your first commit. It will print a message of the following form:

    Created commit 1e169f6: Add new file.
     1 files changed, 15 insertions(+), 0 deletions(-)

    Congratulations!

Like SVN, Git store per-tree versions rather than per-file versions. However, instead of assigning version numbers like SVN does, Git assigns a unique hexadecimal identifier for each commit. Although these identifiers are long (40 characters), when you wish to refer to a particular commit, you only need to type as many characters are needed to make a unique prefix.

My typical workflow looks like this:

  1. Do some editing:

    emacs FILE ...

  2. Add files to Git's staging area:

    git add FILE ...

    Do this whether the files are new files you've added, or existing files you've modified.

  3. Commit the files in the staging area:

    git commit

    Again, Git prompts you for a log message and records a commit.

As a shortcut, git commit -a is equivalent to running git add with any modified files before running git commit. (It does not, however, pick up newly created files.)

The following commands are used to explore the project's history and current state:

  • git log shows recent commits.
  • git status shows which files are in the staging area, which files have been modified, and which newly created files are not managed by git.
  • git diff shows changes in your working copy.
  • git diff --cached shows changes in the staging area, that is, what will be committed when you do git commit.
  • git diff cd12..ab34 diffs the revisions cd12 and ab34.
  • git reset --hard restores your working copy state to that of the last commit.

The CVS version of GNU Emacs supports Git in VC, so it knows when your file is being managed by Git. When it is, you can use the following VC commands:

  • C-x v v commits the current file.
  • C-x v = shows a diff between your working copy and the last committed version (or between any two committed versions).
  • C-x v g displays an annotated version of the file showing, for each line, when that line was last modified, and a heat-map displaying older and newer code in different colors.

To install git on Ubuntu: type sudo apt-get install git-core.

Further reading: Everyday GIT with 20 commands or so, A tour of git.

Tweaking and recompiling .deb packages

Fixing a bug or experimenting with a program often requires going back to the source. Compiling the program yourself from a tarball usually means you lose the benefits of your package system, such as dependency tracking. Fortunately, Debian and Ubuntu include mechanisms to let you compile packages yourself without straying too far from the world of your package manager. It's actually really easy, because the repositories contain information about where to get source files and how to compile them. (I've tried these instructions on Ubuntu.)

  1. Install compilers and other dev tools:
    sudo apt-get install build-essential devscripts fakeroot
  2. Install the build-time dependencies for the package:
    sudo apt-get build-dep FOO
  3. Download the source for your package (to the current directory):
    apt-get source foo
    At this point, you can make whatever modifications you wish to the source.
  4. Compile the package:
    cd FOO-6; debuild -us -uc
    To compile with debugging information, use
    export DEB_BUILD_OPTIONS="debug nostrip noopt"
  5. You now have a shiny new .deb file. Install it with
    dpkg --install FOO_6-1_i386.deb
    Congratulations!

Emacs 22

Emacs 22 has been released. From the Emacs web site:

Emacs version 22 includes GTK+ toolkit support, enhanced mouse support, a new keyboard macro system, improved Unicode support, and drag-and-drop operation on X, plus many new modes and packages including a graphical user interface to GDB, Python mode, the mathematical tool Calc, the remote file editing system Tramp, and more.

Source downloads are available from GNU. Hopefully, most GNU/Linux distributions will package Emacs 22 soon.

Windows users can download Emacs 22 precompiled (installation instructions).

Simple version control with RCS

I had to write quite a bit of code for one of my projects this term. To keep the project manageable, I wanted a simple way to take snapshots of my files at particular milestones, so I could back up and look at previous snapshots if I ever screwed up my code. I ended up doing this with RCS.

RCS is best for situations where a single user is modifying files locally. For remote repositories or when you need to coordinate actions between multiple users, you should use another version control system.

To set up RCS in a directory:

  • Create a subdirectory named RCS.
  • To manage FILE using RCS, run $ ci -u FILE. Type a description of the file when prompted.

Emacs can deal with RCS-managed files out of the box. Here's a typical workflow:

  • Open an RCS-managed file in Emacs. Emacs automatically recognizes that the file is under RCS and displays the version number in the mode line.
  • Lock the file for editing with C-x v v.
  • Make your changes.
  • When you're at a milestone, commit your changes with C-x v v. Emacs prompts you for a commit message. Enter it in the provided buffer and press C-c C-c.
  • To continue editing the file, lock it again wih C-x v v and proceed as above.

Now you get to use the other cool features provided by the VC facility in Emacs:

  • C-x v = displays a diff between your current version and the previous committed version. C-u C-x v = lets you diff any two previous versions of the file. I like to use unified diffs whenever possible, so I have (setf vc-diff-switches "-u").
  • C-x v ~ prompts you for a version number and displays that version of the file in a new buffer.
  • C-x v g displays an annotated version of the file showing, for each line, when that line was last modified, and a heat-map displaying older and newer code in different colors.

Bridging the gap between Emacs and Gnome

I use Emacs whenever I can (which is, nowadays, for almost all the work I do) but I still need to switch away occasionally for some apps. Usually this involves starting up Nautilus, navigating to a directory, finding some files (e.g. PDFs or web pages), and double-clicking them. I wrote some Emacs functions to do the equivalent from Dired, avoiding the need to use Nautilus at all. This makes Dired a lot more useful as a file manager/browser for all kinds of files, not just text files.

gnome-open opens a file using the same application which would have been used to open it had you double-clicked it in Nautilus (Evince for PDFs, OpenOffice for OO.o docs, etc.). The following function takes a file and calls gnome-open on it:

(defun gnome-open-file (filename)
  "gnome-opens the specified file."
  (interactive "fFile to open: ")
  (let ((process-connection-type nil))
    (start-process "" nil "/usr/bin/gnome-open" filename)))

In a Dired buffer, the following function gnome-opens the file on which your cursor is sitting:

(defun dired-gnome-open-file ()
  "Opens the current file in a Dired buffer."
  (interactive)
  (gnome-open-file (dired-get-file-for-visit)))

I bound it to 'E':

(add-hook 'dired-mode-hook (lambda () (local-set-key "E" 'dired-gnome-open-file)))

dabbrev-expand

I discovered this real key-saver a few weeks ago. It makes Emacs appear to be positively psychic.

M-/ or dabbrev-expand completes the word you are in the middle of typing by looking at other words in the buffer. If no match is found it looks in other buffers. So dabbrev can complete the word or name you are typing on the basis of anything else you are reading in Emacs: the text you've previously typed, code or text in other files you have open, even documentation or web pages you have open.

If dabbrev completes your text incorrectly, you can press M-/ repeatedly to get other possible completions.

"The Guided Tour of Emacs"

An illustrated guide based on material from my class Being Productive With Emacs now appears on gnu.org, linked from the Emacs home page.

Read The Guided Tour of Emacs.