Greg Ward avatar Greg Ward committed 7baa05a

Add %p format specifier, for patch name (e.g. MQ, guilt, quilt).

Only implemented for Mercurial + MQ for now. I tore get_mq_patchname()
to pieces, fixed several buffer overflow errors, and make it work more
sensibly.

Should be entirely doable to support git + guilt, if someone wants to
give it a crack.

Comments (0)

Files changed (6)

 void
 free_result(result_t *result)
 {
+    free(result->branch);
     free(result->revision);
-    free(result->branch);
+    free(result->patch);
     free(result->full_revision);
     free(result);
 }
     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 */
  */
 typedef struct {
     char *branch;                       /* name of current branch */
-    char *revision;                     /* current revision */
+    char *revision;                     /* current revision ID */
+    char *patch;                        /* name of current patch */
     int unknown;                        /* any unknown files? */
     int modified;                       /* any local changes? */
 
 #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>
 }
 
 static size_t
-get_mq_patchname(char *dest, const char *nodeid, size_t n)
-{
-    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'
-
-    // 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);
-    }
-
-    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);
-
-        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);
-
-        if (strcmp(patch_nodeid_s, nodeid_s))
-            return 0;
-
-        strncpy(dest, patch, n);
-        dest[n - 1] = '\0';
-        return strlen(dest);
-    }
-    else {
-        debug("failed to read from .hg/patches/status: assuming no mq patch applied");
-        return 0;
-    }
-}
-
-static size_t
 put_nodeid(char *dest, const char *nodeid)
 {
     const size_t SHORT_NODEID_LEN = 6;  // size in binary repr
 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;
 
-    if (!context->options->show_revision)
-        return;
-
     parent_nodes = malloc(NODEID_LEN * 2);
     if (!parent_nodes) {
         debug("malloc failed: out of memory");
     }
     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) {
-        char destbuf[1024] = {'\0'};
-        char *p = destbuf;
-        debug("read nodeids from .hg/dirstate");
+    if (readsize != NODEID_LEN * 2) {
+        return;
+    }
 
-        // first parent
-        if (sum_bytes((unsigned char *) parent_nodes, NODEID_LEN)) {
-            p += put_nodeid(p, parent_nodes);
+    readsize = read_file(".hg/dirstate", parent_nodes, NODEID_LEN * 2);
+    char destbuf[1024] = {'\0'};
+    char *p = destbuf;
+
+    // first parent
+    if (sum_bytes((unsigned char *) parent_nodes, NODEID_LEN)) {
+        p += put_nodeid(p, parent_nodes);
+    }
+
+    // second parent
+    if (sum_bytes((unsigned char *) parent_nodes + NODEID_LEN, NODEID_LEN)) {
+        *p++ = ',';
+        p += put_nodeid(p, parent_nodes + NODEID_LEN);
+    }
+
+    result_set_revision(result, destbuf, -1);
+}
+
+static void
+read_patch_name(vccontext_t *context, result_t *result)
+{
+    if (!context->options->show_patch)
+        return;
+
+    static const char default_status[] = ".hg/patches/status";
+    static const char status_fmt[] = ".hg/patches-%s/status";
+
+    struct stat statbuf;
+    char *status_fn = NULL;
+    char *last_line = NULL;
+
+    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);
+    }
 
