Make 'svn commit --changelist mychange' work.

At the moment, the changelist associations are removed from entries
during post-commit processing.  In a followup change, we'll add a new
option to 'commit' which retains the changelist definition after a
commit.

Note:  somehow this change is failing lock_tests.py.  I'm
investigating, and will commit a fix soon.

* subversion/include/client.h
  (svn_client_commit4):  add a 'changelist' argument, rev function to v4.
  (svn_client_commit3):  deprecate.

* subversion/include/svn_wc.h
  (svn_wc_process_committed3):  update docstring to describe
                                changelist interaction.

* subversion/libsvn_wc/log.c, log.h
  (SVN_WC__LOG_DELETE_CHANGELIST):  new xml command for logfiles.
  (log_do_delete_changelist):  new utility function for log execution.
  (start_handler):  process the new xml command.
  (svn_wc__loggy_delete_changelist):  new function to tweak entry.

* subversion/libsvn_wc/adm_ops.c
  (svn_wc_process_committed3):  remove any changelist association when
                                doing post-commit processing on an entry.

* subversion/libsvn_client/client.h
  (svn_client__harvest_committables):  take a 'changelist_name' arg.

* subversion/libsvn_client/commit_util.c
  (harvest_committables):  take a 'changelist_name' arg, use it as a filter.
  (svn_client_harvest_committables):  take a 'changelist_name' arg,
                                      pass it to static func.
  (svn_client__get_copy_committables): update caller to pass NULL
                                       changelist_name.

* subversion/libsvn_client/commit.c
  (svn_client_commit4):  take a 'changelist_name' arg and pass it on.
  (svn_client_commit3):  become a wrapper around svn_client_commit4().

* subversion/svn/commit-cmd.c
  (svn_cl__commit):  invoke svn_client_commit4(), not 3.

* subversion/svn/main.c
  (svn_cl__cmd_table):  allow 'svn commit' to take --changelist option.

* subversion/tests/cmdline/getopt_tests_data/svn_help_stdout, svn--help_stdout
  Fix expected output of 'svn help' to show changelist command.




git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/changelist-feature@860372 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/subversion/include/svn_client.h b/subversion/include/svn_client.h
index abf316c..c7d99f5 100644
--- a/subversion/include/svn_client.h
+++ b/subversion/include/svn_client.h
@@ -1068,6 +1068,10 @@
  *
  * Unlock paths in the repository, unless @a keep_locks is true.
  *
+ * If @a changelist_name is non-NULL, then use it as a restrictive filter
+ * on items that are committed;  that is, don't commit anything unless
+ * it's a member of changelist @a changelist_name.
+ *
  * Use @a pool for any temporary allocations.
  *
  * If no error is returned and @a (*commit_info_p)->revision is set to
@@ -1077,6 +1081,22 @@
  * @since New in 1.3.
  */
 svn_error_t *
