[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: [PATCH] Re: Future-proofing "svn st" output

From: Gareth McCaughan <Gareth.McCaughan_at_pobox.com>
Date: 2002-08-31 00:23:02 CEST

On Tuesday 27 August 2002 7:32 pm, Karl Fogel wrote:

> Can you repost this patch with a log message? Thanks,

Yes. Here it is. (Sorry about the delay.) It's now against
revision 3092 of Subversion. Unfortunately, Subversion
doesn't seem to want to build for me right now (almost
certainly some sort of pilot error), so I haven't checked
that it still passes all the tests.

One thing this doesn't include is any revision to the Handbook.
I'd rather wait until the patch has been looked at before writing
proper documentation for it, in case things have to change. For
evaluation purposes, all you need to know is that "svn status"
does more or less the same as before (the questions in the
great-grandparent of this message indicate where the minor
differences lie), and that
    svn status [flags] -d
gives a description of the output format of
    svn status [flags]

I'd still be interested in answers to those questions, by the way;
unless I've guessed right about all of them, this patch could be
made better with their help :-).

-------------------- patch begins --------------------
Index: ./cl.h
===================================================================
--- ./cl.h
+++ ./cl.h Wed Aug 28 22:43:01 2002
@@ -83,6 +83,7 @@
   svn_boolean_t verbose; /* be verbose */
   svn_boolean_t update; /* contact the server for the full
story */
   svn_boolean_t strict; /* do strictly what was requested */
+ svn_boolean_t describe_only; /* describe output format, don't
stat */
   svn_stringbuf_t *filedata; /* contents of file used as option
data */
   const char *filedata_encoding; /* the locale/encoding of the
filedata*/
   svn_boolean_t help; /* print usage message */
@@ -268,6 +269,13 @@
                                 svn_boolean_t show_last_committed,
                                 svn_boolean_t skip_unrecognized,
                                 apr_pool_t *pool);
+
+/* Print descriptions of all the fields that would be output
+ by calling svn_cl__print_status_list with parameters
+ DETAILED and SHOW_LAST_COMMITTED having specified values. */
+void svn_cl__describe_status_list (svn_boolean_t detailed,
+ svn_boolean_t
show_last_committed);
+
 
 /* Print a hash that maps property names (char *) to property values
    (svn_stringbuf_t *). The names are assumed to be in UTF-8 format;
Index: ./status.c
===================================================================
--- ./status.c
+++ ./status.c Fri Aug 30 23:06:21 2002
@@ -21,6 +21,10 @@
 
 
 /*** Includes. ***/
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
 #include <apr_hash.h>
 #include <apr_tables.h>
 #include "svn_sorts.h"
@@ -29,200 +33,320 @@
 #include "svn_utf.h"
 #include "cl.h"
 
