1251881Speter/*
2251881Speter * svnmucc.c: Subversion Multiple URL Client
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter *
23251881Speter */
24251881Speter
25251881Speter/*  Multiple URL Command Client
26251881Speter
27251881Speter    Combine a list of mv, cp and rm commands on URLs into a single commit.
28251881Speter
29251881Speter    How it works: the command line arguments are parsed into an array of
30251881Speter    action structures.  The action structures are interpreted to build a
31251881Speter    tree of operation structures.  The tree of operation structures is
32251881Speter    used to drive an RA commit editor to produce a single commit.
33251881Speter
34251881Speter    To build this client, type 'make svnmucc' from the root of your
35251881Speter    Subversion source directory.
36251881Speter*/
37251881Speter
38251881Speter#include <stdio.h>
39251881Speter#include <string.h>
40251881Speter
41251881Speter#include <apr_lib.h>
42251881Speter
43251881Speter#include "svn_hash.h"
44251881Speter#include "svn_client.h"
45251881Speter#include "svn_cmdline.h"
46251881Speter#include "svn_config.h"
47251881Speter#include "svn_error.h"
48251881Speter#include "svn_path.h"
49251881Speter#include "svn_pools.h"
50251881Speter#include "svn_props.h"
51251881Speter#include "svn_ra.h"
52251881Speter#include "svn_string.h"
53251881Speter#include "svn_subst.h"
54251881Speter#include "svn_utf.h"
55251881Speter#include "svn_version.h"
56251881Speter
57251881Speter#include "private/svn_cmdline_private.h"
58251881Speter#include "private/svn_ra_private.h"
59251881Speter#include "private/svn_string_private.h"
60251881Speter
61251881Speter#include "svn_private_config.h"
62251881Speter
63251881Speterstatic void handle_error(svn_error_t *err, apr_pool_t *pool)
64251881Speter{
65251881Speter  if (err)
66251881Speter    svn_handle_error2(err, stderr, FALSE, "svnmucc: ");
67251881Speter  svn_error_clear(err);
68251881Speter  if (pool)
69251881Speter    svn_pool_destroy(pool);
70251881Speter  exit(EXIT_FAILURE);
71251881Speter}
72251881Speter
73251881Speterstatic apr_pool_t *
74251881Speterinit(const char *application)
75251881Speter{
76251881Speter  svn_error_t *err;
77251881Speter  const svn_version_checklist_t checklist[] = {
78251881Speter    {"svn_client", svn_client_version},
79251881Speter    {"svn_subr", svn_subr_version},
80251881Speter    {"svn_ra", svn_ra_version},
81251881Speter    {NULL, NULL}
82251881Speter  };
83251881Speter  SVN_VERSION_DEFINE(my_version);
84251881Speter
85251881Speter  if (svn_cmdline_init(application, stderr))
86251881Speter    exit(EXIT_FAILURE);
87251881Speter
88251881Speter  err = svn_ver_check_list(&my_version, checklist);
89251881Speter  if (err)
90251881Speter    handle_error(err, NULL);
91251881Speter
92251881Speter  return apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
93251881Speter}
94251881Speter
95251881Speterstatic svn_error_t *
96251881Speteropen_tmp_file(apr_file_t **fp,
97251881Speter              void *callback_baton,
98251881Speter              apr_pool_t *pool)
99251881Speter{
100251881Speter  /* Open a unique file;  use APR_DELONCLOSE. */
101251881Speter  return svn_io_open_unique_file3(fp, NULL, NULL, svn_io_file_del_on_close,
102251881Speter                                  pool, pool);
103251881Speter}
104251881Speter
105251881Speterstatic svn_error_t *
106251881Spetercreate_ra_callbacks(svn_ra_callbacks2_t **callbacks,
107251881Speter                    const char *username,
108251881Speter                    const char *password,
109251881Speter                    const char *config_dir,
110251881Speter                    svn_config_t *cfg_config,
111251881Speter                    svn_boolean_t non_interactive,
112251881Speter                    svn_boolean_t trust_server_cert,
113251881Speter                    svn_boolean_t no_auth_cache,
114251881Speter                    apr_pool_t *pool)
115251881Speter{
116251881Speter  SVN_ERR(svn_ra_create_callbacks(callbacks, pool));
117251881Speter
118251881Speter  SVN_ERR(svn_cmdline_create_auth_baton(&(*callbacks)->auth_baton,
119251881Speter                                        non_interactive,
120251881Speter                                        username, password, config_dir,
121251881Speter                                        no_auth_cache,
122251881Speter                                        trust_server_cert,
123251881Speter                                        cfg_config, NULL, NULL, pool));
124251881Speter
125251881Speter  (*callbacks)->open_tmp_file = open_tmp_file;
126251881Speter
127251881Speter  return SVN_NO_ERROR;
128251881Speter}
129251881Speter
130251881Speter
131251881Speter
132251881Speterstatic svn_error_t *
133251881Spetercommit_callback(const svn_commit_info_t *commit_info,
134251881Speter                void *baton,
135251881Speter                apr_pool_t *pool)
136251881Speter{
137251881Speter  SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n",
138251881Speter                             commit_info->revision,
139251881Speter                             (commit_info->author
140251881Speter                              ? commit_info->author : "(no author)"),
141251881Speter                             commit_info->date));
142251881Speter  return SVN_NO_ERROR;
143251881Speter}
144251881Speter
145251881Spetertypedef enum action_code_t {
146251881Speter  ACTION_MV,
147251881Speter  ACTION_MKDIR,
148251881Speter  ACTION_CP,
149251881Speter  ACTION_PROPSET,
150251881Speter  ACTION_PROPSETF,
151251881Speter  ACTION_PROPDEL,
152251881Speter  ACTION_PUT,
153251881Speter  ACTION_RM
154251881Speter} action_code_t;
155251881Speter
156251881Speterstruct operation {
157251881Speter  enum {
158251881Speter    OP_OPEN,
159251881Speter    OP_DELETE,
160251881Speter    OP_ADD,
161251881Speter    OP_REPLACE,
162251881Speter    OP_PROPSET           /* only for files for which no other operation is
163251881Speter                            occuring; directories are OP_OPEN with non-empty
164251881Speter                            props */
165251881Speter  } operation;
166251881Speter  svn_node_kind_t kind;  /* to copy, mkdir, put or set revprops */
167251881Speter  svn_revnum_t rev;      /* to copy, valid for add and replace */
168251881Speter  const char *url;       /* to copy, valid for add and replace */
169251881Speter  const char *src_file;  /* for put, the source file for contents */
170251881Speter  apr_hash_t *children;  /* const char *path -> struct operation * */
171251881Speter  apr_hash_t *prop_mods; /* const char *prop_name ->
172251881Speter                            const svn_string_t *prop_value */
173251881Speter  apr_array_header_t *prop_dels; /* const char *prop_name deletions */
174251881Speter  void *baton;           /* as returned by the commit editor */
175251881Speter};
176251881Speter
177251881Speter
178251881Speter/* An iterator (for use via apr_table_do) which sets node properties.
179251881Speter   REC is a pointer to a struct driver_state. */
180251881Speterstatic svn_error_t *
181251881Speterchange_props(const svn_delta_editor_t *editor,
182251881Speter             void *baton,
183251881Speter             struct operation *child,
184251881Speter             apr_pool_t *pool)
185251881Speter{
186251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
187251881Speter
188251881Speter  if (child->prop_dels)
189251881Speter    {
190251881Speter      int i;
191251881Speter      for (i = 0; i < child->prop_dels->nelts; i++)
192251881Speter        {
193251881Speter          const char *prop_name;
194251881Speter
195251881Speter          svn_pool_clear(iterpool);
196251881Speter          prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *);
197251881Speter          if (child->kind == svn_node_dir)
198251881Speter            SVN_ERR(editor->change_dir_prop(baton, prop_name,
199251881Speter                                            NULL, iterpool));
200251881Speter          else
201251881Speter            SVN_ERR(editor->change_file_prop(baton, prop_name,
202251881Speter                                             NULL, iterpool));
203251881Speter        }
204251881Speter    }
205251881Speter  if (apr_hash_count(child->prop_mods))
206251881Speter    {
207251881Speter      apr_hash_index_t *hi;
208251881Speter      for (hi = apr_hash_first(pool, child->prop_mods);
209251881Speter           hi; hi = apr_hash_next(hi))
210251881Speter        {
211251881Speter          const char *propname = svn__apr_hash_index_key(hi);
212251881Speter          const svn_string_t *val = svn__apr_hash_index_val(hi);
213251881Speter
214251881Speter          svn_pool_clear(iterpool);
215251881Speter          if (child->kind == svn_node_dir)
216251881Speter            SVN_ERR(editor->change_dir_prop(baton, propname, val, iterpool));
217251881Speter          else
218251881Speter            SVN_ERR(editor->change_file_prop(baton, propname, val, iterpool));
219251881Speter        }
220251881Speter    }
221251881Speter
222251881Speter  svn_pool_destroy(iterpool);
223251881Speter  return SVN_NO_ERROR;
224251881Speter}
225251881Speter
226251881Speter
227251881Speter/* Drive EDITOR to affect the change represented by OPERATION.  HEAD
228251881Speter   is the last-known youngest revision in the repository. */
229251881Speterstatic svn_error_t *
230251881Speterdrive(struct operation *operation,
231251881Speter      svn_revnum_t head,
232251881Speter      const svn_delta_editor_t *editor,
233251881Speter      apr_pool_t *pool)
234251881Speter{
235251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
236251881Speter  apr_hash_index_t *hi;
237251881Speter
238251881Speter  for (hi = apr_hash_first(pool, operation->children);
239251881Speter       hi; hi = apr_hash_next(hi))
240251881Speter    {
241251881Speter      const char *key = svn__apr_hash_index_key(hi);
242251881Speter      struct operation *child = svn__apr_hash_index_val(hi);
243251881Speter      void *file_baton = NULL;
244251881Speter
245251881Speter      svn_pool_clear(subpool);
246251881Speter
247251881Speter      /* Deletes and replacements are simple -- delete something. */
248251881Speter      if (child->operation == OP_DELETE || child->operation == OP_REPLACE)
249251881Speter        {
250251881Speter          SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool));
251251881Speter        }
252251881Speter      /* Opens could be for directories or files. */
253251881Speter      if (child->operation == OP_OPEN || child->operation == OP_PROPSET)
254251881Speter        {
255251881Speter          if (child->kind == svn_node_dir)
256251881Speter            {
257251881Speter              SVN_ERR(editor->open_directory(key, operation->baton, head,
258251881Speter                                             subpool, &child->baton));
259251881Speter            }
260251881Speter          else
261251881Speter            {
262251881Speter              SVN_ERR(editor->open_file(key, operation->baton, head,
263251881Speter                                        subpool, &file_baton));
264251881Speter            }
265251881Speter        }
266251881Speter      /* Adds and replacements could also be for directories or files. */
267251881Speter      if (child->operation == OP_ADD || child->operation == OP_REPLACE)
268251881Speter        {
269251881Speter          if (child->kind == svn_node_dir)
270251881Speter            {
271251881Speter              SVN_ERR(editor->add_directory(key, operation->baton,
272251881Speter                                            child->url, child->rev,
273251881Speter                                            subpool, &child->baton));
274251881Speter            }
275251881Speter          else
276251881Speter            {
277251881Speter              SVN_ERR(editor->add_file(key, operation->baton, child->url,
278251881Speter                                       child->rev, subpool, &file_baton));
279251881Speter            }
280251881Speter        }
281251881Speter      /* If there's a source file and an open file baton, we get to
282251881Speter         change textual contents. */
283251881Speter      if ((child->src_file) && (file_baton))
284251881Speter        {
285251881Speter          svn_txdelta_window_handler_t handler;
286251881Speter          void *handler_baton;
287251881Speter          svn_stream_t *contents;
288251881Speter
289251881Speter          SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool,
290251881Speter                                          &handler, &handler_baton));
291251881Speter          if (strcmp(child->src_file, "-") != 0)
292251881Speter            {
293251881Speter              SVN_ERR(svn_stream_open_readonly(&contents, child->src_file,
294251881Speter                                               pool, pool));
295251881Speter            }
296251881Speter          else
297251881Speter            {
298251881Speter              SVN_ERR(svn_stream_for_stdin(&contents, pool));
299251881Speter            }
300251881Speter          SVN_ERR(svn_txdelta_send_stream(contents, handler,
301251881Speter                                          handler_baton, NULL, pool));
302251881Speter        }
303251881Speter      /* If we opened a file, we need to apply outstanding propmods,
304251881Speter         then close it. */
305251881Speter      if (file_baton)
306251881Speter        {
307251881Speter          if (child->kind == svn_node_file)
308251881Speter            {
309251881Speter              SVN_ERR(change_props(editor, file_baton, child, subpool));
310251881Speter            }
311251881Speter          SVN_ERR(editor->close_file(file_baton, NULL, subpool));
312251881Speter        }
313251881Speter      /* If we opened, added, or replaced a directory, we need to
314251881Speter         recurse, apply outstanding propmods, and then close it. */
315251881Speter      if ((child->kind == svn_node_dir)
316251881Speter          && child->operation != OP_DELETE)
317251881Speter        {
318251881Speter          SVN_ERR(change_props(editor, child->baton, child, subpool));
319251881Speter
320251881Speter          SVN_ERR(drive(child, head, editor, subpool));
321251881Speter
322251881Speter          SVN_ERR(editor->close_directory(child->baton, subpool));
323251881Speter        }
324251881Speter    }
325251881Speter  svn_pool_destroy(subpool);
326251881Speter  return SVN_NO_ERROR;
327251881Speter}
328251881Speter
329251881Speter
330251881Speter/* Find the operation associated with PATH, which is a single-path
331251881Speter   component representing a child of the path represented by
332251881Speter   OPERATION.  If no such child operation exists, create a new one of
333251881Speter   type OP_OPEN. */
334251881Speterstatic struct operation *
335251881Speterget_operation(const char *path,
336251881Speter              struct operation *operation,
337251881Speter              apr_pool_t *pool)
338251881Speter{
339251881Speter  struct operation *child = svn_hash_gets(operation->children, path);
340251881Speter  if (! child)
341251881Speter    {
342251881Speter      child = apr_pcalloc(pool, sizeof(*child));
343251881Speter      child->children = apr_hash_make(pool);
344251881Speter      child->operation = OP_OPEN;
345251881Speter      child->rev = SVN_INVALID_REVNUM;
346251881Speter      child->kind = svn_node_dir;
347251881Speter      child->prop_mods = apr_hash_make(pool);
348251881Speter      child->prop_dels = apr_array_make(pool, 1, sizeof(const char *));
349251881Speter      svn_hash_sets(operation->children, path, child);
350251881Speter    }
351251881Speter  return child;
352251881Speter}
353251881Speter
354251881Speter/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */
355251881Speterstatic const char *
356251881Spetersubtract_anchor(const char *anchor, const char *url, apr_pool_t *pool)
357251881Speter{
358251881Speter  return svn_uri_skip_ancestor(anchor, url, pool);
359251881Speter}
360251881Speter
361251881Speter/* Add PATH to the operations tree rooted at OPERATION, creating any
362251881Speter   intermediate nodes that are required.  Here's what's expected for
363251881Speter   each action type:
364251881Speter
365251881Speter      ACTION          URL    REV      SRC-FILE  PROPNAME
366251881Speter      ------------    -----  -------  --------  --------
367251881Speter      ACTION_MKDIR    NULL   invalid  NULL      NULL
368251881Speter      ACTION_CP       valid  valid    NULL      NULL
369251881Speter      ACTION_PUT      NULL   invalid  valid     NULL
370251881Speter      ACTION_RM       NULL   invalid  NULL      NULL
371251881Speter      ACTION_PROPSET  valid  invalid  NULL      valid
372251881Speter      ACTION_PROPDEL  valid  invalid  NULL      valid
373251881Speter
374251881Speter   Node type information is obtained for any copy source (to determine
375251881Speter   whether to create a file or directory) and for any deleted path (to
376251881Speter   ensure it exists since svn_delta_editor_t->delete_entry doesn't
377251881Speter   return an error on non-existent nodes). */
378251881Speterstatic svn_error_t *
379251881Speterbuild(action_code_t action,
380251881Speter      const char *path,
381251881Speter      const char *url,
382251881Speter      svn_revnum_t rev,
383251881Speter      const char *prop_name,
384251881Speter      const svn_string_t *prop_value,
385251881Speter      const char *src_file,
386251881Speter      svn_revnum_t head,
387251881Speter      const char *anchor,
388251881Speter      svn_ra_session_t *session,
389251881Speter      struct operation *operation,
390251881Speter      apr_pool_t *pool)
391251881Speter{
392251881Speter  apr_array_header_t *path_bits = svn_path_decompose(path, pool);
393251881Speter  const char *path_so_far = "";
394251881Speter  const char *copy_src = NULL;
395251881Speter  svn_revnum_t copy_rev = SVN_INVALID_REVNUM;
396251881Speter  int i;
397251881Speter
398251881Speter  /* Look for any previous operations we've recognized for PATH.  If
399251881Speter     any of PATH's ancestors have not yet been traversed, we'll be
400251881Speter     creating OP_OPEN operations for them as we walk down PATH's path
401251881Speter     components. */
402251881Speter  for (i = 0; i < path_bits->nelts; ++i)
403251881Speter    {
404251881Speter      const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *);
405251881Speter      path_so_far = svn_relpath_join(path_so_far, path_bit, pool);
406251881Speter      operation = get_operation(path_so_far, operation, pool);
407251881Speter
408251881Speter      /* If we cross a replace- or add-with-history, remember the
409251881Speter      source of those things in case we need to lookup the node kind
410251881Speter      of one of their children.  And if this isn't such a copy,
411251881Speter      but we've already seen one in of our parent paths, we just need
412251881Speter      to extend that copy source path by our current path
413251881Speter      component. */
414251881Speter      if (operation->url
415251881Speter          && SVN_IS_VALID_REVNUM(operation->rev)
416251881Speter          && (operation->operation == OP_REPLACE
417251881Speter              || operation->operation == OP_ADD))
418251881Speter        {
419251881Speter          copy_src = subtract_anchor(anchor, operation->url, pool);
420251881Speter          copy_rev = operation->rev;
421251881Speter        }
422251881Speter      else if (copy_src)
423251881Speter        {
424251881Speter          copy_src = svn_relpath_join(copy_src, path_bit, pool);
425251881Speter        }
426251881Speter    }
427251881Speter
428251881Speter  /* Handle property changes. */
429251881Speter  if (prop_name)
430251881Speter    {
431251881Speter      if (operation->operation == OP_DELETE)
432251881Speter        return svn_error_createf(SVN_ERR_BAD_URL, NULL,
433251881Speter                                 "cannot set properties on a location being"
434251881Speter                                 " deleted ('%s')", path);
435251881Speter      /* If we're not adding this thing ourselves, check for existence.  */
436251881Speter      if (! ((operation->operation == OP_ADD) ||
437251881Speter             (operation->operation == OP_REPLACE)))
438251881Speter        {
439251881Speter          SVN_ERR(svn_ra_check_path(session,
440251881Speter                                    copy_src ? copy_src : path,
441251881Speter                                    copy_src ? copy_rev : head,
442251881Speter                                    &operation->kind, pool));
443251881Speter          if (operation->kind == svn_node_none)
444251881Speter            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
445251881Speter                                     "propset: '%s' not found", path);
446251881Speter          else if ((operation->kind == svn_node_file)
447251881Speter                   && (operation->operation == OP_OPEN))
448251881Speter            operation->operation = OP_PROPSET;
449251881Speter        }
450251881Speter      if (! prop_value)
451251881Speter        APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name;
452251881Speter      else
453251881Speter        svn_hash_sets(operation->prop_mods, prop_name, prop_value);
454251881Speter      if (!operation->rev)
455251881Speter        operation->rev = rev;
456251881Speter      return SVN_NO_ERROR;
457251881Speter    }
458251881Speter
459251881Speter  /* We won't fuss about multiple operations on the same path in the
460251881Speter     following cases:
461251881Speter
462251881Speter       - the prior operation was, in fact, a no-op (open)
463251881Speter       - the prior operation was a propset placeholder
464251881Speter       - the prior operation was a deletion
465251881Speter
466251881Speter     Note: while the operation structure certainly supports the
467251881Speter     ability to do a copy of a file followed by a put of new contents
468251881Speter     for the file, we don't let that happen (yet).
469251881Speter  */
470251881Speter  if (operation->operation != OP_OPEN
471251881Speter      && operation->operation != OP_PROPSET
472251881Speter      && operation->operation != OP_DELETE)
473251881Speter    return svn_error_createf(SVN_ERR_BAD_URL, NULL,
474251881Speter                             "unsupported multiple operations on '%s'", path);
475251881Speter
476251881Speter  /* For deletions, we validate that there's actually something to
477251881Speter     delete.  If this is a deletion of the child of a copied
478251881Speter     directory, we need to remember to look in the copy source tree to
479251881Speter     verify that this thing actually exists. */
480251881Speter  if (action == ACTION_RM)
481251881Speter    {
482251881Speter      operation->operation = OP_DELETE;
483251881Speter      SVN_ERR(svn_ra_check_path(session,
484251881Speter                                copy_src ? copy_src : path,
485251881Speter                                copy_src ? copy_rev : head,
486251881Speter                                &operation->kind, pool));
487251881Speter      if (operation->kind == svn_node_none)
488251881Speter        {
489251881Speter          if (copy_src && strcmp(path, copy_src))
490251881Speter            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
491251881Speter                                     "'%s' (from '%s:%ld') not found",
492251881Speter                                     path, copy_src, copy_rev);
493251881Speter          else
494251881Speter            return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found",
495251881Speter                                     path);
496251881Speter        }
497251881Speter    }
498251881Speter  /* Handle copy operations (which can be adds or replacements). */
499251881Speter  else if (action == ACTION_CP)
500251881Speter    {
501251881Speter      if (rev > head)
502251881Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
503251881Speter                                "Copy source revision cannot be younger "
504251881Speter                                "than base revision");
505251881Speter      operation->operation =
506251881Speter        operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
507251881Speter      if (operation->operation == OP_ADD)
508251881Speter        {
509251881Speter          /* There is a bug in the current version of mod_dav_svn
510251881Speter             which incorrectly replaces existing directories.
511251881Speter             Therefore we need to check if the target exists
512251881Speter             and raise an error here. */
513251881Speter          SVN_ERR(svn_ra_check_path(session,
514251881Speter                                    copy_src ? copy_src : path,
515251881Speter                                    copy_src ? copy_rev : head,
516251881Speter                                    &operation->kind, pool));
517251881Speter          if (operation->kind != svn_node_none)
518251881Speter            {
519251881Speter              if (copy_src && strcmp(path, copy_src))
520251881Speter                return svn_error_createf(SVN_ERR_BAD_URL, NULL,
521251881Speter                                         "'%s' (from '%s:%ld') already exists",
522251881Speter                                         path, copy_src, copy_rev);
523251881Speter              else
524251881Speter                return svn_error_createf(SVN_ERR_BAD_URL, NULL,
525251881Speter                                         "'%s' already exists", path);
526251881Speter            }
527251881Speter        }
528251881Speter      SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool),
529251881Speter                                rev, &operation->kind, pool));
530251881Speter      if (operation->kind == svn_node_none)
531251881Speter        return svn_error_createf(SVN_ERR_BAD_URL, NULL,
532251881Speter                                 "'%s' not found",
533251881Speter                                  subtract_anchor(anchor, url, pool));
534251881Speter      operation->url = url;
535251881Speter      operation->rev = rev;
536251881Speter    }
537251881Speter  /* Handle mkdir operations (which can be adds or replacements). */
538251881Speter  else if (action == ACTION_MKDIR)
539251881Speter    {
540251881Speter      operation->operation =
541251881Speter        operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
542251881Speter      operation->kind = svn_node_dir;
543251881Speter    }
544251881Speter  /* Handle put operations (which can be adds, replacements, or opens). */
545251881Speter  else if (action == ACTION_PUT)
546251881Speter    {
547251881Speter      if (operation->operation == OP_DELETE)
548251881Speter        {
549251881Speter          operation->operation = OP_REPLACE;
550251881Speter        }
551251881Speter      else
552251881Speter        {
553251881Speter          SVN_ERR(svn_ra_check_path(session,
554251881Speter                                    copy_src ? copy_src : path,
555251881Speter                                    copy_src ? copy_rev : head,
556251881Speter                                    &operation->kind, pool));
557251881Speter          if (operation->kind == svn_node_file)
558251881Speter            operation->operation = OP_OPEN;
559251881Speter          else if (operation->kind == svn_node_none)
560251881Speter            operation->operation = OP_ADD;
561251881Speter          else
562251881Speter            return svn_error_createf(SVN_ERR_BAD_URL, NULL,
563251881Speter                                     "'%s' is not a file", path);
564251881Speter        }
565251881Speter      operation->kind = svn_node_file;
566251881Speter      operation->src_file = src_file;
567251881Speter    }
568251881Speter  else
569251881Speter    {
570251881Speter      /* We shouldn't get here. */
571251881Speter      SVN_ERR_MALFUNCTION();
572251881Speter    }
573251881Speter
574251881Speter  return SVN_NO_ERROR;
575251881Speter}
576251881Speter
577251881Speterstruct action {
578251881Speter  action_code_t action;
579251881Speter
580251881Speter  /* revision (copy-from-rev of path[0] for cp; base-rev for put) */
581251881Speter  svn_revnum_t rev;
582251881Speter
583251881Speter  /* action  path[0]  path[1]
584251881Speter   * ------  -------  -------
585251881Speter   * mv      source   target
586251881Speter   * mkdir   target   (null)
587251881Speter   * cp      source   target
588251881Speter   * put     target   source
589251881Speter   * rm      target   (null)
590251881Speter   * propset target   (null)
591251881Speter   */
592251881Speter  const char *path[2];
593251881Speter
594251881Speter  /* property name/value */
595251881Speter  const char *prop_name;
596251881Speter  const svn_string_t *prop_value;
597251881Speter};
598251881Speter
599251881Speterstruct fetch_baton
600251881Speter{
601251881Speter  svn_ra_session_t *session;
602251881Speter  svn_revnum_t head;
603251881Speter};
604251881Speter
605251881Speterstatic svn_error_t *
606251881Speterfetch_base_func(const char **filename,
607251881Speter                void *baton,
608251881Speter                const char *path,
609251881Speter                svn_revnum_t base_revision,
610251881Speter                apr_pool_t *result_pool,
611251881Speter                apr_pool_t *scratch_pool)
612251881Speter{
613251881Speter  struct fetch_baton *fb = baton;
614251881Speter  svn_stream_t *fstream;
615251881Speter  svn_error_t *err;
616251881Speter
617251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
618251881Speter    base_revision = fb->head;
619251881Speter
620251881Speter  SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
621251881Speter                                 svn_io_file_del_on_pool_cleanup,
622251881Speter                                 result_pool, scratch_pool));
623251881Speter
624251881Speter  err = svn_ra_get_file(fb->session, path, base_revision, fstream, NULL, NULL,
625251881Speter                         scratch_pool);
626251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
627251881Speter    {
628251881Speter      svn_error_clear(err);
629251881Speter      SVN_ERR(svn_stream_close(fstream));
630251881Speter
631251881Speter      *filename = NULL;
632251881Speter      return SVN_NO_ERROR;
633251881Speter    }
634251881Speter  else if (err)
635251881Speter    return svn_error_trace(err);
636251881Speter
637251881Speter  SVN_ERR(svn_stream_close(fstream));
638251881Speter
639251881Speter  return SVN_NO_ERROR;
640251881Speter}
641251881Speter
642251881Speterstatic svn_error_t *
643251881Speterfetch_props_func(apr_hash_t **props,
644251881Speter                 void *baton,
645251881Speter                 const char *path,
646251881Speter                 svn_revnum_t base_revision,
647251881Speter                 apr_pool_t *result_pool,
648251881Speter                 apr_pool_t *scratch_pool)
649251881Speter{
650251881Speter  struct fetch_baton *fb = baton;
651251881Speter  svn_node_kind_t node_kind;
652251881Speter
653251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
654251881Speter    base_revision = fb->head;
655251881Speter
656251881Speter  SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, &node_kind,
657251881Speter                            scratch_pool));
658251881Speter
659251881Speter  if (node_kind == svn_node_file)
660251881Speter    {
661251881Speter      SVN_ERR(svn_ra_get_file(fb->session, path, base_revision, NULL, NULL,
662251881Speter                              props, result_pool));
663251881Speter    }
664251881Speter  else if (node_kind == svn_node_dir)
665251881Speter    {
666251881Speter      apr_array_header_t *tmp_props;
667251881Speter
668251881Speter      SVN_ERR(svn_ra_get_dir2(fb->session, NULL, NULL, props, path,
669251881Speter                              base_revision, 0 /* Dirent fields */,
670251881Speter                              result_pool));
671251881Speter      tmp_props = svn_prop_hash_to_array(*props, result_pool);
672251881Speter      SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
673251881Speter                                   result_pool));
674251881Speter      *props = svn_prop_array_to_hash(tmp_props, result_pool);
675251881Speter    }
676251881Speter  else
677251881Speter    {
678251881Speter      *props = apr_hash_make(result_pool);
679251881Speter    }
680251881Speter
681251881Speter  return SVN_NO_ERROR;
682251881Speter}
683251881Speter
684251881Speterstatic svn_error_t *
685251881Speterfetch_kind_func(svn_node_kind_t *kind,
686251881Speter                void *baton,
687251881Speter                const char *path,
688251881Speter                svn_revnum_t base_revision,
689251881Speter                apr_pool_t *scratch_pool)
690251881Speter{
691251881Speter  struct fetch_baton *fb = baton;
692251881Speter
693251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
694251881Speter    base_revision = fb->head;
695251881Speter
696251881Speter  SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, kind,
697251881Speter                             scratch_pool));
698251881Speter
699251881Speter  return SVN_NO_ERROR;
700251881Speter}
701251881Speter
702251881Speterstatic svn_delta_shim_callbacks_t *
703251881Speterget_shim_callbacks(svn_ra_session_t *session,
704251881Speter                   svn_revnum_t head,
705251881Speter                   apr_pool_t *result_pool)
706251881Speter{
707251881Speter  svn_delta_shim_callbacks_t *callbacks =
708251881Speter                            svn_delta_shim_callbacks_default(result_pool);
709251881Speter  struct fetch_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
710251881Speter
711251881Speter  fb->session = session;
712251881Speter  fb->head = head;
713251881Speter
714251881Speter  callbacks->fetch_props_func = fetch_props_func;
715251881Speter  callbacks->fetch_kind_func = fetch_kind_func;
716251881Speter  callbacks->fetch_base_func = fetch_base_func;
717251881Speter  callbacks->fetch_baton = fb;
718251881Speter
719251881Speter  return callbacks;
720251881Speter}
721251881Speter
722251881Speterstatic svn_error_t *
723251881Speterexecute(const apr_array_header_t *actions,
724251881Speter        const char *anchor,
725251881Speter        apr_hash_t *revprops,
726251881Speter        const char *username,
727251881Speter        const char *password,
728251881Speter        const char *config_dir,
729251881Speter        const apr_array_header_t *config_options,
730251881Speter        svn_boolean_t non_interactive,
731251881Speter        svn_boolean_t trust_server_cert,
732251881Speter        svn_boolean_t no_auth_cache,
733251881Speter        svn_revnum_t base_revision,
734251881Speter        apr_pool_t *pool)
735251881Speter{
736251881Speter  svn_ra_session_t *session;
737251881Speter  svn_ra_session_t *aux_session;
738251881Speter  const char *repos_root;
739251881Speter  svn_revnum_t head;
740251881Speter  const svn_delta_editor_t *editor;
741251881Speter  svn_ra_callbacks2_t *ra_callbacks;
742251881Speter  void *editor_baton;
743251881Speter  struct operation root;
744251881Speter  svn_error_t *err;
745251881Speter  apr_hash_t *config;
746251881Speter  svn_config_t *cfg_config;
747251881Speter  int i;
748251881Speter
749251881Speter  SVN_ERR(svn_config_get_config(&config, config_dir, pool));
750251881Speter  SVN_ERR(svn_cmdline__apply_config_options(config, config_options,
751251881Speter                                            "svnmucc: ", "--config-option"));
752251881Speter  cfg_config = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
753251881Speter
754251881Speter  if (! svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))
755251881Speter    {
756251881Speter      svn_string_t *msg = svn_string_create("", pool);
757251881Speter
758251881Speter      /* If we can do so, try to pop up $EDITOR to fetch a log message. */
759251881Speter      if (non_interactive)
760251881Speter        {
761251881Speter          return svn_error_create
762251881Speter            (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
763251881Speter             _("Cannot invoke editor to get log message "
764251881Speter               "when non-interactive"));
765251881Speter        }
766251881Speter      else
767251881Speter        {
768251881Speter          SVN_ERR(svn_cmdline__edit_string_externally(
769251881Speter                      &msg, NULL, NULL, "", msg, "svnmucc-commit", config,
770251881Speter                      TRUE, NULL, apr_hash_pool_get(revprops)));
771251881Speter        }
772251881Speter
773251881Speter      svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, msg);
774251881Speter    }
775251881Speter
776251881Speter  SVN_ERR(create_ra_callbacks(&ra_callbacks, username, password, config_dir,
777251881Speter                              cfg_config, non_interactive, trust_server_cert,
778251881Speter                              no_auth_cache, pool));
779251881Speter  SVN_ERR(svn_ra_open4(&session, NULL, anchor, NULL, ra_callbacks,
780251881Speter                       NULL, config, pool));
781251881Speter  /* Open, then reparent to avoid AUTHZ errors when opening the reposroot */
782251881Speter  SVN_ERR(svn_ra_open4(&aux_session, NULL, anchor, NULL, ra_callbacks,
783251881Speter                       NULL, config, pool));
784251881Speter  SVN_ERR(svn_ra_get_repos_root2(aux_session, &repos_root, pool));
785251881Speter  SVN_ERR(svn_ra_reparent(aux_session, repos_root, pool));
786251881Speter  SVN_ERR(svn_ra_get_latest_revnum(session, &head, pool));
787251881Speter
788251881Speter  /* Reparent to ANCHOR's dir, if ANCHOR is not a directory. */
789251881Speter  {
790251881Speter    svn_node_kind_t kind;
791251881Speter
792251881Speter    SVN_ERR(svn_ra_check_path(aux_session,
793251881Speter                              svn_uri_skip_ancestor(repos_root, anchor, pool),
794251881Speter                              head, &kind, pool));
795251881Speter    if (kind != svn_node_dir)
796251881Speter      {
797251881Speter        anchor = svn_uri_dirname(anchor, pool);
798251881Speter        SVN_ERR(svn_ra_reparent(session, anchor, pool));
799251881Speter      }
800251881Speter  }
801251881Speter
802251881Speter  if (SVN_IS_VALID_REVNUM(base_revision))
803251881Speter    {
804251881Speter      if (base_revision > head)
805251881Speter        return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
806251881Speter                                 "No such revision %ld (youngest is %ld)",
807251881Speter                                 base_revision, head);
808251881Speter      head = base_revision;
809251881Speter    }
810251881Speter
811251881Speter  memset(&root, 0, sizeof(root));
812251881Speter  root.children = apr_hash_make(pool);
813251881Speter  root.operation = OP_OPEN;
814251881Speter  root.kind = svn_node_dir; /* For setting properties */
815251881Speter  root.prop_mods = apr_hash_make(pool);
816251881Speter  root.prop_dels = apr_array_make(pool, 1, sizeof(const char *));
817251881Speter
818251881Speter  for (i = 0; i < actions->nelts; ++i)
819251881Speter    {
820251881Speter      struct action *action = APR_ARRAY_IDX(actions, i, struct action *);
821251881Speter      const char *path1, *path2;
822251881Speter      switch (action->action)
823251881Speter        {
824251881Speter        case ACTION_MV:
825251881Speter          path1 = subtract_anchor(anchor, action->path[0], pool);
826251881Speter          path2 = subtract_anchor(anchor, action->path[1], pool);
827251881Speter          SVN_ERR(build(ACTION_RM, path1, NULL,
828251881Speter                        SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
829251881Speter                        session, &root, pool));
830251881Speter          SVN_ERR(build(ACTION_CP, path2, action->path[0],
831251881Speter                        head, NULL, NULL, NULL, head, anchor,
832251881Speter                        session, &root, pool));
833251881Speter          break;
834251881Speter        case ACTION_CP:
835251881Speter          path2 = subtract_anchor(anchor, action->path[1], pool);
836251881Speter          if (action->rev == SVN_INVALID_REVNUM)
837251881Speter            action->rev = head;
838251881Speter          SVN_ERR(build(ACTION_CP, path2, action->path[0],
839251881Speter                        action->rev, NULL, NULL, NULL, head, anchor,
840251881Speter                        session, &root, pool));
841251881Speter          break;
842251881Speter        case ACTION_RM:
843251881Speter          path1 = subtract_anchor(anchor, action->path[0], pool);
844251881Speter          SVN_ERR(build(ACTION_RM, path1, NULL,
845251881Speter                        SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
846251881Speter                        session, &root, pool));
847251881Speter          break;
848251881Speter        case ACTION_MKDIR:
849251881Speter          path1 = subtract_anchor(anchor, action->path[0], pool);
850251881Speter          SVN_ERR(build(ACTION_MKDIR, path1, action->path[0],
851251881Speter                        SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
852251881Speter                        session, &root, pool));
853251881Speter          break;
854251881Speter        case ACTION_PUT:
855251881Speter          path1 = subtract_anchor(anchor, action->path[0], pool);
856251881Speter          SVN_ERR(build(ACTION_PUT, path1, action->path[0],
857251881Speter                        SVN_INVALID_REVNUM, NULL, NULL, action->path[1],
858251881Speter                        head, anchor, session, &root, pool));
859251881Speter          break;
860251881Speter        case ACTION_PROPSET:
861251881Speter        case ACTION_PROPDEL:
862251881Speter          path1 = subtract_anchor(anchor, action->path[0], pool);
863251881Speter          SVN_ERR(build(action->action, path1, action->path[0],
864251881Speter                        SVN_INVALID_REVNUM,
865251881Speter                        action->prop_name, action->prop_value,
866251881Speter                        NULL, head, anchor, session, &root, pool));
867251881Speter          break;
868251881Speter        case ACTION_PROPSETF:
869251881Speter        default:
870251881Speter          SVN_ERR_MALFUNCTION_NO_RETURN();
871251881Speter        }
872251881Speter    }
873251881Speter
874251881Speter  SVN_ERR(svn_ra__register_editor_shim_callbacks(session,
875251881Speter                            get_shim_callbacks(aux_session, head, pool)));
876251881Speter  SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &editor_baton, revprops,
877251881Speter                                    commit_callback, NULL, NULL, FALSE, pool));
878251881Speter
879251881Speter  SVN_ERR(editor->open_root(editor_baton, head, pool, &root.baton));
880251881Speter  err = change_props(editor, root.baton, &root, pool);
881251881Speter  if (!err)
882251881Speter    err = drive(&root, head, editor, pool);
883251881Speter  if (!err)
884251881Speter    err = editor->close_directory(root.baton, pool);
885251881Speter  if (!err)
886251881Speter    err = editor->close_edit(editor_baton, pool);
887251881Speter
888251881Speter  if (err)
889251881Speter    err = svn_error_compose_create(err,
890251881Speter                                   editor->abort_edit(editor_baton, pool));
891251881Speter
892251881Speter  return err;
893251881Speter}
894251881Speter
895251881Speterstatic svn_error_t *
896251881Speterread_propvalue_file(const svn_string_t **value_p,
897251881Speter                    const char *filename,
898251881Speter                    apr_pool_t *pool)
899251881Speter{
900251881Speter  svn_stringbuf_t *value;
901251881Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
902251881Speter
903251881Speter  SVN_ERR(svn_stringbuf_from_file2(&value, filename, scratch_pool));
904251881Speter  *value_p = svn_string_create_from_buf(value, pool);
905251881Speter  svn_pool_destroy(scratch_pool);
906251881Speter  return SVN_NO_ERROR;
907251881Speter}
908251881Speter
909251881Speter/* Perform the typical suite of manipulations for user-provided URLs
910251881Speter   on URL, returning the result (allocated from POOL): IRI-to-URI
911251881Speter   conversion, auto-escaping, and canonicalization. */
912251881Speterstatic const char *
913251881Spetersanitize_url(const char *url,
914251881Speter             apr_pool_t *pool)
915251881Speter{
916251881Speter  url = svn_path_uri_from_iri(url, pool);
917251881Speter  url = svn_path_uri_autoescape(url, pool);
918251881Speter  return svn_uri_canonicalize(url, pool);
919251881Speter}
920251881Speter
921251881Speterstatic void
922251881Speterusage(apr_pool_t *pool, int exit_val)
923251881Speter{
924251881Speter  FILE *stream = exit_val == EXIT_SUCCESS ? stdout : stderr;
925251881Speter  svn_error_clear(svn_cmdline_fputs(
926251881Speter    _("Subversion multiple URL command client\n"
927251881Speter      "usage: svnmucc ACTION...\n"
928251881Speter      "\n"
929251881Speter      "  Perform one or more Subversion repository URL-based ACTIONs, committing\n"
930251881Speter      "  the result as a (single) new revision.\n"
931251881Speter      "\n"
932251881Speter      "Actions:\n"
933251881Speter      "  cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL\n"
934251881Speter      "  mkdir URL              : create new directory URL\n"
935251881Speter      "  mv SRC-URL DST-URL     : move SRC-URL to DST-URL\n"
936251881Speter      "  rm URL                 : delete URL\n"
937251881Speter      "  put SRC-FILE URL       : add or modify file URL with contents copied from\n"
938251881Speter      "                           SRC-FILE (use \"-\" to read from standard input)\n"
939251881Speter      "  propset NAME VALUE URL : set property NAME on URL to VALUE\n"
940251881Speter      "  propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n"
941251881Speter      "  propdel NAME URL       : delete property NAME from URL\n"
942251881Speter      "\n"
943251881Speter      "Valid options:\n"
944251881Speter      "  -h, -? [--help]        : display this text\n"
945251881Speter      "  -m [--message] ARG     : use ARG as a log message\n"
946251881Speter      "  -F [--file] ARG        : read log message from file ARG\n"
947251881Speter      "  -u [--username] ARG    : commit the changes as username ARG\n"
948251881Speter      "  -p [--password] ARG    : use ARG as the password\n"
949251881Speter      "  -U [--root-url] ARG    : interpret all action URLs relative to ARG\n"
950251881Speter      "  -r [--revision] ARG    : use revision ARG as baseline for changes\n"
951251881Speter      "  --with-revprop ARG     : set revision property in the following format:\n"
952251881Speter      "                               NAME[=VALUE]\n"
953251881Speter      "  --non-interactive      : do no interactive prompting (default is to\n"
954251881Speter      "                           prompt only if standard input is a terminal)\n"
955251881Speter      "  --force-interactive    : do interactive prompting even if standard\n"
956251881Speter      "                           input is not a terminal\n"
957251881Speter      "  --trust-server-cert    : accept SSL server certificates from unknown\n"
958251881Speter      "                           certificate authorities without prompting (but\n"
959251881Speter      "                           only with '--non-interactive')\n"
960251881Speter      "  -X [--extra-args] ARG  : append arguments from file ARG (one per line;\n"
961251881Speter      "                           use \"-\" to read from standard input)\n"
962251881Speter      "  --config-dir ARG       : use ARG to override the config directory\n"
963251881Speter      "  --config-option ARG    : use ARG to override a configuration option\n"
964251881Speter      "  --no-auth-cache        : do not cache authentication tokens\n"
965251881Speter      "  --version              : print version information\n"),
966251881Speter                  stream, pool));
967251881Speter  svn_pool_destroy(pool);
968251881Speter  exit(exit_val);
969251881Speter}
970251881Speter
971251881Speterstatic void
972251881Speterinsufficient(apr_pool_t *pool)
973251881Speter{
974251881Speter  handle_error(svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
975251881Speter                                "insufficient arguments"),
976251881Speter               pool);
977251881Speter}
978251881Speter
979251881Speterstatic svn_error_t *
980251881Speterdisplay_version(apr_getopt_t *os, apr_pool_t *pool)
981251881Speter{
982251881Speter  const char *ra_desc_start
983251881Speter    = "The following repository access (RA) modules are available:\n\n";
984251881Speter  svn_stringbuf_t *version_footer;
985251881Speter
986251881Speter  version_footer = svn_stringbuf_create(ra_desc_start, pool);
987251881Speter  SVN_ERR(svn_ra_print_modules(version_footer, pool));
988251881Speter
989251881Speter  SVN_ERR(svn_opt_print_help4(os, "svnmucc", TRUE, FALSE, FALSE,
990251881Speter                              version_footer->data,
991251881Speter                              NULL, NULL, NULL, NULL, NULL, pool));
992251881Speter
993251881Speter  return SVN_NO_ERROR;
994251881Speter}
995251881Speter
996251881Speter/* Return an error about the mutual exclusivity of the -m, -F, and
997251881Speter   --with-revprop=svn:log command-line options. */
998251881Speterstatic svn_error_t *
999251881Spetermutually_exclusive_logs_error(void)
1000251881Speter{
1001251881Speter  return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1002251881Speter                          _("--message (-m), --file (-F), and "
1003251881Speter                            "--with-revprop=svn:log are mutually "
1004251881Speter                            "exclusive"));
1005251881Speter}
1006251881Speter
1007251881Speter/* Ensure that the REVPROPS hash contains a command-line-provided log
1008251881Speter   message, if any, and that there was but one source of such a thing
1009251881Speter   provided on that command-line.  */
1010251881Speterstatic svn_error_t *
1011251881Spetersanitize_log_sources(apr_hash_t *revprops,
1012251881Speter                     const char *message,
1013251881Speter                     svn_stringbuf_t *filedata)
1014251881Speter{
1015251881Speter  apr_pool_t *hash_pool = apr_hash_pool_get(revprops);
1016251881Speter
1017251881Speter  /* If we already have a log message in the revprop hash, then just
1018251881Speter     make sure the user didn't try to also use -m or -F.  Otherwise,
1019251881Speter     we need to consult -m or -F to find a log message, if any. */
1020251881Speter  if (svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))
1021251881Speter    {
1022251881Speter      if (filedata || message)
1023251881Speter        return mutually_exclusive_logs_error();
1024251881Speter    }
1025251881Speter  else if (filedata)
1026251881Speter    {
1027251881Speter      if (message)
1028251881Speter        return mutually_exclusive_logs_error();
1029251881Speter
1030251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&message, filedata->data, hash_pool));
1031251881Speter      svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
1032251881Speter                    svn_stringbuf__morph_into_string(filedata));
1033251881Speter    }
1034251881Speter  else if (message)
1035251881Speter    {
1036251881Speter      svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
1037251881Speter                    svn_string_create(message, hash_pool));
1038251881Speter    }
1039251881Speter
1040251881Speter  return SVN_NO_ERROR;
1041251881Speter}
1042251881Speter
1043251881Speterint
1044251881Spetermain(int argc, const char **argv)
1045251881Speter{
1046251881Speter  apr_pool_t *pool = init("svnmucc");
1047251881Speter  apr_array_header_t *actions = apr_array_make(pool, 1,
1048251881Speter                                               sizeof(struct action *));
1049251881Speter  const char *anchor = NULL;
1050251881Speter  svn_error_t *err = SVN_NO_ERROR;
1051251881Speter  apr_getopt_t *opts;
1052251881Speter  enum {
1053251881Speter    config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID,
1054251881Speter    config_inline_opt,
1055251881Speter    no_auth_cache_opt,
1056251881Speter    version_opt,
1057251881Speter    with_revprop_opt,
1058251881Speter    non_interactive_opt,
1059251881Speter    force_interactive_opt,
1060251881Speter    trust_server_cert_opt
1061251881Speter  };
1062251881Speter  static const apr_getopt_option_t options[] = {
1063251881Speter    {"message", 'm', 1, ""},
1064251881Speter    {"file", 'F', 1, ""},
1065251881Speter    {"username", 'u', 1, ""},
1066251881Speter    {"password", 'p', 1, ""},
1067251881Speter    {"root-url", 'U', 1, ""},
1068251881Speter    {"revision", 'r', 1, ""},
1069251881Speter    {"with-revprop",  with_revprop_opt, 1, ""},
1070251881Speter    {"extra-args", 'X', 1, ""},
1071251881Speter    {"help", 'h', 0, ""},
1072251881Speter    {NULL, '?', 0, ""},
1073251881Speter    {"non-interactive", non_interactive_opt, 0, ""},
1074251881Speter    {"force-interactive", force_interactive_opt, 0, ""},
1075251881Speter    {"trust-server-cert", trust_server_cert_opt, 0, ""},
1076251881Speter    {"config-dir", config_dir_opt, 1, ""},
1077251881Speter    {"config-option",  config_inline_opt, 1, ""},
1078251881Speter    {"no-auth-cache",  no_auth_cache_opt, 0, ""},
1079251881Speter    {"version", version_opt, 0, ""},
1080251881Speter    {NULL, 0, 0, NULL}
1081251881Speter  };
1082251881Speter  const char *message = NULL;
1083251881Speter  svn_stringbuf_t *filedata = NULL;
1084251881Speter  const char *username = NULL, *password = NULL;
1085251881Speter  const char *root_url = NULL, *extra_args_file = NULL;
1086251881Speter  const char *config_dir = NULL;
1087251881Speter  apr_array_header_t *config_options;
1088251881Speter  svn_boolean_t non_interactive = FALSE;
1089251881Speter  svn_boolean_t force_interactive = FALSE;
1090251881Speter  svn_boolean_t trust_server_cert = FALSE;
1091251881Speter  svn_boolean_t no_auth_cache = FALSE;
1092251881Speter  svn_revnum_t base_revision = SVN_INVALID_REVNUM;
1093251881Speter  apr_array_header_t *action_args;
1094251881Speter  apr_hash_t *revprops = apr_hash_make(pool);
1095251881Speter  int i;
1096251881Speter
1097251881Speter  config_options = apr_array_make(pool, 0,
1098251881Speter                                  sizeof(svn_cmdline__config_argument_t*));
1099251881Speter
1100251881Speter  apr_getopt_init(&opts, pool, argc, argv);
1101251881Speter  opts->interleave = 1;
1102251881Speter  while (1)
1103251881Speter    {
1104251881Speter      int opt;
1105251881Speter      const char *arg;
1106251881Speter      const char *opt_arg;
1107251881Speter
1108251881Speter      apr_status_t status = apr_getopt_long(opts, options, &opt, &arg);
1109251881Speter      if (APR_STATUS_IS_EOF(status))
1110251881Speter        break;
1111251881Speter      if (status != APR_SUCCESS)
1112251881Speter        handle_error(svn_error_wrap_apr(status, "getopt failure"), pool);
1113251881Speter      switch(opt)
1114251881Speter        {
1115251881Speter        case 'm':
1116251881Speter          err = svn_utf_cstring_to_utf8(&message, arg, pool);
1117251881Speter          if (err)
1118251881Speter            handle_error(err, pool);
1119251881Speter          break;
1120251881Speter        case 'F':
1121251881Speter          {
1122251881Speter            const char *arg_utf8;
1123251881Speter            err = svn_utf_cstring_to_utf8(&arg_utf8, arg, pool);
1124251881Speter            if (! err)
1125251881Speter              err = svn_stringbuf_from_file2(&filedata, arg, pool);
1126251881Speter            if (err)
1127251881Speter              handle_error(err, pool);
1128251881Speter          }
1129251881Speter          break;
1130251881Speter        case 'u':
1131251881Speter          username = apr_pstrdup(pool, arg);
1132251881Speter          break;
1133251881Speter        case 'p':
1134251881Speter          password = apr_pstrdup(pool, arg);
1135251881Speter          break;
1136251881Speter        case 'U':
1137251881Speter          err = svn_utf_cstring_to_utf8(&root_url, arg, pool);
1138251881Speter          if (err)
1139251881Speter            handle_error(err, pool);
1140251881Speter          if (! svn_path_is_url(root_url))
1141251881Speter            handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1142251881Speter                                           "'%s' is not a URL\n", root_url),
1143251881Speter                         pool);
1144251881Speter          root_url = sanitize_url(root_url, pool);
1145251881Speter          break;
1146251881Speter        case 'r':
1147251881Speter          {
1148251881Speter            char *digits_end = NULL;
1149251881Speter            base_revision = strtol(arg, &digits_end, 10);
1150251881Speter            if ((! SVN_IS_VALID_REVNUM(base_revision))
1151251881Speter                || (! digits_end)
1152251881Speter                || *digits_end)
1153251881Speter              handle_error(svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR,
1154251881Speter                                            NULL, "Invalid revision number"),
1155251881Speter                           pool);
1156251881Speter          }
1157251881Speter          break;
1158251881Speter        case with_revprop_opt:
1159251881Speter          err = svn_opt_parse_revprop(&revprops, arg, pool);
1160251881Speter          if (err != SVN_NO_ERROR)
1161251881Speter            handle_error(err, pool);
1162251881Speter          break;
1163251881Speter        case 'X':
1164251881Speter          extra_args_file = apr_pstrdup(pool, arg);
1165251881Speter          break;
1166251881Speter        case non_interactive_opt:
1167251881Speter          non_interactive = TRUE;
1168251881Speter          break;
1169251881Speter        case force_interactive_opt:
1170251881Speter          force_interactive = TRUE;
1171251881Speter          break;
1172251881Speter        case trust_server_cert_opt:
1173251881Speter          trust_server_cert = TRUE;
1174251881Speter          break;
1175251881Speter        case config_dir_opt:
1176251881Speter          err = svn_utf_cstring_to_utf8(&config_dir, arg, pool);
1177251881Speter          if (err)
1178251881Speter            handle_error(err, pool);
1179251881Speter          break;
1180251881Speter        case config_inline_opt:
1181251881Speter          err = svn_utf_cstring_to_utf8(&opt_arg, arg, pool);
1182251881Speter          if (err)
1183251881Speter            handle_error(err, pool);
1184251881Speter
1185251881Speter          err = svn_cmdline__parse_config_option(config_options, opt_arg,
1186251881Speter                                                 pool);
1187251881Speter          if (err)
1188251881Speter            handle_error(err, pool);
1189251881Speter          break;
1190251881Speter        case no_auth_cache_opt:
1191251881Speter          no_auth_cache = TRUE;
1192251881Speter          break;
1193251881Speter        case version_opt:
1194251881Speter          SVN_INT_ERR(display_version(opts, pool));
1195251881Speter          exit(EXIT_SUCCESS);
1196251881Speter          break;
1197251881Speter        case 'h':
1198251881Speter        case '?':
1199251881Speter          usage(pool, EXIT_SUCCESS);
1200251881Speter          break;
1201251881Speter        }
1202251881Speter    }
1203251881Speter
1204251881Speter  if (non_interactive && force_interactive)
1205251881Speter    {
1206251881Speter      err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1207251881Speter                             _("--non-interactive and --force-interactive "
1208251881Speter                               "are mutually exclusive"));
1209251881Speter      return svn_cmdline_handle_exit_error(err, pool, "svnmucc: ");
1210251881Speter    }
1211251881Speter  else
1212251881Speter    non_interactive = !svn_cmdline__be_interactive(non_interactive,
1213251881Speter                                                   force_interactive);
1214251881Speter
1215251881Speter  if (trust_server_cert && !non_interactive)
1216251881Speter    {
1217251881Speter      err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1218251881Speter                             _("--trust-server-cert requires "
1219251881Speter                               "--non-interactive"));
1220251881Speter      return svn_cmdline_handle_exit_error(err, pool, "svnmucc: ");
1221251881Speter    }
1222251881Speter
1223251881Speter  /* Make sure we have a log message to use. */
1224251881Speter  err = sanitize_log_sources(revprops, message, filedata);
1225251881Speter  if (err)
1226251881Speter    handle_error(err, pool);
1227251881Speter
1228251881Speter  /* Copy the rest of our command-line arguments to an array,
1229251881Speter     UTF-8-ing them along the way. */
1230251881Speter  action_args = apr_array_make(pool, opts->argc, sizeof(const char *));
1231251881Speter  while (opts->ind < opts->argc)
1232251881Speter    {
1233251881Speter      const char *arg = opts->argv[opts->ind++];
1234251881Speter      if ((err = svn_utf_cstring_to_utf8(&(APR_ARRAY_PUSH(action_args,
1235251881Speter                                                          const char *)),
1236251881Speter                                         arg, pool)))
1237251881Speter        handle_error(err, pool);
1238251881Speter    }
1239251881Speter
1240251881Speter  /* If there are extra arguments in a supplementary file, tack those
1241251881Speter     on, too (again, in UTF8 form). */
1242251881Speter  if (extra_args_file)
1243251881Speter    {
1244251881Speter      const char *extra_args_file_utf8;
1245251881Speter      svn_stringbuf_t *contents, *contents_utf8;
1246251881Speter
1247251881Speter      err = svn_utf_cstring_to_utf8(&extra_args_file_utf8,
1248251881Speter                                    extra_args_file, pool);
1249251881Speter      if (! err)
1250251881Speter        err = svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool);
1251251881Speter      if (! err)
1252251881Speter        err = svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool);
1253251881Speter      if (err)
1254251881Speter        handle_error(err, pool);
1255251881Speter      svn_cstring_split_append(action_args, contents_utf8->data, "\n\r",
1256251881Speter                               FALSE, pool);
1257251881Speter    }
1258251881Speter
1259251881Speter  /* Now, we iterate over the combined set of arguments -- our actions. */
1260251881Speter  for (i = 0; i < action_args->nelts; )
1261251881Speter    {
1262251881Speter      int j, num_url_args;
1263251881Speter      const char *action_string = APR_ARRAY_IDX(action_args, i, const char *);
1264251881Speter      struct action *action = apr_pcalloc(pool, sizeof(*action));
1265251881Speter
1266251881Speter      /* First, parse the action. */
1267251881Speter      if (! strcmp(action_string, "mv"))
1268251881Speter        action->action = ACTION_MV;
1269251881Speter      else if (! strcmp(action_string, "cp"))
1270251881Speter        action->action = ACTION_CP;
1271251881Speter      else if (! strcmp(action_string, "mkdir"))
1272251881Speter        action->action = ACTION_MKDIR;
1273251881Speter      else if (! strcmp(action_string, "rm"))
1274251881Speter        action->action = ACTION_RM;
1275251881Speter      else if (! strcmp(action_string, "put"))
1276251881Speter        action->action = ACTION_PUT;
1277251881Speter      else if (! strcmp(action_string, "propset"))
1278251881Speter        action->action = ACTION_PROPSET;
1279251881Speter      else if (! strcmp(action_string, "propsetf"))
1280251881Speter        action->action = ACTION_PROPSETF;
1281251881Speter      else if (! strcmp(action_string, "propdel"))
1282251881Speter        action->action = ACTION_PROPDEL;
1283251881Speter      else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h")
1284251881Speter               || ! strcmp(action_string, "help"))
1285251881Speter        usage(pool, EXIT_SUCCESS);
1286251881Speter      else
1287251881Speter        handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1288251881Speter                                       "'%s' is not an action\n",
1289251881Speter                                       action_string), pool);
1290251881Speter      if (++i == action_args->nelts)
1291251881Speter        insufficient(pool);
1292251881Speter
1293251881Speter      /* For copies, there should be a revision number next. */
1294251881Speter      if (action->action == ACTION_CP)
1295251881Speter        {
1296251881Speter          const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *);
1297251881Speter          if (strcmp(rev_str, "head") == 0)
1298251881Speter            action->rev = SVN_INVALID_REVNUM;
1299251881Speter          else if (strcmp(rev_str, "HEAD") == 0)
1300251881Speter            action->rev = SVN_INVALID_REVNUM;
1301251881Speter          else
1302251881Speter            {
1303251881Speter              char *end;
1304251881Speter
1305251881Speter              while (*rev_str == 'r')
1306251881Speter                ++rev_str;
1307251881Speter
1308251881Speter              action->rev = strtol(rev_str, &end, 0);
1309251881Speter              if (*end)
1310251881Speter                handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1311251881Speter                                               "'%s' is not a revision\n",
1312251881Speter                                               rev_str), pool);
1313251881Speter            }
1314251881Speter          if (++i == action_args->nelts)
1315251881Speter            insufficient(pool);
1316251881Speter        }
1317251881Speter      else
1318251881Speter        {
1319251881Speter          action->rev = SVN_INVALID_REVNUM;
1320251881Speter        }
1321251881Speter
1322251881Speter      /* For puts, there should be a local file next. */
1323251881Speter      if (action->action == ACTION_PUT)
1324251881Speter        {
1325251881Speter          action->path[1] =
1326251881Speter            svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
1327251881Speter                                                    const char *), pool);
1328251881Speter          if (++i == action_args->nelts)
1329251881Speter            insufficient(pool);
1330251881Speter        }
1331251881Speter
1332251881Speter      /* For propset, propsetf, and propdel, a property name (and
1333251881Speter         maybe a property value or file which contains one) comes next. */
1334251881Speter      if ((action->action == ACTION_PROPSET)
1335251881Speter          || (action->action == ACTION_PROPSETF)
1336251881Speter          || (action->action == ACTION_PROPDEL))
1337251881Speter        {
1338251881Speter          action->prop_name = APR_ARRAY_IDX(action_args, i, const char *);
1339251881Speter          if (++i == action_args->nelts)
1340251881Speter            insufficient(pool);
1341251881Speter
1342251881Speter          if (action->action == ACTION_PROPDEL)
1343251881Speter            {
1344251881Speter              action->prop_value = NULL;
1345251881Speter            }
1346251881Speter          else if (action->action == ACTION_PROPSET)
1347251881Speter            {
1348251881Speter              action->prop_value =
1349251881Speter                svn_string_create(APR_ARRAY_IDX(action_args, i,
1350251881Speter                                                const char *), pool);
1351251881Speter              if (++i == action_args->nelts)
1352251881Speter                insufficient(pool);
1353251881Speter            }
1354251881Speter          else
1355251881Speter            {
1356251881Speter              const char *propval_file =
1357251881Speter                svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
1358251881Speter                                                        const char *), pool);
1359251881Speter
1360251881Speter              if (++i == action_args->nelts)
1361251881Speter                insufficient(pool);
1362251881Speter
1363251881Speter              err = read_propvalue_file(&(action->prop_value),
1364251881Speter                                        propval_file, pool);
1365251881Speter              if (err)
1366251881Speter                handle_error(err, pool);
1367251881Speter
1368251881Speter              action->action = ACTION_PROPSET;
1369251881Speter            }
1370251881Speter
1371251881Speter          if (action->prop_value
1372251881Speter              && svn_prop_needs_translation(action->prop_name))
1373251881Speter            {
1374251881Speter              svn_string_t *translated_value;
1375251881Speter              err = svn_subst_translate_string2(&translated_value, NULL,
1376251881Speter                                                NULL, action->prop_value, NULL,
1377251881Speter                                                FALSE, pool, pool);
1378251881Speter              if (err)
1379251881Speter                handle_error(
1380251881Speter                    svn_error_quick_wrap(err,
1381251881Speter                                         "Error normalizing property value"),
1382251881Speter                    pool);
1383251881Speter              action->prop_value = translated_value;
1384251881Speter            }
1385251881Speter        }
1386251881Speter
1387251881Speter      /* How many URLs does this action expect? */
1388251881Speter      if (action->action == ACTION_RM
1389251881Speter          || action->action == ACTION_MKDIR
1390251881Speter          || action->action == ACTION_PUT
1391251881Speter          || action->action == ACTION_PROPSET
1392251881Speter          || action->action == ACTION_PROPSETF /* shouldn't see this one */
1393251881Speter          || action->action == ACTION_PROPDEL)
1394251881Speter        num_url_args = 1;
1395251881Speter      else
1396251881Speter        num_url_args = 2;
1397251881Speter
1398251881Speter      /* Parse the required number of URLs. */
1399251881Speter      for (j = 0; j < num_url_args; ++j)
1400251881Speter        {
1401251881Speter          const char *url = APR_ARRAY_IDX(action_args, i, const char *);
1402251881Speter
1403251881Speter          /* If there's a ROOT_URL, we expect URL to be a path
1404251881Speter             relative to ROOT_URL (and we build a full url from the
1405251881Speter             combination of the two).  Otherwise, it should be a full
1406251881Speter             url. */
1407251881Speter          if (! svn_path_is_url(url))
1408251881Speter            {
1409251881Speter              if (! root_url)
1410251881Speter                handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1411251881Speter                                               "'%s' is not a URL, and "
1412251881Speter                                               "--root-url (-U) not provided\n",
1413251881Speter                                               url), pool);
1414251881Speter              /* ### These relpaths are already URI-encoded. */
1415251881Speter              url = apr_pstrcat(pool, root_url, "/",
1416251881Speter                                svn_relpath_canonicalize(url, pool),
1417251881Speter                                (char *)NULL);
1418251881Speter            }
1419251881Speter          url = sanitize_url(url, pool);
1420251881Speter          action->path[j] = url;
1421251881Speter
1422251881Speter          /* The first URL arguments to 'cp', 'pd', 'ps' could be the anchor,
1423251881Speter             but the other URLs should be children of the anchor. */
1424251881Speter          if (! (action->action == ACTION_CP && j == 0)
1425251881Speter              && action->action != ACTION_PROPDEL
1426251881Speter              && action->action != ACTION_PROPSET
1427251881Speter              && action->action != ACTION_PROPSETF)
1428251881Speter            url = svn_uri_dirname(url, pool);
1429251881Speter          if (! anchor)
1430251881Speter            anchor = url;
1431251881Speter          else
1432251881Speter            anchor = svn_uri_get_longest_ancestor(anchor, url, pool);
1433251881Speter
1434251881Speter          if ((++i == action_args->nelts) && (j + 1 < num_url_args))
1435251881Speter            insufficient(pool);
1436251881Speter        }
1437251881Speter      APR_ARRAY_PUSH(actions, struct action *) = action;
1438251881Speter    }
1439251881Speter
1440251881Speter  if (! actions->nelts)
1441251881Speter    usage(pool, EXIT_FAILURE);
1442251881Speter
1443251881Speter  if ((err = execute(actions, anchor, revprops, username, password,
1444251881Speter                     config_dir, config_options, non_interactive,
1445251881Speter                     trust_server_cert, no_auth_cache, base_revision, pool)))
1446251881Speter    {
1447251881Speter      if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
1448251881Speter        err = svn_error_quick_wrap(err,
1449251881Speter                                   _("Authentication failed and interactive"
1450251881Speter                                     " prompting is disabled; see the"
1451251881Speter                                     " --force-interactive option"));
1452251881Speter      handle_error(err, pool);
1453251881Speter    }
1454251881Speter
1455251881Speter  /* Ensure that stdout is flushed, so the user will see all results. */
1456251881Speter  svn_error_clear(svn_cmdline_fflush(stdout));
1457251881Speter
1458251881Speter  svn_pool_destroy(pool);
1459251881Speter  return EXIT_SUCCESS;
1460251881Speter}
1461