+svn_client_commit4(svn_commit_info_t **commit_info_p,
+                   const apr_array_header_t *targets,
+                   svn_boolean_t recurse,
+                   svn_boolean_t keep_locks,
+                   const char *changelist_name,
+                   svn_client_ctx_t *ctx,
+                   apr_pool_t *pool);
+
+/** Similar to svn_client_commit4(), but always passes NULL @a
+ * changelist_name.
+ *
+ * @deprecated Provided for backward compatibility with the 1.3 API.
+ *
+ * @since New in 1.3.
+ */
+svn_error_t *
 svn_client_commit3(svn_commit_info_t **commit_info_p,
                    const apr_array_header_t *targets,
                    svn_boolean_t recurse,
diff --git a/subversion/include/svn_wc.h b/subversion/include/svn_wc.h
index 2bfa204..9397663 100644
--- a/subversion/include/svn_wc.h
+++ b/subversion/include/svn_wc.h
@@ -2292,6 +2292,8 @@
  * If @a remove_lock is @c TRUE, any entryprops related to a repository
  * lock will be removed.
  *
+ * If @a path is a member of a changelist, remove that association.
+ *
  * If @a path is a file and @a digest is non-null, use @a digest as
  * the checksum for the new text base.  Else, calculate the checksum
  * if needed.
diff --git a/subversion/libsvn_client/client.h b/subversion/libsvn_client/client.h
index d5ecccf..ecf7ac2 100644
--- a/subversion/libsvn_client/client.h
+++ b/subversion/libsvn_client/client.h
@@ -534,9 +534,9 @@
    canonical repository URLs.  LOCK_TOKENS will point to a hash table
    with const char * lock tokens, keyed on const char * URLs.  Also,
    LOCKED_DIRS will be an apr_hash_t * hash of svn_wc_adm_access_t *
-   keyed
-   on const char * working copy path directory names which were locked
-   in the process of this crawl.  These will need to be unlocked again post-commit.
+   keyed on const char * working copy path directory names which were
+   locked in the process of this crawl.  These will need to be
+   unlocked again post-commit.
 
    If NONRECURSIVE is specified, subdirectories of directory targets
    found in TARGETS will not be crawled for modifications.
@@ -544,6 +544,10 @@
    If JUST_LOCKED is TRUE, treat unmodified items with lock tokens as
    commit candidates.
 
+   If CHANGELIST_NAME is non-NULL, then use it as a restrictive filter
+   when harvesting committables; that is, don't add a path to
+   COMMITTABLES unless it's a member of the changelist.
+
    If CTX->CANCEL_FUNC is non-null, it will be called with 
    CTX->CANCEL_BATON while harvesting to determine if the client has 
    cancelled the operation.  */
@@ -554,6 +558,7 @@
                                  apr_array_header_t *targets,
                                  svn_boolean_t nonrecursive,
                                  svn_boolean_t just_locked,
+                                 const char *changelist_name,
                                  svn_client_ctx_t *ctx,
                                  apr_pool_t *pool);
 
diff --git a/subversion/libsvn_client/commit.c b/subversion/libsvn_client/commit.c
index 854d214..4805938 100644
--- a/subversion/libsvn_client/commit.c
+++ b/subversion/libsvn_client/commit.c
@@ -1170,10 +1170,11 @@
 
 
 svn_error_t *
-svn_client_commit3(svn_commit_info_t **commit_info_p,
+svn_client_commit4(svn_commit_info_t **commit_info_p,
                    const apr_array_header_t *targets,
                    svn_boolean_t recurse,
                    svn_boolean_t keep_locks,
+                   const char *changelist_name,
                    svn_client_ctx_t *ctx,
                    apr_pool_t *pool)
 {
@@ -1430,6 +1431,7 @@
                                                   rel_targets, 
                                                   recurse ? FALSE : TRUE,
                                                   ! keep_locks,
+                                                  changelist_name,
                                                   ctx,
                                                   pool)))
     goto cleanup;
@@ -1638,6 +1640,18 @@
 }
 
 svn_error_t *