+
+/* To the new reader: You will probably grasp what's going on here
+ much faster by scrolling down to the middle of this file and
+ looking at the all_fields structure, which describes the output
+ of every variant of "svn status". Then take a look at the
+ definition of struct status_item, immediately below this comment.
*/
+
+
+/* A single item to be reported by some variant of "svn st".
+ VARIANTS should be conceived as a set of characters; the item
+ will be included if and only if this set contains the character
code
+ used for the variant requested.
+ WIDTH is the number of characters in the field.
+ FILL_IN is a pointer to a function whose job it is to fill in the
+ contents of the field. It will be handed a pointer to the start of
+ the field, the width requested, and a pointer to the status object
+ being reported on. It guarantees never to write to anything
outside
+ the range 0..WIDTH-1 inclusive. It is safe to assume that the
+ field is filled with spaces to begin with.
+ TAG is a shortish string, containing no whitespace, that another
+ program can use to recognize a field it's interested in. Tags
+ must never change, so choose them wisely.
+ DESC is a string containing a brief human-readable description
+ of the field.
+ Preferably, TAG should be at most 10 characters and DESC at most
60.
+ Then when we describe the fields, everything will fit in at most
+ 2+2+2+1+10+1+60 = 77 characters.
+ Alternatively: FILL_IN, TAG and DESC may all be null pointers.
+ In this case, the action associated with the field is to do
+ nothing.
+ These will be grouped into an array. The last element in the
+ array is distinguished by having VARIANTS a null pointer. */
+struct status_item {
+ const char *variants;
+ size_t width;
+ void (*fill_in)(char *, size_t, const svn_wc_status_t *);
+ const char *tag;
+ const char *desc;
+};
+
+typedef struct status_item status_item_t;
+
+
+
+/* Some of the fill-in functions below nede to be able to write
+ a revision number into a fixed-width field of a string. This
+ is much more painful than it should be, so it's pulled out into
+ this function here. The number is right-aligned within the field;
+ if it won't fit, digits are thrown away at the left-hand end.
+ FIELD is assumed pre-filled with spaces. WIDTH is the number
+ of characters available. We never write to anywhere outside
+ the range 0..WIDTH-1. */
+static void
+fill_in__revnum (char *field, size_t width, svn_revnum_t rev)
+{
+ char temp[21]; /* enough space for an unsigned long, including
terminator */
 
-/* Fill in the first four characters of STR_STATUS with status code
- characters, based on TEXT_STATUS, PROP_STATUS, LOCKED, and COPIED.
+ /* If WIDTH <= 20, we must copy the last WIDTH (non-\0) characters
+ of TEMP to FIELD. If WIDTH > 20, we must copy all of TEMP to
+ the last 20 characters of FIELD. So, COPY_LENGTH is the number
+ of characters to copy across, TEMP_OFFSET is the starting offset
+ within TEMP, and FIELD_OFFSET is the starting offset within
FIELD. */
+ size_t copy_length = width <= 20 ? width : 20;
+ size_t temp_offset = 20 - copy_length;
+ size_t field_offset = width - copy_length;
 
- This function is also used by commit-cmd.c
-*/
-void
-svn_cl__generate_status_codes (char *str_status,
- enum svn_wc_status_kind text_status,
- enum svn_wc_status_kind prop_status,
- svn_boolean_t locked,
- svn_boolean_t copied)
-{
- char text_statuschar, prop_statuschar;
+ sprintf (temp, "%20lu", (unsigned long) rev);
 
- switch (text_status)
- {
- case svn_wc_status_none:
- text_statuschar = ' ';
- break;
- case svn_wc_status_normal:
- text_statuschar = '_';
- break;
- case svn_wc_status_added:
- text_statuschar = 'A';
- break;
- case svn_wc_status_absent:
- text_statuschar = '!';
- break;
- case svn_wc_status_deleted:
- text_statuschar = 'D';
- break;
- case svn_wc_status_replaced:
- text_statuschar = 'R';
- break;
- case svn_wc_status_modified:
- text_statuschar = 'M';
- break;
- case svn_wc_status_merged:
- text_statuschar = 'G';
- break;
- case svn_wc_status_conflicted:
- text_statuschar = 'C';
- break;
- case svn_wc_status_obstructed:
- text_statuschar = '~';
- break;
- case svn_wc_status_unversioned:
- default:
- text_statuschar = '?';
- break;
- }
+ memcpy (field+field_offset, temp+temp_offset, copy_length);
+}
 
- switch (prop_status)
+/* Perform the work of a fill-in function for the status of the
+ text or properties of a node. */
+static char
+fill_in__status (enum svn_wc_status_kind status)
+{
+ switch (status)
     {
- case svn_wc_status_none:
- prop_statuschar = ' ';
- break;
- case svn_wc_status_normal:
- prop_statuschar = '_';
- break;
- case svn_wc_status_added:
- prop_statuschar = 'A';
- break;
- case svn_wc_status_absent:
- prop_statuschar = '!';
- break;
- case svn_wc_status_deleted:
- prop_statuschar = 'D';
- break;
- case svn_wc_status_replaced:
- prop_statuschar = 'R';
- break;
- case svn_wc_status_modified:
- prop_statuschar = 'M';
- break;
- case svn_wc_status_merged:
- prop_statuschar = 'G';
- break;
- case svn_wc_status_conflicted:
- prop_statuschar = 'C';
- break;
- case svn_wc_status_unversioned:
- default:
- prop_statuschar = '?';
- break;
+ case svn_wc_status_none: return ' ';
+ case svn_wc_status_normal: return '_';
+ case svn_wc_status_added: return 'A';
+ case svn_wc_status_absent: return '!';
+ case svn_wc_status_deleted: return 'D';
+ case svn_wc_status_replaced: return 'R';
+ case svn_wc_status_modified: return 'M';
+ case svn_wc_status_merged: return 'G';
+ case svn_wc_status_conflicted: return 'C';
+ case svn_wc_status_obstructed: return '~';
+ case svn_wc_status_unversioned: return '?';
+ default: return '?';
     }
+}
+
+
+
+/* Fill-in function (see definition of STATUS_ITEM above)
+ for the status of the content of a node. */
+static void
+fill_in_text_status (char *field, size_t width, const svn_wc_status_t
*status)
+{
+ assert (width >= 1);
+ field[0] = fill_in__status (status->text_status);
+}
+
+/* Fill-in function (see definition of STATUS_ITEM above)
+ for the status of the properties of a node. */
+static void
+fill_in_prop_status (char *field, size_t width, const svn_wc_status_t
*status)
+{
+ assert (width >= 1);
+ field[0] = fill_in__status (status->prop_status);
+}
+
+/* Fill-in function (see definition of STATUS_ITEM above)
+ for the lockedness of a node. */
+static void
+fill_in_lockedness (char *field, size_t width, const svn_wc_status_t
*status)
+{
+ assert (width >= 1);
+ if (status->locked)
+ field[0] = 'L';
+}
 
- sprintf (str_status, "%c%c%c%c",
- text_statuschar,
- prop_statuschar,
- locked ? 'L' : ' ',
- copied ? '+' : ' ');
+/* Fill-in function (see definition of STATUS_ITEM above)
+ for the copiedness-with-history of a node. */
+static void
+fill_in_copiedness (char *field, size_t width, const svn_wc_status_t
*status)
+{
+ assert (width >= 1);
+ if (status->copied)
+ field[0] = '+';
 }
 
+/* Fill-in function (see definition of STATUS_ITEM above)
+ for the out-of-dateness of a node. */
+static void
+fill_in_out_of_dateness (char *field, size_t width, const
svn_wc_status_t *status)
+{
+ assert (width >= 1);
+ if ((status->repos_text_status != svn_wc_status_none)
+ || (status->repos_prop_status != svn_wc_status_none))
+ field[0] = '*';
+}
+
+/* Fill-in function (see definition of STATUS_ITEM above)
+ for the working revision of a node. */
+static void
+fill_in_working_rev (char *field, size_t width, const svn_wc_status_t
*status)
+{
+ assert (width >= 1);
+
+ if (! status->entry)
+ return;
 
-/* Print a single status structure in the short format */
-static void
-print_short_format (const char *path,
- svn_wc_status_t *status)
+ if (status->entry->revision == SVN_INVALID_REVNUM)
+ field[width-1] = '?';
+ else if (status->copied)
+ field[width-1] = '-';
+ else
+ fill_in__revnum (field, width, status->entry->revision);
+}
+
+/* Fill-in function (see definition of STATUS_ITEM above)
+ for the revision number in which a node last changed. */
+static void
+fill_in_changed_rev (char *field, size_t width, const svn_wc_status_t
*status)
 {
- char str_status[5];
+ assert (width >= 1);
 
- if (! status)
+ if (! status->entry)
     return;
 
- /* Create local-mod status code block. */
- svn_cl__generate_status_codes (str_status,
- status->text_status,
- status->prop_status,
- status->locked,
- status->copied);
-
- printf ("%s %s\n", str_status, path);
+ if (status->entry->cmt_rev == SVN_INVALID_REVNUM)
+ field[width-1] = '?';
+ else
+ fill_in__revnum (field, width, status->entry->cmt_rev);
 }
 
+/* Fill-in function (see definition of STATUS_ITEM above)
+ for the author of the last change to a node. */
+static void
+fill_in_author (char *field, size_t width, const svn_wc_status_t
*status)
+{
+ const char * author = 0;
 
-/* Print a single status structure in the long format */
-static void
-print_long_format (const char *path,
- svn_boolean_t show_last_committed,
- svn_wc_status_t *status)
-{
- char str_status[5];
- char str_rev[7];
- char update_char;
- svn_revnum_t local_rev;
- char last_committed[6 + 3 + 8 + 3 + 1] = { 0 };
+ assert (width >= 1);
 
- if (! status)
+ if (! status->entry)
     return;
 
- /* Create local-mod status code block. */
- svn_cl__generate_status_codes (str_status,
- status->text_status,
- status->prop_status,
- status->locked,
- status->copied);
-
- /* Get local revision number */
- if (status->entry)
- local_rev = status->entry->revision;
+ author = status->entry->cmt_author;
+ if (! author)
+ field[0] = '?';
   else
- local_rev = SVN_INVALID_REVNUM;
+ {
+ size_t length = strlen (author);
+ size_t copy_length = length <= width ? length : width;
+
+ memcpy (field, author, copy_length);
+ }
+}
 
- if (show_last_committed && status->entry)
+static const status_item_t
+all_fields[] = {
+ { "puvw", 1, &fill_in_text_status, "textstatus",
+ "status of file/directory contents" },
+ { "puvw", 1, &fill_in_prop_status, "propstatus",
+ "status of file/directory properties" },
+ { "puvw", 1, &fill_in_lockedness, "locked",
+ "'L' if file/directory is locked by Subversion" },
+ { "puvw", 1, &fill_in_copiedness, "copied",
+ "'+' if to be added with history" },
+ { "puvw", 3, 0, 0, 0 },
+ { " u w", 1, &fill_in_out_of_dateness, "update",
+ "'*' if file/directory needs updating" },
+ { " v ", 1, 0, 0, 0 },
+ { " uvw", 3, 0, 0, 0 },
+ { " uvw", 6, &fill_in_working_rev, "workrev",
+ "working revision number" },
+ { " uvw", 3, 0, 0, 0 },
+ { " vw", 6, &fill_in_changed_rev, "changerev",
+ "revision number of last change" },
+ { " vw", 3, 0, 0, 0 },
+ { " vw", 8, &fill_in_author, "author",
+ "author of last change" },
+ { " vw", 3, 0, 0, 0 },
+ { 0, 0, 0, 0, 0 }
+};
+
+
+
+/* Run through all the items for a particular variant,
+ filling stuff in starting at BUFFER. If BUFFER is a
+ null pointer, don't bother filling anything in.
+ Return the total width we've moved past in doing this.
+ Assume BUFFER is large enough and is full of spaces already.
+ If BUFFER is non-null, STATUS must be too. If BUFFER is null,
+ STATUS is unused.*/
+static size_t
+traverse_fields (char *buffer,
+ char variant,
+ const svn_wc_status_t *status)
+{
+ size_t offset = 0;
+ const status_item_t *item;
+
+ for (item=all_fields; item->variants; ++item)
     {
- char revbuf[20];
- const char *revstr = revbuf;
- const char *author;
-
- author = status->entry->cmt_author;
- if (SVN_IS_VALID_REVNUM (status->entry->cmt_rev))
- sprintf(revbuf, "%" SVN_REVNUM_T_FMT,
status->entry->cmt_rev);
- else
- revstr = " ? ";
-
- /* ### we shouldn't clip the revstr and author, but that
implies a
- ### variable length 'last_committed' which means an
allocation,
- ### which means a pool, ... */
- sprintf (last_committed, "%6.6s %8.8s ",
- revstr,
- author ? author : " ? ");
+ if (! strchr (item->variants, variant))
+ continue;
+
+ if (buffer && item->tag)
+ item->fill_in (buffer+offset, item->width, status);
+ offset += item->width;
     }
- else
- strcpy (last_committed, " ");
 
- /* Set the update character. */
- update_char = ' ';
- if ((status->repos_text_status != svn_wc_status_none)
- || (status->repos_prop_status != svn_wc_status_none))
- update_char = '*';
+ return offset;
+}
 
- /* Determine the appropriate local revision string. */
- if (! status->entry)
- strcpy (str_rev, " ");
- else if (local_rev == SVN_INVALID_REVNUM)
- strcpy (str_rev, " ? ");
- else if (status->copied)
- strcpy (str_rev, " -");
- else
- sprintf (str_rev, "%6ld", local_rev);
+/* Output a single status line for a given field set. */
+static void
+print_status_line (const char *path,
+ char variant,
+ const svn_wc_status_t *status)
+{
+ char prefix[81];
+ size_t prefix_length = traverse_fields (0, variant, 0);
+ assert (prefix_length <= 80);
+
+ memset (prefix, ' ', prefix_length);
+ prefix[prefix_length] = '\0';
 
- /* One Printf to rule them all, one Printf to bind them..." */
- printf ("%s %c %s %s%s\n",
- str_status,
- update_char,
- str_rev,
- show_last_committed ? last_committed : "",
- path);
+ traverse_fields (prefix, variant, status);
+ printf ("%s%s\n", prefix, path);
 }
 
 
+
+/* Output field descriptions for all fields needed by the specified
variant.
+ variant. */
+static void
+describe_fields (char variant)
+{
+ size_t offset = 0;
+ const status_item_t *item;
 
+ for (item=all_fields; item->variants; ++item)
+ {
+ if (! strchr (item->variants, variant))
+ continue;
+
+ if (item->tag)
+ {
+ /* We insist that all offsets are < 100 so that they don't
+ spoil the layout :-). In fact, print_status_line will
barf
+ if the fields (other than the filename) take up more
than
+ 80 characters, and it would be impolite to use even that
much. */
+ assert (offset+item->width < 100);
+ printf ("%2lu..%2lu %-10s %s\n",
+ (unsigned long) offset,
+ (unsigned long) offset+item->width-1,
+ item->tag, item->desc);
+ }
+ offset += item->width;
+ }
+ assert (offset < 100);
+
+ printf ("%2lu.... %-10s %s\n",
+ (unsigned long) offset,
+ "pathname",
+ "file or directory being examined");
+}
+
+
 /* Called by status-cmd.c */
 void
 svn_cl__print_status_list (apr_hash_t *statushash,
@@ -234,13 +358,20 @@
 {
   int i;
   apr_array_header_t *statusarray;
- svn_wc_status_t *status = NULL;
+ const svn_wc_status_t *status = NULL;
+ char variant = 'p';
 
   /* Convert the unordered hash to an ordered, sorted array */
   statusarray = apr_hash_sorted_keys (statushash,
                                       
svn_sort_compare_items_as_paths,
                                       pool);
 
+ /* Identify the fieldset we need */
+ if (show_last_committed)
+ variant = detailed ? 'w' : 'v';
+ else
+ variant = detailed ? 'u' : 'p';
+
   /* Loop over array, printing each name/status-structure */
   for (i = 0; i < statusarray->nelts; i++)
     {
@@ -258,16 +389,29 @@
       if (err)
         svn_handle_error (err, stderr, FALSE);
 
- if (detailed)
- print_long_format (path, show_last_committed, status);
- else
- print_short_format (path, status);
+ print_status_line (path, variant, status);
     }
 
   /* If printing in detailed format, we might have a head revision to
      print as well. */
   if (detailed && (youngest != SVN_INVALID_REVNUM))
     printf ("Head revision: %6ld\n", youngest);
+}
+
+/* Called by status-cmd.c */
+void
+svn_cl__describe_status_list (svn_boolean_t detailed,
+ svn_boolean_t show_last_committed)
+{
+ char variant = 'p';
+
+ /* Identify the fieldset we need */
+ if (show_last_committed)
+ variant = detailed ? 'w' : 'v';
+ else
+ variant = detailed ? 'u' : 'p';
+
+ describe_fields (variant);
 }
 
 
Index: ./main.c
===================================================================
--- ./main.c
+++ ./main.c Fri Aug 30 22:50:33 2002
@@ -75,6 +75,7 @@
     {"strict", svn_cl__strict_opt, 0, "use strict semantics"},
     {"no-ignore", svn_cl__no_ignore_opt, 0,
                       "disregard default and svn:ignore property
ignores"},
+ {"describe-only", 'd', 0, "describe columns; don't really do
status"},
     {"no-auth-cache", svn_cl__no_auth_cache_opt, 0,
                       "do not cache authentication tokens"},
     {0, 0, 0}
@@ -374,6 +375,7 @@
     " With no args, print only locally modified files (no network
access).\n"
     " With -u, add out-of-date information from server.\n"
     " With -v, print excessive information on every file.\n"
+ " With -d, describe the output format given the other flags and
quit.\n"
     "\n"
     "Decoding the status output, each column is one character
wide:\n"
     " 1st column: Add or delete file or directory, or change file
contents\n"
@@ -394,7 +396,7 @@
     " _ 965 938 kfogel
./autogen.sh\n"
     " _ * 965 922 sussman
./build.conf\n"
     " M 965 687 joe
./buildcheck.sh\n",
- { 'u', 'v', 'N', 'q',
+ { 'u', 'v', 'N', 'q', 'd',
       svn_cl__auth_username_opt, svn_cl__auth_password_opt,
       svn_cl__no_auth_cache_opt, svn_cl__no_ignore_opt } },
   
@@ -1022,6 +1024,9 @@
         break;
       case 'q':
         opt_state.quiet = TRUE;
+ break;
+ case 'd':
+ opt_state.describe_only = TRUE;
         break;
       case svn_cl__xml_file_opt:
         err = svn_utf_cstring_to_utf8 (&opt_state.xml_file, opt_arg,
Index: ./status-cmd.c
===================================================================
--- ./status-cmd.c
+++ ./status-cmd.c Tue Aug 6 01:14:18 2002
@@ -47,6 +47,13 @@
   SVN_ERR (svn_cl__args_to_target_array (&targets, os, opt_state,
                                          FALSE, pool));
 
+ if (opt_state->describe_only)
+ {
+ svn_cl__describe_status_list ((opt_state->verbose ||
opt_state->update),
+ opt_state->verbose);
+ return SVN_NO_ERROR;
+ }
+
   /* Build an authentication object to give to libsvn_client. */
   auth_baton = svn_cl__make_auth_baton (opt_state, pool);
 
--------------------- patch ends ---------------------

-------------------- log message begins --------------------
Reimplementation of "svn status", with the goal of making it
possible for a program invoking "svn status" to tell what the
output means, even if new information is added to that output
in the future. The layout is now under the control of a data
structure which is used (1) when displaying status information
and (2) in response to "svn status" with the new --describe-only
command-line argument.

As a side-effect of this reimplementation, some magic numbers
and code duplication have gone away, and so have some
potential buffer overflows.

* cl.h (describe_only): New variable, used only by "svn status",
  true iff we should describe the fields in "svn status" output
  instead of generating a real status report.

* status.c: Rewritten.

* main.c (main): add handling for -d / --describe-only option.

* status-cmd.c (svn_cl__status): handle the case where describe_only
  is true.
--------------------- log message ends ---------------------

-- 
g
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sat Aug 31 00:23:48 2002

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.