1 /*
2  * svnmover.c: Concept Demo for Move Tracking and Branching
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 #include <stdio.h>
25 #include <string.h>
26 #include <assert.h>
27 
28 #include <apr_lib.h>
29 
30 #include "svn_private_config.h"
31 #include "svn_hash.h"
32 #include "svn_iter.h"
33 #include "svn_client.h"
34 #include "svn_cmdline.h"
35 #include "svn_config.h"
36 #include "svn_error.h"
37 #include "svn_path.h"
38 #include "svn_pools.h"
39 #include "svn_props.h"
40 #include "svn_string.h"
41 #include "svn_subst.h"
42 #include "svn_utf.h"
43 #include "svn_version.h"
44 #include "svnmover.h"
45 
46 #include "private/svn_cmdline_private.h"
47 #include "private/svn_subr_private.h"
48 #include "private/svn_branch_repos.h"
49 #include "private/svn_branch_nested.h"
50 #include "private/svn_branch_compat.h"
51 #include "private/svn_ra_private.h"
52 #include "private/svn_string_private.h"
53 #include "private/svn_sorts_private.h"
54 #include "private/svn_token.h"
55 #include "private/svn_client_private.h"
56 #include "private/svn_delta_private.h"
57 
58 #ifdef HAVE_LINENOISE
59 #include "linenoise/linenoise.h"
60 #endif
61 
62 /* Version compatibility check */
63 static svn_error_t *
check_lib_versions(void)64 check_lib_versions(void)
65 {
66   static const svn_version_checklist_t checklist[] =
67     {
68       { "svn_client", svn_client_version },
69       { "svn_subr",   svn_subr_version },
70       { "svn_ra",     svn_ra_version },
71       { NULL, NULL }
72     };
73   SVN_VERSION_DEFINE(my_version);
74 
75   return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
76 }
77 
78 static svn_boolean_t quiet = FALSE;
79 
80 /* UI mode: whether to display output in terms of paths or elements */
81 int the_ui_mode = UI_MODE_EIDS;
82 static const svn_token_map_t ui_mode_map[]
83   = { {"eids", UI_MODE_EIDS},
84       {"e", UI_MODE_EIDS},
85       {"paths", UI_MODE_PATHS},
86       {"p", UI_MODE_PATHS},
87       {"serial", UI_MODE_SERIAL},
88       {"s", UI_MODE_SERIAL},
89       {NULL, SVN_TOKEN_UNKNOWN} };
90 
91 #define is_branch_root_element(branch, eid) \
92   (svn_branch__root_eid(branch) == (eid))
93 
94 /* Is BRANCH1 the same branch as BRANCH2? Compare by full branch-ids; don't
95    require identical branch objects. */
96 #define BRANCH_IS_SAME_BRANCH(branch1, branch2, scratch_pool) \
97   (strcmp(svn_branch__get_id(branch1, scratch_pool), \
98           svn_branch__get_id(branch2, scratch_pool)) == 0)
99 
100 static svn_boolean_t use_coloured_output = FALSE;
101 
102 #ifndef WIN32
103 
104 /* Some ANSI escape codes for controlling text colour in terminal output. */
105 #define TEXT_RESET      "\x1b[0m"
106 #define TEXT_FG_BLACK   "\x1b[30m"
107 #define TEXT_FG_RED     "\x1b[31m"
108 #define TEXT_FG_GREEN   "\x1b[32m"
109 #define TEXT_FG_YELLOW  "\x1b[33m"
110 #define TEXT_FG_BLUE    "\x1b[34m"
111 #define TEXT_FG_MAGENTA "\x1b[35m"
112 #define TEXT_FG_CYAN    "\x1b[36m"
113 #define TEXT_FG_WHITE   "\x1b[37m"
114 #define TEXT_BG_BLACK   "\x1b[40m"
115 #define TEXT_BG_RED     "\x1b[41m"
116 #define TEXT_BG_GREEN   "\x1b[42m"
117 #define TEXT_BG_YELLOW  "\x1b[43m"
118 #define TEXT_BG_BLUE    "\x1b[44m"
119 #define TEXT_BG_MAGENTA "\x1b[45m"
120 #define TEXT_BG_CYAN    "\x1b[46m"
121 #define TEXT_BG_WHITE   "\x1b[47m"
122 
123 #define settext(text_attr) \
124   do { \
125     if (use_coloured_output) \
126       { fputs(text_attr, stdout); fflush(stdout); } \
127   } while (0)
128 #define settext_stderr(text_attr) \
129   do { \
130     if (use_coloured_output) \
131       { fputs(text_attr, stderr); fflush(stderr); } \
132   } while (0)
133 
134 #else
135 
136 /* To support colour on Windows, we could try:
137  *
138  * https://github.com/mattn/ansicolor-w32.c
139  *
140  * (I notice some obvious bugs in its puts/fputs implementations: the #defines
141  * point to _fprintf_w32 instead of _fputs_w32, and puts() fails to append a
142  * newline).
143  */
144 
145 #define settext(code)
146 #define settext_stderr(code)
147 
148 #endif
149 
150 __attribute__((format(printf, 1, 2)))
151 void
svnmover_notify(const char * fmt,...)152 svnmover_notify(const char *fmt,
153                 ...)
154 {
155   va_list ap;
156 
157   settext(TEXT_FG_GREEN);
158   va_start(ap, fmt);
159   vprintf(fmt, ap);
160   va_end(ap);
161   settext(TEXT_RESET);
162   printf("\n");
163 }
164 
165 __attribute__((format(printf, 1, 2)))
166 void
svnmover_notify_v(const char * fmt,...)167 svnmover_notify_v(const char *fmt,
168                   ...)
169 {
170   va_list ap;
171 
172   if (! quiet)
173     {
174       settext(TEXT_FG_BLUE);
175       va_start(ap, fmt);
176       vprintf(fmt, ap);
177       va_end(ap);
178       settext(TEXT_RESET);
179       printf("\n");
180     }
181 }
182 
183 #define SVN_CL__LOG_SEP_STRING \
184   "------------------------------------------------------------------------\n"
185 
186 /* ====================================================================== */
187 
188 /* Set the WC base revision of element EID to BASE_REV.
189  */
190 static void
svnmover_wc_set_base_rev(svnmover_wc_t * wc,svn_branch__state_t * branch,int eid,svn_revnum_t base_rev)191 svnmover_wc_set_base_rev(svnmover_wc_t *wc,
192                          svn_branch__state_t *branch,
193                          int eid,
194                          svn_revnum_t base_rev)
195 {
196   apr_hash_t *branch_base_revs = svn_hash_gets(wc->base_revs, branch->bid);
197   void *val = apr_pmemdup(wc->pool, &base_rev, sizeof(base_rev));
198 
199   if (!branch_base_revs)
200     {
201       branch_base_revs = apr_hash_make(wc->pool);
202       svn_hash_sets(wc->base_revs, apr_pstrdup(wc->pool, branch->bid),
203                     branch_base_revs);
204     }
205   svn_eid__hash_set(branch_base_revs, eid, val);
206 }
207 
208 /* Get the WC base revision of element EID, or SVN_INVALID_REVNUM if
209  * element EID is not present in the WC base.
210  */
211 static svn_revnum_t
svnmover_wc_get_base_rev(svnmover_wc_t * wc,svn_branch__state_t * branch,int eid,apr_pool_t * scratch_pool)212 svnmover_wc_get_base_rev(svnmover_wc_t *wc,
213                          svn_branch__state_t *branch,
214                          int eid,
215                          apr_pool_t *scratch_pool)
216 {
217   apr_hash_t *branch_base_revs = svn_hash_gets(wc->base_revs, branch->bid);
218   svn_error_t *err;
219   svn_element__content_t *element;
220   svn_revnum_t *base_rev_p;
221 
222   if (!branch_base_revs)
223     {
224       return SVN_INVALID_REVNUM;
225     }
226   err = svn_branch__state_get_element(branch, &element, eid, scratch_pool);
227   if (err || !element)
228     {
229       svn_error_clear(err);
230       return SVN_INVALID_REVNUM;
231     }
232 
233   base_rev_p = svn_eid__hash_get(branch_base_revs, eid);
234   if (! base_rev_p)
235     return SVN_INVALID_REVNUM;
236   return *base_rev_p;
237 }
238 
239 /* Set the WC base revision to BASE_REV for each element in WC base branch
240  * BRANCH, including nested branches.
241  */
242 static svn_error_t *
svnmover_wc_set_base_revs_r(svnmover_wc_t * wc,svn_branch__state_t * branch,svn_revnum_t base_rev,apr_pool_t * scratch_pool)243 svnmover_wc_set_base_revs_r(svnmover_wc_t *wc,
244                             svn_branch__state_t *branch,
245                             svn_revnum_t base_rev,
246                             apr_pool_t *scratch_pool)
247 {
248   svn_element__tree_t *elements;
249   apr_hash_index_t *hi;
250 
251   SVN_ERR(svn_branch__state_get_elements(branch, &elements, scratch_pool));
252   for (hi = apr_hash_first(scratch_pool, elements->e_map);
253        hi; hi = apr_hash_next(hi))
254     {
255       int eid = svn_eid__hash_this_key(hi);
256       svn_element__content_t *element;
257 
258       svnmover_wc_set_base_rev(wc, branch, eid, base_rev);
259 
260       /* recurse into nested branches */
261       SVN_ERR(svn_branch__state_get_element(branch, &element, eid,
262                                             scratch_pool));
263       if (element->payload->is_subbranch_root)
264         {
265           const char *subbranch_id
266             = svn_branch__id_nest(branch->bid, eid, scratch_pool);
267           svn_branch__state_t *subbranch
268             = svn_branch__txn_get_branch_by_id(branch->txn, subbranch_id,
269                                                scratch_pool);
270 
271           SVN_ERR(svnmover_wc_set_base_revs_r(wc, subbranch,
272                                               base_rev, scratch_pool));
273         }
274     }
275 
276   return SVN_NO_ERROR;
277 }
278 
279 /* Set the WC base revision to BASE_REV for each element in WC base branch
280  * BRANCH, including nested branches.
281  */
282 static svn_error_t *
svnmover_wc_set_base_revs(svnmover_wc_t * wc,svn_branch__state_t * branch,svn_revnum_t base_rev,apr_pool_t * scratch_pool)283 svnmover_wc_set_base_revs(svnmover_wc_t *wc,
284                           svn_branch__state_t *branch,
285                           svn_revnum_t base_rev,
286                           apr_pool_t *scratch_pool)
287 {
288   wc->base_revs = apr_hash_make(wc->pool);
289   SVN_ERR(svnmover_wc_set_base_revs_r(wc, branch, base_rev, scratch_pool));
290   return SVN_NO_ERROR;
291 }
292 
293 /* Get the lowest and highest base revision numbers in WC base branch
294  * BRANCH, including nested branches.
295  */
296 static svn_error_t *
svnmover_wc_get_base_revs_r(svnmover_wc_t * wc,svn_revnum_t * base_rev_min,svn_revnum_t * base_rev_max,svn_branch__state_t * branch,apr_pool_t * scratch_pool)297 svnmover_wc_get_base_revs_r(svnmover_wc_t *wc,
298                             svn_revnum_t *base_rev_min,
299                             svn_revnum_t *base_rev_max,
300                             svn_branch__state_t *branch,
301                             apr_pool_t *scratch_pool)
302 {
303   svn_element__tree_t *base_elements;
304   apr_hash_index_t *hi;
305 
306   SVN_ERR(svn_branch__state_get_elements(branch, &base_elements,
307                                          scratch_pool));
308 
309   for (hi = apr_hash_first(scratch_pool, base_elements->e_map);
310        hi; hi = apr_hash_next(hi))
311     {
312       int eid = svn_eid__hash_this_key(hi);
313       svn_revnum_t rev = svnmover_wc_get_base_rev(wc, branch, eid,
314                                                   scratch_pool);
315       svn_element__content_t *element;
316 
317       if (*base_rev_min == SVN_INVALID_REVNUM
318           || rev < *base_rev_min)
319         *base_rev_min = rev;
320       if (*base_rev_max == SVN_INVALID_REVNUM
321           || rev > *base_rev_max)
322         *base_rev_max = rev;
323 
324       /* recurse into nested branches */
325       SVN_ERR(svn_branch__state_get_element(branch, &element, eid,
326                                             scratch_pool));
327       if (element->payload->is_subbranch_root)
328         {
329           const char *subbranch_id
330             = svn_branch__id_nest(branch->bid, eid, scratch_pool);
331           svn_branch__state_t *subbranch
332             = svn_branch__txn_get_branch_by_id(branch->txn, subbranch_id,
333                                                scratch_pool);
334 
335           SVN_ERR(svnmover_wc_get_base_revs_r(wc, base_rev_min, base_rev_max,
336                                               subbranch, scratch_pool));
337         }
338     }
339 
340   return SVN_NO_ERROR;
341 }
342 
343 /* Get the lowest and highest base revision numbers in WC.
344  */
345 static svn_error_t *
svnmover_wc_get_base_revs(svnmover_wc_t * wc,svn_revnum_t * base_rev_min,svn_revnum_t * base_rev_max,apr_pool_t * scratch_pool)346 svnmover_wc_get_base_revs(svnmover_wc_t *wc,
347                           svn_revnum_t *base_rev_min,
348                           svn_revnum_t *base_rev_max,
349                           apr_pool_t *scratch_pool)
350 {
351   *base_rev_min = SVN_INVALID_REVNUM;
352   *base_rev_max = SVN_INVALID_REVNUM;
353   SVN_ERR(svnmover_wc_get_base_revs_r(wc, base_rev_min, base_rev_max,
354                                       wc->base->branch, scratch_pool));
355   return SVN_NO_ERROR;
356 }
357 
358 /* Update the WC to revision BASE_REVISION (SVN_INVALID_REVNUM means HEAD).
359  *
360  * Requires these fields in WC:
361  *   head_revision
362  *   repos_root_url
363  *   ra_session
364  *   pool
365  *
366  * Initializes these fields in WC:
367  *   base_revision
368  *   base_branch_id
369  *   base_branch
370  *   working_branch_id
371  *   working_branch
372  *   editor
373  *
374  * Assumes there are no changes in the WC: throws away the existing txn
375  * and starts a new one.
376  */
377 static svn_error_t *
wc_checkout(svnmover_wc_t * wc,svn_revnum_t base_revision,const char * base_branch_id,apr_pool_t * scratch_pool)378 wc_checkout(svnmover_wc_t *wc,
379             svn_revnum_t base_revision,
380             const char *base_branch_id,
381             apr_pool_t *scratch_pool)
382 {
383   const char *branch_info_dir = NULL;
384   svn_branch__compat_fetch_func_t fetch_func;
385   void *fetch_baton;
386   svn_branch__txn_t *base_txn;
387 
388   /* Validate and store the new base revision number */
389   if (! SVN_IS_VALID_REVNUM(base_revision))
390     base_revision = wc->head_revision;
391   else if (base_revision > wc->head_revision)
392     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
393                              _("No such revision %ld (HEAD is %ld)"),
394                              base_revision, wc->head_revision);
395 
396   /* Choose whether to store branching info in a local dir or in revprops.
397      (For now, just to exercise the options, we choose local files for
398      RA-local and revprops for a remote repo.) */
399   if (strncmp(wc->repos_root_url, "file://", 7) == 0)
400     {
401       const char *repos_dir;
402 
403       SVN_ERR(svn_uri_get_dirent_from_file_url(&repos_dir, wc->repos_root_url,
404                                                scratch_pool));
405       branch_info_dir = svn_dirent_join(repos_dir, "branch-info", scratch_pool);
406     }
407 
408   /* Get a mutable transaction based on that rev. (This implementation
409      re-reads all the move-tracking data from the repository.) */
410   SVN_ERR(svn_ra_load_branching_state(&wc->edit_txn,
411                                       &fetch_func, &fetch_baton,
412                                       wc->ra_session, branch_info_dir,
413                                       base_revision,
414                                       wc->pool, scratch_pool));
415 
416   wc->edit_txn = svn_branch__nested_txn_create(wc->edit_txn, wc->pool);
417 
418   /* Store the WC base state */
419   base_txn = svn_branch__repos_get_base_revision_root(wc->edit_txn);
420   wc->base = apr_pcalloc(wc->pool, sizeof(*wc->base));
421   wc->base->revision = base_revision;
422   wc->base->branch
423     = svn_branch__txn_get_branch_by_id(base_txn, base_branch_id, scratch_pool);
424   if (! wc->base->branch)
425     return svn_error_createf(SVN_BRANCH__ERR, NULL,
426                              "Cannot check out WC: branch %s not found in r%ld",
427                              base_branch_id, base_revision);
428   SVN_ERR(svnmover_wc_set_base_revs(wc, wc->base->branch,
429                                     base_revision, scratch_pool));
430 
431   wc->working = apr_pcalloc(wc->pool, sizeof(*wc->working));
432   wc->working->revision = SVN_INVALID_REVNUM;
433   wc->working->branch
434     = svn_branch__txn_get_branch_by_id(wc->edit_txn, base_branch_id,
435                                        scratch_pool);
436   SVN_ERR_ASSERT(wc->working->branch);
437 
438   return SVN_NO_ERROR;
439 }
440 
441 /* Create a simulated WC, in memory.
442  *
443  * Initializes these fields in WC:
444  *   head_revision
445  *   repos_root_url
446  *   ra_session
447  *   made_changes
448  *   ctx
449  *   pool
450  *
451  * BASE_REVISION is the revision to work on, or SVN_INVALID_REVNUM for HEAD.
452  */
453 static svn_error_t *
wc_create(svnmover_wc_t ** wc_p,const char * anchor_url,svn_revnum_t base_revision,const char * base_branch_id,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)454 wc_create(svnmover_wc_t **wc_p,
455           const char *anchor_url,
456           svn_revnum_t base_revision,
457           const char *base_branch_id,
458           svn_client_ctx_t *ctx,
459           apr_pool_t *result_pool,
460           apr_pool_t *scratch_pool)
461 {
462   apr_pool_t *wc_pool = svn_pool_create(result_pool);
463   svnmover_wc_t *wc = apr_pcalloc(wc_pool, sizeof(*wc));
464 
465   wc->pool = wc_pool;
466   wc->ctx = ctx;
467 
468   SVN_ERR(svn_client_open_ra_session2(&wc->ra_session, anchor_url,
469                                       NULL /* wri_abspath */, ctx,
470                                       wc_pool, scratch_pool));
471 
472   SVN_ERR(svn_ra_get_repos_root2(wc->ra_session, &wc->repos_root_url,
473                                  result_pool));
474   SVN_ERR(svn_ra_get_latest_revnum(wc->ra_session, &wc->head_revision,
475                                    scratch_pool));
476   SVN_ERR(svn_ra_reparent(wc->ra_session, wc->repos_root_url, scratch_pool));
477 
478   SVN_ERR(wc_checkout(wc, base_revision, base_branch_id, scratch_pool));
479   *wc_p = wc;
480   return SVN_NO_ERROR;
481 }
482 
483 svn_error_t *
svnmover_element_differences(apr_hash_t ** diff_p,const svn_element__tree_t * left,const svn_element__tree_t * right,apr_hash_t * elements,apr_pool_t * result_pool,apr_pool_t * scratch_pool)484 svnmover_element_differences(apr_hash_t **diff_p,
485                              const svn_element__tree_t *left,
486                              const svn_element__tree_t *right,
487                              apr_hash_t *elements,
488                              apr_pool_t *result_pool,
489                              apr_pool_t *scratch_pool)
490 {
491   apr_hash_t *diff = apr_hash_make(result_pool);
492   apr_hash_index_t *hi;
493 
494   if (! left)
495     left = svn_element__tree_create(NULL, 0 /*root_eid*/, scratch_pool);
496   if (! right)
497     right = svn_element__tree_create(NULL, 0 /*root_eid*/, scratch_pool);
498 
499   /*SVN_DBG(("element_differences(b%s r%ld, b%s r%ld, e%d)",
500            svn_branch__get_id(left->branch, scratch_pool), left->rev,
501            svn_branch__get_id(right->branch, scratch_pool), right->rev,
502            right->eid));*/
503 
504   if (!elements)
505     elements = hash_overlay(left->e_map, right->e_map);
506 
507   for (hi = apr_hash_first(scratch_pool, elements);
508        hi; hi = apr_hash_next(hi))
509     {
510       int e = svn_eid__hash_this_key(hi);
511       svn_element__content_t *element_left
512         = svn_element__tree_get(left, e);
513       svn_element__content_t *element_right
514         = svn_element__tree_get(right, e);
515 
516       if (! svn_element__content_equal(element_left, element_right,
517                                        scratch_pool))
518         {
519           svn_element__content_t **contents
520             = apr_palloc(result_pool, 2 * sizeof(void *));
521 
522           contents[0] = element_left;
523           contents[1] = element_right;
524           svn_eid__hash_set(diff, e, contents);
525         }
526     }
527 
528   *diff_p = diff;
529   return SVN_NO_ERROR;
530 }
531 
532 /*  */
533 static const char *
rev_bid_str(const svn_branch__rev_bid_t * rev_bid,apr_pool_t * result_pool)534 rev_bid_str(const svn_branch__rev_bid_t *rev_bid,
535             apr_pool_t *result_pool)
536 {
537   if (!rev_bid)
538     return "<nil>";
539   return apr_psprintf(result_pool, "r%ld.%s", rev_bid->rev, rev_bid->bid);
540 }
541 
542 /*  */
543 static const char *
list_parents(svn_branch__history_t * history,apr_pool_t * result_pool)544 list_parents(svn_branch__history_t *history,
545              apr_pool_t *result_pool)
546 {
547   const char *result = "";
548   apr_hash_index_t *hi;
549 
550   for (hi = apr_hash_first(result_pool, history->parents);
551        hi; hi = apr_hash_next(hi))
552     {
553       svn_branch__rev_bid_t *parent = apr_hash_this_val(hi);
554       const char *parent_str = rev_bid_str(parent, result_pool);
555 
556       result = apr_psprintf(result_pool, "%s%s%s",
557                             result, result[0] ? ", " : "", parent_str);
558     }
559   return result;
560 }
561 
562 /* Return a string representation of HISTORY.
563  */
564 static const char *
history_str(svn_branch__history_t * history,apr_pool_t * result_pool)565 history_str(svn_branch__history_t *history,
566             apr_pool_t *result_pool)
567 {
568   const char *result
569     = list_parents(history, result_pool);
570 
571   return apr_psprintf(result_pool, "parents={%s}", result);
572 }
573 
574 /*
575  */
576 static svn_error_t *
svn_branch__history_add_parent(svn_branch__history_t * history,svn_revnum_t rev,const char * branch_id,apr_pool_t * scratch_pool)577 svn_branch__history_add_parent(svn_branch__history_t *history,
578                                svn_revnum_t rev,
579                                const char *branch_id,
580                                apr_pool_t *scratch_pool)
581 {
582   apr_pool_t *pool = apr_hash_pool_get(history->parents);
583   svn_branch__rev_bid_t *new_parent;
584 
585   new_parent = svn_branch__rev_bid_create(rev, branch_id, pool);
586   svn_hash_sets(history->parents, apr_pstrdup(pool, branch_id), new_parent);
587   return SVN_NO_ERROR;
588 }
589 
590 /* Set *DIFFERENCE_P to some sort of indication of the difference between
591  * HISTORY1 and HISTORY2, or to null if there is no difference.
592  *
593  * Inputs may be null.
594  */
595 static svn_error_t *
history_diff(const char ** difference_p,svn_branch__history_t * history1,svn_branch__history_t * history2,apr_pool_t * result_pool,apr_pool_t * scratch_pool)596 history_diff(const char **difference_p,
597              svn_branch__history_t *history1,
598              svn_branch__history_t *history2,
599              apr_pool_t *result_pool,
600              apr_pool_t *scratch_pool)
601 {
602   apr_hash_t *combined;
603   apr_hash_index_t *hi;
604   svn_boolean_t different = FALSE;
605 
606   if (! history1)
607     history1 = svn_branch__history_create_empty(scratch_pool);
608   if (! history2)
609     history2 = svn_branch__history_create_empty(scratch_pool);
610   combined = hash_overlay(history1->parents,
611                           history2->parents);
612 
613   for (hi = apr_hash_first(scratch_pool, combined);
614        hi; hi = apr_hash_next(hi))
615     {
616       const char *bid = apr_hash_this_key(hi);
617       svn_branch__rev_bid_t *parent1 = svn_hash_gets(history1->parents, bid);
618       svn_branch__rev_bid_t *parent2 = svn_hash_gets(history2->parents, bid);
619 
620       if (!(parent1 && parent2
621             && svn_branch__rev_bid_equal(parent1, parent2)))
622         {
623           different = TRUE;
624           break;
625         }
626     }
627   if (different)
628     {
629       *difference_p = apr_psprintf(result_pool, "%s -> %s",
630                                    history_str(history1, scratch_pool),
631                                    history_str(history2, scratch_pool));
632     }
633   else
634     {
635       *difference_p = NULL;
636     }
637   return SVN_NO_ERROR;
638 }
639 
640 /* Set *IS_CHANGED to true if EDIT_TXN differs from its base txn, else to
641  * false.
642  *
643  * Notice only a difference in content: branches deleted or added, or branch
644  * contents different. Ignore any differences in branch history metadata.
645  *
646  * ### At least we must ignore the "this branch" parent changing from
647  *     old-revision to new-revision. However we should probably notice
648  *     if a merge parent is added (which means we want to make a commit
649  *     recording this merge, even if no content changed), and perhaps
650  *     other cases.
651  */
652 static svn_error_t *
txn_is_changed(svn_branch__txn_t * edit_txn,svn_boolean_t * is_changed,apr_pool_t * scratch_pool)653 txn_is_changed(svn_branch__txn_t *edit_txn,
654                svn_boolean_t *is_changed,
655                apr_pool_t *scratch_pool)
656 {
657   int i;
658   svn_branch__txn_t *base_txn
659     = svn_branch__repos_get_base_revision_root(edit_txn);
660   apr_array_header_t *edit_branches
661     = svn_branch__txn_get_branches(edit_txn, scratch_pool);
662   apr_array_header_t *base_branches
663     = svn_branch__txn_get_branches(base_txn, scratch_pool);
664 
665   *is_changed = FALSE;
666 
667   /* If any previous branch is now missing, that's a change. */
668   for (i = 0; i < base_branches->nelts; i++)
669     {
670       svn_branch__state_t *base_branch = APR_ARRAY_IDX(base_branches, i, void *);
671       svn_branch__state_t *edit_branch
672         = svn_branch__txn_get_branch_by_id(edit_txn, base_branch->bid,
673                                            scratch_pool);
674 
675       if (! edit_branch)
676         {
677           *is_changed = TRUE;
678           return SVN_NO_ERROR;
679         }
680     }
681 
682   /* If any current branch is new or changed, that's a change. */
683   for (i = 0; i < edit_branches->nelts; i++)
684     {
685       svn_branch__state_t *edit_branch = APR_ARRAY_IDX(edit_branches, i, void *);
686       svn_branch__state_t *base_branch
687         = svn_branch__txn_get_branch_by_id(base_txn, edit_branch->bid,
688                                            scratch_pool);
689       svn_element__tree_t *edit_branch_elements, *base_branch_elements;
690       apr_hash_t *diff;
691 
692       if (! base_branch)
693         {
694           *is_changed = TRUE;
695           return SVN_NO_ERROR;
696         }
697 
698 #if 0
699       /* Compare histories */
700       /* ### No, don't. Ignore any differences in branch history metadata. */
701       {
702       svn_branch__history_t *edit_branch_history;
703       svn_branch__history_t *base_branch_history;
704       const char *history_difference;
705 
706       SVN_ERR(svn_branch__state_get_history(edit_branch, &edit_branch_history,
707                                             scratch_pool));
708       SVN_ERR(svn_branch__state_get_history(base_branch, &base_branch_history,
709                                             scratch_pool));
710       SVN_ERR(history_diff(&history_difference,
711                            edit_branch_history,
712                            base_branch_history,
713                            scratch_pool, scratch_pool));
714       if (history_difference)
715         {
716           *is_changed = TRUE;
717           return SVN_NO_ERROR;
718         }
719       }
720 #endif
721 
722       /* Compare elements */
723       SVN_ERR(svn_branch__state_get_elements(edit_branch, &edit_branch_elements,
724                                              scratch_pool));
725       SVN_ERR(svn_branch__state_get_elements(base_branch, &base_branch_elements,
726                                              scratch_pool));
727       SVN_ERR(svnmover_element_differences(&diff,
728                                            edit_branch_elements,
729                                            base_branch_elements,
730                                            NULL /*all elements*/,
731                                            scratch_pool, scratch_pool));
732       if (apr_hash_count(diff))
733         {
734           *is_changed = TRUE;
735           return SVN_NO_ERROR;
736         }
737     }
738 
739   return SVN_NO_ERROR;
740 }
741 
742 /* Replay the whole-element changes between LEFT_BRANCH and RIGHT_BRANCH
743  * into EDIT_BRANCH.
744  *
745  * Replaying means, for each element E that is changed (added, modified
746  * or deleted) between left and right branches, we set element E in
747  * EDIT_BRANCH to whole value of E in RIGHT_BRANCH. This is not like
748  * merging: each change resets an element's whole value.
749  *
750  * ELEMENTS_TO_DIFF (eid -> [anything]) says which elements to diff; if
751  * null, diff all elements in the union of left & right branches.
752  *
753  * LEFT_BRANCH and/or RIGHT_BRANCH may be null which means the equivalent
754  * of an empty branch.
755  *
756  * Non-recursive: single branch only.
757  */
758 static svn_error_t *
branch_elements_replay(svn_branch__state_t * edit_branch,const svn_branch__state_t * left_branch,const svn_branch__state_t * right_branch,apr_hash_t * elements_to_diff,apr_pool_t * scratch_pool)759 branch_elements_replay(svn_branch__state_t *edit_branch,
760                        const svn_branch__state_t *left_branch,
761                        const svn_branch__state_t *right_branch,
762                        apr_hash_t *elements_to_diff,
763                        apr_pool_t *scratch_pool)
764 {
765   svn_element__tree_t *s_left = NULL, *s_right = NULL;
766   apr_hash_t *diff_left_right;
767   apr_hash_index_t *hi;
768 
769   if (left_branch)
770     SVN_ERR(svn_branch__state_get_elements(left_branch, &s_left,
771                                            scratch_pool));
772   if (right_branch)
773     SVN_ERR(svn_branch__state_get_elements(right_branch, &s_right,
774                                            scratch_pool));
775   SVN_ERR(svnmover_element_differences(&diff_left_right,
776                                        s_left, s_right,
777                                        elements_to_diff,
778                                        scratch_pool, scratch_pool));
779 
780   /* Go through the per-element differences. */
781   for (hi = apr_hash_first(scratch_pool, diff_left_right);
782        hi; hi = apr_hash_next(hi))
783     {
784       int eid = svn_eid__hash_this_key(hi);
785       svn_element__content_t **e_pair = apr_hash_this_val(hi);
786       svn_element__content_t *e0 = e_pair[0], *e1 = e_pair[1];
787 
788       SVN_ERR_ASSERT(!e0
789                      || svn_element__payload_invariants(e0->payload));
790       SVN_ERR_ASSERT(!e1
791                      || svn_element__payload_invariants(e1->payload));
792       SVN_ERR(svn_branch__state_set_element(edit_branch, eid,
793                                             e1, scratch_pool));
794     }
795 
796   return SVN_NO_ERROR;
797 }
798 
799 /*  */
800 static svn_error_t *
get_union_of_subbranches(apr_hash_t ** all_subbranches_p,svn_branch__state_t * left_branch,svn_branch__state_t * right_branch,apr_pool_t * result_pool)801 get_union_of_subbranches(apr_hash_t **all_subbranches_p,
802                          svn_branch__state_t *left_branch,
803                          svn_branch__state_t *right_branch,
804                          apr_pool_t *result_pool)
805 {
806   apr_hash_t *all_subbranches;
807   svn_branch__subtree_t *s_left = NULL;
808   svn_branch__subtree_t *s_right = NULL;
809 
810   if (left_branch)
811     SVN_ERR(svn_branch__get_subtree(left_branch, &s_left,
812                                     svn_branch__root_eid(left_branch),
813                                     result_pool));
814   if (right_branch)
815     SVN_ERR(svn_branch__get_subtree(right_branch, &s_right,
816                                     svn_branch__root_eid(right_branch),
817                                     result_pool));
818   all_subbranches
819     = (s_left && s_right) ? hash_overlay(s_left->subbranches,
820                                          s_right->subbranches)
821         : s_left ? s_left->subbranches
822         : s_right ? s_right->subbranches
823         : apr_hash_make(result_pool);
824 
825   *all_subbranches_p = all_subbranches;
826   return SVN_NO_ERROR;
827 }
828 
829 /* Replay differences between S_LEFT and S_RIGHT into EDITOR:EDIT_BRANCH.
830  *
831  * S_LEFT or S_RIGHT (but not both) may be null meaning an empty set.
832  *
833  * Recurse into subbranches.
834  */
835 static svn_error_t *
svn_branch__replay(svn_branch__txn_t * edit_txn,svn_branch__state_t * edit_branch,svn_branch__state_t * left_branch,svn_branch__state_t * right_branch,apr_pool_t * scratch_pool)836 svn_branch__replay(svn_branch__txn_t *edit_txn,
837                    svn_branch__state_t *edit_branch,
838                    svn_branch__state_t *left_branch,
839                    svn_branch__state_t *right_branch,
840                    apr_pool_t *scratch_pool)
841 {
842   assert((left_branch && right_branch)
843          ? (svn_branch__root_eid(left_branch) == svn_branch__root_eid(right_branch))
844          : (left_branch || right_branch));
845 
846   if (right_branch)
847     {
848       /* Replay this branch */
849       apr_hash_t *elements_to_diff = NULL;  /*means the union of left & right*/
850 
851       SVN_ERR(branch_elements_replay(edit_branch, left_branch, right_branch,
852                                      elements_to_diff, scratch_pool));
853     }
854   else
855     {
856       /* deleted branch LEFT */
857       /* nothing to do -- it will go away because we deleted the outer-branch
858          element where it was attached */
859     }
860 
861   /* Replay any change in history */
862   /* ### Actually, here we just set the output history to the right-hand-side
863      history if that differs from left-hand-side.
864      This doesn't seem right, in general. It's OK if we're just copying
865      a txn into a fresh txn, as for example we do during commit. */
866   {
867     svn_branch__history_t *left_history = NULL;
868     svn_branch__history_t *right_history = NULL;
869     const char *history_difference;
870 
871     if (left_branch)
872       SVN_ERR(svn_branch__state_get_history(left_branch, &left_history,
873                                             scratch_pool));
874     if (right_branch)
875       SVN_ERR(svn_branch__state_get_history(right_branch, &right_history,
876                                             scratch_pool));
877     SVN_ERR(history_diff(&history_difference, left_history, right_history,
878                          scratch_pool, scratch_pool));
879     if (history_difference)
880       {
881         SVN_ERR(svn_branch__state_set_history(edit_branch, right_history,
882                                               scratch_pool));
883       }
884   }
885 
886   /* Replay its subbranches, recursively.
887      (If we're deleting the current branch, we don't also need to
888      explicitly delete its subbranches... do we?) */
889   if (right_branch)
890     {
891       apr_hash_t *all_subbranches;
892       apr_hash_index_t *hi;
893 
894       SVN_ERR(get_union_of_subbranches(&all_subbranches,
895                                        left_branch, right_branch, scratch_pool));
896       for (hi = apr_hash_first(scratch_pool, all_subbranches);
897            hi; hi = apr_hash_next(hi))
898         {
899           int this_eid = svn_eid__hash_this_key(hi);
900           svn_branch__state_t *left_subbranch = NULL;
901           svn_branch__state_t *right_subbranch = NULL;
902           svn_branch__state_t *edit_subbranch = NULL;
903 
904           if (left_branch)
905             SVN_ERR(svn_branch__get_subbranch_at_eid(
906                       left_branch, &left_subbranch, this_eid, scratch_pool));
907           if (right_branch)
908             SVN_ERR(svn_branch__get_subbranch_at_eid(
909                       right_branch, &right_subbranch, this_eid, scratch_pool));
910           /* If the subbranch is to be edited or added, first look up the
911              corresponding edit subbranch, or, if not found, create one. */
912           if (right_subbranch)
913             {
914               const char *new_branch_id
915                 = svn_branch__id_nest(edit_branch->bid, this_eid, scratch_pool);
916 
917               SVN_ERR(svn_branch__txn_open_branch(edit_txn, &edit_subbranch,
918                                                   new_branch_id,
919                                                   svn_branch__root_eid(right_subbranch),
920                                                   NULL /*tree_ref*/,
921                                                   scratch_pool, scratch_pool));
922             }
923 
924           /* recurse */
925           if (edit_subbranch)
926             {
927               SVN_ERR(svn_branch__replay(edit_txn, edit_subbranch,
928                                         left_subbranch, right_subbranch,
929                                         scratch_pool));
930             }
931         }
932     }
933 
934   return SVN_NO_ERROR;
935 }
936 
937 /* Replay differences between LEFT_BRANCH and RIGHT_BRANCH into
938  * EDIT_ROOT_BRANCH.
939  * (Recurse into subbranches.)
940  */
941 static svn_error_t *
replay(svn_branch__txn_t * edit_txn,svn_branch__state_t * edit_root_branch,svn_branch__state_t * left_branch,svn_branch__state_t * right_branch,apr_pool_t * scratch_pool)942 replay(svn_branch__txn_t *edit_txn,
943        svn_branch__state_t *edit_root_branch,
944        svn_branch__state_t *left_branch,
945        svn_branch__state_t *right_branch,
946        apr_pool_t *scratch_pool)
947 {
948   SVN_ERR_ASSERT(left_branch || right_branch);
949 
950   SVN_ERR(svn_branch__replay(edit_txn, edit_root_branch,
951                             left_branch, right_branch, scratch_pool));
952   return SVN_NO_ERROR;
953 }
954 
955 static svn_error_t *
956 commit_callback(const svn_commit_info_t *commit_info,
957                 void *baton,
958                 apr_pool_t *pool);
959 
960 /* Baton for commit_callback(). */
961 typedef struct commit_callback_baton_t
962 {
963   svn_branch__txn_t *edit_txn;
964   const char *wc_base_branch_id;
965   const char *wc_commit_branch_id;
966 
967   /* just-committed revision */
968   svn_revnum_t revision;
969 } commit_callback_baton_t;
970 
971 static svn_error_t *
972 display_diff_of_commit(const commit_callback_baton_t *ccbb,
973                        apr_pool_t *scratch_pool);
974 
975 static svn_error_t *
976 do_topbranch(svn_branch__state_t **new_branch_p,
977              svn_branch__txn_t *txn,
978              svn_branch__rev_bid_eid_t *from,
979              apr_pool_t *result_pool,
980              apr_pool_t *scratch_pool);
981 
982 /* Allocate the same number of new EIDs in NEW_TXN as are already
983  * allocated in OLD_TXN.
984  */
985 static svn_error_t *
allocate_eids(svn_branch__txn_t * new_txn,const svn_branch__txn_t * old_txn,apr_pool_t * scratch_pool)986 allocate_eids(svn_branch__txn_t *new_txn,
987               const svn_branch__txn_t *old_txn,
988               apr_pool_t *scratch_pool)
989 {
990   int num_new_eids;
991   int i;
992 
993   SVN_ERR(svn_branch__txn_get_num_new_eids(old_txn, &num_new_eids,
994                                            scratch_pool));
995   for (i = 0; i < num_new_eids; i++)
996     {
997       SVN_ERR(svn_branch__txn_new_eid(new_txn, NULL, scratch_pool));
998     }
999 
1000   return SVN_NO_ERROR;
1001 }
1002 
1003 /* Update the EIDs, given that a commit has translated all new EIDs
1004  * (negative numbers) to regular EIDs (positive numbers).
1005  *
1006  * ### TODO: This will need to take and use a new-EID-translation rule
1007  *     that must be returned by the commit, as we must not guess (as we
1008  *     presently do) what translation the server performed. This guess
1009  *     will fail once the server does rebasing on commit.
1010  */
1011 static svn_error_t *
update_wc_eids(svnmover_wc_t * wc,apr_pool_t * scratch_pool)1012 update_wc_eids(svnmover_wc_t *wc,
1013                apr_pool_t *scratch_pool)
1014 {
1015   SVN_ERR(allocate_eids(wc->base->branch->txn, wc->working->branch->txn,
1016                         scratch_pool));
1017   SVN_ERR(svn_branch__txn_finalize_eids(wc->base->branch->txn, scratch_pool));
1018   SVN_ERR(svn_branch__txn_finalize_eids(wc->working->branch->txn, scratch_pool));
1019   return SVN_NO_ERROR;
1020 }
1021 
1022 /* Update the WC base value of each committed element to match the
1023  * corresponding WC working element value.
1024  * Update the WC base revision for each committed element to NEW_REV.
1025  *
1026  * The committed elements are determined by diffing base against working.
1027  * ### TODO: When we allow committing a subset of the WC, we'll need to
1028  *     pass in a list of the committed elements.
1029  *
1030  * BASE_BRANCH and/or WORK_BRANCH may be null.
1031  */
1032 static svn_error_t *
update_wc_base_r(svnmover_wc_t * wc,svn_branch__state_t * base_branch,svn_branch__state_t * work_branch,svn_revnum_t new_rev,apr_pool_t * scratch_pool)1033 update_wc_base_r(svnmover_wc_t *wc,
1034                  svn_branch__state_t *base_branch,
1035                  svn_branch__state_t *work_branch,
1036                  svn_revnum_t new_rev,
1037                  apr_pool_t *scratch_pool)
1038 {
1039   svn_element__tree_t *base_elements = NULL, *working_elements = NULL;
1040   apr_hash_t *committed_elements;
1041   apr_hash_index_t *hi;
1042 
1043   if (base_branch)
1044     SVN_ERR(svn_branch__state_get_elements(base_branch, &base_elements,
1045                                            scratch_pool));
1046   if (work_branch)
1047     SVN_ERR(svn_branch__state_get_elements(work_branch, &working_elements,
1048                                            scratch_pool));
1049   SVN_ERR(svnmover_element_differences(&committed_elements,
1050                                        base_elements, working_elements,
1051                                        NULL /*all elements*/,
1052                                        scratch_pool, scratch_pool));
1053 
1054   for (hi = apr_hash_first(scratch_pool, committed_elements);
1055        hi; hi = apr_hash_next(hi))
1056     {
1057       int eid = svn_eid__hash_this_key(hi);
1058       svn_element__content_t *content = NULL;
1059 
1060       if (work_branch)
1061         SVN_ERR(svn_branch__state_get_element(work_branch, &content,
1062                                               eid, scratch_pool));
1063       SVN_ERR(svn_branch__state_set_element(base_branch, eid,
1064                                             content, scratch_pool));
1065       svnmover_wc_set_base_rev(wc, base_branch, eid, new_rev);
1066 
1067       /* recurse into nested branches that exist in working */
1068       if (content && content->payload->is_subbranch_root)
1069         {
1070           svn_branch__state_t *base_subbranch = NULL;
1071           svn_branch__state_t *work_subbranch = NULL;
1072 
1073           if (base_branch)
1074             {
1075               base_subbranch
1076                 = svn_branch__txn_get_branch_by_id(
1077                     base_branch->txn,
1078                     svn_branch__id_nest(base_branch->bid, eid, scratch_pool),
1079                     scratch_pool);
1080             }
1081           if (work_branch)
1082             {
1083               work_subbranch
1084                 = svn_branch__txn_get_branch_by_id(
1085                     work_branch->txn,
1086                     svn_branch__id_nest(work_branch->bid, eid, scratch_pool),
1087                     scratch_pool);
1088             }
1089           if (work_subbranch && !base_subbranch)
1090             {
1091               const char *new_branch_id
1092                 = svn_branch__id_nest(base_branch->bid, eid, scratch_pool);
1093               svn_branch__history_t *history;
1094 
1095               SVN_ERR(svn_branch__txn_open_branch(base_branch->txn,
1096                                                   &base_subbranch,
1097                                                   new_branch_id,
1098                                                   svn_branch__root_eid(work_subbranch),
1099                                                   NULL /*tree_ref*/,
1100                                                   scratch_pool, scratch_pool));
1101               SVN_ERR(svn_branch__state_get_history(
1102                         work_subbranch, &history, scratch_pool));
1103               SVN_ERR(svn_branch__state_set_history(
1104                         base_subbranch, history, scratch_pool));
1105             }
1106           SVN_ERR(update_wc_base_r(wc, base_subbranch, work_subbranch,
1107                                    new_rev, scratch_pool));
1108         }
1109     }
1110 
1111   return SVN_NO_ERROR;
1112 }
1113 
1114 /* Update the WC base value of each committed element to match the
1115  * corresponding WC working element value.
1116  * Update the WC base revision for each committed element to NEW_REV.
1117  *
1118  * The committed elements are determined by diffing base against working.
1119  * ### TODO: When we allow committing a subset of the WC, we'll need to
1120  *     pass in a list of the committed elements.
1121  *
1122  * ### This should be equivalent to 'replay(base, base, working)'. Use that
1123  *     instead.
1124  */
1125 static svn_error_t *
update_wc_base(svnmover_wc_t * wc,svn_revnum_t new_rev,apr_pool_t * scratch_pool)1126 update_wc_base(svnmover_wc_t *wc,
1127                svn_revnum_t new_rev,
1128                apr_pool_t *scratch_pool)
1129 {
1130   svn_branch__state_t *base_branch = wc->base->branch;
1131   svn_branch__state_t *work_branch = wc->working->branch;
1132   SVN_ERR(update_wc_base_r(wc, base_branch, work_branch,
1133                            new_rev, scratch_pool));
1134   return SVN_NO_ERROR;
1135 }
1136 
1137 /* Commit the changes from WC into the repository.
1138  *
1139  * Open a new commit txn to the repo. Replay the changes from WC into it.
1140  * Update the WC base for the committed elements.
1141  *
1142  * Set WC->head_revision and *NEW_REV_P to the committed revision number.
1143  *
1144  * If there are no changes to commit, set *NEW_REV_P to SVN_INVALID_REVNUM
1145  * and do not make a commit and do not change WC->head_revision.
1146  *
1147  * NEW_REV_P may be null if not wanted.
1148  */
1149 static svn_error_t *
wc_commit(svn_revnum_t * new_rev_p,svnmover_wc_t * wc,apr_hash_t * revprops,apr_pool_t * scratch_pool)1150 wc_commit(svn_revnum_t *new_rev_p,
1151           svnmover_wc_t *wc,
1152           apr_hash_t *revprops,
1153           apr_pool_t *scratch_pool)
1154 {
1155   const char *branch_info_dir = NULL;
1156   svn_branch__txn_t *commit_txn;
1157   commit_callback_baton_t ccbb;
1158   svn_boolean_t change_detected;
1159   const char *edit_root_branch_id;
1160   svn_branch__state_t *edit_root_branch;
1161 
1162   SVN_ERR(txn_is_changed(wc->working->branch->txn, &change_detected,
1163                          scratch_pool));
1164   if (! change_detected)
1165     {
1166       wc->list_of_commands = NULL;
1167       if (new_rev_p)
1168         *new_rev_p = SVN_INVALID_REVNUM;
1169       return SVN_NO_ERROR;
1170     }
1171 
1172   /* If no log msg provided, use the list of commands */
1173   if (! svn_hash_gets(revprops, SVN_PROP_REVISION_LOG) && wc->list_of_commands)
1174     {
1175       /* Avoid modifying the passed-in revprops hash */
1176       revprops = apr_hash_copy(scratch_pool, revprops);
1177 
1178       svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
1179                     svn_string_create(wc->list_of_commands, scratch_pool));
1180     }
1181 
1182   /* Choose whether to store branching info in a local dir or in revprops.
1183      (For now, just to exercise the options, we choose local files for
1184      RA-local and revprops for a remote repo.) */
1185   if (strncmp(wc->repos_root_url, "file://", 7) == 0)
1186     {
1187       const char *repos_dir;
1188 
1189       SVN_ERR(svn_uri_get_dirent_from_file_url(&repos_dir, wc->repos_root_url,
1190                                                scratch_pool));
1191       branch_info_dir = svn_dirent_join(repos_dir, "branch-info", scratch_pool);
1192     }
1193 
1194   /* Start a new editor for the commit. */
1195   SVN_ERR(svn_ra_get_commit_txn(wc->ra_session,
1196                                 &commit_txn,
1197                                 revprops,
1198                                 commit_callback, &ccbb,
1199                                 NULL /*lock_tokens*/, FALSE /*keep_locks*/,
1200                                 branch_info_dir,
1201                                 scratch_pool));
1202   /*SVN_ERR(svn_branch__txn_get_debug(&wc->edit_txn, wc->edit_txn, scratch_pool));*/
1203 
1204   edit_root_branch_id = wc->working->branch->bid;
1205   edit_root_branch = svn_branch__txn_get_branch_by_id(
1206                        commit_txn, wc->working->branch->bid, scratch_pool);
1207 
1208   /* We might be creating a new top-level branch in this commit. That is the
1209      only case in which the working branch will not be found in EDIT_TXN.
1210      (Creating any other branch can only be done inside a checkout of a
1211      parent branch.) So, maybe create a new top-level branch. */
1212   if (! edit_root_branch)
1213     {
1214       /* Create a new top-level branch in the edited state. (It will have
1215          an independent new top-level branch number.) */
1216       svn_branch__rev_bid_eid_t *from
1217         = svn_branch__rev_bid_eid_create(wc->base->revision,
1218                                          wc->base->branch->bid,
1219                                          svn_branch__root_eid(wc->base->branch),
1220                                          scratch_pool);
1221 
1222       SVN_ERR(do_topbranch(&edit_root_branch, commit_txn,
1223                            from, scratch_pool, scratch_pool));
1224       edit_root_branch_id = edit_root_branch->bid;
1225     }
1226   /* Allocate all the new eids we'll need in this new txn */
1227   SVN_ERR(allocate_eids(commit_txn, wc->working->branch->txn, scratch_pool));
1228   SVN_ERR(replay(commit_txn, edit_root_branch,
1229                  wc->base->branch,
1230                  wc->working->branch,
1231                  scratch_pool));
1232 
1233   ccbb.edit_txn = commit_txn;
1234   ccbb.wc_base_branch_id = wc->base->branch->bid;
1235   ccbb.wc_commit_branch_id = edit_root_branch_id;
1236 
1237   SVN_ERR(svn_branch__txn_complete(commit_txn, scratch_pool));
1238   SVN_ERR(update_wc_eids(wc, scratch_pool));
1239   SVN_ERR(update_wc_base(wc, ccbb.revision, scratch_pool));
1240   SVN_ERR(display_diff_of_commit(&ccbb, scratch_pool));
1241 
1242   wc->head_revision = ccbb.revision;
1243   if (new_rev_p)
1244     *new_rev_p = ccbb.revision;
1245 
1246   wc->list_of_commands = NULL;
1247 
1248   return SVN_NO_ERROR;
1249 }
1250 
1251 typedef enum action_code_t {
1252   ACTION_INFO_WC,
1253   ACTION_INFO,
1254   ACTION_LIST_CONFLICTS,
1255   ACTION_RESOLVED_CONFLICT,
1256   ACTION_DIFF,
1257   ACTION_LOG,
1258   ACTION_LIST_BRANCHES,
1259   ACTION_LIST_BRANCHES_R,
1260   ACTION_LS,
1261   ACTION_TBRANCH,
1262   ACTION_BRANCH,
1263   ACTION_BRANCH_INTO,
1264   ACTION_MKBRANCH,
1265   ACTION_MERGE3,
1266   ACTION_AUTO_MERGE,
1267   ACTION_MV,
1268   ACTION_MKDIR,
1269   ACTION_PUT_FILE,
1270   ACTION_CAT,
1271   ACTION_CP,
1272   ACTION_RM,
1273   ACTION_CP_RM,
1274   ACTION_BR_RM,
1275   ACTION_BR_INTO_RM,
1276   ACTION_COMMIT,
1277   ACTION_UPDATE,
1278   ACTION_SWITCH,
1279   ACTION_STATUS,
1280   ACTION_REVERT,
1281   ACTION_MIGRATE
1282 } action_code_t;
1283 
1284 typedef struct action_defn_t {
1285   enum action_code_t code;
1286   const char *name;
1287   int num_args;
1288   const char *args_help;
1289   const char *help;
1290 } action_defn_t;
1291 
1292 #define NL "\n                           "
1293 static const action_defn_t action_defn[] =
1294 {
1295   {ACTION_INFO_WC,          "info-wc", 0, "",
1296     "print information about the WC"},
1297   {ACTION_INFO,             "info", 1, "PATH",
1298     "show info about the element at PATH"},
1299   {ACTION_LIST_CONFLICTS,   "conflicts", 0, "",
1300     "list unresolved conflicts"},
1301   {ACTION_RESOLVED_CONFLICT,"resolved", 1, "CONFLICT_ID",
1302     "mark conflict as resolved"},
1303   {ACTION_LIST_BRANCHES,    "branches", 1, "PATH",
1304     "list all branches rooted at the same element as PATH"},
1305   {ACTION_LIST_BRANCHES_R,  "ls-br-r", 0, "",
1306     "list all branches, recursively"},
1307   {ACTION_LS,               "ls", 1, "PATH",
1308     "list elements in the branch found at PATH"},
1309   {ACTION_LOG,              "log", 2, "FROM@REV TO@REV",
1310     "show per-revision diffs between FROM and TO"},
1311   {ACTION_TBRANCH,          "tbranch", 1, "SRC",
1312     "branch the branch-root or branch-subtree at SRC" NL
1313     "to make a new top-level branch"},
1314   {ACTION_BRANCH,           "branch", 2, "SRC DST",
1315     "branch the branch-root or branch-subtree at SRC" NL
1316     "to make a new branch at DST"},
1317   {ACTION_BRANCH_INTO,      "branch-into", 2, "SRC DST",
1318     "make a branch of the existing subtree SRC appear at" NL
1319     "DST as part of the existing branch that contains DST" NL
1320     "(like merging the creation of SRC to DST)"},
1321   {ACTION_MKBRANCH,         "mkbranch", 1, "ROOT",
1322     "make a directory that's the root of a new subbranch"},
1323   {ACTION_DIFF,             "diff", 2, "LEFT@REV RIGHT@REV",
1324     "show differences from subtree LEFT to subtree RIGHT"},
1325   {ACTION_MERGE3,           "merge", 3, "FROM TO YCA@REV",
1326     "3-way merge YCA->FROM into TO"},
1327   {ACTION_AUTO_MERGE,       "automerge", 2, "FROM TO",
1328     "automatic merge FROM into TO"},
1329   {ACTION_CP,               "cp", 2, "REV SRC DST",
1330     "copy SRC@REV to DST"},
1331   {ACTION_MV,               "mv", 2, "SRC DST",
1332     "move SRC to DST"},
1333   {ACTION_RM,               "rm", 1, "PATH",
1334     "delete PATH"},
1335   {ACTION_CP_RM,            "copy-and-delete", 2, "SRC DST",
1336     "copy-and-delete SRC to DST"},
1337   {ACTION_BR_RM,            "branch-and-delete", 2, "SRC DST",
1338     "branch-and-delete SRC to DST"},
1339   {ACTION_BR_INTO_RM,       "branch-into-and-delete", 2, "SRC DST",
1340     "merge-and-delete SRC to DST"},
1341   {ACTION_MKDIR,            "mkdir", 1, "PATH",
1342     "create new directory PATH"},
1343   {ACTION_PUT_FILE,         "put", 2, "LOCAL_FILE PATH",
1344     "add or modify file PATH with text copied from" NL
1345     "LOCAL_FILE (use \"-\" to read from standard input)"},
1346   {ACTION_CAT,              "cat", 1, "PATH",
1347     "display text (for a file) and props (if any) of PATH"},
1348   {ACTION_COMMIT,           "commit", 0, "",
1349     "commit the changes"},
1350   {ACTION_UPDATE,           "update", 1, ".@REV",
1351     "update to revision REV, keeping local changes"},
1352   {ACTION_SWITCH,           "switch", 1, "TARGET[@REV]",
1353     "switch to another branch and/or revision, keeping local changes"},
1354   {ACTION_STATUS,           "status", 0, "",
1355     "same as 'diff .@base .'"},
1356   {ACTION_REVERT,           "revert", 0, "",
1357     "revert all uncommitted changes"},
1358   {ACTION_MIGRATE,          "migrate", 1, ".@REV",
1359     "migrate changes from non-move-tracking revision"},
1360 };
1361 
1362 typedef struct action_t {
1363   /* The original command words (const char *) by which the action was
1364      specified */
1365   apr_array_header_t *action_args;
1366 
1367   action_code_t action;
1368 
1369   /* argument revisions */
1370   svn_opt_revision_t rev_spec[3];
1371 
1372   const char *branch_id[3];
1373 
1374   /* argument paths */
1375   const char *relpath[3];
1376 } action_t;
1377 
1378 /* ====================================================================== */
1379 
1380 /* Find the deepest branch in the repository of which REVNUM:BRANCH_ID:RELPATH
1381  * is either the root element or a normal, non-sub-branch element.
1382  *
1383  * RELPATH is a repository-relative path. REVNUM is a revision number, or
1384  * SVN_INVALID_REVNUM meaning the current txn.
1385  *
1386  * Return the location of the element in that branch, or with
1387  * EID=-1 if no element exists there.
1388  *
1389  * If BRANCH_ID is null, the default is the WC base branch when REVNUM is
1390  * specified, and the WC working branch when REVNUM is SVN_INVALID_REVNUM.
1391  *
1392  * Return an error if branch BRANCH_ID does not exist in r<REVNUM>; otherwise,
1393  * the result will never be NULL, as every path is within at least the root
1394  * branch.
1395  */
1396 static svn_error_t *
find_el_rev_by_rrpath_rev(svn_branch__el_rev_id_t ** el_rev_p,svnmover_wc_t * wc,const svn_opt_revision_t * rev_spec,const char * branch_id,const char * relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1397 find_el_rev_by_rrpath_rev(svn_branch__el_rev_id_t **el_rev_p,
1398                           svnmover_wc_t *wc,
1399                           const svn_opt_revision_t *rev_spec,
1400                           const char *branch_id,
1401                           const char *relpath,
1402                           apr_pool_t *result_pool,
1403                           apr_pool_t *scratch_pool)
1404 {
1405   if (rev_spec->kind == svn_opt_revision_number
1406       || rev_spec->kind == svn_opt_revision_head)
1407     {
1408       svn_revnum_t revnum
1409         = (rev_spec->kind == svn_opt_revision_number)
1410            ? rev_spec->value.number : wc->head_revision;
1411       const svn_branch__repos_t *repos = wc->working->branch->txn->repos;
1412 
1413       if (! branch_id)
1414         branch_id = wc->base->branch->bid;
1415       SVN_ERR(svn_branch__repos_find_el_rev_by_path_rev(el_rev_p, repos,
1416                                                         revnum,
1417                                                         branch_id,
1418                                                         relpath,
1419                                                         result_pool,
1420                                                         scratch_pool));
1421     }
1422   else if (rev_spec->kind == svn_opt_revision_unspecified
1423            || rev_spec->kind == svn_opt_revision_working
1424            || rev_spec->kind == svn_opt_revision_base
1425            || rev_spec->kind == svn_opt_revision_committed)
1426     {
1427       svn_branch__state_t *branch
1428         = branch_id ? svn_branch__txn_get_branch_by_id(
1429                         wc->working->branch->txn, branch_id, scratch_pool)
1430                     : wc->working->branch;
1431       svn_branch__el_rev_id_t *el_rev = apr_palloc(result_pool, sizeof(*el_rev));
1432 
1433       if (! branch)
1434         return svn_error_createf(SVN_BRANCH__ERR, NULL,
1435                                  _("Branch %s not found in working state"),
1436                                  branch_id);
1437       SVN_ERR(svn_branch__find_nested_branch_element_by_relpath(
1438         &el_rev->branch, &el_rev->eid,
1439         branch, relpath, scratch_pool));
1440       if (rev_spec->kind == svn_opt_revision_unspecified
1441           || rev_spec->kind == svn_opt_revision_working)
1442         {
1443           el_rev->rev = SVN_INVALID_REVNUM;
1444         }
1445       else
1446         {
1447           el_rev->rev = svnmover_wc_get_base_rev(wc, el_rev->branch,
1448                                                  el_rev->eid, scratch_pool);
1449         }
1450       *el_rev_p = el_rev;
1451     }
1452   else
1453     {
1454       return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1455                                "'%s@...': revision specifier "
1456                                "must be a number or 'head', 'base' "
1457                                "or 'committed'",
1458                                relpath);
1459     }
1460   SVN_ERR_ASSERT(*el_rev_p);
1461   return SVN_NO_ERROR;
1462 }
1463 
1464 /* Return a string suitable for appending to a displayed element name or
1465  * element id to indicate that it is a subbranch root element for SUBBRANCH.
1466  * Return "" if SUBBRANCH is null.
1467  */
1468 static const char *
branch_str(svn_branch__state_t * subbranch,apr_pool_t * result_pool)1469 branch_str(svn_branch__state_t *subbranch,
1470            apr_pool_t *result_pool)
1471 {
1472   if (subbranch)
1473     return apr_psprintf(result_pool,
1474                       " (branch %s)",
1475                       svn_branch__get_id(subbranch, result_pool));
1476   return "";
1477 }
1478 
1479 /* Return a string suitable for appending to a displayed element name or
1480  * element id to indicate that BRANCH:EID is a subbranch root element.
1481  * Return "" if the element is not a subbranch root element.
1482  */
1483 static const char *
subbranch_str(svn_branch__state_t * branch,int eid,apr_pool_t * result_pool)1484 subbranch_str(svn_branch__state_t *branch,
1485               int eid,
1486               apr_pool_t *result_pool)
1487 {
1488   svn_branch__state_t *subbranch;
1489 
1490   svn_error_clear(svn_branch__get_subbranch_at_eid(branch, &subbranch,
1491                                                    eid, result_pool));
1492   return branch_str(subbranch, result_pool);
1493 }
1494 
1495 /*  */
1496 static const char *
subtree_subbranch_str(svn_branch__subtree_t * subtree,const char * bid,int eid,apr_pool_t * result_pool)1497 subtree_subbranch_str(svn_branch__subtree_t *subtree,
1498                       const char *bid,
1499                       int eid,
1500                       apr_pool_t *result_pool)
1501 {
1502   svn_branch__subtree_t *subbranch
1503     = svn_branch__subtree_get_subbranch_at_eid(subtree, eid, result_pool);
1504 
1505   if (subbranch)
1506     return apr_psprintf(result_pool,
1507                         " (branch %s)",
1508                         svn_branch__id_nest(bid, eid, result_pool));
1509   return "";
1510 }
1511 
1512 /*  */
1513 static const char *
el_rev_id_to_path(svn_branch__el_rev_id_t * el_rev,apr_pool_t * result_pool)1514 el_rev_id_to_path(svn_branch__el_rev_id_t *el_rev,
1515                   apr_pool_t *result_pool)
1516 {
1517   const char *path
1518     = svn_branch__get_rrpath_by_eid(el_rev->branch, el_rev->eid, result_pool);
1519 
1520   return path;
1521 }
1522 
1523 /*  */
1524 static const char *
branch_peid_name_to_path(svn_branch__state_t * to_branch,int to_parent_eid,const char * to_name,apr_pool_t * result_pool)1525 branch_peid_name_to_path(svn_branch__state_t *to_branch,
1526                          int to_parent_eid,
1527                          const char *to_name,
1528                          apr_pool_t *result_pool)
1529 {
1530   const char *path
1531     = svn_relpath_join(svn_branch__get_rrpath_by_eid(to_branch, to_parent_eid,
1532                                                      result_pool),
1533                        to_name, result_pool);
1534 
1535   return path;
1536 }
1537 
1538 /* */
1539 static int
sort_compare_eid_mappings_by_path(const svn_sort__item_t * a,const svn_sort__item_t * b)1540 sort_compare_eid_mappings_by_path(const svn_sort__item_t *a,
1541                                   const svn_sort__item_t *b)
1542 {
1543   const char *astr = a->value, *bstr = b->value;
1544 
1545   return svn_path_compare_paths(astr, bstr);
1546 }
1547 
1548 /* List the elements in BRANCH, in path notation.
1549  *
1550  * List only the elements for which a relpath is known -- that is, elements
1551  * whose parents exist all the way up to the branch root.
1552  */
1553 static svn_error_t *
list_branch_elements(svn_branch__state_t * branch,apr_pool_t * scratch_pool)1554 list_branch_elements(svn_branch__state_t *branch,
1555                      apr_pool_t *scratch_pool)
1556 {
1557   apr_hash_t *eid_to_path = apr_hash_make(scratch_pool);
1558   svn_element__tree_t *elements;
1559   apr_hash_index_t *hi;
1560   svn_eid__hash_iter_t *ei;
1561 
1562   SVN_ERR(svn_branch__state_get_elements(branch, &elements, scratch_pool));
1563   for (hi = apr_hash_first(scratch_pool, elements->e_map);
1564        hi; hi = apr_hash_next(hi))
1565     {
1566       int eid = svn_eid__hash_this_key(hi);
1567       const char *relpath = svn_branch__get_path_by_eid(branch, eid,
1568                                                         scratch_pool);
1569 
1570       svn_eid__hash_set(eid_to_path, eid, relpath);
1571     }
1572   for (SVN_EID__HASH_ITER_SORTED(ei, eid_to_path,
1573                                  sort_compare_eid_mappings_by_path,
1574                                  scratch_pool))
1575     {
1576       int eid = ei->eid;
1577       const char *relpath = ei->val;
1578 
1579       svnmover_notify("    %-20s%s",
1580                       relpath[0] ? relpath : ".",
1581                       subbranch_str(branch, eid, scratch_pool));
1582     }
1583 
1584   return SVN_NO_ERROR;
1585 }
1586 
1587 /*  */
1588 static int
sort_compare_items_by_eid(const svn_sort__item_t * a,const svn_sort__item_t * b)1589 sort_compare_items_by_eid(const svn_sort__item_t *a,
1590                           const svn_sort__item_t *b)
1591 {
1592   int eid_a = *(const int *)a->key;
1593   int eid_b = *(const int *)b->key;
1594 
1595   return eid_a - eid_b;
1596 }
1597 
1598 static const char *
peid_name(const svn_element__content_t * element,apr_pool_t * scratch_pool)1599 peid_name(const svn_element__content_t *element,
1600           apr_pool_t *scratch_pool)
1601 {
1602   if (element->parent_eid == -1)
1603     return apr_psprintf(scratch_pool, "%3s %-10s", "", ".");
1604 
1605   return apr_psprintf(scratch_pool, "%3d/%-10s",
1606                       element->parent_eid, element->name);
1607 }
1608 
1609 static const char elements_by_eid_header[]
1610   = "    eid  parent-eid/name\n"
1611     "    ---  ----------/----";
1612 
1613 /* List all elements in branch BRANCH, in element notation.
1614  */
1615 static svn_error_t *
list_branch_elements_by_eid(svn_branch__state_t * branch,apr_pool_t * scratch_pool)1616 list_branch_elements_by_eid(svn_branch__state_t *branch,
1617                             apr_pool_t *scratch_pool)
1618 {
1619   svn_element__tree_t *elements;
1620   svn_eid__hash_iter_t *ei;
1621 
1622   svnmover_notify_v("%s", elements_by_eid_header);
1623   SVN_ERR(svn_branch__state_get_elements(branch, &elements, scratch_pool));
1624   for (SVN_EID__HASH_ITER_SORTED_BY_EID(ei, elements->e_map, scratch_pool))
1625     {
1626       int eid = ei->eid;
1627       svn_element__content_t *element = ei->val;
1628 
1629       if (element)
1630         {
1631           svnmover_notify("    e%-3d %21s%s",
1632                           eid,
1633                           peid_name(element, scratch_pool),
1634                           subbranch_str(branch, eid, scratch_pool));
1635         }
1636     }
1637 
1638   return SVN_NO_ERROR;
1639 }
1640 
1641 /*  */
1642 static const char *
branch_id_header_str(const char * prefix,apr_pool_t * result_pool)1643 branch_id_header_str(const char *prefix,
1644                      apr_pool_t *result_pool)
1645 {
1646   if (the_ui_mode == UI_MODE_PATHS)
1647     {
1648       return apr_psprintf(result_pool,
1649                           "%sbranch-id  root-path\n"
1650                           "%s---------  ---------",
1651                           prefix, prefix);
1652     }
1653   else
1654     {
1655       return apr_psprintf(result_pool,
1656                           "%sbranch-id  branch-name  root-eid\n"
1657                           "%s---------  -----------  --------",
1658                           prefix, prefix);
1659     }
1660 }
1661 
1662 /* Show the id and path or root-eid of BRANCH.
1663  */
1664 static const char *
branch_id_str(svn_branch__state_t * branch,apr_pool_t * result_pool)1665 branch_id_str(svn_branch__state_t *branch,
1666               apr_pool_t *result_pool)
1667 {
1668   apr_pool_t *scratch_pool = result_pool;
1669 
1670   if (the_ui_mode == UI_MODE_PATHS)
1671     {
1672       return apr_psprintf(result_pool, "%-10s /%s",
1673                           svn_branch__get_id(branch, scratch_pool),
1674                           svn_branch__get_root_rrpath(branch, scratch_pool));
1675     }
1676   else
1677     {
1678       svn_element__content_t *outer_el = NULL;
1679       svn_branch__state_t *outer_branch;
1680       int outer_eid;
1681 
1682       svn_branch__get_outer_branch_and_eid(&outer_branch, &outer_eid,
1683                                            branch, scratch_pool);
1684 
1685       if (outer_branch)
1686         svn_error_clear(svn_branch__state_get_element(outer_branch, &outer_el,
1687                                                       outer_eid, scratch_pool));
1688 
1689       return apr_psprintf(result_pool, "%-10s %-12s root=e%d",
1690                           svn_branch__get_id(branch, scratch_pool),
1691                           outer_el ? outer_el->name : "/",
1692                           svn_branch__root_eid(branch));
1693     }
1694 }
1695 
1696 /* List the branch BRANCH.
1697  *
1698  * If WITH_ELEMENTS is true, also list the elements in it.
1699  */
1700 static svn_error_t *
list_branch(svn_branch__state_t * branch,svn_boolean_t with_elements,apr_pool_t * scratch_pool)1701 list_branch(svn_branch__state_t *branch,
1702             svn_boolean_t with_elements,
1703             apr_pool_t *scratch_pool)
1704 {
1705   svnmover_notify("  %s", branch_id_str(branch, scratch_pool));
1706 
1707   if (with_elements)
1708     {
1709       if (the_ui_mode == UI_MODE_PATHS)
1710         {
1711           SVN_ERR(list_branch_elements(branch, scratch_pool));
1712         }
1713       else
1714         {
1715           SVN_ERR(list_branch_elements_by_eid(branch, scratch_pool));
1716         }
1717     }
1718   return SVN_NO_ERROR;
1719 }
1720 
1721 /* List all branches rooted at EID.
1722  *
1723  * If WITH_ELEMENTS is true, also list the elements in each branch.
1724  */
1725 static svn_error_t *
list_branches(svn_branch__txn_t * txn,int eid,svn_boolean_t with_elements,apr_pool_t * scratch_pool)1726 list_branches(svn_branch__txn_t *txn,
1727               int eid,
1728               svn_boolean_t with_elements,
1729               apr_pool_t *scratch_pool)
1730 {
1731   const apr_array_header_t *branches;
1732   int i;
1733   svn_boolean_t printed_header = FALSE;
1734 
1735   svnmover_notify_v("%s", branch_id_header_str("  ", scratch_pool));
1736 
1737   branches = svn_branch__txn_get_branches(txn, scratch_pool);
1738 
1739   for (i = 0; i < branches->nelts; i++)
1740     {
1741       svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);
1742 
1743       if (svn_branch__root_eid(branch) != eid)
1744         continue;
1745 
1746       SVN_ERR(list_branch(branch, with_elements, scratch_pool));
1747       if (with_elements) /* separate branches by a blank line */
1748         svnmover_notify("%s", "");
1749     }
1750 
1751   for (i = 0; i < branches->nelts; i++)
1752     {
1753       svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);
1754       svn_element__content_t *element;
1755 
1756       SVN_ERR(svn_branch__state_get_element(branch, &element,
1757                                             eid, scratch_pool));
1758       if (! element
1759           || svn_branch__root_eid(branch) == eid)
1760         continue;
1761 
1762       if (! printed_header)
1763         {
1764           if (the_ui_mode == UI_MODE_PATHS)
1765             svnmover_notify_v("branches containing but not rooted at that element:");
1766           else
1767             svnmover_notify_v("branches containing but not rooted at e%d:", eid);
1768           printed_header = TRUE;
1769         }
1770       SVN_ERR(list_branch(branch, with_elements, scratch_pool));
1771       if (with_elements) /* separate branches by a blank line */
1772         svnmover_notify("%s", "");
1773     }
1774 
1775   return SVN_NO_ERROR;
1776 }
1777 
1778 /* List all branches. If WITH_ELEMENTS is true, also list the elements
1779  * in each branch.
1780  */
1781 static svn_error_t *
list_all_branches(svn_branch__txn_t * txn,svn_boolean_t with_elements,apr_pool_t * scratch_pool)1782 list_all_branches(svn_branch__txn_t *txn,
1783                   svn_boolean_t with_elements,
1784                   apr_pool_t *scratch_pool)
1785 {
1786   const apr_array_header_t *branches;
1787   int i;
1788 
1789   branches = svn_branch__txn_get_branches(txn, scratch_pool);
1790 
1791   svnmover_notify_v("branches:");
1792 
1793   for (i = 0; i < branches->nelts; i++)
1794     {
1795       svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);
1796 
1797       SVN_ERR(list_branch(branch, with_elements, scratch_pool));
1798       if (with_elements) /* separate branches by a blank line */
1799         svnmover_notify("%s", "");
1800     }
1801 
1802   return SVN_NO_ERROR;
1803 }
1804 
1805 /* Switch the WC to revision REVISION (SVN_INVALID_REVNUM means HEAD)
1806  * and branch TARGET_BRANCH.
1807  *
1808  * Merge any changes in the existing txn into the new txn.
1809  */
1810 static svn_error_t *
do_switch(svnmover_wc_t * wc,svn_revnum_t revision,svn_branch__state_t * target_branch,apr_pool_t * scratch_pool)1811 do_switch(svnmover_wc_t *wc,
1812           svn_revnum_t revision,
1813           svn_branch__state_t *target_branch,
1814           apr_pool_t *scratch_pool)
1815 {
1816   const char *target_branch_id
1817     = svn_branch__get_id(target_branch, scratch_pool);
1818   /* Keep hold of the previous WC txn */
1819   svn_branch__state_t *previous_base_br = wc->base->branch;
1820   svn_branch__state_t *previous_working_br = wc->working->branch;
1821   svn_boolean_t has_local_changes;
1822 
1823   SVN_ERR(txn_is_changed(previous_working_br->txn,
1824                          &has_local_changes, scratch_pool));
1825 
1826   /* Usually one would switch the WC to another branch (or just another
1827      revision) rooted at the same element. Switching to a branch rooted
1828      at a different element is well defined, but give a warning. */
1829   if (has_local_changes
1830       && svn_branch__root_eid(target_branch)
1831          != svn_branch__root_eid(previous_base_br))
1832     {
1833       svnmover_notify(_("Warning: you are switching from %s rooted at e%d "
1834                         "to %s rooted at e%d, a different root element, "
1835                         "while there are local changes. "),
1836              svn_branch__get_id(previous_base_br, scratch_pool),
1837              svn_branch__root_eid(previous_base_br),
1838              target_branch_id,
1839              svn_branch__root_eid(target_branch));
1840     }
1841 
1842   /* Complete the old edit drive into the 'WC' txn */
1843   SVN_ERR(svn_branch__txn_sequence_point(wc->edit_txn, scratch_pool));
1844 
1845   /* Check out a new WC, re-using the same data object */
1846   SVN_ERR(wc_checkout(wc, revision, target_branch_id, scratch_pool));
1847 
1848   if (has_local_changes)
1849     {
1850       svn_branch__el_rev_id_t *yca, *src, *tgt;
1851 
1852       /* Merge changes from the old into the new WC */
1853       yca = svn_branch__el_rev_id_create(previous_base_br,
1854                                          svn_branch__root_eid(previous_base_br),
1855                                          previous_base_br->txn->rev,
1856                                          scratch_pool);
1857       src = svn_branch__el_rev_id_create(previous_working_br,
1858                                          svn_branch__root_eid(previous_working_br),
1859                                          SVN_INVALID_REVNUM, scratch_pool);
1860       tgt = svn_branch__el_rev_id_create(wc->working->branch,
1861                                          svn_branch__root_eid(wc->working->branch),
1862                                          SVN_INVALID_REVNUM, scratch_pool);
1863       SVN_ERR(svnmover_branch_merge(wc->edit_txn, tgt->branch,
1864                                     &wc->conflicts,
1865                                     src, tgt, yca, wc->pool, scratch_pool));
1866 
1867       if (svnmover_any_conflicts(wc->conflicts))
1868         {
1869           SVN_ERR(svnmover_display_conflicts(wc->conflicts, scratch_pool));
1870         }
1871 
1872       /* ### TODO: If the merge raises conflicts, allow the user to revert
1873              to the pre-update state or resolve the conflicts. Currently
1874              this leaves the merge partially done and the pre-update state
1875              is lost. */
1876     }
1877 
1878   return SVN_NO_ERROR;
1879 }
1880 
1881 /*
1882  */
1883 static svn_error_t *
do_merge(svnmover_wc_t * wc,svn_branch__el_rev_id_t * src,svn_branch__el_rev_id_t * tgt,svn_branch__el_rev_id_t * yca,apr_pool_t * scratch_pool)1884 do_merge(svnmover_wc_t *wc,
1885          svn_branch__el_rev_id_t *src,
1886          svn_branch__el_rev_id_t *tgt,
1887          svn_branch__el_rev_id_t *yca,
1888          apr_pool_t *scratch_pool)
1889 {
1890   svn_branch__history_t *history;
1891 
1892   if (src->eid != tgt->eid || src->eid != yca->eid)
1893     {
1894       svnmover_notify(_("Warning: root elements differ in the requested merge "
1895                         "(from: e%d, to: e%d, yca: e%d)"),
1896                       src->eid, tgt->eid, yca->eid);
1897     }
1898 
1899   SVN_ERR(svnmover_branch_merge(wc->edit_txn, tgt->branch,
1900                                 &wc->conflicts,
1901                                 src, tgt, yca,
1902                                 wc->pool, scratch_pool));
1903 
1904   /* Update the history */
1905   SVN_ERR(svn_branch__state_get_history(tgt->branch, &history, scratch_pool));
1906   /* ### Assume this was a complete merge -- i.e. all changes up to YCA were
1907      previously merged, so now SRC is a new parent. */
1908   SVN_ERR(svn_branch__history_add_parent(history, src->rev, src->branch->bid,
1909                                          scratch_pool));
1910   SVN_ERR(svn_branch__state_set_history(tgt->branch, history, scratch_pool));
1911   svnmover_notify_v(_("--- recorded merge parent as: %ld.%s"),
1912                     src->rev, src->branch->bid);
1913 
1914   if (svnmover_any_conflicts(wc->conflicts))
1915     {
1916       SVN_ERR(svnmover_display_conflicts(wc->conflicts, scratch_pool));
1917     }
1918 
1919   return SVN_NO_ERROR;
1920 }
1921 
1922 /*
1923  */
1924 static svn_error_t *
do_auto_merge(svnmover_wc_t * wc,svn_branch__el_rev_id_t * src,svn_branch__el_rev_id_t * tgt,apr_pool_t * scratch_pool)1925 do_auto_merge(svnmover_wc_t *wc,
1926               svn_branch__el_rev_id_t *src,
1927               svn_branch__el_rev_id_t *tgt,
1928               apr_pool_t *scratch_pool)
1929 {
1930   svn_branch__rev_bid_t *yca;
1931 
1932   /* Find the Youngest Common Ancestor.
1933      ### TODO */
1934   yca = NULL;
1935 
1936   if (yca)
1937     {
1938       svn_branch__repos_t *repos = wc->working->branch->txn->repos;
1939       svn_branch__state_t *yca_branch;
1940       svn_branch__el_rev_id_t *_yca;
1941 
1942       SVN_ERR(svn_branch__repos_get_branch_by_id(&yca_branch, repos,
1943                                                  yca->rev, yca->bid,
1944                                                  scratch_pool));
1945       _yca = svn_branch__el_rev_id_create(yca_branch,
1946                                           svn_branch__root_eid(yca_branch),
1947                                           yca->rev, scratch_pool);
1948 
1949       SVN_ERR(do_merge(wc, src, tgt, _yca, scratch_pool));
1950     }
1951   else
1952     {
1953       return svn_error_create(SVN_BRANCH__ERR, NULL,
1954                               _("Cannot perform automatic merge: "
1955                                 "no YCA found"));
1956     }
1957 
1958   return SVN_NO_ERROR;
1959 }
1960 
1961 /* Show the difference in history metadata between BRANCH1 and BRANCH2.
1962  *
1963  * If HEADER is non-null, print *HEADER and then set *HEADER to null.
1964  *
1965  * BRANCH1 and/or BRANCH2 may be null.
1966  */
1967 static svn_error_t *
show_history_r(svn_branch__state_t * branch,const char * prefix,apr_pool_t * scratch_pool)1968 show_history_r(svn_branch__state_t *branch,
1969                const char *prefix,
1970                apr_pool_t *scratch_pool)
1971 {
1972   svn_branch__history_t *history = NULL;
1973   svn_branch__subtree_t *subtree = NULL;
1974   apr_hash_index_t *hi;
1975 
1976   if (! branch)
1977     return SVN_NO_ERROR;
1978 
1979   SVN_ERR(svn_branch__state_get_history(branch, &history, scratch_pool));
1980   svnmover_notify("%s%s: %s", prefix,
1981                   branch->bid, history_str(history, scratch_pool));
1982 
1983   /* recurse into each subbranch */
1984   SVN_ERR(svn_branch__get_subtree(branch, &subtree,
1985                                   svn_branch__root_eid(branch),
1986                                   scratch_pool));
1987    for (hi = apr_hash_first(scratch_pool, subtree->subbranches);
1988        hi; hi = apr_hash_next(hi))
1989     {
1990       int e = svn_eid__hash_this_key(hi);
1991       svn_branch__state_t *subbranch = NULL;
1992 
1993       SVN_ERR(svn_branch__get_subbranch_at_eid(branch, &subbranch, e,
1994                                                scratch_pool));
1995       if (subbranch)
1996         {
1997           SVN_ERR(show_history_r(subbranch, prefix, scratch_pool));
1998         }
1999     }
2000   return SVN_NO_ERROR;
2001 }
2002 
2003 /*  */
2004 typedef struct diff_item_t
2005 {
2006   int eid;
2007   svn_element__content_t *e0, *e1;
2008   const char *relpath0, *relpath1;
2009   svn_boolean_t modified, reparented, renamed;
2010 } diff_item_t;
2011 
2012 /* Return differences between branch subtrees S_LEFT and S_RIGHT.
2013  * Diff the union of S_LEFT's and S_RIGHT's elements.
2014  *
2015  * Set *DIFF_CHANGES to a hash of (eid -> diff_item_t).
2016  *
2017  * ### This requires 'subtrees' only in order to produce the 'relpath'
2018  *     fields in the output. Other than that, it would work with arbitrary
2019  *     sets of elements.
2020  */
2021 static svn_error_t *
subtree_diff(apr_hash_t ** diff_changes,svn_branch__subtree_t * s_left,svn_branch__subtree_t * s_right,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2022 subtree_diff(apr_hash_t **diff_changes,
2023              svn_branch__subtree_t *s_left,
2024              svn_branch__subtree_t *s_right,
2025              apr_pool_t *result_pool,
2026              apr_pool_t *scratch_pool)
2027 {
2028   apr_hash_t *diff_left_right;
2029   apr_hash_index_t *hi;
2030 
2031   *diff_changes = apr_hash_make(result_pool);
2032 
2033   SVN_ERR(svnmover_element_differences(&diff_left_right,
2034                                        s_left->tree, s_right->tree,
2035                                        NULL /*union of s_left & s_right*/,
2036                                        result_pool, scratch_pool));
2037 
2038   for (hi = apr_hash_first(scratch_pool, diff_left_right);
2039        hi; hi = apr_hash_next(hi))
2040     {
2041       int eid = svn_eid__hash_this_key(hi);
2042       svn_element__content_t **e_pair = apr_hash_this_val(hi);
2043       svn_element__content_t *e0 = e_pair[0], *e1 = e_pair[1];
2044 
2045       if (e0 || e1)
2046         {
2047           diff_item_t *item = apr_palloc(result_pool, sizeof(*item));
2048 
2049           item->eid = eid;
2050           item->e0 = e0;
2051           item->e1 = e1;
2052           item->relpath0 = e0 ? svn_element__tree_get_path_by_eid(
2053                                   s_left->tree, eid, result_pool) : NULL;
2054           item->relpath1 = e1 ? svn_element__tree_get_path_by_eid(
2055                                   s_right->tree, eid, result_pool) : NULL;
2056           item->reparented = (e0 && e1 && e0->parent_eid != e1->parent_eid);
2057           item->renamed = (e0 && e1 && strcmp(e0->name, e1->name) != 0);
2058 
2059           svn_eid__hash_set(*diff_changes, eid, item);
2060         }
2061     }
2062 
2063   return SVN_NO_ERROR;
2064 }
2065 
2066 /* Find the relative order of diff items A and B, according to the
2067  * "major path" of each. The major path means its right-hand relpath, if
2068  * it exists on the right-hand side of the diff, else its left-hand relpath.
2069  *
2070  * Return negative/zero/positive when A sorts before/equal-to/after B.
2071  */
2072 static int
diff_ordering_major_paths(const struct svn_sort__item_t * a,const struct svn_sort__item_t * b)2073 diff_ordering_major_paths(const struct svn_sort__item_t *a,
2074                           const struct svn_sort__item_t *b)
2075 {
2076   const diff_item_t *item_a = a->value, *item_b = b->value;
2077   int deleted_a = (item_a->e0 && ! item_a->e1);
2078   int deleted_b = (item_b->e0 && ! item_b->e1);
2079   const char *major_path_a = (item_a->e1 ? item_a->relpath1 : item_a->relpath0);
2080   const char *major_path_b = (item_b->e1 ? item_b->relpath1 : item_b->relpath0);
2081 
2082   /* Sort deleted items before all others */
2083   if (deleted_a != deleted_b)
2084     return deleted_b - deleted_a;
2085 
2086   /* Sort by path */
2087   return svn_path_compare_paths(major_path_a, major_path_b);
2088 }
2089 
2090 /* Display differences between subtrees LEFT and RIGHT, which are subtrees
2091  * of branches LEFT_BID and RIGHT_BID respectively.
2092  *
2093  * Diff the union of LEFT's and RIGHT's elements.
2094  *
2095  * Use EDITOR to fetch content when needed.
2096  *
2097  * Write a line containing HEADER before any other output, if it is not
2098  * null. Write PREFIX at the start of each line of output, including any
2099  * header line. PREFIX and HEADER should contain no end-of-line characters.
2100  *
2101  * The output refers to paths or to elements according to THE_UI_MODE.
2102  */
2103 static svn_error_t *
show_subtree_diff(svn_branch__subtree_t * left,const char * left_bid,svn_branch__subtree_t * right,const char * right_bid,const char * prefix,const char * header,apr_pool_t * scratch_pool)2104 show_subtree_diff(svn_branch__subtree_t *left,
2105                   const char *left_bid,
2106                   svn_branch__subtree_t *right,
2107                   const char *right_bid,
2108                   const char *prefix,
2109                   const char *header,
2110                   apr_pool_t *scratch_pool)
2111 {
2112   apr_hash_t *diff_changes;
2113   svn_eid__hash_iter_t *ei;
2114 
2115   SVN_ERR_ASSERT(left && left->tree->root_eid != -1
2116                  && right && right->tree->root_eid != -1);
2117 
2118   SVN_ERR(subtree_diff(&diff_changes, left, right,
2119                        scratch_pool, scratch_pool));
2120 
2121   if (header && apr_hash_count(diff_changes))
2122     svnmover_notify("%s%s", prefix, header);
2123 
2124   for (SVN_EID__HASH_ITER_SORTED(ei, diff_changes,
2125                                  (the_ui_mode == UI_MODE_EIDS)
2126                                    ? sort_compare_items_by_eid
2127                                    : diff_ordering_major_paths,
2128                                  scratch_pool))
2129     {
2130       diff_item_t *item = ei->val;
2131       svn_element__content_t *e0 = item->e0, *e1 = item->e1;
2132       char status_mod = (e0 && e1) ? 'M' : e0 ? 'D' : 'A';
2133 
2134       /* For a deleted element whose parent was also deleted, mark it is
2135          less interesting, somehow. (Or we could omit it entirely.) */
2136       if (status_mod == 'D')
2137         {
2138           diff_item_t *parent_item
2139             = svn_eid__hash_get(diff_changes, e0->parent_eid);
2140 
2141           if (parent_item && ! parent_item->e1)
2142             status_mod = 'd';
2143         }
2144 
2145       if (the_ui_mode == UI_MODE_PATHS)
2146         {
2147           const char *major_path = (e1 ? item->relpath1 : item->relpath0);
2148           const char *from = "";
2149 
2150           if (item->reparented || item->renamed)
2151             {
2152               if (! item->reparented)
2153                 from = apr_psprintf(scratch_pool,
2154                                     " (renamed from .../%s)",
2155                                     e0->name);
2156               else if (! item->renamed)
2157                 from = apr_psprintf(scratch_pool,
2158                                     " (moved from %s/...)",
2159                                     svn_relpath_dirname(item->relpath0,
2160                                                         scratch_pool));
2161               else
2162                 from = apr_psprintf(scratch_pool,
2163                                     " (moved+renamed from %s)",
2164                                     item->relpath0);
2165             }
2166           svnmover_notify("%s%c%c%c %s%s%s",
2167                           prefix,
2168                           status_mod,
2169                           item->reparented ? 'v' : ' ',
2170                           item->renamed ? 'r' : ' ',
2171                           major_path,
2172                           subtree_subbranch_str(e0 ? left : right,
2173                                                 e0 ? left_bid : right_bid,
2174                                                 item->eid, scratch_pool),
2175                           from);
2176         }
2177       else
2178         {
2179           svnmover_notify("%s%c%c%c e%-3d  %s%s%s%s%s",
2180                           prefix,
2181                           status_mod,
2182                           item->reparented ? 'v' : ' ',
2183                           item->renamed ? 'r' : ' ',
2184                           item->eid,
2185                           e1 ? peid_name(e1, scratch_pool) : "",
2186                           subtree_subbranch_str(e0 ? left : right,
2187                                                 e0 ? left_bid : right_bid,
2188                                                 item->eid, scratch_pool),
2189                           e0 && e1 ? " (from " : "",
2190                           e0 ? peid_name(e0, scratch_pool) : "",
2191                           e0 && e1 ? ")" : "");
2192         }
2193     }
2194 
2195   return SVN_NO_ERROR;
2196 }
2197 
2198 typedef svn_error_t *
2199 svn_branch__diff_func_t(svn_branch__subtree_t *left,
2200                         const char *left_bid,
2201                         svn_branch__subtree_t *right,
2202                         const char *right_bid,
2203                         const char *prefix,
2204                         const char *header,
2205                         apr_pool_t *scratch_pool);
2206 
2207 /* Display differences between subtrees LEFT and RIGHT.
2208  *
2209  * Recurse into sub-branches.
2210  */
2211 static svn_error_t *
subtree_diff_r(svn_branch__state_t * left_branch,int left_root_eid,svn_branch__state_t * right_branch,int right_root_eid,svn_branch__diff_func_t diff_func,const char * prefix,apr_pool_t * scratch_pool)2212 subtree_diff_r(svn_branch__state_t *left_branch,
2213                int left_root_eid,
2214                svn_branch__state_t *right_branch,
2215                int right_root_eid,
2216                svn_branch__diff_func_t diff_func,
2217                const char *prefix,
2218                apr_pool_t *scratch_pool)
2219 {
2220   svn_branch__subtree_t *left = NULL;
2221   svn_branch__subtree_t *right = NULL;
2222   const char *left_str
2223     = left_branch
2224         ? apr_psprintf(scratch_pool, "%s:e%d at /%s",
2225                        left_branch->bid, left_root_eid,
2226                        svn_branch__get_root_rrpath(left_branch, scratch_pool))
2227         : NULL;
2228   const char *right_str
2229     = right_branch
2230         ? apr_psprintf(scratch_pool, "%s:e%d at /%s",
2231                        right_branch->bid, right_root_eid,
2232                        svn_branch__get_root_rrpath(right_branch, scratch_pool))
2233             : NULL;
2234   const char *header;
2235   apr_hash_t *subbranches_l, *subbranches_r, *subbranches_all;
2236   apr_hash_index_t *hi;
2237 
2238   if (left_branch)
2239     {
2240       SVN_ERR(svn_branch__get_subtree(left_branch, &left, left_root_eid,
2241                                       scratch_pool));
2242     }
2243   if (right_branch)
2244     {
2245       SVN_ERR(svn_branch__get_subtree(right_branch, &right, right_root_eid,
2246                                       scratch_pool));
2247     }
2248 
2249   if (!left)
2250     {
2251       header = apr_psprintf(scratch_pool,
2252                  "--- added branch %s",
2253                  right_str);
2254       svnmover_notify("%s%s", prefix, header);
2255     }
2256   else if (!right)
2257     {
2258       header = apr_psprintf(scratch_pool,
2259                  "--- deleted branch %s",
2260                  left_str);
2261       svnmover_notify("%s%s", prefix, header);
2262     }
2263   else
2264     {
2265       if (strcmp(left_str, right_str) == 0)
2266         {
2267           header = apr_psprintf(
2268                      scratch_pool, "--- diff branch %s",
2269                      left_str);
2270         }
2271       else
2272         {
2273           header = apr_psprintf(
2274                      scratch_pool, "--- diff branch %s : %s",
2275                      left_str, right_str);
2276         }
2277       SVN_ERR(diff_func(left, left_branch->bid, right, right_branch->bid,
2278                         prefix, header,
2279                         scratch_pool));
2280     }
2281 
2282   /* recurse into each subbranch that exists in LEFT and/or in RIGHT */
2283   subbranches_l = left ? left->subbranches : apr_hash_make(scratch_pool);
2284   subbranches_r = right ? right->subbranches : apr_hash_make(scratch_pool);
2285   subbranches_all = hash_overlay(subbranches_l, subbranches_r);
2286 
2287   for (hi = apr_hash_first(scratch_pool, subbranches_all);
2288        hi; hi = apr_hash_next(hi))
2289     {
2290       int e = svn_eid__hash_this_key(hi);
2291       svn_branch__state_t *left_subbranch = NULL, *right_subbranch = NULL;
2292       int left_subbranch_eid = -1, right_subbranch_eid = -1;
2293 
2294       /* recurse */
2295       if (left_branch)
2296         {
2297           SVN_ERR(svn_branch__get_subbranch_at_eid(left_branch, &left_subbranch, e,
2298                                                    scratch_pool));
2299           if (left_subbranch)
2300             {
2301               left_subbranch_eid = svn_branch__root_eid(left_subbranch);
2302             }
2303         }
2304       if (right_branch)
2305         {
2306           SVN_ERR(svn_branch__get_subbranch_at_eid(right_branch, &right_subbranch, e,
2307                                                    scratch_pool));
2308           if (right_subbranch)
2309             {
2310               right_subbranch_eid = svn_branch__root_eid(right_subbranch);
2311             }
2312         }
2313       SVN_ERR(subtree_diff_r(left_subbranch, left_subbranch_eid,
2314                              right_subbranch, right_subbranch_eid,
2315                              diff_func, prefix, scratch_pool));
2316     }
2317   return SVN_NO_ERROR;
2318 }
2319 
2320 /* Display differences between branch subtrees LEFT and RIGHT.
2321  *
2322  * Recurse into sub-branches.
2323  */
2324 static svn_error_t *
branch_diff_r(svn_branch__el_rev_id_t * left,svn_branch__el_rev_id_t * right,svn_branch__diff_func_t diff_func,const char * prefix,apr_pool_t * scratch_pool)2325 branch_diff_r(svn_branch__el_rev_id_t *left,
2326               svn_branch__el_rev_id_t *right,
2327               svn_branch__diff_func_t diff_func,
2328               const char *prefix,
2329               apr_pool_t *scratch_pool)
2330 {
2331   SVN_ERR(subtree_diff_r(left->branch, left->eid,
2332                          right->branch, right->eid,
2333                          diff_func, prefix, scratch_pool));
2334   return SVN_NO_ERROR;
2335 }
2336 
2337 /*  */
2338 static svn_error_t *
do_copy(svn_branch__el_rev_id_t * from_el_rev,svn_branch__state_t * to_branch,svn_branch__eid_t to_parent_eid,const char * new_name,apr_pool_t * scratch_pool)2339 do_copy(svn_branch__el_rev_id_t *from_el_rev,
2340         svn_branch__state_t *to_branch,
2341         svn_branch__eid_t to_parent_eid,
2342         const char *new_name,
2343         apr_pool_t *scratch_pool)
2344 {
2345   const char *from_branch_id = svn_branch__get_id(from_el_rev->branch,
2346                                                   scratch_pool);
2347   svn_branch__rev_bid_eid_t *src_el_rev
2348     = svn_branch__rev_bid_eid_create(from_el_rev->rev, from_branch_id,
2349                                      from_el_rev->eid, scratch_pool);
2350   const char *from_path = el_rev_id_to_path(from_el_rev, scratch_pool);
2351   const char *to_path = branch_peid_name_to_path(to_branch, to_parent_eid,
2352                                                  new_name, scratch_pool);
2353 
2354   SVN_ERR(svn_branch__state_copy_tree(to_branch,
2355                                       src_el_rev, to_parent_eid, new_name,
2356                                       scratch_pool));
2357   svnmover_notify_v("A+   %s (from %s)",
2358                     to_path, from_path);
2359 
2360   return SVN_NO_ERROR;
2361 }
2362 
2363 /*  */
2364 static svn_error_t *
do_delete(svn_branch__state_t * branch,svn_branch__eid_t eid,apr_pool_t * scratch_pool)2365 do_delete(svn_branch__state_t *branch,
2366           svn_branch__eid_t eid,
2367           apr_pool_t *scratch_pool)
2368 {
2369   const char *path = svn_branch__get_rrpath_by_eid(branch, eid, scratch_pool);
2370 
2371   SVN_ERR(svn_branch__state_delete_one(branch, eid, scratch_pool));
2372   svnmover_notify_v("D    %s", path);
2373   return SVN_NO_ERROR;
2374 }
2375 
2376 /*  */
2377 static svn_error_t *
do_mkdir(svn_branch__txn_t * txn,svn_branch__state_t * to_branch,svn_branch__eid_t to_parent_eid,const char * new_name,apr_pool_t * scratch_pool)2378 do_mkdir(svn_branch__txn_t *txn,
2379          svn_branch__state_t *to_branch,
2380          svn_branch__eid_t to_parent_eid,
2381          const char *new_name,
2382          apr_pool_t *scratch_pool)
2383 {
2384   apr_hash_t *props = apr_hash_make(scratch_pool);
2385   svn_element__payload_t *payload
2386     = svn_element__payload_create_dir(props, scratch_pool);
2387   int new_eid;
2388   const char *path = branch_peid_name_to_path(to_branch, to_parent_eid,
2389                                               new_name, scratch_pool);
2390 
2391   SVN_ERR(svn_branch__txn_new_eid(txn, &new_eid, scratch_pool));
2392   SVN_ERR(svn_branch__state_alter_one(to_branch, new_eid,
2393                                       to_parent_eid, new_name, payload,
2394                                       scratch_pool));
2395   svnmover_notify_v("A    %s",
2396                     path);
2397   return SVN_NO_ERROR;
2398 }
2399 
2400 /*  */
2401 static svn_error_t *
do_put_file(svn_branch__txn_t * txn,const char * local_file_path,svn_branch__el_rev_id_t * file_el_rev,svn_branch__el_rev_id_t * parent_el_rev,const char * file_name,apr_pool_t * scratch_pool)2402 do_put_file(svn_branch__txn_t *txn,
2403             const char *local_file_path,
2404             svn_branch__el_rev_id_t *file_el_rev,
2405             svn_branch__el_rev_id_t *parent_el_rev,
2406             const char *file_name,
2407             apr_pool_t *scratch_pool)
2408 {
2409   apr_hash_t *props;
2410   svn_stringbuf_t *text;
2411   int parent_eid;
2412   const char *name;
2413   svn_element__payload_t *payload;
2414 
2415   if (file_el_rev->eid != -1)
2416     {
2417       /* get existing props */
2418       svn_element__content_t *existing_element;
2419 
2420       SVN_ERR(svn_branch__state_get_element(file_el_rev->branch,
2421                                             &existing_element,
2422                                             file_el_rev->eid, scratch_pool));
2423       props = existing_element->payload->props;
2424     }
2425   else
2426     {
2427       props = apr_hash_make(scratch_pool);
2428     }
2429   /* read new text from file */
2430   {
2431     svn_stream_t *src;
2432 
2433     if (strcmp(local_file_path, "-") != 0)
2434       SVN_ERR(svn_stream_open_readonly(&src, local_file_path,
2435                                        scratch_pool, scratch_pool));
2436     else
2437       SVN_ERR(svn_stream_for_stdin2(&src, FALSE, scratch_pool));
2438 
2439     SVN_ERR(svn_stringbuf_from_stream(&text, src, 0, scratch_pool));
2440   }
2441   payload = svn_element__payload_create_file(props, text, scratch_pool);
2442 
2443   if (is_branch_root_element(file_el_rev->branch,
2444                              file_el_rev->eid))
2445     {
2446       parent_eid = -1;
2447       name = "";
2448     }
2449   else
2450     {
2451       parent_eid = parent_el_rev->eid;
2452       name = file_name;
2453     }
2454 
2455   if (file_el_rev->eid != -1)
2456     {
2457       const char *path = el_rev_id_to_path(file_el_rev, scratch_pool);
2458 
2459       SVN_ERR(svn_branch__state_alter_one(file_el_rev->branch, file_el_rev->eid,
2460                                           parent_eid, name, payload,
2461                                           scratch_pool));
2462       svnmover_notify_v("M    %s",
2463                         path);
2464     }
2465   else
2466     {
2467       int new_eid;
2468       const char *path
2469         = branch_peid_name_to_path(parent_el_rev->branch, parent_eid, name,
2470                                    scratch_pool);
2471 
2472       SVN_ERR(svn_branch__txn_new_eid(txn, &new_eid, scratch_pool));
2473       SVN_ERR(svn_branch__state_alter_one(parent_el_rev->branch, new_eid,
2474                                           parent_eid, name, payload,
2475                                           scratch_pool));
2476       file_el_rev->eid = new_eid;
2477       svnmover_notify_v("A    %s",
2478                         path);
2479     }
2480   return SVN_NO_ERROR;
2481 }
2482 
2483 /*  */
2484 static svn_error_t *
do_cat(svn_branch__el_rev_id_t * file_el_rev,apr_pool_t * scratch_pool)2485 do_cat(svn_branch__el_rev_id_t *file_el_rev,
2486        apr_pool_t *scratch_pool)
2487 {
2488   apr_hash_t *props;
2489   svn_stringbuf_t *text;
2490   svn_element__content_t *existing_element;
2491   apr_hash_index_t *hi;
2492 
2493   /* get existing props */
2494   SVN_ERR(svn_branch__state_get_element(file_el_rev->branch, &existing_element,
2495                                         file_el_rev->eid, scratch_pool));
2496 
2497   props = existing_element->payload->props;
2498   text = existing_element->payload->text;
2499 
2500   for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
2501     {
2502       const char *pname = apr_hash_this_key(hi);
2503       svn_string_t *pval = apr_hash_this_val(hi);
2504 
2505       svnmover_notify("property '%s': '%s'", pname, pval->data);
2506     }
2507   if (text)
2508     {
2509       svnmover_notify("%s", text->data);
2510     }
2511   return SVN_NO_ERROR;
2512 }
2513 
2514 /* Find the main parent of branch-state BRANCH. That means:
2515  *   - the only parent (in the case of straight history or branching), else
2516  *   - the parent with the same branch id (in the case of normal merging), else
2517  *   - none (in the case of a new unrelated branch, or a new branch formed
2518  *     by merging two or more other branches).
2519  */
2520 static svn_error_t *
find_branch_main_parent(svn_branch__state_t * branch,svn_branch__rev_bid_t ** predecessor_p,apr_pool_t * result_pool)2521 find_branch_main_parent(svn_branch__state_t *branch,
2522                         svn_branch__rev_bid_t **predecessor_p,
2523                         apr_pool_t *result_pool)
2524 {
2525   svn_branch__history_t *history;
2526   svn_branch__rev_bid_t *our_own_history;
2527   svn_branch__rev_bid_t *predecessor = NULL;
2528 
2529   SVN_ERR(svn_branch__state_get_history(branch, &history, result_pool));
2530   if (apr_hash_count(history->parents) == 1)
2531     {
2532       apr_hash_index_t *hi = apr_hash_first(result_pool, history->parents);
2533 
2534       predecessor = apr_hash_this_val(hi);
2535     }
2536   else if ((our_own_history = svn_hash_gets(history->parents, branch->bid)))
2537     {
2538       predecessor = our_own_history;
2539     }
2540 
2541   if (predecessor_p)
2542     *predecessor_p = predecessor;
2543   return SVN_NO_ERROR;
2544 }
2545 
2546 /* Set *NEW_EL_REV_P to the location where OLD_EL_REV was in the previous
2547  * revision. Follow the "main line" of any branching in its history.
2548  *
2549  * If the same EID...
2550  */
2551 static svn_error_t *
svn_branch__find_predecessor_el_rev(svn_branch__el_rev_id_t ** new_el_rev_p,svn_branch__el_rev_id_t * old_el_rev,apr_pool_t * result_pool)2552 svn_branch__find_predecessor_el_rev(svn_branch__el_rev_id_t **new_el_rev_p,
2553                                     svn_branch__el_rev_id_t *old_el_rev,
2554                                     apr_pool_t *result_pool)
2555 {
2556   const svn_branch__repos_t *repos = old_el_rev->branch->txn->repos;
2557   svn_branch__rev_bid_t *predecessor;
2558   svn_branch__state_t *branch;
2559 
2560   SVN_ERR(find_branch_main_parent(old_el_rev->branch,
2561                                   &predecessor, result_pool));
2562   if (! predecessor)
2563     {
2564       *new_el_rev_p = NULL;
2565       return SVN_NO_ERROR;
2566     }
2567 
2568   SVN_ERR(svn_branch__repos_get_branch_by_id(&branch,
2569                                              repos, predecessor->rev,
2570                                              predecessor->bid, result_pool));
2571   *new_el_rev_p = svn_branch__el_rev_id_create(branch, old_el_rev->eid,
2572                                                predecessor->rev, result_pool);
2573 
2574   return SVN_NO_ERROR;
2575 }
2576 
2577 /* Similar to 'svn log -v', this iterates over the revisions between
2578  * LEFT and RIGHT (currently excluding LEFT), printing a single-rev diff
2579  * for each.
2580  */
2581 static svn_error_t *
do_log(svn_branch__el_rev_id_t * left,svn_branch__el_rev_id_t * right,apr_pool_t * scratch_pool)2582 do_log(svn_branch__el_rev_id_t *left,
2583        svn_branch__el_rev_id_t *right,
2584        apr_pool_t *scratch_pool)
2585 {
2586   svn_revnum_t first_rev = left->rev;
2587 
2588   while (right->rev > first_rev)
2589     {
2590       svn_branch__el_rev_id_t *el_rev_left;
2591 
2592       SVN_ERR(svn_branch__find_predecessor_el_rev(&el_rev_left, right, scratch_pool));
2593 
2594       svnmover_notify(SVN_CL__LOG_SEP_STRING "r%ld | ...",
2595                       right->rev);
2596       svnmover_notify("History:");
2597       SVN_ERR(show_history_r(right->branch, "   ", scratch_pool));
2598       svnmover_notify("Changed elements:");
2599       SVN_ERR(branch_diff_r(el_rev_left, right,
2600                             show_subtree_diff, "   ",
2601                             scratch_pool));
2602       right = el_rev_left;
2603     }
2604 
2605   return SVN_NO_ERROR;
2606 }
2607 
2608 /* Make a subbranch at OUTER_BRANCH : OUTER_PARENT_EID : OUTER_NAME.
2609  *
2610  * The subbranch will consist of a single element given by PAYLOAD.
2611  */
2612 static svn_error_t *
do_mkbranch(const char ** new_branch_id_p,svn_branch__txn_t * txn,svn_branch__state_t * outer_branch,int outer_parent_eid,const char * outer_name,svn_element__payload_t * payload,apr_pool_t * scratch_pool)2613 do_mkbranch(const char **new_branch_id_p,
2614             svn_branch__txn_t *txn,
2615             svn_branch__state_t *outer_branch,
2616             int outer_parent_eid,
2617             const char *outer_name,
2618             svn_element__payload_t *payload,
2619             apr_pool_t *scratch_pool)
2620 {
2621   const char *outer_branch_id = svn_branch__get_id(outer_branch, scratch_pool);
2622   int new_outer_eid, new_inner_eid;
2623   const char *new_branch_id;
2624   svn_branch__state_t *new_branch;
2625   const char *path = branch_peid_name_to_path(outer_branch, outer_parent_eid,
2626                                               outer_name, scratch_pool);
2627 
2628   SVN_ERR(svn_branch__txn_new_eid(txn, &new_outer_eid, scratch_pool));
2629   SVN_ERR(svn_branch__state_alter_one(outer_branch, new_outer_eid,
2630                                       outer_parent_eid, outer_name,
2631                                       svn_element__payload_create_subbranch(
2632                                         scratch_pool), scratch_pool));
2633 
2634   SVN_ERR(svn_branch__txn_new_eid(txn, &new_inner_eid, scratch_pool));
2635   new_branch_id = svn_branch__id_nest(outer_branch_id, new_outer_eid,
2636                                       scratch_pool);
2637   SVN_ERR(svn_branch__txn_open_branch(txn, &new_branch,
2638                                       new_branch_id, new_inner_eid,
2639                                       NULL /*tree_ref*/,
2640                                       scratch_pool, scratch_pool));
2641   SVN_ERR(svn_branch__state_alter_one(new_branch, new_inner_eid,
2642                                       -1, "", payload, scratch_pool));
2643 
2644   svnmover_notify_v("A    %s (branch %s)",
2645                     path,
2646                     new_branch->bid);
2647   if (new_branch_id_p)
2648     *new_branch_id_p = new_branch->bid;
2649   return SVN_NO_ERROR;
2650 }
2651 
2652 /* Branch all or part of an existing branch, making a new branch.
2653  *
2654  * Branch the subtree of FROM_BRANCH found at FROM_EID, to create
2655  * a new branch at TO_OUTER_BRANCH:TO_OUTER_PARENT_EID:NEW_NAME.
2656  *
2657  * FROM_BRANCH:FROM_EID must be an existing element. It may be the
2658  * root of FROM_BRANCH. It must not be the root of a subbranch of
2659  * FROM_BRANCH.
2660  *
2661  * TO_OUTER_BRANCH:TO_OUTER_PARENT_EID must be an existing directory
2662  * and NEW_NAME must be nonexistent in that directory.
2663  */
2664 static svn_error_t *
do_branch(svn_branch__state_t ** new_branch_p,svn_branch__txn_t * txn,svn_branch__rev_bid_eid_t * from,svn_branch__state_t * to_outer_branch,svn_branch__eid_t to_outer_parent_eid,const char * new_name,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2665 do_branch(svn_branch__state_t **new_branch_p,
2666           svn_branch__txn_t *txn,
2667           svn_branch__rev_bid_eid_t *from,
2668           svn_branch__state_t *to_outer_branch,
2669           svn_branch__eid_t to_outer_parent_eid,
2670           const char *new_name,
2671           apr_pool_t *result_pool,
2672           apr_pool_t *scratch_pool)
2673 {
2674   const char *to_outer_branch_id
2675     = to_outer_branch ? svn_branch__get_id(to_outer_branch, scratch_pool) : NULL;
2676   int to_outer_eid;
2677   const char *new_branch_id;
2678   svn_branch__state_t *new_branch;
2679   svn_branch__history_t *history;
2680   const char *to_path
2681     = branch_peid_name_to_path(to_outer_branch,
2682                                to_outer_parent_eid, new_name, scratch_pool);
2683 
2684   /* assign new eid to root element (outer branch) */
2685   SVN_ERR(svn_branch__txn_new_eid(txn, &to_outer_eid, scratch_pool));
2686 
2687   new_branch_id = svn_branch__id_nest(to_outer_branch_id, to_outer_eid,
2688                                       scratch_pool);
2689   SVN_ERR(svn_branch__txn_open_branch(txn, &new_branch,
2690                                       new_branch_id, from->eid, from,
2691                                       result_pool, scratch_pool));
2692   history = svn_branch__history_create_empty(scratch_pool);
2693   SVN_ERR(svn_branch__history_add_parent(history, from->rev, from->bid,
2694                                          scratch_pool));
2695   SVN_ERR(svn_branch__state_set_history(new_branch, history, scratch_pool));
2696   SVN_ERR(svn_branch__state_alter_one(to_outer_branch, to_outer_eid,
2697                                       to_outer_parent_eid, new_name,
2698                                       svn_element__payload_create_subbranch(
2699                                         scratch_pool), scratch_pool));
2700 
2701   svnmover_notify_v("A+   %s (branch %s)",
2702                     to_path,
2703                     new_branch->bid);
2704 
2705   if (new_branch_p)
2706     *new_branch_p = new_branch;
2707   return SVN_NO_ERROR;
2708 }
2709 
2710 static svn_error_t *
do_topbranch(svn_branch__state_t ** new_branch_p,svn_branch__txn_t * txn,svn_branch__rev_bid_eid_t * from,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2711 do_topbranch(svn_branch__state_t **new_branch_p,
2712              svn_branch__txn_t *txn,
2713              svn_branch__rev_bid_eid_t *from,
2714              apr_pool_t *result_pool,
2715              apr_pool_t *scratch_pool)
2716 {
2717   int outer_eid;
2718   const char *new_branch_id;
2719   svn_branch__state_t *new_branch;
2720 
2721   SVN_ERR(svn_branch__txn_new_eid(txn, &outer_eid, scratch_pool));
2722   new_branch_id = svn_branch__id_nest(NULL /*outer_branch*/, outer_eid,
2723                                       scratch_pool);
2724   SVN_ERR(svn_branch__txn_open_branch(txn, &new_branch,
2725                                       new_branch_id, from->eid, from,
2726                                       result_pool, scratch_pool));
2727 
2728   svnmover_notify_v("A+   (branch %s)",
2729                     new_branch->bid);
2730 
2731   if (new_branch_p)
2732     *new_branch_p = new_branch;
2733   return SVN_NO_ERROR;
2734 }
2735 
2736 /* Branch the subtree of FROM_BRANCH found at FROM_EID, to appear
2737  * in the existing branch TO_BRANCH at TO_PARENT_EID:NEW_NAME.
2738  *
2739  * This is like merging the creation of the source subtree into TO_BRANCH.
2740  *
2741  * Any elements of the source subtree that already exist in TO_BRANCH
2742  * are altered. This is like resolving any merge conflicts as 'theirs'.
2743  *
2744  * (### Sometimes the user might prefer that we throw an error if any
2745  * element of the source subtree already exists in TO_BRANCH.)
2746  */
2747 static svn_error_t *
do_branch_into(svn_branch__state_t * from_branch,int from_eid,svn_branch__state_t * to_branch,svn_branch__eid_t to_parent_eid,const char * new_name,apr_pool_t * scratch_pool)2748 do_branch_into(svn_branch__state_t *from_branch,
2749                int from_eid,
2750                svn_branch__state_t *to_branch,
2751                svn_branch__eid_t to_parent_eid,
2752                const char *new_name,
2753                apr_pool_t *scratch_pool)
2754 {
2755   svn_branch__subtree_t *from_subtree;
2756   svn_element__content_t *new_root_content;
2757   const char *to_path = branch_peid_name_to_path(to_branch, to_parent_eid,
2758                                                  new_name, scratch_pool);
2759 
2760   /* Source element must exist */
2761   if (! svn_branch__get_path_by_eid(from_branch, from_eid, scratch_pool))
2762     {
2763       return svn_error_createf(SVN_BRANCH__ERR, NULL,
2764                                _("Cannot branch from %s e%d: "
2765                                  "does not exist"),
2766                                svn_branch__get_id(
2767                                  from_branch, scratch_pool), from_eid);
2768     }
2769 
2770   SVN_ERR(svn_branch__get_subtree(from_branch, &from_subtree, from_eid,
2771                                   scratch_pool));
2772 
2773   /* Change this subtree's root element to TO_PARENT_EID/NEW_NAME. */
2774   new_root_content
2775     = svn_element__tree_get(from_subtree->tree, from_subtree->tree->root_eid);
2776   new_root_content
2777     = svn_element__content_create(to_parent_eid, new_name,
2778                                   new_root_content->payload, scratch_pool);
2779   svn_element__tree_set(from_subtree->tree, from_subtree->tree->root_eid,
2780                         new_root_content);
2781 
2782   /* Populate the new branch mapping */
2783   SVN_ERR(svn_branch__instantiate_elements_r(to_branch, *from_subtree,
2784                                              scratch_pool));
2785   svnmover_notify_v("A+   %s (subtree)",
2786                     to_path);
2787 
2788   return SVN_NO_ERROR;
2789 }
2790 
2791 /* Copy-and-delete.
2792  *
2793  *      copy the subtree at EL_REV to TO_BRANCH:TO_PARENT_EID:TO_NAME
2794  *      delete the subtree at EL_REV
2795  */
2796 static svn_error_t *
do_copy_and_delete(svn_branch__el_rev_id_t * el_rev,svn_branch__state_t * to_branch,int to_parent_eid,const char * to_name,apr_pool_t * scratch_pool)2797 do_copy_and_delete(svn_branch__el_rev_id_t *el_rev,
2798                    svn_branch__state_t *to_branch,
2799                    int to_parent_eid,
2800                    const char *to_name,
2801                    apr_pool_t *scratch_pool)
2802 {
2803   const char *from_path
2804     = svn_branch__get_rrpath_by_eid(el_rev->branch, el_rev->eid, scratch_pool);
2805 
2806   SVN_ERR_ASSERT(! is_branch_root_element(el_rev->branch, el_rev->eid));
2807 
2808   SVN_ERR(do_copy(el_rev, to_branch, to_parent_eid, to_name,
2809                   scratch_pool));
2810 
2811   SVN_ERR(svn_branch__state_delete_one(el_rev->branch, el_rev->eid,
2812                                        scratch_pool));
2813   svnmover_notify_v("D    %s", from_path);
2814 
2815   return SVN_NO_ERROR;
2816 }
2817 
2818 /* Branch-and-delete.
2819  *
2820  *      branch the subtree at EL_REV creating a new nested branch at
2821  *        TO_BRANCH:TO_PARENT_EID:TO_NAME,
2822  *        or creating a new top-level branch if TO_BRANCH is null;
2823  *      delete the subtree at EL_REV
2824  */
2825 static svn_error_t *
do_branch_and_delete(svn_branch__txn_t * edit_txn,svn_branch__el_rev_id_t * el_rev,svn_branch__state_t * to_outer_branch,int to_outer_parent_eid,const char * to_name,apr_pool_t * scratch_pool)2826 do_branch_and_delete(svn_branch__txn_t *edit_txn,
2827                      svn_branch__el_rev_id_t *el_rev,
2828                      svn_branch__state_t *to_outer_branch,
2829                      int to_outer_parent_eid,
2830                      const char *to_name,
2831                      apr_pool_t *scratch_pool)
2832 {
2833   const char *from_branch_id = svn_branch__get_id(el_rev->branch,
2834                                                   scratch_pool);
2835   svn_branch__rev_bid_eid_t *from
2836     = svn_branch__rev_bid_eid_create(el_rev->rev, from_branch_id,
2837                                      el_rev->eid, scratch_pool);
2838   svn_branch__state_t *new_branch;
2839   const char *from_path
2840     = svn_branch__get_rrpath_by_eid(el_rev->branch, el_rev->eid, scratch_pool);
2841 
2842   SVN_ERR_ASSERT(! is_branch_root_element(el_rev->branch, el_rev->eid));
2843 
2844   SVN_ERR(do_branch(&new_branch, edit_txn, from,
2845                     to_outer_branch, to_outer_parent_eid, to_name,
2846                     scratch_pool, scratch_pool));
2847 
2848   SVN_ERR(svn_branch__state_delete_one(el_rev->branch, el_rev->eid,
2849                                        scratch_pool));
2850   svnmover_notify_v("D    %s", from_path);
2851 
2852   return SVN_NO_ERROR;
2853 }
2854 
2855 /* Branch-into-and-delete.
2856  *
2857  * (Previously, confusingly, called 'branch-and-delete'.)
2858  *
2859  * The target branch is different from the source branch.
2860  *
2861  *      delete elements from source branch
2862  *      instantiate (or update) same elements in target branch
2863  *
2864  * For each element being moved, if the element already exists in TO_BRANCH,
2865  * the effect is as if the existing element in TO_BRANCH was first deleted.
2866  */
2867 static svn_error_t *
do_branch_into_and_delete(svn_branch__el_rev_id_t * el_rev,svn_branch__state_t * to_branch,int to_parent_eid,const char * to_name,apr_pool_t * scratch_pool)2868 do_branch_into_and_delete(svn_branch__el_rev_id_t *el_rev,
2869                           svn_branch__state_t *to_branch,
2870                           int to_parent_eid,
2871                           const char *to_name,
2872                           apr_pool_t *scratch_pool)
2873 {
2874   const char *from_path
2875     = svn_branch__get_rrpath_by_eid(el_rev->branch, el_rev->eid, scratch_pool);
2876 
2877   SVN_ERR_ASSERT(! is_branch_root_element(el_rev->branch, el_rev->eid));
2878 
2879   /* This is supposed to be used for moving to a *different* branch.
2880      In fact, this method would also work for moving within one
2881      branch, but we don't currently want to use it for that purpose. */
2882   SVN_ERR_ASSERT(! BRANCH_IS_SAME_BRANCH(el_rev->branch, to_branch,
2883                                          scratch_pool));
2884 
2885   /* Merge the "creation of the source" to the target (aka branch-into) */
2886   SVN_ERR(do_branch_into(el_rev->branch, el_rev->eid,
2887                          to_branch, to_parent_eid, to_name,
2888                          scratch_pool));
2889 
2890   SVN_ERR(svn_branch__state_delete_one(el_rev->branch, el_rev->eid,
2891                                        scratch_pool));
2892   svnmover_notify_v("D    %s", from_path);
2893 
2894   return SVN_NO_ERROR;
2895 }
2896 
2897 /* Interactive options for moving to another branch.
2898  */
2899 static svn_error_t *
do_interactive_cross_branch_move(svn_branch__txn_t * txn,svn_branch__el_rev_id_t * el_rev,svn_branch__el_rev_id_t * to_parent_el_rev,const char * to_name,apr_pool_t * scratch_pool)2900 do_interactive_cross_branch_move(svn_branch__txn_t *txn,
2901                                  svn_branch__el_rev_id_t *el_rev,
2902                                  svn_branch__el_rev_id_t *to_parent_el_rev,
2903                                  const char *to_name,
2904                                  apr_pool_t *scratch_pool)
2905 {
2906   svn_error_t *err;
2907   const char *input;
2908 
2909   if (0 /*### if non-interactive*/)
2910     {
2911       return svn_error_createf(SVN_BRANCH__ERR, NULL,
2912         _("mv: The source and target are in different branches. "
2913           "Some ways to move content to a different branch are, "
2914           "depending on the effect you want to achieve: "
2915           "copy-and-delete, branch-and-delete, branch-into-and-delete"));
2916     }
2917 
2918   svnmover_notify_v(
2919     _("mv: The source and target are in different branches. "
2920       "Some ways to move content to a different branch are, "
2921       "depending on the effect you want to achieve:\n"
2922       "  c: copy-and-delete: cp SOURCE TARGET; rm SOURCE\n"
2923       "  b: branch-and-delete: branch SOURCE TARGET; rm SOURCE\n"
2924       "  i: branch-into-and-delete: branch-into SOURCE TARGET; rm SOURCE\n"
2925       "We can do one of these for you now if you wish.\n"
2926     ));
2927 
2928   settext_stderr(TEXT_FG_YELLOW);
2929   err = svn_cmdline_prompt_user2(
2930           &input,
2931           "Your choice (c, b, i, or just <enter> to do nothing): ",
2932           NULL, scratch_pool);
2933   settext(TEXT_RESET);
2934   if (err && (err->apr_err == SVN_ERR_CANCELLED || err->apr_err == APR_EOF))
2935     {
2936       svn_error_clear(err);
2937       return SVN_NO_ERROR;
2938     }
2939   SVN_ERR(err);
2940 
2941   if (input[0] == 'c' || input[0] == 'C')
2942     {
2943       svnmover_notify_v("Performing 'copy-and-delete SOURCE TARGET'");
2944 
2945       SVN_ERR(do_copy_and_delete(el_rev,
2946                                  to_parent_el_rev->branch,
2947                                  to_parent_el_rev->eid, to_name,
2948                                  scratch_pool));
2949     }
2950   else if (input[0] == 'b' || input[0] == 'B')
2951     {
2952       svnmover_notify_v("Performing 'branch-and-delete SOURCE TARGET'");
2953 
2954       SVN_ERR(do_branch_and_delete(txn, el_rev,
2955                                    to_parent_el_rev->branch,
2956                                    to_parent_el_rev->eid, to_name,
2957                                    scratch_pool));
2958     }
2959   else if (input[0] == 'i' || input[0] == 'I')
2960     {
2961       svnmover_notify_v("Performing 'branch-into-and-delete SOURCE TARGET'");
2962       svnmover_notify_v(
2963         "In the current implementation of this experimental UI, each element "
2964         "instance from the source branch subtree will overwrite any instance "
2965         "of the same element that already exists in the target branch."
2966         );
2967       /* We could instead either throw an error or fall back to copy-and-delete
2968          if any moved element already exists in target branch. */
2969 
2970       SVN_ERR(do_branch_into_and_delete(el_rev,
2971                                         to_parent_el_rev->branch,
2972                                         to_parent_el_rev->eid, to_name,
2973                                         scratch_pool));
2974     }
2975 
2976   return SVN_NO_ERROR;
2977 }
2978 
2979 /* Move.
2980  */
2981 static svn_error_t *
do_move(svn_branch__el_rev_id_t * el_rev,svn_branch__el_rev_id_t * to_parent_el_rev,const char * to_name,apr_pool_t * scratch_pool)2982 do_move(svn_branch__el_rev_id_t *el_rev,
2983         svn_branch__el_rev_id_t *to_parent_el_rev,
2984         const char *to_name,
2985         apr_pool_t *scratch_pool)
2986 {
2987   const char *from_path = el_rev_id_to_path(el_rev, scratch_pool);
2988   const char *to_path
2989     = branch_peid_name_to_path(to_parent_el_rev->branch,
2990                                to_parent_el_rev->eid, to_name, scratch_pool);
2991   /* New payload shall be the same as before */
2992   svn_element__content_t *existing_element;
2993 
2994   SVN_ERR(svn_branch__state_get_element(el_rev->branch, &existing_element,
2995                                         el_rev->eid, scratch_pool));
2996   SVN_ERR(svn_branch__state_alter_one(el_rev->branch, el_rev->eid,
2997                             to_parent_el_rev->eid, to_name,
2998                             existing_element->payload, scratch_pool));
2999   svnmover_notify_v("V    %s (from %s)",
3000                     to_path, from_path);
3001   return SVN_NO_ERROR;
3002 }
3003 
3004 /* This commit callback prints not only a commit summary line but also
3005  * a log-style summary of the changes.
3006  */
3007 static svn_error_t *
commit_callback(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)3008 commit_callback(const svn_commit_info_t *commit_info,
3009                 void *baton,
3010                 apr_pool_t *pool)
3011 {
3012   commit_callback_baton_t *b = baton;
3013 
3014   svnmover_notify("Committed r%ld:", commit_info->revision);
3015 
3016   b->revision = commit_info->revision;
3017   return SVN_NO_ERROR;
3018 }
3019 
3020 /* Display a diff of the commit */
3021 static svn_error_t *
display_diff_of_commit(const commit_callback_baton_t * ccbb,apr_pool_t * scratch_pool)3022 display_diff_of_commit(const commit_callback_baton_t *ccbb,
3023                        apr_pool_t *scratch_pool)
3024 {
3025   svn_branch__txn_t *previous_head_txn
3026     = svn_branch__repos_get_base_revision_root(ccbb->edit_txn);
3027   svn_branch__state_t *base_branch
3028     = svn_branch__txn_get_branch_by_id(previous_head_txn,
3029                                        ccbb->wc_base_branch_id,
3030                                        scratch_pool);
3031   svn_branch__state_t *committed_branch
3032     = svn_branch__txn_get_branch_by_id(ccbb->edit_txn,
3033                                        ccbb->wc_commit_branch_id,
3034                                        scratch_pool);
3035   svn_branch__el_rev_id_t *el_rev_left
3036     = svn_branch__el_rev_id_create(base_branch, svn_branch__root_eid(base_branch),
3037                                    base_branch->txn->rev,
3038                                    scratch_pool);
3039   svn_branch__el_rev_id_t *el_rev_right
3040     = svn_branch__el_rev_id_create(committed_branch,
3041                                    svn_branch__root_eid(committed_branch),
3042                                    committed_branch->txn->rev,
3043                                    scratch_pool);
3044 
3045   SVN_ERR(branch_diff_r(el_rev_left, el_rev_right,
3046                         show_subtree_diff, "   ",
3047                         scratch_pool));
3048   return SVN_NO_ERROR;
3049 }
3050 
3051 static svn_error_t *
commit(svn_revnum_t * new_rev_p,svnmover_wc_t * wc,apr_hash_t * revprops,apr_pool_t * scratch_pool)3052 commit(svn_revnum_t *new_rev_p,
3053        svnmover_wc_t *wc,
3054        apr_hash_t *revprops,
3055        apr_pool_t *scratch_pool)
3056 {
3057   if (svnmover_any_conflicts(wc->conflicts))
3058     {
3059       return svn_error_createf(SVN_BRANCH__ERR, NULL,
3060                                _("Cannot commit because there are "
3061                                  "unresolved conflicts"));
3062     }
3063 
3064   /* Complete the old edit drive (editing the WC working state) */
3065   SVN_ERR(svn_branch__txn_sequence_point(wc->edit_txn, scratch_pool));
3066 
3067   /* Just as in execute() the pool must be a subpool of wc->pool. */
3068   SVN_ERR(wc_commit(new_rev_p, wc, revprops, wc->pool));
3069 
3070   return SVN_NO_ERROR;
3071 }
3072 
3073 /* Commit.
3074  *
3075  * Set *NEW_REV_P to the committed revision number. Update the WC base of
3076  * each committed element to that revision.
3077  *
3078  * If there are no changes to commit, set *NEW_REV_P to SVN_INVALID_REVNUM
3079  * and do not make a commit.
3080  *
3081  * NEW_REV_P may be null if not wanted.
3082  */
3083 static svn_error_t *
do_commit(svn_revnum_t * new_rev_p,svnmover_wc_t * wc,apr_hash_t * revprops,apr_pool_t * scratch_pool)3084 do_commit(svn_revnum_t *new_rev_p,
3085           svnmover_wc_t *wc,
3086           apr_hash_t *revprops,
3087           apr_pool_t *scratch_pool)
3088 {
3089   svn_revnum_t new_rev;
3090 
3091   SVN_ERR(commit(&new_rev, wc, revprops, scratch_pool));
3092 
3093   if (new_rev_p)
3094     *new_rev_p = new_rev;
3095   return SVN_NO_ERROR;
3096 }
3097 
3098 /* Revert all uncommitted changes in WC.
3099  */
3100 static svn_error_t *
do_revert(svnmover_wc_t * wc,apr_pool_t * scratch_pool)3101 do_revert(svnmover_wc_t *wc,
3102           apr_pool_t *scratch_pool)
3103 {
3104   /* Replay the inverse of the current edit txn, into the current edit txn */
3105   SVN_ERR(replay(wc->edit_txn, wc->working->branch,
3106                  wc->working->branch,
3107                  wc->base->branch,
3108                  scratch_pool));
3109   wc->conflicts = NULL;
3110 
3111   return SVN_NO_ERROR;
3112 }
3113 
3114 /* Migration replay baton */
3115 typedef struct migrate_replay_baton_t {
3116   svn_branch__txn_t *edit_txn;
3117   svn_ra_session_t *from_session;
3118   /* Hash (by revnum) of array of svn_repos_move_info_t. */
3119   apr_hash_t *moves;
3120 } migrate_replay_baton_t;
3121 
3122 /* Callback function for svn_ra_replay_range, invoked when starting to parse
3123  * a replay report.
3124  */
3125 static svn_error_t *
migrate_replay_rev_started(svn_revnum_t revision,void * replay_baton,const svn_delta_editor_t ** editor,void ** edit_baton,apr_hash_t * rev_props,apr_pool_t * pool)3126 migrate_replay_rev_started(svn_revnum_t revision,
3127                            void *replay_baton,
3128                            const svn_delta_editor_t **editor,
3129                            void **edit_baton,
3130                            apr_hash_t *rev_props,
3131                            apr_pool_t *pool)
3132 {
3133   migrate_replay_baton_t *rb = replay_baton;
3134   const svn_delta_editor_t *old_editor;
3135   void *old_edit_baton;
3136 
3137   svnmover_notify("migrate: start r%ld", revision);
3138 
3139   SVN_ERR(svn_branch__compat_get_migration_editor(&old_editor, &old_edit_baton,
3140                                                   rb->edit_txn,
3141                                                   rb->from_session, revision,
3142                                                   pool));
3143   SVN_ERR(svn_delta__get_debug_editor(&old_editor, &old_edit_baton,
3144                                       old_editor, old_edit_baton,
3145                                       "migrate: ", pool));
3146 
3147   *editor = old_editor;
3148   *edit_baton = old_edit_baton;
3149 
3150   return SVN_NO_ERROR;
3151 }
3152 
3153 /* Callback function for svn_ra_replay_range, invoked when finishing parsing
3154  * a replay report.
3155  */
3156 static svn_error_t *
migrate_replay_rev_finished(svn_revnum_t revision,void * replay_baton,const svn_delta_editor_t * editor,void * edit_baton,apr_hash_t * rev_props,apr_pool_t * pool)3157 migrate_replay_rev_finished(svn_revnum_t revision,
3158                             void *replay_baton,
3159                             const svn_delta_editor_t *editor,
3160                             void *edit_baton,
3161                             apr_hash_t *rev_props,
3162                             apr_pool_t *pool)
3163 {
3164   migrate_replay_baton_t *rb = replay_baton;
3165   apr_array_header_t *moves_in_revision
3166     = apr_hash_get(rb->moves, &revision, sizeof(revision));
3167 
3168   SVN_ERR(editor->close_edit(edit_baton, pool));
3169 
3170   svnmover_notify("migrate: moves in revision r%ld:", revision);
3171 
3172   if (moves_in_revision)
3173     {
3174       int i;
3175 
3176       for (i = 0; i < moves_in_revision->nelts; i++)
3177         {
3178           svn_repos_move_info_t *this_move
3179             = APR_ARRAY_IDX(moves_in_revision, i, void *);
3180 
3181           if (this_move)
3182             {
3183               svnmover_notify("%s",
3184                      svn_client__format_move_chain_for_display(this_move,
3185                                                                "", pool));
3186             }
3187         }
3188     }
3189 
3190   return SVN_NO_ERROR;
3191 }
3192 
3193 /* Migrate changes from non-move-tracking revisions.
3194  */
3195 static svn_error_t *
do_migrate(svnmover_wc_t * wc,svn_revnum_t start_revision,svn_revnum_t end_revision,apr_pool_t * scratch_pool)3196 do_migrate(svnmover_wc_t *wc,
3197            svn_revnum_t start_revision,
3198            svn_revnum_t end_revision,
3199            apr_pool_t *scratch_pool)
3200 {
3201   migrate_replay_baton_t *rb = apr_pcalloc(scratch_pool, sizeof(*rb));
3202 
3203   if (start_revision < 1 || end_revision < 1
3204       || start_revision > end_revision
3205       || end_revision > wc->head_revision)
3206     {
3207       return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
3208                                _("migrate: Bad revision range (%ld to %ld); "
3209                                  "minimum is 1 and maximum (head) is %ld"),
3210                                start_revision, end_revision,
3211                                wc->head_revision);
3212     }
3213 
3214   /* Scan the repository log for move info */
3215   SVN_ERR(svn_client__get_repos_moves(&rb->moves,
3216                                       "" /*(unused)*/,
3217                                       wc->ra_session,
3218                                       start_revision, end_revision,
3219                                       wc->ctx, scratch_pool, scratch_pool));
3220 
3221   rb->edit_txn = wc->edit_txn;
3222   rb->from_session = wc->ra_session;
3223   SVN_ERR(svn_ra_replay_range(rb->from_session,
3224                               start_revision, end_revision,
3225                               0, TRUE,
3226                               migrate_replay_rev_started,
3227                               migrate_replay_rev_finished,
3228                               rb, scratch_pool));
3229   return SVN_NO_ERROR;
3230 }
3231 
3232 static svn_error_t *
show_branch_history(svn_branch__state_t * branch,apr_pool_t * scratch_pool)3233 show_branch_history(svn_branch__state_t *branch,
3234                     apr_pool_t *scratch_pool)
3235 {
3236   svn_branch__history_t *history;
3237   svn_branch__rev_bid_t *main_parent;
3238   apr_hash_index_t *hi;
3239 
3240   SVN_ERR(svn_branch__state_get_history(branch, &history, scratch_pool));
3241 
3242   SVN_ERR(find_branch_main_parent(branch, &main_parent, scratch_pool));
3243   if (main_parent)
3244     {
3245       if (strcmp(main_parent->bid, branch->bid) == 0)
3246         {
3247           svnmover_notify("  main parent: r%ld.%s",
3248                           main_parent->rev, main_parent->bid);
3249         }
3250       else
3251         {
3252           svnmover_notify("  main parent (branched from): r%ld.%s",
3253                           main_parent->rev, main_parent->bid);
3254         }
3255     }
3256   for (hi = apr_hash_first(scratch_pool, history->parents);
3257        hi; hi = apr_hash_next(hi))
3258     {
3259       svn_branch__rev_bid_t *parent = apr_hash_this_val(hi);
3260 
3261       if (! svn_branch__rev_bid_equal(parent, main_parent))
3262         {
3263           svnmover_notify("  other parent (complete merge): r%ld.%s",
3264                           parent->rev, parent->bid);
3265         }
3266     }
3267 
3268   return SVN_NO_ERROR;
3269 }
3270 
3271 /* Show info about element E.
3272  *
3273  * TODO: Show different info for a repo element versus a WC element.
3274  */
3275 static svn_error_t *
do_info(svnmover_wc_t * wc,svn_branch__el_rev_id_t * e,apr_pool_t * scratch_pool)3276 do_info(svnmover_wc_t *wc,
3277         svn_branch__el_rev_id_t *e,
3278         apr_pool_t *scratch_pool)
3279 {
3280   svnmover_notify("Element Id: %d%s",
3281                   e->eid,
3282                   is_branch_root_element(e->branch, e->eid)
3283                     ? " (branch root)" : "");
3284 
3285   /* Show WC info for a WC working element, or repo info for a repo element */
3286   if (e->rev == SVN_INVALID_REVNUM)
3287     {
3288       svn_branch__state_t *base_branch, *work_branch;
3289       svn_revnum_t base_rev;
3290       svn_element__content_t *e_base, *e_work;
3291       svn_boolean_t is_modified;
3292 
3293       base_branch = svn_branch__txn_get_branch_by_id(
3294                       wc->base->branch->txn, e->branch->bid, scratch_pool);
3295       work_branch = svn_branch__txn_get_branch_by_id(
3296                       wc->working->branch->txn, e->branch->bid, scratch_pool);
3297       base_rev = svnmover_wc_get_base_rev(wc, base_branch, e->eid, scratch_pool);
3298       SVN_ERR(svn_branch__state_get_element(base_branch, &e_base,
3299                                             e->eid, scratch_pool));
3300       SVN_ERR(svn_branch__state_get_element(work_branch, &e_work,
3301                                             e->eid, scratch_pool));
3302       is_modified = !svn_element__content_equal(e_base, e_work,
3303                                                 scratch_pool);
3304 
3305       svnmover_notify("Base Revision: %ld", base_rev);
3306       svnmover_notify("Base Branch:    %s", base_branch->bid);
3307       svnmover_notify("Working Branch: %s", work_branch->bid);
3308       svnmover_notify("Modified:       %s", is_modified ? "yes" : "no");
3309     }
3310   else
3311     {
3312       svnmover_notify("Revision: %ld", e->rev);
3313       svnmover_notify("Branch:    %s", e->branch->bid);
3314     }
3315 
3316   return SVN_NO_ERROR;
3317 }
3318 
3319 
3320 typedef struct arg_t
3321 {
3322   const char *path_name;
3323   svn_branch__el_rev_id_t *el_rev, *parent_el_rev;
3324 } arg_t;
3325 
3326 #define VERIFY_REV_SPECIFIED(op, i)                                     \
3327   if (arg[i]->el_rev->rev == SVN_INVALID_REVNUM)                        \
3328     return svn_error_createf(SVN_BRANCH__ERR, NULL,                   \
3329                              _("%s: '%s': revision number required"),   \
3330                              op, action->relpath[i]);
3331 
3332 #define VERIFY_REV_UNSPECIFIED(op, i)                                   \
3333   if (arg[i]->el_rev->rev != SVN_INVALID_REVNUM)                        \
3334     return svn_error_createf(SVN_BRANCH__ERR, NULL,                   \
3335                              _("%s: '%s@...': revision number not allowed"), \
3336                              op, action->relpath[i]);
3337 
3338 #define VERIFY_EID_NONEXISTENT(op, i)                                   \
3339   if (arg[i]->el_rev->eid != -1)                                        \
3340     return svn_error_createf(SVN_BRANCH__ERR, NULL,                   \
3341                              _("%s: Element already exists at path '%s'"), \
3342                              op, action->relpath[i]);
3343 
3344 #define VERIFY_EID_EXISTS(op, i)                                        \
3345   if (arg[i]->el_rev->eid == -1)                                        \
3346     return svn_error_createf(SVN_BRANCH__ERR, NULL,                   \
3347                              _("%s: Element not found at path '%s%s'"), \
3348                              op, action->relpath[i],                    \
3349                              action->rev_spec[i].kind == svn_opt_revision_unspecified \
3350                                ? "" : "@...");
3351 
3352 #define VERIFY_PARENT_EID_EXISTS(op, i)                                 \
3353   if (arg[i]->parent_el_rev->eid == -1)                                 \
3354     return svn_error_createf(SVN_BRANCH__ERR, NULL,                   \
3355                              _("%s: Element not found at path '%s'"),   \
3356                              op, svn_relpath_dirname(action->relpath[i], pool));
3357 
3358 #define VERIFY_NOT_CHILD_OF_SELF(op, i, j, pool)                        \
3359   if (svn_relpath_skip_ancestor(                                        \
3360         svn_branch__get_rrpath_by_eid(arg[i]->el_rev->branch,           \
3361                                       arg[i]->el_rev->eid, pool),       \
3362         svn_branch__get_rrpath_by_eid(arg[j]->parent_el_rev->branch,    \
3363                                       arg[j]->parent_el_rev->eid, pool))) \
3364     return svn_error_createf(SVN_BRANCH__ERR, NULL,                   \
3365                              _("%s: The specified target is nested "    \
3366                                "inside the source"), op);
3367 
3368 /* If EL_REV specifies the root element of a nested branch, change EL_REV
3369  * to specify the corresponding subbranch-root element of its outer branch.
3370  *
3371  * If EL_REV specifies the root element of a top-level branch, return an
3372  * error.
3373  */
3374 static svn_error_t *
point_to_outer_element_instead(svn_branch__el_rev_id_t * el_rev,const char * op,apr_pool_t * scratch_pool)3375 point_to_outer_element_instead(svn_branch__el_rev_id_t *el_rev,
3376                                const char *op,
3377                                apr_pool_t *scratch_pool)
3378 {
3379   if (is_branch_root_element(el_rev->branch, el_rev->eid))
3380     {
3381       svn_branch__state_t *outer_branch;
3382       int outer_eid;
3383 
3384       svn_branch__get_outer_branch_and_eid(&outer_branch, &outer_eid,
3385                                            el_rev->branch, scratch_pool);
3386 
3387       if (! outer_branch)
3388         return svn_error_createf(SVN_BRANCH__ERR, NULL, "%s: %s", op,
3389                                  _("svnmover cannot delete or move a "
3390                                    "top-level branch"));
3391 
3392       el_rev->eid = outer_eid;
3393       el_rev->branch = outer_branch;
3394     }
3395 
3396   return SVN_NO_ERROR;
3397 }
3398 
3399 static svn_error_t *
execute(svnmover_wc_t * wc,const apr_array_header_t * actions,const char * anchor_url,apr_hash_t * revprops,svn_client_ctx_t * ctx,apr_pool_t * pool)3400 execute(svnmover_wc_t *wc,
3401         const apr_array_header_t *actions,
3402         const char *anchor_url,
3403         apr_hash_t *revprops,
3404         svn_client_ctx_t *ctx,
3405         apr_pool_t *pool)
3406 {
3407   const char *base_relpath;
3408   apr_pool_t *iterpool = svn_pool_create(pool);
3409   int i;
3410 
3411   base_relpath = svn_uri_skip_ancestor(wc->repos_root_url, anchor_url, pool);
3412 
3413   for (i = 0; i < actions->nelts; ++i)
3414     {
3415       action_t *action = APR_ARRAY_IDX(actions, i, action_t *);
3416       int j;
3417       arg_t *arg[3] = { NULL, NULL, NULL };
3418 
3419       svn_pool_clear(iterpool);
3420 
3421       /* Before translating paths to/from elements, need a sequence point */
3422       SVN_ERR(svn_branch__txn_sequence_point(wc->edit_txn, iterpool));
3423 
3424       /* Convert each ACTION[j].{relpath, rev_spec} to
3425          (EL_REV[j], PARENT_EL_REV[j], PATH_NAME[j], REVNUM[j]),
3426          except for the local-path argument of a 'put' command. */
3427       for (j = 0; j < 3; j++)
3428         {
3429           if (action->relpath[j]
3430               && ! (action->action == ACTION_PUT_FILE && j == 0))
3431             {
3432               const char *rrpath, *parent_rrpath;
3433 
3434               arg[j] = apr_palloc(iterpool, sizeof(*arg[j]));
3435 
3436               rrpath = svn_relpath_join(base_relpath, action->relpath[j], iterpool);
3437               parent_rrpath = svn_relpath_dirname(rrpath, iterpool);
3438 
3439               arg[j]->path_name = svn_relpath_basename(rrpath, NULL);
3440               SVN_ERR(find_el_rev_by_rrpath_rev(&arg[j]->el_rev, wc,
3441                                                 &action->rev_spec[j],
3442                                                 action->branch_id[j],
3443                                                 rrpath,
3444                                                 iterpool, iterpool));
3445               SVN_ERR(find_el_rev_by_rrpath_rev(&arg[j]->parent_el_rev, wc,
3446                                                 &action->rev_spec[j],
3447                                                 action->branch_id[j],
3448                                                 parent_rrpath,
3449                                                 iterpool, iterpool));
3450             }
3451         }
3452 
3453       switch (action->action)
3454         {
3455         case ACTION_INFO_WC:
3456           {
3457             svn_boolean_t is_modified;
3458             svn_revnum_t base_rev_min, base_rev_max;
3459 
3460             SVN_ERR(txn_is_changed(wc->working->branch->txn, &is_modified,
3461                                    iterpool));
3462             SVN_ERR(svnmover_wc_get_base_revs(wc, &base_rev_min, &base_rev_max,
3463                                               iterpool));
3464 
3465             svnmover_notify("Repository Root: %s", wc->repos_root_url);
3466             if (base_rev_min == base_rev_max)
3467               svnmover_notify("Base Revision: %ld", base_rev_min);
3468             else
3469               svnmover_notify("Base Revisions: %ld to %ld",
3470                               base_rev_min, base_rev_max);
3471             svnmover_notify("Base Branch:    %s", wc->base->branch->bid);
3472             svnmover_notify("Working Branch: %s", wc->working->branch->bid);
3473             SVN_ERR(show_branch_history(wc->working->branch, iterpool));
3474             svnmover_notify("Modified:       %s", is_modified ? "yes" : "no");
3475           }
3476           break;
3477 
3478         case ACTION_INFO:
3479           VERIFY_EID_EXISTS("info", 0);
3480           {
3481             /* If it's a nested branch root, show info for the outer element
3482                first, and then for the inner element. */
3483             if (is_branch_root_element(arg[0]->el_rev->branch,
3484                                        arg[0]->el_rev->eid))
3485               {
3486                 svn_branch__state_t *outer_branch;
3487                 int outer_eid;
3488 
3489                 svn_branch__get_outer_branch_and_eid(&outer_branch, &outer_eid,
3490                                                      arg[0]->el_rev->branch,
3491                                                      iterpool);
3492                 if (outer_branch)
3493                   {
3494                     svn_branch__el_rev_id_t *outer_e
3495                       = svn_branch__el_rev_id_create(outer_branch, outer_eid,
3496                                                      arg[0]->el_rev->rev,
3497                                                      iterpool);
3498                     SVN_ERR(do_info(wc, outer_e, iterpool));
3499                   }
3500               }
3501             SVN_ERR(do_info(wc, arg[0]->el_rev, iterpool));
3502           }
3503           break;
3504 
3505         case ACTION_LIST_CONFLICTS:
3506           {
3507             if (svnmover_any_conflicts(wc->conflicts))
3508               {
3509                 SVN_ERR(svnmover_display_conflicts(wc->conflicts, iterpool));
3510               }
3511           }
3512           break;
3513 
3514         case ACTION_RESOLVED_CONFLICT:
3515           {
3516             if (svnmover_any_conflicts(wc->conflicts))
3517               {
3518                 SVN_ERR(svnmover_conflict_resolved(wc->conflicts,
3519                                                    action->relpath[0],
3520                                                    iterpool));
3521               }
3522             else
3523               {
3524                 return svn_error_create(SVN_BRANCH__ERR, NULL,
3525                                         _("No conflicts are currently flagged"));
3526               }
3527           }
3528           break;
3529 
3530         case ACTION_DIFF:
3531           VERIFY_EID_EXISTS("diff", 0);
3532           VERIFY_EID_EXISTS("diff", 1);
3533           {
3534             SVN_ERR(branch_diff_r(arg[0]->el_rev /*from*/,
3535                                   arg[1]->el_rev /*to*/,
3536                                   show_subtree_diff, "",
3537                                   iterpool));
3538           }
3539           break;
3540 
3541         case ACTION_STATUS:
3542           {
3543             svn_branch__el_rev_id_t *from, *to;
3544 
3545             from = svn_branch__el_rev_id_create(wc->base->branch,
3546                                                 svn_branch__root_eid(wc->base->branch),
3547                                                 SVN_INVALID_REVNUM, iterpool);
3548             to = svn_branch__el_rev_id_create(wc->working->branch,
3549                                               svn_branch__root_eid(wc->working->branch),
3550                                               SVN_INVALID_REVNUM, iterpool);
3551             SVN_ERR(branch_diff_r(from, to,
3552                                   show_subtree_diff, "",
3553                                   iterpool));
3554           }
3555           break;
3556 
3557         case ACTION_LOG:
3558           VERIFY_EID_EXISTS("log", 0);
3559           VERIFY_EID_EXISTS("log", 1);
3560           {
3561             SVN_ERR(do_log(arg[0]->el_rev /*from*/,
3562                            arg[1]->el_rev /*to*/,
3563                            iterpool));
3564             }
3565           break;
3566 
3567         case ACTION_LIST_BRANCHES:
3568           {
3569             VERIFY_EID_EXISTS("branches", 0);
3570             if (the_ui_mode == UI_MODE_PATHS)
3571               {
3572                 svnmover_notify_v("branches rooted at same element as '%s':",
3573                                   action->relpath[0]);
3574               }
3575             else
3576               {
3577                 svnmover_notify_v("branches rooted at e%d:",
3578                                   arg[0]->el_rev->eid);
3579               }
3580             SVN_ERR(list_branches(
3581                       arg[0]->el_rev->branch->txn,
3582                       arg[0]->el_rev->eid,
3583                       FALSE, iterpool));
3584           }
3585           break;
3586 
3587         case ACTION_LIST_BRANCHES_R:
3588           {
3589             if (the_ui_mode == UI_MODE_SERIAL)
3590               {
3591                 svn_stream_t *stream;
3592                 SVN_ERR(svn_stream_for_stdout(&stream, iterpool));
3593                 SVN_ERR(svn_branch__txn_serialize(wc->working->branch->txn,
3594                           stream,
3595                           iterpool));
3596               }
3597             else
3598               {
3599                 /* Note: BASE_REVISION is always a real revision number, here */
3600                 SVN_ERR(list_all_branches(wc->working->branch->txn, TRUE,
3601                                           iterpool));
3602               }
3603           }
3604           break;
3605 
3606         case ACTION_LS:
3607           {
3608             VERIFY_EID_EXISTS("ls", 0);
3609             if (the_ui_mode == UI_MODE_PATHS)
3610               {
3611                 SVN_ERR(list_branch_elements(arg[0]->el_rev->branch, iterpool));
3612               }
3613             else if (the_ui_mode == UI_MODE_EIDS)
3614               {
3615                 SVN_ERR(list_branch_elements_by_eid(arg[0]->el_rev->branch,
3616                                                     iterpool));
3617               }
3618             else
3619               {
3620                 svn_stream_t *stream;
3621                 SVN_ERR(svn_stream_for_stdout(&stream, iterpool));
3622                 SVN_ERR(svn_branch__state_serialize(stream,
3623                                                     arg[0]->el_rev->branch,
3624                                                     iterpool));
3625               }
3626           }
3627           break;
3628 
3629         case ACTION_TBRANCH:
3630           VERIFY_EID_EXISTS("tbranch", 0);
3631           {
3632             const char *from_branch_id = svn_branch__get_id(arg[0]->el_rev->branch,
3633                                                             iterpool);
3634             svn_branch__rev_bid_eid_t *from
3635               = svn_branch__rev_bid_eid_create(arg[0]->el_rev->rev, from_branch_id,
3636                                                arg[0]->el_rev->eid, iterpool);
3637             svn_branch__state_t *new_branch;
3638 
3639             SVN_ERR(do_topbranch(&new_branch, wc->edit_txn,
3640                                  from,
3641                                  iterpool, iterpool));
3642             /* Switch the WC working state to this new branch */
3643             wc->working->branch = new_branch;
3644           }
3645           break;
3646 
3647         case ACTION_BRANCH:
3648           VERIFY_EID_EXISTS("branch", 0);
3649           VERIFY_REV_UNSPECIFIED("branch", 1);
3650           VERIFY_EID_NONEXISTENT("branch", 1);
3651           VERIFY_PARENT_EID_EXISTS("branch", 1);
3652           {
3653             const char *from_branch_id = svn_branch__get_id(arg[0]->el_rev->branch,
3654                                                             iterpool);
3655             svn_branch__rev_bid_eid_t *from
3656               = svn_branch__rev_bid_eid_create(arg[0]->el_rev->rev, from_branch_id,
3657                                                arg[0]->el_rev->eid, iterpool);
3658             svn_branch__state_t *new_branch;
3659 
3660             SVN_ERR(do_branch(&new_branch, wc->edit_txn,
3661                               from,
3662                               arg[1]->el_rev->branch, arg[1]->parent_el_rev->eid,
3663                               arg[1]->path_name,
3664                               iterpool, iterpool));
3665           }
3666           break;
3667 
3668         case ACTION_BRANCH_INTO:
3669           VERIFY_EID_EXISTS("branch-into", 0);
3670           VERIFY_REV_UNSPECIFIED("branch-into", 1);
3671           VERIFY_EID_NONEXISTENT("branch-into", 1);
3672           VERIFY_PARENT_EID_EXISTS("branch-into", 1);
3673           {
3674             SVN_ERR(do_branch_into(arg[0]->el_rev->branch, arg[0]->el_rev->eid,
3675                                    arg[1]->el_rev->branch,
3676                                    arg[1]->parent_el_rev->eid, arg[1]->path_name,
3677                                    iterpool));
3678           }
3679           break;
3680 
3681         case ACTION_MKBRANCH:
3682           VERIFY_REV_UNSPECIFIED("mkbranch", 0);
3683           VERIFY_EID_NONEXISTENT("mkbranch", 0);
3684           VERIFY_PARENT_EID_EXISTS("mkbranch", 0);
3685           {
3686             apr_hash_t *props = apr_hash_make(iterpool);
3687             svn_element__payload_t *payload
3688               = svn_element__payload_create_dir(props, iterpool);
3689 
3690             SVN_ERR(do_mkbranch(NULL, wc->edit_txn,
3691                                 arg[0]->parent_el_rev->branch,
3692                                 arg[0]->parent_el_rev->eid, arg[0]->path_name,
3693                                 payload, iterpool));
3694           }
3695           break;
3696 
3697         case ACTION_MERGE3:
3698           {
3699             VERIFY_EID_EXISTS("merge", 0);
3700             VERIFY_EID_EXISTS("merge", 1);
3701             VERIFY_REV_UNSPECIFIED("merge", 1);
3702             VERIFY_EID_EXISTS("merge", 2);
3703 
3704             SVN_ERR(do_merge(wc,
3705                              arg[0]->el_rev /*from*/,
3706                              arg[1]->el_rev /*to*/,
3707                              arg[2]->el_rev /*yca*/,
3708                              iterpool));
3709           }
3710           break;
3711 
3712         case ACTION_AUTO_MERGE:
3713           {
3714             VERIFY_EID_EXISTS("merge", 0);
3715             VERIFY_EID_EXISTS("merge", 1);
3716             VERIFY_REV_UNSPECIFIED("merge", 1);
3717 
3718             SVN_ERR(do_auto_merge(wc,
3719                                   arg[0]->el_rev /*from*/,
3720                                   arg[1]->el_rev /*to*/,
3721                                   iterpool));
3722           }
3723           break;
3724 
3725         case ACTION_MV:
3726           SVN_ERR(point_to_outer_element_instead(arg[0]->el_rev, "mv",
3727                                                  iterpool));
3728 
3729           VERIFY_REV_UNSPECIFIED("mv", 0);
3730           VERIFY_EID_EXISTS("mv", 0);
3731           VERIFY_REV_UNSPECIFIED("mv", 1);
3732           VERIFY_EID_NONEXISTENT("mv", 1);
3733           VERIFY_PARENT_EID_EXISTS("mv", 1);
3734           VERIFY_NOT_CHILD_OF_SELF("mv", 0, 1, iterpool);
3735 
3736           /* Simple move/rename within same branch, if possible */
3737           if (BRANCH_IS_SAME_BRANCH(arg[1]->parent_el_rev->branch,
3738                                     arg[0]->el_rev->branch,
3739                                     iterpool))
3740             {
3741               SVN_ERR(do_move(arg[0]->el_rev,
3742                               arg[1]->parent_el_rev, arg[1]->path_name,
3743                               iterpool));
3744             }
3745           else
3746             {
3747               SVN_ERR(do_interactive_cross_branch_move(wc->edit_txn,
3748                                                        arg[0]->el_rev,
3749                                                        arg[1]->parent_el_rev,
3750                                                        arg[1]->path_name,
3751                                                        iterpool));
3752             }
3753           break;
3754 
3755         case ACTION_CP:
3756           VERIFY_REV_SPECIFIED("cp", 0);
3757             /* (Or do we want to support copying from "this txn" too?) */
3758           VERIFY_EID_EXISTS("cp", 0);
3759           VERIFY_REV_UNSPECIFIED("cp", 1);
3760           VERIFY_EID_NONEXISTENT("cp", 1);
3761           VERIFY_PARENT_EID_EXISTS("cp", 1);
3762           SVN_ERR(do_copy(arg[0]->el_rev,
3763                           arg[1]->parent_el_rev->branch,
3764                           arg[1]->parent_el_rev->eid, arg[1]->path_name,
3765                           iterpool));
3766           break;
3767 
3768         case ACTION_RM:
3769           SVN_ERR(point_to_outer_element_instead(arg[0]->el_rev, "rm",
3770                                                  iterpool));
3771 
3772           VERIFY_REV_UNSPECIFIED("rm", 0);
3773           VERIFY_EID_EXISTS("rm", 0);
3774           SVN_ERR(do_delete(arg[0]->el_rev->branch, arg[0]->el_rev->eid,
3775                             iterpool));
3776           break;
3777 
3778         case ACTION_CP_RM:
3779           SVN_ERR(point_to_outer_element_instead(arg[0]->el_rev,
3780                                                  "copy-and-delete", iterpool));
3781 
3782           VERIFY_REV_UNSPECIFIED("copy-and-delete", 0);
3783           VERIFY_EID_EXISTS("copy-and-delete", 0);
3784           VERIFY_REV_UNSPECIFIED("copy-and-delete", 1);
3785           VERIFY_EID_NONEXISTENT("copy-and-delete", 1);
3786           VERIFY_PARENT_EID_EXISTS("copy-and-delete", 1);
3787           VERIFY_NOT_CHILD_OF_SELF("copy-and-delete", 0, 1, iterpool);
3788 
3789           SVN_ERR(do_copy_and_delete(arg[0]->el_rev,
3790                                      arg[1]->parent_el_rev->branch,
3791                                      arg[1]->parent_el_rev->eid,
3792                                      arg[1]->path_name,
3793                                      iterpool));
3794           break;
3795 
3796         case ACTION_BR_RM:
3797           SVN_ERR(point_to_outer_element_instead(arg[0]->el_rev,
3798                                                  "branch-and-delete",
3799                                                  iterpool));
3800 
3801           VERIFY_REV_UNSPECIFIED("branch-and-delete", 0);
3802           VERIFY_EID_EXISTS("branch-and-delete", 0);
3803           VERIFY_REV_UNSPECIFIED("branch-and-delete", 1);
3804           VERIFY_EID_NONEXISTENT("branch-and-delete", 1);
3805           VERIFY_PARENT_EID_EXISTS("branch-and-delete", 1);
3806           VERIFY_NOT_CHILD_OF_SELF("branch-and-delete", 0, 1, iterpool);
3807 
3808           SVN_ERR(do_branch_and_delete(wc->edit_txn,
3809                                        arg[0]->el_rev,
3810                                        arg[1]->parent_el_rev->branch,
3811                                        arg[1]->parent_el_rev->eid,
3812                                        arg[1]->path_name,
3813                                        iterpool));
3814           break;
3815 
3816         case ACTION_BR_INTO_RM:
3817           SVN_ERR(point_to_outer_element_instead(arg[0]->el_rev,
3818                                                  "branch-into-and-delete",
3819                                                  iterpool));
3820 
3821           VERIFY_REV_UNSPECIFIED("branch-into-and-delete", 0);
3822           VERIFY_EID_EXISTS("branch-into-and-delete", 0);
3823           VERIFY_REV_UNSPECIFIED("branch-into-and-delete", 1);
3824           VERIFY_EID_NONEXISTENT("branch-into-and-delete", 1);
3825           VERIFY_PARENT_EID_EXISTS("branch-into-and-delete", 1);
3826           VERIFY_NOT_CHILD_OF_SELF("branch-into-and-delete", 0, 1, iterpool);
3827 
3828           SVN_ERR(do_branch_into_and_delete(arg[0]->el_rev,
3829                                             arg[1]->parent_el_rev->branch,
3830                                             arg[1]->parent_el_rev->eid,
3831                                             arg[1]->path_name,
3832                                             iterpool));
3833           break;
3834 
3835         case ACTION_MKDIR:
3836           VERIFY_REV_UNSPECIFIED("mkdir", 0);
3837           VERIFY_EID_NONEXISTENT("mkdir", 0);
3838           VERIFY_PARENT_EID_EXISTS("mkdir", 0);
3839           SVN_ERR(do_mkdir(wc->edit_txn,
3840                            arg[0]->parent_el_rev->branch,
3841                            arg[0]->parent_el_rev->eid, arg[0]->path_name,
3842                            iterpool));
3843           break;
3844 
3845         case ACTION_PUT_FILE:
3846           VERIFY_REV_UNSPECIFIED("put", 1);
3847           VERIFY_PARENT_EID_EXISTS("put", 1);
3848           SVN_ERR(do_put_file(wc->edit_txn,
3849                               action->relpath[0],
3850                               arg[1]->el_rev,
3851                               arg[1]->parent_el_rev,
3852                               arg[1]->path_name,
3853                               iterpool));
3854           break;
3855 
3856         case ACTION_CAT:
3857           VERIFY_EID_EXISTS("rm", 0);
3858           SVN_ERR(do_cat(arg[0]->el_rev,
3859                          iterpool));
3860           break;
3861 
3862         case ACTION_COMMIT:
3863           {
3864             svn_revnum_t new_rev;
3865 
3866             SVN_ERR(do_commit(&new_rev, wc, revprops, iterpool));
3867             if (! SVN_IS_VALID_REVNUM(new_rev))
3868               {
3869                 svnmover_notify_v("There are no changes to commit.");
3870               }
3871           }
3872           break;
3873 
3874         case ACTION_UPDATE:
3875           /* ### If current WC branch doesn't exist in target rev, should
3876              'update' follow to a different branch? By following merge graph?
3877              Presently it would try to update to a state of nonexistence. */
3878           /* path (or eid) is currently required for syntax, but ignored */
3879           VERIFY_EID_EXISTS("update", 0);
3880           /* We require a rev to be specified because an unspecified rev
3881              currently always means 'working version', whereas we would
3882              want it to mean 'head' for this subcommand. */
3883           VERIFY_REV_SPECIFIED("update", 0);
3884           {
3885             SVN_ERR(do_switch(wc, arg[0]->el_rev->rev, wc->base->branch,
3886                               iterpool));
3887           }
3888           break;
3889 
3890         case ACTION_SWITCH:
3891           VERIFY_EID_EXISTS("switch", 0);
3892           {
3893             SVN_ERR(do_switch(wc, arg[0]->el_rev->rev, arg[0]->el_rev->branch,
3894                               iterpool));
3895           }
3896           break;
3897 
3898         case ACTION_REVERT:
3899           {
3900             SVN_ERR(do_revert(wc, iterpool));
3901           }
3902           break;
3903 
3904         case ACTION_MIGRATE:
3905           /* path (or eid) is currently required for syntax, but ignored */
3906           VERIFY_EID_EXISTS("migrate", 0);
3907           VERIFY_REV_SPECIFIED("migrate", 0);
3908           {
3909             SVN_ERR(do_migrate(wc,
3910                                arg[0]->el_rev->rev, arg[0]->el_rev->rev,
3911                                iterpool));
3912           }
3913           break;
3914 
3915         default:
3916           SVN_ERR_MALFUNCTION();
3917         }
3918 
3919       if (action->action != ACTION_COMMIT)
3920         {
3921           wc->list_of_commands
3922             = apr_psprintf(pool, "%s%s\n",
3923                            wc->list_of_commands ? wc->list_of_commands : "",
3924                            svn_cstring_join2(action->action_args, " ",
3925                                              TRUE, pool));
3926         }
3927     }
3928   svn_pool_destroy(iterpool);
3929   return SVN_NO_ERROR;
3930 }
3931 
3932 /* Perform the typical suite of manipulations for user-provided URLs
3933    on URL, returning the result (allocated from POOL): IRI-to-URI
3934    conversion, auto-escaping, and canonicalization. */
3935 static const char *
sanitize_url(const char * url,apr_pool_t * pool)3936 sanitize_url(const char *url,
3937              apr_pool_t *pool)
3938 {
3939   url = svn_path_uri_from_iri(url, pool);
3940   url = svn_path_uri_autoescape(url, pool);
3941   return svn_uri_canonicalize(url, pool);
3942 }
3943 
3944 static const char *
help_for_subcommand(const action_defn_t * action,apr_pool_t * pool)3945 help_for_subcommand(const action_defn_t *action, apr_pool_t *pool)
3946 {
3947   const char *cmd = apr_psprintf(pool, "%s %s",
3948                                  action->name, action->args_help);
3949 
3950   return apr_psprintf(pool, "  %-22s : %s\n", cmd, action->help);
3951 }
3952 
3953 /* Print a usage message on STREAM, listing only the actions. */
3954 static void
usage_actions_only(FILE * stream,apr_pool_t * pool)3955 usage_actions_only(FILE *stream, apr_pool_t *pool)
3956 {
3957   int i;
3958 
3959   for (i = 0; i < sizeof (action_defn) / sizeof (action_defn[0]); i++)
3960     svn_error_clear(svn_cmdline_fputs(
3961                       help_for_subcommand(&action_defn[i], pool),
3962                       stream, pool));
3963 }
3964 
3965 /* Print a usage message on STREAM. */
3966 static void
usage(FILE * stream,apr_pool_t * pool)3967 usage(FILE *stream, apr_pool_t *pool)
3968 {
3969   svn_error_clear(svn_cmdline_fputs(
3970     _("usage: svnmover -U REPO_URL [ACTION...]\n"
3971       "A client for experimenting with move tracking.\n"
3972       "\n"
3973       "  Commit a batch of ACTIONs to a Subversion repository, as a single\n"
3974       "  new revision.  With no ACTIONs specified, read actions interactively\n"
3975       "  from standard input, until EOF or ^C, and then commit the result.\n"
3976       "\n"
3977       "  Action arguments are of the form\n"
3978       "    [^B<branch-id>/]<path>[@<revnum>]\n"
3979       "  where\n"
3980       "    <branch-id> defaults to the working branch or, when <revnum> is\n"
3981       "                given, to the base branch\n"
3982       "    <path>      is a path relative to the branch\n"
3983       "    <revnum>    is the revision number, when making a historic reference\n"
3984       "\n"
3985       "  Move tracking metadata is stored in the repository, in on-disk files\n"
3986       "  for RA-local or in revprops otherwise.\n"
3987       "\n"
3988       "Actions:\n"),
3989                   stream, pool));
3990   usage_actions_only(stream, pool);
3991   svn_error_clear(svn_cmdline_fputs(
3992     _("\n"
3993       "Valid options:\n"
3994       "  --ui={eids|e|paths|p}  : display information as elements or as paths\n"
3995       "  --colo[u]r={always|never|auto}\n"
3996       "                         : use coloured output; 'auto' means when standard\n"
3997       "                           output goes to a terminal; default: never\n"
3998       "  -h, -? [--help]        : display this text\n"
3999       "  -v [--verbose]         : display debugging messages\n"
4000       "  -q [--quiet]           : suppress notifications\n"
4001       "  -m [--message] ARG     : use ARG as a log message\n"
4002       "  -F [--file] ARG        : read log message from file ARG\n"
4003       "  -u [--username] ARG    : commit the changes as username ARG\n"
4004       "  -p [--password] ARG    : use ARG as the password\n"
4005       "  -U [--root-url] ARG    : interpret all action URLs relative to ARG\n"
4006       "  -r [--revision] ARG    : use revision ARG as baseline for changes\n"
4007       "  -B [--branch-id] ARG   : work on the branch identified by ARG\n"
4008       "  --with-revprop ARG     : set revision property in the following format:\n"
4009       "                               NAME[=VALUE]\n"
4010       "  --non-interactive      : do no interactive prompting (default is to\n"
4011       "                           prompt only if standard input is a terminal)\n"
4012       "  --force-interactive    : do interactive prompting even if standard\n"
4013       "                           input is not a terminal\n"
4014       "  --trust-server-cert    : accept SSL server certificates from unknown\n"
4015       "                           certificate authorities without prompting (but\n"
4016       "                           only with '--non-interactive')\n"
4017       "  -X [--extra-args] ARG  : append arguments from file ARG (one per line;\n"
4018       "                           use \"-\" to read from standard input)\n"
4019       "  --config-dir ARG       : use ARG to override the config directory\n"
4020       "  --config-option ARG    : use ARG to override a configuration option\n"
4021       "  --no-auth-cache        : do not cache authentication tokens\n"
4022       "  --version              : print version information\n"),
4023                   stream, pool));
4024 }
4025 
4026 static svn_error_t *
insufficient(int i,apr_pool_t * pool)4027 insufficient(int i, apr_pool_t *pool)
4028 {
4029   return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
4030                            "insufficient arguments:\n"
4031                            "%s",
4032                            help_for_subcommand(&action_defn[i], pool));
4033 }
4034 
4035 static svn_error_t *
display_version(apr_getopt_t * os,svn_boolean_t _quiet,apr_pool_t * pool)4036 display_version(apr_getopt_t *os, svn_boolean_t _quiet, apr_pool_t *pool)
4037 {
4038   const char *ra_desc_start
4039     = "The following repository access (RA) modules are available:\n\n";
4040   svn_stringbuf_t *version_footer;
4041 
4042   version_footer = svn_stringbuf_create(ra_desc_start, pool);
4043   SVN_ERR(svn_ra_print_modules(version_footer, pool));
4044 
4045   SVN_ERR(svn_opt_print_help5(NULL, "svnmover", TRUE, _quiet, FALSE,
4046                               version_footer->data,
4047                               NULL, NULL, NULL, NULL, NULL, pool));
4048 
4049   return SVN_NO_ERROR;
4050 }
4051 
4052 /* Return an error about the mutual exclusivity of the -m, -F, and
4053    --with-revprop=svn:log command-line options. */
4054 static svn_error_t *
mutually_exclusive_logs_error(void)4055 mutually_exclusive_logs_error(void)
4056 {
4057   return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
4058                           _("--message (-m), --file (-F), and "
4059                             "--with-revprop=svn:log are mutually "
4060                             "exclusive"));
4061 }
4062 
4063 /* Obtain the log message from multiple sources, producing an error
4064    if there are multiple sources. Store the result in *FINAL_MESSAGE.  */
4065 static svn_error_t *
get_log_message(const char ** final_message,const char * message,apr_hash_t * revprops,svn_stringbuf_t * filedata,apr_pool_t * result_pool,apr_pool_t * scratch_pool)4066 get_log_message(const char **final_message,
4067                 const char *message,
4068                 apr_hash_t *revprops,
4069                 svn_stringbuf_t *filedata,
4070                 apr_pool_t *result_pool,
4071                 apr_pool_t *scratch_pool)
4072 {
4073   svn_string_t *msg;
4074 
4075   *final_message = NULL;
4076   /* If we already have a log message in the revprop hash, then just
4077      make sure the user didn't try to also use -m or -F.  Otherwise,
4078      we need to consult -m or -F to find a log message, if any. */
4079   msg = svn_hash_gets(revprops, SVN_PROP_REVISION_LOG);
4080   if (msg)
4081     {
4082       if (filedata || message)
4083         return mutually_exclusive_logs_error();
4084 
4085       /* Remove it from the revprops; it will be re-added later */
4086       svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, NULL);
4087     }
4088   else if (filedata)
4089     {
4090       if (message)
4091         return mutually_exclusive_logs_error();
4092 
4093       msg = svn_string_create(filedata->data, scratch_pool);
4094     }
4095   else if (message)
4096     {
4097       msg = svn_string_create(message, scratch_pool);
4098     }
4099 
4100   if (msg)
4101     {
4102       SVN_ERR_W(svn_subst_translate_string2(&msg, NULL, NULL,
4103                                             msg, NULL, FALSE,
4104                                             result_pool, scratch_pool),
4105                 _("Error normalizing log message to internal format"));
4106 
4107       *final_message = msg->data;
4108     }
4109 
4110   return SVN_NO_ERROR;
4111 }
4112 
4113 static const char *const special_commands[] =
4114 {
4115   "help",
4116   "--verbose",
4117   "--ui=paths", "--ui=eids", "--ui=serial",
4118 };
4119 
4120 /* Parse the action arguments into action structures. */
4121 static svn_error_t *
parse_actions(apr_array_header_t ** actions,apr_array_header_t * action_args,apr_pool_t * pool)4122 parse_actions(apr_array_header_t **actions,
4123               apr_array_header_t *action_args,
4124               apr_pool_t *pool)
4125 {
4126   int i;
4127 
4128   *actions = apr_array_make(pool, 1, sizeof(action_t *));
4129 
4130   for (i = 0; i < action_args->nelts; ++i)
4131     {
4132       int j, k, num_url_args;
4133       const char *action_string = APR_ARRAY_IDX(action_args, i, const char *);
4134       action_t *action = apr_pcalloc(pool, sizeof(*action));
4135       const char *cp_from_rev = NULL;
4136 
4137       /* First, parse the action. Handle some special actions immediately;
4138          handle normal subcommands by looking them up in the table. */
4139       if (! strcmp(action_string, "?") || ! strcmp(action_string, "h")
4140           || ! strcmp(action_string, "help"))
4141         {
4142           usage_actions_only(stdout, pool);
4143           return SVN_NO_ERROR;
4144         }
4145       if (! strncmp(action_string, "--ui=", 5))
4146         {
4147           SVN_ERR(svn_token__from_word_err(&the_ui_mode, ui_mode_map,
4148                                            action_string + 5));
4149           continue;
4150         }
4151       if (! strcmp(action_string, "--verbose")
4152           || ! strcmp(action_string, "-v"))
4153         {
4154           quiet = !quiet;
4155           svnmover_notify("verbose mode %s", quiet ? "off" : "on");
4156           continue;
4157         }
4158       for (j = 0; j < sizeof(action_defn) / sizeof(action_defn[0]); j++)
4159         {
4160           if (strcmp(action_string, action_defn[j].name) == 0)
4161             {
4162               action->action = action_defn[j].code;
4163               num_url_args = action_defn[j].num_args;
4164               break;
4165             }
4166         }
4167       if (j == sizeof(action_defn) / sizeof(action_defn[0]))
4168         return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
4169                                  "'%s' is not an action; try 'help'.",
4170                                  action_string);
4171 
4172       action->action_args = apr_array_make(pool, 0, sizeof(const char *));
4173       APR_ARRAY_PUSH(action->action_args, const char *) = action_string;
4174 
4175       if (action->action == ACTION_CP)
4176         {
4177           /* next argument is the copy source revision */
4178           if (++i == action_args->nelts)
4179             return svn_error_trace(insufficient(j, pool));
4180           cp_from_rev = APR_ARRAY_IDX(action_args, i, const char *);
4181           APR_ARRAY_PUSH(action->action_args, const char *) = cp_from_rev;
4182         }
4183 
4184       /* Parse the required number of URLs. */
4185       for (k = 0; k < num_url_args; ++k)
4186         {
4187           const char *path;
4188 
4189           if (++i == action_args->nelts)
4190             return svn_error_trace(insufficient(j, pool));
4191           path = APR_ARRAY_IDX(action_args, i, const char *);
4192           APR_ARRAY_PUSH(action->action_args, const char *) = path;
4193 
4194           if (cp_from_rev && k == 0)
4195             {
4196               path = apr_psprintf(pool, "%s@%s", path, cp_from_rev);
4197             }
4198 
4199           SVN_ERR(svn_opt_parse_path(&action->rev_spec[k], &path, path, pool));
4200 
4201           /* If there's an ANCHOR_URL, we expect URL to be a path
4202              relative to ANCHOR_URL (and we build a full url from the
4203              combination of the two).  Otherwise, it should be a full
4204              url. */
4205           if (svn_path_is_url(path))
4206             {
4207               return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
4208                                        "Argument '%s' is a URL; use "
4209                                        "--root-url (-U) instead", path);
4210             }
4211           /* Parse "^B<branch-id>/path" syntax. */
4212           if (strncmp("^B", path, 2) == 0)
4213             {
4214               const char *slash = strchr(path, '/');
4215 
4216               action->branch_id[k]
4217                 = slash ? apr_pstrndup(pool, path + 1, slash - (path + 1))
4218                         : path + 1;
4219               path = slash ? slash + 1 : "";
4220             }
4221           /* These args must be relpaths, except for the 'local file' arg
4222              of a 'put' command. */
4223           if (! svn_relpath_is_canonical(path)
4224               && ! (action->action == ACTION_PUT_FILE && k == 0))
4225             {
4226               return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
4227                                        "Argument '%s' is not a relative path "
4228                                        "or a URL", path);
4229             }
4230           action->relpath[k] = path;
4231         }
4232 
4233       APR_ARRAY_PUSH(*actions, action_t *) = action;
4234     }
4235 
4236   return SVN_NO_ERROR;
4237 }
4238 
4239 #ifdef HAVE_LINENOISE
4240 /* A command-line completion callback for the 'Line Noise' interactive
4241  * prompting.
4242  *
4243  * This is called when the user presses the Tab key. It calculates the
4244  * possible completions for the partial line BUF.
4245  *
4246  * ### So far, this only works on a single command keyword at the start
4247  *     of the line.
4248  */
4249 static void
linenoise_completion(const char * buf,linenoiseCompletions * lc)4250 linenoise_completion(const char *buf, linenoiseCompletions *lc)
4251 {
4252   int i;
4253 
4254   for (i = 0; i < sizeof(special_commands) / sizeof(special_commands[0]); i++)
4255     {
4256       /* Suggest each command that matches (and is longer than) what the
4257          user has already typed. Add a space. */
4258       if (strncmp(buf, special_commands[i], strlen(buf)) == 0
4259           && strlen(special_commands[i]) > strlen(buf))
4260         {
4261           static char completion[100];
4262 
4263           apr_cpystrn(completion, special_commands[i], 99);
4264           strcat(completion, " ");
4265           linenoiseAddCompletion(lc, completion);
4266         }
4267     }
4268 
4269   for (i = 0; i < sizeof(action_defn) / sizeof(action_defn[0]); i++)
4270     {
4271       /* Suggest each command that matches (and is longer than) what the
4272          user has already typed. Add a space. */
4273       if (strncmp(buf, action_defn[i].name, strlen(buf)) == 0
4274           && strlen(action_defn[i].name) > strlen(buf))
4275         {
4276           static char completion[100];
4277 
4278           apr_cpystrn(completion, action_defn[i].name, 99);
4279           strcat(completion, " ");
4280           linenoiseAddCompletion(lc, completion);
4281         }
4282     }
4283 }
4284 #endif
4285 
4286 /* Display a prompt, read a line of input and split it into words.
4287  *
4288  * Set *WORDS to null if input is cancelled (by ctrl-C for example).
4289  */
4290 static svn_error_t *
read_words(apr_array_header_t ** words,const char * prompt,apr_pool_t * result_pool)4291 read_words(apr_array_header_t **words,
4292            const char *prompt,
4293            apr_pool_t *result_pool)
4294 {
4295   svn_error_t *err;
4296   const char *input;
4297 
4298   settext(TEXT_FG_YELLOW);
4299   err = svnmover_prompt_user(&input, prompt, result_pool);
4300   settext(TEXT_RESET);
4301   if (err && (err->apr_err == SVN_ERR_CANCELLED || err->apr_err == APR_EOF))
4302     {
4303       *words = NULL;
4304       svn_error_clear(err);
4305       return SVN_NO_ERROR;
4306     }
4307   SVN_ERR(err);
4308   *words = svn_cstring_split(input, " ", TRUE /*chop_whitespace*/, result_pool);
4309 
4310   return SVN_NO_ERROR;
4311 }
4312 
4313 /*
4314  * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
4315  * either return an error to be displayed, or set *EXIT_CODE to non-zero and
4316  * return SVN_NO_ERROR.
4317  */
4318 static svn_error_t *
sub_main(int * exit_code,int argc,const char * argv[],apr_pool_t * pool)4319 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
4320 {
4321   apr_array_header_t *actions;
4322   svn_error_t *err = SVN_NO_ERROR;
4323   apr_getopt_t *opts;
4324   enum {
4325     config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID,
4326     config_inline_opt,
4327     no_auth_cache_opt,
4328     version_opt,
4329     with_revprop_opt,
4330     non_interactive_opt,
4331     force_interactive_opt,
4332     trust_server_cert_opt,
4333     trust_server_cert_failures_opt,
4334     ui_opt,
4335     colour_opt,
4336     auth_password_from_stdin_opt
4337   };
4338   static const apr_getopt_option_t options[] = {
4339     {"verbose", 'v', 0, ""},
4340     {"quiet", 'q', 0, ""},
4341     {"message", 'm', 1, ""},
4342     {"file", 'F', 1, ""},
4343     {"username", 'u', 1, ""},
4344     {"password", 'p', 1, ""},
4345     {"password-from-stdin", auth_password_from_stdin_opt, 1, ""},
4346     {"root-url", 'U', 1, ""},
4347     {"revision", 'r', 1, ""},
4348     {"branch-id", 'B', 1, ""},
4349     {"with-revprop",  with_revprop_opt, 1, ""},
4350     {"extra-args", 'X', 1, ""},
4351     {"help", 'h', 0, ""},
4352     {NULL, '?', 0, ""},
4353     {"non-interactive", non_interactive_opt, 0, ""},
4354     {"force-interactive", force_interactive_opt, 0, ""},
4355     {"trust-server-cert", trust_server_cert_opt, 0, ""},
4356     {"trust-server-cert-failures", trust_server_cert_failures_opt, 1, ""},
4357     {"config-dir", config_dir_opt, 1, ""},
4358     {"config-option",  config_inline_opt, 1, ""},
4359     {"no-auth-cache",  no_auth_cache_opt, 0, ""},
4360     {"version", version_opt, 0, ""},
4361     {"ui", ui_opt, 1, ""},
4362     {"colour", colour_opt, 1, ""},
4363     {"color", colour_opt, 1, ""},
4364     {NULL, 0, 0, NULL}
4365   };
4366   const char *message = NULL;
4367   svn_stringbuf_t *filedata = NULL;
4368   const char *username = NULL, *password = NULL;
4369   const char *anchor_url = NULL, *extra_args_file = NULL;
4370   const char *config_dir = NULL;
4371   apr_array_header_t *config_options;
4372   svn_boolean_t show_version = FALSE;
4373   svn_boolean_t non_interactive = FALSE;
4374   svn_boolean_t force_interactive = FALSE;
4375   svn_boolean_t interactive_actions;
4376   svn_boolean_t trust_unknown_ca = FALSE;
4377   svn_boolean_t trust_cn_mismatch = FALSE;
4378   svn_boolean_t trust_expired = FALSE;
4379   svn_boolean_t trust_not_yet_valid = FALSE;
4380   svn_boolean_t trust_other_failure = FALSE;
4381   svn_boolean_t no_auth_cache = FALSE;
4382   svn_revnum_t base_revision = SVN_INVALID_REVNUM;
4383   const char *branch_id = "B0";  /* default branch */
4384   apr_array_header_t *action_args;
4385   apr_hash_t *revprops = apr_hash_make(pool);
4386   apr_hash_t *cfg_hash;
4387   svn_config_t *cfg_config;
4388   svn_client_ctx_t *ctx;
4389   const char *log_msg;
4390   svn_tristate_t coloured_output = svn_tristate_false;
4391   svnmover_wc_t *wc;
4392   svn_boolean_t read_pass_from_stdin = FALSE;
4393 
4394   /* Check library versions */
4395   SVN_ERR(check_lib_versions());
4396 
4397   config_options = apr_array_make(pool, 0,
4398                                   sizeof(svn_cmdline__config_argument_t*));
4399 
4400   apr_getopt_init(&opts, pool, argc, argv);
4401   opts->interleave = 1;
4402   while (1)
4403     {
4404       int opt;
4405       const char *arg;
4406       const char *opt_arg;
4407 
4408       apr_status_t status = apr_getopt_long(opts, options, &opt, &arg);
4409       if (APR_STATUS_IS_EOF(status))
4410         break;
4411       if (status != APR_SUCCESS)
4412         return svn_error_wrap_apr(status, "getopt failure");
4413       switch(opt)
4414         {
4415         case 'v':
4416           quiet = FALSE;
4417           break;
4418         case 'q':
4419           quiet = TRUE;
4420           break;
4421         case 'm':
4422           SVN_ERR(svn_utf_cstring_to_utf8(&message, arg, pool));
4423           break;
4424         case 'F':
4425           {
4426             const char *filename;
4427             SVN_ERR(svn_utf_cstring_to_utf8(&filename, arg, pool));
4428             SVN_ERR(svn_stringbuf_from_file2(&filedata, filename, pool));
4429           }
4430           break;
4431         case 'u':
4432           username = apr_pstrdup(pool, arg);
4433           break;
4434         case 'p':
4435           password = apr_pstrdup(pool, arg);
4436           break;
4437         case auth_password_from_stdin_opt:
4438           read_pass_from_stdin = TRUE;
4439           break;
4440         case 'U':
4441           SVN_ERR(svn_utf_cstring_to_utf8(&anchor_url, arg, pool));
4442           if (! svn_path_is_url(anchor_url))
4443             return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
4444                                      "'%s' is not a URL", anchor_url);
4445           anchor_url = sanitize_url(anchor_url, pool);
4446           break;
4447         case 'r':
4448           {
4449             const char *saved_arg = arg;
4450             char *digits_end = NULL;
4451             while (*arg == 'r')
4452               arg++;
4453             base_revision = strtol(arg, &digits_end, 10);
4454             if ((! SVN_IS_VALID_REVNUM(base_revision))
4455                 || (! digits_end)
4456                 || *digits_end)
4457               return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
4458                                        _("Invalid revision number '%s'"),
4459                                        saved_arg);
4460           }
4461           break;
4462         case 'B':
4463           branch_id = (arg[0] == 'B') ? apr_pstrdup(pool, arg)
4464                                       : apr_psprintf(pool, "B%s", arg);
4465           break;
4466         case with_revprop_opt:
4467           SVN_ERR(svn_opt_parse_revprop(&revprops, arg, pool));
4468           break;
4469         case 'X':
4470           SVN_ERR(svn_utf_cstring_to_utf8(&extra_args_file, arg, pool));
4471           break;
4472         case non_interactive_opt:
4473           non_interactive = TRUE;
4474           break;
4475         case force_interactive_opt:
4476           force_interactive = TRUE;
4477           break;
4478         case trust_server_cert_opt:
4479           trust_unknown_ca = TRUE;
4480           break;
4481         case trust_server_cert_failures_opt:
4482           SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
4483           SVN_ERR(svn_cmdline__parse_trust_options(
4484                       &trust_unknown_ca,
4485                       &trust_cn_mismatch,
4486                       &trust_expired,
4487                       &trust_not_yet_valid,
4488                       &trust_other_failure,
4489                       opt_arg, pool));
4490           break;
4491         case config_dir_opt:
4492           SVN_ERR(svn_utf_cstring_to_utf8(&config_dir, arg, pool));
4493           break;
4494         case config_inline_opt:
4495           SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
4496           SVN_ERR(svn_cmdline__parse_config_option(config_options, opt_arg,
4497                                                    "svnmover: ", pool));
4498           break;
4499         case no_auth_cache_opt:
4500           no_auth_cache = TRUE;
4501           break;
4502         case version_opt:
4503           show_version = TRUE;
4504           break;
4505         case ui_opt:
4506           SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
4507           SVN_ERR(svn_token__from_word_err(&the_ui_mode, ui_mode_map, opt_arg));
4508           break;
4509         case colour_opt:
4510           if (strcmp(arg, "always") == 0)
4511             coloured_output = svn_tristate_true;
4512           else if (strcmp(arg, "never") == 0)
4513             coloured_output = svn_tristate_false;
4514           else if (strcmp(arg, "auto") == 0)
4515             coloured_output = svn_tristate_unknown;
4516           else
4517             return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
4518                                      _("Bad argument in '--colour=%s': "
4519                                        "use one of 'always', 'never', 'auto'"),
4520                                      arg);
4521           break;
4522         case 'h':
4523         case '?':
4524           usage(stdout, pool);
4525           return SVN_NO_ERROR;
4526         }
4527     }
4528 
4529   if (show_version)
4530     {
4531       SVN_ERR(display_version(opts, quiet, pool));
4532       return SVN_NO_ERROR;
4533     }
4534 
4535   if (coloured_output == svn_tristate_true)
4536     use_coloured_output = TRUE;
4537   else if (coloured_output == svn_tristate_false)
4538     use_coloured_output = FALSE;
4539   else
4540     use_coloured_output = (svn_cmdline__stdout_is_a_terminal()
4541                            && svn_cmdline__stderr_is_a_terminal());
4542 
4543   if (non_interactive && force_interactive)
4544     {
4545       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
4546                               _("--non-interactive and --force-interactive "
4547                                 "are mutually exclusive"));
4548     }
4549   else
4550     non_interactive = !svn_cmdline__be_interactive(non_interactive,
4551                                                    force_interactive);
4552 
4553   if (!non_interactive)
4554     {
4555       if (trust_unknown_ca || trust_cn_mismatch || trust_expired
4556           || trust_not_yet_valid || trust_other_failure)
4557         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
4558                                 _("--trust-server-cert-failures requires "
4559                                   "--non-interactive"));
4560     }
4561 
4562   /* --password-from-stdin can only be used with --non-interactive */
4563   if (read_pass_from_stdin && !non_interactive)
4564     {
4565       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
4566                               _("--password-from-stdin requires "
4567                                 "--non-interactive"));
4568     }
4569 
4570   /* Now initialize the client context */
4571 
4572   err = svn_config_get_config(&cfg_hash, config_dir, pool);
4573   if (err)
4574     {
4575       /* Fallback to default config if the config directory isn't readable
4576          or is not a directory. */
4577       if (APR_STATUS_IS_EACCES(err->apr_err)
4578           || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
4579         {
4580           svn_handle_warning2(stderr, err, "svnmover: ");
4581           svn_error_clear(err);
4582 
4583           SVN_ERR(svn_config__get_default_config(&cfg_hash, pool));
4584         }
4585       else
4586         return err;
4587     }
4588 
4589   if (config_options)
4590     {
4591       svn_error_clear(
4592           svn_cmdline__apply_config_options(cfg_hash, config_options,
4593                                             "svnmover: ", "--config-option"));
4594     }
4595 
4596   /* Get password from stdin if necessary */
4597   if (read_pass_from_stdin)
4598     {
4599       SVN_ERR(svn_cmdline__stdin_readline(&password, pool, pool));
4600     }
4601 
4602   SVN_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
4603 
4604   cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);
4605   SVN_ERR(svn_cmdline_create_auth_baton2(&ctx->auth_baton,
4606                                          non_interactive,
4607                                          username,
4608                                          password,
4609                                          config_dir,
4610                                          no_auth_cache,
4611                                          trust_unknown_ca,
4612                                          trust_cn_mismatch,
4613                                          trust_expired,
4614                                          trust_not_yet_valid,
4615                                          trust_other_failure,
4616                                          cfg_config,
4617                                          ctx->cancel_func,
4618                                          ctx->cancel_baton,
4619                                          pool));
4620 
4621   /* Get the commit log message */
4622   SVN_ERR(get_log_message(&log_msg, message, revprops, filedata,
4623                           pool, pool));
4624 
4625   /* Put the log message in the list of revprops, and check that the user
4626      did not try to supply any other "svn:*" revprops. */
4627   if (svn_prop_has_svn_prop(revprops, pool))
4628     return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
4629                             _("Standard properties can't be set "
4630                               "explicitly as revision properties"));
4631   if (log_msg)
4632     {
4633       svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
4634                     svn_string_create(log_msg, pool));
4635     }
4636 
4637   /* Help command: if given before any actions, then display full help
4638      (and ANCHOR_URL need not have been provided). */
4639   if (opts->ind < opts->argc && strcmp(opts->argv[opts->ind], "help") == 0)
4640     {
4641       usage(stdout, pool);
4642       return SVN_NO_ERROR;
4643     }
4644 
4645   if (!anchor_url)
4646     return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
4647                              "--root-url (-U) not provided");
4648 
4649   /* Copy the rest of our command-line arguments to an array,
4650      UTF-8-ing them along the way. */
4651   /* If there are extra arguments in a supplementary file, tack those
4652      on, too (again, in UTF8 form). */
4653   action_args = apr_array_make(pool, opts->argc, sizeof(const char *));
4654   if (extra_args_file)
4655     {
4656       svn_stringbuf_t *contents, *contents_utf8;
4657 
4658       SVN_ERR(svn_stringbuf_from_file2(&contents, extra_args_file, pool));
4659       SVN_ERR(svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool));
4660       svn_cstring_split_append(action_args, contents_utf8->data, "\n\r",
4661                                FALSE, pool);
4662     }
4663 
4664   interactive_actions = !(opts->ind < opts->argc
4665                           || extra_args_file
4666                           || non_interactive);
4667 
4668   if (interactive_actions)
4669     {
4670 #ifdef HAVE_LINENOISE
4671       linenoiseSetCompletionCallback(linenoise_completion);
4672 #endif
4673     }
4674 
4675   SVN_ERR(wc_create(&wc,
4676                     anchor_url, base_revision,
4677                     branch_id,
4678                     ctx, pool, pool));
4679 
4680   do
4681     {
4682       /* Parse arguments -- converting local style to internal style,
4683        * repos-relative URLs to regular URLs, etc. */
4684       err = svn_client_args_to_target_array2(&action_args, opts, action_args,
4685                                              ctx, FALSE, pool);
4686       if (! err)
4687         err = parse_actions(&actions, action_args, pool);
4688       if (! err)
4689         err = execute(wc, actions, anchor_url, revprops, ctx, pool);
4690       if (err)
4691         {
4692           if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
4693             err = svn_error_quick_wrap(err,
4694                                        _("Authentication failed and interactive"
4695                                          " prompting is disabled; see the"
4696                                          " --force-interactive option"));
4697           if (interactive_actions)
4698             {
4699               /* Display the error, but don't quit */
4700               settext_stderr(TEXT_FG_RED);
4701               svn_handle_error2(err, stderr, FALSE, "svnmover: ");
4702               settext_stderr(TEXT_RESET);
4703               svn_error_clear(err);
4704             }
4705           else
4706             SVN_ERR(err);
4707         }
4708 
4709       /* Possibly read more actions from the command line */
4710       if (interactive_actions)
4711         {
4712           SVN_ERR(read_words(&action_args, "svnmover> ", pool));
4713         }
4714     }
4715   while (interactive_actions && action_args);
4716 
4717   /* Final commit */
4718   err = commit(NULL, wc, revprops, pool);
4719   svn_pool_destroy(wc->pool);
4720   SVN_ERR(err);
4721 
4722   return SVN_NO_ERROR;
4723 }
4724 
4725 int
main(int argc,const char * argv[])4726 main(int argc, const char *argv[])
4727 {
4728   apr_pool_t *pool;
4729   int exit_code = EXIT_SUCCESS;
4730   svn_error_t *err;
4731 
4732   /* Initialize the app. */
4733   if (svn_cmdline_init("svnmover", stderr) != EXIT_SUCCESS)
4734     return EXIT_FAILURE;
4735 
4736   /* Create our top-level pool.  Use a separate mutexless allocator,
4737    * given this application is single threaded.
4738    */
4739   pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
4740 
4741   svn_error_set_malfunction_handler(svn_error_raise_on_malfunction);
4742 
4743   err = sub_main(&exit_code, argc, argv, pool);
4744 
4745   /* Flush stdout and report if it fails. It would be flushed on exit anyway
4746      but this makes sure that output is not silently lost if it fails. */
4747   err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
4748 
4749   if (err)
4750     {
4751       exit_code = EXIT_FAILURE;
4752       settext_stderr(TEXT_FG_RED);
4753       svn_cmdline_handle_exit_error(err, NULL, "svnmover: ");
4754       settext_stderr(TEXT_RESET);
4755     }
4756 
4757   svn_pool_destroy(pool);
4758   return exit_code;
4759 }
4760