+svn_client_commit3(svn_commit_info_t **commit_info_p,
+                   const apr_array_header_t *targets,
+                   svn_boolean_t recurse,
+                   svn_boolean_t keep_locks,
+                   svn_client_ctx_t *ctx,
+                   apr_pool_t *pool)
+{
+  return svn_client_commit4(commit_info_p, targets, recurse, keep_locks,
+                            NULL, ctx, pool);
+}
+
+svn_error_t *
 svn_client_commit2(svn_client_commit_info_t **commit_info_p,
                    const apr_array_header_t *targets,
                    svn_boolean_t recurse,
diff --git a/subversion/libsvn_client/commit_util.c b/subversion/libsvn_client/commit_util.c
index bd17caf..acee715 100644
--- a/subversion/libsvn_client/commit_util.c
+++ b/subversion/libsvn_client/commit_util.c
@@ -194,6 +194,10 @@
    with history as URL, and add 'deleted' entries to COMMITTABLES as
    items to delete in the copy destination.
 
+   If CHANGELIST_NAME is non-NULL, then use it as a restrictive filter
+   when harvesting committables; that is, don't add a path to
+   COMMITTABLES unless it's a member of the changelist.
+
    If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to see 
    if the user has cancelled the operation.  */
 static svn_error_t *
@@ -209,6 +213,7 @@
                      svn_boolean_t copy_mode,
                      svn_boolean_t nonrecursive,
                      svn_boolean_t just_locked,
+                     const char *changelist_name,
                      svn_client_ctx_t *ctx,
                      apr_pool_t *pool)
 {
@@ -478,16 +483,21 @@
   /* Now, if this is something to commit, add it to our list. */
   if (state_flags)
     {
-      /* Finally, add the committable item. */
-      add_committable(committables, path, entry->kind, url,
-                      entry->revision,
-                      cf_url,
-                      cf_rev,
-                      state_flags);
-      if (lock_tokens && entry->lock_token)
-        apr_hash_set(lock_tokens, apr_pstrdup(token_pool, url),
-                     APR_HASH_KEY_STRING,
-                     apr_pstrdup(token_pool, entry->lock_token));
+      if ((changelist_name == NULL)
+          || (entry->changelist
+              && (strcmp(changelist_name, entry->changelist) == 0)))
+        {
+          /* Finally, add the committable item. */
+          add_committable(committables, path, entry->kind, url,
+                          entry->revision,
+                          cf_url,
+                          cf_rev,
+                          state_flags);
+          if (lock_tokens && entry->lock_token)
+            apr_hash_set(lock_tokens, apr_pstrdup(token_pool, url),
+                         APR_HASH_KEY_STRING,
+                         apr_pstrdup(token_pool, entry->lock_token));
+        }
     }
 
   /* For directories, recursively handle each of their entries (except
@@ -564,14 +574,20 @@
                           && childkind == svn_node_none
                           && this_entry->schedule == svn_wc_schedule_delete)
                         {
-                          add_committable(committables, full_path,
-                                          this_entry->kind, used_url,
-                                          SVN_INVALID_REVNUM, 
-                                          NULL,
-                                          SVN_INVALID_REVNUM,
-                                          SVN_CLIENT_COMMIT_ITEM_DELETE);
-                          svn_error_clear(lockerr);
-                          continue; /* don't recurse! */
+                          if ((changelist_name == NULL)
+                              || (entry->changelist
+                                  && (strcmp(changelist_name,
+                                             entry->changelist) == 0)))
+                            {
+                              add_committable(committables, full_path,
+                                              this_entry->kind, used_url,
+                                              SVN_INVALID_REVNUM,
+                                              NULL,
+                                              SVN_INVALID_REVNUM,
+                                              SVN_CLIENT_COMMIT_ITEM_DELETE);
+                              svn_error_clear(lockerr);
+                              continue; /* don't recurse! */
+                            }
                         }
                       else
                         {
@@ -595,6 +611,7 @@
                    adds_only,
                    copy_mode,
                    FALSE, just_locked,
+                   changelist_name,
                    ctx,
                    loop_pool));
         }
@@ -622,6 +639,7 @@
                                  apr_array_header_t *targets,
                                  svn_boolean_t nonrecursive,
                                  svn_boolean_t just_locked,
+                                 const char *changelist_name,
                                  svn_client_ctx_t *ctx,
                                  apr_pool_t *pool)
 {
@@ -750,10 +768,11 @@
                                    ? target
                                    : svn_path_dirname(target, subpool)),
                                   subpool));
-      SVN_ERR(harvest_committables(*committables, *lock_tokens, target, dir_access,
-                                   entry->url, NULL, entry, NULL, FALSE, 
-                                   FALSE, nonrecursive, just_locked, ctx,
-                                   subpool));
+      SVN_ERR(harvest_committables(*committables, *lock_tokens, target,
+                                   dir_access, entry->url, NULL,
+                                   entry, NULL, FALSE, FALSE, nonrecursive,
+                                   just_locked, changelist_name,
+                                   ctx, subpool));
 
       i++;
     }
