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

[PATCH] Make --accept match conflict handling prompt

From: Eric Gillespie <epg_at_google.com>
Date: 2007-09-29 04:29:08 CEST

As part of this change, I renamed a couple identifiers to say
"their" instead of "repos", because both "repos" and "base" are
from the repository; the name "repos" is not clear at all:

  - svn_wc_conflict_description_t.repos_file
  + svn_wc_conflict_description_t.their_file
  - svn_wc_conflict_result_choose_repos
  + svn_wc_conflict_result_choose_theirs

This also happens to match the UI for the interactive conflict
resolution prompt, and for --accept. This is also a good thing,
I think. I asked Sussman about it in IRC, and he said he was
fine with the change. I'd like to go for more consistency, and
rename the user identifiers as well:

  - svn_wc_conflict_description_t.user_file
  + svn_wc_conflict_description_t.my_file
  - svn_wc_conflict_result_choose_user
  + svn_wc_conflict_result_choose_mine

What do people think?

I have another XXX comment in this patch. I know how to test
editor code on unix:

+ # XXX: I know how to test --accept=edit on unix, but not on
+ # other systems...
+ test_file = os.path.abspath(os.path.join(wc_dir, 'conflict-test'))
+ test_editor = "sh -c 'echo $* > %s' 0" % (test_file,)
+ svntest.actions.run_and_verify_svn(None,
+ ['G %s\n' % (rho_path_backup,),
+ 'Updated to revision 2.\n'],
+ [],
+ 'update', '--accept=edit',
+ '--editor-cmd', test_editor,
+ rho_path_backup)
+ got = svntest.main.file_read(test_file)
+ expected = 'tempfile.*\.tmp\n'
+ if not re.match(expected, got):
+ raise svntest.main.Failure('%s !~ %s' % (got, expected))

But I know that won't work on Windows. Is there any way to test
this portably, or should I only test this when os.name == 'posix'?

[[[
Resolve issue 2946: --accept does not match conflict handling prompt.

* subversion/include/svn_client.h
  (svn_client_resolved2): Replace svn_accept_t argument with
    svn_wc_conflict_result_t.

* subversion/include/svn_types.h
  (svn_accept_t, svn_accept_from_word): Remove.

* subversion/include/svn_wc.h
  (svn_wc_conflict_description_t): Rename repos_file to their_file (XXX and
    user_file to my_file?).
  (svn_wc_conflict_result_t): Rename svn_wc_conflict_result_choose_repos to
    svn_wc_conflict_result_choose_theirs (XXX and
    svn_wc_conflict_result_choose_user to
    svn_wc_conflict_result_choose_mine?).
  (svn_wc_resolved_conflict3): Replace svn_accept_t argument with
    svn_wc_conflict_result_t.

* subversion/libsvn_client/resolved.c
  (svn_client_resolved, svn_client_resolved2): Adapt.

* subversion/libsvn_subr/kitchensink.c
  (svn_accept_from_word): Remove.

* subversion/libsvn_wc/adm_crawler.c
  (restore_file): Adapt to svn_wc_resolved_conflict3 changes.

* subversion/libsvn_wc/adm_ops.c
  (resolve_conflict_on_entry, struct resolve_callback_baton): Replace
    svn_accept_t argument with svn_wc_conflict_result_t.
  (resolve_found_entry_callback, svn_wc_resolved_conflict2,
   svn_wc_resolved_conflict3): Adapt.

* subversion/libsvn_wc/merge.c
  (svn_wc__merge_internal): Adapt to svn_wc_conflict_description_t changes.

* subversion/tests/cmdline/basic_tests.py
  (automatic_conflict_resolution): Adapt.

* subversion/tests/cmdline/update_tests.py
  (update_accept_conflicts): Add test for update --accept options.

* subversion/svn/cl.h
  (svn_cl__accept_t): New enumerated type, based on old svn_accept_t but
    with values matching the interactive callback prompt.
  (svn_cl__opt_state_t): Change accept_which to svn_cl__accept_t.
  (svn_cl__conflict_baton_t): Add type for svn_cl__conflict_handler baton.
  (svn_cl__conflict_baton_new, svn_cl__accept_from_word): Declare.
  (svn_cl__conflict_handler): Rename from svn_cl__interactive_conflict_handler.

* subversion/svn/conflict-callbacks.c
  (svn_cl__conflict_baton_new): Add svn_cl__conflict_baton_t constructor.
  (svn_cl__accept_from_word): Adapted from old svn_accept_from_word.
  (print_conflict_description): Adapt to svn_wc_resolved_conflict3 changes.
  (svn_cl__conflict_handler): Rename from svn_cl__interactive_conflict_handler.
    Resolve conflicts based on --accept option if given, else by prompting.

* subversion/svn/main.c
  (svn_cl__option): Adapt help text for --accept option.
  (svn_cl__cmd_table): Use specific --accept help text for resolved, which
    does not accept all --accept actions. Add --accept for update.
  (main): Initialize opt_state.accept_which to svn_cl__accept_invalid. Block
    --accept=launch until it is implemented. Setup ctx->conflict_func based
    on the intersection of the interactive-conflicts config option, --accept
    option, and --non-interactive option.

* subversion/svn/resolved-cmd.c
  (svn_cl__resolved): Turn some --accept actions into the appropriate
    svn_wc_conflict_result_t value to pass to svn_client_resolved2; return
    an error for the rest.
]]]

Index: subversion/include/svn_types.h
===================================================================
--- subversion/include/svn_types.h (revision 26821)
+++ subversion/include/svn_types.h (working copy)
@@ -221,37 +221,6 @@
   svn_recursive
 };
 
