Commits

Alex Efros committed 4a41806 Merge

merge upstream

Comments (0)

Files changed (30)

 ~$
 \.o$
 ^vcprompt$
-
-^tests/\w+-repo(\.tar)?$
+^aclocal
+^autom4te\.cache$
+^config\.
+^configure$
+^Makefile$
+^stamp-h
+^tests/\w+-repo.*(\.tar)?$
+^dist$
+605e46d0d92ed22c297b10ba0f6d6ea0100d94b7 1.0
+d70e0dfc3e0e9a5684846fc669c900f466420e6a 1.1
+e0b3c95ad3c657dc9269cd0b0f584587369cc49c 1.2

Makefile

-
-CFLAGS = -Wall -Wextra -Wno-unused-parameter -g -O2
-
-headers = $(wildcard src/*.h)
-sources = $(wildcard src/*.c)
-objects = $(subst .c,.o,$(sources))
-
-vcprompt: $(objects)
-	$(CC) -o $@ $(objects)
-
-# build a standalone version of capture_child() library for testing
-src/capture: src/capture.c src/capture.h src/common.c src/common.h
-	$(CC) -DTEST_CAPTURE $(CFLAGS) -o $@ src/capture.c src/common.c
-
-# Maximally pessimistic view of header dependencies.
-$(objects): $(headers)
-
-.PHONY: check check-simple check-hg check-git check-fossil
-check: check-simple check-hg check-git check-fossil
-
-hgrepo = tests/hg-repo.tar
-gitrepo = tests/git-repo.tar
-fossilrepo = tests/fossil-repo
-
-check-simple: vcprompt
-	cd tests && ./test-simple
-
-check-hg: vcprompt $(hgrepo)
-	cd tests && ./test-hg
-
-$(hgrepo): tests/setup-hg
-	cd tests && ./setup-hg
-
-check-git: vcprompt $(gitrepo)
-	cd tests && ./test-git
-
-$(gitrepo): tests/setup-git
-	cd tests && ./setup-git
-
-check-fossil: vcprompt $(fossilrepo)
-	cd tests && ./test-fossil
-
-$(fossilrepo): tests/setup-fossil
-	cd tests && ./setup-fossil
-
-clean:
-	rm -f $(objects) vcprompt $(hgrepo) $(gitrepo)
-
-DESTDIR =
-PREFIX = /usr/local
-.PHONY: install
-install: vcprompt
-	install -d $(DESTDIR)$(PREFIX)/bin
-	install -m 755 vcprompt $(DESTDIR)$(PREFIX)/bin
-	install -m 755 vcprompt-hgst $(DESTDIR)$(PREFIX)/bin
+CC = @CC@
+CPPFLAGS = @CPPFLAGS@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+
+headers = $(wildcard src/*.h) config.h
+sources = $(wildcard src/*.c)
+objects = $(subst .c,.o,$(sources))
+
+vcprompt: $(objects) Makefile
+	$(CC) $(LDFLAGS) -o $@ $(objects) $(LIBS)
+
+Makefile: Makefile.in configure
+	./configure -C
+
+config.h: config.h.in
+	rm -f $@
+	./configure -C
+
+configure: configure.ac
+	autoconf
+
+# build a standalone version of capture_child() library for testing
+src/capture: src/capture.c src/capture.h src/common.c src/common.h
+	$(CC) -DTEST_CAPTURE $(CFLAGS) -o $@ src/capture.c src/common.c
+
+# Maximally pessimistic view of header dependencies.
+$(objects): $(headers) Makefile
+
+.PHONY: check check-simple check-hg check-git check-svn check-fossil grind
+check: check-simple check-hg check-git check-svn check-fossil
+
+hgrepo = tests/hg-repo.tar
+gitrepo = tests/git-repo.tar
+svnrepos = tests/svn-repo-1.tar tests/svn-repo-2.tar
+fossilrepo = tests/fossil-repo
+
+check-simple: vcprompt
+	cd tests && ./test-simple
+
+check-hg: vcprompt $(hgrepo)
+	cd tests && ./test-hg
+
+$(hgrepo): tests/setup-hg
+	cd tests && ./setup-hg
+
+check-git: vcprompt $(gitrepo)
+	cd tests && ./test-git
+
+$(gitrepo): tests/setup-git
+	cd tests && ./setup-git
+
+check-svn: vcprompt $(svnrepos)
+	cd tests && ./test-svn
+
+$(svnrepos): tests/setup-svn
+	cd tests && ./setup-svn
+
+check-fossil: vcprompt $(fossilrepo)
+	cd tests && ./test-fossil
+
+$(fossilrepo): tests/setup-fossil
+	cd tests && ./setup-fossil
+
+grind: check
+	make check VCPVALGRIND=y
+
+clean:
+	rm -f $(objects) vcprompt $(hgrepo) $(gitrepo) $(fossilrepo)
+
+DESTDIR =
+PREFIX = /usr/local
+BINDIR = $(DESTDIR)$(PREFIX)/bin
+MANDIR = $(DESTDIR)$(PREFIX)/man/man1
+
+.PHONY: install
+install: vcprompt
+	install -d $(BINDIR) $(MANDIR)
+	install vcprompt $(BINDIR)
+	install vcprompt-hgst $(BINDIR)
+	install vcprompt.1 $(MANDIR)
+
+.PHONY: dist
+dist: configure
+	[ "$$ver" ] || (echo "\$$ver not set" >&2; exit 1)
+	hg archive -r $$ver -t files -X ".hg*" dist/vcprompt-$$ver
+	install -m 0755 configure dist/vcprompt-$$ver/configure
+	(cd dist && tar -czf vcprompt-$$ver.tar.gz vcprompt-$$ver)
+	rm -rf dist/vcprompt-$$ver
-vcprompt
-========
+vcprompt: version control information in your prompt
+====================================================
 
-vcprompt is a little C program that prints a short string, designed to
-be included in your prompt, with barebones information about the current
-working directory for various version control systems.  It is designed
-to be small and lightweight rather than comprehensive.
+vcprompt is a little C program that prints a short string with
+barebones information about the current working directory for various
+version control systems. You configure your shell to include the
+output of vcprompt in your prompt, and you get version control
+information in your prompt.
 
-Currently, it has varying degrees of recognition for Mercurial, Git,
-CVS, and Subversion working copies.
+vcprompt is designed to be small and lightweight rather than
+comprehensive. Currently, it has varying degrees of support for
+Mercurial, Git, Subversion, CVS, and Fossil working copies.
 
-vcprompt has no external dependencies: it does everything with the
-standard C library and POSIX calls.  It should work on any
-POSIX-compliant system with a C99 compiler.
+vcprompt has minimal dependencies: it does as much as it can with the
+standard C library and POSIX calls. It should work on any
+POSIX-compliant system with a C99 compiler. Some optional features
+require external libraries (see "Dependencies" below).
 
-To compile vcprompt:
+To build vcprompt from the source tarball:
 
+  ./configure
+  make
+
+If you're building in a source checkout, you also need GNU autoconf:
+
+  autoconf
+  ./configure
   make
 
 (vcprompt requires GNU make, so if you are using a BSD variant where
 the default make is BSD make, you will need to install GNU make and
 run "gmake".)
 
-To install it:
+To install it in your home directory:
 
   make install PREFIX=$HOME
 
+To make life easier for packagers, the Makefile also supports DESTDIR:
+
+  make install DESTDIR=/tmp/packageroot PREFIX=/usr
+
+Please report build failures to the development mailing list,
+vcprompt-devel@googlegroups.com.
+
+vcprompt includes a fairly comprehensive test suite. If you want to
+run it, see "Testing" below.
+
+
+Dependencies
+============
+
+vcprompt requires GNU make to build.
+
+vcprompt requires GNU autoconf to build from a source checkout (but
+not from a source tarball).
+
+Support for Subversion >= 1.7 requires SQLite 3. If it's not present on
+the build system, vcprompt will support Subversion <= 1.6. Either way,
+the build should succeed and the tests should pass. To install the
+required files:
+
+  sudo apt-get install libsqlite3-dev   # Debian, Ubuntu
+  sudo yum install libsqlite3x-devel    # Fedora, Red Hat
+
+To see which features are built-in to your vcprompt binary, run
+
+  ./vcprompt -F
+
+
+Configuration
+=============
+
+(For more details, see the man page.)
+
 To use it with bash, just call it in PS1:
 
-  PS1='\u@\h:\w $(vcprompt)\$'
+  PS1='\u@\h $(vcprompt)\$ '
 
 To use it with zsh, you need to enable shell option PROMPT_SUBST, and
 then do similarly to bash:
   setopt prompt_subst
   PROMPT='[%n@%m] [%~] $(vcprompt)'
 
-vcprompt prints nothing if the current directory is not managed by a
-recognized VC system.
-
 
 Format Strings
 ==============
 
-You can customize the output of vcprompt using format strings, which can
-be specified either on the command line or in the VCPROMPT_FORMAT
-environment variable.  For example:
+You can customize the output of vcprompt using format strings, which
+can be specified either on the command line or in the VCPROMPT_FORMAT
+environment variable. For example:
 
   vcprompt -f "%b"
 
 
 All other characters are expanded as-is.
 
-The default format string is
+(For more details, see the man page.)
 
-  [%n:%b]
 
-which is notable because it works with every supported VC system.  In
-fact, some features are meaningless with some systems: there is no
-single current revision with CVS, for example.  (And there is no good
-way to quickly find out if there are any uncommitted changes or unknown
-files, for that matter.)
+Testing
+=======
+
+To run vcprompt's test suite:
+
+  make check
+
+Test failures should be loud and obvious. Please report any test
+failures to the development mailing list:
+
+  vcprompt-devel@googlegroups.com.
+
+To check for memory errors, you can run vcprompt's test suite under
+valgrind:
+
+  make grind
+
+Obviously, this requires that you have valgrind installed.
+
+Testing multiple versions of the same tool
+------------------------------------------
+
+Subversion changes its working copy format every couple of years, so
+vcprompt supports three formats: the pre-1.4 XML format, the 1.4..1.6
+plain-text format, and the post-1.7 SQLite format. Actually testing
+these requires that you have different versions of Subversion on hand,
+each installed in a separate prefix.
+
+For example, I keep multiple versions in /usr/local/subversion-1.x, so
+I can test them like this:
+
+  rm -f tests/svn-repo*.tar && make check-svn TOOLPATH=/usr/local/subversion-1.6/bin
+  rm -f tests/svn-repo*.tar && make check-svn TOOLPATH=/usr/local/subversion-1.7/bin
+
+Actually *building* multiple versions of Subversion is harder than you
+would believe. (In fact, I've been unable to build anything older than
+1.5, so vcprompt's support for pre-1.4 working copies is currently
+untested.)
+
+TOOLPATH is supported for all tools; I also keep multiple versions of
+Mercurial around, so I can test vcprompt against them:
+
+  rm -f tests/hg-repo.tar && make check-svn TOOLPATH=/usr/local/mercurial-2.4/bin
+  rm -f tests/hg-repo.tar && make check-svn TOOLPATH=/usr/local/mercurial-2.5/bin
+  [...etc...]
 
 
 Contributing
 
 Patches are welcome.  Please follow these guidelines:
 
-  * Ensure that the tests pass before and after your patch.
-    If you cannot run the tests on a POSIX-compliant system,
-    that is a bug: please let me know.
+  * Ensure that the tests pass before and after your patch. To run the
+    tests quickly:
 
-  * If at all possible, add a test whenever you fix a bug or implement a
-    feature.  If you can write a test that has no dependencies (e.g. no
-    need to execute "git" or "hg" or whatever), add it to
-    tests/test-simple.  Otherwise, add it to the appropriate VC-specific
-    test script, e.g. tests/test-git if it needs to be able to run git.
+      make check
 
-  * Keep the dependencies minimal: preferably just C99 and POSIX.
-    If you need to run an external executable, make sure it makes
-    sense: e.g. it's OK to run "git" in a git working directory, but
-    *only* if we already know we are in a git working directory.
+    To run the tests using valgrind (detect memory leaks):
 
-  * Performance matters!  I wrote vcprompt so that people wouldn't have
-    to spawn and initialize and entire Python or Perl interpreter every
-    time they execute a new shell command.  Using system() to turn
-    around and spawn external commands -- especially ones that involve a
+      make grind
+
+    If you cannot run the tests on a POSIX-compliant system, that is a
+    bug: please let me know.
+
+  * If at all possible, add a test whenever you fix a bug or implement
+    a feature. If you can write a test that has no dependencies (e.g.
+    no need to execute "git" or "hg" or whatever), add it to
+    tests/test-simple. Otherwise, add it to the appropriate
+    VC-specific test script, e.g. tests/test-git if it needs to be
+    able to run git.
+
+  * Keep the dependencies minimal: preferably just C99 and POSIX. If
+    you need to run an external executable, make sure it makes sense:
+    e.g. it's OK to run "git" in a git working directory, but *only*
+    if we already know we are in a git working directory.
+
+  * Performance matters! I wrote vcprompt so that people wouldn't have
+    to spawn and initialize an entire Python or Perl interpreter every
+    time they get a new shell prompt. Using system() to turn around
+    and spawn external commands -- especially ones that involve a
     relatively large runtime penalty like Python scripts -- misses the
     point of vcprompt.
 
     In fact, you'll find that vcprompt contains hacky little
     reimplementations of select bits and pieces of Mercurial, git,
     Subversion, and CVS precisely in order to avoid running external
-    commands.  (And, in the case of Subversion, to avoid depending on a
+    commands. (And, in the case of Subversion, to avoid depending on a
     large, complex library.)
 
   * Stick with my coding style:
     - C99 comments and variable declarations are OK, at least until
       someone complains that their compiler cannot handle them
 
-  * Feel free to add yourself to the contributors list below.
-    (If you don't do it, I'll probably forget.)
+  * Feel free to add yourself to the contributors list below. (If you
+    don't do it, I'll probably forget.)
 
-  
+
 Author Contact
 ==============
 
 repositories:
 
   http://hg.gerg.ca/vcprompt/
-  http://bitbucket.org/gward/vcprompt/overview
+  http://bitbucket.org/gward/vcprompt/
 
 
 Other Contributors
   Yuya Nishihara
   KOIE Hidetaka
   Armin Ronacher
+  Jordi Fita
+  Gregg Lind
+  Jakob Kramer
+  Robson Roberto Souza Peixoto
 
 Thanks to all!
 
 Copyright & License
 ===================
 
-Copyright (C) 2009, 2010, Gregory P. Ward and contributors.
+Copyright (C) 2009-2013, Gregory P. Ward and contributors.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
+/* config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#undef HAVE_ARPA_INET_H
+
+/* Define to 1 if you have the `dup2' function. */
+#undef HAVE_DUP2
+
+/* Define to 1 if you have the `fork' function. */
+#undef HAVE_FORK
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
+   to 0 otherwise. */
+#undef HAVE_MALLOC
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if your system has a GNU libc compatible `realloc' function,
+   and to 0 otherwise. */
+#undef HAVE_REALLOC
+
+/* Define to 1 if you have the `select' function. */
+#undef HAVE_SELECT
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the `strchr' function. */
+#undef HAVE_STRCHR
+
+/* Define to 1 if you have the `strdup' function. */
+#undef HAVE_STRDUP
+
+/* Define to 1 if you have the `strerror' function. */
+#undef HAVE_STRERROR
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the `strstr' function. */
+#undef HAVE_STRSTR
+
+/* Define to 1 if you have the `strtol' function. */
+#undef HAVE_STRTOL
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#undef HAVE_SYS_TIME_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to 1 if you have the `vfork' function. */
+#undef HAVE_VFORK
+
+/* Define to 1 if you have the <vfork.h> header file. */
+#undef HAVE_VFORK_H
+
+/* Define to 1 if `fork' works. */
+#undef HAVE_WORKING_FORK
+
+/* Define to 1 if `vfork' works. */
+#undef HAVE_WORKING_VFORK
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+#undef HAVE_SQLITE3
+#undef HAVE_SQLITE3_H
+#undef HAVE_LIBSQLITE3
+
+#if HAVE_SQLITE3_H && HAVE_LIBSQLITE3
+#  define HAVE_SQLITE3 1
+#endif
+
+/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,
+   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+   #define below would cause a syntax error. */
+#undef _UINT32_T
+
+/* Define to rpl_malloc if the replacement function should be used. */
+#undef malloc
+
+/* Define to `int' if <sys/types.h> does not define. */
+#undef mode_t
+
+/* Define to `int' if <sys/types.h> does not define. */
+#undef pid_t
+
+/* Define to rpl_realloc if the replacement function should be used. */
+#undef realloc
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+#undef size_t
+
+/* Define to `int' if <sys/types.h> does not define. */
+#undef ssize_t
+
+/* Define to the type of an unsigned integer type of width exactly 32 bits if
+   such a type exists and the standard includes do not define it. */
+#undef uint32_t
+
+/* Define as `fork' if `vfork' does not work. */
+#undef vfork
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.63])
+AC_INIT([vcprompt], [1.1], [vcprompt-devel@googlegroups.com])
+AC_CONFIG_SRCDIR([src/fossil.h])
+AC_CONFIG_HEADERS([config.h])
+
+# Extra options
+AC_ARG_WITH([sqlite3],
+            AS_HELP_STRING([--with-sqlite3=PREFIX],
+                           [use sqlite3 in PREFIX (for svn >= 1.7)]),
+	    [],
+	    [with_sqlite3=check])
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_CC_C99
+AC_PROG_MAKE_SET
+
+# crank up the warnings if building with GCC
+if echo $CC | grep -q gcc; then
+    CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter"
+fi
+
+# Checks for header files.
+AC_CHECK_HEADERS([arpa/inet.h stdlib.h string.h sys/time.h unistd.h])
+
+# Checks for third-party libraries.
+if test "$with_sqlite3" = "check" -o "$with_sqlite3" = "yes"; then
+    AC_CHECK_HEADERS([sqlite3.h])
+    AC_CHECK_LIB(sqlite3, sqlite3_open_v2)
+elif test "$with_sqlite3" != "no"; then
+    CPPFLAGS="$CPPFLAGS -I${with_sqlite3}/include"
+    LDFLAGS="$LDFLAGS -L${with_sqlite3}/lib"
+    AC_CHECK_HEADERS([sqlite3.h])
+    AC_CHECK_LIB(sqlite3, sqlite3_open_v2)
+fi
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_MODE_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+AC_TYPE_UINT32_T
+
+# Checks for library functions.
+AC_FUNC_FORK
+AC_FUNC_MALLOC
+AC_FUNC_REALLOC
+AC_CHECK_FUNCS([dup2 select strchr strdup strerror strstr strtol])
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
+vcprompt release process
+========================
+
+* make sure "hg summary --remote" is clean (nothing uncommitted,
+  synced with remote)
+
+* read through README.txt and make sure it's still valid
+
+* read the man page and make sure it's still valid:
+
+    groff -Tascii -man vcprompt.1 | less
+
+* commit and push any required changes
+
+* clone on at least one different machine and run the tests:
+
+    hg clone http://hg.gerg.ca/vcprompt
+    cd vcprompt
+    autoconf && ./configure
+    make grind      # requires valgrind
+    make check      # if valgrind not installed
+
+* select the version number, e.g.:
+
+    ver=x.y.z
+
+* tag the release and build the source tarball
+
+    hg tag $ver
+    make dist ver=$ver
+
+* test the tarball locally
+
+    cp dist/vcprompt-$ver.tar.gz /tmp
+    cd /tmp
+    tar -xzf vcprompt-$ver.tar.gz
+    cd vcprompt-$ver
+    ./configure
+    make grind
+
+* copy to at least one different machine and test there
+
+    host=<something>
+    scp /tmp/vcprompt-$ver.tar.gz $host:/tmp
+    ssh $host "cd /tmp && tar -xzf vcprompt-$ver.tar.gz && cd vcprompt-$ver && make check"
+
+* push outgoing (tag) changeset
+
+    hg push
+
+* upload tarball to bitbucket from
+
+    https://bitbucket.org/gward/vcprompt/downloads
+
+* announce to mailing list (vcprompt-devel@googlegroups.com), e.g.:
+
+"""
+Hi all --
+
+I am pleased to announce the release of vcprompt 1.1. You can download
+the tarball from
+
+  https://bitbucket.org/gward/vcprompt/downloads/vcprompt-1.1.tar.gz
+
+Changes in this release:
+
+* add support for reporting modified/unknown files in Mercurial working
+  dirs (%m and %u format specifiers) (thanks to Geoff Lane)
+
+* tweaks to "make install" (thanks to Jakob Kramer)
+
+If you have any problems, please report them on this mailing list, or
+open a bug report on bitbucket.
+
+        Greg
+"""
+
+* announce on freecode.com
+  (https://freecode.com/projects/vcprompt/releases/new)
+
+* announce on twitter
+/*
+ * Copyright (C) 2012-2013, Gregory P. Ward and contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
 #include "capture.h"
 #include "common.h"
 
     capture_t *result = malloc(sizeof(capture_t));
     if (result == NULL)
         goto err;
-    init_dynbuf(&result->stdout, bufsize);
-    if (result->stdout.buf == NULL)
+    init_dynbuf(&result->childout, bufsize);
+    if (result->childout.buf == NULL)
         goto err;
 
-    init_dynbuf(&result->stderr, bufsize);
-    if (result->stderr.buf == NULL)
+    init_dynbuf(&result->childerr, bufsize);
+    if (result->childerr.buf == NULL)
         goto err;
 
     return result;
 free_capture(capture_t *result)
 {
     if (result != NULL) {
-        if (result->stdout.buf != NULL)
-            free(result->stdout.buf);
-        if (result->stderr.buf != NULL)
-            free(result->stderr.buf);
+        if (result->childout.buf != NULL)
+            free(result->childout.buf);
+        if (result->childerr.buf != NULL)
+            free(result->childerr.buf);
         free(result);
     }
 }
         int maxfd = -1;
         fd_set child_fds;
         FD_ZERO(&child_fds);
-        if (!result->stdout.eof) {
+        if (!result->childout.eof) {
             FD_SET(cstdout, &child_fds);
             maxfd = cstdout;
         }
-        if (!result->stderr.eof) {
+        if (!result->childerr.eof) {
             FD_SET(cstderr, &child_fds);
             maxfd = cstderr;
         }
             break;
 
         if (FD_ISSET(cstdout, &child_fds)) {
-            if (read_dynbuf(cstdout, &result->stdout) < 0)
+            if (read_dynbuf(cstdout, &result->childout) < 0)
                 goto err;
         }
         if (FD_ISSET(cstderr, &child_fds)) {
-            if (read_dynbuf(cstderr, &result->stderr) < 0)
+            if (read_dynbuf(cstderr, &result->childerr) < 0)
                 goto err;
         }
-        done = result->stdout.eof && result->stderr.eof;
+        done = result->childout.eof && result->childerr.eof;
     }
 
     int status;
     if (result->signal != 0)
         debug("child process %s killed by signal %d",
               file, result->signal);
-    if (result->stderr.len > 0)
+    if (result->childerr.len > 0)
         debug("child process %s wrote to stderr:\n%s",
-              file, result->stderr.buf);
+              file, result->childerr.buf);
 
     return result;
  err:
         return 1;
     }
     printf("read %ld bytes from child stdout: >%s<\n",
-           result->stdout.len, result->stdout.buf);
+           result->childout.len, result->childout.buf);
     printf("read %ld bytes from child stderr: >%s<\n",
-           result->stderr.len, result->stderr.buf);
+           result->childerr.len, result->childerr.buf);
     status = result->status;
     printf("child status = %d, signal =%d\n", status, result->signal);
     free_capture(result);
 } dynbuf;
 
 typedef struct {
-    dynbuf stdout;
-    dynbuf stderr;
+    dynbuf childout;
+    dynbuf childerr;
     int status;                 /* exit status that child passed (if any) */
     int signal;                 /* signal that killed the child (if any) */
 } capture_t;
  * be file (unless you are playing funny games) and the last element
  * of argv must be NULL.
  *
