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

[PATCH] New feature: status --filter

From: Eric Gillespie <epg_at_pretzelnet.org>
Date: 2007-10-21 03:57:49 CEST

I've gotten many complaints from users over the years about the
inability to see just the files with conflicts (especially after
a giant scrollback-blowing merge), or just the files that need to
be added, and so on. I give them little grep or sed recipes, but
they grumble; Windows users hate having to install msys or
cygwin, and not everyone who uses Unix systems is familiar with
these tools. Heck, I sometimes waste a few minutes trying to
come up with just the right regular expression.

So, how about this?

[[[
Add status --filter, for showing only items whose status matches
specified criteria.

* subversion/svn/cl.h
  (svn_cl__filter_opt): Add enumerated value for --filter.
  (svn_cl__opt_state_t): Add new filter_arg field.

* subversion/svn/main.c
  (svn_cl__options): Add --filter option and basic help text.
  (svn_cl__cmd_table): Add --filter option to status command, with
    status-specific help text.
  (main): Store --filter opt_arg in opt_state.filter_arg.

* subversion/svn/status-cmd.c
  (enum svn_cl__status_kind): Add enumerated values for filtering
    svn_wc_status_kind values.
  (struct status_filter): Add filter structure for which status types to show.
  (struct status_baton): Add struct status_filter filter field.
  (filter_match_status_kind): Add function to indicate whether a
    svn_wc_status_kind matches a svn_cl__status_kind.
  (filter_match): Add function to indicate whether a svn_wc_status2_t
    matches a struct status_filter, using filter_match_status_kind for
    text and prop status kinds.
  (print_status): Return without processing the status item if
    sb->filter doesn't match it, using filter_match.
  (parse_filter): Parse a --filter argument into a struct status_filter.
  (svn_cl__status): Parse opt_state->filter_arg into sb.filter using
    parse_filter.

* subversion/tests/cmdline/stat_tests.py
  (status_filter): Add tests.

* subversion/tests/cmdline/svntest/actions.py
  (run_and_verify_status): Take optional tuple of addition arguments
    to be passed to status, so status_filter test can pass --filter.
]]]

Index: subversion/tests/cmdline/stat_tests.py
===================================================================
--- subversion/tests/cmdline/stat_tests.py (revision 27293)
+++ subversion/tests/cmdline/stat_tests.py (working copy)
@@ -1553,6 +1553,128 @@
                                      [],
                                      "status", "-u")
 
