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 commited 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)
809 const char *moved_along_path;
810 struct repos_move_info *move;
812 /* See if we can find an even closer move for this moved-along path. */
813 relpath = svn_relpath_skip_ancestor(closest_move->moved_to_repos_relpath,
816 svn_relpath_join(closest_move->moved_from_repos_relpath, relpath,
818 move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool);
826 /* Search for nested moves in REVISION, given the already found MOVES,
827 * all DELETED_PATHS, and all COPIES, from the same revision.
828 * Append any nested moves to the MOVES array. */
830 find_nested_moves(apr_array_header_t *moves,
832 apr_array_header_t *deleted_paths,
833 apr_hash_t *moved_paths,
834 svn_revnum_t revision,
836 const char *repos_root_url,
837 const char *repos_uuid,
838 svn_ra_session_t *ra_session,
839 svn_client_ctx_t *ctx,
840 apr_pool_t *result_pool,
841 apr_pool_t *scratch_pool)
843 apr_array_header_t *nested_moves;
845 apr_pool_t *iterpool;
847 nested_moves = apr_array_make(result_pool, 0,
848 sizeof(struct repos_move_info *));
849 iterpool = svn_pool_create(scratch_pool);
850 for (i = 0; i < deleted_paths->nelts; i++)
852 const char *deleted_path;
853 const char *child_relpath;
854 const char *moved_along_repos_relpath;
855 struct repos_move_info *move;
856 apr_array_header_t *copies_with_same_source_path;
858 svn_boolean_t related;
860 svn_pool_clear(iterpool);
862 deleted_path = APR_ARRAY_IDX(deleted_paths, i, const char *);
863 move = map_deleted_path_to_move(deleted_path, moves, iterpool);
866 child_relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
868 if (child_relpath == NULL || child_relpath[0] == '\0')
869 continue; /* not a nested move */
871 /* Consider: svn mv A B; svn mv B/foo C/foo
872 * Copyfrom for C/foo is A/foo, even though C/foo was moved here from
873 * B/foo. A/foo was not deleted. It is B/foo which was deleted.
874 * We now know about the move A->B and moved-along child_relpath "foo".
875 * Try to detect an ancestral relationship between A/foo and the
876 * moved-along path. */
877 moved_along_repos_relpath =
878 svn_relpath_join(move->moved_from_repos_relpath, child_relpath,
880 copies_with_same_source_path = svn_hash_gets(copies,
881 moved_along_repos_relpath);
882 if (copies_with_same_source_path == NULL)
883 continue; /* not a nested move */
885 for (j = 0; j < copies_with_same_source_path->nelts; j++)
887 struct copy_info *copy;
889 copy = APR_ARRAY_IDX(copies_with_same_source_path, j,
891 SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url,
892 moved_along_repos_relpath,
899 struct repos_move_info *nested_move;
901 /* Remember details of this move. */
902 SVN_ERR(add_new_move(&nested_move, moved_along_repos_relpath,
903 copy->copyto_path, copy->copyfrom_rev,
905 revision, author, moved_paths,
906 ra_session, repos_root_url,
907 result_pool, iterpool));
909 /* Add this move to the list of nested moves in this revision. */
910 APR_ARRAY_PUSH(nested_moves, struct repos_move_info *) =
915 svn_pool_destroy(iterpool);
917 /* Add all nested moves found to the list of all moves in this revision. */
918 apr_array_cat(moves, nested_moves);
923 /* Make a shallow copy of the copied LOG_ITEM in COPIES. */
925 cache_copied_item(apr_hash_t *copies, const char *changed_path,
926 svn_log_changed_path2_t *log_item)
928 apr_pool_t *result_pool = apr_hash_pool_get(copies);
929 struct copy_info *copy = apr_palloc(result_pool, sizeof(*copy));
930 apr_array_header_t *copies_with_same_source_path;
932 copy->copyfrom_path = log_item->copyfrom_path;
933 if (log_item->copyfrom_path[0] == '/')
934 copy->copyfrom_path++;
935 copy->copyto_path = changed_path;
936 copy->copyfrom_rev = log_item->copyfrom_rev;
937 copy->node_kind = log_item->node_kind;
939 copies_with_same_source_path = apr_hash_get(copies, copy->copyfrom_path,
940 APR_HASH_KEY_STRING);
941 if (copies_with_same_source_path == NULL)
943 copies_with_same_source_path = apr_array_make(result_pool, 1,
944 sizeof(struct copy_info *));
945 apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING,
946 copies_with_same_source_path);
948 APR_ARRAY_PUSH(copies_with_same_source_path, struct copy_info *) = copy;
951 /* Implements svn_log_entry_receiver_t.
953 * Find the revision in which a node, optionally ancestrally related to the
954 * node specified via find_deleted_rev_baton, was deleted, When the revision
955 * was found, store it in BATON->DELETED_REV and abort the log operation
956 * by raising SVN_ERR_CEASE_INVOCATION.
958 * If no such revision can be found, leave BATON->DELETED_REV and
959 * BATON->REPLACING_NODE_KIND alone.
961 * If the node was replaced, set BATON->REPLACING_NODE_KIND to the node
962 * kind of the node which replaced the original node. If the node was not
963 * replaced, set BATON->REPLACING_NODE_KIND to svn_node_none.
965 * This function answers the same question as svn_ra_get_deleted_rev() but
966 * works in cases where we do not already know a revision in which the deleted
967 * node once used to exist.
969 * If the node was moved, rather than deleted, return move information
973 find_deleted_rev(void *baton,
974 svn_log_entry_t *log_entry,
975 apr_pool_t *scratch_pool)
977 struct find_deleted_rev_baton *b = baton;
978 apr_hash_index_t *hi;
979 apr_pool_t *iterpool;
980 svn_boolean_t deleted_node_found = FALSE;
981 svn_node_kind_t replacing_node_kind = svn_node_none;
983 if (b->ctx->notify_func2)
985 svn_wc_notify_t *notify;
987 notify = svn_wc_create_notify(
989 svn_wc_notify_tree_conflict_details_progress,
991 notify->revision = log_entry->revision;
992 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
995 /* No paths were changed in this revision. Nothing to do. */
996 if (! log_entry->changed_paths2)
999 iterpool = svn_pool_create(scratch_pool);
1000 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
1002 hi = apr_hash_next(hi))
1004 const char *changed_path = apr_hash_this_key(hi);
1005 svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
1007 svn_pool_clear(iterpool);
1009 /* ### Remove leading slash from paths in log entries. */
1010 if (changed_path[0] == '/')
1013 /* Check if we already found the deleted node we're looking for. */
1014 if (!deleted_node_found &&
1015 svn_path_compare_paths(b->deleted_repos_relpath, changed_path) == 0 &&
1016 (log_item->action == 'D' || log_item->action == 'R'))
1018 deleted_node_found = TRUE;
1020 if (b->related_repos_relpath != NULL &&
1021 b->related_peg_rev != SVN_INVALID_REVNUM)
1023 svn_client__pathrev_t *yca_loc;
1026 /* We found a deleted node which occupies the correct path.
1027 * To be certain that this is the deleted node we're looking for,
1028 * we must establish whether it is ancestrally related to the
1029 * "related node" specified in our baton. */
1030 err = find_yca(&yca_loc,
1031 b->related_repos_relpath,
1033 b->deleted_repos_relpath,
1034 rev_below(log_entry->revision),
1035 b->repos_root_url, b->repos_uuid,
1036 b->extra_ra_session, b->ctx, iterpool, iterpool);
1039 /* ### Happens for moves within other moves and copies. */
1040 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
1042 svn_error_clear(err);
1046 return svn_error_trace(err);
1049 deleted_node_found = (yca_loc != NULL);
1052 if (deleted_node_found && log_item->action == 'R')
1053 replacing_node_kind = log_item->node_kind;
1056 svn_pool_destroy(iterpool);
1058 if (!deleted_node_found)
1060 apr_array_header_t *moves;
1062 moves = apr_hash_get(b->moves_table, &log_entry->revision,
1063 sizeof(svn_revnum_t));
1066 struct repos_move_info *move;
1068 move = map_deleted_path_to_move(b->deleted_repos_relpath,
1069 moves, scratch_pool);
1072 const char *relpath;
1074 /* The node was moved. Update our search path accordingly. */
1076 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
1077 b->deleted_repos_relpath);
1079 b->deleted_repos_relpath =
1080 svn_relpath_join(move->moved_from_repos_relpath, relpath,
1087 svn_string_t *author;
1089 b->deleted_rev = log_entry->revision;
1090 author = svn_hash_gets(log_entry->revprops,
1091 SVN_PROP_REVISION_AUTHOR);
1093 b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
1095 b->deleted_rev_author = _("unknown author");
1097 b->replacing_node_kind = replacing_node_kind;
1099 /* We're done. Abort the log operation. */
1100 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
1103 return SVN_NO_ERROR;
1106 /* Return a localised string representation of the local part of a tree
1107 conflict on a file. */
1108 static svn_error_t *
1109 describe_local_file_node_change(const char **description,
1110 svn_client_conflict_t *conflict,
1111 svn_client_ctx_t *ctx,
1112 apr_pool_t *result_pool,
1113 apr_pool_t *scratch_pool)
1115 svn_wc_conflict_reason_t local_change;
1116 svn_wc_operation_t operation;
1118 local_change = svn_client_conflict_get_local_change(conflict);
1119 operation = svn_client_conflict_get_operation(conflict);
1121 switch (local_change)
1123 case svn_wc_conflict_reason_edited:
1124 if (operation == svn_wc_operation_update ||
1125 operation == svn_wc_operation_switch)
1126 *description = _("A file containing uncommitted changes was "
1127 "found in the working copy.");
1128 else if (operation == svn_wc_operation_merge)
1129 *description = _("A file which differs from the corresponding "
1130 "file on the merge source branch was found "
1131 "in the working copy.");
1133 case svn_wc_conflict_reason_obstructed:
1134 *description = _("A file which already occupies this path was found "
1135 "in the working copy.");
1137 case svn_wc_conflict_reason_unversioned:
1138 *description = _("An unversioned file was found in the working "
1141 case svn_wc_conflict_reason_deleted:
1142 *description = _("A deleted file was found in the working copy.");
1144 case svn_wc_conflict_reason_missing:
1145 if (operation == svn_wc_operation_update ||
1146 operation == svn_wc_operation_switch)
1147 *description = _("No such file was found in the working copy.");
1148 else if (operation == svn_wc_operation_merge)
1150 /* ### display deleted revision */
1151 *description = _("No such file was found in the merge target "
1152 "working copy.\nPerhaps the file has been "
1153 "deleted or moved away in the repository's "
1157 case svn_wc_conflict_reason_added:
1158 case svn_wc_conflict_reason_replaced:
1160 /* ### show more details about copies or replacements? */
1161 *description = _("A file scheduled to be added to the "
1162 "repository in the next commit was found in "
1163 "the working copy.");
1166 case svn_wc_conflict_reason_moved_away:
1168 const char *moved_to_abspath;
1171 err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
1173 conflict->local_abspath,
1178 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1180 moved_to_abspath = NULL;
1181 svn_error_clear(err);
1184 return svn_error_trace(err);
1186 if (operation == svn_wc_operation_update ||
1187 operation == svn_wc_operation_switch)
1189 if (moved_to_abspath == NULL)
1191 /* The move no longer exists. */
1192 *description = _("The file in the working copy had "
1193 "been moved away at the time this "
1194 "conflict was recorded.");
1198 const char *wcroot_abspath;
1200 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1202 conflict->local_abspath,
1205 *description = apr_psprintf(
1207 _("The file in the working copy was "
1208 "moved away to\n'%s'."),
1209 svn_dirent_local_style(
1210 svn_dirent_skip_ancestor(
1216 else if (operation == svn_wc_operation_merge)
1218 if (moved_to_abspath == NULL)
1220 /* The move probably happened in branch history.
1221 * This case cannot happen until we detect incoming
1222 * moves, which we currently don't do. */
1223 /* ### find deleted/moved revision? */
1224 *description = _("The file in the working copy had "
1225 "been moved away at the time this "
1226 "conflict was recorded.");
1230 /* This is a local move in the working copy. */
1231 const char *wcroot_abspath;
1233 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1235 conflict->local_abspath,
1238 *description = apr_psprintf(
1240 _("The file in the working copy was "
1241 "moved away to\n'%s'."),
1242 svn_dirent_local_style(
1243 svn_dirent_skip_ancestor(
1251 case svn_wc_conflict_reason_moved_here:
1253 const char *moved_from_abspath;
1255 SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1257 conflict->local_abspath,
1260 if (operation == svn_wc_operation_update ||
1261 operation == svn_wc_operation_switch)
1263 if (moved_from_abspath == NULL)
1265 /* The move no longer exists. */
1266 *description = _("A file had been moved here in the "
1267 "working copy at the time this "
1268 "conflict was recorded.");
1272 const char *wcroot_abspath;
1274 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1276 conflict->local_abspath,
1279 *description = apr_psprintf(
1281 _("A file was moved here in the "
1282 "working copy from\n'%s'."),
1283 svn_dirent_local_style(
1284 svn_dirent_skip_ancestor(
1286 moved_from_abspath),
1290 else if (operation == svn_wc_operation_merge)
1292 if (moved_from_abspath == NULL)
1294 /* The move probably happened in branch history.
1295 * This case cannot happen until we detect incoming
1296 * moves, which we currently don't do. */
1297 /* ### find deleted/moved revision? */
1298 *description = _("A file had been moved here in the "
1299 "working copy at the time this "
1300 "conflict was recorded.");
1304 const char *wcroot_abspath;
1306 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1308 conflict->local_abspath,
1311 /* This is a local move in the working copy. */
1312 *description = apr_psprintf(
1314 _("A file was moved here in the "
1315 "working copy from\n'%s'."),
1316 svn_dirent_local_style(
1317 svn_dirent_skip_ancestor(
1319 moved_from_abspath),
1327 return SVN_NO_ERROR;
1330 /* Return a localised string representation of the local part of a tree
1331 conflict on a directory. */
1332 static svn_error_t *
1333 describe_local_dir_node_change(const char **description,
1334 svn_client_conflict_t *conflict,
1335 svn_client_ctx_t *ctx,
1336 apr_pool_t *result_pool,
1337 apr_pool_t *scratch_pool)
1339 svn_wc_conflict_reason_t local_change;
1340 svn_wc_operation_t operation;
1342 local_change = svn_client_conflict_get_local_change(conflict);
1343 operation = svn_client_conflict_get_operation(conflict);
1345 switch (local_change)
1347 case svn_wc_conflict_reason_edited:
1348 if (operation == svn_wc_operation_update ||
1349 operation == svn_wc_operation_switch)
1350 *description = _("A directory containing uncommitted changes "
1351 "was found in the working copy.");
1352 else if (operation == svn_wc_operation_merge)
1353 *description = _("A directory which differs from the "
1354 "corresponding directory on the merge source "
1355 "branch was found in the working copy.");
1357 case svn_wc_conflict_reason_obstructed:
1358 *description = _("A directory which already occupies this path was "
1359 "found in the working copy.");
1361 case svn_wc_conflict_reason_unversioned:
1362 *description = _("An unversioned directory was found in the "
1365 case svn_wc_conflict_reason_deleted:
1366 *description = _("A deleted directory was found in the "
1369 case svn_wc_conflict_reason_missing:
1370 if (operation == svn_wc_operation_update ||
1371 operation == svn_wc_operation_switch)
1372 *description = _("No such directory was found in the working copy.");
1373 else if (operation == svn_wc_operation_merge)
1375 /* ### display deleted revision */
1376 *description = _("No such directory was found in the merge "
1377 "target working copy.\nPerhaps the "
1378 "directory has been deleted or moved away "
1379 "in the repository's history?");
1382 case svn_wc_conflict_reason_added:
1383 case svn_wc_conflict_reason_replaced:
1385 /* ### show more details about copies or replacements? */
1386 *description = _("A directory scheduled to be added to the "
1387 "repository in the next commit was found in "
1388 "the working copy.");
1391 case svn_wc_conflict_reason_moved_away:
1393 const char *moved_to_abspath;
1396 err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
1398 conflict->local_abspath,
1403 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1405 moved_to_abspath = NULL;
1406 svn_error_clear(err);
1409 return svn_error_trace(err);
1412 if (operation == svn_wc_operation_update ||
1413 operation == svn_wc_operation_switch)
1415 if (moved_to_abspath == NULL)
1417 /* The move no longer exists. */
1418 *description = _("The directory in the working copy "
1419 "had been moved away at the time "
1420 "this conflict was recorded.");
1424 const char *wcroot_abspath;
1426 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1428 conflict->local_abspath,
1431 *description = apr_psprintf(
1433 _("The directory in the working copy "
1434 "was moved away to\n'%s'."),
1435 svn_dirent_local_style(
1436 svn_dirent_skip_ancestor(
1442 else if (operation == svn_wc_operation_merge)
1444 if (moved_to_abspath == NULL)
1446 /* The move probably happened in branch history.
1447 * This case cannot happen until we detect incoming
1448 * moves, which we currently don't do. */
1449 /* ### find deleted/moved revision? */
1450 *description = _("The directory had been moved away "
1451 "at the time this conflict was "
1456 /* This is a local move in the working copy. */
1457 const char *wcroot_abspath;
1459 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1461 conflict->local_abspath,
1464 *description = apr_psprintf(
1466 _("The directory was moved away to\n"
1468 svn_dirent_local_style(
1469 svn_dirent_skip_ancestor(
1477 case svn_wc_conflict_reason_moved_here:
1479 const char *moved_from_abspath;
1481 SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
1483 conflict->local_abspath,
1486 if (operation == svn_wc_operation_update ||
1487 operation == svn_wc_operation_switch)
1489 if (moved_from_abspath == NULL)
1491 /* The move no longer exists. */
1492 *description = _("A directory had been moved here at "
1493 "the time this conflict was "
1498 const char *wcroot_abspath;
1500 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1502 conflict->local_abspath,
1505 *description = apr_psprintf(
1507 _("A directory was moved here from\n"
1509 svn_dirent_local_style(
1510 svn_dirent_skip_ancestor(
1512 moved_from_abspath),
1516 else if (operation == svn_wc_operation_merge)
1518 if (moved_from_abspath == NULL)
1520 /* The move probably happened in branch history.
1521 * This case cannot happen until we detect incoming
1522 * moves, which we currently don't do. */
1523 /* ### find deleted/moved revision? */
1524 *description = _("A directory had been moved here at "
1525 "the time this conflict was "
1530 /* This is a local move in the working copy. */
1531 const char *wcroot_abspath;
1533 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1535 conflict->local_abspath,
1538 *description = apr_psprintf(
1540 _("A directory was moved here in "
1541 "the working copy from\n'%s'."),
1542 svn_dirent_local_style(
1543 svn_dirent_skip_ancestor(
1545 moved_from_abspath),
1552 return SVN_NO_ERROR;
1555 struct find_moves_baton
1557 /* Variables below are arguments provided by the caller of
1558 * svn_ra_get_log2(). */
1559 const char *repos_root_url;
1560 const char *repos_uuid;
1561 svn_client_ctx_t *ctx;
1562 const char *victim_abspath; /* for notifications */
1563 apr_pool_t *result_pool;
1565 /* A hash table mapping a revision number to an array of struct
1566 * repos_move_info * elements, describing moves.
1568 * Must be allocated in RESULT_POOL by the caller of svn_ra_get_log2().
1570 * If the node was moved, the DELETED_REV is present in this table,
1571 * perhaps along with additional revisions.
1573 * Given a sequence of moves which happened in the repository, such as:
1578 * we map each revision number to all the moves which happened in the
1579 * revision, which looks as follows:
1580 * rA : [(x->z), (a->b)]
1583 * This allows us to later find relevant moves based on a revision number.
1585 * Additionally, we embed the number of the revision in which a move was
1586 * found inside the repos_move_info structure:
1587 * rA : [(rA, x->z), (rA, a->b)]
1590 * And also, all moves pertaining to the same node are chained into a
1591 * doubly-linked list via 'next' and 'prev' pointers (see definition of
1592 * struct repos_move_info). This can be visualized as follows:
1593 * rA : [(rA, x->z, prev=>NULL, next=>NULL),
1594 * (rA, a->b, prev=>NULL, next=>(rB, b->c))]
1595 * rB : [(rB, b->c), prev=>(rA, a->b), next=>(rC, c->d)]
1596 * rC : [(rC, c->d), prev=>(rB, c->d), next=>NULL]
1597 * This way, we can look up all moves relevant to a node, forwards and
1598 * backwards in history, once we have located one move in the chain.
1600 * In the above example, the data tells us that within the revision
1601 * range rA:C, a was moved to d. However, within the revision range
1602 * rA;B, a was moved to b.
1604 apr_hash_t *moves_table;
1606 /* Variables below hold state for find_moves() and are not
1607 * intended to be used by the caller of svn_ra_get_log2().
1608 * Like all other variables, they must be initialized, however. */
1610 /* Temporary map of moved paths to struct repos_move_info.
1611 * Used to link multiple moves of the same node across revisions. */
1612 apr_hash_t *moved_paths;
1614 /* Extra RA session that can be used to make additional requests. */
1615 svn_ra_session_t *extra_ra_session;
1618 /* Implements svn_log_entry_receiver_t. */
1619 static svn_error_t *
1620 find_moves(void *baton, svn_log_entry_t *log_entry, apr_pool_t *scratch_pool)
1622 struct find_moves_baton *b = baton;
1623 apr_hash_index_t *hi;
1624 apr_pool_t *iterpool;
1625 apr_array_header_t *deleted_paths;
1627 apr_array_header_t *moves;
1629 if (b->ctx->notify_func2)
1631 svn_wc_notify_t *notify;
1633 notify = svn_wc_create_notify(
1635 svn_wc_notify_tree_conflict_details_progress,
1637 notify->revision = log_entry->revision;
1638 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
1641 /* No paths were changed in this revision. Nothing to do. */
1642 if (! log_entry->changed_paths2)
1643 return SVN_NO_ERROR;
1645 copies = apr_hash_make(scratch_pool);
1646 deleted_paths = apr_array_make(scratch_pool, 0, sizeof(const char *));
1647 iterpool = svn_pool_create(scratch_pool);
1648 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
1650 hi = apr_hash_next(hi))
1652 const char *changed_path = apr_hash_this_key(hi);
1653 svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
1655 svn_pool_clear(iterpool);
1657 /* ### Remove leading slash from paths in log entries. */
1658 if (changed_path[0] == '/')
1661 /* For move detection, scan for copied nodes in this revision. */
1662 if (log_item->action == 'A' && log_item->copyfrom_path)
1663 cache_copied_item(copies, changed_path, log_item);
1665 /* For move detection, store all deleted_paths. */
1666 if (log_item->action == 'D' || log_item->action == 'R')
1667 APR_ARRAY_PUSH(deleted_paths, const char *) =
1668 apr_pstrdup(scratch_pool, changed_path);
1670 svn_pool_destroy(iterpool);
1672 /* Check for moves in this revision */
1673 SVN_ERR(find_moves_in_revision(b->extra_ra_session,
1674 b->moves_table, b->moved_paths,
1675 log_entry, copies, deleted_paths,
1676 b->repos_root_url, b->repos_uuid,
1677 b->ctx, b->result_pool, scratch_pool));
1679 moves = apr_hash_get(b->moves_table, &log_entry->revision,
1680 sizeof(svn_revnum_t));
1683 const svn_string_t *author;
1685 author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
1686 SVN_ERR(find_nested_moves(moves, copies, deleted_paths,
1687 b->moved_paths, log_entry->revision,
1688 author ? author->data : _("unknown author"),
1691 b->extra_ra_session, b->ctx,
1692 b->result_pool, scratch_pool));
1695 return SVN_NO_ERROR;
1698 /* Find all moves which occured in repository history starting at
1699 * REPOS_RELPATH@START_REV until END_REV (where START_REV > END_REV).
1700 * Return results in *MOVES_TABLE (see struct find_moves_baton for details). */
1701 static svn_error_t *
1702 find_moves_in_revision_range(struct apr_hash_t **moves_table,
1703 const char *repos_relpath,
1704 const char *repos_root_url,
1705 const char *repos_uuid,
1706 const char *victim_abspath,
1707 svn_revnum_t start_rev,
1708 svn_revnum_t end_rev,
1709 svn_client_ctx_t *ctx,
1710 apr_pool_t *result_pool,
1711 apr_pool_t *scratch_pool)
1713 svn_ra_session_t *ra_session;
1715 const char *corrected_url;
1716 apr_array_header_t *paths;
1717 apr_array_header_t *revprops;
1718 struct find_moves_baton b = { 0 };
1720 SVN_ERR_ASSERT(start_rev > end_rev);
1722 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
1724 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
1725 url, NULL, NULL, FALSE, FALSE,
1729 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
1730 APR_ARRAY_PUSH(paths, const char *) = "";
1732 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
1733 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
1735 b.repos_root_url = repos_root_url;
1736 b.repos_uuid = repos_uuid;
1738 b.victim_abspath = victim_abspath;
1739 b.moves_table = apr_hash_make(result_pool);
1740 b.moved_paths = apr_hash_make(scratch_pool);
1741 b.result_pool = result_pool;
1742 SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL,
1743 scratch_pool, scratch_pool));
1745 SVN_ERR(svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
1747 TRUE, /* need the changed paths list */
1748 FALSE, /* need to traverse copies */
1749 FALSE, /* no need for merged revisions */
1754 *moves_table = b.moves_table;
1756 return SVN_NO_ERROR;
1759 /* Return new move information for a moved-along child MOVED_ALONG_RELPATH.
1760 * Set MOVE->NODE_KIND to MOVED_ALONG_NODE_KIND.
1761 * Do not copy MOVE->NEXT and MOVE-PREV.
1762 * If MOVED_ALONG_RELPATH is empty, this effectively copies MOVE to
1763 * RESULT_POOL with NEXT and PREV pointers cleared. */
1764 static struct repos_move_info *
1765 new_path_adjusted_move(struct repos_move_info *move,
1766 const char *moved_along_relpath,
1767 svn_node_kind_t moved_along_node_kind,
1768 apr_pool_t *result_pool)
1770 struct repos_move_info *new_move;
1772 new_move = apr_pcalloc(result_pool, sizeof(*new_move));
1773 new_move->moved_from_repos_relpath =
1774 svn_relpath_join(move->moved_from_repos_relpath, moved_along_relpath,
1776 new_move->moved_to_repos_relpath =
1777 svn_relpath_join(move->moved_to_repos_relpath, moved_along_relpath,
1779 new_move->rev = move->rev;
1780 new_move->rev_author = apr_pstrdup(result_pool, move->rev_author);
1781 new_move->copyfrom_rev = move->copyfrom_rev;
1782 new_move->node_kind = moved_along_node_kind;
1783 /* Ignore prev and next pointers. Caller will set them if needed. */
1788 /* Given a list of MOVES_IN_REVISION, figure out which of these moves again
1789 * move the node which was already moved by PREV_MOVE in the past . */
1790 static svn_error_t *
1791 find_next_moves_in_revision(apr_array_header_t **next_moves,
1792 apr_array_header_t *moves_in_revision,
1793 struct repos_move_info *prev_move,
1794 svn_ra_session_t *ra_session,
1795 const char *repos_root_url,
1796 apr_pool_t *result_pool,
1797 apr_pool_t *scratch_pool)
1800 apr_pool_t *iterpool;
1802 iterpool = svn_pool_create(scratch_pool);
1803 for (i = 0; i < moves_in_revision->nelts; i++)
1805 struct repos_move_info *move;
1806 const char *relpath;
1807 const char *deleted_repos_relpath;
1808 svn_boolean_t related;
1811 svn_pool_clear(iterpool);
1813 /* Check if this move affects the current known path of our node. */
1814 move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *);
1815 relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath,
1816 prev_move->moved_to_repos_relpath);
1817 if (relpath == NULL)
1820 /* It does. So our node must have been deleted again. */
1821 deleted_repos_relpath = svn_relpath_join(move->moved_from_repos_relpath,
1824 /* Tracing back history of the delete-half of this move to the
1825 * copyfrom-revision of the prior move we must end up at the
1826 * delete-half of the prior move. */
1827 err = check_move_ancestry(&related, ra_session, repos_root_url,
1828 deleted_repos_relpath, move->rev,
1829 prev_move->moved_from_repos_relpath,
1830 prev_move->copyfrom_rev,
1831 FALSE, scratch_pool);
1832 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1834 svn_error_clear(err);
1842 struct repos_move_info *new_move;
1844 /* We have a winner. */
1845 new_move = new_path_adjusted_move(move, relpath, prev_move->node_kind,
1847 if (*next_moves == NULL)
1848 *next_moves = apr_array_make(result_pool, 1,
1849 sizeof(struct repos_move_info *));
1850 APR_ARRAY_PUSH(*next_moves, struct repos_move_info *) = new_move;
1853 svn_pool_destroy(iterpool);
1855 return SVN_NO_ERROR;
1859 compare_items_as_revs(const svn_sort__item_t *a, const svn_sort__item_t *b)
1861 return svn_sort_compare_revisions(a->key, b->key);
1864 /* Starting at MOVE->REV, loop over future revisions which contain moves,
1865 * and look for matching next moves in each. Once found, return a list of
1866 * (ambiguous, if more than one) moves in *NEXT_MOVES. */
1867 static svn_error_t *
1868 find_next_moves(apr_array_header_t **next_moves,
1869 apr_hash_t *moves_table,
1870 struct repos_move_info *move,
1871 svn_ra_session_t *ra_session,
1872 const char *repos_root_url,
1873 apr_pool_t *result_pool,
1874 apr_pool_t *scratch_pool)
1876 apr_array_header_t *moves;
1877 apr_array_header_t *revisions;
1878 apr_pool_t *iterpool;
1882 revisions = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool);
1883 iterpool = svn_pool_create(scratch_pool);
1884 for (i = 0; i < revisions->nelts; i++)
1886 svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t);
1887 svn_revnum_t rev = *(svn_revnum_t *)item.key;
1889 svn_pool_clear(iterpool);
1891 if (rev <= move->rev)
1894 moves = apr_hash_get(moves_table, &rev, sizeof(rev));
1895 SVN_ERR(find_next_moves_in_revision(next_moves, moves, move,
1896 ra_session, repos_root_url,
1897 result_pool, iterpool));
1901 svn_pool_destroy(iterpool);
1903 return SVN_NO_ERROR;
1906 /* Trace all future moves of the node moved by MOVE.
1907 * Update MOVE->PREV and MOVE->NEXT accordingly. */
1908 static svn_error_t *
1909 trace_moved_node(apr_hash_t *moves_table,
1910 struct repos_move_info *move,
1911 svn_ra_session_t *ra_session,
1912 const char *repos_root_url,
1913 apr_pool_t *result_pool,
1914 apr_pool_t *scratch_pool)
1916 apr_array_header_t *next_moves;
1918 SVN_ERR(find_next_moves(&next_moves, moves_table, move,
1919 ra_session, repos_root_url,
1920 result_pool, scratch_pool));
1924 apr_pool_t *iterpool;
1926 move->next = next_moves;
1927 iterpool = svn_pool_create(scratch_pool);
1928 for (i = 0; i < next_moves->nelts; i++)
1930 struct repos_move_info *next_move;
1932 svn_pool_clear(iterpool);
1933 next_move = APR_ARRAY_IDX(next_moves, i, struct repos_move_info *);
1934 next_move->prev = move;
1935 SVN_ERR(trace_moved_node(moves_table, next_move,
1936 ra_session, repos_root_url,
1937 result_pool, iterpool));
1939 svn_pool_destroy(iterpool);
1942 return SVN_NO_ERROR;
1945 /* Given a list of MOVES_IN_REVISION, figure out which of these moves
1946 * move the node which was later on moved by NEXT_MOVE. */
1947 static svn_error_t *
1948 find_prev_move_in_revision(struct repos_move_info **prev_move,
1949 apr_array_header_t *moves_in_revision,
1950 struct repos_move_info *next_move,
1951 svn_ra_session_t *ra_session,
1952 const char *repos_root_url,
1953 apr_pool_t *result_pool,
1954 apr_pool_t *scratch_pool)
1957 apr_pool_t *iterpool;
1961 iterpool = svn_pool_create(scratch_pool);
1962 for (i = 0; i < moves_in_revision->nelts; i++)
1964 struct repos_move_info *move;
1965 const char *relpath;
1966 const char *deleted_repos_relpath;
1967 svn_boolean_t related;
1970 svn_pool_clear(iterpool);
1972 /* Check if this move affects the current known path of our node. */
1973 move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *);
1974 relpath = svn_relpath_skip_ancestor(next_move->moved_from_repos_relpath,
1975 move->moved_to_repos_relpath);
1976 if (relpath == NULL)
1979 /* It does. So our node must have been deleted. */
1980 deleted_repos_relpath = svn_relpath_join(
1981 next_move->moved_from_repos_relpath,
1984 /* Tracing back history of the delete-half of the next move to the
1985 * copyfrom-revision of the prior move we must end up at the
1986 * delete-half of the prior move. */
1987 err = check_move_ancestry(&related, ra_session, repos_root_url,
1988 deleted_repos_relpath, next_move->rev,
1989 move->moved_from_repos_relpath,
1991 FALSE, scratch_pool);
1992 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1994 svn_error_clear(err);
2002 /* We have a winner. */
2003 *prev_move = new_path_adjusted_move(move, relpath,
2004 next_move->node_kind,
2009 svn_pool_destroy(iterpool);
2011 return SVN_NO_ERROR;
2015 compare_items_as_revs_reverse(const svn_sort__item_t *a,
2016 const svn_sort__item_t *b)
2018 int c = svn_sort_compare_revisions(a->key, b->key);
2026 /* Starting at MOVE->REV, loop over past revisions which contain moves,
2027 * and look for a matching previous move in each. Once found, return
2028 * it in *PREV_MOVE */
2029 static svn_error_t *
2030 find_prev_move(struct repos_move_info **prev_move,
2031 apr_hash_t *moves_table,
2032 struct repos_move_info *move,
2033 svn_ra_session_t *ra_session,
2034 const char *repos_root_url,
2035 apr_pool_t *result_pool,
2036 apr_pool_t *scratch_pool)
2038 apr_array_header_t *moves;
2039 apr_array_header_t *revisions;
2040 apr_pool_t *iterpool;
2044 revisions = svn_sort__hash(moves_table, compare_items_as_revs_reverse,
2046 iterpool = svn_pool_create(scratch_pool);
2047 for (i = 0; i < revisions->nelts; i++)
2049 svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t);
2050 svn_revnum_t rev = *(svn_revnum_t *)item.key;
2052 svn_pool_clear(iterpool);
2054 if (rev >= move->rev)
2057 moves = apr_hash_get(moves_table, &rev, sizeof(rev));
2058 SVN_ERR(find_prev_move_in_revision(prev_move, moves, move,
2059 ra_session, repos_root_url,
2060 result_pool, iterpool));
2064 svn_pool_destroy(iterpool);
2066 return SVN_NO_ERROR;
2070 /* Trace all past moves of the node moved by MOVE.
2071 * Update MOVE->PREV and MOVE->NEXT accordingly. */
2072 static svn_error_t *
2073 trace_moved_node_backwards(apr_hash_t *moves_table,
2074 struct repos_move_info *move,
2075 svn_ra_session_t *ra_session,
2076 const char *repos_root_url,
2077 apr_pool_t *result_pool,
2078 apr_pool_t *scratch_pool)
2080 struct repos_move_info *prev_move;
2082 SVN_ERR(find_prev_move(&prev_move, moves_table, move,
2083 ra_session, repos_root_url,
2084 result_pool, scratch_pool));
2087 move->prev = prev_move;
2088 prev_move->next = apr_array_make(result_pool, 1,
2089 sizeof(struct repos_move_info *));
2090 APR_ARRAY_PUSH(prev_move->next, struct repos_move_info *) = move;
2092 SVN_ERR(trace_moved_node_backwards(moves_table, prev_move,
2093 ra_session, repos_root_url,
2094 result_pool, scratch_pool));
2097 return SVN_NO_ERROR;
2100 static svn_error_t *
2101 reparent_session_and_fetch_node_kind(svn_node_kind_t *node_kind,
2102 svn_ra_session_t *ra_session,
2104 svn_revnum_t peg_rev,
2105 apr_pool_t *scratch_pool)
2109 err = svn_ra_reparent(ra_session, url, scratch_pool);
2112 if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
2114 svn_error_clear(err);
2115 *node_kind = svn_node_unknown;
2116 return SVN_NO_ERROR;
2119 return svn_error_trace(err);
2122 SVN_ERR(svn_ra_check_path(ra_session, "", peg_rev, node_kind, scratch_pool));
2124 return SVN_NO_ERROR;
2127 /* Scan MOVES_TABLE for moves which affect a particular deleted node, and
2128 * build a set of new move information for this node.
2129 * Return heads of all possible move chains in *MOVES.
2131 * MOVES_TABLE describes moves which happened at arbitrary paths in the
2132 * repository. DELETED_REPOS_RELPATH may have been moved directly or it
2133 * may have been moved along with a parent path. Move information returned
2134 * from this function represents how DELETED_REPOS_RELPATH itself was moved
2135 * from one path to another, effectively "zooming in" on the effective move
2136 * operations which occurred for this particular node. */
2137 static svn_error_t *
2138 find_operative_moves(apr_array_header_t **moves,
2139 apr_hash_t *moves_table,
2140 const char *deleted_repos_relpath,
2141 svn_revnum_t deleted_rev,
2142 svn_ra_session_t *ra_session,
2143 const char *repos_root_url,
2144 apr_pool_t *result_pool,
2145 apr_pool_t *scratch_pool)
2147 apr_array_header_t *moves_in_deleted_rev;
2149 apr_pool_t *iterpool;
2150 const char *session_url, *url = NULL;
2152 moves_in_deleted_rev = apr_hash_get(moves_table, &deleted_rev,
2153 sizeof(deleted_rev));
2154 if (moves_in_deleted_rev == NULL)
2157 return SVN_NO_ERROR;
2160 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool));
2162 /* Look for operative moves in the revision where the node was deleted. */
2163 *moves = apr_array_make(scratch_pool, 0, sizeof(struct repos_move_info *));
2164 iterpool = svn_pool_create(scratch_pool);
2165 for (i = 0; i < moves_in_deleted_rev->nelts; i++)
2167 struct repos_move_info *move;
2168 const char *relpath;
2170 svn_pool_clear(iterpool);
2172 move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *);
2173 relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath,
2174 deleted_repos_relpath);
2175 if (relpath && relpath[0] != '\0')
2177 svn_node_kind_t node_kind;
2179 url = svn_path_url_add_component2(repos_root_url,
2180 deleted_repos_relpath,
2182 SVN_ERR(reparent_session_and_fetch_node_kind(&node_kind,
2184 rev_below(deleted_rev),
2186 move = new_path_adjusted_move(move, relpath, node_kind, result_pool);
2188 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
2192 SVN_ERR(svn_ra_reparent(ra_session, session_url, scratch_pool));
2194 /* If we didn't find any applicable moves, return NULL. */
2195 if ((*moves)->nelts == 0)
2198 svn_pool_destroy(iterpool);
2199 return SVN_NO_ERROR;
2202 /* Figure out what happened to these moves in future revisions. */
2203 for (i = 0; i < (*moves)->nelts; i++)
2205 struct repos_move_info *move;
2207 svn_pool_clear(iterpool);
2209 move = APR_ARRAY_IDX(*moves, i, struct repos_move_info *);
2210 SVN_ERR(trace_moved_node(moves_table, move, ra_session, repos_root_url,
2211 result_pool, iterpool));
2214 svn_pool_destroy(iterpool);
2215 return SVN_NO_ERROR;
2218 /* Try to find a revision older than START_REV, and its author, which deleted
2219 * DELETED_BASENAME in the directory PARENT_REPOS_RELPATH. Assume the deleted
2220 * node is ancestrally related to RELATED_REPOS_RELPATH@RELATED_PEG_REV.
2221 * If no such revision can be found, set *DELETED_REV to SVN_INVALID_REVNUM
2222 * and *DELETED_REV_AUTHOR to NULL.
2223 * If the node was replaced rather than deleted, set *REPLACING_NODE_KIND to
2224 * the node kind of the replacing node. Else, set it to svn_node_unknown.
2225 * Only request the log for revisions up to END_REV from the server.
2226 * If the deleted node was moved, provide heads of move chains in *MOVES.
2227 * If the node was not moved,set *MOVES to NULL.
2229 static svn_error_t *
2230 find_revision_for_suspected_deletion(svn_revnum_t *deleted_rev,
2231 const char **deleted_rev_author,
2232 svn_node_kind_t *replacing_node_kind,
2233 struct apr_array_header_t **moves,
2234 svn_client_conflict_t *conflict,
2235 const char *deleted_basename,
2236 const char *parent_repos_relpath,
2237 svn_revnum_t start_rev,
2238 svn_revnum_t end_rev,
2239 const char *related_repos_relpath,
2240 svn_revnum_t related_peg_rev,
2241 svn_client_ctx_t *ctx,
2242 apr_pool_t *result_pool,
2243 apr_pool_t *scratch_pool)
2245 svn_ra_session_t *ra_session;
2247 const char *corrected_url;
2248 apr_array_header_t *paths;
2249 apr_array_header_t *revprops;
2250 const char *repos_root_url;
2251 const char *repos_uuid;
2252 struct find_deleted_rev_baton b = { 0 };
2253 const char *victim_abspath;
2255 apr_hash_t *moves_table;
2257 SVN_ERR_ASSERT(start_rev > end_rev);
2259 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
2260 conflict, scratch_pool,
2262 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
2264 SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath,
2265 repos_root_url, repos_uuid,
2266 victim_abspath, start_rev, end_rev,
2267 ctx, result_pool, scratch_pool));
2269 url = svn_path_url_add_component2(repos_root_url, parent_repos_relpath,
2271 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
2272 url, NULL, NULL, FALSE, FALSE,
2276 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
2277 APR_ARRAY_PUSH(paths, const char *) = "";
2279 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
2280 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
2282 b.victim_abspath = victim_abspath;
2283 b.deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
2284 deleted_basename, scratch_pool);
2285 b.related_repos_relpath = related_repos_relpath;
2286 b.related_peg_rev = related_peg_rev;
2287 b.deleted_rev = SVN_INVALID_REVNUM;
2288 b.replacing_node_kind = svn_node_unknown;
2289 b.repos_root_url = repos_root_url;
2290 b.repos_uuid = repos_uuid;
2292 b.moves_table = moves_table;
2293 b.result_pool = result_pool;
2294 SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL,
2295 scratch_pool, scratch_pool));
2297 err = svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
2299 TRUE, /* need the changed paths list */
2300 FALSE, /* need to traverse copies */
2301 FALSE, /* no need for merged revisions */
2303 find_deleted_rev, &b,
2307 if (err->apr_err == SVN_ERR_CEASE_INVOCATION &&
2308 b.deleted_rev != SVN_INVALID_REVNUM)
2311 /* Log operation was aborted because we found deleted rev. */
2312 svn_error_clear(err);
2315 return svn_error_trace(err);
2318 if (b.deleted_rev == SVN_INVALID_REVNUM)
2320 struct repos_move_info *move = b.move;
2324 *deleted_rev = move->rev;
2325 *deleted_rev_author = move->rev_author;
2326 *replacing_node_kind = b.replacing_node_kind;
2327 SVN_ERR(find_operative_moves(moves, moves_table,
2328 b.deleted_repos_relpath,
2330 ra_session, repos_root_url,
2331 result_pool, scratch_pool));
2335 /* We could not determine the revision in which the node was
2337 *deleted_rev = SVN_INVALID_REVNUM;
2338 *deleted_rev_author = NULL;
2339 *replacing_node_kind = svn_node_unknown;
2342 return SVN_NO_ERROR;
2346 *deleted_rev = b.deleted_rev;
2347 *deleted_rev_author = b.deleted_rev_author;
2348 *replacing_node_kind = b.replacing_node_kind;
2349 SVN_ERR(find_operative_moves(moves, moves_table,
2350 b.deleted_repos_relpath, b.deleted_rev,
2351 ra_session, repos_root_url,
2352 result_pool, scratch_pool));
2355 return SVN_NO_ERROR;
2358 /* Details for tree conflicts involving a locally missing node. */
2359 struct conflict_tree_local_missing_details
2361 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
2362 svn_revnum_t deleted_rev;
2364 /* Author who committed DELETED_REV. */
2365 const char *deleted_rev_author;
2367 /* The path which was deleted relative to the repository root. */
2368 const char *deleted_repos_relpath;
2370 /* Move information about the conflict victim. If not NULL, this is an
2371 * array of repos_move_info elements. Each element is the head of a
2372 * move chain which starts in DELETED_REV. */
2373 apr_array_header_t *moves;
2375 /* Move information about siblings. Siblings are nodes which share
2376 * a youngest common ancestor with the conflict victim. E.g. in case
2377 * of a merge operation they are part of the merge source branch.
2378 * If not NULL, this is an array of repos_move_info elements.
2379 * Each element is the head of a move chain, which starts at some
2380 * point in history after siblings and conflict victim forked off
2381 * their common ancestor. */
2382 apr_array_header_t *sibling_moves;
2384 /* If not NULL, this is the move target abspath. */
2385 const char *moved_to_abspath;
2388 static svn_error_t *
2389 find_related_node(const char **related_repos_relpath,
2390 svn_revnum_t *related_peg_rev,
2391 const char *younger_related_repos_relpath,
2392 svn_revnum_t younger_related_peg_rev,
2393 const char *older_repos_relpath,
2394 svn_revnum_t older_peg_rev,
2395 svn_client_conflict_t *conflict,
2396 svn_client_ctx_t *ctx,
2397 apr_pool_t *result_pool,
2398 apr_pool_t *scratch_pool)
2400 const char *repos_root_url;
2401 const char *related_url;
2402 const char *corrected_url;
2403 svn_node_kind_t related_node_kind;
2404 svn_ra_session_t *ra_session;
2406 *related_repos_relpath = NULL;
2407 *related_peg_rev = SVN_INVALID_REVNUM;
2409 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
2411 scratch_pool, scratch_pool));
2412 related_url = svn_path_url_add_component2(repos_root_url,
2413 younger_related_repos_relpath,
2415 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
2424 SVN_ERR(svn_ra_check_path(ra_session, "", younger_related_peg_rev,
2425 &related_node_kind, scratch_pool));
2426 if (related_node_kind == svn_node_none)
2428 svn_revnum_t related_deleted_rev;
2429 const char *related_deleted_rev_author;
2430 svn_node_kind_t related_replacing_node_kind;
2431 const char *related_basename;
2432 const char *related_parent_repos_relpath;
2433 apr_array_header_t *related_moves;
2435 /* Looks like the younger node, which we'd like to use as our
2436 * 'related node', was deleted. Try to find its deleted revision
2437 * so we can calculate a peg revision at which it exists.
2438 * The younger node is related to the older node, so we can use
2439 * the older node to guide us in our search. */
2440 related_basename = svn_relpath_basename(younger_related_repos_relpath,
2442 related_parent_repos_relpath =
2443 svn_relpath_dirname(younger_related_repos_relpath, scratch_pool);
2444 SVN_ERR(find_revision_for_suspected_deletion(
2445 &related_deleted_rev, &related_deleted_rev_author,
2446 &related_replacing_node_kind, &related_moves,
2447 conflict, related_basename,
2448 related_parent_repos_relpath,
2449 younger_related_peg_rev, 0,
2450 older_repos_relpath, older_peg_rev,
2451 ctx, conflict->pool, scratch_pool));
2453 /* If we can't find a related node, bail. */
2454 if (related_deleted_rev == SVN_INVALID_REVNUM)
2455 return SVN_NO_ERROR;
2457 /* The node should exist in the revision before it was deleted. */
2458 *related_repos_relpath = younger_related_repos_relpath;
2459 *related_peg_rev = rev_below(related_deleted_rev);
2463 *related_repos_relpath = younger_related_repos_relpath;
2464 *related_peg_rev = younger_related_peg_rev;
2467 return SVN_NO_ERROR;
2470 /* Determine if REPOS_RELPATH@PEG_REV was moved at some point in its history.
2471 * History's range of interest ends at END_REV which must be older than PEG_REV.
2473 * VICTIM_ABSPATH is the abspath of a conflict victim in the working copy and
2474 * will be used in notifications.
2476 * Return any applicable move chain heads in *MOVES.
2477 * If no moves can be found, set *MOVES to NULL. */
2478 static svn_error_t *
2479 find_moves_in_natural_history(apr_array_header_t **moves,
2480 const char *repos_relpath,
2481 svn_revnum_t peg_rev,
2482 svn_node_kind_t node_kind,
2483 svn_revnum_t end_rev,
2484 const char *victim_abspath,
2485 const char *repos_root_url,
2486 const char *repos_uuid,
2487 svn_ra_session_t *ra_session,
2488 svn_client_ctx_t *ctx,
2489 apr_pool_t *result_pool,
2490 apr_pool_t *scratch_pool)
2492 apr_hash_t *moves_table;
2493 apr_array_header_t *revs;
2494 apr_array_header_t *most_recent_moves = NULL;
2496 apr_pool_t *iterpool;
2500 SVN_ERR(find_moves_in_revision_range(&moves_table, repos_relpath,
2501 repos_root_url, repos_uuid,
2502 victim_abspath, peg_rev, end_rev,
2503 ctx, scratch_pool, scratch_pool));
2505 iterpool = svn_pool_create(scratch_pool);
2507 /* Scan the moves table for applicable moves. */
2508 revs = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool);
2509 for (i = revs->nelts - 1; i >= 0; i--)
2511 svn_sort__item_t item = APR_ARRAY_IDX(revs, i, svn_sort__item_t);
2512 apr_array_header_t *moves_in_rev = apr_hash_get(moves_table, item.key,
2513 sizeof(svn_revnum_t));
2516 svn_pool_clear(iterpool);
2518 /* Was repos relpath moved to its location in this revision? */
2519 for (j = 0; j < moves_in_rev->nelts; j++)
2521 struct repos_move_info *move;
2522 const char *relpath;
2524 move = APR_ARRAY_IDX(moves_in_rev, j, struct repos_move_info *);
2525 relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
2529 /* If the move did not happen in our peg revision, make
2530 * sure this move happened on the same line of history. */
2531 if (move->rev != peg_rev)
2533 svn_client__pathrev_t *yca_loc;
2536 err = find_yca(&yca_loc, repos_relpath, peg_rev,
2537 repos_relpath, move->rev,
2538 repos_root_url, repos_uuid,
2539 NULL, ctx, iterpool, iterpool);
2542 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
2544 svn_error_clear(err);
2548 return svn_error_trace(err);
2551 if (yca_loc == NULL || yca_loc->rev != move->rev)
2555 if (most_recent_moves == NULL)
2557 apr_array_make(result_pool, 1,
2558 sizeof(struct repos_move_info *));
2560 /* Copy the move to result pool (even if relpath is ""). */
2561 move = new_path_adjusted_move(move, relpath, node_kind,
2563 APR_ARRAY_PUSH(most_recent_moves,
2564 struct repos_move_info *) = move;
2568 /* If we found one move, or several ambiguous moves, we're done. */
2569 if (most_recent_moves)
2573 if (most_recent_moves && most_recent_moves->nelts > 0)
2575 *moves = apr_array_make(result_pool, 1,
2576 sizeof(struct repos_move_info *));
2578 /* Figure out what happened to the most recent moves in prior
2579 * revisions and build move chains. */
2580 for (i = 0; i < most_recent_moves->nelts; i++)
2582 struct repos_move_info *move;
2584 svn_pool_clear(iterpool);
2586 move = APR_ARRAY_IDX(most_recent_moves, i, struct repos_move_info *);
2587 SVN_ERR(trace_moved_node_backwards(moves_table, move,
2588 ra_session, repos_root_url,
2589 result_pool, iterpool));
2590 /* Follow the move chain backwards. */
2594 /* Return move heads. */
2595 APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
2599 svn_pool_destroy(iterpool);
2601 return SVN_NO_ERROR;
2604 /* Implements tree_conflict_get_details_func_t. */
2605 static svn_error_t *
2606 conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
2607 svn_client_ctx_t *ctx,
2608 apr_pool_t *scratch_pool)
2610 const char *old_repos_relpath;
2611 const char *new_repos_relpath;
2612 const char *parent_repos_relpath;
2613 svn_revnum_t parent_peg_rev;
2614 svn_revnum_t old_rev;
2615 svn_revnum_t new_rev;
2616 svn_revnum_t deleted_rev;
2617 const char *deleted_rev_author;
2618 svn_node_kind_t replacing_node_kind;
2619 const char *deleted_basename;
2620 struct conflict_tree_local_missing_details *details;
2621 apr_array_header_t *moves = NULL;
2622 apr_array_header_t *sibling_moves = NULL;
2623 const char *related_repos_relpath;
2624 svn_revnum_t related_peg_rev;
2625 const char *repos_root_url;
2626 const char *repos_uuid;
2628 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
2629 &old_repos_relpath, &old_rev, NULL, conflict,
2630 scratch_pool, scratch_pool));
2631 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
2632 &new_repos_relpath, &new_rev, NULL, conflict,
2633 scratch_pool, scratch_pool));
2635 /* Scan the conflict victim's parent's log to find a revision which
2636 * deleted the node. */
2637 deleted_basename = svn_dirent_basename(conflict->local_abspath,
2639 SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev, &parent_repos_relpath,
2640 &repos_root_url, &repos_uuid,
2643 conflict->local_abspath,
2648 /* Pick the younger incoming node as our 'related node' which helps
2649 * pin-pointing the deleted conflict victim in history. */
2650 related_repos_relpath =
2651 (old_rev < new_rev ? new_repos_relpath : old_repos_relpath);
2652 related_peg_rev = (old_rev < new_rev ? new_rev : old_rev);
2654 /* Make sure we're going to search the related node in a revision where
2655 * it exists. The younger incoming node might have been deleted in HEAD. */
2656 if (related_repos_relpath != NULL && related_peg_rev != SVN_INVALID_REVNUM)
2657 SVN_ERR(find_related_node(
2658 &related_repos_relpath, &related_peg_rev,
2659 related_repos_relpath, related_peg_rev,
2660 (old_rev < new_rev ? old_repos_relpath : new_repos_relpath),
2661 (old_rev < new_rev ? old_rev : new_rev),
2662 conflict, ctx, scratch_pool, scratch_pool));
2664 SVN_ERR(find_revision_for_suspected_deletion(
2665 &deleted_rev, &deleted_rev_author, &replacing_node_kind, &moves,
2666 conflict, deleted_basename, parent_repos_relpath,
2667 parent_peg_rev, 0, related_repos_relpath, related_peg_rev,
2668 ctx, conflict->pool, scratch_pool));
2670 /* If the victim was not deleted then check if the related path was moved. */
2671 if (deleted_rev == SVN_INVALID_REVNUM)
2673 const char *victim_abspath;
2674 svn_ra_session_t *ra_session;
2675 const char *url, *corrected_url;
2676 svn_client__pathrev_t *yca_loc;
2677 svn_revnum_t end_rev;
2678 svn_node_kind_t related_node_kind;
2680 /* ### The following describes all moves in terms of forward-merges,
2681 * should do we something else for reverse-merges? */
2683 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
2684 url = svn_path_url_add_component2(repos_root_url, related_repos_relpath,
2686 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
2695 /* Set END_REV to our best guess of the nearest YCA revision. */
2696 SVN_ERR(find_nearest_yca(&yca_loc, related_repos_relpath, related_peg_rev,
2697 parent_repos_relpath, parent_peg_rev,
2698 repos_root_url, repos_uuid, ra_session, ctx,
2699 scratch_pool, scratch_pool));
2700 if (yca_loc == NULL)
2701 return SVN_NO_ERROR;
2702 end_rev = yca_loc->rev;
2704 /* END_REV must be smaller than RELATED_PEG_REV, else the call
2705 to find_moves_in_natural_history() below will error out. */
2706 if (end_rev >= related_peg_rev)
2707 end_rev = related_peg_rev > 0 ? related_peg_rev - 1 : 0;
2709 SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev,
2710 &related_node_kind, scratch_pool));
2711 SVN_ERR(find_moves_in_natural_history(&sibling_moves,
2712 related_repos_relpath,
2717 repos_root_url, repos_uuid,
2719 conflict->pool, scratch_pool));
2721 if (sibling_moves == NULL)
2722 return SVN_NO_ERROR;
2724 /* ## TODO: Find the missing node in the WC. */
2727 details = apr_pcalloc(conflict->pool, sizeof(*details));
2728 details->deleted_rev = deleted_rev;
2729 details->deleted_rev_author = deleted_rev_author;
2730 if (deleted_rev != SVN_INVALID_REVNUM)
2731 details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
2734 details->moves = moves;
2735 details->sibling_moves = sibling_moves;
2737 conflict->tree_conflict_local_details = details;
2739 return SVN_NO_ERROR;
2742 /* Return a localised string representation of the local part of a tree
2743 conflict on a non-existent node. */
2744 static svn_error_t *
2745 describe_local_none_node_change(const char **description,
2746 svn_client_conflict_t *conflict,
2747 apr_pool_t *result_pool,
2748 apr_pool_t *scratch_pool)
2750 svn_wc_conflict_reason_t local_change;
2751 svn_wc_operation_t operation;
2753 local_change = svn_client_conflict_get_local_change(conflict);
2754 operation = svn_client_conflict_get_operation(conflict);
2756 switch (local_change)
2758 case svn_wc_conflict_reason_edited:
2759 *description = _("An item containing uncommitted changes was "
2760 "found in the working copy.");
2762 case svn_wc_conflict_reason_obstructed:
2763 *description = _("An item which already occupies this path was found in "
2764 "the working copy.");
2766 case svn_wc_conflict_reason_deleted:
2767 *description = _("A deleted item was found in the working copy.");
2769 case svn_wc_conflict_reason_missing:
2770 if (operation == svn_wc_operation_update ||
2771 operation == svn_wc_operation_switch)
2772 *description = _("No such file or directory was found in the "
2774 else if (operation == svn_wc_operation_merge)
2776 /* ### display deleted revision */
2777 *description = _("No such file or directory was found in the "
2778 "merge target working copy.\nThe item may "
2779 "have been deleted or moved away in the "
2780 "repository's history.");
2783 case svn_wc_conflict_reason_unversioned:
2784 *description = _("An unversioned item was found in the working "
2787 case svn_wc_conflict_reason_added:
2788 case svn_wc_conflict_reason_replaced:
2789 *description = _("An item scheduled to be added to the repository "
2790 "in the next commit was found in the working "
2793 case svn_wc_conflict_reason_moved_away:
2794 *description = _("The item in the working copy had been moved "
2795 "away at the time this conflict was recorded.");
2797 case svn_wc_conflict_reason_moved_here:
2798 *description = _("An item had been moved here in the working copy "
2799 "at the time this conflict was recorded.");
2803 return SVN_NO_ERROR;
2806 /* Append a description of a move chain beginning at NEXT to DESCRIPTION. */
2808 append_moved_to_chain_description(const char *description,
2809 apr_array_header_t *next,
2810 apr_pool_t *result_pool,
2811 apr_pool_t *scratch_pool)
2818 struct repos_move_info *move;
2820 /* Describe the first possible move chain only. Adding multiple chains
2821 * to the description would just be confusing. The user may select a
2822 * different move destination while resolving the conflict. */
2823 move = APR_ARRAY_IDX(next, 0, struct repos_move_info *);
2825 description = apr_psprintf(scratch_pool,
2826 _("%s\nAnd then moved away to '^/%s' by "
2828 description, move->moved_to_repos_relpath,
2829 move->rev_author, move->rev);
2833 return apr_pstrdup(result_pool, description);
2836 /* Implements tree_conflict_get_description_func_t. */
2837 static svn_error_t *
2838 conflict_tree_get_local_description_generic(const char **description,
2839 svn_client_conflict_t *conflict,
2840 svn_client_ctx_t *ctx,
2841 apr_pool_t *result_pool,
2842 apr_pool_t *scratch_pool)
2844 svn_node_kind_t victim_node_kind;
2846 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
2848 *description = NULL;
2850 switch (victim_node_kind)
2853 case svn_node_symlink:
2854 SVN_ERR(describe_local_file_node_change(description, conflict, ctx,
2855 result_pool, scratch_pool));
2858 SVN_ERR(describe_local_dir_node_change(description, conflict, ctx,
2859 result_pool, scratch_pool));
2862 case svn_node_unknown:
2863 SVN_ERR(describe_local_none_node_change(description, conflict,
2864 result_pool, scratch_pool));
2868 return SVN_NO_ERROR;
2871 /* Implements tree_conflict_get_description_func_t. */
2872 static svn_error_t *
2873 conflict_tree_get_description_local_missing(const char **description,
2874 svn_client_conflict_t *conflict,
2875 svn_client_ctx_t *ctx,
2876 apr_pool_t *result_pool,
2877 apr_pool_t *scratch_pool)
2879 struct conflict_tree_local_missing_details *details;
2881 details = conflict->tree_conflict_local_details;
2882 if (details == NULL)
2883 return svn_error_trace(conflict_tree_get_local_description_generic(
2884 description, conflict, ctx,
2885 result_pool, scratch_pool));
2887 if (details->moves || details->sibling_moves)
2889 struct repos_move_info *move;
2891 *description = _("No such file or directory was found in the "
2892 "merge target working copy.\n");
2896 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
2897 if (move->node_kind == svn_node_file)
2898 *description = apr_psprintf(
2900 _("%sThe file was moved to '^/%s' in r%ld by %s."),
2901 *description, move->moved_to_repos_relpath,
2902 move->rev, move->rev_author);
2903 else if (move->node_kind == svn_node_dir)
2904 *description = apr_psprintf(
2906 _("%sThe directory was moved to '^/%s' in "
2908 *description, move->moved_to_repos_relpath,
2909 move->rev, move->rev_author);
2911 *description = apr_psprintf(
2913 _("%sThe item was moved to '^/%s' in r%ld by %s."),
2914 *description, move->moved_to_repos_relpath,
2915 move->rev, move->rev_author);
2916 *description = append_moved_to_chain_description(*description,
2922 if (details->sibling_moves)
2924 move = APR_ARRAY_IDX(details->sibling_moves, 0,
2925 struct repos_move_info *);
2926 if (move->node_kind == svn_node_file)
2927 *description = apr_psprintf(
2929 _("%sThe file '^/%s' was moved to '^/%s' "
2931 *description, move->moved_from_repos_relpath,
2932 move->moved_to_repos_relpath,
2933 move->rev, move->rev_author);
2934 else if (move->node_kind == svn_node_dir)
2935 *description = apr_psprintf(
2937 _("%sThe directory '^/%s' was moved to '^/%s' "
2939 *description, move->moved_from_repos_relpath,
2940 move->moved_to_repos_relpath,
2941 move->rev, move->rev_author);
2943 *description = apr_psprintf(
2945 _("%sThe item '^/%s' was moved to '^/%s' "
2947 *description, move->moved_from_repos_relpath,
2948 move->moved_to_repos_relpath,
2949 move->rev, move->rev_author);
2950 *description = append_moved_to_chain_description(*description,
2957 *description = apr_psprintf(
2959 _("No such file or directory was found in the "
2960 "merge target working copy.\n'^/%s' was deleted "
2962 details->deleted_repos_relpath,
2963 details->deleted_rev, details->deleted_rev_author);
2965 return SVN_NO_ERROR;
2968 /* Return a localised string representation of the incoming part of a
2969 conflict; NULL for non-localised odd cases. */
2971 describe_incoming_change(svn_node_kind_t kind, svn_wc_conflict_action_t action,
2972 svn_wc_operation_t operation)
2977 case svn_node_symlink:
2978 if (operation == svn_wc_operation_update)
2982 case svn_wc_conflict_action_edit:
2983 return _("An update operation tried to edit a file.");
2984 case svn_wc_conflict_action_add:
2985 return _("An update operation tried to add a file.");
2986 case svn_wc_conflict_action_delete:
2987 return _("An update operation tried to delete or move "
2989 case svn_wc_conflict_action_replace:
2990 return _("An update operation tried to replace a file.");
2993 else if (operation == svn_wc_operation_switch)
2997 case svn_wc_conflict_action_edit:
2998 return _("A switch operation tried to edit a file.");
2999 case svn_wc_conflict_action_add:
3000 return _("A switch operation tried to add a file.");
3001 case svn_wc_conflict_action_delete:
3002 return _("A switch operation tried to delete or move "
3004 case svn_wc_conflict_action_replace:
3005 return _("A switch operation tried to replace a file.");
3008 else if (operation == svn_wc_operation_merge)
3012 case svn_wc_conflict_action_edit:
3013 return _("A merge operation tried to edit a file.");
3014 case svn_wc_conflict_action_add:
3015 return _("A merge operation tried to add a file.");
3016 case svn_wc_conflict_action_delete:
3017 return _("A merge operation tried to delete or move "
3019 case svn_wc_conflict_action_replace:
3020 return _("A merge operation tried to replace a file.");
3025 if (operation == svn_wc_operation_update)
3029 case svn_wc_conflict_action_edit:
3030 return _("An update operation tried to change a directory.");
3031 case svn_wc_conflict_action_add:
3032 return _("An update operation tried to add a directory.");
3033 case svn_wc_conflict_action_delete:
3034 return _("An update operation tried to delete or move "
3036 case svn_wc_conflict_action_replace:
3037 return _("An update operation tried to replace a directory.");
3040 else if (operation == svn_wc_operation_switch)
3044 case svn_wc_conflict_action_edit:
3045 return _("A switch operation tried to edit a directory.");
3046 case svn_wc_conflict_action_add:
3047 return _("A switch operation tried to add a directory.");
3048 case svn_wc_conflict_action_delete:
3049 return _("A switch operation tried to delete or move "
3051 case svn_wc_conflict_action_replace:
3052 return _("A switch operation tried to replace a directory.");
3055 else if (operation == svn_wc_operation_merge)
3059 case svn_wc_conflict_action_edit:
3060 return _("A merge operation tried to edit a directory.");
3061 case svn_wc_conflict_action_add:
3062 return _("A merge operation tried to add a directory.");
3063 case svn_wc_conflict_action_delete:
3064 return _("A merge operation tried to delete or move "
3066 case svn_wc_conflict_action_replace:
3067 return _("A merge operation tried to replace a directory.");
3072 case svn_node_unknown:
3073 if (operation == svn_wc_operation_update)
3077 case svn_wc_conflict_action_edit:
3078 return _("An update operation tried to edit an item.");
3079 case svn_wc_conflict_action_add:
3080 return _("An update operation tried to add an item.");
3081 case svn_wc_conflict_action_delete:
3082 return _("An update operation tried to delete or move "
3084 case svn_wc_conflict_action_replace:
3085 return _("An update operation tried to replace an item.");
3088 else if (operation == svn_wc_operation_switch)
3092 case svn_wc_conflict_action_edit:
3093 return _("A switch operation tried to edit an item.");
3094 case svn_wc_conflict_action_add:
3095 return _("A switch operation tried to add an item.");
3096 case svn_wc_conflict_action_delete:
3097 return _("A switch operation tried to delete or move "
3099 case svn_wc_conflict_action_replace:
3100 return _("A switch operation tried to replace an item.");
3103 else if (operation == svn_wc_operation_merge)
3107 case svn_wc_conflict_action_edit:
3108 return _("A merge operation tried to edit an item.");
3109 case svn_wc_conflict_action_add:
3110 return _("A merge operation tried to add an item.");
3111 case svn_wc_conflict_action_delete:
3112 return _("A merge operation tried to delete or move "
3114 case svn_wc_conflict_action_replace:
3115 return _("A merge operation tried to replace an item.");
3124 /* Return a localised string representation of the operation part of a
3127 operation_str(svn_wc_operation_t operation)
3131 case svn_wc_operation_update: return _("upon update");
3132 case svn_wc_operation_switch: return _("upon switch");
3133 case svn_wc_operation_merge: return _("upon merge");
3134 case svn_wc_operation_none: return _("upon none");
3136 SVN_ERR_MALFUNCTION_NO_RETURN();
3141 svn_client_conflict_prop_get_description(const char **description,
3142 svn_client_conflict_t *conflict,
3143 apr_pool_t *result_pool,
3144 apr_pool_t *scratch_pool)
3146 const char *reason_str, *action_str;
3148 /* We provide separately translatable strings for the values that we
3149 * know about, and a fall-back in case any other values occur. */
3150 switch (svn_client_conflict_get_local_change(conflict))
3152 case svn_wc_conflict_reason_edited:
3153 reason_str = _("local edit");
3155 case svn_wc_conflict_reason_added:
3156 reason_str = _("local add");
3158 case svn_wc_conflict_reason_deleted:
3159 reason_str = _("local delete");
3161 case svn_wc_conflict_reason_obstructed:
3162 reason_str = _("local obstruction");
3165 reason_str = apr_psprintf(
3166 scratch_pool, _("local %s"),
3168 map_conflict_reason,
3169 svn_client_conflict_get_local_change(conflict)));
3172 switch (svn_client_conflict_get_incoming_change(conflict))
3174 case svn_wc_conflict_action_edit:
3175 action_str = _("incoming edit");
3177 case svn_wc_conflict_action_add:
3178 action_str = _("incoming add");
3180 case svn_wc_conflict_action_delete:
3181 action_str = _("incoming delete");
3184 action_str = apr_psprintf(
3185 scratch_pool, _("incoming %s"),
3187 map_conflict_action,
3188 svn_client_conflict_get_incoming_change(conflict)));
3191 SVN_ERR_ASSERT(reason_str && action_str);
3193 *description = apr_psprintf(result_pool, _("%s, %s %s"),
3194 reason_str, action_str,
3196 svn_client_conflict_get_operation(conflict)));
3198 return SVN_NO_ERROR;
3201 /* Implements tree_conflict_get_description_func_t. */
3202 static svn_error_t *
3203 conflict_tree_get_incoming_description_generic(
3204 const char **incoming_change_description,
3205 svn_client_conflict_t *conflict,
3206 svn_client_ctx_t *ctx,
3207 apr_pool_t *result_pool,
3208 apr_pool_t *scratch_pool)
3211 svn_node_kind_t incoming_kind;
3212 svn_wc_conflict_action_t conflict_action;
3213 svn_wc_operation_t conflict_operation;
3215 conflict_action = svn_client_conflict_get_incoming_change(conflict);
3216 conflict_operation = svn_client_conflict_get_operation(conflict);
3218 /* Determine the node kind of the incoming change. */
3219 incoming_kind = svn_node_unknown;
3220 if (conflict_action == svn_wc_conflict_action_edit ||
3221 conflict_action == svn_wc_conflict_action_delete)
3223 /* Change is acting on 'src_left' version of the node. */
3224 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
3225 NULL, NULL, &incoming_kind, conflict, scratch_pool,
3228 else if (conflict_action == svn_wc_conflict_action_add ||
3229 conflict_action == svn_wc_conflict_action_replace)
3231 /* Change is acting on 'src_right' version of the node.
3233 * ### For 'replace', the node kind is ambiguous. However, src_left
3234 * ### is NULL for replace, so we must use src_right. */
3235 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
3236 NULL, NULL, &incoming_kind, conflict, scratch_pool,
3240 action = describe_incoming_change(incoming_kind, conflict_action,
3241 conflict_operation);
3244 *incoming_change_description = apr_pstrdup(result_pool, action);
3248 /* A catch-all message for very rare or nominally impossible cases.
3249 It will not be pretty, but is closer to an internal error than
3250 an ordinary user-facing string. */
3251 *incoming_change_description = apr_psprintf(result_pool,
3252 _("incoming %s %s"),
3253 svn_node_kind_to_word(incoming_kind),
3254 svn_token__to_word(map_conflict_action,
3257 return SVN_NO_ERROR;
3260 /* Details for tree conflicts involving incoming deletions and replacements. */
3261 struct conflict_tree_incoming_delete_details
3263 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
3264 svn_revnum_t deleted_rev;
3266 /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. The incoming
3267 * delete is the result of a reverse application of this addition. */
3268 svn_revnum_t added_rev;
3270 /* The path which was deleted/added relative to the repository root. */
3271 const char *repos_relpath;
3273 /* Author who committed DELETED_REV/ADDED_REV. */
3274 const char *rev_author;
3276 /* New node kind for a replaced node. This is svn_node_none for deletions. */
3277 svn_node_kind_t replacing_node_kind;
3279 /* Move information. If not NULL, this is an array of repos_move_info *
3280 * elements. Each element is the head of a move chain which starts in
3281 * DELETED_REV or in ADDED_REV (in which case moves should be interpreted
3283 apr_array_header_t *moves;
3285 /* A map of repos_relpaths and working copy nodes for an incoming move.
3287 * Each key is a "const char *" repository relpath corresponding to a
3288 * possible repository-side move destination node in the revision which
3289 * is the target revision in case of update and switch, or the merge-right
3290 * revision in case of a merge.
3292 * Each value is an apr_array_header_t *.
3293 * Each array consists of "const char *" absolute paths to working copy
3294 * nodes which correspond to the repository node selected by the map key.
3295 * Each such working copy node is a potential local move target which can
3296 * be chosen to "follow" the incoming move when resolving a tree conflict.
3298 * This may be an empty hash map in case if there is no move target path
3299 * in the working copy. */
3300 apr_hash_t *wc_move_targets;
3302 /* The preferred move target repository relpath. This is our key into
3303 * the WC_MOVE_TARGETS map above (can be overridden by the user). */
3304 const char *move_target_repos_relpath;
3306 /* The current index into the list of working copy nodes corresponding to
3307 * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */
3308 int wc_move_target_idx;
3311 /* Get the currently selected repository-side move target path.
3312 * If none was selected yet, determine and return a default one. */
3314 get_moved_to_repos_relpath(
3315 struct conflict_tree_incoming_delete_details *details,
3316 apr_pool_t *scratch_pool)
3318 struct repos_move_info *move;
3320 if (details->move_target_repos_relpath)
3321 return details->move_target_repos_relpath;
3323 if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) > 0)
3325 svn_sort__item_t item;
3326 apr_array_header_t *repos_relpaths;
3328 repos_relpaths = svn_sort__hash(details->wc_move_targets,
3329 svn_sort_compare_items_as_paths,
3331 item = APR_ARRAY_IDX(repos_relpaths, 0, svn_sort__item_t);
3332 return (const char *)item.key;
3335 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3336 return move->moved_to_repos_relpath;
3340 describe_incoming_deletion_upon_update(
3341 struct conflict_tree_incoming_delete_details *details,
3342 svn_node_kind_t victim_node_kind,
3343 svn_revnum_t old_rev,
3344 svn_revnum_t new_rev,
3345 apr_pool_t *result_pool,
3346 apr_pool_t *scratch_pool)
3348 if (details->replacing_node_kind == svn_node_file ||
3349 details->replacing_node_kind == svn_node_symlink)
3351 if (victim_node_kind == svn_node_dir)
3353 const char *description =
3354 apr_psprintf(result_pool,
3355 _("Directory updated from r%ld to r%ld was "
3356 "replaced with a file by %s in r%ld."),
3358 details->rev_author, details->deleted_rev);
3361 struct repos_move_info *move;
3363 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3365 apr_psprintf(result_pool,
3366 _("%s\nThe replaced directory was moved to "
3367 "'^/%s'."), description,
3368 get_moved_to_repos_relpath(details, scratch_pool));
3369 return append_moved_to_chain_description(description,
3376 else if (victim_node_kind == svn_node_file ||
3377 victim_node_kind == svn_node_symlink)
3379 const char *description =
3380 apr_psprintf(result_pool,
3381 _("File updated from r%ld to r%ld was replaced "
3382 "with a file from another line of history by "
3385 details->rev_author, details->deleted_rev);
3388 struct repos_move_info *move;
3390 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3392 apr_psprintf(result_pool,
3393 _("%s\nThe replaced file was moved to '^/%s'."),
3395 get_moved_to_repos_relpath(details, scratch_pool));
3396 return append_moved_to_chain_description(description,
3405 const char *description =
3406 apr_psprintf(result_pool,
3407 _("Item updated from r%ld to r%ld was replaced "
3408 "with a file by %s in r%ld."), old_rev, new_rev,
3409 details->rev_author, details->deleted_rev);
3412 struct repos_move_info *move;
3414 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3416 apr_psprintf(result_pool,
3417 _("%s\nThe replaced item was moved to '^/%s'."),
3419 get_moved_to_repos_relpath(details, scratch_pool));
3420 return append_moved_to_chain_description(description,
3428 else if (details->replacing_node_kind == svn_node_dir)
3430 if (victim_node_kind == svn_node_dir)
3432 const char *description =
3433 apr_psprintf(result_pool,
3434 _("Directory updated from r%ld to r%ld was "
3435 "replaced with a directory from another line "
3436 "of history by %s in r%ld."),
3438 details->rev_author, details->deleted_rev);
3441 struct repos_move_info *move;
3443 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3445 apr_psprintf(result_pool,
3446 _("%s\nThe replaced directory was moved to "
3447 "'^/%s'."), description,
3448 get_moved_to_repos_relpath(details, scratch_pool));
3449 return append_moved_to_chain_description(description,
3456 else if (victim_node_kind == svn_node_file ||
3457 victim_node_kind == svn_node_symlink)
3459 const char *description =
3460 apr_psprintf(result_pool,
3461 _("File updated from r%ld to r%ld was "
3462 "replaced with a directory by %s in r%ld."),
3464 details->rev_author, details->deleted_rev);
3467 struct repos_move_info *move;
3469 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3471 apr_psprintf(result_pool,
3472 _("%s\nThe replaced file was moved to '^/%s'."),
3474 get_moved_to_repos_relpath(details, scratch_pool));
3475 return append_moved_to_chain_description(description,
3484 const char *description =
3485 apr_psprintf(result_pool,
3486 _("Item updated from r%ld to r%ld was replaced "
3487 "by %s in r%ld."), old_rev, new_rev,
3488 details->rev_author, details->deleted_rev);
3491 struct repos_move_info *move;
3493 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3495 apr_psprintf(result_pool,
3496 _("%s\nThe replaced item was moved to '^/%s'."),
3498 get_moved_to_repos_relpath(details, scratch_pool));
3499 return append_moved_to_chain_description(description,
3509 if (victim_node_kind == svn_node_dir)
3513 const char *description;
3514 struct repos_move_info *move;
3516 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3518 apr_psprintf(result_pool,
3519 _("Directory updated from r%ld to r%ld was "
3520 "moved to '^/%s' by %s in r%ld."),
3522 get_moved_to_repos_relpath(details, scratch_pool),
3523 details->rev_author, details->deleted_rev);
3524 return append_moved_to_chain_description(description,
3530 return apr_psprintf(result_pool,
3531 _("Directory updated from r%ld to r%ld was "
3532 "deleted by %s in r%ld."),
3534 details->rev_author, details->deleted_rev);
3536 else if (victim_node_kind == svn_node_file ||
3537 victim_node_kind == svn_node_symlink)
3541 struct repos_move_info *move;
3542 const char *description;
3544 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3546 apr_psprintf(result_pool,
3547 _("File updated from r%ld to r%ld was moved "
3548 "to '^/%s' by %s in r%ld."), old_rev, new_rev,
3549 get_moved_to_repos_relpath(details, scratch_pool),
3550 details->rev_author, details->deleted_rev);
3551 return append_moved_to_chain_description(description,
3557 return apr_psprintf(result_pool,
3558 _("File updated from r%ld to r%ld was "
3559 "deleted by %s in r%ld."), old_rev, new_rev,
3560 details->rev_author, details->deleted_rev);
3566 const char *description;
3567 struct repos_move_info *move;
3569 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3571 apr_psprintf(result_pool,
3572 _("Item updated from r%ld to r%ld was moved "
3573 "to '^/%s' by %s in r%ld."), old_rev, new_rev,
3574 get_moved_to_repos_relpath(details, scratch_pool),
3575 details->rev_author, details->deleted_rev);
3576 return append_moved_to_chain_description(description,
3582 return apr_psprintf(result_pool,
3583 _("Item updated from r%ld to r%ld was "
3584 "deleted by %s in r%ld."), old_rev, new_rev,
3585 details->rev_author, details->deleted_rev);
3591 describe_incoming_reverse_addition_upon_update(
3592 struct conflict_tree_incoming_delete_details *details,
3593 svn_node_kind_t victim_node_kind,
3594 svn_revnum_t old_rev,
3595 svn_revnum_t new_rev,
3596 apr_pool_t *result_pool)
3598 if (details->replacing_node_kind == svn_node_file ||
3599 details->replacing_node_kind == svn_node_symlink)
3601 if (victim_node_kind == svn_node_dir)
3602 return apr_psprintf(result_pool,
3603 _("Directory updated backwards from r%ld to r%ld "
3604 "was a file before the replacement made by %s "
3605 "in r%ld."), old_rev, new_rev,
3606 details->rev_author, details->added_rev);
3607 else if (victim_node_kind == svn_node_file ||
3608 victim_node_kind == svn_node_symlink)
3609 return apr_psprintf(result_pool,
3610 _("File updated backwards from r%ld to r%ld was a "
3611 "file from another line of history before the "
3612 "replacement made by %s in r%ld."),
3614 details->rev_author, details->added_rev);
3616 return apr_psprintf(result_pool,
3617 _("Item updated backwards from r%ld to r%ld was "
3618 "replaced with a file by %s in r%ld."),
3620 details->rev_author, details->added_rev);
3622 else if (details->replacing_node_kind == svn_node_dir)
3624 if (victim_node_kind == svn_node_dir)
3625 return apr_psprintf(result_pool,
3626 _("Directory updated backwards from r%ld to r%ld "
3627 "was a directory from another line of history "
3628 "before the replacement made by %s in "
3629 "r%ld."), old_rev, new_rev,
3630 details->rev_author, details->added_rev);
3631 else if (victim_node_kind == svn_node_file ||
3632 victim_node_kind == svn_node_symlink)
3633 return apr_psprintf(result_pool,
3634 _("File updated backwards from r%ld to r%ld was a "
3635 "directory before the replacement made by %s "
3636 "in r%ld."), old_rev, new_rev,
3637 details->rev_author, details->added_rev);
3639 return apr_psprintf(result_pool,
3640 _("Item updated backwards from r%ld to r%ld was "
3641 "replaced with a directory by %s in r%ld."),
3643 details->rev_author, details->added_rev);
3647 if (victim_node_kind == svn_node_dir)
3648 return apr_psprintf(result_pool,
3649 _("Directory updated backwards from r%ld to r%ld "
3650 "did not exist before it was added by %s in "
3651 "r%ld."), old_rev, new_rev,
3652 details->rev_author, details->added_rev);
3653 else if (victim_node_kind == svn_node_file ||
3654 victim_node_kind == svn_node_symlink)
3655 return apr_psprintf(result_pool,
3656 _("File updated backwards from r%ld to r%ld did "
3657 "not exist before it was added by %s in r%ld."),
3659 details->rev_author, details->added_rev);
3661 return apr_psprintf(result_pool,
3662 _("Item updated backwards from r%ld to r%ld did "
3663 "not exist before it was added by %s in r%ld."),
3665 details->rev_author, details->added_rev);
3670 describe_incoming_deletion_upon_switch(
3671 struct conflict_tree_incoming_delete_details *details,
3672 svn_node_kind_t victim_node_kind,
3673 const char *old_repos_relpath,
3674 svn_revnum_t old_rev,
3675 const char *new_repos_relpath,
3676 svn_revnum_t new_rev,
3677 apr_pool_t *result_pool,
3678 apr_pool_t *scratch_pool)
3680 if (details->replacing_node_kind == svn_node_file ||
3681 details->replacing_node_kind == svn_node_symlink)
3683 if (victim_node_kind == svn_node_dir)
3685 const char *description =
3686 apr_psprintf(result_pool,
3687 _("Directory switched from\n"
3688 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3689 "was replaced with a file by %s in r%ld."),
3690 old_repos_relpath, old_rev,
3691 new_repos_relpath, new_rev,
3692 details->rev_author, details->deleted_rev);
3695 struct repos_move_info *move;
3697 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3699 apr_psprintf(result_pool,
3700 _("%s\nThe replaced directory was moved "
3701 "to '^/%s'."), description,
3702 get_moved_to_repos_relpath(details, scratch_pool));
3703 return append_moved_to_chain_description(description,
3710 else if (victim_node_kind == svn_node_file ||
3711 victim_node_kind == svn_node_symlink)
3713 const char *description =
3714 apr_psprintf(result_pool,
3715 _("File switched from\n"
3716 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3717 "replaced with a file from another line of "
3718 "history by %s in r%ld."),
3719 old_repos_relpath, old_rev,
3720 new_repos_relpath, new_rev,
3721 details->rev_author, details->deleted_rev);
3724 struct repos_move_info *move;
3726 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3728 apr_psprintf(result_pool,
3729 _("%s\nThe replaced file was moved to '^/%s'."),
3731 get_moved_to_repos_relpath(details, scratch_pool));
3732 return append_moved_to_chain_description(description,
3741 const char *description =
3742 apr_psprintf(result_pool,
3743 _("Item switched from\n"
3744 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3745 "replaced with a file by %s in r%ld."),
3746 old_repos_relpath, old_rev,
3747 new_repos_relpath, new_rev,
3748 details->rev_author, details->deleted_rev);
3751 struct repos_move_info *move;
3753 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3755 apr_psprintf(result_pool,
3756 _("%s\nThe replaced item was moved to '^/%s'."),
3758 get_moved_to_repos_relpath(details, scratch_pool));
3759 return append_moved_to_chain_description(description,
3767 else if (details->replacing_node_kind == svn_node_dir)
3769 if (victim_node_kind == svn_node_dir)
3771 const char *description =
3772 apr_psprintf(result_pool,
3773 _("Directory switched from\n"
3774 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3775 "was replaced with a directory from another "
3776 "line of history by %s in r%ld."),
3777 old_repos_relpath, old_rev,
3778 new_repos_relpath, new_rev,
3779 details->rev_author, details->deleted_rev);
3782 struct repos_move_info *move;
3784 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3786 apr_psprintf(result_pool,
3787 _("%s\nThe replaced directory was moved to "
3788 "'^/%s'."), description,
3789 get_moved_to_repos_relpath(details, scratch_pool));
3790 return append_moved_to_chain_description(description,
3797 else if (victim_node_kind == svn_node_file ||
3798 victim_node_kind == svn_node_symlink)
3800 const char *description =
3801 apr_psprintf(result_pool,
3802 _("File switched from\n"
3803 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3804 "was replaced with a directory by %s in r%ld."),
3805 old_repos_relpath, old_rev,
3806 new_repos_relpath, new_rev,
3807 details->rev_author, details->deleted_rev);
3810 struct repos_move_info *move;
3812 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3814 apr_psprintf(result_pool,
3815 _("%s\nThe replaced file was moved to '^/%s'."),
3817 get_moved_to_repos_relpath(details, scratch_pool));
3818 return append_moved_to_chain_description(description,
3827 const char *description =
3828 apr_psprintf(result_pool,
3829 _("Item switched from\n"
3830 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3831 "replaced with a directory by %s in r%ld."),
3832 old_repos_relpath, old_rev,
3833 new_repos_relpath, new_rev,
3834 details->rev_author, details->deleted_rev);
3837 struct repos_move_info *move;
3839 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3841 apr_psprintf(result_pool,
3842 _("%s\nThe replaced item was moved to '^/%s'."),
3844 get_moved_to_repos_relpath(details, scratch_pool));
3845 return append_moved_to_chain_description(description,
3855 if (victim_node_kind == svn_node_dir)
3859 struct repos_move_info *move;
3860 const char *description;
3862 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3864 apr_psprintf(result_pool,
3865 _("Directory switched from\n"
3866 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3867 "was moved to '^/%s' by %s in r%ld."),
3868 old_repos_relpath, old_rev,
3869 new_repos_relpath, new_rev,
3870 get_moved_to_repos_relpath(details, scratch_pool),
3871 details->rev_author, details->deleted_rev);
3872 return append_moved_to_chain_description(description,
3878 return apr_psprintf(result_pool,
3879 _("Directory switched from\n"
3880 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3881 "was deleted by %s in r%ld."),
3882 old_repos_relpath, old_rev,
3883 new_repos_relpath, new_rev,
3884 details->rev_author, details->deleted_rev);
3886 else if (victim_node_kind == svn_node_file ||
3887 victim_node_kind == svn_node_symlink)
3891 struct repos_move_info *move;
3892 const char *description;
3894 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3896 apr_psprintf(result_pool,
3897 _("File switched from\n"
3898 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3899 "moved to '^/%s' by %s in r%ld."),
3900 old_repos_relpath, old_rev,
3901 new_repos_relpath, new_rev,
3902 get_moved_to_repos_relpath(details, scratch_pool),
3903 details->rev_author, details->deleted_rev);
3904 return append_moved_to_chain_description(description,
3910 return apr_psprintf(result_pool,
3911 _("File switched from\n"
3912 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3913 "deleted by %s in r%ld."),
3914 old_repos_relpath, old_rev,
3915 new_repos_relpath, new_rev,
3916 details->rev_author, details->deleted_rev);
3922 struct repos_move_info *move;
3923 const char *description;
3925 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3927 apr_psprintf(result_pool,
3928 _("Item switched from\n"
3929 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3930 "moved to '^/%s' by %s in r%ld."),
3931 old_repos_relpath, old_rev,
3932 new_repos_relpath, new_rev,
3933 get_moved_to_repos_relpath(details, scratch_pool),
3934 details->rev_author, details->deleted_rev);
3935 return append_moved_to_chain_description(description,
3941 return apr_psprintf(result_pool,
3942 _("Item switched from\n"
3943 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3944 "deleted by %s in r%ld."),
3945 old_repos_relpath, old_rev,
3946 new_repos_relpath, new_rev,
3947 details->rev_author, details->deleted_rev);
3953 describe_incoming_reverse_addition_upon_switch(
3954 struct conflict_tree_incoming_delete_details *details,
3955 svn_node_kind_t victim_node_kind,
3956 const char *old_repos_relpath,
3957 svn_revnum_t old_rev,
3958 const char *new_repos_relpath,
3959 svn_revnum_t new_rev,
3960 apr_pool_t *result_pool)
3962 if (details->replacing_node_kind == svn_node_file ||
3963 details->replacing_node_kind == svn_node_symlink)
3965 if (victim_node_kind == svn_node_dir)
3966 return apr_psprintf(result_pool,
3967 _("Directory switched from\n"
3968 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3969 "was a file before the replacement made by %s "
3971 old_repos_relpath, old_rev,
3972 new_repos_relpath, new_rev,
3973 details->rev_author, details->added_rev);
3974 else if (victim_node_kind == svn_node_file ||
3975 victim_node_kind == svn_node_symlink)
3976 return apr_psprintf(result_pool,
3977 _("File switched from\n"
3978 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas a "
3979 "file from another line of history before the "
3980 "replacement made by %s in r%ld."),
3981 old_repos_relpath, old_rev,
3982 new_repos_relpath, new_rev,
3983 details->rev_author, details->added_rev);
3985 return apr_psprintf(result_pool,
3986 _("Item switched from\n"
3987 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3988 "replaced with a file by %s in r%ld."),
3989 old_repos_relpath, old_rev,
3990 new_repos_relpath, new_rev,
3991 details->rev_author, details->added_rev);
3993 else if (details->replacing_node_kind == svn_node_dir)
3995 if (victim_node_kind == svn_node_dir)
3996 return apr_psprintf(result_pool,
3997 _("Directory switched from\n"
3998 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3999 "was a directory from another line of history "
4000 "before the replacement made by %s in r%ld."),
4001 old_repos_relpath, old_rev,
4002 new_repos_relpath, new_rev,
4003 details->rev_author, details->added_rev);
4004 else if (victim_node_kind == svn_node_file ||
4005 victim_node_kind == svn_node_symlink)
4006 return apr_psprintf(result_pool,
4007 _("Directory switched from\n"
4008 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4009 "was a file before the replacement made by %s "
4011 old_repos_relpath, old_rev,
4012 new_repos_relpath, new_rev,
4013 details->rev_author, details->added_rev);
4015 return apr_psprintf(result_pool,
4016 _("Item switched from\n"
4017 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4018 "replaced with a directory by %s in r%ld."),
4019 old_repos_relpath, old_rev,
4020 new_repos_relpath, new_rev,
4021 details->rev_author, details->added_rev);
4025 if (victim_node_kind == svn_node_dir)
4026 return apr_psprintf(result_pool,
4027 _("Directory switched from\n"
4028 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4029 "did not exist before it was added by %s in "
4031 old_repos_relpath, old_rev,
4032 new_repos_relpath, new_rev,
4033 details->rev_author, details->added_rev);
4034 else if (victim_node_kind == svn_node_file ||
4035 victim_node_kind == svn_node_symlink)
4036 return apr_psprintf(result_pool,
4037 _("File switched from\n"
4038 "'^/%s@%ld'\nto\n'^/%s@%ld'\ndid "
4039 "not exist before it was added by %s in "
4041 old_repos_relpath, old_rev,
4042 new_repos_relpath, new_rev,
4043 details->rev_author, details->added_rev);
4045 return apr_psprintf(result_pool,
4046 _("Item switched from\n"
4047 "'^/%s@%ld'\nto\n'^/%s@%ld'\ndid "
4048 "not exist before it was added by %s in "
4050 old_repos_relpath, old_rev,
4051 new_repos_relpath, new_rev,
4052 details->rev_author, details->added_rev);
4057 describe_incoming_deletion_upon_merge(
4058 struct conflict_tree_incoming_delete_details *details,
4059 svn_node_kind_t victim_node_kind,
4060 const char *old_repos_relpath,
4061 svn_revnum_t old_rev,
4062 const char *new_repos_relpath,
4063 svn_revnum_t new_rev,
4064 apr_pool_t *result_pool,
4065 apr_pool_t *scratch_pool)
4067 if (details->replacing_node_kind == svn_node_file ||
4068 details->replacing_node_kind == svn_node_symlink)
4070 if (victim_node_kind == svn_node_dir)
4072 const char *description =
4073 apr_psprintf(result_pool,
4074 _("Directory merged from\n"
4075 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4076 "was replaced with a file by %s in r%ld."),
4077 old_repos_relpath, old_rev,
4078 new_repos_relpath, new_rev,
4079 details->rev_author, details->deleted_rev);
4082 struct repos_move_info *move;
4084 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4086 apr_psprintf(result_pool,
4087 _("%s\nThe replaced directory was moved to "
4088 "'^/%s'."), description,
4089 get_moved_to_repos_relpath(details, scratch_pool));
4090 return append_moved_to_chain_description(description,
4097 else if (victim_node_kind == svn_node_file ||
4098 victim_node_kind == svn_node_symlink)
4100 const char *description =
4101 apr_psprintf(result_pool,
4102 _("File merged from\n"
4103 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4104 "replaced with a file from another line of "
4105 "history by %s in r%ld."),
4106 old_repos_relpath, old_rev,
4107 new_repos_relpath, new_rev,
4108 details->rev_author, details->deleted_rev);
4111 struct repos_move_info *move;
4113 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4115 apr_psprintf(result_pool,
4116 _("%s\nThe replaced file was moved to '^/%s'."),
4118 get_moved_to_repos_relpath(details, scratch_pool));
4119 return append_moved_to_chain_description(description,
4127 return apr_psprintf(result_pool,
4128 _("Item merged from\n"
4129 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4130 "replaced with a file by %s in r%ld."),
4131 old_repos_relpath, old_rev,
4132 new_repos_relpath, new_rev,
4133 details->rev_author, details->deleted_rev);
4135 else if (details->replacing_node_kind == svn_node_dir)
4137 if (victim_node_kind == svn_node_dir)
4139 const char *description =
4140 apr_psprintf(result_pool,
4141 _("Directory merged from\n"
4142 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4143 "was replaced with a directory from another "
4144 "line of history by %s in r%ld."),
4145 old_repos_relpath, old_rev,
4146 new_repos_relpath, new_rev,
4147 details->rev_author, details->deleted_rev);
4150 struct repos_move_info *move;
4152 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4154 apr_psprintf(result_pool,
4155 _("%s\nThe replaced directory was moved to "
4156 "'^/%s'."), description,
4157 get_moved_to_repos_relpath(details, scratch_pool));
4158 return append_moved_to_chain_description(description,
4165 else if (victim_node_kind == svn_node_file ||
4166 victim_node_kind == svn_node_symlink)
4168 const char *description =
4169 apr_psprintf(result_pool,
4170 _("File merged from\n"
4171 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4172 "was replaced with a directory by %s in r%ld."),
4173 old_repos_relpath, old_rev,
4174 new_repos_relpath, new_rev,
4175 details->rev_author, details->deleted_rev);
4178 struct repos_move_info *move;
4180 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4182 apr_psprintf(result_pool,
4183 _("%s\nThe replaced file was moved to '^/%s'."),
4185 get_moved_to_repos_relpath(details, scratch_pool));
4186 return append_moved_to_chain_description(description,
4195 const char *description =
4196 apr_psprintf(result_pool,
4197 _("Item merged from\n"
4198 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4199 "replaced with a directory by %s in r%ld."),
4200 old_repos_relpath, old_rev,
4201 new_repos_relpath, new_rev,
4202 details->rev_author, details->deleted_rev);
4205 struct repos_move_info *move;
4207 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4209 apr_psprintf(result_pool,
4210 _("%s\nThe replaced item was moved to '^/%s'."),
4212 get_moved_to_repos_relpath(details, scratch_pool));
4213 return append_moved_to_chain_description(description,
4223 if (victim_node_kind == svn_node_dir)
4227 struct repos_move_info *move;
4228 const char *description;
4230 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4232 apr_psprintf(result_pool,
4233 _("Directory merged from\n"
4234 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4235 "moved to '^/%s' by %s in r%ld."),
4236 old_repos_relpath, old_rev,
4237 new_repos_relpath, new_rev,
4238 get_moved_to_repos_relpath(details, scratch_pool),
4239 details->rev_author, details->deleted_rev);
4240 return append_moved_to_chain_description(description,
4246 return apr_psprintf(result_pool,
4247 _("Directory merged from\n"
4248 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4249 "deleted by %s in r%ld."),
4250 old_repos_relpath, old_rev,
4251 new_repos_relpath, new_rev,
4252 details->rev_author, details->deleted_rev);
4254 else if (victim_node_kind == svn_node_file ||
4255 victim_node_kind == svn_node_symlink)
4259 struct repos_move_info *move;
4260 const char *description;
4262 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4264 apr_psprintf(result_pool,
4265 _("File merged from\n"
4266 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4267 "moved to '^/%s' by %s in r%ld."),
4268 old_repos_relpath, old_rev,
4269 new_repos_relpath, new_rev,
4270 get_moved_to_repos_relpath(details, scratch_pool),
4271 details->rev_author, details->deleted_rev);
4272 return append_moved_to_chain_description(description,
4278 return apr_psprintf(result_pool,
4279 _("File merged from\n"
4280 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4281 "deleted by %s in r%ld."),
4282 old_repos_relpath, old_rev,
4283 new_repos_relpath, new_rev,
4284 details->rev_author, details->deleted_rev);
4290 struct repos_move_info *move;
4291 const char *description;
4293 move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4295 apr_psprintf(result_pool,
4296 _("Item merged from\n"
4297 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4298 "moved to '^/%s' by %s in r%ld."),
4299 old_repos_relpath, old_rev,
4300 new_repos_relpath, new_rev,
4301 get_moved_to_repos_relpath(details, scratch_pool),
4302 details->rev_author, details->deleted_rev);
4303 return append_moved_to_chain_description(description,
4309 return apr_psprintf(result_pool,
4310 _("Item merged from\n"
4311 "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4312 "deleted by %s in r%ld."),
4313 old_repos_relpath, old_rev,
4314 new_repos_relpath, new_rev,
4315 details->rev_author, details->deleted_rev);
4321 describe_incoming_reverse_addition_upon_merge(
4322 struct conflict_tree_incoming_delete_details *details,
4323 svn_node_kind_t victim_node_kind,
4324 const char *old_repos_relpath,
4325 svn_revnum_t old_rev,
4326 const char *new_repos_relpath,
4327 svn_revnum_t new_rev,
4328 apr_pool_t *result_pool)
4330 if (details->replacing_node_kind == svn_node_file ||
4331 details->replacing_node_kind == svn_node_symlink)
4333 if (victim_node_kind == svn_node_dir)
4334 return apr_psprintf(result_pool,
4335 _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4336 "^/%s@%ld was a file before the replacement "
4337 "made by %s in r%ld."),
4338 old_repos_relpath, old_rev,
4339 new_repos_relpath, new_rev,
4340 details->rev_author, details->added_rev);
4341 else if (victim_node_kind == svn_node_file ||
4342 victim_node_kind == svn_node_symlink)
4343 return apr_psprintf(result_pool,
4344 _("File reverse-merged from\n"
4345 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4346 "was a file from another line of history before "
4347 "the replacement made by %s in r%ld."),
4348 old_repos_relpath, old_rev,
4349 new_repos_relpath, new_rev,
4350 details->rev_author, details->added_rev);
4352 return apr_psprintf(result_pool,
4353 _("Item reverse-merged from\n"
4354 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4355 "was replaced with a file by %s in r%ld."),
4356 old_repos_relpath, old_rev,
4357 new_repos_relpath, new_rev,
4358 details->rev_author, details->added_rev);
4360 else if (details->replacing_node_kind == svn_node_dir)
4362 if (victim_node_kind == svn_node_dir)
4363 return apr_psprintf(result_pool,
4364 _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4365 "^/%s@%ld was a directory from another line "
4366 "of history 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);
4371 else if (victim_node_kind == svn_node_file ||
4372 victim_node_kind == svn_node_symlink)
4373 return apr_psprintf(result_pool,
4374 _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4375 "^/%s@%ld was a file before the replacement "
4376 "made by %s in r%ld."),
4377 old_repos_relpath, old_rev,
4378 new_repos_relpath, new_rev,
4379 details->rev_author, details->added_rev);
4381 return apr_psprintf(result_pool,
4382 _("Item reverse-merged from\n"
4383 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4384 "was replaced with a directory by %s in r%ld."),
4385 old_repos_relpath, old_rev,
4386 new_repos_relpath, new_rev,
4387 details->rev_author, details->added_rev);
4391 if (victim_node_kind == svn_node_dir)
4392 return apr_psprintf(result_pool,
4393 _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4394 "^/%s@%ld did not exist before it was added "
4396 old_repos_relpath, old_rev,
4397 new_repos_relpath, new_rev,
4398 details->rev_author, details->added_rev);
4399 else if (victim_node_kind == svn_node_file ||
4400 victim_node_kind == svn_node_symlink)
4401 return apr_psprintf(result_pool,
4402 _("File reverse-merged from\n"
4403 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4404 "did not exist before it was added by %s in "
4406 old_repos_relpath, old_rev,
4407 new_repos_relpath, new_rev,
4408 details->rev_author, details->added_rev);
4410 return apr_psprintf(result_pool,
4411 _("Item reverse-merged from\n"
4412 "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4413 "did not exist before it was added by %s in "
4415 old_repos_relpath, old_rev,
4416 new_repos_relpath, new_rev,
4417 details->rev_author, details->added_rev);
4421 /* Implements tree_conflict_get_description_func_t. */
4422 static svn_error_t *
4423 conflict_tree_get_description_incoming_delete(
4424 const char **incoming_change_description,
4425 svn_client_conflict_t *conflict,
4426 svn_client_ctx_t *ctx,
4427 apr_pool_t *result_pool,
4428 apr_pool_t *scratch_pool)
4431 svn_node_kind_t victim_node_kind;
4432 svn_wc_operation_t conflict_operation;
4433 const char *old_repos_relpath;
4434 svn_revnum_t old_rev;
4435 const char *new_repos_relpath;
4436 svn_revnum_t new_rev;
4437 struct conflict_tree_incoming_delete_details *details;
4439 if (conflict->tree_conflict_incoming_details == NULL)
4440 return svn_error_trace(conflict_tree_get_incoming_description_generic(
4441 incoming_change_description,
4442 conflict, ctx, result_pool, scratch_pool));
4444 conflict_operation = svn_client_conflict_get_operation(conflict);
4445 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
4446 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
4447 &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
4449 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
4450 &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
4453 details = conflict->tree_conflict_incoming_details;
4455 if (conflict_operation == svn_wc_operation_update)
4457 if (details->deleted_rev != SVN_INVALID_REVNUM)
4459 action = describe_incoming_deletion_upon_update(details,
4466 else /* details->added_rev != SVN_INVALID_REVNUM */
4468 /* This deletion is really the reverse change of an addition. */
4469 action = describe_incoming_reverse_addition_upon_update(
4470 details, victim_node_kind, old_rev, new_rev, result_pool);
4473 else if (conflict_operation == svn_wc_operation_switch)
4475 if (details->deleted_rev != SVN_INVALID_REVNUM)
4477 action = describe_incoming_deletion_upon_switch(details,
4486 else /* details->added_rev != SVN_INVALID_REVNUM */
4488 /* This deletion is really the reverse change of an addition. */
4489 action = describe_incoming_reverse_addition_upon_switch(
4490 details, victim_node_kind, old_repos_relpath, old_rev,
4491 new_repos_relpath, new_rev, result_pool);
4495 else if (conflict_operation == svn_wc_operation_merge)
4497 if (details->deleted_rev != SVN_INVALID_REVNUM)
4499 action = describe_incoming_deletion_upon_merge(details,
4508 else /* details->added_rev != SVN_INVALID_REVNUM */
4510 /* This deletion is really the reverse change of an addition. */
4511 action = describe_incoming_reverse_addition_upon_merge(
4512 details, victim_node_kind, old_repos_relpath, old_rev,
4513 new_repos_relpath, new_rev, result_pool);
4517 *incoming_change_description = apr_pstrdup(result_pool, action);
4519 return SVN_NO_ERROR;
4522 /* Baton for find_added_rev(). */
4523 struct find_added_rev_baton
4525 const char *victim_abspath;
4526 svn_client_ctx_t *ctx;
4527 svn_revnum_t added_rev;
4528 const char *repos_relpath;
4529 const char *parent_repos_relpath;
4533 /* Implements svn_location_segment_receiver_t.
4534 * Finds the revision in which a node was added by tracing 'start'
4535 * revisions in location segments reported for the node.
4536 * If the PARENT_REPOS_RELPATH in the baton is not NULL, only consider
4537 * segments in which the node existed somwhere beneath this path. */
4538 static svn_error_t *
4539 find_added_rev(svn_location_segment_t *segment,
4541 apr_pool_t *scratch_pool)
4543 struct find_added_rev_baton *b = baton;
4545 if (b->ctx->notify_func2)
4547 svn_wc_notify_t *notify;
4549 notify = svn_wc_create_notify(
4551 svn_wc_notify_tree_conflict_details_progress,
4553 notify->revision = segment->range_start;
4554 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
4557 if (segment->path) /* not interested in gaps */
4559 if (b->parent_repos_relpath == NULL ||
4560 svn_relpath_skip_ancestor(b->parent_repos_relpath,
4561 segment->path) != NULL)
4563 b->added_rev = segment->range_start;
4564 b->repos_relpath = apr_pstrdup(b->pool, segment->path);
4568 return SVN_NO_ERROR;
4571 /* Find conflict details in the case where a revision which added a node was
4572 * applied in reverse, resulting in an incoming deletion. */
4573 static svn_error_t *
4574 get_incoming_delete_details_for_reverse_addition(
4575 struct conflict_tree_incoming_delete_details **details,
4576 const char *repos_root_url,
4577 const char *old_repos_relpath,
4578 svn_revnum_t old_rev,
4579 svn_revnum_t new_rev,
4580 svn_client_ctx_t *ctx,
4581 const char *victim_abspath,
4582 apr_pool_t *result_pool,
4583 apr_pool_t *scratch_pool)
4585 svn_ra_session_t *ra_session;
4587 const char *corrected_url;
4588 svn_string_t *author_revprop;
4589 struct find_added_rev_baton b = { 0 };
4591 url = svn_path_url_add_component2(repos_root_url, old_repos_relpath,
4593 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
4602 *details = apr_pcalloc(result_pool, sizeof(**details));
4604 b.victim_abspath = victim_abspath;
4605 b.added_rev = SVN_INVALID_REVNUM;
4606 b.repos_relpath = NULL;
4607 b.parent_repos_relpath = NULL;
4608 b.pool = scratch_pool;
4610 /* Figure out when this node was added. */
4611 SVN_ERR(svn_ra_get_location_segments(ra_session, "", old_rev,
4616 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
4617 SVN_PROP_REVISION_AUTHOR,
4618 &author_revprop, scratch_pool));
4619 (*details)->deleted_rev = SVN_INVALID_REVNUM;
4620 (*details)->added_rev = b.added_rev;
4621 (*details)->repos_relpath = apr_pstrdup(result_pool, b.repos_relpath);
4623 (*details)->rev_author = apr_pstrdup(result_pool, author_revprop->data);
4625 (*details)->rev_author = _("unknown author");
4627 /* Check for replacement. */
4628 (*details)->replacing_node_kind = svn_node_none;
4629 if ((*details)->added_rev > 0)
4631 svn_node_kind_t replaced_node_kind;
4633 SVN_ERR(svn_ra_check_path(ra_session, "",
4634 rev_below((*details)->added_rev),
4635 &replaced_node_kind, scratch_pool));
4636 if (replaced_node_kind != svn_node_none)
4637 SVN_ERR(svn_ra_check_path(ra_session, "", (*details)->added_rev,
4638 &(*details)->replacing_node_kind,
4642 return SVN_NO_ERROR;
4645 /* Follow each move chain starting a MOVE all the way to the end to find
4646 * the possible working copy locations for VICTIM_ABSPATH which corresponds
4647 * to VICTIM_REPOS_REPLATH@VICTIM_REVISION.
4648 * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the
4649 * repos_relpath which is the corresponding move destination in the repository.
4650 * This function is recursive. */
4651 static svn_error_t *
4652 follow_move_chains(apr_hash_t *wc_move_targets,
4653 struct repos_move_info *move,
4654 svn_client_ctx_t *ctx,
4655 const char *victim_abspath,
4656 svn_node_kind_t victim_node_kind,
4657 const char *victim_repos_relpath,
4658 svn_revnum_t victim_revision,
4659 apr_pool_t *result_pool,
4660 apr_pool_t *scratch_pool)
4662 /* If this is the end of a move chain, look for matching paths in
4663 * the working copy and add them to our collection if found. */
4664 if (move->next == NULL)
4666 apr_array_header_t *candidate_abspaths;
4668 /* Gather candidate nodes which represent this moved_to_repos_relpath. */
4669 SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
4670 &candidate_abspaths, ctx->wc_ctx,
4671 victim_abspath, victim_node_kind,
4672 move->moved_to_repos_relpath,
4673 scratch_pool, scratch_pool));
4674 if (candidate_abspaths->nelts > 0)
4676 apr_array_header_t *moved_to_abspaths;
4678 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
4680 moved_to_abspaths = apr_array_make(result_pool, 1,
4681 sizeof (const char *));
4683 for (i = 0; i < candidate_abspaths->nelts; i++)
4685 const char *candidate_abspath;
4686 const char *repos_root_url;
4687 const char *repos_uuid;
4688 const char *candidate_repos_relpath;
4689 svn_revnum_t candidate_revision;
4691 svn_pool_clear(iterpool);
4693 candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
4695 SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
4696 &candidate_repos_relpath,
4703 iterpool, iterpool));
4705 if (candidate_revision == SVN_INVALID_REVNUM)
4708 /* If the conflict victim and the move target candidate
4709 * are not from the same revision we must ensure that
4710 * they are related. */
4711 if (candidate_revision != victim_revision)
4713 svn_client__pathrev_t *yca_loc;
4716 err = find_yca(&yca_loc, victim_repos_relpath,
4718 candidate_repos_relpath,
4720 repos_root_url, repos_uuid,
4721 NULL, ctx, iterpool, iterpool);
4724 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
4726 svn_error_clear(err);
4730 return svn_error_trace(err);
4733 if (yca_loc == NULL)
4737 APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
4738 apr_pstrdup(result_pool, candidate_abspath);
4740 svn_pool_destroy(iterpool);
4742 svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
4749 apr_pool_t *iterpool;
4751 /* Recurse into each of the possible move chains. */
4752 iterpool = svn_pool_create(scratch_pool);
4753 for (i = 0; i < move->next->nelts; i++)
4755 struct repos_move_info *next_move;
4757 svn_pool_clear(iterpool);
4759 next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
4760 SVN_ERR(follow_move_chains(wc_move_targets, next_move,
4761 ctx, victim_abspath, victim_node_kind,
4762 victim_repos_relpath, victim_revision,
4763 result_pool, iterpool));
4766 svn_pool_destroy(iterpool);
4769 return SVN_NO_ERROR;
4772 static svn_error_t *
4773 init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
4774 svn_client_conflict_t *conflict,
4775 svn_client_ctx_t *ctx,
4776 apr_pool_t *scratch_pool)
4779 const char *victim_abspath;
4780 svn_node_kind_t victim_node_kind;
4781 const char *incoming_new_repos_relpath;
4782 svn_revnum_t incoming_new_pegrev;
4783 svn_wc_operation_t operation;
4785 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
4786 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
4787 operation = svn_client_conflict_get_operation(conflict);
4788 /* ### Should we get the old location in case of reverse-merges? */
4789 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
4790 &incoming_new_repos_relpath, &incoming_new_pegrev,
4792 scratch_pool, scratch_pool));
4793 details->wc_move_targets = apr_hash_make(conflict->pool);
4794 for (i = 0; i < details->moves->nelts; i++)
4796 struct repos_move_info *move;
4798 move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
4799 SVN_ERR(follow_move_chains(details->wc_move_targets, move,
4800 ctx, victim_abspath,
4802 incoming_new_repos_relpath,
4803 incoming_new_pegrev,
4804 conflict->pool, scratch_pool));
4807 /* Initialize to the first possible move target. Hopefully,
4808 * in most cases there will only be one candidate anyway. */
4809 details->move_target_repos_relpath =
4810 get_moved_to_repos_relpath(details, scratch_pool);
4811 details->wc_move_target_idx = 0;
4813 /* If only one move target exists after an update or switch,
4814 * recommend a resolution option which follows the incoming move. */
4815 if (apr_hash_count(details->wc_move_targets) == 1 &&
4816 (operation == svn_wc_operation_update ||
4817 operation == svn_wc_operation_switch))
4819 apr_array_header_t *wc_abspaths;
4821 wc_abspaths = svn_hash_gets(details->wc_move_targets,
4822 details->move_target_repos_relpath);
4823 if (wc_abspaths->nelts == 1)
4825 svn_client_conflict_option_id_t recommended[] =
4827 /* Only one of these will be present for any given conflict. */
4828 svn_client_conflict_option_incoming_move_file_text_merge,
4829 svn_client_conflict_option_incoming_move_dir_merge,
4830 svn_client_conflict_option_local_move_file_text_merge
4832 apr_array_header_t *options;
4834 SVN_ERR(svn_client_conflict_tree_get_resolution_options(
4835 &options, conflict, ctx, scratch_pool, scratch_pool));
4836 for (i = 0; i < (sizeof(recommended) / sizeof(recommended[0])); i++)
4838 svn_client_conflict_option_id_t option_id = recommended[i];
4840 if (svn_client_conflict_option_find_by_id(options, option_id))
4842 conflict->recommended_option_id = option_id;
4849 return SVN_NO_ERROR;
4852 /* Implements tree_conflict_get_details_func_t.
4853 * Find the revision in which the victim was deleted in the repository. */
4854 static svn_error_t *
4855 conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict,
4856 svn_client_ctx_t *ctx,
4857 apr_pool_t *scratch_pool)
4859 const char *old_repos_relpath;
4860 const char *new_repos_relpath;
4861 const char *repos_root_url;
4862 svn_revnum_t old_rev;
4863 svn_revnum_t new_rev;
4864 svn_node_kind_t old_kind;
4865 svn_node_kind_t new_kind;
4866 struct conflict_tree_incoming_delete_details *details;
4867 svn_wc_operation_t operation;
4869 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
4870 &old_repos_relpath, &old_rev, &old_kind, conflict, scratch_pool,
4872 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
4873 &new_repos_relpath, &new_rev, &new_kind, conflict, scratch_pool,
4875 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
4877 scratch_pool, scratch_pool));
4878 operation = svn_client_conflict_get_operation(conflict);
4880 if (operation == svn_wc_operation_update)
4882 if (old_rev < new_rev)
4884 const char *parent_repos_relpath;
4885 svn_revnum_t parent_peg_rev;
4886 svn_revnum_t deleted_rev;
4887 const char *deleted_rev_author;
4888 svn_node_kind_t replacing_node_kind;
4889 apr_array_header_t *moves;
4890 const char *related_repos_relpath;
4891 svn_revnum_t related_peg_rev;
4893 /* The update operation went forward in history. */
4894 SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev,
4895 &parent_repos_relpath,
4899 conflict->local_abspath,
4903 if (new_kind == svn_node_none)
4905 SVN_ERR(find_related_node(&related_repos_relpath,
4907 new_repos_relpath, new_rev,
4908 old_repos_relpath, old_rev,
4910 scratch_pool, scratch_pool));
4914 /* related to self */
4915 related_repos_relpath = NULL;
4916 related_peg_rev = SVN_INVALID_REVNUM;
4919 SVN_ERR(find_revision_for_suspected_deletion(
4920 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
4922 svn_dirent_basename(conflict->local_abspath, scratch_pool),
4923 parent_repos_relpath, parent_peg_rev,
4924 new_kind == svn_node_none ? 0 : old_rev,
4925 related_repos_relpath, related_peg_rev,
4926 ctx, conflict->pool, scratch_pool));
4927 if (deleted_rev == SVN_INVALID_REVNUM)
4929 /* We could not determine the revision in which the node was
4930 * deleted. We cannot provide the required details so the best
4931 * we can do is fall back to the default description. */
4932 return SVN_NO_ERROR;
4935 details = apr_pcalloc(conflict->pool, sizeof(*details));
4936 details->deleted_rev = deleted_rev;
4937 details->added_rev = SVN_INVALID_REVNUM;
4938 details->repos_relpath = apr_pstrdup(conflict->pool,
4940 details->rev_author = deleted_rev_author;
4941 details->replacing_node_kind = replacing_node_kind;
4942 details->moves = moves;
4944 else /* new_rev < old_rev */
4946 /* The update operation went backwards in history.
4947 * Figure out when this node was added. */
4948 SVN_ERR(get_incoming_delete_details_for_reverse_addition(
4949 &details, repos_root_url, old_repos_relpath,
4950 old_rev, new_rev, ctx,
4951 svn_client_conflict_get_local_abspath(conflict),
4952 conflict->pool, scratch_pool));
4955 else if (operation == svn_wc_operation_switch ||
4956 operation == svn_wc_operation_merge)
4958 if (old_rev < new_rev)
4960 svn_revnum_t deleted_rev;
4961 const char *deleted_rev_author;
4962 svn_node_kind_t replacing_node_kind;
4963 apr_array_header_t *moves;
4965 /* The switch/merge operation went forward in history.
4967 * The deletion of the node happened on the branch we switched to
4968 * or merged from. Scan new_repos_relpath's parent's log to find
4969 * the revision which deleted the node. */
4970 SVN_ERR(find_revision_for_suspected_deletion(
4971 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
4973 svn_relpath_basename(new_repos_relpath, scratch_pool),
4974 svn_relpath_dirname(new_repos_relpath, scratch_pool),
4975 new_rev, old_rev, old_repos_relpath, old_rev, ctx,
4976 conflict->pool, scratch_pool));
4977 if (deleted_rev == SVN_INVALID_REVNUM)
4979 /* We could not determine the revision in which the node was
4980 * deleted. We cannot provide the required details so the best
4981 * we can do is fall back to the default description. */
4982 return SVN_NO_ERROR;
4985 details = apr_pcalloc(conflict->pool, sizeof(*details));
4986 details->deleted_rev = deleted_rev;
4987 details->added_rev = SVN_INVALID_REVNUM;
4988 details->repos_relpath = apr_pstrdup(conflict->pool,
4990 details->rev_author = apr_pstrdup(conflict->pool,
4991 deleted_rev_author);
4992 details->replacing_node_kind = replacing_node_kind;
4993 details->moves = moves;
4995 else /* new_rev < old_rev */
4997 /* The switch/merge operation went backwards in history.
4998 * Figure out when the node we switched away from, or merged
4999 * from another branch, was added. */
5000 SVN_ERR(get_incoming_delete_details_for_reverse_addition(
5001 &details, repos_root_url, old_repos_relpath,
5002 old_rev, new_rev, ctx,
5003 svn_client_conflict_get_local_abspath(conflict),
5004 conflict->pool, scratch_pool));
5012 conflict->tree_conflict_incoming_details = details;
5014 if (details && details->moves)
5015 SVN_ERR(init_wc_move_targets(details, conflict, ctx, scratch_pool));
5017 return SVN_NO_ERROR;
5020 /* Details for tree conflicts involving incoming additions. */
5021 struct conflict_tree_incoming_add_details
5023 /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. */
5024 svn_revnum_t added_rev;
5026 /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV.
5027 * Note that both ADDED_REV and DELETED_REV may be valid for update/switch.
5028 * See comment in conflict_tree_get_details_incoming_add() for details. */
5029 svn_revnum_t deleted_rev;
5031 /* The path which was added/deleted relative to the repository root. */
5032 const char *repos_relpath;
5034 /* Authors who committed ADDED_REV/DELETED_REV. */
5035 const char *added_rev_author;
5036 const char *deleted_rev_author;
5038 /* Move information. If not NULL, this is an array of repos_move_info *
5039 * elements. Each element is the head of a move chain which starts in
5040 * ADDED_REV or in DELETED_REV (in which case moves should be interpreted
5042 apr_array_header_t *moves;
5045 /* Implements tree_conflict_get_details_func_t.
5046 * Find the revision in which the victim was added in the repository. */
5047 static svn_error_t *
5048 conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
5049 svn_client_ctx_t *ctx,
5050 apr_pool_t *scratch_pool)
5052 const char *old_repos_relpath;
5053 const char *new_repos_relpath;
5054 const char *repos_root_url;
5055 svn_revnum_t old_rev;
5056 svn_revnum_t new_rev;
5057 struct conflict_tree_incoming_add_details *details;
5058 svn_wc_operation_t operation;
5060 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5061 &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
5063 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5064 &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
5066 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5068 scratch_pool, scratch_pool));
5069 operation = svn_client_conflict_get_operation(conflict);
5071 if (operation == svn_wc_operation_update ||
5072 operation == svn_wc_operation_switch)
5074 /* Only the new repository location is recorded for the node which
5075 * caused an incoming addition. There is no pre-update/pre-switch
5076 * revision to be recorded for the node since it does not exist in
5077 * the repository at that revision.
5078 * The implication is that we cannot know whether the operation went
5079 * forward or backwards in history. So always try to find an added
5080 * and a deleted revision for the node. Users must figure out by whether
5081 * the addition or deletion caused the conflict. */
5083 const char *corrected_url;
5084 svn_string_t *author_revprop;
5085 struct find_added_rev_baton b = { 0 };
5086 svn_ra_session_t *ra_session;
5087 svn_revnum_t deleted_rev;
5088 svn_revnum_t head_rev;
5090 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5092 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5101 details = apr_pcalloc(conflict->pool, sizeof(*details));
5103 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict),
5104 b.added_rev = SVN_INVALID_REVNUM;
5105 b.repos_relpath = NULL;
5106 b.parent_repos_relpath = NULL;
5107 b.pool = scratch_pool;
5109 /* Figure out when this node was added. */
5110 SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
5111 new_rev, SVN_INVALID_REVNUM,
5115 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
5116 SVN_PROP_REVISION_AUTHOR,
5117 &author_revprop, scratch_pool));
5118 details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath);
5119 details->added_rev = b.added_rev;
5121 details->added_rev_author = apr_pstrdup(conflict->pool,
5122 author_revprop->data);
5124 details->added_rev_author = _("unknown author");
5125 details->deleted_rev = SVN_INVALID_REVNUM;
5126 details->deleted_rev_author = NULL;
5128 /* Figure out whether this node was deleted later.
5129 * ### Could probably optimize by infering both addition and deletion
5130 * ### from svn_ra_get_location_segments() call above. */
5131 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &head_rev, scratch_pool));
5132 if (new_rev < head_rev)
5134 SVN_ERR(svn_ra_get_deleted_rev(ra_session, "", new_rev, head_rev,
5135 &deleted_rev, scratch_pool));
5136 if (SVN_IS_VALID_REVNUM(deleted_rev))
5138 SVN_ERR(svn_ra_rev_prop(ra_session, deleted_rev,
5139 SVN_PROP_REVISION_AUTHOR,
5140 &author_revprop, scratch_pool));
5141 details->deleted_rev = deleted_rev;
5143 details->deleted_rev_author = apr_pstrdup(conflict->pool,
5144 author_revprop->data);
5146 details->deleted_rev_author = _("unknown author");
5150 else if (operation == svn_wc_operation_merge)
5152 if (old_rev < new_rev)
5154 /* The merge operation went forwards in history.
5155 * The addition of the node happened on the branch we merged form.
5156 * Scan the nodes's history to find the revision which added it. */
5158 const char *corrected_url;
5159 svn_string_t *author_revprop;
5160 struct find_added_rev_baton b = { 0 };
5161 svn_ra_session_t *ra_session;
5163 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5165 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5174 details = apr_pcalloc(conflict->pool, sizeof(*details));
5175 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
5177 b.added_rev = SVN_INVALID_REVNUM;
5178 b.repos_relpath = NULL;
5179 b.parent_repos_relpath = NULL;
5180 b.pool = scratch_pool;
5182 /* Figure out when this node was added. */
5183 SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
5188 SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
5189 SVN_PROP_REVISION_AUTHOR,
5190 &author_revprop, scratch_pool));
5191 details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath);
5192 details->added_rev = b.added_rev;
5194 details->added_rev_author = apr_pstrdup(conflict->pool,
5195 author_revprop->data);
5197 details->added_rev_author = _("unknown author");
5198 details->deleted_rev = SVN_INVALID_REVNUM;
5199 details->deleted_rev_author = NULL;
5203 /* The merge operation was a reverse-merge.
5204 * This addition is in fact a deletion, applied in reverse,
5205 * which happened on the branch we merged from.
5206 * Find the revision which deleted the node. */
5207 svn_revnum_t deleted_rev;
5208 const char *deleted_rev_author;
5209 svn_node_kind_t replacing_node_kind;
5210 apr_array_header_t *moves;
5212 SVN_ERR(find_revision_for_suspected_deletion(
5213 &deleted_rev, &deleted_rev_author, &replacing_node_kind,
5215 svn_relpath_basename(old_repos_relpath, scratch_pool),
5216 svn_relpath_dirname(old_repos_relpath, scratch_pool),
5218 NULL, SVN_INVALID_REVNUM, /* related to self */
5220 conflict->pool, scratch_pool));
5221 if (deleted_rev == SVN_INVALID_REVNUM)
5223 /* We could not determine the revision in which the node was
5224 * deleted. We cannot provide the required details so the best
5225 * we can do is fall back to the default description. */
5226 return SVN_NO_ERROR;
5229 details = apr_pcalloc(conflict->pool, sizeof(*details));
5230 details->repos_relpath = apr_pstrdup(conflict->pool,
5232 details->deleted_rev = deleted_rev;
5233 details->deleted_rev_author = apr_pstrdup(conflict->pool,
5234 deleted_rev_author);
5236 details->added_rev = SVN_INVALID_REVNUM;
5237 details->added_rev_author = NULL;
5238 details->moves = moves;
5246 conflict->tree_conflict_incoming_details = details;
5248 return SVN_NO_ERROR;
5252 describe_incoming_add_upon_update(
5253 struct conflict_tree_incoming_add_details *details,
5254 svn_node_kind_t new_node_kind,
5255 svn_revnum_t new_rev,
5256 apr_pool_t *result_pool)
5258 if (new_node_kind == svn_node_dir)
5260 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5261 SVN_IS_VALID_REVNUM(details->deleted_rev))
5262 return apr_psprintf(result_pool,
5263 _("A new directory appeared during update to r%ld; "
5264 "it was added by %s in r%ld and later deleted "
5265 "by %s in r%ld."), new_rev,
5266 details->added_rev_author, details->added_rev,
5267 details->deleted_rev_author, details->deleted_rev);
5268 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5269 return apr_psprintf(result_pool,
5270 _("A new directory appeared during update to r%ld; "
5271 "it was added by %s in r%ld."), new_rev,
5272 details->added_rev_author, details->added_rev);
5274 return apr_psprintf(result_pool,
5275 _("A new directory appeared during update to r%ld; "
5276 "it was deleted by %s in r%ld."), new_rev,
5277 details->deleted_rev_author, details->deleted_rev);
5279 else if (new_node_kind == svn_node_file ||
5280 new_node_kind == svn_node_symlink)
5282 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5283 SVN_IS_VALID_REVNUM(details->deleted_rev))
5284 return apr_psprintf(result_pool,
5285 _("A new file appeared during update to r%ld; "
5286 "it was added by %s in r%ld and later deleted "
5287 "by %s in r%ld."), new_rev,
5288 details->added_rev_author, details->added_rev,
5289 details->deleted_rev_author, details->deleted_rev);
5290 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5291 return apr_psprintf(result_pool,
5292 _("A new file appeared during update to r%ld; "
5293 "it was added by %s in r%ld."), new_rev,
5294 details->added_rev_author, details->added_rev);
5296 return apr_psprintf(result_pool,
5297 _("A new file appeared during update to r%ld; "
5298 "it was deleted by %s in r%ld."), new_rev,
5299 details->deleted_rev_author, details->deleted_rev);
5303 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5304 SVN_IS_VALID_REVNUM(details->deleted_rev))
5305 return apr_psprintf(result_pool,
5306 _("A new item appeared during update to r%ld; "
5307 "it was added by %s in r%ld and later deleted "
5308 "by %s in r%ld."), new_rev,
5309 details->added_rev_author, details->added_rev,
5310 details->deleted_rev_author, details->deleted_rev);
5311 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5312 return apr_psprintf(result_pool,
5313 _("A new item appeared during update to r%ld; "
5314 "it was added by %s in r%ld."), new_rev,
5315 details->added_rev_author, details->added_rev);
5317 return apr_psprintf(result_pool,
5318 _("A new item appeared during update to r%ld; "
5319 "it was deleted by %s in r%ld."), new_rev,
5320 details->deleted_rev_author, details->deleted_rev);
5325 describe_incoming_add_upon_switch(
5326 struct conflict_tree_incoming_add_details *details,
5327 svn_node_kind_t victim_node_kind,
5328 const char *new_repos_relpath,
5329 svn_revnum_t new_rev,
5330 apr_pool_t *result_pool)
5332 if (victim_node_kind == svn_node_dir)
5334 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5335 SVN_IS_VALID_REVNUM(details->deleted_rev))
5336 return apr_psprintf(result_pool,
5337 _("A new directory appeared during switch to\n"
5339 "It was added by %s in r%ld and later deleted "
5340 "by %s in r%ld."), new_repos_relpath, new_rev,
5341 details->added_rev_author, details->added_rev,
5342 details->deleted_rev_author, details->deleted_rev);
5343 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5344 return apr_psprintf(result_pool,
5345 _("A new directory appeared during switch to\n"
5346 "'^/%s@%ld'.\nIt was added by %s in r%ld."),
5347 new_repos_relpath, new_rev,
5348 details->added_rev_author, details->added_rev);
5350 return apr_psprintf(result_pool,
5351 _("A new directory appeared during switch to\n"
5352 "'^/%s@%ld'.\nIt was deleted by %s in r%ld."),
5353 new_repos_relpath, new_rev,
5354 details->deleted_rev_author, details->deleted_rev);
5356 else if (victim_node_kind == svn_node_file ||
5357 victim_node_kind == svn_node_symlink)
5359 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5360 SVN_IS_VALID_REVNUM(details->deleted_rev))
5361 return apr_psprintf(result_pool,
5362 _("A new file appeared during switch to\n"
5364 "It was added by %s in r%ld and later deleted "
5365 "by %s in r%ld."), new_repos_relpath, new_rev,
5366 details->added_rev_author, details->added_rev,
5367 details->deleted_rev_author, details->deleted_rev);
5368 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5369 return apr_psprintf(result_pool,
5370 _("A new file appeared during switch to\n"
5372 "It was added by %s in r%ld."),
5373 new_repos_relpath, new_rev,
5374 details->added_rev_author, details->added_rev);
5376 return apr_psprintf(result_pool,
5377 _("A new file appeared during switch to\n"
5379 "It was deleted by %s in r%ld."),
5380 new_repos_relpath, new_rev,
5381 details->deleted_rev_author, details->deleted_rev);
5385 if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5386 SVN_IS_VALID_REVNUM(details->deleted_rev))
5387 return apr_psprintf(result_pool,
5388 _("A new item appeared during switch to\n"
5390 "It was added by %s in r%ld and later deleted "
5391 "by %s in r%ld."), new_repos_relpath, new_rev,
5392 details->added_rev_author, details->added_rev,
5393 details->deleted_rev_author, details->deleted_rev);
5394 else if (SVN_IS_VALID_REVNUM(details->added_rev))
5395 return apr_psprintf(result_pool,
5396 _("A new item appeared during switch to\n"
5398 "It was added by %s in r%ld."),
5399 new_repos_relpath, new_rev,
5400 details->added_rev_author, details->added_rev);
5402 return apr_psprintf(result_pool,
5403 _("A new item appeared during switch to\n"
5405 "It was deleted by %s in r%ld."),
5406 new_repos_relpath, new_rev,
5407 details->deleted_rev_author, details->deleted_rev);
5412 describe_incoming_add_upon_merge(
5413 struct conflict_tree_incoming_add_details *details,
5414 svn_node_kind_t new_node_kind,
5415 svn_revnum_t old_rev,
5416 const char *new_repos_relpath,
5417 svn_revnum_t new_rev,
5418 apr_pool_t *result_pool)
5420 if (new_node_kind == svn_node_dir)
5422 if (old_rev + 1 == new_rev)
5423 return apr_psprintf(result_pool,
5424 _("A new directory appeared during merge of\n"
5425 "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5426 new_repos_relpath, new_rev,
5427 details->added_rev_author, details->added_rev);
5429 return apr_psprintf(result_pool,
5430 _("A new directory appeared during merge of\n"
5431 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5432 new_repos_relpath, old_rev + 1, new_rev,
5433 details->added_rev_author, details->added_rev);
5435 else if (new_node_kind == svn_node_file ||
5436 new_node_kind == svn_node_symlink)
5438 if (old_rev + 1 == new_rev)
5439 return apr_psprintf(result_pool,
5440 _("A new file appeared during merge of\n"
5441 "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5442 new_repos_relpath, new_rev,
5443 details->added_rev_author, details->added_rev);
5445 return apr_psprintf(result_pool,
5446 _("A new file appeared during merge of\n"
5447 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5448 new_repos_relpath, old_rev + 1, new_rev,
5449 details->added_rev_author, details->added_rev);
5453 if (old_rev + 1 == new_rev)
5454 return apr_psprintf(result_pool,
5455 _("A new item appeared during merge of\n"
5456 "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5457 new_repos_relpath, new_rev,
5458 details->added_rev_author, details->added_rev);
5460 return apr_psprintf(result_pool,
5461 _("A new item appeared during merge of\n"
5462 "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5463 new_repos_relpath, old_rev + 1, new_rev,
5464 details->added_rev_author, details->added_rev);
5469 describe_incoming_reverse_deletion_upon_merge(
5470 struct conflict_tree_incoming_add_details *details,
5471 svn_node_kind_t new_node_kind,
5472 const char *old_repos_relpath,
5473 svn_revnum_t old_rev,
5474 svn_revnum_t new_rev,
5475 apr_pool_t *result_pool)
5477 if (new_node_kind == svn_node_dir)
5479 if (new_rev + 1 == old_rev)
5480 return apr_psprintf(result_pool,
5481 _("A new directory appeared during reverse-merge of"
5482 "\n'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5483 old_repos_relpath, old_rev,
5484 details->deleted_rev_author,
5485 details->deleted_rev);
5487 return apr_psprintf(result_pool,
5488 _("A new directory appeared during reverse-merge "
5489 "of\n'^/%s:%ld-%ld'.\n"
5490 "It was deleted by %s in r%ld."),
5491 old_repos_relpath, new_rev, rev_below(old_rev),
5492 details->deleted_rev_author,
5493 details->deleted_rev);
5495 else if (new_node_kind == svn_node_file ||
5496 new_node_kind == svn_node_symlink)
5498 if (new_rev + 1 == old_rev)
5499 return apr_psprintf(result_pool,
5500 _("A new file appeared during reverse-merge of\n"
5501 "'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5502 old_repos_relpath, old_rev,
5503 details->deleted_rev_author,
5504 details->deleted_rev);
5506 return apr_psprintf(result_pool,
5507 _("A new file appeared during reverse-merge of\n"
5508 "'^/%s:%ld-%ld'.\nIt was deleted by %s in r%ld."),
5509 old_repos_relpath, new_rev + 1, old_rev,
5510 details->deleted_rev_author,
5511 details->deleted_rev);
5515 if (new_rev + 1 == old_rev)
5516 return apr_psprintf(result_pool,
5517 _("A new item appeared during reverse-merge of\n"
5518 "'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5519 old_repos_relpath, old_rev,
5520 details->deleted_rev_author,
5521 details->deleted_rev);
5523 return apr_psprintf(result_pool,
5524 _("A new item appeared during reverse-merge of\n"
5525 "'^/%s:%ld-%ld'.\nIt was deleted by %s in r%ld."),
5526 old_repos_relpath, new_rev + 1, old_rev,
5527 details->deleted_rev_author,
5528 details->deleted_rev);
5532 /* Implements tree_conflict_get_description_func_t. */
5533 static svn_error_t *
5534 conflict_tree_get_description_incoming_add(
5535 const char **incoming_change_description,
5536 svn_client_conflict_t *conflict,
5537 svn_client_ctx_t *ctx,
5538 apr_pool_t *result_pool,
5539 apr_pool_t *scratch_pool)
5542 svn_node_kind_t victim_node_kind;
5543 svn_wc_operation_t conflict_operation;
5544 const char *old_repos_relpath;
5545 svn_revnum_t old_rev;
5546 svn_node_kind_t old_node_kind;
5547 const char *new_repos_relpath;
5548 svn_revnum_t new_rev;
5549 svn_node_kind_t new_node_kind;
5550 struct conflict_tree_incoming_add_details *details;
5552 if (conflict->tree_conflict_incoming_details == NULL)
5553 return svn_error_trace(conflict_tree_get_incoming_description_generic(
5554 incoming_change_description, conflict, ctx,
5555 result_pool, scratch_pool));
5557 conflict_operation = svn_client_conflict_get_operation(conflict);
5558 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
5560 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5561 &old_repos_relpath, &old_rev, &old_node_kind, conflict,
5562 scratch_pool, scratch_pool));
5563 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5564 &new_repos_relpath, &new_rev, &new_node_kind, conflict,
5565 scratch_pool, scratch_pool));
5567 details = conflict->tree_conflict_incoming_details;
5569 if (conflict_operation == svn_wc_operation_update)
5571 action = describe_incoming_add_upon_update(details,
5576 else if (conflict_operation == svn_wc_operation_switch)
5578 action = describe_incoming_add_upon_switch(details,
5584 else if (conflict_operation == svn_wc_operation_merge)
5586 if (old_rev < new_rev)
5587 action = describe_incoming_add_upon_merge(details,
5594 action = describe_incoming_reverse_deletion_upon_merge(
5595 details, new_node_kind, old_repos_relpath,
5596 old_rev, new_rev, result_pool);
5599 *incoming_change_description = apr_pstrdup(result_pool, action);
5601 return SVN_NO_ERROR;
5604 /* Details for tree conflicts involving incoming edits.
5605 * Note that we store an array of these. Each element corresponds to a
5606 * revision within the old/new range in which a modification occured. */
5607 struct conflict_tree_incoming_edit_details
5609 /* The revision in which the edit ocurred. */
5612 /* The author of the revision. */
5615 /** Is the text modified? May be svn_tristate_unknown. */
5616 svn_tristate_t text_modified;
5618 /** Are properties modified? May be svn_tristate_unknown. */
5619 svn_tristate_t props_modified;
5621 /** For directories, are children modified?
5622 * May be svn_tristate_unknown. */
5623 svn_tristate_t children_modified;
5625 /* The path which was edited, relative to the repository root. */
5626 const char *repos_relpath;
5629 /* Baton for find_modified_rev(). */
5630 struct find_modified_rev_baton {
5631 const char *victim_abspath;
5632 svn_client_ctx_t *ctx;
5633 apr_array_header_t *edits;
5634 const char *repos_relpath;
5635 svn_node_kind_t node_kind;
5636 apr_pool_t *result_pool;
5637 apr_pool_t *scratch_pool;
5640 /* Implements svn_log_entry_receiver_t. */
5641 static svn_error_t *
5642 find_modified_rev(void *baton,
5643 svn_log_entry_t *log_entry,
5644 apr_pool_t *scratch_pool)
5646 struct find_modified_rev_baton *b = baton;
5647 struct conflict_tree_incoming_edit_details *details = NULL;
5648 svn_string_t *author;
5649 apr_hash_index_t *hi;
5650 apr_pool_t *iterpool;
5652 if (b->ctx->notify_func2)
5654 svn_wc_notify_t *notify;
5656 notify = svn_wc_create_notify(
5658 svn_wc_notify_tree_conflict_details_progress,
5660 notify->revision = log_entry->revision;
5661 b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
5664 /* No paths were changed in this revision. Nothing to do. */
5665 if (! log_entry->changed_paths2)
5666 return SVN_NO_ERROR;
5668 details = apr_pcalloc(b->result_pool, sizeof(*details));
5669 details->rev = log_entry->revision;
5670 author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
5672 details->author = apr_pstrdup(b->result_pool, author->data);
5674 details->author = _("unknown author");
5676 details->text_modified = svn_tristate_unknown;
5677 details->props_modified = svn_tristate_unknown;
5678 details->children_modified = svn_tristate_unknown;
5680 iterpool = svn_pool_create(scratch_pool);
5681 for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
5683 hi = apr_hash_next(hi))
5687 svn_log_changed_path2_t *log_item;
5689 svn_pool_clear(iterpool);
5691 apr_hash_this(hi, (void *) &path, NULL, &val);
5694 /* ### Remove leading slash from paths in log entries. */
5696 path = svn_relpath_canonicalize(path, iterpool);
5698 if (svn_path_compare_paths(b->repos_relpath, path) == 0 &&
5699 (log_item->action == 'M' || log_item->action == 'A'))
5701 details->text_modified = log_item->text_modified;
5702 details->props_modified = log_item->props_modified;
5703 details->repos_relpath = apr_pstrdup(b->result_pool, path);
5705 if (log_item->copyfrom_path)
5706 b->repos_relpath = apr_pstrdup(b->scratch_pool,
5707 log_item->copyfrom_path);
5709 else if (b->node_kind == svn_node_dir &&
5710 svn_relpath_skip_ancestor(b->repos_relpath, path) != NULL)
5711 details->children_modified = svn_tristate_true;
5714 if (b->node_kind == svn_node_dir &&
5715 details->children_modified == svn_tristate_unknown)
5716 details->children_modified = svn_tristate_false;
5718 APR_ARRAY_PUSH(b->edits, struct conflict_tree_incoming_edit_details *) =
5721 svn_pool_destroy(iterpool);
5723 return SVN_NO_ERROR;
5726 /* Implements tree_conflict_get_details_func_t.
5727 * Find one or more revisions in which the victim was modified in the
5729 static svn_error_t *
5730 conflict_tree_get_details_incoming_edit(svn_client_conflict_t *conflict,
5731 svn_client_ctx_t *ctx,
5732 apr_pool_t *scratch_pool)
5734 const char *old_repos_relpath;
5735 const char *new_repos_relpath;
5736 const char *repos_root_url;
5737 svn_revnum_t old_rev;
5738 svn_revnum_t new_rev;
5739 svn_node_kind_t old_node_kind;
5740 svn_node_kind_t new_node_kind;
5741 svn_wc_operation_t operation;
5743 const char *corrected_url;
5744 svn_ra_session_t *ra_session;
5745 apr_array_header_t *paths;
5746 apr_array_header_t *revprops;
5747 struct find_modified_rev_baton b = { 0 };
5749 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5750 &old_repos_relpath, &old_rev, &old_node_kind, conflict,
5751 scratch_pool, scratch_pool));
5752 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5753 &new_repos_relpath, &new_rev, &new_node_kind, conflict,
5754 scratch_pool, scratch_pool));
5755 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5757 scratch_pool, scratch_pool));
5758 operation = svn_client_conflict_get_operation(conflict);
5759 if (operation == svn_wc_operation_update)
5761 b.node_kind = old_rev < new_rev ? new_node_kind : old_node_kind;
5763 /* If there is no node then we cannot find any edits. */
5764 if (b.node_kind == svn_node_none)
5765 return SVN_NO_ERROR;
5767 url = svn_path_url_add_component2(repos_root_url,
5768 old_rev < new_rev ? new_repos_relpath
5769 : old_repos_relpath,
5772 b.repos_relpath = old_rev < new_rev ? new_repos_relpath
5773 : old_repos_relpath;
5775 else if (operation == svn_wc_operation_switch ||
5776 operation == svn_wc_operation_merge)
5778 url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5781 b.repos_relpath = new_repos_relpath;
5782 b.node_kind = new_node_kind;
5785 SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5794 paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
5795 APR_ARRAY_PUSH(paths, const char *) = "";
5797 revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
5798 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
5801 b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
5802 b.result_pool = conflict->pool;
5803 b.scratch_pool = scratch_pool;
5804 b.edits = apr_array_make(
5806 sizeof(struct conflict_tree_incoming_edit_details *));
5808 SVN_ERR(svn_ra_get_log2(ra_session, paths,
5809 old_rev < new_rev ? old_rev : new_rev,
5810 old_rev < new_rev ? new_rev : old_rev,
5812 TRUE, /* need the changed paths list */
5813 FALSE, /* need to traverse copies */
5814 FALSE, /* no need for merged revisions */
5816 find_modified_rev, &b,
5819 conflict->tree_conflict_incoming_details = b.edits;
5821 return SVN_NO_ERROR;
5825 describe_incoming_edit_upon_update(svn_revnum_t old_rev,
5826 svn_revnum_t new_rev,
5827 svn_node_kind_t old_node_kind,
5828 svn_node_kind_t new_node_kind,
5829 apr_pool_t *result_pool)
5831 if (old_rev < new_rev)
5833 if (new_node_kind == svn_node_dir)
5834 return apr_psprintf(result_pool,
5835 _("Changes destined for a directory arrived "
5836 "via the following revisions during update "
5837 "from r%ld to r%ld."), old_rev, new_rev);
5838 else if (new_node_kind == svn_node_file ||
5839 new_node_kind == svn_node_symlink)
5840 return apr_psprintf(result_pool,
5841 _("Changes destined for a file arrived "
5842 "via the following revisions during update "
5843 "from r%ld to r%ld"), old_rev, new_rev);
5845 return apr_psprintf(result_pool,
5846 _("Changes from the following revisions arrived "
5847 "during update from r%ld to r%ld"),
5852 if (new_node_kind == svn_node_dir)
5853 return apr_psprintf(result_pool,
5854 _("Changes destined for a directory arrived "
5855 "via the following revisions during backwards "
5856 "update from r%ld to r%ld"),
5858 else if (new_node_kind == svn_node_file ||
5859 new_node_kind == svn_node_symlink)
5860 return apr_psprintf(result_pool,
5861 _("Changes destined for a file arrived "
5862 "via the following revisions during backwards "
5863 "update from r%ld to r%ld"),
5866 return apr_psprintf(result_pool,
5867 _("Changes from the following revisions arrived "
5868 "during backwards update from r%ld to r%ld"),
5874 describe_incoming_edit_upon_switch(const char *new_repos_relpath,
5875 svn_revnum_t new_rev,
5876 svn_node_kind_t new_node_kind,
5877 apr_pool_t *result_pool)
5879 if (new_node_kind == svn_node_dir)
5880 return apr_psprintf(result_pool,
5881 _("Changes destined for a directory arrived via "
5882 "the following revisions during switch to\n"
5884 new_repos_relpath, new_rev);
5885 else if (new_node_kind == svn_node_file ||
5886 new_node_kind == svn_node_symlink)
5887 return apr_psprintf(result_pool,
5888 _("Changes destined for a directory arrived via "
5889 "the following revisions during switch to\n"
5891 new_repos_relpath, new_rev);
5893 return apr_psprintf(result_pool,
5894 _("Changes from the following revisions arrived "
5895 "during switch to\n'^/%s@r%ld'"),
5896 new_repos_relpath, new_rev);
5899 /* Return a string showing the list of revisions in EDITS, ensuring
5900 * the string won't grow too large for display. */
5902 describe_incoming_edit_list_modified_revs(apr_array_header_t *edits,
5903 apr_pool_t *result_pool)
5905 int num_revs_to_skip;
5906 static const int min_revs_for_skipping = 5;
5907 static const int max_revs_to_display = 8;
5911 if (edits->nelts <= max_revs_to_display)
5912 num_revs_to_skip = 0;
5915 /* Check if we should insert a placeholder for some revisions because
5916 * the string would grow too long for display otherwise. */
5917 num_revs_to_skip = edits->nelts - max_revs_to_display;
5918 if (num_revs_to_skip < min_revs_for_skipping)
5920 /* Don't bother with the placeholder. Just list all revisions. */
5921 num_revs_to_skip = 0;
5925 for (i = 0; i < edits->nelts; i++)
5927 struct conflict_tree_incoming_edit_details *details;
5929 details = APR_ARRAY_IDX(edits, i,
5930 struct conflict_tree_incoming_edit_details *);
5931 if (num_revs_to_skip > 0)
5933 /* Insert a placeholder for revisions falling into the middle of
5934 * the range so we'll get something that looks like:
5935 * 1, 2, 3, 4, 5 [ placeholder ] 95, 96, 97, 98, 99 */
5936 if (i < max_revs_to_display / 2)
5937 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
5938 details->rev, details->author,
5939 i < edits->nelts - 1 ? "," : "");
5940 else if (i >= max_revs_to_display / 2 &&
5941 i < edits->nelts - (max_revs_to_display / 2))
5945 if (i == edits->nelts - (max_revs_to_display / 2))
5946 s = apr_psprintf(result_pool,
5947 _("%s\n [%d revisions omitted for "
5949 s, num_revs_to_skip);
5951 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
5952 details->rev, details->author,
5953 i < edits->nelts - 1 ? "," : "");
5957 s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
5958 details->rev, details->author,
5959 i < edits->nelts - 1 ? "," : "");
5965 /* Implements tree_conflict_get_description_func_t. */
5966 static svn_error_t *
5967 conflict_tree_get_description_incoming_edit(
5968 const char **incoming_change_description,
5969 svn_client_conflict_t *conflict,
5970 svn_client_ctx_t *ctx,
5971 apr_pool_t *result_pool,
5972 apr_pool_t *scratch_pool)
5975 svn_wc_operation_t conflict_operation;
5976 const char *old_repos_relpath;
5977 svn_revnum_t old_rev;
5978 svn_node_kind_t old_node_kind;
5979 const char *new_repos_relpath;
5980 svn_revnum_t new_rev;
5981 svn_node_kind_t new_node_kind;
5982 apr_array_header_t *edits;
5984 if (conflict->tree_conflict_incoming_details == NULL)
5985 return svn_error_trace(conflict_tree_get_incoming_description_generic(
5986 incoming_change_description, conflict, ctx,
5987 result_pool, scratch_pool));
5989 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5990 &old_repos_relpath, &old_rev, &old_node_kind, conflict,
5991 scratch_pool, scratch_pool));
5992 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5993 &new_repos_relpath, &new_rev, &new_node_kind, conflict,
5994 scratch_pool, scratch_pool));
5996 conflict_operation = svn_client_conflict_get_operation(conflict);
5998 edits = conflict->tree_conflict_incoming_details;
6000 if (conflict_operation == svn_wc_operation_update)
6001 action = describe_incoming_edit_upon_update(old_rev, new_rev,
6002 old_node_kind, new_node_kind,
6004 else if (conflict_operation == svn_wc_operation_switch)
6005 action = describe_incoming_edit_upon_switch(new_repos_relpath, new_rev,
6006 new_node_kind, scratch_pool);
6007 else if (conflict_operation == svn_wc_operation_merge)
6009 /* Handle merge inline because it returns early sometimes. */
6010 if (old_rev < new_rev)
6012 if (old_rev + 1 == new_rev)
6014 if (new_node_kind == svn_node_dir)
6015 action = apr_psprintf(scratch_pool,
6016 _("Changes destined for a directory "
6017 "arrived during merge of\n"
6019 new_repos_relpath, new_rev);
6020 else if (new_node_kind == svn_node_file ||
6021 new_node_kind == svn_node_symlink)
6022 action = apr_psprintf(scratch_pool,
6023 _("Changes destined for a file "
6024 "arrived during merge of\n"
6026 new_repos_relpath, new_rev);
6028 action = apr_psprintf(scratch_pool,
6029 _("Changes arrived during merge of\n"
6031 new_repos_relpath, new_rev);
6033 *incoming_change_description = apr_pstrdup(result_pool, action);
6035 return SVN_NO_ERROR;
6039 if (new_node_kind == svn_node_dir)
6040 action = apr_psprintf(scratch_pool,
6041 _("Changes destined for a directory "
6042 "arrived via the following revisions "
6043 "during merge of\n'^/%s:%ld-%ld'"),
6044 new_repos_relpath, old_rev + 1, new_rev);
6045 else if (new_node_kind == svn_node_file ||
6046 new_node_kind == svn_node_symlink)
6047 action = apr_psprintf(scratch_pool,
6048 _("Changes destined for a file "
6049 "arrived via the following revisions "
6050 "during merge of\n'^/%s:%ld-%ld'"),
6051 new_repos_relpath, old_rev + 1, new_rev);
6053 action = apr_psprintf(scratch_pool,
6054 _("Changes from the following revisions "
6055 "arrived during merge of\n"
6057 new_repos_relpath, old_rev + 1, new_rev);
6062 if (new_rev + 1 == old_rev)
6064 if (new_node_kind == svn_node_dir)
6065 action = apr_psprintf(scratch_pool,
6066 _("Changes destined for a directory "
6067 "arrived during reverse-merge of\n"
6069 new_repos_relpath, old_rev);
6070 else if (new_node_kind == svn_node_file ||
6071 new_node_kind == svn_node_symlink)
6072 action = apr_psprintf(scratch_pool,
6073 _("Changes destined for a file "
6074 "arrived during reverse-merge of\n"
6076 new_repos_relpath, old_rev);
6078 action = apr_psprintf(scratch_pool,
6079 _("Changes arrived during reverse-merge "
6081 new_repos_relpath, old_rev);
6083 *incoming_change_description = apr_pstrdup(result_pool, action);
6085 return SVN_NO_ERROR;
6089 if (new_node_kind == svn_node_dir)
6090 action = apr_psprintf(scratch_pool,
6091 _("Changes destined for a directory "
6092 "arrived via the following revisions "
6093 "during reverse-merge of\n"
6095 new_repos_relpath, new_rev + 1, old_rev);
6096 else if (new_node_kind == svn_node_file ||
6097 new_node_kind == svn_node_symlink)
6098 action = apr_psprintf(scratch_pool,
6099 _("Changes destined for a file "
6100 "arrived via the following revisions "
6101 "during reverse-merge of\n"
6103 new_repos_relpath, new_rev + 1, old_rev);
6106 action = apr_psprintf(scratch_pool,
6107 _("Changes from the following revisions "
6108 "arrived during reverse-merge of\n"
6110 new_repos_relpath, new_rev + 1, old_rev);
6115 action = apr_psprintf(scratch_pool, "%s:\n%s", action,
6116 describe_incoming_edit_list_modified_revs(
6117 edits, scratch_pool));
6118 *incoming_change_description = apr_pstrdup(result_pool, action);
6120 return SVN_NO_ERROR;
6124 svn_client_conflict_tree_get_description(
6125 const char **incoming_change_description,
6126 const char **local_change_description,
6127 svn_client_conflict_t *conflict,
6128 svn_client_ctx_t *ctx,
6129 apr_pool_t *result_pool,
6130 apr_pool_t *scratch_pool)
6132 SVN_ERR(conflict->tree_conflict_get_incoming_description_func(
6133 incoming_change_description,
6134 conflict, ctx, result_pool, scratch_pool));
6136 SVN_ERR(conflict->tree_conflict_get_local_description_func(
6137 local_change_description,
6138 conflict, ctx, result_pool, scratch_pool));
6140 return SVN_NO_ERROR;
6144 svn_client_conflict_option_set_merged_propval(
6145 svn_client_conflict_option_t *option,
6146 const svn_string_t *merged_propval)
6148 option->type_data.prop.merged_propval = svn_string_dup(merged_propval,
6152 /* Implements conflict_option_resolve_func_t. */
6153 static svn_error_t *
6154 resolve_postpone(svn_client_conflict_option_t *option,
6155 svn_client_conflict_t *conflict,
6156 svn_client_ctx_t *ctx,
6157 apr_pool_t *scratch_pool)
6159 return SVN_NO_ERROR; /* Nothing to do. */
6162 /* Implements conflict_option_resolve_func_t. */
6163 static svn_error_t *
6164 resolve_text_conflict(svn_client_conflict_option_t *option,
6165 svn_client_conflict_t *conflict,
6166 svn_client_ctx_t *ctx,
6167 apr_pool_t *scratch_pool)
6169 svn_client_conflict_option_id_t option_id;
6170 const char *local_abspath;
6171 const char *lock_abspath;
6172 svn_wc_conflict_choice_t conflict_choice;
6175 option_id = svn_client_conflict_option_get_id(option);
6176 conflict_choice = conflict_option_id_to_wc_conflict_choice(option_id);
6177 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6179 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6181 scratch_pool, scratch_pool));
6182 err = svn_wc__conflict_text_mark_resolved(ctx->wc_ctx,
6190 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6193 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6196 conflict->resolution_text = option_id;
6198 return SVN_NO_ERROR;
6201 /* Implements conflict_option_resolve_func_t. */
6202 static svn_error_t *
6203 resolve_prop_conflict(svn_client_conflict_option_t *option,
6204 svn_client_conflict_t *conflict,
6205 svn_client_ctx_t *ctx,
6206 apr_pool_t *scratch_pool)
6208 svn_client_conflict_option_id_t option_id;
6209 svn_wc_conflict_choice_t conflict_choice;
6210 const char *local_abspath;
6211 const char *lock_abspath;
6212 const char *propname = option->type_data.prop.propname;
6214 const svn_string_t *merged_value;
6216 option_id = svn_client_conflict_option_get_id(option);
6217 conflict_choice = conflict_option_id_to_wc_conflict_choice(option_id);
6218 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6220 if (option_id == svn_client_conflict_option_merged_text)
6221 merged_value = option->type_data.prop.merged_propval;
6223 merged_value = NULL;
6225 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6227 scratch_pool, scratch_pool));
6228 err = svn_wc__conflict_prop_mark_resolved(ctx->wc_ctx, local_abspath,
6229 propname, conflict_choice,
6234 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6237 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6240 if (propname[0] == '\0')
6242 apr_hash_index_t *hi;
6244 /* All properties have been resolved to the same option. */
6245 for (hi = apr_hash_first(scratch_pool, conflict->prop_conflicts);
6247 hi = apr_hash_next(hi))
6249 const char *this_propname = apr_hash_this_key(hi);
6251 svn_hash_sets(conflict->resolved_props,
6252 apr_pstrdup(apr_hash_pool_get(conflict->resolved_props),
6255 svn_hash_sets(conflict->prop_conflicts, this_propname, NULL);
6258 conflict->legacy_prop_conflict_propname = NULL;
6262 svn_hash_sets(conflict->resolved_props,
6263 apr_pstrdup(apr_hash_pool_get(conflict->resolved_props),
6266 svn_hash_sets(conflict->prop_conflicts, propname, NULL);
6268 if (apr_hash_count(conflict->prop_conflicts) > 0)
6269 conflict->legacy_prop_conflict_propname =
6270 apr_hash_this_key(apr_hash_first(scratch_pool,
6271 conflict->prop_conflicts));
6273 conflict->legacy_prop_conflict_propname = NULL;
6276 return SVN_NO_ERROR;
6279 /* Implements conflict_option_resolve_func_t. */
6280 static svn_error_t *
6281 resolve_accept_current_wc_state(svn_client_conflict_option_t *option,
6282 svn_client_conflict_t *conflict,
6283 svn_client_ctx_t *ctx,
6284 apr_pool_t *scratch_pool)
6286 svn_client_conflict_option_id_t option_id;
6287 const char *local_abspath;
6288 const char *lock_abspath;
6291 option_id = svn_client_conflict_option_get_id(option);
6292 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6294 if (option_id != svn_client_conflict_option_accept_current_wc_state)
6295 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6296 _("Tree conflict on '%s' can only be resolved "
6297 "to the current working copy state"),
6298 svn_dirent_local_style(local_abspath,
6301 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6303 scratch_pool, scratch_pool));
6305 /* Resolve to current working copy state. */
6306 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
6308 /* svn_wc__del_tree_conflict doesn't handle notification for us */
6309 if (ctx->notify_func2)
6310 ctx->notify_func2(ctx->notify_baton2,
6311 svn_wc_create_notify(local_abspath,
6312 svn_wc_notify_resolved_tree,
6316 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6321 conflict->resolution_tree = option_id;
6323 return SVN_NO_ERROR;
6326 /* Implements conflict_option_resolve_func_t. */
6327 static svn_error_t *
6328 resolve_update_break_moved_away(svn_client_conflict_option_t *option,
6329 svn_client_conflict_t *conflict,
6330 svn_client_ctx_t *ctx,
6331 apr_pool_t *scratch_pool)
6333 const char *local_abspath;
6334 const char *lock_abspath;
6337 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6339 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6341 scratch_pool, scratch_pool));
6342 err = svn_wc__conflict_tree_update_break_moved_away(ctx->wc_ctx,
6349 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6354 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6356 return SVN_NO_ERROR;
6359 /* Implements conflict_option_resolve_func_t. */
6360 static svn_error_t *
6361 resolve_update_raise_moved_away(svn_client_conflict_option_t *option,
6362 svn_client_conflict_t *conflict,
6363 svn_client_ctx_t *ctx,
6364 apr_pool_t *scratch_pool)
6366 const char *local_abspath;
6367 const char *lock_abspath;
6370 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6372 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6374 scratch_pool, scratch_pool));
6375 err = svn_wc__conflict_tree_update_raise_moved_away(ctx->wc_ctx,
6382 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6387 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6389 return SVN_NO_ERROR;
6392 /* Implements conflict_option_resolve_func_t. */
6393 static svn_error_t *
6394 resolve_update_moved_away_node(svn_client_conflict_option_t *option,
6395 svn_client_conflict_t *conflict,
6396 svn_client_ctx_t *ctx,
6397 apr_pool_t *scratch_pool)
6399 const char *local_abspath;
6400 const char *lock_abspath;
6403 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6405 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6407 scratch_pool, scratch_pool));
6408 err = svn_wc__conflict_tree_update_moved_away_node(ctx->wc_ctx,
6415 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6418 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6421 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6423 return SVN_NO_ERROR;
6426 /* Verify the local working copy state matches what we expect when an
6427 * incoming add vs add tree conflict exists after an update operation.
6428 * We assume the update operation leaves the working copy in a state which
6429 * prefers the local change and cancels the incoming addition.
6430 * Run a quick sanity check and error out if it looks as if the
6431 * working copy was modified since, even though it's not easy to make
6432 * such modifications without also clearing the conflict marker. */
6433 static svn_error_t *
6434 verify_local_state_for_incoming_add_upon_update(
6435 svn_client_conflict_t *conflict,
6436 svn_client_conflict_option_t *option,
6437 svn_client_ctx_t *ctx,
6438 apr_pool_t *scratch_pool)
6440 const char *local_abspath;
6441 svn_client_conflict_option_id_t option_id;
6442 const char *wcroot_abspath;
6443 svn_wc_operation_t operation;
6444 const char *incoming_new_repos_relpath;
6445 svn_revnum_t incoming_new_pegrev;
6446 svn_node_kind_t incoming_new_kind;
6447 const char *base_repos_relpath;
6448 svn_revnum_t base_rev;
6449 svn_node_kind_t base_kind;
6450 const char *local_style_relpath;
6451 svn_boolean_t is_added;
6454 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6455 option_id = svn_client_conflict_option_get_id(option);
6456 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
6457 local_abspath, scratch_pool,
6459 operation = svn_client_conflict_get_operation(conflict);
6460 SVN_ERR_ASSERT(operation == svn_wc_operation_update);
6462 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
6463 &incoming_new_repos_relpath, &incoming_new_pegrev,
6464 &incoming_new_kind, conflict, scratch_pool,
6467 local_style_relpath = svn_dirent_local_style(
6468 svn_dirent_skip_ancestor(wcroot_abspath,
6472 /* Check if a local addition addition replaces the incoming new node. */
6473 err = svn_wc__node_get_base(&base_kind, &base_rev, &base_repos_relpath,
6474 NULL, NULL, NULL, ctx->wc_ctx, local_abspath,
6475 FALSE, scratch_pool, scratch_pool);
6476 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
6478 if (option_id == svn_client_conflict_option_incoming_add_ignore)
6479 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6480 _("Cannot resolve tree conflict on '%s' "
6481 "(expected a base node but found none)"),
6482 local_style_relpath);
6483 else if (option_id ==
6484 svn_client_conflict_option_incoming_added_dir_replace)
6485 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6486 _("Cannot resolve tree conflict on '%s' "
6487 "(expected a base node but found none)"),
6488 local_style_relpath);
6490 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6491 _("Unexpected option id '%d'"), option_id);
6494 return svn_error_trace(err);
6496 if (base_kind != incoming_new_kind)
6498 if (option_id == svn_client_conflict_option_incoming_add_ignore)
6499 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6500 _("Cannot resolve tree conflict on '%s' "
6501 "(expected base node kind '%s', "
6503 local_style_relpath,
6504 svn_node_kind_to_word(incoming_new_kind),
6505 svn_node_kind_to_word(base_kind));
6506 else if (option_id ==
6507 svn_client_conflict_option_incoming_added_dir_replace)
6508 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6509 _("Cannot resolve tree conflict on '%s' "
6510 "(expected base node kind '%s', "
6512 local_style_relpath,
6513 svn_node_kind_to_word(incoming_new_kind),
6514 svn_node_kind_to_word(base_kind));
6516 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6517 _("Unexpected option id '%d'"), option_id);
6520 if (strcmp(base_repos_relpath, incoming_new_repos_relpath) != 0 ||
6521 base_rev != incoming_new_pegrev)
6523 if (option_id == svn_client_conflict_option_incoming_add_ignore)
6524 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6525 _("Cannot resolve tree conflict on '%s' "
6526 "(expected base node from '^/%s@%ld', "
6527 "but found '^/%s@%ld')"),
6528 local_style_relpath,
6529 incoming_new_repos_relpath,
6530 incoming_new_pegrev,
6531 base_repos_relpath, base_rev);
6532 else if (option_id ==
6533 svn_client_conflict_option_incoming_added_dir_replace)
6534 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6535 _("Cannot resolve tree conflict on '%s' "
6536 "(expected base node from '^/%s@%ld', "
6537 "but found '^/%s@%ld')"),
6538 local_style_relpath,
6539 incoming_new_repos_relpath,
6540 incoming_new_pegrev,
6541 base_repos_relpath, base_rev);
6543 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6544 _("Unexpected option id '%d'"), option_id);
6547 SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, local_abspath,
6551 if (option_id == svn_client_conflict_option_incoming_add_ignore)
6552 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6553 _("Cannot resolve tree conflict on '%s' "
6554 "(expected an added item, but the item "
6556 local_style_relpath);
6558 else if (option_id ==
6559 svn_client_conflict_option_incoming_added_dir_replace)
6560 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6561 _("Cannot resolve tree conflict on '%s' "
6562 "(expected an added item, but the item "
6564 local_style_relpath);
6566 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6567 _("Unexpected option id '%d'"), option_id);
6570 return SVN_NO_ERROR;
6574 /* Implements conflict_option_resolve_func_t. */
6575 static svn_error_t *
6576 resolve_incoming_add_ignore(svn_client_conflict_option_t *option,
6577 svn_client_conflict_t *conflict,
6578 svn_client_ctx_t *ctx,
6579 apr_pool_t *scratch_pool)
6581 const char *local_abspath;
6582 const char *lock_abspath;
6583 svn_wc_operation_t operation;
6586 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6587 operation = svn_client_conflict_get_operation(conflict);
6589 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6591 scratch_pool, scratch_pool));
6593 if (operation == svn_wc_operation_update)
6595 err = verify_local_state_for_incoming_add_upon_update(conflict, option,
6601 /* All other options for this conflict actively fetch the incoming
6602 * new node. We can ignore the incoming new node by doing nothing. */
6604 /* Resolve to current working copy state. */
6605 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
6607 /* svn_wc__del_tree_conflict doesn't handle notification for us */
6608 if (ctx->notify_func2)
6609 ctx->notify_func2(ctx->notify_baton2,
6610 svn_wc_create_notify(local_abspath,
6611 svn_wc_notify_resolved_tree,
6616 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6621 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6623 return SVN_NO_ERROR;
6626 /* Delete entry and wc props from a set of properties. */
6628 filter_props(apr_hash_t *props, apr_pool_t *scratch_pool)
6630 apr_hash_index_t *hi;
6632 for (hi = apr_hash_first(scratch_pool, props);
6634 hi = apr_hash_next(hi))
6636 const char *propname = apr_hash_this_key(hi);
6638 if (!svn_wc_is_normal_prop(propname))
6639 svn_hash_sets(props, propname, NULL);
6643 /* Implements conflict_option_resolve_func_t. */
6644 static svn_error_t *
6645 resolve_merge_incoming_added_file_text_update(
6646 svn_client_conflict_option_t *option,
6647 svn_client_conflict_t *conflict,
6648 svn_client_ctx_t *ctx,
6649 apr_pool_t *scratch_pool)
6651 const char *wc_tmpdir;
6652 const char *local_abspath;
6653 const char *lock_abspath;
6654 svn_wc_merge_outcome_t merge_content_outcome;
6655 svn_wc_notify_state_t merge_props_outcome;
6656 const char *empty_file_abspath;
6657 const char *working_file_tmp_abspath;
6658 svn_stream_t *working_file_stream;
6659 svn_stream_t *working_file_tmp_stream;
6660 apr_hash_t *working_props;
6661 apr_array_header_t *propdiffs;
6664 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6666 /* Set up tempory storage for the working version of file. */
6667 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
6668 scratch_pool, scratch_pool));
6669 SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
6670 &working_file_tmp_abspath, wc_tmpdir,
6671 /* Don't delete automatically! */
6672 svn_io_file_del_none,
6673 scratch_pool, scratch_pool));
6675 /* Copy the detranslated working file to temporary storage. */
6676 SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
6677 local_abspath, local_abspath,
6678 SVN_WC_TRANSLATE_TO_NF,
6679 scratch_pool, scratch_pool));
6680 SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
6681 ctx->cancel_func, ctx->cancel_baton,
6684 /* Get a copy of the working file's properties. */
6685 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
6686 scratch_pool, scratch_pool));
6687 filter_props(working_props, scratch_pool);
6689 /* Create an empty file as fake "merge-base" for the two added files.
6690 * The files are not ancestrally related so this is the best we can do. */
6691 SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
6692 svn_io_file_del_on_pool_cleanup,
6693 scratch_pool, scratch_pool));
6695 /* Create a property diff which shows all props as added. */
6696 SVN_ERR(svn_prop_diffs(&propdiffs, working_props,
6697 apr_hash_make(scratch_pool), scratch_pool));
6699 /* ### The following WC modifications should be atomic. */
6700 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6702 scratch_pool, scratch_pool));
6704 /* Revert the path in order to restore the repository's line of
6705 * history, which is part of the BASE tree. This revert operation
6706 * is why are being careful about not losing the temporary copy. */
6707 err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_empty,
6708 FALSE, NULL, TRUE, FALSE,
6709 NULL, NULL, /* no cancellation */
6710 ctx->notify_func2, ctx->notify_baton2,
6715 /* Perform the file merge. ### Merge into tempfile and then rename on top? */
6716 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
6717 ctx->wc_ctx, empty_file_abspath,
6718 working_file_tmp_abspath, local_abspath,
6719 NULL, NULL, NULL, /* labels */
6720 NULL, NULL, /* conflict versions */
6721 FALSE, /* dry run */
6722 NULL, NULL, /* diff3_cmd, merge_options */
6724 NULL, NULL, /* conflict func/baton */
6725 NULL, NULL, /* don't allow user to cancel here */
6730 err = svn_error_quick_wrapf(
6731 err, _("If needed, a backup copy of '%s' can be found at '%s'"),
6732 svn_dirent_local_style(local_abspath, scratch_pool),
6733 svn_dirent_local_style(working_file_tmp_abspath, scratch_pool));
6734 err = svn_error_compose_create(err,
6735 svn_wc__release_write_lock(ctx->wc_ctx,
6738 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6741 if (ctx->notify_func2)
6743 svn_wc_notify_t *notify;
6745 /* Tell the world about the file merge that just happened. */
6746 notify = svn_wc_create_notify(local_abspath,
6747 svn_wc_notify_update_update,
6749 if (merge_content_outcome == svn_wc_merge_conflict)
6750 notify->content_state = svn_wc_notify_state_conflicted;
6752 notify->content_state = svn_wc_notify_state_merged;
6753 notify->prop_state = merge_props_outcome;
6754 notify->kind = svn_node_file;
6755 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
6757 /* And also about the successfully resolved tree conflict. */
6758 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
6760 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
6763 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6765 /* All is good -- remove temporary copy of the working file. */
6766 SVN_ERR(svn_io_remove_file2(working_file_tmp_abspath, TRUE, scratch_pool));
6768 return SVN_NO_ERROR;
6771 /* Implements conflict_option_resolve_func_t. */
6772 static svn_error_t *
6773 resolve_merge_incoming_added_file_text_merge(
6774 svn_client_conflict_option_t *option,
6775 svn_client_conflict_t *conflict,
6776 svn_client_ctx_t *ctx,
6777 apr_pool_t *scratch_pool)
6779 svn_ra_session_t *ra_session;
6781 const char *corrected_url;
6782 const char *repos_root_url;
6783 const char *wc_tmpdir;
6784 const char *incoming_new_repos_relpath;
6785 svn_revnum_t incoming_new_pegrev;
6786 const char *local_abspath;
6787 const char *lock_abspath;
6788 svn_wc_merge_outcome_t merge_content_outcome;
6789 svn_wc_notify_state_t merge_props_outcome;
6790 apr_file_t *incoming_new_file;
6791 const char *incoming_new_tmp_abspath;
6792 const char *empty_file_abspath;
6793 svn_stream_t *incoming_new_stream;
6794 apr_hash_t *incoming_new_props;
6795 apr_array_header_t *propdiffs;
6798 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6800 /* Set up temporary storage for the repository version of file. */
6801 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
6802 scratch_pool, scratch_pool));
6803 SVN_ERR(svn_io_open_unique_file3(&incoming_new_file,
6804 &incoming_new_tmp_abspath, wc_tmpdir,
6805 svn_io_file_del_on_pool_cleanup,
6806 scratch_pool, scratch_pool));
6807 incoming_new_stream = svn_stream_from_aprfile2(incoming_new_file, TRUE,
6810 /* Fetch the incoming added file from the repository. */
6811 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
6812 &incoming_new_repos_relpath, &incoming_new_pegrev,
6813 NULL, conflict, scratch_pool,
6815 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
6816 conflict, scratch_pool,
6818 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
6820 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
6821 url, NULL, NULL, FALSE, FALSE,
6824 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev,
6825 incoming_new_stream, NULL, /* fetched_rev */
6826 &incoming_new_props, scratch_pool));
6828 /* Flush file to disk. */
6829 SVN_ERR(svn_stream_close(incoming_new_stream));
6830 SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool));
6832 filter_props(incoming_new_props, scratch_pool);
6834 /* Create an empty file as fake "merge-base" for the two added files.
6835 * The files are not ancestrally related so this is the best we can do. */
6836 SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
6837 svn_io_file_del_on_pool_cleanup,
6838 scratch_pool, scratch_pool));
6840 /* Create a property diff which shows all props as added. */
6841 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_new_props,
6842 apr_hash_make(scratch_pool), scratch_pool));
6844 /* ### The following WC modifications should be atomic. */
6845 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6847 scratch_pool, scratch_pool));
6848 /* Resolve to current working copy state. svn_wc_merge5() requires this. */
6849 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
6851 return svn_error_compose_create(err,
6852 svn_wc__release_write_lock(ctx->wc_ctx,
6855 /* Perform the file merge. ### Merge into tempfile and then rename on top? */
6856 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
6857 ctx->wc_ctx, empty_file_abspath,
6858 incoming_new_tmp_abspath, local_abspath,
6859 NULL, NULL, NULL, /* labels */
6860 NULL, NULL, /* conflict versions */
6861 FALSE, /* dry run */
6862 NULL, NULL, /* diff3_cmd, merge_options */
6864 NULL, NULL, /* conflict func/baton */
6865 NULL, NULL, /* don't allow user to cancel here */
6867 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6870 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6873 if (ctx->notify_func2)
6875 svn_wc_notify_t *notify;
6877 /* Tell the world about the file merge that just happened. */
6878 notify = svn_wc_create_notify(local_abspath,
6879 svn_wc_notify_update_update,
6881 if (merge_content_outcome == svn_wc_merge_conflict)
6882 notify->content_state = svn_wc_notify_state_conflicted;
6884 notify->content_state = svn_wc_notify_state_merged;
6885 notify->prop_state = merge_props_outcome;
6886 notify->kind = svn_node_file;
6887 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
6889 /* And also about the successfully resolved tree conflict. */
6890 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
6892 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
6895 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6897 return SVN_NO_ERROR;
6900 /* Implements conflict_option_resolve_func_t. */
6901 static svn_error_t *
6902 resolve_merge_incoming_added_file_replace_and_merge(
6903 svn_client_conflict_option_t *option,
6904 svn_client_conflict_t *conflict,
6905 svn_client_ctx_t *ctx,
6906 apr_pool_t *scratch_pool)
6908 svn_ra_session_t *ra_session;
6910 const char *corrected_url;
6911 const char *repos_root_url;
6912 const char *incoming_new_repos_relpath;
6913 svn_revnum_t incoming_new_pegrev;
6914 apr_file_t *incoming_new_file;
6915 svn_stream_t *incoming_new_stream;
6916 apr_hash_t *incoming_new_props;
6917 const char *local_abspath;
6918 const char *lock_abspath;
6919 const char *wc_tmpdir;
6920 svn_stream_t *working_file_tmp_stream;
6921 const char *working_file_tmp_abspath;
6922 svn_stream_t *working_file_stream;
6923 apr_hash_t *working_props;
6925 svn_wc_merge_outcome_t merge_content_outcome;
6926 svn_wc_notify_state_t merge_props_outcome;
6927 apr_file_t *empty_file;
6928 const char *empty_file_abspath;
6929 apr_array_header_t *propdiffs;
6931 local_abspath = svn_client_conflict_get_local_abspath(conflict);
6933 /* Set up tempory storage for the working version of file. */
6934 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
6935 scratch_pool, scratch_pool));
6936 SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
6937 &working_file_tmp_abspath, wc_tmpdir,
6938 svn_io_file_del_on_pool_cleanup,
6939 scratch_pool, scratch_pool));
6941 /* Copy the detranslated working file to temporary storage. */
6942 SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
6943 local_abspath, local_abspath,
6944 SVN_WC_TRANSLATE_TO_NF,
6945 scratch_pool, scratch_pool));
6946 SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
6947 ctx->cancel_func, ctx->cancel_baton,
6950 /* Get a copy of the working file's properties. */
6951 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
6952 scratch_pool, scratch_pool));
6954 /* Fetch the incoming added file from the repository. */
6955 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
6956 &incoming_new_repos_relpath, &incoming_new_pegrev,
6957 NULL, conflict, scratch_pool,
6959 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
6960 conflict, scratch_pool,
6962 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
6964 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
6965 url, NULL, NULL, FALSE, FALSE,
6969 url = corrected_url;
6970 SVN_ERR(svn_io_open_unique_file3(&incoming_new_file, NULL, wc_tmpdir,
6971 svn_io_file_del_on_pool_cleanup,
6972 scratch_pool, scratch_pool));
6973 incoming_new_stream = svn_stream_from_aprfile2(incoming_new_file, TRUE,
6975 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev,
6976 incoming_new_stream, NULL, /* fetched_rev */
6977 &incoming_new_props, scratch_pool));
6978 /* Flush file to disk. */
6979 SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool));
6981 /* Reset the stream in preparation for adding its content to WC. */
6982 SVN_ERR(svn_stream_reset(incoming_new_stream));
6984 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6986 scratch_pool, scratch_pool));
6988 /* ### The following WC modifications should be atomic. */
6990 /* Replace the working file with the file from the repository. */
6991 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
6992 NULL, NULL, /* don't allow user to cancel here */
6993 ctx->notify_func2, ctx->notify_baton2,
6997 err = svn_wc_add_repos_file4(ctx->wc_ctx, local_abspath,
6998 incoming_new_stream,
6999 NULL, /* ### could we merge first, then set
7000 ### the merged content here? */
7002 NULL, /* ### merge props first, set here? */
7003 url, incoming_new_pegrev,
7004 NULL, NULL, /* don't allow user to cancel here */
7009 if (ctx->notify_func2)
7011 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
7014 notify->kind = svn_node_file;
7015 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7018 /* Resolve to current working copy state. svn_wc_merge5() requires this. */
7019 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7023 /* Create an empty file as fake "merge-base" for the two added files.
7024 * The files are not ancestrally related so this is the best we can do. */
7025 err = svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
7026 svn_io_file_del_on_pool_cleanup,
7027 scratch_pool, scratch_pool);
7031 filter_props(incoming_new_props, scratch_pool);
7033 /* Create a property diff for the files. */
7034 err = svn_prop_diffs(&propdiffs, incoming_new_props,
7035 working_props, scratch_pool);
7039 /* Perform the file merge. */
7040 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
7041 ctx->wc_ctx, empty_file_abspath,
7042 working_file_tmp_abspath, local_abspath,
7043 NULL, NULL, NULL, /* labels */
7044 NULL, NULL, /* conflict versions */
7045 FALSE, /* dry run */
7046 NULL, NULL, /* diff3_cmd, merge_options */
7048 NULL, NULL, /* conflict func/baton */
7049 NULL, NULL, /* don't allow user to cancel here */
7054 if (ctx->notify_func2)
7056 svn_wc_notify_t *notify = svn_wc_create_notify(
7058 svn_wc_notify_update_update,
7061 if (merge_content_outcome == svn_wc_merge_conflict)
7062 notify->content_state = svn_wc_notify_state_conflicted;
7064 notify->content_state = svn_wc_notify_state_merged;
7065 notify->prop_state = merge_props_outcome;
7066 notify->kind = svn_node_file;
7067 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7071 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7074 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7077 SVN_ERR(svn_stream_close(incoming_new_stream));
7079 if (ctx->notify_func2)
7081 svn_wc_notify_t *notify = svn_wc_create_notify(
7083 svn_wc_notify_resolved_tree,
7086 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7089 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7091 return SVN_NO_ERROR;
7094 static svn_error_t *
7095 raise_tree_conflict(const char *local_abspath,
7096 svn_wc_conflict_action_t incoming_change,
7097 svn_wc_conflict_reason_t local_change,
7098 svn_node_kind_t local_node_kind,
7099 svn_node_kind_t merge_left_kind,
7100 svn_node_kind_t merge_right_kind,
7101 const char *repos_root_url,
7102 const char *repos_uuid,
7103 const char *repos_relpath,
7104 svn_revnum_t merge_left_rev,
7105 svn_revnum_t merge_right_rev,
7106 svn_wc_context_t *wc_ctx,
7107 svn_wc_notify_func2_t notify_func2,
7108 void *notify_baton2,
7109 apr_pool_t *scratch_pool)
7111 svn_wc_conflict_description2_t *conflict;
7112 const svn_wc_conflict_version_t *left_version;
7113 const svn_wc_conflict_version_t *right_version;
7115 left_version = svn_wc_conflict_version_create2(repos_root_url,
7121 right_version = svn_wc_conflict_version_create2(repos_root_url,
7127 conflict = svn_wc_conflict_description_create_tree2(local_abspath,
7129 svn_wc_operation_merge,
7133 conflict->action = incoming_change;
7134 conflict->reason = local_change;
7136 SVN_ERR(svn_wc__add_tree_conflict(wc_ctx, conflict, scratch_pool));
7140 svn_wc_notify_t *notify;
7142 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
7144 notify->kind = local_node_kind;
7145 notify_func2(notify_baton2, notify, scratch_pool);
7148 return SVN_NO_ERROR;
7151 struct merge_newly_added_dir_baton {
7152 const char *target_abspath;
7153 svn_client_ctx_t *ctx;
7154 const char *repos_root_url;
7155 const char *repos_uuid;
7156 const char *added_repos_relpath;
7157 svn_revnum_t merge_left_rev;
7158 svn_revnum_t merge_right_rev;
7161 static svn_error_t *
7162 merge_added_dir_props(const char *target_abspath,
7163 const char *added_repos_relpath,
7164 apr_hash_t *added_props,
7165 const char *repos_root_url,
7166 const char *repos_uuid,
7167 svn_revnum_t merge_left_rev,
7168 svn_revnum_t merge_right_rev,
7169 svn_client_ctx_t *ctx,
7170 apr_pool_t *scratch_pool)
7172 svn_wc_notify_state_t property_state;
7173 apr_array_header_t *propchanges;
7174 const svn_wc_conflict_version_t *left_version;
7175 const svn_wc_conflict_version_t *right_version;
7176 apr_hash_index_t *hi;
7178 left_version = svn_wc_conflict_version_create2(
7179 repos_root_url, repos_uuid, added_repos_relpath,
7180 merge_left_rev, svn_node_none, scratch_pool);
7182 right_version = svn_wc_conflict_version_create2(
7183 repos_root_url, repos_uuid, added_repos_relpath,
7184 merge_right_rev, svn_node_dir, scratch_pool);
7186 propchanges = apr_array_make(scratch_pool, apr_hash_count(added_props),
7187 sizeof(svn_prop_t));
7188 for (hi = apr_hash_first(scratch_pool, added_props);
7190 hi = apr_hash_next(hi))
7194 prop.name = apr_hash_this_key(hi);
7195 prop.value = apr_hash_this_val(hi);
7197 if (svn_wc_is_normal_prop(prop.name))
7198 APR_ARRAY_PUSH(propchanges, svn_prop_t) = prop;
7201 SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx,
7203 left_version, right_version,
7204 apr_hash_make(scratch_pool),
7206 FALSE, /* not a dry-run */
7207 NULL, NULL, NULL, NULL,
7210 if (ctx->notify_func2)
7212 svn_wc_notify_t *notify;
7214 notify = svn_wc_create_notify(target_abspath,
7215 svn_wc_notify_update_update,
7217 notify->kind = svn_node_dir;
7218 notify->content_state = svn_wc_notify_state_unchanged;;
7219 notify->prop_state = property_state;
7220 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7223 return SVN_NO_ERROR;
7226 /* An svn_diff_tree_processor_t callback. */
7227 static svn_error_t *
7228 diff_dir_added(const char *relpath,
7229 const svn_diff_source_t *copyfrom_source,
7230 const svn_diff_source_t *right_source,
7231 apr_hash_t *copyfrom_props,
7232 apr_hash_t *right_props,
7234 const struct svn_diff_tree_processor_t *processor,
7235 apr_pool_t *scratch_pool)
7237 struct merge_newly_added_dir_baton *b = processor->baton;
7238 const char *local_abspath;
7239 const char *copyfrom_url;
7240 svn_node_kind_t db_kind;
7241 svn_node_kind_t on_disk_kind;
7242 apr_hash_index_t *hi;
7244 /* Handle the root of the added directory tree. */
7245 if (relpath[0] == '\0')
7247 /* ### svn_wc_merge_props3() requires this... */
7248 SVN_ERR(svn_wc__del_tree_conflict(b->ctx->wc_ctx, b->target_abspath,
7250 SVN_ERR(merge_added_dir_props(b->target_abspath,
7251 b->added_repos_relpath, right_props,
7252 b->repos_root_url, b->repos_uuid,
7253 b->merge_left_rev, b->merge_right_rev,
7254 b->ctx, scratch_pool));
7255 return SVN_NO_ERROR;
7259 local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
7261 SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath,
7262 FALSE, FALSE, scratch_pool));
7263 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
7265 if (db_kind == svn_node_dir && on_disk_kind == svn_node_dir)
7267 SVN_ERR(merge_added_dir_props(svn_dirent_join(b->target_abspath, relpath,
7269 b->added_repos_relpath, right_props,
7270 b->repos_root_url, b->repos_uuid,
7271 b->merge_left_rev, b->merge_right_rev,
7272 b->ctx, scratch_pool));
7273 return SVN_NO_ERROR;
7276 if (db_kind != svn_node_none && db_kind != svn_node_unknown)
7278 SVN_ERR(raise_tree_conflict(
7279 local_abspath, svn_wc_conflict_action_add,
7280 svn_wc_conflict_reason_obstructed,
7281 db_kind, svn_node_none, svn_node_dir,
7282 b->repos_root_url, b->repos_uuid,
7283 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7284 b->merge_left_rev, b->merge_right_rev,
7285 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7287 return SVN_NO_ERROR;
7290 if (on_disk_kind != svn_node_none)
7292 SVN_ERR(raise_tree_conflict(
7293 local_abspath, svn_wc_conflict_action_add,
7294 svn_wc_conflict_reason_obstructed, db_kind,
7295 svn_node_none, svn_node_dir, b->repos_root_url, b->repos_uuid,
7296 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7297 b->merge_left_rev, b->merge_right_rev,
7298 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7300 return SVN_NO_ERROR;
7303 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
7304 copyfrom_url = apr_pstrcat(scratch_pool, b->repos_root_url, "/",
7305 right_source->repos_relpath, SVN_VA_NULL);
7306 SVN_ERR(svn_wc_add4(b->ctx->wc_ctx, local_abspath, svn_depth_infinity,
7307 copyfrom_url, right_source->revision,
7308 NULL, NULL, /* cancel func/baton */
7309 b->ctx->notify_func2, b->ctx->notify_baton2,
7312 for (hi = apr_hash_first(scratch_pool, right_props);
7314 hi = apr_hash_next(hi))
7316 const char *propname = apr_hash_this_key(hi);
7317 const svn_string_t *propval = apr_hash_this_val(hi);
7319 SVN_ERR(svn_wc_prop_set4(b->ctx->wc_ctx, local_abspath,
7320 propname, propval, svn_depth_empty,
7321 FALSE, NULL /* do not skip checks */,
7322 NULL, NULL, /* cancel func/baton */
7323 b->ctx->notify_func2, b->ctx->notify_baton2,
7327 return SVN_NO_ERROR;
7330 static svn_error_t *
7331 merge_added_files(const char *local_abspath,
7332 const char *incoming_added_file_abspath,
7333 apr_hash_t *incoming_added_file_props,
7334 svn_client_ctx_t *ctx,
7335 apr_pool_t *scratch_pool)
7337 svn_wc_merge_outcome_t merge_content_outcome;
7338 svn_wc_notify_state_t merge_props_outcome;
7339 apr_file_t *empty_file;
7340 const char *empty_file_abspath;
7341 apr_array_header_t *propdiffs;
7342 apr_hash_t *working_props;
7344 /* Create an empty file as fake "merge-base" for the two added files.
7345 * The files are not ancestrally related so this is the best we can do. */
7346 SVN_ERR(svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
7347 svn_io_file_del_on_pool_cleanup,
7348 scratch_pool, scratch_pool));
7350 /* Get a copy of the working file's properties. */
7351 SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
7352 scratch_pool, scratch_pool));
7354 /* Create a property diff for the files. */
7355 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_added_file_props,
7356 working_props, scratch_pool));
7358 /* Perform the file merge. */
7359 SVN_ERR(svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
7360 ctx->wc_ctx, empty_file_abspath,
7361 incoming_added_file_abspath, local_abspath,
7362 NULL, NULL, NULL, /* labels */
7363 NULL, NULL, /* conflict versions */
7364 FALSE, /* dry run */
7365 NULL, NULL, /* diff3_cmd, merge_options */
7367 NULL, NULL, /* conflict func/baton */
7368 NULL, NULL, /* don't allow user to cancel here */
7371 if (ctx->notify_func2)
7373 svn_wc_notify_t *notify = svn_wc_create_notify(
7375 svn_wc_notify_update_update,
7378 if (merge_content_outcome == svn_wc_merge_conflict)
7379 notify->content_state = svn_wc_notify_state_conflicted;
7381 notify->content_state = svn_wc_notify_state_merged;
7382 notify->prop_state = merge_props_outcome;
7383 notify->kind = svn_node_file;
7384 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7387 return SVN_NO_ERROR;
7390 /* An svn_diff_tree_processor_t callback. */
7391 static svn_error_t *
7392 diff_file_added(const char *relpath,
7393 const svn_diff_source_t *copyfrom_source,
7394 const svn_diff_source_t *right_source,
7395 const char *copyfrom_file,
7396 const char *right_file,
7397 apr_hash_t *copyfrom_props,
7398 apr_hash_t *right_props,
7400 const struct svn_diff_tree_processor_t *processor,
7401 apr_pool_t *scratch_pool)
7403 struct merge_newly_added_dir_baton *b = processor->baton;
7404 const char *local_abspath;
7405 svn_node_kind_t db_kind;
7406 svn_node_kind_t on_disk_kind;
7407 apr_array_header_t *propsarray;
7408 apr_array_header_t *regular_props;
7410 local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
7412 SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath,
7413 FALSE, FALSE, scratch_pool));
7414 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
7416 if (db_kind == svn_node_file && on_disk_kind == svn_node_file)
7418 propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
7419 SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, ®ular_props,
7421 SVN_ERR(merge_added_files(local_abspath, right_file,
7422 svn_prop_array_to_hash(regular_props,
7424 b->ctx, scratch_pool));
7425 return SVN_NO_ERROR;
7428 if (db_kind != svn_node_none && db_kind != svn_node_unknown)
7430 SVN_ERR(raise_tree_conflict(
7431 local_abspath, svn_wc_conflict_action_add,
7432 svn_wc_conflict_reason_obstructed,
7433 db_kind, svn_node_none, svn_node_file,
7434 b->repos_root_url, b->repos_uuid,
7435 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7436 b->merge_left_rev, b->merge_right_rev,
7437 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7439 return SVN_NO_ERROR;
7442 if (on_disk_kind != svn_node_none)
7444 SVN_ERR(raise_tree_conflict(
7445 local_abspath, svn_wc_conflict_action_add,
7446 svn_wc_conflict_reason_obstructed, db_kind,
7447 svn_node_none, svn_node_file, b->repos_root_url, b->repos_uuid,
7448 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7449 b->merge_left_rev, b->merge_right_rev,
7450 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7452 return SVN_NO_ERROR;
7455 propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
7456 SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, ®ular_props,
7458 SVN_ERR(svn_io_copy_file(right_file, local_abspath, FALSE, scratch_pool));
7459 SVN_ERR(svn_wc_add_from_disk3(b->ctx->wc_ctx, local_abspath,
7460 svn_prop_array_to_hash(regular_props,
7462 FALSE, b->ctx->notify_func2,
7463 b->ctx->notify_baton2, scratch_pool));
7465 return SVN_NO_ERROR;
7468 /* Merge a newly added directory into TARGET_ABSPATH in the working copy.
7470 * This uses a diff-tree processor because our standard merge operation
7471 * is not set up for merges where the merge-source anchor is itself an
7472 * added directory (i.e. does not exist on one side of the diff).
7473 * The standard merge will only merge additions of children of a path
7474 * that exists across the entire revision range being merged.
7475 * But in our case, SOURCE1 does not yet exist in REV1, but SOURCE2
7476 * does exist in REV2. Thus we use a diff processor.
7478 static svn_error_t *
7479 merge_newly_added_dir(const char *added_repos_relpath,
7480 const char *source1,
7482 const char *source2,
7484 const char *target_abspath,
7485 svn_boolean_t reverse_merge,
7486 svn_client_ctx_t *ctx,
7487 apr_pool_t *result_pool,
7488 apr_pool_t *scratch_pool)
7490 svn_diff_tree_processor_t *processor;
7491 struct merge_newly_added_dir_baton baton = { 0 };
7492 const svn_diff_tree_processor_t *diff_processor;
7493 svn_ra_session_t *ra_session;
7494 const char *corrected_url;
7495 svn_ra_session_t *extra_ra_session;
7496 const svn_ra_reporter3_t *reporter;
7497 void *reporter_baton;
7498 const svn_delta_editor_t *diff_editor;
7499 void *diff_edit_baton;
7500 const char *anchor1;
7501 const char *anchor2;
7502 const char *target1;
7503 const char *target2;
7505 svn_uri_split(&anchor1, &target1, source1, scratch_pool);
7506 svn_uri_split(&anchor2, &target2, source2, scratch_pool);
7508 baton.target_abspath = target_abspath;
7510 baton.added_repos_relpath = added_repos_relpath;
7511 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
7512 &baton.repos_root_url, &baton.repos_uuid,
7513 ctx->wc_ctx, target_abspath,
7514 scratch_pool, scratch_pool));
7515 baton.merge_left_rev = rev1;
7516 baton.merge_right_rev = rev2;
7518 processor = svn_diff__tree_processor_create(&baton, scratch_pool);
7519 processor->dir_added = diff_dir_added;
7520 processor->file_added = diff_file_added;
7522 diff_processor = processor;
7524 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
7528 /* Filter the first path component using a filter processor, until we fixed
7529 the diff processing to handle this directly */
7530 diff_processor = svn_diff__tree_processor_filter_create(
7531 diff_processor, target1, scratch_pool);
7533 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7534 anchor2, NULL, NULL, FALSE,
7536 scratch_pool, scratch_pool));
7538 anchor2 = corrected_url;
7540 /* Extra RA session is used during the editor calls to fetch file contents. */
7541 SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor2,
7542 scratch_pool, scratch_pool));
7544 /* Create a repos-repos diff editor. */
7545 SVN_ERR(svn_client__get_diff_editor2(
7546 &diff_editor, &diff_edit_baton,
7547 extra_ra_session, svn_depth_infinity, rev1, TRUE,
7548 diff_processor, ctx->cancel_func, ctx->cancel_baton,
7551 /* We want to switch our txn into URL2 */
7552 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
7553 rev2, target1, svn_depth_infinity, TRUE, TRUE,
7554 source2, diff_editor, diff_edit_baton, scratch_pool));
7556 /* Drive the reporter; do the diff. */
7557 SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
7562 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
7564 return SVN_NO_ERROR;
7567 /* Implements conflict_option_resolve_func_t. */
7568 static svn_error_t *
7569 resolve_merge_incoming_added_dir_merge(svn_client_conflict_option_t *option,
7570 svn_client_conflict_t *conflict,
7571 svn_client_ctx_t *ctx,
7572 apr_pool_t *scratch_pool)
7574 const char *repos_root_url;
7575 const char *incoming_old_repos_relpath;
7576 svn_revnum_t incoming_old_pegrev;
7577 const char *incoming_new_repos_relpath;
7578 svn_revnum_t incoming_new_pegrev;
7579 const char *local_abspath;
7580 const char *lock_abspath;
7581 struct conflict_tree_incoming_add_details *details;
7582 const char *added_repos_relpath;
7583 const char *source1;
7585 const char *source2;
7589 local_abspath = svn_client_conflict_get_local_abspath(conflict);
7591 details = conflict->tree_conflict_incoming_details;
7592 if (details == NULL)
7593 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7594 _("Conflict resolution option '%d' requires "
7595 "details for tree conflict at '%s' to be "
7596 "fetched from the repository"),
7598 svn_dirent_local_style(local_abspath,
7601 /* Set up merge sources to merge the entire incoming added directory tree. */
7602 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7603 conflict, scratch_pool,
7605 source1 = svn_path_url_add_component2(repos_root_url,
7606 details->repos_relpath,
7608 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
7609 &incoming_old_repos_relpath, &incoming_old_pegrev,
7610 NULL, conflict, scratch_pool, scratch_pool));
7611 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
7612 &incoming_new_repos_relpath, &incoming_new_pegrev,
7613 NULL, conflict, scratch_pool, scratch_pool));
7614 if (incoming_old_pegrev < incoming_new_pegrev) /* forward merge */
7616 if (details->added_rev == SVN_INVALID_REVNUM)
7617 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7618 _("Could not determine when '%s' was "
7619 "added the repository"),
7620 svn_dirent_local_style(local_abspath,
7622 rev1 = rev_below(details->added_rev);
7623 source2 = svn_path_url_add_component2(repos_root_url,
7624 incoming_new_repos_relpath,
7626 rev2 = incoming_new_pegrev;
7627 added_repos_relpath = incoming_new_repos_relpath;
7629 else /* reverse-merge */
7631 if (details->deleted_rev == SVN_INVALID_REVNUM)
7632 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7633 _("Could not determine when '%s' was "
7634 "deleted from the repository"),
7635 svn_dirent_local_style(local_abspath,
7637 rev1 = details->deleted_rev;
7638 source2 = svn_path_url_add_component2(repos_root_url,
7639 incoming_old_repos_relpath,
7641 rev2 = incoming_old_pegrev;
7642 added_repos_relpath = incoming_new_repos_relpath;
7645 /* ### The following WC modifications should be atomic. */
7646 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7648 scratch_pool, scratch_pool));
7650 /* ### wrap in a transaction */
7651 err = merge_newly_added_dir(added_repos_relpath,
7652 source1, rev1, source2, rev2,
7654 (incoming_old_pegrev > incoming_new_pegrev),
7655 ctx, scratch_pool, scratch_pool);
7657 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7659 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7662 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7665 if (ctx->notify_func2)
7666 ctx->notify_func2(ctx->notify_baton2,
7667 svn_wc_create_notify(local_abspath,
7668 svn_wc_notify_resolved_tree,
7672 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7674 return SVN_NO_ERROR;
7677 /* Implements conflict_option_resolve_func_t. */
7678 static svn_error_t *
7679 resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option,
7680 svn_client_conflict_t *conflict,
7681 svn_client_ctx_t *ctx,
7682 apr_pool_t *scratch_pool)
7684 const char *local_abspath;
7685 const char *lock_abspath;
7688 local_abspath = svn_client_conflict_get_local_abspath(conflict);
7690 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
7691 &lock_abspath, ctx->wc_ctx, local_abspath,
7692 scratch_pool, scratch_pool));
7694 err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx,
7702 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7707 return SVN_NO_ERROR;
7710 /* A baton for notification_adjust_func(). */
7711 struct notification_adjust_baton
7713 svn_wc_notify_func2_t inner_func;
7715 const char *checkout_abspath;
7716 const char *final_abspath;
7719 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
7720 * baton is BATON->inner_baton) and adjusts the notification paths that
7721 * start with BATON->checkout_abspath to start instead with
7722 * BATON->final_abspath. */
7724 notification_adjust_func(void *baton,
7725 const svn_wc_notify_t *notify,
7728 struct notification_adjust_baton *nb = baton;
7729 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
7730 const char *relpath;
7732 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
7733 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
7736 nb->inner_func(nb->inner_baton, inner_notify, pool);
7739 /* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by
7740 * replacing the local directory with the incoming directory.
7741 * If MERGE_DIRS is set, also merge the directories after replacing. */
7742 static svn_error_t *
7743 merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
7744 svn_client_conflict_t *conflict,
7745 svn_client_ctx_t *ctx,
7746 svn_boolean_t merge_dirs,
7747 apr_pool_t *scratch_pool)
7749 svn_ra_session_t *ra_session;
7751 const char *corrected_url;
7752 const char *repos_root_url;
7753 const char *incoming_new_repos_relpath;
7754 svn_revnum_t incoming_new_pegrev;
7755 const char *local_abspath;
7756 const char *lock_abspath;
7757 const char *tmpdir_abspath, *tmp_abspath;
7759 svn_revnum_t copy_src_revnum;
7760 svn_opt_revision_t copy_src_peg_revision;
7761 svn_boolean_t timestamp_sleep;
7762 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
7763 void *old_notify_baton2 = ctx->notify_baton2;
7764 struct notification_adjust_baton nb;
7766 local_abspath = svn_client_conflict_get_local_abspath(conflict);
7768 /* Find the URL of the incoming added directory in the repository. */
7769 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
7770 &incoming_new_repos_relpath, &incoming_new_pegrev,
7771 NULL, conflict, scratch_pool,
7773 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7774 conflict, scratch_pool,
7776 url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
7778 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7779 url, NULL, NULL, FALSE, FALSE,
7783 url = corrected_url;
7786 /* Find a temporary location in which to check out the copy source. */
7787 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, local_abspath,
7788 scratch_pool, scratch_pool));
7790 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
7791 svn_io_file_del_on_close,
7792 scratch_pool, scratch_pool));
7794 /* Make a new checkout of the requested source. While doing so,
7795 * resolve copy_src_revnum to an actual revision number in case it
7796 * was until now 'invalid' meaning 'head'. Ask this function not to
7797 * sleep for timestamps, by passing a sleep_needed output param.
7798 * Send notifications for all nodes except the root node, and adjust
7799 * them to refer to the destination rather than this temporary path. */
7801 nb.inner_func = ctx->notify_func2;
7802 nb.inner_baton = ctx->notify_baton2;
7803 nb.checkout_abspath = tmp_abspath;
7804 nb.final_abspath = local_abspath;
7805 ctx->notify_func2 = notification_adjust_func;
7806 ctx->notify_baton2 = &nb;
7808 copy_src_peg_revision.kind = svn_opt_revision_number;
7809 copy_src_peg_revision.value.number = incoming_new_pegrev;
7811 err = svn_client__checkout_internal(©_src_revnum, ×tamp_sleep,
7813 ©_src_peg_revision,
7814 ©_src_peg_revision,
7816 TRUE, /* we want to ignore externals */
7817 FALSE, /* we don't allow obstructions */
7818 ra_session, ctx, scratch_pool);
7820 ctx->notify_func2 = old_notify_func2;
7821 ctx->notify_baton2 = old_notify_baton2;
7825 /* ### The following WC modifications should be atomic. */
7827 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7831 scratch_pool, scratch_pool));
7833 /* Remove the working directory. */
7834 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
7835 NULL, NULL, /* don't allow user to cancel here */
7836 ctx->notify_func2, ctx->notify_baton2,
7841 /* Schedule dst_path for addition in parent, with copy history.
7842 Don't send any notification here.
7843 Then remove the temporary checkout's .svn dir in preparation for
7844 moving the rest of it into the final destination. */
7845 err = svn_wc_copy3(ctx->wc_ctx, tmp_abspath, local_abspath,
7846 TRUE /* metadata_only */,
7847 NULL, NULL, /* don't allow user to cancel here */
7848 NULL, NULL, scratch_pool);
7852 err = svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
7853 FALSE, scratch_pool, scratch_pool);
7856 err = svn_wc_remove_from_revision_control2(ctx->wc_ctx,
7859 NULL, NULL, /* don't cancel */
7864 /* Move the temporary disk tree into place. */
7865 err = svn_io_file_rename2(tmp_abspath, local_abspath, FALSE, scratch_pool);
7869 if (ctx->notify_func2)
7871 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
7874 notify->kind = svn_node_dir;
7875 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7878 /* Resolve to current working copy state.
7879 * svn_client__merge_locked() requires this. */
7880 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7886 svn_revnum_t base_revision;
7887 const char *base_repos_relpath;
7888 struct find_added_rev_baton b = { 0 };
7890 /* Find the URL and revision of the directory we have just replaced. */
7891 err = svn_wc__node_get_base(NULL, &base_revision, &base_repos_relpath,
7892 NULL, NULL, NULL, ctx->wc_ctx, local_abspath,
7893 FALSE, scratch_pool, scratch_pool);
7897 url = svn_path_url_add_component2(repos_root_url, base_repos_relpath,
7900 /* Trace the replaced directory's history to its origin. */
7901 err = svn_ra_reparent(ra_session, url, scratch_pool);
7904 b.victim_abspath = local_abspath;
7906 b.added_rev = SVN_INVALID_REVNUM;
7907 b.repos_relpath = NULL;
7908 b.parent_repos_relpath = svn_relpath_dirname(base_repos_relpath,
7910 b.pool = scratch_pool;
7912 err = svn_ra_get_location_segments(ra_session, "", base_revision,
7913 base_revision, SVN_INVALID_REVNUM,
7919 if (b.added_rev == SVN_INVALID_REVNUM)
7921 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7922 _("Could not determine the revision in "
7923 "which '^/%s' was added to the "
7925 base_repos_relpath);
7929 /* Merge the replaced directory into the directory which replaced it.
7930 * We do not need to consider a reverse-merge here since the source of
7931 * this merge was part of the merge target working copy, not a branch
7932 * in the repository. */
7933 err = merge_newly_added_dir(base_repos_relpath,
7934 url, rev_below(b.added_rev), url,
7935 base_revision, local_abspath, FALSE,
7936 ctx, scratch_pool, scratch_pool);
7942 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7945 svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7948 if (ctx->notify_func2)
7950 svn_wc_notify_t *notify = svn_wc_create_notify(
7952 svn_wc_notify_resolved_tree,
7955 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7958 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7960 return SVN_NO_ERROR;
7963 /* Implements conflict_option_resolve_func_t. */
7964 static svn_error_t *
7965 resolve_merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
7966 svn_client_conflict_t *conflict,
7967 svn_client_ctx_t *ctx,
7968 apr_pool_t *scratch_pool)
7970 return svn_error_trace(merge_incoming_added_dir_replace(option,
7977 /* Implements conflict_option_resolve_func_t. */
7978 static svn_error_t *
7979 resolve_merge_incoming_added_dir_replace_and_merge(
7980 svn_client_conflict_option_t *option,
7981 svn_client_conflict_t *conflict,
7982 svn_client_ctx_t *ctx,
7983 apr_pool_t *scratch_pool)
7985 return svn_error_trace(merge_incoming_added_dir_replace(option,
7992 /* Verify the local working copy state matches what we expect when an
7993 * incoming deletion tree conflict exists.
7994 * We assume update/merge/switch operations leave the working copy in a
7995 * state which prefers the local change and cancels the deletion.
7996 * Run a quick sanity check and error out if it looks as if the
7997 * working copy was modified since, even though it's not easy to make
7998 * such modifications without also clearing the conflict marker. */
7999 static svn_error_t *
8000 verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict,
8001 svn_client_conflict_option_t *option,
8002 svn_client_ctx_t *ctx,
8003 apr_pool_t *scratch_pool)
8005 const char *local_abspath;
8006 const char *wcroot_abspath;
8007 svn_wc_operation_t operation;
8009 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8010 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
8011 local_abspath, scratch_pool,
8013 operation = svn_client_conflict_get_operation(conflict);
8015 if (operation == svn_wc_operation_update ||
8016 operation == svn_wc_operation_switch)
8018 struct conflict_tree_incoming_delete_details *details;
8019 svn_boolean_t is_copy;
8020 svn_revnum_t copyfrom_rev;
8021 const char *copyfrom_repos_relpath;
8023 details = conflict->tree_conflict_incoming_details;
8024 if (details == NULL)
8025 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8026 _("Conflict resolution option '%d' requires "
8027 "details for tree conflict at '%s' to be "
8028 "fetched from the repository."),
8030 svn_dirent_local_style(local_abspath,
8033 /* Ensure that the item is a copy of itself from before it was deleted.
8034 * Update and switch are supposed to set this up when flagging the
8036 SVN_ERR(svn_wc__node_get_origin(&is_copy, ©from_rev,
8037 ©from_repos_relpath,
8038 NULL, NULL, NULL, NULL,
8039 ctx->wc_ctx, local_abspath, FALSE,
8040 scratch_pool, scratch_pool));
8042 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8043 _("Cannot resolve tree conflict on '%s' "
8044 "(expected a copied item, but the item "
8046 svn_dirent_local_style(
8047 svn_dirent_skip_ancestor(
8049 conflict->local_abspath),
8051 else if (details->deleted_rev == SVN_INVALID_REVNUM &&
8052 details->added_rev == SVN_INVALID_REVNUM)
8053 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8054 _("Could not find the revision in which '%s' "
8055 "was deleted from the repository"),
8056 svn_dirent_local_style(
8057 svn_dirent_skip_ancestor(
8059 conflict->local_abspath),
8061 else if (details->deleted_rev != SVN_INVALID_REVNUM &&
8062 copyfrom_rev >= details->deleted_rev)
8063 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8064 _("Cannot resolve tree conflict on '%s' "
8065 "(expected an item copied from a revision "
8066 "smaller than r%ld, but the item was "
8067 "copied from r%ld)"),
8068 svn_dirent_local_style(
8069 svn_dirent_skip_ancestor(
8070 wcroot_abspath, conflict->local_abspath),
8072 details->deleted_rev, copyfrom_rev);
8074 else if (details->added_rev != SVN_INVALID_REVNUM &&
8075 copyfrom_rev < details->added_rev)
8076 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8077 _("Cannot resolve tree conflict on '%s' "
8078 "(expected an item copied from a revision "
8079 "larger than r%ld, but the item was "
8080 "copied from r%ld)"),
8081 svn_dirent_local_style(
8082 svn_dirent_skip_ancestor(
8083 wcroot_abspath, conflict->local_abspath),
8085 details->added_rev, copyfrom_rev);
8086 else if (operation == svn_wc_operation_update)
8088 const char *old_repos_relpath;
8090 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8091 &old_repos_relpath, NULL, NULL, conflict,
8092 scratch_pool, scratch_pool));
8093 if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 &&
8094 strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
8095 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8096 _("Cannot resolve tree conflict on '%s' "
8097 "(expected an item copied from '^/%s' "
8098 "or from '^/%s' but the item was "
8099 "copied from '^/%s@%ld')"),
8100 svn_dirent_local_style(
8101 svn_dirent_skip_ancestor(
8102 wcroot_abspath, conflict->local_abspath),
8104 details->repos_relpath,
8106 copyfrom_repos_relpath, copyfrom_rev);
8108 else if (operation == svn_wc_operation_switch)
8110 const char *old_repos_relpath;
8112 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8113 &old_repos_relpath, NULL, NULL, conflict,
8114 scratch_pool, scratch_pool));
8116 if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
8117 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8118 _("Cannot resolve tree conflict on '%s' "
8119 "(expected an item copied from '^/%s', "
8120 "but the item was copied from "
8122 svn_dirent_local_style(
8123 svn_dirent_skip_ancestor(
8125 conflict->local_abspath),
8128 copyfrom_repos_relpath, copyfrom_rev);
8131 else if (operation == svn_wc_operation_merge)
8133 svn_node_kind_t victim_node_kind;
8134 svn_node_kind_t on_disk_kind;
8136 /* For merge, all we can do is ensure that the item still exists. */
8138 svn_client_conflict_tree_get_victim_node_kind(conflict);
8139 SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
8141 if (victim_node_kind != on_disk_kind)
8142 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8143 _("Cannot resolve tree conflict on '%s' "
8144 "(expected node kind '%s' but found '%s')"),
8145 svn_dirent_local_style(
8146 svn_dirent_skip_ancestor(
8147 wcroot_abspath, conflict->local_abspath),
8149 svn_node_kind_to_word(victim_node_kind),
8150 svn_node_kind_to_word(on_disk_kind));
8153 return SVN_NO_ERROR;
8156 /* Implements conflict_option_resolve_func_t. */
8157 static svn_error_t *
8158 resolve_incoming_delete_ignore(svn_client_conflict_option_t *option,
8159 svn_client_conflict_t *conflict,
8160 svn_client_ctx_t *ctx,
8161 apr_pool_t *scratch_pool)
8163 svn_client_conflict_option_id_t option_id;
8164 const char *local_abspath;
8165 const char *lock_abspath;
8168 option_id = svn_client_conflict_option_get_id(option);
8169 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8171 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8173 scratch_pool, scratch_pool));
8175 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8180 /* Resolve to the current working copy state. */
8181 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
8183 /* svn_wc__del_tree_conflict doesn't handle notification for us */
8184 if (ctx->notify_func2)
8185 ctx->notify_func2(ctx->notify_baton2,
8186 svn_wc_create_notify(local_abspath,
8187 svn_wc_notify_resolved_tree,
8192 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8197 conflict->resolution_tree = option_id;
8199 return SVN_NO_ERROR;
8202 /* Implements conflict_option_resolve_func_t. */
8203 static svn_error_t *
8204 resolve_incoming_delete_accept(svn_client_conflict_option_t *option,
8205 svn_client_conflict_t *conflict,
8206 svn_client_ctx_t *ctx,
8207 apr_pool_t *scratch_pool)
8209 svn_client_conflict_option_id_t option_id;
8210 const char *local_abspath;
8211 const char *parent_abspath;
8212 const char *lock_abspath;
8215 option_id = svn_client_conflict_option_get_id(option);
8216 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8218 /* Deleting a node requires a lock on the node's parent. */
8219 parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
8220 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8222 scratch_pool, scratch_pool));
8224 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8229 /* Delete the tree conflict victim. Marks the conflict resolved. */
8230 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
8231 NULL, NULL, /* don't allow user to cancel here */
8232 ctx->notify_func2, ctx->notify_baton2,
8236 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
8238 /* Not a versioned path. This can happen if the victim has already
8239 * been deleted in our branche's history, for example. Either way,
8240 * the item is gone, which is what we want, so don't treat this as
8242 svn_error_clear(err);
8244 /* Resolve to current working copy state. */
8245 err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath,
8253 if (ctx->notify_func2)
8254 ctx->notify_func2(ctx->notify_baton2,
8255 svn_wc_create_notify(local_abspath,
8256 svn_wc_notify_resolved_tree,
8261 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8266 conflict->resolution_tree = option_id;
8268 return SVN_NO_ERROR;
8271 /* Implements conflict_option_resolve_func_t. */
8272 static svn_error_t *
8273 resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
8274 svn_client_conflict_t *conflict,
8275 svn_client_ctx_t *ctx,
8276 apr_pool_t *scratch_pool)
8278 svn_client_conflict_option_id_t option_id;
8279 const char *local_abspath;
8280 svn_wc_operation_t operation;
8281 const char *lock_abspath;
8283 const char *repos_root_url;
8284 const char *incoming_old_repos_relpath;
8285 svn_revnum_t incoming_old_pegrev;
8286 const char *incoming_new_repos_relpath;
8287 svn_revnum_t incoming_new_pegrev;
8288 const char *wc_tmpdir;
8289 const char *ancestor_abspath;
8290 svn_stream_t *ancestor_stream;
8291 apr_hash_t *ancestor_props;
8292 apr_hash_t *victim_props;
8293 apr_hash_t *move_target_props;
8294 const char *ancestor_url;
8295 const char *corrected_url;
8296 svn_ra_session_t *ra_session;
8297 svn_wc_merge_outcome_t merge_content_outcome;
8298 svn_wc_notify_state_t merge_props_outcome;
8299 apr_array_header_t *propdiffs;
8300 struct conflict_tree_incoming_delete_details *details;
8301 apr_array_header_t *possible_moved_to_abspaths;
8302 const char *moved_to_abspath;
8303 const char *incoming_abspath = NULL;
8305 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8306 operation = svn_client_conflict_get_operation(conflict);
8307 details = conflict->tree_conflict_incoming_details;
8308 if (details == NULL || details->moves == NULL)
8309 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8310 _("The specified conflict resolution option "
8311 "requires details for tree conflict at '%s' "
8312 "to be fetched from the repository first."),
8313 svn_dirent_local_style(local_abspath,
8315 if (operation == svn_wc_operation_none)
8316 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
8317 _("Invalid operation code '%d' recorded for "
8318 "conflict at '%s'"), operation,
8319 svn_dirent_local_style(local_abspath,
8322 option_id = svn_client_conflict_option_get_id(option);
8323 SVN_ERR_ASSERT(option_id ==
8324 svn_client_conflict_option_incoming_move_file_text_merge ||
8326 svn_client_conflict_option_incoming_move_dir_merge);
8328 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8329 conflict, scratch_pool,
8331 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8332 &incoming_old_repos_relpath, &incoming_old_pegrev,
8333 NULL, conflict, scratch_pool,
8335 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8336 &incoming_new_repos_relpath, &incoming_new_pegrev,
8337 NULL, conflict, scratch_pool,
8340 /* Set up temporary storage for the common ancestor version of the file. */
8341 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
8342 scratch_pool, scratch_pool));
8343 SVN_ERR(svn_stream_open_unique(&ancestor_stream,
8344 &ancestor_abspath, wc_tmpdir,
8345 svn_io_file_del_on_pool_cleanup,
8346 scratch_pool, scratch_pool));
8348 /* Fetch the ancestor file's content. */
8349 ancestor_url = svn_path_url_add_component2(repos_root_url,
8350 incoming_old_repos_relpath,
8352 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8353 ancestor_url, NULL, NULL,
8355 scratch_pool, scratch_pool));
8356 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
8357 ancestor_stream, NULL, /* fetched_rev */
8358 &ancestor_props, scratch_pool));
8359 filter_props(ancestor_props, scratch_pool);
8361 /* Close stream to flush ancestor file to disk. */
8362 SVN_ERR(svn_stream_close(ancestor_stream));
8364 possible_moved_to_abspaths =
8365 svn_hash_gets(details->wc_move_targets,
8366 get_moved_to_repos_relpath(details, scratch_pool));
8367 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths,
8368 details->wc_move_target_idx,
8371 /* ### The following WC modifications should be atomic. */
8372 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
8373 &lock_abspath, ctx->wc_ctx,
8374 svn_dirent_get_longest_ancestor(local_abspath,
8377 scratch_pool, scratch_pool));
8379 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8384 /* Get a copy of the conflict victim's properties. */
8385 err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, local_abspath,
8386 scratch_pool, scratch_pool);
8390 /* Get a copy of the move target's properties. */
8391 err = svn_wc_prop_list2(&move_target_props, ctx->wc_ctx,
8393 scratch_pool, scratch_pool);
8397 /* Create a property diff for the files. */
8398 err = svn_prop_diffs(&propdiffs, move_target_props, victim_props,
8403 if (operation == svn_wc_operation_update ||
8404 operation == svn_wc_operation_switch)
8406 svn_stream_t *working_stream;
8407 svn_stream_t *incoming_stream;
8409 /* Create a temporary copy of the working file in repository-normal form.
8410 * Set up this temporary file to be automatically removed. */
8411 err = svn_stream_open_unique(&incoming_stream,
8412 &incoming_abspath, wc_tmpdir,
8413 svn_io_file_del_on_pool_cleanup,
8414 scratch_pool, scratch_pool);
8418 err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx,
8419 local_abspath, local_abspath,
8420 SVN_WC_TRANSLATE_TO_NF,
8421 scratch_pool, scratch_pool);
8425 err = svn_stream_copy3(working_stream, incoming_stream,
8426 NULL, NULL, /* no cancellation */
8431 else if (operation == svn_wc_operation_merge)
8433 svn_stream_t *incoming_stream;
8434 svn_stream_t *move_target_stream;
8436 /* Set aside the current move target file. This is required to apply
8437 * the move, and only then perform a three-way text merge between
8438 * the ancestor's file, our working file (which we would move to
8439 * the destination), and the file that we have set aside, which
8440 * contains the incoming fulltext.
8441 * Set up this temporary file to NOT be automatically removed. */
8442 err = svn_stream_open_unique(&incoming_stream,
8443 &incoming_abspath, wc_tmpdir,
8444 svn_io_file_del_none,
8445 scratch_pool, scratch_pool);
8449 err = svn_wc__translated_stream(&move_target_stream, ctx->wc_ctx,
8450 moved_to_abspath, moved_to_abspath,
8451 SVN_WC_TRANSLATE_TO_NF,
8452 scratch_pool, scratch_pool);
8456 err = svn_stream_copy3(move_target_stream, incoming_stream,
8457 NULL, NULL, /* no cancellation */
8462 /* Apply the incoming move. */
8463 err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool);
8466 err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
8467 FALSE, /* ordinary (not meta-data only) move */
8468 FALSE, /* mixed-revisions don't apply to files */
8469 NULL, NULL, /* don't allow user to cancel here */
8470 NULL, NULL, /* no extra notification */
8476 SVN_ERR_MALFUNCTION();
8478 /* Perform the file merge. */
8479 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
8480 ctx->wc_ctx, ancestor_abspath,
8481 incoming_abspath, moved_to_abspath,
8482 NULL, NULL, NULL, /* labels */
8483 NULL, NULL, /* conflict versions */
8484 FALSE, /* dry run */
8485 NULL, NULL, /* diff3_cmd, merge_options */
8486 apr_hash_count(ancestor_props) ? ancestor_props : NULL,
8488 NULL, NULL, /* conflict func/baton */
8489 NULL, NULL, /* don't allow user to cancel here */
8491 svn_io_sleep_for_timestamps(moved_to_abspath, scratch_pool);
8495 if (operation == svn_wc_operation_merge && incoming_abspath)
8497 err = svn_io_remove_file2(incoming_abspath, TRUE, scratch_pool);
8500 incoming_abspath = NULL;
8503 if (ctx->notify_func2)
8505 svn_wc_notify_t *notify;
8507 /* Tell the world about the file merge that just happened. */
8508 notify = svn_wc_create_notify(moved_to_abspath,
8509 svn_wc_notify_update_update,
8511 if (merge_content_outcome == svn_wc_merge_conflict)
8512 notify->content_state = svn_wc_notify_state_conflicted;
8514 notify->content_state = svn_wc_notify_state_merged;
8515 notify->prop_state = merge_props_outcome;
8516 notify->kind = svn_node_file;
8517 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8520 if (operation == svn_wc_operation_update ||
8521 operation == svn_wc_operation_switch)
8523 /* Delete the tree conflict victim (clears the tree conflict marker). */
8524 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
8525 NULL, NULL, /* don't allow user to cancel here */
8526 NULL, NULL, /* no extra notification */
8532 if (ctx->notify_func2)
8534 svn_wc_notify_t *notify;
8536 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
8538 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8541 conflict->resolution_tree = option_id;
8544 if (err && operation == svn_wc_operation_merge && incoming_abspath)
8545 err = svn_error_quick_wrapf(
8546 err, _("If needed, a backup copy of '%s' can be found at '%s'"),
8547 svn_dirent_local_style(moved_to_abspath, scratch_pool),
8548 svn_dirent_local_style(incoming_abspath, scratch_pool));
8549 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8554 return SVN_NO_ERROR;
8557 /* Implements conflict_option_resolve_func_t. */
8558 static svn_error_t *
8559 resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
8560 svn_client_conflict_t *conflict,
8561 svn_client_ctx_t *ctx,
8562 apr_pool_t *scratch_pool)
8564 svn_client_conflict_option_id_t option_id;
8565 const char *local_abspath;
8566 svn_wc_operation_t operation;
8567 const char *lock_abspath;
8569 const char *repos_root_url;
8570 const char *repos_uuid;
8571 const char *incoming_old_repos_relpath;
8572 svn_revnum_t incoming_old_pegrev;
8573 const char *incoming_new_repos_relpath;
8574 svn_revnum_t incoming_new_pegrev;
8575 const char *victim_repos_relpath;
8576 svn_revnum_t victim_peg_rev;
8577 const char *moved_to_repos_relpath;
8578 svn_revnum_t moved_to_peg_rev;
8579 struct conflict_tree_incoming_delete_details *details;
8580 apr_array_header_t *possible_moved_to_abspaths;
8581 const char *moved_to_abspath;
8582 svn_client__pathrev_t *yca_loc;
8583 svn_opt_revision_t yca_opt_rev;
8584 svn_client__conflict_report_t *conflict_report;
8585 svn_boolean_t is_copy;
8586 svn_boolean_t is_modified;
8588 local_abspath = svn_client_conflict_get_local_abspath(conflict);
8589 operation = svn_client_conflict_get_operation(conflict);
8590 details = conflict->tree_conflict_incoming_details;
8591 if (details == NULL || details->moves == NULL)
8592 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8593 _("The specified conflict resolution option "
8594 "requires details for tree conflict at '%s' "
8595 "to be fetched from the repository first."),
8596 svn_dirent_local_style(local_abspath,
8599 option_id = svn_client_conflict_option_get_id(option);
8600 SVN_ERR_ASSERT(option_id ==
8601 svn_client_conflict_option_incoming_move_dir_merge);
8603 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
8604 conflict, scratch_pool,
8606 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8607 &incoming_old_repos_relpath, &incoming_old_pegrev,
8608 NULL, conflict, scratch_pool,
8610 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8611 &incoming_new_repos_relpath, &incoming_new_pegrev,
8612 NULL, conflict, scratch_pool,
8615 /* Get repository location of the moved-away node (the conflict victim). */
8616 if (operation == svn_wc_operation_update ||
8617 operation == svn_wc_operation_switch)
8619 victim_repos_relpath = incoming_old_repos_relpath;
8620 victim_peg_rev = incoming_old_pegrev;
8622 else if (operation == svn_wc_operation_merge)
8623 SVN_ERR(svn_wc__node_get_repos_info(&victim_peg_rev, &victim_repos_relpath,
8624 NULL, NULL, ctx->wc_ctx, local_abspath,
8625 scratch_pool, scratch_pool));
8627 /* Get repository location of the moved-here node (incoming move). */
8628 possible_moved_to_abspaths =
8629 svn_hash_gets(details->wc_move_targets,
8630 get_moved_to_repos_relpath(details, scratch_pool));
8631 moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths,
8632 details->wc_move_target_idx,
8635 /* ### The following WC modifications should be atomic. */
8637 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
8638 &lock_abspath, ctx->wc_ctx,
8639 svn_dirent_get_longest_ancestor(local_abspath,
8642 scratch_pool, scratch_pool));
8644 err = svn_wc__node_get_origin(&is_copy, &moved_to_peg_rev,
8645 &moved_to_repos_relpath,
8646 NULL, NULL, NULL, NULL,
8647 ctx->wc_ctx, moved_to_abspath, FALSE,
8648 scratch_pool, scratch_pool);
8651 if (!is_copy && operation == svn_wc_operation_merge)
8653 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8654 _("Cannot resolve tree conflict on '%s' "
8655 "(expected a copied item at '%s', but the "
8656 "item is not a copy)"),
8657 svn_dirent_local_style(local_abspath,
8659 svn_dirent_local_style(moved_to_abspath,
8664 if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM)
8666 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8667 _("Cannot resolve tree conflict on '%s' "
8668 "(could not determine origin of '%s')"),
8669 svn_dirent_local_style(local_abspath,
8671 svn_dirent_local_style(moved_to_abspath,
8676 /* Now find the youngest common ancestor of these nodes. */
8677 err = find_yca(&yca_loc, victim_repos_relpath, victim_peg_rev,
8678 moved_to_repos_relpath, moved_to_peg_rev,
8679 repos_root_url, repos_uuid,
8680 NULL, ctx, scratch_pool, scratch_pool);
8684 if (yca_loc == NULL)
8686 err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8687 _("Cannot resolve tree conflict on '%s' "
8688 "(could not find common ancestor of '^/%s@%ld' "
8689 " and '^/%s@%ld')"),
8690 svn_dirent_local_style(local_abspath,
8692 victim_repos_relpath, victim_peg_rev,
8693 moved_to_repos_relpath, moved_to_peg_rev);
8697 yca_opt_rev.kind = svn_opt_revision_number;
8698 yca_opt_rev.value.number = yca_loc->rev;
8700 err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8705 if (operation == svn_wc_operation_merge)
8707 const char *move_target_url;
8708 svn_opt_revision_t incoming_new_opt_rev;
8710 /* Revert the incoming move target directory. */
8711 SVN_ERR(svn_wc_revert5(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity,
8712 FALSE, NULL, TRUE, FALSE,
8713 NULL, NULL, /* no cancellation */
8714 ctx->notify_func2, ctx->notify_baton2,
8717 /* The move operation is not part of natural history. We must replicate
8718 * this move in our history. Record a move in the working copy. */
8719 err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
8720 FALSE, /* this is not a meta-data only move */
8721 TRUE, /* allow mixed-revisions just in case */
8722 NULL, NULL, /* don't allow user to cancel here */
8723 ctx->notify_func2, ctx->notify_baton2,
8728 /* Merge YCA_URL@YCA_REV->MOVE_TARGET_URL@MERGE_RIGHT into move target. */
8729 move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
8730 get_moved_to_repos_relpath(details,
8733 incoming_new_opt_rev.kind = svn_opt_revision_number;
8734 incoming_new_opt_rev.value.number = incoming_new_pegrev;
8735 err = svn_client__merge_locked(&conflict_report,
8736 yca_loc->url, &yca_opt_rev,
8737 move_target_url, &incoming_new_opt_rev,
8738 moved_to_abspath, svn_depth_infinity,
8739 TRUE, TRUE, /* do a no-ancestry merge */
8740 FALSE, FALSE, FALSE,
8741 TRUE, /* Allow mixed-rev just in case,
8742 * since conflict victims can't be
8743 * updated to straighten out
8744 * mixed-rev trees. */
8745 NULL, ctx, scratch_pool, scratch_pool);
8751 SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
8752 operation == svn_wc_operation_switch);
8754 /* Merge local modifications into the incoming move target dir. */
8755 err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath,
8756 TRUE, ctx->cancel_func, ctx->cancel_baton,
8763 err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx,
8775 /* The move operation is part of our natural history.
8776 * Delete the tree conflict victim (clears the tree conflict marker). */
8777 err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
8778 NULL, NULL, /* don't allow user to cancel here */
8779 NULL, NULL, /* no extra notification */
8785 if (ctx->notify_func2)
8787 svn_wc_notify_t *notify;
8789 notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
8791 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8794 conflict->resolution_tree = option_id;
8797 err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8802 return SVN_NO_ERROR;
8805 /* Implements conflict_option_resolve_func_t. */
8806 static svn_error_t *
8807 resolve_local_move_file_merge(svn_client_conflict_option_t *option,
8808 svn_client_conflict_t *conflict,
8809 svn_client_ctx_t *ctx,
8810 apr_pool_t *scratch_pool)
8812 const char *lock_abspath;
8814 const char *repos_root_url;
8815 const char *incoming_old_repos_relpath;
8816 svn_revnum_t incoming_old_pegrev;
8817 const char *incoming_new_repos_relpath;
8818 svn_revnum_t incoming_new_pegrev;
8819 const char *wc_tmpdir;
8820 const char *ancestor_tmp_abspath;
8821 const char *incoming_tmp_abspath;
8822 apr_hash_t *ancestor_props;
8823 apr_hash_t *incoming_props;
8824 svn_stream_t *stream;
8826 const char *corrected_url;
8827 const char *old_session_url;
8828 svn_ra_session_t *ra_session;
8829 svn_wc_merge_outcome_t merge_content_outcome;
8830 svn_wc_notify_state_t merge_props_outcome;
8831 apr_array_header_t *propdiffs;
8832 struct conflict_tree_local_missing_details *details;
8834 details = conflict->tree_conflict_local_details;
8836 SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8837 conflict, scratch_pool,
8839 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8840 &incoming_old_repos_relpath, &incoming_old_pegrev,
8841 NULL, conflict, scratch_pool,
8843 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8844 &incoming_new_repos_relpath, &incoming_new_pegrev,
8845 NULL, conflict, scratch_pool,
8848 SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx,
8849 details->moved_to_abspath,
8850 scratch_pool, scratch_pool));
8852 /* Fetch the common ancestor file's content. */
8853 SVN_ERR(svn_stream_open_unique(&stream, &ancestor_tmp_abspath, wc_tmpdir,
8854 svn_io_file_del_on_pool_cleanup,
8855 scratch_pool, scratch_pool));
8856 url = svn_path_url_add_component2(repos_root_url,
8857 incoming_old_repos_relpath,
8859 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8862 scratch_pool, scratch_pool));
8863 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, stream, NULL,
8864 &ancestor_props, scratch_pool));
8865 filter_props(ancestor_props, scratch_pool);
8867 /* Close stream to flush the file to disk. */
8868 SVN_ERR(svn_stream_close(stream));
8870 /* Do the same for the incoming file's content. */
8871 SVN_ERR(svn_stream_open_unique(&stream, &incoming_tmp_abspath, wc_tmpdir,
8872 svn_io_file_del_on_pool_cleanup,
8873 scratch_pool, scratch_pool));
8874 url = svn_path_url_add_component2(repos_root_url,
8875 incoming_new_repos_relpath,
8877 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
8878 url, scratch_pool));
8879 SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, stream, NULL,
8880 &incoming_props, scratch_pool));
8881 /* Close stream to flush the file to disk. */
8882 SVN_ERR(svn_stream_close(stream));
8884 filter_props(incoming_props, scratch_pool);
8886 /* Create a property diff for the files. */
8887 SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props,
8890 /* ### The following WC modifications should be atomic. */
8891 SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
8892 &lock_abspath, ctx->wc_ctx,
8893 svn_dirent_get_longest_ancestor(conflict->local_abspath,
8894 details->moved_to_abspath,
8896 scratch_pool, scratch_pool));
8898 /* Perform the file merge. */
8899 err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
8901 ancestor_tmp_abspath, incoming_tmp_abspath,
8902 details->moved_to_abspath,
8903 NULL, NULL, NULL, /* labels */
8904 NULL, NULL, /* conflict versions */
8905 FALSE, /* dry run */
8906 NULL, NULL, /* diff3_cmd, merge_options */
8907 apr_hash_count(ancestor_props) ? ancestor_props : NULL,
8909 NULL, NULL, /* conflict func/baton */
8910 NULL, NULL, /* don't allow user to cancel here */
8912 svn_io_sleep_for_timestamps(details->moved_to_abspath, scratch_pool);
8914 return svn_error_compose_create(err,
8915 svn_wc__release_write_lock(ctx->wc_ctx,
8919 err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
8921 err = svn_error_compose_create(err,
8922 svn_wc__release_write_lock(ctx->wc_ctx,
8926 return svn_error_trace(err);
8928 if (ctx->notify_func2)
8930 svn_wc_notify_t *notify;
8932 /* Tell the world about the file merge that just happened. */
8933 notify = svn_wc_create_notify(details->moved_to_abspath,
8934 svn_wc_notify_update_update,
8936 if (merge_content_outcome == svn_wc_merge_conflict)
8937 notify->content_state = svn_wc_notify_state_conflicted;
8939 notify->content_state = svn_wc_notify_state_merged;
8940 notify->prop_state = merge_props_outcome;
8941 notify->kind = svn_node_file;
8942 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8944 /* And also about the successfully resolved tree conflict. */
8945 notify = svn_wc_create_notify(conflict->local_abspath,
8946 svn_wc_notify_resolved_tree,
8948 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8951 conflict->resolution_tree = svn_client_conflict_option_get_id(option);
8953 return SVN_NO_ERROR;
8956 static svn_error_t *
8957 assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
8959 svn_boolean_t text_conflicted;
8961 SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL,
8962 conflict, scratch_pool,
8965 SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */
8967 return SVN_NO_ERROR;
8970 static svn_error_t *
8971 assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
8973 apr_array_header_t *props_conflicted;
8975 SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
8976 conflict, scratch_pool,
8979 /* ### return proper error? */
8980 SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0);
8982 return SVN_NO_ERROR;
8985 static svn_error_t *
8986 assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
8988 svn_boolean_t tree_conflicted;
8990 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
8991 conflict, scratch_pool,
8994 SVN_ERR_ASSERT(tree_conflicted); /* ### return proper error? */
8996 return SVN_NO_ERROR;
8999 /* Helper to add to conflict resolution option to array of OPTIONS.
9000 * Resolution option object will be allocated from OPTIONS->POOL
9001 * and DESCRIPTION will be copied to this pool.
9002 * Returns pointer to the created conflict resolution option. */
9003 static svn_client_conflict_option_t *
9004 add_resolution_option(apr_array_header_t *options,
9005 svn_client_conflict_t *conflict,
9006 svn_client_conflict_option_id_t id,
9008 const char *description,
9009 conflict_option_resolve_func_t resolve_func)
9011 svn_client_conflict_option_t *option;
9013 option = apr_pcalloc(options->pool, sizeof(*option));
9014 option->pool = options->pool;
9016 option->label = apr_pstrdup(option->pool, label);
9017 option->description = apr_pstrdup(option->pool, description);
9018 option->conflict = conflict;
9019 option->do_resolve_func = resolve_func;
9021 APR_ARRAY_PUSH(options, const svn_client_conflict_option_t *) = option;
9027 svn_client_conflict_text_get_resolution_options(apr_array_header_t **options,
9028 svn_client_conflict_t *conflict,
9029 svn_client_ctx_t *ctx,
9030 apr_pool_t *result_pool,
9031 apr_pool_t *scratch_pool)
9033 const char *mime_type;
9035 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
9037 *options = apr_array_make(result_pool, 7,
9038 sizeof(svn_client_conflict_option_t *));
9040 add_resolution_option(*options, conflict,
9041 svn_client_conflict_option_postpone,
9043 _("skip this conflict and leave it unresolved"),
9046 mime_type = svn_client_conflict_text_get_mime_type(conflict);
9047 if (mime_type && svn_mime_type_is_binary(mime_type))
9049 /* Resolver options for a binary file conflict. */
9050 add_resolution_option(*options, conflict,
9051 svn_client_conflict_option_base_text,
9053 _("discard local and incoming changes for this binary file"),
9054 resolve_text_conflict);
9056 add_resolution_option(*options, conflict,
9057 svn_client_conflict_option_incoming_text,
9058 _("Accept incoming"),
9059 _("accept incoming version of binary file"),
9060 resolve_text_conflict);
9062 add_resolution_option(*options, conflict,
9063 svn_client_conflict_option_working_text,
9064 _("Mark as resolved"),
9065 _("accept binary file as it appears in the working copy"),
9066 resolve_text_conflict);
9070 /* Resolver options for a text file conflict. */
9071 add_resolution_option(*options, conflict,
9072 svn_client_conflict_option_base_text,
9074 _("discard local and incoming changes for this file"),
9075 resolve_text_conflict);
9077 add_resolution_option(*options, conflict,
9078 svn_client_conflict_option_incoming_text,
9079 _("Accept incoming"),
9080 _("accept incoming version of entire file"),
9081 resolve_text_conflict);
9083 add_resolution_option(*options, conflict,
9084 svn_client_conflict_option_working_text,
9085 _("Reject incoming"),
9086 _("reject all incoming changes for this file"),
9087 resolve_text_conflict);
9089 add_resolution_option(*options, conflict,
9090 svn_client_conflict_option_incoming_text_where_conflicted,
9091 _("Accept incoming for conflicts"),
9092 _("accept changes only where they conflict"),
9093 resolve_text_conflict);
9095 add_resolution_option(*options, conflict,
9096 svn_client_conflict_option_working_text_where_conflicted,
9097 _("Reject conflicts"),
9098 _("reject changes which conflict and accept the rest"),
9099 resolve_text_conflict);
9101 add_resolution_option(*options, conflict,
9102 svn_client_conflict_option_merged_text,
9103 _("Mark as resolved"),
9104 _("accept the file as it appears in the working copy"),
9105 resolve_text_conflict);
9108 return SVN_NO_ERROR;
9112 svn_client_conflict_prop_get_resolution_options(apr_array_header_t **options,
9113 svn_client_conflict_t *conflict,
9114 svn_client_ctx_t *ctx,
9115 apr_pool_t *result_pool,
9116 apr_pool_t *scratch_pool)
9118 SVN_ERR(assert_prop_conflict(conflict, scratch_pool));
9120 *options = apr_array_make(result_pool, 7,
9121 sizeof(svn_client_conflict_option_t *));
9123 add_resolution_option(*options, conflict,
9124 svn_client_conflict_option_postpone,
9126 _("skip this conflict and leave it unresolved"),
9129 add_resolution_option(*options, conflict,
9130 svn_client_conflict_option_base_text,
9132 _("discard local and incoming changes for this property"),
9133 resolve_prop_conflict);
9135 add_resolution_option(*options, conflict,
9136 svn_client_conflict_option_incoming_text,
9137 _("Accept incoming"),
9138 _("accept incoming version of entire property value"),
9139 resolve_prop_conflict);
9141 add_resolution_option(*options, conflict,
9142 svn_client_conflict_option_working_text,
9143 _("Mark as resolved"),
9144 _("accept working copy version of entire property value"),
9145 resolve_prop_conflict);
9147 add_resolution_option(*options, conflict,
9148 svn_client_conflict_option_incoming_text_where_conflicted,
9149 _("Accept incoming for conflicts"),
9150 _("accept incoming changes only where they conflict"),
9151 resolve_prop_conflict);
9153 add_resolution_option(*options, conflict,
9154 svn_client_conflict_option_working_text_where_conflicted,
9155 _("Reject conflicts"),
9156 _("reject changes which conflict and accept the rest"),
9157 resolve_prop_conflict);
9159 add_resolution_option(*options, conflict,
9160 svn_client_conflict_option_merged_text,
9162 _("accept merged version of property value"),
9163 resolve_prop_conflict);
9165 return SVN_NO_ERROR;
9168 /* Configure 'accept current wc state' resolution option for a tree conflict. */
9169 static svn_error_t *
9170 configure_option_accept_current_wc_state(svn_client_conflict_t *conflict,
9171 apr_array_header_t *options)
9173 svn_wc_operation_t operation;
9174 svn_wc_conflict_action_t incoming_change;
9175 svn_wc_conflict_reason_t local_change;
9176 conflict_option_resolve_func_t do_resolve_func;
9178 operation = svn_client_conflict_get_operation(conflict);
9179 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9180 local_change = svn_client_conflict_get_local_change(conflict);
9182 if ((operation == svn_wc_operation_update ||
9183 operation == svn_wc_operation_switch) &&
9184 (local_change == svn_wc_conflict_reason_moved_away ||
9185 local_change == svn_wc_conflict_reason_deleted ||
9186 local_change == svn_wc_conflict_reason_replaced) &&
9187 incoming_change == svn_wc_conflict_action_edit)
9189 /* We must break moves if the user accepts the current working copy
9190 * state instead of updating a moved-away node or updating children
9191 * moved outside of deleted or replaced directory nodes.
9192 * Else such moves would be left in an invalid state. */
9193 do_resolve_func = resolve_update_break_moved_away;
9196 do_resolve_func = resolve_accept_current_wc_state;
9198 add_resolution_option(options, conflict,
9199 svn_client_conflict_option_accept_current_wc_state,
9200 _("Mark as resolved"),
9201 _("accept current working copy state"),
9204 return SVN_NO_ERROR;
9207 /* Configure 'update move destination' resolution option for a tree conflict. */
9208 static svn_error_t *
9209 configure_option_update_move_destination(svn_client_conflict_t *conflict,
9210 apr_array_header_t *options)
9212 svn_wc_operation_t operation;
9213 svn_wc_conflict_action_t incoming_change;
9214 svn_wc_conflict_reason_t local_change;
9216 operation = svn_client_conflict_get_operation(conflict);
9217 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9218 local_change = svn_client_conflict_get_local_change(conflict);
9220 if ((operation == svn_wc_operation_update ||
9221 operation == svn_wc_operation_switch) &&
9222 incoming_change == svn_wc_conflict_action_edit &&
9223 local_change == svn_wc_conflict_reason_moved_away)
9225 add_resolution_option(
9227 svn_client_conflict_option_update_move_destination,
9228 _("Update move destination"),
9229 _("apply incoming changes to move destination"),
9230 resolve_update_moved_away_node);
9233 return SVN_NO_ERROR;
9236 /* Configure 'update raise moved away children' resolution option for a tree
9238 static svn_error_t *
9239 configure_option_update_raise_moved_away_children(
9240 svn_client_conflict_t *conflict,
9241 apr_array_header_t *options)
9243 svn_wc_operation_t operation;
9244 svn_wc_conflict_action_t incoming_change;
9245 svn_wc_conflict_reason_t local_change;
9246 svn_node_kind_t victim_node_kind;
9248 operation = svn_client_conflict_get_operation(conflict);
9249 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9250 local_change = svn_client_conflict_get_local_change(conflict);
9251 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9253 if ((operation == svn_wc_operation_update ||
9254 operation == svn_wc_operation_switch) &&
9255 incoming_change == svn_wc_conflict_action_edit &&
9256 (local_change == svn_wc_conflict_reason_deleted ||
9257 local_change == svn_wc_conflict_reason_replaced) &&
9258 victim_node_kind == svn_node_dir)
9260 add_resolution_option(
9262 svn_client_conflict_option_update_any_moved_away_children,
9263 _("Update any moved-away children"),
9264 _("prepare for updating moved-away children, if any"),
9265 resolve_update_raise_moved_away);
9268 return SVN_NO_ERROR;
9271 /* Configure 'incoming add ignore' resolution option for a tree conflict. */
9272 static svn_error_t *
9273 configure_option_incoming_add_ignore(svn_client_conflict_t *conflict,
9274 svn_client_ctx_t *ctx,
9275 apr_array_header_t *options,
9276 apr_pool_t *scratch_pool)
9278 svn_wc_operation_t operation;
9279 svn_wc_conflict_action_t incoming_change;
9280 svn_wc_conflict_reason_t local_change;
9281 const char *incoming_new_repos_relpath;
9282 svn_revnum_t incoming_new_pegrev;
9283 svn_node_kind_t victim_node_kind;
9285 operation = svn_client_conflict_get_operation(conflict);
9286 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9287 local_change = svn_client_conflict_get_local_change(conflict);
9288 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9289 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9290 &incoming_new_repos_relpath, &incoming_new_pegrev,
9291 NULL, conflict, scratch_pool,
9294 /* This option is only available for directories. */
9295 if (victim_node_kind == svn_node_dir &&
9296 incoming_change == svn_wc_conflict_action_add &&
9297 (local_change == svn_wc_conflict_reason_obstructed ||
9298 local_change == svn_wc_conflict_reason_added))
9300 const char *description;
9301 const char *wcroot_abspath;
9303 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9304 conflict->local_abspath, scratch_pool,
9306 if (operation == svn_wc_operation_merge)
9308 apr_psprintf(scratch_pool,
9309 _("ignore and do not add '^/%s@%ld' here"),
9310 incoming_new_repos_relpath, incoming_new_pegrev);
9311 else if (operation == svn_wc_operation_update ||
9312 operation == svn_wc_operation_switch)
9314 if (victim_node_kind == svn_node_file)
9316 apr_psprintf(scratch_pool,
9317 _("replace '^/%s@%ld' with the locally added file"),
9318 incoming_new_repos_relpath, incoming_new_pegrev);
9319 else if (victim_node_kind == svn_node_dir)
9321 apr_psprintf(scratch_pool,
9322 _("replace '^/%s@%ld' with the locally added "
9324 incoming_new_repos_relpath, incoming_new_pegrev);
9327 apr_psprintf(scratch_pool,
9328 _("replace '^/%s@%ld' with the locally added item"),
9329 incoming_new_repos_relpath, incoming_new_pegrev);
9332 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9333 _("unexpected operation code '%d'"),
9335 add_resolution_option(
9336 options, conflict, svn_client_conflict_option_incoming_add_ignore,
9337 _("Ignore incoming addition"), description, resolve_incoming_add_ignore);
9340 return SVN_NO_ERROR;
9343 /* Configure 'incoming added file text merge' resolution option for a tree
9345 static svn_error_t *
9346 configure_option_incoming_added_file_text_merge(svn_client_conflict_t *conflict,
9347 svn_client_ctx_t *ctx,
9348 apr_array_header_t *options,
9349 apr_pool_t *scratch_pool)
9351 svn_wc_operation_t operation;
9352 svn_wc_conflict_action_t incoming_change;
9353 svn_wc_conflict_reason_t local_change;
9354 svn_node_kind_t victim_node_kind;
9355 const char *incoming_new_repos_relpath;
9356 svn_revnum_t incoming_new_pegrev;
9357 svn_node_kind_t incoming_new_kind;
9359 operation = svn_client_conflict_get_operation(conflict);
9360 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9361 local_change = svn_client_conflict_get_local_change(conflict);
9362 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9363 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9364 &incoming_new_repos_relpath, &incoming_new_pegrev,
9365 &incoming_new_kind, conflict, scratch_pool,
9368 if (victim_node_kind == svn_node_file &&
9369 incoming_new_kind == svn_node_file &&
9370 incoming_change == svn_wc_conflict_action_add &&
9371 (local_change == svn_wc_conflict_reason_obstructed ||
9372 local_change == svn_wc_conflict_reason_added))
9374 const char *description;
9375 const char *wcroot_abspath;
9377 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9378 conflict->local_abspath, scratch_pool,
9381 if (operation == svn_wc_operation_merge)
9383 apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"),
9384 incoming_new_repos_relpath, incoming_new_pegrev,
9385 svn_dirent_local_style(
9386 svn_dirent_skip_ancestor(wcroot_abspath,
9387 conflict->local_abspath),
9391 apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"),
9392 svn_dirent_local_style(
9393 svn_dirent_skip_ancestor(wcroot_abspath,
9394 conflict->local_abspath),
9396 incoming_new_repos_relpath, incoming_new_pegrev);
9398 add_resolution_option(
9400 svn_client_conflict_option_incoming_added_file_text_merge,
9401 _("Merge the files"), description,
9402 operation == svn_wc_operation_merge
9403 ? resolve_merge_incoming_added_file_text_merge
9404 : resolve_merge_incoming_added_file_text_update);
9407 return SVN_NO_ERROR;
9410 /* Configure 'incoming added file replace and merge' resolution option for a
9412 static svn_error_t *
9413 configure_option_incoming_added_file_replace_and_merge(
9414 svn_client_conflict_t *conflict,
9415 svn_client_ctx_t *ctx,
9416 apr_array_header_t *options,
9417 apr_pool_t *scratch_pool)
9419 svn_wc_operation_t operation;
9420 svn_wc_conflict_action_t incoming_change;
9421 svn_wc_conflict_reason_t local_change;
9422 svn_node_kind_t victim_node_kind;
9423 const char *incoming_new_repos_relpath;
9424 svn_revnum_t incoming_new_pegrev;
9425 svn_node_kind_t incoming_new_kind;
9427 operation = svn_client_conflict_get_operation(conflict);
9428 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9429 local_change = svn_client_conflict_get_local_change(conflict);
9430 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9431 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9432 &incoming_new_repos_relpath, &incoming_new_pegrev,
9433 &incoming_new_kind, conflict, scratch_pool,
9436 if (operation == svn_wc_operation_merge &&
9437 victim_node_kind == svn_node_file &&
9438 incoming_new_kind == svn_node_file &&
9439 incoming_change == svn_wc_conflict_action_add &&
9440 local_change == svn_wc_conflict_reason_obstructed)
9442 const char *wcroot_abspath;
9443 const char *description;
9445 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9446 conflict->local_abspath, scratch_pool,
9449 apr_psprintf(scratch_pool,
9450 _("delete '%s', copy '^/%s@%ld' here, and merge the files"),
9451 svn_dirent_local_style(
9452 svn_dirent_skip_ancestor(wcroot_abspath,
9453 conflict->local_abspath),
9455 incoming_new_repos_relpath, incoming_new_pegrev);
9457 add_resolution_option(
9459 svn_client_conflict_option_incoming_added_file_replace_and_merge,
9460 _("Replace and merge"),
9461 description, resolve_merge_incoming_added_file_replace_and_merge);
9464 return SVN_NO_ERROR;
9467 /* Configure 'incoming added dir merge' resolution option for a tree
9469 static svn_error_t *
9470 configure_option_incoming_added_dir_merge(svn_client_conflict_t *conflict,
9471 svn_client_ctx_t *ctx,
9472 apr_array_header_t *options,
9473 apr_pool_t *scratch_pool)
9475 svn_wc_operation_t operation;
9476 svn_wc_conflict_action_t incoming_change;
9477 svn_wc_conflict_reason_t local_change;
9478 svn_node_kind_t victim_node_kind;
9479 const char *incoming_new_repos_relpath;
9480 svn_revnum_t incoming_new_pegrev;
9481 svn_node_kind_t incoming_new_kind;
9483 operation = svn_client_conflict_get_operation(conflict);
9484 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9485 local_change = svn_client_conflict_get_local_change(conflict);
9486 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9487 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9488 &incoming_new_repos_relpath, &incoming_new_pegrev,
9489 &incoming_new_kind, conflict, scratch_pool,
9492 if (victim_node_kind == svn_node_dir &&
9493 incoming_new_kind == svn_node_dir &&
9494 incoming_change == svn_wc_conflict_action_add &&
9495 (local_change == svn_wc_conflict_reason_added ||
9496 (operation == svn_wc_operation_merge &&
9497 local_change == svn_wc_conflict_reason_obstructed)))
9500 const char *description;
9501 const char *wcroot_abspath;
9503 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9504 conflict->local_abspath, scratch_pool,
9506 if (operation == svn_wc_operation_merge)
9508 apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"),
9509 incoming_new_repos_relpath, incoming_new_pegrev,
9510 svn_dirent_local_style(
9511 svn_dirent_skip_ancestor(wcroot_abspath,
9512 conflict->local_abspath),
9516 apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"),
9517 svn_dirent_local_style(
9518 svn_dirent_skip_ancestor(wcroot_abspath,
9519 conflict->local_abspath),
9521 incoming_new_repos_relpath, incoming_new_pegrev);
9523 add_resolution_option(options, conflict,
9524 svn_client_conflict_option_incoming_added_dir_merge,
9525 _("Merge the directories"), description,
9526 operation == svn_wc_operation_merge
9527 ? resolve_merge_incoming_added_dir_merge
9528 : resolve_update_incoming_added_dir_merge);
9531 return SVN_NO_ERROR;
9534 /* Configure 'incoming added dir replace' resolution option for a tree
9536 static svn_error_t *
9537 configure_option_incoming_added_dir_replace(svn_client_conflict_t *conflict,
9538 svn_client_ctx_t *ctx,
9539 apr_array_header_t *options,
9540 apr_pool_t *scratch_pool)
9542 svn_wc_operation_t operation;
9543 svn_wc_conflict_action_t incoming_change;
9544 svn_wc_conflict_reason_t local_change;
9545 svn_node_kind_t victim_node_kind;
9546 const char *incoming_new_repos_relpath;
9547 svn_revnum_t incoming_new_pegrev;
9548 svn_node_kind_t incoming_new_kind;
9550 operation = svn_client_conflict_get_operation(conflict);
9551 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9552 local_change = svn_client_conflict_get_local_change(conflict);
9553 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9554 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9555 &incoming_new_repos_relpath, &incoming_new_pegrev,
9556 &incoming_new_kind, conflict, scratch_pool,
9559 if (operation == svn_wc_operation_merge &&
9560 victim_node_kind == svn_node_dir &&
9561 incoming_new_kind == svn_node_dir &&
9562 incoming_change == svn_wc_conflict_action_add &&
9563 local_change == svn_wc_conflict_reason_obstructed)
9565 const char *description;
9566 const char *wcroot_abspath;
9568 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9569 conflict->local_abspath, scratch_pool,
9572 apr_psprintf(scratch_pool, _("delete '%s' and copy '^/%s@%ld' here"),
9573 svn_dirent_local_style(
9574 svn_dirent_skip_ancestor(wcroot_abspath,
9575 conflict->local_abspath),
9577 incoming_new_repos_relpath, incoming_new_pegrev);
9578 add_resolution_option(
9580 svn_client_conflict_option_incoming_added_dir_replace,
9581 _("Delete my directory and replace it with incoming directory"),
9582 description, resolve_merge_incoming_added_dir_replace);
9585 return SVN_NO_ERROR;
9588 /* Configure 'incoming added dir replace and merge' resolution option
9589 * for a tree conflict. */
9590 static svn_error_t *
9591 configure_option_incoming_added_dir_replace_and_merge(
9592 svn_client_conflict_t *conflict,
9593 svn_client_ctx_t *ctx,
9594 apr_array_header_t *options,
9595 apr_pool_t *scratch_pool)
9597 svn_wc_operation_t operation;
9598 svn_wc_conflict_action_t incoming_change;
9599 svn_wc_conflict_reason_t local_change;
9600 svn_node_kind_t victim_node_kind;
9601 const char *incoming_new_repos_relpath;
9602 svn_revnum_t incoming_new_pegrev;
9603 svn_node_kind_t incoming_new_kind;
9605 operation = svn_client_conflict_get_operation(conflict);
9606 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9607 local_change = svn_client_conflict_get_local_change(conflict);
9608 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9609 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9610 &incoming_new_repos_relpath, &incoming_new_pegrev,
9611 &incoming_new_kind, conflict, scratch_pool,
9614 if (operation == svn_wc_operation_merge &&
9615 victim_node_kind == svn_node_dir &&
9616 incoming_new_kind == svn_node_dir &&
9617 incoming_change == svn_wc_conflict_action_add &&
9618 local_change == svn_wc_conflict_reason_obstructed)
9620 const char *description;
9621 const char *wcroot_abspath;
9623 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9624 conflict->local_abspath, scratch_pool,
9627 apr_psprintf(scratch_pool,
9628 _("delete '%s', copy '^/%s@%ld' here, and merge the directories"),
9629 svn_dirent_local_style(
9630 svn_dirent_skip_ancestor(wcroot_abspath,
9631 conflict->local_abspath),
9633 incoming_new_repos_relpath, incoming_new_pegrev);
9635 add_resolution_option(
9637 svn_client_conflict_option_incoming_added_dir_replace_and_merge,
9638 _("Replace and merge"),
9639 description, resolve_merge_incoming_added_dir_replace_and_merge);
9642 return SVN_NO_ERROR;
9645 /* Configure 'incoming delete ignore' resolution option for a tree conflict. */
9646 static svn_error_t *
9647 configure_option_incoming_delete_ignore(svn_client_conflict_t *conflict,
9648 svn_client_ctx_t *ctx,
9649 apr_array_header_t *options,
9650 apr_pool_t *scratch_pool)
9652 svn_wc_operation_t operation;
9653 svn_wc_conflict_action_t incoming_change;
9654 svn_wc_conflict_reason_t local_change;
9655 const char *incoming_new_repos_relpath;
9656 svn_revnum_t incoming_new_pegrev;
9658 operation = svn_client_conflict_get_operation(conflict);
9659 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9660 local_change = svn_client_conflict_get_local_change(conflict);
9661 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9662 &incoming_new_repos_relpath, &incoming_new_pegrev,
9663 NULL, conflict, scratch_pool,
9666 if (incoming_change == svn_wc_conflict_action_delete)
9668 const char *description;
9669 struct conflict_tree_incoming_delete_details *incoming_details;
9670 svn_boolean_t is_incoming_move;
9672 incoming_details = conflict->tree_conflict_incoming_details;
9673 is_incoming_move = (incoming_details != NULL &&
9674 incoming_details->moves != NULL);
9675 if (local_change == svn_wc_conflict_reason_moved_away ||
9676 local_change == svn_wc_conflict_reason_edited)
9678 /* An option which ignores the incoming deletion makes no sense
9679 * if we know there was a local move and/or an incoming move. */
9680 if (is_incoming_move)
9681 return SVN_NO_ERROR;
9683 else if (local_change == svn_wc_conflict_reason_deleted)
9685 /* If the local item was deleted and conflict details were fetched
9686 * and indicate that there was no move, then this is an actual
9687 * 'delete vs delete' situation. An option which ignores the incoming
9688 * deletion makes no sense in that case because there is no local
9689 * node to preserve. */
9690 if (!is_incoming_move)
9691 return SVN_NO_ERROR;
9693 else if (local_change == svn_wc_conflict_reason_missing &&
9694 operation == svn_wc_operation_merge)
9696 struct conflict_tree_local_missing_details *local_details;
9697 svn_boolean_t is_local_move; /* "local" to branch history */
9699 local_details = conflict->tree_conflict_local_details;
9700 is_local_move = (local_details != NULL &&
9701 local_details->moves != NULL);
9703 if (!is_incoming_move && !is_local_move)
9704 return SVN_NO_ERROR;
9708 apr_psprintf(scratch_pool, _("ignore the deletion of '^/%s@%ld'"),
9709 incoming_new_repos_relpath, incoming_new_pegrev);
9711 add_resolution_option(options, conflict,
9712 svn_client_conflict_option_incoming_delete_ignore,
9713 _("Ignore incoming deletion"), description,
9714 resolve_incoming_delete_ignore);
9717 return SVN_NO_ERROR;
9720 /* Configure 'incoming delete accept' resolution option for a tree conflict. */
9721 static svn_error_t *
9722 configure_option_incoming_delete_accept(svn_client_conflict_t *conflict,
9723 svn_client_ctx_t *ctx,
9724 apr_array_header_t *options,
9725 apr_pool_t *scratch_pool)
9727 svn_wc_conflict_action_t incoming_change;
9728 svn_wc_conflict_reason_t local_change;
9729 const char *incoming_new_repos_relpath;
9730 svn_revnum_t incoming_new_pegrev;
9732 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9733 local_change = svn_client_conflict_get_local_change(conflict);
9734 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9735 &incoming_new_repos_relpath, &incoming_new_pegrev,
9736 NULL, conflict, scratch_pool,
9739 if (incoming_change == svn_wc_conflict_action_delete)
9741 struct conflict_tree_incoming_delete_details *incoming_details;
9742 svn_boolean_t is_incoming_move;
9744 incoming_details = conflict->tree_conflict_incoming_details;
9745 is_incoming_move = (incoming_details != NULL &&
9746 incoming_details->moves != NULL);
9747 if (is_incoming_move &&
9748 (local_change == svn_wc_conflict_reason_edited ||
9749 local_change == svn_wc_conflict_reason_moved_away))
9751 /* An option which accepts the incoming deletion makes no sense
9752 * if we know there was a local move and/or an incoming move. */
9753 return SVN_NO_ERROR;
9757 const char *description;
9758 const char *wcroot_abspath;
9759 const char *local_abspath;
9761 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9762 conflict->local_abspath, scratch_pool,
9764 local_abspath = svn_client_conflict_get_local_abspath(conflict);
9766 apr_psprintf(scratch_pool, _("accept the deletion of '%s'"),
9767 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9770 add_resolution_option(
9772 svn_client_conflict_option_incoming_delete_accept,
9773 _("Accept incoming deletion"), description,
9774 resolve_incoming_delete_accept);
9778 return SVN_NO_ERROR;
9781 static svn_error_t *
9782 describe_incoming_move_merge_conflict_option(
9783 const char **description,
9784 svn_client_conflict_t *conflict,
9785 svn_client_ctx_t *ctx,
9786 struct conflict_tree_incoming_delete_details *details,
9787 apr_pool_t *result_pool,
9788 apr_pool_t *scratch_pool)
9790 apr_array_header_t *move_target_wc_abspaths;
9791 svn_wc_operation_t operation;
9792 const char *victim_abspath;
9793 const char *moved_to_abspath;
9794 const char *wcroot_abspath;
9796 move_target_wc_abspaths =
9797 svn_hash_gets(details->wc_move_targets,
9798 get_moved_to_repos_relpath(details, scratch_pool));
9799 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
9800 details->wc_move_target_idx,
9803 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
9804 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9805 victim_abspath, scratch_pool,
9808 operation = svn_client_conflict_get_operation(conflict);
9809 if (operation == svn_wc_operation_merge)
9812 result_pool, _("move '%s' to '%s' and merge"),
9813 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9816 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9822 result_pool, _("move and merge local changes from '%s' into '%s'"),
9823 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9826 svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9830 return SVN_NO_ERROR;
9833 /* Configure 'incoming move file merge' resolution option for
9834 * a tree conflict. */
9835 static svn_error_t *
9836 configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
9837 svn_client_ctx_t *ctx,
9838 apr_array_header_t *options,
9839 apr_pool_t *scratch_pool)
9841 svn_node_kind_t victim_node_kind;
9842 svn_wc_conflict_action_t incoming_change;
9843 const char *incoming_old_repos_relpath;
9844 svn_revnum_t incoming_old_pegrev;
9845 svn_node_kind_t incoming_old_kind;
9846 const char *incoming_new_repos_relpath;
9847 svn_revnum_t incoming_new_pegrev;
9848 svn_node_kind_t incoming_new_kind;
9849 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9850 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9851 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9852 &incoming_old_repos_relpath, &incoming_old_pegrev,
9853 &incoming_old_kind, conflict, scratch_pool,
9855 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9856 &incoming_new_repos_relpath, &incoming_new_pegrev,
9857 &incoming_new_kind, conflict, scratch_pool,
9860 if (victim_node_kind == svn_node_file &&
9861 incoming_old_kind == svn_node_file &&
9862 incoming_new_kind == svn_node_none &&
9863 incoming_change == svn_wc_conflict_action_delete)
9865 struct conflict_tree_incoming_delete_details *details;
9866 const char *description;
9868 details = conflict->tree_conflict_incoming_details;
9869 if (details == NULL || details->moves == NULL)
9870 return SVN_NO_ERROR;
9872 if (apr_hash_count(details->wc_move_targets) == 0)
9873 return SVN_NO_ERROR;
9875 SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
9880 add_resolution_option(
9882 svn_client_conflict_option_incoming_move_file_text_merge,
9883 _("Move and merge"), description,
9884 resolve_incoming_move_file_text_merge);
9887 return SVN_NO_ERROR;
9890 /* Configure 'incoming move dir merge' resolution option for
9891 * a tree conflict. */
9892 static svn_error_t *
9893 configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
9894 svn_client_ctx_t *ctx,
9895 apr_array_header_t *options,
9896 apr_pool_t *scratch_pool)
9898 svn_node_kind_t victim_node_kind;
9899 svn_wc_conflict_action_t incoming_change;
9900 svn_wc_conflict_reason_t local_change;
9901 const char *incoming_old_repos_relpath;
9902 svn_revnum_t incoming_old_pegrev;
9903 svn_node_kind_t incoming_old_kind;
9904 const char *incoming_new_repos_relpath;
9905 svn_revnum_t incoming_new_pegrev;
9906 svn_node_kind_t incoming_new_kind;
9908 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9909 local_change = svn_client_conflict_get_local_change(conflict);
9910 victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9911 SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9912 &incoming_old_repos_relpath, &incoming_old_pegrev,
9913 &incoming_old_kind, conflict, scratch_pool,
9915 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9916 &incoming_new_repos_relpath, &incoming_new_pegrev,
9917 &incoming_new_kind, conflict, scratch_pool,
9920 if (victim_node_kind == svn_node_dir &&
9921 incoming_old_kind == svn_node_dir &&
9922 incoming_new_kind == svn_node_none &&
9923 incoming_change == svn_wc_conflict_action_delete &&
9924 local_change == svn_wc_conflict_reason_edited)
9926 struct conflict_tree_incoming_delete_details *details;
9927 const char *description;
9929 details = conflict->tree_conflict_incoming_details;
9930 if (details == NULL || details->moves == NULL)
9931 return SVN_NO_ERROR;
9933 if (apr_hash_count(details->wc_move_targets) == 0)
9934 return SVN_NO_ERROR;
9936 SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
9941 add_resolution_option(options, conflict,
9942 svn_client_conflict_option_incoming_move_dir_merge,
9943 _("Move and merge"), description,
9944 resolve_incoming_move_dir_merge);
9947 return SVN_NO_ERROR;
9950 /* Configure 'local move file merge' resolution option for
9951 * a tree conflict. */
9952 static svn_error_t *
9953 configure_option_local_move_file_merge(svn_client_conflict_t *conflict,
9954 svn_client_ctx_t *ctx,
9955 apr_array_header_t *options,
9956 apr_pool_t *scratch_pool)
9958 svn_wc_operation_t operation;
9959 svn_wc_conflict_action_t incoming_change;
9960 svn_wc_conflict_reason_t local_change;
9961 const char *incoming_new_repos_relpath;
9962 svn_revnum_t incoming_new_pegrev;
9964 operation = svn_client_conflict_get_operation(conflict);
9965 incoming_change = svn_client_conflict_get_incoming_change(conflict);
9966 local_change = svn_client_conflict_get_local_change(conflict);
9967 SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9968 &incoming_new_repos_relpath, &incoming_new_pegrev,
9969 NULL, conflict, scratch_pool,
9972 if (operation == svn_wc_operation_merge &&
9973 incoming_change == svn_wc_conflict_action_edit &&
9974 local_change == svn_wc_conflict_reason_missing)
9976 struct conflict_tree_local_missing_details *details;
9978 details = conflict->tree_conflict_local_details;
9979 if (details != NULL && details->moves != NULL)
9981 apr_hash_t *wc_move_targets = apr_hash_make(scratch_pool);
9982 apr_pool_t *iterpool;
9985 iterpool = svn_pool_create(scratch_pool);
9986 for (i = 0; i < details->moves->nelts; i++)
9988 struct repos_move_info *move;
9990 svn_pool_clear(iterpool);
9991 move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
9992 SVN_ERR(follow_move_chains(wc_move_targets, move, ctx,
9993 conflict->local_abspath,
9995 incoming_new_repos_relpath,
9996 incoming_new_pegrev,
9997 scratch_pool, iterpool));
9999 svn_pool_destroy(iterpool);
10001 if (apr_hash_count(wc_move_targets) > 0)
10003 apr_array_header_t *move_target_repos_relpaths;
10004 const svn_sort__item_t *item;
10005 apr_array_header_t *moved_to_abspaths;
10006 const char *description;
10007 const char *wcroot_abspath;
10009 /* Initialize to the first possible move target. Hopefully,
10010 * in most cases there will only be one candidate anyway. */
10011 move_target_repos_relpaths = svn_sort__hash(
10013 svn_sort_compare_items_as_paths,
10015 item = &APR_ARRAY_IDX(move_target_repos_relpaths,
10016 0, svn_sort__item_t);
10017 moved_to_abspaths = item->value;
10018 details->moved_to_abspath =
10019 apr_pstrdup(conflict->pool,
10020 APR_ARRAY_IDX(moved_to_abspaths, 0, const char *));
10022 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10023 conflict->local_abspath,
10024 scratch_pool, scratch_pool));
10027 scratch_pool, _("apply changes to move destination '%s'"),
10028 svn_dirent_local_style(
10029 svn_dirent_skip_ancestor(wcroot_abspath,
10030 details->moved_to_abspath),
10033 add_resolution_option(
10035 svn_client_conflict_option_local_move_file_text_merge,
10036 _("Apply to move destination"),
10037 description, resolve_local_move_file_merge);
10040 details->moved_to_abspath = NULL;
10044 return SVN_NO_ERROR;
10048 svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
10049 apr_array_header_t **possible_moved_to_repos_relpaths,
10050 svn_client_conflict_option_t *option,
10051 apr_pool_t *result_pool,
10052 apr_pool_t *scratch_pool)
10054 svn_client_conflict_t *conflict = option->conflict;
10055 struct conflict_tree_incoming_delete_details *details;
10056 const char *victim_abspath;
10057 apr_array_header_t *sorted_repos_relpaths;
10060 SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
10061 svn_client_conflict_option_incoming_move_file_text_merge ||
10062 svn_client_conflict_option_get_id(option) ==
10063 svn_client_conflict_option_incoming_move_dir_merge);
10065 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10066 details = conflict->tree_conflict_incoming_details;
10067 if (details == NULL || details->wc_move_targets == NULL)
10068 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10069 _("Getting a list of possible move targets "
10070 "requires details for tree conflict at '%s' "
10071 "to be fetched from the repository first"),
10072 svn_dirent_local_style(victim_abspath,
10075 /* Return a copy of the repos replath candidate list. */
10076 sorted_repos_relpaths = svn_sort__hash(details->wc_move_targets,
10077 svn_sort_compare_items_as_paths,
10080 *possible_moved_to_repos_relpaths = apr_array_make(
10082 sorted_repos_relpaths->nelts,
10083 sizeof (const char *));
10084 for (i = 0; i < sorted_repos_relpaths->nelts; i++)
10086 svn_sort__item_t item;
10087 const char *repos_relpath;
10089 item = APR_ARRAY_IDX(sorted_repos_relpaths, i, svn_sort__item_t);
10090 repos_relpath = item.key;
10091 APR_ARRAY_PUSH(*possible_moved_to_repos_relpaths, const char *) =
10092 apr_pstrdup(result_pool, repos_relpath);
10095 return SVN_NO_ERROR;
10099 svn_client_conflict_option_set_moved_to_repos_relpath(
10100 svn_client_conflict_option_t *option,
10101 int preferred_move_target_idx,
10102 svn_client_ctx_t *ctx,
10103 apr_pool_t *scratch_pool)
10105 svn_client_conflict_t *conflict = option->conflict;
10106 struct conflict_tree_incoming_delete_details *details;
10107 const char *victim_abspath;
10108 apr_array_header_t *move_target_repos_relpaths;
10109 svn_sort__item_t item;
10110 const char *move_target_repos_relpath;
10111 apr_hash_index_t *hi;
10113 SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
10114 svn_client_conflict_option_incoming_move_file_text_merge ||
10115 svn_client_conflict_option_get_id(option) ==
10116 svn_client_conflict_option_incoming_move_dir_merge);
10118 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10119 details = conflict->tree_conflict_incoming_details;
10120 if (details == NULL || details->wc_move_targets == NULL)
10121 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10122 _("Setting a move target requires details "
10123 "for tree conflict at '%s' to be fetched "
10124 "from the repository first"),
10125 svn_dirent_local_style(victim_abspath,
10128 if (preferred_move_target_idx < 0 ||
10129 preferred_move_target_idx >= apr_hash_count(details->wc_move_targets))
10130 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
10131 _("Index '%d' is out of bounds of the possible "
10132 "move target list for '%s'"),
10133 preferred_move_target_idx,
10134 svn_dirent_local_style(victim_abspath,
10137 /* Translate the index back into a hash table key. */
10138 move_target_repos_relpaths =
10139 svn_sort__hash(details->wc_move_targets,
10140 svn_sort_compare_items_as_paths,
10142 item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx,
10144 move_target_repos_relpath = item.key;
10145 /* Find our copy of the hash key and remember the user's preference. */
10146 for (hi = apr_hash_first(scratch_pool, details->wc_move_targets);
10148 hi = apr_hash_next(hi))
10150 const char *repos_relpath = apr_hash_this_key(hi);
10152 if (strcmp(move_target_repos_relpath, repos_relpath) == 0)
10154 details->move_target_repos_relpath = repos_relpath;
10155 /* Update option description. */
10156 SVN_ERR(describe_incoming_move_merge_conflict_option(
10157 &option->description,
10163 return SVN_NO_ERROR;
10167 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
10168 _("Repository path '%s' not found in list of "
10169 "possible move targets for '%s'"),
10170 move_target_repos_relpath,
10171 svn_dirent_local_style(victim_abspath,
10176 svn_client_conflict_option_get_moved_to_abspath_candidates(
10177 apr_array_header_t **possible_moved_to_abspaths,
10178 svn_client_conflict_option_t *option,
10179 apr_pool_t *result_pool,
10180 apr_pool_t *scratch_pool)
10182 svn_client_conflict_t *conflict = option->conflict;
10183 struct conflict_tree_incoming_delete_details *details;
10184 const char *victim_abspath;
10185 apr_array_header_t *move_target_wc_abspaths;
10188 SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
10189 svn_client_conflict_option_incoming_move_file_text_merge ||
10190 svn_client_conflict_option_get_id(option) ==
10191 svn_client_conflict_option_incoming_move_dir_merge);
10193 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10194 details = conflict->tree_conflict_incoming_details;
10195 if (details == NULL || details->wc_move_targets == NULL)
10196 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10197 _("Getting a list of possible move targets "
10198 "requires details for tree conflict at '%s' "
10199 "to be fetched from the repository first"),
10200 svn_dirent_local_style(victim_abspath,
10203 move_target_wc_abspaths =
10204 svn_hash_gets(details->wc_move_targets,
10205 get_moved_to_repos_relpath(details, scratch_pool));
10207 /* Return a copy of the option's move target candidate list. */
10208 *possible_moved_to_abspaths =
10209 apr_array_make(result_pool, move_target_wc_abspaths->nelts,
10210 sizeof (const char *));
10211 for (i = 0; i < move_target_wc_abspaths->nelts; i++)
10213 const char *moved_to_abspath;
10215 moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
10217 APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
10218 apr_pstrdup(result_pool, moved_to_abspath);
10221 return SVN_NO_ERROR;
10225 svn_client_conflict_option_set_moved_to_abspath(
10226 svn_client_conflict_option_t *option,
10227 int preferred_move_target_idx,
10228 svn_client_ctx_t *ctx,
10229 apr_pool_t *scratch_pool)
10231 svn_client_conflict_t *conflict = option->conflict;
10232 struct conflict_tree_incoming_delete_details *details;
10233 const char *victim_abspath;
10234 apr_array_header_t *move_target_wc_abspaths;
10236 SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
10237 svn_client_conflict_option_incoming_move_file_text_merge ||
10238 svn_client_conflict_option_get_id(option) ==
10239 svn_client_conflict_option_incoming_move_dir_merge);
10241 victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10242 details = conflict->tree_conflict_incoming_details;
10243 if (details == NULL || details->wc_move_targets == NULL)
10244 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10245 _("Setting a move target requires details "
10246 "for tree conflict at '%s' to be fetched "
10247 "from the repository first"),
10248 svn_dirent_local_style(victim_abspath,
10251 move_target_wc_abspaths =
10252 svn_hash_gets(details->wc_move_targets,
10253 get_moved_to_repos_relpath(details, scratch_pool));
10255 if (preferred_move_target_idx < 0 ||
10256 preferred_move_target_idx > move_target_wc_abspaths->nelts)
10257 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
10258 _("Index '%d' is out of bounds of the possible "
10259 "move target list for '%s'"),
10260 preferred_move_target_idx,
10261 svn_dirent_local_style(victim_abspath,
10264 /* Record the user's preference. */
10265 details->wc_move_target_idx = preferred_move_target_idx;
10267 /* Update option description. */
10268 SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description,
10273 return SVN_NO_ERROR;
10277 svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options,
10278 svn_client_conflict_t *conflict,
10279 svn_client_ctx_t *ctx,
10280 apr_pool_t *result_pool,
10281 apr_pool_t *scratch_pool)
10283 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
10285 *options = apr_array_make(result_pool, 2,
10286 sizeof(svn_client_conflict_option_t *));
10288 /* Add postpone option. */
10289 add_resolution_option(*options, conflict,
10290 svn_client_conflict_option_postpone,
10292 _("skip this conflict and leave it unresolved"),
10295 /* Add an option which marks the conflict resolved. */
10296 SVN_ERR(configure_option_accept_current_wc_state(conflict, *options));
10298 /* Configure options which offer automatic resolution. */
10299 SVN_ERR(configure_option_update_move_destination(conflict, *options));
10300 SVN_ERR(configure_option_update_raise_moved_away_children(conflict,
10302 SVN_ERR(configure_option_incoming_add_ignore(conflict, ctx, *options,
10304 SVN_ERR(configure_option_incoming_added_file_text_merge(conflict, ctx,
10307 SVN_ERR(configure_option_incoming_added_file_replace_and_merge(conflict,
10311 SVN_ERR(configure_option_incoming_added_dir_merge(conflict, ctx,
10314 SVN_ERR(configure_option_incoming_added_dir_replace(conflict, ctx,
10317 SVN_ERR(configure_option_incoming_added_dir_replace_and_merge(conflict,
10321 SVN_ERR(configure_option_incoming_delete_ignore(conflict, ctx, *options,
10323 SVN_ERR(configure_option_incoming_delete_accept(conflict, ctx, *options,
10325 SVN_ERR(configure_option_incoming_move_file_merge(conflict, ctx, *options,
10327 SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options,
10329 SVN_ERR(configure_option_local_move_file_merge(conflict, ctx, *options,
10332 return SVN_NO_ERROR;
10335 /* Swallow authz failures and return SVN_NO_ERROR in that case.
10336 * Otherwise, return ERR unchanged. */
10337 static svn_error_t *
10338 ignore_authz_failures(svn_error_t *err)
10340 if (err && ( svn_error_find_cause(err, SVN_ERR_AUTHZ_UNREADABLE)
10341 || svn_error_find_cause(err, SVN_ERR_RA_NOT_AUTHORIZED)
10342 || svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)))
10344 svn_error_clear(err);
10345 err = SVN_NO_ERROR;
10352 svn_client_conflict_tree_get_details(svn_client_conflict_t *conflict,
10353 svn_client_ctx_t *ctx,
10354 apr_pool_t *scratch_pool)
10356 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
10358 if (ctx->notify_func2)
10360 svn_wc_notify_t *notify;
10362 notify = svn_wc_create_notify(
10363 svn_client_conflict_get_local_abspath(conflict),
10364 svn_wc_notify_begin_search_tree_conflict_details,
10366 ctx->notify_func2(ctx->notify_baton2, notify,
10370 /* Collecting conflict details may fail due to insufficient access rights.
10371 * This is not a failure but simply restricts our future options. */
10372 if (conflict->tree_conflict_get_incoming_details_func)
10373 SVN_ERR(ignore_authz_failures(
10374 conflict->tree_conflict_get_incoming_details_func(conflict, ctx,
10378 if (conflict->tree_conflict_get_local_details_func)
10379 SVN_ERR(ignore_authz_failures(
10380 conflict->tree_conflict_get_local_details_func(conflict, ctx,
10383 if (ctx->notify_func2)
10385 svn_wc_notify_t *notify;
10387 notify = svn_wc_create_notify(
10388 svn_client_conflict_get_local_abspath(conflict),
10389 svn_wc_notify_end_search_tree_conflict_details,
10391 ctx->notify_func2(ctx->notify_baton2, notify,
10395 return SVN_NO_ERROR;
10398 svn_client_conflict_option_id_t
10399 svn_client_conflict_option_get_id(svn_client_conflict_option_t *option)
10405 svn_client_conflict_option_get_label(svn_client_conflict_option_t *option,
10406 apr_pool_t *result_pool)
10408 return apr_pstrdup(result_pool, option->label);
10412 svn_client_conflict_option_get_description(svn_client_conflict_option_t *option,
10413 apr_pool_t *result_pool)
10415 return apr_pstrdup(result_pool, option->description);
10418 svn_client_conflict_option_id_t
10419 svn_client_conflict_get_recommended_option_id(svn_client_conflict_t *conflict)
10421 return conflict->recommended_option_id;
10425 svn_client_conflict_text_resolve(svn_client_conflict_t *conflict,
10426 svn_client_conflict_option_t *option,
10427 svn_client_ctx_t *ctx,
10428 apr_pool_t *scratch_pool)
10430 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
10431 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
10433 return SVN_NO_ERROR;
10436 svn_client_conflict_option_t *
10437 svn_client_conflict_option_find_by_id(apr_array_header_t *options,
10438 svn_client_conflict_option_id_t option_id)
10442 for (i = 0; i < options->nelts; i++)
10444 svn_client_conflict_option_t *this_option;
10445 svn_client_conflict_option_id_t this_option_id;
10447 this_option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
10448 this_option_id = svn_client_conflict_option_get_id(this_option);
10450 if (this_option_id == option_id)
10451 return this_option;
10458 svn_client_conflict_text_resolve_by_id(
10459 svn_client_conflict_t *conflict,
10460 svn_client_conflict_option_id_t option_id,
10461 svn_client_ctx_t *ctx,
10462 apr_pool_t *scratch_pool)
10464 apr_array_header_t *resolution_options;
10465 svn_client_conflict_option_t *option;
10467 SVN_ERR(svn_client_conflict_text_get_resolution_options(
10468 &resolution_options, conflict, ctx,
10469 scratch_pool, scratch_pool));
10470 option = svn_client_conflict_option_find_by_id(resolution_options,
10472 if (option == NULL)
10473 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
10475 _("Inapplicable conflict resolution option "
10476 "given for conflicted path '%s'"),
10477 svn_dirent_local_style(conflict->local_abspath,
10480 SVN_ERR(svn_client_conflict_text_resolve(conflict, option, ctx, scratch_pool));
10482 return SVN_NO_ERROR;
10485 svn_client_conflict_option_id_t
10486 svn_client_conflict_text_get_resolution(svn_client_conflict_t *conflict)
10488 return conflict->resolution_text;
10492 svn_client_conflict_prop_resolve(svn_client_conflict_t *conflict,
10493 const char *propname,
10494 svn_client_conflict_option_t *option,
10495 svn_client_ctx_t *ctx,
10496 apr_pool_t *scratch_pool)
10498 SVN_ERR(assert_prop_conflict(conflict, scratch_pool));
10499 option->type_data.prop.propname = propname;
10500 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
10502 return SVN_NO_ERROR;
10506 svn_client_conflict_prop_resolve_by_id(
10507 svn_client_conflict_t *conflict,
10508 const char *propname,
10509 svn_client_conflict_option_id_t option_id,
10510 svn_client_ctx_t *ctx,
10511 apr_pool_t *scratch_pool)
10513 apr_array_header_t *resolution_options;
10514 svn_client_conflict_option_t *option;
10516 SVN_ERR(svn_client_conflict_prop_get_resolution_options(
10517 &resolution_options, conflict, ctx,
10518 scratch_pool, scratch_pool));
10519 option = svn_client_conflict_option_find_by_id(resolution_options,
10521 if (option == NULL)
10522 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
10524 _("Inapplicable conflict resolution option "
10525 "given for conflicted path '%s'"),
10526 svn_dirent_local_style(conflict->local_abspath,
10528 SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option, ctx,
10531 return SVN_NO_ERROR;
10534 svn_client_conflict_option_id_t
10535 svn_client_conflict_prop_get_resolution(svn_client_conflict_t *conflict,
10536 const char *propname)
10538 svn_client_conflict_option_t *option;
10540 option = svn_hash_gets(conflict->resolved_props, propname);
10541 if (option == NULL)
10542 return svn_client_conflict_option_unspecified;
10544 return svn_client_conflict_option_get_id(option);
10548 svn_client_conflict_tree_resolve(svn_client_conflict_t *conflict,
10549 svn_client_conflict_option_t *option,
10550 svn_client_ctx_t *ctx,
10551 apr_pool_t *scratch_pool)
10553 SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
10554 SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
10556 return SVN_NO_ERROR;
10560 svn_client_conflict_tree_resolve_by_id(
10561 svn_client_conflict_t *conflict,
10562 svn_client_conflict_option_id_t option_id,
10563 svn_client_ctx_t *ctx,
10564 apr_pool_t *scratch_pool)
10566 apr_array_header_t *resolution_options;
10567 svn_client_conflict_option_t *option;
10569 SVN_ERR(svn_client_conflict_tree_get_resolution_options(
10570 &resolution_options, conflict, ctx,
10571 scratch_pool, scratch_pool));
10572 option = svn_client_conflict_option_find_by_id(resolution_options,
10574 if (option == NULL)
10575 return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
10577 _("Inapplicable conflict resolution option "
10578 "given for conflicted path '%s'"),
10579 svn_dirent_local_style(conflict->local_abspath,
10581 SVN_ERR(svn_client_conflict_tree_resolve(conflict, option, ctx, scratch_pool));
10583 return SVN_NO_ERROR;
10586 svn_client_conflict_option_id_t
10587 svn_client_conflict_tree_get_resolution(svn_client_conflict_t *conflict)
10589 return conflict->resolution_tree;
10592 /* Return the legacy conflict descriptor which is wrapped by CONFLICT. */
10593 static const svn_wc_conflict_description2_t *
10594 get_conflict_desc2_t(svn_client_conflict_t *conflict)
10596 if (conflict->legacy_text_conflict)
10597 return conflict->legacy_text_conflict;
10599 if (conflict->legacy_tree_conflict)
10600 return conflict->legacy_tree_conflict;
10602 if (conflict->prop_conflicts && conflict->legacy_prop_conflict_propname)
10603 return svn_hash_gets(conflict->prop_conflicts,
10604 conflict->legacy_prop_conflict_propname);
10610 svn_client_conflict_get_conflicted(svn_boolean_t *text_conflicted,
10611 apr_array_header_t **props_conflicted,
10612 svn_boolean_t *tree_conflicted,
10613 svn_client_conflict_t *conflict,
10614 apr_pool_t *result_pool,
10615 apr_pool_t *scratch_pool)
10617 if (text_conflicted)
10618 *text_conflicted = (conflict->legacy_text_conflict != NULL);
10620 if (props_conflicted)
10622 if (conflict->prop_conflicts)
10623 SVN_ERR(svn_hash_keys(props_conflicted, conflict->prop_conflicts,
10626 *props_conflicted = apr_array_make(result_pool, 0,
10627 sizeof(const char*));
10630 if (tree_conflicted)
10631 *tree_conflicted = (conflict->legacy_tree_conflict != NULL);
10633 return SVN_NO_ERROR;
10637 svn_client_conflict_get_local_abspath(svn_client_conflict_t *conflict)
10639 return conflict->local_abspath;
10643 svn_client_conflict_get_operation(svn_client_conflict_t *conflict)
10645 return get_conflict_desc2_t(conflict)->operation;
10648 svn_wc_conflict_action_t
10649 svn_client_conflict_get_incoming_change(svn_client_conflict_t *conflict)
10651 return get_conflict_desc2_t(conflict)->action;
10654 svn_wc_conflict_reason_t
10655 svn_client_conflict_get_local_change(svn_client_conflict_t *conflict)
10657 return get_conflict_desc2_t(conflict)->reason;
10661 svn_client_conflict_get_repos_info(const char **repos_root_url,
10662 const char **repos_uuid,
10663 svn_client_conflict_t *conflict,
10664 apr_pool_t *result_pool,
10665 apr_pool_t *scratch_pool)
10667 if (repos_root_url)
10669 if (get_conflict_desc2_t(conflict)->src_left_version)
10671 get_conflict_desc2_t(conflict)->src_left_version->repos_url;
10672 else if (get_conflict_desc2_t(conflict)->src_right_version)
10674 get_conflict_desc2_t(conflict)->src_right_version->repos_url;
10676 *repos_root_url = NULL;
10681 if (get_conflict_desc2_t(conflict)->src_left_version)
10683 get_conflict_desc2_t(conflict)->src_left_version->repos_uuid;
10684 else if (get_conflict_desc2_t(conflict)->src_right_version)
10686 get_conflict_desc2_t(conflict)->src_right_version->repos_uuid;
10688 *repos_uuid = NULL;
10691 return SVN_NO_ERROR;
10695 svn_client_conflict_get_incoming_old_repos_location(
10696 const char **incoming_old_repos_relpath,
10697 svn_revnum_t *incoming_old_pegrev,
10698 svn_node_kind_t *incoming_old_node_kind,
10699 svn_client_conflict_t *conflict,
10700 apr_pool_t *result_pool,
10701 apr_pool_t *scratch_pool)
10703 if (incoming_old_repos_relpath)
10705 if (get_conflict_desc2_t(conflict)->src_left_version)
10706 *incoming_old_repos_relpath =
10707 get_conflict_desc2_t(conflict)->src_left_version->path_in_repos;
10709 *incoming_old_repos_relpath = NULL;
10712 if (incoming_old_pegrev)
10714 if (get_conflict_desc2_t(conflict)->src_left_version)
10715 *incoming_old_pegrev =
10716 get_conflict_desc2_t(conflict)->src_left_version->peg_rev;
10718 *incoming_old_pegrev = SVN_INVALID_REVNUM;
10721 if (incoming_old_node_kind)
10723 if (get_conflict_desc2_t(conflict)->src_left_version)
10724 *incoming_old_node_kind =
10725 get_conflict_desc2_t(conflict)->src_left_version->node_kind;
10727 *incoming_old_node_kind = svn_node_none;
10730 return SVN_NO_ERROR;
10734 svn_client_conflict_get_incoming_new_repos_location(
10735 const char **incoming_new_repos_relpath,
10736 svn_revnum_t *incoming_new_pegrev,
10737 svn_node_kind_t *incoming_new_node_kind,
10738 svn_client_conflict_t *conflict,
10739 apr_pool_t *result_pool,
10740 apr_pool_t *scratch_pool)
10742 if (incoming_new_repos_relpath)
10744 if (get_conflict_desc2_t(conflict)->src_right_version)
10745 *incoming_new_repos_relpath =
10746 get_conflict_desc2_t(conflict)->src_right_version->path_in_repos;
10748 *incoming_new_repos_relpath = NULL;
10751 if (incoming_new_pegrev)
10753 if (get_conflict_desc2_t(conflict)->src_right_version)
10754 *incoming_new_pegrev =
10755 get_conflict_desc2_t(conflict)->src_right_version->peg_rev;
10757 *incoming_new_pegrev = SVN_INVALID_REVNUM;
10760 if (incoming_new_node_kind)
10762 if (get_conflict_desc2_t(conflict)->src_right_version)
10763 *incoming_new_node_kind =
10764 get_conflict_desc2_t(conflict)->src_right_version->node_kind;
10766 *incoming_new_node_kind = svn_node_none;
10769 return SVN_NO_ERROR;
10773 svn_client_conflict_tree_get_victim_node_kind(svn_client_conflict_t *conflict)
10775 SVN_ERR_ASSERT_NO_RETURN(assert_tree_conflict(conflict, conflict->pool)
10778 return get_conflict_desc2_t(conflict)->node_kind;
10782 svn_client_conflict_prop_get_propvals(const svn_string_t **base_propval,
10783 const svn_string_t **working_propval,
10784 const svn_string_t **incoming_old_propval,
10785 const svn_string_t **incoming_new_propval,
10786 svn_client_conflict_t *conflict,
10787 const char *propname,
10788 apr_pool_t *result_pool)
10790 const svn_wc_conflict_description2_t *desc;
10792 SVN_ERR(assert_prop_conflict(conflict, conflict->pool));
10794 desc = svn_hash_gets(conflict->prop_conflicts, propname);
10796 return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10797 _("Property '%s' is not in conflict."), propname);
10801 svn_string_dup(desc->prop_value_base, result_pool);
10803 if (working_propval)
10805 svn_string_dup(desc->prop_value_working, result_pool);
10807 if (incoming_old_propval)
10808 *incoming_old_propval =
10809 svn_string_dup(desc->prop_value_incoming_old, result_pool);
10811 if (incoming_new_propval)
10812 *incoming_new_propval =
10813 svn_string_dup(desc->prop_value_incoming_new, result_pool);
10815 return SVN_NO_ERROR;
10819 svn_client_conflict_prop_get_reject_abspath(svn_client_conflict_t *conflict)
10821 SVN_ERR_ASSERT_NO_RETURN(assert_prop_conflict(conflict, conflict->pool)
10824 /* svn_wc_conflict_description2_t stores this path in 'their_abspath' */
10825 return get_conflict_desc2_t(conflict)->their_abspath;
10829 svn_client_conflict_text_get_mime_type(svn_client_conflict_t *conflict)
10831 SVN_ERR_ASSERT_NO_RETURN(assert_text_conflict(conflict, conflict->pool)
10834 return get_conflict_desc2_t(conflict)->mime_type;
10838 svn_client_conflict_text_get_contents(const char **base_abspath,
10839 const char **working_abspath,
10840 const char **incoming_old_abspath,
10841 const char **incoming_new_abspath,
10842 svn_client_conflict_t *conflict,
10843 apr_pool_t *result_pool,
10844 apr_pool_t *scratch_pool)
10846 SVN_ERR(assert_text_conflict(conflict, scratch_pool));
10850 if (svn_client_conflict_get_operation(conflict) ==
10851 svn_wc_operation_merge)
10852 *base_abspath = NULL; /* ### WC base contents not available yet */
10853 else /* update/switch */
10854 *base_abspath = get_conflict_desc2_t(conflict)->base_abspath;
10857 if (working_abspath)
10858 *working_abspath = get_conflict_desc2_t(conflict)->my_abspath;
10860 if (incoming_old_abspath)
10861 *incoming_old_abspath = get_conflict_desc2_t(conflict)->base_abspath;
10863 if (incoming_new_abspath)
10864 *incoming_new_abspath = get_conflict_desc2_t(conflict)->their_abspath;
10866 return SVN_NO_ERROR;
10869 /* Set up type-specific data for a new conflict object. */
10870 static svn_error_t *
10871 conflict_type_specific_setup(svn_client_conflict_t *conflict,
10872 apr_pool_t *scratch_pool)
10874 svn_boolean_t tree_conflicted;
10875 svn_wc_conflict_action_t incoming_change;
10876 svn_wc_conflict_reason_t local_change;
10878 /* For now, we only deal with tree conflicts here. */
10879 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
10880 conflict, scratch_pool,
10882 if (!tree_conflicted)
10883 return SVN_NO_ERROR;
10885 /* Set a default description function. */
10886 conflict->tree_conflict_get_incoming_description_func =
10887 conflict_tree_get_incoming_description_generic;
10888 conflict->tree_conflict_get_local_description_func =
10889 conflict_tree_get_local_description_generic;
10891 incoming_change = svn_client_conflict_get_incoming_change(conflict);
10892 local_change = svn_client_conflict_get_local_change(conflict);
10894 /* Set type-specific description and details functions. */
10895 if (incoming_change == svn_wc_conflict_action_delete ||
10896 incoming_change == svn_wc_conflict_action_replace)
10898 conflict->tree_conflict_get_incoming_description_func =
10899 conflict_tree_get_description_incoming_delete;
10900 conflict->tree_conflict_get_incoming_details_func =
10901 conflict_tree_get_details_incoming_delete;
10903 else if (incoming_change == svn_wc_conflict_action_add)
10905 conflict->tree_conflict_get_incoming_description_func =
10906 conflict_tree_get_description_incoming_add;
10907 conflict->tree_conflict_get_incoming_details_func =
10908 conflict_tree_get_details_incoming_add;
10910 else if (incoming_change == svn_wc_conflict_action_edit)
10912 conflict->tree_conflict_get_incoming_description_func =
10913 conflict_tree_get_description_incoming_edit;
10914 conflict->tree_conflict_get_incoming_details_func =
10915 conflict_tree_get_details_incoming_edit;
10918 if (local_change == svn_wc_conflict_reason_missing)
10920 conflict->tree_conflict_get_local_description_func =
10921 conflict_tree_get_description_local_missing;
10922 conflict->tree_conflict_get_local_details_func =
10923 conflict_tree_get_details_local_missing;
10926 return SVN_NO_ERROR;
10930 svn_client_conflict_get(svn_client_conflict_t **conflict,
10931 const char *local_abspath,
10932 svn_client_ctx_t *ctx,
10933 apr_pool_t *result_pool,
10934 apr_pool_t *scratch_pool)
10936 const apr_array_header_t *descs;
10939 *conflict = apr_pcalloc(result_pool, sizeof(**conflict));
10941 (*conflict)->local_abspath = apr_pstrdup(result_pool, local_abspath);
10942 (*conflict)->resolution_text = svn_client_conflict_option_unspecified;
10943 (*conflict)->resolution_tree = svn_client_conflict_option_unspecified;
10944 (*conflict)->resolved_props = apr_hash_make(result_pool);
10945 (*conflict)->recommended_option_id = svn_client_conflict_option_unspecified;
10946 (*conflict)->pool = result_pool;
10948 /* Add all legacy conflict descriptors we can find. Eventually, this code
10949 * path should stop relying on svn_wc_conflict_description2_t entirely. */
10950 SVN_ERR(svn_wc__read_conflict_descriptions2_t(&descs, ctx->wc_ctx,
10952 result_pool, scratch_pool));
10953 for (i = 0; i < descs->nelts; i++)
10955 const svn_wc_conflict_description2_t *desc;
10957 desc = APR_ARRAY_IDX(descs, i, const svn_wc_conflict_description2_t *);
10958 add_legacy_desc_to_conflict(desc, *conflict, result_pool);
10961 SVN_ERR(conflict_type_specific_setup(*conflict, scratch_pool));
10963 return SVN_NO_ERROR;
10966 /* Baton for conflict_status_walker */
10967 struct conflict_status_walker_baton
10969 svn_client_conflict_walk_func_t conflict_walk_func;
10970 void *conflict_walk_func_baton;
10971 svn_client_ctx_t *ctx;
10972 svn_wc_notify_func2_t notify_func;
10973 void *notify_baton;
10974 svn_boolean_t resolved_a_tree_conflict;
10975 apr_hash_t *unresolved_tree_conflicts;
10978 /* Implements svn_wc_notify_func2_t to collect new conflicts caused by
10979 resolving a tree conflict. */
10981 tree_conflict_collector(void *baton,
10982 const svn_wc_notify_t *notify,
10985 struct conflict_status_walker_baton *cswb = baton;
10987 if (cswb->notify_func)
10988 cswb->notify_func(cswb->notify_baton, notify, pool);
10990 if (cswb->unresolved_tree_conflicts
10991 && (notify->action == svn_wc_notify_tree_conflict
10992 || notify->prop_state == svn_wc_notify_state_conflicted
10993 || notify->content_state == svn_wc_notify_state_conflicted))
10995 if (!svn_hash_gets(cswb->unresolved_tree_conflicts, notify->path))
10997 const char *tc_abspath;
10998 apr_pool_t *hash_pool;
11000 hash_pool = apr_hash_pool_get(cswb->unresolved_tree_conflicts);
11001 tc_abspath = apr_pstrdup(hash_pool, notify->path);
11002 svn_hash_sets(cswb->unresolved_tree_conflicts, tc_abspath, "");
11008 * Record a tree conflict resolution failure due to error condition ERR
11009 * in the RESOLVE_LATER hash table. If the hash table is not available
11010 * (meaning the caller does not wish to retry resolution later), or if
11011 * the error condition does not indicate circumstances where another
11012 * existing tree conflict is blocking the resolution attempt, then
11013 * return the error ERR itself.
11015 static svn_error_t *
11016 handle_tree_conflict_resolution_failure(const char *local_abspath,
11018 apr_hash_t *unresolved_tree_conflicts)
11020 const char *tc_abspath;
11022 if (!unresolved_tree_conflicts
11023 || (err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE
11024 && err->apr_err != SVN_ERR_WC_FOUND_CONFLICT))
11025 return svn_error_trace(err); /* Give up. Do not retry resolution later. */
11027 svn_error_clear(err);
11028 tc_abspath = apr_pstrdup(apr_hash_pool_get(unresolved_tree_conflicts),
11031 svn_hash_sets(unresolved_tree_conflicts, tc_abspath, "");
11033 return SVN_NO_ERROR; /* Caller may retry after resolving other conflicts. */
11036 /* Implements svn_wc_status4_t to walk all conflicts to resolve.
11038 static svn_error_t *
11039 conflict_status_walker(void *baton,
11040 const char *local_abspath,
11041 const svn_wc_status3_t *status,
11042 apr_pool_t *scratch_pool)
11044 struct conflict_status_walker_baton *cswb = baton;
11045 svn_client_conflict_t *conflict;
11047 svn_boolean_t tree_conflicted;
11049 if (!status->conflicted)
11050 return SVN_NO_ERROR;
11052 SVN_ERR(svn_client_conflict_get(&conflict, local_abspath, cswb->ctx,
11053 scratch_pool, scratch_pool));
11054 SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
11055 conflict, scratch_pool,
11057 err = cswb->conflict_walk_func(cswb->conflict_walk_func_baton,
11058 conflict, scratch_pool);
11061 if (tree_conflicted)
11062 SVN_ERR(handle_tree_conflict_resolution_failure(
11063 local_abspath, err, cswb->unresolved_tree_conflicts));
11066 return svn_error_trace(err);
11069 if (tree_conflicted)
11071 svn_client_conflict_option_id_t resolution;
11073 resolution = svn_client_conflict_tree_get_resolution(conflict);
11074 if (resolution != svn_client_conflict_option_unspecified &&
11075 resolution != svn_client_conflict_option_postpone)
11076 cswb->resolved_a_tree_conflict = TRUE;
11079 return SVN_NO_ERROR;
11083 svn_client_conflict_walk(const char *local_abspath,
11085 svn_client_conflict_walk_func_t conflict_walk_func,
11086 void *conflict_walk_func_baton,
11087 svn_client_ctx_t *ctx,
11088 apr_pool_t *scratch_pool)
11090 struct conflict_status_walker_baton cswb;
11091 apr_pool_t *iterpool = NULL;
11092 svn_error_t *err = SVN_NO_ERROR;
11094 if (depth == svn_depth_unknown)
11095 depth = svn_depth_infinity;
11097 cswb.conflict_walk_func = conflict_walk_func;
11098 cswb.conflict_walk_func_baton = conflict_walk_func_baton;
11100 cswb.resolved_a_tree_conflict = FALSE;
11101 cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool);
11103 if (ctx->notify_func2)
11104 ctx->notify_func2(ctx->notify_baton2,
11105 svn_wc_create_notify(
11107 svn_wc_notify_conflict_resolver_starting,
11111 /* Swap in our notify_func wrapper. We must revert this before returning! */
11112 cswb.notify_func = ctx->notify_func2;
11113 cswb.notify_baton = ctx->notify_baton2;
11114 ctx->notify_func2 = tree_conflict_collector;
11115 ctx->notify_baton2 = &cswb;
11117 err = svn_wc_walk_status(ctx->wc_ctx,
11120 FALSE /* get_all */,
11121 FALSE /* no_ignore */,
11122 TRUE /* ignore_text_mods */,
11123 NULL /* ignore_patterns */,
11124 conflict_status_walker, &cswb,
11125 ctx->cancel_func, ctx->cancel_baton,
11128 /* If we got new tree conflicts (or delayed conflicts) during the initial
11129 walk, we now walk them one by one as closure. */
11130 while (!err && cswb.unresolved_tree_conflicts &&
11131 apr_hash_count(cswb.unresolved_tree_conflicts))
11133 apr_hash_index_t *hi;
11134 svn_wc_status3_t *status = NULL;
11135 const char *tc_abspath = NULL;
11138 svn_pool_clear(iterpool);
11140 iterpool = svn_pool_create(scratch_pool);
11142 hi = apr_hash_first(scratch_pool, cswb.unresolved_tree_conflicts);
11143 cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool);
11144 cswb.resolved_a_tree_conflict = FALSE;
11146 for (; hi && !err; hi = apr_hash_next(hi))
11148 svn_pool_clear(iterpool);
11150 tc_abspath = apr_hash_this_key(hi);
11152 if (ctx->cancel_func)
11154 err = ctx->cancel_func(ctx->cancel_baton);
11159 err = svn_error_trace(svn_wc_status3(&status, ctx->wc_ctx,
11161 iterpool, iterpool));
11165 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
11166 status, scratch_pool));
11171 if (!err && !cswb.resolved_a_tree_conflict && tc_abspath &&
11172 apr_hash_count(cswb.unresolved_tree_conflicts))
11174 /* None of the remaining conflicts got resolved, without any error.
11175 * Disable the 'unresolved_tree_conflicts' cache and try again. */
11176 cswb.unresolved_tree_conflicts = NULL;
11178 /* Run the most recent resolve operation again.
11179 * We still have status and tc_abspath for that one.
11180 * This should uncover the error which prevents resolution. */
11181 err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
11182 status, scratch_pool));
11183 SVN_ERR_ASSERT(err != NULL);
11185 err = svn_error_createf(
11186 SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
11187 _("Unable to resolve pending conflict on '%s'"),
11188 svn_dirent_local_style(tc_abspath, scratch_pool));
11194 svn_pool_destroy(iterpool);
11196 ctx->notify_func2 = cswb.notify_func;
11197 ctx->notify_baton2 = cswb.notify_baton;
11199 if (!err && ctx->notify_func2)
11200 ctx->notify_func2(ctx->notify_baton2,
11201 svn_wc_create_notify(local_abspath,
11202 svn_wc_notify_conflict_resolver_done,
11206 return svn_error_trace(err);