1/* 2 * propedit-cmd.c -- Edit properties of files/dirs using $EDITOR 3 * 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 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 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 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_hash.h" 31#include "svn_cmdline.h" 32#include "svn_wc.h" 33#include "svn_pools.h" 34#include "svn_client.h" 35#include "svn_string.h" 36#include "svn_dirent_uri.h" 37#include "svn_path.h" 38#include "svn_error.h" 39#include "svn_utf.h" 40#include "svn_props.h" 41#include "cl.h" 42 43#include "private/svn_cmdline_private.h" 44#include "svn_private_config.h" 45 46 47/*** Code. ***/ 48struct commit_info_baton 49{ 50 const char *pname; 51 const char *target_local; 52}; 53 54static svn_error_t * 55commit_info_handler(const svn_commit_info_t *commit_info, 56 void *baton, 57 apr_pool_t *pool) 58{ 59 struct commit_info_baton *cib = baton; 60 61 SVN_ERR(svn_cmdline_printf(pool, 62 _("Set new value for property '%s' on '%s'\n"), 63 cib->pname, cib->target_local)); 64 SVN_ERR(svn_cl__print_commit_info(commit_info, NULL, pool)); 65 66 return SVN_NO_ERROR; 67} 68 69/* This implements the `svn_opt_subcommand_t' interface. */ 70svn_error_t * 71svn_cl__propedit(apr_getopt_t *os, 72 void *baton, 73 apr_pool_t *pool) 74{ 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; 78 apr_array_header_t *args, *targets; 79 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, pname, pool)); 85 if (! svn_prop_name_is_valid(pname)) 86 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 87 _("'%s' is not a valid Subversion property name"), 88 pname); 89 if (!opt_state->force) 90 SVN_ERR(svn_cl__check_svn_prop_name(pname, opt_state->revprop, 91 svn_cl__prop_use_edit, pool)); 92 93 if (opt_state->encoding && !svn_prop_needs_translation(pname)) 94 return svn_error_create 95 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 96 _("--encoding option applies only to textual" 97 " Subversion-controlled properties")); 98 99 /* Suck up all the remaining arguments into a targets array */ 100 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 101 opt_state->targets, 102 ctx, FALSE, pool)); 103 104 /* We do our own notifications */ 105 ctx->notify_func2 = NULL; 106 107 if (opt_state->revprop) /* operate on a revprop */ 108 { 109 svn_revnum_t rev; 110 const char *URL; 111 svn_string_t *propval; 112 svn_string_t original_propval; 113 const char *temp_dir; 114 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); 118 119 SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets, 120 &URL, ctx, pool)); 121 122 /* Fetch the current property. */ 123 SVN_ERR(svn_client_revprop_get(pname, &propval, 124 URL, &(opt_state->start_revision), 125 &rev, ctx, pool)); 126 127 if (! propval) 128 { 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 */ 134 } 135 else 136 { 137 original_propval = *propval; 138 } 139 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( 144 &propval, NULL, 145 opt_state->editor_cmd, temp_dir, 146 propval, "svn-prop", 147 ctx->config, 148 svn_prop_needs_translation(pname), 149 opt_state->encoding, pool)); 150 151 /* ...and re-set the property's value accordingly. */ 152 if (propval) 153 { 154 SVN_ERR(svn_client_revprop_set2(pname, 155 propval, &original_propval, 156 URL, &(opt_state->start_revision), 157 &rev, opt_state->force, ctx, pool)); 158 159 SVN_ERR 160 (svn_cmdline_printf 161 (pool, 162 _("Set new value for property '%s' on revision %ld\n"), 163 pname, rev)); 164 } 165 else 166 { 167 SVN_ERR(svn_cmdline_printf 168 (pool, _("No changes to property '%s' on revision %ld\n"), 169 pname, rev)); 170 } 171 } 172 else if (opt_state->start_revision.kind != svn_opt_revision_unspecified) 173 { 174 return svn_error_createf 175 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 176 _("Cannot specify revision for editing versioned property '%s'"), 177 pname); 178 } 179 else /* operate on a normal, versioned property (not a revprop) */ 180 { 181 apr_pool_t *subpool = svn_pool_create(pool); 182 struct commit_info_baton cib; 183 int i; 184 185 /* The customary implicit dot rule has been prone to user error 186 * here. For example, Jon Trowbridge <trow@gnu.og> did 187 * 188 * $ svn propedit HACKING 189 * 190 * and then when he closed his editor, he was surprised to see 191 * 192 * Set new value for property 'HACKING' on '' 193 * 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 198 * property. 199 */ 200 if (targets->nelts == 0) 201 { 202 return svn_error_create 203 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 204 _("Explicit target argument required")); 205 } 206 207 SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); 208 209 cib.pname = pname; 210 211 /* For each target, edit the property PNAME. */ 212 for (i = 0; i < targets->nelts; i++) 213 { 214 apr_hash_t *props; 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; 223 224 svn_pool_clear(subpool); 225 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); 226 227 if (!svn_path_is_url(target)) 228 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url, target, subpool)); 229 else 230 abspath_or_url = target; 231 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; 235 236 /* Fetch the current property. */ 237 SVN_ERR(svn_client_propget5(&props, NULL, pname, abspath_or_url, 238 &peg_revision, 239 &(opt_state->start_revision), 240 &base_rev, svn_depth_empty, 241 NULL, ctx, subpool, subpool)); 242 243 /* Get the property value. */ 244 propval = svn_hash_gets(props, abspath_or_url); 245 if (! propval) 246 propval = svn_string_create_empty(subpool); 247 248 if (svn_path_is_url(target)) 249 { 250 /* For URLs, put the temporary file in the current directory. */ 251 base_dir = "."; 252 } 253 else 254 { 255 if (opt_state->message || opt_state->filedata || 256 opt_state->revprop_table) 257 { 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")); 262 } 263 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)); 267 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); 274 } 275 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, 280 base_dir, 281 propval, 282 "svn-prop", 283 ctx->config, 284 svn_prop_needs_translation 285 (pname), 286 opt_state->encoding, 287 subpool)); 288 289 target_local = svn_path_is_url(target) ? target 290 : svn_dirent_local_style(target, subpool); 291 cib.target_local = target_local; 292 293 /* ...and re-set the property's value accordingly. */ 294 if (edited_propval && !svn_string_compare(propval, edited_propval)) 295 { 296 svn_error_t *err = SVN_NO_ERROR; 297 298 svn_cl__check_boolean_prop_val(pname, edited_propval->data, 299 subpool); 300 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, 304 subpool)); 305 if (svn_path_is_url(target)) 306 { 307 err = svn_client_propset_remote(pname, edited_propval, 308 target, opt_state->force, 309 base_rev, 310 opt_state->revprop_table, 311 commit_info_handler, &cib, 312 ctx, subpool); 313 } 314 else 315 { 316 apr_array_header_t *targs = apr_array_make(subpool, 1, 317 sizeof(const char *)); 318 319 APR_ARRAY_PUSH(targs, const char *) = target; 320 321 SVN_ERR(svn_cl__propset_print_binary_mime_type_warning( 322 targs, pname, propval, subpool)); 323 324 err = svn_client_propset_local(pname, edited_propval, 325 targs, svn_depth_empty, 326 opt_state->force, NULL, 327 ctx, subpool); 328 } 329 330 if (ctx->log_msg_func3) 331 SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, 332 err, pool)); 333 else if (err) 334 return svn_error_trace(err); 335 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 338 propedit). */ 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, target_local)); 343 } 344 else 345 { 346 SVN_ERR 347 (svn_cmdline_printf 348 (subpool, _("No changes to property '%s' on '%s'\n"), 349 pname, target_local)); 350 } 351 } 352 svn_pool_destroy(subpool); 353 } 354 355 return SVN_NO_ERROR; 356} 357