-/** The concept of automatic conflict resolution.
- *
- * @since New in 1.5.
- */
-typedef enum
-{
- /* Invalid accept flag */
- svn_accept_invalid = -1,
-
- /* Resolve the conflict as usual */
- svn_accept_none,
-
- /* Resolve the conflict with the pre-conflict base file */
- svn_accept_left,
-
- /* Resolve the conflict with the pre-conflict working copy file */
- svn_accept_working,
-
- /* Resolve the conflict with the post-conflict base file */
- svn_accept_right,
-
-} svn_accept_t;
-
-/** Return the appropriate accept for @a accept_str. @a word is as
- * returned from svn_accept_to_word().
- *
- * @since New in 1.5.
- */
-svn_accept_t
-svn_accept_from_word(const char *word);
-
 /** The concept of depth for directories.
  *
  * @note This is similar to, but not exactly the same as, the WebDAV
Index: subversion/include/svn_wc.h
===================================================================
--- subversion/include/svn_wc.h (revision 26821)
+++ subversion/include/svn_wc.h (working copy)
@@ -1007,7 +1007,8 @@
    * they default to NULL.) */
 
   const char *base_file; /* common ancestor of the two files being merged */
- const char *repos_file; /* repository's version of the file */
+ const char *their_file; /* their version of the file */
+ /* XXX I'd also prefer "my" here and "mine" below; why be inconsistent? */
   const char *user_file; /* user's locally-edited version of the file */
   const char *merged_file; /* merged version of file; has conflict markers */
 
@@ -1036,7 +1037,7 @@
      "installing" the chosen file as the final version of the file.*/
 
   svn_wc_conflict_result_choose_base, /* user chooses the base file */
- svn_wc_conflict_result_choose_repos, /* user chooses the repository file */
+ svn_wc_conflict_result_choose_theirs, /* user chooses their file */
   svn_wc_conflict_result_choose_user, /* user chooses own version of file */
   svn_wc_conflict_result_choose_merged /* user chooses the merged-file
                                            (which she may have
@@ -2662,15 +2663,12 @@
  * if any); if @c svn_depth_infinity, resolve @a path and every
  * conflicted file or directory anywhere beneath it.
  *
- * @a accept_ is the argument used to facilitate automatic conflict resolution.
- * If @a accept_ is svn_accept_left, the contents of the conflicted file will
- * be replaced with the prestine contents of the pre-modification base file
- * contents. If @a accept_ is svn_accept_right, the contents of the conflicted
- * file will be replaced with the post-conflict base file contents. If @a
- * accept_ is svn_accept_working, the contents of the conflicted file will be
- * the content of the pre-conflict working copy file. If @a accept_ is
- * svn_accept_default, conflict resolution will be handled just like before
- * automatic conflict resolution was availble.
+ * If @a conflict_result is svn_wc_conflict_result_choose_base, resolve the
+ * conflict with the old file contents; if
+ * svn_wc_conflict_result_choose_user, use the original working contents;
+ * if svn_wc_conflict_result_choose_theirs, the new contents; and if
+ * svn_wc_conflict_result_choose_merged, don't change the contents at all,
+ * just remove the conflict status (i.e. pre-1.5 behavior).
  *
  * @a adm_access is an access baton, with a write lock, for @a path.
  *
@@ -2700,7 +2698,7 @@
                                        svn_boolean_t resolve_text,
                                        svn_boolean_t resolve_props,
                                        svn_depth_t depth,
- svn_accept_t accept_,
+ svn_wc_conflict_result_t conflict_result,
                                        svn_wc_notify_func2_t notify_func,
                                        void *notify_baton,
                                        svn_cancel_func_t cancel_func,
Index: subversion/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h (revision 26821)
+++ subversion/include/svn_client.h (working copy)
@@ -2705,17 +2705,12 @@
  * if any); if @c svn_depth_infinity, resolve @a path and every
  * conflicted file or directory anywhere beneath it.
  *
- * @a accept_which is the argument used to facilitate automatic
- * conflict resolution. If @a accept_which is svn_accept_left, the
- * contents of the conflicted file will be replaced with the prestine
- * contents of the pre-modification base file contents. If @a
- * accept_which is svn_accept_right, the contents of the conflicted
- * file will be replaced with the post-conflict base file contents. If @a
- * accept_which is svn_accept_working, the contents of the conflicted
- * file will be the content of the pre-conflict working copy file. If
- * @a accept_which is svn_accept_default, conflict resolution will
- * be handled just like before automatic conflict resolution was
- * availble.
+ * If @a conflict_result is svn_wc_conflict_result_choose_base, resolve the
+ * conflict with the old file contents; if
+ * svn_wc_conflict_result_choose_user, use the original working contents;
+ * if svn_wc_conflict_result_choose_theirs, the new contents; and if
+ * svn_wc_conflict_result_choose_merged, don't change the contents at all,
+ * just remove the conflict status (i.e. pre-1.5 behavior).
  *
  * If @a path is not in a state of conflict to begin with, do nothing.
  * If @a path's conflict state is removed and @a ctx->notify_func2 is non-null,
@@ -2726,7 +2721,7 @@
 svn_error_t *
 svn_client_resolved2(const char *path,
                      svn_depth_t depth,
- svn_accept_t accept_which,
+ svn_wc_conflict_result_t conflict_result,
                      svn_client_ctx_t *ctx,
                      apr_pool_t *pool);
 
Index: subversion/libsvn_wc/merge.c
===================================================================
--- subversion/libsvn_wc/merge.c (revision 26821)
+++ subversion/libsvn_wc/merge.c (working copy)
@@ -416,7 +416,7 @@
               cdesc.action = svn_wc_conflict_action_edit;
               cdesc.reason = svn_wc_conflict_reason_edited;
               cdesc.base_file = left;
- cdesc.repos_file = right;
+ cdesc.their_file = right;
               cdesc.user_file = tmp_target;
               cdesc.merged_file = result_target;
 
@@ -436,7 +436,7 @@
                       contains_conflicts = FALSE;
                       goto merge_complete;
                     }
- case svn_wc_conflict_result_choose_repos:
+ case svn_wc_conflict_result_choose_theirs:
                     {
                       SVN_ERR(svn_wc__loggy_copy
                               (log_accum, NULL, adm_access,
@@ -648,7 +648,7 @@
           cdesc.action = svn_wc_conflict_action_edit;
           cdesc.reason = svn_wc_conflict_reason_edited;
           cdesc.base_file = left;
- cdesc.repos_file = right;
+ cdesc.their_file = right;
           cdesc.user_file = tmp_target;
           cdesc.merged_file = NULL; /* notice there is NO merged file! */
 