- * On return, capture->stdout.buf will be the child's stdout, and
- * capture->stdout.len the number of bytes read. capture->stdout.buf
+ * On return, capture->childout.buf will be the child's stdout, and
+ * capture->childout.len the number of bytes read. capture->childout.buf
  * is null terminated, so as long as the child's output is textual,
  * you can use it as a string. Similarly, child's stderr is in
- * capture->stderr.buf and capture->stderr.len.
+ * capture->childerr.buf and capture->childerr.len. Caller is responsible
+ * for freeing the result with free_capture().
  */
 capture_t *
 capture_child(const char *file, char *const argv[]);
 /*
- * Copyright (C) 2009, 2010, Gregory P. Ward and contributors.
+ * Copyright (C) 2009-2013, Gregory P. Ward and contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
 
 #include "common.h"
 
-result_t* init_result()
+result_t*
+init_result()
 {
     return (result_t*) calloc(1, sizeof(result_t));
 }
 
-void free_result(result_t* result)
+void
+free_result(result_t *result)
 {
+    free(result->branch);
     free(result->revision);
-    free(result->branch);
+    free(result->patch);
+    free(result->full_revision);
     free(result);
 }
 
 static options_t* _options = NULL;
 
-void set_options(options_t* options)
+void
+set_options(options_t *options)
 {
     _options = options;
 }
 
-int debug_mode()
+int
+debug_mode()
 {
     return _options->debug;
 }
 
-int result_set_revision(result_t* result, const char *revision, int len)
+int
+result_set_revision(result_t *result, const char *revision, int len)
 {
     if (result->revision)
         free(result->revision);
     return !!result->revision;
 }
 
-int result_set_branch(result_t* result, const char *branch)
+int
+result_set_branch(result_t *result, const char *branch)
 {
     if (result->branch)
         free(result->branch);
 
 vccontext_t*
 init_context(const char *name,
-             options_t* options,
+             options_t *options,
              int (*probe)(vccontext_t*),
              result_t* (*get_info)(vccontext_t*))
 {
-    vccontext_t* context = (vccontext_t*) calloc(1, sizeof(vccontext_t));
+    vccontext_t *context = (vccontext_t*) calloc(1, sizeof(vccontext_t));
     context->options = options;
     context->name = name;
     context->probe = probe;
 }
 
 void
-free_context(vccontext_t* context)
+free_context(vccontext_t *context)
 {
+    free(context->rel_path);
     free(context);
 }
 
 void
-debug(char* fmt, ...)
+debug(char *fmt, ...)
 {
     va_list args;
 
 }
 
 static int
-_testmode(char* name, mode_t bits, char what[])
+_testmode(char *name, mode_t bits, char what[])
 {
     struct stat statbuf;
     if (stat(name, &statbuf) < 0) {
     }
     if ((statbuf.st_mode & bits) == 0) {
         debug("'%s' not a %s", name, what);
-	return 0;
+        return 0;
     }
     return 1;
 }
 
 int
-isdir(char* name)
+isdir(char *name)
 {
     return _testmode(name, S_IFDIR, "directory");
 }
 
 int
-isfile(char* name)
+isfile(char *name)
 {
     return _testmode(name, S_IFREG, "regular file");
 }
 
 int
-read_first_line(char* filename, char* buf, int size)
+read_first_line(char *filename, char *buf, int size)
 {
-    FILE* file;
+    FILE *file;
 
     file = fopen(filename, "r");
     if (file == NULL) {
 }
 
 int
-read_last_line(char* filename, char* buf, int size)
+read_last_line(char *filename, char *buf, int size)
 {
-    FILE* file;
+    FILE *file;
 
     file = fopen(filename, "r");
     if (file == NULL) {
 }
 
 int
-read_file(const char* filename, char* buf, int size)
+read_file(const char *filename, char *buf, int size)
 {
-    FILE* file;
+    FILE *file;
     int readsize;
 
     file = fopen(filename, "r");
 }
 
 void
-chop_newline(char* buf)
+chop_newline(char *buf)
 {
     int len = strlen(buf);
     if (buf[len-1] == '\n')
 }
 
 void
-dump_hex(const char* data, char* buf, int datasize)
+dump_hex(char *dest, const char *data, int datasize)
 {
     const char HEXSTR[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
                              '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
     int i;
 
     for (i = 0; i < datasize; ++i) {
-        buf[i * 2] = HEXSTR[(unsigned char) data[i] >> 4];
-        buf[i * 2 + 1] = HEXSTR[(unsigned char) data[i] & 0x0f];
+        dest[i * 2] = HEXSTR[(unsigned char) data[i] >> 4];
+        dest[i * 2 + 1] = HEXSTR[(unsigned char) data[i] & 0x0f];
     }
 
-    buf[i * 2] = '\0';
+    dest[i * 2] = '\0';
 }
 
 void
  */
 typedef struct {
     int debug;
-    char* format;                       /* e.g. "[%b%u%m]" */
+    char *format;                       /* e.g. "[%b%u%m]" */
     int show_branch;                    /* show current branch? */
     int show_revision;                  /* show current revision? */
+    int show_patch;                     /* show patch name? */
     int show_unknown;                   /* show ? if unknown files? */
     int show_modified;                  /* show + if local changes? */
+    unsigned int timeout;               /* timeout in milliseconds */
+    int show_features;                  /* list builtin features */
 } options_t;
 
 /* What we figured out by analyzing the working dir: info that
  * the user's prompt.
  */
 typedef struct {
-    char* branch;                       /* name of current branch */
-    char* revision;                     /* current revision */
+    char *branch;                       /* name of current branch */
+    char *revision;                     /* current revision ID */
+    char *patch;                        /* name of current patch */
     int unknown;                        /* any unknown files? */
     int modified;                       /* any local changes? */
+
+    /* revision ID in VC-specific, not-necessarily-human-readable form */
+    void *full_revision;
 } result_t;
 
