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 Sat Aug 31 01:53:19 2002 @@ -21,6 +21,10 @@ /*** Includes. ***/ +#include +#include +#include + #include #include #include "svn_sorts.h" @@ -29,200 +33,328 @@ #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; + apr_size_t width; + void (*fill_in)(char *, apr_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, apr_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. */ + apr_size_t copy_length = width <= 20 ? width : 20; + apr_size_t temp_offset = 20 - copy_length; + apr_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, apr_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, apr_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, apr_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, apr_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, apr_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, apr_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, apr_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, apr_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; + { + apr_size_t length = strlen (author); + apr_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 apr_size_t +traverse_fields (char *buffer, + char variant, + const svn_wc_status_t *status) +{ + apr_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]; + apr_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) +{ + apr_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 +366,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 +397,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 Sat Aug 31 01:57:38 2002 @@ -75,6 +75,8 @@ {"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", svn_cl__describe_only_opt, 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 +376,8 @@ " 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 --describe-only, describe the output format\n" + " (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" @@ -396,7 +400,8 @@ " M 965 687 joe ./buildcheck.sh\n", { 'u', 'v', 'N', 'q', svn_cl__auth_username_opt, svn_cl__auth_password_opt, - svn_cl__no_auth_cache_opt, svn_cl__no_ignore_opt } }, + svn_cl__no_auth_cache_opt, svn_cl__no_ignore_opt, + svn_cl__describe_only_opt } }, { "switch", svn_cl__switch, {"sw"}, "Update working copy to mirror a new URL\n" @@ -1022,6 +1027,9 @@ break; case 'q': opt_state.quiet = TRUE; + break; + case svn_cl__describe_only_opt: + 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);