@@ -669,7 +669,7 @@
                   contains_conflicts = FALSE;
                   goto merge_complete;
                 }
- case svn_wc_conflict_result_choose_repos:
+ case svn_wc_conflict_result_choose_theirs:
                 {
                   SVN_ERR(svn_wc__loggy_copy
                           (log_accum, NULL, adm_access,
Index: subversion/libsvn_wc/adm_crawler.c
===================================================================
--- subversion/libsvn_wc/adm_crawler.c (revision 26821)
+++ subversion/libsvn_wc/adm_crawler.c (working copy)
@@ -88,7 +88,8 @@
 
   /* Remove any text conflict */
   SVN_ERR(svn_wc_resolved_conflict3(file_path, adm_access, TRUE, FALSE,
- svn_depth_empty, svn_accept_left,
+ svn_depth_empty,
+ svn_wc_conflict_result_choose_merged,
                                     NULL, NULL, NULL, NULL, pool));
 
   if (use_commit_times)
Index: subversion/libsvn_wc/adm_ops.c
===================================================================
--- subversion/libsvn_wc/adm_ops.c (revision 26821)
+++ subversion/libsvn_wc/adm_ops.c (working copy)
@@ -2546,9 +2546,12 @@
 
 /* Conflict resolution involves removing the conflict files, if they exist,
    and clearing the conflict filenames from the entry. The latter needs to
- be done whether or not the conflict files exist. If @a accept is anything
- but svn_accept_default, automatically resolve the
- conflict with the respective temporary file contents.
+ be done whether or not the conflict files exist. If @a conflict_result
+ is svn_wc_conflict_result_choose_base, resolve the conflict with the old
+ file contents; if svn_wc_conflict_result_choose_user, use the original
+ working contents; if svn_wc_conflict_result_choose_theirs, the new
+ contents; and if svn_wc_conflict_result_choose_merged, don't change the
+ contents at all, just remove the conflict status (i.e. pre-1.5 behavior).
 
    @since 1.5 Automatic Conflict Resolution (Issue 2784)
 
@@ -2563,7 +2566,7 @@
                           const char *base_name,
                           svn_boolean_t resolve_text,
                           svn_boolean_t resolve_props,
- svn_accept_t accept_which,
+ svn_wc_conflict_result_t conflict_result,
                           svn_wc_notify_func2_t notify_func,
                           void *notify_baton,
                           apr_pool_t *pool)
@@ -2575,23 +2578,23 @@
 
   /* Handle automatic conflict resolution before the temporary files are
    * deleted, if necessary. */
- switch (accept_which)
+ switch (conflict_result)
     {
- case svn_accept_left:
- auto_resolve_src = entry->conflict_old;
- break;
- case svn_accept_working:
- auto_resolve_src = entry->conflict_wrk;
- break;
- case svn_accept_right:
- auto_resolve_src = entry->conflict_new;
- break;
- case svn_accept_none:
- auto_resolve_src = NULL;
- break;
- case svn_accept_invalid:
- return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
- _("Invalid 'accept' argument"));
+ case svn_wc_conflict_result_choose_base:
+ auto_resolve_src = entry->conflict_old;
+ break;
+ case svn_wc_conflict_result_choose_user:
+ auto_resolve_src = entry->conflict_wrk;
+ break;
+ case svn_wc_conflict_result_choose_theirs:
+ auto_resolve_src = entry->conflict_new;
+ break;
+ case svn_wc_conflict_result_choose_merged:
+ auto_resolve_src = NULL;
+ break;
+ default:
+ return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Invalid 'conflict_result' argument"));
     }
 
     if (auto_resolve_src)
@@ -2675,7 +2678,7 @@
   /* TRUE if property conflicts are to be resolved. */
   svn_boolean_t resolve_props;
   /* The type of automatic conflict resolution to perform */
- svn_accept_t accept_;
+ svn_wc_conflict_result_t conflict_result;
   /* An access baton for the tree, with write access */
   svn_wc_adm_access_t *adm_access;
   /* Notification function and baton */
@@ -2710,7 +2713,7 @@
 
   return resolve_conflict_on_entry(path, entry, adm_access, base_name,
                                    baton->resolve_text, baton->resolve_props,
- baton->accept_, baton->notify_func,
+ baton->conflict_result, baton->notify_func,
                                    baton->notify_baton, pool);
 }
 
@@ -2758,7 +2761,8 @@
                           apr_pool_t *pool)
 {
   return svn_wc_resolved_conflict3(path, adm_access, resolve_text,
- resolve_props, recurse, svn_accept_none,
+ resolve_props, recurse,
+ svn_wc_conflict_result_choose_merged,
                                    notify_func, notify_baton, cancel_func,
                                    cancel_baton, pool);
 }
@@ -2769,7 +2773,7 @@
                           svn_boolean_t resolve_text,
                           svn_boolean_t resolve_props,
                           svn_depth_t depth,
- svn_accept_t accept_,
+ svn_wc_conflict_result_t conflict_result,
                           svn_wc_notify_func2_t notify_func,
                           void *notify_baton,
                           svn_cancel_func_t cancel_func,
@@ -2783,7 +2787,7 @@
   baton->adm_access = adm_access;
   baton->notify_func = notify_func;
   baton->notify_baton = notify_baton;
- baton->accept_ = accept_;
+ baton->conflict_result = conflict_result;
 
   if (depth == svn_depth_empty)
     {
Index: subversion/libsvn_subr/kitchensink.c
===================================================================
--- subversion/libsvn_subr/kitchensink.c (revision 26821)
+++ subversion/libsvn_subr/kitchensink.c (working copy)
@@ -68,20 +68,6 @@
   return uuid_str;
 }
 