+def status_filter(sbox):
+ "status --filter"
+
+ # First check invalid usage.
+ svntest.actions.run_and_verify_svn(None, None,
+ ".*Unknown --filter status code 'd'",
+ 'status', '--filter', 'd')
+
+ # Now prepare for real testing.
+ sbox.build()
+ wc_dir = sbox.wc_dir
+ A_path = os.path.join(wc_dir, 'A')
+ mu_path = os.path.join(A_path, 'mu')
+ alpha_path = os.path.join(A_path, 'B', 'E', 'alpha')
+ beta_url = sbox.repo_url + '/A/B/E/beta'
+ lambda_path = os.path.join(A_path, 'B', 'lambda')
+ D_path = os.path.join(A_path, 'D')
+ gamma_path = os.path.join(D_path, 'gamma')
+ rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
+ # Add one line to mu and gamma, commit this as r2.
+ change_files_and_commit(wc_dir, ['A/mu', 'A/D/gamma'])
+ # Make a second wc.
+ wc2 = sbox.add_wc_path('wc2')
+ svntest.actions.duplicate_dir(wc_dir, wc2)
+ iota = os.path.join(wc2, 'iota')
+ # Update mu and gamma to r1.
+ expected_output = svntest.wc.State(wc_dir, {
+ 'A/mu' : Item(status='U '),
+ 'A/D/gamma' : Item(status='U '),
+ })
+ expected_disk = svntest.main.greek_state.copy()
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ svntest.actions.run_and_verify_update(wc_dir, expected_output,
+ expected_disk, expected_status,
+ None, None, None, None, None, None,
+ '-r1', wc_dir)
+ # Switch alpha.
+ expected_output = svntest.wc.State(wc_dir, {
+ 'A/B/E/alpha' : Item(status='U '),
+ })
+ expected_disk.tweak('A/B/E/alpha', contents="This is the file 'beta'.\n")
+ expected_status.tweak('A/B/E/alpha', wc_rev=2, switched='S')
+ svntest.actions.run_and_verify_switch(wc_dir, alpha_path, beta_url,
+ expected_output,
+ expected_disk, expected_status)
+ # Add a different line to now-out-of-date mu.
+ svntest.main.file_append(mu_path, "new line for mu\n")
+ expected_disk.tweak('A/mu', contents=("This is the file 'mu'.\n"
+ "new line for mu\n"))
+ expected_status.tweak('A/mu', wc_rev=1, status='M ')
+ # Same for gamma, but update it again, giving it a text conflict.
+ svntest.main.file_append(gamma_path, "new line for gamma\n")
+ expected_output = svntest.wc.State(wc_dir, {
+ 'A/D/gamma' : Item(status='C '),
+ })
+ expected_disk.tweak('A/D/gamma', contents=("This is the file 'gamma'.\n"
+ "<<<<<<< .mine\n"
+ "new line for gamma\n"
+ "=======\n"
+ "new line of text>>>>>>> .r2\n"))
+ expected_status.tweak('A/D/gamma', wc_rev=2, status='C ')
+ extra_files = ['gamma.*\.r1', 'gamma.*\.r2', 'gamma.*\.mine']
+ svntest.actions.run_and_verify_update(wc_dir, expected_output,
+ expected_disk, expected_status,
+ None,
+ svntest.tree.detect_conflict_files,
+ extra_files,
+ None, None, None,
+ gamma_path)
+ # Copy rho to D.
+ svntest.actions.run_and_verify_svn(None, None, [],
+ 'cp', rho_path, D_path)
+ # Lock lambda in wc_dir and iota in wc2.
+ svntest.actions.run_and_verify_svn(None, ".*locked by user", [], 'lock',
+ '--username', svntest.main.wc_author,
+ '--password', svntest.main.wc_passwd,
+ '-m', 'lambda lock comment', lambda_path)
+ svntest.actions.run_and_verify_svn(None, ".*locked by user", [], 'lock',
+ '--username', svntest.main.wc_author,
+ '--password', svntest.main.wc_passwd,
+ '-m', 'iota lock comment', iota)
+ # Finally we test.
+
+ # only modified
+ expected_status = svntest.wc.State(wc_dir, {
+ 'A/mu': Item(status='M ', wc_rev=1),
+ })
+ svntest.actions.run_and_verify_status(wc_dir, expected_status,
+ args=('--filter', 'M'))
+ # only locked
+ expected_status = svntest.wc.State(wc_dir, {
+ 'A/B/lambda': Item(status=' ', writelocked='K', wc_rev=1),
+ 'iota': Item(status=' ', writelocked='O', wc_rev=1),
+ })
+ svntest.actions.run_and_verify_status(wc_dir, expected_status,
+ args=('--filter', 'K'))
+ # only wc locks
+ expected_status = svntest.wc.State(wc_dir, {})
+ svntest.actions.run_and_verify_status(wc_dir, expected_status,
+ args=('--filter', 'L'))
+ # only out-of-date or modified
+ expected_status = svntest.wc.State(wc_dir, {
+ 'A/mu': Item(status='M ', wc_rev=1),
+ })
+ svntest.actions.run_and_verify_status(wc_dir, expected_status,
+ args=('--filter', '*M'))
+ # only out-of-date or conflicts
+ expected_status = svntest.wc.State(wc_dir, {
+ 'A/mu': Item(status='M ', wc_rev=1),
+ 'A/D/gamma': Item(status='C ', wc_rev=2),
+ })
+ svntest.actions.run_and_verify_status(wc_dir, expected_status,
+ args=('--filter', '*C'))
+ # only copied or switched
+ expected_status = svntest.wc.State(wc_dir, {
+ 'A/B/E/alpha': Item(status=' ', wc_rev=2, switched='S'),
+ 'A/D/rho': Item(status='A ', copied='+', wc_rev='-'),
+ })
+ svntest.actions.run_and_verify_status(wc_dir, expected_status,
+ args=('--filter', '+S'))
+
+
 ########################################################################
 # Run the tests
 