@@ -819,7 +838,8 @@
   /* Handle our TARGET. */
   SVN_ERR(harvest_committables(*committables, NULL, target,
                                adm_access, new_url, entry->url, entry, NULL,
-                               FALSE, TRUE, FALSE, FALSE, ctx, pool));
+                               FALSE, TRUE, FALSE, FALSE,
+                               NULL, ctx, pool));
 
   return SVN_NO_ERROR;
 }
diff --git a/subversion/libsvn_wc/adm_ops.c b/subversion/libsvn_wc/adm_ops.c
index 2638567..10b71d1 100644
--- a/subversion/libsvn_wc/adm_ops.c
+++ b/subversion/libsvn_wc/adm_ops.c
@@ -432,6 +432,11 @@
     SVN_ERR(svn_wc__loggy_delete_lock(&logtags, adm_access,
                                       base_name, pool));
 
+  /* Also, if the file was part of a changelist, being committed has
+     the effect of *removing* changelist membership. */
+  SVN_ERR(svn_wc__loggy_delete_changelist(&logtags, adm_access,
+                                          base_name, pool));
+
   /* Regardless of whether it's a file or dir, the "main" logfile
      contains a command to bump the revision attribute (and
      timestamp). */
diff --git a/subversion/libsvn_wc/log.c b/subversion/libsvn_wc/log.c
index 13f3183..bc1d0e8 100644
--- a/subversion/libsvn_wc/log.c
+++ b/subversion/libsvn_wc/log.c
@@ -67,6 +67,9 @@
 /* Delete lock related fields from the entry SVN_WC__LOG_ATTR_NAME. */
 #define SVN_WC__LOG_DELETE_LOCK         "delete-lock"
 
+/* Delete changelist field from the entry SVN_WC__LOG_ATTR_NAME. */
+#define SVN_WC__LOG_DELETE_CHANGELIST   "delete-changelist"
+
 /* Delete the entry SVN_WC__LOG_ATTR_NAME. */
 #define SVN_WC__LOG_DELETE_ENTRY        "delete-entry"
 
@@ -827,6 +830,29 @@
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+log_do_delete_changelist(struct log_runner *loggy,
+                         const char *name)
+{
+  svn_error_t *err;
+  svn_wc_entry_t entry;
+
+  entry.changelist = NULL;
+
+  /* Now write the new entry out */
+  err = svn_wc__entry_modify(loggy->adm_access, name,
+                             &entry,
+                             SVN_WC__ENTRY_MODIFY_CHANGELIST,
+                             FALSE, loggy->pool);
+  if (err)
+    return svn_error_createf(pick_error_code(loggy), err,
+                             _("Error removing changelist from entry '%s'"),
+                             name);
+  loggy->entries_modified = TRUE;
+
+  return SVN_NO_ERROR;
+}
+
 /* Ben sez:  this log command is (at the moment) only executed by the
    update editor.  It attempts to forcefully remove working data. */
 static svn_error_t *
@@ -1507,6 +1533,9 @@
   else if (strcmp(eltname, SVN_WC__LOG_DELETE_LOCK) == 0) {
     err = log_do_delete_lock(loggy, name);
   }
+  else if (strcmp(eltname, SVN_WC__LOG_DELETE_CHANGELIST) == 0) {
+    err = log_do_delete_changelist(loggy, name);
+  }
   else if (strcmp(eltname, SVN_WC__LOG_DELETE_ENTRY) == 0) {
     err = log_do_delete_entry(loggy, name);
   }
@@ -1944,6 +1973,21 @@
 
 
 svn_error_t *