-svn_accept_t
-svn_accept_from_word(const char *word)
-{
- if (strcmp(word, "left") == 0)
- return svn_accept_left;
- if (strcmp(word, "working") == 0)
- return svn_accept_working;
- if (strcmp(word, "right") == 0)
- return svn_accept_right;
- /* Return svn_accept_invalid which means that the passed string is not
- * a recognized accept option. */
- return svn_accept_invalid;
-}
-
 const char *
 svn_depth_to_word(svn_depth_t depth)
 {
Index: subversion/libsvn_client/resolved.c
===================================================================
--- subversion/libsvn_client/resolved.c (revision 26821)
+++ subversion/libsvn_client/resolved.c (working copy)
@@ -37,13 +37,14 @@
                     svn_client_ctx_t *ctx,
                     apr_pool_t *pool)
 {
- return svn_client_resolved2(path, recursive, svn_accept_none, ctx, pool);
+ return svn_client_resolved2(path, recursive,
+ svn_wc_conflict_result_choose_merged, ctx, pool);
 }
 
 svn_error_t *
 svn_client_resolved2(const char *path,
                      svn_depth_t depth,
- svn_accept_t accept_which,
+ svn_wc_conflict_result_t conflict_result,
                      svn_client_ctx_t *ctx,
                      apr_pool_t *pool)
 {
@@ -59,7 +60,7 @@
                                  pool));
 
   SVN_ERR(svn_wc_resolved_conflict3(path, adm_access, TRUE, TRUE, depth,
- accept_which,
+ conflict_result,
                                     ctx->notify_func2, ctx->notify_baton2,
                                     ctx->cancel_func, ctx->cancel_baton,
                                     pool));
Index: subversion/tests/cmdline/basic_tests.py
===================================================================
--- subversion/tests/cmdline/basic_tests.py (revision 26821)
+++ subversion/tests/cmdline/basic_tests.py (working copy)
@@ -2138,30 +2138,41 @@
   # So now lambda, mu and rho are all in a "conflicted" state. Run 'svn
   # resolved' with the respective "--accept[mine|orig|repo]" flag.
 
+ # But first, check --accept actions resolved does not accept.
+ svntest.actions.run_and_verify_svn(None,
+ # stdout, stderr
+ None,
+ ".*invalid 'accept' ARG",
+ 'resolved', '--accept=edit')
+ # TODO: when launch is implemented, this error message will match the rest.
+ svntest.actions.run_and_verify_svn(None,
+ # stdout, stderr
+ None,
+ '.*--accept=launch not yet implemented',
+ 'resolved', '--accept=launch')
+ svntest.actions.run_and_verify_svn(None,
+ # stdout, stderr
+ None,
+ ".*invalid 'accept' ARG",
+ 'resolved', '--accept=postpone')
   # Run 'svn resolved --accept=NOTVALID. Using omega for the test.
   svntest.actions.run_and_verify_svn("Resolved command", None,
- "svn: 'NOTVALID' is not a valid accept value; "
- "try 'left', 'right', or 'working'\n",
+ ".*NOTVALID' is not a valid accept value",
                                      'resolved',
                                      '--accept=NOTVALID',
                                      omega_path_backup)
 
- # Run 'svn resolved --accept=left. Using lambda for the test.
+ # Resolve lambda, mu, and rho with different --accept options.
   svntest.actions.run_and_verify_svn("Resolved command", None, [],
- 'resolved',
- '--accept=left',
+ 'resolved', '--accept=base',
                                      lambda_path_backup)
-
- # Run 'svn resolved --accept=working. Using mu for the test.
   svntest.actions.run_and_verify_svn("Resolved command", None, [],
                                      'resolved',
- '--accept=working',
+ '--accept=mine',
                                      mu_path_backup)
-
- # Run 'svn resolved --accept=right. Using rho for the test.
   svntest.actions.run_and_verify_svn("Resolved command", None, [],
                                      'resolved',
- '--accept=right',
+ '--accept=theirs',
                                      rho_path_backup)
 
   # Set the expected disk contents for the test
Index: subversion/tests/cmdline/update_tests.py
===================================================================
--- subversion/tests/cmdline/update_tests.py (revision 26821)
+++ subversion/tests/cmdline/update_tests.py (working copy)
@@ -3248,7 +3248,202 @@
                                         expected_disk,
                                         expected_status)
 
+#----------------------------------------------------------------------
 