-int result_set_revision(result_t* result, const char *revision, int len);
-int result_set_branch(result_t* result, const char *branch);
+int result_set_revision(result_t *result, const char *revision, int len);
+int result_set_branch(result_t *result, const char *branch);
 
 typedef struct vccontext_t vccontext_t;
 struct vccontext_t {
     const char *name;                   /* name of the VC system */
-    options_t* options;
+    options_t *options;
+
+    /* Path from wc root to cwd: eg. if we're in foo/bar/baz, but wc
+     * metadata is in foo/.hg (or whatever), rel_path is "bar/baz".
+     * Only makes sense for VC systems with a single metadata
+     * directory for a whole tree: git, hg, svn >= 1.7, etc.
+     */
+    char *rel_path;
 
     /* context methods */
     int (*probe)(vccontext_t*);
 
 vccontext_t*
 init_context(const char *name,
-             options_t* options,
+             options_t *options,
              int (*probe)(vccontext_t*),
              result_t* (*get_info)(vccontext_t*));
+
 void
-free_context(vccontext_t* context);
-    
+free_context(vccontext_t *context);
+
 result_t*
 init_result();
 
  * debug mode is on (e.g. from the command line -d).
  */
 void
-debug(char* fmt, ...);
+debug(char *fmt, ...);
 
 /* stat() the specified file and return true if it is a directory, false
  * if stat() failed or it is not a directory.
  */
 int
-isdir(char* name);
+isdir(char *name);
 
 /* stat() the specified file and return true if it is a regular file,
  * false if stat() failed or it is not a regular file.
  */
 int
-isfile(char* name);
+isfile(char *name);
 
 /* Open the specified file, read the first line (up to size-1 chars) to
  * buf, and close the file.  buf will not contain a newline.  Caller
  * i.e. only visible if running in debug mode.
  */
 int
-read_first_line(char* filename, char* buf, int size);
+read_first_line(char *filename, char *buf, int size);
 
 /* Open the specified file, reading and discarding every line except the
  * last.  The last line is written to buf (up to size-1 chars) without
  * Return value and error handling: same as read_first_line().
  */
 int
-read_last_line(char* filename, char* buf, int size);
+read_last_line(char *filename, char *buf, int size);
 
 /* Open and read the specified file to buf (up to size chars).  Caller
  * must allocate at least size chars for buf.  buf is assumed to be
  * handling: same as read_first_line().
  */
 int
-read_file(const char* filename, char* buf, int size);
+read_file(const char *filename, char *buf, int size);
 
 /* If the last char of buf is '\n', replace it with '\0', i.e. terminate
  * the string one char earlier.
  */
 void
-chop_newline(char* buf);
+chop_newline(char *buf);
 
-/* Encode datasize bytes of binary data to hex chars in buf.  Caller
- * must allocate at least datasize*2 + 1 chars for buf.
+/* Encode datasize bytes of binary data to hex chars in dest. Caller
+ * must allocate at least datasize * 2 + 1 chars for dest.
  */
 void
-dump_hex(const char* data, char* buf, int datasize);
+dump_hex(char *dest, const char *data, int datasize);
 
 /* Copy up to nchars chars from src to dest, stopping at the first
  * newline and terminating dest with a NUL char.  On return, it is
 /*
- * Copyright (C) 2009, 2010, Gregory P. Ward and contributors.
+ * Copyright (C) 2009-2013, Gregory P. Ward and contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
 #include "cvs.h"
 
 static int
-cvs_probe(vccontext_t* context)
+cvs_probe(vccontext_t *context)
 {
     return isfile("CVS/Entries");
 }
 
 static result_t*
-cvs_get_info(vccontext_t* context)
+cvs_get_info(vccontext_t *context)
 {
-    result_t* result = init_result();
+    result_t *result = init_result();
     char buf[1024];
 
     if (!read_first_line("CVS/Tag", buf, 1024)) {
             result_set_branch(result, buf + 1);
         }
         else {
-            /* non-branch sticky tag or sticky date */            
-           result_set_branch(result, "(unknown)");
+            /* non-branch sticky tag or sticky date */
+            result_set_branch(result, "(unknown)");
         }
     }
     return result;
 }
 