@@ -1590,6 +1712,7 @@
               status_depth_local,
               status_depth_update,
               status_dash_u_type_change,
+ status_filter,
              ]
 
 if __name__ == '__main__':
Index: subversion/tests/cmdline/svntest/actions.py
===================================================================
--- subversion/tests/cmdline/svntest/actions.py (revision 27293)
+++ subversion/tests/cmdline/svntest/actions.py (working copy)
@@ -840,11 +840,12 @@
                           singleton_handler_a = None,
                           a_baton = None,
                           singleton_handler_b = None,
- b_baton = None):
+ b_baton = None,
+ args = ()):
   """Run 'status' on WC_DIR_NAME and compare it with the
   expected OUTPUT_TREE. SINGLETON_HANDLER_A and SINGLETON_HANDLER_B will
   be passed to tree.compare_trees - see that function's doc string for
- more details.
+ more details. ARGS is an optional tuple of additional arguments.
   Returns on success, raises on failure."""
 
   if isinstance(output_tree, wc.State):
@@ -853,7 +854,7 @@
   output, errput = main.run_svn (None, 'status', '-v', '-u', '-q',
                                  '--username', main.wc_author,
                                  '--password', main.wc_passwd,
- wc_dir_name)
+ wc_dir_name, *args)
 
   actual = tree.build_tree_from_status (output)
 
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h (revision 27293)
+++ subversion/svn/cl.h (working copy)
@@ -55,6 +55,7 @@
   svn_cl__dry_run_opt,
   svn_cl__editor_cmd_opt,
   svn_cl__encoding_opt,
+ svn_cl__filter_opt,
   svn_cl__force_log_opt,
   svn_cl__force_opt,
   svn_cl__keep_changelist_opt,
@@ -207,6 +208,7 @@
   svn_boolean_t parents; /* create intermediate directories */
   svn_boolean_t use_merge_history; /* use/display extra merge information */
   svn_cl__accept_t accept_which; /* how to handle conflicts */
+ char *filter_arg; /* --filter ARG */
 
 } svn_cl__opt_state_t;
 
Index: subversion/svn/main.c
===================================================================
--- subversion/svn/main.c (revision 27293)
+++ subversion/svn/main.c (working copy)
@@ -57,6 +57,7 @@
  */
 const apr_getopt_option_t svn_cl__options[] =
 {
+ {"filter", svn_cl__filter_opt, 1, N_("filter output by ARG")},
   {"force", svn_cl__force_opt, 0, N_("force operation to run")},
   {"force-log", svn_cl__force_log_opt, 0,
                     N_("force validity of log message source")},
@@ -830,7 +831,9 @@
     { 'u', 'v', 'N', svn_cl__depth_opt, 'q', svn_cl__no_ignore_opt,
       svn_cl__incremental_opt, svn_cl__xml_opt, SVN_CL__AUTH_OPTIONS,
       svn_cl__config_dir_opt, svn_cl__ignore_externals_opt,
- svn_cl__changelist_opt} },
+ svn_cl__changelist_opt, svn_cl__filter_opt},
+ {{svn_cl__filter_opt,
+ N_("only show items matching the list of status codes\n")}} },
 
   { "switch", svn_cl__switch, {"sw"}, N_
     ("Update the working copy to a different URL.\n"
@@ -1414,6 +1417,9 @@
                                _("'%s' is not a valid accept value"), opt_arg),
              pool, "svn: ");
         break;
+ case svn_cl__filter_opt:
+ opt_state.filter_arg = apr_pstrdup(pool, opt_arg);
+ break;
       default:
         /* Hmmm. Perhaps this would be a good place to squirrel away
            opts that commands like svn diff might need. Hmmm indeed. */