+def update_accept_conflicts(sbox):
+ "update --accept automatic conflict resolution"
+
+ sbox.build()
+ wc_dir = sbox.wc_dir
+
+ # Make a backup copy of the working copy
+ wc_backup = sbox.add_wc_path('backup')
+ svntest.actions.duplicate_dir(wc_dir, wc_backup)
+
+ # Make a few local mods to files which will be committed
+ iota_path = os.path.join(wc_dir, 'iota')
+ lambda_path = os.path.join(wc_dir, 'A', 'B', 'lambda')
+ mu_path = os.path.join(wc_dir, 'A', 'mu')
+ alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha')
+ beta_path = os.path.join(wc_dir, 'A', 'B', 'E', 'beta')
+ rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
+ svntest.main.file_append(lambda_path, 'Their appended text for lambda\n')
+ svntest.main.file_append(iota_path, 'Their appended text for iota\n')
+ svntest.main.file_append(mu_path, 'Their appended text for mu\n')
+ svntest.main.file_append(alpha_path, 'Their appended text for alpha\n')
+ svntest.main.file_append(beta_path, 'Their appended text for beta\n')
+ svntest.main.file_append(rho_path, 'Their appended text for rho\n')
+
+ # Make a few local mods to files which will be conflicted
+ iota_path_backup = os.path.join(wc_backup, 'iota')
+ lambda_path_backup = os.path.join(wc_backup, 'A', 'B', 'lambda')
+ mu_path_backup = os.path.join(wc_backup, 'A', 'mu')
+ alpha_path_backup = os.path.join(wc_backup, 'A', 'B', 'E', 'alpha')
+ beta_path_backup = os.path.join(wc_backup, 'A', 'B', 'E', 'beta')
+ rho_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'rho')
+ svntest.main.file_append(iota_path_backup,
+ 'My appended text for iota\n')
+ svntest.main.file_append(lambda_path_backup,
+ 'My appended text for lambda\n')
+ svntest.main.file_append(mu_path_backup,
+ 'My appended text for mu\n')
+ svntest.main.file_append(alpha_path_backup,
+ 'My appended text for alpha\n')
+ svntest.main.file_append(beta_path_backup,
+ 'My appended text for beta\n')
+ svntest.main.file_append(rho_path_backup,
+ 'My appended text for rho\n')
+
+ # Created expected output tree for 'svn ci'
+ expected_output = svntest.wc.State(wc_dir, {
+ 'iota' : Item(verb='Sending'),
+ 'A/B/lambda' : Item(verb='Sending'),
+ 'A/mu' : Item(verb='Sending'),
+ 'A/B/E/alpha': Item(verb='Sending'),
+ 'A/B/E/beta': Item(verb='Sending'),
+ 'A/D/G/rho' : Item(verb='Sending'),
+ })
+
+ # Create expected status tree; all local revisions should be at 1,
+ # but mu and rho should be at revision 2.
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.tweak('iota', wc_rev=2)
+ expected_status.tweak('A/B/lambda', wc_rev=2)
+ expected_status.tweak('A/mu', wc_rev=2)
+ expected_status.tweak('A/B/E/alpha', wc_rev=2)
+ expected_status.tweak('A/B/E/beta', wc_rev=2)
+ expected_status.tweak('A/D/G/rho', wc_rev=2)
+
+ # Commit.
+ svntest.actions.run_and_verify_commit(wc_dir, expected_output,
+ expected_status, None,
+ None, None, None, None, wc_dir)
+
+ # Now we'll update each of our 5 files in wc_backup; each one will get
+ # conflicts, and we'll handle each with a different --accept option.
+
+# os.chdir(wc_backup)
+# os.system('zsh')
+
+ # iota: no accept option
+ # Just leave the conflicts alone, since run_and_verify_svn already uses
+ # the --non-interactive option.
+ svntest.actions.run_and_verify_svn(None,
+ ['C %s\n' % (iota_path_backup,),
+ 'Updated to revision 2.\n'],
+ [],
+ 'update', iota_path_backup)
+
+ # lambda: --accept=postpone
+ # Just leave the conflicts alone.
+ svntest.actions.run_and_verify_svn(None,
+ ['C %s\n' % (lambda_path_backup,),
+ 'Updated to revision 2.\n'],
+ [],
+ 'update', '--accept=postpone',
+ lambda_path_backup)
+
+ # mu: --accept=base
+ # Accept the pre-update base file.
+ svntest.actions.run_and_verify_svn(None,
+ ['G %s\n' % (mu_path_backup,),
+ 'Updated to revision 2.\n'],
+ [],
+ 'update', '--accept=base',
+ mu_path_backup)
+
+ # alpha: --accept=mine
+ # Accept the user's working file.
+ svntest.actions.run_and_verify_svn(None,
+ ['G %s\n' % (alpha_path_backup,),
+ 'Updated to revision 2.\n'],
+ [],
+ 'update', '--accept=mine',
+ alpha_path_backup)
+
+ # beta: --accept=theirs
+ # Accept their file.
+ svntest.actions.run_and_verify_svn(None,
+ ['G %s\n' % (beta_path_backup,),
+ 'Updated to revision 2.\n'],
+ [],
+ 'update', '--accept=theirs',
+ beta_path_backup)
+
+ # rho: --accept=edit
+ # Run editor and accept the edited file.
+ # XXX: I know how to test --accept=edit on unix, but not on
+ # other systems...
+ test_file = os.path.abspath(os.path.join(wc_dir, 'conflict-test'))
+ test_editor = "sh -c 'echo $* > %s' 0" % (test_file,)
+ svntest.actions.run_and_verify_svn(None,
+ ['G %s\n' % (rho_path_backup,),
+ 'Updated to revision 2.\n'],
+ [],
+ 'update', '--accept=edit',
+ '--editor-cmd', test_editor,
+ rho_path_backup)
+ got = svntest.main.file_read(test_file)
+ expected = 'tempfile.*\.tmp\n'
+ if not re.match(expected, got):
+ raise svntest.main.Failure('%s !~ %s' % (got, expected))
+
+ # Set the expected disk contents for the test
+ expected_disk = svntest.main.greek_state.copy()
+
+ expected_disk.tweak('iota', contents=("This is the file 'iota'.\n"
+ '<<<<<<< .mine\n'
+ 'My appended text for iota\n'
+ '=======\n'
+ 'Their appended text for iota\n'
+ '>>>>>>> .r2\n'))
+ expected_disk.tweak('A/B/lambda', contents=("This is the file 'lambda'.\n"
+ '<<<<<<< .mine\n'
+ 'My appended text for lambda\n'
+ '=======\n'
+ 'Their appended text for lambda\n'
+ '>>>>>>> .r2\n'))
+ expected_disk.tweak('A/mu', contents="This is the file 'mu'.\n")
+ expected_disk.tweak('A/B/E/alpha', contents=("This is the file 'alpha'.\n"
+ 'My appended text for alpha\n'))
+ expected_disk.tweak('A/B/E/beta', contents=("This is the file 'beta'.\n"
+ 'Their appended text for beta\n'))
+ expected_disk.tweak('A/D/G/rho', contents=("This is the file 'rho'.\n"
+ '<<<<<<< .mine\n'
+ 'My appended text for rho\n'
+ '=======\n'
+ 'Their appended text for rho\n'
+ '>>>>>>> .r2\n'))
+
+ # Set the expected extra files for the test
+ extra_files = ['iota.*\.r1', 'iota.*\.r2', 'iota.*\.mine',
+ 'lambda.*\.r1', 'lambda.*\.r2', 'lambda.*\.mine']
+
+ # Set the expected status for the test
+ expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
+ expected_status.tweak('iota', 'A/B/lambda', 'A/mu',
+ 'A/B/E/alpha', 'A/B/E/beta',
+ 'A/D/G/rho', wc_rev=2)
+ expected_status.tweak('iota', status='C ')
+ expected_status.tweak('A/B/lambda', status='C ')
+ expected_status.tweak('A/mu', status='M ')
+ expected_status.tweak('A/B/E/alpha', status='M ')
+ expected_status.tweak('A/B/E/beta', status=' ')
+ expected_status.tweak('A/D/G/rho', status='M ')
+
+ # Set the expected output for the test
+ expected_output = wc.State(wc_backup, {})
+
+ # Do the update and check the results in three ways.
+ svntest.actions.run_and_verify_update(wc_backup,
+ expected_output,
+ expected_disk,
+ expected_status,
+ None,
+ svntest.tree.detect_conflict_files,
+ extra_files)
+
+
 #######################################################################
 # Run the tests
 
