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