2 * propedit-cmd.c -- Edit properties of files/dirs using $EDITOR
4 * ====================================================================
5 * Copyright (c) 2000-2004 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
25 #include "svn_cmdline.h"
27 #include "svn_pools.h"
28 #include "svn_client.h"
29 #include "svn_string.h"
31 #include "svn_error.h"
33 #include "svn_props.h"
36 #include "svn_private_config.h"
41 /* This implements the `svn_opt_subcommand_t' interface. */
43 svn_cl__propedit(apr_getopt_t
*os
,
47 svn_cl__opt_state_t
*opt_state
= ((svn_cl__cmd_baton_t
*) baton
)->opt_state
;
48 svn_client_ctx_t
*ctx
= ((svn_cl__cmd_baton_t
*) baton
)->ctx
;
49 const char *pname
, *pname_utf8
;
50 apr_array_header_t
*args
, *targets
;
53 /* Validate the input and get the property's name (and a UTF-8
54 version of that name). */
55 SVN_ERR(svn_opt_parse_num_args(&args
, os
, 1, pool
));
56 pname
= APR_ARRAY_IDX(args
, 0, const char *);
57 SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8
, pname
, pool
));
58 if (! svn_prop_name_is_valid(pname_utf8
))
59 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME
, NULL
,
60 _("'%s' is not a valid Subversion property name"),
62 if (opt_state
->encoding
&& !svn_prop_needs_translation(pname_utf8
))
63 return svn_error_create
64 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
65 _("--encoding option applies only to textual"
66 " Subversion-controlled properties"));
68 /* Suck up all the remaining arguments into a targets array */
69 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets
, os
,
73 if (opt_state
->revprop
) /* operate on a revprop */
77 svn_string_t
*propval
;
80 /* Implicit "." is okay for revision properties; it just helps
81 us find the right repository. */
82 svn_opt_push_implicit_dot_target(targets
, pool
);
84 SVN_ERR(svn_cl__revprop_prepare(&opt_state
->start_revision
, targets
,
87 /* Fetch the current property. */
88 SVN_ERR(svn_client_revprop_get(pname_utf8
, &propval
,
89 URL
, &(opt_state
->start_revision
),
92 propval
= svn_string_create("", pool
);
94 /* Run the editor on a temporary file which contains the
95 original property value... */
96 SVN_ERR(svn_io_temp_dir(&temp_dir
, pool
));
97 SVN_ERR(svn_cl__edit_string_externally
99 opt_state
->editor_cmd
, temp_dir
,
102 svn_prop_needs_translation(pname_utf8
),
103 opt_state
->encoding
, pool
));
105 /* ...and re-set the property's value accordingly. */
108 SVN_ERR(svn_client_revprop_set(pname_utf8
, propval
,
109 URL
, &(opt_state
->start_revision
),
110 &rev
, opt_state
->force
, ctx
, pool
));
115 _("Set new value for property '%s' on revision %ld\n"),
120 SVN_ERR(svn_cmdline_printf
121 (pool
, _("No changes to property '%s' on revision %ld\n"),
125 else if (opt_state
->start_revision
.kind
!= svn_opt_revision_unspecified
)
127 return svn_error_createf
128 (SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
129 _("Cannot specify revision for editing versioned property '%s'"),
132 else /* operate on a normal, versioned property (not a revprop) */
134 apr_pool_t
*subpool
= svn_pool_create(pool
);
136 /* The customary implicit dot rule has been prone to user error
137 * here. For example, Jon Trowbridge <trow@gnu.og> did
139 * $ svn propedit HACKING
141 * and then when he closed his editor, he was surprised to see
143 * Set new value for property 'HACKING' on ''
145 * ...meaning that the property named 'HACKING' had been set on
146 * the current working directory, with the value taken from the
147 * editor. So we don't do the implicit dot thing anymore; an
148 * explicit target is always required when editing a versioned
151 if (targets
->nelts
== 0)
153 return svn_error_create
154 (SVN_ERR_CL_INSUFFICIENT_ARGS
, NULL
,
155 _("Explicit target argument required"));
158 /* For each target, edit the property PNAME. */
159 for (i
= 0; i
< targets
->nelts
; i
++)
162 const char *target
= APR_ARRAY_IDX(targets
, i
, const char *);
163 svn_string_t
*propval
, *edited_propval
;
164 const char *base_dir
= target
;
165 const char *target_local
;
166 svn_wc_adm_access_t
*adm_access
;
167 const svn_wc_entry_t
*entry
;
168 svn_opt_revision_t peg_revision
;
169 svn_revnum_t base_rev
= SVN_INVALID_REVNUM
;
171 svn_pool_clear(subpool
);
172 SVN_ERR(svn_cl__check_cancel(ctx
->cancel_baton
));
174 /* Propedits can only happen on HEAD or the working copy, so
175 the peg revision can be as unspecified. */
176 peg_revision
.kind
= svn_opt_revision_unspecified
;
178 /* Fetch the current property. */
179 SVN_ERR(svn_client_propget3(&props
, pname_utf8
, target
,
181 &(opt_state
->start_revision
),
182 &base_rev
, svn_depth_empty
,
183 NULL
, ctx
, subpool
));
185 /* Get the property value. */
186 propval
= apr_hash_get(props
, target
, APR_HASH_KEY_STRING
);
188 propval
= svn_string_create("", subpool
);
190 if (svn_path_is_url(target
))
192 /* For URLs, put the temporary file in the current directory. */
197 if (opt_state
->message
|| opt_state
->filedata
||
198 opt_state
->revprop_table
)
200 return svn_error_create
201 (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE
, NULL
,
202 _("Local, non-commit operations do not take a log message "
203 "or revision properties"));
206 /* Split the path if it is a file path. */
207 SVN_ERR(svn_wc_adm_probe_open3(&adm_access
, NULL
, target
,
208 FALSE
, 0, ctx
->cancel_func
,
209 ctx
->cancel_baton
, subpool
));
210 SVN_ERR(svn_wc_entry(&entry
, target
, adm_access
, FALSE
, subpool
));
212 return svn_error_createf
213 (SVN_ERR_ENTRY_NOT_FOUND
, NULL
,
214 _("'%s' does not appear to be a working copy path"), target
);
215 if (entry
->kind
== svn_node_file
)
216 svn_path_split(target
, &base_dir
, NULL
, subpool
);
219 /* Run the editor on a temporary file which contains the
220 original property value... */
221 SVN_ERR(svn_cl__edit_string_externally(&edited_propval
, NULL
,
222 opt_state
->editor_cmd
,
227 svn_prop_needs_translation
232 target_local
= svn_path_is_url(target
) ? target
233 : svn_path_local_style(target
, subpool
);
235 /* ...and re-set the property's value accordingly. */
236 if (edited_propval
&& !svn_string_compare(propval
, edited_propval
))
238 svn_commit_info_t
*commit_info
= NULL
;
239 svn_error_t
*err
= SVN_NO_ERROR
;
241 svn_cl__check_boolean_prop_val(pname_utf8
, edited_propval
->data
,
244 if (ctx
->log_msg_func3
)
245 SVN_ERR(svn_cl__make_log_msg_baton(&(ctx
->log_msg_baton3
),
246 opt_state
, NULL
, ctx
->config
,
249 ctx
->revprop_table
= opt_state
->revprop_table
;
251 err
= svn_client_propset3(&commit_info
,
252 pname_utf8
, edited_propval
, target
,
253 svn_depth_empty
, opt_state
->force
,
256 if (ctx
->log_msg_func3
)
257 SVN_ERR(svn_cl__cleanup_log_msg(ctx
->log_msg_baton3
, err
));
261 /* Print a message if we successfully committed or if it
262 was just a wc propset (but not if the user aborted an URL
264 if (commit_info
|| ! svn_path_is_url(target
))
267 (subpool
, _("Set new value for property '%s' on '%s'\n"),
268 pname_utf8
, target_local
));
270 if (commit_info
&& ! opt_state
->quiet
)
271 SVN_ERR(svn_cl__print_commit_info(commit_info
, subpool
));
277 (subpool
, _("No changes to property '%s' on '%s'\n"),
278 pname_utf8
, target_local
));
281 svn_pool_destroy(subpool
);