@@ -3292,6 +3487,7 @@
               update_conflicted,
               mergeinfo_update_elision,
               XFail(update_handles_copyfrom),
+ update_accept_conflicts,
              ]
 
 if __name__ == '__main__':
Index: subversion/svn/cl.h
===================================================================
--- subversion/svn/cl.h (revision 26821)
+++ subversion/svn/cl.h (working copy)
@@ -90,6 +90,34 @@
 } svn_cl__longopt_t;
 
 
+/** --accept actions
+ *
+ * @since New in 1.5.
+ */
+typedef enum
+{
+ /* invalid or unspecified accept action */
+ svn_cl__accept_invalid = -1,
+
+ /* Leave conflicts alone, for later resolution. */
+ svn_cl__accept_postpone,
+
+ /* Resolve the conflict with the pre-conflict base file. */
+ svn_cl__accept_base,
+
+ /* Resolve the conflict with the pre-conflict working copy file. */
+ svn_cl__accept_mine,
+
+ /* Resolve the conflict with the post-conflict base file. */
+ svn_cl__accept_theirs,
+
+ /* Launch user's editor and resolve conflict with edited file. */
+ svn_cl__accept_edit,
+
+ /* Launch user's resolver and resolve conflict with edited file. */
+ svn_cl__accept_launch,
+} svn_cl__accept_t;
+
 
 /*** Command dispatch. ***/
 
@@ -161,7 +189,7 @@
   apr_hash_t *revprop_table; /* table of revision properties to get/set */
   svn_boolean_t parents; /* create intermediate directories */
   svn_boolean_t use_merge_history; /* use/display extra merge information */
- svn_accept_t accept_which; /* automatically resolve conflict */
+ svn_cl__accept_t accept_which; /* how to handle conflicts */
 
 } svn_cl__opt_state_t;
 
@@ -248,6 +276,29 @@
 
 /* Various conflict-resolution callbacks. */
 
+typedef struct {
+ svn_cl__accept_t accept_which;
+ apr_hash_t *config;
+ const char *editor_cmd;
+ svn_boolean_t external_failed;
+ svn_boolean_t interactive;
+} svn_cl__conflict_baton_t;
+
+/* Return address of newly allocated and initialized svn_cl__conflict_baton_t.
+ * @since New in 1.5.
+ */
+svn_cl__conflict_baton_t *
+svn_cl__conflict_baton_new(svn_cl__accept_t accept_which,
+ apr_hash_t *config,
+ const char *editor_cmd,
+ apr_pool_t *pool);
+
+/* Return svn_cl__accept_t value corresponding to @ word.
+ * @since New in 1.5.
+ */
+svn_cl__accept_t
+svn_cl__accept_from_word(const char *word);
+
 /* A mindless implementation of svn_wc_conflict_resolver_func_t that
  * does absolutely nothing to resolve conflicts. */
 svn_error_t *
@@ -260,7 +311,7 @@
    one of the 3 fulltexts, edit the merged file on the spot, or just
    skip the conflict (to be resolved later). */
 svn_error_t *