+svn_wc__loggy_delete_changelist(svn_stringbuf_t **log_accum,
+                                svn_wc_adm_access_t *adm_access,
+                                const char *path,
+                                apr_pool_t *pool)
+{
+  svn_xml_make_open_tag(log_accum, pool, svn_xml_self_closing,
+                        SVN_WC__LOG_DELETE_CHANGELIST,
+                        SVN_WC__LOG_ATTR_NAME, path,
+                        NULL);
+
+  return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
 svn_wc__loggy_entry_modify(svn_stringbuf_t **log_accum,
                            svn_wc_adm_access_t *adm_access,
                            const char *name,
diff --git a/subversion/libsvn_wc/log.h b/subversion/libsvn_wc/log.h
index 42329ce..58bd784 100644
--- a/subversion/libsvn_wc/log.h
+++ b/subversion/libsvn_wc/log.h
@@ -111,13 +111,20 @@
 /* Extend **LOG_ACCUM with log instructions to delete lock related
    fields from the entry belonging to PATH.
 */
-
 svn_error_t *
 svn_wc__loggy_delete_lock(svn_stringbuf_t **log_accum,
                           svn_wc_adm_access_t *adm_access,
                           const char *path,
                           apr_pool_t *pool);
 
+/* Extend **LOG_ACCUM with log instructions to delete changelist
+   from the entry belonging to PATH.
+*/
+svn_error_t *
+svn_wc__loggy_delete_changelist(svn_stringbuf_t **log_accum,
+                                svn_wc_adm_access_t *adm_access,
+                                const char *path,
+                                apr_pool_t *pool);
 
 /* Extend **LOG_ACCUM with commands to modify the entry associated with NAME
    according to the flags specified in MODIFY_FLAGS, based on the values
diff --git a/subversion/svn/commit-cmd.c b/subversion/svn/commit-cmd.c
index d9db0e7..11c34ba 100644
--- a/subversion/svn/commit-cmd.c
+++ b/subversion/svn/commit-cmd.c
@@ -92,11 +92,12 @@
 
   /* Commit. */
   SVN_ERR(svn_cl__cleanup_log_msg
-          (ctx->log_msg_baton2, svn_client_commit3(&commit_info,
+          (ctx->log_msg_baton2, svn_client_commit4(&commit_info,
                                                    targets,
                                                    opt_state->nonrecursive
                                                    ? FALSE : TRUE,
                                                    no_unlock,
+                                                   opt_state->changelist,
                                                    ctx,
                                                    pool)));
   if (commit_info && ! opt_state->quiet)
diff --git a/subversion/svn/main.c b/subversion/svn/main.c
index 3176bcf..e64083b 100644
--- a/subversion/svn/main.c
+++ b/subversion/svn/main.c
@@ -270,7 +270,8 @@
        "  successful commit.\n"),
 #endif
     {'q', 'N', svn_cl__targets_opt, svn_cl__no_unlock_opt,
-     SVN_CL__LOG_MSG_OPTIONS, SVN_CL__AUTH_OPTIONS, svn_cl__config_dir_opt} },
+     SVN_CL__LOG_MSG_OPTIONS, SVN_CL__AUTH_OPTIONS,
+     svn_cl__changelist_opt, svn_cl__config_dir_opt} },
 
   { "copy", svn_cl__copy, {"cp"}, N_
     ("Duplicate something in working copy or repository, remembering history.\n"
diff --git a/subversion/tests/cmdline/getopt_tests_data/svn--help_stdout b/subversion/tests/cmdline/getopt_tests_data/svn--help_stdout
index 9def574..e2ac3e9 100644
--- a/subversion/tests/cmdline/getopt_tests_data/svn--help_stdout
+++ b/subversion/tests/cmdline/getopt_tests_data/svn--help_stdout
@@ -10,6 +10,7 @@
    add
    blame (praise, annotate, ann)
    cat
+   changelist (cl)
    checkout (co)
    cleanup
    commit (ci)
diff --git a/subversion/tests/cmdline/getopt_tests_data/svn_help_stdout b/subversion/tests/cmdline/getopt_tests_data/svn_help_stdout
index 9def574..e2ac3e9 100644
--- a/subversion/tests/cmdline/getopt_tests_data/svn_help_stdout
+++ b/subversion/tests/cmdline/getopt_tests_data/svn_help_stdout
@@ -10,6 +10,7 @@
    add
    blame (praise, annotate, ann)
    cat
+   changelist (cl)
    checkout (co)
    cleanup
    commit (ci)