-vccontext_t* get_cvs_context(options_t* options)
+vccontext_t*
+get_cvs_context(options_t *options)
 {
     return init_context("cvs", options, cvs_probe, cvs_get_info);
 }
 
 #include "common.h"
 
-vccontext_t* get_cvs_context(options_t* options);
+vccontext_t *
+get_cvs_context(options_t *options);
 
 #endif
 #include "capture.h"
 
 static int
-fossil_probe(vccontext_t* context)
+fossil_probe(vccontext_t *context)
 {
     return isfile("_FOSSIL_") || isfile(".fslckout");
 }
 
 static result_t*
-fossil_get_info(vccontext_t* context)
+fossil_get_info(vccontext_t *context)
 {
-    result_t* result = init_result();
+    result_t *result = init_result();
     char *t;
     int tab_len = 14;
     char buf2[81];
         debug("unable to execute 'fossil status'");
         return NULL;
     }
-    char *cstdout = capture->stdout.buf;
+    char *cstdout = capture->childout.buf;
 
     if (context->options->show_branch) {
         if ((t = strstr(cstdout, "\ntags:"))) {
                             strstr(cstdout, "\nUPDATED") ||
                             strstr(cstdout, "\nMERGED"));
     }
+
+    cstdout = NULL;
+    free_capture(capture);
+
     if (context->options->show_unknown) {
         // This can't be read from 'fossil status' output
         char *argv[] = {"fossil", "extra", NULL};
             debug("unable to execute 'fossil extra'");
             return NULL;
         }