-svn_cl__interactive_conflict_handler(svn_wc_conflict_result_t *result,
+svn_cl__conflict_handler(svn_wc_conflict_result_t *result,
                                      const svn_wc_conflict_description_t *desc,
                                      void *baton,
                                      apr_pool_t *pool);
Index: subversion/svn/resolved-cmd.c
===================================================================
--- subversion/svn/resolved-cmd.c (revision 26821)
+++ subversion/svn/resolved-cmd.c (working copy)
@@ -29,7 +29,9 @@
 #include "svn_pools.h"
 #include "cl.h"
 
+#include "svn_private_config.h"
 
+
 
 /*** Code. ***/
 
@@ -41,11 +43,31 @@
 {
   svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
   svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ svn_wc_conflict_result_t conflict_result;
   svn_error_t *err;
   apr_array_header_t *targets;
   int i;
   apr_pool_t *subpool;
 
+ switch (opt_state->accept_which)
+ {
+ case svn_cl__accept_invalid:
+ conflict_result = svn_wc_conflict_result_choose_merged;
+ break;
+ case svn_cl__accept_base:
+ conflict_result = svn_wc_conflict_result_choose_base;
+ break;
+ case svn_cl__accept_theirs:
+ conflict_result = svn_wc_conflict_result_choose_theirs;
+ break;
+ case svn_cl__accept_mine:
+ conflict_result = svn_wc_conflict_result_choose_user;
+ break;
+ default:
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("invalid 'accept' ARG"));
+ }
+
   SVN_ERR(svn_opt_args_to_target_array2(&targets, os,
                                         opt_state->targets, pool));
   if (! targets->nelts)
@@ -65,8 +87,7 @@
       svn_pool_clear(subpool);
       SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
       err = svn_client_resolved2(target,
- opt_state->depth,
- opt_state->accept_which,
+ opt_state->depth, conflict_result,
                                  ctx,
                                  subpool);
       if (err)
Index: subversion/svn/conflict-callbacks.c
===================================================================
--- subversion/svn/conflict-callbacks.c (revision 26821)
+++ subversion/svn/conflict-callbacks.c (working copy)
@@ -38,6 +38,40 @@
 
 
 
+svn_cl__conflict_baton_t *
+svn_cl__conflict_baton_new(svn_cl__accept_t accept_which,
+ apr_hash_t *config,
+ const char *editor_cmd,
+ apr_pool_t *pool)
+{
+ svn_cl__conflict_baton_t *b = apr_palloc(pool, sizeof(*b));
+ b->accept_which = accept_which;
+ b->config = config;
+ b->editor_cmd = editor_cmd;
+ b->external_failed = FALSE;
+ return b;
+}
+
+svn_cl__accept_t
+svn_cl__accept_from_word(const char *word)
+{
+ if (strcmp(word, "postpone") == 0)
+ return svn_cl__accept_postpone;
+ if (strcmp(word, "base") == 0)
+ return svn_cl__accept_base;
+ if (strcmp(word, "mine") == 0)
+ return svn_cl__accept_mine;
+ if (strcmp(word, "theirs") == 0)
+ return svn_cl__accept_theirs;
+ if (strcmp(word, "edit") == 0)
+ return svn_cl__accept_edit;
+ if (strcmp(word, "launch") == 0)
+ return svn_cl__accept_launch;
+ /* word is an invalid action. */
+ return svn_cl__accept_invalid;
+}
+
+
 /* Utility to print a full description of the conflict. */
 static svn_error_t *
 print_conflict_description(const svn_wc_conflict_description_t *desc,
@@ -104,9 +138,9 @@
   if (desc->base_file)
     SVN_ERR(svn_cmdline_printf(pool, _(" Ancestor file: %s\n"),
                                desc->base_file));
- if (desc->repos_file)
- SVN_ERR(svn_cmdline_printf(pool, _(" Repository's file: %s\n"),
- desc->repos_file));
+ if (desc->their_file)
+ SVN_ERR(svn_cmdline_printf(pool, _(" Their file: %s\n"),
+ desc->their_file));
   if (desc->user_file)
     SVN_ERR(svn_cmdline_printf(pool, _(" User's file: %s\n"),
                                desc->user_file));
@@ -135,15 +169,92 @@
 }
 
 
-/* A conflict callback which does real user prompting. */
+/* Implement svn_wc_conflict_resolver_func_t; resolves based on
+ --accept option if given, else by prompting. */
 svn_error_t *
-svn_cl__interactive_conflict_handler(svn_wc_conflict_result_t *result,
- const svn_wc_conflict_description_t *desc,
- void *baton,
- apr_pool_t *pool)
+svn_cl__conflict_handler(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description_t *desc,
+ void *baton,
+ apr_pool_t *pool)
 {
- apr_pool_t *subpool = svn_pool_create(pool);
+ svn_cl__conflict_baton_t *b = baton;
+ svn_error_t *err;
+ apr_pool_t *subpool;
 
+ switch (b->accept_which)
+ {
+ case svn_cl__accept_invalid:
+ /* No --accept option, fall through to prompting. */
+ break;
+ case svn_cl__accept_postpone:
+ *result = svn_wc_conflict_result_conflicted;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_base:
+ *result = svn_wc_conflict_result_choose_base;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_mine:
+ *result = svn_wc_conflict_result_choose_user;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_theirs:
+ *result = svn_wc_conflict_result_choose_theirs;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_edit:
+ if (desc->merged_file)
+ {
+ if (b->external_failed)
+ {
+ *result = svn_wc_conflict_result_conflicted;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_cl__edit_file_externally(desc->merged_file,
+ b->editor_cmd, b->config, pool);
+ if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
+ {
+ SVN_ERR(svn_cmdline_printf(subpool,
+ err->message ? err->message :
+ _("No editor found,"
+ " leaving all conflicts.\n")));
+ svn_error_clear(err);
+ b->external_failed = TRUE;
+ }
+ else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
+ {
+ SVN_ERR(svn_cmdline_printf(subpool,
+ err->message ? err->message :
+ _("Error running editor,"
+ " leaving all conflicts.\n")));
+ svn_error_clear(err);
+ b->external_failed = TRUE;
+ }
+ else if (err)
+ return err;
+ *result = svn_wc_conflict_result_choose_merged;
+ return SVN_NO_ERROR;
+ }
+ /* else, fall through to prompting. */
+ break;
+ case svn_cl__accept_launch:
+ if (desc->base_file && desc->their_file && desc->user_file)
+ {
+ if (b->external_failed)
+ {
+ *result = svn_wc_conflict_result_conflicted;
+ return SVN_NO_ERROR;
+ }
+
+ /* ### TODO: launch $SVNMERGE tool here with 3 fulltexts; we
+ never get here, as this is blocked at --accept parsing in
+ main.c. */
+ }
+ /* else, fall through to prompting. */
+ break;
+ }
+
+ /* We're in interactive mode and either the user gave no --accept
+ option or the option did not apply; let's prompt. */
+ subpool = svn_pool_create(pool);
+
   /* Handle conflicting file contents, which is the most common case. */
   if ((desc->node_kind == svn_node_file)
       && (desc->action == svn_wc_conflict_action_edit)
@@ -195,7 +306,7 @@
             }
           if (strcmp(answer, "t") == 0)
             {
- *result = svn_wc_conflict_result_choose_repos;
+ *result = svn_wc_conflict_result_choose_theirs;
               break;
             }
           if (strcmp(answer, "d") == 0)
@@ -228,7 +339,8 @@
                 {
                   svn_error_t *eerr;
                   eerr = svn_cl__edit_file_externally(desc->merged_file,
- NULL, NULL, subpool);
+ b->editor_cmd,
+ b->config, subpool);
                   if (eerr && (eerr->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
                     {
                       SVN_ERR(svn_cmdline_printf(subpool,
@@ -253,7 +365,7 @@
             }
           if (strcmp(answer, "l") == 0)
             {
- if (desc->base_file && desc->repos_file && desc->user_file)
+ if (desc->base_file && desc->their_file && desc->user_file)
                 {
                   /* ### TODO: launch $SVNMERGE tool here with 3 fulltexts. */
                   SVN_ERR(svn_cmdline_printf(
@@ -289,7 +401,7 @@
       choose_user skip addition, skip addition skip addition
                         add existing item
 
- choose_repos destroy file, schedule-delete, revert add,
+ choose_theirs destroy file, schedule-delete, revert add,
                         add new item. add new item. rm file,
                                                             add new item
 
@@ -334,7 +446,7 @@
             }
           if (strcmp(answer, "t") == 0)
             {
- *result = svn_wc_conflict_result_choose_repos;
+ *result = svn_wc_conflict_result_choose_theirs;
               break;
             }
         }
Index: subversion/svn/main.c
===================================================================
--- subversion/svn/main.c (revision 26821)
+++ subversion/svn/main.c (working copy)
@@ -211,9 +211,10 @@
                        " "
                        "history")},
   {"accept", svn_cl__accept_opt, 1,
- N_("specify automatic conflict resolution source\n"
+ N_("specify automatic conflict resolution action\n"
                        " "
- "('left', 'right', or 'working')")},
+ /* TODO: 'launch' */
+ "('postpone', 'base', 'mine', 'theirs', 'edit')")},
   {0, 0, 0, 0},
 };
 
@@ -715,7 +716,11 @@
      " remove conflict markers; it merely removes the conflict-related\n"
      " artifact files and allows PATH to be committed again.\n"),
     {svn_cl__targets_opt, 'R', svn_cl__depth_opt, 'q',
- svn_cl__config_dir_opt, svn_cl__accept_opt} },
+ svn_cl__config_dir_opt, svn_cl__accept_opt},
+ {{svn_cl__accept_opt, N_("specify automatic conflict resolution source\n"
+ " "
+ /* TODO: 'launch' */
+ "('base', 'mine', 'theirs')")}} },
 
   { "revert", svn_cl__revert, {0}, N_
     ("Restore pristine working copy file (undo most local edits).\n"
@@ -874,7 +879,8 @@
      " in the first column with code 'E'.\n"),
     {'r', 'N', svn_cl__depth_opt, 'q', svn_cl__merge_cmd_opt,
      svn_cl__force_opt, SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt,
- svn_cl__ignore_externals_opt, svn_cl__changelist_opt} },
+ svn_cl__ignore_externals_opt, svn_cl__changelist_opt,
+ svn_cl__editor_cmd_opt, svn_cl__accept_opt} },
 
   { NULL, NULL, {0}, NULL, {0} }
 };
