2 * conflicts.c: conflict resolver implementation
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
30 #include "svn_types.h"
32 #include "svn_client.h"
33 #include "svn_error.h"
34 #include "svn_dirent_uri.h"
36 #include "svn_pools.h"
37 #include "svn_props.h"
39 #include "svn_sorts.h"
40 #include "svn_subst.h"
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"
49 #include "svn_private_config.h"
51 #define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
54 /*** Dealing with conflicts. ***/
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);
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);
71 struct svn_client_conflict_t
73 const char *local_abspath;
74 apr_hash_t *prop_conflicts;
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;
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;
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;
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;
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;
103 /* The pool this conflict was allocated from. */
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;
111 /* The recommended resolution option's ID. */
112 svn_client_conflict_option_id_t recommended_option_id;
115 /* Resolves conflict to OPTION and sets CONFLICT->RESOLUTION accordingly.
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);
126 struct svn_client_conflict_option_t
128 svn_client_conflict_option_id_t id;
130 const char *description;
132 svn_client_conflict_t *conflict;
133 conflict_option_resolve_func_t do_resolve_func;
135 /* The pool this option was allocated from. */
138 /* Data which is specific to particular conflicts and options. */
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;
145 /* A merged property value, if supplied by the API user, else NULL. */
146 const svn_string_t *merged_propval;
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.
157 static svn_wc_conflict_choice_t
158 conflict_option_id_to_wc_conflict_choice(
159 svn_client_conflict_option_id_t option_id)
164 case svn_client_conflict_option_undefined:
165 return svn_wc_conflict_choose_undefined;
167 case svn_client_conflict_option_postpone:
168 return svn_wc_conflict_choose_postpone;
170 case svn_client_conflict_option_base_text:
171 return svn_wc_conflict_choose_base;
173 case svn_client_conflict_option_incoming_text:
174 return svn_wc_conflict_choose_theirs_full;
176 case svn_client_conflict_option_working_text:
177 return svn_wc_conflict_choose_mine_full;
179 case svn_client_conflict_option_incoming_text_where_conflicted:
180 return svn_wc_conflict_choose_theirs_conflict;
182 case svn_client_conflict_option_working_text_where_conflicted:
183 return svn_wc_conflict_choose_mine_conflict;
185 case svn_client_conflict_option_merged_text:
186 return svn_wc_conflict_choose_merged;
188 case svn_client_conflict_option_unspecified:
189 return svn_wc_conflict_choose_unspecified;
195 return svn_wc_conflict_choose_undefined;
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)
205 case svn_wc_conflict_kind_text:
206 conflict->legacy_text_conflict = desc;
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;
216 case svn_wc_conflict_kind_tree:
217 conflict->legacy_tree_conflict = desc;
221 SVN_ERR_ASSERT_NO_RETURN(FALSE); /* unknown kind of conflict */
225 /* A map for svn_wc_conflict_action_t values to strings */
226 static const svn_token_map_t map_conflict_action[] =
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 },
235 /* A map for svn_wc_conflict_reason_t values to strings */
236 static const svn_token_map_t map_conflict_reason[] =
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 },
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. */
258 /* The author who committed the revision in which this move was committed. */
259 const char *rev_author;
261 /* The repository relpath the node was moved from in this revision. */
262 const char *moved_from_repos_relpath;
264 /* The repository relpath the node was moved to in this revision. */
265 const char *moved_to_repos_relpath;
267 /* The copyfrom revision of the moved-to path. */
268 svn_revnum_t copyfrom_rev;
270 /* The node kind of the item being moved. */
271 svn_node_kind_t node_kind;
273 /* Prev pointer. NULL if no prior move exists in the chain. */
274 struct repos_move_info *prev;
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;
288 rev_below(svn_revnum_t rev)
290 SVN_ERR_ASSERT_NO_RETURN(rev != SVN_INVALID_REVNUM);
291 SVN_ERR_ASSERT_NO_RETURN(rev > 0);
293 return rev == 1 ? 1 : rev - 1;
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. */
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)
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;
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,
322 deleted_repos_relpath,
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,
331 deleted_location = apr_hash_get(locations, ©from_rev,
332 sizeof(svn_revnum_t));
333 if (deleted_location)
335 if (deleted_location[0] == '/')
337 if (strcmp(deleted_location, copyfrom_path) != 0)
349 if (check_last_changed_rev)
351 svn_dirent_t *dirent;
353 /* Verify that copyfrom_rev >= last-changed revision of the
355 SVN_ERR(svn_ra_stat(ra_session, "", rev_below(deleted_rev), &dirent,
357 if (dirent == NULL || copyfrom_rev < dirent->created_rev)
369 const char *copyto_path;
370 const char *copyfrom_path;
371 svn_revnum_t copyfrom_rev;
372 svn_node_kind_t node_kind;
375 /* Allocate and return a NEW_MOVE, and update MOVED_PATHS with this new move. */
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,
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)
390 struct repos_move_info *move;
391 struct repos_move_info *next_move;
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;
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);
408 svn_boolean_t related;
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,
416 move->moved_from_repos_relpath,
418 FALSE, scratch_pool));
421 SVN_ERR_ASSERT(move->rev < next_move->rev);
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;
432 /* Make this move the head of our next-move linking map. */
433 svn_hash_sets(moved_paths, move->moved_from_repos_relpath, move);
439 /* Push a MOVE into the MOVES_TABLE. */
441 push_move(struct repos_move_info *move, apr_hash_t *moves_table,
442 apr_pool_t *result_pool)
444 apr_array_header_t *moves;
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));
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);
454 APR_ARRAY_PUSH(moves, struct repos_move_info *) = move;
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. */
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)
473 svn_client__pathrev_t *loc1;
474 svn_client__pathrev_t *loc2;
478 loc1 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid,
479 peg_rev1, repos_relpath1,
481 loc2 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid,
482 peg_rev2, repos_relpath2,
484 SVN_ERR(svn_client__get_youngest_common_ancestor(yca_loc, loc1, loc2,
486 result_pool, scratch_pool));
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.
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.
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)
513 svn_client__pathrev_t *yca_loc;
515 apr_pool_t *iterpool;
521 iterpool = svn_pool_create(scratch_pool);
524 c1 = svn_path_component_count(repos_relpath1);
527 svn_pool_clear(iterpool);
530 c2 = svn_path_component_count(repos_relpath2);
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);
538 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
540 svn_error_clear(err);
544 return svn_error_trace(err);
550 svn_pool_destroy(iterpool);
554 p2 = svn_relpath_dirname(p2, scratch_pool);
557 p1 = svn_relpath_dirname(p1, scratch_pool);
560 svn_pool_destroy(iterpool);
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. */
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,
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)
582 svn_client__pathrev_t *yca_loc;
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);
592 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
594 svn_error_clear(err);
598 return svn_error_trace(err);
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));
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. */
614 match_copies_to_deletion(const char *deleted_repos_relpath,
615 svn_revnum_t deleted_rev,
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)
627 apr_hash_index_t *hi;
628 apr_pool_t *iterpool;
630 iterpool = svn_pool_create(scratch_pool);
631 for (hi = apr_hash_first(scratch_pool, copies);
633 hi = apr_hash_next(hi))
635 const char *copyfrom_path = apr_hash_this_key(hi);
636 apr_array_header_t *copies_with_same_source_path;
639 svn_pool_clear(iterpool);
641 copies_with_same_source_path = apr_hash_this_val(hi);
643 if (strcmp(copyfrom_path, deleted_repos_relpath) == 0)
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++)
649 struct copy_info *copy;
650 svn_boolean_t related;
651 struct repos_move_info *move;
653 copy = APR_ARRAY_IDX(copies_with_same_source_path, i,
655 SVN_ERR(check_move_ancestry(&related,
656 ra_session, repos_root_url,
657 deleted_repos_relpath,
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);
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++)
681 struct copy_info *copy;
682 struct repos_move_info *move = NULL;
684 copy = APR_ARRAY_IDX(copies_with_same_source_path, i,
686 SVN_ERR(find_related_move(&move, copy, deleted_repos_relpath,
689 repos_root_url, repos_uuid,
691 result_pool, iterpool));
693 push_move(move, moves_table, result_pool);
697 svn_pool_destroy(iterpool);
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. */
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,
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)
718 apr_pool_t *iterpool;
720 const svn_string_t *author;
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++)
726 const char *deleted_repos_relpath;
728 svn_pool_clear(iterpool);
730 deleted_repos_relpath = APR_ARRAY_IDX(deleted_paths, i, const char *);
731 SVN_ERR(match_copies_to_deletion(deleted_repos_relpath,
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));
739 svn_pool_destroy(iterpool);
744 struct find_deleted_rev_baton
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 */
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;
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. */
765 /* Extra RA session that can be used to make additional requests. */
766 svn_ra_session_t *extra_ra_session;
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 *
773 map_deleted_path_to_move(const char *deleted_relpath,
774 apr_array_header_t *moves,
775 apr_pool_t *scratch_pool)
777 struct repos_move_info *closest_move = NULL;
778 apr_size_t min_components = 0;
781 for (i = 0; i < moves->nelts; i++)
784 struct repos_move_info *move;
786 move = APR_ARRAY_IDX(moves, i, struct repos_move_info *);
787 if (strcmp(move->moved_from_repos_relpath, deleted_relpath) == 0)
790 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
794 /* This could be a nested move. Return the path-wise closest move. */
795 const apr_size_t c = svn_path_component_count(relpath);
798 else if (min_components == 0 || c < min_components)
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,
813 if (relpath && relpath[0] != '\0')
815 struct repos_move_info *move;
816 const char *moved_along_path =
817 svn_relpath_join(closest_move->moved_from_repos_relpath, relpath,
819 move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool);
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. */
832 find_nested_moves(apr_array_header_t *moves,
834 apr_array_header_t *deleted_paths,
835 apr_hash_t *moved_paths,
836 svn_revnum_t revision,
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)
845 apr_array_header_t *nested_moves;
847 apr_pool_t *iterpool;
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++)
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;
860 svn_boolean_t related;
862 svn_pool_clear(iterpool);
864 deleted_path = APR_ARRAY_IDX(deleted_paths, i, const char *);
865 move = map_deleted_path_to_move(deleted_path, moves, iterpool);
868 child_relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
870 if (child_relpath == NULL || child_relpath[0] == '\0')
871 continue; /* not a nested move */
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,
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 */
887 for (j = 0; j < copies_with_same_source_path->nelts; j++)
889 struct copy_info *copy;
891 copy = APR_ARRAY_IDX(copies_with_same_source_path, j,
893 SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url,
894 moved_along_repos_relpath,
901 struct repos_move_info *nested_move;
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,
907 revision, author, moved_paths,
908 ra_session, repos_root_url,
909 result_pool, iterpool));
911 /* Add this move to the list of nested moves in this revision. */
912 APR_ARRAY_PUSH(nested_moves, struct repos_move_info *) =
917 svn_pool_destroy(iterpool);
919 /* Add all nested moves found to the list of all moves in this revision. */
920 apr_array_cat(moves, nested_moves);
925 /* Make a shallow copy of the copied LOG_ITEM in COPIES. */
927 cache_copied_item(apr_hash_t *copies, const char *changed_path,
928 svn_log_changed_path2_t *log_item)
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;
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;
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)
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);
950 APR_ARRAY_PUSH(copies_with_same_source_path, struct copy_info *) = copy;
953 /* Implements svn_log_entry_receiver_t.
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.
960 * If no such revision can be found, leave BATON->DELETED_REV and
961 * BATON->REPLACING_NODE_KIND alone.
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.
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.
971 * If the node was moved, rather than deleted, return move information
975 find_deleted_rev(void *baton,
976 svn_log_entry_t *log_entry,
977 apr_pool_t *scratch_pool)
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;
985 if (b->ctx->notify_func2)
987 svn_wc_notify_t *notify;
989 notify = svn_wc_create_notify(
991 svn_wc_notify_tree_conflict_details_progress,
993 notify->revision = log_entry->revision;
994 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
997 /* No paths were changed in this revision. Nothing to do. */
998 if (! log_entry->changed_paths2)
1001 iterpool = svn_pool_create(scratch_pool);
1002 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
1004 hi = apr_hash_next(hi))
1006 const char *changed_path = apr_hash_this_key(hi);
1007 svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
1009 svn_pool_clear(iterpool);
1011 /* ### Remove leading slash from paths in log entries. */
1012 if (changed_path[0] == '/')
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'))
1020 deleted_node_found = TRUE;
1022 if (b->related_repos_relpath != NULL &&
1023 b->related_peg_rev != SVN_INVALID_REVNUM)
1025 svn_client__pathrev_t *yca_loc;
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,
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);
1041 /* ### Happens for moves within other moves and copies. */
1042 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
1044 svn_error_clear(err);
1048 return svn_error_trace(err);
1051 deleted_node_found = (yca_loc != NULL);
1054 if (deleted_node_found && log_item->action == 'R')
1055 replacing_node_kind = log_item->node_kind;
1058 svn_pool_destroy(iterpool);
1060 if (!deleted_node_found)
1062 apr_array_header_t *moves;
1064 if (b->moves_table == NULL)
1065 return SVN_NO_ERROR;
1067 moves = apr_hash_get(b->moves_table, &log_entry->revision,
1068 sizeof(svn_revnum_t));
1071 struct repos_move_info *move;
1073 move = map_deleted_path_to_move(b->deleted_repos_relpath,
1074 moves, scratch_pool);
1077 const char *relpath;
1079 /* The node was moved. Update our search path accordingly. */
1081 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
1082 b->deleted_repos_relpath);
1084 b->deleted_repos_relpath =
1085 svn_relpath_join(move->moved_from_repos_relpath, relpath,
1092 svn_string_t *author;
1094 b->deleted_rev = log_entry->revision;
1095 author = svn_hash_gets(log_entry->revprops,
1096 SVN_PROP_REVISION_AUTHOR);
1098 b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
1100 b->deleted_rev_author = _("unknown author");
1102 b->replacing_node_kind = replacing_node_kind;
1104 /* We're done. Abort the log operation. */
1105 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
1108 return SVN_NO_ERROR;
1111 /* Return a localised string representation of the local part of a tree
1112 conflict on a file. */
1113 static svn_error_t *
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)
1120 svn_wc_conflict_reason_t local_change;
1121 svn_wc_operation_t operation;
1123 local_change = svn_client_conflict_get_local_change(conflict);
1124 operation = svn_client_conflict_get_operation(conflict);
1126 switch (local_change)
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.");
1138 case svn_wc_conflict_reason_obstructed:
1139 *description = _("A file which already occupies this path was found "
1140 "in the working copy.");
1142 case svn_wc_conflict_reason_unversioned:
1143 *description = _("An unversioned file was found in the working "
1146 case svn_wc_conflict_reason_deleted:
1147 *description = _("A deleted file was found in the working copy.");
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)
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 "
1162 case svn_wc_conflict_reason_added:
1163 case svn_wc_conflict_reason_replaced:
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.");
1171 case svn_wc_conflict_reason_moved_away:
1173 const char *moved_to_abspath;
1176 err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
1178 conflict->local_abspath,
1183 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1185 moved_to_abspath = NULL;
1186 svn_error_clear(err);
1189 return svn_error_trace(err);
1191 if (operation == svn_wc_operation_update ||
1192 operation == svn_wc_operation_switch)
1194 if (moved_to_abspath == NULL)
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.");
1203 const char *wcroot_abspath;
1205 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1207 conflict->local_abspath,
1210 *description = apr_psprintf(
1212 _("The file in the working copy was "
1213 "moved away to\n'%s'."),
1214 svn_dirent_local_style(
1215 svn_dirent_skip_ancestor(
1221 else if (operation == svn_wc_operation_merge)
1223 if (moved_to_abspath == NULL)
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.");
1235 /* This is a local move in the working copy. */
1236 const char *wcroot_abspath;
1238 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1240 conflict->local_abspath,
1243 *description = apr_psprintf(
1245 _("The file in the working copy was "
1246 "moved away to\n'%s'."),
1247 svn_dirent_local_style(
1248 svn_dirent_skip_ancestor(
1256 case svn_wc_conflict_reason_moved_here:
1258 const char *moved_from_abspath;
1260 SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1262 conflict->local_abspath,
1265 if (operation == svn_wc_operation_update ||
1266 operation == svn_wc_operation_switch)
1268 if (moved_from_abspath == NULL)
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.");
1277 const char *wcroot_abspath;
1279 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1281 conflict->local_abspath,
1284 *description = apr_psprintf(
1286 _("A file was moved here in the "
1287 "working copy from\n'%s'."),
1288 svn_dirent_local_style(
1289 svn_dirent_skip_ancestor(
1291 moved_from_abspath),
1295 else if (operation == svn_wc_operation_merge)
1297 if (moved_from_abspath == NULL)
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.");
1309 const char *wcroot_abspath;
1311 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1313 conflict->local_abspath,
1316 /* This is a local move in the working copy. */
1317 *description = apr_psprintf(
1319 _("A file was moved here in the "
1320 "working copy from\n'%s'."),
1321 svn_dirent_local_style(
1322 svn_dirent_skip_ancestor(
1324 moved_from_abspath),
1332 return SVN_NO_ERROR;
1335 /* Return a localised string representation of the local part of a tree
1336 conflict on a directory. */
1337 static svn_error_t *
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)
1344 svn_wc_conflict_reason_t local_change;
1345 svn_wc_operation_t operation;
1347 local_change = svn_client_conflict_get_local_change(conflict);
1348 operation = svn_client_conflict_get_operation(conflict);
1350 switch (local_change)
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.");
1362 case svn_wc_conflict_reason_obstructed:
1363 *description = _("A directory which already occupies this path was "
1364 "found in the working copy.");
1366 case svn_wc_conflict_reason_unversioned:
1367 *description = _("An unversioned directory was found in the "
1370 case svn_wc_conflict_reason_deleted:
1371 *description = _("A deleted directory was found in the "
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)
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?");
1387 case svn_wc_conflict_reason_added:
1388 case svn_wc_conflict_reason_replaced:
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.");
1396 case svn_wc_conflict_reason_moved_away:
1398 const char *moved_to_abspath;
1401 err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
1403 conflict->local_abspath,
1408 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1410 moved_to_abspath = NULL;
1411 svn_error_clear(err);
1414 return svn_error_trace(err);
1417 if (operation == svn_wc_operation_update ||
1418 operation == svn_wc_operation_switch)
1420 if (moved_to_abspath == NULL)
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.");
1429 const char *wcroot_abspath;
1431 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1433 conflict->local_abspath,
1436 *description = apr_psprintf(
1438 _("The directory in the working copy "
1439 "was moved away to\n'%s'."),
1440 svn_dirent_local_style(
1441 svn_dirent_skip_ancestor(
1447 else if (operation == svn_wc_operation_merge)
1449 if (moved_to_abspath == NULL)
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 "
1461 /* This is a local move in the working copy. */
1462 const char *wcroot_abspath;
1464 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1466 conflict->local_abspath,
1469 *description = apr_psprintf(
1471 _("The directory was moved away to\n"
1473 svn_dirent_local_style(
1474 svn_dirent_skip_ancestor(
1482 case svn_wc_conflict_reason_moved_here:
1484 const char *moved_from_abspath;
1486 SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1488 conflict->local_abspath,
1491 if (operation == svn_wc_operation_update ||
1492 operation == svn_wc_operation_switch)
1494 if (moved_from_abspath == NULL)
1496 /* The move no longer exists. */
1497 *description = _("A directory had been moved here at "
1498 "the time this conflict was "
1503 const char *wcroot_abspath;
1505 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1507 conflict->local_abspath,
1510 *description = apr_psprintf(
1512 _("A directory was moved here from\n"
1514 svn_dirent_local_style(
1515 svn_dirent_skip_ancestor(
1517 moved_from_abspath),
1521 else if (operation == svn_wc_operation_merge)
1523 if (moved_from_abspath == NULL)
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 "
1535 /* This is a local move in the working copy. */
1536 const char *wcroot_abspath;
1538 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1540 conflict->local_abspath,
1543 *description = apr_psprintf(
1545 _("A directory was moved here in "
1546 "the working copy from\n'%s'."),
1547 svn_dirent_local_style(
1548 svn_dirent_skip_ancestor(
1550 moved_from_abspath),
1557 return SVN_NO_ERROR;
1560 struct find_moves_baton
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;
1570 /* A hash table mapping a revision number to an array of struct
1571 * repos_move_info * elements, describing moves.
1573 * Must be allocated in RESULT_POOL by the caller of svn_ra_get_log2().
1575 * If the node was moved, the DELETED_REV is present in this table,
1576 * perhaps along with additional revisions.
1578 * Given a sequence of moves which happened in the repository, such as:
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)]
1588 * This allows us to later find relevant moves based on a revision number.
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)]
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.
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.
1609 apr_hash_t *moves_table;
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. */
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;
1619 /* Extra RA session that can be used to make additional requests. */
1620 svn_ra_session_t *extra_ra_session;
1623 /* Implements svn_log_entry_receiver_t. */
1624 static svn_error_t *
1625 find_moves(void *baton, svn_log_entry_t *log_entry, apr_pool_t *scratch_pool)
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;
1632 apr_array_header_t *moves;
1634 if (b->ctx->notify_func2)
1636 svn_wc_notify_t *notify;
1638 notify = svn_wc_create_notify(
1640 svn_wc_notify_tree_conflict_details_progress,
1642 notify->revision = log_entry->revision;
1643 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
1646 /* No paths were changed in this revision. Nothing to do. */
1647 if (! log_entry->changed_paths2)
1648 return SVN_NO_ERROR;
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);
1655 hi = apr_hash_next(hi))
1657 const char *changed_path = apr_hash_this_key(hi);
1658 svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
1660 svn_pool_clear(iterpool);
1662 /* ### Remove leading slash from paths in log entries. */
1663 if (changed_path[0] == '/')
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);
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);
1675 svn_pool_destroy(iterpool);
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));
1684 moves = apr_hash_get(b->moves_table, &log_entry->revision,
1685 sizeof(svn_revnum_t));
1688 const svn_string_t *author;
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"),
1696 b->extra_ra_session, b->ctx,
1697 b->result_pool, scratch_pool));
1700 return SVN_NO_ERROR;
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 *
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)
1718 svn_ra_session_t *ra_session;
1720 const char *corrected_url;
1721 apr_array_header_t *paths;
1722 apr_array_header_t *revprops;
1723 struct find_moves_baton b = { 0 };
1725 SVN_ERR_ASSERT(start_rev > end_rev);
1727 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
1729 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
1730 url, NULL, NULL, FALSE, FALSE,
1734 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
1735 APR_ARRAY_PUSH(paths, const char *) = "";
1737 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
1738 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
1740 b.repos_root_url = repos_root_url;
1741 b.repos_uuid = repos_uuid;
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));
1750 SVN_ERR(svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
1752 TRUE, /* need the changed paths list */
1753 FALSE, /* need to traverse copies */
1754 FALSE, /* no need for merged revisions */
1759 *moves_table = b.moves_table;
1761 return SVN_NO_ERROR;
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 *
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)
1775 struct repos_move_info *new_move;
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,
1781 new_move->moved_to_repos_relpath =
1782 svn_relpath_join(move->moved_to_repos_relpath, moved_along_relpath,
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. */
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 *
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)
1805 apr_pool_t *iterpool;
1807 iterpool = svn_pool_create(scratch_pool);
1808 for (i = 0; i < moves_in_revision->nelts; i++)
1810 struct repos_move_info *move;
1811 const char *relpath;
1812 const char *deleted_repos_relpath;
1813 svn_boolean_t related;
1816 svn_pool_clear(iterpool);
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)
1825 /* It does. So our node must have been deleted again. */
1826 deleted_repos_relpath = svn_relpath_join(move->moved_from_repos_relpath,
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)
1839 svn_error_clear(err);
1847 struct repos_move_info *new_move;
1849 /* We have a winner. */
1850 new_move = new_path_adjusted_move(move, relpath, prev_move->node_kind,
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;
1858 svn_pool_destroy(iterpool);
1860 return SVN_NO_ERROR;
1864 compare_items_as_revs(const svn_sort__item_t *a, const svn_sort__item_t *b)
1866 return svn_sort_compare_revisions(a->key, b->key);
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 *
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)
1881 apr_array_header_t *moves;
1882 apr_array_header_t *revisions;
1883 apr_pool_t *iterpool;
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++)
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;
1894 svn_pool_clear(iterpool);
1896 if (rev <= move->rev)
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));
1906 svn_pool_destroy(iterpool);
1908 return SVN_NO_ERROR;
1911 /* Trace all future moves of the node moved by MOVE.
1912 * Update MOVE->PREV and MOVE->NEXT accordingly. */
1913 static svn_error_t *
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)
1921 apr_array_header_t *next_moves;
1923 SVN_ERR(find_next_moves(&next_moves, moves_table, move,
1924 ra_session, repos_root_url,
1925 result_pool, scratch_pool));
1929 apr_pool_t *iterpool;
1931 move->next = next_moves;
1932 iterpool = svn_pool_create(scratch_pool);
1933 for (i = 0; i < next_moves->nelts; i++)
1935 struct repos_move_info *next_move;
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));
1944 svn_pool_destroy(iterpool);
1947 return SVN_NO_ERROR;
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 *
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)
1962 apr_pool_t *iterpool;
1966 iterpool = svn_pool_create(scratch_pool);
1967 for (i = 0; i < moves_in_revision->nelts; i++)
1969 struct repos_move_info *move;
1970 const char *relpath;
1971 const char *deleted_repos_relpath;
1972 svn_boolean_t related;
1975 svn_pool_clear(iterpool);
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)
1984 /* It does. So our node must have been deleted. */
1985 deleted_repos_relpath = svn_relpath_join(
1986 next_move->moved_from_repos_relpath,
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,
1996 FALSE, scratch_pool);
1997 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1999 svn_error_clear(err);
2007 /* We have a winner. */
2008 *prev_move = new_path_adjusted_move(move, relpath,
2009 next_move->node_kind,
2014 svn_pool_destroy(iterpool);
2016 return SVN_NO_ERROR;
2020 compare_items_as_revs_reverse(const svn_sort__item_t *a,
2021 const svn_sort__item_t *b)
2023 int c = svn_sort_compare_revisions(a->key, b->key);
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 *
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)
2043 apr_array_header_t *moves;
2044 apr_array_header_t *revisions;
2045 apr_pool_t *iterpool;
2049 revisions = svn_sort__hash(moves_table, compare_items_as_revs_reverse,
2051 iterpool = svn_pool_create(scratch_pool);
2052 for (i = 0; i < revisions->nelts; i++)
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;
2057 svn_pool_clear(iterpool);
2059 if (rev >= move->rev)
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));
2069 svn_pool_destroy(iterpool);
2071 return SVN_NO_ERROR;
2075 /* Trace all past moves of the node moved by MOVE.
2076 * Update MOVE->PREV and MOVE->NEXT accordingly. */
2077 static svn_error_t *
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)
2085 struct repos_move_info *prev_move;
2087 SVN_ERR(find_prev_move(&prev_move, moves_table, move,
2088 ra_session, repos_root_url,
2089 result_pool, scratch_pool));
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;
2097 SVN_ERR(trace_moved_node_backwards(moves_table, prev_move,
2098 ra_session, repos_root_url,
2099 result_pool, scratch_pool));
2102 return SVN_NO_ERROR;
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.
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 *
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)
2125 apr_array_header_t *moves_in_deleted_rev;
2127 apr_pool_t *iterpool;
2128 const char *session_url, *url = NULL;
2130 moves_in_deleted_rev = apr_hash_get(moves_table, &deleted_rev,
2131 sizeof(deleted_rev));
2132 if (moves_in_deleted_rev == NULL)
2135 return SVN_NO_ERROR;
2138 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool));
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++)
2145 struct repos_move_info *move;
2146 const char *relpath;
2148 svn_pool_clear(iterpool);
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)
2153 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
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')
2162 struct repos_move_info *nested_move;
2163 const char *actual_deleted_repos_relpath;
2165 actual_deleted_repos_relpath =
2166 svn_relpath_join(move->moved_from_repos_relpath, relpath,
2168 nested_move = map_deleted_path_to_move(actual_deleted_repos_relpath,
2169 moves_in_deleted_rev,
2172 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = nested_move;
2177 SVN_ERR(svn_ra_reparent(ra_session, session_url, scratch_pool));
2179 /* If we didn't find any applicable moves, return NULL. */
2180 if ((*moves)->nelts == 0)
2183 svn_pool_destroy(iterpool);
2184 return SVN_NO_ERROR;
2187 /* Figure out what happened to these moves in future revisions. */
2188 for (i = 0; i < (*moves)->nelts; i++)
2190 struct repos_move_info *move;
2192 svn_pool_clear(iterpool);
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));
2199 svn_pool_destroy(iterpool);
2200 return SVN_NO_ERROR;
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.
2214 static svn_error_t *
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)
2230 svn_ra_session_t *ra_session;
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;
2240 apr_hash_t *moves_table;
2242 SVN_ERR_ASSERT(start_rev > end_rev);
2244 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
2245 conflict, scratch_pool,
2247 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
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));
2255 url = svn_path_url_add_component2(repos_root_url, parent_repos_relpath,
2257 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
2258 url, NULL, NULL, FALSE, FALSE,
2262 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
2263 APR_ARRAY_PUSH(paths, const char *) = "";
2265 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
2266 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
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;
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));
2284 err = svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
2286 TRUE, /* need the changed paths list */
2287 FALSE, /* need to traverse copies */
2288 FALSE, /* no need for merged revisions */
2290 find_deleted_rev, &b,
2294 if (err->apr_err == SVN_ERR_CEASE_INVOCATION &&
2295 b.deleted_rev != SVN_INVALID_REVNUM)
2298 /* Log operation was aborted because we found deleted rev. */
2299 svn_error_clear(err);
2302 return svn_error_trace(err);
2305 if (b.deleted_rev == SVN_INVALID_REVNUM)
2307 struct repos_move_info *move = b.move;
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,
2317 ra_session, repos_root_url,
2318 result_pool, scratch_pool));
2322 /* We could not determine the revision in which the node was
2324 *deleted_rev = SVN_INVALID_REVNUM;
2325 *deleted_rev_author = NULL;
2326 *replacing_node_kind = svn_node_unknown;
2330 return SVN_NO_ERROR;
2334 *deleted_rev = b.deleted_rev;
2335 *deleted_rev_author = b.deleted_rev_author;
2336 *replacing_node_kind = b.replacing_node_kind;
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));
2344 return SVN_NO_ERROR;
2347 /* Details for tree conflicts involving a locally missing node. */
2348 struct conflict_tree_local_missing_details
2350 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
2351 svn_revnum_t deleted_rev;
2353 /* ### Add 'added_rev', like in conflict_tree_incoming_delete_details? */
2355 /* Author who committed DELETED_REV. */
2356 const char *deleted_rev_author;
2358 /* The path which was deleted relative to the repository root. */
2359 const char *deleted_repos_relpath;
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;
2366 /* If moves is not NULL, a map of repos_relpaths and working copy nodes.
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.
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.
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;
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;
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;
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;
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;
2407 static svn_error_t *
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)
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;
2425 *related_repos_relpath = NULL;
2426 *related_peg_rev = SVN_INVALID_REVNUM;
2428 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
2430 scratch_pool, scratch_pool));
2431 related_url = svn_path_url_add_component2(repos_root_url,
2432 younger_related_repos_relpath,
2434 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
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)
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;
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,
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));
2472 /* If we can't find a related node, bail. */
2473 if (related_deleted_rev == SVN_INVALID_REVNUM)
2474 return SVN_NO_ERROR;
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);
2482 *related_repos_relpath = younger_related_repos_relpath;
2483 *related_peg_rev = younger_related_peg_rev;
2486 return SVN_NO_ERROR;
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.
2492 * VICTIM_ABSPATH is the abspath of a conflict victim in the working copy and
2493 * will be used in notifications.
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 *
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)
2511 apr_hash_t *moves_table;
2512 apr_array_header_t *revs;
2513 apr_array_header_t *most_recent_moves = NULL;
2515 apr_pool_t *iterpool;
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));
2524 iterpool = svn_pool_create(scratch_pool);
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--)
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));
2535 svn_pool_clear(iterpool);
2537 /* Was repos relpath moved to its location in this revision? */
2538 for (j = 0; j < moves_in_rev->nelts; j++)
2540 struct repos_move_info *move;
2541 const char *relpath;
2543 move = APR_ARRAY_IDX(moves_in_rev, j, struct repos_move_info *);
2544 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
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)
2552 svn_client__pathrev_t *yca_loc;
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);
2561 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
2563 svn_error_clear(err);
2567 return svn_error_trace(err);
2570 if (yca_loc == NULL || yca_loc->rev != move->rev)
2574 if (most_recent_moves == NULL)
2576 apr_array_make(result_pool, 1,
2577 sizeof(struct repos_move_info *));
2579 /* Copy the move to result pool (even if relpath is ""). */
2580 move = new_path_adjusted_move(move, relpath, node_kind,
2582 APR_ARRAY_PUSH(most_recent_moves,
2583 struct repos_move_info *) = move;
2587 /* If we found one move, or several ambiguous moves, we're done. */
2588 if (most_recent_moves)
2592 if (most_recent_moves && most_recent_moves->nelts > 0)
2594 *moves = apr_array_make(result_pool, 1,
2595 sizeof(struct repos_move_info *));
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++)
2601 struct repos_move_info *move;
2603 svn_pool_clear(iterpool);
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. */
2613 /* Return move heads. */
2614 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
2618 svn_pool_destroy(iterpool);
2620 return SVN_NO_ERROR;
2623 static svn_error_t *
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)
2632 const char *basename;
2633 apr_array_header_t *abspaths;
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,
2641 apr_array_cat(candidates, abspaths);
2645 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2646 for (i = 0; i < move->next->nelts; i++)
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);
2655 svn_pool_destroy(iterpool);
2658 return SVN_NO_ERROR;
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 *
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)
2678 apr_array_header_t *candidate_abspaths;
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));
2687 if (candidate_abspaths->nelts > 0)
2689 apr_array_header_t *moved_to_abspaths;
2691 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2693 moved_to_abspaths = apr_array_make(result_pool, 1,
2694 sizeof (const char *));
2696 for (i = 0; i < candidate_abspaths->nelts; i++)
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;
2704 svn_pool_clear(iterpool);
2706 candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
2708 SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
2709 &candidate_repos_relpath,
2716 iterpool, iterpool));
2718 if (candidate_revision == SVN_INVALID_REVNUM)
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)
2726 svn_client__pathrev_t *yca_loc;
2729 err = find_yca(&yca_loc, victim_repos_relpath,
2731 candidate_repos_relpath,
2733 repos_root_url, repos_uuid,
2734 NULL, ctx, iterpool, iterpool);
2737 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
2739 svn_error_clear(err);
2743 return svn_error_trace(err);
2746 if (yca_loc == NULL)
2750 APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
2751 apr_pstrdup(result_pool, candidate_abspath);
2753 svn_pool_destroy(iterpool);
2755 svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
2762 apr_pool_t *iterpool;
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++)
2768 struct repos_move_info *next_move;
2770 svn_pool_clear(iterpool);
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));
2779 svn_pool_destroy(iterpool);
2782 return SVN_NO_ERROR;
2785 /* Implements tree_conflict_get_details_func_t. */
2786 static svn_error_t *
2787 conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
2788 svn_client_ctx_t *ctx,
2789 apr_pool_t *scratch_pool)
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;
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));
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,
2827 SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev, &parent_repos_relpath,
2828 &repos_root_url, &repos_uuid,
2831 conflict->local_abspath,
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;
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);
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));
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,
2860 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
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));
2874 end_rev = yca_loc->rev;
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;
2882 end_rev = 0; /* ### We might walk through all of history... */
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));
2891 /* If the victim was not deleted then check if the related path was moved. */
2892 if (deleted_rev == SVN_INVALID_REVNUM)
2894 const char *victim_abspath;
2895 svn_node_kind_t related_node_kind;
2896 apr_array_header_t *candidates;
2898 apr_pool_t *iterpool;
2900 /* ### The following describes all moves in terms of forward-merges,
2901 * should do we something else for reverse-merges? */
2903 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
2907 end_rev = yca_loc->rev;
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;
2915 end_rev = 0; /* ### We might walk through all of history... */
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,
2925 repos_root_url, repos_uuid,
2927 conflict->pool, scratch_pool));
2929 if (sibling_moves == NULL)
2930 return SVN_NO_ERROR;
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.
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.
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++)
2948 struct repos_move_info *move;
2951 svn_pool_clear(iterpool);
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,
2957 ? new_kind : old_kind,
2958 move, ctx, iterpool,
2961 /* Determine whether a candidate node shares a YCA with the victim. */
2962 for (j = 0; j < candidates->nelts; j++)
2964 const char *candidate_abspath;
2965 const char *candidate_repos_relpath;
2966 svn_revnum_t candidate_revision;
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,
2976 iterpool, iterpool));
2977 err = find_yca(&yca_loc,
2979 ? new_repos_relpath : old_repos_relpath,
2980 old_rev < new_rev ? new_rev : old_rev,
2981 candidate_repos_relpath,
2983 repos_root_url, repos_uuid,
2984 NULL, ctx, iterpool, iterpool);
2987 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
2989 svn_error_clear(err);
2993 return svn_error_trace(err);
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);
3006 svn_pool_destroy(iterpool);
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,
3016 details->moves = moves;
3017 if (details->moves != NULL)
3019 apr_pool_t *iterpool;
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++)
3026 struct repos_move_info *move;
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,
3035 scratch_pool, iterpool));
3037 svn_pool_destroy(iterpool);
3039 if (apr_hash_count(details->wc_move_targets) > 0)
3041 apr_array_header_t *move_target_repos_relpaths;
3042 const svn_sort__item_t *item;
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,
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;
3057 details->move_target_repos_relpath = NULL;
3058 details->wc_move_target_idx = 0;
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)
3066 apr_array_header_t *wc_abspaths;
3068 wc_abspaths = svn_hash_gets(details->wc_move_targets,
3069 details->move_target_repos_relpath);
3070 if (wc_abspaths->nelts == 1)
3072 svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
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;
3082 else if (details->wc_siblings && details->wc_siblings->nelts == 1)
3084 svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
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;
3094 conflict->tree_conflict_local_details = details;
3096 return SVN_NO_ERROR;
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 *
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)
3107 svn_wc_conflict_reason_t local_change;
3108 svn_wc_operation_t operation;
3110 local_change = svn_client_conflict_get_local_change(conflict);
3111 operation = svn_client_conflict_get_operation(conflict);
3113 switch (local_change)
3115 case svn_wc_conflict_reason_edited:
3116 *description = _("An item containing uncommitted changes was "
3117 "found in the working copy.");
3119 case svn_wc_conflict_reason_obstructed:
3120 *description = _("An item which already occupies this path was found in "
3121 "the working copy.");
3123 case svn_wc_conflict_reason_deleted:
3124 *description = _("A deleted item was found in the working copy.");
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 "
3131 else if (operation == svn_wc_operation_merge)
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.");
3140 case svn_wc_conflict_reason_unversioned:
3141 *description = _("An unversioned item was found in the working "
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 "
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.");
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.");
3160 return SVN_NO_ERROR;
3163 /* Append a description of a move chain beginning at NEXT to DESCRIPTION. */
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)
3175 struct repos_move_info *move;
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 *);
3182 description = apr_psprintf(scratch_pool,
3183 _("%s\nAnd then moved away to '^/%s' by "
3185 description, move->moved_to_repos_relpath,
3186 move->rev_author, move->rev);
3190 return apr_pstrdup(result_pool, description);
3193 /* Implements tree_conflict_get_description_func_t. */
3194 static svn_error_t *
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)
3201 svn_node_kind_t victim_node_kind;
3203 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
3205 *description = NULL;
3207 switch (victim_node_kind)
3210 case svn_node_symlink:
3211 SVN_ERR(describe_local_file_node_change(description, conflict, ctx,
3212 result_pool, scratch_pool));
3215 SVN_ERR(describe_local_dir_node_change(description, conflict, ctx,
3216 result_pool, scratch_pool));
3219 case svn_node_unknown:
3220 SVN_ERR(describe_local_none_node_change(description, conflict,
3221 result_pool, scratch_pool));
3225 return SVN_NO_ERROR;
3228 /* Implements tree_conflict_get_description_func_t. */
3229 static svn_error_t *
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)
3236 struct conflict_tree_local_missing_details *details;
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));
3244 if (details->moves || details->sibling_moves)
3246 struct repos_move_info *move;
3248 *description = _("No such file or directory was found in the "
3249 "merge target working copy.\n");
3253 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3254 if (move->node_kind == svn_node_file)
3255 *description = apr_psprintf(
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(
3263 _("%sThe directory was moved to '^/%s' in "
3265 *description, move->moved_to_repos_relpath,
3266 move->rev, move->rev_author);
3268 *description = apr_psprintf(
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,
3279 if (details->sibling_moves)
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(
3286 _("%sThe file '^/%s' was moved to '^/%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(
3294 _("%sThe directory '^/%s' was moved to '^/%s' "
3296 *description, move->moved_from_repos_relpath,
3297 move->moved_to_repos_relpath,
3298 move->rev, move->rev_author);
3300 *description = apr_psprintf(
3302 _("%sThe item '^/%s' was moved to '^/%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,
3314 *description = apr_psprintf(
3316 _("No such file or directory was found in the "
3317 "merge target working copy.\n'^/%s' was deleted "
3319 details->deleted_repos_relpath,
3320 details->deleted_rev, details->deleted_rev_author);
3322 return SVN_NO_ERROR;
3325 /* Return a localised string representation of the incoming part of a
3326 conflict; NULL for non-localised odd cases. */
3328 describe_incoming_change(svn_node_kind_t kind, svn_wc_conflict_action_t action,
3329 svn_wc_operation_t operation)
3334 case svn_node_symlink:
3335 if (operation == svn_wc_operation_update)
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 "
3346 case svn_wc_conflict_action_replace:
3347 return _("An update operation tried to replace a file.");
3350 else if (operation == svn_wc_operation_switch)
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 "
3361 case svn_wc_conflict_action_replace:
3362 return _("A switch operation tried to replace a file.");
3365 else if (operation == svn_wc_operation_merge)
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 "
3376 case svn_wc_conflict_action_replace:
3377 return _("A merge operation tried to replace a file.");
3382 if (operation == svn_wc_operation_update)
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 "
3393 case svn_wc_conflict_action_replace:
3394 return _("An update operation tried to replace a directory.");
3397 else if (operation == svn_wc_operation_switch)
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 "
3408 case svn_wc_conflict_action_replace:
3409 return _("A switch operation tried to replace a directory.");
3412 else if (operation == svn_wc_operation_merge)
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 "
3423 case svn_wc_conflict_action_replace:
3424 return _("A merge operation tried to replace a directory.");
3429 case svn_node_unknown:
3430 if (operation == svn_wc_operation_update)
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 "
3441 case svn_wc_conflict_action_replace:
3442 return _("An update operation tried to replace an item.");
3445 else if (operation == svn_wc_operation_switch)
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 "
3456 case svn_wc_conflict_action_replace:
3457 return _("A switch operation tried to replace an item.");
3460 else if (operation == svn_wc_operation_merge)
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 "
3471 case svn_wc_conflict_action_replace:
3472 return _("A merge operation tried to replace an item.");
3481 /* Return a localised string representation of the operation part of a
3484 operation_str(svn_wc_operation_t operation)
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");
3493 SVN_ERR_MALFUNCTION_NO_RETURN();
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)
3503 const char *reason_str, *action_str;
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))
3509 case svn_wc_conflict_reason_edited:
3510 reason_str = _("local edit");
3512 case svn_wc_conflict_reason_added:
3513 reason_str = _("local add");
3515 case svn_wc_conflict_reason_deleted:
3516 reason_str = _("local delete");
3518 case svn_wc_conflict_reason_obstructed:
3519 reason_str = _("local obstruction");
3522 reason_str = apr_psprintf(
3523 scratch_pool, _("local %s"),
3525 map_conflict_reason,
3526 svn_client_conflict_get_local_change(conflict)));
3529 switch (svn_client_conflict_get_incoming_change(conflict))
3531 case svn_wc_conflict_action_edit:
3532 action_str = _("incoming edit");
3534 case svn_wc_conflict_action_add:
3535 action_str = _("incoming add");
3537 case svn_wc_conflict_action_delete:
3538 action_str = _("incoming delete");
3541 action_str = apr_psprintf(
3542 scratch_pool, _("incoming %s"),
3544 map_conflict_action,
3545 svn_client_conflict_get_incoming_change(conflict)));
3548 SVN_ERR_ASSERT(reason_str && action_str);
3550 *description = apr_psprintf(result_pool, _("%s, %s %s"),
3551 reason_str, action_str,
3553 svn_client_conflict_get_operation(conflict)));
3555 return SVN_NO_ERROR;
3558 /* Implements tree_conflict_get_description_func_t. */
3559 static svn_error_t *
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)
3568 svn_node_kind_t incoming_kind;
3569 svn_wc_conflict_action_t conflict_action;
3570 svn_wc_operation_t conflict_operation;
3572 conflict_action = svn_client_conflict_get_incoming_change(conflict);
3573 conflict_operation = svn_client_conflict_get_operation(conflict);
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)
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,
3585 else if (conflict_action == svn_wc_conflict_action_add ||
3586 conflict_action == svn_wc_conflict_action_replace)
3588 /* Change is acting on 'src_right' version of the node.
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,
3597 action = describe_incoming_change(incoming_kind, conflict_action,
3598 conflict_operation);
3601 *incoming_change_description = apr_pstrdup(result_pool, action);
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,
3614 return SVN_NO_ERROR;
3617 /* Details for tree conflicts involving incoming deletions and replacements. */
3618 struct conflict_tree_incoming_delete_details
3620 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
3621 svn_revnum_t deleted_rev;
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;
3627 /* The path which was deleted/added relative to the repository root. */
3628 const char *repos_relpath;
3630 /* Author who committed DELETED_REV/ADDED_REV. */
3631 const char *rev_author;
3633 /* New node kind for a replaced node. This is svn_node_none for deletions. */
3634 svn_node_kind_t replacing_node_kind;
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
3640 apr_array_header_t *moves;
3642 /* A map of repos_relpaths and working copy nodes for an incoming move.
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.
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.
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;
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;
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;
3668 /* Get the currently selected repository-side move target path.
3669 * If none was selected yet, determine and return a default one. */
3671 get_moved_to_repos_relpath(
3672 struct conflict_tree_incoming_delete_details *details,
3673 apr_pool_t *scratch_pool)
3675 struct repos_move_info *move;
3677 if (details->move_target_repos_relpath)
3678 return details->move_target_repos_relpath;
3680 if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) > 0)
3682 svn_sort__item_t item;
3683 apr_array_header_t *repos_relpaths;
3685 repos_relpaths = svn_sort__hash(details->wc_move_targets,
3686 svn_sort_compare_items_as_paths,
3688 item = APR_ARRAY_IDX(repos_relpaths, 0, svn_sort__item_t);
3689 return (const char *)item.key;
3692 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3693 return move->moved_to_repos_relpath;
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)
3705 if (details->replacing_node_kind == svn_node_file ||
3706 details->replacing_node_kind == svn_node_symlink)
3708 if (victim_node_kind == svn_node_dir)
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."),
3715 details->rev_author, details->deleted_rev);
3718 struct repos_move_info *move;
3720 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
3733 else if (victim_node_kind == svn_node_file ||
3734 victim_node_kind == svn_node_symlink)
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 "
3742 details->rev_author, details->deleted_rev);
3745 struct repos_move_info *move;
3747 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3749 apr_psprintf(result_pool,
3750 _("%s\nThe replaced file was moved to '^/%s'."),
3752 get_moved_to_repos_relpath(details, scratch_pool));
3753 return append_moved_to_chain_description(description,
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);
3769 struct repos_move_info *move;
3771 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3773 apr_psprintf(result_pool,
3774 _("%s\nThe replaced item was moved to '^/%s'."),
3776 get_moved_to_repos_relpath(details, scratch_pool));
3777 return append_moved_to_chain_description(description,
3785 else if (details->replacing_node_kind == svn_node_dir)
3787 if (victim_node_kind == svn_node_dir)
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."),
3795 details->rev_author, details->deleted_rev);
3798 struct repos_move_info *move;
3800 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
3813 else if (victim_node_kind == svn_node_file ||
3814 victim_node_kind == svn_node_symlink)
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."),
3821 details->rev_author, details->deleted_rev);
3824 struct repos_move_info *move;
3826 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3828 apr_psprintf(result_pool,
3829 _("%s\nThe replaced file was moved to '^/%s'."),
3831 get_moved_to_repos_relpath(details, scratch_pool));
3832 return append_moved_to_chain_description(description,
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);
3848 struct repos_move_info *move;
3850 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3852 apr_psprintf(result_pool,
3853 _("%s\nThe replaced item was moved to '^/%s'."),
3855 get_moved_to_repos_relpath(details, scratch_pool));
3856 return append_moved_to_chain_description(description,
3866 if (victim_node_kind == svn_node_dir)
3870 const char *description;
3871 struct repos_move_info *move;
3873 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3875 apr_psprintf(result_pool,
3876 _("Directory updated from r%ld to r%ld was "
3877 "moved to '^/%s' by %s in r%ld."),
3879 get_moved_to_repos_relpath(details, scratch_pool),
3880 details->rev_author, details->deleted_rev);
3881 return append_moved_to_chain_description(description,
3887 return apr_psprintf(result_pool,
3888 _("Directory updated from r%ld to r%ld was "
3889 "deleted by %s in r%ld."),
3891 details->rev_author, details->deleted_rev);
3893 else if (victim_node_kind == svn_node_file ||
3894 victim_node_kind == svn_node_symlink)
3898 struct repos_move_info *move;
3899 const char *description;
3901 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
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);
3923 const char *description;
3924 struct repos_move_info *move;
3926 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
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);
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)
3955 if (details->replacing_node_kind == svn_node_file ||
3956 details->replacing_node_kind == svn_node_symlink)
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."),
3971 details->rev_author, details->added_rev);
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."),
3977 details->rev_author, details->added_rev);
3979 else if (details->replacing_node_kind == svn_node_dir)
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);
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."),
4000 details->rev_author, details->added_rev);
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."),
4016 details->rev_author, details->added_rev);
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."),
4022 details->rev_author, details->added_rev);
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)
4037 if (details->replacing_node_kind == svn_node_file ||
4038 details->replacing_node_kind == svn_node_symlink)
4040 if (victim_node_kind == svn_node_dir)
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);
4052 struct repos_move_info *move;
4054 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
4067 else if (victim_node_kind == svn_node_file ||
4068 victim_node_kind == svn_node_symlink)
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);
4081 struct repos_move_info *move;
4083 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4085 apr_psprintf(result_pool,
4086 _("%s\nThe replaced file was moved to '^/%s'."),
4088 get_moved_to_repos_relpath(details, scratch_pool));
4089 return append_moved_to_chain_description(description,
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);
4108 struct repos_move_info *move;
4110 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4112 apr_psprintf(result_pool,
4113 _("%s\nThe replaced item was moved to '^/%s'."),
4115 get_moved_to_repos_relpath(details, scratch_pool));
4116 return append_moved_to_chain_description(description,
4124 else if (details->replacing_node_kind == svn_node_dir)
4126 if (victim_node_kind == svn_node_dir)
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);
4139 struct repos_move_info *move;
4141 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
4154 else if (victim_node_kind == svn_node_file ||
4155 victim_node_kind == svn_node_symlink)
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);
4167 struct repos_move_info *move;
4169 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4171 apr_psprintf(result_pool,
4172 _("%s\nThe replaced file was moved to '^/%s'."),
4174 get_moved_to_repos_relpath(details, scratch_pool));
4175 return append_moved_to_chain_description(description,
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);
4194 struct repos_move_info *move;
4196 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4198 apr_psprintf(result_pool,
4199 _("%s\nThe replaced item was moved to '^/%s'."),
4201 get_moved_to_repos_relpath(details, scratch_pool));
4202 return append_moved_to_chain_description(description,
4212 if (victim_node_kind == svn_node_dir)
4216 struct repos_move_info *move;
4217 const char *description;
4219 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
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);
4243 else if (victim_node_kind == svn_node_file ||
4244 victim_node_kind == svn_node_symlink)
4248 struct repos_move_info *move;
4249 const char *description;
4251 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
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);
4279 struct repos_move_info *move;
4280 const char *description;
4282 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
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);
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)
4319 if (details->replacing_node_kind == svn_node_file ||
4320 details->replacing_node_kind == svn_node_symlink)
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 "
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);
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);
4350 else if (details->replacing_node_kind == svn_node_dir)
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 "
4368 old_repos_relpath, old_rev,
4369 new_repos_relpath, new_rev,
4370 details->rev_author, details->added_rev);
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);
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 "
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 "
4398 old_repos_relpath, old_rev,
4399 new_repos_relpath, new_rev,
4400 details->rev_author, details->added_rev);
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 "
4407 old_repos_relpath, old_rev,
4408 new_repos_relpath, new_rev,
4409 details->rev_author, details->added_rev);
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)
4424 if (details->replacing_node_kind == svn_node_file ||
4425 details->replacing_node_kind == svn_node_symlink)
4427 if (victim_node_kind == svn_node_dir)
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);
4439 struct repos_move_info *move;
4441 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
4454 else if (victim_node_kind == svn_node_file ||
4455 victim_node_kind == svn_node_symlink)
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);
4468 struct repos_move_info *move;
4470 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4472 apr_psprintf(result_pool,
4473 _("%s\nThe replaced file was moved to '^/%s'."),
4475 get_moved_to_repos_relpath(details, scratch_pool));
4476 return append_moved_to_chain_description(description,
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);
4492 else if (details->replacing_node_kind == svn_node_dir)
4494 if (victim_node_kind == svn_node_dir)
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);
4507 struct repos_move_info *move;
4509 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
4522 else if (victim_node_kind == svn_node_file ||
4523 victim_node_kind == svn_node_symlink)
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);
4535 struct repos_move_info *move;
4537 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4539 apr_psprintf(result_pool,
4540 _("%s\nThe replaced file was moved to '^/%s'."),
4542 get_moved_to_repos_relpath(details, scratch_pool));
4543 return append_moved_to_chain_description(description,
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);
4562 struct repos_move_info *move;
4564 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4566 apr_psprintf(result_pool,
4567 _("%s\nThe replaced item was moved to '^/%s'."),
4569 get_moved_to_repos_relpath(details, scratch_pool));
4570 return append_moved_to_chain_description(description,
4580 if (victim_node_kind == svn_node_dir)
4584 struct repos_move_info *move;
4585 const char *description;
4587 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
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);
4611 else if (victim_node_kind == svn_node_file ||
4612 victim_node_kind == svn_node_symlink)
4616 struct repos_move_info *move;
4617 const char *description;
4619 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
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);
4647 struct repos_move_info *move;
4648 const char *description;
4650 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
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,
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);
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)
4687 if (details->replacing_node_kind == svn_node_file ||
4688 details->replacing_node_kind == svn_node_symlink)
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);
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);
4717 else if (details->replacing_node_kind == svn_node_dir)
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 "
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);
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);
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 "
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 "
4763 old_repos_relpath, old_rev,
4764 new_repos_relpath, new_rev,
4765 details->rev_author, details->added_rev);
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 "
4772 old_repos_relpath, old_rev,
4773 new_repos_relpath, new_rev,
4774 details->rev_author, details->added_rev);
4778 /* Implements tree_conflict_get_description_func_t. */
4779 static svn_error_t *
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)
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;
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));
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,
4806 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
4807 &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
4810 details = conflict->tree_conflict_incoming_details;
4812 if (conflict_operation == svn_wc_operation_update)
4814 if (details->deleted_rev != SVN_INVALID_REVNUM)
4816 action = describe_incoming_deletion_upon_update(details,
4823 else /* details->added_rev != SVN_INVALID_REVNUM */
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);
4830 else if (conflict_operation == svn_wc_operation_switch)
4832 if (details->deleted_rev != SVN_INVALID_REVNUM)
4834 action = describe_incoming_deletion_upon_switch(details,
4843 else /* details->added_rev != SVN_INVALID_REVNUM */
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);
4852 else if (conflict_operation == svn_wc_operation_merge)
4854 if (details->deleted_rev != SVN_INVALID_REVNUM)
4856 action = describe_incoming_deletion_upon_merge(details,
4865 else /* details->added_rev != SVN_INVALID_REVNUM */
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);
4874 *incoming_change_description = apr_pstrdup(result_pool, action);
4876 return SVN_NO_ERROR;
4879 /* Baton for find_added_rev(). */
4880 struct find_added_rev_baton
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;
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 *
4896 find_added_rev(svn_location_segment_t *segment,
4898 apr_pool_t *scratch_pool)
4900 struct find_added_rev_baton *b = baton;
4902 if (b->ctx->notify_func2)
4904 svn_wc_notify_t *notify;
4906 notify = svn_wc_create_notify(
4908 svn_wc_notify_tree_conflict_details_progress,
4910 notify->revision = segment->range_start;
4911 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
4914 if (segment->path) /* not interested in gaps */
4916 if (b->parent_repos_relpath == NULL ||
4917 svn_relpath_skip_ancestor(b->parent_repos_relpath,
4918 segment->path) != NULL)
4920 b->added_rev = segment->range_start;
4921 b->repos_relpath = apr_pstrdup(b->pool, segment->path);
4925 return SVN_NO_ERROR;
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 *
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)
4942 svn_ra_session_t *ra_session;
4944 const char *corrected_url;
4945 svn_string_t *author_revprop;
4946 struct find_added_rev_baton b = { 0 };
4948 url = svn_path_url_add_component2(repos_root_url, old_repos_relpath,
4950 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
4959 *details = apr_pcalloc(result_pool, sizeof(**details));
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;
4967 /* Figure out when this node was added. */
4968 SVN_ERR(svn_ra_get_location_segments(ra_session, "", old_rev,
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);
4980 (*details)->rev_author = apr_pstrdup(result_pool, author_revprop->data);
4982 (*details)->rev_author = _("unknown author");
4984 /* Check for replacement. */
4985 (*details)->replacing_node_kind = svn_node_none;
4986 if ((*details)->added_rev > 0)
4988 svn_node_kind_t replaced_node_kind;
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,
4999 return SVN_NO_ERROR;
5002 static svn_error_t *
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)
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;
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,
5020 scratch_pool, scratch_pool));
5021 details->wc_move_targets = apr_hash_make(conflict->pool);
5022 for (i = 0; i < details->moves->nelts; i++)
5024 struct repos_move_info *move;
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,
5030 incoming_new_repos_relpath,
5031 incoming_new_pegrev,
5032 conflict->pool, scratch_pool));
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;
5041 /* If only one move target exists recommend a resolution option. */
5042 if (apr_hash_count(details->wc_move_targets) == 1)
5044 apr_array_header_t *wc_abspaths;
5046 wc_abspaths = svn_hash_gets(details->wc_move_targets,
5047 details->move_target_repos_relpath);
5048 if (wc_abspaths->nelts == 1)
5050 svn_client_conflict_option_id_t recommended[] =
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,
5060 apr_array_header_t *options;
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++)
5066 svn_client_conflict_option_id_t option_id = recommended[i];
5068 if (svn_client_conflict_option_find_by_id(options, option_id))
5070 conflict->recommended_option_id = option_id;
5077 return SVN_NO_ERROR;
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 *
5083 conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict,
5084 svn_client_ctx_t *ctx,
5085 apr_pool_t *scratch_pool)
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;
5097 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5098 &old_repos_relpath, &old_rev, &old_kind, conflict, scratch_pool,
5100 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5101 &new_repos_relpath, &new_rev, &new_kind, conflict, scratch_pool,
5103 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5105 scratch_pool, scratch_pool));
5106 operation = svn_client_conflict_get_operation(conflict);
5108 if (operation == svn_wc_operation_update)
5110 if (old_rev < new_rev)
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;
5122 /* The update operation went forward in history. */
5123 SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev,
5124 &parent_repos_relpath,
5128 conflict->local_abspath,
5132 if (new_kind == svn_node_none)
5134 SVN_ERR(find_related_node(&related_repos_relpath,
5136 new_repos_relpath, new_rev,
5137 old_repos_relpath, old_rev,
5139 scratch_pool, scratch_pool));
5143 /* related to self */
5144 related_repos_relpath = NULL;
5145 related_peg_rev = SVN_INVALID_REVNUM;
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);
5152 SVN_ERR(find_revision_for_suspected_deletion(
5153 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
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)
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;
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,
5172 details->rev_author = deleted_rev_author;
5173 details->replacing_node_kind = replacing_node_kind;
5174 details->moves = moves;
5176 else /* new_rev < old_rev */
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));
5187 else if (operation == svn_wc_operation_switch ||
5188 operation == svn_wc_operation_merge)
5190 if (old_rev < new_rev)
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;
5197 /* The switch/merge operation went forward in history.
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,
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)
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;
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,
5222 details->rev_author = apr_pstrdup(conflict->pool,
5223 deleted_rev_author);
5224 details->replacing_node_kind = replacing_node_kind;
5225 details->moves = moves;
5227 else /* new_rev < old_rev */
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));
5244 conflict->tree_conflict_incoming_details = details;
5246 if (details && details->moves)
5247 SVN_ERR(init_wc_move_targets(details, conflict, ctx, scratch_pool));
5249 return SVN_NO_ERROR;
5252 /* Details for tree conflicts involving incoming additions. */
5253 struct conflict_tree_incoming_add_details
5255 /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. */
5256 svn_revnum_t added_rev;
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;
5263 /* The path which was added/deleted relative to the repository root. */
5264 const char *repos_relpath;
5266 /* Authors who committed ADDED_REV/DELETED_REV. */
5267 const char *added_rev_author;
5268 const char *deleted_rev_author;
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
5274 apr_array_header_t *moves;
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 *
5280 conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
5281 svn_client_ctx_t *ctx,
5282 apr_pool_t *scratch_pool)
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;
5292 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5293 &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
5295 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5296 &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
5298 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5300 scratch_pool, scratch_pool));
5301 operation = svn_client_conflict_get_operation(conflict);
5303 if (operation == svn_wc_operation_update ||
5304 operation == svn_wc_operation_switch)
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. */
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;
5322 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5324 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5333 details = apr_pcalloc(conflict->pool, sizeof(*details));
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;
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,
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;
5353 details->added_rev_author = apr_pstrdup(conflict->pool,
5354 author_revprop->data);
5356 details->added_rev_author = _("unknown author");
5357 details->deleted_rev = SVN_INVALID_REVNUM;
5358 details->deleted_rev_author = NULL;
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)
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))
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;
5375 details->deleted_rev_author = apr_pstrdup(conflict->pool,
5376 author_revprop->data);
5378 details->deleted_rev_author = _("unknown author");
5382 else if (operation == svn_wc_operation_merge &&
5383 strcmp(old_repos_relpath, new_repos_relpath) == 0)
5385 if (old_rev < new_rev)
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. */
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;
5396 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5398 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5407 details = apr_pcalloc(conflict->pool, sizeof(*details));
5408 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
5410 b.added_rev = SVN_INVALID_REVNUM;
5411 b.repos_relpath = NULL;
5412 b.parent_repos_relpath = NULL;
5413 b.pool = scratch_pool;
5415 /* Figure out when this node was added. */
5416 SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
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;
5427 details->added_rev_author = apr_pstrdup(conflict->pool,
5428 author_revprop->data);
5430 details->added_rev_author = _("unknown author");
5431 details->deleted_rev = SVN_INVALID_REVNUM;
5432 details->deleted_rev_author = NULL;
5434 else if (old_rev > new_rev)
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;
5445 SVN_ERR(find_revision_for_suspected_deletion(
5446 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
5448 svn_relpath_basename(old_repos_relpath, scratch_pool),
5449 svn_relpath_dirname(old_repos_relpath, scratch_pool),
5451 NULL, SVN_INVALID_REVNUM, /* related to self */
5453 conflict->pool, scratch_pool));
5454 if (deleted_rev == SVN_INVALID_REVNUM)
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;
5462 details = apr_pcalloc(conflict->pool, sizeof(*details));
5463 details->repos_relpath = apr_pstrdup(conflict->pool,
5465 details->deleted_rev = deleted_rev;
5466 details->deleted_rev_author = apr_pstrdup(conflict->pool,
5467 deleted_rev_author);
5469 details->added_rev = SVN_INVALID_REVNUM;
5470 details->added_rev_author = NULL;
5471 details->moves = moves;
5475 conflict->tree_conflict_incoming_details = details;
5477 return SVN_NO_ERROR;
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)
5487 if (new_node_kind == svn_node_dir)
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);
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);
5508 else if (new_node_kind == svn_node_file ||
5509 new_node_kind == svn_node_symlink)
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);
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);
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);
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);
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)
5561 if (victim_node_kind == svn_node_dir)
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"
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);
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);
5585 else if (victim_node_kind == svn_node_file ||
5586 victim_node_kind == svn_node_symlink)
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"
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"
5601 "It was added by %s in r%ld."),
5602 new_repos_relpath, new_rev,
5603 details->added_rev_author, details->added_rev);
5605 return apr_psprintf(result_pool,
5606 _("A new file appeared during switch to\n"
5608 "It was deleted by %s in r%ld."),
5609 new_repos_relpath, new_rev,
5610 details->deleted_rev_author, details->deleted_rev);
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"
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"
5627 "It was added by %s in r%ld."),
5628 new_repos_relpath, new_rev,
5629 details->added_rev_author, details->added_rev);
5631 return apr_psprintf(result_pool,
5632 _("A new item appeared during switch to\n"
5634 "It was deleted by %s in r%ld."),
5635 new_repos_relpath, new_rev,
5636 details->deleted_rev_author, details->deleted_rev);
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)
5649 if (new_node_kind == svn_node_dir)
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);
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);
5664 else if (new_node_kind == svn_node_file ||
5665 new_node_kind == svn_node_symlink)
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);
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);
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);
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);
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)
5706 if (new_node_kind == svn_node_dir)
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);
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);
5724 else if (new_node_kind == svn_node_file ||
5725 new_node_kind == svn_node_symlink)
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);
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);
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);
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);
5761 /* Implements tree_conflict_get_description_func_t. */
5762 static svn_error_t *
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)
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;
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));
5786 conflict_operation = svn_client_conflict_get_operation(conflict);
5787 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
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));
5796 details = conflict->tree_conflict_incoming_details;
5798 if (conflict_operation == svn_wc_operation_update)
5800 action = describe_incoming_add_upon_update(details,
5805 else if (conflict_operation == svn_wc_operation_switch)
5807 action = describe_incoming_add_upon_switch(details,
5813 else if (conflict_operation == svn_wc_operation_merge)
5815 if (old_rev < new_rev)
5816 action = describe_incoming_add_upon_merge(details,
5823 action = describe_incoming_reverse_deletion_upon_merge(
5824 details, new_node_kind, old_repos_relpath,
5825 old_rev, new_rev, result_pool);
5828 *incoming_change_description = apr_pstrdup(result_pool, action);
5830 return SVN_NO_ERROR;
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
5838 /* The revision in which the edit ocurred. */
5841 /* The author of the revision. */
5844 /** Is the text modified? May be svn_tristate_unknown. */
5845 svn_tristate_t text_modified;
5847 /** Are properties modified? May be svn_tristate_unknown. */
5848 svn_tristate_t props_modified;
5850 /** For directories, are children modified?
5851 * May be svn_tristate_unknown. */
5852 svn_tristate_t children_modified;
5854 /* The path which was edited, relative to the repository root. */
5855 const char *repos_relpath;
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;
5869 /* Implements svn_log_entry_receiver_t. */
5870 static svn_error_t *
5871 find_modified_rev(void *baton,
5872 svn_log_entry_t *log_entry,
5873 apr_pool_t *scratch_pool)
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;
5881 if (b->ctx->notify_func2)
5883 svn_wc_notify_t *notify;
5885 notify = svn_wc_create_notify(
5887 svn_wc_notify_tree_conflict_details_progress,
5889 notify->revision = log_entry->revision;
5890 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
5893 /* No paths were changed in this revision. Nothing to do. */
5894 if (! log_entry->changed_paths2)
5895 return SVN_NO_ERROR;
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);
5901 details->author = apr_pstrdup(b->result_pool, author->data);
5903 details->author = _("unknown author");
5905 details->text_modified = svn_tristate_unknown;
5906 details->props_modified = svn_tristate_unknown;
5907 details->children_modified = svn_tristate_unknown;
5909 iterpool = svn_pool_create(scratch_pool);
5910 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
5912 hi = apr_hash_next(hi))
5916 svn_log_changed_path2_t *log_item;
5918 svn_pool_clear(iterpool);
5920 apr_hash_this(hi, (void *) &path, NULL, &val);
5923 /* ### Remove leading slash from paths in log entries. */
5925 path = svn_relpath_canonicalize(path, iterpool);
5927 if (svn_path_compare_paths(b->repos_relpath, path) == 0 &&
5928 (log_item->action == 'M' || log_item->action == 'A'))
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);
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,
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;
5946 if (b->node_kind == svn_node_dir &&
5947 details->children_modified == svn_tristate_unknown)
5948 details->children_modified = svn_tristate_false;
5950 APR_ARRAY_PUSH(b->edits, struct conflict_tree_incoming_edit_details *) =
5953 svn_pool_destroy(iterpool);
5955 return SVN_NO_ERROR;
5958 /* Implements tree_conflict_get_details_func_t.
5959 * Find one or more revisions in which the victim was modified in the
5961 static svn_error_t *
5962 conflict_tree_get_details_incoming_edit(svn_client_conflict_t *conflict,
5963 svn_client_ctx_t *ctx,
5964 apr_pool_t *scratch_pool)
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;
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 };
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,
5989 scratch_pool, scratch_pool));
5990 operation = svn_client_conflict_get_operation(conflict);
5991 if (operation == svn_wc_operation_update)
5993 b.node_kind = old_rev < new_rev ? new_node_kind : old_node_kind;
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;
5999 url = svn_path_url_add_component2(repos_root_url,
6000 old_rev < new_rev ? new_repos_relpath
6001 : old_repos_relpath,
6004 b.repos_relpath = old_rev < new_rev ? new_repos_relpath
6005 : old_repos_relpath;
6007 else if (operation == svn_wc_operation_switch ||
6008 operation == svn_wc_operation_merge)
6010 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
6013 b.repos_relpath = new_repos_relpath;
6014 b.node_kind = new_node_kind;
6017 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
6026 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
6027 APR_ARRAY_PUSH(paths, const char *) = "";
6029 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
6030 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
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(
6038 sizeof(struct conflict_tree_incoming_edit_details *));
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,
6044 TRUE, /* need the changed paths list */
6045 FALSE, /* need to traverse copies */
6046 FALSE, /* no need for merged revisions */
6048 find_modified_rev, &b,
6051 conflict->tree_conflict_incoming_details = b.edits;
6053 return SVN_NO_ERROR;
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)
6063 if (old_rev < new_rev)
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);
6077 return apr_psprintf(result_pool,
6078 _("Changes from the following revisions arrived "
6079 "during update from r%ld to r%ld"),
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"),
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"),
6098 return apr_psprintf(result_pool,
6099 _("Changes from the following revisions arrived "
6100 "during backwards update from r%ld to r%ld"),
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)
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"
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"
6123 new_repos_relpath, new_rev);
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);
6131 /* Return a string showing the list of revisions in EDITS, ensuring
6132 * the string won't grow too large for display. */
6134 describe_incoming_edit_list_modified_revs(apr_array_header_t *edits,
6135 apr_pool_t *result_pool)
6137 int num_revs_to_skip;
6138 static const int min_revs_for_skipping = 5;
6139 static const int max_revs_to_display = 8;
6143 if (edits->nelts == 0)
6144 return _(" (no revisions found)");
6146 if (edits->nelts <= max_revs_to_display)
6147 num_revs_to_skip = 0;
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)
6155 /* Don't bother with the placeholder. Just list all revisions. */
6156 num_revs_to_skip = 0;
6160 for (i = 0; i < edits->nelts; i++)
6162 struct conflict_tree_incoming_edit_details *details;
6164 details = APR_ARRAY_IDX(edits, i,
6165 struct conflict_tree_incoming_edit_details *);
6166 if (num_revs_to_skip > 0)
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))
6180 if (i == edits->nelts - (max_revs_to_display / 2))
6181 s = apr_psprintf(result_pool,
6182 Q_("%s\n [%d revision omitted for "
6184 "%s\n [%d revisions omitted for "
6187 s, num_revs_to_skip);
6189 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
6190 details->rev, details->author,
6191 i < edits->nelts - 1 ? "," : "");
6195 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
6196 details->rev, details->author,
6197 i < edits->nelts - 1 ? "," : "");
6203 /* Implements tree_conflict_get_description_func_t. */
6204 static svn_error_t *
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)
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;
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));
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));
6234 conflict_operation = svn_client_conflict_get_operation(conflict);
6236 edits = conflict->tree_conflict_incoming_details;
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,
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)
6247 /* Handle merge inline because it returns early sometimes. */
6248 if (old_rev < new_rev)
6250 if (old_rev + 1 == new_rev)
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"
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"
6264 new_repos_relpath, new_rev);
6266 action = apr_psprintf(scratch_pool,
6267 _("Changes arrived during merge of\n"
6269 new_repos_relpath, new_rev);
6271 *incoming_change_description = apr_pstrdup(result_pool, action);
6273 return SVN_NO_ERROR;
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);
6291 action = apr_psprintf(scratch_pool,
6292 _("Changes from the following revisions "
6293 "arrived during merge of\n"
6295 new_repos_relpath, old_rev + 1, new_rev);
6300 if (new_rev + 1 == old_rev)
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"
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"
6314 new_repos_relpath, old_rev);
6316 action = apr_psprintf(scratch_pool,
6317 _("Changes arrived during reverse-merge "
6319 new_repos_relpath, old_rev);
6321 *incoming_change_description = apr_pstrdup(result_pool, action);
6323 return SVN_NO_ERROR;
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"
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"
6341 new_repos_relpath, new_rev + 1, old_rev);
6344 action = apr_psprintf(scratch_pool,
6345 _("Changes from the following revisions "
6346 "arrived during reverse-merge of\n"
6348 new_repos_relpath, new_rev + 1, old_rev);
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);
6358 return SVN_NO_ERROR;
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)
6370 SVN_ERR(conflict->tree_conflict_get_incoming_description_func(
6371 incoming_change_description,
6372 conflict, ctx, result_pool, scratch_pool));
6374 SVN_ERR(conflict->tree_conflict_get_local_description_func(
6375 local_change_description,
6376 conflict, ctx, result_pool, scratch_pool));
6378 return SVN_NO_ERROR;
6382 svn_client_conflict_option_set_merged_propval(
6383 svn_client_conflict_option_t *option,
6384 const svn_string_t *merged_propval)
6386 option->type_data.prop.merged_propval = svn_string_dup(merged_propval,
6390 /* Implements conflict_option_resolve_func_t. */
6391 static svn_error_t *
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)
6397 return SVN_NO_ERROR; /* Nothing to do. */
6400 /* Implements conflict_option_resolve_func_t. */
6401 static svn_error_t *
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)
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;
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);
6417 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6419 scratch_pool, scratch_pool));
6420 err = svn_wc__conflict_text_mark_resolved(ctx->wc_ctx,
6428 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6431 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6434 conflict->resolution_text = option_id;
6436 return SVN_NO_ERROR;
6439 /* Implements conflict_option_resolve_func_t. */
6440 static svn_error_t *
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)
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;
6452 const svn_string_t *merged_value;
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);
6458 if (option_id == svn_client_conflict_option_merged_text)
6459 merged_value = option->type_data.prop.merged_propval;
6461 merged_value = NULL;
6463 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6465 scratch_pool, scratch_pool));
6466 err = svn_wc__conflict_prop_mark_resolved(ctx->wc_ctx, local_abspath,
6467 propname, conflict_choice,
6472 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6475 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6478 if (propname[0] == '\0')
6480 apr_hash_index_t *hi;
6482 /* All properties have been resolved to the same option. */
6483 for (hi = apr_hash_first(scratch_pool, conflict->prop_conflicts);
6485 hi = apr_hash_next(hi))
6487 const char *this_propname = apr_hash_this_key(hi);
6489 svn_hash_sets(conflict->resolved_props,
6490 apr_pstrdup(apr_hash_pool_get(conflict->resolved_props),
6493 svn_hash_sets(conflict->prop_conflicts, this_propname, NULL);
6496 conflict->legacy_prop_conflict_propname = NULL;
6500 svn_hash_sets(conflict->resolved_props,
6501 apr_pstrdup(apr_hash_pool_get(conflict->resolved_props),
6504 svn_hash_sets(conflict->prop_conflicts, propname, NULL);
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));
6511 conflict->legacy_prop_conflict_propname = NULL;
6514 return SVN_NO_ERROR;
6517 /* Implements conflict_option_resolve_func_t. */
6518 static svn_error_t *
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)
6524 svn_client_conflict_option_id_t option_id;
6525 const char *local_abspath;
6526 const char *lock_abspath;
6529 option_id = svn_client_conflict_option_get_id(option);
6530 local_abspath = svn_client_conflict_get_local_abspath(conflict);
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,
6539 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6541 scratch_pool, scratch_pool));
6543 /* Resolve to current working copy state. */
6544 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
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,
6554 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6559 conflict->resolution_tree = option_id;
6561 return SVN_NO_ERROR;
6564 /* Implements conflict_option_resolve_func_t. */
6565 static svn_error_t *
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)
6571 const char *local_abspath;
6572 const char *lock_abspath;
6575 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6577 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6579 scratch_pool, scratch_pool));
6580 err = svn_wc__conflict_tree_update_break_moved_away(ctx->wc_ctx,
6587 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6592 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6594 return SVN_NO_ERROR;
6597 /* Implements conflict_option_resolve_func_t. */
6598 static svn_error_t *
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)
6604 const char *local_abspath;
6605 const char *lock_abspath;
6608 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6610 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6612 scratch_pool, scratch_pool));
6613 err = svn_wc__conflict_tree_update_raise_moved_away(ctx->wc_ctx,
6620 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6625 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6627 return SVN_NO_ERROR;
6630 /* Implements conflict_option_resolve_func_t. */
6631 static svn_error_t *
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)
6637 const char *local_abspath;
6638 const char *lock_abspath;
6641 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6643 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6645 scratch_pool, scratch_pool));
6646 err = svn_wc__conflict_tree_update_moved_away_node(ctx->wc_ctx,
6653 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6656 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6659 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6661 return SVN_NO_ERROR;
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 *
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)
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;
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,
6697 operation = svn_client_conflict_get_operation(conflict);
6698 SVN_ERR_ASSERT(operation == svn_wc_operation_update);
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,
6705 local_style_relpath = svn_dirent_local_style(
6706 svn_dirent_skip_ancestor(wcroot_abspath,
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)
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);
6728 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6729 _("Unexpected option id '%d'"), option_id);
6732 return svn_error_trace(err);
6734 if (base_kind != incoming_new_kind)
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', "
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', "
6750 local_style_relpath,
6751 svn_node_kind_to_word(incoming_new_kind),
6752 svn_node_kind_to_word(base_kind));
6754 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6755 _("Unexpected option id '%d'"), option_id);
6758 if (strcmp(base_repos_relpath, incoming_new_repos_relpath) != 0 ||
6759 base_rev != incoming_new_pegrev)
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);
6781 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6782 _("Unexpected option id '%d'"), option_id);
6785 SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, local_abspath,
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 "
6794 local_style_relpath);
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 "
6802 local_style_relpath);
6804 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6805 _("Unexpected option id '%d'"), option_id);
6808 return SVN_NO_ERROR;
6812 /* Implements conflict_option_resolve_func_t. */
6813 static svn_error_t *
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)
6819 const char *local_abspath;
6820 const char *lock_abspath;
6821 svn_wc_operation_t operation;
6824 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6825 operation = svn_client_conflict_get_operation(conflict);
6827 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6829 scratch_pool, scratch_pool));
6831 if (operation == svn_wc_operation_update)
6833 err = verify_local_state_for_incoming_add_upon_update(conflict, option,
6839 /* All other options for this conflict actively fetch the incoming
6840 * new node. We can ignore the incoming new node by doing nothing. */
6842 /* Resolve to current working copy state. */
6843 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
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,
6854 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6859 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6861 return SVN_NO_ERROR;
6864 /* Delete entry and wc props from a set of properties. */
6866 filter_props(apr_hash_t *props, apr_pool_t *scratch_pool)
6868 apr_hash_index_t *hi;
6870 for (hi = apr_hash_first(scratch_pool, props);
6872 hi = apr_hash_next(hi))
6874 const char *propname = apr_hash_this_key(hi);
6876 if (!svn_wc_is_normal_prop(propname))
6877 svn_hash_sets(props, propname, NULL);
6881 /* Implements conflict_option_resolve_func_t. */
6882 static svn_error_t *
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)
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;
6901 svn_wc_conflict_reason_t local_change;
6903 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6904 local_change = svn_client_conflict_get_local_change(conflict);
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));
6915 if (local_change == svn_wc_conflict_reason_unversioned)
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);
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);
6936 SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
6937 ctx->cancel_func, ctx->cancel_baton,
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));
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));
6950 /* ### The following WC modifications should be atomic. */
6951 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6953 scratch_pool, scratch_pool));
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,
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 */
6976 NULL, NULL, /* conflict func/baton */
6977 NULL, NULL, /* don't allow user to cancel here */
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,
6990 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6993 if (ctx->notify_func2)
6995 svn_wc_notify_t *notify;
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,
7001 if (merge_content_outcome == svn_wc_merge_conflict)
7002 notify->content_state = svn_wc_notify_state_conflicted;
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);
7009 /* And also about the successfully resolved tree conflict. */
7010 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
7012 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7015 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
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));
7020 return SVN_NO_ERROR;
7023 /* Implements conflict_option_resolve_func_t. */
7024 static svn_error_t *
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)
7031 svn_ra_session_t *ra_session;
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;
7050 local_abspath = svn_client_conflict_get_local_abspath(conflict);
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,
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,
7067 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7068 conflict, scratch_pool,
7070 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
7072 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7073 url, NULL, NULL, FALSE, FALSE,
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));
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));
7084 filter_props(incoming_new_props, scratch_pool);
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));
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));
7096 /* ### The following WC modifications should be atomic. */
7097 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
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);
7103 return svn_error_compose_create(err,
7104 svn_wc__release_write_lock(ctx->wc_ctx,
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 */
7116 NULL, NULL, /* conflict func/baton */
7117 NULL, NULL, /* don't allow user to cancel here */
7119 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7122 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7125 if (ctx->notify_func2)
7127 svn_wc_notify_t *notify;
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,
7133 if (merge_content_outcome == svn_wc_merge_conflict)
7134 notify->content_state = svn_wc_notify_state_conflicted;
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);
7141 /* And also about the successfully resolved tree conflict. */
7142 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
7144 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7147 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7149 return SVN_NO_ERROR;
7152 /* Implements conflict_option_resolve_func_t. */
7153 static svn_error_t *
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)
7160 svn_ra_session_t *ra_session;
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;
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;
7183 local_abspath = svn_client_conflict_get_local_abspath(conflict);
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));
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,
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));
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,
7211 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7212 conflict, scratch_pool,
7214 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
7216 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7217 url, NULL, NULL, FALSE, FALSE,
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,
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));
7233 /* Reset the stream in preparation for adding its content to WC. */
7234 SVN_ERR(svn_stream_reset(incoming_new_stream));
7236 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7238 scratch_pool, scratch_pool));
7240 /* ### The following WC modifications should be atomic. */
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,
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? */
7254 NULL, /* ### merge props first, set here? */
7255 url, incoming_new_pegrev,
7256 NULL, NULL, /* don't allow user to cancel here */
7261 if (ctx->notify_func2)
7263 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
7266 notify->kind = svn_node_file;
7267 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
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);
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);
7283 filter_props(incoming_new_props, scratch_pool);
7285 /* Create a property diff for the files. */
7286 err = svn_prop_diffs(&propdiffs, incoming_new_props,
7287 working_props, scratch_pool);
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 */
7300 NULL, NULL, /* conflict func/baton */
7301 NULL, NULL, /* don't allow user to cancel here */
7306 if (ctx->notify_func2)
7308 svn_wc_notify_t *notify = svn_wc_create_notify(
7310 svn_wc_notify_update_update,
7313 if (merge_content_outcome == svn_wc_merge_conflict)
7314 notify->content_state = svn_wc_notify_state_conflicted;
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);
7323 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7326 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7329 SVN_ERR(svn_stream_close(incoming_new_stream));
7331 if (ctx->notify_func2)
7333 svn_wc_notify_t *notify = svn_wc_create_notify(
7335 svn_wc_notify_resolved_tree,
7338 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7341 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7343 return SVN_NO_ERROR;
7346 static svn_error_t *
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)
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;
7367 left_version = svn_wc_conflict_version_create2(repos_root_url,
7373 right_version = svn_wc_conflict_version_create2(repos_root_url,
7379 conflict = svn_wc_conflict_description_create_tree2(local_abspath,
7381 svn_wc_operation_merge,
7385 conflict->action = incoming_change;
7386 conflict->reason = local_change;
7388 SVN_ERR(svn_wc__add_tree_conflict(wc_ctx, conflict, scratch_pool));
7392 svn_wc_notify_t *notify;
7394 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
7396 notify->kind = local_node_kind;
7397 notify_func2(notify_baton2, notify, scratch_pool);
7400 return SVN_NO_ERROR;
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;
7413 static svn_error_t *
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)
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;
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);
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);
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);
7442 hi = apr_hash_next(hi))
7446 prop.name = apr_hash_this_key(hi);
7447 prop.value = apr_hash_this_val(hi);
7449 if (svn_wc_is_normal_prop(prop.name))
7450 APR_ARRAY_PUSH(propchanges, svn_prop_t) = prop;
7453 SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx,
7455 left_version, right_version,
7456 apr_hash_make(scratch_pool),
7458 FALSE, /* not a dry-run */
7459 NULL, NULL, NULL, NULL,
7462 if (ctx->notify_func2)
7464 svn_wc_notify_t *notify;
7466 notify = svn_wc_create_notify(target_abspath,
7467 svn_wc_notify_update_update,
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);
7475 return SVN_NO_ERROR;
7478 /* An svn_diff_tree_processor_t callback. */
7479 static svn_error_t *
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,
7486 const struct svn_diff_tree_processor_t *processor,
7487 apr_pool_t *scratch_pool)
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;
7496 /* Handle the root of the added directory tree. */
7497 if (relpath[0] == '\0')
7499 /* ### svn_wc_merge_props3() requires this... */
7500 SVN_ERR(svn_wc__del_tree_conflict(b->ctx->wc_ctx, b->target_abspath,
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;
7511 local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
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));
7517 if (db_kind == svn_node_dir && on_disk_kind == svn_node_dir)
7519 SVN_ERR(merge_added_dir_props(svn_dirent_join(b->target_abspath, relpath,
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;
7528 if (db_kind != svn_node_none && db_kind != svn_node_unknown)
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,
7539 return SVN_NO_ERROR;
7542 if (on_disk_kind != svn_node_none)
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,
7552 return SVN_NO_ERROR;
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,
7564 for (hi = apr_hash_first(scratch_pool, right_props);
7566 hi = apr_hash_next(hi))
7568 const char *propname = apr_hash_this_key(hi);
7569 const svn_string_t *propval = apr_hash_this_val(hi);
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,
7579 return SVN_NO_ERROR;
7582 static svn_error_t *
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)
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;
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));
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));
7606 /* Create a property diff for the files. */
7607 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_added_file_props,
7608 working_props, scratch_pool));
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 */
7619 NULL, NULL, /* conflict func/baton */
7620 NULL, NULL, /* don't allow user to cancel here */
7623 if (ctx->notify_func2)
7625 svn_wc_notify_t *notify = svn_wc_create_notify(
7627 svn_wc_notify_update_update,
7630 if (merge_content_outcome == svn_wc_merge_conflict)
7631 notify->content_state = svn_wc_notify_state_conflicted;
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);
7639 return SVN_NO_ERROR;
7642 /* An svn_diff_tree_processor_t callback. */
7643 static svn_error_t *
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,
7652 const struct svn_diff_tree_processor_t *processor,
7653 apr_pool_t *scratch_pool)
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;
7662 local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
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));
7668 if (db_kind == svn_node_file && on_disk_kind == svn_node_file)
7670 propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
7671 SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, ®ular_props,
7673 SVN_ERR(merge_added_files(local_abspath, right_file,
7674 svn_prop_array_to_hash(regular_props,
7676 b->ctx, scratch_pool));
7677 return SVN_NO_ERROR;
7680 if (db_kind != svn_node_none && db_kind != svn_node_unknown)
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,
7691 return SVN_NO_ERROR;
7694 if (on_disk_kind != svn_node_none)
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,
7704 return SVN_NO_ERROR;
7707 propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
7708 SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, ®ular_props,
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,
7714 FALSE, b->ctx->notify_func2,
7715 b->ctx->notify_baton2, scratch_pool));
7717 return SVN_NO_ERROR;
7720 /* Merge a newly added directory into TARGET_ABSPATH in the working copy.
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.
7730 static svn_error_t *
7731 merge_newly_added_dir(const char *added_repos_relpath,
7732 const char *source1,
7734 const char *source2,
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)
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;
7757 svn_uri_split(&anchor1, &target1, source1, scratch_pool);
7758 svn_uri_split(&anchor2, &target2, source2, scratch_pool);
7760 baton.target_abspath = target_abspath;
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;
7770 processor = svn_diff__tree_processor_create(&baton, scratch_pool);
7771 processor->dir_added = diff_dir_added;
7772 processor->file_added = diff_file_added;
7774 diff_processor = processor;
7776 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
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);
7784 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7785 anchor2, NULL, NULL, FALSE,
7787 scratch_pool, scratch_pool));
7789 anchor2 = corrected_url;
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));
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,
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));
7807 /* Drive the reporter; do the diff. */
7808 SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
7813 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
7815 return SVN_NO_ERROR;
7818 /* Implements conflict_option_resolve_func_t. */
7819 static svn_error_t *
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)
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;
7836 const char *source2;
7840 local_abspath = svn_client_conflict_get_local_abspath(conflict);
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"),
7849 svn_dirent_local_style(local_abspath,
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,
7856 source1 = svn_path_url_add_component2(repos_root_url,
7857 details->repos_relpath,
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 */
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,
7873 rev1 = rev_below(details->added_rev);
7874 source2 = svn_path_url_add_component2(repos_root_url,
7875 incoming_new_repos_relpath,
7877 rev2 = incoming_new_pegrev;
7878 added_repos_relpath = incoming_new_repos_relpath;
7880 else /* reverse-merge */
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,
7888 rev1 = details->deleted_rev;
7889 source2 = svn_path_url_add_component2(repos_root_url,
7890 incoming_old_repos_relpath,
7892 rev2 = incoming_old_pegrev;
7893 added_repos_relpath = incoming_new_repos_relpath;
7896 /* ### The following WC modifications should be atomic. */
7897 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7899 scratch_pool, scratch_pool));
7901 /* ### wrap in a transaction */
7902 err = merge_newly_added_dir(added_repos_relpath,
7903 source1, rev1, source2, rev2,
7905 (incoming_old_pegrev > incoming_new_pegrev),
7906 ctx, scratch_pool, scratch_pool);
7908 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7910 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7913 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
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,
7923 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7925 return SVN_NO_ERROR;
7928 /* Implements conflict_option_resolve_func_t. */
7929 static svn_error_t *
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)
7935 const char *local_abspath;
7936 const char *lock_abspath;
7938 svn_wc_conflict_reason_t local_change;
7940 local_abspath = svn_client_conflict_get_local_abspath(conflict);
7941 local_change = svn_client_conflict_get_local_change(conflict);
7943 if (local_change == svn_wc_conflict_reason_unversioned)
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));
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,
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,
7980 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7985 return SVN_NO_ERROR;
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 *
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)
7998 svn_ra_session_t *ra_session;
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;
8007 svn_boolean_t timestamp_sleep;
8009 local_abspath = svn_client_conflict_get_local_abspath(conflict);
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,
8016 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8017 conflict, scratch_pool,
8019 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
8021 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8022 url, NULL, NULL, FALSE, FALSE,
8026 url = corrected_url;
8028 /* ### The following WC modifications should be atomic. */
8030 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8034 scratch_pool, scratch_pool));
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,
8044 err = svn_client__repos_to_wc_copy_by_editor(×tamp_sleep,
8046 url, incoming_new_pegrev,
8048 ra_session, ctx, scratch_pool);
8052 if (ctx->notify_func2)
8054 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
8057 notify->kind = svn_node_dir;
8058 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
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);
8069 svn_revnum_t base_revision;
8070 const char *base_repos_relpath;
8071 struct find_added_rev_baton b = { 0 };
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);
8080 url = svn_path_url_add_component2(repos_root_url, base_repos_relpath,
8083 /* Trace the replaced directory's history to its origin. */
8084 err = svn_ra_reparent(ra_session, url, scratch_pool);
8087 b.victim_abspath = local_abspath;
8089 b.added_rev = SVN_INVALID_REVNUM;
8090 b.repos_relpath = NULL;
8091 b.parent_repos_relpath = svn_relpath_dirname(base_repos_relpath,
8093 b.pool = scratch_pool;
8095 err = svn_ra_get_location_segments(ra_session, "", base_revision,
8096 base_revision, SVN_INVALID_REVNUM,
8102 if (b.added_rev == SVN_INVALID_REVNUM)
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 "
8108 base_repos_relpath);
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);
8125 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8128 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
8131 if (ctx->notify_func2)
8133 svn_wc_notify_t *notify = svn_wc_create_notify(
8135 svn_wc_notify_resolved_tree,
8138 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8141 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
8143 return SVN_NO_ERROR;
8146 /* Implements conflict_option_resolve_func_t. */
8147 static svn_error_t *
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)
8153 return svn_error_trace(merge_incoming_added_dir_replace(option,
8160 /* Implements conflict_option_resolve_func_t. */
8161 static svn_error_t *
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)
8168 return svn_error_trace(merge_incoming_added_dir_replace(option,
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 *
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)
8187 svn_boolean_t is_copy;
8188 svn_revnum_t copyfrom_rev;
8189 const char *copyfrom_repos_relpath;
8191 SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
8192 operation == svn_wc_operation_switch);
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));
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 "
8204 svn_dirent_local_style(
8205 svn_dirent_skip_ancestor(
8207 conflict->local_abspath),
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),
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),
8232 details->added_rev, copyfrom_rev);
8233 else if (operation == svn_wc_operation_update)
8235 const char *old_repos_relpath;
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),
8251 details->repos_relpath,
8253 copyfrom_repos_relpath, copyfrom_rev);
8255 else if (operation == svn_wc_operation_switch)
8257 const char *old_repos_relpath;
8259 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8260 &old_repos_relpath, NULL, NULL, conflict,
8261 scratch_pool, scratch_pool));
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 "
8269 svn_dirent_local_style(
8270 svn_dirent_skip_ancestor(
8272 conflict->local_abspath),
8275 copyfrom_repos_relpath, copyfrom_rev);
8278 return SVN_NO_ERROR;
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 *
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)
8294 const char *local_abspath;
8295 const char *wcroot_abspath;
8296 svn_wc_operation_t operation;
8297 svn_wc_conflict_reason_t local_change;
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,
8303 operation = svn_client_conflict_get_operation(conflict);
8304 local_change = svn_client_conflict_get_local_change(conflict);
8306 if (operation == svn_wc_operation_update ||
8307 operation == svn_wc_operation_switch)
8309 struct conflict_tree_incoming_delete_details *details;
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."),
8318 svn_dirent_local_style(local_abspath,
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(
8329 conflict->local_abspath),
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,
8337 else if (operation == svn_wc_operation_merge)
8339 svn_node_kind_t victim_node_kind;
8340 svn_node_kind_t on_disk_kind;
8342 /* For merge, all we can do is ensure that the item still exists. */
8344 svn_client_conflict_tree_get_victim_node_kind(conflict);
8345 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
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),
8355 svn_node_kind_to_word(victim_node_kind),
8356 svn_node_kind_to_word(on_disk_kind));
8359 return SVN_NO_ERROR;
8362 /* Implements conflict_option_resolve_func_t. */
8363 static svn_error_t *
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)
8369 svn_client_conflict_option_id_t option_id;
8370 const char *local_abspath;
8371 const char *lock_abspath;
8374 option_id = svn_client_conflict_option_get_id(option);
8375 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8377 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8379 scratch_pool, scratch_pool));
8381 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8386 /* Resolve to the current working copy state. */
8387 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
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,
8398 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8403 conflict->resolution_tree = option_id;
8405 return SVN_NO_ERROR;
8408 /* Implements conflict_option_resolve_func_t. */
8409 static svn_error_t *
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)
8415 svn_client_conflict_option_id_t option_id;
8416 const char *local_abspath;
8417 const char *parent_abspath;
8418 const char *lock_abspath;
8421 option_id = svn_client_conflict_option_get_id(option);
8422 local_abspath = svn_client_conflict_get_local_abspath(conflict);
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,
8428 scratch_pool, scratch_pool));
8430 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
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,
8442 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
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
8448 svn_error_clear(err);
8450 /* Resolve to current working copy state. */
8451 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath,
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,
8467 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8472 conflict->resolution_tree = option_id;
8474 return SVN_NO_ERROR;
8477 /* Implements conflict_option_resolve_func_t. */
8478 static svn_error_t *
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)
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;
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;
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,
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,
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 ||
8535 svn_client_conflict_option_both_moved_file_move_merge);
8537 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8538 conflict, 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,
8544 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8545 &incoming_new_repos_relpath, &incoming_new_pegrev,
8546 NULL, conflict, scratch_pool,
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));
8557 /* Fetch the ancestor file's content. */
8558 ancestor_url = svn_path_url_add_component2(repos_root_url,
8559 incoming_old_repos_relpath,
8561 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8562 ancestor_url, NULL, NULL,
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);
8570 /* Close stream to flush ancestor file to disk. */
8571 SVN_ERR(svn_stream_close(ancestor_stream));
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,
8580 if (local_change == svn_wc_conflict_reason_missing)
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;
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 *);
8595 merge_source_abspath = victim_abspath;
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,
8603 scratch_pool, scratch_pool));
8605 if (local_change != svn_wc_conflict_reason_missing)
8607 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
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);
8619 /* Get a copy of the move target's properties. */
8620 err = svn_wc_prop_list2(&move_target_props, ctx->wc_ctx,
8622 scratch_pool, scratch_pool);
8626 /* Create a property diff for the files. */
8627 err = svn_prop_diffs(&propdiffs, move_target_props, victim_props,
8632 if (operation == svn_wc_operation_update ||
8633 operation == svn_wc_operation_switch)
8635 svn_stream_t *moved_to_stream;
8636 svn_stream_t *incoming_stream;
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);
8647 err = svn_wc__translated_stream(&moved_to_stream, ctx->wc_ctx,
8650 SVN_WC_TRANSLATE_TO_NF,
8651 scratch_pool, scratch_pool);
8655 err = svn_stream_copy3(moved_to_stream, incoming_stream,
8656 NULL, NULL, /* no cancellation */
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,
8673 else if (operation == svn_wc_operation_merge)
8675 svn_stream_t *incoming_stream;
8676 svn_stream_t *move_target_stream;
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);
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);
8698 err = svn_stream_copy3(move_target_stream, incoming_stream,
8699 NULL, NULL, /* no cancellation */
8704 /* Apply the incoming move. */
8705 err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool);
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 */
8718 SVN_ERR_MALFUNCTION();
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,
8730 NULL, NULL, /* conflict func/baton */
8731 NULL, NULL, /* don't allow user to cancel here */
8733 svn_io_sleep_for_timestamps(moved_to_abspath, scratch_pool);
8737 if (operation == svn_wc_operation_merge && incoming_abspath)
8739 err = svn_io_remove_file2(incoming_abspath, TRUE, scratch_pool);
8742 incoming_abspath = NULL;
8745 if (ctx->notify_func2)
8747 svn_wc_notify_t *notify;
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,
8753 if (merge_content_outcome == svn_wc_merge_conflict)
8754 notify->content_state = svn_wc_notify_state_conflicted;
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);
8762 if (operation == svn_wc_operation_update ||
8763 operation == svn_wc_operation_switch)
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 */
8773 else if (local_change == svn_wc_conflict_reason_missing)
8775 /* Clear tree conflict marker. */
8776 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath,
8782 if (ctx->notify_func2)
8784 svn_wc_notify_t *notify;
8786 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
8788 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8791 conflict->resolution_tree = option_id;
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,
8804 return SVN_NO_ERROR;
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 *
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)
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;
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;
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,
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,
8863 option_id = svn_client_conflict_option_get_id(option);
8864 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge);
8866 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8867 conflict, 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,
8873 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8874 &incoming_new_repos_relpath, &incoming_new_pegrev,
8875 NULL, conflict, scratch_pool,
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));
8886 /* Fetch the ancestor file's content. */
8887 ancestor_url = svn_path_url_add_component2(repos_root_url,
8888 incoming_old_repos_relpath,
8890 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8891 ancestor_url, NULL, NULL,
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);
8899 /* Close stream to flush ancestor file to disk. */
8900 SVN_ERR(svn_stream_close(ancestor_stream));
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 *);
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 *);
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,
8921 scratch_pool, scratch_pool));
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);
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);
8937 /* Create a property diff for the files. */
8938 err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
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,
8953 NULL, NULL, /* conflict func/baton */
8954 NULL, NULL, /* don't allow user to cancel here */
8959 if (ctx->notify_func2)
8961 svn_wc_notify_t *notify;
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,
8967 if (merge_content_outcome == svn_wc_merge_conflict)
8968 notify->content_state = svn_wc_notify_state_conflicted;
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);
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,
8986 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
8990 if (ctx->notify_func2)
8992 svn_wc_notify_t *notify;
8994 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
8996 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8999 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
9001 conflict->resolution_tree = option_id;
9004 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
9009 return SVN_NO_ERROR;
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 *
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)
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;
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;
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' "
9052 "to be fetched from the repository first."),
9053 svn_dirent_local_style(victim_abspath,
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,
9062 option_id = svn_client_conflict_option_get_id(option);
9063 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_dir_merge);
9065 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
9066 conflict, 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,
9072 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9073 &incoming_new_repos_relpath, &incoming_new_pegrev,
9074 NULL, conflict, scratch_pool,
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 *);
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 *);
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,
9096 scratch_pool, scratch_pool));
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;
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);
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,
9134 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
9138 if (ctx->notify_func2)
9140 svn_wc_notify_t *notify;
9142 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
9144 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9147 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
9149 conflict->resolution_tree = option_id;
9152 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
9157 return SVN_NO_ERROR;
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 *
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)
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;
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;
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' "
9200 "to be fetched from the repository first."),
9201 svn_dirent_local_style(victim_abspath,
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,
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);
9214 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
9215 conflict, 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,
9221 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9222 &incoming_new_repos_relpath, &incoming_new_pegrev,
9223 NULL, conflict, scratch_pool,
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 *);
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 *);
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,
9245 scratch_pool, scratch_pool));
9247 /* Revert the incoming move target directory. */
9248 err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
9250 FALSE, NULL, TRUE, FALSE,
9251 TRUE /*added_keep_local*/,
9252 NULL, NULL, /* no cancellation */
9253 ctx->notify_func2, ctx->notify_baton2,
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,
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;
9277 incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
9278 incoming_details->move_target_repos_relpath,
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);
9296 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
9300 if (ctx->notify_func2)
9302 svn_wc_notify_t *notify;
9304 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
9306 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9309 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
9311 conflict->resolution_tree = option_id;
9314 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
9319 return SVN_NO_ERROR;
9322 /* Implements conflict_option_resolve_func_t. */
9323 static svn_error_t *
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)
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;
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;
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,
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);
9368 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
9369 conflict, 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,
9375 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9376 &incoming_new_repos_relpath, &incoming_new_pegrev,
9377 NULL, conflict, scratch_pool,
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)
9384 victim_repos_relpath = incoming_old_repos_relpath;
9385 victim_peg_rev = incoming_old_pegrev;
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));
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,
9400 /* ### The following WC modifications should be atomic. */
9402 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
9403 &lock_abspath, ctx->wc_ctx,
9404 svn_dirent_get_longest_ancestor(local_abspath,
9407 scratch_pool, scratch_pool));
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);
9416 if (!is_copy && operation == svn_wc_operation_merge)
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,
9424 svn_dirent_local_style(moved_to_abspath,
9429 if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM)
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,
9436 svn_dirent_local_style(moved_to_abspath,
9441 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
9446 if (operation == svn_wc_operation_merge)
9448 const char *move_target_url;
9449 svn_opt_revision_t incoming_new_opt_rev;
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,
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,
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,
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);
9500 SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
9501 operation == svn_wc_operation_switch);
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,
9512 err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx,
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 */
9534 if (ctx->notify_func2)
9536 svn_wc_notify_t *notify;
9538 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
9540 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9543 conflict->resolution_tree = option_id;
9546 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
9551 return SVN_NO_ERROR;
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 *
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)
9563 const char *lock_abspath;
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;
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;
9587 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9588 conflict->local_abspath, scratch_pool,
9591 details = conflict->tree_conflict_local_details;
9593 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
9594 conflict, 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,
9600 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9601 &incoming_new_repos_relpath, &incoming_new_pegrev,
9602 NULL, conflict, scratch_pool,
9605 if (details->wc_siblings)
9607 merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
9608 details->preferred_sibling_idx,
9611 else if (details->wc_move_targets && details->move_target_repos_relpath)
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,
9620 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9621 _("Corresponding working copy node not found "
9623 svn_dirent_local_style(
9624 svn_dirent_skip_ancestor(
9625 wcroot_abspath, conflict->local_abspath),
9628 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx,
9629 merge_target_abspath,
9630 scratch_pool, scratch_pool));
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,
9639 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
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);
9647 /* Close stream to flush the file to disk. */
9648 SVN_ERR(svn_stream_close(stream));
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,
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));
9664 filter_props(incoming_props, scratch_pool);
9666 /* Create a property diff for the files. */
9667 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props,
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,
9676 scratch_pool, scratch_pool));
9678 /* Perform the file merge. */
9679 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
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,
9689 NULL, NULL, /* conflict func/baton */
9690 NULL, NULL, /* don't allow user to cancel here */
9692 svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
9694 return svn_error_compose_create(err,
9695 svn_wc__release_write_lock(ctx->wc_ctx,
9699 err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
9701 err = svn_error_compose_create(err,
9702 svn_wc__release_write_lock(ctx->wc_ctx,
9706 return svn_error_trace(err);
9708 if (ctx->notify_func2)
9710 svn_wc_notify_t *notify;
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,
9716 if (merge_content_outcome == svn_wc_merge_conflict)
9717 notify->content_state = svn_wc_notify_state_conflicted;
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);
9724 /* And also about the successfully resolved tree conflict. */
9725 notify = svn_wc_create_notify(conflict->local_abspath,
9726 svn_wc_notify_resolved_tree,
9728 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9731 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
9733 return SVN_NO_ERROR;
9736 /* Implements conflict_option_resolve_func_t. */
9737 static svn_error_t *
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)
9743 const char *lock_abspath;
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;
9758 details = conflict->tree_conflict_local_details;
9760 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
9761 conflict, 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,
9767 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9768 &incoming_new_repos_relpath, &incoming_new_pegrev,
9769 NULL, conflict, scratch_pool,
9772 if (details->wc_move_targets)
9774 apr_array_header_t *moves;
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 *);
9782 merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
9783 details->preferred_sibling_idx,
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,
9792 scratch_pool, scratch_pool));
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,
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);
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,
9828 return svn_error_trace(err);
9830 if (ctx->notify_func2)
9832 svn_wc_notify_t *notify;
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,
9838 if (conflict_report)
9839 notify->content_state = svn_wc_notify_state_conflicted;
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);
9845 /* And also about the successfully resolved tree conflict. */
9846 notify = svn_wc_create_notify(conflict->local_abspath,
9847 svn_wc_notify_resolved_tree,
9849 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
9852 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
9854 return SVN_NO_ERROR;
9857 static svn_error_t *
9858 assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
9860 svn_boolean_t text_conflicted;
9862 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL,
9863 conflict, scratch_pool,
9866 SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */
9868 return SVN_NO_ERROR;
9871 static svn_error_t *
9872 assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
9874 apr_array_header_t *props_conflicted;
9876 SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
9877 conflict, scratch_pool,
9880 /* ### return proper error? */
9881 SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0);
9883 return SVN_NO_ERROR;
9886 static svn_error_t *
9887 assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
9889 svn_boolean_t tree_conflicted;
9891 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
9892 conflict, scratch_pool,
9895 SVN_ERR_ASSERT(tree_conflicted); /* ### return proper error? */
9897 return SVN_NO_ERROR;
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 *
9905 add_resolution_option(apr_array_header_t *options,
9906 svn_client_conflict_t *conflict,
9907 svn_client_conflict_option_id_t id,
9909 const char *description,
9910 conflict_option_resolve_func_t resolve_func)
9912 svn_client_conflict_option_t *option;
9914 option = apr_pcalloc(options->pool, sizeof(*option));
9915 option->pool = options->pool;
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;
9922 APR_ARRAY_PUSH(options, const svn_client_conflict_option_t *) = option;
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)
9934 const char *mime_type;
9936 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
9938 *options = apr_array_make(result_pool, 7,
9939 sizeof(svn_client_conflict_option_t *));
9941 add_resolution_option(*options, conflict,
9942 svn_client_conflict_option_postpone,
9944 _("skip this conflict and leave it unresolved"),
9947 mime_type = svn_client_conflict_text_get_mime_type(conflict);
9948 if (mime_type && svn_mime_type_is_binary(mime_type))
9950 /* Resolver options for a binary file conflict. */
9951 add_resolution_option(*options, conflict,
9952 svn_client_conflict_option_base_text,
9954 _("discard local and incoming changes for this binary file"),
9955 resolve_text_conflict);
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);
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);
9971 /* Resolver options for a text file conflict. */
9972 add_resolution_option(*options, conflict,
9973 svn_client_conflict_option_base_text,
9975 _("discard local and incoming changes for this file"),
9976 resolve_text_conflict);
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);
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);
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);
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);
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);
10009 return SVN_NO_ERROR;
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)
10019 SVN_ERR(assert_prop_conflict(conflict, scratch_pool));
10021 *options = apr_array_make(result_pool, 7,
10022 sizeof(svn_client_conflict_option_t *));
10024 add_resolution_option(*options, conflict,
10025 svn_client_conflict_option_postpone,
10027 _("skip this conflict and leave it unresolved"),
10030 add_resolution_option(*options, conflict,
10031 svn_client_conflict_option_base_text,
10033 _("discard local and incoming changes for this property"),
10034 resolve_prop_conflict);
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);
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);
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);
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);
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);
10066 return SVN_NO_ERROR;
10069 /* Configure 'accept current wc state' resolution option for a tree conflict. */
10070 static svn_error_t *
10071 configure_option_accept_current_wc_state(svn_client_conflict_t *conflict,
10072 apr_array_header_t *options)
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;
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);
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)
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;
10097 do_resolve_func = resolve_accept_current_wc_state;
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"),
10105 return SVN_NO_ERROR;
10108 /* Configure 'update move destination' resolution option for a tree conflict. */
10109 static svn_error_t *
10110 configure_option_update_move_destination(svn_client_conflict_t *conflict,
10111 apr_array_header_t *options)
10113 svn_wc_operation_t operation;
10114 svn_wc_conflict_action_t incoming_change;
10115 svn_wc_conflict_reason_t local_change;
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);
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)
10126 add_resolution_option(
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);
10134 return SVN_NO_ERROR;
10137 /* Configure 'update raise moved away children' resolution option for a tree
10139 static svn_error_t *
10140 configure_option_update_raise_moved_away_children(
10141 svn_client_conflict_t *conflict,
10142 apr_array_header_t *options)
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;
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);
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)
10161 add_resolution_option(
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);
10169 return SVN_NO_ERROR;
10172 /* Configure 'incoming add ignore' resolution option for a tree conflict. */
10173 static svn_error_t *
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)
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;
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,
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))
10201 const char *description;
10202 const char *wcroot_abspath;
10204 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10205 conflict->local_abspath, scratch_pool,
10207 if (operation == svn_wc_operation_merge)
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)
10215 if (victim_node_kind == svn_node_file)
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)
10222 apr_psprintf(scratch_pool,
10223 _("replace '^/%s@%ld' with the locally added "
10225 incoming_new_repos_relpath, incoming_new_pegrev);
10228 apr_psprintf(scratch_pool,
10229 _("replace '^/%s@%ld' with the locally added item"),
10230 incoming_new_repos_relpath, incoming_new_pegrev);
10233 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10234 _("unexpected operation code '%d'"),
10236 add_resolution_option(
10237 options, conflict, svn_client_conflict_option_incoming_add_ignore,
10238 _("Ignore incoming addition"), description, resolve_incoming_add_ignore);
10241 return SVN_NO_ERROR;
10244 /* Configure 'incoming added file text merge' resolution option for a tree
10246 static svn_error_t *
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)
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;
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,
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))
10276 const char *description;
10277 const char *wcroot_abspath;
10279 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10280 conflict->local_abspath, scratch_pool,
10283 if (operation == svn_wc_operation_merge)
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),
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),
10298 incoming_new_repos_relpath, incoming_new_pegrev);
10300 add_resolution_option(
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);
10309 return SVN_NO_ERROR;
10312 /* Configure 'incoming added file replace and merge' resolution option for a
10313 * tree conflict. */
10314 static svn_error_t *
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)
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;
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,
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)
10344 const char *wcroot_abspath;
10345 const char *description;
10347 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10348 conflict->local_abspath, scratch_pool,
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),
10357 incoming_new_repos_relpath, incoming_new_pegrev);
10359 add_resolution_option(
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);
10366 return SVN_NO_ERROR;
10369 /* Configure 'incoming added dir merge' resolution option for a tree
10371 static svn_error_t *
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)
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;
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,
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)))
10403 const char *description;
10404 const char *wcroot_abspath;
10406 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10407 conflict->local_abspath, scratch_pool,
10409 if (operation == svn_wc_operation_merge)
10411 if (conflict->tree_conflict_incoming_details == NULL)
10412 return SVN_NO_ERROR;
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),
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),
10429 incoming_new_repos_relpath, incoming_new_pegrev);
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);
10439 return SVN_NO_ERROR;
10442 /* Configure 'incoming added dir replace' resolution option for a tree
10444 static svn_error_t *
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)
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;
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,
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)
10473 const char *description;
10474 const char *wcroot_abspath;
10476 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10477 conflict->local_abspath, scratch_pool,
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),
10485 incoming_new_repos_relpath, incoming_new_pegrev);
10486 add_resolution_option(
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);
10493 return SVN_NO_ERROR;
10496 /* Configure 'incoming added dir replace and merge' resolution option
10497 * for a tree conflict. */
10498 static svn_error_t *
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)
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;
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,
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)
10528 const char *description;
10529 const char *wcroot_abspath;
10531 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10532 conflict->local_abspath, scratch_pool,
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),
10541 incoming_new_repos_relpath, incoming_new_pegrev);
10543 add_resolution_option(
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);
10550 return SVN_NO_ERROR;
10553 /* Configure 'incoming delete ignore' resolution option for a tree conflict. */
10554 static svn_error_t *
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)
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;
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,
10574 if (incoming_change == svn_wc_conflict_action_delete)
10576 const char *description;
10577 struct conflict_tree_incoming_delete_details *incoming_details;
10578 svn_boolean_t is_incoming_move;
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)
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;
10591 else if (local_change == svn_wc_conflict_reason_deleted)
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;
10601 else if (local_change == svn_wc_conflict_reason_missing &&
10602 operation == svn_wc_operation_merge)
10604 struct conflict_tree_local_missing_details *local_details;
10605 svn_boolean_t is_local_move; /* "local" to branch history */
10607 local_details = conflict->tree_conflict_local_details;
10608 is_local_move = (local_details != NULL &&
10609 local_details->moves != NULL);
10611 if (is_incoming_move || is_local_move)
10612 return SVN_NO_ERROR;
10616 apr_psprintf(scratch_pool, _("ignore the deletion of '^/%s@%ld'"),
10617 incoming_new_repos_relpath, incoming_new_pegrev);
10619 add_resolution_option(options, conflict,
10620 svn_client_conflict_option_incoming_delete_ignore,
10621 _("Ignore incoming deletion"), description,
10622 resolve_incoming_delete_ignore);
10625 return SVN_NO_ERROR;
10628 /* Configure 'incoming delete accept' resolution option for a tree conflict. */
10629 static svn_error_t *
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)
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;
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,
10647 if (incoming_change == svn_wc_conflict_action_delete)
10649 struct conflict_tree_incoming_delete_details *incoming_details;
10650 svn_boolean_t is_incoming_move;
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))
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;
10666 const char *description;
10667 const char *wcroot_abspath;
10668 const char *local_abspath;
10670 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10671 conflict->local_abspath, scratch_pool,
10673 local_abspath = svn_client_conflict_get_local_abspath(conflict);
10675 apr_psprintf(scratch_pool, _("accept the deletion of '%s'"),
10676 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10679 add_resolution_option(
10681 svn_client_conflict_option_incoming_delete_accept,
10682 _("Accept incoming deletion"), description,
10683 resolve_incoming_delete_accept);
10687 return SVN_NO_ERROR;
10690 static svn_error_t *
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)
10699 svn_wc_operation_t operation;
10700 const char *victim_abspath;
10701 svn_node_kind_t victim_node_kind;
10702 const char *wcroot_abspath;
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,
10710 operation = svn_client_conflict_get_operation(conflict);
10711 if (operation == svn_wc_operation_merge)
10713 const char *incoming_moved_abspath = NULL;
10715 if (victim_node_kind == svn_node_none)
10717 /* This is an incoming move vs local move conflict. */
10718 struct conflict_tree_incoming_delete_details *details;
10720 details = conflict->tree_conflict_incoming_details;
10721 if (details->wc_move_targets)
10723 apr_array_header_t *moves;
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,
10733 if (incoming_moved_abspath)
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). */
10742 result_pool, _("move '%s' to '%s' and merge"),
10743 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10746 svn_dirent_local_style(svn_dirent_skip_ancestor(
10748 incoming_moved_abspath),
10755 result_pool, _("move '%s' to '%s' and merge"),
10756 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10759 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10767 result_pool, _("move and merge local changes from '%s' into '%s'"),
10768 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10771 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
10775 return SVN_NO_ERROR;
10778 /* Configure 'incoming move file merge' resolution option for
10779 * a tree conflict. */
10780 static svn_error_t *
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)
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;
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,
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,
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)
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;
10819 details = conflict->tree_conflict_incoming_details;
10820 if (details == NULL || details->moves == NULL)
10821 return SVN_NO_ERROR;
10823 if (apr_hash_count(details->wc_move_targets) == 0)
10824 return SVN_NO_ERROR;
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,
10832 SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
10837 add_resolution_option(
10839 svn_client_conflict_option_incoming_move_file_text_merge,
10840 _("Move and merge"), description,
10841 resolve_incoming_move_file_text_merge);
10844 return SVN_NO_ERROR;
10847 /* Configure 'incoming move dir merge' resolution option for
10848 * a tree conflict. */
10849 static svn_error_t *
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)
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;
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,
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,
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)
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;
10888 details = conflict->tree_conflict_incoming_details;
10889 if (details == NULL || details->moves == NULL)
10890 return SVN_NO_ERROR;
10892 if (apr_hash_count(details->wc_move_targets) == 0)
10893 return SVN_NO_ERROR;
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,
10901 SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
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);
10912 return SVN_NO_ERROR;
10915 /* Configure 'local move file merge' resolution option for
10916 * a tree conflict. */
10917 static svn_error_t *
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)
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;
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,
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,
10946 if (operation == svn_wc_operation_merge &&
10947 incoming_change == svn_wc_conflict_action_edit &&
10948 local_change == svn_wc_conflict_reason_missing)
10950 struct conflict_tree_local_missing_details *details;
10951 const char *wcroot_abspath;
10953 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10954 conflict->local_abspath,
10955 scratch_pool, scratch_pool));
10957 details = conflict->tree_conflict_local_details;
10958 if (details != NULL && details->moves != NULL &&
10959 details->move_target_repos_relpath != NULL)
10961 apr_array_header_t *moves;
10962 const char *moved_to_abspath;
10963 const char *description;
10965 moves = svn_hash_gets(details->wc_move_targets,
10966 details->move_target_repos_relpath);
10968 APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *);
10972 scratch_pool, _("apply changes to move destination '%s'"),
10973 svn_dirent_local_style(
10974 svn_dirent_skip_ancestor(wcroot_abspath, moved_to_abspath),
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))
10982 add_resolution_option(
10984 svn_client_conflict_option_local_move_file_text_merge,
10985 _("Apply to move destination"),
10986 description, resolve_local_move_file_merge);
10990 add_resolution_option(
10992 svn_client_conflict_option_local_move_dir_merge,
10993 _("Apply to move destination"),
10994 description, resolve_local_move_dir_merge);
10999 return SVN_NO_ERROR;
11002 /* Configure 'sibling move file/dir merge' resolution option for
11003 * a tree conflict. */
11004 static svn_error_t *
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)
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;
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,
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,
11032 if (operation == svn_wc_operation_merge &&
11033 incoming_change == svn_wc_conflict_action_edit &&
11034 local_change == svn_wc_conflict_reason_missing)
11036 struct conflict_tree_local_missing_details *details;
11037 const char *wcroot_abspath;
11039 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11040 conflict->local_abspath,
11041 scratch_pool, scratch_pool));
11043 details = conflict->tree_conflict_local_details;
11044 if (details != NULL && details->wc_siblings != NULL)
11046 const char *description;
11047 const char *sibling;
11050 apr_pstrdup(conflict->pool,
11051 APR_ARRAY_IDX(details->wc_siblings,
11052 details->preferred_sibling_idx,
11056 scratch_pool, _("apply changes to '%s'"),
11057 svn_dirent_local_style(
11058 svn_dirent_skip_ancestor(wcroot_abspath, sibling),
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))
11066 add_resolution_option(
11068 svn_client_conflict_option_sibling_move_file_text_merge,
11069 _("Apply to corresponding local location"),
11070 description, resolve_local_move_file_merge);
11074 add_resolution_option(
11076 svn_client_conflict_option_sibling_move_dir_merge,
11077 _("Apply to corresponding local location"),
11078 description, resolve_local_move_dir_merge);
11083 return SVN_NO_ERROR;
11086 struct conflict_tree_update_local_moved_away_details {
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.
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;
11098 /* Current index into the list of working copy paths in WC_MOVE_TARGETS. */
11099 int preferred_move_target_idx;
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
11109 static svn_error_t *
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)
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;
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;
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,
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,
11161 option_id = svn_client_conflict_option_get_id(option);
11162 SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge);
11164 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
11165 conflict, 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,
11171 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
11172 &incoming_new_repos_relpath, &incoming_new_pegrev,
11173 NULL, conflict, scratch_pool,
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));
11184 /* Fetch the ancestor file's content. */
11185 ancestor_url = svn_path_url_add_component2(repos_root_url,
11186 incoming_old_repos_relpath,
11188 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
11189 ancestor_url, NULL, NULL,
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);
11197 /* Close stream to flush ancestor file to disk. */
11198 SVN_ERR(svn_stream_close(ancestor_stream));
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 *);
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 *);
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,
11218 scratch_pool, scratch_pool));
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);
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);
11234 /* Create a property diff for the files. */
11235 err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
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,
11250 NULL, NULL, /* conflict func/baton */
11251 NULL, NULL, /* don't allow user to cancel here */
11256 if (ctx->notify_func2)
11258 svn_wc_notify_t *notify;
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,
11264 if (merge_content_outcome == svn_wc_merge_conflict)
11265 notify->content_state = svn_wc_notify_state_conflicted;
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);
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 */
11284 /* Remove moved-away file from disk. */
11285 err = svn_io_remove_file2(incoming_moved_to_abspath, TRUE, scratch_pool);
11289 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
11293 if (ctx->notify_func2)
11295 svn_wc_notify_t *notify;
11297 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
11299 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
11302 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
11304 conflict->resolution_tree = option_id;
11307 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
11312 return SVN_NO_ERROR;
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 *
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)
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;
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;
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,
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,
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);
11375 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
11376 conflict, 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,
11382 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
11383 &incoming_new_repos_relpath, &incoming_new_pegrev,
11384 NULL, conflict, scratch_pool,
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));
11395 /* Fetch the ancestor file's content. */
11396 ancestor_url = svn_path_url_add_component2(repos_root_url,
11397 incoming_old_repos_relpath,
11399 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
11400 ancestor_url, NULL, NULL,
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);
11408 /* Close stream to flush ancestor file to disk. */
11409 SVN_ERR(svn_stream_close(ancestor_stream));
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 *);
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 *);
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,
11429 scratch_pool, scratch_pool));
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);
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);
11445 /* Create a property diff for the files. */
11446 err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
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,
11461 NULL, NULL, /* conflict func/baton */
11462 NULL, NULL, /* don't allow user to cancel here */
11467 if (ctx->notify_func2)
11469 svn_wc_notify_t *notify;
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,
11475 if (merge_content_outcome == svn_wc_merge_conflict)
11476 notify->content_state = svn_wc_notify_state_conflicted;
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);
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,
11495 err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
11499 if (ctx->notify_func2)
11501 svn_wc_notify_t *notify;
11503 notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
11505 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
11508 svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
11510 conflict->resolution_tree = option_id;
11513 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
11518 return SVN_NO_ERROR;
11521 /* Implements tree_conflict_get_details_func_t. */
11522 static svn_error_t *
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)
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;
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));
11536 details = apr_pcalloc(conflict->pool, sizeof(*details));
11538 details->wc_move_targets = apr_array_make(conflict->pool, 1,
11539 sizeof(const char *));
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,
11550 conflict->tree_conflict_local_details = details;
11552 return SVN_NO_ERROR;
11555 static svn_error_t *
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)
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;
11565 operation = svn_client_conflict_get_operation(conflict);
11567 *incoming_moved_to_abspath = NULL;
11568 *local_moved_to_abspath = NULL;
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;
11575 incoming_move_target_wc_abspaths =
11576 svn_hash_gets(incoming_details->wc_move_targets,
11577 get_moved_to_repos_relpath(incoming_details,
11579 *incoming_moved_to_abspath =
11580 APR_ARRAY_IDX(incoming_move_target_wc_abspaths,
11581 incoming_details->wc_move_target_idx, const char *);
11583 if (operation == svn_wc_operation_merge)
11585 struct conflict_tree_local_missing_details *local_details;
11586 apr_array_header_t *local_moves;
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;
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,
11601 struct conflict_tree_update_local_moved_away_details *local_details;
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;
11608 *local_moved_to_abspath =
11609 APR_ARRAY_IDX(local_details->wc_move_targets,
11610 local_details->preferred_move_target_idx,
11614 return SVN_NO_ERROR;
11617 static svn_error_t *
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)
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;
11630 *description = NULL;
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;
11638 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11639 conflict->local_abspath, scratch_pool,
11642 operation = svn_client_conflict_get_operation(conflict);
11644 if (operation == svn_wc_operation_merge)
11646 /* In case of a merge, the incoming move has A+ (copied) status... */
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),
11654 svn_dirent_local_style(
11655 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
11660 /* ...but in case of update/switch the local move has "A+" status. */
11664 _("override incoming move and merge incoming changes from '%s' "
11666 svn_dirent_local_style(
11667 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
11669 svn_dirent_local_style(
11670 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
11674 return SVN_NO_ERROR;
11677 static svn_error_t *
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)
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;
11690 *description = NULL;
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;
11698 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11699 conflict->local_abspath, scratch_pool,
11702 operation = svn_client_conflict_get_operation(conflict);
11704 if (operation == svn_wc_operation_merge)
11706 SVN_ERR(describe_incoming_move_merge_conflict_option(
11707 description, conflict, ctx, local_moved_to_abspath,
11708 scratch_pool, scratch_pool));
11715 _("accept incoming move and merge local changes from "
11717 svn_dirent_local_style(
11718 svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
11720 svn_dirent_local_style(
11721 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
11725 return SVN_NO_ERROR;
11728 /* Configure 'both moved file merge' resolution options for a tree conflict. */
11729 static svn_error_t *
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)
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;
11747 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11748 conflict->local_abspath, scratch_pool,
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,
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,
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)
11777 const char *description;
11779 SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge(
11780 &description, conflict, ctx, conflict->pool, scratch_pool));
11782 if (description == NULL) /* details not fetched yet */
11783 return SVN_NO_ERROR;
11785 add_resolution_option(
11786 options, conflict, svn_client_conflict_option_both_moved_file_merge,
11787 _("Merge to corresponding local location"),
11789 operation == svn_wc_operation_merge ?
11790 resolve_both_moved_file_text_merge :
11791 resolve_both_moved_file_update_keep_local_move);
11793 SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge(
11794 &description, conflict, ctx, conflict->pool, scratch_pool));
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);
11804 return SVN_NO_ERROR;
11807 /* Configure 'both moved dir merge' resolution options for a tree conflict. */
11808 static svn_error_t *
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)
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;
11826 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
11827 conflict->local_abspath, scratch_pool,
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,
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,
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)
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;
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;
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;
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,
11874 incoming_move_target_wc_abspaths =
11875 svn_hash_gets(incoming_details->wc_move_targets,
11876 get_moved_to_repos_relpath(incoming_details,
11878 incoming_moved_to_abspath =
11879 APR_ARRAY_IDX(incoming_move_target_wc_abspaths,
11880 incoming_details->wc_move_target_idx, const char *);
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),
11888 svn_dirent_local_style(
11889 svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
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);
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);
11905 return SVN_NO_ERROR;
11908 /* Return a copy of the repos replath candidate list. */
11909 static svn_error_t *
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)
11916 apr_array_header_t *sorted_repos_relpaths;
11919 sorted_repos_relpaths = svn_sort__hash(wc_move_targets,
11920 svn_sort_compare_items_as_paths,
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++)
11928 svn_sort__item_t item;
11929 const char *repos_relpath;
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);
11937 return SVN_NO_ERROR;
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)
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;
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)
11966 /* We cannot operate on this option. */
11967 *possible_moved_to_repos_relpaths = NULL;
11968 return SVN_NO_ERROR;
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);
11976 if (operation == svn_wc_operation_merge &&
11977 incoming_change == svn_wc_conflict_action_edit &&
11978 local_change == svn_wc_conflict_reason_missing)
11980 struct conflict_tree_local_missing_details *details;
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,
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));
11997 *possible_moved_to_repos_relpaths = NULL;
12001 struct conflict_tree_incoming_delete_details *details;
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,
12012 SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths,
12013 details->wc_move_targets,
12014 result_pool, scratch_pool));
12017 return SVN_NO_ERROR;
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)
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));
12036 static svn_error_t *
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)
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;
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,
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,
12061 item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx,
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);
12067 hi = apr_hash_next(hi))
12069 const char *repos_relpath = apr_hash_this_key(hi);
12071 if (strcmp(move_target_repos_relpath, repos_relpath) == 0)
12073 *new_hash_key = repos_relpath;
12074 return SVN_NO_ERROR;
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,
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)
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;
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. */
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);
12118 if (operation == svn_wc_operation_merge &&
12119 incoming_change == svn_wc_conflict_action_edit &&
12120 local_change == svn_wc_conflict_reason_missing)
12122 struct conflict_tree_local_missing_details *details;
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,
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;
12139 /* Update option description. */
12140 SVN_ERR(conflict_tree_get_description_local_missing(
12141 &option->description, conflict, ctx,
12142 conflict->pool, scratch_pool));
12146 struct conflict_tree_incoming_delete_details *details;
12147 apr_array_header_t *move_target_wc_abspaths;
12148 const char *moved_to_abspath;
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,
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;
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,
12172 SVN_ERR(describe_incoming_move_merge_conflict_option(
12173 &option->description,
12180 return SVN_NO_ERROR;
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)
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));
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)
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;
12212 svn_client_conflict_option_id_t id;
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)
12226 /* We cannot operate on this option. */
12227 *possible_moved_to_abspaths = NULL;
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);
12236 if (operation == svn_wc_operation_merge &&
12237 incoming_change == svn_wc_conflict_action_edit &&
12238 local_change == svn_wc_conflict_reason_missing)
12240 struct conflict_tree_local_missing_details *details;
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,
12252 *possible_moved_to_abspaths = apr_array_make(result_pool, 1,
12253 sizeof (const char *));
12254 if (details->wc_move_targets)
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++)
12262 const char *moved_to_abspath;
12264 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
12266 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
12267 apr_pstrdup(result_pool, moved_to_abspath);
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)
12275 for (i = 0; i < details->wc_siblings->nelts; i++)
12277 const char *sibling_abspath;
12279 sibling_abspath = APR_ARRAY_IDX(details->wc_siblings, i,
12281 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
12282 apr_pstrdup(result_pool, sibling_abspath);
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)
12291 struct conflict_tree_update_local_moved_away_details *details;
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,
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++)
12308 const char *moved_to_abspath;
12310 moved_to_abspath = APR_ARRAY_IDX(details->wc_move_targets, i,
12312 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
12313 apr_pstrdup(result_pool, moved_to_abspath);
12318 struct conflict_tree_incoming_delete_details *details;
12319 apr_array_header_t *move_target_wc_abspaths;
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,
12330 move_target_wc_abspaths =
12331 svn_hash_gets(details->wc_move_targets,
12332 get_moved_to_repos_relpath(details, scratch_pool));
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++)
12340 const char *moved_to_abspath;
12342 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
12344 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
12345 apr_pstrdup(result_pool, moved_to_abspath);
12349 return SVN_NO_ERROR;
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)
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));
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)
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;
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. */
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);
12400 if (operation == svn_wc_operation_merge &&
12401 incoming_change == svn_wc_conflict_action_edit &&
12402 local_change == svn_wc_conflict_reason_missing)
12404 struct conflict_tree_local_missing_details *details;
12405 const char *wcroot_abspath;
12406 const char *preferred_sibling;
12408 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
12410 conflict->local_abspath,
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,
12424 if (details->wc_siblings)
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,
12434 /* Record the user's preference. */
12435 details->preferred_sibling_idx = preferred_move_target_idx;
12437 /* Update option description. */
12438 preferred_sibling = APR_ARRAY_IDX(details->wc_siblings,
12439 details->preferred_sibling_idx,
12441 option->description =
12443 conflict->pool, _("apply changes to '%s'"),
12444 svn_dirent_local_style(
12445 svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling),
12448 else if (details->wc_move_targets)
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);
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,
12464 /* Record the user's preference. */
12465 details->wc_move_target_idx = preferred_move_target_idx;
12467 /* Update option description. */
12468 SVN_ERR(conflict_tree_get_description_local_missing(
12469 &option->description, conflict, ctx,
12470 conflict->pool, scratch_pool));
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)
12478 struct conflict_tree_update_local_moved_away_details *details;
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,
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,
12498 /* Record the user's preference. */
12499 details->preferred_move_target_idx = preferred_move_target_idx;
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,
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)
12513 else if (id == svn_client_conflict_option_both_moved_dir_move_merge)
12518 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
12519 _("Unexpected option id '%d'"), id);
12523 struct conflict_tree_incoming_delete_details *details;
12524 apr_array_header_t *move_target_wc_abspaths;
12525 const char *moved_to_abspath;
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,
12536 move_target_wc_abspaths =
12537 svn_hash_gets(details->wc_move_targets,
12538 get_moved_to_repos_relpath(details, scratch_pool));
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,
12549 /* Record the user's preference. */
12550 details->wc_move_target_idx = preferred_move_target_idx;
12552 /* Update option description. */
12553 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
12554 details->wc_move_target_idx,
12556 SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description,
12562 return SVN_NO_ERROR;
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)
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));
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)
12588 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
12590 *options = apr_array_make(result_pool, 2,
12591 sizeof(svn_client_conflict_option_t *));
12593 /* Add postpone option. */
12594 add_resolution_option(*options, conflict,
12595 svn_client_conflict_option_postpone,
12597 _("skip this conflict and leave it unresolved"),
12600 /* Add an option which marks the conflict resolved. */
12601 SVN_ERR(configure_option_accept_current_wc_state(conflict, *options));
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,
12607 SVN_ERR(configure_option_incoming_add_ignore(conflict, ctx, *options,
12609 SVN_ERR(configure_option_incoming_added_file_text_merge(conflict, ctx,
12612 SVN_ERR(configure_option_incoming_added_file_replace_and_merge(conflict,
12616 SVN_ERR(configure_option_incoming_added_dir_merge(conflict, ctx,
12619 SVN_ERR(configure_option_incoming_added_dir_replace(conflict, ctx,
12622 SVN_ERR(configure_option_incoming_added_dir_replace_and_merge(conflict,
12626 SVN_ERR(configure_option_incoming_delete_ignore(conflict, ctx, *options,
12628 SVN_ERR(configure_option_incoming_delete_accept(conflict, ctx, *options,
12630 SVN_ERR(configure_option_incoming_move_file_merge(conflict, ctx, *options,
12632 SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options,
12634 SVN_ERR(configure_option_local_move_file_or_dir_merge(conflict, ctx,
12637 SVN_ERR(configure_option_sibling_move_merge(conflict, ctx, *options,
12639 SVN_ERR(configure_option_both_moved_file_merge(conflict, ctx, *options,
12641 SVN_ERR(configure_option_both_moved_dir_merge(conflict, ctx, *options,
12644 return SVN_NO_ERROR;
12647 /* Swallow authz failures and return SVN_NO_ERROR in that case.
12648 * Otherwise, return ERR unchanged. */
12649 static svn_error_t *
12650 ignore_authz_failures(svn_error_t *err)
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)))
12656 svn_error_clear(err);
12657 err = SVN_NO_ERROR;
12664 svn_client_conflict_tree_get_details(svn_client_conflict_t *conflict,
12665 svn_client_ctx_t *ctx,
12666 apr_pool_t *scratch_pool)
12668 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
12670 if (ctx->notify_func2)
12672 svn_wc_notify_t *notify;
12674 notify = svn_wc_create_notify(
12675 svn_client_conflict_get_local_abspath(conflict),
12676 svn_wc_notify_begin_search_tree_conflict_details,
12678 ctx->notify_func2(ctx->notify_baton2, notify,
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,
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,
12695 if (ctx->notify_func2)
12697 svn_wc_notify_t *notify;
12699 notify = svn_wc_create_notify(
12700 svn_client_conflict_get_local_abspath(conflict),
12701 svn_wc_notify_end_search_tree_conflict_details,
12703 ctx->notify_func2(ctx->notify_baton2, notify,
12707 return SVN_NO_ERROR;
12710 svn_client_conflict_option_id_t
12711 svn_client_conflict_option_get_id(svn_client_conflict_option_t *option)
12717 svn_client_conflict_option_get_label(svn_client_conflict_option_t *option,
12718 apr_pool_t *result_pool)
12720 return apr_pstrdup(result_pool, option->label);
12724 svn_client_conflict_option_get_description(svn_client_conflict_option_t *option,
12725 apr_pool_t *result_pool)
12727 return apr_pstrdup(result_pool, option->description);
12730 svn_client_conflict_option_id_t
12731 svn_client_conflict_get_recommended_option_id(svn_client_conflict_t *conflict)
12733 return conflict->recommended_option_id;
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)
12742 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
12743 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
12745 return SVN_NO_ERROR;
12748 svn_client_conflict_option_t *
12749 svn_client_conflict_option_find_by_id(apr_array_header_t *options,
12750 svn_client_conflict_option_id_t option_id)
12754 for (i = 0; i < options->nelts; i++)
12756 svn_client_conflict_option_t *this_option;
12757 svn_client_conflict_option_id_t this_option_id;
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);
12762 if (this_option_id == option_id)
12763 return this_option;
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)
12776 apr_array_header_t *resolution_options;
12777 svn_client_conflict_option_t *option;
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,
12784 if (option == NULL)
12785 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
12787 _("Inapplicable conflict resolution option "
12788 "given for conflicted path '%s'"),
12789 svn_dirent_local_style(conflict->local_abspath,
12792 SVN_ERR(svn_client_conflict_text_resolve(conflict, option, ctx, scratch_pool));
12794 return SVN_NO_ERROR;
12797 svn_client_conflict_option_id_t
12798 svn_client_conflict_text_get_resolution(svn_client_conflict_t *conflict)
12800 return conflict->resolution_text;
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)
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));
12814 return SVN_NO_ERROR;
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)
12825 apr_array_header_t *resolution_options;
12826 svn_client_conflict_option_t *option;
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,
12833 if (option == NULL)
12834 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
12836 _("Inapplicable conflict resolution option "
12837 "given for conflicted path '%s'"),
12838 svn_dirent_local_style(conflict->local_abspath,
12840 SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option, ctx,
12843 return SVN_NO_ERROR;
12846 svn_client_conflict_option_id_t
12847 svn_client_conflict_prop_get_resolution(svn_client_conflict_t *conflict,
12848 const char *propname)
12850 svn_client_conflict_option_t *option;
12852 option = svn_hash_gets(conflict->resolved_props, propname);
12853 if (option == NULL)
12854 return svn_client_conflict_option_unspecified;
12856 return svn_client_conflict_option_get_id(option);
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)
12865 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
12866 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
12868 return SVN_NO_ERROR;
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)
12878 apr_array_header_t *resolution_options;
12879 svn_client_conflict_option_t *option;
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,
12886 if (option == NULL)
12887 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
12889 _("Inapplicable conflict resolution option "
12890 "given for conflicted path '%s'"),
12891 svn_dirent_local_style(conflict->local_abspath,
12893 SVN_ERR(svn_client_conflict_tree_resolve(conflict, option, ctx, scratch_pool));
12895 return SVN_NO_ERROR;
12898 svn_client_conflict_option_id_t
12899 svn_client_conflict_tree_get_resolution(svn_client_conflict_t *conflict)
12901 return conflict->resolution_tree;
12904 /* Return the legacy conflict descriptor which is wrapped by CONFLICT. */
12905 static const svn_wc_conflict_description2_t *
12906 get_conflict_desc2_t(svn_client_conflict_t *conflict)
12908 if (conflict->legacy_text_conflict)
12909 return conflict->legacy_text_conflict;
12911 if (conflict->legacy_tree_conflict)
12912 return conflict->legacy_tree_conflict;
12914 if (conflict->prop_conflicts && conflict->legacy_prop_conflict_propname)
12915 return svn_hash_gets(conflict->prop_conflicts,
12916 conflict->legacy_prop_conflict_propname);
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)
12929 if (text_conflicted)
12930 *text_conflicted = (conflict->legacy_text_conflict != NULL);
12932 if (props_conflicted)
12934 if (conflict->prop_conflicts)
12935 SVN_ERR(svn_hash_keys(props_conflicted, conflict->prop_conflicts,
12938 *props_conflicted = apr_array_make(result_pool, 0,
12939 sizeof(const char*));
12942 if (tree_conflicted)
12943 *tree_conflicted = (conflict->legacy_tree_conflict != NULL);
12945 return SVN_NO_ERROR;
12949 svn_client_conflict_get_local_abspath(svn_client_conflict_t *conflict)
12951 return conflict->local_abspath;
12955 svn_client_conflict_get_operation(svn_client_conflict_t *conflict)
12957 return get_conflict_desc2_t(conflict)->operation;
12960 svn_wc_conflict_action_t
12961 svn_client_conflict_get_incoming_change(svn_client_conflict_t *conflict)
12963 return get_conflict_desc2_t(conflict)->action;
12966 svn_wc_conflict_reason_t
12967 svn_client_conflict_get_local_change(svn_client_conflict_t *conflict)
12969 return get_conflict_desc2_t(conflict)->reason;
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)
12979 if (repos_root_url)
12981 if (get_conflict_desc2_t(conflict)->src_left_version)
12983 get_conflict_desc2_t(conflict)->src_left_version->repos_url;
12984 else if (get_conflict_desc2_t(conflict)->src_right_version)
12986 get_conflict_desc2_t(conflict)->src_right_version->repos_url;
12988 *repos_root_url = NULL;
12993 if (get_conflict_desc2_t(conflict)->src_left_version)
12995 get_conflict_desc2_t(conflict)->src_left_version->repos_uuid;
12996 else if (get_conflict_desc2_t(conflict)->src_right_version)
12998 get_conflict_desc2_t(conflict)->src_right_version->repos_uuid;
13000 *repos_uuid = NULL;
13003 return SVN_NO_ERROR;
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)
13015 if (incoming_old_repos_relpath)
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;
13021 *incoming_old_repos_relpath = NULL;
13024 if (incoming_old_pegrev)
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;
13030 *incoming_old_pegrev = SVN_INVALID_REVNUM;
13033 if (incoming_old_node_kind)
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;
13039 *incoming_old_node_kind = svn_node_none;
13042 return SVN_NO_ERROR;
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)
13054 if (incoming_new_repos_relpath)
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;
13060 *incoming_new_repos_relpath = NULL;
13063 if (incoming_new_pegrev)
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;
13069 *incoming_new_pegrev = SVN_INVALID_REVNUM;
13072 if (incoming_new_node_kind)
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;
13078 *incoming_new_node_kind = svn_node_none;
13081 return SVN_NO_ERROR;
13085 svn_client_conflict_tree_get_victim_node_kind(svn_client_conflict_t *conflict)
13087 SVN_ERR_ASSERT_NO_RETURN(assert_tree_conflict(conflict, conflict->pool)
13090 return get_conflict_desc2_t(conflict)->node_kind;
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)
13102 const svn_wc_conflict_description2_t *desc;
13104 SVN_ERR(assert_prop_conflict(conflict, conflict->pool));
13106 desc = svn_hash_gets(conflict->prop_conflicts, propname);
13108 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
13109 _("Property '%s' is not in conflict."), propname);
13113 svn_string_dup(desc->prop_value_base, result_pool);
13115 if (working_propval)
13117 svn_string_dup(desc->prop_value_working, result_pool);
13119 if (incoming_old_propval)
13120 *incoming_old_propval =
13121 svn_string_dup(desc->prop_value_incoming_old, result_pool);
13123 if (incoming_new_propval)
13124 *incoming_new_propval =
13125 svn_string_dup(desc->prop_value_incoming_new, result_pool);
13127 return SVN_NO_ERROR;
13131 svn_client_conflict_prop_get_reject_abspath(svn_client_conflict_t *conflict)
13133 SVN_ERR_ASSERT_NO_RETURN(assert_prop_conflict(conflict, conflict->pool)
13136 /* svn_wc_conflict_description2_t stores this path in 'their_abspath' */
13137 return get_conflict_desc2_t(conflict)->their_abspath;
13141 svn_client_conflict_text_get_mime_type(svn_client_conflict_t *conflict)
13143 SVN_ERR_ASSERT_NO_RETURN(assert_text_conflict(conflict, conflict->pool)
13146 return get_conflict_desc2_t(conflict)->mime_type;
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)
13158 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
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;
13169 if (working_abspath)
13170 *working_abspath = get_conflict_desc2_t(conflict)->my_abspath;
13172 if (incoming_old_abspath)
13173 *incoming_old_abspath = get_conflict_desc2_t(conflict)->base_abspath;
13175 if (incoming_new_abspath)
13176 *incoming_new_abspath = get_conflict_desc2_t(conflict)->their_abspath;
13178 return SVN_NO_ERROR;
13181 /* Set up type-specific data for a new conflict object. */
13182 static svn_error_t *
13183 conflict_type_specific_setup(svn_client_conflict_t *conflict,
13184 apr_pool_t *scratch_pool)
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;
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,
13195 if (!tree_conflicted)
13196 return SVN_NO_ERROR;
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;
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);
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)
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;
13217 else if (incoming_change == svn_wc_conflict_action_add)
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;
13224 else if (incoming_change == svn_wc_conflict_action_edit)
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;
13232 if (local_change == svn_wc_conflict_reason_missing)
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;
13239 else if (local_change == svn_wc_conflict_reason_moved_away &&
13240 operation == svn_wc_operation_update /* ### what about switch? */)
13242 conflict->tree_conflict_get_local_details_func =
13243 conflict_tree_get_details_update_local_moved_away;
13246 return SVN_NO_ERROR;
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)
13256 const apr_array_header_t *descs;
13259 *conflict = apr_pcalloc(result_pool, sizeof(**conflict));
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;
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,
13272 result_pool, scratch_pool));
13273 for (i = 0; i < descs->nelts; i++)
13275 const svn_wc_conflict_description2_t *desc;
13277 desc = APR_ARRAY_IDX(descs, i, const svn_wc_conflict_description2_t *);
13278 add_legacy_desc_to_conflict(desc, *conflict, result_pool);
13281 SVN_ERR(conflict_type_specific_setup(*conflict, scratch_pool));
13283 return SVN_NO_ERROR;
13286 /* Baton for conflict_status_walker */
13287 struct conflict_status_walker_baton
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;
13298 /* Implements svn_wc_notify_func2_t to collect new conflicts caused by
13299 resolving a tree conflict. */
13301 tree_conflict_collector(void *baton,
13302 const svn_wc_notify_t *notify,
13305 struct conflict_status_walker_baton *cswb = baton;
13307 if (cswb->notify_func)
13308 cswb->notify_func(cswb->notify_baton, notify, pool);
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))
13315 if (!svn_hash_gets(cswb->unresolved_tree_conflicts, notify->path))
13317 const char *tc_abspath;
13318 apr_pool_t *hash_pool;
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, "");
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.
13335 static svn_error_t *
13336 handle_tree_conflict_resolution_failure(const char *local_abspath,
13338 apr_hash_t *unresolved_tree_conflicts)
13340 const char *tc_abspath;
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. */
13347 svn_error_clear(err);
13348 tc_abspath = apr_pstrdup(apr_hash_pool_get(unresolved_tree_conflicts),
13351 svn_hash_sets(unresolved_tree_conflicts, tc_abspath, "");
13353 return SVN_NO_ERROR; /* Caller may retry after resolving other conflicts. */
13356 /* Implements svn_wc_status4_t to walk all conflicts to resolve.
13358 static svn_error_t *
13359 conflict_status_walker(void *baton,
13360 const char *local_abspath,
13361 const svn_wc_status3_t *status,
13362 apr_pool_t *scratch_pool)
13364 struct conflict_status_walker_baton *cswb = baton;
13365 svn_client_conflict_t *conflict;
13367 svn_boolean_t tree_conflicted;
13369 if (!status->conflicted)
13370 return SVN_NO_ERROR;
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,
13377 err = cswb->conflict_walk_func(cswb->conflict_walk_func_baton,
13378 conflict, scratch_pool);
13381 if (tree_conflicted)
13382 SVN_ERR(handle_tree_conflict_resolution_failure(
13383 local_abspath, err, cswb->unresolved_tree_conflicts));
13386 return svn_error_trace(err);
13389 if (tree_conflicted)
13391 svn_client_conflict_option_id_t resolution;
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;
13399 return SVN_NO_ERROR;
13403 svn_client_conflict_walk(const char *local_abspath,
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)
13410 struct conflict_status_walker_baton cswb;
13411 apr_pool_t *iterpool = NULL;
13412 svn_error_t *err = SVN_NO_ERROR;
13414 if (depth == svn_depth_unknown)
13415 depth = svn_depth_infinity;
13417 cswb.conflict_walk_func = conflict_walk_func;
13418 cswb.conflict_walk_func_baton = conflict_walk_func_baton;
13420 cswb.resolved_a_tree_conflict = FALSE;
13421 cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool);
13423 if (ctx->notify_func2)
13424 ctx->notify_func2(ctx->notify_baton2,
13425 svn_wc_create_notify(
13427 svn_wc_notify_conflict_resolver_starting,
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;
13437 err = svn_wc_walk_status(ctx->wc_ctx,
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,
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))
13453 apr_hash_index_t *hi;
13454 svn_wc_status3_t *status = NULL;
13455 const char *tc_abspath = NULL;
13458 svn_pool_clear(iterpool);
13460 iterpool = svn_pool_create(scratch_pool);
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;
13466 for (; hi && !err; hi = apr_hash_next(hi))
13468 svn_pool_clear(iterpool);
13470 tc_abspath = apr_hash_this_key(hi);
13472 if (ctx->cancel_func)
13474 err = ctx->cancel_func(ctx->cancel_baton);
13479 err = svn_error_trace(svn_wc_status3(&status, ctx->wc_ctx,
13481 iterpool, iterpool));
13485 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
13486 status, scratch_pool));
13491 if (!err && !cswb.resolved_a_tree_conflict && tc_abspath &&
13492 apr_hash_count(cswb.unresolved_tree_conflicts))
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;
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);
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));
13514 svn_pool_destroy(iterpool);
13516 ctx->notify_func2 = cswb.notify_func;
13517 ctx->notify_baton2 = cswb.notify_baton;
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,
13526 return svn_error_trace(err);