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