1 /*
2 * conflicts.c: conflict resolver implementation
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26
27
28 /*** Includes. ***/
29
30 #include "svn_types.h"
31 #include "svn_wc.h"
32 #include "svn_client.h"
33 #include "svn_error.h"
34 #include "svn_dirent_uri.h"
35 #include "svn_path.h"
36 #include "svn_pools.h"
37 #include "svn_props.h"
38 #include "svn_hash.h"
39 #include "svn_sorts.h"
40 #include "svn_subst.h"
41 #include "client.h"
42
43 #include "private/svn_diff_tree.h"
44 #include "private/svn_ra_private.h"
45 #include "private/svn_sorts_private.h"
46 #include "private/svn_token.h"
47 #include "private/svn_wc_private.h"
48
49 #include "svn_private_config.h"
50
51 #define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
52
53
54 /*** Dealing with conflicts. ***/
55
56 /* Describe a tree conflict. */
57 typedef svn_error_t *(*tree_conflict_get_description_func_t)(
58 const char **change_description,
59 svn_client_conflict_t *conflict,
60 svn_client_ctx_t *ctx,
61 apr_pool_t *result_pool,
62 apr_pool_t *scratch_pool);
63
64 /* Get more information about a tree conflict.
65 * This function may contact the repository. */
66 typedef svn_error_t *(*tree_conflict_get_details_func_t)(
67 svn_client_conflict_t *conflict,
68 svn_client_ctx_t *ctx,
69 apr_pool_t *scratch_pool);
70
71 struct svn_client_conflict_t
72 {
73 const char *local_abspath;
74 apr_hash_t *prop_conflicts;
75
76 /* Indicate which options were chosen to resolve a text or tree conflict
77 * on the conflicted node. */
78 svn_client_conflict_option_id_t resolution_text;
79 svn_client_conflict_option_id_t resolution_tree;
80
81 /* A mapping from const char* property name to pointers to
82 * svn_client_conflict_option_t for all properties which had their
83 * conflicts resolved. Indicates which options were chosen to resolve
84 * the property conflicts. */
85 apr_hash_t *resolved_props;
86
87 /* Ask a tree conflict to describe itself. */
88 tree_conflict_get_description_func_t
89 tree_conflict_get_incoming_description_func;
90 tree_conflict_get_description_func_t
91 tree_conflict_get_local_description_func;
92
93 /* Ask a tree conflict to find out more information about itself
94 * by contacting the repository. */
95 tree_conflict_get_details_func_t tree_conflict_get_incoming_details_func;
96 tree_conflict_get_details_func_t tree_conflict_get_local_details_func;
97
98 /* Any additional information found can be stored here and may be used
99 * when describing a tree conflict. */
100 void *tree_conflict_incoming_details;
101 void *tree_conflict_local_details;
102
103 /* The pool this conflict was allocated from. */
104 apr_pool_t *pool;
105
106 /* Conflict data provided by libsvn_wc. */
107 const svn_wc_conflict_description2_t *legacy_text_conflict;
108 const char *legacy_prop_conflict_propname;
109 const svn_wc_conflict_description2_t *legacy_tree_conflict;
110
111 /* The recommended resolution option's ID. */
112 svn_client_conflict_option_id_t recommended_option_id;
113 };
114
115 /* Resolves conflict to OPTION and sets CONFLICT->RESOLUTION accordingly.
116 *
117 * May raise an error in case the conflict could not be resolved. A common
118 * case would be a tree conflict the resolution of which depends on other
119 * tree conflicts to be resolved first. */
120 typedef svn_error_t *(*conflict_option_resolve_func_t)(
121 svn_client_conflict_option_t *option,
122 svn_client_conflict_t *conflict,
123 svn_client_ctx_t *ctx,
124 apr_pool_t *scratch_pool);
125
126 struct svn_client_conflict_option_t
127 {
128 svn_client_conflict_option_id_t id;
129 const char *label;
130 const char *description;
131
132 svn_client_conflict_t *conflict;
133 conflict_option_resolve_func_t do_resolve_func;
134
135 /* The pool this option was allocated from. */
136 apr_pool_t *pool;
137
138 /* Data which is specific to particular conflicts and options. */
139 union {
140 struct {
141 /* Indicates the property to resolve in case of a property conflict.
142 * If set to "", all properties are resolved to this option. */
143 const char *propname;
144
145 /* A merged property value, if supplied by the API user, else NULL. */
146 const svn_string_t *merged_propval;
147 } prop;
148 } type_data;
149
150 };
151
152 /*
153 * Return a legacy conflict choice corresponding to OPTION_ID.
154 * Return svn_wc_conflict_choose_undefined if no corresponding
155 * legacy conflict choice exists.
156 */
157 static svn_wc_conflict_choice_t
conflict_option_id_to_wc_conflict_choice(svn_client_conflict_option_id_t option_id)158 conflict_option_id_to_wc_conflict_choice(
159 svn_client_conflict_option_id_t option_id)
160 {
161
162 switch (option_id)
163 {
164 case svn_client_conflict_option_undefined:
165 return svn_wc_conflict_choose_undefined;
166
167 case svn_client_conflict_option_postpone:
168 return svn_wc_conflict_choose_postpone;
169
170 case svn_client_conflict_option_base_text:
171 return svn_wc_conflict_choose_base;
172
173 case svn_client_conflict_option_incoming_text:
174 return svn_wc_conflict_choose_theirs_full;
175
176 case svn_client_conflict_option_working_text:
177 return svn_wc_conflict_choose_mine_full;
178
179 case svn_client_conflict_option_incoming_text_where_conflicted:
180 return svn_wc_conflict_choose_theirs_conflict;
181
182 case svn_client_conflict_option_working_text_where_conflicted:
183 return svn_wc_conflict_choose_mine_conflict;
184
185 case svn_client_conflict_option_merged_text:
186 return svn_wc_conflict_choose_merged;
187
188 case svn_client_conflict_option_unspecified:
189 return svn_wc_conflict_choose_unspecified;
190
191 default:
192 break;
193 }
194
195 return svn_wc_conflict_choose_undefined;
196 }
197
198 static void
add_legacy_desc_to_conflict(const svn_wc_conflict_description2_t * desc,svn_client_conflict_t * conflict,apr_pool_t * result_pool)199 add_legacy_desc_to_conflict(const svn_wc_conflict_description2_t *desc,
200 svn_client_conflict_t *conflict,
201 apr_pool_t *result_pool)
202 {
203 switch (desc->kind)
204 {
205 case svn_wc_conflict_kind_text:
206 conflict->legacy_text_conflict = desc;
207 break;
208
209 case svn_wc_conflict_kind_property:
210 if (conflict->prop_conflicts == NULL)
211 conflict->prop_conflicts = apr_hash_make(result_pool);
212 svn_hash_sets(conflict->prop_conflicts, desc->property_name, desc);
213 conflict->legacy_prop_conflict_propname = desc->property_name;
214 break;
215
216 case svn_wc_conflict_kind_tree:
217 conflict->legacy_tree_conflict = desc;
218 break;
219
220 default:
221 SVN_ERR_ASSERT_NO_RETURN(FALSE); /* unknown kind of conflict */
222 }
223 }
224
225 /* A map for svn_wc_conflict_action_t values to strings */
226 static const svn_token_map_t map_conflict_action[] =
227 {
228 { "edit", svn_wc_conflict_action_edit },
229 { "delete", svn_wc_conflict_action_delete },
230 { "add", svn_wc_conflict_action_add },
231 { "replace", svn_wc_conflict_action_replace },
232 { NULL, 0 }
233 };
234
235 /* A map for svn_wc_conflict_reason_t values to strings */
236 static const svn_token_map_t map_conflict_reason[] =
237 {
238 { "edit", svn_wc_conflict_reason_edited },
239 { "delete", svn_wc_conflict_reason_deleted },
240 { "missing", svn_wc_conflict_reason_missing },
241 { "obstruction", svn_wc_conflict_reason_obstructed },
242 { "add", svn_wc_conflict_reason_added },
243 { "replace", svn_wc_conflict_reason_replaced },
244 { "unversioned", svn_wc_conflict_reason_unversioned },
245 { "moved-away", svn_wc_conflict_reason_moved_away },
246 { "moved-here", svn_wc_conflict_reason_moved_here },
247 { NULL, 0 }
248 };
249
250 /* Describes a server-side move (really a copy+delete within the same
251 * revision) which was identified by scanning the revision log.
252 * This structure can represent one or more "chains" of moves, i.e.
253 * multiple move operations which occurred across a range of revisions. */
254 struct repos_move_info {
255 /* The revision in which this move was committed. */
256 svn_revnum_t rev;
257
258 /* The author who committed the revision in which this move was committed. */
259 const char *rev_author;
260
261 /* The repository relpath the node was moved from in this revision. */
262 const char *moved_from_repos_relpath;
263
264 /* The repository relpath the node was moved to in this revision. */
265 const char *moved_to_repos_relpath;
266
267 /* The copyfrom revision of the moved-to path. */
268 svn_revnum_t copyfrom_rev;
269
270 /* The node kind of the item being moved. */
271 svn_node_kind_t node_kind;
272
273 /* Prev pointer. NULL if no prior move exists in the chain. */
274 struct repos_move_info *prev;
275
276 /* An array of struct repos_move_info * elements, each representing
277 * a possible way forward in the move chain. NULL if no next move
278 * exists in this chain. If the deleted node was copied only once in
279 * this revision, then this array has only one element and the move
280 * chain does not fork. But if this revision contains multiple copies of
281 * the deleted node, each of these copies appears as an element of this
282 * array, and each element represents a different path the next move
283 * might have taken. */
284 apr_array_header_t *next;
285 };
286
287 static svn_revnum_t
rev_below(svn_revnum_t rev)288 rev_below(svn_revnum_t rev)
289 {
290 SVN_ERR_ASSERT_NO_RETURN(rev != SVN_INVALID_REVNUM);
291 SVN_ERR_ASSERT_NO_RETURN(rev > 0);
292
293 return rev == 1 ? 1 : rev - 1;
294 }
295
296 /* Set *RELATED to true if the deleted node DELETED_REPOS_RELPATH@DELETED_REV
297 * is an ancestor of the copied node COPYFROM_PATH@COPYFROM_REV.
298 * If CHECK_LAST_CHANGED_REV is non-zero, also ensure that the copied node
299 * is a copy of the deleted node's last-changed revision's content, rather
300 * than a copy of some older content. If it's not, set *RELATED to false. */
301 static svn_error_t *
check_move_ancestry(svn_boolean_t * related,svn_ra_session_t * ra_session,const char * repos_root_url,const char * deleted_repos_relpath,svn_revnum_t deleted_rev,const char * copyfrom_path,svn_revnum_t copyfrom_rev,svn_boolean_t check_last_changed_rev,apr_pool_t * scratch_pool)302 check_move_ancestry(svn_boolean_t *related,
303 svn_ra_session_t *ra_session,
304 const char *repos_root_url,
305 const char *deleted_repos_relpath,
306 svn_revnum_t deleted_rev,
307 const char *copyfrom_path,
308 svn_revnum_t copyfrom_rev,
309 svn_boolean_t check_last_changed_rev,
310 apr_pool_t *scratch_pool)
311 {
312 apr_hash_t *locations;
313 const char *deleted_url;
314 const char *deleted_location;
315 apr_array_header_t *location_revisions;
316 const char *old_session_url;
317
318 location_revisions = apr_array_make(scratch_pool, 1, sizeof(svn_revnum_t));
319 APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = copyfrom_rev;
320 deleted_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool,
321 repos_root_url, "/",
322 deleted_repos_relpath,
323 NULL),
324 scratch_pool);
325 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
326 deleted_url, scratch_pool));
327 SVN_ERR(svn_ra_get_locations(ra_session, &locations, "",
328 rev_below(deleted_rev), location_revisions,
329 scratch_pool));
330
331 deleted_location = apr_hash_get(locations, ©from_rev,
332 sizeof(svn_revnum_t));
333 if (deleted_location)
334 {
335 if (deleted_location[0] == '/')
336 deleted_location++;
337 if (strcmp(deleted_location, copyfrom_path) != 0)
338 {
339 *related = FALSE;
340 return SVN_NO_ERROR;
341 }
342 }
343 else
344 {
345 *related = FALSE;
346 return SVN_NO_ERROR;
347 }
348
349 if (check_last_changed_rev)
350 {
351 svn_dirent_t *dirent;
352
353 /* Verify that copyfrom_rev >= last-changed revision of the
354 * deleted node. */
355 SVN_ERR(svn_ra_stat(ra_session, "", rev_below(deleted_rev), &dirent,
356 scratch_pool));
357 if (dirent == NULL || copyfrom_rev < dirent->created_rev)
358 {
359 *related = FALSE;
360 return SVN_NO_ERROR;
361 }
362 }
363
364 *related = TRUE;
365 return SVN_NO_ERROR;
366 }
367
368 struct copy_info {
369 const char *copyto_path;
370 const char *copyfrom_path;
371 svn_revnum_t copyfrom_rev;
372 svn_node_kind_t node_kind;
373 };
374
375 /* Allocate and return a NEW_MOVE, and update MOVED_PATHS with this new move. */
376 static svn_error_t *
add_new_move(struct repos_move_info ** new_move,const char * deleted_repos_relpath,const char * copyto_path,svn_revnum_t copyfrom_rev,svn_node_kind_t node_kind,svn_revnum_t revision,const char * author,apr_hash_t * moved_paths,svn_ra_session_t * ra_session,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)377 add_new_move(struct repos_move_info **new_move,
378 const char *deleted_repos_relpath,
379 const char *copyto_path,
380 svn_revnum_t copyfrom_rev,
381 svn_node_kind_t node_kind,
382 svn_revnum_t revision,
383 const char *author,
384 apr_hash_t *moved_paths,
385 svn_ra_session_t *ra_session,
386 const char *repos_root_url,
387 apr_pool_t *result_pool,
388 apr_pool_t *scratch_pool)
389 {
390 struct repos_move_info *move;
391 struct repos_move_info *next_move;
392
393 move = apr_pcalloc(result_pool, sizeof(*move));
394 move->moved_from_repos_relpath = apr_pstrdup(result_pool,
395 deleted_repos_relpath);
396 move->moved_to_repos_relpath = apr_pstrdup(result_pool, copyto_path);
397 move->rev = revision;
398 move->rev_author = apr_pstrdup(result_pool, author);
399 move->copyfrom_rev = copyfrom_rev;
400 move->node_kind = node_kind;
401
402 /* Link together multiple moves of the same node.
403 * Note that we're traversing history backwards, so moves already
404 * present in the list happened in younger revisions. */
405 next_move = svn_hash_gets(moved_paths, move->moved_to_repos_relpath);
406 if (next_move)
407 {
408 svn_boolean_t related;
409
410 /* Tracing back history of the delete-half of the next move
411 * to the copyfrom-revision of the prior move we must end up
412 * at the delete-half of the prior move. */
413 SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url,
414 next_move->moved_from_repos_relpath,
415 next_move->rev,
416 move->moved_from_repos_relpath,
417 move->copyfrom_rev,
418 FALSE, scratch_pool));
419 if (related)
420 {
421 SVN_ERR_ASSERT(move->rev < next_move->rev);
422
423 /* Prepend this move to the linked list. */
424 if (move->next == NULL)
425 move->next = apr_array_make(result_pool, 1,
426 sizeof (struct repos_move_info *));
427 APR_ARRAY_PUSH(move->next, struct repos_move_info *) = next_move;
428 next_move->prev = move;
429 }
430 }
431
432 /* Make this move the head of our next-move linking map. */
433 svn_hash_sets(moved_paths, move->moved_from_repos_relpath, move);
434
435 *new_move = move;
436 return SVN_NO_ERROR;
437 }
438
439 /* Push a MOVE into the MOVES_TABLE. */
440 static void
push_move(struct repos_move_info * move,apr_hash_t * moves_table,apr_pool_t * result_pool)441 push_move(struct repos_move_info *move, apr_hash_t *moves_table,
442 apr_pool_t *result_pool)
443 {
444 apr_array_header_t *moves;
445
446 /* Add this move to the list of moves in the revision. */
447 moves = apr_hash_get(moves_table, &move->rev, sizeof(svn_revnum_t));
448 if (moves == NULL)
449 {
450 /* It is the first move in this revision. Create the list. */
451 moves = apr_array_make(result_pool, 1, sizeof(struct repos_move_info *));
452 apr_hash_set(moves_table, &move->rev, sizeof(svn_revnum_t), moves);
453 }
454 APR_ARRAY_PUSH(moves, struct repos_move_info *) = move;
455 }
456
457 /* Find the youngest common ancestor of REPOS_RELPATH1@PEG_REV1 and
458 * REPOS_RELPATH2@PEG_REV2. Return the result in *YCA_LOC.
459 * Set *YCA_LOC to NULL if no common ancestor exists. */
460 static svn_error_t *
find_yca(svn_client__pathrev_t ** yca_loc,const char * repos_relpath1,svn_revnum_t peg_rev1,const char * repos_relpath2,svn_revnum_t peg_rev2,const char * repos_root_url,const char * repos_uuid,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)461 find_yca(svn_client__pathrev_t **yca_loc,
462 const char *repos_relpath1,
463 svn_revnum_t peg_rev1,
464 const char *repos_relpath2,
465 svn_revnum_t peg_rev2,
466 const char *repos_root_url,
467 const char *repos_uuid,
468 svn_ra_session_t *ra_session,
469 svn_client_ctx_t *ctx,
470 apr_pool_t *result_pool,
471 apr_pool_t *scratch_pool)
472 {
473 svn_client__pathrev_t *loc1;
474 svn_client__pathrev_t *loc2;
475
476 *yca_loc = NULL;
477
478 loc1 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid,
479 peg_rev1, repos_relpath1,
480 scratch_pool);
481 loc2 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid,
482 peg_rev2, repos_relpath2,
483 scratch_pool);
484 SVN_ERR(svn_client__get_youngest_common_ancestor(yca_loc, loc1, loc2,
485 ra_session, ctx,
486 result_pool, scratch_pool));
487
488 return SVN_NO_ERROR;
489 }
490
491 /* Like find_yca, expect that a YCA could also be found via a brute-force
492 * search of parents of REPOS_RELPATH1 and REPOS_RELPATH2, if no "direct"
493 * YCA exists. An implicit assumption is that some parent of REPOS_RELPATH1
494 * is a branch of some parent of REPOS_RELPATH2.
495 *
496 * This function can guess a "good enough" YCA for 'missing nodes' which do
497 * not exist in the working copy, e.g. when a file edit is merged to a path
498 * which does not exist in the working copy.
499 */
500 static svn_error_t *
find_nearest_yca(svn_client__pathrev_t ** yca_locp,const char * repos_relpath1,svn_revnum_t peg_rev1,const char * repos_relpath2,svn_revnum_t peg_rev2,const char * repos_root_url,const char * repos_uuid,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)501 find_nearest_yca(svn_client__pathrev_t **yca_locp,
502 const char *repos_relpath1,
503 svn_revnum_t peg_rev1,
504 const char *repos_relpath2,
505 svn_revnum_t peg_rev2,
506 const char *repos_root_url,
507 const char *repos_uuid,
508 svn_ra_session_t *ra_session,
509 svn_client_ctx_t *ctx,
510 apr_pool_t *result_pool,
511 apr_pool_t *scratch_pool)
512 {
513 svn_client__pathrev_t *yca_loc;
514 svn_error_t *err;
515 apr_pool_t *iterpool;
516 const char *p1, *p2;
517 apr_size_t c1, c2;
518
519 *yca_locp = NULL;
520
521 iterpool = svn_pool_create(scratch_pool);
522
523 p1 = repos_relpath1;
524 c1 = svn_path_component_count(repos_relpath1);
525 while (c1--)
526 {
527 svn_pool_clear(iterpool);
528
529 p2 = repos_relpath2;
530 c2 = svn_path_component_count(repos_relpath2);
531 while (c2--)
532 {
533 err = find_yca(&yca_loc, p1, peg_rev1, p2, peg_rev2,
534 repos_root_url, repos_uuid, ra_session, ctx,
535 result_pool, iterpool);
536 if (err)
537 {
538 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
539 {
540 svn_error_clear(err);
541 yca_loc = NULL;
542 }
543 else
544 return svn_error_trace(err);
545 }
546
547 if (yca_loc)
548 {
549 *yca_locp = yca_loc;
550 svn_pool_destroy(iterpool);
551 return SVN_NO_ERROR;
552 }
553
554 p2 = svn_relpath_dirname(p2, scratch_pool);
555 }
556
557 p1 = svn_relpath_dirname(p1, scratch_pool);
558 }
559
560 svn_pool_destroy(iterpool);
561
562 return SVN_NO_ERROR;
563 }
564
565 /* Check if the copied node described by COPY and the DELETED_PATH@DELETED_REV
566 * share a common ancestor. If so, return new repos_move_info in *MOVE which
567 * describes a move from the deleted path to that copy's destination. */
568 static svn_error_t *
find_related_move(struct repos_move_info ** move,struct copy_info * copy,const char * deleted_repos_relpath,svn_revnum_t deleted_rev,const char * author,apr_hash_t * moved_paths,const char * repos_root_url,const char * repos_uuid,svn_client_ctx_t * ctx,svn_ra_session_t * ra_session,apr_pool_t * result_pool,apr_pool_t * scratch_pool)569 find_related_move(struct repos_move_info **move,
570 struct copy_info *copy,
571 const char *deleted_repos_relpath,
572 svn_revnum_t deleted_rev,
573 const char *author,
574 apr_hash_t *moved_paths,
575 const char *repos_root_url,
576 const char *repos_uuid,
577 svn_client_ctx_t *ctx,
578 svn_ra_session_t *ra_session,
579 apr_pool_t *result_pool,
580 apr_pool_t *scratch_pool)
581 {
582 svn_client__pathrev_t *yca_loc;
583 svn_error_t *err;
584
585 *move = NULL;
586 err = find_yca(&yca_loc, copy->copyfrom_path, copy->copyfrom_rev,
587 deleted_repos_relpath, rev_below(deleted_rev),
588 repos_root_url, repos_uuid, ra_session, ctx,
589 scratch_pool, scratch_pool);
590 if (err)
591 {
592 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
593 {
594 svn_error_clear(err);
595 yca_loc = NULL;
596 }
597 else
598 return svn_error_trace(err);
599 }
600
601 if (yca_loc)
602 SVN_ERR(add_new_move(move, deleted_repos_relpath,
603 copy->copyto_path, copy->copyfrom_rev,
604 copy->node_kind, deleted_rev, author,
605 moved_paths, ra_session, repos_root_url,
606 result_pool, scratch_pool));
607
608 return SVN_NO_ERROR;
609 }
610
611 /* Detect moves by matching DELETED_REPOS_RELPATH@DELETED_REV to the copies
612 * in COPIES. Add any moves found to MOVES_TABLE and update MOVED_PATHS. */
613 static svn_error_t *
match_copies_to_deletion(const char * deleted_repos_relpath,svn_revnum_t deleted_rev,const char * author,apr_hash_t * copies,apr_hash_t * moves_table,apr_hash_t * moved_paths,const char * repos_root_url,const char * repos_uuid,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)614 match_copies_to_deletion(const char *deleted_repos_relpath,
615 svn_revnum_t deleted_rev,
616 const char *author,
617 apr_hash_t *copies,
618 apr_hash_t *moves_table,
619 apr_hash_t *moved_paths,
620 const char *repos_root_url,
621 const char *repos_uuid,
622 svn_ra_session_t *ra_session,
623 svn_client_ctx_t *ctx,
624 apr_pool_t *result_pool,
625 apr_pool_t *scratch_pool)
626 {
627 apr_hash_index_t *hi;
628 apr_pool_t *iterpool;
629
630 iterpool = svn_pool_create(scratch_pool);
631 for (hi = apr_hash_first(scratch_pool, copies);
632 hi != NULL;
633 hi = apr_hash_next(hi))
634 {
635 const char *copyfrom_path = apr_hash_this_key(hi);
636 apr_array_header_t *copies_with_same_source_path;
637 int i;
638
639 svn_pool_clear(iterpool);
640
641 copies_with_same_source_path = apr_hash_this_val(hi);
642
643 if (strcmp(copyfrom_path, deleted_repos_relpath) == 0)
644 {
645 /* We found a copyfrom path which matches a deleted node.
646 * Check if the deleted node is an ancestor of the copied node. */
647 for (i = 0; i < copies_with_same_source_path->nelts; i++)
648 {
649 struct copy_info *copy;
650 svn_boolean_t related;
651 struct repos_move_info *move;
652
653 copy = APR_ARRAY_IDX(copies_with_same_source_path, i,
654 struct copy_info *);
655 SVN_ERR(check_move_ancestry(&related,
656 ra_session, repos_root_url,
657 deleted_repos_relpath,
658 deleted_rev,
659 copy->copyfrom_path,
660 copy->copyfrom_rev,
661 TRUE, iterpool));
662 if (!related)
663 continue;
664
665 /* Remember details of this move. */
666 SVN_ERR(add_new_move(&move, deleted_repos_relpath,
667 copy->copyto_path, copy->copyfrom_rev,
668 copy->node_kind, deleted_rev, author,
669 moved_paths, ra_session, repos_root_url,
670 result_pool, iterpool));
671 push_move(move, moves_table, result_pool);
672 }
673 }
674 else
675 {
676 /* Check if this deleted node is related to any copies in this
677 * revision. These could be moves of the deleted node which
678 * were merged here from other lines of history. */
679 for (i = 0; i < copies_with_same_source_path->nelts; i++)
680 {
681 struct copy_info *copy;
682 struct repos_move_info *move = NULL;
683
684 copy = APR_ARRAY_IDX(copies_with_same_source_path, i,
685 struct copy_info *);
686 SVN_ERR(find_related_move(&move, copy, deleted_repos_relpath,
687 deleted_rev, author,
688 moved_paths,
689 repos_root_url, repos_uuid,
690 ctx, ra_session,
691 result_pool, iterpool));
692 if (move)
693 push_move(move, moves_table, result_pool);
694 }
695 }
696 }
697 svn_pool_destroy(iterpool);
698
699 return SVN_NO_ERROR;
700 }
701
702 /* Update MOVES_TABLE and MOVED_PATHS based on information from
703 * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS.
704 * Use RA_SESSION to perform the necessary requests. */
705 static svn_error_t *
find_moves_in_revision(svn_ra_session_t * ra_session,apr_hash_t * moves_table,apr_hash_t * moved_paths,svn_log_entry_t * log_entry,apr_hash_t * copies,apr_array_header_t * deleted_paths,const char * repos_root_url,const char * repos_uuid,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)706 find_moves_in_revision(svn_ra_session_t *ra_session,
707 apr_hash_t *moves_table,
708 apr_hash_t *moved_paths,
709 svn_log_entry_t *log_entry,
710 apr_hash_t *copies,
711 apr_array_header_t *deleted_paths,
712 const char *repos_root_url,
713 const char *repos_uuid,
714 svn_client_ctx_t *ctx,
715 apr_pool_t *result_pool,
716 apr_pool_t *scratch_pool)
717 {
718 apr_pool_t *iterpool;
719 int i;
720 const svn_string_t *author;
721
722 author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
723 iterpool = svn_pool_create(scratch_pool);
724 for (i = 0; i < deleted_paths->nelts; i++)
725 {
726 const char *deleted_repos_relpath;
727
728 svn_pool_clear(iterpool);
729
730 deleted_repos_relpath = APR_ARRAY_IDX(deleted_paths, i, const char *);
731 SVN_ERR(match_copies_to_deletion(deleted_repos_relpath,
732 log_entry->revision,
733 author ? author->data
734 : _("unknown author"),
735 copies, moves_table, moved_paths,
736 repos_root_url, repos_uuid, ra_session,
737 ctx, result_pool, iterpool));
738 }
739 svn_pool_destroy(iterpool);
740
741 return SVN_NO_ERROR;
742 }
743
744 struct find_deleted_rev_baton
745 {
746 /* Variables below are arguments provided by the caller of
747 * svn_ra_get_log2(). */
748 const char *deleted_repos_relpath;
749 const char *related_repos_relpath;
750 svn_revnum_t related_peg_rev;
751 const char *repos_root_url;
752 const char *repos_uuid;
753 svn_client_ctx_t *ctx;
754 const char *victim_abspath; /* for notifications */
755
756 /* Variables below are results for the caller of svn_ra_get_log2(). */
757 svn_revnum_t deleted_rev;
758 const char *deleted_rev_author;
759 svn_node_kind_t replacing_node_kind;
760 apr_pool_t *result_pool;
761
762 apr_hash_t *moves_table; /* Obtained from find_moves_in_revision(). */
763 struct repos_move_info *move; /* Last known move which affected the node. */
764
765 /* Extra RA session that can be used to make additional requests. */
766 svn_ra_session_t *extra_ra_session;
767 };
768
769 /* If DELETED_RELPATH matches the moved-from path of a move in MOVES,
770 * or if DELETED_RELPATH is a child of a moved-to path in MOVES, return
771 * a struct move_info for the corresponding move. Else, return NULL. */
772 static struct repos_move_info *
map_deleted_path_to_move(const char * deleted_relpath,apr_array_header_t * moves,apr_pool_t * scratch_pool)773 map_deleted_path_to_move(const char *deleted_relpath,
774 apr_array_header_t *moves,
775 apr_pool_t *scratch_pool)
776 {
777 struct repos_move_info *closest_move = NULL;
778 apr_size_t min_components = 0;
779 int i;
780
781 for (i = 0; i < moves->nelts; i++)
782 {
783 const char *relpath;
784 struct repos_move_info *move;
785
786 move = APR_ARRAY_IDX(moves, i, struct repos_move_info *);
787 if (strcmp(move->moved_from_repos_relpath, deleted_relpath) == 0)
788 return move;
789
790 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
791 deleted_relpath);
792 if (relpath)
793 {
794 /* This could be a nested move. Return the path-wise closest move. */
795 const apr_size_t c = svn_path_component_count(relpath);
796 if (c == 0)
797 return move;
798 else if (min_components == 0 || c < min_components)
799 {
800 min_components = c;
801 closest_move = move;
802 }
803 }
804 }
805
806 if (closest_move)
807 {
808 const char *relpath;
809
810 /* See if we can find an even closer move for this moved-along path. */
811 relpath = svn_relpath_skip_ancestor(closest_move->moved_to_repos_relpath,
812 deleted_relpath);
813 if (relpath && relpath[0] != '\0')
814 {
815 struct repos_move_info *move;
816 const char *moved_along_path =
817 svn_relpath_join(closest_move->moved_from_repos_relpath, relpath,
818 scratch_pool);
819 move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool);
820 if (move)
821 return move;
822 }
823 }
824
825 return closest_move;
826 }
827
828 /* Search for nested moves in REVISION, given the already found MOVES,
829 * all DELETED_PATHS, and all COPIES, from the same revision.
830 * Append any nested moves to the MOVES array. */
831 static svn_error_t *
find_nested_moves(apr_array_header_t * moves,apr_hash_t * copies,apr_array_header_t * deleted_paths,apr_hash_t * moved_paths,svn_revnum_t revision,const char * author,const char * repos_root_url,const char * repos_uuid,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)832 find_nested_moves(apr_array_header_t *moves,
833 apr_hash_t *copies,
834 apr_array_header_t *deleted_paths,
835 apr_hash_t *moved_paths,
836 svn_revnum_t revision,
837 const char *author,
838 const char *repos_root_url,
839 const char *repos_uuid,
840 svn_ra_session_t *ra_session,
841 svn_client_ctx_t *ctx,
842 apr_pool_t *result_pool,
843 apr_pool_t *scratch_pool)
844 {
845 apr_array_header_t *nested_moves;
846 int i;
847 apr_pool_t *iterpool;
848
849 nested_moves = apr_array_make(result_pool, 0,
850 sizeof(struct repos_move_info *));
851 iterpool = svn_pool_create(scratch_pool);
852 for (i = 0; i < deleted_paths->nelts; i++)
853 {
854 const char *deleted_path;
855 const char *child_relpath;
856 const char *moved_along_repos_relpath;
857 struct repos_move_info *move;
858 apr_array_header_t *copies_with_same_source_path;
859 int j;
860 svn_boolean_t related;
861
862 svn_pool_clear(iterpool);
863
864 deleted_path = APR_ARRAY_IDX(deleted_paths, i, const char *);
865 move = map_deleted_path_to_move(deleted_path, moves, iterpool);
866 if (move == NULL)
867 continue;
868 child_relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
869 deleted_path);
870 if (child_relpath == NULL || child_relpath[0] == '\0')
871 continue; /* not a nested move */
872
873 /* Consider: svn mv A B; svn mv B/foo C/foo
874 * Copyfrom for C/foo is A/foo, even though C/foo was moved here from
875 * B/foo. A/foo was not deleted. It is B/foo which was deleted.
876 * We now know about the move A->B and moved-along child_relpath "foo".
877 * Try to detect an ancestral relationship between A/foo and the
878 * moved-along path. */
879 moved_along_repos_relpath =
880 svn_relpath_join(move->moved_from_repos_relpath, child_relpath,
881 iterpool);
882 copies_with_same_source_path = svn_hash_gets(copies,
883 moved_along_repos_relpath);
884 if (copies_with_same_source_path == NULL)
885 continue; /* not a nested move */
886
887 for (j = 0; j < copies_with_same_source_path->nelts; j++)
888 {
889 struct copy_info *copy;
890
891 copy = APR_ARRAY_IDX(copies_with_same_source_path, j,
892 struct copy_info *);
893 SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url,
894 moved_along_repos_relpath,
895 revision,
896 copy->copyfrom_path,
897 copy->copyfrom_rev,
898 TRUE, iterpool));
899 if (related)
900 {
901 struct repos_move_info *nested_move;
902
903 /* Remember details of this move. */
904 SVN_ERR(add_new_move(&nested_move, moved_along_repos_relpath,
905 copy->copyto_path, copy->copyfrom_rev,
906 copy->node_kind,
907 revision, author, moved_paths,
908 ra_session, repos_root_url,
909 result_pool, iterpool));
910
911 /* Add this move to the list of nested moves in this revision. */
912 APR_ARRAY_PUSH(nested_moves, struct repos_move_info *) =
913 nested_move;
914 }
915 }
916 }
917 svn_pool_destroy(iterpool);
918
919 /* Add all nested moves found to the list of all moves in this revision. */
920 apr_array_cat(moves, nested_moves);
921
922 return SVN_NO_ERROR;
923 }
924
925 /* Make a shallow copy of the copied LOG_ITEM in COPIES. */
926 static void
cache_copied_item(apr_hash_t * copies,const char * changed_path,svn_log_changed_path2_t * log_item)927 cache_copied_item(apr_hash_t *copies, const char *changed_path,
928 svn_log_changed_path2_t *log_item)
929 {
930 apr_pool_t *result_pool = apr_hash_pool_get(copies);
931 struct copy_info *copy = apr_palloc(result_pool, sizeof(*copy));
932 apr_array_header_t *copies_with_same_source_path;
933
934 copy->copyfrom_path = log_item->copyfrom_path;
935 if (log_item->copyfrom_path[0] == '/')
936 copy->copyfrom_path++;
937 copy->copyto_path = changed_path;
938 copy->copyfrom_rev = log_item->copyfrom_rev;
939 copy->node_kind = log_item->node_kind;
940
941 copies_with_same_source_path = apr_hash_get(copies, copy->copyfrom_path,
942 APR_HASH_KEY_STRING);
943 if (copies_with_same_source_path == NULL)
944 {
945 copies_with_same_source_path = apr_array_make(result_pool, 1,
946 sizeof(struct copy_info *));
947 apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING,
948 copies_with_same_source_path);
949 }
950 APR_ARRAY_PUSH(copies_with_same_source_path, struct copy_info *) = copy;
951 }
952
953 /* Implements svn_log_entry_receiver_t.
954 *
955 * Find the revision in which a node, optionally ancestrally related to the
956 * node specified via find_deleted_rev_baton, was deleted, When the revision
957 * was found, store it in BATON->DELETED_REV and abort the log operation
958 * by raising SVN_ERR_CEASE_INVOCATION.
959 *
960 * If no such revision can be found, leave BATON->DELETED_REV and
961 * BATON->REPLACING_NODE_KIND alone.
962 *
963 * If the node was replaced, set BATON->REPLACING_NODE_KIND to the node
964 * kind of the node which replaced the original node. If the node was not
965 * replaced, set BATON->REPLACING_NODE_KIND to svn_node_none.
966 *
967 * This function answers the same question as svn_ra_get_deleted_rev() but
968 * works in cases where we do not already know a revision in which the deleted
969 * node once used to exist.
970 *
971 * If the node was moved, rather than deleted, return move information
972 * in BATON->MOVE.
973 */
974 static svn_error_t *
find_deleted_rev(void * baton,svn_log_entry_t * log_entry,apr_pool_t * scratch_pool)975 find_deleted_rev(void *baton,
976 svn_log_entry_t *log_entry,
977 apr_pool_t *scratch_pool)
978 {
979 struct find_deleted_rev_baton *b = baton;
980 apr_hash_index_t *hi;
981 apr_pool_t *iterpool;
982 svn_boolean_t deleted_node_found = FALSE;
983 svn_node_kind_t replacing_node_kind = svn_node_none;
984
985 if (b->ctx->notify_func2)
986 {
987 svn_wc_notify_t *notify;
988
989 notify = svn_wc_create_notify(
990 b->victim_abspath,
991 svn_wc_notify_tree_conflict_details_progress,
992 scratch_pool),
993 notify->revision = log_entry->revision;
994 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
995 }
996
997 /* No paths were changed in this revision. Nothing to do. */
998 if (! log_entry->changed_paths2)
999 return SVN_NO_ERROR;
1000
1001 iterpool = svn_pool_create(scratch_pool);
1002 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
1003 hi != NULL;
1004 hi = apr_hash_next(hi))
1005 {
1006 const char *changed_path = apr_hash_this_key(hi);
1007 svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
1008
1009 svn_pool_clear(iterpool);
1010
1011 /* ### Remove leading slash from paths in log entries. */
1012 if (changed_path[0] == '/')
1013 changed_path++;
1014
1015 /* Check if we already found the deleted node we're looking for. */
1016 if (!deleted_node_found &&
1017 svn_path_compare_paths(b->deleted_repos_relpath, changed_path) == 0 &&
1018 (log_item->action == 'D' || log_item->action == 'R'))
1019 {
1020 deleted_node_found = TRUE;
1021
1022 if (b->related_repos_relpath != NULL &&
1023 b->related_peg_rev != SVN_INVALID_REVNUM)
1024 {
1025 svn_client__pathrev_t *yca_loc;
1026 svn_error_t *err;
1027
1028 /* We found a deleted node which occupies the correct path.
1029 * To be certain that this is the deleted node we're looking for,
1030 * we must establish whether it is ancestrally related to the
1031 * "related node" specified in our baton. */
1032 err = find_yca(&yca_loc,
1033 b->related_repos_relpath,
1034 b->related_peg_rev,
1035 b->deleted_repos_relpath,
1036 rev_below(log_entry->revision),
1037 b->repos_root_url, b->repos_uuid,
1038 b->extra_ra_session, b->ctx, iterpool, iterpool);
1039 if (err)
1040 {
1041 /* ### Happens for moves within other moves and copies. */
1042 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
1043 {
1044 svn_error_clear(err);
1045 yca_loc = NULL;
1046 }
1047 else
1048 return svn_error_trace(err);
1049 }
1050
1051 deleted_node_found = (yca_loc != NULL);
1052 }
1053
1054 if (deleted_node_found && log_item->action == 'R')
1055 replacing_node_kind = log_item->node_kind;
1056 }
1057 }
1058 svn_pool_destroy(iterpool);
1059
1060 if (!deleted_node_found)
1061 {
1062 apr_array_header_t *moves;
1063
1064 if (b->moves_table == NULL)
1065 return SVN_NO_ERROR;
1066
1067 moves = apr_hash_get(b->moves_table, &log_entry->revision,
1068 sizeof(svn_revnum_t));
1069 if (moves)
1070 {
1071 struct repos_move_info *move;
1072
1073 move = map_deleted_path_to_move(b->deleted_repos_relpath,
1074 moves, scratch_pool);
1075 if (move)
1076 {
1077 const char *relpath;
1078
1079 /* The node was moved. Update our search path accordingly. */
1080 b->move = move;
1081 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
1082 b->deleted_repos_relpath);
1083 if (relpath)
1084 b->deleted_repos_relpath =
1085 svn_relpath_join(move->moved_from_repos_relpath, relpath,
1086 b->result_pool);
1087 }
1088 }
1089 }
1090 else
1091 {
1092 svn_string_t *author;
1093
1094 b->deleted_rev = log_entry->revision;
1095 author = svn_hash_gets(log_entry->revprops,
1096 SVN_PROP_REVISION_AUTHOR);
1097 if (author)
1098 b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
1099 else
1100 b->deleted_rev_author = _("unknown author");
1101
1102 b->replacing_node_kind = replacing_node_kind;
1103
1104 /* We're done. Abort the log operation. */
1105 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
1106 }
1107
1108 return SVN_NO_ERROR;
1109 }
1110
1111 /* Return a localised string representation of the local part of a tree
1112 conflict on a file. */
1113 static svn_error_t *
describe_local_file_node_change(const char ** description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1114 describe_local_file_node_change(const char **description,
1115 svn_client_conflict_t *conflict,
1116 svn_client_ctx_t *ctx,
1117 apr_pool_t *result_pool,
1118 apr_pool_t *scratch_pool)
1119 {
1120 svn_wc_conflict_reason_t local_change;
1121 svn_wc_operation_t operation;
1122
1123 local_change = svn_client_conflict_get_local_change(conflict);
1124 operation = svn_client_conflict_get_operation(conflict);
1125
1126 switch (local_change)
1127 {
1128 case svn_wc_conflict_reason_edited:
1129 if (operation == svn_wc_operation_update ||
1130 operation == svn_wc_operation_switch)
1131 *description = _("A file containing uncommitted changes was "
1132 "found in the working copy.");
1133 else if (operation == svn_wc_operation_merge)
1134 *description = _("A file which differs from the corresponding "
1135 "file on the merge source branch was found "
1136 "in the working copy.");
1137 break;
1138 case svn_wc_conflict_reason_obstructed:
1139 *description = _("A file which already occupies this path was found "
1140 "in the working copy.");
1141 break;
1142 case svn_wc_conflict_reason_unversioned:
1143 *description = _("An unversioned file was found in the working "
1144 "copy.");
1145 break;
1146 case svn_wc_conflict_reason_deleted:
1147 *description = _("A deleted file was found in the working copy.");
1148 break;
1149 case svn_wc_conflict_reason_missing:
1150 if (operation == svn_wc_operation_update ||
1151 operation == svn_wc_operation_switch)
1152 *description = _("No such file was found in the working copy.");
1153 else if (operation == svn_wc_operation_merge)
1154 {
1155 /* ### display deleted revision */
1156 *description = _("No such file was found in the merge target "
1157 "working copy.\nPerhaps the file has been "
1158 "deleted or moved away in the repository's "
1159 "history?");
1160 }
1161 break;
1162 case svn_wc_conflict_reason_added:
1163 case svn_wc_conflict_reason_replaced:
1164 {
1165 /* ### show more details about copies or replacements? */
1166 *description = _("A file scheduled to be added to the "
1167 "repository in the next commit was found in "
1168 "the working copy.");
1169 }
1170 break;
1171 case svn_wc_conflict_reason_moved_away:
1172 {
1173 const char *moved_to_abspath;
1174 svn_error_t *err;
1175
1176 err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
1177 ctx->wc_ctx,
1178 conflict->local_abspath,
1179 scratch_pool,
1180 scratch_pool);
1181 if (err)
1182 {
1183 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1184 {
1185 moved_to_abspath = NULL;
1186 svn_error_clear(err);
1187 }
1188 else
1189 return svn_error_trace(err);
1190 }
1191 if (operation == svn_wc_operation_update ||
1192 operation == svn_wc_operation_switch)
1193 {
1194 if (moved_to_abspath == NULL)
1195 {
1196 /* The move no longer exists. */
1197 *description = _("The file in the working copy had "
1198 "been moved away at the time this "
1199 "conflict was recorded.");
1200 }
1201 else
1202 {
1203 const char *wcroot_abspath;
1204
1205 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1206 ctx->wc_ctx,
1207 conflict->local_abspath,
1208 scratch_pool,
1209 scratch_pool));
1210 *description = apr_psprintf(
1211 result_pool,
1212 _("The file in the working copy was "
1213 "moved away to\n'%s'."),
1214 svn_dirent_local_style(
1215 svn_dirent_skip_ancestor(
1216 wcroot_abspath,
1217 moved_to_abspath),
1218 scratch_pool));
1219 }
1220 }
1221 else if (operation == svn_wc_operation_merge)
1222 {
1223 if (moved_to_abspath == NULL)
1224 {
1225 /* The move probably happened in branch history.
1226 * This case cannot happen until we detect incoming
1227 * moves, which we currently don't do. */
1228 /* ### find deleted/moved revision? */
1229 *description = _("The file in the working copy had "
1230 "been moved away at the time this "
1231 "conflict was recorded.");
1232 }
1233 else
1234 {
1235 /* This is a local move in the working copy. */
1236 const char *wcroot_abspath;
1237
1238 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1239 ctx->wc_ctx,
1240 conflict->local_abspath,
1241 scratch_pool,
1242 scratch_pool));
1243 *description = apr_psprintf(
1244 result_pool,
1245 _("The file in the working copy was "
1246 "moved away to\n'%s'."),
1247 svn_dirent_local_style(
1248 svn_dirent_skip_ancestor(
1249 wcroot_abspath,
1250 moved_to_abspath),
1251 scratch_pool));
1252 }
1253 }
1254 break;
1255 }
1256 case svn_wc_conflict_reason_moved_here:
1257 {
1258 const char *moved_from_abspath;
1259
1260 SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1261 ctx->wc_ctx,
1262 conflict->local_abspath,
1263 scratch_pool,
1264 scratch_pool));
1265 if (operation == svn_wc_operation_update ||
1266 operation == svn_wc_operation_switch)
1267 {
1268 if (moved_from_abspath == NULL)
1269 {
1270 /* The move no longer exists. */
1271 *description = _("A file had been moved here in the "
1272 "working copy at the time this "
1273 "conflict was recorded.");
1274 }
1275 else
1276 {
1277 const char *wcroot_abspath;
1278
1279 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1280 ctx->wc_ctx,
1281 conflict->local_abspath,
1282 scratch_pool,
1283 scratch_pool));
1284 *description = apr_psprintf(
1285 result_pool,
1286 _("A file was moved here in the "
1287 "working copy from\n'%s'."),
1288 svn_dirent_local_style(
1289 svn_dirent_skip_ancestor(
1290 wcroot_abspath,
1291 moved_from_abspath),
1292 scratch_pool));
1293 }
1294 }
1295 else if (operation == svn_wc_operation_merge)
1296 {
1297 if (moved_from_abspath == NULL)
1298 {
1299 /* The move probably happened in branch history.
1300 * This case cannot happen until we detect incoming
1301 * moves, which we currently don't do. */
1302 /* ### find deleted/moved revision? */
1303 *description = _("A file had been moved here in the "
1304 "working copy at the time this "
1305 "conflict was recorded.");
1306 }
1307 else
1308 {
1309 const char *wcroot_abspath;
1310
1311 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1312 ctx->wc_ctx,
1313 conflict->local_abspath,
1314 scratch_pool,
1315 scratch_pool));
1316 /* This is a local move in the working copy. */
1317 *description = apr_psprintf(
1318 result_pool,
1319 _("A file was moved here in the "
1320 "working copy from\n'%s'."),
1321 svn_dirent_local_style(
1322 svn_dirent_skip_ancestor(
1323 wcroot_abspath,
1324 moved_from_abspath),
1325 scratch_pool));
1326 }
1327 }
1328 break;
1329 }
1330 }
1331
1332 return SVN_NO_ERROR;
1333 }
1334
1335 /* Return a localised string representation of the local part of a tree
1336 conflict on a directory. */
1337 static svn_error_t *
describe_local_dir_node_change(const char ** description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1338 describe_local_dir_node_change(const char **description,
1339 svn_client_conflict_t *conflict,
1340 svn_client_ctx_t *ctx,
1341 apr_pool_t *result_pool,
1342 apr_pool_t *scratch_pool)
1343 {
1344 svn_wc_conflict_reason_t local_change;
1345 svn_wc_operation_t operation;
1346
1347 local_change = svn_client_conflict_get_local_change(conflict);
1348 operation = svn_client_conflict_get_operation(conflict);
1349
1350 switch (local_change)
1351 {
1352 case svn_wc_conflict_reason_edited:
1353 if (operation == svn_wc_operation_update ||
1354 operation == svn_wc_operation_switch)
1355 *description = _("A directory containing uncommitted changes "
1356 "was found in the working copy.");
1357 else if (operation == svn_wc_operation_merge)
1358 *description = _("A directory which differs from the "
1359 "corresponding directory on the merge source "
1360 "branch was found in the working copy.");
1361 break;
1362 case svn_wc_conflict_reason_obstructed:
1363 *description = _("A directory which already occupies this path was "
1364 "found in the working copy.");
1365 break;
1366 case svn_wc_conflict_reason_unversioned:
1367 *description = _("An unversioned directory was found in the "
1368 "working copy.");
1369 break;
1370 case svn_wc_conflict_reason_deleted:
1371 *description = _("A deleted directory was found in the "
1372 "working copy.");
1373 break;
1374 case svn_wc_conflict_reason_missing:
1375 if (operation == svn_wc_operation_update ||
1376 operation == svn_wc_operation_switch)
1377 *description = _("No such directory was found in the working copy.");
1378 else if (operation == svn_wc_operation_merge)
1379 {
1380 /* ### display deleted revision */
1381 *description = _("No such directory was found in the merge "
1382 "target working copy.\nPerhaps the "
1383 "directory has been deleted or moved away "
1384 "in the repository's history?");
1385 }
1386 break;
1387 case svn_wc_conflict_reason_added:
1388 case svn_wc_conflict_reason_replaced:
1389 {
1390 /* ### show more details about copies or replacements? */
1391 *description = _("A directory scheduled to be added to the "
1392 "repository in the next commit was found in "
1393 "the working copy.");
1394 }
1395 break;
1396 case svn_wc_conflict_reason_moved_away:
1397 {
1398 const char *moved_to_abspath;
1399 svn_error_t *err;
1400
1401 err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
1402 ctx->wc_ctx,
1403 conflict->local_abspath,
1404 scratch_pool,
1405 scratch_pool);
1406 if (err)
1407 {
1408 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1409 {
1410 moved_to_abspath = NULL;
1411 svn_error_clear(err);
1412 }
1413 else
1414 return svn_error_trace(err);
1415 }
1416
1417 if (operation == svn_wc_operation_update ||
1418 operation == svn_wc_operation_switch)
1419 {
1420 if (moved_to_abspath == NULL)
1421 {
1422 /* The move no longer exists. */
1423 *description = _("The directory in the working copy "
1424 "had been moved away at the time "
1425 "this conflict was recorded.");
1426 }
1427 else
1428 {
1429 const char *wcroot_abspath;
1430
1431 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1432 ctx->wc_ctx,
1433 conflict->local_abspath,
1434 scratch_pool,
1435 scratch_pool));
1436 *description = apr_psprintf(
1437 result_pool,
1438 _("The directory in the working copy "
1439 "was moved away to\n'%s'."),
1440 svn_dirent_local_style(
1441 svn_dirent_skip_ancestor(
1442 wcroot_abspath,
1443 moved_to_abspath),
1444 scratch_pool));
1445 }
1446 }
1447 else if (operation == svn_wc_operation_merge)
1448 {
1449 if (moved_to_abspath == NULL)
1450 {
1451 /* The move probably happened in branch history.
1452 * This case cannot happen until we detect incoming
1453 * moves, which we currently don't do. */
1454 /* ### find deleted/moved revision? */
1455 *description = _("The directory had been moved away "
1456 "at the time this conflict was "
1457 "recorded.");
1458 }
1459 else
1460 {
1461 /* This is a local move in the working copy. */
1462 const char *wcroot_abspath;
1463
1464 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1465 ctx->wc_ctx,
1466 conflict->local_abspath,
1467 scratch_pool,
1468 scratch_pool));
1469 *description = apr_psprintf(
1470 result_pool,
1471 _("The directory was moved away to\n"
1472 "'%s'."),
1473 svn_dirent_local_style(
1474 svn_dirent_skip_ancestor(
1475 wcroot_abspath,
1476 moved_to_abspath),
1477 scratch_pool));
1478 }
1479 }
1480 }
1481 break;
1482 case svn_wc_conflict_reason_moved_here:
1483 {
1484 const char *moved_from_abspath;
1485
1486 SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1487 ctx->wc_ctx,
1488 conflict->local_abspath,
1489 scratch_pool,
1490 scratch_pool));
1491 if (operation == svn_wc_operation_update ||
1492 operation == svn_wc_operation_switch)
1493 {
1494 if (moved_from_abspath == NULL)
1495 {
1496 /* The move no longer exists. */
1497 *description = _("A directory had been moved here at "
1498 "the time this conflict was "
1499 "recorded.");
1500 }
1501 else
1502 {
1503 const char *wcroot_abspath;
1504
1505 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1506 ctx->wc_ctx,
1507 conflict->local_abspath,
1508 scratch_pool,
1509 scratch_pool));
1510 *description = apr_psprintf(
1511 result_pool,
1512 _("A directory was moved here from\n"
1513 "'%s'."),
1514 svn_dirent_local_style(
1515 svn_dirent_skip_ancestor(
1516 wcroot_abspath,
1517 moved_from_abspath),
1518 scratch_pool));
1519 }
1520 }
1521 else if (operation == svn_wc_operation_merge)
1522 {
1523 if (moved_from_abspath == NULL)
1524 {
1525 /* The move probably happened in branch history.
1526 * This case cannot happen until we detect incoming
1527 * moves, which we currently don't do. */
1528 /* ### find deleted/moved revision? */
1529 *description = _("A directory had been moved here at "
1530 "the time this conflict was "
1531 "recorded.");
1532 }
1533 else
1534 {
1535 /* This is a local move in the working copy. */
1536 const char *wcroot_abspath;
1537
1538 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1539 ctx->wc_ctx,
1540 conflict->local_abspath,
1541 scratch_pool,
1542 scratch_pool));
1543 *description = apr_psprintf(
1544 result_pool,
1545 _("A directory was moved here in "
1546 "the working copy from\n'%s'."),
1547 svn_dirent_local_style(
1548 svn_dirent_skip_ancestor(
1549 wcroot_abspath,
1550 moved_from_abspath),
1551 scratch_pool));
1552 }
1553 }
1554 }
1555 }
1556
1557 return SVN_NO_ERROR;
1558 }
1559
1560 struct find_moves_baton
1561 {
1562 /* Variables below are arguments provided by the caller of
1563 * svn_ra_get_log2(). */
1564 const char *repos_root_url;
1565 const char *repos_uuid;
1566 svn_client_ctx_t *ctx;
1567 const char *victim_abspath; /* for notifications */
1568 apr_pool_t *result_pool;
1569
1570 /* A hash table mapping a revision number to an array of struct
1571 * repos_move_info * elements, describing moves.
1572 *
1573 * Must be allocated in RESULT_POOL by the caller of svn_ra_get_log2().
1574 *
1575 * If the node was moved, the DELETED_REV is present in this table,
1576 * perhaps along with additional revisions.
1577 *
1578 * Given a sequence of moves which happened in the repository, such as:
1579 * rA: mv x->z
1580 * rA: mv a->b
1581 * rB: mv b->c
1582 * rC: mv c->d
1583 * we map each revision number to all the moves which happened in the
1584 * revision, which looks as follows:
1585 * rA : [(x->z), (a->b)]
1586 * rB : [(b->c)]
1587 * rC : [(c->d)]
1588 * This allows us to later find relevant moves based on a revision number.
1589 *
1590 * Additionally, we embed the number of the revision in which a move was
1591 * found inside the repos_move_info structure:
1592 * rA : [(rA, x->z), (rA, a->b)]
1593 * rB : [(rB, b->c)]
1594 * rC : [(rC, c->d)]
1595 * And also, all moves pertaining to the same node are chained into a
1596 * doubly-linked list via 'next' and 'prev' pointers (see definition of
1597 * struct repos_move_info). This can be visualized as follows:
1598 * rA : [(rA, x->z, prev=>NULL, next=>NULL),
1599 * (rA, a->b, prev=>NULL, next=>(rB, b->c))]
1600 * rB : [(rB, b->c), prev=>(rA, a->b), next=>(rC, c->d)]
1601 * rC : [(rC, c->d), prev=>(rB, c->d), next=>NULL]
1602 * This way, we can look up all moves relevant to a node, forwards and
1603 * backwards in history, once we have located one move in the chain.
1604 *
1605 * In the above example, the data tells us that within the revision
1606 * range rA:C, a was moved to d. However, within the revision range
1607 * rA;B, a was moved to b.
1608 */
1609 apr_hash_t *moves_table;
1610
1611 /* Variables below hold state for find_moves() and are not
1612 * intended to be used by the caller of svn_ra_get_log2().
1613 * Like all other variables, they must be initialized, however. */
1614
1615 /* Temporary map of moved paths to struct repos_move_info.
1616 * Used to link multiple moves of the same node across revisions. */
1617 apr_hash_t *moved_paths;
1618
1619 /* Extra RA session that can be used to make additional requests. */
1620 svn_ra_session_t *extra_ra_session;
1621 };
1622
1623 /* Implements svn_log_entry_receiver_t. */
1624 static svn_error_t *
find_moves(void * baton,svn_log_entry_t * log_entry,apr_pool_t * scratch_pool)1625 find_moves(void *baton, svn_log_entry_t *log_entry, apr_pool_t *scratch_pool)
1626 {
1627 struct find_moves_baton *b = baton;
1628 apr_hash_index_t *hi;
1629 apr_pool_t *iterpool;
1630 apr_array_header_t *deleted_paths;
1631 apr_hash_t *copies;
1632 apr_array_header_t *moves;
1633
1634 if (b->ctx->notify_func2)
1635 {
1636 svn_wc_notify_t *notify;
1637
1638 notify = svn_wc_create_notify(
1639 b->victim_abspath,
1640 svn_wc_notify_tree_conflict_details_progress,
1641 scratch_pool),
1642 notify->revision = log_entry->revision;
1643 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
1644 }
1645
1646 /* No paths were changed in this revision. Nothing to do. */
1647 if (! log_entry->changed_paths2)
1648 return SVN_NO_ERROR;
1649
1650 copies = apr_hash_make(scratch_pool);
1651 deleted_paths = apr_array_make(scratch_pool, 0, sizeof(const char *));
1652 iterpool = svn_pool_create(scratch_pool);
1653 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
1654 hi != NULL;
1655 hi = apr_hash_next(hi))
1656 {
1657 const char *changed_path = apr_hash_this_key(hi);
1658 svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
1659
1660 svn_pool_clear(iterpool);
1661
1662 /* ### Remove leading slash from paths in log entries. */
1663 if (changed_path[0] == '/')
1664 changed_path++;
1665
1666 /* For move detection, scan for copied nodes in this revision. */
1667 if (log_item->action == 'A' && log_item->copyfrom_path)
1668 cache_copied_item(copies, changed_path, log_item);
1669
1670 /* For move detection, store all deleted_paths. */
1671 if (log_item->action == 'D' || log_item->action == 'R')
1672 APR_ARRAY_PUSH(deleted_paths, const char *) =
1673 apr_pstrdup(scratch_pool, changed_path);
1674 }
1675 svn_pool_destroy(iterpool);
1676
1677 /* Check for moves in this revision */
1678 SVN_ERR(find_moves_in_revision(b->extra_ra_session,
1679 b->moves_table, b->moved_paths,
1680 log_entry, copies, deleted_paths,
1681 b->repos_root_url, b->repos_uuid,
1682 b->ctx, b->result_pool, scratch_pool));
1683
1684 moves = apr_hash_get(b->moves_table, &log_entry->revision,
1685 sizeof(svn_revnum_t));
1686 if (moves)
1687 {
1688 const svn_string_t *author;
1689
1690 author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
1691 SVN_ERR(find_nested_moves(moves, copies, deleted_paths,
1692 b->moved_paths, log_entry->revision,
1693 author ? author->data : _("unknown author"),
1694 b->repos_root_url,
1695 b->repos_uuid,
1696 b->extra_ra_session, b->ctx,
1697 b->result_pool, scratch_pool));
1698 }
1699
1700 return SVN_NO_ERROR;
1701 }
1702
1703 /* Find all moves which occured in repository history starting at
1704 * REPOS_RELPATH@START_REV until END_REV (where START_REV > END_REV).
1705 * Return results in *MOVES_TABLE (see struct find_moves_baton for details). */
1706 static svn_error_t *
find_moves_in_revision_range(struct apr_hash_t ** moves_table,const char * repos_relpath,const char * repos_root_url,const char * repos_uuid,const char * victim_abspath,svn_revnum_t start_rev,svn_revnum_t end_rev,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1707 find_moves_in_revision_range(struct apr_hash_t **moves_table,
1708 const char *repos_relpath,
1709 const char *repos_root_url,
1710 const char *repos_uuid,
1711 const char *victim_abspath,
1712 svn_revnum_t start_rev,
1713 svn_revnum_t end_rev,
1714 svn_client_ctx_t *ctx,
1715 apr_pool_t *result_pool,
1716 apr_pool_t *scratch_pool)
1717 {
1718 svn_ra_session_t *ra_session;
1719 const char *url;
1720 const char *corrected_url;
1721 apr_array_header_t *paths;
1722 apr_array_header_t *revprops;
1723 struct find_moves_baton b = { 0 };
1724
1725 SVN_ERR_ASSERT(start_rev > end_rev);
1726
1727 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
1728 scratch_pool);
1729 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
1730 url, NULL, NULL, FALSE, FALSE,
1731 ctx, scratch_pool,
1732 scratch_pool));
1733
1734 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
1735 APR_ARRAY_PUSH(paths, const char *) = "";
1736
1737 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
1738 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
1739
1740 b.repos_root_url = repos_root_url;
1741 b.repos_uuid = repos_uuid;
1742 b.ctx = ctx;
1743 b.victim_abspath = victim_abspath;
1744 b.moves_table = apr_hash_make(result_pool);
1745 b.moved_paths = apr_hash_make(scratch_pool);
1746 b.result_pool = result_pool;
1747 SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL,
1748 scratch_pool, scratch_pool));
1749
1750 SVN_ERR(svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
1751 0, /* no limit */
1752 TRUE, /* need the changed paths list */
1753 FALSE, /* need to traverse copies */
1754 FALSE, /* no need for merged revisions */
1755 revprops,
1756 find_moves, &b,
1757 scratch_pool));
1758
1759 *moves_table = b.moves_table;
1760
1761 return SVN_NO_ERROR;
1762 }
1763
1764 /* Return new move information for a moved-along child MOVED_ALONG_RELPATH.
1765 * Set MOVE->NODE_KIND to MOVED_ALONG_NODE_KIND.
1766 * Do not copy MOVE->NEXT and MOVE-PREV.
1767 * If MOVED_ALONG_RELPATH is empty, this effectively copies MOVE to
1768 * RESULT_POOL with NEXT and PREV pointers cleared. */
1769 static struct repos_move_info *
new_path_adjusted_move(struct repos_move_info * move,const char * moved_along_relpath,svn_node_kind_t moved_along_node_kind,apr_pool_t * result_pool)1770 new_path_adjusted_move(struct repos_move_info *move,
1771 const char *moved_along_relpath,
1772 svn_node_kind_t moved_along_node_kind,
1773 apr_pool_t *result_pool)
1774 {
1775 struct repos_move_info *new_move;
1776
1777 new_move = apr_pcalloc(result_pool, sizeof(*new_move));
1778 new_move->moved_from_repos_relpath =
1779 svn_relpath_join(move->moved_from_repos_relpath, moved_along_relpath,
1780 result_pool);
1781 new_move->moved_to_repos_relpath =
1782 svn_relpath_join(move->moved_to_repos_relpath, moved_along_relpath,
1783 result_pool);
1784 new_move->rev = move->rev;
1785 new_move->rev_author = apr_pstrdup(result_pool, move->rev_author);
1786 new_move->copyfrom_rev = move->copyfrom_rev;
1787 new_move->node_kind = moved_along_node_kind;
1788 /* Ignore prev and next pointers. Caller will set them if needed. */
1789
1790 return new_move;
1791 }
1792
1793 /* Given a list of MOVES_IN_REVISION, figure out which of these moves again
1794 * move the node which was already moved by PREV_MOVE in the past . */
1795 static svn_error_t *
find_next_moves_in_revision(apr_array_header_t ** next_moves,apr_array_header_t * moves_in_revision,struct repos_move_info * prev_move,svn_ra_session_t * ra_session,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1796 find_next_moves_in_revision(apr_array_header_t **next_moves,
1797 apr_array_header_t *moves_in_revision,
1798 struct repos_move_info *prev_move,
1799 svn_ra_session_t *ra_session,
1800 const char *repos_root_url,
1801 apr_pool_t *result_pool,
1802 apr_pool_t *scratch_pool)
1803 {
1804 int i;
1805 apr_pool_t *iterpool;
1806
1807 iterpool = svn_pool_create(scratch_pool);
1808 for (i = 0; i < moves_in_revision->nelts; i++)
1809 {
1810 struct repos_move_info *move;
1811 const char *relpath;
1812 const char *deleted_repos_relpath;
1813 svn_boolean_t related;
1814 svn_error_t *err;
1815
1816 svn_pool_clear(iterpool);
1817
1818 /* Check if this move affects the current known path of our node. */
1819 move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *);
1820 relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath,
1821 prev_move->moved_to_repos_relpath);
1822 if (relpath == NULL)
1823 continue;
1824
1825 /* It does. So our node must have been deleted again. */
1826 deleted_repos_relpath = svn_relpath_join(move->moved_from_repos_relpath,
1827 relpath, iterpool);
1828
1829 /* Tracing back history of the delete-half of this move to the
1830 * copyfrom-revision of the prior move we must end up at the
1831 * delete-half of the prior move. */
1832 err = check_move_ancestry(&related, ra_session, repos_root_url,
1833 deleted_repos_relpath, move->rev,
1834 prev_move->moved_from_repos_relpath,
1835 prev_move->copyfrom_rev,
1836 FALSE, scratch_pool);
1837 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1838 {
1839 svn_error_clear(err);
1840 continue;
1841 }
1842 else
1843 SVN_ERR(err);
1844
1845 if (related)
1846 {
1847 struct repos_move_info *new_move;
1848
1849 /* We have a winner. */
1850 new_move = new_path_adjusted_move(move, relpath, prev_move->node_kind,
1851 result_pool);
1852 if (*next_moves == NULL)
1853 *next_moves = apr_array_make(result_pool, 1,
1854 sizeof(struct repos_move_info *));
1855 APR_ARRAY_PUSH(*next_moves, struct repos_move_info *) = new_move;
1856 }
1857 }
1858 svn_pool_destroy(iterpool);
1859
1860 return SVN_NO_ERROR;
1861 }
1862
1863 static int
compare_items_as_revs(const svn_sort__item_t * a,const svn_sort__item_t * b)1864 compare_items_as_revs(const svn_sort__item_t *a, const svn_sort__item_t *b)
1865 {
1866 return svn_sort_compare_revisions(a->key, b->key);
1867 }
1868
1869 /* Starting at MOVE->REV, loop over future revisions which contain moves,
1870 * and look for matching next moves in each. Once found, return a list of
1871 * (ambiguous, if more than one) moves in *NEXT_MOVES. */
1872 static svn_error_t *
find_next_moves(apr_array_header_t ** next_moves,apr_hash_t * moves_table,struct repos_move_info * move,svn_ra_session_t * ra_session,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1873 find_next_moves(apr_array_header_t **next_moves,
1874 apr_hash_t *moves_table,
1875 struct repos_move_info *move,
1876 svn_ra_session_t *ra_session,
1877 const char *repos_root_url,
1878 apr_pool_t *result_pool,
1879 apr_pool_t *scratch_pool)
1880 {
1881 apr_array_header_t *moves;
1882 apr_array_header_t *revisions;
1883 apr_pool_t *iterpool;
1884 int i;
1885
1886 *next_moves = NULL;
1887 revisions = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool);
1888 iterpool = svn_pool_create(scratch_pool);
1889 for (i = 0; i < revisions->nelts; i++)
1890 {
1891 svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t);
1892 svn_revnum_t rev = *(svn_revnum_t *)item.key;
1893
1894 svn_pool_clear(iterpool);
1895
1896 if (rev <= move->rev)
1897 continue;
1898
1899 moves = apr_hash_get(moves_table, &rev, sizeof(rev));
1900 SVN_ERR(find_next_moves_in_revision(next_moves, moves, move,
1901 ra_session, repos_root_url,
1902 result_pool, iterpool));
1903 if (*next_moves)
1904 break;
1905 }
1906 svn_pool_destroy(iterpool);
1907
1908 return SVN_NO_ERROR;
1909 }
1910
1911 /* Trace all future moves of the node moved by MOVE.
1912 * Update MOVE->PREV and MOVE->NEXT accordingly. */
1913 static svn_error_t *
trace_moved_node(apr_hash_t * moves_table,struct repos_move_info * move,svn_ra_session_t * ra_session,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1914 trace_moved_node(apr_hash_t *moves_table,
1915 struct repos_move_info *move,
1916 svn_ra_session_t *ra_session,
1917 const char *repos_root_url,
1918 apr_pool_t *result_pool,
1919 apr_pool_t *scratch_pool)
1920 {
1921 apr_array_header_t *next_moves;
1922
1923 SVN_ERR(find_next_moves(&next_moves, moves_table, move,
1924 ra_session, repos_root_url,
1925 result_pool, scratch_pool));
1926 if (next_moves)
1927 {
1928 int i;
1929 apr_pool_t *iterpool;
1930
1931 move->next = next_moves;
1932 iterpool = svn_pool_create(scratch_pool);
1933 for (i = 0; i < next_moves->nelts; i++)
1934 {
1935 struct repos_move_info *next_move;
1936
1937 svn_pool_clear(iterpool);
1938 next_move = APR_ARRAY_IDX(next_moves, i, struct repos_move_info *);
1939 next_move->prev = move;
1940 SVN_ERR(trace_moved_node(moves_table, next_move,
1941 ra_session, repos_root_url,
1942 result_pool, iterpool));
1943 }
1944 svn_pool_destroy(iterpool);
1945 }
1946
1947 return SVN_NO_ERROR;
1948 }
1949
1950 /* Given a list of MOVES_IN_REVISION, figure out which of these moves
1951 * move the node which was later on moved by NEXT_MOVE. */
1952 static svn_error_t *
find_prev_move_in_revision(struct repos_move_info ** prev_move,apr_array_header_t * moves_in_revision,struct repos_move_info * next_move,svn_ra_session_t * ra_session,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1953 find_prev_move_in_revision(struct repos_move_info **prev_move,
1954 apr_array_header_t *moves_in_revision,
1955 struct repos_move_info *next_move,
1956 svn_ra_session_t *ra_session,
1957 const char *repos_root_url,
1958 apr_pool_t *result_pool,
1959 apr_pool_t *scratch_pool)
1960 {
1961 int i;
1962 apr_pool_t *iterpool;
1963
1964 *prev_move = NULL;
1965
1966 iterpool = svn_pool_create(scratch_pool);
1967 for (i = 0; i < moves_in_revision->nelts; i++)
1968 {
1969 struct repos_move_info *move;
1970 const char *relpath;
1971 const char *deleted_repos_relpath;
1972 svn_boolean_t related;
1973 svn_error_t *err;
1974
1975 svn_pool_clear(iterpool);
1976
1977 /* Check if this move affects the current known path of our node. */
1978 move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *);
1979 relpath = svn_relpath_skip_ancestor(next_move->moved_from_repos_relpath,
1980 move->moved_to_repos_relpath);
1981 if (relpath == NULL)
1982 continue;
1983
1984 /* It does. So our node must have been deleted. */
1985 deleted_repos_relpath = svn_relpath_join(
1986 next_move->moved_from_repos_relpath,
1987 relpath, iterpool);
1988
1989 /* Tracing back history of the delete-half of the next move to the
1990 * copyfrom-revision of the prior move we must end up at the
1991 * delete-half of the prior move. */
1992 err = check_move_ancestry(&related, ra_session, repos_root_url,
1993 deleted_repos_relpath, next_move->rev,
1994 move->moved_from_repos_relpath,
1995 move->copyfrom_rev,
1996 FALSE, scratch_pool);
1997 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1998 {
1999 svn_error_clear(err);
2000 continue;
2001 }
2002 else
2003 SVN_ERR(err);
2004
2005 if (related)
2006 {
2007 /* We have a winner. */
2008 *prev_move = new_path_adjusted_move(move, relpath,
2009 next_move->node_kind,
2010 result_pool);
2011 break;
2012 }
2013 }
2014 svn_pool_destroy(iterpool);
2015
2016 return SVN_NO_ERROR;
2017 }
2018
2019 static int
compare_items_as_revs_reverse(const svn_sort__item_t * a,const svn_sort__item_t * b)2020 compare_items_as_revs_reverse(const svn_sort__item_t *a,
2021 const svn_sort__item_t *b)
2022 {
2023 int c = svn_sort_compare_revisions(a->key, b->key);
2024 if (c < 0)
2025 return 1;
2026 if (c > 0)
2027 return -1;
2028 return c;
2029 }
2030
2031 /* Starting at MOVE->REV, loop over past revisions which contain moves,
2032 * and look for a matching previous move in each. Once found, return
2033 * it in *PREV_MOVE */
2034 static svn_error_t *
find_prev_move(struct repos_move_info ** prev_move,apr_hash_t * moves_table,struct repos_move_info * move,svn_ra_session_t * ra_session,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2035 find_prev_move(struct repos_move_info **prev_move,
2036 apr_hash_t *moves_table,
2037 struct repos_move_info *move,
2038 svn_ra_session_t *ra_session,
2039 const char *repos_root_url,
2040 apr_pool_t *result_pool,
2041 apr_pool_t *scratch_pool)
2042 {
2043 apr_array_header_t *moves;
2044 apr_array_header_t *revisions;
2045 apr_pool_t *iterpool;
2046 int i;
2047
2048 *prev_move = NULL;
2049 revisions = svn_sort__hash(moves_table, compare_items_as_revs_reverse,
2050 scratch_pool);
2051 iterpool = svn_pool_create(scratch_pool);
2052 for (i = 0; i < revisions->nelts; i++)
2053 {
2054 svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t);
2055 svn_revnum_t rev = *(svn_revnum_t *)item.key;
2056
2057 svn_pool_clear(iterpool);
2058
2059 if (rev >= move->rev)
2060 continue;
2061
2062 moves = apr_hash_get(moves_table, &rev, sizeof(rev));
2063 SVN_ERR(find_prev_move_in_revision(prev_move, moves, move,
2064 ra_session, repos_root_url,
2065 result_pool, iterpool));
2066 if (*prev_move)
2067 break;
2068 }
2069 svn_pool_destroy(iterpool);
2070
2071 return SVN_NO_ERROR;
2072 }
2073
2074
2075 /* Trace all past moves of the node moved by MOVE.
2076 * Update MOVE->PREV and MOVE->NEXT accordingly. */
2077 static svn_error_t *
trace_moved_node_backwards(apr_hash_t * moves_table,struct repos_move_info * move,svn_ra_session_t * ra_session,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2078 trace_moved_node_backwards(apr_hash_t *moves_table,
2079 struct repos_move_info *move,
2080 svn_ra_session_t *ra_session,
2081 const char *repos_root_url,
2082 apr_pool_t *result_pool,
2083 apr_pool_t *scratch_pool)
2084 {
2085 struct repos_move_info *prev_move;
2086
2087 SVN_ERR(find_prev_move(&prev_move, moves_table, move,
2088 ra_session, repos_root_url,
2089 result_pool, scratch_pool));
2090 if (prev_move)
2091 {
2092 move->prev = prev_move;
2093 prev_move->next = apr_array_make(result_pool, 1,
2094 sizeof(struct repos_move_info *));
2095 APR_ARRAY_PUSH(prev_move->next, struct repos_move_info *) = move;
2096
2097 SVN_ERR(trace_moved_node_backwards(moves_table, prev_move,
2098 ra_session, repos_root_url,
2099 result_pool, scratch_pool));
2100 }
2101
2102 return SVN_NO_ERROR;
2103 }
2104
2105 /* Scan MOVES_TABLE for moves which affect a particular deleted node, and
2106 * build a set of new move information for this node.
2107 * Return heads of all possible move chains in *MOVES.
2108 *
2109 * MOVES_TABLE describes moves which happened at arbitrary paths in the
2110 * repository. DELETED_REPOS_RELPATH may have been moved directly or it
2111 * may have been moved along with a parent path. Move information returned
2112 * from this function represents how DELETED_REPOS_RELPATH itself was moved
2113 * from one path to another, effectively "zooming in" on the effective move
2114 * operations which occurred for this particular node. */
2115 static svn_error_t *
find_operative_moves(apr_array_header_t ** moves,apr_hash_t * moves_table,const char * deleted_repos_relpath,svn_revnum_t deleted_rev,svn_ra_session_t * ra_session,const char * repos_root_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2116 find_operative_moves(apr_array_header_t **moves,
2117 apr_hash_t *moves_table,
2118 const char *deleted_repos_relpath,
2119 svn_revnum_t deleted_rev,
2120 svn_ra_session_t *ra_session,
2121 const char *repos_root_url,
2122 apr_pool_t *result_pool,
2123 apr_pool_t *scratch_pool)
2124 {
2125 apr_array_header_t *moves_in_deleted_rev;
2126 int i;
2127 apr_pool_t *iterpool;
2128 const char *session_url, *url = NULL;
2129
2130 moves_in_deleted_rev = apr_hash_get(moves_table, &deleted_rev,
2131 sizeof(deleted_rev));
2132 if (moves_in_deleted_rev == NULL)
2133 {
2134 *moves = NULL;
2135 return SVN_NO_ERROR;
2136 }
2137
2138 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool));
2139
2140 /* Look for operative moves in the revision where the node was deleted. */
2141 *moves = apr_array_make(scratch_pool, 0, sizeof(struct repos_move_info *));
2142 iterpool = svn_pool_create(scratch_pool);
2143 for (i = 0; i < moves_in_deleted_rev->nelts; i++)
2144 {
2145 struct repos_move_info *move;
2146 const char *relpath;
2147
2148 svn_pool_clear(iterpool);
2149
2150 move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *);
2151 if (strcmp(move->moved_from_repos_relpath, deleted_repos_relpath) == 0)
2152 {
2153 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
2154 continue;
2155 }
2156
2157 /* Test for an operative nested move. */
2158 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
2159 deleted_repos_relpath);
2160 if (relpath && relpath[0] != '\0')
2161 {
2162 struct repos_move_info *nested_move;
2163 const char *actual_deleted_repos_relpath;
2164
2165 actual_deleted_repos_relpath =
2166 svn_relpath_join(move->moved_from_repos_relpath, relpath,
2167 iterpool);
2168 nested_move = map_deleted_path_to_move(actual_deleted_repos_relpath,
2169 moves_in_deleted_rev,
2170 iterpool);
2171 if (nested_move)
2172 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = nested_move;
2173 }
2174 }
2175
2176 if (url != NULL)
2177 SVN_ERR(svn_ra_reparent(ra_session, session_url, scratch_pool));
2178
2179 /* If we didn't find any applicable moves, return NULL. */
2180 if ((*moves)->nelts == 0)
2181 {
2182 *moves = NULL;
2183 svn_pool_destroy(iterpool);
2184 return SVN_NO_ERROR;
2185 }
2186
2187 /* Figure out what happened to these moves in future revisions. */
2188 for (i = 0; i < (*moves)->nelts; i++)
2189 {
2190 struct repos_move_info *move;
2191
2192 svn_pool_clear(iterpool);
2193
2194 move = APR_ARRAY_IDX(*moves, i, struct repos_move_info *);
2195 SVN_ERR(trace_moved_node(moves_table, move, ra_session, repos_root_url,
2196 result_pool, iterpool));
2197 }
2198
2199 svn_pool_destroy(iterpool);
2200 return SVN_NO_ERROR;
2201 }
2202
2203 /* Try to find a revision older than START_REV, and its author, which deleted
2204 * DELETED_BASENAME in the directory PARENT_REPOS_RELPATH. Assume the deleted
2205 * node is ancestrally related to RELATED_REPOS_RELPATH@RELATED_PEG_REV.
2206 * If no such revision can be found, set *DELETED_REV to SVN_INVALID_REVNUM
2207 * and *DELETED_REV_AUTHOR to NULL.
2208 * If the node was replaced rather than deleted, set *REPLACING_NODE_KIND to
2209 * the node kind of the replacing node. Else, set it to svn_node_unknown.
2210 * Only request the log for revisions up to END_REV from the server.
2211 * If MOVES it not NULL, and the deleted node was moved, provide heads of
2212 * move chains in *MOVES, or, if the node was not moved, set *MOVES to NULL.
2213 */
2214 static svn_error_t *
find_revision_for_suspected_deletion(svn_revnum_t * deleted_rev,const char ** deleted_rev_author,svn_node_kind_t * replacing_node_kind,struct apr_array_header_t ** moves,svn_client_conflict_t * conflict,const char * deleted_basename,const char * parent_repos_relpath,svn_revnum_t start_rev,svn_revnum_t end_rev,const char * related_repos_relpath,svn_revnum_t related_peg_rev,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2215 find_revision_for_suspected_deletion(svn_revnum_t *deleted_rev,
2216 const char **deleted_rev_author,
2217 svn_node_kind_t *replacing_node_kind,
2218 struct apr_array_header_t **moves,
2219 svn_client_conflict_t *conflict,
2220 const char *deleted_basename,
2221 const char *parent_repos_relpath,
2222 svn_revnum_t start_rev,
2223 svn_revnum_t end_rev,
2224 const char *related_repos_relpath,
2225 svn_revnum_t related_peg_rev,
2226 svn_client_ctx_t *ctx,
2227 apr_pool_t *result_pool,
2228 apr_pool_t *scratch_pool)
2229 {
2230 svn_ra_session_t *ra_session;
2231 const char *url;
2232 const char *corrected_url;
2233 apr_array_header_t *paths;
2234 apr_array_header_t *revprops;
2235 const char *repos_root_url;
2236 const char *repos_uuid;
2237 struct find_deleted_rev_baton b = { 0 };
2238 const char *victim_abspath;
2239 svn_error_t *err;
2240 apr_hash_t *moves_table;
2241
2242 SVN_ERR_ASSERT(start_rev > end_rev);
2243
2244 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
2245 conflict, scratch_pool,
2246 scratch_pool));
2247 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
2248
2249 if (moves)
2250 SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath,
2251 repos_root_url, repos_uuid,
2252 victim_abspath, start_rev, end_rev,
2253 ctx, result_pool, scratch_pool));
2254
2255 url = svn_path_url_add_component2(repos_root_url, parent_repos_relpath,
2256 scratch_pool);
2257 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
2258 url, NULL, NULL, FALSE, FALSE,
2259 ctx, scratch_pool,
2260 scratch_pool));
2261
2262 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
2263 APR_ARRAY_PUSH(paths, const char *) = "";
2264
2265 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
2266 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
2267
2268 b.victim_abspath = victim_abspath;
2269 b.deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
2270 deleted_basename, scratch_pool);
2271 b.related_repos_relpath = related_repos_relpath;
2272 b.related_peg_rev = related_peg_rev;
2273 b.deleted_rev = SVN_INVALID_REVNUM;
2274 b.replacing_node_kind = svn_node_unknown;
2275 b.repos_root_url = repos_root_url;
2276 b.repos_uuid = repos_uuid;
2277 b.ctx = ctx;
2278 if (moves)
2279 b.moves_table = moves_table;
2280 b.result_pool = result_pool;
2281 SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL,
2282 scratch_pool, scratch_pool));
2283
2284 err = svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
2285 0, /* no limit */
2286 TRUE, /* need the changed paths list */
2287 FALSE, /* need to traverse copies */
2288 FALSE, /* no need for merged revisions */
2289 revprops,
2290 find_deleted_rev, &b,
2291 scratch_pool);
2292 if (err)
2293 {
2294 if (err->apr_err == SVN_ERR_CEASE_INVOCATION &&
2295 b.deleted_rev != SVN_INVALID_REVNUM)
2296
2297 {
2298 /* Log operation was aborted because we found deleted rev. */
2299 svn_error_clear(err);
2300 }
2301 else
2302 return svn_error_trace(err);
2303 }
2304
2305 if (b.deleted_rev == SVN_INVALID_REVNUM)
2306 {
2307 struct repos_move_info *move = b.move;
2308
2309 if (moves && move)
2310 {
2311 *deleted_rev = move->rev;
2312 *deleted_rev_author = move->rev_author;
2313 *replacing_node_kind = b.replacing_node_kind;
2314 SVN_ERR(find_operative_moves(moves, moves_table,
2315 b.deleted_repos_relpath,
2316 move->rev,
2317 ra_session, repos_root_url,
2318 result_pool, scratch_pool));
2319 }
2320 else
2321 {
2322 /* We could not determine the revision in which the node was
2323 * deleted. */
2324 *deleted_rev = SVN_INVALID_REVNUM;
2325 *deleted_rev_author = NULL;
2326 *replacing_node_kind = svn_node_unknown;
2327 if (moves)
2328 *moves = NULL;
2329 }
2330 return SVN_NO_ERROR;
2331 }
2332 else
2333 {
2334 *deleted_rev = b.deleted_rev;
2335 *deleted_rev_author = b.deleted_rev_author;
2336 *replacing_node_kind = b.replacing_node_kind;
2337 if (moves)
2338 SVN_ERR(find_operative_moves(moves, moves_table,
2339 b.deleted_repos_relpath, b.deleted_rev,
2340 ra_session, repos_root_url,
2341 result_pool, scratch_pool));
2342 }
2343
2344 return SVN_NO_ERROR;
2345 }
2346
2347 /* Details for tree conflicts involving a locally missing node. */
2348 struct conflict_tree_local_missing_details
2349 {
2350 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
2351 svn_revnum_t deleted_rev;
2352
2353 /* ### Add 'added_rev', like in conflict_tree_incoming_delete_details? */
2354
2355 /* Author who committed DELETED_REV. */
2356 const char *deleted_rev_author;
2357
2358 /* The path which was deleted relative to the repository root. */
2359 const char *deleted_repos_relpath;
2360
2361 /* Move information about the conflict victim. If not NULL, this is an
2362 * array of 'struct repos_move_info *' elements. Each element is the
2363 * head of a move chain which starts in DELETED_REV. */
2364 apr_array_header_t *moves;
2365
2366 /* If moves is not NULL, a map of repos_relpaths and working copy nodes.
2367 *
2368 * Each key is a "const char *" repository relpath corresponding to a
2369 * possible repository-side move destination node in the revision which
2370 * is the merge-right revision in case of a merge.
2371 *
2372 * Each value is an apr_array_header_t *.
2373 * Each array consists of "const char *" absolute paths to working copy
2374 * nodes which correspond to the repository node selected by the map key.
2375 * Each such working copy node is a potential local move target which can
2376 * be chosen to find a suitable merge target when resolving a tree conflict.
2377 *
2378 * This may be an empty hash map in case if there is no move target path
2379 * in the working copy. */
2380 apr_hash_t *wc_move_targets;
2381
2382 /* If not NULL, the preferred move target repository relpath. This is our key
2383 * into the WC_MOVE_TARGETS map above (can be overridden by the user). */
2384 const char *move_target_repos_relpath;
2385
2386 /* The current index into the list of working copy nodes corresponding to
2387 * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */
2388 int wc_move_target_idx;
2389
2390 /* Move information about siblings. Siblings are nodes which share
2391 * a youngest common ancestor with the conflict victim. E.g. in case
2392 * of a merge operation they are part of the merge source branch.
2393 * If not NULL, this is an array of 'struct repos_move_info *' elements.
2394 * Each element is the head of a move chain, which starts at some
2395 * point in history after siblings and conflict victim forked off
2396 * their common ancestor. */
2397 apr_array_header_t *sibling_moves;
2398
2399 /* List of nodes in the WC which are suitable merge targets for changes
2400 * merged from any moved sibling. Array elements are 'const char *'
2401 * absolute paths of working copy nodes. This array contains multiple
2402 * elements only if ambiguous matches were found in the WC. */
2403 apr_array_header_t *wc_siblings;
2404 int preferred_sibling_idx;
2405 };
2406
2407 static svn_error_t *
find_related_node(const char ** related_repos_relpath,svn_revnum_t * related_peg_rev,const char * younger_related_repos_relpath,svn_revnum_t younger_related_peg_rev,const char * older_repos_relpath,svn_revnum_t older_peg_rev,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2408 find_related_node(const char **related_repos_relpath,
2409 svn_revnum_t *related_peg_rev,
2410 const char *younger_related_repos_relpath,
2411 svn_revnum_t younger_related_peg_rev,
2412 const char *older_repos_relpath,
2413 svn_revnum_t older_peg_rev,
2414 svn_client_conflict_t *conflict,
2415 svn_client_ctx_t *ctx,
2416 apr_pool_t *result_pool,
2417 apr_pool_t *scratch_pool)
2418 {
2419 const char *repos_root_url;
2420 const char *related_url;
2421 const char *corrected_url;
2422 svn_node_kind_t related_node_kind;
2423 svn_ra_session_t *ra_session;
2424
2425 *related_repos_relpath = NULL;
2426 *related_peg_rev = SVN_INVALID_REVNUM;
2427
2428 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
2429 conflict,
2430 scratch_pool, scratch_pool));
2431 related_url = svn_path_url_add_component2(repos_root_url,
2432 younger_related_repos_relpath,
2433 scratch_pool);
2434 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
2435 &corrected_url,
2436 related_url, NULL,
2437 NULL,
2438 FALSE,
2439 FALSE,
2440 ctx,
2441 scratch_pool,
2442 scratch_pool));
2443 SVN_ERR(svn_ra_check_path(ra_session, "", younger_related_peg_rev,
2444 &related_node_kind, scratch_pool));
2445 if (related_node_kind == svn_node_none)
2446 {
2447 svn_revnum_t related_deleted_rev;
2448 const char *related_deleted_rev_author;
2449 svn_node_kind_t related_replacing_node_kind;
2450 const char *related_basename;
2451 const char *related_parent_repos_relpath;
2452 apr_array_header_t *related_moves;
2453
2454 /* Looks like the younger node, which we'd like to use as our
2455 * 'related node', was deleted. Try to find its deleted revision
2456 * so we can calculate a peg revision at which it exists.
2457 * The younger node is related to the older node, so we can use
2458 * the older node to guide us in our search. */
2459 related_basename = svn_relpath_basename(younger_related_repos_relpath,
2460 scratch_pool);
2461 related_parent_repos_relpath =
2462 svn_relpath_dirname(younger_related_repos_relpath, scratch_pool);
2463 SVN_ERR(find_revision_for_suspected_deletion(
2464 &related_deleted_rev, &related_deleted_rev_author,
2465 &related_replacing_node_kind, &related_moves,
2466 conflict, related_basename,
2467 related_parent_repos_relpath,
2468 younger_related_peg_rev, 0,
2469 older_repos_relpath, older_peg_rev,
2470 ctx, conflict->pool, scratch_pool));
2471
2472 /* If we can't find a related node, bail. */
2473 if (related_deleted_rev == SVN_INVALID_REVNUM)
2474 return SVN_NO_ERROR;
2475
2476 /* The node should exist in the revision before it was deleted. */
2477 *related_repos_relpath = younger_related_repos_relpath;
2478 *related_peg_rev = rev_below(related_deleted_rev);
2479 }
2480 else
2481 {
2482 *related_repos_relpath = younger_related_repos_relpath;
2483 *related_peg_rev = younger_related_peg_rev;
2484 }
2485
2486 return SVN_NO_ERROR;
2487 }
2488
2489 /* Determine if REPOS_RELPATH@PEG_REV was moved at some point in its history.
2490 * History's range of interest ends at END_REV which must be older than PEG_REV.
2491 *
2492 * VICTIM_ABSPATH is the abspath of a conflict victim in the working copy and
2493 * will be used in notifications.
2494 *
2495 * Return any applicable move chain heads in *MOVES.
2496 * If no moves can be found, set *MOVES to NULL. */
2497 static svn_error_t *
find_moves_in_natural_history(apr_array_header_t ** moves,const char * repos_relpath,svn_revnum_t peg_rev,svn_node_kind_t node_kind,svn_revnum_t end_rev,const char * victim_abspath,const char * repos_root_url,const char * repos_uuid,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2498 find_moves_in_natural_history(apr_array_header_t **moves,
2499 const char *repos_relpath,
2500 svn_revnum_t peg_rev,
2501 svn_node_kind_t node_kind,
2502 svn_revnum_t end_rev,
2503 const char *victim_abspath,
2504 const char *repos_root_url,
2505 const char *repos_uuid,
2506 svn_ra_session_t *ra_session,
2507 svn_client_ctx_t *ctx,
2508 apr_pool_t *result_pool,
2509 apr_pool_t *scratch_pool)
2510 {
2511 apr_hash_t *moves_table;
2512 apr_array_header_t *revs;
2513 apr_array_header_t *most_recent_moves = NULL;
2514 int i;
2515 apr_pool_t *iterpool;
2516
2517 *moves = NULL;
2518
2519 SVN_ERR(find_moves_in_revision_range(&moves_table, repos_relpath,
2520 repos_root_url, repos_uuid,
2521 victim_abspath, peg_rev, end_rev,
2522 ctx, scratch_pool, scratch_pool));
2523
2524 iterpool = svn_pool_create(scratch_pool);
2525
2526 /* Scan the moves table for applicable moves. */
2527 revs = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool);
2528 for (i = revs->nelts - 1; i >= 0; i--)
2529 {
2530 svn_sort__item_t item = APR_ARRAY_IDX(revs, i, svn_sort__item_t);
2531 apr_array_header_t *moves_in_rev = apr_hash_get(moves_table, item.key,
2532 sizeof(svn_revnum_t));
2533 int j;
2534
2535 svn_pool_clear(iterpool);
2536
2537 /* Was repos relpath moved to its location in this revision? */
2538 for (j = 0; j < moves_in_rev->nelts; j++)
2539 {
2540 struct repos_move_info *move;
2541 const char *relpath;
2542
2543 move = APR_ARRAY_IDX(moves_in_rev, j, struct repos_move_info *);
2544 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
2545 repos_relpath);
2546 if (relpath)
2547 {
2548 /* If the move did not happen in our peg revision, make
2549 * sure this move happened on the same line of history. */
2550 if (move->rev != peg_rev)
2551 {
2552 svn_client__pathrev_t *yca_loc;
2553 svn_error_t *err;
2554
2555 err = find_yca(&yca_loc, repos_relpath, peg_rev,
2556 repos_relpath, move->rev,
2557 repos_root_url, repos_uuid,
2558 NULL, ctx, iterpool, iterpool);
2559 if (err)
2560 {
2561 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
2562 {
2563 svn_error_clear(err);
2564 yca_loc = NULL;
2565 }
2566 else
2567 return svn_error_trace(err);
2568 }
2569
2570 if (yca_loc == NULL || yca_loc->rev != move->rev)
2571 continue;
2572 }
2573
2574 if (most_recent_moves == NULL)
2575 most_recent_moves =
2576 apr_array_make(result_pool, 1,
2577 sizeof(struct repos_move_info *));
2578
2579 /* Copy the move to result pool (even if relpath is ""). */
2580 move = new_path_adjusted_move(move, relpath, node_kind,
2581 result_pool);
2582 APR_ARRAY_PUSH(most_recent_moves,
2583 struct repos_move_info *) = move;
2584 }
2585 }
2586
2587 /* If we found one move, or several ambiguous moves, we're done. */
2588 if (most_recent_moves)
2589 break;
2590 }
2591
2592 if (most_recent_moves && most_recent_moves->nelts > 0)
2593 {
2594 *moves = apr_array_make(result_pool, 1,
2595 sizeof(struct repos_move_info *));
2596
2597 /* Figure out what happened to the most recent moves in prior
2598 * revisions and build move chains. */
2599 for (i = 0; i < most_recent_moves->nelts; i++)
2600 {
2601 struct repos_move_info *move;
2602
2603 svn_pool_clear(iterpool);
2604
2605 move = APR_ARRAY_IDX(most_recent_moves, i, struct repos_move_info *);
2606 SVN_ERR(trace_moved_node_backwards(moves_table, move,
2607 ra_session, repos_root_url,
2608 result_pool, iterpool));
2609 /* Follow the move chain backwards. */
2610 while (move->prev)
2611 move = move->prev;
2612
2613 /* Return move heads. */
2614 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
2615 }
2616 }
2617
2618 svn_pool_destroy(iterpool);
2619
2620 return SVN_NO_ERROR;
2621 }
2622
2623 static svn_error_t *
collect_sibling_move_candidates(apr_array_header_t * candidates,const char * victim_abspath,svn_node_kind_t victim_kind,struct repos_move_info * move,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2624 collect_sibling_move_candidates(apr_array_header_t *candidates,
2625 const char *victim_abspath,
2626 svn_node_kind_t victim_kind,
2627 struct repos_move_info *move,
2628 svn_client_ctx_t *ctx,
2629 apr_pool_t *result_pool,
2630 apr_pool_t *scratch_pool)
2631 {
2632 const char *basename;
2633 apr_array_header_t *abspaths;
2634 int i;
2635
2636 basename = svn_relpath_basename(move->moved_from_repos_relpath, scratch_pool);
2637 SVN_ERR(svn_wc__find_working_nodes_with_basename(&abspaths, victim_abspath,
2638 basename, victim_kind,
2639 ctx->wc_ctx, result_pool,
2640 scratch_pool));
2641 apr_array_cat(candidates, abspaths);
2642
2643 if (move->next)
2644 {
2645 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2646 for (i = 0; i < move->next->nelts; i++)
2647 {
2648 struct repos_move_info *next_move;
2649 next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
2650 SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath,
2651 victim_kind, next_move, ctx,
2652 result_pool, iterpool));
2653 svn_pool_clear(iterpool);
2654 }
2655 svn_pool_destroy(iterpool);
2656 }
2657
2658 return SVN_NO_ERROR;
2659 }
2660
2661 /* Follow each move chain starting a MOVE all the way to the end to find
2662 * the possible working copy locations for VICTIM_ABSPATH which corresponds
2663 * to VICTIM_REPOS_REPLATH@VICTIM_REVISION.
2664 * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the
2665 * repos_relpath which is the corresponding move destination in the repository.
2666 * This function is recursive. */
2667 static svn_error_t *
follow_move_chains(apr_hash_t * wc_move_targets,struct repos_move_info * move,svn_client_ctx_t * ctx,const char * victim_abspath,svn_node_kind_t victim_node_kind,const char * victim_repos_relpath,svn_revnum_t victim_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2668 follow_move_chains(apr_hash_t *wc_move_targets,
2669 struct repos_move_info *move,
2670 svn_client_ctx_t *ctx,
2671 const char *victim_abspath,
2672 svn_node_kind_t victim_node_kind,
2673 const char *victim_repos_relpath,
2674 svn_revnum_t victim_revision,
2675 apr_pool_t *result_pool,
2676 apr_pool_t *scratch_pool)
2677 {
2678 apr_array_header_t *candidate_abspaths;
2679
2680 /* Gather candidate nodes which represent this moved_to_repos_relpath. */
2681 SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
2682 &candidate_abspaths, ctx->wc_ctx,
2683 victim_abspath, victim_node_kind,
2684 move->moved_to_repos_relpath,
2685 scratch_pool, scratch_pool));
2686
2687 if (candidate_abspaths->nelts > 0)
2688 {
2689 apr_array_header_t *moved_to_abspaths;
2690 int i;
2691 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2692
2693 moved_to_abspaths = apr_array_make(result_pool, 1,
2694 sizeof (const char *));
2695
2696 for (i = 0; i < candidate_abspaths->nelts; i++)
2697 {
2698 const char *candidate_abspath;
2699 const char *repos_root_url;
2700 const char *repos_uuid;
2701 const char *candidate_repos_relpath;
2702 svn_revnum_t candidate_revision;
2703
2704 svn_pool_clear(iterpool);
2705
2706 candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
2707 const char *);
2708 SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
2709 &candidate_repos_relpath,
2710 &repos_root_url,
2711 &repos_uuid,
2712 NULL, NULL,
2713 ctx->wc_ctx,
2714 candidate_abspath,
2715 FALSE,
2716 iterpool, iterpool));
2717
2718 if (candidate_revision == SVN_INVALID_REVNUM)
2719 continue;
2720
2721 /* If the conflict victim and the move target candidate
2722 * are not from the same revision we must ensure that
2723 * they are related. */
2724 if (candidate_revision != victim_revision)
2725 {
2726 svn_client__pathrev_t *yca_loc;
2727 svn_error_t *err;
2728
2729 err = find_yca(&yca_loc, victim_repos_relpath,
2730 victim_revision,
2731 candidate_repos_relpath,
2732 candidate_revision,
2733 repos_root_url, repos_uuid,
2734 NULL, ctx, iterpool, iterpool);
2735 if (err)
2736 {
2737 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
2738 {
2739 svn_error_clear(err);
2740 yca_loc = NULL;
2741 }
2742 else
2743 return svn_error_trace(err);
2744 }
2745
2746 if (yca_loc == NULL)
2747 continue;
2748 }
2749
2750 APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
2751 apr_pstrdup(result_pool, candidate_abspath);
2752 }
2753 svn_pool_destroy(iterpool);
2754
2755 svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
2756 moved_to_abspaths);
2757 }
2758
2759 if (move->next)
2760 {
2761 int i;
2762 apr_pool_t *iterpool;
2763
2764 /* Recurse into each of the possible move chains. */
2765 iterpool = svn_pool_create(scratch_pool);
2766 for (i = 0; i < move->next->nelts; i++)
2767 {
2768 struct repos_move_info *next_move;
2769
2770 svn_pool_clear(iterpool);
2771
2772 next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
2773 SVN_ERR(follow_move_chains(wc_move_targets, next_move,
2774 ctx, victim_abspath, victim_node_kind,
2775 victim_repos_relpath, victim_revision,
2776 result_pool, iterpool));
2777
2778 }
2779 svn_pool_destroy(iterpool);
2780 }
2781
2782 return SVN_NO_ERROR;
2783 }
2784
2785 /* Implements tree_conflict_get_details_func_t. */
2786 static svn_error_t *
conflict_tree_get_details_local_missing(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2787 conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
2788 svn_client_ctx_t *ctx,
2789 apr_pool_t *scratch_pool)
2790 {
2791 const char *old_repos_relpath;
2792 const char *new_repos_relpath;
2793 const char *parent_repos_relpath;
2794 svn_revnum_t parent_peg_rev;
2795 svn_revnum_t old_rev;
2796 svn_revnum_t new_rev;
2797 svn_revnum_t deleted_rev;
2798 svn_node_kind_t old_kind;
2799 svn_node_kind_t new_kind;
2800 const char *deleted_rev_author;
2801 svn_node_kind_t replacing_node_kind;
2802 const char *deleted_basename;
2803 struct conflict_tree_local_missing_details *details;
2804 apr_array_header_t *moves = NULL;
2805 apr_array_header_t *sibling_moves = NULL;
2806 apr_array_header_t *wc_siblings = NULL;
2807 const char *related_repos_relpath;
2808 svn_revnum_t related_peg_rev;
2809 const char *repos_root_url;
2810 const char *repos_uuid;
2811 const char *url, *corrected_url;
2812 svn_ra_session_t *ra_session;
2813 svn_client__pathrev_t *yca_loc;
2814 svn_revnum_t end_rev;
2815
2816 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
2817 &old_repos_relpath, &old_rev, &old_kind, conflict,
2818 scratch_pool, scratch_pool));
2819 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
2820 &new_repos_relpath, &new_rev, &new_kind, conflict,
2821 scratch_pool, scratch_pool));
2822
2823 /* Scan the conflict victim's parent's log to find a revision which
2824 * deleted the node. */
2825 deleted_basename = svn_dirent_basename(conflict->local_abspath,
2826 scratch_pool);
2827 SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev, &parent_repos_relpath,
2828 &repos_root_url, &repos_uuid,
2829 ctx->wc_ctx,
2830 svn_dirent_dirname(
2831 conflict->local_abspath,
2832 scratch_pool),
2833 scratch_pool,
2834 scratch_pool));
2835
2836 /* If the parent is not part of the repository-side tree checked out
2837 * into this working copy, then bail. We do not support this case yet. */
2838 if (parent_peg_rev == SVN_INVALID_REVNUM)
2839 return SVN_NO_ERROR;
2840
2841 /* Pick the younger incoming node as our 'related node' which helps
2842 * pin-pointing the deleted conflict victim in history. */
2843 related_repos_relpath =
2844 (old_rev < new_rev ? new_repos_relpath : old_repos_relpath);
2845 related_peg_rev = (old_rev < new_rev ? new_rev : old_rev);
2846
2847 /* Make sure we're going to search the related node in a revision where
2848 * it exists. The younger incoming node might have been deleted in HEAD. */
2849 if (related_repos_relpath != NULL && related_peg_rev != SVN_INVALID_REVNUM)
2850 SVN_ERR(find_related_node(
2851 &related_repos_relpath, &related_peg_rev,
2852 related_repos_relpath, related_peg_rev,
2853 (old_rev < new_rev ? old_repos_relpath : new_repos_relpath),
2854 (old_rev < new_rev ? old_rev : new_rev),
2855 conflict, ctx, scratch_pool, scratch_pool));
2856
2857 /* Set END_REV to our best guess of the nearest YCA revision. */
2858 url = svn_path_url_add_component2(repos_root_url, related_repos_relpath,
2859 scratch_pool);
2860 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
2861 &corrected_url,
2862 url, NULL, NULL,
2863 FALSE,
2864 FALSE,
2865 ctx,
2866 scratch_pool,
2867 scratch_pool));
2868 SVN_ERR(find_nearest_yca(&yca_loc, related_repos_relpath, related_peg_rev,
2869 parent_repos_relpath, parent_peg_rev,
2870 repos_root_url, repos_uuid, ra_session, ctx,
2871 scratch_pool, scratch_pool));
2872 if (yca_loc)
2873 {
2874 end_rev = yca_loc->rev;
2875
2876 /* END_REV must be smaller than PARENT_PEG_REV, else the call to
2877 * find_revision_for_suspected_deletion() below will abort. */
2878 if (end_rev >= parent_peg_rev)
2879 end_rev = parent_peg_rev > 0 ? parent_peg_rev - 1 : 0;
2880 }
2881 else
2882 end_rev = 0; /* ### We might walk through all of history... */
2883
2884 SVN_ERR(find_revision_for_suspected_deletion(
2885 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
2886 yca_loc ? &moves : NULL,
2887 conflict, deleted_basename, parent_repos_relpath,
2888 parent_peg_rev, end_rev, related_repos_relpath, related_peg_rev,
2889 ctx, conflict->pool, scratch_pool));
2890
2891 /* If the victim was not deleted then check if the related path was moved. */
2892 if (deleted_rev == SVN_INVALID_REVNUM)
2893 {
2894 const char *victim_abspath;
2895 svn_node_kind_t related_node_kind;
2896 apr_array_header_t *candidates;
2897 int i;
2898 apr_pool_t *iterpool;
2899
2900 /* ### The following describes all moves in terms of forward-merges,
2901 * should do we something else for reverse-merges? */
2902
2903 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
2904
2905 if (yca_loc)
2906 {
2907 end_rev = yca_loc->rev;
2908
2909 /* END_REV must be smaller than RELATED_PEG_REV, else the call
2910 to find_moves_in_natural_history() below will error out. */
2911 if (end_rev >= related_peg_rev)
2912 end_rev = related_peg_rev > 0 ? related_peg_rev - 1 : 0;
2913 }
2914 else
2915 end_rev = 0; /* ### We might walk through all of history... */
2916
2917 SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev,
2918 &related_node_kind, scratch_pool));
2919 SVN_ERR(find_moves_in_natural_history(&sibling_moves,
2920 related_repos_relpath,
2921 related_peg_rev,
2922 related_node_kind,
2923 end_rev,
2924 victim_abspath,
2925 repos_root_url, repos_uuid,
2926 ra_session, ctx,
2927 conflict->pool, scratch_pool));
2928
2929 if (sibling_moves == NULL)
2930 return SVN_NO_ERROR;
2931
2932 /* Find the missing node in the WC. In theory, this requires tracing
2933 * back history of every node in the WC to check for a YCA with the
2934 * conflict victim. This operation would obviously be quite expensive.
2935 *
2936 * However, assuming that the victim was not moved in the merge target,
2937 * we can take a short-cut: The basename of the node cannot have changed,
2938 * so we can limit history tracing to nodes with a matching basename.
2939 *
2940 * This approach solves the conflict case where an edit to a file which
2941 * was moved on one branch is cherry-picked to another branch where the
2942 * corresponding file has not been moved (yet). It does not solve move
2943 * vs. move conflicts, but such conflicts are not yet supported by the
2944 * resolver anyway and are hard to solve without server-side support. */
2945 iterpool = svn_pool_create(scratch_pool);
2946 for (i = 0; i < sibling_moves->nelts; i++)
2947 {
2948 struct repos_move_info *move;
2949 int j;
2950
2951 svn_pool_clear(iterpool);
2952
2953 move = APR_ARRAY_IDX(sibling_moves, i, struct repos_move_info *);
2954 candidates = apr_array_make(iterpool, 1, sizeof(const char *));
2955 SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath,
2956 old_rev < new_rev
2957 ? new_kind : old_kind,
2958 move, ctx, iterpool,
2959 iterpool));
2960
2961 /* Determine whether a candidate node shares a YCA with the victim. */
2962 for (j = 0; j < candidates->nelts; j++)
2963 {
2964 const char *candidate_abspath;
2965 const char *candidate_repos_relpath;
2966 svn_revnum_t candidate_revision;
2967 svn_error_t *err;
2968
2969 candidate_abspath = APR_ARRAY_IDX(candidates, j, const char *);
2970 SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
2971 &candidate_repos_relpath,
2972 NULL, NULL, NULL, NULL,
2973 ctx->wc_ctx,
2974 candidate_abspath,
2975 FALSE,
2976 iterpool, iterpool));
2977 err = find_yca(&yca_loc,
2978 old_rev < new_rev
2979 ? new_repos_relpath : old_repos_relpath,
2980 old_rev < new_rev ? new_rev : old_rev,
2981 candidate_repos_relpath,
2982 candidate_revision,
2983 repos_root_url, repos_uuid,
2984 NULL, ctx, iterpool, iterpool);
2985 if (err)
2986 {
2987 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
2988 {
2989 svn_error_clear(err);
2990 yca_loc = NULL;
2991 }
2992 else
2993 return svn_error_trace(err);
2994 }
2995
2996 if (yca_loc)
2997 {
2998 if (wc_siblings == NULL)
2999 wc_siblings = apr_array_make(conflict->pool, 1,
3000 sizeof(const char *));
3001 APR_ARRAY_PUSH(wc_siblings, const char *) =
3002 apr_pstrdup(conflict->pool, candidate_abspath);
3003 }
3004 }
3005 }
3006 svn_pool_destroy(iterpool);
3007 }
3008
3009 details = apr_pcalloc(conflict->pool, sizeof(*details));
3010 details->deleted_rev = deleted_rev;
3011 details->deleted_rev_author = deleted_rev_author;
3012 if (deleted_rev != SVN_INVALID_REVNUM)
3013 details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
3014 deleted_basename,
3015 conflict->pool);
3016 details->moves = moves;
3017 if (details->moves != NULL)
3018 {
3019 apr_pool_t *iterpool;
3020 int i;
3021
3022 details->wc_move_targets = apr_hash_make(conflict->pool);
3023 iterpool = svn_pool_create(scratch_pool);
3024 for (i = 0; i < details->moves->nelts; i++)
3025 {
3026 struct repos_move_info *move;
3027
3028 svn_pool_clear(iterpool);
3029 move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
3030 SVN_ERR(follow_move_chains(details->wc_move_targets, move, ctx,
3031 conflict->local_abspath,
3032 new_kind,
3033 new_repos_relpath,
3034 new_rev,
3035 scratch_pool, iterpool));
3036 }
3037 svn_pool_destroy(iterpool);
3038
3039 if (apr_hash_count(details->wc_move_targets) > 0)
3040 {
3041 apr_array_header_t *move_target_repos_relpaths;
3042 const svn_sort__item_t *item;
3043
3044 /* Initialize to the first possible move target. Hopefully,
3045 * in most cases there will only be one candidate anyway. */
3046 move_target_repos_relpaths = svn_sort__hash(
3047 details->wc_move_targets,
3048 svn_sort_compare_items_as_paths,
3049 scratch_pool);
3050 item = &APR_ARRAY_IDX(move_target_repos_relpaths,
3051 0, svn_sort__item_t);
3052 details->move_target_repos_relpath = item->key;
3053 details->wc_move_target_idx = 0;
3054 }
3055 else
3056 {
3057 details->move_target_repos_relpath = NULL;
3058 details->wc_move_target_idx = 0;
3059 }
3060 }
3061
3062 details->sibling_moves = sibling_moves;
3063 details->wc_siblings = wc_siblings;
3064 if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) == 1)
3065 {
3066 apr_array_header_t *wc_abspaths;
3067
3068 wc_abspaths = svn_hash_gets(details->wc_move_targets,
3069 details->move_target_repos_relpath);
3070 if (wc_abspaths->nelts == 1)
3071 {
3072 svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
3073
3074 if (kind == svn_node_file)
3075 conflict->recommended_option_id =
3076 svn_client_conflict_option_local_move_file_text_merge;
3077 else if (kind == svn_node_dir)
3078 conflict->recommended_option_id =
3079 svn_client_conflict_option_local_move_dir_merge;
3080 }
3081 }
3082 else if (details->wc_siblings && details->wc_siblings->nelts == 1)
3083 {
3084 svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
3085
3086 if (kind == svn_node_file)
3087 conflict->recommended_option_id =
3088 svn_client_conflict_option_sibling_move_file_text_merge;
3089 else if (kind == svn_node_dir)
3090 conflict->recommended_option_id =
3091 svn_client_conflict_option_sibling_move_dir_merge;
3092 }
3093
3094 conflict->tree_conflict_local_details = details;
3095
3096 return SVN_NO_ERROR;
3097 }
3098
3099 /* Return a localised string representation of the local part of a tree
3100 conflict on a non-existent node. */
3101 static svn_error_t *
describe_local_none_node_change(const char ** description,svn_client_conflict_t * conflict,apr_pool_t * result_pool,apr_pool_t * scratch_pool)3102 describe_local_none_node_change(const char **description,
3103 svn_client_conflict_t *conflict,
3104 apr_pool_t *result_pool,
3105 apr_pool_t *scratch_pool)
3106 {
3107 svn_wc_conflict_reason_t local_change;
3108 svn_wc_operation_t operation;
3109
3110 local_change = svn_client_conflict_get_local_change(conflict);
3111 operation = svn_client_conflict_get_operation(conflict);
3112
3113 switch (local_change)
3114 {
3115 case svn_wc_conflict_reason_edited:
3116 *description = _("An item containing uncommitted changes was "
3117 "found in the working copy.");
3118 break;
3119 case svn_wc_conflict_reason_obstructed:
3120 *description = _("An item which already occupies this path was found in "
3121 "the working copy.");
3122 break;
3123 case svn_wc_conflict_reason_deleted:
3124 *description = _("A deleted item was found in the working copy.");
3125 break;
3126 case svn_wc_conflict_reason_missing:
3127 if (operation == svn_wc_operation_update ||
3128 operation == svn_wc_operation_switch)
3129 *description = _("No such file or directory was found in the "
3130 "working copy.");
3131 else if (operation == svn_wc_operation_merge)
3132 {
3133 /* ### display deleted revision */
3134 *description = _("No such file or directory was found in the "
3135 "merge target working copy.\nThe item may "
3136 "have been deleted or moved away in the "
3137 "repository's history.");
3138 }
3139 break;
3140 case svn_wc_conflict_reason_unversioned:
3141 *description = _("An unversioned item was found in the working "
3142 "copy.");
3143 break;
3144 case svn_wc_conflict_reason_added:
3145 case svn_wc_conflict_reason_replaced:
3146 *description = _("An item scheduled to be added to the repository "
3147 "in the next commit was found in the working "
3148 "copy.");
3149 break;
3150 case svn_wc_conflict_reason_moved_away:
3151 *description = _("The item in the working copy had been moved "
3152 "away at the time this conflict was recorded.");
3153 break;
3154 case svn_wc_conflict_reason_moved_here:
3155 *description = _("An item had been moved here in the working copy "
3156 "at the time this conflict was recorded.");
3157 break;
3158 }
3159
3160 return SVN_NO_ERROR;
3161 }
3162
3163 /* Append a description of a move chain beginning at NEXT to DESCRIPTION. */
3164 static const char *
append_moved_to_chain_description(const char * description,apr_array_header_t * next,apr_pool_t * result_pool,apr_pool_t * scratch_pool)3165 append_moved_to_chain_description(const char *description,
3166 apr_array_header_t *next,
3167 apr_pool_t *result_pool,
3168 apr_pool_t *scratch_pool)
3169 {
3170 if (next == NULL)
3171 return description;
3172
3173 while (next)
3174 {
3175 struct repos_move_info *move;
3176
3177 /* Describe the first possible move chain only. Adding multiple chains
3178 * to the description would just be confusing. The user may select a
3179 * different move destination while resolving the conflict. */
3180 move = APR_ARRAY_IDX(next, 0, struct repos_move_info *);
3181
3182 description = apr_psprintf(scratch_pool,
3183 _("%s\nAnd then moved away to '^/%s' by "
3184 "%s in r%ld."),
3185 description, move->moved_to_repos_relpath,
3186 move->rev_author, move->rev);
3187 next = move->next;
3188 }
3189
3190 return apr_pstrdup(result_pool, description);
3191 }
3192
3193 /* Implements tree_conflict_get_description_func_t. */
3194 static svn_error_t *
conflict_tree_get_local_description_generic(const char ** description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)3195 conflict_tree_get_local_description_generic(const char **description,
3196 svn_client_conflict_t *conflict,
3197 svn_client_ctx_t *ctx,
3198 apr_pool_t *result_pool,
3199 apr_pool_t *scratch_pool)
3200 {
3201 svn_node_kind_t victim_node_kind;
3202
3203 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
3204
3205 *description = NULL;
3206
3207 switch (victim_node_kind)
3208 {
3209 case svn_node_file:
3210 case svn_node_symlink:
3211 SVN_ERR(describe_local_file_node_change(description, conflict, ctx,
3212 result_pool, scratch_pool));
3213 break;
3214 case svn_node_dir:
3215 SVN_ERR(describe_local_dir_node_change(description, conflict, ctx,
3216 result_pool, scratch_pool));
3217 break;
3218 case svn_node_none:
3219 case svn_node_unknown:
3220 SVN_ERR(describe_local_none_node_change(description, conflict,
3221 result_pool, scratch_pool));
3222 break;
3223 }
3224
3225 return SVN_NO_ERROR;
3226 }
3227
3228 /* Implements tree_conflict_get_description_func_t. */
3229 static svn_error_t *
conflict_tree_get_description_local_missing(const char ** description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)3230 conflict_tree_get_description_local_missing(const char **description,
3231 svn_client_conflict_t *conflict,
3232 svn_client_ctx_t *ctx,
3233 apr_pool_t *result_pool,
3234 apr_pool_t *scratch_pool)
3235 {
3236 struct conflict_tree_local_missing_details *details;
3237
3238 details = conflict->tree_conflict_local_details;
3239 if (details == NULL)
3240 return svn_error_trace(conflict_tree_get_local_description_generic(
3241 description, conflict, ctx,
3242 result_pool, scratch_pool));
3243
3244 if (details->moves || details->sibling_moves)
3245 {
3246 struct repos_move_info *move;
3247
3248 *description = _("No such file or directory was found in the "
3249 "merge target working copy.\n");
3250
3251 if (details->moves)
3252 {
3253 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3254 if (move->node_kind == svn_node_file)
3255 *description = apr_psprintf(
3256 result_pool,
3257 _("%sThe file was moved to '^/%s' in r%ld by %s."),
3258 *description, move->moved_to_repos_relpath,
3259 move->rev, move->rev_author);
3260 else if (move->node_kind == svn_node_dir)
3261 *description = apr_psprintf(
3262 result_pool,
3263 _("%sThe directory was moved to '^/%s' in "
3264 "r%ld by %s."),
3265 *description, move->moved_to_repos_relpath,
3266 move->rev, move->rev_author);
3267 else
3268 *description = apr_psprintf(
3269 result_pool,
3270 _("%sThe item was moved to '^/%s' in r%ld by %s."),
3271 *description, move->moved_to_repos_relpath,
3272 move->rev, move->rev_author);
3273 *description = append_moved_to_chain_description(*description,
3274 move->next,
3275 result_pool,
3276 scratch_pool);
3277 }
3278
3279 if (details->sibling_moves)
3280 {
3281 move = APR_ARRAY_IDX(details->sibling_moves, 0,
3282 struct repos_move_info *);
3283 if (move->node_kind == svn_node_file)
3284 *description = apr_psprintf(
3285 result_pool,
3286 _("%sThe file '^/%s' was moved to '^/%s' "
3287 "in r%ld by %s."),
3288 *description, move->moved_from_repos_relpath,
3289 move->moved_to_repos_relpath,
3290 move->rev, move->rev_author);
3291 else if (move->node_kind == svn_node_dir)
3292 *description = apr_psprintf(
3293 result_pool,
3294 _("%sThe directory '^/%s' was moved to '^/%s' "
3295 "in r%ld by %s."),
3296 *description, move->moved_from_repos_relpath,
3297 move->moved_to_repos_relpath,
3298 move->rev, move->rev_author);
3299 else
3300 *description = apr_psprintf(
3301 result_pool,
3302 _("%sThe item '^/%s' was moved to '^/%s' "
3303 "in r%ld by %s."),
3304 *description, move->moved_from_repos_relpath,
3305 move->moved_to_repos_relpath,
3306 move->rev, move->rev_author);
3307 *description = append_moved_to_chain_description(*description,
3308 move->next,
3309 result_pool,
3310 scratch_pool);
3311 }
3312 }
3313 else
3314 *description = apr_psprintf(
3315 result_pool,
3316 _("No such file or directory was found in the "
3317 "merge target working copy.\n'^/%s' was deleted "
3318 "in r%ld by %s."),
3319 details->deleted_repos_relpath,
3320 details->deleted_rev, details->deleted_rev_author);
3321
3322 return SVN_NO_ERROR;
3323 }
3324
3325 /* Return a localised string representation of the incoming part of a
3326 conflict; NULL for non-localised odd cases. */
3327 static const char *
describe_incoming_change(svn_node_kind_t kind,svn_wc_conflict_action_t action,svn_wc_operation_t operation)3328 describe_incoming_change(svn_node_kind_t kind, svn_wc_conflict_action_t action,
3329 svn_wc_operation_t operation)
3330 {
3331 switch (kind)
3332 {
3333 case svn_node_file:
3334 case svn_node_symlink:
3335 if (operation == svn_wc_operation_update)
3336 {
3337 switch (action)
3338 {
3339 case svn_wc_conflict_action_edit:
3340 return _("An update operation tried to edit a file.");
3341 case svn_wc_conflict_action_add:
3342 return _("An update operation tried to add a file.");
3343 case svn_wc_conflict_action_delete:
3344 return _("An update operation tried to delete or move "
3345 "a file.");
3346 case svn_wc_conflict_action_replace:
3347 return _("An update operation tried to replace a file.");
3348 }
3349 }
3350 else if (operation == svn_wc_operation_switch)
3351 {
3352 switch (action)
3353 {
3354 case svn_wc_conflict_action_edit:
3355 return _("A switch operation tried to edit a file.");
3356 case svn_wc_conflict_action_add:
3357 return _("A switch operation tried to add a file.");
3358 case svn_wc_conflict_action_delete:
3359 return _("A switch operation tried to delete or move "
3360 "a file.");
3361 case svn_wc_conflict_action_replace:
3362 return _("A switch operation tried to replace a file.");
3363 }
3364 }
3365 else if (operation == svn_wc_operation_merge)
3366 {
3367 switch (action)
3368 {
3369 case svn_wc_conflict_action_edit:
3370 return _("A merge operation tried to edit a file.");
3371 case svn_wc_conflict_action_add:
3372 return _("A merge operation tried to add a file.");
3373 case svn_wc_conflict_action_delete:
3374 return _("A merge operation tried to delete or move "
3375 "a file.");
3376 case svn_wc_conflict_action_replace:
3377 return _("A merge operation tried to replace a file.");
3378 }
3379 }
3380 break;
3381 case svn_node_dir:
3382 if (operation == svn_wc_operation_update)
3383 {
3384 switch (action)
3385 {
3386 case svn_wc_conflict_action_edit:
3387 return _("An update operation tried to change a directory.");
3388 case svn_wc_conflict_action_add:
3389 return _("An update operation tried to add a directory.");
3390 case svn_wc_conflict_action_delete:
3391 return _("An update operation tried to delete or move "
3392 "a directory.");
3393 case svn_wc_conflict_action_replace:
3394 return _("An update operation tried to replace a directory.");
3395 }
3396 }
3397 else if (operation == svn_wc_operation_switch)
3398 {
3399 switch (action)
3400 {
3401 case svn_wc_conflict_action_edit:
3402 return _("A switch operation tried to edit a directory.");
3403 case svn_wc_conflict_action_add:
3404 return _("A switch operation tried to add a directory.");
3405 case svn_wc_conflict_action_delete:
3406 return _("A switch operation tried to delete or move "
3407 "a directory.");
3408 case svn_wc_conflict_action_replace:
3409 return _("A switch operation tried to replace a directory.");
3410 }
3411 }
3412 else if (operation == svn_wc_operation_merge)
3413 {
3414 switch (action)
3415 {
3416 case svn_wc_conflict_action_edit:
3417 return _("A merge operation tried to edit a directory.");
3418 case svn_wc_conflict_action_add:
3419 return _("A merge operation tried to add a directory.");
3420 case svn_wc_conflict_action_delete:
3421 return _("A merge operation tried to delete or move "
3422 "a directory.");
3423 case svn_wc_conflict_action_replace:
3424 return _("A merge operation tried to replace a directory.");
3425 }
3426 }
3427 break;
3428 case svn_node_none:
3429 case svn_node_unknown:
3430 if (operation == svn_wc_operation_update)
3431 {
3432 switch (action)
3433 {
3434 case svn_wc_conflict_action_edit:
3435 return _("An update operation tried to edit an item.");
3436 case svn_wc_conflict_action_add:
3437 return _("An update operation tried to add an item.");
3438 case svn_wc_conflict_action_delete:
3439 return _("An update operation tried to delete or move "
3440 "an item.");
3441 case svn_wc_conflict_action_replace:
3442 return _("An update operation tried to replace an item.");
3443 }
3444 }
3445 else if (operation == svn_wc_operation_switch)
3446 {
3447 switch (action)
3448 {
3449 case svn_wc_conflict_action_edit:
3450 return _("A switch operation tried to edit an item.");
3451 case svn_wc_conflict_action_add:
3452 return _("A switch operation tried to add an item.");
3453 case svn_wc_conflict_action_delete:
3454 return _("A switch operation tried to delete or move "
3455 "an item.");
3456 case svn_wc_conflict_action_replace:
3457 return _("A switch operation tried to replace an item.");
3458 }
3459 }
3460 else if (operation == svn_wc_operation_merge)
3461 {
3462 switch (action)
3463 {
3464 case svn_wc_conflict_action_edit:
3465 return _("A merge operation tried to edit an item.");
3466 case svn_wc_conflict_action_add:
3467 return _("A merge operation tried to add an item.");
3468 case svn_wc_conflict_action_delete:
3469 return _("A merge operation tried to delete or move "
3470 "an item.");
3471 case svn_wc_conflict_action_replace:
3472 return _("A merge operation tried to replace an item.");
3473 }
3474 }
3475 break;
3476 }
3477
3478 return NULL;
3479 }
3480
3481 /* Return a localised string representation of the operation part of a
3482 conflict. */
3483 static const char *
operation_str(svn_wc_operation_t operation)3484 operation_str(svn_wc_operation_t operation)
3485 {
3486 switch (operation)
3487 {
3488 case svn_wc_operation_update: return _("upon update");
3489 case svn_wc_operation_switch: return _("upon switch");
3490 case svn_wc_operation_merge: return _("upon merge");
3491 case svn_wc_operation_none: return _("upon none");
3492 }
3493 SVN_ERR_MALFUNCTION_NO_RETURN();
3494 return NULL;
3495 }
3496
3497 svn_error_t *
svn_client_conflict_prop_get_description(const char ** description,svn_client_conflict_t * conflict,apr_pool_t * result_pool,apr_pool_t * scratch_pool)3498 svn_client_conflict_prop_get_description(const char **description,
3499 svn_client_conflict_t *conflict,
3500 apr_pool_t *result_pool,
3501 apr_pool_t *scratch_pool)
3502 {
3503 const char *reason_str, *action_str;
3504
3505 /* We provide separately translatable strings for the values that we
3506 * know about, and a fall-back in case any other values occur. */
3507 switch (svn_client_conflict_get_local_change(conflict))
3508 {
3509 case svn_wc_conflict_reason_edited:
3510 reason_str = _("local edit");
3511 break;
3512 case svn_wc_conflict_reason_added:
3513 reason_str = _("local add");
3514 break;
3515 case svn_wc_conflict_reason_deleted:
3516 reason_str = _("local delete");
3517 break;
3518 case svn_wc_conflict_reason_obstructed:
3519 reason_str = _("local obstruction");
3520 break;
3521 default:
3522 reason_str = apr_psprintf(
3523 scratch_pool, _("local %s"),
3524 svn_token__to_word(
3525 map_conflict_reason,
3526 svn_client_conflict_get_local_change(conflict)));
3527 break;
3528 }
3529 switch (svn_client_conflict_get_incoming_change(conflict))
3530 {
3531 case svn_wc_conflict_action_edit:
3532 action_str = _("incoming edit");
3533 break;
3534 case svn_wc_conflict_action_add:
3535 action_str = _("incoming add");
3536 break;
3537 case svn_wc_conflict_action_delete:
3538 action_str = _("incoming delete");
3539 break;
3540 default:
3541 action_str = apr_psprintf(
3542 scratch_pool, _("incoming %s"),
3543 svn_token__to_word(
3544 map_conflict_action,
3545 svn_client_conflict_get_incoming_change(conflict)));
3546 break;
3547 }
3548 SVN_ERR_ASSERT(reason_str && action_str);
3549
3550 *description = apr_psprintf(result_pool, _("%s, %s %s"),
3551 reason_str, action_str,
3552 operation_str(
3553 svn_client_conflict_get_operation(conflict)));
3554
3555 return SVN_NO_ERROR;
3556 }
3557
3558 /* Implements tree_conflict_get_description_func_t. */
3559 static svn_error_t *
conflict_tree_get_incoming_description_generic(const char ** incoming_change_description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)3560 conflict_tree_get_incoming_description_generic(
3561 const char **incoming_change_description,
3562 svn_client_conflict_t *conflict,
3563 svn_client_ctx_t *ctx,
3564 apr_pool_t *result_pool,
3565 apr_pool_t *scratch_pool)
3566 {
3567 const char *action;
3568 svn_node_kind_t incoming_kind;
3569 svn_wc_conflict_action_t conflict_action;
3570 svn_wc_operation_t conflict_operation;
3571
3572 conflict_action = svn_client_conflict_get_incoming_change(conflict);
3573 conflict_operation = svn_client_conflict_get_operation(conflict);
3574
3575 /* Determine the node kind of the incoming change. */
3576 incoming_kind = svn_node_unknown;
3577 if (conflict_action == svn_wc_conflict_action_edit ||
3578 conflict_action == svn_wc_conflict_action_delete)
3579 {
3580 /* Change is acting on 'src_left' version of the node. */
3581 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
3582 NULL, NULL, &incoming_kind, conflict, scratch_pool,
3583 scratch_pool));
3584 }
3585 else if (conflict_action == svn_wc_conflict_action_add ||
3586 conflict_action == svn_wc_conflict_action_replace)
3587 {
3588 /* Change is acting on 'src_right' version of the node.
3589 *
3590 * ### For 'replace', the node kind is ambiguous. However, src_left
3591 * ### is NULL for replace, so we must use src_right. */
3592 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
3593 NULL, NULL, &incoming_kind, conflict, scratch_pool,
3594 scratch_pool));
3595 }
3596
3597 action = describe_incoming_change(incoming_kind, conflict_action,
3598 conflict_operation);
3599 if (action)
3600 {
3601 *incoming_change_description = apr_pstrdup(result_pool, action);
3602 }
3603 else
3604 {
3605 /* A catch-all message for very rare or nominally impossible cases.
3606 It will not be pretty, but is closer to an internal error than
3607 an ordinary user-facing string. */
3608 *incoming_change_description = apr_psprintf(result_pool,
3609 _("incoming %s %s"),
3610 svn_node_kind_to_word(incoming_kind),
3611 svn_token__to_word(map_conflict_action,
3612 conflict_action));
3613 }
3614 return SVN_NO_ERROR;
3615 }
3616
3617 /* Details for tree conflicts involving incoming deletions and replacements. */
3618 struct conflict_tree_incoming_delete_details
3619 {
3620 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
3621 svn_revnum_t deleted_rev;
3622
3623 /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. The incoming
3624 * delete is the result of a reverse application of this addition. */
3625 svn_revnum_t added_rev;
3626
3627 /* The path which was deleted/added relative to the repository root. */
3628 const char *repos_relpath;
3629
3630 /* Author who committed DELETED_REV/ADDED_REV. */
3631 const char *rev_author;
3632
3633 /* New node kind for a replaced node. This is svn_node_none for deletions. */
3634 svn_node_kind_t replacing_node_kind;
3635
3636 /* Move information. If not NULL, this is an array of repos_move_info *
3637 * elements. Each element is the head of a move chain which starts in
3638 * DELETED_REV or in ADDED_REV (in which case moves should be interpreted
3639 * in reverse). */
3640 apr_array_header_t *moves;
3641
3642 /* A map of repos_relpaths and working copy nodes for an incoming move.
3643 *
3644 * Each key is a "const char *" repository relpath corresponding to a
3645 * possible repository-side move destination node in the revision which
3646 * is the target revision in case of update and switch, or the merge-right
3647 * revision in case of a merge.
3648 *
3649 * Each value is an apr_array_header_t *.
3650 * Each array consists of "const char *" absolute paths to working copy
3651 * nodes which correspond to the repository node selected by the map key.
3652 * Each such working copy node is a potential local move target which can
3653 * be chosen to "follow" the incoming move when resolving a tree conflict.
3654 *
3655 * This may be an empty hash map in case if there is no move target path
3656 * in the working copy. */
3657 apr_hash_t *wc_move_targets;
3658
3659 /* The preferred move target repository relpath. This is our key into
3660 * the WC_MOVE_TARGETS map above (can be overridden by the user). */
3661 const char *move_target_repos_relpath;
3662
3663 /* The current index into the list of working copy nodes corresponding to
3664 * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */
3665 int wc_move_target_idx;
3666 };
3667
3668 /* Get the currently selected repository-side move target path.
3669 * If none was selected yet, determine and return a default one. */
3670 static const char *
get_moved_to_repos_relpath(struct conflict_tree_incoming_delete_details * details,apr_pool_t * scratch_pool)3671 get_moved_to_repos_relpath(
3672 struct conflict_tree_incoming_delete_details *details,
3673 apr_pool_t *scratch_pool)
3674 {
3675 struct repos_move_info *move;
3676
3677 if (details->move_target_repos_relpath)
3678 return details->move_target_repos_relpath;
3679
3680 if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) > 0)
3681 {
3682 svn_sort__item_t item;
3683 apr_array_header_t *repos_relpaths;
3684
3685 repos_relpaths = svn_sort__hash(details->wc_move_targets,
3686 svn_sort_compare_items_as_paths,
3687 scratch_pool);
3688 item = APR_ARRAY_IDX(repos_relpaths, 0, svn_sort__item_t);
3689 return (const char *)item.key;
3690 }
3691
3692 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3693 return move->moved_to_repos_relpath;
3694 }
3695
3696 static const char *
describe_incoming_deletion_upon_update(struct conflict_tree_incoming_delete_details * details,svn_node_kind_t victim_node_kind,svn_revnum_t old_rev,svn_revnum_t new_rev,apr_pool_t * result_pool,apr_pool_t * scratch_pool)3697 describe_incoming_deletion_upon_update(
3698 struct conflict_tree_incoming_delete_details *details,
3699 svn_node_kind_t victim_node_kind,
3700 svn_revnum_t old_rev,
3701 svn_revnum_t new_rev,
3702 apr_pool_t *result_pool,
3703 apr_pool_t *scratch_pool)
3704 {
3705 if (details->replacing_node_kind == svn_node_file ||
3706 details->replacing_node_kind == svn_node_symlink)
3707 {
3708 if (victim_node_kind == svn_node_dir)
3709 {
3710 const char *description =
3711 apr_psprintf(result_pool,
3712 _("Directory updated from r%ld to r%ld was "
3713 "replaced with a file by %s in r%ld."),
3714 old_rev, new_rev,
3715 details->rev_author, details->deleted_rev);
3716 if (details->moves)
3717 {
3718 struct repos_move_info *move;
3719
3720 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3721 description =
3722 apr_psprintf(result_pool,
3723 _("%s\nThe replaced directory was moved to "
3724 "'^/%s'."), description,
3725 get_moved_to_repos_relpath(details, scratch_pool));
3726 return append_moved_to_chain_description(description,
3727 move->next,
3728 result_pool,
3729 scratch_pool);
3730 }
3731 return description;
3732 }
3733 else if (victim_node_kind == svn_node_file ||
3734 victim_node_kind == svn_node_symlink)
3735 {
3736 const char *description =
3737 apr_psprintf(result_pool,
3738 _("File updated from r%ld to r%ld was replaced "
3739 "with a file from another line of history by "
3740 "%s in r%ld."),
3741 old_rev, new_rev,
3742 details->rev_author, details->deleted_rev);
3743 if (details->moves)
3744 {
3745 struct repos_move_info *move;
3746
3747 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3748 description =
3749 apr_psprintf(result_pool,
3750 _("%s\nThe replaced file was moved to '^/%s'."),
3751 description,
3752 get_moved_to_repos_relpath(details, scratch_pool));
3753 return append_moved_to_chain_description(description,
3754 move->next,
3755 result_pool,
3756 scratch_pool);
3757 }
3758 return description;
3759 }
3760 else
3761 {
3762 const char *description =
3763 apr_psprintf(result_pool,
3764 _("Item updated from r%ld to r%ld was replaced "
3765 "with a file by %s in r%ld."), old_rev, new_rev,
3766 details->rev_author, details->deleted_rev);
3767 if (details->moves)
3768 {
3769 struct repos_move_info *move;
3770
3771 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3772 description =
3773 apr_psprintf(result_pool,
3774 _("%s\nThe replaced item was moved to '^/%s'."),
3775 description,
3776 get_moved_to_repos_relpath(details, scratch_pool));
3777 return append_moved_to_chain_description(description,
3778 move->next,
3779 result_pool,
3780 scratch_pool);
3781 }
3782 return description;
3783 }
3784 }
3785 else if (details->replacing_node_kind == svn_node_dir)
3786 {
3787 if (victim_node_kind == svn_node_dir)
3788 {
3789 const char *description =
3790 apr_psprintf(result_pool,
3791 _("Directory updated from r%ld to r%ld was "
3792 "replaced with a directory from another line "
3793 "of history by %s in r%ld."),
3794 old_rev, new_rev,
3795 details->rev_author, details->deleted_rev);
3796 if (details->moves)
3797 {
3798 struct repos_move_info *move;
3799
3800 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3801 description =
3802 apr_psprintf(result_pool,
3803 _("%s\nThe replaced directory was moved to "
3804 "'^/%s'."), description,
3805 get_moved_to_repos_relpath(details, scratch_pool));
3806 return append_moved_to_chain_description(description,
3807 move->next,
3808 result_pool,
3809 scratch_pool);
3810 }
3811 return description;
3812 }
3813 else if (victim_node_kind == svn_node_file ||
3814 victim_node_kind == svn_node_symlink)
3815 {
3816 const char *description =
3817 apr_psprintf(result_pool,
3818 _("File updated from r%ld to r%ld was "
3819 "replaced with a directory by %s in r%ld."),
3820 old_rev, new_rev,
3821 details->rev_author, details->deleted_rev);
3822 if (details->moves)
3823 {
3824 struct repos_move_info *move;
3825
3826 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3827 description =
3828 apr_psprintf(result_pool,
3829 _("%s\nThe replaced file was moved to '^/%s'."),
3830 description,
3831 get_moved_to_repos_relpath(details, scratch_pool));
3832 return append_moved_to_chain_description(description,
3833 move->next,
3834 result_pool,
3835 scratch_pool);
3836 }
3837 return description;
3838 }
3839 else
3840 {
3841 const char *description =
3842 apr_psprintf(result_pool,
3843 _("Item updated from r%ld to r%ld was replaced "
3844 "by %s in r%ld."), old_rev, new_rev,
3845 details->rev_author, details->deleted_rev);
3846 if (details->moves)
3847 {
3848 struct repos_move_info *move;
3849
3850 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3851 description =
3852 apr_psprintf(result_pool,
3853 _("%s\nThe replaced item was moved to '^/%s'."),
3854 description,
3855 get_moved_to_repos_relpath(details, scratch_pool));
3856 return append_moved_to_chain_description(description,
3857 move->next,
3858 result_pool,
3859 scratch_pool);
3860 }
3861 return description;
3862 }
3863 }
3864 else
3865 {
3866 if (victim_node_kind == svn_node_dir)
3867 {
3868 if (details->moves)
3869 {
3870 const char *description;
3871 struct repos_move_info *move;
3872
3873 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3874 description =
3875 apr_psprintf(result_pool,
3876 _("Directory updated from r%ld to r%ld was "
3877 "moved to '^/%s' by %s in r%ld."),
3878 old_rev, new_rev,
3879 get_moved_to_repos_relpath(details, scratch_pool),
3880 details->rev_author, details->deleted_rev);
3881 return append_moved_to_chain_description(description,
3882 move->next,
3883 result_pool,
3884 scratch_pool);
3885 }
3886 else
3887 return apr_psprintf(result_pool,
3888 _("Directory updated from r%ld to r%ld was "
3889 "deleted by %s in r%ld."),
3890 old_rev, new_rev,
3891 details->rev_author, details->deleted_rev);
3892 }
3893 else if (victim_node_kind == svn_node_file ||
3894 victim_node_kind == svn_node_symlink)
3895 {
3896 if (details->moves)
3897 {
3898 struct repos_move_info *move;
3899 const char *description;
3900
3901 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3902 description =
3903 apr_psprintf(result_pool,
3904 _("File updated from r%ld to r%ld was moved "
3905 "to '^/%s' by %s in r%ld."), old_rev, new_rev,
3906 get_moved_to_repos_relpath(details, scratch_pool),
3907 details->rev_author, details->deleted_rev);
3908 return append_moved_to_chain_description(description,
3909 move->next,
3910 result_pool,
3911 scratch_pool);
3912 }
3913 else
3914 return apr_psprintf(result_pool,
3915 _("File updated from r%ld to r%ld was "
3916 "deleted by %s in r%ld."), old_rev, new_rev,
3917 details->rev_author, details->deleted_rev);
3918 }
3919 else
3920 {
3921 if (details->moves)
3922 {
3923 const char *description;
3924 struct repos_move_info *move;
3925
3926 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3927 description =
3928 apr_psprintf(result_pool,
3929 _("Item updated from r%ld to r%ld was moved "
3930 "to '^/%s' by %s in r%ld."), old_rev, new_rev,
3931 get_moved_to_repos_relpath(details, scratch_pool),
3932 details->rev_author, details->deleted_rev);
3933 return append_moved_to_chain_description(description,
3934 move->next,
3935 result_pool,
3936 scratch_pool);
3937 }
3938 else
3939 return apr_psprintf(result_pool,
3940 _("Item updated from r%ld to r%ld was "
3941 "deleted by %s in r%ld."), old_rev, new_rev,
3942 details->rev_author, details->deleted_rev);
3943 }
3944 }
3945 }
3946
3947 static const char *
describe_incoming_reverse_addition_upon_update(struct conflict_tree_incoming_delete_details * details,svn_node_kind_t victim_node_kind,svn_revnum_t old_rev,svn_revnum_t new_rev,apr_pool_t * result_pool)3948 describe_incoming_reverse_addition_upon_update(
3949 struct conflict_tree_incoming_delete_details *details,
3950 svn_node_kind_t victim_node_kind,
3951 svn_revnum_t old_rev,
3952 svn_revnum_t new_rev,
3953 apr_pool_t *result_pool)
3954 {
3955 if (details->replacing_node_kind == svn_node_file ||
3956 details->replacing_node_kind == svn_node_symlink)
3957 {
3958 if (victim_node_kind == svn_node_dir)
3959 return apr_psprintf(result_pool,
3960 _("Directory updated backwards from r%ld to r%ld "
3961 "was a file before the replacement made by %s "
3962 "in r%ld."), old_rev, new_rev,
3963 details->rev_author, details->added_rev);
3964 else if (victim_node_kind == svn_node_file ||
3965 victim_node_kind == svn_node_symlink)
3966 return apr_psprintf(result_pool,
3967 _("File updated backwards from r%ld to r%ld was a "
3968 "file from another line of history before the "
3969 "replacement made by %s in r%ld."),
3970 old_rev, new_rev,
3971 details->rev_author, details->added_rev);
3972 else
3973 return apr_psprintf(result_pool,
3974 _("Item updated backwards from r%ld to r%ld was "
3975 "replaced with a file by %s in r%ld."),
3976 old_rev, new_rev,
3977 details->rev_author, details->added_rev);
3978 }
3979 else if (details->replacing_node_kind == svn_node_dir)
3980 {
3981 if (victim_node_kind == svn_node_dir)
3982 return apr_psprintf(result_pool,
3983 _("Directory updated backwards from r%ld to r%ld "
3984 "was a directory from another line of history "
3985 "before the replacement made by %s in "
3986 "r%ld."), old_rev, new_rev,
3987 details->rev_author, details->added_rev);
3988 else if (victim_node_kind == svn_node_file ||
3989 victim_node_kind == svn_node_symlink)
3990 return apr_psprintf(result_pool,
3991 _("File updated backwards from r%ld to r%ld was a "
3992 "directory before the replacement made by %s "
3993 "in r%ld."), old_rev, new_rev,
3994 details->rev_author, details->added_rev);
3995 else
3996 return apr_psprintf(result_pool,
3997 _("Item updated backwards from r%ld to r%ld was "
3998 "replaced with a directory by %s in r%ld."),
3999 old_rev, new_rev,
4000 details->rev_author, details->added_rev);
4001 }
4002 else
4003 {
4004 if (victim_node_kind == svn_node_dir)
4005 return apr_psprintf(result_pool,
4006 _("Directory updated backwards from r%ld to r%ld "
4007 "did not exist before it was added by %s in "
4008 "r%ld."), old_rev, new_rev,
4009 details->rev_author, details->added_rev);
4010 else if (victim_node_kind == svn_node_file ||
4011 victim_node_kind == svn_node_symlink)
4012 return apr_psprintf(result_pool,
4013 _("File updated backwards from r%ld to r%ld did "
4014 "not exist before it was added by %s in r%ld."),
4015 old_rev, new_rev,
4016 details->rev_author, details->added_rev);
4017 else
4018 return apr_psprintf(result_pool,
4019 _("Item updated backwards from r%ld to r%ld did "
4020 "not exist before it was added by %s in r%ld."),
4021 old_rev, new_rev,
4022 details->rev_author, details->added_rev);
4023 }
4024 }
4025
4026 static const char *
describe_incoming_deletion_upon_switch(struct conflict_tree_incoming_delete_details * details,svn_node_kind_t victim_node_kind,const char * old_repos_relpath,svn_revnum_t old_rev,const char * new_repos_relpath,svn_revnum_t new_rev,apr_pool_t * result_pool,apr_pool_t * scratch_pool)4027 describe_incoming_deletion_upon_switch(
4028 struct conflict_tree_incoming_delete_details *details,
4029 svn_node_kind_t victim_node_kind,
4030 const char *old_repos_relpath,
4031 svn_revnum_t old_rev,
4032 const char *new_repos_relpath,
4033 svn_revnum_t new_rev,
4034 apr_pool_t *result_pool,
4035 apr_pool_t *scratch_pool)
4036 {
4037 if (details->replacing_node_kind == svn_node_file ||
4038 details->replacing_node_kind == svn_node_symlink)
4039 {
4040 if (victim_node_kind == svn_node_dir)
4041 {
4042 const char *description =
4043 apr_psprintf(result_pool,
4044 _("Directory switched from\n"
4045 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4046 "was replaced with a file by %s in r%ld."),
4047 old_repos_relpath, old_rev,
4048 new_repos_relpath, new_rev,
4049 details->rev_author, details->deleted_rev);
4050 if (details->moves)
4051 {
4052 struct repos_move_info *move;
4053
4054 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4055 description =
4056 apr_psprintf(result_pool,
4057 _("%s\nThe replaced directory was moved "
4058 "to '^/%s'."), description,
4059 get_moved_to_repos_relpath(details, scratch_pool));
4060 return append_moved_to_chain_description(description,
4061 move->next,
4062 result_pool,
4063 scratch_pool);
4064 }
4065 return description;
4066 }
4067 else if (victim_node_kind == svn_node_file ||
4068 victim_node_kind == svn_node_symlink)
4069 {
4070 const char *description =
4071 apr_psprintf(result_pool,
4072 _("File switched from\n"
4073 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4074 "replaced with a file from another line of "
4075 "history by %s in r%ld."),
4076 old_repos_relpath, old_rev,
4077 new_repos_relpath, new_rev,
4078 details->rev_author, details->deleted_rev);
4079 if (details->moves)
4080 {
4081 struct repos_move_info *move;
4082
4083 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4084 description =
4085 apr_psprintf(result_pool,
4086 _("%s\nThe replaced file was moved to '^/%s'."),
4087 description,
4088 get_moved_to_repos_relpath(details, scratch_pool));
4089 return append_moved_to_chain_description(description,
4090 move->next,
4091 result_pool,
4092 scratch_pool);
4093 }
4094 return description;
4095 }
4096 else
4097 {
4098 const char *description =
4099 apr_psprintf(result_pool,
4100 _("Item switched from\n"
4101 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4102 "replaced with a file by %s in r%ld."),
4103 old_repos_relpath, old_rev,
4104 new_repos_relpath, new_rev,
4105 details->rev_author, details->deleted_rev);
4106 if (details->moves)
4107 {
4108 struct repos_move_info *move;
4109
4110 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4111 description =
4112 apr_psprintf(result_pool,
4113 _("%s\nThe replaced item was moved to '^/%s'."),
4114 description,
4115 get_moved_to_repos_relpath(details, scratch_pool));
4116 return append_moved_to_chain_description(description,
4117 move->next,
4118 result_pool,
4119 scratch_pool);
4120 }
4121 return description;
4122 }
4123 }
4124 else if (details->replacing_node_kind == svn_node_dir)
4125 {
4126 if (victim_node_kind == svn_node_dir)
4127 {
4128 const char *description =
4129 apr_psprintf(result_pool,
4130 _("Directory switched from\n"
4131 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4132 "was replaced with a directory from another "
4133 "line of history by %s in r%ld."),
4134 old_repos_relpath, old_rev,
4135 new_repos_relpath, new_rev,
4136 details->rev_author, details->deleted_rev);
4137 if (details->moves)
4138 {
4139 struct repos_move_info *move;
4140
4141 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4142 description =
4143 apr_psprintf(result_pool,
4144 _("%s\nThe replaced directory was moved to "
4145 "'^/%s'."), description,
4146 get_moved_to_repos_relpath(details, scratch_pool));
4147 return append_moved_to_chain_description(description,
4148 move->next,
4149 result_pool,
4150 scratch_pool);
4151 }
4152 return description;
4153 }
4154 else if (victim_node_kind == svn_node_file ||
4155 victim_node_kind == svn_node_symlink)
4156 {
4157 const char *description =
4158 apr_psprintf(result_pool,
4159 _("File switched from\n"
4160 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4161 "was replaced with a directory by %s in r%ld."),
4162 old_repos_relpath, old_rev,
4163 new_repos_relpath, new_rev,
4164 details->rev_author, details->deleted_rev);
4165 if (details->moves)
4166 {
4167 struct repos_move_info *move;
4168
4169 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4170 description =
4171 apr_psprintf(result_pool,
4172 _("%s\nThe replaced file was moved to '^/%s'."),
4173 description,
4174 get_moved_to_repos_relpath(details, scratch_pool));
4175 return append_moved_to_chain_description(description,
4176 move->next,
4177 result_pool,
4178 scratch_pool);
4179 }
4180 return description;
4181 }
4182 else
4183 {
4184 const char *description =
4185 apr_psprintf(result_pool,
4186 _("Item switched from\n"
4187 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4188 "replaced with a directory by %s in r%ld."),
4189 old_repos_relpath, old_rev,
4190 new_repos_relpath, new_rev,
4191 details->rev_author, details->deleted_rev);
4192 if (details->moves)
4193 {
4194 struct repos_move_info *move;
4195
4196 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4197 description =
4198 apr_psprintf(result_pool,
4199 _("%s\nThe replaced item was moved to '^/%s'."),
4200 description,
4201 get_moved_to_repos_relpath(details, scratch_pool));
4202 return append_moved_to_chain_description(description,
4203 move->next,
4204 result_pool,
4205 scratch_pool);
4206 }
4207 return description;
4208 }
4209 }
4210 else
4211 {
4212 if (victim_node_kind == svn_node_dir)
4213 {
4214 if (details->moves)
4215 {
4216 struct repos_move_info *move;
4217 const char *description;
4218
4219 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4220 description =
4221 apr_psprintf(result_pool,
4222 _("Directory switched from\n"
4223 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4224 "was moved to '^/%s' by %s in r%ld."),
4225 old_repos_relpath, old_rev,
4226 new_repos_relpath, new_rev,
4227 get_moved_to_repos_relpath(details, scratch_pool),
4228 details->rev_author, details->deleted_rev);
4229 return append_moved_to_chain_description(description,
4230 move->next,
4231 result_pool,
4232 scratch_pool);
4233 }
4234 else
4235 return apr_psprintf(result_pool,
4236 _("Directory switched from\n"
4237 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4238 "was deleted by %s in r%ld."),
4239 old_repos_relpath, old_rev,
4240 new_repos_relpath, new_rev,
4241 details->rev_author, details->deleted_rev);
4242 }
4243 else if (victim_node_kind == svn_node_file ||
4244 victim_node_kind == svn_node_symlink)
4245 {
4246 if (details->moves)
4247 {
4248 struct repos_move_info *move;
4249 const char *description;
4250
4251 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4252 description =
4253 apr_psprintf(result_pool,
4254 _("File switched from\n"
4255 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4256 "moved to '^/%s' by %s in r%ld."),
4257 old_repos_relpath, old_rev,
4258 new_repos_relpath, new_rev,
4259 get_moved_to_repos_relpath(details, scratch_pool),
4260 details->rev_author, details->deleted_rev);
4261 return append_moved_to_chain_description(description,
4262 move->next,
4263 result_pool,
4264 scratch_pool);
4265 }
4266 else
4267 return apr_psprintf(result_pool,
4268 _("File switched from\n"
4269 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4270 "deleted by %s in r%ld."),
4271 old_repos_relpath, old_rev,
4272 new_repos_relpath, new_rev,
4273 details->rev_author, details->deleted_rev);
4274 }
4275 else
4276 {
4277 if (details->moves)
4278 {
4279 struct repos_move_info *move;
4280 const char *description;
4281
4282 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4283 description =
4284 apr_psprintf(result_pool,
4285 _("Item switched from\n"
4286 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4287 "moved to '^/%s' by %s in r%ld."),
4288 old_repos_relpath, old_rev,
4289 new_repos_relpath, new_rev,
4290 get_moved_to_repos_relpath(details, scratch_pool),
4291 details->rev_author, details->deleted_rev);
4292 return append_moved_to_chain_description(description,
4293 move->next,
4294 result_pool,
4295 scratch_pool);
4296 }
4297 else
4298 return apr_psprintf(result_pool,
4299 _("Item switched from\n"
4300 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4301 "deleted by %s in r%ld."),
4302 old_repos_relpath, old_rev,
4303 new_repos_relpath, new_rev,
4304 details->rev_author, details->deleted_rev);
4305 }
4306 }
4307 }
4308
4309 static const char *
describe_incoming_reverse_addition_upon_switch(struct conflict_tree_incoming_delete_details * details,svn_node_kind_t victim_node_kind,const char * old_repos_relpath,svn_revnum_t old_rev,const char * new_repos_relpath,svn_revnum_t new_rev,apr_pool_t * result_pool)4310 describe_incoming_reverse_addition_upon_switch(
4311 struct conflict_tree_incoming_delete_details *details,
4312 svn_node_kind_t victim_node_kind,
4313 const char *old_repos_relpath,
4314 svn_revnum_t old_rev,
4315 const char *new_repos_relpath,
4316 svn_revnum_t new_rev,
4317 apr_pool_t *result_pool)
4318 {
4319 if (details->replacing_node_kind == svn_node_file ||
4320 details->replacing_node_kind == svn_node_symlink)
4321 {
4322 if (victim_node_kind == svn_node_dir)
4323 return apr_psprintf(result_pool,
4324 _("Directory switched from\n"
4325 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4326 "was a file before the replacement made by %s "
4327 "in r%ld."),
4328 old_repos_relpath, old_rev,
4329 new_repos_relpath, new_rev,
4330 details->rev_author, details->added_rev);
4331 else if (victim_node_kind == svn_node_file ||
4332 victim_node_kind == svn_node_symlink)
4333 return apr_psprintf(result_pool,
4334 _("File switched from\n"
4335 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas a "
4336 "file from another line of history before the "
4337 "replacement made by %s in r%ld."),
4338 old_repos_relpath, old_rev,
4339 new_repos_relpath, new_rev,
4340 details->rev_author, details->added_rev);
4341 else
4342 return apr_psprintf(result_pool,
4343 _("Item switched from\n"
4344 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4345 "replaced with a file by %s in r%ld."),
4346 old_repos_relpath, old_rev,
4347 new_repos_relpath, new_rev,
4348 details->rev_author, details->added_rev);
4349 }
4350 else if (details->replacing_node_kind == svn_node_dir)
4351 {
4352 if (victim_node_kind == svn_node_dir)
4353 return apr_psprintf(result_pool,
4354 _("Directory switched from\n"
4355 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4356 "was a directory from another line of history "
4357 "before the replacement made by %s in r%ld."),
4358 old_repos_relpath, old_rev,
4359 new_repos_relpath, new_rev,
4360 details->rev_author, details->added_rev);
4361 else if (victim_node_kind == svn_node_file ||
4362 victim_node_kind == svn_node_symlink)
4363 return apr_psprintf(result_pool,
4364 _("Directory switched from\n"
4365 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4366 "was a file before the replacement made by %s "
4367 "in r%ld."),
4368 old_repos_relpath, old_rev,
4369 new_repos_relpath, new_rev,
4370 details->rev_author, details->added_rev);
4371 else
4372 return apr_psprintf(result_pool,
4373 _("Item switched from\n"
4374 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4375 "replaced with a directory by %s in r%ld."),
4376 old_repos_relpath, old_rev,
4377 new_repos_relpath, new_rev,
4378 details->rev_author, details->added_rev);
4379 }
4380 else
4381 {
4382 if (victim_node_kind == svn_node_dir)
4383 return apr_psprintf(result_pool,
4384 _("Directory switched from\n"
4385 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4386 "did not exist before it was added by %s in "
4387 "r%ld."),
4388 old_repos_relpath, old_rev,
4389 new_repos_relpath, new_rev,
4390 details->rev_author, details->added_rev);
4391 else if (victim_node_kind == svn_node_file ||
4392 victim_node_kind == svn_node_symlink)
4393 return apr_psprintf(result_pool,
4394 _("File switched from\n"
4395 "'^/%s@%ld'\nto\n'^/%s@%ld'\ndid "
4396 "not exist before it was added by %s in "
4397 "r%ld."),
4398 old_repos_relpath, old_rev,
4399 new_repos_relpath, new_rev,
4400 details->rev_author, details->added_rev);
4401 else
4402 return apr_psprintf(result_pool,
4403 _("Item switched from\n"
4404 "'^/%s@%ld'\nto\n'^/%s@%ld'\ndid "
4405 "not exist before it was added by %s in "
4406 "r%ld."),
4407 old_repos_relpath, old_rev,
4408 new_repos_relpath, new_rev,
4409 details->rev_author, details->added_rev);
4410 }
4411 }
4412
4413 static const char *
describe_incoming_deletion_upon_merge(struct conflict_tree_incoming_delete_details * details,svn_node_kind_t victim_node_kind,const char * old_repos_relpath,svn_revnum_t old_rev,const char * new_repos_relpath,svn_revnum_t new_rev,apr_pool_t * result_pool,apr_pool_t * scratch_pool)4414 describe_incoming_deletion_upon_merge(
4415 struct conflict_tree_incoming_delete_details *details,
4416 svn_node_kind_t victim_node_kind,
4417 const char *old_repos_relpath,
4418 svn_revnum_t old_rev,
4419 const char *new_repos_relpath,
4420 svn_revnum_t new_rev,
4421 apr_pool_t *result_pool,
4422 apr_pool_t *scratch_pool)
4423 {
4424 if (details->replacing_node_kind == svn_node_file ||
4425 details->replacing_node_kind == svn_node_symlink)
4426 {
4427 if (victim_node_kind == svn_node_dir)
4428 {
4429 const char *description =
4430 apr_psprintf(result_pool,
4431 _("Directory merged from\n"
4432 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4433 "was replaced with a file by %s in r%ld."),
4434 old_repos_relpath, old_rev,
4435 new_repos_relpath, new_rev,
4436 details->rev_author, details->deleted_rev);
4437 if (details->moves)
4438 {
4439 struct repos_move_info *move;
4440
4441 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4442 description =
4443 apr_psprintf(result_pool,
4444 _("%s\nThe replaced directory was moved to "
4445 "'^/%s'."), description,
4446 get_moved_to_repos_relpath(details, scratch_pool));
4447 return append_moved_to_chain_description(description,
4448 move->next,
4449 result_pool,
4450 scratch_pool);
4451 }
4452 return description;
4453 }
4454 else if (victim_node_kind == svn_node_file ||
4455 victim_node_kind == svn_node_symlink)
4456 {
4457 const char *description =
4458 apr_psprintf(result_pool,
4459 _("File merged from\n"
4460 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4461 "replaced with a file from another line of "
4462 "history by %s in r%ld."),
4463 old_repos_relpath, old_rev,
4464 new_repos_relpath, new_rev,
4465 details->rev_author, details->deleted_rev);
4466 if (details->moves)
4467 {
4468 struct repos_move_info *move;
4469
4470 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4471 description =
4472 apr_psprintf(result_pool,
4473 _("%s\nThe replaced file was moved to '^/%s'."),
4474 description,
4475 get_moved_to_repos_relpath(details, scratch_pool));
4476 return append_moved_to_chain_description(description,
4477 move->next,
4478 result_pool,
4479 scratch_pool);
4480 }
4481 return description;
4482 }
4483 else
4484 return apr_psprintf(result_pool,
4485 _("Item merged from\n"
4486 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4487 "replaced with a file by %s in r%ld."),
4488 old_repos_relpath, old_rev,
4489 new_repos_relpath, new_rev,
4490 details->rev_author, details->deleted_rev);
4491 }
4492 else if (details->replacing_node_kind == svn_node_dir)
4493 {
4494 if (victim_node_kind == svn_node_dir)
4495 {
4496 const char *description =
4497 apr_psprintf(result_pool,
4498 _("Directory merged from\n"
4499 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4500 "was replaced with a directory from another "
4501 "line of history by %s in r%ld."),
4502 old_repos_relpath, old_rev,
4503 new_repos_relpath, new_rev,
4504 details->rev_author, details->deleted_rev);
4505 if (details->moves)
4506 {
4507 struct repos_move_info *move;
4508
4509 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4510 description =
4511 apr_psprintf(result_pool,
4512 _("%s\nThe replaced directory was moved to "
4513 "'^/%s'."), description,
4514 get_moved_to_repos_relpath(details, scratch_pool));
4515 return append_moved_to_chain_description(description,
4516 move->next,
4517 result_pool,
4518 scratch_pool);
4519 }
4520 return description;
4521 }
4522 else if (victim_node_kind == svn_node_file ||
4523 victim_node_kind == svn_node_symlink)
4524 {
4525 const char *description =
4526 apr_psprintf(result_pool,
4527 _("File merged from\n"
4528 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4529 "was replaced with a directory by %s in r%ld."),
4530 old_repos_relpath, old_rev,
4531 new_repos_relpath, new_rev,
4532 details->rev_author, details->deleted_rev);
4533 if (details->moves)
4534 {
4535 struct repos_move_info *move;
4536
4537 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4538 description =
4539 apr_psprintf(result_pool,
4540 _("%s\nThe replaced file was moved to '^/%s'."),
4541 description,
4542 get_moved_to_repos_relpath(details, scratch_pool));
4543 return append_moved_to_chain_description(description,
4544 move->next,
4545 result_pool,
4546 scratch_pool);
4547 }
4548 return description;
4549 }
4550 else
4551 {
4552 const char *description =
4553 apr_psprintf(result_pool,
4554 _("Item merged from\n"
4555 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4556 "replaced with a directory by %s in r%ld."),
4557 old_repos_relpath, old_rev,
4558 new_repos_relpath, new_rev,
4559 details->rev_author, details->deleted_rev);
4560 if (details->moves)
4561 {
4562 struct repos_move_info *move;
4563
4564 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4565 description =
4566 apr_psprintf(result_pool,
4567 _("%s\nThe replaced item was moved to '^/%s'."),
4568 description,
4569 get_moved_to_repos_relpath(details, scratch_pool));
4570 return append_moved_to_chain_description(description,
4571 move->next,
4572 result_pool,
4573 scratch_pool);
4574 }
4575 return description;
4576 }
4577 }
4578 else
4579 {
4580 if (victim_node_kind == svn_node_dir)
4581 {
4582 if (details->moves)
4583 {
4584 struct repos_move_info *move;
4585 const char *description;
4586
4587 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4588 description =
4589 apr_psprintf(result_pool,
4590 _("Directory merged from\n"
4591 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4592 "moved to '^/%s' by %s in r%ld."),
4593 old_repos_relpath, old_rev,
4594 new_repos_relpath, new_rev,
4595 get_moved_to_repos_relpath(details, scratch_pool),
4596 details->rev_author, details->deleted_rev);
4597 return append_moved_to_chain_description(description,
4598 move->next,
4599 result_pool,
4600 scratch_pool);
4601 }
4602 else
4603 return apr_psprintf(result_pool,
4604 _("Directory merged from\n"
4605 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4606 "deleted by %s in r%ld."),
4607 old_repos_relpath, old_rev,
4608 new_repos_relpath, new_rev,
4609 details->rev_author, details->deleted_rev);
4610 }
4611 else if (victim_node_kind == svn_node_file ||
4612 victim_node_kind == svn_node_symlink)
4613 {
4614 if (details->moves)
4615 {
4616 struct repos_move_info *move;
4617 const char *description;
4618
4619 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4620 description =
4621 apr_psprintf(result_pool,
4622 _("File merged from\n"
4623 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4624 "moved to '^/%s' by %s in r%ld."),
4625 old_repos_relpath, old_rev,
4626 new_repos_relpath, new_rev,
4627 get_moved_to_repos_relpath(details, scratch_pool),
4628 details->rev_author, details->deleted_rev);
4629 return append_moved_to_chain_description(description,
4630 move->next,
4631 result_pool,
4632 scratch_pool);
4633 }
4634 else
4635 return apr_psprintf(result_pool,
4636 _("File merged from\n"
4637 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4638 "deleted by %s in r%ld."),
4639 old_repos_relpath, old_rev,
4640 new_repos_relpath, new_rev,
4641 details->rev_author, details->deleted_rev);
4642 }
4643 else
4644 {
4645 if (details->moves)
4646 {
4647 struct repos_move_info *move;
4648 const char *description;
4649
4650 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4651 description =
4652 apr_psprintf(result_pool,
4653 _("Item merged from\n"
4654 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4655 "moved to '^/%s' by %s in r%ld."),
4656 old_repos_relpath, old_rev,
4657 new_repos_relpath, new_rev,
4658 get_moved_to_repos_relpath(details, scratch_pool),
4659 details->rev_author, details->deleted_rev);
4660 return append_moved_to_chain_description(description,
4661 move->next,
4662 result_pool,
4663 scratch_pool);
4664 }
4665 else
4666 return apr_psprintf(result_pool,
4667 _("Item merged from\n"
4668 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4669 "deleted by %s in r%ld."),
4670 old_repos_relpath, old_rev,
4671 new_repos_relpath, new_rev,
4672 details->rev_author, details->deleted_rev);
4673 }
4674 }
4675 }
4676
4677 static const char *
describe_incoming_reverse_addition_upon_merge(struct conflict_tree_incoming_delete_details * details,svn_node_kind_t victim_node_kind,const char * old_repos_relpath,svn_revnum_t old_rev,const char * new_repos_relpath,svn_revnum_t new_rev,apr_pool_t * result_pool)4678 describe_incoming_reverse_addition_upon_merge(
4679 struct conflict_tree_incoming_delete_details *details,
4680 svn_node_kind_t victim_node_kind,
4681 const char *old_repos_relpath,
4682 svn_revnum_t old_rev,
4683 const char *new_repos_relpath,
4684 svn_revnum_t new_rev,
4685 apr_pool_t *result_pool)
4686 {
4687 if (details->replacing_node_kind == svn_node_file ||
4688 details->replacing_node_kind == svn_node_symlink)
4689 {
4690 if (victim_node_kind == svn_node_dir)
4691 return apr_psprintf(result_pool,
4692 _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4693 "^/%s@%ld was a file before the replacement "
4694 "made by %s in r%ld."),
4695 old_repos_relpath, old_rev,
4696 new_repos_relpath, new_rev,
4697 details->rev_author, details->added_rev);
4698 else if (victim_node_kind == svn_node_file ||
4699 victim_node_kind == svn_node_symlink)
4700 return apr_psprintf(result_pool,
4701 _("File reverse-merged from\n"
4702 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4703 "was a file from another line of history before "
4704 "the replacement made by %s in r%ld."),
4705 old_repos_relpath, old_rev,
4706 new_repos_relpath, new_rev,
4707 details->rev_author, details->added_rev);
4708 else
4709 return apr_psprintf(result_pool,
4710 _("Item reverse-merged from\n"
4711 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4712 "was replaced with a file by %s in r%ld."),
4713 old_repos_relpath, old_rev,
4714 new_repos_relpath, new_rev,
4715 details->rev_author, details->added_rev);
4716 }
4717 else if (details->replacing_node_kind == svn_node_dir)
4718 {
4719 if (victim_node_kind == svn_node_dir)
4720 return apr_psprintf(result_pool,
4721 _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4722 "^/%s@%ld was a directory from another line "
4723 "of history before the replacement made by %s "
4724 "in r%ld."),
4725 old_repos_relpath, old_rev,
4726 new_repos_relpath, new_rev,
4727 details->rev_author, details->added_rev);
4728 else if (victim_node_kind == svn_node_file ||
4729 victim_node_kind == svn_node_symlink)
4730 return apr_psprintf(result_pool,
4731 _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4732 "^/%s@%ld was a file before the replacement "
4733 "made by %s in r%ld."),
4734 old_repos_relpath, old_rev,
4735 new_repos_relpath, new_rev,
4736 details->rev_author, details->added_rev);
4737 else
4738 return apr_psprintf(result_pool,
4739 _("Item reverse-merged from\n"
4740 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4741 "was replaced with a directory by %s in r%ld."),
4742 old_repos_relpath, old_rev,
4743 new_repos_relpath, new_rev,
4744 details->rev_author, details->added_rev);
4745 }
4746 else
4747 {
4748 if (victim_node_kind == svn_node_dir)
4749 return apr_psprintf(result_pool,
4750 _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4751 "^/%s@%ld did not exist before it was added "
4752 "by %s in r%ld."),
4753 old_repos_relpath, old_rev,
4754 new_repos_relpath, new_rev,
4755 details->rev_author, details->added_rev);
4756 else if (victim_node_kind == svn_node_file ||
4757 victim_node_kind == svn_node_symlink)
4758 return apr_psprintf(result_pool,
4759 _("File reverse-merged from\n"
4760 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4761 "did not exist before it was added by %s in "
4762 "r%ld."),
4763 old_repos_relpath, old_rev,
4764 new_repos_relpath, new_rev,
4765 details->rev_author, details->added_rev);
4766 else
4767 return apr_psprintf(result_pool,
4768 _("Item reverse-merged from\n"
4769 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4770 "did not exist before it was added by %s in "
4771 "r%ld."),
4772 old_repos_relpath, old_rev,
4773 new_repos_relpath, new_rev,
4774 details->rev_author, details->added_rev);
4775 }
4776 }
4777
4778 /* Implements tree_conflict_get_description_func_t. */
4779 static svn_error_t *
conflict_tree_get_description_incoming_delete(const char ** incoming_change_description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)4780 conflict_tree_get_description_incoming_delete(
4781 const char **incoming_change_description,
4782 svn_client_conflict_t *conflict,
4783 svn_client_ctx_t *ctx,
4784 apr_pool_t *result_pool,
4785 apr_pool_t *scratch_pool)
4786 {
4787 const char *action;
4788 svn_node_kind_t victim_node_kind;
4789 svn_wc_operation_t conflict_operation;
4790 const char *old_repos_relpath;
4791 svn_revnum_t old_rev;
4792 const char *new_repos_relpath;
4793 svn_revnum_t new_rev;
4794 struct conflict_tree_incoming_delete_details *details;
4795
4796 if (conflict->tree_conflict_incoming_details == NULL)
4797 return svn_error_trace(conflict_tree_get_incoming_description_generic(
4798 incoming_change_description,
4799 conflict, ctx, result_pool, scratch_pool));
4800
4801 conflict_operation = svn_client_conflict_get_operation(conflict);
4802 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
4803 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
4804 &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
4805 scratch_pool));
4806 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
4807 &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
4808 scratch_pool));
4809
4810 details = conflict->tree_conflict_incoming_details;
4811
4812 if (conflict_operation == svn_wc_operation_update)
4813 {
4814 if (details->deleted_rev != SVN_INVALID_REVNUM)
4815 {
4816 action = describe_incoming_deletion_upon_update(details,
4817 victim_node_kind,
4818 old_rev,
4819 new_rev,
4820 result_pool,
4821 scratch_pool);
4822 }
4823 else /* details->added_rev != SVN_INVALID_REVNUM */
4824 {
4825 /* This deletion is really the reverse change of an addition. */
4826 action = describe_incoming_reverse_addition_upon_update(
4827 details, victim_node_kind, old_rev, new_rev, result_pool);
4828 }
4829 }
4830 else if (conflict_operation == svn_wc_operation_switch)
4831 {
4832 if (details->deleted_rev != SVN_INVALID_REVNUM)
4833 {
4834 action = describe_incoming_deletion_upon_switch(details,
4835 victim_node_kind,
4836 old_repos_relpath,
4837 old_rev,
4838 new_repos_relpath,
4839 new_rev,
4840 result_pool,
4841 scratch_pool);
4842 }
4843 else /* details->added_rev != SVN_INVALID_REVNUM */
4844 {
4845 /* This deletion is really the reverse change of an addition. */
4846 action = describe_incoming_reverse_addition_upon_switch(
4847 details, victim_node_kind, old_repos_relpath, old_rev,
4848 new_repos_relpath, new_rev, result_pool);
4849
4850 }
4851 }
4852 else if (conflict_operation == svn_wc_operation_merge)
4853 {
4854 if (details->deleted_rev != SVN_INVALID_REVNUM)
4855 {
4856 action = describe_incoming_deletion_upon_merge(details,
4857 victim_node_kind,
4858 old_repos_relpath,
4859 old_rev,
4860 new_repos_relpath,
4861 new_rev,
4862 result_pool,
4863 scratch_pool);
4864 }
4865 else /* details->added_rev != SVN_INVALID_REVNUM */
4866 {
4867 /* This deletion is really the reverse change of an addition. */
4868 action = describe_incoming_reverse_addition_upon_merge(
4869 details, victim_node_kind, old_repos_relpath, old_rev,
4870 new_repos_relpath, new_rev, result_pool);
4871 }
4872 }
4873
4874 *incoming_change_description = apr_pstrdup(result_pool, action);
4875
4876 return SVN_NO_ERROR;
4877 }
4878
4879 /* Baton for find_added_rev(). */
4880 struct find_added_rev_baton
4881 {
4882 const char *victim_abspath;
4883 svn_client_ctx_t *ctx;
4884 svn_revnum_t added_rev;
4885 const char *repos_relpath;
4886 const char *parent_repos_relpath;
4887 apr_pool_t *pool;
4888 };
4889
4890 /* Implements svn_location_segment_receiver_t.
4891 * Finds the revision in which a node was added by tracing 'start'
4892 * revisions in location segments reported for the node.
4893 * If the PARENT_REPOS_RELPATH in the baton is not NULL, only consider
4894 * segments in which the node existed somwhere beneath this path. */
4895 static svn_error_t *
find_added_rev(svn_location_segment_t * segment,void * baton,apr_pool_t * scratch_pool)4896 find_added_rev(svn_location_segment_t *segment,
4897 void *baton,
4898 apr_pool_t *scratch_pool)
4899 {
4900 struct find_added_rev_baton *b = baton;
4901
4902 if (b->ctx->notify_func2)
4903 {
4904 svn_wc_notify_t *notify;
4905
4906 notify = svn_wc_create_notify(
4907 b->victim_abspath,
4908 svn_wc_notify_tree_conflict_details_progress,
4909 scratch_pool),
4910 notify->revision = segment->range_start;
4911 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
4912 }
4913
4914 if (segment->path) /* not interested in gaps */
4915 {
4916 if (b->parent_repos_relpath == NULL ||
4917 svn_relpath_skip_ancestor(b->parent_repos_relpath,
4918 segment->path) != NULL)
4919 {
4920 b->added_rev = segment->range_start;
4921 b->repos_relpath = apr_pstrdup(b->pool, segment->path);
4922 }
4923 }
4924
4925 return SVN_NO_ERROR;
4926 }
4927
4928 /* Find conflict details in the case where a revision which added a node was
4929 * applied in reverse, resulting in an incoming deletion. */
4930 static svn_error_t *
get_incoming_delete_details_for_reverse_addition(struct conflict_tree_incoming_delete_details ** details,const char * repos_root_url,const char * old_repos_relpath,svn_revnum_t old_rev,svn_revnum_t new_rev,svn_client_ctx_t * ctx,const char * victim_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)4931 get_incoming_delete_details_for_reverse_addition(
4932 struct conflict_tree_incoming_delete_details **details,
4933 const char *repos_root_url,
4934 const char *old_repos_relpath,
4935 svn_revnum_t old_rev,
4936 svn_revnum_t new_rev,
4937 svn_client_ctx_t *ctx,
4938 const char *victim_abspath,
4939 apr_pool_t *result_pool,
4940 apr_pool_t *scratch_pool)
4941 {
4942 svn_ra_session_t *ra_session;
4943 const char *url;
4944 const char *corrected_url;
4945 svn_string_t *author_revprop;
4946 struct find_added_rev_baton b = { 0 };
4947
4948 url = svn_path_url_add_component2(repos_root_url, old_repos_relpath,
4949 scratch_pool);
4950 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
4951 &corrected_url,
4952 url, NULL, NULL,
4953 FALSE,
4954 FALSE,
4955 ctx,
4956 scratch_pool,
4957 scratch_pool));
4958
4959 *details = apr_pcalloc(result_pool, sizeof(**details));
4960 b.ctx = ctx;
4961 b.victim_abspath = victim_abspath;
4962 b.added_rev = SVN_INVALID_REVNUM;
4963 b.repos_relpath = NULL;
4964 b.parent_repos_relpath = NULL;
4965 b.pool = scratch_pool;
4966
4967 /* Figure out when this node was added. */
4968 SVN_ERR(svn_ra_get_location_segments(ra_session, "", old_rev,
4969 old_rev, new_rev,
4970 find_added_rev, &b,
4971 scratch_pool));
4972
4973 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
4974 SVN_PROP_REVISION_AUTHOR,
4975 &author_revprop, scratch_pool));
4976 (*details)->deleted_rev = SVN_INVALID_REVNUM;
4977 (*details)->added_rev = b.added_rev;
4978 (*details)->repos_relpath = apr_pstrdup(result_pool, b.repos_relpath);
4979 if (author_revprop)
4980 (*details)->rev_author = apr_pstrdup(result_pool, author_revprop->data);
4981 else
4982 (*details)->rev_author = _("unknown author");
4983
4984 /* Check for replacement. */
4985 (*details)->replacing_node_kind = svn_node_none;
4986 if ((*details)->added_rev > 0)
4987 {
4988 svn_node_kind_t replaced_node_kind;
4989
4990 SVN_ERR(svn_ra_check_path(ra_session, "",
4991 rev_below((*details)->added_rev),
4992 &replaced_node_kind, scratch_pool));
4993 if (replaced_node_kind != svn_node_none)
4994 SVN_ERR(svn_ra_check_path(ra_session, "", (*details)->added_rev,
4995 &(*details)->replacing_node_kind,
4996 scratch_pool));
4997 }
4998
4999 return SVN_NO_ERROR;
5000 }
5001
5002 static svn_error_t *
init_wc_move_targets(struct conflict_tree_incoming_delete_details * details,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)5003 init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
5004 svn_client_conflict_t *conflict,
5005 svn_client_ctx_t *ctx,
5006 apr_pool_t *scratch_pool)
5007 {
5008 int i;
5009 const char *victim_abspath;
5010 svn_node_kind_t victim_node_kind;
5011 const char *incoming_new_repos_relpath;
5012 svn_revnum_t incoming_new_pegrev;
5013
5014 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
5015 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
5016 /* ### Should we get the old location in case of reverse-merges? */
5017 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5018 &incoming_new_repos_relpath, &incoming_new_pegrev,
5019 NULL, conflict,
5020 scratch_pool, scratch_pool));
5021 details->wc_move_targets = apr_hash_make(conflict->pool);
5022 for (i = 0; i < details->moves->nelts; i++)
5023 {
5024 struct repos_move_info *move;
5025
5026 move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
5027 SVN_ERR(follow_move_chains(details->wc_move_targets, move,
5028 ctx, victim_abspath,
5029 victim_node_kind,
5030 incoming_new_repos_relpath,
5031 incoming_new_pegrev,
5032 conflict->pool, scratch_pool));
5033 }
5034
5035 /* Initialize to the first possible move target. Hopefully,
5036 * in most cases there will only be one candidate anyway. */
5037 details->move_target_repos_relpath =
5038 get_moved_to_repos_relpath(details, scratch_pool);
5039 details->wc_move_target_idx = 0;
5040
5041 /* If only one move target exists recommend a resolution option. */
5042 if (apr_hash_count(details->wc_move_targets) == 1)
5043 {
5044 apr_array_header_t *wc_abspaths;
5045
5046 wc_abspaths = svn_hash_gets(details->wc_move_targets,
5047 details->move_target_repos_relpath);
5048 if (wc_abspaths->nelts == 1)
5049 {
5050 svn_client_conflict_option_id_t recommended[] =
5051 {
5052 /* Only one of these will be present for any given conflict. */
5053 svn_client_conflict_option_incoming_move_file_text_merge,
5054 svn_client_conflict_option_incoming_move_dir_merge,
5055 svn_client_conflict_option_local_move_file_text_merge,
5056 svn_client_conflict_option_local_move_dir_merge,
5057 svn_client_conflict_option_sibling_move_file_text_merge,
5058 svn_client_conflict_option_sibling_move_dir_merge,
5059 };
5060 apr_array_header_t *options;
5061
5062 SVN_ERR(svn_client_conflict_tree_get_resolution_options(
5063 &options, conflict, ctx, scratch_pool, scratch_pool));
5064 for (i = 0; i < (sizeof(recommended) / sizeof(recommended[0])); i++)
5065 {
5066 svn_client_conflict_option_id_t option_id = recommended[i];
5067
5068 if (svn_client_conflict_option_find_by_id(options, option_id))
5069 {
5070 conflict->recommended_option_id = option_id;
5071 break;
5072 }
5073 }
5074 }
5075 }
5076
5077 return SVN_NO_ERROR;
5078 }
5079
5080 /* Implements tree_conflict_get_details_func_t.
5081 * Find the revision in which the victim was deleted in the repository. */
5082 static svn_error_t *
conflict_tree_get_details_incoming_delete(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)5083 conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict,
5084 svn_client_ctx_t *ctx,
5085 apr_pool_t *scratch_pool)
5086 {
5087 const char *old_repos_relpath;
5088 const char *new_repos_relpath;
5089 const char *repos_root_url;
5090 svn_revnum_t old_rev;
5091 svn_revnum_t new_rev;
5092 svn_node_kind_t old_kind;
5093 svn_node_kind_t new_kind;
5094 struct conflict_tree_incoming_delete_details *details;
5095 svn_wc_operation_t operation;
5096
5097 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5098 &old_repos_relpath, &old_rev, &old_kind, conflict, scratch_pool,
5099 scratch_pool));
5100 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5101 &new_repos_relpath, &new_rev, &new_kind, conflict, scratch_pool,
5102 scratch_pool));
5103 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5104 conflict,
5105 scratch_pool, scratch_pool));
5106 operation = svn_client_conflict_get_operation(conflict);
5107
5108 if (operation == svn_wc_operation_update)
5109 {
5110 if (old_rev < new_rev)
5111 {
5112 const char *parent_repos_relpath;
5113 svn_revnum_t parent_peg_rev;
5114 svn_revnum_t deleted_rev;
5115 svn_revnum_t end_rev;
5116 const char *deleted_rev_author;
5117 svn_node_kind_t replacing_node_kind;
5118 apr_array_header_t *moves;
5119 const char *related_repos_relpath;
5120 svn_revnum_t related_peg_rev;
5121
5122 /* The update operation went forward in history. */
5123 SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev,
5124 &parent_repos_relpath,
5125 NULL, NULL,
5126 ctx->wc_ctx,
5127 svn_dirent_dirname(
5128 conflict->local_abspath,
5129 scratch_pool),
5130 scratch_pool,
5131 scratch_pool));
5132 if (new_kind == svn_node_none)
5133 {
5134 SVN_ERR(find_related_node(&related_repos_relpath,
5135 &related_peg_rev,
5136 new_repos_relpath, new_rev,
5137 old_repos_relpath, old_rev,
5138 conflict, ctx,
5139 scratch_pool, scratch_pool));
5140 }
5141 else
5142 {
5143 /* related to self */
5144 related_repos_relpath = NULL;
5145 related_peg_rev = SVN_INVALID_REVNUM;
5146 }
5147
5148 end_rev = (new_kind == svn_node_none ? 0 : old_rev);
5149 if (end_rev >= parent_peg_rev)
5150 end_rev = (parent_peg_rev > 0 ? parent_peg_rev - 1 : 0);
5151
5152 SVN_ERR(find_revision_for_suspected_deletion(
5153 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
5154 &moves, conflict,
5155 svn_dirent_basename(conflict->local_abspath, scratch_pool),
5156 parent_repos_relpath, parent_peg_rev, end_rev,
5157 related_repos_relpath, related_peg_rev,
5158 ctx, conflict->pool, scratch_pool));
5159 if (deleted_rev == SVN_INVALID_REVNUM)
5160 {
5161 /* We could not determine the revision in which the node was
5162 * deleted. We cannot provide the required details so the best
5163 * we can do is fall back to the default description. */
5164 return SVN_NO_ERROR;
5165 }
5166
5167 details = apr_pcalloc(conflict->pool, sizeof(*details));
5168 details->deleted_rev = deleted_rev;
5169 details->added_rev = SVN_INVALID_REVNUM;
5170 details->repos_relpath = apr_pstrdup(conflict->pool,
5171 new_repos_relpath);
5172 details->rev_author = deleted_rev_author;
5173 details->replacing_node_kind = replacing_node_kind;
5174 details->moves = moves;
5175 }
5176 else /* new_rev < old_rev */
5177 {
5178 /* The update operation went backwards in history.
5179 * Figure out when this node was added. */
5180 SVN_ERR(get_incoming_delete_details_for_reverse_addition(
5181 &details, repos_root_url, old_repos_relpath,
5182 old_rev, new_rev, ctx,
5183 svn_client_conflict_get_local_abspath(conflict),
5184 conflict->pool, scratch_pool));
5185 }
5186 }
5187 else if (operation == svn_wc_operation_switch ||
5188 operation == svn_wc_operation_merge)
5189 {
5190 if (old_rev < new_rev)
5191 {
5192 svn_revnum_t deleted_rev;
5193 const char *deleted_rev_author;
5194 svn_node_kind_t replacing_node_kind;
5195 apr_array_header_t *moves;
5196
5197 /* The switch/merge operation went forward in history.
5198 *
5199 * The deletion of the node happened on the branch we switched to
5200 * or merged from. Scan new_repos_relpath's parent's log to find
5201 * the revision which deleted the node. */
5202 SVN_ERR(find_revision_for_suspected_deletion(
5203 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
5204 &moves, conflict,
5205 svn_relpath_basename(new_repos_relpath, scratch_pool),
5206 svn_relpath_dirname(new_repos_relpath, scratch_pool),
5207 new_rev, old_rev, old_repos_relpath, old_rev, ctx,
5208 conflict->pool, scratch_pool));
5209 if (deleted_rev == SVN_INVALID_REVNUM)
5210 {
5211 /* We could not determine the revision in which the node was
5212 * deleted. We cannot provide the required details so the best
5213 * we can do is fall back to the default description. */
5214 return SVN_NO_ERROR;
5215 }
5216
5217 details = apr_pcalloc(conflict->pool, sizeof(*details));
5218 details->deleted_rev = deleted_rev;
5219 details->added_rev = SVN_INVALID_REVNUM;
5220 details->repos_relpath = apr_pstrdup(conflict->pool,
5221 new_repos_relpath);
5222 details->rev_author = apr_pstrdup(conflict->pool,
5223 deleted_rev_author);
5224 details->replacing_node_kind = replacing_node_kind;
5225 details->moves = moves;
5226 }
5227 else /* new_rev < old_rev */
5228 {
5229 /* The switch/merge operation went backwards in history.
5230 * Figure out when the node we switched away from, or merged
5231 * from another branch, was added. */
5232 SVN_ERR(get_incoming_delete_details_for_reverse_addition(
5233 &details, repos_root_url, old_repos_relpath,
5234 old_rev, new_rev, ctx,
5235 svn_client_conflict_get_local_abspath(conflict),
5236 conflict->pool, scratch_pool));
5237 }
5238 }
5239 else
5240 {
5241 details = NULL;
5242 }
5243
5244 conflict->tree_conflict_incoming_details = details;
5245
5246 if (details && details->moves)
5247 SVN_ERR(init_wc_move_targets(details, conflict, ctx, scratch_pool));
5248
5249 return SVN_NO_ERROR;
5250 }
5251
5252 /* Details for tree conflicts involving incoming additions. */
5253 struct conflict_tree_incoming_add_details
5254 {
5255 /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. */
5256 svn_revnum_t added_rev;
5257
5258 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV.
5259 * Note that both ADDED_REV and DELETED_REV may be valid for update/switch.
5260 * See comment in conflict_tree_get_details_incoming_add() for details. */
5261 svn_revnum_t deleted_rev;
5262
5263 /* The path which was added/deleted relative to the repository root. */
5264 const char *repos_relpath;
5265
5266 /* Authors who committed ADDED_REV/DELETED_REV. */
5267 const char *added_rev_author;
5268 const char *deleted_rev_author;
5269
5270 /* Move information. If not NULL, this is an array of repos_move_info *
5271 * elements. Each element is the head of a move chain which starts in
5272 * ADDED_REV or in DELETED_REV (in which case moves should be interpreted
5273 * in reverse). */
5274 apr_array_header_t *moves;
5275 };
5276
5277 /* Implements tree_conflict_get_details_func_t.
5278 * Find the revision in which the victim was added in the repository. */
5279 static svn_error_t *
conflict_tree_get_details_incoming_add(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)5280 conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
5281 svn_client_ctx_t *ctx,
5282 apr_pool_t *scratch_pool)
5283 {
5284 const char *old_repos_relpath;
5285 const char *new_repos_relpath;
5286 const char *repos_root_url;
5287 svn_revnum_t old_rev;
5288 svn_revnum_t new_rev;
5289 struct conflict_tree_incoming_add_details *details = NULL;
5290 svn_wc_operation_t operation;
5291
5292 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5293 &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
5294 scratch_pool));
5295 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5296 &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
5297 scratch_pool));
5298 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5299 conflict,
5300 scratch_pool, scratch_pool));
5301 operation = svn_client_conflict_get_operation(conflict);
5302
5303 if (operation == svn_wc_operation_update ||
5304 operation == svn_wc_operation_switch)
5305 {
5306 /* Only the new repository location is recorded for the node which
5307 * caused an incoming addition. There is no pre-update/pre-switch
5308 * revision to be recorded for the node since it does not exist in
5309 * the repository at that revision.
5310 * The implication is that we cannot know whether the operation went
5311 * forward or backwards in history. So always try to find an added
5312 * and a deleted revision for the node. Users must figure out by whether
5313 * the addition or deletion caused the conflict. */
5314 const char *url;
5315 const char *corrected_url;
5316 svn_string_t *author_revprop;
5317 struct find_added_rev_baton b = { 0 };
5318 svn_ra_session_t *ra_session;
5319 svn_revnum_t deleted_rev;
5320 svn_revnum_t head_rev;
5321
5322 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5323 scratch_pool);
5324 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5325 &corrected_url,
5326 url, NULL, NULL,
5327 FALSE,
5328 FALSE,
5329 ctx,
5330 scratch_pool,
5331 scratch_pool));
5332
5333 details = apr_pcalloc(conflict->pool, sizeof(*details));
5334 b.ctx = ctx,
5335 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict),
5336 b.added_rev = SVN_INVALID_REVNUM;
5337 b.repos_relpath = NULL;
5338 b.parent_repos_relpath = NULL;
5339 b.pool = scratch_pool;
5340
5341 /* Figure out when this node was added. */
5342 SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
5343 new_rev, SVN_INVALID_REVNUM,
5344 find_added_rev, &b,
5345 scratch_pool));
5346
5347 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
5348 SVN_PROP_REVISION_AUTHOR,
5349 &author_revprop, scratch_pool));
5350 details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath);
5351 details->added_rev = b.added_rev;
5352 if (author_revprop)
5353 details->added_rev_author = apr_pstrdup(conflict->pool,
5354 author_revprop->data);
5355 else
5356 details->added_rev_author = _("unknown author");
5357 details->deleted_rev = SVN_INVALID_REVNUM;
5358 details->deleted_rev_author = NULL;
5359
5360 /* Figure out whether this node was deleted later.
5361 * ### Could probably optimize by infering both addition and deletion
5362 * ### from svn_ra_get_location_segments() call above. */
5363 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &head_rev, scratch_pool));
5364 if (new_rev < head_rev)
5365 {
5366 SVN_ERR(svn_ra_get_deleted_rev(ra_session, "", new_rev, head_rev,
5367 &deleted_rev, scratch_pool));
5368 if (SVN_IS_VALID_REVNUM(deleted_rev))
5369 {
5370 SVN_ERR(svn_ra_rev_prop(ra_session, deleted_rev,
5371 SVN_PROP_REVISION_AUTHOR,
5372 &author_revprop, scratch_pool));
5373 details->deleted_rev = deleted_rev;
5374 if (author_revprop)
5375 details->deleted_rev_author = apr_pstrdup(conflict->pool,
5376 author_revprop->data);
5377 else
5378 details->deleted_rev_author = _("unknown author");
5379 }
5380 }
5381 }
5382 else if (operation == svn_wc_operation_merge &&
5383 strcmp(old_repos_relpath, new_repos_relpath) == 0)
5384 {
5385 if (old_rev < new_rev)
5386 {
5387 /* The merge operation went forwards in history.
5388 * The addition of the node happened on the branch we merged form.
5389 * Scan the nodes's history to find the revision which added it. */
5390 const char *url;
5391 const char *corrected_url;
5392 svn_string_t *author_revprop;
5393 struct find_added_rev_baton b = { 0 };
5394 svn_ra_session_t *ra_session;
5395
5396 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5397 scratch_pool);
5398 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5399 &corrected_url,
5400 url, NULL, NULL,
5401 FALSE,
5402 FALSE,
5403 ctx,
5404 scratch_pool,
5405 scratch_pool));
5406
5407 details = apr_pcalloc(conflict->pool, sizeof(*details));
5408 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
5409 b.ctx = ctx;
5410 b.added_rev = SVN_INVALID_REVNUM;
5411 b.repos_relpath = NULL;
5412 b.parent_repos_relpath = NULL;
5413 b.pool = scratch_pool;
5414
5415 /* Figure out when this node was added. */
5416 SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
5417 new_rev, old_rev,
5418 find_added_rev, &b,
5419 scratch_pool));
5420
5421 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
5422 SVN_PROP_REVISION_AUTHOR,
5423 &author_revprop, scratch_pool));
5424 details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath);
5425 details->added_rev = b.added_rev;
5426 if (author_revprop)
5427 details->added_rev_author = apr_pstrdup(conflict->pool,
5428 author_revprop->data);
5429 else
5430 details->added_rev_author = _("unknown author");
5431 details->deleted_rev = SVN_INVALID_REVNUM;
5432 details->deleted_rev_author = NULL;
5433 }
5434 else if (old_rev > new_rev)
5435 {
5436 /* The merge operation was a reverse-merge.
5437 * This addition is in fact a deletion, applied in reverse,
5438 * which happened on the branch we merged from.
5439 * Find the revision which deleted the node. */
5440 svn_revnum_t deleted_rev;
5441 const char *deleted_rev_author;
5442 svn_node_kind_t replacing_node_kind;
5443 apr_array_header_t *moves;
5444
5445 SVN_ERR(find_revision_for_suspected_deletion(
5446 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
5447 &moves, conflict,
5448 svn_relpath_basename(old_repos_relpath, scratch_pool),
5449 svn_relpath_dirname(old_repos_relpath, scratch_pool),
5450 old_rev, new_rev,
5451 NULL, SVN_INVALID_REVNUM, /* related to self */
5452 ctx,
5453 conflict->pool, scratch_pool));
5454 if (deleted_rev == SVN_INVALID_REVNUM)
5455 {
5456 /* We could not determine the revision in which the node was
5457 * deleted. We cannot provide the required details so the best
5458 * we can do is fall back to the default description. */
5459 return SVN_NO_ERROR;
5460 }
5461
5462 details = apr_pcalloc(conflict->pool, sizeof(*details));
5463 details->repos_relpath = apr_pstrdup(conflict->pool,
5464 new_repos_relpath);
5465 details->deleted_rev = deleted_rev;
5466 details->deleted_rev_author = apr_pstrdup(conflict->pool,
5467 deleted_rev_author);
5468
5469 details->added_rev = SVN_INVALID_REVNUM;
5470 details->added_rev_author = NULL;
5471 details->moves = moves;
5472 }
5473 }
5474
5475 conflict->tree_conflict_incoming_details = details;
5476
5477 return SVN_NO_ERROR;
5478 }
5479
5480 static const char *
describe_incoming_add_upon_update(struct conflict_tree_incoming_add_details * details,svn_node_kind_t new_node_kind,svn_revnum_t new_rev,apr_pool_t * result_pool)5481 describe_incoming_add_upon_update(
5482 struct conflict_tree_incoming_add_details *details,
5483 svn_node_kind_t new_node_kind,
5484 svn_revnum_t new_rev,
5485 apr_pool_t *result_pool)
5486 {
5487 if (new_node_kind == svn_node_dir)
5488 {
5489 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5490 SVN_IS_VALID_REVNUM(details->deleted_rev))
5491 return apr_psprintf(result_pool,
5492 _("A new directory appeared during update to r%ld; "
5493 "it was added by %s in r%ld and later deleted "
5494 "by %s in r%ld."), new_rev,
5495 details->added_rev_author, details->added_rev,
5496 details->deleted_rev_author, details->deleted_rev);
5497 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5498 return apr_psprintf(result_pool,
5499 _("A new directory appeared during update to r%ld; "
5500 "it was added by %s in r%ld."), new_rev,
5501 details->added_rev_author, details->added_rev);
5502 else
5503 return apr_psprintf(result_pool,
5504 _("A new directory appeared during update to r%ld; "
5505 "it was deleted by %s in r%ld."), new_rev,
5506 details->deleted_rev_author, details->deleted_rev);
5507 }
5508 else if (new_node_kind == svn_node_file ||
5509 new_node_kind == svn_node_symlink)
5510 {
5511 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5512 SVN_IS_VALID_REVNUM(details->deleted_rev))
5513 return apr_psprintf(result_pool,
5514 _("A new file appeared during update to r%ld; "
5515 "it was added by %s in r%ld and later deleted "
5516 "by %s in r%ld."), new_rev,
5517 details->added_rev_author, details->added_rev,
5518 details->deleted_rev_author, details->deleted_rev);
5519 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5520 return apr_psprintf(result_pool,
5521 _("A new file appeared during update to r%ld; "
5522 "it was added by %s in r%ld."), new_rev,
5523 details->added_rev_author, details->added_rev);
5524 else
5525 return apr_psprintf(result_pool,
5526 _("A new file appeared during update to r%ld; "
5527 "it was deleted by %s in r%ld."), new_rev,
5528 details->deleted_rev_author, details->deleted_rev);
5529 }
5530 else
5531 {
5532 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5533 SVN_IS_VALID_REVNUM(details->deleted_rev))
5534 return apr_psprintf(result_pool,
5535 _("A new item appeared during update to r%ld; "
5536 "it was added by %s in r%ld and later deleted "
5537 "by %s in r%ld."), new_rev,
5538 details->added_rev_author, details->added_rev,
5539 details->deleted_rev_author, details->deleted_rev);
5540 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5541 return apr_psprintf(result_pool,
5542 _("A new item appeared during update to r%ld; "
5543 "it was added by %s in r%ld."), new_rev,
5544 details->added_rev_author, details->added_rev);
5545 else
5546 return apr_psprintf(result_pool,
5547 _("A new item appeared during update to r%ld; "
5548 "it was deleted by %s in r%ld."), new_rev,
5549 details->deleted_rev_author, details->deleted_rev);
5550 }
5551 }
5552
5553 static const char *
describe_incoming_add_upon_switch(struct conflict_tree_incoming_add_details * details,svn_node_kind_t victim_node_kind,const char * new_repos_relpath,svn_revnum_t new_rev,apr_pool_t * result_pool)5554 describe_incoming_add_upon_switch(
5555 struct conflict_tree_incoming_add_details *details,
5556 svn_node_kind_t victim_node_kind,
5557 const char *new_repos_relpath,
5558 svn_revnum_t new_rev,
5559 apr_pool_t *result_pool)
5560 {
5561 if (victim_node_kind == svn_node_dir)
5562 {
5563 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5564 SVN_IS_VALID_REVNUM(details->deleted_rev))
5565 return apr_psprintf(result_pool,
5566 _("A new directory appeared during switch to\n"
5567 "'^/%s@%ld'.\n"
5568 "It was added by %s in r%ld and later deleted "
5569 "by %s in r%ld."), new_repos_relpath, new_rev,
5570 details->added_rev_author, details->added_rev,
5571 details->deleted_rev_author, details->deleted_rev);
5572 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5573 return apr_psprintf(result_pool,
5574 _("A new directory appeared during switch to\n"
5575 "'^/%s@%ld'.\nIt was added by %s in r%ld."),
5576 new_repos_relpath, new_rev,
5577 details->added_rev_author, details->added_rev);
5578 else
5579 return apr_psprintf(result_pool,
5580 _("A new directory appeared during switch to\n"
5581 "'^/%s@%ld'.\nIt was deleted by %s in r%ld."),
5582 new_repos_relpath, new_rev,
5583 details->deleted_rev_author, details->deleted_rev);
5584 }
5585 else if (victim_node_kind == svn_node_file ||
5586 victim_node_kind == svn_node_symlink)
5587 {
5588 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5589 SVN_IS_VALID_REVNUM(details->deleted_rev))
5590 return apr_psprintf(result_pool,
5591 _("A new file appeared during switch to\n"
5592 "'^/%s@%ld'.\n"
5593 "It was added by %s in r%ld and later deleted "
5594 "by %s in r%ld."), new_repos_relpath, new_rev,
5595 details->added_rev_author, details->added_rev,
5596 details->deleted_rev_author, details->deleted_rev);
5597 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5598 return apr_psprintf(result_pool,
5599 _("A new file appeared during switch to\n"
5600 "'^/%s@%ld'.\n"
5601 "It was added by %s in r%ld."),
5602 new_repos_relpath, new_rev,
5603 details->added_rev_author, details->added_rev);
5604 else
5605 return apr_psprintf(result_pool,
5606 _("A new file appeared during switch to\n"
5607 "'^/%s@%ld'.\n"
5608 "It was deleted by %s in r%ld."),
5609 new_repos_relpath, new_rev,
5610 details->deleted_rev_author, details->deleted_rev);
5611 }
5612 else
5613 {
5614 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5615 SVN_IS_VALID_REVNUM(details->deleted_rev))
5616 return apr_psprintf(result_pool,
5617 _("A new item appeared during switch to\n"
5618 "'^/%s@%ld'.\n"
5619 "It was added by %s in r%ld and later deleted "
5620 "by %s in r%ld."), new_repos_relpath, new_rev,
5621 details->added_rev_author, details->added_rev,
5622 details->deleted_rev_author, details->deleted_rev);
5623 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5624 return apr_psprintf(result_pool,
5625 _("A new item appeared during switch to\n"
5626 "'^/%s@%ld'.\n"
5627 "It was added by %s in r%ld."),
5628 new_repos_relpath, new_rev,
5629 details->added_rev_author, details->added_rev);
5630 else
5631 return apr_psprintf(result_pool,
5632 _("A new item appeared during switch to\n"
5633 "'^/%s@%ld'.\n"
5634 "It was deleted by %s in r%ld."),
5635 new_repos_relpath, new_rev,
5636 details->deleted_rev_author, details->deleted_rev);
5637 }
5638 }
5639
5640 static const char *
describe_incoming_add_upon_merge(struct conflict_tree_incoming_add_details * details,svn_node_kind_t new_node_kind,svn_revnum_t old_rev,const char * new_repos_relpath,svn_revnum_t new_rev,apr_pool_t * result_pool)5641 describe_incoming_add_upon_merge(
5642 struct conflict_tree_incoming_add_details *details,
5643 svn_node_kind_t new_node_kind,
5644 svn_revnum_t old_rev,
5645 const char *new_repos_relpath,
5646 svn_revnum_t new_rev,
5647 apr_pool_t *result_pool)
5648 {
5649 if (new_node_kind == svn_node_dir)
5650 {
5651 if (old_rev + 1 == new_rev)
5652 return apr_psprintf(result_pool,
5653 _("A new directory appeared during merge of\n"
5654 "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5655 new_repos_relpath, new_rev,
5656 details->added_rev_author, details->added_rev);
5657 else
5658 return apr_psprintf(result_pool,
5659 _("A new directory appeared during merge of\n"
5660 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5661 new_repos_relpath, old_rev + 1, new_rev,
5662 details->added_rev_author, details->added_rev);
5663 }
5664 else if (new_node_kind == svn_node_file ||
5665 new_node_kind == svn_node_symlink)
5666 {
5667 if (old_rev + 1 == new_rev)
5668 return apr_psprintf(result_pool,
5669 _("A new file appeared during merge of\n"
5670 "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5671 new_repos_relpath, new_rev,
5672 details->added_rev_author, details->added_rev);
5673 else
5674 return apr_psprintf(result_pool,
5675 _("A new file appeared during merge of\n"
5676 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5677 new_repos_relpath, old_rev + 1, new_rev,
5678 details->added_rev_author, details->added_rev);
5679 }
5680 else
5681 {
5682 if (old_rev + 1 == new_rev)
5683 return apr_psprintf(result_pool,
5684 _("A new item appeared during merge of\n"
5685 "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5686 new_repos_relpath, new_rev,
5687 details->added_rev_author, details->added_rev);
5688 else
5689 return apr_psprintf(result_pool,
5690 _("A new item appeared during merge of\n"
5691 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5692 new_repos_relpath, old_rev + 1, new_rev,
5693 details->added_rev_author, details->added_rev);
5694 }
5695 }
5696
5697 static const char *
describe_incoming_reverse_deletion_upon_merge(struct conflict_tree_incoming_add_details * details,svn_node_kind_t new_node_kind,const char * old_repos_relpath,svn_revnum_t old_rev,svn_revnum_t new_rev,apr_pool_t * result_pool)5698 describe_incoming_reverse_deletion_upon_merge(
5699 struct conflict_tree_incoming_add_details *details,
5700 svn_node_kind_t new_node_kind,
5701 const char *old_repos_relpath,
5702 svn_revnum_t old_rev,
5703 svn_revnum_t new_rev,
5704 apr_pool_t *result_pool)
5705 {
5706 if (new_node_kind == svn_node_dir)
5707 {
5708 if (new_rev + 1 == old_rev)
5709 return apr_psprintf(result_pool,
5710 _("A new directory appeared during reverse-merge of"
5711 "\n'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5712 old_repos_relpath, old_rev,
5713 details->deleted_rev_author,
5714 details->deleted_rev);
5715 else
5716 return apr_psprintf(result_pool,
5717 _("A new directory appeared during reverse-merge "
5718 "of\n'^/%s:%ld-%ld'.\n"
5719 "It was deleted by %s in r%ld."),
5720 old_repos_relpath, new_rev, rev_below(old_rev),
5721 details->deleted_rev_author,
5722 details->deleted_rev);
5723 }
5724 else if (new_node_kind == svn_node_file ||
5725 new_node_kind == svn_node_symlink)
5726 {
5727 if (new_rev + 1 == old_rev)
5728 return apr_psprintf(result_pool,
5729 _("A new file appeared during reverse-merge of\n"
5730 "'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5731 old_repos_relpath, old_rev,
5732 details->deleted_rev_author,
5733 details->deleted_rev);
5734 else
5735 return apr_psprintf(result_pool,
5736 _("A new file appeared during reverse-merge of\n"
5737 "'^/%s:%ld-%ld'.\nIt was deleted by %s in r%ld."),
5738 old_repos_relpath, new_rev + 1, old_rev,
5739 details->deleted_rev_author,
5740 details->deleted_rev);
5741 }
5742 else
5743 {
5744 if (new_rev + 1 == old_rev)
5745 return apr_psprintf(result_pool,
5746 _("A new item appeared during reverse-merge of\n"
5747 "'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5748 old_repos_relpath, old_rev,
5749 details->deleted_rev_author,
5750 details->deleted_rev);
5751 else
5752 return apr_psprintf(result_pool,
5753 _("A new item appeared during reverse-merge of\n"
5754 "'^/%s:%ld-%ld'.\nIt was deleted by %s in r%ld."),
5755 old_repos_relpath, new_rev + 1, old_rev,
5756 details->deleted_rev_author,
5757 details->deleted_rev);
5758 }
5759 }
5760
5761 /* Implements tree_conflict_get_description_func_t. */
5762 static svn_error_t *
conflict_tree_get_description_incoming_add(const char ** incoming_change_description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)5763 conflict_tree_get_description_incoming_add(
5764 const char **incoming_change_description,
5765 svn_client_conflict_t *conflict,
5766 svn_client_ctx_t *ctx,
5767 apr_pool_t *result_pool,
5768 apr_pool_t *scratch_pool)
5769 {
5770 const char *action;
5771 svn_node_kind_t victim_node_kind;
5772 svn_wc_operation_t conflict_operation;
5773 const char *old_repos_relpath;
5774 svn_revnum_t old_rev;
5775 svn_node_kind_t old_node_kind;
5776 const char *new_repos_relpath;
5777 svn_revnum_t new_rev;
5778 svn_node_kind_t new_node_kind;
5779 struct conflict_tree_incoming_add_details *details;
5780
5781 if (conflict->tree_conflict_incoming_details == NULL)
5782 return svn_error_trace(conflict_tree_get_incoming_description_generic(
5783 incoming_change_description, conflict, ctx,
5784 result_pool, scratch_pool));
5785
5786 conflict_operation = svn_client_conflict_get_operation(conflict);
5787 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
5788
5789 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5790 &old_repos_relpath, &old_rev, &old_node_kind, conflict,
5791 scratch_pool, scratch_pool));
5792 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5793 &new_repos_relpath, &new_rev, &new_node_kind, conflict,
5794 scratch_pool, scratch_pool));
5795
5796 details = conflict->tree_conflict_incoming_details;
5797
5798 if (conflict_operation == svn_wc_operation_update)
5799 {
5800 action = describe_incoming_add_upon_update(details,
5801 new_node_kind,
5802 new_rev,
5803 result_pool);
5804 }
5805 else if (conflict_operation == svn_wc_operation_switch)
5806 {
5807 action = describe_incoming_add_upon_switch(details,
5808 victim_node_kind,
5809 new_repos_relpath,
5810 new_rev,
5811 result_pool);
5812 }
5813 else if (conflict_operation == svn_wc_operation_merge)
5814 {
5815 if (old_rev < new_rev)
5816 action = describe_incoming_add_upon_merge(details,
5817 new_node_kind,
5818 old_rev,
5819 new_repos_relpath,
5820 new_rev,
5821 result_pool);
5822 else
5823 action = describe_incoming_reverse_deletion_upon_merge(
5824 details, new_node_kind, old_repos_relpath,
5825 old_rev, new_rev, result_pool);
5826 }
5827
5828 *incoming_change_description = apr_pstrdup(result_pool, action);
5829
5830 return SVN_NO_ERROR;
5831 }
5832
5833 /* Details for tree conflicts involving incoming edits.
5834 * Note that we store an array of these. Each element corresponds to a
5835 * revision within the old/new range in which a modification occured. */
5836 struct conflict_tree_incoming_edit_details
5837 {
5838 /* The revision in which the edit ocurred. */
5839 svn_revnum_t rev;
5840
5841 /* The author of the revision. */
5842 const char *author;
5843
5844 /** Is the text modified? May be svn_tristate_unknown. */
5845 svn_tristate_t text_modified;
5846
5847 /** Are properties modified? May be svn_tristate_unknown. */
5848 svn_tristate_t props_modified;
5849
5850 /** For directories, are children modified?
5851 * May be svn_tristate_unknown. */
5852 svn_tristate_t children_modified;
5853
5854 /* The path which was edited, relative to the repository root. */
5855 const char *repos_relpath;
5856 };
5857
5858 /* Baton for find_modified_rev(). */
5859 struct find_modified_rev_baton {
5860 const char *victim_abspath;
5861 svn_client_ctx_t *ctx;
5862 apr_array_header_t *edits;
5863 const char *repos_relpath;
5864 svn_node_kind_t node_kind;
5865 apr_pool_t *result_pool;
5866 apr_pool_t *scratch_pool;
5867 };
5868
5869 /* Implements svn_log_entry_receiver_t. */
5870 static svn_error_t *
find_modified_rev(void * baton,svn_log_entry_t * log_entry,apr_pool_t * scratch_pool)5871 find_modified_rev(void *baton,
5872 svn_log_entry_t *log_entry,
5873 apr_pool_t *scratch_pool)
5874 {
5875 struct find_modified_rev_baton *b = baton;
5876 struct conflict_tree_incoming_edit_details *details = NULL;
5877 svn_string_t *author;
5878 apr_hash_index_t *hi;
5879 apr_pool_t *iterpool;
5880
5881 if (b->ctx->notify_func2)
5882 {
5883 svn_wc_notify_t *notify;
5884
5885 notify = svn_wc_create_notify(
5886 b->victim_abspath,
5887 svn_wc_notify_tree_conflict_details_progress,
5888 scratch_pool),
5889 notify->revision = log_entry->revision;
5890 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
5891 }
5892
5893 /* No paths were changed in this revision. Nothing to do. */
5894 if (! log_entry->changed_paths2)
5895 return SVN_NO_ERROR;
5896
5897 details = apr_pcalloc(b->result_pool, sizeof(*details));
5898 details->rev = log_entry->revision;
5899 author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
5900 if (author)
5901 details->author = apr_pstrdup(b->result_pool, author->data);
5902 else
5903 details->author = _("unknown author");
5904
5905 details->text_modified = svn_tristate_unknown;
5906 details->props_modified = svn_tristate_unknown;
5907 details->children_modified = svn_tristate_unknown;
5908
5909 iterpool = svn_pool_create(scratch_pool);
5910 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
5911 hi != NULL;
5912 hi = apr_hash_next(hi))
5913 {
5914 void *val;
5915 const char *path;
5916 svn_log_changed_path2_t *log_item;
5917
5918 svn_pool_clear(iterpool);
5919
5920 apr_hash_this(hi, (void *) &path, NULL, &val);
5921 log_item = val;
5922
5923 /* ### Remove leading slash from paths in log entries. */
5924 if (path[0] == '/')
5925 path = svn_relpath_canonicalize(path, iterpool);
5926
5927 if (svn_path_compare_paths(b->repos_relpath, path) == 0 &&
5928 (log_item->action == 'M' || log_item->action == 'A'))
5929 {
5930 details->text_modified = log_item->text_modified;
5931 details->props_modified = log_item->props_modified;
5932 details->repos_relpath = apr_pstrdup(b->result_pool, path);
5933
5934 if (log_item->copyfrom_path)
5935 b->repos_relpath = apr_pstrdup(b->scratch_pool,
5936 /* ### remove leading slash */
5937 svn_relpath_canonicalize(
5938 log_item->copyfrom_path,
5939 iterpool));
5940 }
5941 else if (b->node_kind == svn_node_dir &&
5942 svn_relpath_skip_ancestor(b->repos_relpath, path) != NULL)
5943 details->children_modified = svn_tristate_true;
5944 }
5945
5946 if (b->node_kind == svn_node_dir &&
5947 details->children_modified == svn_tristate_unknown)
5948 details->children_modified = svn_tristate_false;
5949
5950 APR_ARRAY_PUSH(b->edits, struct conflict_tree_incoming_edit_details *) =
5951 details;
5952
5953 svn_pool_destroy(iterpool);
5954
5955 return SVN_NO_ERROR;
5956 }
5957
5958 /* Implements tree_conflict_get_details_func_t.
5959 * Find one or more revisions in which the victim was modified in the
5960 * repository. */
5961 static svn_error_t *
conflict_tree_get_details_incoming_edit(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)5962 conflict_tree_get_details_incoming_edit(svn_client_conflict_t *conflict,
5963 svn_client_ctx_t *ctx,
5964 apr_pool_t *scratch_pool)
5965 {
5966 const char *old_repos_relpath;
5967 const char *new_repos_relpath;
5968 const char *repos_root_url;
5969 svn_revnum_t old_rev;
5970 svn_revnum_t new_rev;
5971 svn_node_kind_t old_node_kind;
5972 svn_node_kind_t new_node_kind;
5973 svn_wc_operation_t operation;
5974 const char *url;
5975 const char *corrected_url;
5976 svn_ra_session_t *ra_session;
5977 apr_array_header_t *paths;
5978 apr_array_header_t *revprops;
5979 struct find_modified_rev_baton b = { 0 };
5980
5981 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5982 &old_repos_relpath, &old_rev, &old_node_kind, conflict,
5983 scratch_pool, scratch_pool));
5984 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5985 &new_repos_relpath, &new_rev, &new_node_kind, conflict,
5986 scratch_pool, scratch_pool));
5987 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5988 conflict,
5989 scratch_pool, scratch_pool));
5990 operation = svn_client_conflict_get_operation(conflict);
5991 if (operation == svn_wc_operation_update)
5992 {
5993 b.node_kind = old_rev < new_rev ? new_node_kind : old_node_kind;
5994
5995 /* If there is no node then we cannot find any edits. */
5996 if (b.node_kind == svn_node_none)
5997 return SVN_NO_ERROR;
5998
5999 url = svn_path_url_add_component2(repos_root_url,
6000 old_rev < new_rev ? new_repos_relpath
6001 : old_repos_relpath,
6002 scratch_pool);
6003
6004 b.repos_relpath = old_rev < new_rev ? new_repos_relpath
6005 : old_repos_relpath;
6006 }
6007 else if (operation == svn_wc_operation_switch ||
6008 operation == svn_wc_operation_merge)
6009 {
6010 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
6011 scratch_pool);
6012
6013 b.repos_relpath = new_repos_relpath;
6014 b.node_kind = new_node_kind;
6015 }
6016
6017 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
6018 &corrected_url,
6019 url, NULL, NULL,
6020 FALSE,
6021 FALSE,
6022 ctx,
6023 scratch_pool,
6024 scratch_pool));
6025
6026 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
6027 APR_ARRAY_PUSH(paths, const char *) = "";
6028
6029 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
6030 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
6031
6032 b.ctx = ctx;
6033 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
6034 b.result_pool = conflict->pool;
6035 b.scratch_pool = scratch_pool;
6036 b.edits = apr_array_make(
6037 conflict->pool, 0,
6038 sizeof(struct conflict_tree_incoming_edit_details *));
6039
6040 SVN_ERR(svn_ra_get_log2(ra_session, paths,
6041 old_rev < new_rev ? old_rev : new_rev,
6042 old_rev < new_rev ? new_rev : old_rev,
6043 0, /* no limit */
6044 TRUE, /* need the changed paths list */
6045 FALSE, /* need to traverse copies */
6046 FALSE, /* no need for merged revisions */
6047 revprops,
6048 find_modified_rev, &b,
6049 scratch_pool));
6050
6051 conflict->tree_conflict_incoming_details = b.edits;
6052
6053 return SVN_NO_ERROR;
6054 }
6055
6056 static const char *
describe_incoming_edit_upon_update(svn_revnum_t old_rev,svn_revnum_t new_rev,svn_node_kind_t old_node_kind,svn_node_kind_t new_node_kind,apr_pool_t * result_pool)6057 describe_incoming_edit_upon_update(svn_revnum_t old_rev,
6058 svn_revnum_t new_rev,
6059 svn_node_kind_t old_node_kind,
6060 svn_node_kind_t new_node_kind,
6061 apr_pool_t *result_pool)
6062 {
6063 if (old_rev < new_rev)
6064 {
6065 if (new_node_kind == svn_node_dir)
6066 return apr_psprintf(result_pool,
6067 _("Changes destined for a directory arrived "
6068 "via the following revisions during update "
6069 "from r%ld to r%ld."), old_rev, new_rev);
6070 else if (new_node_kind == svn_node_file ||
6071 new_node_kind == svn_node_symlink)
6072 return apr_psprintf(result_pool,
6073 _("Changes destined for a file arrived "
6074 "via the following revisions during update "
6075 "from r%ld to r%ld"), old_rev, new_rev);
6076 else
6077 return apr_psprintf(result_pool,
6078 _("Changes from the following revisions arrived "
6079 "during update from r%ld to r%ld"),
6080 old_rev, new_rev);
6081 }
6082 else
6083 {
6084 if (new_node_kind == svn_node_dir)
6085 return apr_psprintf(result_pool,
6086 _("Changes destined for a directory arrived "
6087 "via the following revisions during backwards "
6088 "update from r%ld to r%ld"),
6089 old_rev, new_rev);
6090 else if (new_node_kind == svn_node_file ||
6091 new_node_kind == svn_node_symlink)
6092 return apr_psprintf(result_pool,
6093 _("Changes destined for a file arrived "
6094 "via the following revisions during backwards "
6095 "update from r%ld to r%ld"),
6096 old_rev, new_rev);
6097 else
6098 return apr_psprintf(result_pool,
6099 _("Changes from the following revisions arrived "
6100 "during backwards update from r%ld to r%ld"),
6101 old_rev, new_rev);
6102 }
6103 }
6104
6105 static const char *
describe_incoming_edit_upon_switch(const char * new_repos_relpath,svn_revnum_t new_rev,svn_node_kind_t new_node_kind,apr_pool_t * result_pool)6106 describe_incoming_edit_upon_switch(const char *new_repos_relpath,
6107 svn_revnum_t new_rev,
6108 svn_node_kind_t new_node_kind,
6109 apr_pool_t *result_pool)
6110 {
6111 if (new_node_kind == svn_node_dir)
6112 return apr_psprintf(result_pool,
6113 _("Changes destined for a directory arrived via "
6114 "the following revisions during switch to\n"
6115 "'^/%s@r%ld'"),
6116 new_repos_relpath, new_rev);
6117 else if (new_node_kind == svn_node_file ||
6118 new_node_kind == svn_node_symlink)
6119 return apr_psprintf(result_pool,
6120 _("Changes destined for a directory arrived via "
6121 "the following revisions during switch to\n"
6122 "'^/%s@r%ld'"),
6123 new_repos_relpath, new_rev);
6124 else
6125 return apr_psprintf(result_pool,
6126 _("Changes from the following revisions arrived "
6127 "during switch to\n'^/%s@r%ld'"),
6128 new_repos_relpath, new_rev);
6129 }
6130
6131 /* Return a string showing the list of revisions in EDITS, ensuring
6132 * the string won't grow too large for display. */
6133 static const char *
describe_incoming_edit_list_modified_revs(apr_array_header_t * edits,apr_pool_t * result_pool)6134 describe_incoming_edit_list_modified_revs(apr_array_header_t *edits,
6135 apr_pool_t *result_pool)
6136 {
6137 int num_revs_to_skip;
6138 static const int min_revs_for_skipping = 5;
6139 static const int max_revs_to_display = 8;
6140 const char *s = "";
6141 int i;
6142
6143 if (edits->nelts == 0)
6144 return _(" (no revisions found)");
6145
6146 if (edits->nelts <= max_revs_to_display)
6147 num_revs_to_skip = 0;
6148 else
6149 {
6150 /* Check if we should insert a placeholder for some revisions because
6151 * the string would grow too long for display otherwise. */
6152 num_revs_to_skip = edits->nelts - max_revs_to_display;
6153 if (num_revs_to_skip < min_revs_for_skipping)
6154 {
6155 /* Don't bother with the placeholder. Just list all revisions. */
6156 num_revs_to_skip = 0;
6157 }
6158 }
6159
6160 for (i = 0; i < edits->nelts; i++)
6161 {
6162 struct conflict_tree_incoming_edit_details *details;
6163
6164 details = APR_ARRAY_IDX(edits, i,
6165 struct conflict_tree_incoming_edit_details *);
6166 if (num_revs_to_skip > 0)
6167 {
6168 /* Insert a placeholder for revisions falling into the middle of
6169 * the range so we'll get something that looks like:
6170 * 1, 2, 3, 4, 5 [ placeholder ] 95, 96, 97, 98, 99 */
6171 if (i < max_revs_to_display / 2)
6172 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
6173 details->rev, details->author,
6174 i < edits->nelts - 1 ? "," : "");
6175 else if (i >= max_revs_to_display / 2 &&
6176 i < edits->nelts - (max_revs_to_display / 2))
6177 continue;
6178 else
6179 {
6180 if (i == edits->nelts - (max_revs_to_display / 2))
6181 s = apr_psprintf(result_pool,
6182 Q_("%s\n [%d revision omitted for "
6183 "brevity],\n",
6184 "%s\n [%d revisions omitted for "
6185 "brevity],\n",
6186 num_revs_to_skip),
6187 s, num_revs_to_skip);
6188
6189 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
6190 details->rev, details->author,
6191 i < edits->nelts - 1 ? "," : "");
6192 }
6193 }
6194 else
6195 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
6196 details->rev, details->author,
6197 i < edits->nelts - 1 ? "," : "");
6198 }
6199
6200 return s;
6201 }
6202
6203 /* Implements tree_conflict_get_description_func_t. */
6204 static svn_error_t *
conflict_tree_get_description_incoming_edit(const char ** incoming_change_description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)6205 conflict_tree_get_description_incoming_edit(
6206 const char **incoming_change_description,
6207 svn_client_conflict_t *conflict,
6208 svn_client_ctx_t *ctx,
6209 apr_pool_t *result_pool,
6210 apr_pool_t *scratch_pool)
6211 {
6212 const char *action;
6213 svn_wc_operation_t conflict_operation;
6214 const char *old_repos_relpath;
6215 svn_revnum_t old_rev;
6216 svn_node_kind_t old_node_kind;
6217 const char *new_repos_relpath;
6218 svn_revnum_t new_rev;
6219 svn_node_kind_t new_node_kind;
6220 apr_array_header_t *edits;
6221
6222 if (conflict->tree_conflict_incoming_details == NULL)
6223 return svn_error_trace(conflict_tree_get_incoming_description_generic(
6224 incoming_change_description, conflict, ctx,
6225 result_pool, scratch_pool));
6226
6227 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
6228 &old_repos_relpath, &old_rev, &old_node_kind, conflict,
6229 scratch_pool, scratch_pool));
6230 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
6231 &new_repos_relpath, &new_rev, &new_node_kind, conflict,
6232 scratch_pool, scratch_pool));
6233
6234 conflict_operation = svn_client_conflict_get_operation(conflict);
6235
6236 edits = conflict->tree_conflict_incoming_details;
6237
6238 if (conflict_operation == svn_wc_operation_update)
6239 action = describe_incoming_edit_upon_update(old_rev, new_rev,
6240 old_node_kind, new_node_kind,
6241 scratch_pool);
6242 else if (conflict_operation == svn_wc_operation_switch)
6243 action = describe_incoming_edit_upon_switch(new_repos_relpath, new_rev,
6244 new_node_kind, scratch_pool);
6245 else if (conflict_operation == svn_wc_operation_merge)
6246 {
6247 /* Handle merge inline because it returns early sometimes. */
6248 if (old_rev < new_rev)
6249 {
6250 if (old_rev + 1 == new_rev)
6251 {
6252 if (new_node_kind == svn_node_dir)
6253 action = apr_psprintf(scratch_pool,
6254 _("Changes destined for a directory "
6255 "arrived during merge of\n"
6256 "'^/%s:%ld'."),
6257 new_repos_relpath, new_rev);
6258 else if (new_node_kind == svn_node_file ||
6259 new_node_kind == svn_node_symlink)
6260 action = apr_psprintf(scratch_pool,
6261 _("Changes destined for a file "
6262 "arrived during merge of\n"
6263 "'^/%s:%ld'."),
6264 new_repos_relpath, new_rev);
6265 else
6266 action = apr_psprintf(scratch_pool,
6267 _("Changes arrived during merge of\n"
6268 "'^/%s:%ld'."),
6269 new_repos_relpath, new_rev);
6270
6271 *incoming_change_description = apr_pstrdup(result_pool, action);
6272
6273 return SVN_NO_ERROR;
6274 }
6275 else
6276 {
6277 if (new_node_kind == svn_node_dir)
6278 action = apr_psprintf(scratch_pool,
6279 _("Changes destined for a directory "
6280 "arrived via the following revisions "
6281 "during merge of\n'^/%s:%ld-%ld'"),
6282 new_repos_relpath, old_rev + 1, new_rev);
6283 else if (new_node_kind == svn_node_file ||
6284 new_node_kind == svn_node_symlink)
6285 action = apr_psprintf(scratch_pool,
6286 _("Changes destined for a file "
6287 "arrived via the following revisions "
6288 "during merge of\n'^/%s:%ld-%ld'"),
6289 new_repos_relpath, old_rev + 1, new_rev);
6290 else
6291 action = apr_psprintf(scratch_pool,
6292 _("Changes from the following revisions "
6293 "arrived during merge of\n"
6294 "'^/%s:%ld-%ld'"),
6295 new_repos_relpath, old_rev + 1, new_rev);
6296 }
6297 }
6298 else
6299 {
6300 if (new_rev + 1 == old_rev)
6301 {
6302 if (new_node_kind == svn_node_dir)
6303 action = apr_psprintf(scratch_pool,
6304 _("Changes destined for a directory "
6305 "arrived during reverse-merge of\n"
6306 "'^/%s:%ld'."),
6307 new_repos_relpath, old_rev);
6308 else if (new_node_kind == svn_node_file ||
6309 new_node_kind == svn_node_symlink)
6310 action = apr_psprintf(scratch_pool,
6311 _("Changes destined for a file "
6312 "arrived during reverse-merge of\n"
6313 "'^/%s:%ld'."),
6314 new_repos_relpath, old_rev);
6315 else
6316 action = apr_psprintf(scratch_pool,
6317 _("Changes arrived during reverse-merge "
6318 "of\n'^/%s:%ld'."),
6319 new_repos_relpath, old_rev);
6320
6321 *incoming_change_description = apr_pstrdup(result_pool, action);
6322
6323 return SVN_NO_ERROR;
6324 }
6325 else
6326 {
6327 if (new_node_kind == svn_node_dir)
6328 action = apr_psprintf(scratch_pool,
6329 _("Changes destined for a directory "
6330 "arrived via the following revisions "
6331 "during reverse-merge of\n"
6332 "'^/%s:%ld-%ld'"),
6333 new_repos_relpath, new_rev + 1, old_rev);
6334 else if (new_node_kind == svn_node_file ||
6335 new_node_kind == svn_node_symlink)
6336 action = apr_psprintf(scratch_pool,
6337 _("Changes destined for a file "
6338 "arrived via the following revisions "
6339 "during reverse-merge of\n"
6340 "'^/%s:%ld-%ld'"),
6341 new_repos_relpath, new_rev + 1, old_rev);
6342
6343 else
6344 action = apr_psprintf(scratch_pool,
6345 _("Changes from the following revisions "
6346 "arrived during reverse-merge of\n"
6347 "'^/%s:%ld-%ld'"),
6348 new_repos_relpath, new_rev + 1, old_rev);
6349 }
6350 }
6351 }
6352
6353 action = apr_psprintf(scratch_pool, "%s:\n%s", action,
6354 describe_incoming_edit_list_modified_revs(
6355 edits, scratch_pool));
6356 *incoming_change_description = apr_pstrdup(result_pool, action);
6357
6358 return SVN_NO_ERROR;
6359 }
6360
6361 svn_error_t *
svn_client_conflict_tree_get_description(const char ** incoming_change_description,const char ** local_change_description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)6362 svn_client_conflict_tree_get_description(
6363 const char **incoming_change_description,
6364 const char **local_change_description,
6365 svn_client_conflict_t *conflict,
6366 svn_client_ctx_t *ctx,
6367 apr_pool_t *result_pool,
6368 apr_pool_t *scratch_pool)
6369 {
6370 SVN_ERR(conflict->tree_conflict_get_incoming_description_func(
6371 incoming_change_description,
6372 conflict, ctx, result_pool, scratch_pool));
6373
6374 SVN_ERR(conflict->tree_conflict_get_local_description_func(
6375 local_change_description,
6376 conflict, ctx, result_pool, scratch_pool));
6377
6378 return SVN_NO_ERROR;
6379 }
6380
6381 void
svn_client_conflict_option_set_merged_propval(svn_client_conflict_option_t * option,const svn_string_t * merged_propval)6382 svn_client_conflict_option_set_merged_propval(
6383 svn_client_conflict_option_t *option,
6384 const svn_string_t *merged_propval)
6385 {
6386 option->type_data.prop.merged_propval = svn_string_dup(merged_propval,
6387 option->pool);
6388 }
6389
6390 /* Implements conflict_option_resolve_func_t. */
6391 static svn_error_t *
resolve_postpone(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6392 resolve_postpone(svn_client_conflict_option_t *option,
6393 svn_client_conflict_t *conflict,
6394 svn_client_ctx_t *ctx,
6395 apr_pool_t *scratch_pool)
6396 {
6397 return SVN_NO_ERROR; /* Nothing to do. */
6398 }
6399
6400 /* Implements conflict_option_resolve_func_t. */
6401 static svn_error_t *
resolve_text_conflict(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6402 resolve_text_conflict(svn_client_conflict_option_t *option,
6403 svn_client_conflict_t *conflict,
6404 svn_client_ctx_t *ctx,
6405 apr_pool_t *scratch_pool)
6406 {
6407 svn_client_conflict_option_id_t option_id;
6408 const char *local_abspath;
6409 const char *lock_abspath;
6410 svn_wc_conflict_choice_t conflict_choice;
6411 svn_error_t *err;
6412
6413 option_id = svn_client_conflict_option_get_id(option);
6414 conflict_choice = conflict_option_id_to_wc_conflict_choice(option_id);
6415 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6416
6417 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6418 local_abspath,
6419 scratch_pool, scratch_pool));
6420 err = svn_wc__conflict_text_mark_resolved(ctx->wc_ctx,
6421 local_abspath,
6422 conflict_choice,
6423 ctx->cancel_func,
6424 ctx->cancel_baton,
6425 ctx->notify_func2,
6426 ctx->notify_baton2,
6427 scratch_pool);
6428 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6429 lock_abspath,
6430 scratch_pool));
6431 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6432 SVN_ERR(err);
6433
6434 conflict->resolution_text = option_id;
6435
6436 return SVN_NO_ERROR;
6437 }
6438
6439 /* Implements conflict_option_resolve_func_t. */
6440 static svn_error_t *
resolve_prop_conflict(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6441 resolve_prop_conflict(svn_client_conflict_option_t *option,
6442 svn_client_conflict_t *conflict,
6443 svn_client_ctx_t *ctx,
6444 apr_pool_t *scratch_pool)
6445 {
6446 svn_client_conflict_option_id_t option_id;
6447 svn_wc_conflict_choice_t conflict_choice;
6448 const char *local_abspath;
6449 const char *lock_abspath;
6450 const char *propname = option->type_data.prop.propname;
6451 svn_error_t *err;
6452 const svn_string_t *merged_value;
6453
6454 option_id = svn_client_conflict_option_get_id(option);
6455 conflict_choice = conflict_option_id_to_wc_conflict_choice(option_id);
6456 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6457
6458 if (option_id == svn_client_conflict_option_merged_text)
6459 merged_value = option->type_data.prop.merged_propval;
6460 else
6461 merged_value = NULL;
6462
6463 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6464 local_abspath,
6465 scratch_pool, scratch_pool));
6466 err = svn_wc__conflict_prop_mark_resolved(ctx->wc_ctx, local_abspath,
6467 propname, conflict_choice,
6468 merged_value,
6469 ctx->notify_func2,
6470 ctx->notify_baton2,
6471 scratch_pool);
6472 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6473 lock_abspath,
6474 scratch_pool));
6475 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6476 SVN_ERR(err);
6477
6478 if (propname[0] == '\0')
6479 {
6480 apr_hash_index_t *hi;
6481
6482 /* All properties have been resolved to the same option. */
6483 for (hi = apr_hash_first(scratch_pool, conflict->prop_conflicts);
6484 hi;
6485 hi = apr_hash_next(hi))
6486 {
6487 const char *this_propname = apr_hash_this_key(hi);
6488
6489 svn_hash_sets(conflict->resolved_props,
6490 apr_pstrdup(apr_hash_pool_get(conflict->resolved_props),
6491 this_propname),
6492 option);
6493 svn_hash_sets(conflict->prop_conflicts, this_propname, NULL);
6494 }
6495
6496 conflict->legacy_prop_conflict_propname = NULL;
6497 }
6498 else
6499 {
6500 svn_hash_sets(conflict->resolved_props,
6501 apr_pstrdup(apr_hash_pool_get(conflict->resolved_props),
6502 propname),
6503 option);
6504 svn_hash_sets(conflict->prop_conflicts, propname, NULL);
6505
6506 if (apr_hash_count(conflict->prop_conflicts) > 0)
6507 conflict->legacy_prop_conflict_propname =
6508 apr_hash_this_key(apr_hash_first(scratch_pool,
6509 conflict->prop_conflicts));
6510 else
6511 conflict->legacy_prop_conflict_propname = NULL;
6512 }
6513
6514 return SVN_NO_ERROR;
6515 }
6516
6517 /* Implements conflict_option_resolve_func_t. */
6518 static svn_error_t *
resolve_accept_current_wc_state(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6519 resolve_accept_current_wc_state(svn_client_conflict_option_t *option,
6520 svn_client_conflict_t *conflict,
6521 svn_client_ctx_t *ctx,
6522 apr_pool_t *scratch_pool)
6523 {
6524 svn_client_conflict_option_id_t option_id;
6525 const char *local_abspath;
6526 const char *lock_abspath;
6527 svn_error_t *err;
6528
6529 option_id = svn_client_conflict_option_get_id(option);
6530 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6531
6532 if (option_id != svn_client_conflict_option_accept_current_wc_state)
6533 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6534 _("Tree conflict on '%s' can only be resolved "
6535 "to the current working copy state"),
6536 svn_dirent_local_style(local_abspath,
6537 scratch_pool));
6538
6539 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6540 local_abspath,
6541 scratch_pool, scratch_pool));
6542
6543 /* Resolve to current working copy state. */
6544 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
6545
6546 /* svn_wc__del_tree_conflict doesn't handle notification for us */
6547 if (ctx->notify_func2)
6548 ctx->notify_func2(ctx->notify_baton2,
6549 svn_wc_create_notify(local_abspath,
6550 svn_wc_notify_resolved_tree,
6551 scratch_pool),
6552 scratch_pool);
6553
6554 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6555 lock_abspath,
6556 scratch_pool));
6557 SVN_ERR(err);
6558
6559 conflict->resolution_tree = option_id;
6560
6561 return SVN_NO_ERROR;
6562 }
6563
6564 /* Implements conflict_option_resolve_func_t. */
6565 static svn_error_t *
resolve_update_break_moved_away(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6566 resolve_update_break_moved_away(svn_client_conflict_option_t *option,
6567 svn_client_conflict_t *conflict,
6568 svn_client_ctx_t *ctx,
6569 apr_pool_t *scratch_pool)
6570 {
6571 const char *local_abspath;
6572 const char *lock_abspath;
6573 svn_error_t *err;
6574
6575 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6576
6577 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6578 local_abspath,
6579 scratch_pool, scratch_pool));
6580 err = svn_wc__conflict_tree_update_break_moved_away(ctx->wc_ctx,
6581 local_abspath,
6582 ctx->cancel_func,
6583 ctx->cancel_baton,
6584 ctx->notify_func2,
6585 ctx->notify_baton2,
6586 scratch_pool);
6587 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6588 lock_abspath,
6589 scratch_pool));
6590 SVN_ERR(err);
6591
6592 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6593
6594 return SVN_NO_ERROR;
6595 }
6596
6597 /* Implements conflict_option_resolve_func_t. */
6598 static svn_error_t *
resolve_update_raise_moved_away(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6599 resolve_update_raise_moved_away(svn_client_conflict_option_t *option,
6600 svn_client_conflict_t *conflict,
6601 svn_client_ctx_t *ctx,
6602 apr_pool_t *scratch_pool)
6603 {
6604 const char *local_abspath;
6605 const char *lock_abspath;
6606 svn_error_t *err;
6607
6608 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6609
6610 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6611 local_abspath,
6612 scratch_pool, scratch_pool));
6613 err = svn_wc__conflict_tree_update_raise_moved_away(ctx->wc_ctx,
6614 local_abspath,
6615 ctx->cancel_func,
6616 ctx->cancel_baton,
6617 ctx->notify_func2,
6618 ctx->notify_baton2,
6619 scratch_pool);
6620 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6621 lock_abspath,
6622 scratch_pool));
6623 SVN_ERR(err);
6624
6625 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6626
6627 return SVN_NO_ERROR;
6628 }
6629
6630 /* Implements conflict_option_resolve_func_t. */
6631 static svn_error_t *
resolve_update_moved_away_node(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6632 resolve_update_moved_away_node(svn_client_conflict_option_t *option,
6633 svn_client_conflict_t *conflict,
6634 svn_client_ctx_t *ctx,
6635 apr_pool_t *scratch_pool)
6636 {
6637 const char *local_abspath;
6638 const char *lock_abspath;
6639 svn_error_t *err;
6640
6641 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6642
6643 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6644 local_abspath,
6645 scratch_pool, scratch_pool));
6646 err = svn_wc__conflict_tree_update_moved_away_node(ctx->wc_ctx,
6647 local_abspath,
6648 ctx->cancel_func,
6649 ctx->cancel_baton,
6650 ctx->notify_func2,
6651 ctx->notify_baton2,
6652 scratch_pool);
6653 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6654 lock_abspath,
6655 scratch_pool));
6656 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6657 SVN_ERR(err);
6658
6659 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6660
6661 return SVN_NO_ERROR;
6662 }
6663
6664 /* Verify the local working copy state matches what we expect when an
6665 * incoming add vs add tree conflict exists after an update operation.
6666 * We assume the update operation leaves the working copy in a state which
6667 * prefers the local change and cancels the incoming addition.
6668 * Run a quick sanity check and error out if it looks as if the
6669 * working copy was modified since, even though it's not easy to make
6670 * such modifications without also clearing the conflict marker. */
6671 static svn_error_t *
verify_local_state_for_incoming_add_upon_update(svn_client_conflict_t * conflict,svn_client_conflict_option_t * option,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6672 verify_local_state_for_incoming_add_upon_update(
6673 svn_client_conflict_t *conflict,
6674 svn_client_conflict_option_t *option,
6675 svn_client_ctx_t *ctx,
6676 apr_pool_t *scratch_pool)
6677 {
6678 const char *local_abspath;
6679 svn_client_conflict_option_id_t option_id;
6680 const char *wcroot_abspath;
6681 svn_wc_operation_t operation;
6682 const char *incoming_new_repos_relpath;
6683 svn_revnum_t incoming_new_pegrev;
6684 svn_node_kind_t incoming_new_kind;
6685 const char *base_repos_relpath;
6686 svn_revnum_t base_rev;
6687 svn_node_kind_t base_kind;
6688 const char *local_style_relpath;
6689 svn_boolean_t is_added;
6690 svn_error_t *err;
6691
6692 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6693 option_id = svn_client_conflict_option_get_id(option);
6694 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
6695 local_abspath, scratch_pool,
6696 scratch_pool));
6697 operation = svn_client_conflict_get_operation(conflict);
6698 SVN_ERR_ASSERT(operation == svn_wc_operation_update);
6699
6700 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
6701 &incoming_new_repos_relpath, &incoming_new_pegrev,
6702 &incoming_new_kind, conflict, scratch_pool,
6703 scratch_pool));
6704
6705 local_style_relpath = svn_dirent_local_style(
6706 svn_dirent_skip_ancestor(wcroot_abspath,
6707 local_abspath),
6708 scratch_pool);
6709
6710 /* Check if a local addition addition replaces the incoming new node. */
6711 err = svn_wc__node_get_base(&base_kind, &base_rev, &base_repos_relpath,
6712 NULL, NULL, NULL, ctx->wc_ctx, local_abspath,
6713 FALSE, scratch_pool, scratch_pool);
6714 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
6715 {
6716 if (option_id == svn_client_conflict_option_incoming_add_ignore)
6717 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6718 _("Cannot resolve tree conflict on '%s' "
6719 "(expected a base node but found none)"),
6720 local_style_relpath);
6721 else if (option_id ==
6722 svn_client_conflict_option_incoming_added_dir_replace)
6723 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6724 _("Cannot resolve tree conflict on '%s' "
6725 "(expected a base node but found none)"),
6726 local_style_relpath);
6727 else
6728 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6729 _("Unexpected option id '%d'"), option_id);
6730 }
6731 else if (err)
6732 return svn_error_trace(err);
6733
6734 if (base_kind != incoming_new_kind)
6735 {
6736 if (option_id == svn_client_conflict_option_incoming_add_ignore)
6737 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6738 _("Cannot resolve tree conflict on '%s' "
6739 "(expected base node kind '%s', "
6740 "but found '%s')"),
6741 local_style_relpath,
6742 svn_node_kind_to_word(incoming_new_kind),
6743 svn_node_kind_to_word(base_kind));
6744 else if (option_id ==
6745 svn_client_conflict_option_incoming_added_dir_replace)
6746 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6747 _("Cannot resolve tree conflict on '%s' "
6748 "(expected base node kind '%s', "
6749 "but found '%s')"),
6750 local_style_relpath,
6751 svn_node_kind_to_word(incoming_new_kind),
6752 svn_node_kind_to_word(base_kind));
6753 else
6754 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6755 _("Unexpected option id '%d'"), option_id);
6756 }
6757
6758 if (strcmp(base_repos_relpath, incoming_new_repos_relpath) != 0 ||
6759 base_rev != incoming_new_pegrev)
6760 {
6761 if (option_id == svn_client_conflict_option_incoming_add_ignore)
6762 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6763 _("Cannot resolve tree conflict on '%s' "
6764 "(expected base node from '^/%s@%ld', "
6765 "but found '^/%s@%ld')"),
6766 local_style_relpath,
6767 incoming_new_repos_relpath,
6768 incoming_new_pegrev,
6769 base_repos_relpath, base_rev);
6770 else if (option_id ==
6771 svn_client_conflict_option_incoming_added_dir_replace)
6772 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6773 _("Cannot resolve tree conflict on '%s' "
6774 "(expected base node from '^/%s@%ld', "
6775 "but found '^/%s@%ld')"),
6776 local_style_relpath,
6777 incoming_new_repos_relpath,
6778 incoming_new_pegrev,
6779 base_repos_relpath, base_rev);
6780 else
6781 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6782 _("Unexpected option id '%d'"), option_id);
6783 }
6784
6785 SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, local_abspath,
6786 scratch_pool));
6787 if (!is_added)
6788 {
6789 if (option_id == svn_client_conflict_option_incoming_add_ignore)
6790 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6791 _("Cannot resolve tree conflict on '%s' "
6792 "(expected an added item, but the item "
6793 "is not added)"),
6794 local_style_relpath);
6795
6796 else if (option_id ==
6797 svn_client_conflict_option_incoming_added_dir_replace)
6798 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6799 _("Cannot resolve tree conflict on '%s' "
6800 "(expected an added item, but the item "
6801 "is not added)"),
6802 local_style_relpath);
6803 else
6804 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6805 _("Unexpected option id '%d'"), option_id);
6806 }
6807
6808 return SVN_NO_ERROR;
6809 }
6810
6811
6812 /* Implements conflict_option_resolve_func_t. */
6813 static svn_error_t *
resolve_incoming_add_ignore(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6814 resolve_incoming_add_ignore(svn_client_conflict_option_t *option,
6815 svn_client_conflict_t *conflict,
6816 svn_client_ctx_t *ctx,
6817 apr_pool_t *scratch_pool)
6818 {
6819 const char *local_abspath;
6820 const char *lock_abspath;
6821 svn_wc_operation_t operation;
6822 svn_error_t *err;
6823
6824 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6825 operation = svn_client_conflict_get_operation(conflict);
6826
6827 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6828 local_abspath,
6829 scratch_pool, scratch_pool));
6830
6831 if (operation == svn_wc_operation_update)
6832 {
6833 err = verify_local_state_for_incoming_add_upon_update(conflict, option,
6834 ctx, scratch_pool);
6835 if (err)
6836 goto unlock_wc;
6837 }
6838
6839 /* All other options for this conflict actively fetch the incoming
6840 * new node. We can ignore the incoming new node by doing nothing. */
6841
6842 /* Resolve to current working copy state. */
6843 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
6844
6845 /* svn_wc__del_tree_conflict doesn't handle notification for us */
6846 if (ctx->notify_func2)
6847 ctx->notify_func2(ctx->notify_baton2,
6848 svn_wc_create_notify(local_abspath,
6849 svn_wc_notify_resolved_tree,
6850 scratch_pool),
6851 scratch_pool);
6852
6853 unlock_wc:
6854 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6855 lock_abspath,
6856 scratch_pool));
6857 SVN_ERR(err);
6858
6859 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6860
6861 return SVN_NO_ERROR;
6862 }
6863
6864 /* Delete entry and wc props from a set of properties. */
6865 static void
filter_props(apr_hash_t * props,apr_pool_t * scratch_pool)6866 filter_props(apr_hash_t *props, apr_pool_t *scratch_pool)
6867 {
6868 apr_hash_index_t *hi;
6869
6870 for (hi = apr_hash_first(scratch_pool, props);
6871 hi != NULL;
6872 hi = apr_hash_next(hi))
6873 {
6874 const char *propname = apr_hash_this_key(hi);
6875
6876 if (!svn_wc_is_normal_prop(propname))
6877 svn_hash_sets(props, propname, NULL);
6878 }
6879 }
6880
6881 /* Implements conflict_option_resolve_func_t. */
6882 static svn_error_t *
resolve_merge_incoming_added_file_text_update(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)6883 resolve_merge_incoming_added_file_text_update(
6884 svn_client_conflict_option_t *option,
6885 svn_client_conflict_t *conflict,
6886 svn_client_ctx_t *ctx,
6887 apr_pool_t *scratch_pool)
6888 {
6889 const char *wc_tmpdir;
6890 const char *local_abspath;
6891 const char *lock_abspath;
6892 svn_wc_merge_outcome_t merge_content_outcome;
6893 svn_wc_notify_state_t merge_props_outcome;
6894 const char *empty_file_abspath;
6895 const char *working_file_tmp_abspath;
6896 svn_stream_t *working_file_stream;
6897 svn_stream_t *working_file_tmp_stream;
6898 apr_hash_t *working_props;
6899 apr_array_header_t *propdiffs;
6900 svn_error_t *err;
6901 svn_wc_conflict_reason_t local_change;
6902
6903 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6904 local_change = svn_client_conflict_get_local_change(conflict);
6905
6906 /* Set up tempory storage for the working version of file. */
6907 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
6908 scratch_pool, scratch_pool));
6909 SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
6910 &working_file_tmp_abspath, wc_tmpdir,
6911 /* Don't delete automatically! */
6912 svn_io_file_del_none,
6913 scratch_pool, scratch_pool));
6914
6915 if (local_change == svn_wc_conflict_reason_unversioned)
6916 {
6917 /* Copy the unversioned file to temporary storage. */
6918 SVN_ERR(svn_stream_open_readonly(&working_file_stream, local_abspath,
6919 scratch_pool, scratch_pool));
6920 /* Unversioned files have no properties. */
6921 working_props = apr_hash_make(scratch_pool);
6922 }
6923 else
6924 {
6925 /* Copy the detranslated working file to temporary storage. */
6926 SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
6927 local_abspath, local_abspath,
6928 SVN_WC_TRANSLATE_TO_NF,
6929 scratch_pool, scratch_pool));
6930 /* Get a copy of the working file's properties. */
6931 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
6932 scratch_pool, scratch_pool));
6933 filter_props(working_props, scratch_pool);
6934 }
6935
6936 SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
6937 ctx->cancel_func, ctx->cancel_baton,
6938 scratch_pool));
6939
6940 /* Create an empty file as fake "merge-base" for the two added files.
6941 * The files are not ancestrally related so this is the best we can do. */
6942 SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
6943 svn_io_file_del_on_pool_cleanup,
6944 scratch_pool, scratch_pool));
6945
6946 /* Create a property diff which shows all props as added. */
6947 SVN_ERR(svn_prop_diffs(&propdiffs, working_props,
6948 apr_hash_make(scratch_pool), scratch_pool));
6949
6950 /* ### The following WC modifications should be atomic. */
6951 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6952 local_abspath,
6953 scratch_pool, scratch_pool));
6954
6955 /* Revert the path in order to restore the repository's line of
6956 * history, which is part of the BASE tree. This revert operation
6957 * is why are being careful about not losing the temporary copy. */
6958 err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_empty,
6959 FALSE, NULL, TRUE, FALSE,
6960 TRUE /*added_keep_local*/,
6961 NULL, NULL, /* no cancellation */
6962 ctx->notify_func2, ctx->notify_baton2,
6963 scratch_pool);
6964 if (err)
6965 goto unlock_wc;
6966
6967 /* Perform the file merge. ### Merge into tempfile and then rename on top? */
6968 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
6969 ctx->wc_ctx, empty_file_abspath,
6970 working_file_tmp_abspath, local_abspath,
6971 NULL, NULL, NULL, /* labels */
6972 NULL, NULL, /* conflict versions */
6973 FALSE, /* dry run */
6974 NULL, NULL, /* diff3_cmd, merge_options */
6975 NULL, propdiffs,
6976 NULL, NULL, /* conflict func/baton */
6977 NULL, NULL, /* don't allow user to cancel here */
6978 scratch_pool);
6979
6980 unlock_wc:
6981 if (err)
6982 err = svn_error_quick_wrapf(
6983 err, _("If needed, a backup copy of '%s' can be found at '%s'"),
6984 svn_dirent_local_style(local_abspath, scratch_pool),
6985 svn_dirent_local_style(working_file_tmp_abspath, scratch_pool));
6986 err = svn_error_compose_create(err,
6987 svn_wc__release_write_lock(ctx->wc_ctx,
6988 lock_abspath,
6989 scratch_pool));
6990 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6991 SVN_ERR(err);
6992
6993 if (ctx->notify_func2)
6994 {
6995 svn_wc_notify_t *notify;
6996
6997 /* Tell the world about the file merge that just happened. */
6998 notify = svn_wc_create_notify(local_abspath,
6999 svn_wc_notify_update_update,
7000 scratch_pool);
7001 if (merge_content_outcome == svn_wc_merge_conflict)
7002 notify->content_state = svn_wc_notify_state_conflicted;
7003 else
7004 notify->content_state = svn_wc_notify_state_merged;
7005 notify->prop_state = merge_props_outcome;
7006 notify->kind = svn_node_file;
7007 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7008
7009 /* And also about the successfully resolved tree conflict. */
7010 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
7011 scratch_pool);
7012 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7013 }
7014
7015 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7016
7017 /* All is good -- remove temporary copy of the working file. */
7018 SVN_ERR(svn_io_remove_file2(working_file_tmp_abspath, TRUE, scratch_pool));
7019
7020 return SVN_NO_ERROR;
7021 }
7022
7023 /* Implements conflict_option_resolve_func_t. */
7024 static svn_error_t *
resolve_merge_incoming_added_file_text_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)7025 resolve_merge_incoming_added_file_text_merge(
7026 svn_client_conflict_option_t *option,
7027 svn_client_conflict_t *conflict,
7028 svn_client_ctx_t *ctx,
7029 apr_pool_t *scratch_pool)
7030 {
7031 svn_ra_session_t *ra_session;
7032 const char *url;
7033 const char *corrected_url;
7034 const char *repos_root_url;
7035 const char *wc_tmpdir;
7036 const char *incoming_new_repos_relpath;
7037 svn_revnum_t incoming_new_pegrev;
7038 const char *local_abspath;
7039 const char *lock_abspath;
7040 svn_wc_merge_outcome_t merge_content_outcome;
7041 svn_wc_notify_state_t merge_props_outcome;
7042 apr_file_t *incoming_new_file;
7043 const char *incoming_new_tmp_abspath;
7044 const char *empty_file_abspath;
7045 svn_stream_t *incoming_new_stream;
7046 apr_hash_t *incoming_new_props;
7047 apr_array_header_t *propdiffs;
7048 svn_error_t *err;
7049
7050 local_abspath = svn_client_conflict_get_local_abspath(conflict);
7051
7052 /* Set up temporary storage for the repository version of file. */
7053 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
7054 scratch_pool, scratch_pool));
7055 SVN_ERR(svn_io_open_unique_file3(&incoming_new_file,
7056 &incoming_new_tmp_abspath, wc_tmpdir,
7057 svn_io_file_del_on_pool_cleanup,
7058 scratch_pool, scratch_pool));
7059 incoming_new_stream = svn_stream_from_aprfile2(incoming_new_file, TRUE,
7060 scratch_pool);
7061
7062 /* Fetch the incoming added file from the repository. */
7063 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
7064 &incoming_new_repos_relpath, &incoming_new_pegrev,
7065 NULL, conflict, scratch_pool,
7066 scratch_pool));
7067 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7068 conflict, scratch_pool,
7069 scratch_pool));
7070 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
7071 scratch_pool);
7072 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7073 url, NULL, NULL, FALSE, FALSE,
7074 ctx, scratch_pool,
7075 scratch_pool));
7076 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev,
7077 incoming_new_stream, NULL, /* fetched_rev */
7078 &incoming_new_props, scratch_pool));
7079
7080 /* Flush file to disk. */
7081 SVN_ERR(svn_stream_close(incoming_new_stream));
7082 SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool));
7083
7084 filter_props(incoming_new_props, scratch_pool);
7085
7086 /* Create an empty file as fake "merge-base" for the two added files.
7087 * The files are not ancestrally related so this is the best we can do. */
7088 SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
7089 svn_io_file_del_on_pool_cleanup,
7090 scratch_pool, scratch_pool));
7091
7092 /* Create a property diff which shows all props as added. */
7093 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_new_props,
7094 apr_hash_make(scratch_pool), scratch_pool));
7095
7096 /* ### The following WC modifications should be atomic. */
7097 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7098 local_abspath,
7099 scratch_pool, scratch_pool));
7100 /* Resolve to current working copy state. svn_wc_merge5() requires this. */
7101 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7102 if (err)
7103 return svn_error_compose_create(err,
7104 svn_wc__release_write_lock(ctx->wc_ctx,
7105 lock_abspath,
7106 scratch_pool));
7107 /* Perform the file merge. ### Merge into tempfile and then rename on top? */
7108 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
7109 ctx->wc_ctx, empty_file_abspath,
7110 incoming_new_tmp_abspath, local_abspath,
7111 NULL, NULL, NULL, /* labels */
7112 NULL, NULL, /* conflict versions */
7113 FALSE, /* dry run */
7114 NULL, NULL, /* diff3_cmd, merge_options */
7115 NULL, propdiffs,
7116 NULL, NULL, /* conflict func/baton */
7117 NULL, NULL, /* don't allow user to cancel here */
7118 scratch_pool);
7119 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7120 lock_abspath,
7121 scratch_pool));
7122 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7123 SVN_ERR(err);
7124
7125 if (ctx->notify_func2)
7126 {
7127 svn_wc_notify_t *notify;
7128
7129 /* Tell the world about the file merge that just happened. */
7130 notify = svn_wc_create_notify(local_abspath,
7131 svn_wc_notify_update_update,
7132 scratch_pool);
7133 if (merge_content_outcome == svn_wc_merge_conflict)
7134 notify->content_state = svn_wc_notify_state_conflicted;
7135 else
7136 notify->content_state = svn_wc_notify_state_merged;
7137 notify->prop_state = merge_props_outcome;
7138 notify->kind = svn_node_file;
7139 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7140
7141 /* And also about the successfully resolved tree conflict. */
7142 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
7143 scratch_pool);
7144 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7145 }
7146
7147 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7148
7149 return SVN_NO_ERROR;
7150 }
7151
7152 /* Implements conflict_option_resolve_func_t. */
7153 static svn_error_t *
resolve_merge_incoming_added_file_replace_and_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)7154 resolve_merge_incoming_added_file_replace_and_merge(
7155 svn_client_conflict_option_t *option,
7156 svn_client_conflict_t *conflict,
7157 svn_client_ctx_t *ctx,
7158 apr_pool_t *scratch_pool)
7159 {
7160 svn_ra_session_t *ra_session;
7161 const char *url;
7162 const char *corrected_url;
7163 const char *repos_root_url;
7164 const char *incoming_new_repos_relpath;
7165 svn_revnum_t incoming_new_pegrev;
7166 apr_file_t *incoming_new_file;
7167 svn_stream_t *incoming_new_stream;
7168 apr_hash_t *incoming_new_props;
7169 const char *local_abspath;
7170 const char *lock_abspath;
7171 const char *wc_tmpdir;
7172 svn_stream_t *working_file_tmp_stream;
7173 const char *working_file_tmp_abspath;
7174 svn_stream_t *working_file_stream;
7175 apr_hash_t *working_props;
7176 svn_error_t *err;
7177 svn_wc_merge_outcome_t merge_content_outcome;
7178 svn_wc_notify_state_t merge_props_outcome;
7179 apr_file_t *empty_file;
7180 const char *empty_file_abspath;
7181 apr_array_header_t *propdiffs;
7182
7183 local_abspath = svn_client_conflict_get_local_abspath(conflict);
7184
7185 /* Set up tempory storage for the working version of file. */
7186 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
7187 scratch_pool, scratch_pool));
7188 SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
7189 &working_file_tmp_abspath, wc_tmpdir,
7190 svn_io_file_del_on_pool_cleanup,
7191 scratch_pool, scratch_pool));
7192
7193 /* Copy the detranslated working file to temporary storage. */
7194 SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
7195 local_abspath, local_abspath,
7196 SVN_WC_TRANSLATE_TO_NF,
7197 scratch_pool, scratch_pool));
7198 SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
7199 ctx->cancel_func, ctx->cancel_baton,
7200 scratch_pool));
7201
7202 /* Get a copy of the working file's properties. */
7203 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
7204 scratch_pool, scratch_pool));
7205
7206 /* Fetch the incoming added file from the repository. */
7207 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
7208 &incoming_new_repos_relpath, &incoming_new_pegrev,
7209 NULL, conflict, scratch_pool,
7210 scratch_pool));
7211 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7212 conflict, scratch_pool,
7213 scratch_pool));
7214 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
7215 scratch_pool);
7216 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7217 url, NULL, NULL, FALSE, FALSE,
7218 ctx, scratch_pool,
7219 scratch_pool));
7220 if (corrected_url)
7221 url = corrected_url;
7222 SVN_ERR(svn_io_open_unique_file3(&incoming_new_file, NULL, wc_tmpdir,
7223 svn_io_file_del_on_pool_cleanup,
7224 scratch_pool, scratch_pool));
7225 incoming_new_stream = svn_stream_from_aprfile2(incoming_new_file, TRUE,
7226 scratch_pool);
7227 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev,
7228 incoming_new_stream, NULL, /* fetched_rev */
7229 &incoming_new_props, scratch_pool));
7230 /* Flush file to disk. */
7231 SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool));
7232
7233 /* Reset the stream in preparation for adding its content to WC. */
7234 SVN_ERR(svn_stream_reset(incoming_new_stream));
7235
7236 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7237 local_abspath,
7238 scratch_pool, scratch_pool));
7239
7240 /* ### The following WC modifications should be atomic. */
7241
7242 /* Replace the working file with the file from the repository. */
7243 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
7244 NULL, NULL, /* don't allow user to cancel here */
7245 ctx->notify_func2, ctx->notify_baton2,
7246 scratch_pool);
7247 if (err)
7248 goto unlock_wc;
7249 err = svn_wc_add_repos_file4(ctx->wc_ctx, local_abspath,
7250 incoming_new_stream,
7251 NULL, /* ### could we merge first, then set
7252 ### the merged content here? */
7253 incoming_new_props,
7254 NULL, /* ### merge props first, set here? */
7255 url, incoming_new_pegrev,
7256 NULL, NULL, /* don't allow user to cancel here */
7257 scratch_pool);
7258 if (err)
7259 goto unlock_wc;
7260
7261 if (ctx->notify_func2)
7262 {
7263 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
7264 svn_wc_notify_add,
7265 scratch_pool);
7266 notify->kind = svn_node_file;
7267 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7268 }
7269
7270 /* Resolve to current working copy state. svn_wc_merge5() requires this. */
7271 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7272 if (err)
7273 goto unlock_wc;
7274
7275 /* Create an empty file as fake "merge-base" for the two added files.
7276 * The files are not ancestrally related so this is the best we can do. */
7277 err = svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
7278 svn_io_file_del_on_pool_cleanup,
7279 scratch_pool, scratch_pool);
7280 if (err)
7281 goto unlock_wc;
7282
7283 filter_props(incoming_new_props, scratch_pool);
7284
7285 /* Create a property diff for the files. */
7286 err = svn_prop_diffs(&propdiffs, incoming_new_props,
7287 working_props, scratch_pool);
7288 if (err)
7289 goto unlock_wc;
7290
7291 /* Perform the file merge. */
7292 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
7293 ctx->wc_ctx, empty_file_abspath,
7294 working_file_tmp_abspath, local_abspath,
7295 NULL, NULL, NULL, /* labels */
7296 NULL, NULL, /* conflict versions */
7297 FALSE, /* dry run */
7298 NULL, NULL, /* diff3_cmd, merge_options */
7299 NULL, propdiffs,
7300 NULL, NULL, /* conflict func/baton */
7301 NULL, NULL, /* don't allow user to cancel here */
7302 scratch_pool);
7303 if (err)
7304 goto unlock_wc;
7305
7306 if (ctx->notify_func2)
7307 {
7308 svn_wc_notify_t *notify = svn_wc_create_notify(
7309 local_abspath,
7310 svn_wc_notify_update_update,
7311 scratch_pool);
7312
7313 if (merge_content_outcome == svn_wc_merge_conflict)
7314 notify->content_state = svn_wc_notify_state_conflicted;
7315 else
7316 notify->content_state = svn_wc_notify_state_merged;
7317 notify->prop_state = merge_props_outcome;
7318 notify->kind = svn_node_file;
7319 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7320 }
7321
7322 unlock_wc:
7323 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7324 lock_abspath,
7325 scratch_pool));
7326 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7327 SVN_ERR(err);
7328
7329 SVN_ERR(svn_stream_close(incoming_new_stream));
7330
7331 if (ctx->notify_func2)
7332 {
7333 svn_wc_notify_t *notify = svn_wc_create_notify(
7334 local_abspath,
7335 svn_wc_notify_resolved_tree,
7336 scratch_pool);
7337
7338 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7339 }
7340
7341 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7342
7343 return SVN_NO_ERROR;
7344 }
7345
7346 static svn_error_t *
raise_tree_conflict(const char * local_abspath,svn_wc_conflict_action_t incoming_change,svn_wc_conflict_reason_t local_change,svn_node_kind_t local_node_kind,svn_node_kind_t merge_left_kind,svn_node_kind_t merge_right_kind,const char * repos_root_url,const char * repos_uuid,const char * repos_relpath,svn_revnum_t merge_left_rev,svn_revnum_t merge_right_rev,svn_wc_context_t * wc_ctx,svn_wc_notify_func2_t notify_func2,void * notify_baton2,apr_pool_t * scratch_pool)7347 raise_tree_conflict(const char *local_abspath,
7348 svn_wc_conflict_action_t incoming_change,
7349 svn_wc_conflict_reason_t local_change,
7350 svn_node_kind_t local_node_kind,
7351 svn_node_kind_t merge_left_kind,
7352 svn_node_kind_t merge_right_kind,
7353 const char *repos_root_url,
7354 const char *repos_uuid,
7355 const char *repos_relpath,
7356 svn_revnum_t merge_left_rev,
7357 svn_revnum_t merge_right_rev,
7358 svn_wc_context_t *wc_ctx,
7359 svn_wc_notify_func2_t notify_func2,
7360 void *notify_baton2,
7361 apr_pool_t *scratch_pool)
7362 {
7363 svn_wc_conflict_description2_t *conflict;
7364 const svn_wc_conflict_version_t *left_version;
7365 const svn_wc_conflict_version_t *right_version;
7366
7367 left_version = svn_wc_conflict_version_create2(repos_root_url,
7368 repos_uuid,
7369 repos_relpath,
7370 merge_left_rev,
7371 merge_left_kind,
7372 scratch_pool);
7373 right_version = svn_wc_conflict_version_create2(repos_root_url,
7374 repos_uuid,
7375 repos_relpath,
7376 merge_right_rev,
7377 merge_right_kind,
7378 scratch_pool);
7379 conflict = svn_wc_conflict_description_create_tree2(local_abspath,
7380 local_node_kind,
7381 svn_wc_operation_merge,
7382 left_version,
7383 right_version,
7384 scratch_pool);
7385 conflict->action = incoming_change;
7386 conflict->reason = local_change;
7387
7388 SVN_ERR(svn_wc__add_tree_conflict(wc_ctx, conflict, scratch_pool));
7389
7390 if (notify_func2)
7391 {
7392 svn_wc_notify_t *notify;
7393
7394 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
7395 scratch_pool);
7396 notify->kind = local_node_kind;
7397 notify_func2(notify_baton2, notify, scratch_pool);
7398 }
7399
7400 return SVN_NO_ERROR;
7401 }
7402
7403 struct merge_newly_added_dir_baton {
7404 const char *target_abspath;
7405 svn_client_ctx_t *ctx;
7406 const char *repos_root_url;
7407 const char *repos_uuid;
7408 const char *added_repos_relpath;
7409 svn_revnum_t merge_left_rev;
7410 svn_revnum_t merge_right_rev;
7411 };
7412
7413 static svn_error_t *
merge_added_dir_props(const char * target_abspath,const char * added_repos_relpath,apr_hash_t * added_props,const char * repos_root_url,const char * repos_uuid,svn_revnum_t merge_left_rev,svn_revnum_t merge_right_rev,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)7414 merge_added_dir_props(const char *target_abspath,
7415 const char *added_repos_relpath,
7416 apr_hash_t *added_props,
7417 const char *repos_root_url,
7418 const char *repos_uuid,
7419 svn_revnum_t merge_left_rev,
7420 svn_revnum_t merge_right_rev,
7421 svn_client_ctx_t *ctx,
7422 apr_pool_t *scratch_pool)
7423 {
7424 svn_wc_notify_state_t property_state;
7425 apr_array_header_t *propchanges;
7426 const svn_wc_conflict_version_t *left_version;
7427 const svn_wc_conflict_version_t *right_version;
7428 apr_hash_index_t *hi;
7429
7430 left_version = svn_wc_conflict_version_create2(
7431 repos_root_url, repos_uuid, added_repos_relpath,
7432 merge_left_rev, svn_node_none, scratch_pool);
7433
7434 right_version = svn_wc_conflict_version_create2(
7435 repos_root_url, repos_uuid, added_repos_relpath,
7436 merge_right_rev, svn_node_dir, scratch_pool);
7437
7438 propchanges = apr_array_make(scratch_pool, apr_hash_count(added_props),
7439 sizeof(svn_prop_t));
7440 for (hi = apr_hash_first(scratch_pool, added_props);
7441 hi;
7442 hi = apr_hash_next(hi))
7443 {
7444 svn_prop_t prop;
7445
7446 prop.name = apr_hash_this_key(hi);
7447 prop.value = apr_hash_this_val(hi);
7448
7449 if (svn_wc_is_normal_prop(prop.name))
7450 APR_ARRAY_PUSH(propchanges, svn_prop_t) = prop;
7451 }
7452
7453 SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx,
7454 target_abspath,
7455 left_version, right_version,
7456 apr_hash_make(scratch_pool),
7457 propchanges,
7458 FALSE, /* not a dry-run */
7459 NULL, NULL, NULL, NULL,
7460 scratch_pool));
7461
7462 if (ctx->notify_func2)
7463 {
7464 svn_wc_notify_t *notify;
7465
7466 notify = svn_wc_create_notify(target_abspath,
7467 svn_wc_notify_update_update,
7468 scratch_pool);
7469 notify->kind = svn_node_dir;
7470 notify->content_state = svn_wc_notify_state_unchanged;;
7471 notify->prop_state = property_state;
7472 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7473 }
7474
7475 return SVN_NO_ERROR;
7476 }
7477
7478 /* An svn_diff_tree_processor_t callback. */
7479 static svn_error_t *
diff_dir_added(const char * relpath,const svn_diff_source_t * copyfrom_source,const svn_diff_source_t * right_source,apr_hash_t * copyfrom_props,apr_hash_t * right_props,void * dir_baton,const struct svn_diff_tree_processor_t * processor,apr_pool_t * scratch_pool)7480 diff_dir_added(const char *relpath,
7481 const svn_diff_source_t *copyfrom_source,
7482 const svn_diff_source_t *right_source,
7483 apr_hash_t *copyfrom_props,
7484 apr_hash_t *right_props,
7485 void *dir_baton,
7486 const struct svn_diff_tree_processor_t *processor,
7487 apr_pool_t *scratch_pool)
7488 {
7489 struct merge_newly_added_dir_baton *b = processor->baton;
7490 const char *local_abspath;
7491 const char *copyfrom_url;
7492 svn_node_kind_t db_kind;
7493 svn_node_kind_t on_disk_kind;
7494 apr_hash_index_t *hi;
7495
7496 /* Handle the root of the added directory tree. */
7497 if (relpath[0] == '\0')
7498 {
7499 /* ### svn_wc_merge_props3() requires this... */
7500 SVN_ERR(svn_wc__del_tree_conflict(b->ctx->wc_ctx, b->target_abspath,
7501 scratch_pool));
7502 SVN_ERR(merge_added_dir_props(b->target_abspath,
7503 b->added_repos_relpath, right_props,
7504 b->repos_root_url, b->repos_uuid,
7505 b->merge_left_rev, b->merge_right_rev,
7506 b->ctx, scratch_pool));
7507 return SVN_NO_ERROR;
7508
7509 }
7510
7511 local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
7512
7513 SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath,
7514 FALSE, FALSE, scratch_pool));
7515 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
7516
7517 if (db_kind == svn_node_dir && on_disk_kind == svn_node_dir)
7518 {
7519 SVN_ERR(merge_added_dir_props(svn_dirent_join(b->target_abspath, relpath,
7520 scratch_pool),
7521 b->added_repos_relpath, right_props,
7522 b->repos_root_url, b->repos_uuid,
7523 b->merge_left_rev, b->merge_right_rev,
7524 b->ctx, scratch_pool));
7525 return SVN_NO_ERROR;
7526 }
7527
7528 if (db_kind != svn_node_none && db_kind != svn_node_unknown)
7529 {
7530 SVN_ERR(raise_tree_conflict(
7531 local_abspath, svn_wc_conflict_action_add,
7532 svn_wc_conflict_reason_obstructed,
7533 db_kind, svn_node_none, svn_node_dir,
7534 b->repos_root_url, b->repos_uuid,
7535 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7536 b->merge_left_rev, b->merge_right_rev,
7537 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7538 scratch_pool));
7539 return SVN_NO_ERROR;
7540 }
7541
7542 if (on_disk_kind != svn_node_none)
7543 {
7544 SVN_ERR(raise_tree_conflict(
7545 local_abspath, svn_wc_conflict_action_add,
7546 svn_wc_conflict_reason_obstructed, db_kind,
7547 svn_node_none, svn_node_dir, b->repos_root_url, b->repos_uuid,
7548 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7549 b->merge_left_rev, b->merge_right_rev,
7550 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7551 scratch_pool));
7552 return SVN_NO_ERROR;
7553 }
7554
7555 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
7556 copyfrom_url = apr_pstrcat(scratch_pool, b->repos_root_url, "/",
7557 right_source->repos_relpath, SVN_VA_NULL);
7558 SVN_ERR(svn_wc_add4(b->ctx->wc_ctx, local_abspath, svn_depth_infinity,
7559 copyfrom_url, right_source->revision,
7560 NULL, NULL, /* cancel func/baton */
7561 b->ctx->notify_func2, b->ctx->notify_baton2,
7562 scratch_pool));
7563
7564 for (hi = apr_hash_first(scratch_pool, right_props);
7565 hi;
7566 hi = apr_hash_next(hi))
7567 {
7568 const char *propname = apr_hash_this_key(hi);
7569 const svn_string_t *propval = apr_hash_this_val(hi);
7570
7571 SVN_ERR(svn_wc_prop_set4(b->ctx->wc_ctx, local_abspath,
7572 propname, propval, svn_depth_empty,
7573 FALSE, NULL /* do not skip checks */,
7574 NULL, NULL, /* cancel func/baton */
7575 b->ctx->notify_func2, b->ctx->notify_baton2,
7576 scratch_pool));
7577 }
7578
7579 return SVN_NO_ERROR;
7580 }
7581
7582 static svn_error_t *
merge_added_files(const char * local_abspath,const char * incoming_added_file_abspath,apr_hash_t * incoming_added_file_props,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)7583 merge_added_files(const char *local_abspath,
7584 const char *incoming_added_file_abspath,
7585 apr_hash_t *incoming_added_file_props,
7586 svn_client_ctx_t *ctx,
7587 apr_pool_t *scratch_pool)
7588 {
7589 svn_wc_merge_outcome_t merge_content_outcome;
7590 svn_wc_notify_state_t merge_props_outcome;
7591 apr_file_t *empty_file;
7592 const char *empty_file_abspath;
7593 apr_array_header_t *propdiffs;
7594 apr_hash_t *working_props;
7595
7596 /* Create an empty file as fake "merge-base" for the two added files.
7597 * The files are not ancestrally related so this is the best we can do. */
7598 SVN_ERR(svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
7599 svn_io_file_del_on_pool_cleanup,
7600 scratch_pool, scratch_pool));
7601
7602 /* Get a copy of the working file's properties. */
7603 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
7604 scratch_pool, scratch_pool));
7605
7606 /* Create a property diff for the files. */
7607 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_added_file_props,
7608 working_props, scratch_pool));
7609
7610 /* Perform the file merge. */
7611 SVN_ERR(svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
7612 ctx->wc_ctx, empty_file_abspath,
7613 incoming_added_file_abspath, local_abspath,
7614 NULL, NULL, NULL, /* labels */
7615 NULL, NULL, /* conflict versions */
7616 FALSE, /* dry run */
7617 NULL, NULL, /* diff3_cmd, merge_options */
7618 NULL, propdiffs,
7619 NULL, NULL, /* conflict func/baton */
7620 NULL, NULL, /* don't allow user to cancel here */
7621 scratch_pool));
7622
7623 if (ctx->notify_func2)
7624 {
7625 svn_wc_notify_t *notify = svn_wc_create_notify(
7626 local_abspath,
7627 svn_wc_notify_update_update,
7628 scratch_pool);
7629
7630 if (merge_content_outcome == svn_wc_merge_conflict)
7631 notify->content_state = svn_wc_notify_state_conflicted;
7632 else
7633 notify->content_state = svn_wc_notify_state_merged;
7634 notify->prop_state = merge_props_outcome;
7635 notify->kind = svn_node_file;
7636 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7637 }
7638
7639 return SVN_NO_ERROR;
7640 }
7641
7642 /* An svn_diff_tree_processor_t callback. */
7643 static svn_error_t *
diff_file_added(const char * relpath,const svn_diff_source_t * copyfrom_source,const svn_diff_source_t * right_source,const char * copyfrom_file,const char * right_file,apr_hash_t * copyfrom_props,apr_hash_t * right_props,void * file_baton,const struct svn_diff_tree_processor_t * processor,apr_pool_t * scratch_pool)7644 diff_file_added(const char *relpath,
7645 const svn_diff_source_t *copyfrom_source,
7646 const svn_diff_source_t *right_source,
7647 const char *copyfrom_file,
7648 const char *right_file,
7649 apr_hash_t *copyfrom_props,
7650 apr_hash_t *right_props,
7651 void *file_baton,
7652 const struct svn_diff_tree_processor_t *processor,
7653 apr_pool_t *scratch_pool)
7654 {
7655 struct merge_newly_added_dir_baton *b = processor->baton;
7656 const char *local_abspath;
7657 svn_node_kind_t db_kind;
7658 svn_node_kind_t on_disk_kind;
7659 apr_array_header_t *propsarray;
7660 apr_array_header_t *regular_props;
7661
7662 local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
7663
7664 SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath,
7665 FALSE, FALSE, scratch_pool));
7666 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
7667
7668 if (db_kind == svn_node_file && on_disk_kind == svn_node_file)
7669 {
7670 propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
7671 SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, ®ular_props,
7672 scratch_pool));
7673 SVN_ERR(merge_added_files(local_abspath, right_file,
7674 svn_prop_array_to_hash(regular_props,
7675 scratch_pool),
7676 b->ctx, scratch_pool));
7677 return SVN_NO_ERROR;
7678 }
7679
7680 if (db_kind != svn_node_none && db_kind != svn_node_unknown)
7681 {
7682 SVN_ERR(raise_tree_conflict(
7683 local_abspath, svn_wc_conflict_action_add,
7684 svn_wc_conflict_reason_obstructed,
7685 db_kind, svn_node_none, svn_node_file,
7686 b->repos_root_url, b->repos_uuid,
7687 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7688 b->merge_left_rev, b->merge_right_rev,
7689 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7690 scratch_pool));
7691 return SVN_NO_ERROR;
7692 }
7693
7694 if (on_disk_kind != svn_node_none)
7695 {
7696 SVN_ERR(raise_tree_conflict(
7697 local_abspath, svn_wc_conflict_action_add,
7698 svn_wc_conflict_reason_obstructed, db_kind,
7699 svn_node_none, svn_node_file, b->repos_root_url, b->repos_uuid,
7700 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7701 b->merge_left_rev, b->merge_right_rev,
7702 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7703 scratch_pool));
7704 return SVN_NO_ERROR;
7705 }
7706
7707 propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
7708 SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, ®ular_props,
7709 scratch_pool));
7710 SVN_ERR(svn_io_copy_file(right_file, local_abspath, FALSE, scratch_pool));
7711 SVN_ERR(svn_wc_add_from_disk3(b->ctx->wc_ctx, local_abspath,
7712 svn_prop_array_to_hash(regular_props,
7713 scratch_pool),
7714 FALSE, b->ctx->notify_func2,
7715 b->ctx->notify_baton2, scratch_pool));
7716
7717 return SVN_NO_ERROR;
7718 }
7719
7720 /* Merge a newly added directory into TARGET_ABSPATH in the working copy.
7721 *
7722 * This uses a diff-tree processor because our standard merge operation
7723 * is not set up for merges where the merge-source anchor is itself an
7724 * added directory (i.e. does not exist on one side of the diff).
7725 * The standard merge will only merge additions of children of a path
7726 * that exists across the entire revision range being merged.
7727 * But in our case, SOURCE1 does not yet exist in REV1, but SOURCE2
7728 * does exist in REV2. Thus we use a diff processor.
7729 */
7730 static svn_error_t *
merge_newly_added_dir(const char * added_repos_relpath,const char * source1,svn_revnum_t rev1,const char * source2,svn_revnum_t rev2,const char * target_abspath,svn_boolean_t reverse_merge,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)7731 merge_newly_added_dir(const char *added_repos_relpath,
7732 const char *source1,
7733 svn_revnum_t rev1,
7734 const char *source2,
7735 svn_revnum_t rev2,
7736 const char *target_abspath,
7737 svn_boolean_t reverse_merge,
7738 svn_client_ctx_t *ctx,
7739 apr_pool_t *result_pool,
7740 apr_pool_t *scratch_pool)
7741 {
7742 svn_diff_tree_processor_t *processor;
7743 struct merge_newly_added_dir_baton baton = { 0 };
7744 const svn_diff_tree_processor_t *diff_processor;
7745 svn_ra_session_t *ra_session;
7746 const char *corrected_url;
7747 svn_ra_session_t *extra_ra_session;
7748 const svn_ra_reporter3_t *reporter;
7749 void *reporter_baton;
7750 const svn_delta_editor_t *diff_editor;
7751 void *diff_edit_baton;
7752 const char *anchor1;
7753 const char *anchor2;
7754 const char *target1;
7755 const char *target2;
7756
7757 svn_uri_split(&anchor1, &target1, source1, scratch_pool);
7758 svn_uri_split(&anchor2, &target2, source2, scratch_pool);
7759
7760 baton.target_abspath = target_abspath;
7761 baton.ctx = ctx;
7762 baton.added_repos_relpath = added_repos_relpath;
7763 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
7764 &baton.repos_root_url, &baton.repos_uuid,
7765 ctx->wc_ctx, target_abspath,
7766 scratch_pool, scratch_pool));
7767 baton.merge_left_rev = rev1;
7768 baton.merge_right_rev = rev2;
7769
7770 processor = svn_diff__tree_processor_create(&baton, scratch_pool);
7771 processor->dir_added = diff_dir_added;
7772 processor->file_added = diff_file_added;
7773
7774 diff_processor = processor;
7775 if (reverse_merge)
7776 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
7777 scratch_pool);
7778
7779 /* Filter the first path component using a filter processor, until we fixed
7780 the diff processing to handle this directly */
7781 diff_processor = svn_diff__tree_processor_filter_create(
7782 diff_processor, target1, scratch_pool);
7783
7784 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7785 anchor2, NULL, NULL, FALSE,
7786 FALSE, ctx,
7787 scratch_pool, scratch_pool));
7788 if (corrected_url)
7789 anchor2 = corrected_url;
7790
7791 /* Extra RA session is used during the editor calls to fetch file contents. */
7792 SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor2,
7793 scratch_pool, scratch_pool));
7794
7795 /* Create a repos-repos diff editor. */
7796 SVN_ERR(svn_client__get_diff_editor2(
7797 &diff_editor, &diff_edit_baton,
7798 extra_ra_session, svn_depth_infinity, rev1, TRUE,
7799 diff_processor, ctx->cancel_func, ctx->cancel_baton,
7800 scratch_pool));
7801
7802 /* We want to switch our txn into URL2 */
7803 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
7804 rev2, target1, svn_depth_infinity, TRUE, TRUE,
7805 source2, diff_editor, diff_edit_baton, scratch_pool));
7806
7807 /* Drive the reporter; do the diff. */
7808 SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
7809 svn_depth_infinity,
7810 FALSE, NULL,
7811 scratch_pool));
7812
7813 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
7814
7815 return SVN_NO_ERROR;
7816 }
7817
7818 /* Implements conflict_option_resolve_func_t. */
7819 static svn_error_t *
resolve_merge_incoming_added_dir_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)7820 resolve_merge_incoming_added_dir_merge(svn_client_conflict_option_t *option,
7821 svn_client_conflict_t *conflict,
7822 svn_client_ctx_t *ctx,
7823 apr_pool_t *scratch_pool)
7824 {
7825 const char *repos_root_url;
7826 const char *incoming_old_repos_relpath;
7827 svn_revnum_t incoming_old_pegrev;
7828 const char *incoming_new_repos_relpath;
7829 svn_revnum_t incoming_new_pegrev;
7830 const char *local_abspath;
7831 const char *lock_abspath;
7832 struct conflict_tree_incoming_add_details *details;
7833 const char *added_repos_relpath;
7834 const char *source1;
7835 svn_revnum_t rev1;
7836 const char *source2;
7837 svn_revnum_t rev2;
7838 svn_error_t *err;
7839
7840 local_abspath = svn_client_conflict_get_local_abspath(conflict);
7841
7842 details = conflict->tree_conflict_incoming_details;
7843 if (details == NULL)
7844 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7845 _("Conflict resolution option '%d' requires "
7846 "details for tree conflict at '%s' to be "
7847 "fetched from the repository"),
7848 option->id,
7849 svn_dirent_local_style(local_abspath,
7850 scratch_pool));
7851
7852 /* Set up merge sources to merge the entire incoming added directory tree. */
7853 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7854 conflict, scratch_pool,
7855 scratch_pool));
7856 source1 = svn_path_url_add_component2(repos_root_url,
7857 details->repos_relpath,
7858 scratch_pool);
7859 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
7860 &incoming_old_repos_relpath, &incoming_old_pegrev,
7861 NULL, conflict, scratch_pool, scratch_pool));
7862 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
7863 &incoming_new_repos_relpath, &incoming_new_pegrev,
7864 NULL, conflict, scratch_pool, scratch_pool));
7865 if (incoming_old_pegrev < incoming_new_pegrev) /* forward merge */
7866 {
7867 if (details->added_rev == SVN_INVALID_REVNUM)
7868 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7869 _("Could not determine when '%s' was "
7870 "added the repository"),
7871 svn_dirent_local_style(local_abspath,
7872 scratch_pool));
7873 rev1 = rev_below(details->added_rev);
7874 source2 = svn_path_url_add_component2(repos_root_url,
7875 incoming_new_repos_relpath,
7876 scratch_pool);
7877 rev2 = incoming_new_pegrev;
7878 added_repos_relpath = incoming_new_repos_relpath;
7879 }
7880 else /* reverse-merge */
7881 {
7882 if (details->deleted_rev == SVN_INVALID_REVNUM)
7883 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7884 _("Could not determine when '%s' was "
7885 "deleted from the repository"),
7886 svn_dirent_local_style(local_abspath,
7887 scratch_pool));
7888 rev1 = details->deleted_rev;
7889 source2 = svn_path_url_add_component2(repos_root_url,
7890 incoming_old_repos_relpath,
7891 scratch_pool);
7892 rev2 = incoming_old_pegrev;
7893 added_repos_relpath = incoming_new_repos_relpath;
7894 }
7895
7896 /* ### The following WC modifications should be atomic. */
7897 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7898 local_abspath,
7899 scratch_pool, scratch_pool));
7900
7901 /* ### wrap in a transaction */
7902 err = merge_newly_added_dir(added_repos_relpath,
7903 source1, rev1, source2, rev2,
7904 local_abspath,
7905 (incoming_old_pegrev > incoming_new_pegrev),
7906 ctx, scratch_pool, scratch_pool);
7907 if (!err)
7908 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7909
7910 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7911 lock_abspath,
7912 scratch_pool));
7913 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7914 SVN_ERR(err);
7915
7916 if (ctx->notify_func2)
7917 ctx->notify_func2(ctx->notify_baton2,
7918 svn_wc_create_notify(local_abspath,
7919 svn_wc_notify_resolved_tree,
7920 scratch_pool),
7921 scratch_pool);
7922
7923 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7924
7925 return SVN_NO_ERROR;
7926 }
7927
7928 /* Implements conflict_option_resolve_func_t. */
7929 static svn_error_t *
resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)7930 resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option,
7931 svn_client_conflict_t *conflict,
7932 svn_client_ctx_t *ctx,
7933 apr_pool_t *scratch_pool)
7934 {
7935 const char *local_abspath;
7936 const char *lock_abspath;
7937 svn_error_t *err;
7938 svn_wc_conflict_reason_t local_change;
7939
7940 local_abspath = svn_client_conflict_get_local_abspath(conflict);
7941 local_change = svn_client_conflict_get_local_change(conflict);
7942
7943 if (local_change == svn_wc_conflict_reason_unversioned)
7944 {
7945 char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
7946 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
7947 &lock_abspath, ctx->wc_ctx, parent_abspath,
7948 scratch_pool, scratch_pool));
7949
7950 /* The update/switch operation has added the incoming versioned
7951 * directory as a deleted op-depth layer. We can revert this layer
7952 * to make the incoming tree appear in the working copy.
7953 * This meta-data-only revert operation effecively merges the
7954 * versioned and unversioned trees but leaves all unversioned files as
7955 * they were. This is the best we can do; 3-way merging of unversioned
7956 * files with files from the repository is impossible because there is
7957 * no known merge base. No unversioned data will be lost, and any
7958 * differences to files in the repository will show up in 'svn diff'. */
7959 err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_infinity,
7960 FALSE, NULL, TRUE, TRUE /* metadata_only */,
7961 TRUE /*added_keep_local*/,
7962 NULL, NULL, /* no cancellation */
7963 ctx->notify_func2, ctx->notify_baton2,
7964 scratch_pool);
7965 }
7966 else
7967 {
7968 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
7969 &lock_abspath, ctx->wc_ctx, local_abspath,
7970 scratch_pool, scratch_pool));
7971 err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx,
7972 local_abspath,
7973 ctx->cancel_func,
7974 ctx->cancel_baton,
7975 ctx->notify_func2,
7976 ctx->notify_baton2,
7977 scratch_pool);
7978 }
7979
7980 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7981 lock_abspath,
7982 scratch_pool));
7983 SVN_ERR(err);
7984
7985 return SVN_NO_ERROR;
7986 }
7987
7988 /* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by
7989 * replacing the local directory with the incoming directory.
7990 * If MERGE_DIRS is set, also merge the directories after replacing. */
7991 static svn_error_t *
merge_incoming_added_dir_replace(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,svn_boolean_t merge_dirs,apr_pool_t * scratch_pool)7992 merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
7993 svn_client_conflict_t *conflict,
7994 svn_client_ctx_t *ctx,
7995 svn_boolean_t merge_dirs,
7996 apr_pool_t *scratch_pool)
7997 {
7998 svn_ra_session_t *ra_session;
7999 const char *url;
8000 const char *corrected_url;
8001 const char *repos_root_url;
8002 const char *incoming_new_repos_relpath;
8003 svn_revnum_t incoming_new_pegrev;
8004 const char *local_abspath;
8005 const char *lock_abspath;
8006 svn_error_t *err;
8007 svn_boolean_t timestamp_sleep;
8008
8009 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8010
8011 /* Find the URL of the incoming added directory in the repository. */
8012 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8013 &incoming_new_repos_relpath, &incoming_new_pegrev,
8014 NULL, conflict, scratch_pool,
8015 scratch_pool));
8016 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8017 conflict, scratch_pool,
8018 scratch_pool));
8019 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
8020 scratch_pool);
8021 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8022 url, NULL, NULL, FALSE, FALSE,
8023 ctx, scratch_pool,
8024 scratch_pool));
8025 if (corrected_url)
8026 url = corrected_url;
8027
8028 /* ### The following WC modifications should be atomic. */
8029
8030 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8031 svn_dirent_dirname(
8032 local_abspath,
8033 scratch_pool),
8034 scratch_pool, scratch_pool));
8035
8036 /* Remove the working directory. */
8037 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
8038 NULL, NULL, /* don't allow user to cancel here */
8039 ctx->notify_func2, ctx->notify_baton2,
8040 scratch_pool);
8041 if (err)
8042 goto unlock_wc;
8043
8044 err = svn_client__repos_to_wc_copy_by_editor(×tamp_sleep,
8045 svn_node_dir,
8046 url, incoming_new_pegrev,
8047 local_abspath,
8048 ra_session, ctx, scratch_pool);
8049 if (err)
8050 goto unlock_wc;
8051
8052 if (ctx->notify_func2)
8053 {
8054 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
8055 svn_wc_notify_add,
8056 scratch_pool);
8057 notify->kind = svn_node_dir;
8058 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8059 }
8060
8061 /* Resolve to current working copy state.
8062 * svn_client__merge_locked() requires this. */
8063 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
8064 if (err)
8065 goto unlock_wc;
8066
8067 if (merge_dirs)
8068 {
8069 svn_revnum_t base_revision;
8070 const char *base_repos_relpath;
8071 struct find_added_rev_baton b = { 0 };
8072
8073 /* Find the URL and revision of the directory we have just replaced. */
8074 err = svn_wc__node_get_base(NULL, &base_revision, &base_repos_relpath,
8075 NULL, NULL, NULL, ctx->wc_ctx, local_abspath,
8076 FALSE, scratch_pool, scratch_pool);
8077 if (err)
8078 goto unlock_wc;
8079
8080 url = svn_path_url_add_component2(repos_root_url, base_repos_relpath,
8081 scratch_pool);
8082
8083 /* Trace the replaced directory's history to its origin. */
8084 err = svn_ra_reparent(ra_session, url, scratch_pool);
8085 if (err)
8086 goto unlock_wc;
8087 b.victim_abspath = local_abspath;
8088 b.ctx = ctx;
8089 b.added_rev = SVN_INVALID_REVNUM;
8090 b.repos_relpath = NULL;
8091 b.parent_repos_relpath = svn_relpath_dirname(base_repos_relpath,
8092 scratch_pool);
8093 b.pool = scratch_pool;
8094
8095 err = svn_ra_get_location_segments(ra_session, "", base_revision,
8096 base_revision, SVN_INVALID_REVNUM,
8097 find_added_rev, &b,
8098 scratch_pool);
8099 if (err)
8100 goto unlock_wc;
8101
8102 if (b.added_rev == SVN_INVALID_REVNUM)
8103 {
8104 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8105 _("Could not determine the revision in "
8106 "which '^/%s' was added to the "
8107 "repository.\n"),
8108 base_repos_relpath);
8109 goto unlock_wc;
8110 }
8111
8112 /* Merge the replaced directory into the directory which replaced it.
8113 * We do not need to consider a reverse-merge here since the source of
8114 * this merge was part of the merge target working copy, not a branch
8115 * in the repository. */
8116 err = merge_newly_added_dir(base_repos_relpath,
8117 url, rev_below(b.added_rev), url,
8118 base_revision, local_abspath, FALSE,
8119 ctx, scratch_pool, scratch_pool);
8120 if (err)
8121 goto unlock_wc;
8122 }
8123
8124 unlock_wc:
8125 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8126 lock_abspath,
8127 scratch_pool));
8128 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
8129 SVN_ERR(err);
8130
8131 if (ctx->notify_func2)
8132 {
8133 svn_wc_notify_t *notify = svn_wc_create_notify(
8134 local_abspath,
8135 svn_wc_notify_resolved_tree,
8136 scratch_pool);
8137
8138 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8139 }
8140
8141 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
8142
8143 return SVN_NO_ERROR;
8144 }
8145
8146 /* Implements conflict_option_resolve_func_t. */
8147 static svn_error_t *
resolve_merge_incoming_added_dir_replace(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)8148 resolve_merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
8149 svn_client_conflict_t *conflict,
8150 svn_client_ctx_t *ctx,
8151 apr_pool_t *scratch_pool)
8152 {
8153 return svn_error_trace(merge_incoming_added_dir_replace(option,
8154 conflict,
8155 ctx,
8156 FALSE,
8157 scratch_pool));
8158 }
8159
8160 /* Implements conflict_option_resolve_func_t. */
8161 static svn_error_t *
resolve_merge_incoming_added_dir_replace_and_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)8162 resolve_merge_incoming_added_dir_replace_and_merge(
8163 svn_client_conflict_option_t *option,
8164 svn_client_conflict_t *conflict,
8165 svn_client_ctx_t *ctx,
8166 apr_pool_t *scratch_pool)
8167 {
8168 return svn_error_trace(merge_incoming_added_dir_replace(option,
8169 conflict,
8170 ctx,
8171 TRUE,
8172 scratch_pool));
8173 }
8174
8175 /* Ensure the conflict victim is a copy of itself from before it was deleted.
8176 * Update and switch are supposed to set this up when flagging the conflict. */
8177 static svn_error_t *
ensure_local_edit_vs_incoming_deletion_copied_state(struct conflict_tree_incoming_delete_details * details,svn_wc_operation_t operation,const char * wcroot_abspath,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)8178 ensure_local_edit_vs_incoming_deletion_copied_state(
8179 struct conflict_tree_incoming_delete_details *details,
8180 svn_wc_operation_t operation,
8181 const char *wcroot_abspath,
8182 svn_client_conflict_t *conflict,
8183 svn_client_ctx_t *ctx,
8184 apr_pool_t *scratch_pool)
8185 {
8186
8187 svn_boolean_t is_copy;
8188 svn_revnum_t copyfrom_rev;
8189 const char *copyfrom_repos_relpath;
8190
8191 SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
8192 operation == svn_wc_operation_switch);
8193
8194 SVN_ERR(svn_wc__node_get_origin(&is_copy, ©from_rev,
8195 ©from_repos_relpath,
8196 NULL, NULL, NULL, NULL,
8197 ctx->wc_ctx, conflict->local_abspath,
8198 FALSE, scratch_pool, scratch_pool));
8199 if (!is_copy)
8200 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8201 _("Cannot resolve tree conflict on '%s' "
8202 "(expected a copied item, but the item "
8203 "is not a copy)"),
8204 svn_dirent_local_style(
8205 svn_dirent_skip_ancestor(
8206 wcroot_abspath,
8207 conflict->local_abspath),
8208 scratch_pool));
8209 else if (details->deleted_rev != SVN_INVALID_REVNUM &&
8210 copyfrom_rev >= details->deleted_rev)
8211 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8212 _("Cannot resolve tree conflict on '%s' "
8213 "(expected an item copied from a revision "
8214 "smaller than r%ld, but the item was "
8215 "copied from r%ld)"),
8216 svn_dirent_local_style(
8217 svn_dirent_skip_ancestor(
8218 wcroot_abspath, conflict->local_abspath),
8219 scratch_pool),
8220 details->deleted_rev, copyfrom_rev);
8221 else if (details->added_rev != SVN_INVALID_REVNUM &&
8222 copyfrom_rev < details->added_rev)
8223 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8224 _("Cannot resolve tree conflict on '%s' "
8225 "(expected an item copied from a revision "
8226 "larger than r%ld, but the item was "
8227 "copied from r%ld)"),
8228 svn_dirent_local_style(
8229 svn_dirent_skip_ancestor(
8230 wcroot_abspath, conflict->local_abspath),
8231 scratch_pool),
8232 details->added_rev, copyfrom_rev);
8233 else if (operation == svn_wc_operation_update)
8234 {
8235 const char *old_repos_relpath;
8236
8237 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8238 &old_repos_relpath, NULL, NULL, conflict,
8239 scratch_pool, scratch_pool));
8240 if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 &&
8241 strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
8242 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8243 _("Cannot resolve tree conflict on '%s' "
8244 "(expected an item copied from '^/%s' "
8245 "or from '^/%s' but the item was "
8246 "copied from '^/%s@%ld')"),
8247 svn_dirent_local_style(
8248 svn_dirent_skip_ancestor(
8249 wcroot_abspath, conflict->local_abspath),
8250 scratch_pool),
8251 details->repos_relpath,
8252 old_repos_relpath,
8253 copyfrom_repos_relpath, copyfrom_rev);
8254 }
8255 else if (operation == svn_wc_operation_switch)
8256 {
8257 const char *old_repos_relpath;
8258
8259 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8260 &old_repos_relpath, NULL, NULL, conflict,
8261 scratch_pool, scratch_pool));
8262
8263 if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
8264 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8265 _("Cannot resolve tree conflict on '%s' "
8266 "(expected an item copied from '^/%s', "
8267 "but the item was copied from "
8268 "'^/%s@%ld')"),
8269 svn_dirent_local_style(
8270 svn_dirent_skip_ancestor(
8271 wcroot_abspath,
8272 conflict->local_abspath),
8273 scratch_pool),
8274 old_repos_relpath,
8275 copyfrom_repos_relpath, copyfrom_rev);
8276 }
8277
8278 return SVN_NO_ERROR;
8279 }
8280
8281 /* Verify the local working copy state matches what we expect when an
8282 * incoming deletion tree conflict exists.
8283 * We assume update/merge/switch operations leave the working copy in a
8284 * state which prefers the local change and cancels the deletion.
8285 * Run a quick sanity check and error out if it looks as if the
8286 * working copy was modified since, even though it's not easy to make
8287 * such modifications without also clearing the conflict marker. */
8288 static svn_error_t *
verify_local_state_for_incoming_delete(svn_client_conflict_t * conflict,svn_client_conflict_option_t * option,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)8289 verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict,
8290 svn_client_conflict_option_t *option,
8291 svn_client_ctx_t *ctx,
8292 apr_pool_t *scratch_pool)
8293 {
8294 const char *local_abspath;
8295 const char *wcroot_abspath;
8296 svn_wc_operation_t operation;
8297 svn_wc_conflict_reason_t local_change;
8298
8299 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8300 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
8301 local_abspath, scratch_pool,
8302 scratch_pool));
8303 operation = svn_client_conflict_get_operation(conflict);
8304 local_change = svn_client_conflict_get_local_change(conflict);
8305
8306 if (operation == svn_wc_operation_update ||
8307 operation == svn_wc_operation_switch)
8308 {
8309 struct conflict_tree_incoming_delete_details *details;
8310
8311 details = conflict->tree_conflict_incoming_details;
8312 if (details == NULL)
8313 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8314 _("Conflict resolution option '%d' requires "
8315 "details for tree conflict at '%s' to be "
8316 "fetched from the repository."),
8317 option->id,
8318 svn_dirent_local_style(local_abspath,
8319 scratch_pool));
8320
8321 if (details->deleted_rev == SVN_INVALID_REVNUM &&
8322 details->added_rev == SVN_INVALID_REVNUM)
8323 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8324 _("Could not find the revision in which '%s' "
8325 "was deleted from the repository"),
8326 svn_dirent_local_style(
8327 svn_dirent_skip_ancestor(
8328 wcroot_abspath,
8329 conflict->local_abspath),
8330 scratch_pool));
8331
8332 if (local_change == svn_wc_conflict_reason_edited)
8333 SVN_ERR(ensure_local_edit_vs_incoming_deletion_copied_state(
8334 details, operation, wcroot_abspath, conflict, ctx,
8335 scratch_pool));
8336 }
8337 else if (operation == svn_wc_operation_merge)
8338 {
8339 svn_node_kind_t victim_node_kind;
8340 svn_node_kind_t on_disk_kind;
8341
8342 /* For merge, all we can do is ensure that the item still exists. */
8343 victim_node_kind =
8344 svn_client_conflict_tree_get_victim_node_kind(conflict);
8345 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
8346
8347 if (victim_node_kind != on_disk_kind)
8348 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8349 _("Cannot resolve tree conflict on '%s' "
8350 "(expected node kind '%s' but found '%s')"),
8351 svn_dirent_local_style(
8352 svn_dirent_skip_ancestor(
8353 wcroot_abspath, conflict->local_abspath),
8354 scratch_pool),
8355 svn_node_kind_to_word(victim_node_kind),
8356 svn_node_kind_to_word(on_disk_kind));
8357 }
8358
8359 return SVN_NO_ERROR;
8360 }
8361
8362 /* Implements conflict_option_resolve_func_t. */
8363 static svn_error_t *
resolve_incoming_delete_ignore(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)8364 resolve_incoming_delete_ignore(svn_client_conflict_option_t *option,
8365 svn_client_conflict_t *conflict,
8366 svn_client_ctx_t *ctx,
8367 apr_pool_t *scratch_pool)
8368 {
8369 svn_client_conflict_option_id_t option_id;
8370 const char *local_abspath;
8371 const char *lock_abspath;
8372 svn_error_t *err;
8373
8374 option_id = svn_client_conflict_option_get_id(option);
8375 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8376
8377 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8378 local_abspath,
8379 scratch_pool, scratch_pool));
8380
8381 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8382 scratch_pool);
8383 if (err)
8384 goto unlock_wc;
8385
8386 /* Resolve to the current working copy state. */
8387 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
8388
8389 /* svn_wc__del_tree_conflict doesn't handle notification for us */
8390 if (ctx->notify_func2)
8391 ctx->notify_func2(ctx->notify_baton2,
8392 svn_wc_create_notify(local_abspath,
8393 svn_wc_notify_resolved_tree,
8394 scratch_pool),
8395 scratch_pool);
8396
8397 unlock_wc:
8398 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8399 lock_abspath,
8400 scratch_pool));
8401 SVN_ERR(err);
8402
8403 conflict->resolution_tree = option_id;
8404
8405 return SVN_NO_ERROR;
8406 }
8407
8408 /* Implements conflict_option_resolve_func_t. */
8409 static svn_error_t *
resolve_incoming_delete_accept(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)8410 resolve_incoming_delete_accept(svn_client_conflict_option_t *option,
8411 svn_client_conflict_t *conflict,
8412 svn_client_ctx_t *ctx,
8413 apr_pool_t *scratch_pool)
8414 {
8415 svn_client_conflict_option_id_t option_id;
8416 const char *local_abspath;
8417 const char *parent_abspath;
8418 const char *lock_abspath;
8419 svn_error_t *err;
8420
8421 option_id = svn_client_conflict_option_get_id(option);
8422 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8423
8424 /* Deleting a node requires a lock on the node's parent. */
8425 parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
8426 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8427 parent_abspath,
8428 scratch_pool, scratch_pool));
8429
8430 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8431 scratch_pool);
8432 if (err)
8433 goto unlock_wc;
8434
8435 /* Delete the tree conflict victim. Marks the conflict resolved. */
8436 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
8437 NULL, NULL, /* don't allow user to cancel here */
8438 ctx->notify_func2, ctx->notify_baton2,
8439 scratch_pool);
8440 if (err)
8441 {
8442 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
8443 {
8444 /* Not a versioned path. This can happen if the victim has already
8445 * been deleted in our branche's history, for example. Either way,
8446 * the item is gone, which is what we want, so don't treat this as
8447 * a fatal error. */
8448 svn_error_clear(err);
8449
8450 /* Resolve to current working copy state. */
8451 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath,
8452 scratch_pool);
8453 }
8454
8455 if (err)
8456 goto unlock_wc;
8457 }
8458
8459 if (ctx->notify_func2)
8460 ctx->notify_func2(ctx->notify_baton2,
8461 svn_wc_create_notify(local_abspath,
8462 svn_wc_notify_resolved_tree,
8463 scratch_pool),
8464 scratch_pool);
8465
8466 unlock_wc:
8467 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8468 lock_abspath,
8469 scratch_pool));
8470 SVN_ERR(err);
8471
8472 conflict->resolution_tree = option_id;
8473
8474 return SVN_NO_ERROR;
8475 }
8476
8477 /* Implements conflict_option_resolve_func_t. */
8478 static svn_error_t *
resolve_incoming_move_file_text_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)8479 resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
8480 svn_client_conflict_t *conflict,
8481 svn_client_ctx_t *ctx,
8482 apr_pool_t *scratch_pool)
8483 {
8484 svn_client_conflict_option_id_t option_id;
8485 const char *victim_abspath;
8486 const char *merge_source_abspath;
8487 svn_wc_conflict_reason_t local_change;
8488 svn_wc_operation_t operation;
8489 const char *lock_abspath;
8490 svn_error_t *err;
8491 const char *repos_root_url;
8492 const char *incoming_old_repos_relpath;
8493 svn_revnum_t incoming_old_pegrev;
8494 const char *incoming_new_repos_relpath;
8495 svn_revnum_t incoming_new_pegrev;
8496 const char *wc_tmpdir;
8497 const char *ancestor_abspath;
8498 svn_stream_t *ancestor_stream;
8499 apr_hash_t *ancestor_props;
8500 apr_hash_t *victim_props;
8501 apr_hash_t *move_target_props;
8502 const char *ancestor_url;
8503 const char *corrected_url;
8504 svn_ra_session_t *ra_session;
8505 svn_wc_merge_outcome_t merge_content_outcome;
8506 svn_wc_notify_state_t merge_props_outcome;
8507 apr_array_header_t *propdiffs;
8508 struct conflict_tree_incoming_delete_details *details;
8509 apr_array_header_t *possible_moved_to_abspaths;
8510 const char *moved_to_abspath;
8511 const char *incoming_abspath = NULL;
8512
8513 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
8514 local_change = svn_client_conflict_get_local_change(conflict);
8515 operation = svn_client_conflict_get_operation(conflict);
8516 details = conflict->tree_conflict_incoming_details;
8517 if (details == NULL || details->moves == NULL)
8518 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8519 _("The specified conflict resolution option "
8520 "requires details for tree conflict at '%s' "
8521 "to be fetched from the repository first."),
8522 svn_dirent_local_style(victim_abspath,
8523 scratch_pool));
8524 if (operation == svn_wc_operation_none)
8525 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
8526 _("Invalid operation code '%d' recorded for "
8527 "conflict at '%s'"), operation,
8528 svn_dirent_local_style(victim_abspath,
8529 scratch_pool));
8530
8531 option_id = svn_client_conflict_option_get_id(option);
8532 SVN_ERR_ASSERT(option_id ==
8533 svn_client_conflict_option_incoming_move_file_text_merge ||
8534 option_id ==
8535 svn_client_conflict_option_both_moved_file_move_merge);
8536
8537 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8538 conflict, scratch_pool,
8539 scratch_pool));
8540 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8541 &incoming_old_repos_relpath, &incoming_old_pegrev,
8542 NULL, conflict, scratch_pool,
8543 scratch_pool));
8544 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8545 &incoming_new_repos_relpath, &incoming_new_pegrev,
8546 NULL, conflict, scratch_pool,
8547 scratch_pool));
8548
8549 /* Set up temporary storage for the common ancestor version of the file. */
8550 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
8551 scratch_pool, scratch_pool));
8552 SVN_ERR(svn_stream_open_unique(&ancestor_stream,
8553 &ancestor_abspath, wc_tmpdir,
8554 svn_io_file_del_on_pool_cleanup,
8555 scratch_pool, scratch_pool));
8556
8557 /* Fetch the ancestor file's content. */
8558 ancestor_url = svn_path_url_add_component2(repos_root_url,
8559 incoming_old_repos_relpath,
8560 scratch_pool);
8561 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8562 ancestor_url, NULL, NULL,
8563 FALSE, FALSE, ctx,
8564 scratch_pool, scratch_pool));
8565 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
8566 ancestor_stream, NULL, /* fetched_rev */
8567 &ancestor_props, scratch_pool));
8568 filter_props(ancestor_props, scratch_pool);
8569
8570 /* Close stream to flush ancestor file to disk. */
8571 SVN_ERR(svn_stream_close(ancestor_stream));
8572
8573 possible_moved_to_abspaths =
8574 svn_hash_gets(details->wc_move_targets,
8575 get_moved_to_repos_relpath(details, scratch_pool));
8576 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths,
8577 details->wc_move_target_idx,
8578 const char *);
8579
8580 if (local_change == svn_wc_conflict_reason_missing)
8581 {
8582 /* This is an incoming move vs local move conflict.
8583 * Merge from the local move's target location to the
8584 * incoming move's target location. */
8585 struct conflict_tree_local_missing_details *local_details;
8586 apr_array_header_t *moves;
8587
8588 local_details = conflict->tree_conflict_local_details;
8589 moves = svn_hash_gets(local_details->wc_move_targets,
8590 local_details->move_target_repos_relpath);
8591 merge_source_abspath =
8592 APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *);
8593 }
8594 else
8595 merge_source_abspath = victim_abspath;
8596
8597 /* ### The following WC modifications should be atomic. */
8598 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
8599 &lock_abspath, ctx->wc_ctx,
8600 svn_dirent_get_longest_ancestor(victim_abspath,
8601 moved_to_abspath,
8602 scratch_pool),
8603 scratch_pool, scratch_pool));
8604
8605 if (local_change != svn_wc_conflict_reason_missing)
8606 {
8607 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8608 scratch_pool);
8609 if (err)
8610 goto unlock_wc;
8611 }
8612
8613 /* Get a copy of the conflict victim's properties. */
8614 err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, merge_source_abspath,
8615 scratch_pool, scratch_pool);
8616 if (err)
8617 goto unlock_wc;
8618
8619 /* Get a copy of the move target's properties. */
8620 err = svn_wc_prop_list2(&move_target_props, ctx->wc_ctx,
8621 moved_to_abspath,
8622 scratch_pool, scratch_pool);
8623 if (err)
8624 goto unlock_wc;
8625
8626 /* Create a property diff for the files. */
8627 err = svn_prop_diffs(&propdiffs, move_target_props, victim_props,
8628 scratch_pool);
8629 if (err)
8630 goto unlock_wc;
8631
8632 if (operation == svn_wc_operation_update ||
8633 operation == svn_wc_operation_switch)
8634 {
8635 svn_stream_t *moved_to_stream;
8636 svn_stream_t *incoming_stream;
8637
8638 /* Create a temporary copy of the moved file in repository-normal form.
8639 * Set up this temporary file to be automatically removed. */
8640 err = svn_stream_open_unique(&incoming_stream,
8641 &incoming_abspath, wc_tmpdir,
8642 svn_io_file_del_on_pool_cleanup,
8643 scratch_pool, scratch_pool);
8644 if (err)
8645 goto unlock_wc;
8646
8647 err = svn_wc__translated_stream(&moved_to_stream, ctx->wc_ctx,
8648 moved_to_abspath,
8649 moved_to_abspath,
8650 SVN_WC_TRANSLATE_TO_NF,
8651 scratch_pool, scratch_pool);
8652 if (err)
8653 goto unlock_wc;
8654
8655 err = svn_stream_copy3(moved_to_stream, incoming_stream,
8656 NULL, NULL, /* no cancellation */
8657 scratch_pool);
8658 if (err)
8659 goto unlock_wc;
8660
8661 /* Overwrite the moved file with the conflict victim's content.
8662 * Incoming changes will be merged in from the temporary file created
8663 * above. This is required to correctly make local changes show up as
8664 * 'mine' during the three-way text merge between the ancestor file,
8665 * the conflict victim ('mine'), and the moved file ('theirs') which
8666 * was brought in by the update/switch operation and occupies the path
8667 * of the merge target. */
8668 err = svn_io_copy_file(merge_source_abspath, moved_to_abspath, FALSE,
8669 scratch_pool);
8670 if (err)
8671 goto unlock_wc;
8672 }
8673 else if (operation == svn_wc_operation_merge)
8674 {
8675 svn_stream_t *incoming_stream;
8676 svn_stream_t *move_target_stream;
8677
8678 /* Set aside the current move target file. This is required to apply
8679 * the move, and only then perform a three-way text merge between
8680 * the ancestor's file, our working file (which we would move to
8681 * the destination), and the file that we have set aside, which
8682 * contains the incoming fulltext.
8683 * Set up this temporary file to NOT be automatically removed. */
8684 err = svn_stream_open_unique(&incoming_stream,
8685 &incoming_abspath, wc_tmpdir,
8686 svn_io_file_del_none,
8687 scratch_pool, scratch_pool);
8688 if (err)
8689 goto unlock_wc;
8690
8691 err = svn_wc__translated_stream(&move_target_stream, ctx->wc_ctx,
8692 moved_to_abspath, moved_to_abspath,
8693 SVN_WC_TRANSLATE_TO_NF,
8694 scratch_pool, scratch_pool);
8695 if (err)
8696 goto unlock_wc;
8697
8698 err = svn_stream_copy3(move_target_stream, incoming_stream,
8699 NULL, NULL, /* no cancellation */
8700 scratch_pool);
8701 if (err)
8702 goto unlock_wc;
8703
8704 /* Apply the incoming move. */
8705 err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool);
8706 if (err)
8707 goto unlock_wc;
8708 err = svn_wc__move2(ctx->wc_ctx, merge_source_abspath, moved_to_abspath,
8709 FALSE, /* ordinary (not meta-data only) move */
8710 FALSE, /* mixed-revisions don't apply to files */
8711 NULL, NULL, /* don't allow user to cancel here */
8712 NULL, NULL, /* no extra notification */
8713 scratch_pool);
8714 if (err)
8715 goto unlock_wc;
8716 }
8717 else
8718 SVN_ERR_MALFUNCTION();
8719
8720 /* Perform the file merge. */
8721 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
8722 ctx->wc_ctx, ancestor_abspath,
8723 incoming_abspath, moved_to_abspath,
8724 NULL, NULL, NULL, /* labels */
8725 NULL, NULL, /* conflict versions */
8726 FALSE, /* dry run */
8727 NULL, NULL, /* diff3_cmd, merge_options */
8728 apr_hash_count(ancestor_props) ? ancestor_props : NULL,
8729 propdiffs,
8730 NULL, NULL, /* conflict func/baton */
8731 NULL, NULL, /* don't allow user to cancel here */
8732 scratch_pool);
8733 svn_io_sleep_for_timestamps(moved_to_abspath, scratch_pool);
8734 if (err)
8735 goto unlock_wc;
8736
8737 if (operation == svn_wc_operation_merge && incoming_abspath)
8738 {
8739 err = svn_io_remove_file2(incoming_abspath, TRUE, scratch_pool);
8740 if (err)
8741 goto unlock_wc;
8742 incoming_abspath = NULL;
8743 }
8744
8745 if (ctx->notify_func2)
8746 {
8747 svn_wc_notify_t *notify;
8748
8749 /* Tell the world about the file merge that just happened. */
8750 notify = svn_wc_create_notify(moved_to_abspath,
8751 svn_wc_notify_update_update,
8752 scratch_pool);
8753 if (merge_content_outcome == svn_wc_merge_conflict)
8754 notify->content_state = svn_wc_notify_state_conflicted;
8755 else
8756 notify->content_state = svn_wc_notify_state_merged;
8757 notify->prop_state = merge_props_outcome;
8758 notify->kind = svn_node_file;
8759 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8760 }
8761
8762 if (operation == svn_wc_operation_update ||
8763 operation == svn_wc_operation_switch)
8764 {
8765 /* Delete the tree conflict victim (clears the tree conflict marker). */
8766 err = svn_wc_delete4(ctx->wc_ctx, victim_abspath, FALSE, FALSE,
8767 NULL, NULL, /* don't allow user to cancel here */
8768 NULL, NULL, /* no extra notification */
8769 scratch_pool);
8770 if (err)
8771 goto unlock_wc;
8772 }
8773 else if (local_change == svn_wc_conflict_reason_missing)
8774 {
8775 /* Clear tree conflict marker. */
8776 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath,
8777 scratch_pool);
8778 if (err)
8779 goto unlock_wc;
8780 }
8781
8782 if (ctx->notify_func2)
8783 {
8784 svn_wc_notify_t *notify;
8785
8786 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
8787 scratch_pool);
8788 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8789 }
8790
8791 conflict->resolution_tree = option_id;
8792
8793 unlock_wc:
8794 if (err && operation == svn_wc_operation_merge && incoming_abspath)
8795 err = svn_error_quick_wrapf(
8796 err, _("If needed, a backup copy of '%s' can be found at '%s'"),
8797 svn_dirent_local_style(moved_to_abspath, scratch_pool),
8798 svn_dirent_local_style(incoming_abspath, scratch_pool));
8799 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8800 lock_abspath,
8801 scratch_pool));
8802 SVN_ERR(err);
8803
8804 return SVN_NO_ERROR;
8805 }
8806
8807 /* Implements conflict_option_resolve_func_t.
8808 * Resolve an incoming move vs local move conflict by merging from the
8809 * incoming move's target location to the local move's target location,
8810 * overriding the incoming move. */
8811 static svn_error_t *
resolve_both_moved_file_text_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)8812 resolve_both_moved_file_text_merge(svn_client_conflict_option_t *option,
8813 svn_client_conflict_t *conflict,
8814 svn_client_ctx_t *ctx,
8815 apr_pool_t *scratch_pool)
8816 {
8817 svn_client_conflict_option_id_t option_id;
8818 const char *victim_abspath;
8819 const char *local_moved_to_abspath;
8820 svn_wc_operation_t operation;
8821 const char *lock_abspath;
8822 svn_error_t *err;
8823 const char *repos_root_url;
8824 const char *incoming_old_repos_relpath;
8825 svn_revnum_t incoming_old_pegrev;
8826 const char *incoming_new_repos_relpath;
8827 svn_revnum_t incoming_new_pegrev;
8828 const char *wc_tmpdir;
8829 const char *ancestor_abspath;
8830 svn_stream_t *ancestor_stream;
8831 apr_hash_t *ancestor_props;
8832 apr_hash_t *incoming_props;
8833 apr_hash_t *local_props;
8834 const char *ancestor_url;
8835 const char *corrected_url;
8836 svn_ra_session_t *ra_session;
8837 svn_wc_merge_outcome_t merge_content_outcome;
8838 svn_wc_notify_state_t merge_props_outcome;
8839 apr_array_header_t *propdiffs;
8840 struct conflict_tree_incoming_delete_details *incoming_details;
8841 apr_array_header_t *possible_moved_to_abspaths;
8842 const char *incoming_moved_to_abspath;
8843 struct conflict_tree_local_missing_details *local_details;
8844 apr_array_header_t *local_moves;
8845
8846 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
8847 operation = svn_client_conflict_get_operation(conflict);
8848 incoming_details = conflict->tree_conflict_incoming_details;
8849 if (incoming_details == NULL || incoming_details->moves == NULL)
8850 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8851 _("The specified conflict resolution option "
8852 "requires details for tree conflict at '%s' "
8853 "to be fetched from the repository first."),
8854 svn_dirent_local_style(victim_abspath,
8855 scratch_pool));
8856 if (operation == svn_wc_operation_none)
8857 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
8858 _("Invalid operation code '%d' recorded for "
8859 "conflict at '%s'"), operation,
8860 svn_dirent_local_style(victim_abspath,
8861 scratch_pool));
8862
8863 option_id = svn_client_conflict_option_get_id(option);
8864 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge);
8865
8866 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8867 conflict, scratch_pool,
8868 scratch_pool));
8869 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8870 &incoming_old_repos_relpath, &incoming_old_pegrev,
8871 NULL, conflict, scratch_pool,
8872 scratch_pool));
8873 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8874 &incoming_new_repos_relpath, &incoming_new_pegrev,
8875 NULL, conflict, scratch_pool,
8876 scratch_pool));
8877
8878 /* Set up temporary storage for the common ancestor version of the file. */
8879 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
8880 scratch_pool, scratch_pool));
8881 SVN_ERR(svn_stream_open_unique(&ancestor_stream,
8882 &ancestor_abspath, wc_tmpdir,
8883 svn_io_file_del_on_pool_cleanup,
8884 scratch_pool, scratch_pool));
8885
8886 /* Fetch the ancestor file's content. */
8887 ancestor_url = svn_path_url_add_component2(repos_root_url,
8888 incoming_old_repos_relpath,
8889 scratch_pool);
8890 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8891 ancestor_url, NULL, NULL,
8892 FALSE, FALSE, ctx,
8893 scratch_pool, scratch_pool));
8894 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
8895 ancestor_stream, NULL, /* fetched_rev */
8896 &ancestor_props, scratch_pool));
8897 filter_props(ancestor_props, scratch_pool);
8898
8899 /* Close stream to flush ancestor file to disk. */
8900 SVN_ERR(svn_stream_close(ancestor_stream));
8901
8902 possible_moved_to_abspaths =
8903 svn_hash_gets(incoming_details->wc_move_targets,
8904 get_moved_to_repos_relpath(incoming_details, scratch_pool));
8905 incoming_moved_to_abspath =
8906 APR_ARRAY_IDX(possible_moved_to_abspaths,
8907 incoming_details->wc_move_target_idx, const char *);
8908
8909 local_details = conflict->tree_conflict_local_details;
8910 local_moves = svn_hash_gets(local_details->wc_move_targets,
8911 local_details->move_target_repos_relpath);
8912 local_moved_to_abspath =
8913 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *);
8914
8915 /* ### The following WC modifications should be atomic. */
8916 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
8917 &lock_abspath, ctx->wc_ctx,
8918 svn_dirent_get_longest_ancestor(victim_abspath,
8919 local_moved_to_abspath,
8920 scratch_pool),
8921 scratch_pool, scratch_pool));
8922
8923 /* Get a copy of the incoming moved item's properties. */
8924 err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx,
8925 incoming_moved_to_abspath,
8926 scratch_pool, scratch_pool);
8927 if (err)
8928 goto unlock_wc;
8929
8930 /* Get a copy of the local move target's properties. */
8931 err = svn_wc_prop_list2(&local_props, ctx->wc_ctx,
8932 local_moved_to_abspath,
8933 scratch_pool, scratch_pool);
8934 if (err)
8935 goto unlock_wc;
8936
8937 /* Create a property diff for the files. */
8938 err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
8939 scratch_pool);
8940 if (err)
8941 goto unlock_wc;
8942
8943 /* Perform the file merge. */
8944 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
8945 ctx->wc_ctx, ancestor_abspath,
8946 incoming_moved_to_abspath, local_moved_to_abspath,
8947 NULL, NULL, NULL, /* labels */
8948 NULL, NULL, /* conflict versions */
8949 FALSE, /* dry run */
8950 NULL, NULL, /* diff3_cmd, merge_options */
8951 apr_hash_count(ancestor_props) ? ancestor_props : NULL,
8952 propdiffs,
8953 NULL, NULL, /* conflict func/baton */
8954 NULL, NULL, /* don't allow user to cancel here */
8955 scratch_pool);
8956 if (err)
8957 goto unlock_wc;
8958
8959 if (ctx->notify_func2)
8960 {
8961 svn_wc_notify_t *notify;
8962
8963 /* Tell the world about the file merge that just happened. */
8964 notify = svn_wc_create_notify(local_moved_to_abspath,
8965 svn_wc_notify_update_update,
8966 scratch_pool);
8967 if (merge_content_outcome == svn_wc_merge_conflict)
8968 notify->content_state = svn_wc_notify_state_conflicted;
8969 else
8970 notify->content_state = svn_wc_notify_state_merged;
8971 notify->prop_state = merge_props_outcome;
8972 notify->kind = svn_node_file;
8973 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8974 }
8975
8976 /* Revert local addition of the incoming move's target. */
8977 err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
8978 svn_depth_infinity, FALSE, NULL, TRUE, FALSE,
8979 FALSE /*added_keep_local*/,
8980 NULL, NULL, /* no cancellation */
8981 ctx->notify_func2, ctx->notify_baton2,
8982 scratch_pool);
8983 if (err)
8984 goto unlock_wc;
8985
8986 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
8987 if (err)
8988 goto unlock_wc;
8989
8990 if (ctx->notify_func2)
8991 {
8992 svn_wc_notify_t *notify;
8993
8994 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
8995 scratch_pool);
8996 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8997 }
8998
8999 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
9000
9001 conflict->resolution_tree = option_id;
9002
9003 unlock_wc:
9004 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
9005 lock_abspath,
9006 scratch_pool));
9007 SVN_ERR(err);
9008
9009 return SVN_NO_ERROR;
9010 }
9011
9012 /* Implements conflict_option_resolve_func_t.
9013 * Resolve an incoming move vs local move conflict by moving the locally moved
9014 * directory to the incoming move target location, and then merging changes. */
9015 static svn_error_t *
resolve_both_moved_dir_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)9016 resolve_both_moved_dir_merge(svn_client_conflict_option_t *option,
9017 svn_client_conflict_t *conflict,
9018 svn_client_ctx_t *ctx,
9019 apr_pool_t *scratch_pool)
9020 {
9021 svn_client_conflict_option_id_t option_id;
9022 const char *victim_abspath;
9023 const char *local_moved_to_abspath;
9024 svn_wc_operation_t operation;
9025 const char *lock_abspath;
9026 svn_error_t *err;
9027 const char *repos_root_url;
9028 const char *incoming_old_repos_relpath;
9029 svn_revnum_t incoming_old_pegrev;
9030 const char *incoming_new_repos_relpath;
9031 svn_revnum_t incoming_new_pegrev;
9032 const char *incoming_moved_repos_relpath;
9033 struct conflict_tree_incoming_delete_details *incoming_details;
9034 apr_array_header_t *possible_moved_to_abspaths;
9035 const char *incoming_moved_to_abspath;
9036 struct conflict_tree_local_missing_details *local_details;
9037 apr_array_header_t *local_moves;
9038 svn_client__conflict_report_t *conflict_report;
9039 const char *incoming_old_url;
9040 const char *incoming_moved_url;
9041 svn_opt_revision_t incoming_old_opt_rev;
9042 svn_opt_revision_t incoming_moved_opt_rev;
9043
9044 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
9045 operation = svn_client_conflict_get_operation(conflict);
9046 incoming_details = conflict->tree_conflict_incoming_details;
9047 if (incoming_details == NULL || incoming_details->moves == NULL)
9048 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9049 _("The specified conflict resolution option "
9050 "requires details for tree conflict at '%s' "
9051
9052 "to be fetched from the repository first."),
9053 svn_dirent_local_style(victim_abspath,
9054 scratch_pool));
9055 if (operation == svn_wc_operation_none)
9056 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
9057 _("Invalid operation code '%d' recorded for "
9058 "conflict at '%s'"), operation,
9059 svn_dirent_local_style(victim_abspath,
9060 scratch_pool));
9061
9062 option_id = svn_client_conflict_option_get_id(option);
9063 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_dir_merge);
9064
9065 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
9066 conflict, scratch_pool,
9067 scratch_pool));
9068 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9069 &incoming_old_repos_relpath, &incoming_old_pegrev,
9070 NULL, conflict, scratch_pool,
9071 scratch_pool));
9072 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9073 &incoming_new_repos_relpath, &incoming_new_pegrev,
9074 NULL, conflict, scratch_pool,
9075 scratch_pool));
9076
9077 possible_moved_to_abspaths =
9078 svn_hash_gets(incoming_details->wc_move_targets,
9079 get_moved_to_repos_relpath(incoming_details, scratch_pool));
9080 incoming_moved_to_abspath =
9081 APR_ARRAY_IDX(possible_moved_to_abspaths,
9082 incoming_details->wc_move_target_idx, const char *);
9083
9084 local_details = conflict->tree_conflict_local_details;
9085 local_moves = svn_hash_gets(local_details->wc_move_targets,
9086 local_details->move_target_repos_relpath);
9087 local_moved_to_abspath =
9088 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *);
9089
9090 /* ### The following WC modifications should be atomic. */
9091 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
9092 &lock_abspath, ctx->wc_ctx,
9093 svn_dirent_get_longest_ancestor(victim_abspath,
9094 local_moved_to_abspath,
9095 scratch_pool),
9096 scratch_pool, scratch_pool));
9097
9098 /* Perform the merge. */
9099 incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9100 incoming_old_repos_relpath, SVN_VA_NULL);
9101 incoming_old_opt_rev.kind = svn_opt_revision_number;
9102 incoming_old_opt_rev.value.number = incoming_old_pegrev;
9103
9104 incoming_moved_repos_relpath =
9105 get_moved_to_repos_relpath(incoming_details, scratch_pool);
9106 incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9107 incoming_moved_repos_relpath, SVN_VA_NULL);
9108 incoming_moved_opt_rev.kind = svn_opt_revision_number;
9109 incoming_moved_opt_rev.value.number = incoming_new_pegrev;
9110 err = svn_client__merge_locked(&conflict_report,
9111 incoming_old_url, &incoming_old_opt_rev,
9112 incoming_moved_url, &incoming_moved_opt_rev,
9113 local_moved_to_abspath, svn_depth_infinity,
9114 TRUE, TRUE, /* do a no-ancestry merge */
9115 FALSE, FALSE, FALSE,
9116 TRUE, /* Allow mixed-rev just in case,
9117 * since conflict victims can't be
9118 * updated to straighten out
9119 * mixed-rev trees. */
9120 NULL, ctx, scratch_pool, scratch_pool);
9121 if (err)
9122 goto unlock_wc;
9123
9124 /* Revert local addition of the incoming move's target. */
9125 err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
9126 svn_depth_infinity, FALSE, NULL, TRUE, FALSE,
9127 FALSE /*added_keep_local*/,
9128 NULL, NULL, /* no cancellation */
9129 ctx->notify_func2, ctx->notify_baton2,
9130 scratch_pool);
9131 if (err)
9132 goto unlock_wc;
9133
9134 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
9135 if (err)
9136 goto unlock_wc;
9137
9138 if (ctx->notify_func2)
9139 {
9140 svn_wc_notify_t *notify;
9141
9142 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
9143 scratch_pool);
9144 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9145 }
9146
9147 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
9148
9149 conflict->resolution_tree = option_id;
9150
9151 unlock_wc:
9152 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
9153 lock_abspath,
9154 scratch_pool));
9155 SVN_ERR(err);
9156
9157 return SVN_NO_ERROR;
9158 }
9159
9160 /* Implements conflict_option_resolve_func_t.
9161 * Resolve an incoming move vs local move conflict by merging from the
9162 * incoming move's target location to the local move's target location,
9163 * overriding the incoming move. */
9164 static svn_error_t *
resolve_both_moved_dir_move_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)9165 resolve_both_moved_dir_move_merge(svn_client_conflict_option_t *option,
9166 svn_client_conflict_t *conflict,
9167 svn_client_ctx_t *ctx,
9168 apr_pool_t *scratch_pool)
9169 {
9170 svn_client_conflict_option_id_t option_id;
9171 const char *victim_abspath;
9172 const char *local_moved_to_abspath;
9173 svn_wc_operation_t operation;
9174 const char *lock_abspath;
9175 svn_error_t *err;
9176 const char *repos_root_url;
9177 const char *incoming_old_repos_relpath;
9178 svn_revnum_t incoming_old_pegrev;
9179 const char *incoming_new_repos_relpath;
9180 svn_revnum_t incoming_new_pegrev;
9181 struct conflict_tree_incoming_delete_details *incoming_details;
9182 apr_array_header_t *possible_moved_to_abspaths;
9183 const char *incoming_moved_to_abspath;
9184 struct conflict_tree_local_missing_details *local_details;
9185 apr_array_header_t *local_moves;
9186 svn_client__conflict_report_t *conflict_report;
9187 const char *incoming_old_url;
9188 const char *incoming_moved_url;
9189 svn_opt_revision_t incoming_old_opt_rev;
9190 svn_opt_revision_t incoming_moved_opt_rev;
9191
9192 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
9193 operation = svn_client_conflict_get_operation(conflict);
9194 incoming_details = conflict->tree_conflict_incoming_details;
9195 if (incoming_details == NULL || incoming_details->moves == NULL)
9196 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9197 _("The specified conflict resolution option "
9198 "requires details for tree conflict at '%s' "
9199
9200 "to be fetched from the repository first."),
9201 svn_dirent_local_style(victim_abspath,
9202 scratch_pool));
9203 if (operation == svn_wc_operation_none)
9204 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
9205 _("Invalid operation code '%d' recorded for "
9206 "conflict at '%s'"), operation,
9207 svn_dirent_local_style(victim_abspath,
9208 scratch_pool));
9209
9210 option_id = svn_client_conflict_option_get_id(option);
9211 SVN_ERR_ASSERT(option_id ==
9212 svn_client_conflict_option_both_moved_dir_move_merge);
9213
9214 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
9215 conflict, scratch_pool,
9216 scratch_pool));
9217 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9218 &incoming_old_repos_relpath, &incoming_old_pegrev,
9219 NULL, conflict, scratch_pool,
9220 scratch_pool));
9221 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9222 &incoming_new_repos_relpath, &incoming_new_pegrev,
9223 NULL, conflict, scratch_pool,
9224 scratch_pool));
9225
9226 possible_moved_to_abspaths =
9227 svn_hash_gets(incoming_details->wc_move_targets,
9228 get_moved_to_repos_relpath(incoming_details, scratch_pool));
9229 incoming_moved_to_abspath =
9230 APR_ARRAY_IDX(possible_moved_to_abspaths,
9231 incoming_details->wc_move_target_idx, const char *);
9232
9233 local_details = conflict->tree_conflict_local_details;
9234 local_moves = svn_hash_gets(local_details->wc_move_targets,
9235 local_details->move_target_repos_relpath);
9236 local_moved_to_abspath =
9237 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *);
9238
9239 /* ### The following WC modifications should be atomic. */
9240 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
9241 &lock_abspath, ctx->wc_ctx,
9242 svn_dirent_get_longest_ancestor(victim_abspath,
9243 local_moved_to_abspath,
9244 scratch_pool),
9245 scratch_pool, scratch_pool));
9246
9247 /* Revert the incoming move target directory. */
9248 err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
9249 svn_depth_infinity,
9250 FALSE, NULL, TRUE, FALSE,
9251 TRUE /*added_keep_local*/,
9252 NULL, NULL, /* no cancellation */
9253 ctx->notify_func2, ctx->notify_baton2,
9254 scratch_pool);
9255 if (err)
9256 goto unlock_wc;
9257
9258 /* The move operation is not part of natural history. We must replicate
9259 * this move in our history. Record a move in the working copy. */
9260 err = svn_wc__move2(ctx->wc_ctx, local_moved_to_abspath,
9261 incoming_moved_to_abspath,
9262 FALSE, /* this is not a meta-data only move */
9263 TRUE, /* allow mixed-revisions just in case */
9264 NULL, NULL, /* don't allow user to cancel here */
9265 ctx->notify_func2, ctx->notify_baton2,
9266 scratch_pool);
9267 if (err)
9268 goto unlock_wc;
9269
9270 /* Merge INCOMING_OLD_URL@MERGE_LEFT->INCOMING_MOVED_URL@MERGE_RIGHT
9271 * into the locally moved merge target. */
9272 incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9273 incoming_old_repos_relpath, SVN_VA_NULL);
9274 incoming_old_opt_rev.kind = svn_opt_revision_number;
9275 incoming_old_opt_rev.value.number = incoming_old_pegrev;
9276
9277 incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9278 incoming_details->move_target_repos_relpath,
9279 SVN_VA_NULL);
9280 incoming_moved_opt_rev.kind = svn_opt_revision_number;
9281 incoming_moved_opt_rev.value.number = incoming_new_pegrev;
9282 err = svn_client__merge_locked(&conflict_report,
9283 incoming_old_url, &incoming_old_opt_rev,
9284 incoming_moved_url, &incoming_moved_opt_rev,
9285 incoming_moved_to_abspath, svn_depth_infinity,
9286 TRUE, TRUE, /* do a no-ancestry merge */
9287 FALSE, FALSE, FALSE,
9288 TRUE, /* Allow mixed-rev just in case,
9289 * since conflict victims can't be
9290 * updated to straighten out
9291 * mixed-rev trees. */
9292 NULL, ctx, scratch_pool, scratch_pool);
9293 if (err)
9294 goto unlock_wc;
9295
9296 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
9297 if (err)
9298 goto unlock_wc;
9299
9300 if (ctx->notify_func2)
9301 {
9302 svn_wc_notify_t *notify;
9303
9304 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
9305 scratch_pool);
9306 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9307 }
9308
9309 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
9310
9311 conflict->resolution_tree = option_id;
9312
9313 unlock_wc:
9314 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
9315 lock_abspath,
9316 scratch_pool));
9317 SVN_ERR(err);
9318
9319 return SVN_NO_ERROR;
9320 }
9321
9322 /* Implements conflict_option_resolve_func_t. */
9323 static svn_error_t *
resolve_incoming_move_dir_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)9324 resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
9325 svn_client_conflict_t *conflict,
9326 svn_client_ctx_t *ctx,
9327 apr_pool_t *scratch_pool)
9328 {
9329 svn_client_conflict_option_id_t option_id;
9330 const char *local_abspath;
9331 svn_wc_operation_t operation;
9332 const char *lock_abspath;
9333 svn_error_t *err;
9334 const char *repos_root_url;
9335 const char *repos_uuid;
9336 const char *incoming_old_repos_relpath;
9337 svn_revnum_t incoming_old_pegrev;
9338 const char *incoming_new_repos_relpath;
9339 svn_revnum_t incoming_new_pegrev;
9340 const char *victim_repos_relpath;
9341 svn_revnum_t victim_peg_rev;
9342 const char *moved_to_repos_relpath;
9343 svn_revnum_t moved_to_peg_rev;
9344 struct conflict_tree_incoming_delete_details *details;
9345 apr_array_header_t *possible_moved_to_abspaths;
9346 const char *moved_to_abspath;
9347 const char *incoming_old_url;
9348 svn_opt_revision_t incoming_old_opt_rev;
9349 svn_client__conflict_report_t *conflict_report;
9350 svn_boolean_t is_copy;
9351 svn_boolean_t is_modified;
9352
9353 local_abspath = svn_client_conflict_get_local_abspath(conflict);
9354 operation = svn_client_conflict_get_operation(conflict);
9355 details = conflict->tree_conflict_incoming_details;
9356 if (details == NULL || details->moves == NULL)
9357 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9358 _("The specified conflict resolution option "
9359 "requires details for tree conflict at '%s' "
9360 "to be fetched from the repository first."),
9361 svn_dirent_local_style(local_abspath,
9362 scratch_pool));
9363
9364 option_id = svn_client_conflict_option_get_id(option);
9365 SVN_ERR_ASSERT(option_id ==
9366 svn_client_conflict_option_incoming_move_dir_merge);
9367
9368 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
9369 conflict, scratch_pool,
9370 scratch_pool));
9371 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9372 &incoming_old_repos_relpath, &incoming_old_pegrev,
9373 NULL, conflict, scratch_pool,
9374 scratch_pool));
9375 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9376 &incoming_new_repos_relpath, &incoming_new_pegrev,
9377 NULL, conflict, scratch_pool,
9378 scratch_pool));
9379
9380 /* Get repository location of the moved-away node (the conflict victim). */
9381 if (operation == svn_wc_operation_update ||
9382 operation == svn_wc_operation_switch)
9383 {
9384 victim_repos_relpath = incoming_old_repos_relpath;
9385 victim_peg_rev = incoming_old_pegrev;
9386 }
9387 else if (operation == svn_wc_operation_merge)
9388 SVN_ERR(svn_wc__node_get_repos_info(&victim_peg_rev, &victim_repos_relpath,
9389 NULL, NULL, ctx->wc_ctx, local_abspath,
9390 scratch_pool, scratch_pool));
9391
9392 /* Get repository location of the moved-here node (incoming move). */
9393 possible_moved_to_abspaths =
9394 svn_hash_gets(details->wc_move_targets,
9395 get_moved_to_repos_relpath(details, scratch_pool));
9396 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths,
9397 details->wc_move_target_idx,
9398 const char *);
9399
9400 /* ### The following WC modifications should be atomic. */
9401
9402 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
9403 &lock_abspath, ctx->wc_ctx,
9404 svn_dirent_get_longest_ancestor(local_abspath,
9405 moved_to_abspath,
9406 scratch_pool),
9407 scratch_pool, scratch_pool));
9408
9409 err = svn_wc__node_get_origin(&is_copy, &moved_to_peg_rev,
9410 &moved_to_repos_relpath,
9411 NULL, NULL, NULL, NULL,
9412 ctx->wc_ctx, moved_to_abspath, FALSE,
9413 scratch_pool, scratch_pool);
9414 if (err)
9415 goto unlock_wc;
9416 if (!is_copy && operation == svn_wc_operation_merge)
9417 {
9418 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9419 _("Cannot resolve tree conflict on '%s' "
9420 "(expected a copied item at '%s', but the "
9421 "item is not a copy)"),
9422 svn_dirent_local_style(local_abspath,
9423 scratch_pool),
9424 svn_dirent_local_style(moved_to_abspath,
9425 scratch_pool));
9426 goto unlock_wc;
9427 }
9428
9429 if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM)
9430 {
9431 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9432 _("Cannot resolve tree conflict on '%s' "
9433 "(could not determine origin of '%s')"),
9434 svn_dirent_local_style(local_abspath,
9435 scratch_pool),
9436 svn_dirent_local_style(moved_to_abspath,
9437 scratch_pool));
9438 goto unlock_wc;
9439 }
9440
9441 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
9442 scratch_pool);
9443 if (err)
9444 goto unlock_wc;
9445
9446 if (operation == svn_wc_operation_merge)
9447 {
9448 const char *move_target_url;
9449 svn_opt_revision_t incoming_new_opt_rev;
9450
9451 /* Revert the incoming move target directory. */
9452 err = svn_wc_revert6(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity,
9453 FALSE, NULL, TRUE, FALSE,
9454 TRUE /*added_keep_local*/,
9455 NULL, NULL, /* no cancellation */
9456 ctx->notify_func2, ctx->notify_baton2,
9457 scratch_pool);
9458 if (err)
9459 goto unlock_wc;
9460
9461 /* The move operation is not part of natural history. We must replicate
9462 * this move in our history. Record a move in the working copy. */
9463 err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
9464 FALSE, /* this is not a meta-data only move */
9465 TRUE, /* allow mixed-revisions just in case */
9466 NULL, NULL, /* don't allow user to cancel here */
9467 ctx->notify_func2, ctx->notify_baton2,
9468 scratch_pool);
9469 if (err)
9470 goto unlock_wc;
9471
9472 /* Merge INCOMING_OLD_URL@MERGE_LEFT->MOVE_TARGET_URL@MERGE_RIGHT
9473 * into move target. */
9474 incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9475 incoming_old_repos_relpath, SVN_VA_NULL);
9476 incoming_old_opt_rev.kind = svn_opt_revision_number;
9477 incoming_old_opt_rev.value.number = incoming_old_pegrev;
9478 move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9479 get_moved_to_repos_relpath(details,
9480 scratch_pool),
9481 SVN_VA_NULL);
9482 incoming_new_opt_rev.kind = svn_opt_revision_number;
9483 incoming_new_opt_rev.value.number = incoming_new_pegrev;
9484 err = svn_client__merge_locked(&conflict_report,
9485 incoming_old_url, &incoming_old_opt_rev,
9486 move_target_url, &incoming_new_opt_rev,
9487 moved_to_abspath, svn_depth_infinity,
9488 TRUE, TRUE, /* do a no-ancestry merge */
9489 FALSE, FALSE, FALSE,
9490 TRUE, /* Allow mixed-rev just in case,
9491 * since conflict victims can't be
9492 * updated to straighten out
9493 * mixed-rev trees. */
9494 NULL, ctx, scratch_pool, scratch_pool);
9495 if (err)
9496 goto unlock_wc;
9497 }
9498 else
9499 {
9500 SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
9501 operation == svn_wc_operation_switch);
9502
9503 /* Merge local modifications into the incoming move target dir. */
9504 err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath,
9505 TRUE, ctx->cancel_func, ctx->cancel_baton,
9506 scratch_pool);
9507 if (err)
9508 goto unlock_wc;
9509
9510 if (is_modified)
9511 {
9512 err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx,
9513 local_abspath,
9514 moved_to_abspath,
9515 ctx->cancel_func,
9516 ctx->cancel_baton,
9517 ctx->notify_func2,
9518 ctx->notify_baton2,
9519 scratch_pool);
9520 if (err)
9521 goto unlock_wc;
9522 }
9523
9524 /* The move operation is part of our natural history.
9525 * Delete the tree conflict victim (clears the tree conflict marker). */
9526 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
9527 NULL, NULL, /* don't allow user to cancel here */
9528 NULL, NULL, /* no extra notification */
9529 scratch_pool);
9530 if (err)
9531 goto unlock_wc;
9532 }
9533
9534 if (ctx->notify_func2)
9535 {
9536 svn_wc_notify_t *notify;
9537
9538 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
9539 scratch_pool);
9540 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9541 }
9542
9543 conflict->resolution_tree = option_id;
9544
9545 unlock_wc:
9546 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
9547 lock_abspath,
9548 scratch_pool));
9549 SVN_ERR(err);
9550
9551 return SVN_NO_ERROR;
9552 }
9553
9554 /* Implements conflict_option_resolve_func_t.
9555 * Handles svn_client_conflict_option_local_move_file_text_merge
9556 * and svn_client_conflict_option_sibling_move_file_text_merge. */
9557 static svn_error_t *
resolve_local_move_file_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)9558 resolve_local_move_file_merge(svn_client_conflict_option_t *option,
9559 svn_client_conflict_t *conflict,
9560 svn_client_ctx_t *ctx,
9561 apr_pool_t *scratch_pool)
9562 {
9563 const char *lock_abspath;
9564 svn_error_t *err;
9565 const char *repos_root_url;
9566 const char *incoming_old_repos_relpath;
9567 svn_revnum_t incoming_old_pegrev;
9568 const char *incoming_new_repos_relpath;
9569 svn_revnum_t incoming_new_pegrev;
9570 const char *wc_tmpdir;
9571 const char *ancestor_tmp_abspath;
9572 const char *incoming_tmp_abspath;
9573 apr_hash_t *ancestor_props;
9574 apr_hash_t *incoming_props;
9575 svn_stream_t *stream;
9576 const char *url;
9577 const char *corrected_url;
9578 const char *old_session_url;
9579 svn_ra_session_t *ra_session;
9580 svn_wc_merge_outcome_t merge_content_outcome;
9581 svn_wc_notify_state_t merge_props_outcome;
9582 apr_array_header_t *propdiffs;
9583 struct conflict_tree_local_missing_details *details;
9584 const char *merge_target_abspath;
9585 const char *wcroot_abspath;
9586
9587 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9588 conflict->local_abspath, scratch_pool,
9589 scratch_pool));
9590
9591 details = conflict->tree_conflict_local_details;
9592
9593 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
9594 conflict, scratch_pool,
9595 scratch_pool));
9596 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9597 &incoming_old_repos_relpath, &incoming_old_pegrev,
9598 NULL, conflict, scratch_pool,
9599 scratch_pool));
9600 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9601 &incoming_new_repos_relpath, &incoming_new_pegrev,
9602 NULL, conflict, scratch_pool,
9603 scratch_pool));
9604
9605 if (details->wc_siblings)
9606 {
9607 merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
9608 details->preferred_sibling_idx,
9609 const char *);
9610 }
9611 else if (details->wc_move_targets && details->move_target_repos_relpath)
9612 {
9613 apr_array_header_t *moves;
9614 moves = svn_hash_gets(details->wc_move_targets,
9615 details->move_target_repos_relpath);
9616 merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx,
9617 const char *);
9618 }
9619 else
9620 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9621 _("Corresponding working copy node not found "
9622 "for '%s'"),
9623 svn_dirent_local_style(
9624 svn_dirent_skip_ancestor(
9625 wcroot_abspath, conflict->local_abspath),
9626 scratch_pool));
9627
9628 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx,
9629 merge_target_abspath,
9630 scratch_pool, scratch_pool));
9631
9632 /* Fetch the common ancestor file's content. */
9633 SVN_ERR(svn_stream_open_unique(&stream, &ancestor_tmp_abspath, wc_tmpdir,
9634 svn_io_file_del_on_pool_cleanup,
9635 scratch_pool, scratch_pool));
9636 url = svn_path_url_add_component2(repos_root_url,
9637 incoming_old_repos_relpath,
9638 scratch_pool);
9639 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
9640 url, NULL, NULL,
9641 FALSE, FALSE, ctx,
9642 scratch_pool, scratch_pool));
9643 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, stream, NULL,
9644 &ancestor_props, scratch_pool));
9645 filter_props(ancestor_props, scratch_pool);
9646
9647 /* Close stream to flush the file to disk. */
9648 SVN_ERR(svn_stream_close(stream));
9649
9650 /* Do the same for the incoming file's content. */
9651 SVN_ERR(svn_stream_open_unique(&stream, &incoming_tmp_abspath, wc_tmpdir,
9652 svn_io_file_del_on_pool_cleanup,
9653 scratch_pool, scratch_pool));
9654 url = svn_path_url_add_component2(repos_root_url,
9655 incoming_new_repos_relpath,
9656 scratch_pool);
9657 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
9658 url, scratch_pool));
9659 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, stream, NULL,
9660 &incoming_props, scratch_pool));
9661 /* Close stream to flush the file to disk. */
9662 SVN_ERR(svn_stream_close(stream));
9663
9664 filter_props(incoming_props, scratch_pool);
9665
9666 /* Create a property diff for the files. */
9667 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props,
9668 scratch_pool));
9669
9670 /* ### The following WC modifications should be atomic. */
9671 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
9672 &lock_abspath, ctx->wc_ctx,
9673 svn_dirent_get_longest_ancestor(conflict->local_abspath,
9674 merge_target_abspath,
9675 scratch_pool),
9676 scratch_pool, scratch_pool));
9677
9678 /* Perform the file merge. */
9679 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
9680 ctx->wc_ctx,
9681 ancestor_tmp_abspath, incoming_tmp_abspath,
9682 merge_target_abspath,
9683 NULL, NULL, NULL, /* labels */
9684 NULL, NULL, /* conflict versions */
9685 FALSE, /* dry run */
9686 NULL, NULL, /* diff3_cmd, merge_options */
9687 apr_hash_count(ancestor_props) ? ancestor_props : NULL,
9688 propdiffs,
9689 NULL, NULL, /* conflict func/baton */
9690 NULL, NULL, /* don't allow user to cancel here */
9691 scratch_pool);
9692 svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
9693 if (err)
9694 return svn_error_compose_create(err,
9695 svn_wc__release_write_lock(ctx->wc_ctx,
9696 lock_abspath,
9697 scratch_pool));
9698
9699 err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
9700 scratch_pool);
9701 err = svn_error_compose_create(err,
9702 svn_wc__release_write_lock(ctx->wc_ctx,
9703 lock_abspath,
9704 scratch_pool));
9705 if (err)
9706 return svn_error_trace(err);
9707
9708 if (ctx->notify_func2)
9709 {
9710 svn_wc_notify_t *notify;
9711
9712 /* Tell the world about the file merge that just happened. */
9713 notify = svn_wc_create_notify(merge_target_abspath,
9714 svn_wc_notify_update_update,
9715 scratch_pool);
9716 if (merge_content_outcome == svn_wc_merge_conflict)
9717 notify->content_state = svn_wc_notify_state_conflicted;
9718 else
9719 notify->content_state = svn_wc_notify_state_merged;
9720 notify->prop_state = merge_props_outcome;
9721 notify->kind = svn_node_file;
9722 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9723
9724 /* And also about the successfully resolved tree conflict. */
9725 notify = svn_wc_create_notify(conflict->local_abspath,
9726 svn_wc_notify_resolved_tree,
9727 scratch_pool);
9728 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9729 }
9730
9731 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
9732
9733 return SVN_NO_ERROR;
9734 }
9735
9736 /* Implements conflict_option_resolve_func_t. */
9737 static svn_error_t *
resolve_local_move_dir_merge(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)9738 resolve_local_move_dir_merge(svn_client_conflict_option_t *option,
9739 svn_client_conflict_t *conflict,
9740 svn_client_ctx_t *ctx,
9741 apr_pool_t *scratch_pool)
9742 {
9743 const char *lock_abspath;
9744 svn_error_t *err;
9745 const char *repos_root_url;
9746 const char *incoming_old_repos_relpath;
9747 svn_revnum_t incoming_old_pegrev;
9748 const char *incoming_new_repos_relpath;
9749 svn_revnum_t incoming_new_pegrev;
9750 struct conflict_tree_local_missing_details *details;
9751 const char *merge_target_abspath;
9752 const char *incoming_old_url;
9753 const char *incoming_new_url;
9754 svn_opt_revision_t incoming_old_opt_rev;
9755 svn_opt_revision_t incoming_new_opt_rev;
9756 svn_client__conflict_report_t *conflict_report;
9757
9758 details = conflict->tree_conflict_local_details;
9759
9760 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
9761 conflict, scratch_pool,
9762 scratch_pool));
9763 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9764 &incoming_old_repos_relpath, &incoming_old_pegrev,
9765 NULL, conflict, scratch_pool,
9766 scratch_pool));
9767 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9768 &incoming_new_repos_relpath, &incoming_new_pegrev,
9769 NULL, conflict, scratch_pool,
9770 scratch_pool));
9771
9772 if (details->wc_move_targets)
9773 {
9774 apr_array_header_t *moves;
9775
9776 moves = svn_hash_gets(details->wc_move_targets,
9777 details->move_target_repos_relpath);
9778 merge_target_abspath =
9779 APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *);
9780 }
9781 else
9782 merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
9783 details->preferred_sibling_idx,
9784 const char *);
9785
9786 /* ### The following WC modifications should be atomic. */
9787 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
9788 &lock_abspath, ctx->wc_ctx,
9789 svn_dirent_get_longest_ancestor(conflict->local_abspath,
9790 merge_target_abspath,
9791 scratch_pool),
9792 scratch_pool, scratch_pool));
9793
9794 /* Resolve to current working copy state.
9795 * svn_client__merge_locked() requires this. */
9796 err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
9797 scratch_pool);
9798 if (err)
9799 goto unlock_wc;
9800
9801 /* Merge outstanding changes to the merge target. */
9802 incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9803 incoming_old_repos_relpath, SVN_VA_NULL);
9804 incoming_old_opt_rev.kind = svn_opt_revision_number;
9805 incoming_old_opt_rev.value.number = incoming_old_pegrev;
9806 incoming_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9807 incoming_new_repos_relpath, SVN_VA_NULL);
9808 incoming_new_opt_rev.kind = svn_opt_revision_number;
9809 incoming_new_opt_rev.value.number = incoming_new_pegrev;
9810 err = svn_client__merge_locked(&conflict_report,
9811 incoming_old_url, &incoming_old_opt_rev,
9812 incoming_new_url, &incoming_new_opt_rev,
9813 merge_target_abspath, svn_depth_infinity,
9814 TRUE, TRUE, /* do a no-ancestry merge */
9815 FALSE, FALSE, FALSE,
9816 TRUE, /* Allow mixed-rev just in case,
9817 * since conflict victims can't be
9818 * updated to straighten out
9819 * mixed-rev trees. */
9820 NULL, ctx, scratch_pool, scratch_pool);
9821 unlock_wc:
9822 svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
9823 err = svn_error_compose_create(err,
9824 svn_wc__release_write_lock(ctx->wc_ctx,
9825 lock_abspath,
9826 scratch_pool));
9827 if (err)
9828 return svn_error_trace(err);
9829
9830 if (ctx->notify_func2)
9831 {
9832 svn_wc_notify_t *notify;
9833
9834 /* Tell the world about the file merge that just happened. */
9835 notify = svn_wc_create_notify(merge_target_abspath,
9836 svn_wc_notify_update_update,
9837 scratch_pool);
9838 if (conflict_report)
9839 notify->content_state = svn_wc_notify_state_conflicted;
9840 else
9841 notify->content_state = svn_wc_notify_state_merged;
9842 notify->kind = svn_node_dir;
9843 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9844
9845 /* And also about the successfully resolved tree conflict. */
9846 notify = svn_wc_create_notify(conflict->local_abspath,
9847 svn_wc_notify_resolved_tree,
9848 scratch_pool);
9849 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9850 }
9851
9852 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
9853
9854 return SVN_NO_ERROR;
9855 }
9856
9857 static svn_error_t *
assert_text_conflict(svn_client_conflict_t * conflict,apr_pool_t * scratch_pool)9858 assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
9859 {
9860 svn_boolean_t text_conflicted;
9861
9862 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL,
9863 conflict, scratch_pool,
9864 scratch_pool));
9865
9866 SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */
9867
9868 return SVN_NO_ERROR;
9869 }
9870
9871 static svn_error_t *
assert_prop_conflict(svn_client_conflict_t * conflict,apr_pool_t * scratch_pool)9872 assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
9873 {
9874 apr_array_header_t *props_conflicted;
9875
9876 SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
9877 conflict, scratch_pool,
9878 scratch_pool));
9879
9880 /* ### return proper error? */
9881 SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0);
9882
9883 return SVN_NO_ERROR;
9884 }
9885
9886 static svn_error_t *
assert_tree_conflict(svn_client_conflict_t * conflict,apr_pool_t * scratch_pool)9887 assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
9888 {
9889 svn_boolean_t tree_conflicted;
9890
9891 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
9892 conflict, scratch_pool,
9893 scratch_pool));
9894
9895 SVN_ERR_ASSERT(tree_conflicted); /* ### return proper error? */
9896
9897 return SVN_NO_ERROR;
9898 }
9899
9900 /* Helper to add to conflict resolution option to array of OPTIONS.
9901 * Resolution option object will be allocated from OPTIONS->POOL
9902 * and DESCRIPTION will be copied to this pool.
9903 * Returns pointer to the created conflict resolution option. */
9904 static svn_client_conflict_option_t *
add_resolution_option(apr_array_header_t * options,svn_client_conflict_t * conflict,svn_client_conflict_option_id_t id,const char * label,const char * description,conflict_option_resolve_func_t resolve_func)9905 add_resolution_option(apr_array_header_t *options,
9906 svn_client_conflict_t *conflict,
9907 svn_client_conflict_option_id_t id,
9908 const char *label,
9909 const char *description,
9910 conflict_option_resolve_func_t resolve_func)
9911 {
9912 svn_client_conflict_option_t *option;
9913
9914 option = apr_pcalloc(options->pool, sizeof(*option));
9915 option->pool = options->pool;
9916 option->id = id;
9917 option->label = apr_pstrdup(option->pool, label);
9918 option->description = apr_pstrdup(option->pool, description);
9919 option->conflict = conflict;
9920 option->do_resolve_func = resolve_func;
9921
9922 APR_ARRAY_PUSH(options, const svn_client_conflict_option_t *) = option;
9923
9924 return option;
9925 }
9926
9927 svn_error_t *
svn_client_conflict_text_get_resolution_options(apr_array_header_t ** options,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)9928 svn_client_conflict_text_get_resolution_options(apr_array_header_t **options,
9929 svn_client_conflict_t *conflict,
9930 svn_client_ctx_t *ctx,
9931 apr_pool_t *result_pool,
9932 apr_pool_t *scratch_pool)
9933 {
9934 const char *mime_type;
9935
9936 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
9937
9938 *options = apr_array_make(result_pool, 7,
9939 sizeof(svn_client_conflict_option_t *));
9940
9941 add_resolution_option(*options, conflict,
9942 svn_client_conflict_option_postpone,
9943 _("Postpone"),
9944 _("skip this conflict and leave it unresolved"),
9945 resolve_postpone);
9946
9947 mime_type = svn_client_conflict_text_get_mime_type(conflict);
9948 if (mime_type && svn_mime_type_is_binary(mime_type))
9949 {
9950 /* Resolver options for a binary file conflict. */
9951 add_resolution_option(*options, conflict,
9952 svn_client_conflict_option_base_text,
9953 _("Accept base"),
9954 _("discard local and incoming changes for this binary file"),
9955 resolve_text_conflict);
9956
9957 add_resolution_option(*options, conflict,
9958 svn_client_conflict_option_incoming_text,
9959 _("Accept incoming"),
9960 _("accept incoming version of binary file"),
9961 resolve_text_conflict);
9962
9963 add_resolution_option(*options, conflict,
9964 svn_client_conflict_option_working_text,
9965 _("Mark as resolved"),
9966 _("accept binary file as it appears in the working copy"),
9967 resolve_text_conflict);
9968 }
9969 else
9970 {
9971 /* Resolver options for a text file conflict. */
9972 add_resolution_option(*options, conflict,
9973 svn_client_conflict_option_base_text,
9974 _("Accept base"),
9975 _("discard local and incoming changes for this file"),
9976 resolve_text_conflict);
9977
9978 add_resolution_option(*options, conflict,
9979 svn_client_conflict_option_incoming_text,
9980 _("Accept incoming"),
9981 _("accept incoming version of entire file"),
9982 resolve_text_conflict);
9983
9984 add_resolution_option(*options, conflict,
9985 svn_client_conflict_option_working_text,
9986 _("Reject incoming"),
9987 _("reject all incoming changes for this file"),
9988 resolve_text_conflict);
9989
9990 add_resolution_option(*options, conflict,
9991 svn_client_conflict_option_incoming_text_where_conflicted,
9992 _("Accept incoming for conflicts"),
9993 _("accept incoming changes only where they conflict"),
9994 resolve_text_conflict);
9995
9996 add_resolution_option(*options, conflict,
9997 svn_client_conflict_option_working_text_where_conflicted,
9998 _("Reject conflicts"),
9999 _("reject incoming changes which conflict and accept the rest"),
10000 resolve_text_conflict);
10001
10002 add_resolution_option(*options, conflict,
10003 svn_client_conflict_option_merged_text,
10004 _("Mark as resolved"),
10005 _("accept the file as it appears in the working copy"),
10006 resolve_text_conflict);
10007 }
10008
10009 return SVN_NO_ERROR;
10010 }
10011
10012 svn_error_t *
svn_client_conflict_prop_get_resolution_options(apr_array_header_t ** options,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)10013 svn_client_conflict_prop_get_resolution_options(apr_array_header_t **options,
10014 svn_client_conflict_t *conflict,
10015 svn_client_ctx_t *ctx,
10016 apr_pool_t *result_pool,
10017 apr_pool_t *scratch_pool)
10018 {
10019 SVN_ERR(assert_prop_conflict(conflict, scratch_pool));
10020
10021 *options = apr_array_make(result_pool, 7,
10022 sizeof(svn_client_conflict_option_t *));
10023
10024 add_resolution_option(*options, conflict,
10025 svn_client_conflict_option_postpone,
10026 _("Postpone"),
10027 _("skip this conflict and leave it unresolved"),
10028 resolve_postpone);
10029
10030 add_resolution_option(*options, conflict,
10031 svn_client_conflict_option_base_text,
10032 _("Accept base"),
10033 _("discard local and incoming changes for this property"),
10034 resolve_prop_conflict);
10035
10036 add_resolution_option(*options, conflict,
10037 svn_client_conflict_option_incoming_text,
10038 _("Accept incoming"),
10039 _("accept incoming version of entire property value"),
10040 resolve_prop_conflict);
10041
10042 add_resolution_option(*options, conflict,
10043 svn_client_conflict_option_working_text,
10044 _("Mark as resolved"),
10045 _("accept working copy version of entire property value"),
10046 resolve_prop_conflict);
10047
10048 add_resolution_option(*options, conflict,
10049 svn_client_conflict_option_incoming_text_where_conflicted,
10050 _("Accept incoming for conflicts"),
10051 _("accept incoming changes only where they conflict"),
10052 resolve_prop_conflict);
10053
10054 add_resolution_option(*options, conflict,
10055 svn_client_conflict_option_working_text_where_conflicted,
10056 _("Reject conflicts"),
10057 _("reject changes which conflict and accept the rest"),
10058 resolve_prop_conflict);
10059
10060 add_resolution_option(*options, conflict,
10061 svn_client_conflict_option_merged_text,
10062 _("Accept merged"),
10063 _("accept merged version of property value"),
10064 resolve_prop_conflict);
10065
10066 return SVN_NO_ERROR;
10067 }
10068
10069 /* Configure 'accept current wc state' resolution option for a tree conflict. */
10070 static svn_error_t *
configure_option_accept_current_wc_state(svn_client_conflict_t * conflict,apr_array_header_t * options)10071 configure_option_accept_current_wc_state(svn_client_conflict_t *conflict,
10072 apr_array_header_t *options)
10073 {
10074 svn_wc_operation_t operation;
10075 svn_wc_conflict_action_t incoming_change;
10076 svn_wc_conflict_reason_t local_change;
10077 conflict_option_resolve_func_t do_resolve_func;
10078
10079 operation = svn_client_conflict_get_operation(conflict);
10080 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10081 local_change = svn_client_conflict_get_local_change(conflict);
10082
10083 if ((operation == svn_wc_operation_update ||
10084 operation == svn_wc_operation_switch) &&
10085 (local_change == svn_wc_conflict_reason_moved_away ||
10086 local_change == svn_wc_conflict_reason_deleted ||
10087 local_change == svn_wc_conflict_reason_replaced) &&
10088 incoming_change == svn_wc_conflict_action_edit)
10089 {
10090 /* We must break moves if the user accepts the current working copy
10091 * state instead of updating a moved-away node or updating children
10092 * moved outside of deleted or replaced directory nodes.
10093 * Else such moves would be left in an invalid state. */
10094 do_resolve_func = resolve_update_break_moved_away;
10095 }
10096 else
10097 do_resolve_func = resolve_accept_current_wc_state;
10098
10099 add_resolution_option(options, conflict,
10100 svn_client_conflict_option_accept_current_wc_state,
10101 _("Mark as resolved"),
10102 _("accept current working copy state"),
10103 do_resolve_func);
10104
10105 return SVN_NO_ERROR;
10106 }
10107
10108 /* Configure 'update move destination' resolution option for a tree conflict. */
10109 static svn_error_t *
configure_option_update_move_destination(svn_client_conflict_t * conflict,apr_array_header_t * options)10110 configure_option_update_move_destination(svn_client_conflict_t *conflict,
10111 apr_array_header_t *options)
10112 {
10113 svn_wc_operation_t operation;
10114 svn_wc_conflict_action_t incoming_change;
10115 svn_wc_conflict_reason_t local_change;
10116
10117 operation = svn_client_conflict_get_operation(conflict);
10118 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10119 local_change = svn_client_conflict_get_local_change(conflict);
10120
10121 if ((operation == svn_wc_operation_update ||
10122 operation == svn_wc_operation_switch) &&
10123 incoming_change == svn_wc_conflict_action_edit &&
10124 local_change == svn_wc_conflict_reason_moved_away)
10125 {
10126 add_resolution_option(
10127 options, conflict,
10128 svn_client_conflict_option_update_move_destination,
10129 _("Update move destination"),
10130 _("apply incoming changes to move destination"),
10131 resolve_update_moved_away_node);
10132 }
10133
10134 return SVN_NO_ERROR;
10135 }
10136
10137 /* Configure 'update raise moved away children' resolution option for a tree
10138 * conflict. */
10139 static svn_error_t *
configure_option_update_raise_moved_away_children(svn_client_conflict_t * conflict,apr_array_header_t * options)10140 configure_option_update_raise_moved_away_children(
10141 svn_client_conflict_t *conflict,
10142 apr_array_header_t *options)
10143 {
10144 svn_wc_operation_t operation;
10145 svn_wc_conflict_action_t incoming_change;
10146 svn_wc_conflict_reason_t local_change;
10147 svn_node_kind_t victim_node_kind;
10148
10149 operation = svn_client_conflict_get_operation(conflict);
10150 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10151 local_change = svn_client_conflict_get_local_change(conflict);
10152 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10153
10154 if ((operation == svn_wc_operation_update ||
10155 operation == svn_wc_operation_switch) &&
10156 incoming_change == svn_wc_conflict_action_edit &&
10157 (local_change == svn_wc_conflict_reason_deleted ||
10158 local_change == svn_wc_conflict_reason_replaced) &&
10159 victim_node_kind == svn_node_dir)
10160 {
10161 add_resolution_option(
10162 options, conflict,
10163 svn_client_conflict_option_update_any_moved_away_children,
10164 _("Update any moved-away children"),
10165 _("prepare for updating moved-away children, if any"),
10166 resolve_update_raise_moved_away);
10167 }
10168
10169 return SVN_NO_ERROR;
10170 }
10171
10172 /* Configure 'incoming add ignore' resolution option for a tree conflict. */
10173 static svn_error_t *
configure_option_incoming_add_ignore(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10174 configure_option_incoming_add_ignore(svn_client_conflict_t *conflict,
10175 svn_client_ctx_t *ctx,
10176 apr_array_header_t *options,
10177 apr_pool_t *scratch_pool)
10178 {
10179 svn_wc_operation_t operation;
10180 svn_wc_conflict_action_t incoming_change;
10181 svn_wc_conflict_reason_t local_change;
10182 const char *incoming_new_repos_relpath;
10183 svn_revnum_t incoming_new_pegrev;
10184 svn_node_kind_t victim_node_kind;
10185
10186 operation = svn_client_conflict_get_operation(conflict);
10187 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10188 local_change = svn_client_conflict_get_local_change(conflict);
10189 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10190 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10191 &incoming_new_repos_relpath, &incoming_new_pegrev,
10192 NULL, conflict, scratch_pool,
10193 scratch_pool));
10194
10195 /* This option is only available for directories. */
10196 if (victim_node_kind == svn_node_dir &&
10197 incoming_change == svn_wc_conflict_action_add &&
10198 (local_change == svn_wc_conflict_reason_obstructed ||
10199 local_change == svn_wc_conflict_reason_added))
10200 {
10201 const char *description;
10202 const char *wcroot_abspath;
10203
10204 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10205 conflict->local_abspath, scratch_pool,
10206 scratch_pool));
10207 if (operation == svn_wc_operation_merge)
10208 description =
10209 apr_psprintf(scratch_pool,
10210 _("ignore and do not add '^/%s@%ld' here"),
10211 incoming_new_repos_relpath, incoming_new_pegrev);
10212 else if (operation == svn_wc_operation_update ||
10213 operation == svn_wc_operation_switch)
10214 {
10215 if (victim_node_kind == svn_node_file)
10216 description =
10217 apr_psprintf(scratch_pool,
10218 _("replace '^/%s@%ld' with the locally added file"),
10219 incoming_new_repos_relpath, incoming_new_pegrev);
10220 else if (victim_node_kind == svn_node_dir)
10221 description =
10222 apr_psprintf(scratch_pool,
10223 _("replace '^/%s@%ld' with the locally added "
10224 "directory"),
10225 incoming_new_repos_relpath, incoming_new_pegrev);
10226 else
10227 description =
10228 apr_psprintf(scratch_pool,
10229 _("replace '^/%s@%ld' with the locally added item"),
10230 incoming_new_repos_relpath, incoming_new_pegrev);
10231 }
10232 else
10233 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10234 _("unexpected operation code '%d'"),
10235 operation);
10236 add_resolution_option(
10237 options, conflict, svn_client_conflict_option_incoming_add_ignore,
10238 _("Ignore incoming addition"), description, resolve_incoming_add_ignore);
10239 }
10240
10241 return SVN_NO_ERROR;
10242 }
10243
10244 /* Configure 'incoming added file text merge' resolution option for a tree
10245 * conflict. */
10246 static svn_error_t *
configure_option_incoming_added_file_text_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10247 configure_option_incoming_added_file_text_merge(svn_client_conflict_t *conflict,
10248 svn_client_ctx_t *ctx,
10249 apr_array_header_t *options,
10250 apr_pool_t *scratch_pool)
10251 {
10252 svn_wc_operation_t operation;
10253 svn_wc_conflict_action_t incoming_change;
10254 svn_wc_conflict_reason_t local_change;
10255 svn_node_kind_t victim_node_kind;
10256 const char *incoming_new_repos_relpath;
10257 svn_revnum_t incoming_new_pegrev;
10258 svn_node_kind_t incoming_new_kind;
10259
10260 operation = svn_client_conflict_get_operation(conflict);
10261 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10262 local_change = svn_client_conflict_get_local_change(conflict);
10263 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10264 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10265 &incoming_new_repos_relpath, &incoming_new_pegrev,
10266 &incoming_new_kind, conflict, scratch_pool,
10267 scratch_pool));
10268
10269 if (victim_node_kind == svn_node_file &&
10270 incoming_new_kind == svn_node_file &&
10271 incoming_change == svn_wc_conflict_action_add &&
10272 (local_change == svn_wc_conflict_reason_obstructed ||
10273 local_change == svn_wc_conflict_reason_unversioned ||
10274 local_change == svn_wc_conflict_reason_added))
10275 {
10276 const char *description;
10277 const char *wcroot_abspath;
10278
10279 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10280 conflict->local_abspath, scratch_pool,
10281 scratch_pool));
10282
10283 if (operation == svn_wc_operation_merge)
10284 description =
10285 apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"),
10286 incoming_new_repos_relpath, incoming_new_pegrev,
10287 svn_dirent_local_style(
10288 svn_dirent_skip_ancestor(wcroot_abspath,
10289 conflict->local_abspath),
10290 scratch_pool));
10291 else
10292 description =
10293 apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"),
10294 svn_dirent_local_style(
10295 svn_dirent_skip_ancestor(wcroot_abspath,
10296 conflict->local_abspath),
10297 scratch_pool),
10298 incoming_new_repos_relpath, incoming_new_pegrev);
10299
10300 add_resolution_option(
10301 options, conflict,
10302 svn_client_conflict_option_incoming_added_file_text_merge,
10303 _("Merge the files"), description,
10304 operation == svn_wc_operation_merge
10305 ? resolve_merge_incoming_added_file_text_merge
10306 : resolve_merge_incoming_added_file_text_update);
10307 }
10308
10309 return SVN_NO_ERROR;
10310 }
10311
10312 /* Configure 'incoming added file replace and merge' resolution option for a
10313 * tree conflict. */
10314 static svn_error_t *
configure_option_incoming_added_file_replace_and_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10315 configure_option_incoming_added_file_replace_and_merge(
10316 svn_client_conflict_t *conflict,
10317 svn_client_ctx_t *ctx,
10318 apr_array_header_t *options,
10319 apr_pool_t *scratch_pool)
10320 {
10321 svn_wc_operation_t operation;
10322 svn_wc_conflict_action_t incoming_change;
10323 svn_wc_conflict_reason_t local_change;
10324 svn_node_kind_t victim_node_kind;
10325 const char *incoming_new_repos_relpath;
10326 svn_revnum_t incoming_new_pegrev;
10327 svn_node_kind_t incoming_new_kind;
10328
10329 operation = svn_client_conflict_get_operation(conflict);
10330 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10331 local_change = svn_client_conflict_get_local_change(conflict);
10332 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10333 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10334 &incoming_new_repos_relpath, &incoming_new_pegrev,
10335 &incoming_new_kind, conflict, scratch_pool,
10336 scratch_pool));
10337
10338 if (operation == svn_wc_operation_merge &&
10339 victim_node_kind == svn_node_file &&
10340 incoming_new_kind == svn_node_file &&
10341 incoming_change == svn_wc_conflict_action_add &&
10342 local_change == svn_wc_conflict_reason_obstructed)
10343 {
10344 const char *wcroot_abspath;
10345 const char *description;
10346
10347 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10348 conflict->local_abspath, scratch_pool,
10349 scratch_pool));
10350 description =
10351 apr_psprintf(scratch_pool,
10352 _("delete '%s', copy '^/%s@%ld' here, and merge the files"),
10353 svn_dirent_local_style(
10354 svn_dirent_skip_ancestor(wcroot_abspath,
10355 conflict->local_abspath),
10356 scratch_pool),
10357 incoming_new_repos_relpath, incoming_new_pegrev);
10358
10359 add_resolution_option(
10360 options, conflict,
10361 svn_client_conflict_option_incoming_added_file_replace_and_merge,
10362 _("Replace and merge"),
10363 description, resolve_merge_incoming_added_file_replace_and_merge);
10364 }
10365
10366 return SVN_NO_ERROR;
10367 }
10368
10369 /* Configure 'incoming added dir merge' resolution option for a tree
10370 * conflict. */
10371 static svn_error_t *
configure_option_incoming_added_dir_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10372 configure_option_incoming_added_dir_merge(svn_client_conflict_t *conflict,
10373 svn_client_ctx_t *ctx,
10374 apr_array_header_t *options,
10375 apr_pool_t *scratch_pool)
10376 {
10377 svn_wc_operation_t operation;
10378 svn_wc_conflict_action_t incoming_change;
10379 svn_wc_conflict_reason_t local_change;
10380 svn_node_kind_t victim_node_kind;
10381 const char *incoming_new_repos_relpath;
10382 svn_revnum_t incoming_new_pegrev;
10383 svn_node_kind_t incoming_new_kind;
10384
10385 operation = svn_client_conflict_get_operation(conflict);
10386 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10387 local_change = svn_client_conflict_get_local_change(conflict);
10388 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10389 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10390 &incoming_new_repos_relpath, &incoming_new_pegrev,
10391 &incoming_new_kind, conflict, scratch_pool,
10392 scratch_pool));
10393
10394 if (victim_node_kind == svn_node_dir &&
10395 incoming_new_kind == svn_node_dir &&
10396 incoming_change == svn_wc_conflict_action_add &&
10397 (local_change == svn_wc_conflict_reason_added ||
10398 (operation == svn_wc_operation_merge &&
10399 local_change == svn_wc_conflict_reason_obstructed) ||
10400 (operation != svn_wc_operation_merge &&
10401 local_change == svn_wc_conflict_reason_unversioned)))
10402 {
10403 const char *description;
10404 const char *wcroot_abspath;
10405
10406 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10407 conflict->local_abspath, scratch_pool,
10408 scratch_pool));
10409 if (operation == svn_wc_operation_merge)
10410 {
10411 if (conflict->tree_conflict_incoming_details == NULL)
10412 return SVN_NO_ERROR;
10413
10414 description =
10415 apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"),
10416 incoming_new_repos_relpath, incoming_new_pegrev,
10417 svn_dirent_local_style(
10418 svn_dirent_skip_ancestor(wcroot_abspath,
10419 conflict->local_abspath),
10420 scratch_pool));
10421 }
10422 else
10423 description =
10424 apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"),
10425 svn_dirent_local_style(
10426 svn_dirent_skip_ancestor(wcroot_abspath,
10427 conflict->local_abspath),
10428 scratch_pool),
10429 incoming_new_repos_relpath, incoming_new_pegrev);
10430
10431 add_resolution_option(options, conflict,
10432 svn_client_conflict_option_incoming_added_dir_merge,
10433 _("Merge the directories"), description,
10434 operation == svn_wc_operation_merge
10435 ? resolve_merge_incoming_added_dir_merge
10436 : resolve_update_incoming_added_dir_merge);
10437 }
10438
10439 return SVN_NO_ERROR;
10440 }
10441
10442 /* Configure 'incoming added dir replace' resolution option for a tree
10443 * conflict. */
10444 static svn_error_t *
configure_option_incoming_added_dir_replace(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10445 configure_option_incoming_added_dir_replace(svn_client_conflict_t *conflict,
10446 svn_client_ctx_t *ctx,
10447 apr_array_header_t *options,
10448 apr_pool_t *scratch_pool)
10449 {
10450 svn_wc_operation_t operation;
10451 svn_wc_conflict_action_t incoming_change;
10452 svn_wc_conflict_reason_t local_change;
10453 svn_node_kind_t victim_node_kind;
10454 const char *incoming_new_repos_relpath;
10455 svn_revnum_t incoming_new_pegrev;
10456 svn_node_kind_t incoming_new_kind;
10457
10458 operation = svn_client_conflict_get_operation(conflict);
10459 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10460 local_change = svn_client_conflict_get_local_change(conflict);
10461 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10462 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10463 &incoming_new_repos_relpath, &incoming_new_pegrev,
10464 &incoming_new_kind, conflict, scratch_pool,
10465 scratch_pool));
10466
10467 if (operation == svn_wc_operation_merge &&
10468 victim_node_kind == svn_node_dir &&
10469 incoming_new_kind == svn_node_dir &&
10470 incoming_change == svn_wc_conflict_action_add &&
10471 local_change == svn_wc_conflict_reason_obstructed)
10472 {
10473 const char *description;
10474 const char *wcroot_abspath;
10475
10476 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10477 conflict->local_abspath, scratch_pool,
10478 scratch_pool));
10479 description =
10480 apr_psprintf(scratch_pool, _("delete '%s' and copy '^/%s@%ld' here"),
10481 svn_dirent_local_style(
10482 svn_dirent_skip_ancestor(wcroot_abspath,
10483 conflict->local_abspath),
10484 scratch_pool),
10485 incoming_new_repos_relpath, incoming_new_pegrev);
10486 add_resolution_option(
10487 options, conflict,
10488 svn_client_conflict_option_incoming_added_dir_replace,
10489 _("Delete my directory and replace it with incoming directory"),
10490 description, resolve_merge_incoming_added_dir_replace);
10491 }
10492
10493 return SVN_NO_ERROR;
10494 }
10495
10496 /* Configure 'incoming added dir replace and merge' resolution option
10497 * for a tree conflict. */
10498 static svn_error_t *
configure_option_incoming_added_dir_replace_and_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10499 configure_option_incoming_added_dir_replace_and_merge(
10500 svn_client_conflict_t *conflict,
10501 svn_client_ctx_t *ctx,
10502 apr_array_header_t *options,
10503 apr_pool_t *scratch_pool)
10504 {
10505 svn_wc_operation_t operation;
10506 svn_wc_conflict_action_t incoming_change;
10507 svn_wc_conflict_reason_t local_change;
10508 svn_node_kind_t victim_node_kind;
10509 const char *incoming_new_repos_relpath;
10510 svn_revnum_t incoming_new_pegrev;
10511 svn_node_kind_t incoming_new_kind;
10512
10513 operation = svn_client_conflict_get_operation(conflict);
10514 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10515 local_change = svn_client_conflict_get_local_change(conflict);
10516 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10517 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10518 &incoming_new_repos_relpath, &incoming_new_pegrev,
10519 &incoming_new_kind, conflict, scratch_pool,
10520 scratch_pool));
10521
10522 if (operation == svn_wc_operation_merge &&
10523 victim_node_kind == svn_node_dir &&
10524 incoming_new_kind == svn_node_dir &&
10525 incoming_change == svn_wc_conflict_action_add &&
10526 local_change == svn_wc_conflict_reason_obstructed)
10527 {
10528 const char *description;
10529 const char *wcroot_abspath;
10530
10531 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10532 conflict->local_abspath, scratch_pool,
10533 scratch_pool));
10534 description =
10535 apr_psprintf(scratch_pool,
10536 _("delete '%s', copy '^/%s@%ld' here, and merge the directories"),
10537 svn_dirent_local_style(
10538 svn_dirent_skip_ancestor(wcroot_abspath,
10539 conflict->local_abspath),
10540 scratch_pool),
10541 incoming_new_repos_relpath, incoming_new_pegrev);
10542
10543 add_resolution_option(
10544 options, conflict,
10545 svn_client_conflict_option_incoming_added_dir_replace_and_merge,
10546 _("Replace and merge"),
10547 description, resolve_merge_incoming_added_dir_replace_and_merge);
10548 }
10549
10550 return SVN_NO_ERROR;
10551 }
10552
10553 /* Configure 'incoming delete ignore' resolution option for a tree conflict. */
10554 static svn_error_t *
configure_option_incoming_delete_ignore(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10555 configure_option_incoming_delete_ignore(svn_client_conflict_t *conflict,
10556 svn_client_ctx_t *ctx,
10557 apr_array_header_t *options,
10558 apr_pool_t *scratch_pool)
10559 {
10560 svn_wc_operation_t operation;
10561 svn_wc_conflict_action_t incoming_change;
10562 svn_wc_conflict_reason_t local_change;
10563 const char *incoming_new_repos_relpath;
10564 svn_revnum_t incoming_new_pegrev;
10565
10566 operation = svn_client_conflict_get_operation(conflict);
10567 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10568 local_change = svn_client_conflict_get_local_change(conflict);
10569 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10570 &incoming_new_repos_relpath, &incoming_new_pegrev,
10571 NULL, conflict, scratch_pool,
10572 scratch_pool));
10573
10574 if (incoming_change == svn_wc_conflict_action_delete)
10575 {
10576 const char *description;
10577 struct conflict_tree_incoming_delete_details *incoming_details;
10578 svn_boolean_t is_incoming_move;
10579
10580 incoming_details = conflict->tree_conflict_incoming_details;
10581 is_incoming_move = (incoming_details != NULL &&
10582 incoming_details->moves != NULL);
10583 if (local_change == svn_wc_conflict_reason_moved_away ||
10584 local_change == svn_wc_conflict_reason_edited)
10585 {
10586 /* An option which ignores the incoming deletion makes no sense
10587 * if we know there was a local move and/or an incoming move. */
10588 if (is_incoming_move)
10589 return SVN_NO_ERROR;
10590 }
10591 else if (local_change == svn_wc_conflict_reason_deleted)
10592 {
10593 /* If the local item was deleted and conflict details were fetched
10594 * and indicate that there was no move, then this is an actual
10595 * 'delete vs delete' situation. An option which ignores the incoming
10596 * deletion makes no sense in that case because there is no local
10597 * node to preserve. */
10598 if (!is_incoming_move)
10599 return SVN_NO_ERROR;
10600 }
10601 else if (local_change == svn_wc_conflict_reason_missing &&
10602 operation == svn_wc_operation_merge)
10603 {
10604 struct conflict_tree_local_missing_details *local_details;
10605 svn_boolean_t is_local_move; /* "local" to branch history */
10606
10607 local_details = conflict->tree_conflict_local_details;
10608 is_local_move = (local_details != NULL &&
10609 local_details->moves != NULL);
10610
10611 if (is_incoming_move || is_local_move)
10612 return SVN_NO_ERROR;
10613 }
10614
10615 description =
10616 apr_psprintf(scratch_pool, _("ignore the deletion of '^/%s@%ld'"),
10617 incoming_new_repos_relpath, incoming_new_pegrev);
10618
10619 add_resolution_option(options, conflict,
10620 svn_client_conflict_option_incoming_delete_ignore,
10621 _("Ignore incoming deletion"), description,
10622 resolve_incoming_delete_ignore);
10623 }
10624
10625 return SVN_NO_ERROR;
10626 }
10627
10628 /* Configure 'incoming delete accept' resolution option for a tree conflict. */
10629 static svn_error_t *
configure_option_incoming_delete_accept(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10630 configure_option_incoming_delete_accept(svn_client_conflict_t *conflict,
10631 svn_client_ctx_t *ctx,
10632 apr_array_header_t *options,
10633 apr_pool_t *scratch_pool)
10634 {
10635 svn_wc_conflict_action_t incoming_change;
10636 svn_wc_conflict_reason_t local_change;
10637 const char *incoming_new_repos_relpath;
10638 svn_revnum_t incoming_new_pegrev;
10639
10640 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10641 local_change = svn_client_conflict_get_local_change(conflict);
10642 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10643 &incoming_new_repos_relpath, &incoming_new_pegrev,
10644 NULL, conflict, scratch_pool,
10645 scratch_pool));
10646
10647 if (incoming_change == svn_wc_conflict_action_delete)
10648 {
10649 struct conflict_tree_incoming_delete_details *incoming_details;
10650 svn_boolean_t is_incoming_move;
10651
10652 incoming_details = conflict->tree_conflict_incoming_details;
10653 is_incoming_move = (incoming_details != NULL &&
10654 incoming_details->moves != NULL);
10655 if (is_incoming_move &&
10656 (local_change == svn_wc_conflict_reason_edited ||
10657 local_change == svn_wc_conflict_reason_moved_away ||
10658 local_change == svn_wc_conflict_reason_missing))
10659 {
10660 /* An option which accepts the incoming deletion makes no sense
10661 * if we know there was a local move and/or an incoming move. */
10662 return SVN_NO_ERROR;
10663 }
10664 else
10665 {
10666 const char *description;
10667 const char *wcroot_abspath;
10668 const char *local_abspath;
10669
10670 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10671 conflict->local_abspath, scratch_pool,
10672 scratch_pool));
10673 local_abspath = svn_client_conflict_get_local_abspath(conflict);
10674 description =
10675 apr_psprintf(scratch_pool, _("accept the deletion of '%s'"),
10676 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10677 local_abspath),
10678 scratch_pool));
10679 add_resolution_option(
10680 options, conflict,
10681 svn_client_conflict_option_incoming_delete_accept,
10682 _("Accept incoming deletion"), description,
10683 resolve_incoming_delete_accept);
10684 }
10685 }
10686
10687 return SVN_NO_ERROR;
10688 }
10689
10690 static svn_error_t *
describe_incoming_move_merge_conflict_option(const char ** description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,const char * moved_to_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)10691 describe_incoming_move_merge_conflict_option(
10692 const char **description,
10693 svn_client_conflict_t *conflict,
10694 svn_client_ctx_t *ctx,
10695 const char *moved_to_abspath,
10696 apr_pool_t *result_pool,
10697 apr_pool_t *scratch_pool)
10698 {
10699 svn_wc_operation_t operation;
10700 const char *victim_abspath;
10701 svn_node_kind_t victim_node_kind;
10702 const char *wcroot_abspath;
10703
10704 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10705 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10706 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10707 victim_abspath, scratch_pool,
10708 scratch_pool));
10709
10710 operation = svn_client_conflict_get_operation(conflict);
10711 if (operation == svn_wc_operation_merge)
10712 {
10713 const char *incoming_moved_abspath = NULL;
10714
10715 if (victim_node_kind == svn_node_none)
10716 {
10717 /* This is an incoming move vs local move conflict. */
10718 struct conflict_tree_incoming_delete_details *details;
10719
10720 details = conflict->tree_conflict_incoming_details;
10721 if (details->wc_move_targets)
10722 {
10723 apr_array_header_t *moves;
10724
10725 moves = svn_hash_gets(details->wc_move_targets,
10726 details->move_target_repos_relpath);
10727 incoming_moved_abspath =
10728 APR_ARRAY_IDX(moves, details->wc_move_target_idx,
10729 const char *);
10730 }
10731 }
10732
10733 if (incoming_moved_abspath)
10734 {
10735 /* The 'move and merge' option follows the incoming move; note that
10736 * moved_to_abspath points to the current location of an item which
10737 * was moved in the history of our merge target branch. If the user
10738 * chooses 'move and merge', that item will be moved again (i.e. it
10739 * will be moved to and merged with incoming_moved_abspath's item). */
10740 *description =
10741 apr_psprintf(
10742 result_pool, _("move '%s' to '%s' and merge"),
10743 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10744 moved_to_abspath),
10745 scratch_pool),
10746 svn_dirent_local_style(svn_dirent_skip_ancestor(
10747 wcroot_abspath,
10748 incoming_moved_abspath),
10749 scratch_pool));
10750 }
10751 else
10752 {
10753 *description =
10754 apr_psprintf(
10755 result_pool, _("move '%s' to '%s' and merge"),
10756 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10757 victim_abspath),
10758 scratch_pool),
10759 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10760 moved_to_abspath),
10761 scratch_pool));
10762 }
10763 }
10764 else
10765 *description =
10766 apr_psprintf(
10767 result_pool, _("move and merge local changes from '%s' into '%s'"),
10768 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10769 victim_abspath),
10770 scratch_pool),
10771 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10772 moved_to_abspath),
10773 scratch_pool));
10774
10775 return SVN_NO_ERROR;
10776 }
10777
10778 /* Configure 'incoming move file merge' resolution option for
10779 * a tree conflict. */
10780 static svn_error_t *
configure_option_incoming_move_file_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10781 configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
10782 svn_client_ctx_t *ctx,
10783 apr_array_header_t *options,
10784 apr_pool_t *scratch_pool)
10785 {
10786 svn_node_kind_t victim_node_kind;
10787 svn_wc_conflict_action_t incoming_change;
10788 svn_wc_conflict_reason_t local_change;
10789 const char *incoming_old_repos_relpath;
10790 svn_revnum_t incoming_old_pegrev;
10791 svn_node_kind_t incoming_old_kind;
10792 const char *incoming_new_repos_relpath;
10793 svn_revnum_t incoming_new_pegrev;
10794 svn_node_kind_t incoming_new_kind;
10795
10796 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10797 local_change = svn_client_conflict_get_local_change(conflict);
10798 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10799 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
10800 &incoming_old_repos_relpath, &incoming_old_pegrev,
10801 &incoming_old_kind, conflict, scratch_pool,
10802 scratch_pool));
10803 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10804 &incoming_new_repos_relpath, &incoming_new_pegrev,
10805 &incoming_new_kind, conflict, scratch_pool,
10806 scratch_pool));
10807
10808 if (victim_node_kind == svn_node_file &&
10809 incoming_old_kind == svn_node_file &&
10810 incoming_new_kind == svn_node_none &&
10811 incoming_change == svn_wc_conflict_action_delete &&
10812 local_change == svn_wc_conflict_reason_edited)
10813 {
10814 struct conflict_tree_incoming_delete_details *details;
10815 const char *description;
10816 apr_array_header_t *move_target_wc_abspaths;
10817 const char *moved_to_abspath;
10818
10819 details = conflict->tree_conflict_incoming_details;
10820 if (details == NULL || details->moves == NULL)
10821 return SVN_NO_ERROR;
10822
10823 if (apr_hash_count(details->wc_move_targets) == 0)
10824 return SVN_NO_ERROR;
10825
10826 move_target_wc_abspaths =
10827 svn_hash_gets(details->wc_move_targets,
10828 get_moved_to_repos_relpath(details, scratch_pool));
10829 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
10830 details->wc_move_target_idx,
10831 const char *);
10832 SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
10833 conflict, ctx,
10834 moved_to_abspath,
10835 scratch_pool,
10836 scratch_pool));
10837 add_resolution_option(
10838 options, conflict,
10839 svn_client_conflict_option_incoming_move_file_text_merge,
10840 _("Move and merge"), description,
10841 resolve_incoming_move_file_text_merge);
10842 }
10843
10844 return SVN_NO_ERROR;
10845 }
10846
10847 /* Configure 'incoming move dir merge' resolution option for
10848 * a tree conflict. */
10849 static svn_error_t *
configure_option_incoming_dir_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10850 configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
10851 svn_client_ctx_t *ctx,
10852 apr_array_header_t *options,
10853 apr_pool_t *scratch_pool)
10854 {
10855 svn_node_kind_t victim_node_kind;
10856 svn_wc_conflict_action_t incoming_change;
10857 svn_wc_conflict_reason_t local_change;
10858 const char *incoming_old_repos_relpath;
10859 svn_revnum_t incoming_old_pegrev;
10860 svn_node_kind_t incoming_old_kind;
10861 const char *incoming_new_repos_relpath;
10862 svn_revnum_t incoming_new_pegrev;
10863 svn_node_kind_t incoming_new_kind;
10864
10865 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10866 local_change = svn_client_conflict_get_local_change(conflict);
10867 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
10868 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
10869 &incoming_old_repos_relpath, &incoming_old_pegrev,
10870 &incoming_old_kind, conflict, scratch_pool,
10871 scratch_pool));
10872 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10873 &incoming_new_repos_relpath, &incoming_new_pegrev,
10874 &incoming_new_kind, conflict, scratch_pool,
10875 scratch_pool));
10876
10877 if (victim_node_kind == svn_node_dir &&
10878 incoming_old_kind == svn_node_dir &&
10879 incoming_new_kind == svn_node_none &&
10880 incoming_change == svn_wc_conflict_action_delete &&
10881 local_change == svn_wc_conflict_reason_edited)
10882 {
10883 struct conflict_tree_incoming_delete_details *details;
10884 const char *description;
10885 apr_array_header_t *move_target_wc_abspaths;
10886 const char *moved_to_abspath;
10887
10888 details = conflict->tree_conflict_incoming_details;
10889 if (details == NULL || details->moves == NULL)
10890 return SVN_NO_ERROR;
10891
10892 if (apr_hash_count(details->wc_move_targets) == 0)
10893 return SVN_NO_ERROR;
10894
10895 move_target_wc_abspaths =
10896 svn_hash_gets(details->wc_move_targets,
10897 get_moved_to_repos_relpath(details, scratch_pool));
10898 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
10899 details->wc_move_target_idx,
10900 const char *);
10901 SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
10902 conflict, ctx,
10903 moved_to_abspath,
10904 scratch_pool,
10905 scratch_pool));
10906 add_resolution_option(options, conflict,
10907 svn_client_conflict_option_incoming_move_dir_merge,
10908 _("Move and merge"), description,
10909 resolve_incoming_move_dir_merge);
10910 }
10911
10912 return SVN_NO_ERROR;
10913 }
10914
10915 /* Configure 'local move file merge' resolution option for
10916 * a tree conflict. */
10917 static svn_error_t *
configure_option_local_move_file_or_dir_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)10918 configure_option_local_move_file_or_dir_merge(
10919 svn_client_conflict_t *conflict,
10920 svn_client_ctx_t *ctx,
10921 apr_array_header_t *options,
10922 apr_pool_t *scratch_pool)
10923 {
10924 svn_wc_operation_t operation;
10925 svn_wc_conflict_action_t incoming_change;
10926 svn_wc_conflict_reason_t local_change;
10927 const char *incoming_old_repos_relpath;
10928 svn_revnum_t incoming_old_pegrev;
10929 svn_node_kind_t incoming_old_kind;
10930 const char *incoming_new_repos_relpath;
10931 svn_revnum_t incoming_new_pegrev;
10932 svn_node_kind_t incoming_new_kind;
10933
10934 operation = svn_client_conflict_get_operation(conflict);
10935 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10936 local_change = svn_client_conflict_get_local_change(conflict);
10937 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
10938 &incoming_old_repos_relpath, &incoming_old_pegrev,
10939 &incoming_old_kind, conflict, scratch_pool,
10940 scratch_pool));
10941 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
10942 &incoming_new_repos_relpath, &incoming_new_pegrev,
10943 &incoming_new_kind, conflict, scratch_pool,
10944 scratch_pool));
10945
10946 if (operation == svn_wc_operation_merge &&
10947 incoming_change == svn_wc_conflict_action_edit &&
10948 local_change == svn_wc_conflict_reason_missing)
10949 {
10950 struct conflict_tree_local_missing_details *details;
10951 const char *wcroot_abspath;
10952
10953 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10954 conflict->local_abspath,
10955 scratch_pool, scratch_pool));
10956
10957 details = conflict->tree_conflict_local_details;
10958 if (details != NULL && details->moves != NULL &&
10959 details->move_target_repos_relpath != NULL)
10960 {
10961 apr_array_header_t *moves;
10962 const char *moved_to_abspath;
10963 const char *description;
10964
10965 moves = svn_hash_gets(details->wc_move_targets,
10966 details->move_target_repos_relpath);
10967 moved_to_abspath =
10968 APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *);
10969
10970 description =
10971 apr_psprintf(
10972 scratch_pool, _("apply changes to move destination '%s'"),
10973 svn_dirent_local_style(
10974 svn_dirent_skip_ancestor(wcroot_abspath, moved_to_abspath),
10975 scratch_pool));
10976
10977 if ((incoming_old_kind == svn_node_file ||
10978 incoming_old_kind == svn_node_none) &&
10979 (incoming_new_kind == svn_node_file ||
10980 incoming_new_kind == svn_node_none))
10981 {
10982 add_resolution_option(
10983 options, conflict,
10984 svn_client_conflict_option_local_move_file_text_merge,
10985 _("Apply to move destination"),
10986 description, resolve_local_move_file_merge);
10987 }
10988 else
10989 {
10990 add_resolution_option(
10991 options, conflict,
10992 svn_client_conflict_option_local_move_dir_merge,
10993 _("Apply to move destination"),
10994 description, resolve_local_move_dir_merge);
10995 }
10996 }
10997 }
10998
10999 return SVN_NO_ERROR;
11000 }
11001
11002 /* Configure 'sibling move file/dir merge' resolution option for
11003 * a tree conflict. */
11004 static svn_error_t *
configure_option_sibling_move_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)11005 configure_option_sibling_move_merge(svn_client_conflict_t *conflict,
11006 svn_client_ctx_t *ctx,
11007 apr_array_header_t *options,
11008 apr_pool_t *scratch_pool)
11009 {
11010 svn_wc_operation_t operation;
11011 svn_wc_conflict_action_t incoming_change;
11012 svn_wc_conflict_reason_t local_change;
11013 const char *incoming_old_repos_relpath;
11014 svn_revnum_t incoming_old_pegrev;
11015 svn_node_kind_t incoming_old_kind;
11016 const char *incoming_new_repos_relpath;
11017 svn_revnum_t incoming_new_pegrev;
11018 svn_node_kind_t incoming_new_kind;
11019
11020 operation = svn_client_conflict_get_operation(conflict);
11021 incoming_change = svn_client_conflict_get_incoming_change(conflict);
11022 local_change = svn_client_conflict_get_local_change(conflict);
11023 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
11024 &incoming_old_repos_relpath, &incoming_old_pegrev,
11025 &incoming_old_kind, conflict, scratch_pool,
11026 scratch_pool));
11027 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
11028 &incoming_new_repos_relpath, &incoming_new_pegrev,
11029 &incoming_new_kind, conflict, scratch_pool,
11030 scratch_pool));
11031
11032 if (operation == svn_wc_operation_merge &&
11033 incoming_change == svn_wc_conflict_action_edit &&
11034 local_change == svn_wc_conflict_reason_missing)
11035 {
11036 struct conflict_tree_local_missing_details *details;
11037 const char *wcroot_abspath;
11038
11039 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11040 conflict->local_abspath,
11041 scratch_pool, scratch_pool));
11042
11043 details = conflict->tree_conflict_local_details;
11044 if (details != NULL && details->wc_siblings != NULL)
11045 {
11046 const char *description;
11047 const char *sibling;
11048
11049 sibling =
11050 apr_pstrdup(conflict->pool,
11051 APR_ARRAY_IDX(details->wc_siblings,
11052 details->preferred_sibling_idx,
11053 const char *));
11054 description =
11055 apr_psprintf(
11056 scratch_pool, _("apply changes to '%s'"),
11057 svn_dirent_local_style(
11058 svn_dirent_skip_ancestor(wcroot_abspath, sibling),
11059 scratch_pool));
11060
11061 if ((incoming_old_kind == svn_node_file ||
11062 incoming_old_kind == svn_node_none) &&
11063 (incoming_new_kind == svn_node_file ||
11064 incoming_new_kind == svn_node_none))
11065 {
11066 add_resolution_option(
11067 options, conflict,
11068 svn_client_conflict_option_sibling_move_file_text_merge,
11069 _("Apply to corresponding local location"),
11070 description, resolve_local_move_file_merge);
11071 }
11072 else
11073 {
11074 add_resolution_option(
11075 options, conflict,
11076 svn_client_conflict_option_sibling_move_dir_merge,
11077 _("Apply to corresponding local location"),
11078 description, resolve_local_move_dir_merge);
11079 }
11080 }
11081 }
11082
11083 return SVN_NO_ERROR;
11084 }
11085
11086 struct conflict_tree_update_local_moved_away_details {
11087 /*
11088 * This array consists of "const char *" absolute paths to working copy
11089 * nodes which are uncomitted copies and correspond to the repository path
11090 * of the conflict victim.
11091 * Each such working copy node is a potential local move target which can
11092 * be chosen to find a suitable merge target when resolving a tree conflict.
11093 *
11094 * This may be an empty array in case if there is no move target path in
11095 * the working copy. */
11096 apr_array_header_t *wc_move_targets;
11097
11098 /* Current index into the list of working copy paths in WC_MOVE_TARGETS. */
11099 int preferred_move_target_idx;
11100 };
11101
11102 /* Implements conflict_option_resolve_func_t.
11103 * Resolve an incoming move vs local move conflict by merging from the
11104 * incoming move's target location to the local move's target location,
11105 * overriding the incoming move. The original local move was broken during
11106 * update/switch, so overriding the incoming move involves recording a new
11107 * move from the incoming move's target location to the local move's target
11108 * location. */
11109 static svn_error_t *
resolve_both_moved_file_update_keep_local_move(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)11110 resolve_both_moved_file_update_keep_local_move(
11111 svn_client_conflict_option_t *option,
11112 svn_client_conflict_t *conflict,
11113 svn_client_ctx_t *ctx,
11114 apr_pool_t *scratch_pool)
11115 {
11116 svn_client_conflict_option_id_t option_id;
11117 const char *victim_abspath;
11118 const char *local_moved_to_abspath;
11119 svn_wc_operation_t operation;
11120 const char *lock_abspath;
11121 svn_error_t *err;
11122 const char *repos_root_url;
11123 const char *incoming_old_repos_relpath;
11124 svn_revnum_t incoming_old_pegrev;
11125 const char *incoming_new_repos_relpath;
11126 svn_revnum_t incoming_new_pegrev;
11127 const char *wc_tmpdir;
11128 const char *ancestor_abspath;
11129 svn_stream_t *ancestor_stream;
11130 apr_hash_t *ancestor_props;
11131 apr_hash_t *incoming_props;
11132 apr_hash_t *local_props;
11133 const char *ancestor_url;
11134 const char *corrected_url;
11135 svn_ra_session_t *ra_session;
11136 svn_wc_merge_outcome_t merge_content_outcome;
11137 svn_wc_notify_state_t merge_props_outcome;
11138 apr_array_header_t *propdiffs;
11139 struct conflict_tree_incoming_delete_details *incoming_details;
11140 apr_array_header_t *possible_moved_to_abspaths;
11141 const char *incoming_moved_to_abspath;
11142 struct conflict_tree_update_local_moved_away_details *local_details;
11143
11144 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
11145 operation = svn_client_conflict_get_operation(conflict);
11146 incoming_details = conflict->tree_conflict_incoming_details;
11147 if (incoming_details == NULL || incoming_details->moves == NULL)
11148 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
11149 _("The specified conflict resolution option "
11150 "requires details for tree conflict at '%s' "
11151 "to be fetched from the repository first."),
11152 svn_dirent_local_style(victim_abspath,
11153 scratch_pool));
11154 if (operation == svn_wc_operation_none)
11155 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
11156 _("Invalid operation code '%d' recorded for "
11157 "conflict at '%s'"), operation,
11158 svn_dirent_local_style(victim_abspath,
11159 scratch_pool));
11160
11161 option_id = svn_client_conflict_option_get_id(option);
11162 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge);
11163
11164 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
11165 conflict, scratch_pool,
11166 scratch_pool));
11167 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
11168 &incoming_old_repos_relpath, &incoming_old_pegrev,
11169 NULL, conflict, scratch_pool,
11170 scratch_pool));
11171 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
11172 &incoming_new_repos_relpath, &incoming_new_pegrev,
11173 NULL, conflict, scratch_pool,
11174 scratch_pool));
11175
11176 /* Set up temporary storage for the common ancestor version of the file. */
11177 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
11178 scratch_pool, scratch_pool));
11179 SVN_ERR(svn_stream_open_unique(&ancestor_stream,
11180 &ancestor_abspath, wc_tmpdir,
11181 svn_io_file_del_on_pool_cleanup,
11182 scratch_pool, scratch_pool));
11183
11184 /* Fetch the ancestor file's content. */
11185 ancestor_url = svn_path_url_add_component2(repos_root_url,
11186 incoming_old_repos_relpath,
11187 scratch_pool);
11188 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
11189 ancestor_url, NULL, NULL,
11190 FALSE, FALSE, ctx,
11191 scratch_pool, scratch_pool));
11192 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
11193 ancestor_stream, NULL, /* fetched_rev */
11194 &ancestor_props, scratch_pool));
11195 filter_props(ancestor_props, scratch_pool);
11196
11197 /* Close stream to flush ancestor file to disk. */
11198 SVN_ERR(svn_stream_close(ancestor_stream));
11199
11200 possible_moved_to_abspaths =
11201 svn_hash_gets(incoming_details->wc_move_targets,
11202 get_moved_to_repos_relpath(incoming_details, scratch_pool));
11203 incoming_moved_to_abspath =
11204 APR_ARRAY_IDX(possible_moved_to_abspaths,
11205 incoming_details->wc_move_target_idx, const char *);
11206
11207 local_details = conflict->tree_conflict_local_details;
11208 local_moved_to_abspath =
11209 APR_ARRAY_IDX(local_details->wc_move_targets,
11210 local_details->preferred_move_target_idx, const char *);
11211
11212 /* ### The following WC modifications should be atomic. */
11213 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
11214 &lock_abspath, ctx->wc_ctx,
11215 svn_dirent_get_longest_ancestor(victim_abspath,
11216 local_moved_to_abspath,
11217 scratch_pool),
11218 scratch_pool, scratch_pool));
11219
11220 /* Get a copy of the incoming moved item's properties. */
11221 err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx,
11222 incoming_moved_to_abspath,
11223 scratch_pool, scratch_pool);
11224 if (err)
11225 goto unlock_wc;
11226
11227 /* Get a copy of the local move target's properties. */
11228 err = svn_wc_prop_list2(&local_props, ctx->wc_ctx,
11229 local_moved_to_abspath,
11230 scratch_pool, scratch_pool);
11231 if (err)
11232 goto unlock_wc;
11233
11234 /* Create a property diff for the files. */
11235 err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
11236 scratch_pool);
11237 if (err)
11238 goto unlock_wc;
11239
11240 /* Perform the file merge. */
11241 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
11242 ctx->wc_ctx, ancestor_abspath,
11243 incoming_moved_to_abspath, local_moved_to_abspath,
11244 NULL, NULL, NULL, /* labels */
11245 NULL, NULL, /* conflict versions */
11246 FALSE, /* dry run */
11247 NULL, NULL, /* diff3_cmd, merge_options */
11248 apr_hash_count(ancestor_props) ? ancestor_props : NULL,
11249 propdiffs,
11250 NULL, NULL, /* conflict func/baton */
11251 NULL, NULL, /* don't allow user to cancel here */
11252 scratch_pool);
11253 if (err)
11254 goto unlock_wc;
11255
11256 if (ctx->notify_func2)
11257 {
11258 svn_wc_notify_t *notify;
11259
11260 /* Tell the world about the file merge that just happened. */
11261 notify = svn_wc_create_notify(local_moved_to_abspath,
11262 svn_wc_notify_update_update,
11263 scratch_pool);
11264 if (merge_content_outcome == svn_wc_merge_conflict)
11265 notify->content_state = svn_wc_notify_state_conflicted;
11266 else
11267 notify->content_state = svn_wc_notify_state_merged;
11268 notify->prop_state = merge_props_outcome;
11269 notify->kind = svn_node_file;
11270 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
11271 }
11272
11273 /* Record a new move which overrides the incoming move. */
11274 err = svn_wc__move2(ctx->wc_ctx, incoming_moved_to_abspath,
11275 local_moved_to_abspath,
11276 TRUE, /* meta-data only move */
11277 FALSE, /* mixed-revisions don't apply to files */
11278 NULL, NULL, /* don't allow user to cancel here */
11279 NULL, NULL, /* no extra notification */
11280 scratch_pool);
11281 if (err)
11282 goto unlock_wc;
11283
11284 /* Remove moved-away file from disk. */
11285 err = svn_io_remove_file2(incoming_moved_to_abspath, TRUE, scratch_pool);
11286 if (err)
11287 goto unlock_wc;
11288
11289 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
11290 if (err)
11291 goto unlock_wc;
11292
11293 if (ctx->notify_func2)
11294 {
11295 svn_wc_notify_t *notify;
11296
11297 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
11298 scratch_pool);
11299 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
11300 }
11301
11302 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
11303
11304 conflict->resolution_tree = option_id;
11305
11306 unlock_wc:
11307 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
11308 lock_abspath,
11309 scratch_pool));
11310 SVN_ERR(err);
11311
11312 return SVN_NO_ERROR;
11313 }
11314
11315 /* Implements conflict_option_resolve_func_t.
11316 * Resolve an incoming move vs local move conflict by merging from the
11317 * local move's target location to the incoming move's target location,
11318 * and reverting the local move. */
11319 static svn_error_t *
resolve_both_moved_file_update_keep_incoming_move(svn_client_conflict_option_t * option,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)11320 resolve_both_moved_file_update_keep_incoming_move(
11321 svn_client_conflict_option_t *option,
11322 svn_client_conflict_t *conflict,
11323 svn_client_ctx_t *ctx,
11324 apr_pool_t *scratch_pool)
11325 {
11326 svn_client_conflict_option_id_t option_id;
11327 const char *victim_abspath;
11328 const char *local_moved_to_abspath;
11329 svn_wc_operation_t operation;
11330 const char *lock_abspath;
11331 svn_error_t *err;
11332 const char *repos_root_url;
11333 const char *incoming_old_repos_relpath;
11334 svn_revnum_t incoming_old_pegrev;
11335 const char *incoming_new_repos_relpath;
11336 svn_revnum_t incoming_new_pegrev;
11337 const char *wc_tmpdir;
11338 const char *ancestor_abspath;
11339 svn_stream_t *ancestor_stream;
11340 apr_hash_t *ancestor_props;
11341 apr_hash_t *incoming_props;
11342 apr_hash_t *local_props;
11343 const char *ancestor_url;
11344 const char *corrected_url;
11345 svn_ra_session_t *ra_session;
11346 svn_wc_merge_outcome_t merge_content_outcome;
11347 svn_wc_notify_state_t merge_props_outcome;
11348 apr_array_header_t *propdiffs;
11349 struct conflict_tree_incoming_delete_details *incoming_details;
11350 apr_array_header_t *possible_moved_to_abspaths;
11351 const char *incoming_moved_to_abspath;
11352 struct conflict_tree_update_local_moved_away_details *local_details;
11353
11354 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
11355 operation = svn_client_conflict_get_operation(conflict);
11356 incoming_details = conflict->tree_conflict_incoming_details;
11357 if (incoming_details == NULL || incoming_details->moves == NULL)
11358 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
11359 _("The specified conflict resolution option "
11360 "requires details for tree conflict at '%s' "
11361 "to be fetched from the repository first."),
11362 svn_dirent_local_style(victim_abspath,
11363 scratch_pool));
11364 if (operation == svn_wc_operation_none)
11365 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
11366 _("Invalid operation code '%d' recorded for "
11367 "conflict at '%s'"), operation,
11368 svn_dirent_local_style(victim_abspath,
11369 scratch_pool));
11370
11371 option_id = svn_client_conflict_option_get_id(option);
11372 SVN_ERR_ASSERT(option_id ==
11373 svn_client_conflict_option_both_moved_file_move_merge);
11374
11375 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
11376 conflict, scratch_pool,
11377 scratch_pool));
11378 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
11379 &incoming_old_repos_relpath, &incoming_old_pegrev,
11380 NULL, conflict, scratch_pool,
11381 scratch_pool));
11382 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
11383 &incoming_new_repos_relpath, &incoming_new_pegrev,
11384 NULL, conflict, scratch_pool,
11385 scratch_pool));
11386
11387 /* Set up temporary storage for the common ancestor version of the file. */
11388 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
11389 scratch_pool, scratch_pool));
11390 SVN_ERR(svn_stream_open_unique(&ancestor_stream,
11391 &ancestor_abspath, wc_tmpdir,
11392 svn_io_file_del_on_pool_cleanup,
11393 scratch_pool, scratch_pool));
11394
11395 /* Fetch the ancestor file's content. */
11396 ancestor_url = svn_path_url_add_component2(repos_root_url,
11397 incoming_old_repos_relpath,
11398 scratch_pool);
11399 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
11400 ancestor_url, NULL, NULL,
11401 FALSE, FALSE, ctx,
11402 scratch_pool, scratch_pool));
11403 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
11404 ancestor_stream, NULL, /* fetched_rev */
11405 &ancestor_props, scratch_pool));
11406 filter_props(ancestor_props, scratch_pool);
11407
11408 /* Close stream to flush ancestor file to disk. */
11409 SVN_ERR(svn_stream_close(ancestor_stream));
11410
11411 possible_moved_to_abspaths =
11412 svn_hash_gets(incoming_details->wc_move_targets,
11413 get_moved_to_repos_relpath(incoming_details, scratch_pool));
11414 incoming_moved_to_abspath =
11415 APR_ARRAY_IDX(possible_moved_to_abspaths,
11416 incoming_details->wc_move_target_idx, const char *);
11417
11418 local_details = conflict->tree_conflict_local_details;
11419 local_moved_to_abspath =
11420 APR_ARRAY_IDX(local_details->wc_move_targets,
11421 local_details->preferred_move_target_idx, const char *);
11422
11423 /* ### The following WC modifications should be atomic. */
11424 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
11425 &lock_abspath, ctx->wc_ctx,
11426 svn_dirent_get_longest_ancestor(victim_abspath,
11427 local_moved_to_abspath,
11428 scratch_pool),
11429 scratch_pool, scratch_pool));
11430
11431 /* Get a copy of the incoming moved item's properties. */
11432 err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx,
11433 incoming_moved_to_abspath,
11434 scratch_pool, scratch_pool);
11435 if (err)
11436 goto unlock_wc;
11437
11438 /* Get a copy of the local move target's properties. */
11439 err = svn_wc_prop_list2(&local_props, ctx->wc_ctx,
11440 local_moved_to_abspath,
11441 scratch_pool, scratch_pool);
11442 if (err)
11443 goto unlock_wc;
11444
11445 /* Create a property diff for the files. */
11446 err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
11447 scratch_pool);
11448 if (err)
11449 goto unlock_wc;
11450
11451 /* Perform the file merge. */
11452 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
11453 ctx->wc_ctx, ancestor_abspath,
11454 local_moved_to_abspath, incoming_moved_to_abspath,
11455 NULL, NULL, NULL, /* labels */
11456 NULL, NULL, /* conflict versions */
11457 FALSE, /* dry run */
11458 NULL, NULL, /* diff3_cmd, merge_options */
11459 apr_hash_count(ancestor_props) ? ancestor_props : NULL,
11460 propdiffs,
11461 NULL, NULL, /* conflict func/baton */
11462 NULL, NULL, /* don't allow user to cancel here */
11463 scratch_pool);
11464 if (err)
11465 goto unlock_wc;
11466
11467 if (ctx->notify_func2)
11468 {
11469 svn_wc_notify_t *notify;
11470
11471 /* Tell the world about the file merge that just happened. */
11472 notify = svn_wc_create_notify(local_moved_to_abspath,
11473 svn_wc_notify_update_update,
11474 scratch_pool);
11475 if (merge_content_outcome == svn_wc_merge_conflict)
11476 notify->content_state = svn_wc_notify_state_conflicted;
11477 else
11478 notify->content_state = svn_wc_notify_state_merged;
11479 notify->prop_state = merge_props_outcome;
11480 notify->kind = svn_node_file;
11481 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
11482 }
11483
11484 /* Revert the copy-half of the local move. The delete-half of this move
11485 * has already been deleted during the update/switch operation. */
11486 err = svn_wc_revert6(ctx->wc_ctx, local_moved_to_abspath, svn_depth_empty,
11487 FALSE, NULL, TRUE, FALSE,
11488 TRUE /*added_keep_local*/,
11489 NULL, NULL, /* no cancellation */
11490 ctx->notify_func2, ctx->notify_baton2,
11491 scratch_pool);
11492 if (err)
11493 goto unlock_wc;
11494
11495 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
11496 if (err)
11497 goto unlock_wc;
11498
11499 if (ctx->notify_func2)
11500 {
11501 svn_wc_notify_t *notify;
11502
11503 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
11504 scratch_pool);
11505 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
11506 }
11507
11508 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
11509
11510 conflict->resolution_tree = option_id;
11511
11512 unlock_wc:
11513 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
11514 lock_abspath,
11515 scratch_pool));
11516 SVN_ERR(err);
11517
11518 return SVN_NO_ERROR;
11519 }
11520
11521 /* Implements tree_conflict_get_details_func_t. */
11522 static svn_error_t *
conflict_tree_get_details_update_local_moved_away(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)11523 conflict_tree_get_details_update_local_moved_away(
11524 svn_client_conflict_t *conflict,
11525 svn_client_ctx_t *ctx,
11526 apr_pool_t *scratch_pool)
11527 {
11528 struct conflict_tree_update_local_moved_away_details *details;
11529 const char *incoming_old_repos_relpath;
11530 svn_node_kind_t incoming_old_kind;
11531
11532 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
11533 &incoming_old_repos_relpath, NULL, &incoming_old_kind,
11534 conflict, scratch_pool, scratch_pool));
11535
11536 details = apr_pcalloc(conflict->pool, sizeof(*details));
11537
11538 details->wc_move_targets = apr_array_make(conflict->pool, 1,
11539 sizeof(const char *));
11540
11541 /* Search the WC for copies of the conflict victim. */
11542 SVN_ERR(svn_wc__find_copies_of_repos_path(&details->wc_move_targets,
11543 conflict->local_abspath,
11544 incoming_old_repos_relpath,
11545 incoming_old_kind,
11546 ctx->wc_ctx,
11547 conflict->pool,
11548 scratch_pool));
11549
11550 conflict->tree_conflict_local_details = details;
11551
11552 return SVN_NO_ERROR;
11553 }
11554
11555 static svn_error_t *
get_both_moved_file_paths(const char ** incoming_moved_to_abspath,const char ** local_moved_to_abspath,svn_client_conflict_t * conflict,apr_pool_t * scratch_pool)11556 get_both_moved_file_paths(const char **incoming_moved_to_abspath,
11557 const char **local_moved_to_abspath,
11558 svn_client_conflict_t *conflict,
11559 apr_pool_t *scratch_pool)
11560 {
11561 struct conflict_tree_incoming_delete_details *incoming_details;
11562 apr_array_header_t *incoming_move_target_wc_abspaths;
11563 svn_wc_operation_t operation;
11564
11565 operation = svn_client_conflict_get_operation(conflict);
11566
11567 *incoming_moved_to_abspath = NULL;
11568 *local_moved_to_abspath = NULL;
11569
11570 incoming_details = conflict->tree_conflict_incoming_details;
11571 if (incoming_details == NULL || incoming_details->moves == NULL ||
11572 apr_hash_count(incoming_details->wc_move_targets) == 0)
11573 return SVN_NO_ERROR;
11574
11575 incoming_move_target_wc_abspaths =
11576 svn_hash_gets(incoming_details->wc_move_targets,
11577 get_moved_to_repos_relpath(incoming_details,
11578 scratch_pool));
11579 *incoming_moved_to_abspath =
11580 APR_ARRAY_IDX(incoming_move_target_wc_abspaths,
11581 incoming_details->wc_move_target_idx, const char *);
11582
11583 if (operation == svn_wc_operation_merge)
11584 {
11585 struct conflict_tree_local_missing_details *local_details;
11586 apr_array_header_t *local_moves;
11587
11588 local_details = conflict->tree_conflict_local_details;
11589 if (local_details == NULL ||
11590 apr_hash_count(local_details->wc_move_targets) == 0)
11591 return SVN_NO_ERROR;
11592
11593 local_moves = svn_hash_gets(local_details->wc_move_targets,
11594 local_details->move_target_repos_relpath);
11595 *local_moved_to_abspath =
11596 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx,
11597 const char *);
11598 }
11599 else
11600 {
11601 struct conflict_tree_update_local_moved_away_details *local_details;
11602
11603 local_details = conflict->tree_conflict_local_details;
11604 if (local_details == NULL ||
11605 local_details->wc_move_targets->nelts == 0)
11606 return SVN_NO_ERROR;
11607
11608 *local_moved_to_abspath =
11609 APR_ARRAY_IDX(local_details->wc_move_targets,
11610 local_details->preferred_move_target_idx,
11611 const char *);
11612 }
11613
11614 return SVN_NO_ERROR;
11615 }
11616
11617 static svn_error_t *
conflict_tree_get_description_update_both_moved_file_merge(const char ** description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)11618 conflict_tree_get_description_update_both_moved_file_merge(
11619 const char **description,
11620 svn_client_conflict_t *conflict,
11621 svn_client_ctx_t *ctx,
11622 apr_pool_t *result_pool,
11623 apr_pool_t *scratch_pool)
11624 {
11625 const char *incoming_moved_to_abspath;
11626 const char *local_moved_to_abspath;
11627 svn_wc_operation_t operation;
11628 const char *wcroot_abspath;
11629
11630 *description = NULL;
11631
11632 SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath,
11633 &local_moved_to_abspath,
11634 conflict, scratch_pool));
11635 if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL)
11636 return SVN_NO_ERROR;
11637
11638 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11639 conflict->local_abspath, scratch_pool,
11640 scratch_pool));
11641
11642 operation = svn_client_conflict_get_operation(conflict);
11643
11644 if (operation == svn_wc_operation_merge)
11645 {
11646 /* In case of a merge, the incoming move has A+ (copied) status... */
11647 *description =
11648 apr_psprintf(
11649 scratch_pool,
11650 _("apply changes to '%s' and revert addition of '%s'"),
11651 svn_dirent_local_style(
11652 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
11653 scratch_pool),
11654 svn_dirent_local_style(
11655 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
11656 scratch_pool));
11657 }
11658 else
11659 {
11660 /* ...but in case of update/switch the local move has "A+" status. */
11661 *description =
11662 apr_psprintf(
11663 scratch_pool,
11664 _("override incoming move and merge incoming changes from '%s' "
11665 "to '%s'"),
11666 svn_dirent_local_style(
11667 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
11668 scratch_pool),
11669 svn_dirent_local_style(
11670 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
11671 scratch_pool));
11672 }
11673
11674 return SVN_NO_ERROR;
11675 }
11676
11677 static svn_error_t *
conflict_tree_get_description_update_both_moved_file_move_merge(const char ** description,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)11678 conflict_tree_get_description_update_both_moved_file_move_merge(
11679 const char **description,
11680 svn_client_conflict_t *conflict,
11681 svn_client_ctx_t *ctx,
11682 apr_pool_t *result_pool,
11683 apr_pool_t *scratch_pool)
11684 {
11685 const char *incoming_moved_to_abspath;
11686 const char *local_moved_to_abspath;
11687 svn_wc_operation_t operation;
11688 const char *wcroot_abspath;
11689
11690 *description = NULL;
11691
11692 SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath,
11693 &local_moved_to_abspath,
11694 conflict, scratch_pool));
11695 if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL)
11696 return SVN_NO_ERROR;
11697
11698 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11699 conflict->local_abspath, scratch_pool,
11700 scratch_pool));
11701
11702 operation = svn_client_conflict_get_operation(conflict);
11703
11704 if (operation == svn_wc_operation_merge)
11705 {
11706 SVN_ERR(describe_incoming_move_merge_conflict_option(
11707 description, conflict, ctx, local_moved_to_abspath,
11708 scratch_pool, scratch_pool));
11709 }
11710 else
11711 {
11712 *description =
11713 apr_psprintf(
11714 scratch_pool,
11715 _("accept incoming move and merge local changes from "
11716 "'%s' to '%s'"),
11717 svn_dirent_local_style(
11718 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
11719 scratch_pool),
11720 svn_dirent_local_style(
11721 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
11722 scratch_pool));
11723 }
11724
11725 return SVN_NO_ERROR;
11726 }
11727
11728 /* Configure 'both moved file merge' resolution options for a tree conflict. */
11729 static svn_error_t *
configure_option_both_moved_file_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)11730 configure_option_both_moved_file_merge(svn_client_conflict_t *conflict,
11731 svn_client_ctx_t *ctx,
11732 apr_array_header_t *options,
11733 apr_pool_t *scratch_pool)
11734 {
11735 svn_wc_operation_t operation;
11736 svn_node_kind_t victim_node_kind;
11737 svn_wc_conflict_action_t incoming_change;
11738 svn_wc_conflict_reason_t local_change;
11739 const char *incoming_old_repos_relpath;
11740 svn_revnum_t incoming_old_pegrev;
11741 svn_node_kind_t incoming_old_kind;
11742 const char *incoming_new_repos_relpath;
11743 svn_revnum_t incoming_new_pegrev;
11744 svn_node_kind_t incoming_new_kind;
11745 const char *wcroot_abspath;
11746
11747 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11748 conflict->local_abspath, scratch_pool,
11749 scratch_pool));
11750
11751 operation = svn_client_conflict_get_operation(conflict);
11752 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
11753 incoming_change = svn_client_conflict_get_incoming_change(conflict);
11754 local_change = svn_client_conflict_get_local_change(conflict);
11755 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
11756 &incoming_old_repos_relpath, &incoming_old_pegrev,
11757 &incoming_old_kind, conflict, scratch_pool,
11758 scratch_pool));
11759 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
11760 &incoming_new_repos_relpath, &incoming_new_pegrev,
11761 &incoming_new_kind, conflict, scratch_pool,
11762 scratch_pool));
11763
11764 /* ### what about the switch operation? */
11765 if (((operation == svn_wc_operation_merge &&
11766 victim_node_kind == svn_node_none) ||
11767 (operation == svn_wc_operation_update &&
11768 victim_node_kind == svn_node_file)) &&
11769 incoming_old_kind == svn_node_file &&
11770 incoming_new_kind == svn_node_none &&
11771 ((operation == svn_wc_operation_merge &&
11772 local_change == svn_wc_conflict_reason_missing) ||
11773 (operation == svn_wc_operation_update &&
11774 local_change == svn_wc_conflict_reason_moved_away)) &&
11775 incoming_change == svn_wc_conflict_action_delete)
11776 {
11777 const char *description;
11778
11779 SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge(
11780 &description, conflict, ctx, conflict->pool, scratch_pool));
11781
11782 if (description == NULL) /* details not fetched yet */
11783 return SVN_NO_ERROR;
11784
11785 add_resolution_option(
11786 options, conflict, svn_client_conflict_option_both_moved_file_merge,
11787 _("Merge to corresponding local location"),
11788 description,
11789 operation == svn_wc_operation_merge ?
11790 resolve_both_moved_file_text_merge :
11791 resolve_both_moved_file_update_keep_local_move);
11792
11793 SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge(
11794 &description, conflict, ctx, conflict->pool, scratch_pool));
11795
11796 add_resolution_option(options, conflict,
11797 svn_client_conflict_option_both_moved_file_move_merge,
11798 _("Move and merge"), description,
11799 operation == svn_wc_operation_merge ?
11800 resolve_incoming_move_file_text_merge :
11801 resolve_both_moved_file_update_keep_incoming_move);
11802 }
11803
11804 return SVN_NO_ERROR;
11805 }
11806
11807 /* Configure 'both moved dir merge' resolution options for a tree conflict. */
11808 static svn_error_t *
configure_option_both_moved_dir_merge(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_array_header_t * options,apr_pool_t * scratch_pool)11809 configure_option_both_moved_dir_merge(svn_client_conflict_t *conflict,
11810 svn_client_ctx_t *ctx,
11811 apr_array_header_t *options,
11812 apr_pool_t *scratch_pool)
11813 {
11814 svn_wc_operation_t operation;
11815 svn_node_kind_t victim_node_kind;
11816 svn_wc_conflict_action_t incoming_change;
11817 svn_wc_conflict_reason_t local_change;
11818 const char *incoming_old_repos_relpath;
11819 svn_revnum_t incoming_old_pegrev;
11820 svn_node_kind_t incoming_old_kind;
11821 const char *incoming_new_repos_relpath;
11822 svn_revnum_t incoming_new_pegrev;
11823 svn_node_kind_t incoming_new_kind;
11824 const char *wcroot_abspath;
11825
11826 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11827 conflict->local_abspath, scratch_pool,
11828 scratch_pool));
11829
11830 operation = svn_client_conflict_get_operation(conflict);
11831 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
11832 incoming_change = svn_client_conflict_get_incoming_change(conflict);
11833 local_change = svn_client_conflict_get_local_change(conflict);
11834 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
11835 &incoming_old_repos_relpath, &incoming_old_pegrev,
11836 &incoming_old_kind, conflict, scratch_pool,
11837 scratch_pool));
11838 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
11839 &incoming_new_repos_relpath, &incoming_new_pegrev,
11840 &incoming_new_kind, conflict, scratch_pool,
11841 scratch_pool));
11842
11843 if (operation == svn_wc_operation_merge &&
11844 victim_node_kind == svn_node_none &&
11845 incoming_old_kind == svn_node_dir &&
11846 incoming_new_kind == svn_node_none &&
11847 local_change == svn_wc_conflict_reason_missing &&
11848 incoming_change == svn_wc_conflict_action_delete)
11849 {
11850 struct conflict_tree_incoming_delete_details *incoming_details;
11851 struct conflict_tree_local_missing_details *local_details;
11852 const char *description;
11853 apr_array_header_t *local_moves;
11854 const char *local_moved_to_abspath;
11855 const char *incoming_moved_to_abspath;
11856 apr_array_header_t *incoming_move_target_wc_abspaths;
11857
11858 incoming_details = conflict->tree_conflict_incoming_details;
11859 if (incoming_details == NULL || incoming_details->moves == NULL ||
11860 apr_hash_count(incoming_details->wc_move_targets) == 0)
11861 return SVN_NO_ERROR;
11862
11863 local_details = conflict->tree_conflict_local_details;
11864 if (local_details == NULL ||
11865 apr_hash_count(local_details->wc_move_targets) == 0)
11866 return SVN_NO_ERROR;
11867
11868 local_moves = svn_hash_gets(local_details->wc_move_targets,
11869 local_details->move_target_repos_relpath);
11870 local_moved_to_abspath =
11871 APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx,
11872 const char *);
11873
11874 incoming_move_target_wc_abspaths =
11875 svn_hash_gets(incoming_details->wc_move_targets,
11876 get_moved_to_repos_relpath(incoming_details,
11877 scratch_pool));
11878 incoming_moved_to_abspath =
11879 APR_ARRAY_IDX(incoming_move_target_wc_abspaths,
11880 incoming_details->wc_move_target_idx, const char *);
11881
11882 description =
11883 apr_psprintf(
11884 scratch_pool, _("apply changes to '%s' and revert addition of '%s'"),
11885 svn_dirent_local_style(
11886 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
11887 scratch_pool),
11888 svn_dirent_local_style(
11889 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
11890 scratch_pool));
11891 add_resolution_option(
11892 options, conflict, svn_client_conflict_option_both_moved_dir_merge,
11893 _("Merge to corresponding local location"),
11894 description, resolve_both_moved_dir_merge);
11895
11896 SVN_ERR(describe_incoming_move_merge_conflict_option(
11897 &description, conflict, ctx, local_moved_to_abspath,
11898 scratch_pool, scratch_pool));
11899 add_resolution_option(options, conflict,
11900 svn_client_conflict_option_both_moved_dir_move_merge,
11901 _("Move and merge"), description,
11902 resolve_both_moved_dir_move_merge);
11903 }
11904
11905 return SVN_NO_ERROR;
11906 }
11907
11908 /* Return a copy of the repos replath candidate list. */
11909 static svn_error_t *
get_repos_relpath_candidates(apr_array_header_t ** possible_moved_to_repos_relpaths,apr_hash_t * wc_move_targets,apr_pool_t * result_pool,apr_pool_t * scratch_pool)11910 get_repos_relpath_candidates(
11911 apr_array_header_t **possible_moved_to_repos_relpaths,
11912 apr_hash_t *wc_move_targets,
11913 apr_pool_t *result_pool,
11914 apr_pool_t *scratch_pool)
11915 {
11916 apr_array_header_t *sorted_repos_relpaths;
11917 int i;
11918
11919 sorted_repos_relpaths = svn_sort__hash(wc_move_targets,
11920 svn_sort_compare_items_as_paths,
11921 scratch_pool);
11922
11923 *possible_moved_to_repos_relpaths =
11924 apr_array_make(result_pool, sorted_repos_relpaths->nelts,
11925 sizeof (const char *));
11926 for (i = 0; i < sorted_repos_relpaths->nelts; i++)
11927 {
11928 svn_sort__item_t item;
11929 const char *repos_relpath;
11930
11931 item = APR_ARRAY_IDX(sorted_repos_relpaths, i, svn_sort__item_t);
11932 repos_relpath = item.key;
11933 APR_ARRAY_PUSH(*possible_moved_to_repos_relpaths, const char *) =
11934 apr_pstrdup(result_pool, repos_relpath);
11935 }
11936
11937 return SVN_NO_ERROR;
11938 }
11939
11940 svn_error_t *
svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(apr_array_header_t ** possible_moved_to_repos_relpaths,svn_client_conflict_option_t * option,apr_pool_t * result_pool,apr_pool_t * scratch_pool)11941 svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(
11942 apr_array_header_t **possible_moved_to_repos_relpaths,
11943 svn_client_conflict_option_t *option,
11944 apr_pool_t *result_pool,
11945 apr_pool_t *scratch_pool)
11946 {
11947 svn_client_conflict_t *conflict = option->conflict;
11948 const char *victim_abspath;
11949 svn_wc_operation_t operation;
11950 svn_wc_conflict_action_t incoming_change;
11951 svn_wc_conflict_reason_t local_change;
11952 svn_client_conflict_option_id_t id;
11953
11954 id = svn_client_conflict_option_get_id(option);
11955 if (id != svn_client_conflict_option_incoming_move_file_text_merge &&
11956 id != svn_client_conflict_option_incoming_move_dir_merge &&
11957 id != svn_client_conflict_option_local_move_file_text_merge &&
11958 id != svn_client_conflict_option_local_move_dir_merge &&
11959 id != svn_client_conflict_option_sibling_move_file_text_merge &&
11960 id != svn_client_conflict_option_sibling_move_dir_merge &&
11961 id != svn_client_conflict_option_both_moved_file_merge &&
11962 id != svn_client_conflict_option_both_moved_file_move_merge &&
11963 id != svn_client_conflict_option_both_moved_dir_merge &&
11964 id != svn_client_conflict_option_both_moved_dir_move_merge)
11965 {
11966 /* We cannot operate on this option. */
11967 *possible_moved_to_repos_relpaths = NULL;
11968 return SVN_NO_ERROR;
11969 }
11970
11971 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
11972 operation = svn_client_conflict_get_operation(conflict);
11973 incoming_change = svn_client_conflict_get_incoming_change(conflict);
11974 local_change = svn_client_conflict_get_local_change(conflict);
11975
11976 if (operation == svn_wc_operation_merge &&
11977 incoming_change == svn_wc_conflict_action_edit &&
11978 local_change == svn_wc_conflict_reason_missing)
11979 {
11980 struct conflict_tree_local_missing_details *details;
11981
11982 details = conflict->tree_conflict_local_details;
11983 if (details == NULL ||
11984 (details->wc_move_targets == NULL && details->wc_siblings == NULL))
11985 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
11986 _("Getting a list of possible move targets "
11987 "requires details for tree conflict at '%s' "
11988 "to be fetched from the repository first"),
11989 svn_dirent_local_style(victim_abspath,
11990 scratch_pool));
11991
11992 if (details->wc_move_targets)
11993 SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths,
11994 details->wc_move_targets,
11995 result_pool, scratch_pool));
11996 else
11997 *possible_moved_to_repos_relpaths = NULL;
11998 }
11999 else
12000 {
12001 struct conflict_tree_incoming_delete_details *details;
12002
12003 details = conflict->tree_conflict_incoming_details;
12004 if (details == NULL || details->wc_move_targets == NULL)
12005 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12006 _("Getting a list of possible move targets "
12007 "requires details for tree conflict at '%s' "
12008 "to be fetched from the repository first"),
12009 svn_dirent_local_style(victim_abspath,
12010 scratch_pool));
12011
12012 SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths,
12013 details->wc_move_targets,
12014 result_pool, scratch_pool));
12015 }
12016
12017 return SVN_NO_ERROR;
12018 }
12019
12020 svn_error_t *
svn_client_conflict_option_get_moved_to_repos_relpath_candidates(apr_array_header_t ** possible_moved_to_repos_relpaths,svn_client_conflict_option_t * option,apr_pool_t * result_pool,apr_pool_t * scratch_pool)12021 svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
12022 apr_array_header_t **possible_moved_to_repos_relpaths,
12023 svn_client_conflict_option_t *option,
12024 apr_pool_t *result_pool,
12025 apr_pool_t *scratch_pool)
12026 {
12027 /* The only difference to API version 2 is an assertion failure if
12028 * an unexpected option is passed.
12029 * We do not emulate this old behaviour since clients written against
12030 * the previous API will just keep working. */
12031 return svn_error_trace(
12032 svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(
12033 possible_moved_to_repos_relpaths, option, result_pool, scratch_pool));
12034 }
12035
12036 static svn_error_t *
set_wc_move_target(const char ** new_hash_key,apr_hash_t * wc_move_targets,int preferred_move_target_idx,const char * victim_abspath,apr_pool_t * scratch_pool)12037 set_wc_move_target(const char **new_hash_key,
12038 apr_hash_t *wc_move_targets,
12039 int preferred_move_target_idx,
12040 const char *victim_abspath,
12041 apr_pool_t *scratch_pool)
12042 {
12043 apr_array_header_t *move_target_repos_relpaths;
12044 svn_sort__item_t item;
12045 const char *move_target_repos_relpath;
12046 apr_hash_index_t *hi;
12047
12048 if (preferred_move_target_idx < 0 ||
12049 preferred_move_target_idx >= apr_hash_count(wc_move_targets))
12050 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
12051 _("Index '%d' is out of bounds of the possible "
12052 "move target list for '%s'"),
12053 preferred_move_target_idx,
12054 svn_dirent_local_style(victim_abspath,
12055 scratch_pool));
12056
12057 /* Translate the index back into a hash table key. */
12058 move_target_repos_relpaths = svn_sort__hash(wc_move_targets,
12059 svn_sort_compare_items_as_paths,
12060 scratch_pool);
12061 item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx,
12062 svn_sort__item_t);
12063 move_target_repos_relpath = item.key;
12064 /* Find our copy of the hash key and remember the user's preference. */
12065 for (hi = apr_hash_first(scratch_pool, wc_move_targets);
12066 hi != NULL;
12067 hi = apr_hash_next(hi))
12068 {
12069 const char *repos_relpath = apr_hash_this_key(hi);
12070
12071 if (strcmp(move_target_repos_relpath, repos_relpath) == 0)
12072 {
12073 *new_hash_key = repos_relpath;
12074 return SVN_NO_ERROR;
12075 }
12076 }
12077
12078 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
12079 _("Repository path '%s' not found in list of "
12080 "possible move targets for '%s'"),
12081 move_target_repos_relpath,
12082 svn_dirent_local_style(victim_abspath,
12083 scratch_pool));
12084 }
12085
12086 svn_error_t *
svn_client_conflict_option_set_moved_to_repos_relpath2(svn_client_conflict_option_t * option,int preferred_move_target_idx,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12087 svn_client_conflict_option_set_moved_to_repos_relpath2(
12088 svn_client_conflict_option_t *option,
12089 int preferred_move_target_idx,
12090 svn_client_ctx_t *ctx,
12091 apr_pool_t *scratch_pool)
12092 {
12093 svn_client_conflict_t *conflict = option->conflict;
12094 const char *victim_abspath;
12095 svn_wc_operation_t operation;
12096 svn_wc_conflict_action_t incoming_change;
12097 svn_wc_conflict_reason_t local_change;
12098 svn_client_conflict_option_id_t id;
12099
12100 id = svn_client_conflict_option_get_id(option);
12101 if (id != svn_client_conflict_option_incoming_move_file_text_merge &&
12102 id != svn_client_conflict_option_incoming_move_dir_merge &&
12103 id != svn_client_conflict_option_local_move_file_text_merge &&
12104 id != svn_client_conflict_option_local_move_dir_merge &&
12105 id != svn_client_conflict_option_sibling_move_file_text_merge &&
12106 id != svn_client_conflict_option_sibling_move_dir_merge &&
12107 id != svn_client_conflict_option_both_moved_file_merge &&
12108 id != svn_client_conflict_option_both_moved_file_move_merge &&
12109 id != svn_client_conflict_option_both_moved_dir_merge &&
12110 id != svn_client_conflict_option_both_moved_dir_move_merge)
12111 return SVN_NO_ERROR; /* We cannot operate on this option. Nothing to do. */
12112
12113 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
12114 operation = svn_client_conflict_get_operation(conflict);
12115 incoming_change = svn_client_conflict_get_incoming_change(conflict);
12116 local_change = svn_client_conflict_get_local_change(conflict);
12117
12118 if (operation == svn_wc_operation_merge &&
12119 incoming_change == svn_wc_conflict_action_edit &&
12120 local_change == svn_wc_conflict_reason_missing)
12121 {
12122 struct conflict_tree_local_missing_details *details;
12123
12124 details = conflict->tree_conflict_local_details;
12125 if (details == NULL || details->wc_move_targets == NULL)
12126 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12127 _("Setting a move target requires details "
12128 "for tree conflict at '%s' to be fetched "
12129 "from the repository first"),
12130 svn_dirent_local_style(victim_abspath,
12131 scratch_pool));
12132
12133 SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath,
12134 details->wc_move_targets,
12135 preferred_move_target_idx,
12136 victim_abspath, scratch_pool));
12137 details->wc_move_target_idx = 0;
12138
12139 /* Update option description. */
12140 SVN_ERR(conflict_tree_get_description_local_missing(
12141 &option->description, conflict, ctx,
12142 conflict->pool, scratch_pool));
12143 }
12144 else
12145 {
12146 struct conflict_tree_incoming_delete_details *details;
12147 apr_array_header_t *move_target_wc_abspaths;
12148 const char *moved_to_abspath;
12149
12150 details = conflict->tree_conflict_incoming_details;
12151 if (details == NULL || details->wc_move_targets == NULL)
12152 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12153 _("Setting a move target requires details "
12154 "for tree conflict at '%s' to be fetched "
12155 "from the repository first"),
12156 svn_dirent_local_style(victim_abspath,
12157 scratch_pool));
12158
12159 SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath,
12160 details->wc_move_targets,
12161 preferred_move_target_idx,
12162 victim_abspath, scratch_pool));
12163 details->wc_move_target_idx = 0;
12164
12165 /* Update option description. */
12166 move_target_wc_abspaths =
12167 svn_hash_gets(details->wc_move_targets,
12168 get_moved_to_repos_relpath(details, scratch_pool));
12169 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
12170 details->wc_move_target_idx,
12171 const char *);
12172 SVN_ERR(describe_incoming_move_merge_conflict_option(
12173 &option->description,
12174 conflict, ctx,
12175 moved_to_abspath,
12176 conflict->pool,
12177 scratch_pool));
12178 }
12179
12180 return SVN_NO_ERROR;
12181 }
12182
12183 svn_error_t *
svn_client_conflict_option_set_moved_to_repos_relpath(svn_client_conflict_option_t * option,int preferred_move_target_idx,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12184 svn_client_conflict_option_set_moved_to_repos_relpath(
12185 svn_client_conflict_option_t *option,
12186 int preferred_move_target_idx,
12187 svn_client_ctx_t *ctx,
12188 apr_pool_t *scratch_pool)
12189 {
12190 /* The only difference to API version 2 is an assertion failure if
12191 * an unexpected option is passed.
12192 * We do not emulate this old behaviour since clients written against
12193 * the previous API will just keep working. */
12194 return svn_error_trace(
12195 svn_client_conflict_option_set_moved_to_repos_relpath2(option,
12196 preferred_move_target_idx, ctx, scratch_pool));
12197 }
12198
12199 svn_error_t *
svn_client_conflict_option_get_moved_to_abspath_candidates2(apr_array_header_t ** possible_moved_to_abspaths,svn_client_conflict_option_t * option,apr_pool_t * result_pool,apr_pool_t * scratch_pool)12200 svn_client_conflict_option_get_moved_to_abspath_candidates2(
12201 apr_array_header_t **possible_moved_to_abspaths,
12202 svn_client_conflict_option_t *option,
12203 apr_pool_t *result_pool,
12204 apr_pool_t *scratch_pool)
12205 {
12206 svn_client_conflict_t *conflict = option->conflict;
12207 const char *victim_abspath;
12208 svn_wc_operation_t operation;
12209 svn_wc_conflict_action_t incoming_change;
12210 svn_wc_conflict_reason_t local_change;
12211 int i;
12212 svn_client_conflict_option_id_t id;
12213
12214 id = svn_client_conflict_option_get_id(option);
12215 if (id != svn_client_conflict_option_incoming_move_file_text_merge &&
12216 id != svn_client_conflict_option_incoming_move_dir_merge &&
12217 id != svn_client_conflict_option_local_move_file_text_merge &&
12218 id != svn_client_conflict_option_local_move_dir_merge &&
12219 id != svn_client_conflict_option_sibling_move_file_text_merge &&
12220 id != svn_client_conflict_option_sibling_move_dir_merge &&
12221 id != svn_client_conflict_option_both_moved_file_merge &&
12222 id != svn_client_conflict_option_both_moved_file_move_merge &&
12223 id != svn_client_conflict_option_both_moved_dir_merge &&
12224 id != svn_client_conflict_option_both_moved_dir_move_merge)
12225 {
12226 /* We cannot operate on this option. */
12227 *possible_moved_to_abspaths = NULL;
12228 return NULL;
12229 }
12230
12231 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
12232 operation = svn_client_conflict_get_operation(conflict);
12233 incoming_change = svn_client_conflict_get_incoming_change(conflict);
12234 local_change = svn_client_conflict_get_local_change(conflict);
12235
12236 if (operation == svn_wc_operation_merge &&
12237 incoming_change == svn_wc_conflict_action_edit &&
12238 local_change == svn_wc_conflict_reason_missing)
12239 {
12240 struct conflict_tree_local_missing_details *details;
12241
12242 details = conflict->tree_conflict_local_details;
12243 if (details == NULL ||
12244 (details->wc_move_targets == NULL && details->wc_siblings == NULL))
12245 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12246 _("Getting a list of possible move siblings "
12247 "requires details for tree conflict at '%s' "
12248 "to be fetched from the repository first"),
12249 svn_dirent_local_style(victim_abspath,
12250 scratch_pool));
12251
12252 *possible_moved_to_abspaths = apr_array_make(result_pool, 1,
12253 sizeof (const char *));
12254 if (details->wc_move_targets)
12255 {
12256 apr_array_header_t *move_target_wc_abspaths;
12257 move_target_wc_abspaths =
12258 svn_hash_gets(details->wc_move_targets,
12259 details->move_target_repos_relpath);
12260 for (i = 0; i < move_target_wc_abspaths->nelts; i++)
12261 {
12262 const char *moved_to_abspath;
12263
12264 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
12265 const char *);
12266 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
12267 apr_pstrdup(result_pool, moved_to_abspath);
12268 }
12269 }
12270
12271 /* ### Siblings are actually 'corresponding nodes', not 'move targets'.
12272 ### But we provide them here to avoid another API function. */
12273 if (details->wc_siblings)
12274 {
12275 for (i = 0; i < details->wc_siblings->nelts; i++)
12276 {
12277 const char *sibling_abspath;
12278
12279 sibling_abspath = APR_ARRAY_IDX(details->wc_siblings, i,
12280 const char *);
12281 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
12282 apr_pstrdup(result_pool, sibling_abspath);
12283 }
12284 }
12285 }
12286 else if ((operation == svn_wc_operation_update ||
12287 operation == svn_wc_operation_switch) &&
12288 incoming_change == svn_wc_conflict_action_delete &&
12289 local_change == svn_wc_conflict_reason_moved_away)
12290 {
12291 struct conflict_tree_update_local_moved_away_details *details;
12292
12293 details = conflict->tree_conflict_local_details;
12294 if (details == NULL || details->wc_move_targets == NULL)
12295 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12296 _("Getting a list of possible move targets "
12297 "requires details for tree conflict at '%s' "
12298 "to be fetched from the repository first"),
12299 svn_dirent_local_style(victim_abspath,
12300 scratch_pool));
12301
12302 /* Return a copy of the option's move target candidate list. */
12303 *possible_moved_to_abspaths =
12304 apr_array_make(result_pool, details->wc_move_targets->nelts,
12305 sizeof (const char *));
12306 for (i = 0; i < details->wc_move_targets->nelts; i++)
12307 {
12308 const char *moved_to_abspath;
12309
12310 moved_to_abspath = APR_ARRAY_IDX(details->wc_move_targets, i,
12311 const char *);
12312 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
12313 apr_pstrdup(result_pool, moved_to_abspath);
12314 }
12315 }
12316 else
12317 {
12318 struct conflict_tree_incoming_delete_details *details;
12319 apr_array_header_t *move_target_wc_abspaths;
12320
12321 details = conflict->tree_conflict_incoming_details;
12322 if (details == NULL || details->wc_move_targets == NULL)
12323 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12324 _("Getting a list of possible move targets "
12325 "requires details for tree conflict at '%s' "
12326 "to be fetched from the repository first"),
12327 svn_dirent_local_style(victim_abspath,
12328 scratch_pool));
12329
12330 move_target_wc_abspaths =
12331 svn_hash_gets(details->wc_move_targets,
12332 get_moved_to_repos_relpath(details, scratch_pool));
12333
12334 /* Return a copy of the option's move target candidate list. */
12335 *possible_moved_to_abspaths =
12336 apr_array_make(result_pool, move_target_wc_abspaths->nelts,
12337 sizeof (const char *));
12338 for (i = 0; i < move_target_wc_abspaths->nelts; i++)
12339 {
12340 const char *moved_to_abspath;
12341
12342 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
12343 const char *);
12344 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
12345 apr_pstrdup(result_pool, moved_to_abspath);
12346 }
12347 }
12348
12349 return SVN_NO_ERROR;
12350 }
12351
12352 svn_error_t *
svn_client_conflict_option_get_moved_to_abspath_candidates(apr_array_header_t ** possible_moved_to_abspaths,svn_client_conflict_option_t * option,apr_pool_t * result_pool,apr_pool_t * scratch_pool)12353 svn_client_conflict_option_get_moved_to_abspath_candidates(
12354 apr_array_header_t **possible_moved_to_abspaths,
12355 svn_client_conflict_option_t *option,
12356 apr_pool_t *result_pool,
12357 apr_pool_t *scratch_pool)
12358 {
12359 /* The only difference to API version 2 is an assertion failure if
12360 * an unexpected option is passed.
12361 * We do not emulate this old behaviour since clients written against
12362 * the previous API will just keep working. */
12363 return svn_error_trace(
12364 svn_client_conflict_option_get_moved_to_abspath_candidates2(
12365 possible_moved_to_abspaths, option, result_pool, scratch_pool));
12366 }
12367
12368 svn_error_t *
svn_client_conflict_option_set_moved_to_abspath2(svn_client_conflict_option_t * option,int preferred_move_target_idx,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12369 svn_client_conflict_option_set_moved_to_abspath2(
12370 svn_client_conflict_option_t *option,
12371 int preferred_move_target_idx,
12372 svn_client_ctx_t *ctx,
12373 apr_pool_t *scratch_pool)
12374 {
12375 svn_client_conflict_t *conflict = option->conflict;
12376 const char *victim_abspath;
12377 svn_wc_operation_t operation;
12378 svn_wc_conflict_action_t incoming_change;
12379 svn_wc_conflict_reason_t local_change;
12380 svn_client_conflict_option_id_t id;
12381
12382 id = svn_client_conflict_option_get_id(option);
12383 if (id != svn_client_conflict_option_incoming_move_file_text_merge &&
12384 id != svn_client_conflict_option_incoming_move_dir_merge &&
12385 id != svn_client_conflict_option_local_move_file_text_merge &&
12386 id != svn_client_conflict_option_local_move_dir_merge &&
12387 id != svn_client_conflict_option_sibling_move_file_text_merge &&
12388 id != svn_client_conflict_option_sibling_move_dir_merge &&
12389 id != svn_client_conflict_option_both_moved_file_merge &&
12390 id != svn_client_conflict_option_both_moved_file_move_merge &&
12391 id != svn_client_conflict_option_both_moved_dir_merge &&
12392 id != svn_client_conflict_option_both_moved_dir_move_merge)
12393 return NULL; /* We cannot operate on this option. Nothing to do. */
12394
12395 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
12396 operation = svn_client_conflict_get_operation(conflict);
12397 incoming_change = svn_client_conflict_get_incoming_change(conflict);
12398 local_change = svn_client_conflict_get_local_change(conflict);
12399
12400 if (operation == svn_wc_operation_merge &&
12401 incoming_change == svn_wc_conflict_action_edit &&
12402 local_change == svn_wc_conflict_reason_missing)
12403 {
12404 struct conflict_tree_local_missing_details *details;
12405 const char *wcroot_abspath;
12406 const char *preferred_sibling;
12407
12408 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
12409 ctx->wc_ctx,
12410 conflict->local_abspath,
12411 scratch_pool,
12412 scratch_pool));
12413
12414 details = conflict->tree_conflict_local_details;
12415 if (details == NULL || (details->wc_siblings == NULL &&
12416 details->wc_move_targets == NULL))
12417 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12418 _("Setting a move target requires details "
12419 "for tree conflict at '%s' to be fetched "
12420 "from the repository first"),
12421 svn_dirent_local_style(victim_abspath,
12422 scratch_pool));
12423
12424 if (details->wc_siblings)
12425 {
12426 if (preferred_move_target_idx < 0 ||
12427 preferred_move_target_idx > details->wc_siblings->nelts)
12428 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
12429 _("Index '%d' is out of bounds of the "
12430 "possible move sibling list for '%s'"),
12431 preferred_move_target_idx,
12432 svn_dirent_local_style(victim_abspath,
12433 scratch_pool));
12434 /* Record the user's preference. */
12435 details->preferred_sibling_idx = preferred_move_target_idx;
12436
12437 /* Update option description. */
12438 preferred_sibling = APR_ARRAY_IDX(details->wc_siblings,
12439 details->preferred_sibling_idx,
12440 const char *);
12441 option->description =
12442 apr_psprintf(
12443 conflict->pool, _("apply changes to '%s'"),
12444 svn_dirent_local_style(
12445 svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling),
12446 scratch_pool));
12447 }
12448 else if (details->wc_move_targets)
12449 {
12450 apr_array_header_t *move_target_wc_abspaths;
12451 move_target_wc_abspaths =
12452 svn_hash_gets(details->wc_move_targets,
12453 details->move_target_repos_relpath);
12454
12455 if (preferred_move_target_idx < 0 ||
12456 preferred_move_target_idx > move_target_wc_abspaths->nelts)
12457 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
12458 _("Index '%d' is out of bounds of the possible "
12459 "move target list for '%s'"),
12460 preferred_move_target_idx,
12461 svn_dirent_local_style(victim_abspath,
12462 scratch_pool));
12463
12464 /* Record the user's preference. */
12465 details->wc_move_target_idx = preferred_move_target_idx;
12466
12467 /* Update option description. */
12468 SVN_ERR(conflict_tree_get_description_local_missing(
12469 &option->description, conflict, ctx,
12470 conflict->pool, scratch_pool));
12471 }
12472 }
12473 else if ((operation == svn_wc_operation_update ||
12474 operation == svn_wc_operation_switch) &&
12475 incoming_change == svn_wc_conflict_action_delete &&
12476 local_change == svn_wc_conflict_reason_moved_away)
12477 {
12478 struct conflict_tree_update_local_moved_away_details *details;
12479
12480 details = conflict->tree_conflict_local_details;
12481 if (details == NULL || details->wc_move_targets == NULL)
12482 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12483 _("Setting a move target requires details "
12484 "for tree conflict at '%s' to be fetched "
12485 "from the repository first"),
12486 svn_dirent_local_style(victim_abspath,
12487 scratch_pool));
12488
12489 if (preferred_move_target_idx < 0 ||
12490 preferred_move_target_idx > details->wc_move_targets->nelts)
12491 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
12492 _("Index '%d' is out of bounds of the "
12493 "possible move target list for '%s'"),
12494 preferred_move_target_idx,
12495 svn_dirent_local_style(victim_abspath,
12496 scratch_pool));
12497
12498 /* Record the user's preference. */
12499 details->preferred_move_target_idx = preferred_move_target_idx;
12500
12501 /* Update option description. */
12502 if (id == svn_client_conflict_option_both_moved_file_merge)
12503 SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge(
12504 &option->description, conflict, ctx, conflict->pool,
12505 scratch_pool));
12506 else if (id == svn_client_conflict_option_both_moved_file_move_merge)
12507 SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge(
12508 &option->description, conflict, ctx, conflict->pool, scratch_pool));
12509 #if 0 /* ### TODO: Also handle options for directories! */
12510 else if (id == svn_client_conflict_option_both_moved_dir_merge)
12511 {
12512 }
12513 else if (id == svn_client_conflict_option_both_moved_dir_move_merge)
12514 {
12515 }
12516 #endif
12517 else
12518 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12519 _("Unexpected option id '%d'"), id);
12520 }
12521 else
12522 {
12523 struct conflict_tree_incoming_delete_details *details;
12524 apr_array_header_t *move_target_wc_abspaths;
12525 const char *moved_to_abspath;
12526
12527 details = conflict->tree_conflict_incoming_details;
12528 if (details == NULL || details->wc_move_targets == NULL)
12529 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12530 _("Setting a move target requires details "
12531 "for tree conflict at '%s' to be fetched "
12532 "from the repository first"),
12533 svn_dirent_local_style(victim_abspath,
12534 scratch_pool));
12535
12536 move_target_wc_abspaths =
12537 svn_hash_gets(details->wc_move_targets,
12538 get_moved_to_repos_relpath(details, scratch_pool));
12539
12540 if (preferred_move_target_idx < 0 ||
12541 preferred_move_target_idx > move_target_wc_abspaths->nelts)
12542 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
12543 _("Index '%d' is out of bounds of the possible "
12544 "move target list for '%s'"),
12545 preferred_move_target_idx,
12546 svn_dirent_local_style(victim_abspath,
12547 scratch_pool));
12548
12549 /* Record the user's preference. */
12550 details->wc_move_target_idx = preferred_move_target_idx;
12551
12552 /* Update option description. */
12553 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
12554 details->wc_move_target_idx,
12555 const char *);
12556 SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description,
12557 conflict, ctx,
12558 moved_to_abspath,
12559 conflict->pool,
12560 scratch_pool));
12561 }
12562 return SVN_NO_ERROR;
12563 }
12564
12565 svn_error_t *
svn_client_conflict_option_set_moved_to_abspath(svn_client_conflict_option_t * option,int preferred_move_target_idx,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12566 svn_client_conflict_option_set_moved_to_abspath(
12567 svn_client_conflict_option_t *option,
12568 int preferred_move_target_idx,
12569 svn_client_ctx_t *ctx,
12570 apr_pool_t *scratch_pool)
12571 {
12572 /* The only difference to API version 2 is an assertion failure if
12573 * an unexpected option is passed.
12574 * We do not emulate this old behaviour since clients written against
12575 * the previous API will just keep working. */
12576 return svn_error_trace(
12577 svn_client_conflict_option_set_moved_to_abspath2(option,
12578 preferred_move_target_idx, ctx, scratch_pool));
12579 }
12580
12581 svn_error_t *
svn_client_conflict_tree_get_resolution_options(apr_array_header_t ** options,svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)12582 svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options,
12583 svn_client_conflict_t *conflict,
12584 svn_client_ctx_t *ctx,
12585 apr_pool_t *result_pool,
12586 apr_pool_t *scratch_pool)
12587 {
12588 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
12589
12590 *options = apr_array_make(result_pool, 2,
12591 sizeof(svn_client_conflict_option_t *));
12592
12593 /* Add postpone option. */
12594 add_resolution_option(*options, conflict,
12595 svn_client_conflict_option_postpone,
12596 _("Postpone"),
12597 _("skip this conflict and leave it unresolved"),
12598 resolve_postpone);
12599
12600 /* Add an option which marks the conflict resolved. */
12601 SVN_ERR(configure_option_accept_current_wc_state(conflict, *options));
12602
12603 /* Configure options which offer automatic resolution. */
12604 SVN_ERR(configure_option_update_move_destination(conflict, *options));
12605 SVN_ERR(configure_option_update_raise_moved_away_children(conflict,
12606 *options));
12607 SVN_ERR(configure_option_incoming_add_ignore(conflict, ctx, *options,
12608 scratch_pool));
12609 SVN_ERR(configure_option_incoming_added_file_text_merge(conflict, ctx,
12610 *options,
12611 scratch_pool));
12612 SVN_ERR(configure_option_incoming_added_file_replace_and_merge(conflict,
12613 ctx,
12614 *options,
12615 scratch_pool));
12616 SVN_ERR(configure_option_incoming_added_dir_merge(conflict, ctx,
12617 *options,
12618 scratch_pool));
12619 SVN_ERR(configure_option_incoming_added_dir_replace(conflict, ctx,
12620 *options,
12621 scratch_pool));
12622 SVN_ERR(configure_option_incoming_added_dir_replace_and_merge(conflict,
12623 ctx,
12624 *options,
12625 scratch_pool));
12626 SVN_ERR(configure_option_incoming_delete_ignore(conflict, ctx, *options,
12627 scratch_pool));
12628 SVN_ERR(configure_option_incoming_delete_accept(conflict, ctx, *options,
12629 scratch_pool));
12630 SVN_ERR(configure_option_incoming_move_file_merge(conflict, ctx, *options,
12631 scratch_pool));
12632 SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options,
12633 scratch_pool));
12634 SVN_ERR(configure_option_local_move_file_or_dir_merge(conflict, ctx,
12635 *options,
12636 scratch_pool));
12637 SVN_ERR(configure_option_sibling_move_merge(conflict, ctx, *options,
12638 scratch_pool));
12639 SVN_ERR(configure_option_both_moved_file_merge(conflict, ctx, *options,
12640 scratch_pool));
12641 SVN_ERR(configure_option_both_moved_dir_merge(conflict, ctx, *options,
12642 scratch_pool));
12643
12644 return SVN_NO_ERROR;
12645 }
12646
12647 /* Swallow authz failures and return SVN_NO_ERROR in that case.
12648 * Otherwise, return ERR unchanged. */
12649 static svn_error_t *
ignore_authz_failures(svn_error_t * err)12650 ignore_authz_failures(svn_error_t *err)
12651 {
12652 if (err && ( svn_error_find_cause(err, SVN_ERR_AUTHZ_UNREADABLE)
12653 || svn_error_find_cause(err, SVN_ERR_RA_NOT_AUTHORIZED)
12654 || svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)))
12655 {
12656 svn_error_clear(err);
12657 err = SVN_NO_ERROR;
12658 }
12659
12660 return err;
12661 }
12662
12663 svn_error_t *
svn_client_conflict_tree_get_details(svn_client_conflict_t * conflict,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12664 svn_client_conflict_tree_get_details(svn_client_conflict_t *conflict,
12665 svn_client_ctx_t *ctx,
12666 apr_pool_t *scratch_pool)
12667 {
12668 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
12669
12670 if (ctx->notify_func2)
12671 {
12672 svn_wc_notify_t *notify;
12673
12674 notify = svn_wc_create_notify(
12675 svn_client_conflict_get_local_abspath(conflict),
12676 svn_wc_notify_begin_search_tree_conflict_details,
12677 scratch_pool),
12678 ctx->notify_func2(ctx->notify_baton2, notify,
12679 scratch_pool);
12680 }
12681
12682 /* Collecting conflict details may fail due to insufficient access rights.
12683 * This is not a failure but simply restricts our future options. */
12684 if (conflict->tree_conflict_get_incoming_details_func)
12685 SVN_ERR(ignore_authz_failures(
12686 conflict->tree_conflict_get_incoming_details_func(conflict, ctx,
12687 scratch_pool)));
12688
12689
12690 if (conflict->tree_conflict_get_local_details_func)
12691 SVN_ERR(ignore_authz_failures(
12692 conflict->tree_conflict_get_local_details_func(conflict, ctx,
12693 scratch_pool)));
12694
12695 if (ctx->notify_func2)
12696 {
12697 svn_wc_notify_t *notify;
12698
12699 notify = svn_wc_create_notify(
12700 svn_client_conflict_get_local_abspath(conflict),
12701 svn_wc_notify_end_search_tree_conflict_details,
12702 scratch_pool),
12703 ctx->notify_func2(ctx->notify_baton2, notify,
12704 scratch_pool);
12705 }
12706
12707 return SVN_NO_ERROR;
12708 }
12709
12710 svn_client_conflict_option_id_t
svn_client_conflict_option_get_id(svn_client_conflict_option_t * option)12711 svn_client_conflict_option_get_id(svn_client_conflict_option_t *option)
12712 {
12713 return option->id;
12714 }
12715
12716 const char *
svn_client_conflict_option_get_label(svn_client_conflict_option_t * option,apr_pool_t * result_pool)12717 svn_client_conflict_option_get_label(svn_client_conflict_option_t *option,
12718 apr_pool_t *result_pool)
12719 {
12720 return apr_pstrdup(result_pool, option->label);
12721 }
12722
12723 const char *
svn_client_conflict_option_get_description(svn_client_conflict_option_t * option,apr_pool_t * result_pool)12724 svn_client_conflict_option_get_description(svn_client_conflict_option_t *option,
12725 apr_pool_t *result_pool)
12726 {
12727 return apr_pstrdup(result_pool, option->description);
12728 }
12729
12730 svn_client_conflict_option_id_t
svn_client_conflict_get_recommended_option_id(svn_client_conflict_t * conflict)12731 svn_client_conflict_get_recommended_option_id(svn_client_conflict_t *conflict)
12732 {
12733 return conflict->recommended_option_id;
12734 }
12735
12736 svn_error_t *
svn_client_conflict_text_resolve(svn_client_conflict_t * conflict,svn_client_conflict_option_t * option,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12737 svn_client_conflict_text_resolve(svn_client_conflict_t *conflict,
12738 svn_client_conflict_option_t *option,
12739 svn_client_ctx_t *ctx,
12740 apr_pool_t *scratch_pool)
12741 {
12742 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
12743 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
12744
12745 return SVN_NO_ERROR;
12746 }
12747
12748 svn_client_conflict_option_t *
svn_client_conflict_option_find_by_id(apr_array_header_t * options,svn_client_conflict_option_id_t option_id)12749 svn_client_conflict_option_find_by_id(apr_array_header_t *options,
12750 svn_client_conflict_option_id_t option_id)
12751 {
12752 int i;
12753
12754 for (i = 0; i < options->nelts; i++)
12755 {
12756 svn_client_conflict_option_t *this_option;
12757 svn_client_conflict_option_id_t this_option_id;
12758
12759 this_option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
12760 this_option_id = svn_client_conflict_option_get_id(this_option);
12761
12762 if (this_option_id == option_id)
12763 return this_option;
12764 }
12765
12766 return NULL;
12767 }
12768
12769 svn_error_t *
svn_client_conflict_text_resolve_by_id(svn_client_conflict_t * conflict,svn_client_conflict_option_id_t option_id,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12770 svn_client_conflict_text_resolve_by_id(
12771 svn_client_conflict_t *conflict,
12772 svn_client_conflict_option_id_t option_id,
12773 svn_client_ctx_t *ctx,
12774 apr_pool_t *scratch_pool)
12775 {
12776 apr_array_header_t *resolution_options;
12777 svn_client_conflict_option_t *option;
12778
12779 SVN_ERR(svn_client_conflict_text_get_resolution_options(
12780 &resolution_options, conflict, ctx,
12781 scratch_pool, scratch_pool));
12782 option = svn_client_conflict_option_find_by_id(resolution_options,
12783 option_id);
12784 if (option == NULL)
12785 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
12786 NULL,
12787 _("Inapplicable conflict resolution option "
12788 "given for conflicted path '%s'"),
12789 svn_dirent_local_style(conflict->local_abspath,
12790 scratch_pool));
12791
12792 SVN_ERR(svn_client_conflict_text_resolve(conflict, option, ctx, scratch_pool));
12793
12794 return SVN_NO_ERROR;
12795 }
12796
12797 svn_client_conflict_option_id_t
svn_client_conflict_text_get_resolution(svn_client_conflict_t * conflict)12798 svn_client_conflict_text_get_resolution(svn_client_conflict_t *conflict)
12799 {
12800 return conflict->resolution_text;
12801 }
12802
12803 svn_error_t *
svn_client_conflict_prop_resolve(svn_client_conflict_t * conflict,const char * propname,svn_client_conflict_option_t * option,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12804 svn_client_conflict_prop_resolve(svn_client_conflict_t *conflict,
12805 const char *propname,
12806 svn_client_conflict_option_t *option,
12807 svn_client_ctx_t *ctx,
12808 apr_pool_t *scratch_pool)
12809 {
12810 SVN_ERR(assert_prop_conflict(conflict, scratch_pool));
12811 option->type_data.prop.propname = propname;
12812 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
12813
12814 return SVN_NO_ERROR;
12815 }
12816
12817 svn_error_t *
svn_client_conflict_prop_resolve_by_id(svn_client_conflict_t * conflict,const char * propname,svn_client_conflict_option_id_t option_id,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12818 svn_client_conflict_prop_resolve_by_id(
12819 svn_client_conflict_t *conflict,
12820 const char *propname,
12821 svn_client_conflict_option_id_t option_id,
12822 svn_client_ctx_t *ctx,
12823 apr_pool_t *scratch_pool)
12824 {
12825 apr_array_header_t *resolution_options;
12826 svn_client_conflict_option_t *option;
12827
12828 SVN_ERR(svn_client_conflict_prop_get_resolution_options(
12829 &resolution_options, conflict, ctx,
12830 scratch_pool, scratch_pool));
12831 option = svn_client_conflict_option_find_by_id(resolution_options,
12832 option_id);
12833 if (option == NULL)
12834 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
12835 NULL,
12836 _("Inapplicable conflict resolution option "
12837 "given for conflicted path '%s'"),
12838 svn_dirent_local_style(conflict->local_abspath,
12839 scratch_pool));
12840 SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option, ctx,
12841 scratch_pool));
12842
12843 return SVN_NO_ERROR;
12844 }
12845
12846 svn_client_conflict_option_id_t
svn_client_conflict_prop_get_resolution(svn_client_conflict_t * conflict,const char * propname)12847 svn_client_conflict_prop_get_resolution(svn_client_conflict_t *conflict,
12848 const char *propname)
12849 {
12850 svn_client_conflict_option_t *option;
12851
12852 option = svn_hash_gets(conflict->resolved_props, propname);
12853 if (option == NULL)
12854 return svn_client_conflict_option_unspecified;
12855
12856 return svn_client_conflict_option_get_id(option);
12857 }
12858
12859 svn_error_t *
svn_client_conflict_tree_resolve(svn_client_conflict_t * conflict,svn_client_conflict_option_t * option,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12860 svn_client_conflict_tree_resolve(svn_client_conflict_t *conflict,
12861 svn_client_conflict_option_t *option,
12862 svn_client_ctx_t *ctx,
12863 apr_pool_t *scratch_pool)
12864 {
12865 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
12866 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
12867
12868 return SVN_NO_ERROR;
12869 }
12870
12871 svn_error_t *
svn_client_conflict_tree_resolve_by_id(svn_client_conflict_t * conflict,svn_client_conflict_option_id_t option_id,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)12872 svn_client_conflict_tree_resolve_by_id(
12873 svn_client_conflict_t *conflict,
12874 svn_client_conflict_option_id_t option_id,
12875 svn_client_ctx_t *ctx,
12876 apr_pool_t *scratch_pool)
12877 {
12878 apr_array_header_t *resolution_options;
12879 svn_client_conflict_option_t *option;
12880
12881 SVN_ERR(svn_client_conflict_tree_get_resolution_options(
12882 &resolution_options, conflict, ctx,
12883 scratch_pool, scratch_pool));
12884 option = svn_client_conflict_option_find_by_id(resolution_options,
12885 option_id);
12886 if (option == NULL)
12887 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
12888 NULL,
12889 _("Inapplicable conflict resolution option "
12890 "given for conflicted path '%s'"),
12891 svn_dirent_local_style(conflict->local_abspath,
12892 scratch_pool));
12893 SVN_ERR(svn_client_conflict_tree_resolve(conflict, option, ctx, scratch_pool));
12894
12895 return SVN_NO_ERROR;
12896 }
12897
12898 svn_client_conflict_option_id_t
svn_client_conflict_tree_get_resolution(svn_client_conflict_t * conflict)12899 svn_client_conflict_tree_get_resolution(svn_client_conflict_t *conflict)
12900 {
12901 return conflict->resolution_tree;
12902 }
12903
12904 /* Return the legacy conflict descriptor which is wrapped by CONFLICT. */
12905 static const svn_wc_conflict_description2_t *
get_conflict_desc2_t(svn_client_conflict_t * conflict)12906 get_conflict_desc2_t(svn_client_conflict_t *conflict)
12907 {
12908 if (conflict->legacy_text_conflict)
12909 return conflict->legacy_text_conflict;
12910
12911 if (conflict->legacy_tree_conflict)
12912 return conflict->legacy_tree_conflict;
12913
12914 if (conflict->prop_conflicts && conflict->legacy_prop_conflict_propname)
12915 return svn_hash_gets(conflict->prop_conflicts,
12916 conflict->legacy_prop_conflict_propname);
12917
12918 return NULL;
12919 }
12920
12921 svn_error_t *
svn_client_conflict_get_conflicted(svn_boolean_t * text_conflicted,apr_array_header_t ** props_conflicted,svn_boolean_t * tree_conflicted,svn_client_conflict_t * conflict,apr_pool_t * result_pool,apr_pool_t * scratch_pool)12922 svn_client_conflict_get_conflicted(svn_boolean_t *text_conflicted,
12923 apr_array_header_t **props_conflicted,
12924 svn_boolean_t *tree_conflicted,
12925 svn_client_conflict_t *conflict,
12926 apr_pool_t *result_pool,
12927 apr_pool_t *scratch_pool)
12928 {
12929 if (text_conflicted)
12930 *text_conflicted = (conflict->legacy_text_conflict != NULL);
12931
12932 if (props_conflicted)
12933 {
12934 if (conflict->prop_conflicts)
12935 SVN_ERR(svn_hash_keys(props_conflicted, conflict->prop_conflicts,
12936 result_pool));
12937 else
12938 *props_conflicted = apr_array_make(result_pool, 0,
12939 sizeof(const char*));
12940 }
12941
12942 if (tree_conflicted)
12943 *tree_conflicted = (conflict->legacy_tree_conflict != NULL);
12944
12945 return SVN_NO_ERROR;
12946 }
12947
12948 const char *
svn_client_conflict_get_local_abspath(svn_client_conflict_t * conflict)12949 svn_client_conflict_get_local_abspath(svn_client_conflict_t *conflict)
12950 {
12951 return conflict->local_abspath;
12952 }
12953
12954 svn_wc_operation_t
svn_client_conflict_get_operation(svn_client_conflict_t * conflict)12955 svn_client_conflict_get_operation(svn_client_conflict_t *conflict)
12956 {
12957 return get_conflict_desc2_t(conflict)->operation;
12958 }
12959
12960 svn_wc_conflict_action_t
svn_client_conflict_get_incoming_change(svn_client_conflict_t * conflict)12961 svn_client_conflict_get_incoming_change(svn_client_conflict_t *conflict)
12962 {
12963 return get_conflict_desc2_t(conflict)->action;
12964 }
12965
12966 svn_wc_conflict_reason_t
svn_client_conflict_get_local_change(svn_client_conflict_t * conflict)12967 svn_client_conflict_get_local_change(svn_client_conflict_t *conflict)
12968 {
12969 return get_conflict_desc2_t(conflict)->reason;
12970 }
12971
12972 svn_error_t *
svn_client_conflict_get_repos_info(const char ** repos_root_url,const char ** repos_uuid,svn_client_conflict_t * conflict,apr_pool_t * result_pool,apr_pool_t * scratch_pool)12973 svn_client_conflict_get_repos_info(const char **repos_root_url,
12974 const char **repos_uuid,
12975 svn_client_conflict_t *conflict,
12976 apr_pool_t *result_pool,
12977 apr_pool_t *scratch_pool)
12978 {
12979 if (repos_root_url)
12980 {
12981 if (get_conflict_desc2_t(conflict)->src_left_version)
12982 *repos_root_url =
12983 get_conflict_desc2_t(conflict)->src_left_version->repos_url;
12984 else if (get_conflict_desc2_t(conflict)->src_right_version)
12985 *repos_root_url =
12986 get_conflict_desc2_t(conflict)->src_right_version->repos_url;
12987 else
12988 *repos_root_url = NULL;
12989 }
12990
12991 if (repos_uuid)
12992 {
12993 if (get_conflict_desc2_t(conflict)->src_left_version)
12994 *repos_uuid =
12995 get_conflict_desc2_t(conflict)->src_left_version->repos_uuid;
12996 else if (get_conflict_desc2_t(conflict)->src_right_version)
12997 *repos_uuid =
12998 get_conflict_desc2_t(conflict)->src_right_version->repos_uuid;
12999 else
13000 *repos_uuid = NULL;
13001 }
13002
13003 return SVN_NO_ERROR;
13004 }
13005
13006 svn_error_t *
svn_client_conflict_get_incoming_old_repos_location(const char ** incoming_old_repos_relpath,svn_revnum_t * incoming_old_pegrev,svn_node_kind_t * incoming_old_node_kind,svn_client_conflict_t * conflict,apr_pool_t * result_pool,apr_pool_t * scratch_pool)13007 svn_client_conflict_get_incoming_old_repos_location(
13008 const char **incoming_old_repos_relpath,
13009 svn_revnum_t *incoming_old_pegrev,
13010 svn_node_kind_t *incoming_old_node_kind,
13011 svn_client_conflict_t *conflict,
13012 apr_pool_t *result_pool,
13013 apr_pool_t *scratch_pool)
13014 {
13015 if (incoming_old_repos_relpath)
13016 {
13017 if (get_conflict_desc2_t(conflict)->src_left_version)
13018 *incoming_old_repos_relpath =
13019 get_conflict_desc2_t(conflict)->src_left_version->path_in_repos;
13020 else
13021 *incoming_old_repos_relpath = NULL;
13022 }
13023
13024 if (incoming_old_pegrev)
13025 {
13026 if (get_conflict_desc2_t(conflict)->src_left_version)
13027 *incoming_old_pegrev =
13028 get_conflict_desc2_t(conflict)->src_left_version->peg_rev;
13029 else
13030 *incoming_old_pegrev = SVN_INVALID_REVNUM;
13031 }
13032
13033 if (incoming_old_node_kind)
13034 {
13035 if (get_conflict_desc2_t(conflict)->src_left_version)
13036 *incoming_old_node_kind =
13037 get_conflict_desc2_t(conflict)->src_left_version->node_kind;
13038 else
13039 *incoming_old_node_kind = svn_node_none;
13040 }
13041
13042 return SVN_NO_ERROR;
13043 }
13044
13045 svn_error_t *
svn_client_conflict_get_incoming_new_repos_location(const char ** incoming_new_repos_relpath,svn_revnum_t * incoming_new_pegrev,svn_node_kind_t * incoming_new_node_kind,svn_client_conflict_t * conflict,apr_pool_t * result_pool,apr_pool_t * scratch_pool)13046 svn_client_conflict_get_incoming_new_repos_location(
13047 const char **incoming_new_repos_relpath,
13048 svn_revnum_t *incoming_new_pegrev,
13049 svn_node_kind_t *incoming_new_node_kind,
13050 svn_client_conflict_t *conflict,
13051 apr_pool_t *result_pool,
13052 apr_pool_t *scratch_pool)
13053 {
13054 if (incoming_new_repos_relpath)
13055 {
13056 if (get_conflict_desc2_t(conflict)->src_right_version)
13057 *incoming_new_repos_relpath =
13058 get_conflict_desc2_t(conflict)->src_right_version->path_in_repos;
13059 else
13060 *incoming_new_repos_relpath = NULL;
13061 }
13062
13063 if (incoming_new_pegrev)
13064 {
13065 if (get_conflict_desc2_t(conflict)->src_right_version)
13066 *incoming_new_pegrev =
13067 get_conflict_desc2_t(conflict)->src_right_version->peg_rev;
13068 else
13069 *incoming_new_pegrev = SVN_INVALID_REVNUM;
13070 }
13071
13072 if (incoming_new_node_kind)
13073 {
13074 if (get_conflict_desc2_t(conflict)->src_right_version)
13075 *incoming_new_node_kind =
13076 get_conflict_desc2_t(conflict)->src_right_version->node_kind;
13077 else
13078 *incoming_new_node_kind = svn_node_none;
13079 }
13080
13081 return SVN_NO_ERROR;
13082 }
13083
13084 svn_node_kind_t
svn_client_conflict_tree_get_victim_node_kind(svn_client_conflict_t * conflict)13085 svn_client_conflict_tree_get_victim_node_kind(svn_client_conflict_t *conflict)
13086 {
13087 SVN_ERR_ASSERT_NO_RETURN(assert_tree_conflict(conflict, conflict->pool)
13088 == SVN_NO_ERROR);
13089
13090 return get_conflict_desc2_t(conflict)->node_kind;
13091 }
13092
13093 svn_error_t *
svn_client_conflict_prop_get_propvals(const svn_string_t ** base_propval,const svn_string_t ** working_propval,const svn_string_t ** incoming_old_propval,const svn_string_t ** incoming_new_propval,svn_client_conflict_t * conflict,const char * propname,apr_pool_t * result_pool)13094 svn_client_conflict_prop_get_propvals(const svn_string_t **base_propval,
13095 const svn_string_t **working_propval,
13096 const svn_string_t **incoming_old_propval,
13097 const svn_string_t **incoming_new_propval,
13098 svn_client_conflict_t *conflict,
13099 const char *propname,
13100 apr_pool_t *result_pool)
13101 {
13102 const svn_wc_conflict_description2_t *desc;
13103
13104 SVN_ERR(assert_prop_conflict(conflict, conflict->pool));
13105
13106 desc = svn_hash_gets(conflict->prop_conflicts, propname);
13107 if (desc == NULL)
13108 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
13109 _("Property '%s' is not in conflict."), propname);
13110
13111 if (base_propval)
13112 *base_propval =
13113 svn_string_dup(desc->prop_value_base, result_pool);
13114
13115 if (working_propval)
13116 *working_propval =
13117 svn_string_dup(desc->prop_value_working, result_pool);
13118
13119 if (incoming_old_propval)
13120 *incoming_old_propval =
13121 svn_string_dup(desc->prop_value_incoming_old, result_pool);
13122
13123 if (incoming_new_propval)
13124 *incoming_new_propval =
13125 svn_string_dup(desc->prop_value_incoming_new, result_pool);
13126
13127 return SVN_NO_ERROR;
13128 }
13129
13130 const char *
svn_client_conflict_prop_get_reject_abspath(svn_client_conflict_t * conflict)13131 svn_client_conflict_prop_get_reject_abspath(svn_client_conflict_t *conflict)
13132 {
13133 SVN_ERR_ASSERT_NO_RETURN(assert_prop_conflict(conflict, conflict->pool)
13134 == SVN_NO_ERROR);
13135
13136 /* svn_wc_conflict_description2_t stores this path in 'their_abspath' */
13137 return get_conflict_desc2_t(conflict)->their_abspath;
13138 }
13139
13140 const char *
svn_client_conflict_text_get_mime_type(svn_client_conflict_t * conflict)13141 svn_client_conflict_text_get_mime_type(svn_client_conflict_t *conflict)
13142 {
13143 SVN_ERR_ASSERT_NO_RETURN(assert_text_conflict(conflict, conflict->pool)
13144 == SVN_NO_ERROR);
13145
13146 return get_conflict_desc2_t(conflict)->mime_type;
13147 }
13148
13149 svn_error_t *
svn_client_conflict_text_get_contents(const char ** base_abspath,const char ** working_abspath,const char ** incoming_old_abspath,const char ** incoming_new_abspath,svn_client_conflict_t * conflict,apr_pool_t * result_pool,apr_pool_t * scratch_pool)13150 svn_client_conflict_text_get_contents(const char **base_abspath,
13151 const char **working_abspath,
13152 const char **incoming_old_abspath,
13153 const char **incoming_new_abspath,
13154 svn_client_conflict_t *conflict,
13155 apr_pool_t *result_pool,
13156 apr_pool_t *scratch_pool)
13157 {
13158 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
13159
13160 if (base_abspath)
13161 {
13162 if (svn_client_conflict_get_operation(conflict) ==
13163 svn_wc_operation_merge)
13164 *base_abspath = NULL; /* ### WC base contents not available yet */
13165 else /* update/switch */
13166 *base_abspath = get_conflict_desc2_t(conflict)->base_abspath;
13167 }
13168
13169 if (working_abspath)
13170 *working_abspath = get_conflict_desc2_t(conflict)->my_abspath;
13171
13172 if (incoming_old_abspath)
13173 *incoming_old_abspath = get_conflict_desc2_t(conflict)->base_abspath;
13174
13175 if (incoming_new_abspath)
13176 *incoming_new_abspath = get_conflict_desc2_t(conflict)->their_abspath;
13177
13178 return SVN_NO_ERROR;
13179 }
13180
13181 /* Set up type-specific data for a new conflict object. */
13182 static svn_error_t *
conflict_type_specific_setup(svn_client_conflict_t * conflict,apr_pool_t * scratch_pool)13183 conflict_type_specific_setup(svn_client_conflict_t *conflict,
13184 apr_pool_t *scratch_pool)
13185 {
13186 svn_boolean_t tree_conflicted;
13187 svn_wc_operation_t operation;
13188 svn_wc_conflict_action_t incoming_change;
13189 svn_wc_conflict_reason_t local_change;
13190
13191 /* For now, we only deal with tree conflicts here. */
13192 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
13193 conflict, scratch_pool,
13194 scratch_pool));
13195 if (!tree_conflicted)
13196 return SVN_NO_ERROR;
13197
13198 /* Set a default description function. */
13199 conflict->tree_conflict_get_incoming_description_func =
13200 conflict_tree_get_incoming_description_generic;
13201 conflict->tree_conflict_get_local_description_func =
13202 conflict_tree_get_local_description_generic;
13203
13204 operation = svn_client_conflict_get_operation(conflict);
13205 incoming_change = svn_client_conflict_get_incoming_change(conflict);
13206 local_change = svn_client_conflict_get_local_change(conflict);
13207
13208 /* Set type-specific description and details functions. */
13209 if (incoming_change == svn_wc_conflict_action_delete ||
13210 incoming_change == svn_wc_conflict_action_replace)
13211 {
13212 conflict->tree_conflict_get_incoming_description_func =
13213 conflict_tree_get_description_incoming_delete;
13214 conflict->tree_conflict_get_incoming_details_func =
13215 conflict_tree_get_details_incoming_delete;
13216 }
13217 else if (incoming_change == svn_wc_conflict_action_add)
13218 {
13219 conflict->tree_conflict_get_incoming_description_func =
13220 conflict_tree_get_description_incoming_add;
13221 conflict->tree_conflict_get_incoming_details_func =
13222 conflict_tree_get_details_incoming_add;
13223 }
13224 else if (incoming_change == svn_wc_conflict_action_edit)
13225 {
13226 conflict->tree_conflict_get_incoming_description_func =
13227 conflict_tree_get_description_incoming_edit;
13228 conflict->tree_conflict_get_incoming_details_func =
13229 conflict_tree_get_details_incoming_edit;
13230 }
13231
13232 if (local_change == svn_wc_conflict_reason_missing)
13233 {
13234 conflict->tree_conflict_get_local_description_func =
13235 conflict_tree_get_description_local_missing;
13236 conflict->tree_conflict_get_local_details_func =
13237 conflict_tree_get_details_local_missing;
13238 }
13239 else if (local_change == svn_wc_conflict_reason_moved_away &&
13240 operation == svn_wc_operation_update /* ### what about switch? */)
13241 {
13242 conflict->tree_conflict_get_local_details_func =
13243 conflict_tree_get_details_update_local_moved_away;
13244 }
13245
13246 return SVN_NO_ERROR;
13247 }
13248
13249 svn_error_t *
svn_client_conflict_get(svn_client_conflict_t ** conflict,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)13250 svn_client_conflict_get(svn_client_conflict_t **conflict,
13251 const char *local_abspath,
13252 svn_client_ctx_t *ctx,
13253 apr_pool_t *result_pool,
13254 apr_pool_t *scratch_pool)
13255 {
13256 const apr_array_header_t *descs;
13257 int i;
13258
13259 *conflict = apr_pcalloc(result_pool, sizeof(**conflict));
13260
13261 (*conflict)->local_abspath = apr_pstrdup(result_pool, local_abspath);
13262 (*conflict)->resolution_text = svn_client_conflict_option_unspecified;
13263 (*conflict)->resolution_tree = svn_client_conflict_option_unspecified;
13264 (*conflict)->resolved_props = apr_hash_make(result_pool);
13265 (*conflict)->recommended_option_id = svn_client_conflict_option_unspecified;
13266 (*conflict)->pool = result_pool;
13267
13268 /* Add all legacy conflict descriptors we can find. Eventually, this code
13269 * path should stop relying on svn_wc_conflict_description2_t entirely. */
13270 SVN_ERR(svn_wc__read_conflict_descriptions2_t(&descs, ctx->wc_ctx,
13271 local_abspath,
13272 result_pool, scratch_pool));
13273 for (i = 0; i < descs->nelts; i++)
13274 {
13275 const svn_wc_conflict_description2_t *desc;
13276
13277 desc = APR_ARRAY_IDX(descs, i, const svn_wc_conflict_description2_t *);
13278 add_legacy_desc_to_conflict(desc, *conflict, result_pool);
13279 }
13280
13281 SVN_ERR(conflict_type_specific_setup(*conflict, scratch_pool));
13282
13283 return SVN_NO_ERROR;
13284 }
13285
13286 /* Baton for conflict_status_walker */
13287 struct conflict_status_walker_baton
13288 {
13289 svn_client_conflict_walk_func_t conflict_walk_func;
13290 void *conflict_walk_func_baton;
13291 svn_client_ctx_t *ctx;
13292 svn_wc_notify_func2_t notify_func;
13293 void *notify_baton;
13294 svn_boolean_t resolved_a_tree_conflict;
13295 apr_hash_t *unresolved_tree_conflicts;
13296 };
13297
13298 /* Implements svn_wc_notify_func2_t to collect new conflicts caused by
13299 resolving a tree conflict. */
13300 static void
tree_conflict_collector(void * baton,const svn_wc_notify_t * notify,apr_pool_t * pool)13301 tree_conflict_collector(void *baton,
13302 const svn_wc_notify_t *notify,
13303 apr_pool_t *pool)
13304 {
13305 struct conflict_status_walker_baton *cswb = baton;
13306
13307 if (cswb->notify_func)
13308 cswb->notify_func(cswb->notify_baton, notify, pool);
13309
13310 if (cswb->unresolved_tree_conflicts
13311 && (notify->action == svn_wc_notify_tree_conflict
13312 || notify->prop_state == svn_wc_notify_state_conflicted
13313 || notify->content_state == svn_wc_notify_state_conflicted))
13314 {
13315 if (!svn_hash_gets(cswb->unresolved_tree_conflicts, notify->path))
13316 {
13317 const char *tc_abspath;
13318 apr_pool_t *hash_pool;
13319
13320 hash_pool = apr_hash_pool_get(cswb->unresolved_tree_conflicts);
13321 tc_abspath = apr_pstrdup(hash_pool, notify->path);
13322 svn_hash_sets(cswb->unresolved_tree_conflicts, tc_abspath, "");
13323 }
13324 }
13325 }
13326
13327 /*
13328 * Record a tree conflict resolution failure due to error condition ERR
13329 * in the RESOLVE_LATER hash table. If the hash table is not available
13330 * (meaning the caller does not wish to retry resolution later), or if
13331 * the error condition does not indicate circumstances where another
13332 * existing tree conflict is blocking the resolution attempt, then
13333 * return the error ERR itself.
13334 */
13335 static svn_error_t *
handle_tree_conflict_resolution_failure(const char * local_abspath,svn_error_t * err,apr_hash_t * unresolved_tree_conflicts)13336 handle_tree_conflict_resolution_failure(const char *local_abspath,
13337 svn_error_t *err,
13338 apr_hash_t *unresolved_tree_conflicts)
13339 {
13340 const char *tc_abspath;
13341
13342 if (!unresolved_tree_conflicts
13343 || (err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE
13344 && err->apr_err != SVN_ERR_WC_FOUND_CONFLICT))
13345 return svn_error_trace(err); /* Give up. Do not retry resolution later. */
13346
13347 svn_error_clear(err);
13348 tc_abspath = apr_pstrdup(apr_hash_pool_get(unresolved_tree_conflicts),
13349 local_abspath);
13350
13351 svn_hash_sets(unresolved_tree_conflicts, tc_abspath, "");
13352
13353 return SVN_NO_ERROR; /* Caller may retry after resolving other conflicts. */
13354 }
13355
13356 /* Implements svn_wc_status4_t to walk all conflicts to resolve.
13357 */
13358 static svn_error_t *
conflict_status_walker(void * baton,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)13359 conflict_status_walker(void *baton,
13360 const char *local_abspath,
13361 const svn_wc_status3_t *status,
13362 apr_pool_t *scratch_pool)
13363 {
13364 struct conflict_status_walker_baton *cswb = baton;
13365 svn_client_conflict_t *conflict;
13366 svn_error_t *err;
13367 svn_boolean_t tree_conflicted;
13368
13369 if (!status->conflicted)
13370 return SVN_NO_ERROR;
13371
13372 SVN_ERR(svn_client_conflict_get(&conflict, local_abspath, cswb->ctx,
13373 scratch_pool, scratch_pool));
13374 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
13375 conflict, scratch_pool,
13376 scratch_pool));
13377 err = cswb->conflict_walk_func(cswb->conflict_walk_func_baton,
13378 conflict, scratch_pool);
13379 if (err)
13380 {
13381 if (tree_conflicted)
13382 SVN_ERR(handle_tree_conflict_resolution_failure(
13383 local_abspath, err, cswb->unresolved_tree_conflicts));
13384
13385 else
13386 return svn_error_trace(err);
13387 }
13388
13389 if (tree_conflicted)
13390 {
13391 svn_client_conflict_option_id_t resolution;
13392
13393 resolution = svn_client_conflict_tree_get_resolution(conflict);
13394 if (resolution != svn_client_conflict_option_unspecified &&
13395 resolution != svn_client_conflict_option_postpone)
13396 cswb->resolved_a_tree_conflict = TRUE;
13397 }
13398
13399 return SVN_NO_ERROR;
13400 }
13401
13402 svn_error_t *
svn_client_conflict_walk(const char * local_abspath,svn_depth_t depth,svn_client_conflict_walk_func_t conflict_walk_func,void * conflict_walk_func_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)13403 svn_client_conflict_walk(const char *local_abspath,
13404 svn_depth_t depth,
13405 svn_client_conflict_walk_func_t conflict_walk_func,
13406 void *conflict_walk_func_baton,
13407 svn_client_ctx_t *ctx,
13408 apr_pool_t *scratch_pool)
13409 {
13410 struct conflict_status_walker_baton cswb;
13411 apr_pool_t *iterpool = NULL;
13412 svn_error_t *err = SVN_NO_ERROR;
13413
13414 if (depth == svn_depth_unknown)
13415 depth = svn_depth_infinity;
13416
13417 cswb.conflict_walk_func = conflict_walk_func;
13418 cswb.conflict_walk_func_baton = conflict_walk_func_baton;
13419 cswb.ctx = ctx;
13420 cswb.resolved_a_tree_conflict = FALSE;
13421 cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool);
13422
13423 if (ctx->notify_func2)
13424 ctx->notify_func2(ctx->notify_baton2,
13425 svn_wc_create_notify(
13426 local_abspath,
13427 svn_wc_notify_conflict_resolver_starting,
13428 scratch_pool),
13429 scratch_pool);
13430
13431 /* Swap in our notify_func wrapper. We must revert this before returning! */
13432 cswb.notify_func = ctx->notify_func2;
13433 cswb.notify_baton = ctx->notify_baton2;
13434 ctx->notify_func2 = tree_conflict_collector;
13435 ctx->notify_baton2 = &cswb;
13436
13437 err = svn_wc_walk_status(ctx->wc_ctx,
13438 local_abspath,
13439 depth,
13440 FALSE /* get_all */,
13441 FALSE /* no_ignore */,
13442 TRUE /* ignore_text_mods */,
13443 NULL /* ignore_patterns */,
13444 conflict_status_walker, &cswb,
13445 ctx->cancel_func, ctx->cancel_baton,
13446 scratch_pool);
13447
13448 /* If we got new tree conflicts (or delayed conflicts) during the initial
13449 walk, we now walk them one by one as closure. */
13450 while (!err && cswb.unresolved_tree_conflicts &&
13451 apr_hash_count(cswb.unresolved_tree_conflicts))
13452 {
13453 apr_hash_index_t *hi;
13454 svn_wc_status3_t *status = NULL;
13455 const char *tc_abspath = NULL;
13456
13457 if (iterpool)
13458 svn_pool_clear(iterpool);
13459 else
13460 iterpool = svn_pool_create(scratch_pool);
13461
13462 hi = apr_hash_first(scratch_pool, cswb.unresolved_tree_conflicts);
13463 cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool);
13464 cswb.resolved_a_tree_conflict = FALSE;
13465
13466 for (; hi && !err; hi = apr_hash_next(hi))
13467 {
13468 svn_pool_clear(iterpool);
13469
13470 tc_abspath = apr_hash_this_key(hi);
13471
13472 if (ctx->cancel_func)
13473 {
13474 err = ctx->cancel_func(ctx->cancel_baton);
13475 if (err)
13476 break;
13477 }
13478
13479 err = svn_error_trace(svn_wc_status3(&status, ctx->wc_ctx,
13480 tc_abspath,
13481 iterpool, iterpool));
13482 if (err)
13483 break;
13484
13485 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
13486 status, scratch_pool));
13487 if (err)
13488 break;
13489 }
13490
13491 if (!err && !cswb.resolved_a_tree_conflict && tc_abspath &&
13492 apr_hash_count(cswb.unresolved_tree_conflicts))
13493 {
13494 /* None of the remaining conflicts got resolved, without any error.
13495 * Disable the 'unresolved_tree_conflicts' cache and try again. */
13496 cswb.unresolved_tree_conflicts = NULL;
13497
13498 /* Run the most recent resolve operation again.
13499 * We still have status and tc_abspath for that one.
13500 * This should uncover the error which prevents resolution. */
13501 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
13502 status, scratch_pool));
13503 SVN_ERR_ASSERT(err != NULL);
13504
13505 err = svn_error_createf(
13506 SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
13507 _("Unable to resolve pending conflict on '%s'"),
13508 svn_dirent_local_style(tc_abspath, scratch_pool));
13509 break;
13510 }
13511 }
13512
13513 if (iterpool)
13514 svn_pool_destroy(iterpool);
13515
13516 ctx->notify_func2 = cswb.notify_func;
13517 ctx->notify_baton2 = cswb.notify_baton;
13518
13519 if (!err && ctx->notify_func2)
13520 ctx->notify_func2(ctx->notify_baton2,
13521 svn_wc_create_notify(local_abspath,
13522 svn_wc_notify_conflict_resolver_done,
13523 scratch_pool),
13524 scratch_pool);
13525
13526 return svn_error_trace(err);
13527 }
13528