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