Index: subversion/svn/status-cmd.c
===================================================================
--- subversion/svn/status-cmd.c (revision 27293)
+++ subversion/svn/status-cmd.c (working copy)
@@ -39,6 +39,34 @@
 
 /*** Code. ***/
 
+/* values for text and prop fields of struct status_filter */
+enum svn_cl__status_kind {
+ svn_cl__status_none_or_normal = 1 << 0,
+ svn_cl__status_unversioned = 1 << 1,
+ svn_cl__status_added = 1 << 2,
+ svn_cl__status_incomplete_or_missing = 1 << 3,
+ svn_cl__status_deleted = 1 << 4,
+ svn_cl__status_replaced = 1 << 5,
+ svn_cl__status_modified = 1 << 6,
+ svn_cl__status_merged = 1 << 7,
+ svn_cl__status_conflicted = 1 << 8,
+ svn_cl__status_ignored = 1 << 9,
+ svn_cl__status_obstructed = 1 << 10,
+ svn_cl__status_external = 1 << 11,
+};
+
+/* which status types to show */
+struct status_filter
+{
+ svn_boolean_t all; /* show all */
+ enum svn_cl__status_kind kind; /* bit field for text/prop status to show */
+ svn_boolean_t locked; /* show items locked with wc lock */
+ svn_boolean_t wc_locked; /* show items with wc locks */
+ svn_boolean_t copied; /* show copied items */
+ svn_boolean_t switched; /* show switched items */
+ svn_boolean_t out_of_date; /* show outof-date items */
+};
+
 struct status_baton
 {
   /* These fields all correspond to the ones in the
@@ -55,6 +83,7 @@
   svn_boolean_t had_print_error; /* To avoid printing lots of errors if we get
                                      errors while printing to stdout */
   svn_boolean_t xml_mode;
+ struct status_filter filter; /* which status types to show */
 };
 
 
@@ -134,6 +163,95 @@
 }
 
 