-        result->unknown = (capture->stdout.len > 0);
+        result->unknown = (capture->childout.len > 0);
+        free_capture(capture);
     }
 
     return result;
 }
 
-vccontext_t* get_fossil_context(options_t* options)
+vccontext_t*
+get_fossil_context(options_t *options)
 {
     return init_context("fossil", options, fossil_probe, fossil_get_info);
 }
 
 #include "common.h"
 
-vccontext_t* get_fossil_context(options_t* options);
+vccontext_t *
+get_fossil_context(options_t *options);
 
 #endif
 /*
- * Copyright (C) 2009, 2010, Gregory P. Ward and contributors.
+ * Copyright (C) 2009-2013, Gregory P. Ward and contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
 
 
 static int
-git_probe(vccontext_t* context)
+git_probe(vccontext_t *context)
 {
     return isdir(".git");
 }
 
 static result_t*
-git_get_info(vccontext_t* context)
+git_get_info(vccontext_t *context)
 {
-    result_t* result = init_result();
+    result_t *result = init_result();
     char buf[1024];
 
     if (!read_first_line(".git/HEAD", buf, 1024)) {
         debug("unable to read .git/HEAD: assuming not a git repo");
-        return NULL;
+        goto err;
     }
-    else {
-        char* prefix = "ref: refs/heads/";
-        int prefixlen = strlen(prefix);
 
-        if (context->options->show_branch || context->options->show_revision) {
-            int found_branch = 0;
-            if (strncmp(prefix, buf, prefixlen) == 0) {
-                /* yep, we're on a known branch */
-                debug("read a head ref from .git/HEAD: '%s'", buf);
-                if (result_set_branch(result, buf + prefixlen))
-                    found_branch = 1;
-            }
-            else {
-                /* if it's not a branch name, assume it is a commit ID */
-                debug(".git/HEAD doesn't look like a head ref: unknown branch");
-                result_set_branch(result, "(unknown)");
+    char *prefix = "ref: refs/heads/";
+    int prefixlen = strlen(prefix);
+
+    if (context->options->show_branch || context->options->show_revision) {
+        int found_branch = 0;
+        if (strncmp(prefix, buf, prefixlen) == 0) {
+            /* yep, we're on a known branch */
+            debug("read a head ref from .git/HEAD: '%s'", buf);
+            if (result_set_branch(result, buf + prefixlen))
+                found_branch = 1;
+        }
+        else {
+            /* if it's not a branch name, assume it is a commit ID */
+            debug(".git/HEAD doesn't look like a head ref: unknown branch");
+            result_set_branch(result, "(unknown)");
+            result_set_revision(result, buf, 12);
+        }
+        if (context->options->show_revision && found_branch) {
+            char buf[1024];
+            char filename[1024] = ".git/refs/heads/";
+            int nchars = sizeof(filename) - strlen(filename) - 1;
+            strncat(filename, result->branch, nchars);
+            if (read_first_line(filename, buf, 1024)) {
                 result_set_revision(result, buf, 12);
             }
-            if (context->options->show_revision && found_branch) {
-                char buf[1024];
-                char filename[1024] = ".git/refs/heads/";
-                int nchars = sizeof(filename) - strlen(filename) - 1;
-                strncat(filename, result->branch, nchars);
-                if (read_first_line(filename, buf, 1024)) {
-                    result_set_revision(result, buf, 12);
-                }
-            }
         }
-        if (context->options->show_modified) {
-            char *argv[] = {
-                "git", "diff", "--no-ext-diff", "--quiet", "--exit-code", NULL};
-            capture_t *capture = capture_child("git", argv);
-            result->modified = (capture->status == 1);
+    }
+    if (context->options->show_modified) {
+        char *argv[] = {
+            "git", "diff", "--no-ext-diff", "--quiet", "--exit-code", NULL};
+        capture_t *capture = capture_child("git", argv);
+        result->modified = (capture->status == 1);
 
-            /* any other outcome (including failure to fork/exec,
-               failure to run git, or diff error): assume no
-               modifications */
-            free_capture(capture);
-        }
-        if (context->options->show_unknown) {
-            char *argv[] = {
-                "git", "ls-files", "--others", "--exclude-standard", NULL};
-            capture_t *capture = capture_child("git", argv);
-            result->unknown = (capture != NULL && capture->stdout.len > 0);
+        /* any other outcome (including failure to fork/exec,
+           failure to run git, or diff error): assume no
+           modifications */
+        free_capture(capture);
+    }
+    if (context->options->show_unknown) {
+        char *argv[] = {
+            "git", "ls-files", "--others", "--exclude-standard", NULL};
+        capture_t *capture = capture_child("git", argv);
+        result->unknown = (capture != NULL && capture->childout.len > 0);
 
-            /* again, ignore other errors and assume no unknown files */
-            free_capture(capture);
-        }
+        /* again, ignore other errors and assume no unknown files */
+        free_capture(capture);
     }
 
     return result;
+
+ err:
+    free_result(result);
+    return NULL;
 }
 
-vccontext_t* get_git_context(options_t* options)
+vccontext_t*
+get_git_context(options_t *options)
 {
     return init_context("git", options, git_probe, git_get_info);
 }
 
 #include "common.h"
 
