2 * propedit-cmd.c -- Edit properties of files/dirs using $EDITOR
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
31 #include "svn_cmdline.h"
33 #include "svn_pools.h"
34 #include "svn_client.h"
35 #include "svn_string.h"
36 #include "svn_dirent_uri.h"
38 #include "svn_error.h"
40 #include "svn_props.h"
43 #include "private/svn_cmdline_private.h"
44 #include "svn_private_config.h"
48 struct commit_info_baton
50 const char *pname_utf8;
51 const char *target_local;
55 commit_info_handler(const svn_commit_info_t *commit_info,
59 struct commit_info_baton *cib = baton;
61 SVN_ERR(svn_cmdline_printf(pool,
62 _("Set new value for property '%s' on '%s'\n"),
63 cib->pname_utf8, cib->target_local));
64 SVN_ERR(svn_cl__print_commit_info(commit_info, NULL, pool));
69 /* This implements the `svn_opt_subcommand_t' interface. */
71 svn_cl__propedit(apr_getopt_t *os,
75 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
76 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
77 const char *pname, *pname_utf8;
78 apr_array_header_t *args, *targets;
80 /* Validate the input and get the property's name (and a UTF-8
81 version of that name). */
82 SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
83 pname = APR_ARRAY_IDX(args, 0, const char *);
84 SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool));
85 if (! svn_prop_name_is_valid(pname_utf8))
86 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
87 _("'%s' is not a valid Subversion property name"),
89 if (!opt_state->force)
90 SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop,
91 svn_cl__prop_use_edit, pool));
93 if (opt_state->encoding && !svn_prop_needs_translation(pname_utf8))
94 return svn_error_create
95 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
96 _("--encoding option applies only to textual"
97 " Subversion-controlled properties"));
99 /* Suck up all the remaining arguments into a targets array */
100 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
104 /* We do our own notifications */
105 ctx->notify_func2 = NULL;
107 if (opt_state->revprop) /* operate on a revprop */
111 svn_string_t *propval;
112 svn_string_t original_propval;
113 const char *temp_dir;
115 /* Implicit "." is okay for revision properties; it just helps
116 us find the right repository. */
117 svn_opt_push_implicit_dot_target(targets, pool);
119 SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
122 /* Fetch the current property. */
123 SVN_ERR(svn_client_revprop_get(pname_utf8, &propval,
124 URL, &(opt_state->start_revision),
129 propval = svn_string_create_empty(pool);
130 /* This is how we signify to svn_client_revprop_set2() that
131 we want it to check that the original value hasn't
132 changed, but that that original value was non-existent: */
133 original_propval.data = NULL; /* and .len is ignored */
137 original_propval = *propval;
140 /* Run the editor on a temporary file which contains the
141 original property value... */
142 SVN_ERR(svn_io_temp_dir(&temp_dir, pool));
143 SVN_ERR(svn_cmdline__edit_string_externally(
145 opt_state->editor_cmd, temp_dir,
148 svn_prop_needs_translation(pname_utf8),
149 opt_state->encoding, pool));
151 /* ...and re-set the property's value accordingly. */
154 SVN_ERR(svn_client_revprop_set2(pname_utf8,
155 propval, &original_propval,
156 URL, &(opt_state->start_revision),
157 &rev, opt_state->force, ctx, pool));
162 _("Set new value for property '%s' on revision %ld\n"),
167 SVN_ERR(svn_cmdline_printf
168 (pool, _("No changes to property '%s' on revision %ld\n"),
172 else if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
174 return svn_error_createf
175 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
176 _("Cannot specify revision for editing versioned property '%s'"),
179 else /* operate on a normal, versioned property (not a revprop) */
181 apr_pool_t *subpool = svn_pool_create(pool);
182 struct commit_info_baton cib;
185 /* The customary implicit dot rule has been prone to user error
186 * here. For example, Jon Trowbridge <trow@gnu.og> did
188 * $ svn propedit HACKING
190 * and then when he closed his editor, he was surprised to see
192 * Set new value for property 'HACKING' on ''
194 * ...meaning that the property named 'HACKING' had been set on
195 * the current working directory, with the value taken from the
196 * editor. So we don't do the implicit dot thing anymore; an
197 * explicit target is always required when editing a versioned
200 if (targets->nelts == 0)
202 return svn_error_create
203 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
204 _("Explicit target argument required"));
207 SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
209 cib.pname_utf8 = pname_utf8;
211 /* For each target, edit the property PNAME. */
212 for (i = 0; i < targets->nelts; i++)
215 const char *target = APR_ARRAY_IDX(targets, i, const char *);
216 svn_string_t *propval, *edited_propval;
217 const char *base_dir = target;
218 const char *target_local;
219 const char *abspath_or_url;
220 svn_node_kind_t kind;
221 svn_opt_revision_t peg_revision;
222 svn_revnum_t base_rev = SVN_INVALID_REVNUM;
224 svn_pool_clear(subpool);
225 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
227 if (!svn_path_is_url(target))
228 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url, target, subpool));
230 abspath_or_url = target;
232 /* Propedits can only happen on HEAD or the working copy, so
233 the peg revision can be as unspecified. */
234 peg_revision.kind = svn_opt_revision_unspecified;
236 /* Fetch the current property. */
237 SVN_ERR(svn_client_propget5(&props, NULL, pname_utf8, abspath_or_url,
239 &(opt_state->start_revision),
240 &base_rev, svn_depth_empty,
241 NULL, ctx, subpool, subpool));
243 /* Get the property value. */
244 propval = svn_hash_gets(props, abspath_or_url);
246 propval = svn_string_create_empty(subpool);
248 if (svn_path_is_url(target))
250 /* For URLs, put the temporary file in the current directory. */
255 if (opt_state->message || opt_state->filedata ||
256 opt_state->revprop_table)
258 return svn_error_create
259 (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
260 _("Local, non-commit operations do not take a log message "
261 "or revision properties"));
264 /* Split the path if it is a file path. */
265 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath_or_url,
266 FALSE, FALSE, subpool));
268 if (kind == svn_node_none)
269 return svn_error_createf(
270 SVN_ERR_ENTRY_NOT_FOUND, NULL,
271 _("'%s' does not appear to be a working copy path"), target);
272 if (kind == svn_node_file)
273 base_dir = svn_dirent_dirname(target, subpool);
276 /* Run the editor on a temporary file which contains the
277 original property value... */
278 SVN_ERR(svn_cmdline__edit_string_externally(&edited_propval, NULL,
279 opt_state->editor_cmd,
284 svn_prop_needs_translation
289 target_local = svn_path_is_url(target) ? target
290 : svn_dirent_local_style(target, subpool);
291 cib.target_local = target_local;
293 /* ...and re-set the property's value accordingly. */
294 if (edited_propval && !svn_string_compare(propval, edited_propval))
296 svn_error_t *err = SVN_NO_ERROR;
298 svn_cl__check_boolean_prop_val(pname_utf8, edited_propval->data,
301 if (ctx->log_msg_func3)
302 SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3),
303 opt_state, NULL, ctx->config,
305 if (svn_path_is_url(target))
307 err = svn_client_propset_remote(pname_utf8, edited_propval,
308 target, opt_state->force,
310 opt_state->revprop_table,
311 commit_info_handler, &cib,
316 apr_array_header_t *targs = apr_array_make(subpool, 1,
317 sizeof(const char *));
319 APR_ARRAY_PUSH(targs, const char *) = target;
321 SVN_ERR(svn_cl__propset_print_binary_mime_type_warning(
322 targs, pname_utf8, propval, subpool));
324 err = svn_client_propset_local(pname_utf8, edited_propval,
325 targs, svn_depth_empty,
326 opt_state->force, NULL,
330 if (ctx->log_msg_func3)
331 SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3,
334 return svn_error_trace(err);
336 /* Print a message if we successfully committed or if it
337 was just a wc propset (but not if the user aborted a URL
339 if (!svn_path_is_url(target))
340 SVN_ERR(svn_cmdline_printf(
341 subpool, _("Set new value for property '%s' on '%s'\n"),
342 pname_utf8, target_local));
348 (subpool, _("No changes to property '%s' on '%s'\n"),
349 pname_utf8, target_local));
352 svn_pool_destroy(subpool);