@@ -1031,7 +1037,7 @@
   opt_state.start_revision.kind = svn_opt_revision_unspecified;
   opt_state.end_revision.kind = svn_opt_revision_unspecified;
   opt_state.depth = svn_depth_unknown;
- opt_state.accept_which = svn_accept_none;
+ opt_state.accept_which = svn_cl__accept_invalid;
 
   /* No args? Show usage. */
   if (argc <= 1)
@@ -1388,20 +1394,18 @@
         opt_state.use_merge_history = TRUE;
         break;
       case svn_cl__accept_opt:
- opt_state.accept_which = svn_accept_from_word(opt_arg);
-
- /* We need to make sure that the value passed to the accept flag
- * was one of the available options. Since svn_accept_invalid is what
- * gets set when one of the three expected are not passed, checking
- * for this as part of the command line parsing makes sense. */
- if (opt_state.accept_which == svn_accept_invalid)
- {
- return svn_cmdline_handle_exit_error
+ opt_state.accept_which = svn_cl__accept_from_word(opt_arg);
+ if (opt_state.accept_which == svn_cl__accept_invalid)
+ return svn_cmdline_handle_exit_error
             (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- _("'%s' is not a valid accept value; try "
- "'left', 'right', or 'working'"),
- opt_arg), pool, "svn: ");
- }
+ _("'%s' is not a valid accept value"), opt_arg),
+ pool, "svn: ");
+ /* TODO: launch */
+ if (opt_state.accept_which == svn_cl__accept_launch)
+ return svn_cmdline_handle_exit_error
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=launch not yet implemented")),
+ pool, "svn: ");
       default:
         /* Hmmm. Perhaps this would be a good place to squirrel away
            opts that commands like svn diff might need. Hmmm indeed. */
@@ -1734,16 +1738,40 @@
                                                  we can change this. */
     svn_handle_error2(err, stderr, TRUE, "svn: ");
 
- if (interactive_conflicts
- && (! opt_state.non_interactive ))
+ if ((opt_state.accept_which == svn_cl__accept_invalid
+ && (!interactive_conflicts || opt_state.non_interactive))
+ || opt_state.accept_which == svn_cl__accept_postpone)
     {
- ctx->conflict_func = svn_cl__interactive_conflict_handler;
+ /* If no --accept option at all and we're non-interactive, we're
+ leaving the conflicts behind, so don't need the callback. Same if
+ the user said to postpone. */
+ ctx->conflict_func = NULL;
       ctx->conflict_baton = NULL;
     }
   else
     {
- ctx->conflict_func = NULL;
- ctx->conflict_baton = NULL;
+ if (opt_state.non_interactive)
+ {
+ if (opt_state.accept_which == svn_cl__accept_edit)
+ return svn_cmdline_handle_exit_error
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=edit incompatible with"
+ " --non-interactive")),
+ pool, "svn: ");
+ if (opt_state.accept_which == svn_cl__accept_launch)
+ return svn_cmdline_handle_exit_error
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=launch incompatible with"
+ " --non-interactive")),
+ pool, "svn: ");
+ }
+
+ ctx->conflict_func = svn_cl__conflict_handler;
+ ctx->conflict_baton = svn_cl__conflict_baton_new(
+ opt_state.accept_which,
+ ctx->config,
+ opt_state.editor_cmd,
+ pool);
     }
 
   /* And now we finally run the subcommand. */

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sat Sep 29 04:29:30 2007

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.