+/* Return TRUE if FILTER matches STATUS_KIND, else FALSE. */
+static svn_boolean_t
+filter_match_status_kind(enum svn_cl__status_kind filter,
+ enum svn_wc_status_kind status_kind)
+{
+ switch (status_kind)
+ {
+ case svn_wc_status_none:
+ case svn_wc_status_normal:
+ if (filter & svn_cl__status_none_or_normal)
+ return TRUE;
+ break;
+ case svn_wc_status_unversioned:
+ if (filter & svn_cl__status_unversioned)
+ return TRUE;
+ break;
+ case svn_wc_status_added:
+ if (filter & svn_cl__status_added)
+ return TRUE;
+ break;
+ case svn_wc_status_incomplete:
+ case svn_wc_status_missing:
+ if (filter & svn_cl__status_incomplete_or_missing)
+ return TRUE;
+ break;
+ case svn_wc_status_deleted:
+ if (filter & svn_cl__status_deleted)
+ return TRUE;
+ break;
+ case svn_wc_status_replaced:
+ if (filter & svn_cl__status_replaced)
+ return TRUE;
+ break;
+ case svn_wc_status_modified:
+ if (filter & svn_cl__status_modified)
+ return TRUE;
+ break;
+ case svn_wc_status_merged:
+ if (filter & svn_cl__status_merged)
+ return TRUE;
+ break;
+ case svn_wc_status_conflicted:
+ if (filter & svn_cl__status_conflicted)
+ return TRUE;
+ break;
+ case svn_wc_status_ignored:
+ if (filter & svn_cl__status_ignored)
+ return TRUE;
+ break;
+ case svn_wc_status_obstructed:
+ if (filter & svn_cl__status_obstructed)
+ return TRUE;
+ break;
+ case svn_wc_status_external:
+ if (filter & svn_cl__status_external)
+ return TRUE;
+ break;
+ }
+ return FALSE;
+}
+
+/* Return TRUE if FILTER matches STATUS, else FALSE. */
+static svn_boolean_t
+filter_match(struct status_filter *filter, svn_wc_status2_t *status)
+{
+ if (filter->all)
+ return TRUE;
+ if (filter_match_status_kind(filter->kind, status->text_status))
+ return TRUE;
+ if (filter_match_status_kind(filter->kind, status->prop_status))
+ return TRUE;
+ if (filter->locked &&
+ ((status->entry && status->entry->lock_token)
+ || status->repos_lock))
+ return TRUE;
+ if (filter->wc_locked && status->locked)
+ return TRUE;
+ if (filter->copied && status->copied)
+ return TRUE;
+ if (filter->switched && status->switched)
+ return TRUE;
+ if (filter->out_of_date
+ && (status->repos_text_status != svn_wc_status_none
+ || status->repos_prop_status != svn_wc_status_none))
+ return TRUE;
+ return FALSE;
+}
+
+
 /* A status callback function for printing STATUS for PATH. */
 static void
 print_status(void *baton,
@@ -142,6 +260,9 @@
 {
   struct status_baton *sb = baton;
 
+ if (!filter_match(&sb->filter, status))
+ return;
+
   /* If there's a changelist attached to the entry, then we don't print
      the item, but instead dup & cache the status structure for later. */
   if (status->entry && status->entry->changelist)
@@ -200,6 +321,92 @@
   return SVN_NO_ERROR;
 }
 
+
+/* Parse status codes from FILTER_ARG into FILTER, allocating in POOL.
+ If FILTER_ARG is NULL, set FILTER->all to TRUE. */
+static svn_error_t *
+parse_filter(struct status_filter *filter, char *filter_arg,
+ apr_pool_t *pool)
+{
+ const char *p;
+ if (filter_arg == NULL)
+ {
+ filter->all = TRUE;
+ return SVN_NO_ERROR;
+ }
+ filter->all = FALSE;
+ filter->kind = 0;
+ filter->locked = filter->wc_locked = filter->copied = FALSE;
+ filter->switched = filter->out_of_date = FALSE;
+
+ for (p = filter_arg; *p; p++)
+ {
+ switch (*p)
+ {
+ /* text and property status codes in columns 1 and 2 */
+ case ' ':
+ filter->kind |= svn_cl__status_none_or_normal;
+ break;
+ case 'A':
+ filter->kind |= svn_cl__status_added;
+ break;
+ case '!':
+ filter->kind |= svn_cl__status_incomplete_or_missing;
+ break;
+ case 'D':
+ filter->kind |= svn_cl__status_deleted;
+ break;
+ case 'R':
+ filter->kind |= svn_cl__status_replaced;
+ break;
+ case 'M':
+ filter->kind |= svn_cl__status_modified;
+ break;
+ case 'G':
+ filter->kind |= svn_cl__status_merged;
+ break;
+ case 'C':
+ filter->kind |= svn_cl__status_conflicted;
+ break;
+ case '~':
+ filter->kind |= svn_cl__status_obstructed;
+ break;
+ case 'I':
+ filter->kind |= svn_cl__status_ignored;
+ break;
+ case 'X':
+ filter->kind |= svn_cl__status_external;
+ break;
+ case '?':
+ filter->kind |= svn_cl__status_unversioned;
+ break;
+ /* status codes in other columns */
+ case 'K':
+ case 'O':
+ case 'T':
+ case 'B':
+ filter->locked = TRUE;
+ break;
+ case 'L':
+ filter->wc_locked = TRUE;
+ break;
+ case '+':
+ filter->copied = TRUE;
+ break;
+ case 'S':
+ filter->switched = TRUE;
+ break;
+ case '*':
+ filter->out_of_date = TRUE;
+ break;
+ default:
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Unknown --filter status code '%c'"), *p);
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
 /* This implements the `svn_opt_subcommand_t' interface. */
 svn_error_t *
 svn_cl__status(apr_getopt_t *os,
@@ -216,6 +423,8 @@
   svn_opt_revision_t rev;
   struct status_baton sb;
 
+ SVN_ERR(parse_filter(&sb.filter, opt_state->filter_arg, pool));
+
   /* Before allowing svn_opt_args_to_target_array2() to canonicalize
      all the targets, we need to build a list of targets made of both
      ones the user typed, as well as any specified by --changelist. */

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sun Oct 21 03:58:00 2007

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