-vccontext_t* get_git_context(options_t* options);
+vccontext_t *
+get_git_context(options_t *options);
 
 #endif
 /*
- * Copyright (C) 2009, 2010, Gregory P. Ward and contributors.
+ * Copyright (C) 2009-2013, Gregory P. Ward and contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
 #if defined __BEOS__ && !defined __HAIKU__
 #include <ByteOrder.h>
 #include <arpa/inet.h>
 #endif
 
+#include "capture.h"
 #include "common.h"
 #include "hg.h"
 
 #define NODEID_LEN 20
 
 static int
-hg_probe(vccontext_t* context)
+hg_probe(vccontext_t *context)
 {
     return isdir(".hg");
 }
 
-static int sum_bytes(const unsigned char* data, int size)
+/* return true if data contains any non-zero bytes */
+static int
+non_zero(const unsigned char *data, int size)
 {
-    int i, sum = 0;
-    for (i = 0; i < size; ++i) {
-        sum += data[i];
+    int i;
+    for (i = 0; i < size; i++) {
+       if (data[i] != 0)
+           return 1;
     }
-    return sum;
+    return 0;
 }
 
-static int is_revlog_inlined(FILE *f)
+static int
+is_revlog_inlined(FILE *rlfile)
 {
     const unsigned int REVLOGNGINLINEDATA = 1 << 16;
     int revlog_ver;
     size_t origpos, rlen;
 
-    origpos = ftell(f);
-    rlen = fread(&revlog_ver, sizeof(revlog_ver), 1, f);
-    fseek(f, origpos, SEEK_SET);
+    origpos = ftell(rlfile);
+    rlen = fread(&revlog_ver, sizeof(revlog_ver), 1, rlfile);
+    fseek(rlfile, origpos, SEEK_SET);
 
     revlog_ver = ntohl(revlog_ver);
     return (rlen == 1) ? revlog_ver & REVLOGNGINLINEDATA : 0;
 } csinfo_t;
 
 //! get changeset info for the specified nodeid
-static csinfo_t get_csinfo(const char* nodeid)
+static csinfo_t
+get_csinfo(const char *nodeid)
 {
     // only supports RevlogNG. See mercurial/parsers.c for details.
-    const char* REVLOG_FILENAME = ".hg/store/00changelog.i";
+    const char *REVLOG_FILENAME = ".hg/store/00changelog.i";
     const size_t ENTRY_LEN = 64, COMP_LEN_OFS = 8, NODEID_OFS = 32;
 
     char buf[ENTRY_LEN];
-    FILE* f;
+    FILE *rlfile;
     int inlined;
     csinfo_t csinfo = {"", -1, 0};
     int i;
 
-    f = fopen(REVLOG_FILENAME, "rb");
-    if (!f) {
+    rlfile = fopen(REVLOG_FILENAME, "rb");
+    if (!rlfile) {
         debug("error opening '%s': %s", REVLOG_FILENAME, strerror(errno));
         return csinfo;
     }
 
-    inlined = is_revlog_inlined(f);
+    inlined = is_revlog_inlined(rlfile);
 
-    for (i = 0; !feof(f); ++i) {
+    for (i = 0; !feof(rlfile); ++i) {
         size_t comp_len, rlen;
 
-        rlen = fread(buf, 1, ENTRY_LEN, f);
+        rlen = fread(buf, 1, ENTRY_LEN, rlfile);
         if (rlen == 0) break;
         if (rlen != ENTRY_LEN) {
             debug("error while reading '%s': incomplete entry (read = %d)",
             csinfo.istip = 1;
         }
 
-        if (inlined) fseek(f, comp_len, SEEK_CUR);
+        if (inlined) fseek(rlfile, comp_len, SEEK_CUR);
     }
 
-    fclose(f);
+    fclose(rlfile);
     return csinfo;
 }
 
-static size_t get_mq_patchname(char* str, const char* nodeid, size_t n)
+static size_t
+put_nodeid(char *dest, const char *nodeid)
 {
-    char buf[1024];
-    char status_filename[512] = ".hg/patches/status";
-    static const char QQ_STATUS_FILE_PAT[] = ".hg/patches-%s/status";
-    static const size_t MAX_QQ_NAME = sizeof(status_filename)
-        - (sizeof(QQ_STATUS_FILE_PAT) - 2 - 1);  // - "%s" - '\0'
+    const size_t SHORT_NODEID_LEN = 6;  // size in binary repr
+    char *p = dest;
 
-    // multiple patch queues, introduced in Mercurial 1.6
-    if (read_first_line(".hg/patches.queue", buf, MAX_QQ_NAME) && buf[0]) {
-        debug("read first line from .hg/patches.queue: '%s'", buf);
-        sprintf(status_filename, QQ_STATUS_FILE_PAT, buf);
+    csinfo_t csinfo = get_csinfo(nodeid);
+    if (csinfo.rev >= 0) {
+        p += sprintf(p, "%d", csinfo.rev);
+    }
+    else {
+        dump_hex(p, nodeid, SHORT_NODEID_LEN);
+        p += SHORT_NODEID_LEN * 2;
+    }
+    return p - dest;
+}
+
+static void
+read_parents(vccontext_t *context, result_t *result)
+{
+    if (!context->options->show_revision && !context->options->show_patch)
+        return;
+
+    char *parent_nodes;         /* two binary changeset IDs */
+    size_t readsize;
+
+    parent_nodes = malloc(NODEID_LEN * 2);
+    if (!parent_nodes) {
+        debug("malloc failed: out of memory");
+        return;
+    }
+    result->full_revision = parent_nodes;
+
+    debug("reading first %d bytes of dirstate to parent_nodes (%p)",
+          NODEID_LEN * 2, parent_nodes);
+    readsize = read_file(".hg/dirstate", parent_nodes, NODEID_LEN * 2);
+    if (readsize != NODEID_LEN * 2) {
+        return;
     }
 
-    if (read_last_line(status_filename, buf, 1024)) {
-        char nodeid_s[NODEID_LEN * 2 + 1], *p, *patch, *patch_nodeid_s;
-        dump_hex(nodeid, nodeid_s, NODEID_LEN);
+    readsize = read_file(".hg/dirstate", parent_nodes, NODEID_LEN * 2);
+    char destbuf[1024] = {'\0'};
+    char *p = destbuf;
 
-        debug("read last line from %s: '%s'", status_filename, buf);
-        p = strchr(buf, ':');
-        if (!p) return 0;
-        *p = '\0';
-        patch_nodeid_s = buf;
-        patch = p + 1;
-        debug("patch name found: '%s', nodeid: %s", patch, patch_nodeid_s);
+    // first parent
+    if (non_zero((unsigned char *) parent_nodes, NODEID_LEN)) {
+        p += put_nodeid(p, parent_nodes);
+    }
 
-        if (strcmp(patch_nodeid_s, nodeid_s)) return 0;
+    // second parent
+    if (non_zero((unsigned char *) parent_nodes + NODEID_LEN, NODEID_LEN)) {
+        *p++ = ',';
+        p += put_nodeid(p, parent_nodes + NODEID_LEN);
+    }
 
-        strncpy(str, patch, n);
-        str[n - 1] = '\0';
-        return strlen(str);
-    }
-    else {
-        debug("failed to read from .hg/patches/status: assuming no mq patch applied");
-        return 0;
-    }
+    result_set_revision(result, destbuf, -1);
 }
 
-static size_t put_nodeid(char* str, const char* nodeid)
+static void
+read_patch_name(vccontext_t *context, result_t *result)
 {
-    const size_t SHORT_NODEID_LEN = 6;  // size in binary repr
-    char buf[512], *p = str;
-    size_t n;
+    if (!context->options->show_patch)
+        return;
 
-    csinfo_t csinfo = get_csinfo(nodeid);
+    static const char default_status[] = ".hg/patches/status";
+    static const char status_fmt[] = ".hg/patches-%s/status";
 
-    if (csinfo.rev >= 0) p += sprintf(p, "%d:", csinfo.rev);
+    struct stat statbuf;
+    char *status_fn = NULL;
+    char *last_line = NULL;
 
-    dump_hex(nodeid, p, SHORT_NODEID_LEN);
-    p += SHORT_NODEID_LEN * 2;
+    if (stat(".hg/patches.queues", &statbuf) == 0) {
+        /* The name of the current patch queue cannot possibly be
+           longer than the name of all patch queues concatenated. */
+        size_t max_qname = (size_t) statbuf.st_size;
+        char *qname = malloc(max_qname + 1);
+        int ok = read_first_line(".hg/patches.queue", qname, max_qname);
+        if (ok && strlen(qname) > 0) {
+            debug("read queue name from .hg/patches.queue: '%s'", qname);
+            status_fn = malloc(strlen(default_status) + 1 + strlen(qname) + 1);
+            sprintf(status_fn, status_fmt, qname);
+        }
+        free(qname);
+    }
 
-    n = get_mq_patchname(buf, nodeid, sizeof(buf));
-    if (n) {
-        *p = '['; ++p;
-        memcpy(p, buf, n); p += n;
-        *p = ']'; ++p;
-        *p = '\0';
-    } else {
-        if (csinfo.istip) {
-            strcpy(p, "[tip]");
-            p += 5;
+    /* Failed to read patches.queues and/or patches.queue: assume
+       there is just a single patch queue. */
+    if (status_fn == NULL) {
+        status_fn = strdup(default_status);
+    }
+
+    if (stat(status_fn, &statbuf) < 0) {
+        debug("failed to stat %s: assuming no patch applied", status_fn);
+        goto done;
+    }
+    if (statbuf.st_size == 0) {
+        debug("status file %s is empty: no patch applied", status_fn);
+        goto done;
+    }
+
+    /* Last line of the file cannot possibly be longer than the whole
+       file */
+    size_t max_line = (size_t) statbuf.st_size;
+    last_line = malloc(max_line + 1);
+    if (!read_last_line(status_fn, last_line, max_line + 1)) {
+        debug("failed to read from %s: assuming no mq patch applied", status_fn);
+        goto done;
+    }
+    debug("read last line from %s: '%s'", status_fn, last_line);
+
+    char nodeid_s[NODEID_LEN * 2 + 1];
+    dump_hex(nodeid_s, result->full_revision, NODEID_LEN);
+
+    if (strncmp(nodeid_s, last_line, NODEID_LEN * 2) == 0) {
+        result->patch = strdup(last_line + NODEID_LEN * 2 + 1);
+    }
+
+ done:
+    free(status_fn);
+    free(last_line);
+}
+
+static void
+read_modified_unknown(vccontext_t *context, result_t *result)
+{
+    // No easy way that we know to get the modified or unknown status
+    // without forking an hg process. Replace this with a more efficient version
+    // if you ever figure it out.
+    if (!context->options->show_modified && !context->options->show_unknown)
+        return;
+
+    char *argv[] = {"hg", "--quiet", "status",
+                    "--modified", "--added", "--removed",
+                    "--unknown", NULL};
+    if (!context->options->show_unknown) {
+        // asking hg to search for unknown files can be expensive, so
+        // skip it unless the user wants it
+        argv[6] = NULL;
+    }
+    capture_t *capture = capture_child("hg", argv);
+    if (capture == NULL) {
+        debug("unable to execute 'hg status'");
+        return;
+    }
+    char *cstdout = capture->childout.buf;
+    for (char *ch = cstdout; *ch != 0; ch++) {
+        if (ch == cstdout || *(ch-1) == '\n') {
+            // at start of output or start of line: look for ?, M, etc.
+            if (context->options->show_unknown && *ch == '?') {
+                result->unknown = 1;
+            }
+            if (context->options->show_modified &&
+                (*ch == 'M' || *ch == 'A' || *ch == 'R')) {
+                result->modified = 1;
+            }
         }
     }
 
-    return p - str;
-}
-
-static void
-read_parents(vccontext_t* context, result_t* result)
-{
-    char buf[NODEID_LEN * 2];
-    size_t readsize;
-
-    if (!context->options->show_revision) return;
-
-    readsize = read_file(".hg/dirstate", buf, NODEID_LEN * 2);
-    if (readsize == NODEID_LEN * 2) {
-        char destbuf[1024] = {'\0'};
-        char* p = destbuf;
-        debug("read nodeids from .hg/dirstate");
-
-        // first parent
-        if (sum_bytes((unsigned char *) buf, NODEID_LEN)) {
-            p += put_nodeid(p, buf);
-        }
-
-        // second parent
-        if (sum_bytes((unsigned char *) buf + NODEID_LEN, NODEID_LEN)) {
-            *p = ','; ++p;
-            p += put_nodeid(p, buf + NODEID_LEN);
-        }
-
-        result_set_revision(result, destbuf, -1);
-    }
-    else {
-        debug("failed to read from .hg/dirstate");
-    }
+    cstdout = NULL;
+    free_capture(capture);
 }
 
 static result_t*
-hg_get_info(vccontext_t* context)
+hg_get_info(vccontext_t *context)
 {
-    result_t* result = init_result();
+    result_t *result = init_result();
     char buf[1024];
 
     // prefer bookmark because it tends to be more informative
     }
 
     read_parents(context, result);
+    read_patch_name(context, result);
+    read_modified_unknown(context, result);
 
     if (context->options->show_modified || context->options->show_unknown) {
         int status = system(context->options->show_unknown ? "vcprompt-hgst -u" : "vcprompt-hgst");
     return result;
 }
 
-vccontext_t* get_hg_context(options_t* options)
+vccontext_t*
+get_hg_context(options_t *options)
 {
     return init_context("hg", options, hg_probe, hg_get_info);
 }
 
 #include "common.h"
 
-vccontext_t* get_hg_context(options_t* options);
+vccontext_t *
+get_hg_context(options_t *options);
 
 #endif
 /*
- * Copyright (C) 2009, 2010, Gregory P. Ward and contributors.
+ * Copyright (C) 2009-2013, Gregory P. Ward and contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
  * (at your option) any later version.
  */
 
+#include "../config.h"
+
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
+#include <unistd.h>
+#if HAVE_SQLITE3
+# include <sqlite3.h>
+#endif
+
 #include "common.h"
 #include "svn.h"
 
 #include <ctype.h>
 
 static int
-svn_probe(vccontext_t* context)
+svn_probe(vccontext_t *context)
 {
     return isdir(".svn");
 }
 
-static result_t*
-svn_get_info(vccontext_t* context)
+static char *
+get_branch_name(char *repos_path)
 {
-    result_t* result = init_result();
-    char buf[1024];
+    // if repos_path endswith "trunk"
+    //     return "trunk"
+    // else if repos_path endswith "branches/*"
+    //     return whatever matched "*"
+    // else
+    //     no idea
 
-    if (!read_first_line(".svn/entries", buf, 1024)) {
-        debug("failed to read from .svn/entries: assuming not an svn repo");
+    // if the final component is "trunk", that's where we are
+    char *slash = strrchr(repos_path, '/');
+    char *name = slash ? slash + 1 : repos_path;
+    if (strcmp(name, "trunk") == 0) {
+        debug("found svn trunk");
+        return strdup(name);
+    }
+    if (slash == NULL) {
+        debug("no branch in svn repos_path '%s'", repos_path);
         return NULL;
     }
-    else {
-        FILE *fp;
-        fp = fopen(".svn/entries", "r");
-        char line[1024];
 
-        // Check the version
-        if (fgets(line, sizeof(line), fp)) {
-            if(isdigit(line[0])) {
-                // Custom file format (working copy created by svn >= 1.4)
-
-                // Read and discard line 2 (name), 3 (entries kind)
-                if (fgets(line, sizeof(line), fp) == NULL ||
-                    fgets(line, sizeof(line), fp) == NULL) {
-                    debug("early EOF reading .svn/entries");
-                    fclose(fp);
-                    return NULL;
-                }
-
-                // Get the revision number
-                if (fgets(line, sizeof(line), fp)) {
-                    chop_newline(line);
-                    result->revision = strdup(line);
-                    debug("read a svn revision from .svn/entries: '%s'", line);
-                }
-                else {
-                    debug("early EOF: expected revision number");
-                    fclose(fp);
-                    return NULL;
-                }
-            }
-            else {
-                // XML file format (working copy created by svn < 1.4)
-                char rev[100];
-                char* marker = "revision=";
-                char* p = NULL;
-                while (fgets(line, sizeof(line), fp))
-                    if ((p = strstr(line, marker)) != NULL)
-                        break;
-                if (p == NULL) {
-                    debug("no 'revision=' line found in .svn/entries");
-                    return NULL;
-                }
-                if (sscanf(p, " %*[^\"]\"%[0-9]\"", rev) == 1) {
-                    result_set_revision(result, rev, -1);
-                    debug("read svn revision from .svn/entries: '%s'", rev);
-                }
-            }
-        }
-        fclose(fp);
+    // backup and see if the previous component is "branches", in which
+    // case 'name' points to the branch name
+    *slash = 0;
+    slash = strrchr(repos_path, '/');
+    char *prev = slash ? slash + 1 : repos_path;
+    if (strncmp(prev, "branches", 8) == 0) {
+        debug("found svn branch name: %s", name);
+        return strdup(name);
     }
-    return result;
+    debug("could not find branch name in svn repos_path '%s'", repos_path);
+    return NULL;
 }
 
-vccontext_t* get_svn_context(options_t* options)
+
+#if HAVE_SQLITE3
+static int
+svn_read_sqlite(vccontext_t *context, result_t *result)
+{
+    int ok = 0;
+    int retval;
+    sqlite3 *conn = NULL;
+    sqlite3_stmt *res = NULL;
+    const char *tail;
+    char * repos_path = NULL;
+
+    retval = sqlite3_open_v2(".svn/wc.db", &conn, SQLITE_OPEN_READONLY, NULL);
+    if (retval != SQLITE_OK) {
+        debug("error opening database in .svn/wc.db: %s", sqlite3_errstr(retval));
+        goto err;
+    }
+    // unclear when wc_id is anything other than 1
+    char *sql = ("select changed_revision from nodes "
+                 "where wc_id = 1 and local_relpath = ''");
+    const char *textval;
+    retval = sqlite3_prepare_v2(conn, sql, strlen(sql), &res, &tail);
+    if (retval != SQLITE_OK) {
+        debug("error running query: %s", sqlite3_errstr(retval));
+        goto err;
+    }
+    retval = sqlite3_step(res);
+    if (retval != SQLITE_DONE && retval != SQLITE_ROW) {
+        debug("error fetching result row: %s", sqlite3_errstr(retval));
+        goto err;
+    }
+    textval = (const char *) sqlite3_column_text(res, 0);
+    if (textval == NULL) {
+        debug("could not retrieve value of nodes.changed_revision");
+        goto err;
+    }
+    result->revision = strdup(textval);
+    sqlite3_finalize(res);
+
+    sql = "select repos_path from nodes where local_relpath = ?";
+    retval = sqlite3_prepare_v2(conn, sql, strlen(sql), &res, &tail);
+    if (retval != SQLITE_OK) {
+        debug("error querying for repos_path: %s", sqlite3_errstr(retval));
+        goto err;
+    }
+    retval = sqlite3_bind_text(res, 1,
+                               context->rel_path, strlen(context->rel_path),
+                               SQLITE_STATIC);
+    if (retval != SQLITE_OK) {
+        debug("error binding parameter: %s", sqlite3_errstr(retval));
+        goto err;
+    }
+    retval = sqlite3_step(res);
+    if (retval != SQLITE_DONE && retval != SQLITE_ROW) {
+        debug("error fetching result row: %s", sqlite3_errstr(retval));
+        goto err;
+    }
+
+    textval = (const char *) sqlite3_column_text(res, 0);
+    if (textval == NULL) {
+        debug("could not retrieve value of nodes.repos_path");
+        goto err;
+    }
+    repos_path = strdup(textval);
+    result->branch = get_branch_name(repos_path);
+
+    ok = 1;
+
+ err:
+    if (res != NULL)
+        sqlite3_finalize(res);
+    if (conn != NULL)
+        sqlite3_close(conn);
+    if (repos_path != NULL)
+        free(repos_path);
+    return ok;
+}
+#else
+static int
+svn_read_sqlite(vccontext_t *context, result_t *result)
+{
+    debug("vcprompt built without sqlite3 (cannot support svn >= 1.7)");
+    return 0;
+}
+#endif
+
+static int
+svn_read_custom(FILE *fp, char line[], int size, int line_num, result_t *result)
+{
+    // Caller has already read line 1. Read lines 2..5, discarding 2..4.
+    while (line_num <= 5) {
+        if (fgets(line, size, fp) == NULL) {
+            debug(".svn/entries: early EOF (line %d empty)", line_num);
+            return 0;
+        }
+        line_num++;
+    }
+
+    // Line 5 is the complete URL for the working dir (repos_root
+    // + repos_path). To parse it easily, we first need the
+    // repos_root from line 6.
+    char *repos_root;
+    int root_len;
+    char *repos_path = strdup(line);
+    chop_newline(repos_path);
+    if (fgets(line, size, fp) == NULL) {
+        debug(".svn/entries: early EOF (line %d empty)", line_num);
+        return 0;
+    }
+    line_num++;
+    repos_root = line;
+    chop_newline(repos_root);
+    root_len = strlen(repos_root);
+    if (strncmp(repos_path, repos_root, root_len) != 0) {
+        debug(".svn/entries: repos_path (%s) does not start with "
+              "repos_root (%s)",
+              repos_path, repos_root);
+        free(repos_path);
+        return 0;
+    }
+    result->branch = get_branch_name(repos_path + root_len + 1);
+    free(repos_path);
+
+    // Lines 6 .. 10 are also uninteresting.
+    while (line_num <= 11) {
+        if (fgets(line, size, fp) == NULL) {
+            debug(".svn/entries: early EOF (line %d empty)", line_num);
+            return 0;
+        }
+        line_num++;
+    }
+
+    // Line 11 is the revision number we care about, now in 'line'.
+    chop_newline(line);
+    result->revision = strdup(line);
+    debug("read svn revision from .svn/entries: '%s'", line);
+    return 1;
+}
+
+static int
+svn_read_xml(FILE *fp, char line[], int size, int line_num, result_t *result)
+{
+    char rev[100];
+    char *marker = "revision=";
+    char *p = NULL;
+    while (fgets(line, size, fp)) {
+        if ((p = strstr(line, marker)) != NULL) {
+            break;
+        }
+    }
+    if (p == NULL) {
+        debug("no 'revision=' line found in .svn/entries");
+        return 0;
+    }
+    if (sscanf(p, " %*[^\"]\"%[0-9]\"", rev) == 1) {
+        result_set_revision(result, rev, -1);
+        debug("read svn revision from .svn/entries: '%s'", rev);
+    }
+    return 1;
+}
+
+static result_t*
+svn_get_info(vccontext_t *context)
+{
+    result_t *result = init_result();
+    FILE *fp = NULL;
+
+    fp = fopen(".svn/entries", "r");
+    if (!fp) {
+        debug("failed to open .svn/entries: not an svn working copy");
+        goto err;
+    }
+    char line[1024];
+    int line_num = 1;                   // the line we're about to read
+
+    if (fgets(line, sizeof(line), fp) == NULL) {
+        debug(".svn/entries: empty file");
+        goto err;
+    }
+    line_num++;
+
+    int ok;
+    if (access(".svn/wc.db", F_OK) == 0) {
+        // SQLite file format (working copy created by svn >= 1.7)
+        ok = svn_read_sqlite(context, result);
+    }
+    // First line of the file tells us what the format is.
+    else if (isdigit(line[0])) {
+        // Custom file format (working copy created by svn >= 1.4)
+        ok = svn_read_custom(fp, line, sizeof(line), line_num, result);
+    }
+    else {
+        // XML file format (working copy created by svn < 1.4)
+        ok = svn_read_xml(fp, line, sizeof(line), line_num, result);
+    }
+    if (ok) {
+        fclose(fp);
+        return result;
+    }
+
+ err:
+    free(result);
+    if (fp)
+        fclose(fp);
+    return NULL;
+}
+
+vccontext_t*
+get_svn_context(options_t *options)
 {
     return init_context("svn", options, svn_probe, svn_get_info);
 }
 
 #include "common.h"
 
-vccontext_t* get_svn_context(options_t* options);
+vccontext_t *
+get_svn_context(options_t *options);
 
 #endif
 /*
- * Copyright (C) 2009, 2010, Gregory P. Ward and contributors.
+ * Copyright (C) 2009-2013, Gregory P. Ward and contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by
  * (at your option) any later version.
  */
 
+#include "../config.h"
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <limits.h>
 
 #include "common.h"
 #include "cvs.h"
 #include "bzr.h"
 */
 
-#define DEFAULT_FORMAT "[%n:%b%m%u] "
+static char* features[] = {
+    /* Some version control systems don't change their working copy
+       format every couple of versions (or they are just
+       unmaintained), so we don't need versioned feature strings. */
+    "cvs",
+    "hg",
+    "git",
+    "fossil",
 
-void parse_args(int argc, char** argv, options_t* options)
+    /* Support for Subversion up to 1.6 is unconditional, because those
+       versions don't require any additional libraries. Subversion >= 1.7
+       requires SQLite, so it's conditional. */
+    "svn-1.3",
+    "svn-1.4",
+    "svn-1.5",
+    "svn-1.6",
+#if HAVE_SQLITE3
+    "svn-1.7",
+    "svn-1.8",
+#endif
+    0,
+};
+
+#define DEFAULT_FORMAT "[%n:%b] "
+
+void
+parse_args(int argc, char** argv, options_t *options)
 {
     int opt;