-        // second parent
-        if (sum_bytes((unsigned char *) parent_nodes + NODEID_LEN, NODEID_LEN)) {
-            *p = ','; ++p;
-            p += put_nodeid(p, parent_nodes + NODEID_LEN);
-        }
+    /* 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);
+    }
 
-        result_set_revision(result, destbuf, -1);
+    if (stat(status_fn, &statbuf) < 0) {
+        debug("failed to stat %s: assuming no patch applied", status_fn);
+        goto done;
     }
-    else {
-        debug("failed to read from .hg/dirstate");
+    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(result->full_revision, nodeid_s, 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 result_t*
     }
 
     read_parents(context, result);
+    read_patch_name(context, result);
 
     return result;
 }
                 DEFAULT_FORMAT,
                 " %b  show branch\n"
                 " %r  show revision\n"
+                " %p  show patch name (MQ, guilt, ...)\n"
                 " %u  show unknown\n"
                 " %m  show modified\n"
                 " %n  show VC name\n"
     size_t i;
 
     options->show_branch = 0;
+    options->show_revision = 0;
+    options->show_patch = 0;
     options->show_unknown = 0;
     options->show_modified = 0;
 
                 case 'r':
                     options->show_revision = 1;
                     break;
+            	case 'p':
+                    options->show_patch = 1;
+                    break;
                 case 'u':
                     options->show_unknown = 1;
                     break;
                     if (result->revision != NULL)
                         fputs(result->revision, stdout);
                     break;
+            	case 'p':
+                    if (result->patch != NULL)
+                        fputs(result->patch, stdout);
                 case 'u':
                     if (result->unknown)
                         putc('?', stdout);

tests/test-simple

 
     printf '0123456789abcdefghij\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' \
         > .hg/dirstate
-    echo '303132333435363738396162636465666768696a:bar.diff' >> .hg/patches/status
-    assert_vcprompt "hg_mq applied 1" "hg:303132333435/foo" "%n:%r/%b"
+    echo '303132333435363738396162636465666768696a:bar' >> .hg/patches/status
+    assert_vcprompt "hg_mq applied 1" "foo/bar" "%b/%p"
 
     printf 'a123456789abcdefghij\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' \
         > .hg/dirstate
-    echo '613132333435363738396162636465666768696a:baz.diff' >> .hg/patches/status
+    echo '613132333435363738396162636465666768696a:baz' >> .hg/patches/status
     echo > .hg/patches.queue  # default queue
-    assert_vcprompt "hg_mq applied 2" "hg:613132333435/foo" "%n:%r/%b"
+    assert_vcprompt "hg_mq applied 2" "foo/baz" "%b/%p"
 
     mkdir .hg/patches-foo
     printf 'a123456789abcdefghij\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' \
         > .hg/dirstate
-    echo '613132333435363738396162636465666768696a:qux.diff' >> .hg/patches-foo/status
+    echo '613132333435363738396162636465666768696a:qux' >> .hg/patches-foo/status
+    echo -e 'foo\npatches\n' > .hg/patches.queues
     echo 'foo' > .hg/patches.queue  # named queue
-    assert_vcprompt "hg_mq applied named mq" "hg:613132333435/foo" "%n:%r/%b"
+    assert_vcprompt "hg_mq applied named mq" "foo/qux" "%b/%p"
 }
 
 test_simple_hg_revlog ()
 .B %r
 The current revision number, changeset ID, or commit ID.
 .TP
+.B %p
+The name of the currently applied patch, if any (Mercurial + MQ only,
+but it looks like this could easily be supported for git + guilt).
+.TP
 .B %u
 A single "?" if there are any unknown (untracked) files in the working
 dir. Slow.
 .B vcprompt
 reports the branch as "unknown".
 
-Format specified
+Format specifier
 .B %r
 (revision) expands to the first 12 characters of the commit ID of
 HEAD.
 
 Format specifier
+.B %p
+is not yet implemented.
+
+Format specifier
 .B %m
 is supported by running "git diff --no-ext-diff --quiet --exit-code",
 so it can be expensive in a large working dir.
 .B vcprompt
 !
 
+Format specifier
+.B %p
+is implemented by reading MQ internals.
+
 Format specifiers
 .B %m
 and
 directories and which is the branch name. There might not even be a
 branch name.
 
+Format specifier
+.B %p
+is not implemented (it makes no sense with Subversion).
+
 Format specifiers
 .B %m
 and
 .B %r
 is not supported because CVS has no global revision ID.
 
+Format specifier
+.B %p
+is not implemented (it makes no sense with CVS).
+
 Format specifiers
 .B %m
 and
 expensive for other version control systems.
 
 Format specifier
+.B %p
+is not implemented.
+
+Format specifier
 .B %u
 requires running "fossil extra", so has an extra penalty compared to
 the ther format specifiers.
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.