On 10/20/07, Eric Gillespie <epg@pretzelnet.org> wrote:
> 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?
Good idea. A few minor nits:
> [[[
> 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")}} },
Why bother to override the description of an option that's only used
once? (ie, why introduce a completely dead string?)
>
> { "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 */
Add to this comment an explanation of how this relates to
svn_wc_status_kind. Why are none and normal merged, and incomplete
and missing? (Also, perhaps consider adding a non-doxygen
comment to the declaration of svn_wc_status_kind in svn_wc.h
suggesting that if you add kinds there, you might want to add a kind
here too.)
> +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 */
Er, do you mean repos lock here? Otherwise this line and the one
below sure sound the same.
> + 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 */
missing hyphen between "out" and "of"
> +};
> +
> 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,
Is it typical to declare variables that are of the type "bitmask
composed of elements of type enum foo |ed together" as the type "enum
foo"?
> + 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
>
>
--
David Glasser | glasser_at_davidglasser.net | http://www.davidglasser.net/
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Tue Oct 23 00:40:58 2007