]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_client/conflicts.c
MFV r353615: 9485 Optimize possible split block search space
[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 commited 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       const char *moved_along_path;
810       struct repos_move_info *move;
811       
812       /* See if we can find an even closer move for this moved-along path. */
813       relpath = svn_relpath_skip_ancestor(closest_move->moved_to_repos_relpath,
814                                           deleted_relpath);
815       moved_along_path =
816         svn_relpath_join(closest_move->moved_from_repos_relpath, relpath,
817                          scratch_pool);
818       move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool);
819       if (move)
820         return move;
821     }
822
823   return closest_move;
824 }
825
826 /* Search for nested moves in REVISION, given the already found MOVES,
827  * all DELETED_PATHS, and all COPIES, from the same revision.
828  * Append any nested moves to the MOVES array. */
829 static svn_error_t *
830 find_nested_moves(apr_array_header_t *moves,
831                   apr_hash_t *copies,
832                   apr_array_header_t *deleted_paths,
833                   apr_hash_t *moved_paths,
834                   svn_revnum_t revision,
835                   const char *author,
836                   const char *repos_root_url,
837                   const char *repos_uuid,
838                   svn_ra_session_t *ra_session,
839                   svn_client_ctx_t *ctx,
840                   apr_pool_t *result_pool,
841                   apr_pool_t *scratch_pool)
842 {
843   apr_array_header_t *nested_moves;
844   int i;
845   apr_pool_t *iterpool;
846
847   nested_moves = apr_array_make(result_pool, 0,
848                                 sizeof(struct repos_move_info *));
849   iterpool = svn_pool_create(scratch_pool);
850   for (i = 0; i < deleted_paths->nelts; i++)
851     {
852       const char *deleted_path;
853       const char *child_relpath;
854       const char *moved_along_repos_relpath;
855       struct repos_move_info *move;
856       apr_array_header_t *copies_with_same_source_path;
857       int j;
858       svn_boolean_t related;
859
860       svn_pool_clear(iterpool);
861
862       deleted_path = APR_ARRAY_IDX(deleted_paths, i, const char *);
863       move = map_deleted_path_to_move(deleted_path, moves, iterpool);
864       if (move == NULL)
865         continue;
866       child_relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
867                                                 deleted_path);
868       if (child_relpath == NULL || child_relpath[0] == '\0')
869         continue; /* not a nested move */
870
871       /* Consider: svn mv A B; svn mv B/foo C/foo
872        * Copyfrom for C/foo is A/foo, even though C/foo was moved here from
873        * B/foo. A/foo was not deleted. It is B/foo which was deleted.
874        * We now know about the move A->B and moved-along child_relpath "foo".
875        * Try to detect an ancestral relationship between A/foo and the
876        * moved-along path. */
877       moved_along_repos_relpath =
878         svn_relpath_join(move->moved_from_repos_relpath, child_relpath,
879                          iterpool);
880       copies_with_same_source_path = svn_hash_gets(copies,
881                                                    moved_along_repos_relpath);
882       if (copies_with_same_source_path == NULL)
883         continue; /* not a nested move */
884
885       for (j = 0; j < copies_with_same_source_path->nelts; j++)
886         {
887           struct copy_info *copy;
888
889           copy = APR_ARRAY_IDX(copies_with_same_source_path, j,
890                                struct copy_info *);
891           SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url,
892                                       moved_along_repos_relpath,
893                                       revision,
894                                       copy->copyfrom_path,
895                                       copy->copyfrom_rev,
896                                       TRUE, iterpool));
897           if (related)
898             {
899               struct repos_move_info *nested_move;
900
901               /* Remember details of this move. */
902               SVN_ERR(add_new_move(&nested_move, moved_along_repos_relpath,
903                                    copy->copyto_path, copy->copyfrom_rev,
904                                    copy->node_kind,
905                                    revision, author, moved_paths,
906                                    ra_session, repos_root_url,
907                                    result_pool, iterpool));
908
909               /* Add this move to the list of nested moves in this revision. */
910               APR_ARRAY_PUSH(nested_moves, struct repos_move_info *) =
911                 nested_move;
912             }
913         }
914     }
915   svn_pool_destroy(iterpool);
916
917   /* Add all nested moves found to the list of all moves in this revision. */
918   apr_array_cat(moves, nested_moves);
919
920   return SVN_NO_ERROR;
921 }
922
923 /* Make a shallow copy of the copied LOG_ITEM in COPIES. */
924 static void
925 cache_copied_item(apr_hash_t *copies, const char *changed_path,
926                   svn_log_changed_path2_t *log_item)
927 {
928   apr_pool_t *result_pool = apr_hash_pool_get(copies);
929   struct copy_info *copy = apr_palloc(result_pool, sizeof(*copy));
930   apr_array_header_t *copies_with_same_source_path;
931
932   copy->copyfrom_path = log_item->copyfrom_path;
933   if (log_item->copyfrom_path[0] == '/')
934     copy->copyfrom_path++;
935   copy->copyto_path = changed_path;
936   copy->copyfrom_rev = log_item->copyfrom_rev;
937   copy->node_kind = log_item->node_kind;
938
939   copies_with_same_source_path = apr_hash_get(copies, copy->copyfrom_path,
940                                               APR_HASH_KEY_STRING);
941   if (copies_with_same_source_path == NULL)
942     {
943       copies_with_same_source_path = apr_array_make(result_pool, 1,
944                                                     sizeof(struct copy_info *));
945       apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING,
946                    copies_with_same_source_path);
947     }
948   APR_ARRAY_PUSH(copies_with_same_source_path, struct copy_info *) = copy;
949 }
950
951 /* Implements svn_log_entry_receiver_t.
952  *
953  * Find the revision in which a node, optionally ancestrally related to the
954  * node specified via find_deleted_rev_baton, was deleted, When the revision
955  * was found, store it in BATON->DELETED_REV and abort the log operation
956  * by raising SVN_ERR_CEASE_INVOCATION.
957  *
958  * If no such revision can be found, leave BATON->DELETED_REV and
959  * BATON->REPLACING_NODE_KIND alone.
960  *
961  * If the node was replaced, set BATON->REPLACING_NODE_KIND to the node
962  * kind of the node which replaced the original node. If the node was not
963  * replaced, set BATON->REPLACING_NODE_KIND to svn_node_none.
964  *
965  * This function answers the same question as svn_ra_get_deleted_rev() but
966  * works in cases where we do not already know a revision in which the deleted
967  * node once used to exist.
968  * 
969  * If the node was moved, rather than deleted, return move information
970  * in BATON->MOVE.
971  */
972 static svn_error_t *
973 find_deleted_rev(void *baton,
974                  svn_log_entry_t *log_entry,
975                  apr_pool_t *scratch_pool)
976 {
977   struct find_deleted_rev_baton *b = baton;
978   apr_hash_index_t *hi;
979   apr_pool_t *iterpool;
980   svn_boolean_t deleted_node_found = FALSE;
981   svn_node_kind_t replacing_node_kind = svn_node_none;
982
983   if (b->ctx->notify_func2)
984     {
985       svn_wc_notify_t *notify;
986
987       notify = svn_wc_create_notify(
988                  b->victim_abspath,
989                  svn_wc_notify_tree_conflict_details_progress,
990                  scratch_pool),
991       notify->revision = log_entry->revision;
992       b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
993     }
994
995   /* No paths were changed in this revision.  Nothing to do. */
996   if (! log_entry->changed_paths2)
997     return SVN_NO_ERROR;
998
999   iterpool = svn_pool_create(scratch_pool);
1000   for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
1001        hi != NULL;
1002        hi = apr_hash_next(hi))
1003     {
1004       const char *changed_path = apr_hash_this_key(hi);
1005       svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
1006
1007       svn_pool_clear(iterpool);
1008
1009       /* ### Remove leading slash from paths in log entries. */
1010       if (changed_path[0] == '/')
1011           changed_path++;
1012
1013       /* Check if we already found the deleted node we're looking for. */
1014       if (!deleted_node_found &&
1015           svn_path_compare_paths(b->deleted_repos_relpath, changed_path) == 0 &&
1016           (log_item->action == 'D' || log_item->action == 'R'))
1017         {
1018           deleted_node_found = TRUE;
1019
1020           if (b->related_repos_relpath != NULL &&
1021               b->related_peg_rev != SVN_INVALID_REVNUM)
1022             {
1023               svn_client__pathrev_t *yca_loc;
1024               svn_error_t *err;
1025
1026               /* We found a deleted node which occupies the correct path.
1027                * To be certain that this is the deleted node we're looking for,
1028                * we must establish whether it is ancestrally related to the
1029                * "related node" specified in our baton. */
1030               err = find_yca(&yca_loc,
1031                              b->related_repos_relpath,
1032                              b->related_peg_rev,
1033                              b->deleted_repos_relpath,
1034                              rev_below(log_entry->revision),
1035                              b->repos_root_url, b->repos_uuid,
1036                              b->extra_ra_session, b->ctx, iterpool, iterpool);
1037               if (err)
1038                 {
1039                   /* ### Happens for moves within other moves and copies. */
1040                   if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
1041                     {
1042                       svn_error_clear(err);
1043                       yca_loc = NULL;
1044                     }
1045                   else
1046                     return svn_error_trace(err);
1047                 }
1048
1049               deleted_node_found = (yca_loc != NULL);
1050             }
1051
1052           if (deleted_node_found && log_item->action == 'R')
1053             replacing_node_kind = log_item->node_kind;
1054         }
1055     }
1056   svn_pool_destroy(iterpool);
1057
1058   if (!deleted_node_found)
1059     {
1060       apr_array_header_t *moves;
1061
1062       moves = apr_hash_get(b->moves_table, &log_entry->revision,
1063                            sizeof(svn_revnum_t));
1064       if (moves)
1065         {
1066           struct repos_move_info *move;
1067
1068           move = map_deleted_path_to_move(b->deleted_repos_relpath,
1069                                           moves, scratch_pool);
1070           if (move)
1071             {
1072               const char *relpath;
1073
1074               /* The node was moved. Update our search path accordingly. */
1075               b->move = move;
1076               relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
1077                                                   b->deleted_repos_relpath);
1078               if (relpath)
1079                 b->deleted_repos_relpath =
1080                   svn_relpath_join(move->moved_from_repos_relpath, relpath,
1081                                    b->result_pool);
1082             }
1083         }
1084     }
1085   else
1086     {
1087       svn_string_t *author;
1088
1089       b->deleted_rev = log_entry->revision;
1090       author = svn_hash_gets(log_entry->revprops,
1091                              SVN_PROP_REVISION_AUTHOR);
1092       if (author)
1093         b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
1094       else
1095         b->deleted_rev_author = _("unknown author");
1096           
1097       b->replacing_node_kind = replacing_node_kind;
1098
1099       /* We're done. Abort the log operation. */
1100       return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
1101     }
1102
1103   return SVN_NO_ERROR;
1104 }
1105
1106 /* Return a localised string representation of the local part of a tree
1107    conflict on a file. */
1108 static svn_error_t *
1109 describe_local_file_node_change(const char **description,
1110                                 svn_client_conflict_t *conflict,
1111                                 svn_client_ctx_t *ctx,
1112                                 apr_pool_t *result_pool,
1113                                 apr_pool_t *scratch_pool)
1114 {
1115   svn_wc_conflict_reason_t local_change;
1116   svn_wc_operation_t operation;
1117
1118   local_change = svn_client_conflict_get_local_change(conflict);
1119   operation = svn_client_conflict_get_operation(conflict);
1120
1121   switch (local_change)
1122     {
1123       case svn_wc_conflict_reason_edited:
1124         if (operation == svn_wc_operation_update ||
1125             operation == svn_wc_operation_switch)
1126           *description = _("A file containing uncommitted changes was "
1127                            "found in the working copy.");
1128         else if (operation == svn_wc_operation_merge)
1129           *description = _("A file which differs from the corresponding "
1130                            "file on the merge source branch was found "
1131                            "in the working copy.");
1132         break;
1133       case svn_wc_conflict_reason_obstructed:
1134         *description = _("A file which already occupies this path was found "
1135                          "in the working copy.");
1136         break;
1137       case svn_wc_conflict_reason_unversioned:
1138         *description = _("An unversioned file was found in the working "
1139                          "copy.");
1140         break;
1141       case svn_wc_conflict_reason_deleted:
1142         *description = _("A deleted file was found in the working copy.");
1143         break;
1144       case svn_wc_conflict_reason_missing:
1145         if (operation == svn_wc_operation_update ||
1146             operation == svn_wc_operation_switch)
1147           *description = _("No such file was found in the working copy.");
1148         else if (operation == svn_wc_operation_merge)
1149           {
1150             /* ### display deleted revision */
1151             *description = _("No such file was found in the merge target "
1152                              "working copy.\nPerhaps the file has been "
1153                              "deleted or moved away in the repository's "
1154                              "history?");
1155           }
1156         break;
1157       case svn_wc_conflict_reason_added:
1158       case svn_wc_conflict_reason_replaced:
1159         {
1160           /* ### show more details about copies or replacements? */
1161           *description = _("A file scheduled to be added to the "
1162                            "repository in the next commit was found in "
1163                            "the working copy.");
1164         }
1165         break;
1166       case svn_wc_conflict_reason_moved_away:
1167         {
1168           const char *moved_to_abspath;
1169           svn_error_t *err;
1170
1171           err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 
1172                                             ctx->wc_ctx,
1173                                             conflict->local_abspath,
1174                                             scratch_pool,
1175                                             scratch_pool);
1176           if (err)
1177             {
1178               if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1179                 {
1180                   moved_to_abspath = NULL;
1181                   svn_error_clear(err);
1182                 }
1183               else
1184                 return svn_error_trace(err);
1185             }
1186           if (operation == svn_wc_operation_update ||
1187               operation == svn_wc_operation_switch)
1188             {
1189               if (moved_to_abspath == NULL)
1190                 {
1191                   /* The move no longer exists. */
1192                   *description = _("The file in the working copy had "
1193                                    "been moved away at the time this "
1194                                    "conflict was recorded.");
1195                 }
1196               else
1197                 {
1198                   const char *wcroot_abspath;
1199
1200                   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1201                                              ctx->wc_ctx,
1202                                              conflict->local_abspath,
1203                                              scratch_pool,
1204                                              scratch_pool));
1205                   *description = apr_psprintf(
1206                                    result_pool,
1207                                    _("The file in the working copy was "
1208                                      "moved away to\n'%s'."),
1209                                    svn_dirent_local_style(
1210                                      svn_dirent_skip_ancestor(
1211                                        wcroot_abspath,
1212                                        moved_to_abspath),
1213                                      scratch_pool));
1214                 }
1215             }
1216           else if (operation == svn_wc_operation_merge)
1217             {
1218               if (moved_to_abspath == NULL)
1219                 {
1220                   /* The move probably happened in branch history.
1221                    * This case cannot happen until we detect incoming
1222                    * moves, which we currently don't do. */
1223                   /* ### find deleted/moved revision? */
1224                   *description = _("The file in the working copy had "
1225                                    "been moved away at the time this "
1226                                    "conflict was recorded.");
1227                 }
1228               else
1229                 {
1230                   /* This is a local move in the working copy. */
1231                   const char *wcroot_abspath;
1232
1233                   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1234                                              ctx->wc_ctx,
1235                                              conflict->local_abspath,
1236                                              scratch_pool,
1237                                              scratch_pool));
1238                   *description = apr_psprintf(
1239                                    result_pool,
1240                                    _("The file in the working copy was "
1241                                      "moved away to\n'%s'."),
1242                                    svn_dirent_local_style(
1243                                      svn_dirent_skip_ancestor(
1244                                        wcroot_abspath,
1245                                        moved_to_abspath),
1246                                      scratch_pool));
1247                 }
1248             }
1249           break;
1250         }
1251       case svn_wc_conflict_reason_moved_here:
1252         {
1253           const char *moved_from_abspath;
1254
1255           SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, 
1256                                               ctx->wc_ctx,
1257                                               conflict->local_abspath,
1258                                               scratch_pool,
1259                                               scratch_pool));
1260           if (operation == svn_wc_operation_update ||
1261               operation == svn_wc_operation_switch)
1262             {
1263               if (moved_from_abspath == NULL)
1264                 {
1265                   /* The move no longer exists. */
1266                   *description = _("A file had been moved here in the "
1267                                    "working copy at the time this "
1268                                    "conflict was recorded.");
1269                 }
1270               else
1271                 {
1272                   const char *wcroot_abspath;
1273
1274                   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1275                                              ctx->wc_ctx,
1276                                              conflict->local_abspath,
1277                                              scratch_pool,
1278                                              scratch_pool));
1279                   *description = apr_psprintf(
1280                                    result_pool,
1281                                    _("A file was moved here in the "
1282                                      "working copy from\n'%s'."),
1283                                    svn_dirent_local_style(
1284                                      svn_dirent_skip_ancestor(
1285                                        wcroot_abspath,
1286                                        moved_from_abspath),
1287                                      scratch_pool));
1288                 }
1289             }
1290           else if (operation == svn_wc_operation_merge)
1291             {
1292               if (moved_from_abspath == NULL)
1293                 {
1294                   /* The move probably happened in branch history.
1295                    * This case cannot happen until we detect incoming
1296                    * moves, which we currently don't do. */
1297                   /* ### find deleted/moved revision? */
1298                   *description = _("A file had been moved here in the "
1299                                    "working copy at the time this "
1300                                    "conflict was recorded.");
1301                 }
1302               else
1303                 {
1304                   const char *wcroot_abspath;
1305
1306                   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1307                                              ctx->wc_ctx,
1308                                              conflict->local_abspath,
1309                                              scratch_pool,
1310                                              scratch_pool));
1311                   /* This is a local move in the working copy. */
1312                   *description = apr_psprintf(
1313                                    result_pool,
1314                                    _("A file was moved here in the "
1315                                      "working copy from\n'%s'."),
1316                                    svn_dirent_local_style(
1317                                      svn_dirent_skip_ancestor(
1318                                        wcroot_abspath,
1319                                        moved_from_abspath),
1320                                      scratch_pool));
1321                 }
1322             }
1323           break;
1324         }
1325     }
1326
1327   return SVN_NO_ERROR;
1328 }
1329
1330 /* Return a localised string representation of the local part of a tree
1331    conflict on a directory. */
1332 static svn_error_t *
1333 describe_local_dir_node_change(const char **description,
1334                                svn_client_conflict_t *conflict,
1335                                svn_client_ctx_t *ctx,
1336                                apr_pool_t *result_pool,
1337                                apr_pool_t *scratch_pool)
1338 {
1339   svn_wc_conflict_reason_t local_change;
1340   svn_wc_operation_t operation;
1341
1342   local_change = svn_client_conflict_get_local_change(conflict);
1343   operation = svn_client_conflict_get_operation(conflict);
1344
1345   switch (local_change)
1346     {
1347       case svn_wc_conflict_reason_edited:
1348         if (operation == svn_wc_operation_update ||
1349             operation == svn_wc_operation_switch)
1350           *description = _("A directory containing uncommitted changes "
1351                            "was found in the working copy.");
1352         else if (operation == svn_wc_operation_merge)
1353           *description = _("A directory which differs from the "
1354                            "corresponding directory on the merge source "
1355                            "branch was found in the working copy.");
1356         break;
1357       case svn_wc_conflict_reason_obstructed:
1358         *description = _("A directory which already occupies this path was "
1359                          "found in the working copy.");
1360         break;
1361       case svn_wc_conflict_reason_unversioned:
1362         *description = _("An unversioned directory was found in the "
1363                          "working copy.");
1364         break;
1365       case svn_wc_conflict_reason_deleted:
1366         *description = _("A deleted directory was found in the "
1367                          "working copy.");
1368         break;
1369       case svn_wc_conflict_reason_missing:
1370         if (operation == svn_wc_operation_update ||
1371             operation == svn_wc_operation_switch)
1372           *description = _("No such directory was found in the working copy.");
1373         else if (operation == svn_wc_operation_merge)
1374           {
1375             /* ### display deleted revision */
1376             *description = _("No such directory was found in the merge "
1377                              "target working copy.\nPerhaps the "
1378                              "directory has been deleted or moved away "
1379                              "in the repository's history?");
1380           }
1381         break;
1382       case svn_wc_conflict_reason_added:
1383       case svn_wc_conflict_reason_replaced:
1384         {
1385           /* ### show more details about copies or replacements? */
1386           *description = _("A directory scheduled to be added to the "
1387                            "repository in the next commit was found in "
1388                            "the working copy.");
1389         }
1390         break;
1391       case svn_wc_conflict_reason_moved_away:
1392         {
1393           const char *moved_to_abspath;
1394           svn_error_t *err;
1395
1396           err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 
1397                                             ctx->wc_ctx,
1398                                             conflict->local_abspath,
1399                                             scratch_pool,
1400                                             scratch_pool);
1401           if (err)
1402             {
1403               if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1404                 {
1405                   moved_to_abspath = NULL;
1406                   svn_error_clear(err);
1407                 }
1408               else
1409                 return svn_error_trace(err);
1410             }
1411
1412           if (operation == svn_wc_operation_update ||
1413               operation == svn_wc_operation_switch)
1414             {
1415               if (moved_to_abspath == NULL)
1416                 {
1417                   /* The move no longer exists. */
1418                   *description = _("The directory in the working copy "
1419                                    "had been moved away at the time "
1420                                    "this conflict was recorded.");
1421                 }
1422               else
1423                 {
1424                   const char *wcroot_abspath;
1425
1426                   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1427                                              ctx->wc_ctx,
1428                                              conflict->local_abspath,
1429                                              scratch_pool,
1430                                              scratch_pool));
1431                   *description = apr_psprintf(
1432                                    result_pool,
1433                                    _("The directory in the working copy "
1434                                      "was moved away to\n'%s'."),
1435                                    svn_dirent_local_style(
1436                                      svn_dirent_skip_ancestor(
1437                                        wcroot_abspath,
1438                                        moved_to_abspath),
1439                                      scratch_pool));
1440                 }
1441             }
1442           else if (operation == svn_wc_operation_merge)
1443             {
1444               if (moved_to_abspath == NULL)
1445                 {
1446                   /* The move probably happened in branch history.
1447                    * This case cannot happen until we detect incoming
1448                    * moves, which we currently don't do. */
1449                   /* ### find deleted/moved revision? */
1450                   *description = _("The directory had been moved away "
1451                                    "at the time this conflict was "
1452                                    "recorded.");
1453                 }
1454               else
1455                 {
1456                   /* This is a local move in the working copy. */
1457                   const char *wcroot_abspath;
1458
1459                   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1460                                              ctx->wc_ctx,
1461                                              conflict->local_abspath,
1462                                              scratch_pool,
1463                                              scratch_pool));
1464                   *description = apr_psprintf(
1465                                    result_pool,
1466                                    _("The directory was moved away to\n"
1467                                      "'%s'."),
1468                                    svn_dirent_local_style(
1469                                      svn_dirent_skip_ancestor(
1470                                        wcroot_abspath,
1471                                        moved_to_abspath),
1472                                      scratch_pool));
1473                 }
1474             }
1475           }
1476           break;
1477       case svn_wc_conflict_reason_moved_here:
1478         {
1479           const char *moved_from_abspath;
1480
1481           SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, 
1482                                               ctx->wc_ctx,
1483                                               conflict->local_abspath,
1484                                               scratch_pool,
1485                                               scratch_pool));
1486           if (operation == svn_wc_operation_update ||
1487               operation == svn_wc_operation_switch)
1488             {
1489               if (moved_from_abspath == NULL)
1490                 {
1491                   /* The move no longer exists. */
1492                   *description = _("A directory had been moved here at "
1493                                    "the time this conflict was "
1494                                    "recorded.");
1495                 }
1496               else
1497                 {
1498                   const char *wcroot_abspath;
1499
1500                   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1501                                              ctx->wc_ctx,
1502                                              conflict->local_abspath,
1503                                              scratch_pool,
1504                                              scratch_pool));
1505                   *description = apr_psprintf(
1506                                    result_pool,
1507                                    _("A directory was moved here from\n"
1508                                      "'%s'."),
1509                                    svn_dirent_local_style(
1510                                      svn_dirent_skip_ancestor(
1511                                        wcroot_abspath,
1512                                        moved_from_abspath),
1513                                      scratch_pool));
1514                 }
1515             }
1516           else if (operation == svn_wc_operation_merge)
1517             {
1518               if (moved_from_abspath == NULL)
1519                 {
1520                   /* The move probably happened in branch history.
1521                    * This case cannot happen until we detect incoming
1522                    * moves, which we currently don't do. */
1523                   /* ### find deleted/moved revision? */
1524                   *description = _("A directory had been moved here at "
1525                                    "the time this conflict was "
1526                                    "recorded.");
1527                 }
1528               else
1529                 {
1530                   /* This is a local move in the working copy. */
1531                   const char *wcroot_abspath;
1532
1533                   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
1534                                              ctx->wc_ctx,
1535                                              conflict->local_abspath,
1536                                              scratch_pool,
1537                                              scratch_pool));
1538                   *description = apr_psprintf(
1539                                    result_pool,
1540                                    _("A directory was moved here in "
1541                                      "the working copy from\n'%s'."),
1542                                    svn_dirent_local_style(
1543                                      svn_dirent_skip_ancestor(
1544                                        wcroot_abspath,
1545                                        moved_from_abspath),
1546                                      scratch_pool));
1547                 }
1548             }
1549         }
1550     }
1551
1552   return SVN_NO_ERROR;
1553 }
1554
1555 struct find_moves_baton
1556 {
1557   /* Variables below are arguments provided by the caller of
1558    * svn_ra_get_log2(). */
1559   const char *repos_root_url;
1560   const char *repos_uuid;
1561   svn_client_ctx_t *ctx;
1562   const char *victim_abspath; /* for notifications */
1563   apr_pool_t *result_pool;
1564
1565   /* A hash table mapping a revision number to an array of struct
1566    * repos_move_info * elements, describing moves.
1567    *
1568    * Must be allocated in RESULT_POOL by the caller of svn_ra_get_log2().
1569    *
1570    * If the node was moved, the DELETED_REV is present in this table,
1571    * perhaps along with additional revisions.
1572    *
1573    * Given a sequence of moves which happened in the repository, such as:
1574    *   rA: mv x->z
1575    *   rA: mv a->b
1576    *   rB: mv b->c
1577    *   rC: mv c->d
1578    * we map each revision number to all the moves which happened in the
1579    * revision, which looks as follows: 
1580    *   rA : [(x->z), (a->b)]
1581    *   rB : [(b->c)]
1582    *   rC : [(c->d)]
1583    * This allows us to later find relevant moves based on a revision number.
1584    *
1585    * Additionally, we embed the number of the revision in which a move was
1586    * found inside the repos_move_info structure:
1587    *   rA : [(rA, x->z), (rA, a->b)]
1588    *   rB : [(rB, b->c)]
1589    *   rC : [(rC, c->d)]
1590    * And also, all moves pertaining to the same node are chained into a
1591    * doubly-linked list via 'next' and 'prev' pointers (see definition of
1592    * struct repos_move_info). This can be visualized as follows:
1593    *   rA : [(rA, x->z, prev=>NULL, next=>NULL),
1594    *         (rA, a->b, prev=>NULL, next=>(rB, b->c))]
1595    *   rB : [(rB, b->c), prev=>(rA, a->b), next=>(rC, c->d)]
1596    *   rC : [(rC, c->d), prev=>(rB, c->d), next=>NULL]
1597    * This way, we can look up all moves relevant to a node, forwards and
1598    * backwards in history, once we have located one move in the chain.
1599    *
1600    * In the above example, the data tells us that within the revision
1601    * range rA:C, a was moved to d. However, within the revision range
1602    * rA;B, a was moved to b.
1603    */
1604   apr_hash_t *moves_table;
1605
1606   /* Variables below hold state for find_moves() and are not
1607    * intended to be used by the caller of svn_ra_get_log2().
1608    * Like all other variables, they must be initialized, however. */
1609
1610   /* Temporary map of moved paths to struct repos_move_info.
1611    * Used to link multiple moves of the same node across revisions. */
1612   apr_hash_t *moved_paths;
1613
1614   /* Extra RA session that can be used to make additional requests. */
1615   svn_ra_session_t *extra_ra_session;
1616 };
1617
1618 /* Implements svn_log_entry_receiver_t. */
1619 static svn_error_t *
1620 find_moves(void *baton, svn_log_entry_t *log_entry, apr_pool_t *scratch_pool)
1621 {
1622   struct find_moves_baton *b = baton;
1623   apr_hash_index_t *hi;
1624   apr_pool_t *iterpool;
1625   apr_array_header_t *deleted_paths;
1626   apr_hash_t *copies;
1627   apr_array_header_t *moves;
1628
1629   if (b->ctx->notify_func2)
1630     {
1631       svn_wc_notify_t *notify;
1632
1633       notify = svn_wc_create_notify(
1634                  b->victim_abspath,
1635                  svn_wc_notify_tree_conflict_details_progress,
1636                  scratch_pool),
1637       notify->revision = log_entry->revision;
1638       b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
1639     }
1640
1641   /* No paths were changed in this revision.  Nothing to do. */
1642   if (! log_entry->changed_paths2)
1643     return SVN_NO_ERROR;
1644
1645   copies = apr_hash_make(scratch_pool);
1646   deleted_paths = apr_array_make(scratch_pool, 0, sizeof(const char *));
1647   iterpool = svn_pool_create(scratch_pool);
1648   for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
1649        hi != NULL;
1650        hi = apr_hash_next(hi))
1651     {
1652       const char *changed_path = apr_hash_this_key(hi);
1653       svn_log_changed_path2_t *log_item = apr_hash_this_val(hi);
1654
1655       svn_pool_clear(iterpool);
1656
1657       /* ### Remove leading slash from paths in log entries. */
1658       if (changed_path[0] == '/')
1659           changed_path++;
1660
1661       /* For move detection, scan for copied nodes in this revision. */
1662       if (log_item->action == 'A' && log_item->copyfrom_path)
1663         cache_copied_item(copies, changed_path, log_item);
1664
1665       /* For move detection, store all deleted_paths. */
1666       if (log_item->action == 'D' || log_item->action == 'R')
1667         APR_ARRAY_PUSH(deleted_paths, const char *) =
1668           apr_pstrdup(scratch_pool, changed_path);
1669     }
1670   svn_pool_destroy(iterpool);
1671
1672   /* Check for moves in this revision */
1673   SVN_ERR(find_moves_in_revision(b->extra_ra_session,
1674                                  b->moves_table, b->moved_paths,
1675                                  log_entry, copies, deleted_paths,
1676                                  b->repos_root_url, b->repos_uuid,
1677                                  b->ctx, b->result_pool, scratch_pool));
1678
1679   moves = apr_hash_get(b->moves_table, &log_entry->revision,
1680                        sizeof(svn_revnum_t));
1681   if (moves)
1682     {
1683       const svn_string_t *author;
1684
1685       author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
1686       SVN_ERR(find_nested_moves(moves, copies, deleted_paths,
1687                                 b->moved_paths, log_entry->revision,
1688                                 author ? author->data : _("unknown author"),
1689                                 b->repos_root_url,
1690                                 b->repos_uuid,
1691                                 b->extra_ra_session, b->ctx,
1692                                 b->result_pool, scratch_pool));
1693     }
1694
1695   return SVN_NO_ERROR;
1696 }
1697
1698 /* Find all moves which occured in repository history starting at
1699  * REPOS_RELPATH@START_REV until END_REV (where START_REV > END_REV).
1700  * Return results in *MOVES_TABLE (see struct find_moves_baton for details). */
1701 static svn_error_t *
1702 find_moves_in_revision_range(struct apr_hash_t **moves_table,
1703                              const char *repos_relpath,
1704                              const char *repos_root_url,
1705                              const char *repos_uuid,
1706                              const char *victim_abspath,
1707                              svn_revnum_t start_rev,
1708                              svn_revnum_t end_rev,
1709                              svn_client_ctx_t *ctx,
1710                              apr_pool_t *result_pool,
1711                              apr_pool_t *scratch_pool)
1712 {
1713   svn_ra_session_t *ra_session;
1714   const char *url;
1715   const char *corrected_url;
1716   apr_array_header_t *paths;
1717   apr_array_header_t *revprops;
1718   struct find_moves_baton b = { 0 };
1719
1720   SVN_ERR_ASSERT(start_rev > end_rev);
1721
1722   url = svn_path_url_add_component2(repos_root_url, repos_relpath,
1723                                     scratch_pool);
1724   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
1725                                                url, NULL, NULL, FALSE, FALSE,
1726                                                ctx, scratch_pool,
1727                                                scratch_pool));
1728
1729   paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
1730   APR_ARRAY_PUSH(paths, const char *) = "";
1731
1732   revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
1733   APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
1734
1735   b.repos_root_url = repos_root_url;
1736   b.repos_uuid = repos_uuid;
1737   b.ctx = ctx;
1738   b.victim_abspath = victim_abspath;
1739   b.moves_table = apr_hash_make(result_pool);
1740   b.moved_paths = apr_hash_make(scratch_pool);
1741   b.result_pool = result_pool;
1742   SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL,
1743                               scratch_pool, scratch_pool));
1744
1745   SVN_ERR(svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
1746                           0, /* no limit */
1747                           TRUE, /* need the changed paths list */
1748                           FALSE, /* need to traverse copies */
1749                           FALSE, /* no need for merged revisions */
1750                           revprops,
1751                           find_moves, &b,
1752                           scratch_pool));
1753
1754   *moves_table = b.moves_table;
1755
1756   return SVN_NO_ERROR;
1757 }
1758
1759 /* Return new move information for a moved-along child MOVED_ALONG_RELPATH.
1760  * Set MOVE->NODE_KIND to MOVED_ALONG_NODE_KIND.
1761  * Do not copy MOVE->NEXT and MOVE-PREV.
1762  * If MOVED_ALONG_RELPATH is empty, this effectively copies MOVE to
1763  * RESULT_POOL with NEXT and PREV pointers cleared. */
1764 static struct repos_move_info *
1765 new_path_adjusted_move(struct repos_move_info *move,
1766                        const char *moved_along_relpath,
1767                        svn_node_kind_t moved_along_node_kind,
1768                        apr_pool_t *result_pool)
1769 {
1770   struct repos_move_info *new_move;
1771
1772   new_move = apr_pcalloc(result_pool, sizeof(*new_move));
1773   new_move->moved_from_repos_relpath =
1774     svn_relpath_join(move->moved_from_repos_relpath, moved_along_relpath,
1775                      result_pool);
1776   new_move->moved_to_repos_relpath =
1777     svn_relpath_join(move->moved_to_repos_relpath, moved_along_relpath,
1778                      result_pool);
1779   new_move->rev = move->rev;
1780   new_move->rev_author = apr_pstrdup(result_pool, move->rev_author);
1781   new_move->copyfrom_rev = move->copyfrom_rev;
1782   new_move->node_kind = moved_along_node_kind;
1783   /* Ignore prev and next pointers. Caller will set them if needed. */
1784
1785   return new_move;
1786 }
1787
1788 /* Given a list of MOVES_IN_REVISION, figure out which of these moves again
1789  * move the node which was already moved by PREV_MOVE in the past . */
1790 static svn_error_t *
1791 find_next_moves_in_revision(apr_array_header_t **next_moves,
1792                             apr_array_header_t *moves_in_revision,
1793                             struct repos_move_info *prev_move,
1794                             svn_ra_session_t *ra_session,
1795                             const char *repos_root_url,
1796                             apr_pool_t *result_pool,
1797                             apr_pool_t *scratch_pool)
1798 {
1799   int i;
1800   apr_pool_t *iterpool;
1801
1802   iterpool = svn_pool_create(scratch_pool);
1803   for (i = 0; i < moves_in_revision->nelts; i++)
1804     {
1805       struct repos_move_info *move;
1806       const char *relpath;
1807       const char *deleted_repos_relpath;
1808       svn_boolean_t related;
1809       svn_error_t *err;
1810
1811       svn_pool_clear(iterpool);
1812
1813       /* Check if this move affects the current known path of our node. */
1814       move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *);
1815       relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath,
1816                                           prev_move->moved_to_repos_relpath);
1817       if (relpath == NULL)
1818         continue;
1819
1820       /* It does. So our node must have been deleted again. */
1821       deleted_repos_relpath = svn_relpath_join(move->moved_from_repos_relpath,
1822                                                relpath, iterpool);
1823
1824       /* Tracing back history of the delete-half of this move to the
1825        * copyfrom-revision of the prior move we must end up at the
1826        * delete-half of the prior move. */
1827       err = check_move_ancestry(&related, ra_session, repos_root_url,
1828                                 deleted_repos_relpath, move->rev,
1829                                 prev_move->moved_from_repos_relpath,
1830                                 prev_move->copyfrom_rev,
1831                                 FALSE, scratch_pool);
1832       if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1833         {
1834           svn_error_clear(err);
1835           continue;
1836         }
1837       else
1838         SVN_ERR(err);
1839
1840       if (related)
1841         {
1842           struct repos_move_info *new_move;
1843
1844           /* We have a winner. */
1845           new_move = new_path_adjusted_move(move, relpath, prev_move->node_kind,
1846                                             result_pool);
1847           if (*next_moves == NULL)
1848             *next_moves = apr_array_make(result_pool, 1,
1849                                          sizeof(struct repos_move_info *));
1850           APR_ARRAY_PUSH(*next_moves, struct repos_move_info *) = new_move;
1851         }
1852     }
1853   svn_pool_destroy(iterpool);
1854
1855   return SVN_NO_ERROR;
1856 }
1857
1858 static int
1859 compare_items_as_revs(const svn_sort__item_t *a, const svn_sort__item_t *b)
1860 {
1861   return svn_sort_compare_revisions(a->key, b->key);
1862 }
1863
1864 /* Starting at MOVE->REV, loop over future revisions which contain moves,
1865  * and look for matching next moves in each. Once found, return a list of
1866  * (ambiguous, if more than one) moves in *NEXT_MOVES. */
1867 static svn_error_t *
1868 find_next_moves(apr_array_header_t **next_moves,
1869                 apr_hash_t *moves_table,
1870                 struct repos_move_info *move,
1871                 svn_ra_session_t *ra_session,
1872                 const char *repos_root_url,
1873                 apr_pool_t *result_pool,
1874                 apr_pool_t *scratch_pool)
1875 {
1876   apr_array_header_t *moves;
1877   apr_array_header_t *revisions;
1878   apr_pool_t *iterpool;
1879   int i;
1880
1881   *next_moves = NULL;
1882   revisions = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool);
1883   iterpool = svn_pool_create(scratch_pool);
1884   for (i = 0; i < revisions->nelts; i++)
1885     {
1886       svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t);
1887       svn_revnum_t rev = *(svn_revnum_t *)item.key;
1888
1889       svn_pool_clear(iterpool);
1890
1891       if (rev <= move->rev)
1892         continue;
1893
1894       moves = apr_hash_get(moves_table, &rev, sizeof(rev));
1895       SVN_ERR(find_next_moves_in_revision(next_moves, moves, move,
1896                                           ra_session, repos_root_url,
1897                                           result_pool, iterpool));
1898       if (*next_moves)
1899         break;
1900     }
1901   svn_pool_destroy(iterpool);
1902
1903   return SVN_NO_ERROR;
1904 }
1905
1906 /* Trace all future moves of the node moved by MOVE.
1907  * Update MOVE->PREV and MOVE->NEXT accordingly. */
1908 static svn_error_t *
1909 trace_moved_node(apr_hash_t *moves_table,
1910                  struct repos_move_info *move,
1911                  svn_ra_session_t *ra_session,
1912                  const char *repos_root_url,
1913                  apr_pool_t *result_pool,
1914                  apr_pool_t *scratch_pool)
1915 {
1916   apr_array_header_t *next_moves;
1917
1918   SVN_ERR(find_next_moves(&next_moves, moves_table, move,
1919                           ra_session, repos_root_url,
1920                           result_pool, scratch_pool));
1921   if (next_moves)
1922     {
1923       int i;
1924       apr_pool_t *iterpool;
1925
1926       move->next = next_moves;
1927       iterpool = svn_pool_create(scratch_pool);
1928       for (i = 0; i < next_moves->nelts; i++)
1929         {
1930           struct repos_move_info *next_move;
1931
1932           svn_pool_clear(iterpool);
1933           next_move = APR_ARRAY_IDX(next_moves, i, struct repos_move_info *);
1934           next_move->prev = move;
1935           SVN_ERR(trace_moved_node(moves_table, next_move,
1936                                    ra_session, repos_root_url,
1937                                    result_pool, iterpool));
1938         }
1939       svn_pool_destroy(iterpool);
1940     }
1941
1942   return SVN_NO_ERROR;
1943 }
1944
1945 /* Given a list of MOVES_IN_REVISION, figure out which of these moves
1946  * move the node which was later on moved by NEXT_MOVE. */
1947 static svn_error_t *
1948 find_prev_move_in_revision(struct repos_move_info **prev_move,
1949                            apr_array_header_t *moves_in_revision,
1950                            struct repos_move_info *next_move,
1951                            svn_ra_session_t *ra_session,
1952                            const char *repos_root_url,
1953                            apr_pool_t *result_pool,
1954                            apr_pool_t *scratch_pool)
1955 {
1956   int i;
1957   apr_pool_t *iterpool;
1958
1959   *prev_move = NULL;
1960
1961   iterpool = svn_pool_create(scratch_pool);
1962   for (i = 0; i < moves_in_revision->nelts; i++)
1963     {
1964       struct repos_move_info *move;
1965       const char *relpath;
1966       const char *deleted_repos_relpath;
1967       svn_boolean_t related;
1968       svn_error_t *err;
1969
1970       svn_pool_clear(iterpool);
1971
1972       /* Check if this move affects the current known path of our node. */
1973       move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *);
1974       relpath = svn_relpath_skip_ancestor(next_move->moved_from_repos_relpath,
1975                                           move->moved_to_repos_relpath);
1976       if (relpath == NULL)
1977         continue;
1978
1979       /* It does. So our node must have been deleted. */
1980       deleted_repos_relpath = svn_relpath_join(
1981                                 next_move->moved_from_repos_relpath,
1982                                 relpath, iterpool);
1983
1984       /* Tracing back history of the delete-half of the next move to the
1985        * copyfrom-revision of the prior move we must end up at the
1986        * delete-half of the prior move. */
1987       err = check_move_ancestry(&related, ra_session, repos_root_url,
1988                                 deleted_repos_relpath, next_move->rev,
1989                                 move->moved_from_repos_relpath,
1990                                 move->copyfrom_rev,
1991                                 FALSE, scratch_pool);
1992       if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1993         {
1994           svn_error_clear(err);
1995           continue;
1996         }
1997       else
1998         SVN_ERR(err);
1999
2000       if (related)
2001         {
2002           /* We have a winner. */
2003           *prev_move = new_path_adjusted_move(move, relpath,
2004                                               next_move->node_kind,
2005                                               result_pool);
2006           break;
2007         }
2008     }
2009   svn_pool_destroy(iterpool);
2010
2011   return SVN_NO_ERROR;
2012 }
2013
2014 static int
2015 compare_items_as_revs_reverse(const svn_sort__item_t *a,
2016                               const svn_sort__item_t *b)
2017 {
2018   int c = svn_sort_compare_revisions(a->key, b->key);
2019   if (c < 0)
2020     return 1;
2021   if (c > 0)
2022     return -1;
2023   return c;
2024 }
2025
2026 /* Starting at MOVE->REV, loop over past revisions which contain moves,
2027  * and look for a matching previous move in each. Once found, return
2028  * it in *PREV_MOVE */
2029 static svn_error_t *
2030 find_prev_move(struct repos_move_info **prev_move,
2031                apr_hash_t *moves_table,
2032                struct repos_move_info *move,
2033                svn_ra_session_t *ra_session,
2034                const char *repos_root_url,
2035                apr_pool_t *result_pool,
2036                apr_pool_t *scratch_pool)
2037 {
2038   apr_array_header_t *moves;
2039   apr_array_header_t *revisions;
2040   apr_pool_t *iterpool;
2041   int i;
2042
2043   *prev_move = NULL;
2044   revisions = svn_sort__hash(moves_table, compare_items_as_revs_reverse,
2045                              scratch_pool);
2046   iterpool = svn_pool_create(scratch_pool);
2047   for (i = 0; i < revisions->nelts; i++)
2048     {
2049       svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t);
2050       svn_revnum_t rev = *(svn_revnum_t *)item.key;
2051
2052       svn_pool_clear(iterpool);
2053
2054       if (rev >= move->rev)
2055         continue;
2056
2057       moves = apr_hash_get(moves_table, &rev, sizeof(rev));
2058       SVN_ERR(find_prev_move_in_revision(prev_move, moves, move,
2059                                          ra_session, repos_root_url,
2060                                          result_pool, iterpool));
2061       if (*prev_move)
2062         break;
2063     }
2064   svn_pool_destroy(iterpool);
2065
2066   return SVN_NO_ERROR;
2067 }
2068
2069
2070 /* Trace all past moves of the node moved by MOVE.
2071  * Update MOVE->PREV and MOVE->NEXT accordingly. */
2072 static svn_error_t *
2073 trace_moved_node_backwards(apr_hash_t *moves_table,
2074                            struct repos_move_info *move,
2075                            svn_ra_session_t *ra_session,
2076                            const char *repos_root_url,
2077                            apr_pool_t *result_pool,
2078                            apr_pool_t *scratch_pool)
2079 {
2080   struct repos_move_info *prev_move;
2081
2082   SVN_ERR(find_prev_move(&prev_move, moves_table, move,
2083                          ra_session, repos_root_url,
2084                          result_pool, scratch_pool));
2085   if (prev_move)
2086     {
2087       move->prev = prev_move;
2088       prev_move->next = apr_array_make(result_pool, 1,
2089                                        sizeof(struct repos_move_info *));
2090       APR_ARRAY_PUSH(prev_move->next, struct repos_move_info *) = move;
2091
2092       SVN_ERR(trace_moved_node_backwards(moves_table, prev_move,
2093                                          ra_session, repos_root_url,
2094                                          result_pool, scratch_pool));
2095     }
2096
2097   return SVN_NO_ERROR;
2098 }
2099
2100 static svn_error_t *
2101 reparent_session_and_fetch_node_kind(svn_node_kind_t *node_kind,
2102                                      svn_ra_session_t *ra_session,
2103                                      const char *url,
2104                                      svn_revnum_t peg_rev,
2105                                      apr_pool_t *scratch_pool)
2106 {
2107   svn_error_t *err;
2108
2109   err = svn_ra_reparent(ra_session, url, scratch_pool);
2110   if (err)
2111     {
2112       if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
2113         {
2114           svn_error_clear(err);
2115           *node_kind = svn_node_unknown;
2116           return SVN_NO_ERROR;
2117         }
2118     
2119       return svn_error_trace(err);
2120     }
2121
2122   SVN_ERR(svn_ra_check_path(ra_session, "", peg_rev, node_kind, scratch_pool));
2123
2124   return SVN_NO_ERROR;
2125 }
2126
2127 /* Scan MOVES_TABLE for moves which affect a particular deleted node, and
2128  * build a set of new move information for this node.
2129  * Return heads of all possible move chains in *MOVES.
2130  *
2131  * MOVES_TABLE describes moves which happened at arbitrary paths in the
2132  * repository. DELETED_REPOS_RELPATH may have been moved directly or it
2133  * may have been moved along with a parent path. Move information returned
2134  * from this function represents how DELETED_REPOS_RELPATH itself was moved
2135  * from one path to another, effectively "zooming in" on the effective move
2136  * operations which occurred for this particular node. */
2137 static svn_error_t *
2138 find_operative_moves(apr_array_header_t **moves,
2139                      apr_hash_t *moves_table,
2140                      const char *deleted_repos_relpath,
2141                      svn_revnum_t deleted_rev,
2142                      svn_ra_session_t *ra_session,
2143                      const char *repos_root_url,
2144                      apr_pool_t *result_pool,
2145                      apr_pool_t *scratch_pool)
2146 {
2147   apr_array_header_t *moves_in_deleted_rev;
2148   int i;
2149   apr_pool_t *iterpool;
2150   const char *session_url, *url = NULL;
2151
2152   moves_in_deleted_rev = apr_hash_get(moves_table, &deleted_rev,
2153                                       sizeof(deleted_rev));
2154   if (moves_in_deleted_rev == NULL)
2155     {
2156       *moves = NULL;
2157       return SVN_NO_ERROR;
2158     }
2159
2160   SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool));
2161
2162   /* Look for operative moves in the revision where the node was deleted. */
2163   *moves = apr_array_make(scratch_pool, 0, sizeof(struct repos_move_info *));
2164   iterpool = svn_pool_create(scratch_pool);
2165   for (i = 0; i < moves_in_deleted_rev->nelts; i++)
2166     {
2167       struct repos_move_info *move;
2168       const char *relpath;
2169
2170       svn_pool_clear(iterpool);
2171
2172       move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *);
2173       relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath,
2174                                           deleted_repos_relpath);
2175       if (relpath && relpath[0] != '\0')
2176         {
2177           svn_node_kind_t node_kind;
2178
2179           url = svn_path_url_add_component2(repos_root_url,
2180                                             deleted_repos_relpath,
2181                                             iterpool);
2182           SVN_ERR(reparent_session_and_fetch_node_kind(&node_kind,
2183                                                        ra_session, url,
2184                                                        rev_below(deleted_rev),
2185                                                        iterpool));
2186           move = new_path_adjusted_move(move, relpath, node_kind, result_pool);
2187         }
2188       APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
2189     }
2190
2191   if (url != NULL)
2192     SVN_ERR(svn_ra_reparent(ra_session, session_url, scratch_pool));
2193
2194   /* If we didn't find any applicable moves, return NULL. */
2195   if ((*moves)->nelts == 0)
2196     {
2197       *moves = NULL;
2198       svn_pool_destroy(iterpool);
2199       return SVN_NO_ERROR;
2200    }
2201
2202   /* Figure out what happened to these moves in future revisions. */
2203   for (i = 0; i < (*moves)->nelts; i++)
2204     {
2205       struct repos_move_info *move;
2206
2207       svn_pool_clear(iterpool);
2208
2209       move = APR_ARRAY_IDX(*moves, i, struct repos_move_info *);
2210       SVN_ERR(trace_moved_node(moves_table, move, ra_session, repos_root_url,
2211                                result_pool, iterpool));
2212     }
2213
2214   svn_pool_destroy(iterpool);
2215   return SVN_NO_ERROR;
2216 }
2217
2218 /* Try to find a revision older than START_REV, and its author, which deleted
2219  * DELETED_BASENAME in the directory PARENT_REPOS_RELPATH. Assume the deleted
2220  * node is ancestrally related to RELATED_REPOS_RELPATH@RELATED_PEG_REV.
2221  * If no such revision can be found, set *DELETED_REV to SVN_INVALID_REVNUM
2222  * and *DELETED_REV_AUTHOR to NULL.
2223  * If the node was replaced rather than deleted, set *REPLACING_NODE_KIND to
2224  * the node kind of the replacing node. Else, set it to svn_node_unknown.
2225  * Only request the log for revisions up to END_REV from the server.
2226  * If the deleted node was moved, provide heads of move chains in *MOVES.
2227  * If the node was not moved,set *MOVES to NULL.
2228  */
2229 static svn_error_t *
2230 find_revision_for_suspected_deletion(svn_revnum_t *deleted_rev,
2231                                      const char **deleted_rev_author,
2232                                      svn_node_kind_t *replacing_node_kind,
2233                                      struct apr_array_header_t **moves,
2234                                      svn_client_conflict_t *conflict,
2235                                      const char *deleted_basename,
2236                                      const char *parent_repos_relpath,
2237                                      svn_revnum_t start_rev,
2238                                      svn_revnum_t end_rev,
2239                                      const char *related_repos_relpath,
2240                                      svn_revnum_t related_peg_rev,
2241                                      svn_client_ctx_t *ctx,
2242                                      apr_pool_t *result_pool,
2243                                      apr_pool_t *scratch_pool)
2244 {
2245   svn_ra_session_t *ra_session;
2246   const char *url;
2247   const char *corrected_url;
2248   apr_array_header_t *paths;
2249   apr_array_header_t *revprops;
2250   const char *repos_root_url;
2251   const char *repos_uuid;
2252   struct find_deleted_rev_baton b = { 0 };
2253   const char *victim_abspath;
2254   svn_error_t *err;
2255   apr_hash_t *moves_table;
2256
2257   SVN_ERR_ASSERT(start_rev > end_rev);
2258
2259   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
2260                                              conflict, scratch_pool,
2261                                              scratch_pool));
2262   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
2263
2264   SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath,
2265                                        repos_root_url, repos_uuid,
2266                                        victim_abspath, start_rev, end_rev,
2267                                        ctx, result_pool, scratch_pool));
2268
2269   url = svn_path_url_add_component2(repos_root_url, parent_repos_relpath,
2270                                     scratch_pool);
2271   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
2272                                                url, NULL, NULL, FALSE, FALSE,
2273                                                ctx, scratch_pool,
2274                                                scratch_pool));
2275
2276   paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
2277   APR_ARRAY_PUSH(paths, const char *) = "";
2278
2279   revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
2280   APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
2281
2282   b.victim_abspath = victim_abspath;
2283   b.deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
2284                                              deleted_basename, scratch_pool);
2285   b.related_repos_relpath = related_repos_relpath;
2286   b.related_peg_rev = related_peg_rev;
2287   b.deleted_rev = SVN_INVALID_REVNUM;
2288   b.replacing_node_kind = svn_node_unknown;
2289   b.repos_root_url = repos_root_url;
2290   b.repos_uuid = repos_uuid;
2291   b.ctx = ctx;
2292   b.moves_table = moves_table;
2293   b.result_pool = result_pool;
2294   SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL,
2295                               scratch_pool, scratch_pool));
2296
2297   err = svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
2298                         0, /* no limit */
2299                         TRUE, /* need the changed paths list */
2300                         FALSE, /* need to traverse copies */
2301                         FALSE, /* no need for merged revisions */
2302                         revprops,
2303                         find_deleted_rev, &b,
2304                         scratch_pool);
2305   if (err)
2306     {
2307       if (err->apr_err == SVN_ERR_CEASE_INVOCATION &&
2308           b.deleted_rev != SVN_INVALID_REVNUM)
2309
2310         {
2311           /* Log operation was aborted because we found deleted rev. */
2312           svn_error_clear(err);
2313         }
2314       else
2315         return svn_error_trace(err);
2316     }
2317
2318   if (b.deleted_rev == SVN_INVALID_REVNUM)
2319     {
2320       struct repos_move_info *move = b.move;
2321
2322       if (move)
2323         {
2324           *deleted_rev = move->rev;
2325           *deleted_rev_author = move->rev_author;
2326           *replacing_node_kind = b.replacing_node_kind;
2327           SVN_ERR(find_operative_moves(moves, moves_table,
2328                                        b.deleted_repos_relpath,
2329                                        move->rev,
2330                                        ra_session, repos_root_url,
2331                                        result_pool, scratch_pool));
2332         }
2333       else
2334         {
2335           /* We could not determine the revision in which the node was
2336            * deleted. */
2337           *deleted_rev = SVN_INVALID_REVNUM;
2338           *deleted_rev_author = NULL;
2339           *replacing_node_kind = svn_node_unknown;
2340           *moves = NULL;
2341         }
2342       return SVN_NO_ERROR;
2343     }
2344   else
2345     {
2346       *deleted_rev = b.deleted_rev;
2347       *deleted_rev_author = b.deleted_rev_author;
2348       *replacing_node_kind = b.replacing_node_kind;
2349       SVN_ERR(find_operative_moves(moves, moves_table,
2350                                    b.deleted_repos_relpath, b.deleted_rev,
2351                                    ra_session, repos_root_url,
2352                                    result_pool, scratch_pool));
2353     }
2354
2355   return SVN_NO_ERROR;
2356 }
2357
2358 /* Details for tree conflicts involving a locally missing node. */
2359 struct conflict_tree_local_missing_details
2360 {
2361   /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
2362   svn_revnum_t deleted_rev;
2363
2364   /* Author who committed DELETED_REV. */
2365   const char *deleted_rev_author;
2366
2367   /* The path which was deleted relative to the repository root. */
2368   const char *deleted_repos_relpath;
2369
2370   /* Move information about the conflict victim. If not NULL, this is an
2371    * array of repos_move_info elements. Each element is the head of a
2372    * move chain which starts in DELETED_REV. */
2373   apr_array_header_t *moves;
2374
2375   /* Move information about siblings. Siblings are nodes which share
2376    * a youngest common ancestor with the conflict victim. E.g. in case
2377    * of a merge operation they are part of the merge source branch.
2378    * If not NULL, this is an array of repos_move_info elements.
2379    * Each element is the head of a move chain, which starts at some
2380    * point in history after siblings and conflict victim forked off
2381    * their common ancestor. */
2382   apr_array_header_t *sibling_moves;
2383
2384   /* If not NULL, this is the move target abspath. */
2385   const char *moved_to_abspath;
2386 };
2387
2388 static svn_error_t *
2389 find_related_node(const char **related_repos_relpath,
2390                   svn_revnum_t *related_peg_rev,
2391                   const char *younger_related_repos_relpath,
2392                   svn_revnum_t younger_related_peg_rev,
2393                   const char *older_repos_relpath,
2394                   svn_revnum_t older_peg_rev,
2395                   svn_client_conflict_t *conflict,
2396                   svn_client_ctx_t *ctx,
2397                   apr_pool_t *result_pool,
2398                   apr_pool_t *scratch_pool)
2399 {
2400   const char *repos_root_url;
2401   const char *related_url;
2402   const char *corrected_url;
2403   svn_node_kind_t related_node_kind;
2404   svn_ra_session_t *ra_session;
2405
2406   *related_repos_relpath = NULL;
2407   *related_peg_rev = SVN_INVALID_REVNUM;
2408
2409   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
2410                                              conflict,
2411                                              scratch_pool, scratch_pool));
2412   related_url = svn_path_url_add_component2(repos_root_url,
2413                                             younger_related_repos_relpath,
2414                                             scratch_pool);
2415   SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
2416                                                &corrected_url,
2417                                                related_url, NULL,
2418                                                NULL,
2419                                                FALSE,
2420                                                FALSE,
2421                                                ctx,
2422                                                scratch_pool,
2423                                                scratch_pool));
2424   SVN_ERR(svn_ra_check_path(ra_session, "", younger_related_peg_rev,
2425                             &related_node_kind, scratch_pool));
2426   if (related_node_kind == svn_node_none)
2427     {
2428       svn_revnum_t related_deleted_rev;
2429       const char *related_deleted_rev_author;
2430       svn_node_kind_t related_replacing_node_kind;
2431       const char *related_basename;
2432       const char *related_parent_repos_relpath;
2433       apr_array_header_t *related_moves;
2434
2435       /* Looks like the younger node, which we'd like to use as our
2436        * 'related node', was deleted. Try to find its deleted revision
2437        *  so we can calculate a peg revision at which it exists.
2438        * The younger node is related to the older node, so we can use
2439        * the older node to guide us in our search. */
2440       related_basename = svn_relpath_basename(younger_related_repos_relpath,
2441                                               scratch_pool);
2442       related_parent_repos_relpath =
2443         svn_relpath_dirname(younger_related_repos_relpath, scratch_pool);
2444       SVN_ERR(find_revision_for_suspected_deletion(
2445                 &related_deleted_rev, &related_deleted_rev_author,
2446                 &related_replacing_node_kind, &related_moves,
2447                 conflict, related_basename,
2448                 related_parent_repos_relpath,
2449                 younger_related_peg_rev, 0,
2450                 older_repos_relpath, older_peg_rev,
2451                 ctx, conflict->pool, scratch_pool));
2452
2453       /* If we can't find a related node, bail. */
2454       if (related_deleted_rev == SVN_INVALID_REVNUM)
2455         return SVN_NO_ERROR;
2456
2457       /* The node should exist in the revision before it was deleted. */
2458       *related_repos_relpath = younger_related_repos_relpath;
2459       *related_peg_rev = rev_below(related_deleted_rev);
2460     }
2461   else
2462     {
2463       *related_repos_relpath = younger_related_repos_relpath;
2464       *related_peg_rev = younger_related_peg_rev;
2465     }
2466
2467   return SVN_NO_ERROR;
2468 }
2469
2470 /* Determine if REPOS_RELPATH@PEG_REV was moved at some point in its history.
2471  * History's range of interest ends at END_REV which must be older than PEG_REV.
2472  *
2473  * VICTIM_ABSPATH is the abspath of a conflict victim in the working copy and
2474  * will be used in notifications.
2475  *
2476  * Return any applicable move chain heads in *MOVES.
2477  * If no moves can be found, set *MOVES to NULL. */
2478 static svn_error_t *
2479 find_moves_in_natural_history(apr_array_header_t **moves,
2480                               const char *repos_relpath,
2481                               svn_revnum_t peg_rev,
2482                               svn_node_kind_t node_kind,
2483                               svn_revnum_t end_rev,
2484                               const char *victim_abspath,
2485                               const char *repos_root_url,
2486                               const char *repos_uuid,
2487                               svn_ra_session_t *ra_session,
2488                               svn_client_ctx_t *ctx,
2489                               apr_pool_t *result_pool,
2490                               apr_pool_t *scratch_pool)
2491 {
2492   apr_hash_t *moves_table;
2493   apr_array_header_t *revs;
2494   apr_array_header_t *most_recent_moves = NULL;
2495   int i;
2496   apr_pool_t *iterpool;
2497
2498   *moves = NULL;
2499
2500   SVN_ERR(find_moves_in_revision_range(&moves_table, repos_relpath,
2501                                        repos_root_url, repos_uuid,
2502                                        victim_abspath, peg_rev, end_rev,
2503                                        ctx, scratch_pool, scratch_pool));
2504
2505   iterpool = svn_pool_create(scratch_pool);
2506
2507   /* Scan the moves table for applicable moves. */
2508   revs = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool);
2509   for (i = revs->nelts - 1; i >= 0; i--)
2510     {
2511       svn_sort__item_t item = APR_ARRAY_IDX(revs, i, svn_sort__item_t);
2512       apr_array_header_t *moves_in_rev = apr_hash_get(moves_table, item.key,
2513                                                       sizeof(svn_revnum_t));
2514       int j;
2515
2516       svn_pool_clear(iterpool);
2517
2518       /* Was repos relpath moved to its location in this revision? */
2519       for (j = 0; j < moves_in_rev->nelts; j++)
2520         {
2521           struct repos_move_info *move;
2522           const char *relpath;
2523
2524           move = APR_ARRAY_IDX(moves_in_rev, j, struct repos_move_info *);
2525           relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
2526                                               repos_relpath);
2527           if (relpath)
2528             {
2529               /* If the move did not happen in our peg revision, make
2530                * sure this move happened on the same line of history. */
2531               if (move->rev != peg_rev)
2532                 {
2533                   svn_client__pathrev_t *yca_loc;
2534                   svn_error_t *err;
2535
2536                   err = find_yca(&yca_loc, repos_relpath, peg_rev,
2537                                  repos_relpath, move->rev,
2538                                  repos_root_url, repos_uuid,
2539                                  NULL, ctx, iterpool, iterpool);
2540                   if (err)
2541                     {
2542                       if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
2543                         {
2544                           svn_error_clear(err);
2545                           yca_loc = NULL;
2546                         }
2547                       else
2548                         return svn_error_trace(err);
2549                     }
2550
2551                   if (yca_loc == NULL || yca_loc->rev != move->rev)
2552                     continue;
2553                 }
2554
2555               if (most_recent_moves == NULL)
2556                 most_recent_moves =
2557                   apr_array_make(result_pool, 1,
2558                                  sizeof(struct repos_move_info *));
2559
2560               /* Copy the move to result pool (even if relpath is ""). */
2561               move = new_path_adjusted_move(move, relpath, node_kind,
2562                                             result_pool);
2563               APR_ARRAY_PUSH(most_recent_moves,
2564                              struct repos_move_info *) = move;
2565             }
2566         }
2567
2568       /* If we found one move, or several ambiguous moves, we're done. */
2569       if (most_recent_moves)
2570         break;
2571     }
2572
2573   if (most_recent_moves && most_recent_moves->nelts > 0)
2574     {
2575       *moves = apr_array_make(result_pool, 1,
2576                               sizeof(struct repos_move_info *));
2577
2578       /* Figure out what happened to the most recent moves in prior
2579        * revisions and build move chains. */
2580       for (i = 0; i < most_recent_moves->nelts; i++)
2581         {
2582           struct repos_move_info *move;
2583
2584           svn_pool_clear(iterpool);
2585
2586           move = APR_ARRAY_IDX(most_recent_moves, i, struct repos_move_info *);
2587           SVN_ERR(trace_moved_node_backwards(moves_table, move,
2588                                              ra_session, repos_root_url,
2589                                              result_pool, iterpool));
2590           /* Follow the move chain backwards. */
2591           while (move->prev)
2592             move = move->prev;
2593
2594           /* Return move heads. */
2595           APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
2596         }
2597     }
2598
2599   svn_pool_destroy(iterpool);
2600
2601   return SVN_NO_ERROR;
2602 }
2603
2604 /* Implements tree_conflict_get_details_func_t. */
2605 static svn_error_t *
2606 conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
2607                                         svn_client_ctx_t *ctx,
2608                                         apr_pool_t *scratch_pool)
2609 {
2610   const char *old_repos_relpath;
2611   const char *new_repos_relpath;
2612   const char *parent_repos_relpath;
2613   svn_revnum_t parent_peg_rev;
2614   svn_revnum_t old_rev;
2615   svn_revnum_t new_rev;
2616   svn_revnum_t deleted_rev;
2617   const char *deleted_rev_author;
2618   svn_node_kind_t replacing_node_kind;
2619   const char *deleted_basename;
2620   struct conflict_tree_local_missing_details *details;
2621   apr_array_header_t *moves = NULL;
2622   apr_array_header_t *sibling_moves = NULL;
2623   const char *related_repos_relpath;
2624   svn_revnum_t related_peg_rev;
2625   const char *repos_root_url;
2626   const char *repos_uuid;
2627
2628   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
2629             &old_repos_relpath, &old_rev, NULL, conflict,
2630             scratch_pool, scratch_pool));
2631   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
2632             &new_repos_relpath, &new_rev, NULL, conflict,
2633             scratch_pool, scratch_pool));
2634
2635   /* Scan the conflict victim's parent's log to find a revision which
2636    * deleted the node. */
2637   deleted_basename = svn_dirent_basename(conflict->local_abspath,
2638                                          scratch_pool);
2639   SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev, &parent_repos_relpath,
2640                                       &repos_root_url, &repos_uuid,
2641                                       ctx->wc_ctx,
2642                                       svn_dirent_dirname(
2643                                         conflict->local_abspath,
2644                                         scratch_pool),
2645                                       scratch_pool,
2646                                       scratch_pool));
2647
2648   /* Pick the younger incoming node as our 'related node' which helps
2649    * pin-pointing the deleted conflict victim in history. */
2650   related_repos_relpath = 
2651             (old_rev < new_rev ? new_repos_relpath : old_repos_relpath);
2652   related_peg_rev = (old_rev < new_rev ? new_rev : old_rev);
2653
2654   /* Make sure we're going to search the related node in a revision where
2655    * it exists. The younger incoming node might have been deleted in HEAD. */
2656   if (related_repos_relpath != NULL && related_peg_rev != SVN_INVALID_REVNUM)
2657     SVN_ERR(find_related_node(
2658               &related_repos_relpath, &related_peg_rev,
2659               related_repos_relpath, related_peg_rev,
2660               (old_rev < new_rev ? old_repos_relpath : new_repos_relpath),
2661               (old_rev < new_rev ? old_rev : new_rev),
2662               conflict, ctx, scratch_pool, scratch_pool));
2663     
2664   SVN_ERR(find_revision_for_suspected_deletion(
2665             &deleted_rev, &deleted_rev_author, &replacing_node_kind, &moves,
2666             conflict, deleted_basename, parent_repos_relpath,
2667             parent_peg_rev, 0, related_repos_relpath, related_peg_rev,
2668             ctx, conflict->pool, scratch_pool));
2669
2670   /* If the victim was not deleted then check if the related path was moved. */
2671   if (deleted_rev == SVN_INVALID_REVNUM)
2672     {
2673       const char *victim_abspath;
2674       svn_ra_session_t *ra_session;
2675       const char *url, *corrected_url;
2676       svn_client__pathrev_t *yca_loc;
2677       svn_revnum_t end_rev;
2678       svn_node_kind_t related_node_kind;
2679
2680       /* ### The following describes all moves in terms of forward-merges,
2681        * should do we something else for reverse-merges? */
2682
2683       victim_abspath = svn_client_conflict_get_local_abspath(conflict);
2684       url = svn_path_url_add_component2(repos_root_url, related_repos_relpath,
2685                                         scratch_pool);
2686       SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
2687                                                    &corrected_url,
2688                                                    url, NULL, NULL,
2689                                                    FALSE,
2690                                                    FALSE,
2691                                                    ctx,
2692                                                    scratch_pool,
2693                                                    scratch_pool));
2694
2695       /* Set END_REV to our best guess of the nearest YCA revision. */
2696       SVN_ERR(find_nearest_yca(&yca_loc, related_repos_relpath, related_peg_rev,
2697                                parent_repos_relpath, parent_peg_rev,
2698                                repos_root_url, repos_uuid, ra_session, ctx,
2699                                scratch_pool, scratch_pool));
2700       if (yca_loc == NULL)
2701         return SVN_NO_ERROR;
2702       end_rev = yca_loc->rev;
2703
2704       /* END_REV must be smaller than RELATED_PEG_REV, else the call
2705          to find_moves_in_natural_history() below will error out. */
2706       if (end_rev >= related_peg_rev)
2707         end_rev = related_peg_rev > 0 ? related_peg_rev - 1 : 0;
2708
2709       SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev,
2710                                 &related_node_kind, scratch_pool));
2711       SVN_ERR(find_moves_in_natural_history(&sibling_moves,
2712                                             related_repos_relpath,
2713                                             related_peg_rev,
2714                                             related_node_kind,
2715                                             end_rev,
2716                                             victim_abspath,
2717                                             repos_root_url, repos_uuid,
2718                                             ra_session, ctx,
2719                                             conflict->pool, scratch_pool));
2720
2721       if (sibling_moves == NULL)
2722         return SVN_NO_ERROR;
2723
2724       /* ## TODO: Find the missing node in the WC. */
2725     }
2726
2727   details = apr_pcalloc(conflict->pool, sizeof(*details));
2728   details->deleted_rev = deleted_rev;
2729   details->deleted_rev_author = deleted_rev_author;
2730   if (deleted_rev != SVN_INVALID_REVNUM)
2731     details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
2732                                                       deleted_basename,
2733                                                       conflict->pool); 
2734   details->moves = moves;
2735   details->sibling_moves = sibling_moves;
2736                                          
2737   conflict->tree_conflict_local_details = details;
2738
2739   return SVN_NO_ERROR;
2740 }
2741
2742 /* Return a localised string representation of the local part of a tree
2743    conflict on a non-existent node. */
2744 static svn_error_t *
2745 describe_local_none_node_change(const char **description,
2746                                 svn_client_conflict_t *conflict,
2747                                 apr_pool_t *result_pool,
2748                                 apr_pool_t *scratch_pool)
2749 {
2750   svn_wc_conflict_reason_t local_change;
2751   svn_wc_operation_t operation;
2752
2753   local_change = svn_client_conflict_get_local_change(conflict);
2754   operation = svn_client_conflict_get_operation(conflict);
2755
2756   switch (local_change)
2757     {
2758     case svn_wc_conflict_reason_edited:
2759       *description = _("An item containing uncommitted changes was "
2760                        "found in the working copy.");
2761       break;
2762     case svn_wc_conflict_reason_obstructed:
2763       *description = _("An item which already occupies this path was found in "
2764                        "the working copy.");
2765       break;
2766     case svn_wc_conflict_reason_deleted:
2767       *description = _("A deleted item was found in the working copy.");
2768       break;
2769     case svn_wc_conflict_reason_missing:
2770       if (operation == svn_wc_operation_update ||
2771           operation == svn_wc_operation_switch)
2772         *description = _("No such file or directory was found in the "
2773                          "working copy.");
2774       else if (operation == svn_wc_operation_merge)
2775         {
2776           /* ### display deleted revision */
2777           *description = _("No such file or directory was found in the "
2778                            "merge target working copy.\nThe item may "
2779                            "have been deleted or moved away in the "
2780                            "repository's history.");
2781         }
2782       break;
2783     case svn_wc_conflict_reason_unversioned:
2784       *description = _("An unversioned item was found in the working "
2785                        "copy.");
2786       break;
2787     case svn_wc_conflict_reason_added:
2788     case svn_wc_conflict_reason_replaced:
2789       *description = _("An item scheduled to be added to the repository "
2790                        "in the next commit was found in the working "
2791                        "copy.");
2792       break;
2793     case svn_wc_conflict_reason_moved_away:
2794       *description = _("The item in the working copy had been moved "
2795                        "away at the time this conflict was recorded.");
2796       break;
2797     case svn_wc_conflict_reason_moved_here:
2798       *description = _("An item had been moved here in the working copy "
2799                        "at the time this conflict was recorded.");
2800       break;
2801     }
2802
2803   return SVN_NO_ERROR;
2804 }
2805
2806 /* Append a description of a move chain beginning at NEXT to DESCRIPTION. */
2807 static const char *
2808 append_moved_to_chain_description(const char *description,
2809                                   apr_array_header_t *next,
2810                                   apr_pool_t *result_pool,
2811                                   apr_pool_t *scratch_pool)
2812 {
2813   if (next == NULL)
2814     return description;
2815
2816   while (next)
2817     {
2818       struct repos_move_info *move;
2819
2820       /* Describe the first possible move chain only. Adding multiple chains
2821        * to the description would just be confusing. The user may select a
2822        * different move destination while resolving the conflict. */
2823       move = APR_ARRAY_IDX(next, 0, struct repos_move_info *);
2824
2825       description = apr_psprintf(scratch_pool,
2826                                  _("%s\nAnd then moved away to '^/%s' by "
2827                                    "%s in r%ld."),
2828                                  description, move->moved_to_repos_relpath,
2829                                  move->rev_author, move->rev);
2830       next = move->next;
2831     }
2832
2833   return apr_pstrdup(result_pool, description);
2834 }
2835
2836 /* Implements tree_conflict_get_description_func_t. */
2837 static svn_error_t *
2838 conflict_tree_get_local_description_generic(const char **description,
2839                                             svn_client_conflict_t *conflict,
2840                                             svn_client_ctx_t *ctx,
2841                                             apr_pool_t *result_pool,
2842                                             apr_pool_t *scratch_pool)
2843 {
2844   svn_node_kind_t victim_node_kind;
2845
2846   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
2847
2848   *description = NULL;
2849
2850   switch (victim_node_kind)
2851     {
2852       case svn_node_file:
2853       case svn_node_symlink:
2854         SVN_ERR(describe_local_file_node_change(description, conflict, ctx,
2855                                                 result_pool, scratch_pool));
2856         break;
2857       case svn_node_dir:
2858         SVN_ERR(describe_local_dir_node_change(description, conflict, ctx,
2859                                                result_pool, scratch_pool));
2860         break;
2861       case svn_node_none:
2862       case svn_node_unknown:
2863         SVN_ERR(describe_local_none_node_change(description, conflict,
2864                                                 result_pool, scratch_pool));
2865         break;
2866     }
2867
2868   return SVN_NO_ERROR;
2869 }
2870
2871 /* Implements tree_conflict_get_description_func_t. */
2872 static svn_error_t *
2873 conflict_tree_get_description_local_missing(const char **description,
2874                                             svn_client_conflict_t *conflict,
2875                                             svn_client_ctx_t *ctx,
2876                                             apr_pool_t *result_pool,
2877                                             apr_pool_t *scratch_pool)
2878 {
2879   struct conflict_tree_local_missing_details *details;
2880
2881   details = conflict->tree_conflict_local_details;
2882   if (details == NULL)
2883     return svn_error_trace(conflict_tree_get_local_description_generic(
2884                              description, conflict, ctx,
2885                              result_pool, scratch_pool));
2886
2887   if (details->moves || details->sibling_moves)
2888     {
2889       struct repos_move_info *move;
2890       
2891       *description = _("No such file or directory was found in the "
2892                        "merge target working copy.\n");
2893
2894       if (details->moves)
2895         {
2896           move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
2897           if (move->node_kind == svn_node_file)
2898             *description = apr_psprintf(
2899                              result_pool,
2900                              _("%sThe file was moved to '^/%s' in r%ld by %s."),
2901                              *description, move->moved_to_repos_relpath,
2902                              move->rev, move->rev_author);
2903           else if (move->node_kind == svn_node_dir)
2904             *description = apr_psprintf(
2905                              result_pool,
2906                              _("%sThe directory was moved to '^/%s' in "
2907                                "r%ld by %s."),
2908                              *description, move->moved_to_repos_relpath,
2909                              move->rev, move->rev_author);
2910           else
2911             *description = apr_psprintf(
2912                              result_pool,
2913                              _("%sThe item was moved to '^/%s' in r%ld by %s."),
2914                              *description, move->moved_to_repos_relpath,
2915                              move->rev, move->rev_author);
2916           *description = append_moved_to_chain_description(*description,
2917                                                            move->next,
2918                                                            result_pool,
2919                                                            scratch_pool);
2920         }
2921
2922       if (details->sibling_moves)
2923         {
2924           move = APR_ARRAY_IDX(details->sibling_moves, 0,
2925                                struct repos_move_info *);
2926           if (move->node_kind == svn_node_file)
2927             *description = apr_psprintf(
2928                              result_pool,
2929                              _("%sThe file '^/%s' was moved to '^/%s' "
2930                                "in r%ld by %s."),
2931                              *description, move->moved_from_repos_relpath,
2932                              move->moved_to_repos_relpath,
2933                              move->rev, move->rev_author);
2934           else if (move->node_kind == svn_node_dir)
2935             *description = apr_psprintf(
2936                              result_pool,
2937                              _("%sThe directory '^/%s' was moved to '^/%s' "
2938                                "in r%ld by %s."),
2939                              *description, move->moved_from_repos_relpath,
2940                              move->moved_to_repos_relpath,
2941                              move->rev, move->rev_author);
2942           else
2943             *description = apr_psprintf(
2944                              result_pool,
2945                              _("%sThe item '^/%s' was moved to '^/%s' "
2946                                "in r%ld by %s."),
2947                              *description, move->moved_from_repos_relpath,
2948                              move->moved_to_repos_relpath,
2949                              move->rev, move->rev_author);
2950           *description = append_moved_to_chain_description(*description,
2951                                                            move->next,
2952                                                            result_pool,
2953                                                            scratch_pool);
2954         }
2955     }
2956   else
2957     *description = apr_psprintf(
2958                      result_pool,
2959                      _("No such file or directory was found in the "
2960                        "merge target working copy.\n'^/%s' was deleted "
2961                        "in r%ld by %s."),
2962                      details->deleted_repos_relpath,
2963                      details->deleted_rev, details->deleted_rev_author);
2964
2965   return SVN_NO_ERROR;
2966 }
2967
2968 /* Return a localised string representation of the incoming part of a
2969    conflict; NULL for non-localised odd cases. */
2970 static const char *
2971 describe_incoming_change(svn_node_kind_t kind, svn_wc_conflict_action_t action,
2972                          svn_wc_operation_t operation)
2973 {
2974   switch (kind)
2975     {
2976       case svn_node_file:
2977       case svn_node_symlink:
2978         if (operation == svn_wc_operation_update)
2979           {
2980             switch (action)
2981               {
2982                 case svn_wc_conflict_action_edit:
2983                   return _("An update operation tried to edit a file.");
2984                 case svn_wc_conflict_action_add:
2985                   return _("An update operation tried to add a file.");
2986                 case svn_wc_conflict_action_delete:
2987                   return _("An update operation tried to delete or move "
2988                            "a file.");
2989                 case svn_wc_conflict_action_replace:
2990                   return _("An update operation tried to replace a file.");
2991               }
2992           }
2993         else if (operation == svn_wc_operation_switch)
2994           {
2995             switch (action)
2996               {
2997                 case svn_wc_conflict_action_edit:
2998                   return _("A switch operation tried to edit a file.");
2999                 case svn_wc_conflict_action_add:
3000                   return _("A switch operation tried to add a file.");
3001                 case svn_wc_conflict_action_delete:
3002                   return _("A switch operation tried to delete or move "
3003                            "a file.");
3004                 case svn_wc_conflict_action_replace:
3005                   return _("A switch operation tried to replace a file.");
3006               }
3007           }
3008         else if (operation == svn_wc_operation_merge)
3009           {
3010             switch (action)
3011               {
3012                 case svn_wc_conflict_action_edit:
3013                   return _("A merge operation tried to edit a file.");
3014                 case svn_wc_conflict_action_add:
3015                   return _("A merge operation tried to add a file.");
3016                 case svn_wc_conflict_action_delete:
3017                   return _("A merge operation tried to delete or move "
3018                            "a file.");
3019                 case svn_wc_conflict_action_replace:
3020                   return _("A merge operation tried to replace a file.");
3021             }
3022           }
3023         break;
3024       case svn_node_dir:
3025         if (operation == svn_wc_operation_update)
3026           {
3027             switch (action)
3028               {
3029                 case svn_wc_conflict_action_edit:
3030                   return _("An update operation tried to change a directory.");
3031                 case svn_wc_conflict_action_add:
3032                   return _("An update operation tried to add a directory.");
3033                 case svn_wc_conflict_action_delete:
3034                   return _("An update operation tried to delete or move "
3035                            "a directory.");
3036                 case svn_wc_conflict_action_replace:
3037                   return _("An update operation tried to replace a directory.");
3038               }
3039           }
3040         else if (operation == svn_wc_operation_switch)
3041           {
3042             switch (action)
3043               {
3044                 case svn_wc_conflict_action_edit:
3045                   return _("A switch operation tried to edit a directory.");
3046                 case svn_wc_conflict_action_add:
3047                   return _("A switch operation tried to add a directory.");
3048                 case svn_wc_conflict_action_delete:
3049                   return _("A switch operation tried to delete or move "
3050                            "a directory.");
3051                 case svn_wc_conflict_action_replace:
3052                   return _("A switch operation tried to replace a directory.");
3053               }
3054           }
3055         else if (operation == svn_wc_operation_merge)
3056           {
3057             switch (action)
3058               {
3059                 case svn_wc_conflict_action_edit:
3060                   return _("A merge operation tried to edit a directory.");
3061                 case svn_wc_conflict_action_add:
3062                   return _("A merge operation tried to add a directory.");
3063                 case svn_wc_conflict_action_delete:
3064                   return _("A merge operation tried to delete or move "
3065                            "a directory.");
3066                 case svn_wc_conflict_action_replace:
3067                   return _("A merge operation tried to replace a directory.");
3068             }
3069           }
3070         break;
3071       case svn_node_none:
3072       case svn_node_unknown:
3073         if (operation == svn_wc_operation_update)
3074           {
3075             switch (action)
3076               {
3077                 case svn_wc_conflict_action_edit:
3078                   return _("An update operation tried to edit an item.");
3079                 case svn_wc_conflict_action_add:
3080                   return _("An update operation tried to add an item.");
3081                 case svn_wc_conflict_action_delete:
3082                   return _("An update operation tried to delete or move "
3083                            "an item.");
3084                 case svn_wc_conflict_action_replace:
3085                   return _("An update operation tried to replace an item.");
3086               }
3087           }
3088         else if (operation == svn_wc_operation_switch)
3089           {
3090             switch (action)
3091               {
3092                 case svn_wc_conflict_action_edit:
3093                   return _("A switch operation tried to edit an item.");
3094                 case svn_wc_conflict_action_add:
3095                   return _("A switch operation tried to add an item.");
3096                 case svn_wc_conflict_action_delete:
3097                   return _("A switch operation tried to delete or move "
3098                            "an item.");
3099                 case svn_wc_conflict_action_replace:
3100                   return _("A switch operation tried to replace an item.");
3101               }
3102           }
3103         else if (operation == svn_wc_operation_merge)
3104           {
3105             switch (action)
3106               {
3107                 case svn_wc_conflict_action_edit:
3108                   return _("A merge operation tried to edit an item.");
3109                 case svn_wc_conflict_action_add:
3110                   return _("A merge operation tried to add an item.");
3111                 case svn_wc_conflict_action_delete:
3112                   return _("A merge operation tried to delete or move "
3113                            "an item.");
3114                 case svn_wc_conflict_action_replace:
3115                   return _("A merge operation tried to replace an item.");
3116               }
3117           }
3118         break;
3119     }
3120
3121   return NULL;
3122 }
3123
3124 /* Return a localised string representation of the operation part of a
3125    conflict. */
3126 static const char *
3127 operation_str(svn_wc_operation_t operation)
3128 {
3129   switch (operation)
3130     {
3131     case svn_wc_operation_update: return _("upon update");
3132     case svn_wc_operation_switch: return _("upon switch");
3133     case svn_wc_operation_merge:  return _("upon merge");
3134     case svn_wc_operation_none:   return _("upon none");
3135     }
3136   SVN_ERR_MALFUNCTION_NO_RETURN();
3137   return NULL;
3138 }
3139
3140 svn_error_t *
3141 svn_client_conflict_prop_get_description(const char **description,
3142                                          svn_client_conflict_t *conflict,
3143                                          apr_pool_t *result_pool,
3144                                          apr_pool_t *scratch_pool)
3145 {
3146   const char *reason_str, *action_str;
3147
3148   /* We provide separately translatable strings for the values that we
3149    * know about, and a fall-back in case any other values occur. */
3150   switch (svn_client_conflict_get_local_change(conflict))
3151     {
3152       case svn_wc_conflict_reason_edited:
3153         reason_str = _("local edit");
3154         break;
3155       case svn_wc_conflict_reason_added:
3156         reason_str = _("local add");
3157         break;
3158       case svn_wc_conflict_reason_deleted:
3159         reason_str = _("local delete");
3160         break;
3161       case svn_wc_conflict_reason_obstructed:
3162         reason_str = _("local obstruction");
3163         break;
3164       default:
3165         reason_str = apr_psprintf(
3166                        scratch_pool, _("local %s"),
3167                        svn_token__to_word(
3168                          map_conflict_reason,
3169                          svn_client_conflict_get_local_change(conflict)));
3170         break;
3171     }
3172   switch (svn_client_conflict_get_incoming_change(conflict))
3173     {
3174       case svn_wc_conflict_action_edit:
3175         action_str = _("incoming edit");
3176         break;
3177       case svn_wc_conflict_action_add:
3178         action_str = _("incoming add");
3179         break;
3180       case svn_wc_conflict_action_delete:
3181         action_str = _("incoming delete");
3182         break;
3183       default:
3184         action_str = apr_psprintf(
3185                        scratch_pool, _("incoming %s"),
3186                        svn_token__to_word(
3187                          map_conflict_action,
3188                          svn_client_conflict_get_incoming_change(conflict)));
3189         break;
3190     }
3191   SVN_ERR_ASSERT(reason_str && action_str);
3192
3193   *description = apr_psprintf(result_pool, _("%s, %s %s"),
3194                               reason_str, action_str,
3195                               operation_str(
3196                                 svn_client_conflict_get_operation(conflict)));
3197
3198   return SVN_NO_ERROR;
3199 }
3200
3201 /* Implements tree_conflict_get_description_func_t. */
3202 static svn_error_t *
3203 conflict_tree_get_incoming_description_generic(
3204   const char **incoming_change_description,
3205   svn_client_conflict_t *conflict,
3206   svn_client_ctx_t *ctx,
3207   apr_pool_t *result_pool,
3208   apr_pool_t *scratch_pool)
3209 {
3210   const char *action;
3211   svn_node_kind_t incoming_kind;
3212   svn_wc_conflict_action_t conflict_action;
3213   svn_wc_operation_t conflict_operation;
3214
3215   conflict_action = svn_client_conflict_get_incoming_change(conflict);
3216   conflict_operation = svn_client_conflict_get_operation(conflict);
3217
3218   /* Determine the node kind of the incoming change. */
3219   incoming_kind = svn_node_unknown;
3220   if (conflict_action == svn_wc_conflict_action_edit ||
3221       conflict_action == svn_wc_conflict_action_delete)
3222     {
3223       /* Change is acting on 'src_left' version of the node. */
3224       SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
3225                 NULL, NULL, &incoming_kind, conflict, scratch_pool,
3226                 scratch_pool));
3227     }
3228   else if (conflict_action == svn_wc_conflict_action_add ||
3229            conflict_action == svn_wc_conflict_action_replace)
3230     {
3231       /* Change is acting on 'src_right' version of the node.
3232        *
3233        * ### For 'replace', the node kind is ambiguous. However, src_left
3234        * ### is NULL for replace, so we must use src_right. */
3235       SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
3236                 NULL, NULL, &incoming_kind, conflict, scratch_pool,
3237                 scratch_pool));
3238     }
3239
3240   action = describe_incoming_change(incoming_kind, conflict_action,
3241                                     conflict_operation);
3242   if (action)
3243     {
3244       *incoming_change_description = apr_pstrdup(result_pool, action);
3245     }
3246   else
3247     {
3248       /* A catch-all message for very rare or nominally impossible cases.
3249          It will not be pretty, but is closer to an internal error than
3250          an ordinary user-facing string. */
3251       *incoming_change_description = apr_psprintf(result_pool,
3252                                        _("incoming %s %s"),
3253                                        svn_node_kind_to_word(incoming_kind),
3254                                        svn_token__to_word(map_conflict_action,
3255                                                           conflict_action));
3256     }
3257   return SVN_NO_ERROR;
3258 }
3259
3260 /* Details for tree conflicts involving incoming deletions and replacements. */
3261 struct conflict_tree_incoming_delete_details
3262 {
3263   /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
3264   svn_revnum_t deleted_rev;
3265
3266   /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. The incoming
3267    * delete is the result of a reverse application of this addition. */
3268   svn_revnum_t added_rev;
3269
3270   /* The path which was deleted/added relative to the repository root. */
3271   const char *repos_relpath;
3272
3273   /* Author who committed DELETED_REV/ADDED_REV. */
3274   const char *rev_author;
3275
3276   /* New node kind for a replaced node. This is svn_node_none for deletions. */
3277   svn_node_kind_t replacing_node_kind;
3278
3279   /* Move information. If not NULL, this is an array of repos_move_info *
3280    * elements. Each element is the head of a move chain which starts in
3281    * DELETED_REV or in ADDED_REV (in which case moves should be interpreted
3282    * in reverse). */
3283   apr_array_header_t *moves;
3284
3285   /* A map of repos_relpaths and working copy nodes for an incoming move.
3286    *
3287    * Each key is a "const char *" repository relpath corresponding to a
3288    * possible repository-side move destination node in the revision which
3289    * is the target revision in case of update and switch, or the merge-right
3290    * revision in case of a merge.
3291    *
3292    * Each value is an apr_array_header_t *.
3293    * Each array consists of "const char *" absolute paths to working copy
3294    * nodes which correspond to the repository node selected by the map key.
3295    * Each such working copy node is a potential local move target which can
3296    * be chosen to "follow" the incoming move when resolving a tree conflict.
3297    *
3298    * This may be an empty hash map in case if there is no move target path
3299    * in the working copy. */
3300   apr_hash_t *wc_move_targets;
3301
3302   /* The preferred move target repository relpath. This is our key into
3303    * the WC_MOVE_TARGETS map above (can be overridden by the user). */
3304   const char *move_target_repos_relpath;
3305
3306   /* The current index into the list of working copy nodes corresponding to
3307    * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */
3308   int wc_move_target_idx;
3309 };
3310
3311 /* Get the currently selected repository-side move target path.
3312  * If none was selected yet, determine and return a default one. */
3313 static const char *
3314 get_moved_to_repos_relpath(
3315   struct conflict_tree_incoming_delete_details *details,
3316   apr_pool_t *scratch_pool)
3317 {
3318   struct repos_move_info *move;
3319
3320   if (details->move_target_repos_relpath)
3321     return details->move_target_repos_relpath;
3322
3323   if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) > 0)
3324     {
3325       svn_sort__item_t item;
3326       apr_array_header_t *repos_relpaths;
3327
3328       repos_relpaths = svn_sort__hash(details->wc_move_targets,
3329                                       svn_sort_compare_items_as_paths,
3330                                       scratch_pool);
3331       item = APR_ARRAY_IDX(repos_relpaths, 0, svn_sort__item_t);
3332       return (const char *)item.key;
3333     }
3334
3335   move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3336   return move->moved_to_repos_relpath;
3337 }
3338
3339 static const char *
3340 describe_incoming_deletion_upon_update(
3341   struct conflict_tree_incoming_delete_details *details,
3342   svn_node_kind_t victim_node_kind,
3343   svn_revnum_t old_rev,
3344   svn_revnum_t new_rev,
3345   apr_pool_t *result_pool,
3346   apr_pool_t *scratch_pool)
3347 {
3348   if (details->replacing_node_kind == svn_node_file ||
3349       details->replacing_node_kind == svn_node_symlink)
3350     {
3351       if (victim_node_kind == svn_node_dir)
3352         {
3353           const char *description =
3354             apr_psprintf(result_pool,
3355                          _("Directory updated from r%ld to r%ld was "
3356                            "replaced with a file by %s in r%ld."),
3357                          old_rev, new_rev,
3358                          details->rev_author, details->deleted_rev);
3359           if (details->moves)
3360             {
3361               struct repos_move_info *move;
3362
3363               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3364               description =
3365                 apr_psprintf(result_pool,
3366                              _("%s\nThe replaced directory was moved to "
3367                                "'^/%s'."), description,
3368                              get_moved_to_repos_relpath(details, scratch_pool));
3369               return append_moved_to_chain_description(description,
3370                                                        move->next,
3371                                                        result_pool,
3372                                                        scratch_pool);
3373             }
3374           return description;
3375         }
3376       else if (victim_node_kind == svn_node_file ||
3377                victim_node_kind == svn_node_symlink)
3378         {
3379           const char *description =
3380             apr_psprintf(result_pool,
3381                          _("File updated from r%ld to r%ld was replaced "
3382                            "with a file from another line of history by "
3383                            "%s in r%ld."),
3384                          old_rev, new_rev,
3385                          details->rev_author, details->deleted_rev);
3386           if (details->moves)
3387             {
3388               struct repos_move_info *move;
3389
3390               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3391               description =
3392                 apr_psprintf(result_pool,
3393                              _("%s\nThe replaced file was moved to '^/%s'."),
3394                              description,
3395                              get_moved_to_repos_relpath(details, scratch_pool));
3396               return append_moved_to_chain_description(description,
3397                                                        move->next,
3398                                                        result_pool,
3399                                                        scratch_pool);
3400             }
3401           return description;
3402         }
3403       else
3404         {
3405           const char *description =
3406             apr_psprintf(result_pool,
3407                          _("Item updated from r%ld to r%ld was replaced "
3408                            "with a file by %s in r%ld."), old_rev, new_rev,
3409                          details->rev_author, details->deleted_rev);
3410           if (details->moves)
3411             {
3412               struct repos_move_info *move;
3413
3414               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3415               description =
3416                 apr_psprintf(result_pool,
3417                              _("%s\nThe replaced item was moved to '^/%s'."),
3418                              description,
3419                              get_moved_to_repos_relpath(details, scratch_pool));
3420               return append_moved_to_chain_description(description,
3421                                                        move->next,
3422                                                        result_pool,
3423                                                        scratch_pool);
3424             }
3425           return description;
3426         }
3427     }
3428   else if (details->replacing_node_kind == svn_node_dir)
3429     {
3430       if (victim_node_kind == svn_node_dir)
3431         {
3432           const char *description =
3433             apr_psprintf(result_pool,
3434                           _("Directory updated from r%ld to r%ld was "
3435                             "replaced with a directory from another line "
3436                             "of history by %s in r%ld."),
3437                           old_rev, new_rev,
3438                           details->rev_author, details->deleted_rev);
3439           if (details->moves)
3440             {
3441               struct repos_move_info *move;
3442
3443               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3444               description =
3445                 apr_psprintf(result_pool,
3446                              _("%s\nThe replaced directory was moved to "
3447                                "'^/%s'."), description,
3448                              get_moved_to_repos_relpath(details, scratch_pool));
3449               return append_moved_to_chain_description(description,
3450                                                        move->next,
3451                                                        result_pool,
3452                                                        scratch_pool);
3453             }
3454           return description;
3455         }
3456       else if (victim_node_kind == svn_node_file ||
3457                victim_node_kind == svn_node_symlink)
3458         {
3459           const char *description =
3460             apr_psprintf(result_pool,
3461                          _("File updated from r%ld to r%ld was "
3462                            "replaced with a directory by %s in r%ld."),
3463                          old_rev, new_rev,
3464                          details->rev_author, details->deleted_rev);
3465           if (details->moves)
3466             {
3467               struct repos_move_info *move;
3468
3469               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3470               description =
3471                 apr_psprintf(result_pool,
3472                              _("%s\nThe replaced file was moved to '^/%s'."),
3473                              description,
3474                              get_moved_to_repos_relpath(details, scratch_pool));
3475               return append_moved_to_chain_description(description,
3476                                                        move->next,
3477                                                        result_pool,
3478                                                        scratch_pool);
3479             }
3480           return description;
3481         }
3482       else
3483         {
3484           const char *description =
3485             apr_psprintf(result_pool,
3486                          _("Item updated from r%ld to r%ld was replaced "
3487                            "by %s in r%ld."), old_rev, new_rev,
3488                          details->rev_author, details->deleted_rev);
3489           if (details->moves)
3490             {
3491               struct repos_move_info *move;
3492
3493               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3494               description =
3495                 apr_psprintf(result_pool,
3496                              _("%s\nThe replaced item was moved to '^/%s'."),
3497                              description,
3498                              get_moved_to_repos_relpath(details, scratch_pool));
3499               return append_moved_to_chain_description(description,
3500                                                        move->next,
3501                                                        result_pool,
3502                                                        scratch_pool);
3503             }
3504           return description;
3505         }
3506     }
3507   else
3508     {
3509       if (victim_node_kind == svn_node_dir)
3510         {
3511           if (details->moves)
3512             {
3513               const char *description;
3514               struct repos_move_info *move;
3515
3516               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3517               description =
3518                 apr_psprintf(result_pool,
3519                              _("Directory updated from r%ld to r%ld was "
3520                                "moved to '^/%s' by %s in r%ld."),
3521                              old_rev, new_rev,
3522                              get_moved_to_repos_relpath(details, scratch_pool),
3523                              details->rev_author, details->deleted_rev);
3524               return append_moved_to_chain_description(description,
3525                                                        move->next,
3526                                                        result_pool,
3527                                                        scratch_pool);
3528             }
3529           else
3530             return apr_psprintf(result_pool,
3531                                 _("Directory updated from r%ld to r%ld was "
3532                                   "deleted by %s in r%ld."),
3533                                 old_rev, new_rev,
3534                                 details->rev_author, details->deleted_rev);
3535         }
3536       else if (victim_node_kind == svn_node_file ||
3537                victim_node_kind == svn_node_symlink)
3538         {
3539           if (details->moves)
3540             {
3541               struct repos_move_info *move;
3542               const char *description;
3543
3544               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3545               description =
3546                 apr_psprintf(result_pool,
3547                              _("File updated from r%ld to r%ld was moved "
3548                                "to '^/%s' by %s in r%ld."), old_rev, new_rev,
3549                              get_moved_to_repos_relpath(details, scratch_pool),
3550                              details->rev_author, details->deleted_rev);
3551               return append_moved_to_chain_description(description,
3552                                                        move->next,
3553                                                        result_pool,
3554                                                        scratch_pool);
3555             }
3556           else
3557             return apr_psprintf(result_pool,
3558                                 _("File updated from r%ld to r%ld was "
3559                                   "deleted by %s in r%ld."), old_rev, new_rev,
3560                                 details->rev_author, details->deleted_rev);
3561         }
3562       else
3563         {
3564           if (details->moves)
3565             {
3566               const char *description;
3567               struct repos_move_info *move;
3568
3569               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3570               description = 
3571                 apr_psprintf(result_pool,
3572                              _("Item updated from r%ld to r%ld was moved "
3573                                "to '^/%s' by %s in r%ld."), old_rev, new_rev,
3574                              get_moved_to_repos_relpath(details, scratch_pool),
3575                              details->rev_author, details->deleted_rev);
3576               return append_moved_to_chain_description(description,
3577                                                        move->next,
3578                                                        result_pool,
3579                                                        scratch_pool);
3580             }
3581           else
3582             return apr_psprintf(result_pool,
3583                                 _("Item updated from r%ld to r%ld was "
3584                                   "deleted by %s in r%ld."), old_rev, new_rev,
3585                                 details->rev_author, details->deleted_rev);
3586         }
3587     }
3588 }
3589
3590 static const char *
3591 describe_incoming_reverse_addition_upon_update(
3592   struct conflict_tree_incoming_delete_details *details,
3593   svn_node_kind_t victim_node_kind,
3594   svn_revnum_t old_rev,
3595   svn_revnum_t new_rev,
3596   apr_pool_t *result_pool)
3597 {
3598   if (details->replacing_node_kind == svn_node_file ||
3599       details->replacing_node_kind == svn_node_symlink)
3600     {
3601       if (victim_node_kind == svn_node_dir)
3602         return apr_psprintf(result_pool,
3603                             _("Directory updated backwards from r%ld to r%ld "
3604                               "was a file before the replacement made by %s "
3605                               "in r%ld."), old_rev, new_rev,
3606                             details->rev_author, details->added_rev);
3607       else if (victim_node_kind == svn_node_file ||
3608                victim_node_kind == svn_node_symlink)
3609         return apr_psprintf(result_pool,
3610                             _("File updated backwards from r%ld to r%ld was a "
3611                               "file from another line of history before the "
3612                               "replacement made by %s in r%ld."),
3613                             old_rev, new_rev,
3614                             details->rev_author, details->added_rev);
3615       else
3616         return apr_psprintf(result_pool,
3617                             _("Item updated backwards from r%ld to r%ld was "
3618                               "replaced with a file by %s in r%ld."),
3619                             old_rev, new_rev,
3620                             details->rev_author, details->added_rev);
3621     }
3622   else if (details->replacing_node_kind == svn_node_dir)
3623     {
3624       if (victim_node_kind == svn_node_dir)
3625         return apr_psprintf(result_pool,
3626                             _("Directory updated backwards from r%ld to r%ld "
3627                               "was a directory from another line of history "
3628                               "before the replacement made by %s in "
3629                               "r%ld."), old_rev, new_rev,
3630                             details->rev_author, details->added_rev);
3631       else if (victim_node_kind == svn_node_file ||
3632                victim_node_kind == svn_node_symlink)
3633         return apr_psprintf(result_pool,
3634                             _("File updated backwards from r%ld to r%ld was a "
3635                               "directory before the replacement made by %s "
3636                               "in r%ld."), old_rev, new_rev,
3637                             details->rev_author, details->added_rev);
3638       else
3639         return apr_psprintf(result_pool,
3640                             _("Item updated backwards from r%ld to r%ld was "
3641                               "replaced with a directory by %s in r%ld."),
3642                             old_rev, new_rev,
3643                             details->rev_author, details->added_rev);
3644     }
3645   else
3646     {
3647       if (victim_node_kind == svn_node_dir)
3648         return apr_psprintf(result_pool,
3649                             _("Directory updated backwards from r%ld to r%ld "
3650                               "did not exist before it was added by %s in "
3651                               "r%ld."), old_rev, new_rev,
3652                             details->rev_author, details->added_rev);
3653       else if (victim_node_kind == svn_node_file ||
3654                victim_node_kind == svn_node_symlink)
3655         return apr_psprintf(result_pool,
3656                             _("File updated backwards from r%ld to r%ld did "
3657                               "not exist before it was added by %s in r%ld."),
3658                             old_rev, new_rev,
3659                             details->rev_author, details->added_rev);
3660       else
3661         return apr_psprintf(result_pool,
3662                             _("Item updated backwards from r%ld to r%ld did "
3663                               "not exist before it was added by %s in r%ld."),
3664                             old_rev, new_rev,
3665                             details->rev_author, details->added_rev);
3666     }
3667 }
3668
3669 static const char *
3670 describe_incoming_deletion_upon_switch(
3671   struct conflict_tree_incoming_delete_details *details,
3672   svn_node_kind_t victim_node_kind,
3673   const char *old_repos_relpath,
3674   svn_revnum_t old_rev,
3675   const char *new_repos_relpath,
3676   svn_revnum_t new_rev,
3677   apr_pool_t *result_pool,
3678   apr_pool_t *scratch_pool)
3679 {
3680   if (details->replacing_node_kind == svn_node_file ||
3681       details->replacing_node_kind == svn_node_symlink)
3682     {
3683       if (victim_node_kind == svn_node_dir)
3684         {
3685           const char *description =
3686             apr_psprintf(result_pool,
3687                          _("Directory switched from\n"
3688                            "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3689                            "was replaced with a file by %s in r%ld."),
3690                          old_repos_relpath, old_rev,
3691                          new_repos_relpath, new_rev,
3692                          details->rev_author, details->deleted_rev);
3693           if (details->moves)
3694             {
3695               struct repos_move_info *move;
3696
3697               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3698               description =
3699                 apr_psprintf(result_pool,
3700                              _("%s\nThe replaced directory was moved "
3701                                "to '^/%s'."), description,
3702                              get_moved_to_repos_relpath(details, scratch_pool));
3703               return append_moved_to_chain_description(description,
3704                                                        move->next,
3705                                                        result_pool,
3706                                                        scratch_pool);
3707             }
3708           return description;    
3709         }
3710       else if (victim_node_kind == svn_node_file ||
3711                victim_node_kind == svn_node_symlink)
3712         {
3713           const char *description =
3714             apr_psprintf(result_pool,
3715                          _("File switched from\n"
3716                            "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3717                            "replaced with a file from another line of "
3718                            "history by %s in r%ld."),
3719                          old_repos_relpath, old_rev,
3720                          new_repos_relpath, new_rev,
3721                          details->rev_author, details->deleted_rev);
3722           if (details->moves)
3723             {
3724               struct repos_move_info *move;
3725
3726               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3727               description =
3728                 apr_psprintf(result_pool,
3729                              _("%s\nThe replaced file was moved to '^/%s'."),
3730                              description,
3731                              get_moved_to_repos_relpath(details, scratch_pool));
3732               return append_moved_to_chain_description(description,
3733                                                        move->next,
3734                                                        result_pool,
3735                                                        scratch_pool);
3736             }
3737           return description;
3738         }
3739       else
3740         {
3741           const char *description =
3742             apr_psprintf(result_pool,
3743                          _("Item switched from\n"
3744                            "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3745                            "replaced with a file by %s in r%ld."),
3746                          old_repos_relpath, old_rev,
3747                          new_repos_relpath, new_rev,
3748                          details->rev_author, details->deleted_rev);
3749           if (details->moves)
3750             {
3751               struct repos_move_info *move;
3752
3753               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3754               description =
3755                 apr_psprintf(result_pool,
3756                              _("%s\nThe replaced item was moved to '^/%s'."),
3757                              description,
3758                              get_moved_to_repos_relpath(details, scratch_pool));
3759               return append_moved_to_chain_description(description,
3760                                                        move->next,
3761                                                        result_pool,
3762                                                        scratch_pool);
3763             }
3764           return description;
3765         }
3766     }
3767   else if (details->replacing_node_kind == svn_node_dir)
3768     {
3769       if (victim_node_kind == svn_node_dir)
3770         {
3771           const char *description =
3772             apr_psprintf(result_pool,
3773                          _("Directory switched from\n"
3774                            "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3775                            "was replaced with a directory from another "
3776                            "line of history by %s in r%ld."),
3777                          old_repos_relpath, old_rev,
3778                          new_repos_relpath, new_rev,
3779                          details->rev_author, details->deleted_rev);
3780           if (details->moves)
3781             {
3782               struct repos_move_info *move;
3783
3784               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3785               description =
3786                 apr_psprintf(result_pool,
3787                              _("%s\nThe replaced directory was moved to "
3788                                "'^/%s'."), description,
3789                              get_moved_to_repos_relpath(details, scratch_pool));
3790               return append_moved_to_chain_description(description,
3791                                                        move->next,
3792                                                        result_pool,
3793                                                        scratch_pool);
3794             }
3795           return description;
3796         }
3797       else if (victim_node_kind == svn_node_file ||
3798                victim_node_kind == svn_node_symlink)
3799         {
3800           const char *description =
3801             apr_psprintf(result_pool,
3802                          _("File switched from\n"
3803                            "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3804                            "was replaced with a directory by %s in r%ld."),
3805                          old_repos_relpath, old_rev,
3806                          new_repos_relpath, new_rev,
3807                          details->rev_author, details->deleted_rev);
3808           if (details->moves)
3809             {
3810               struct repos_move_info *move;
3811
3812               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3813               description =
3814                 apr_psprintf(result_pool,
3815                              _("%s\nThe replaced file was moved to '^/%s'."),
3816                              description,
3817                              get_moved_to_repos_relpath(details, scratch_pool));
3818               return append_moved_to_chain_description(description,
3819                                                        move->next,
3820                                                        result_pool,
3821                                                        scratch_pool);
3822             }
3823           return description;
3824         }
3825       else
3826         {
3827           const char *description =
3828             apr_psprintf(result_pool,
3829                          _("Item switched from\n"
3830                            "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3831                            "replaced with a directory by %s in r%ld."),
3832                          old_repos_relpath, old_rev,
3833                          new_repos_relpath, new_rev,
3834                          details->rev_author, details->deleted_rev);
3835           if (details->moves)
3836             {
3837               struct repos_move_info *move;
3838
3839               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3840               description =
3841                 apr_psprintf(result_pool,
3842                              _("%s\nThe replaced item was moved to '^/%s'."),
3843                              description,
3844                              get_moved_to_repos_relpath(details, scratch_pool));
3845               return append_moved_to_chain_description(description,
3846                                                        move->next,
3847                                                        result_pool,
3848                                                        scratch_pool);
3849             }
3850           return description;
3851         }
3852     }
3853   else
3854     {
3855       if (victim_node_kind == svn_node_dir)
3856         {
3857           if (details->moves)
3858             {
3859               struct repos_move_info *move;
3860               const char *description;
3861               
3862               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3863               description =
3864                 apr_psprintf(result_pool,
3865                              _("Directory switched from\n"
3866                                "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3867                                "was moved to '^/%s' by %s in r%ld."),
3868                              old_repos_relpath, old_rev,
3869                              new_repos_relpath, new_rev,
3870                              get_moved_to_repos_relpath(details, scratch_pool),
3871                              details->rev_author, details->deleted_rev);
3872               return append_moved_to_chain_description(description,
3873                                                        move->next,
3874                                                        result_pool,
3875                                                        scratch_pool);
3876             }
3877           else
3878             return apr_psprintf(result_pool,
3879                                 _("Directory switched from\n"
3880                                   "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3881                                   "was deleted by %s in r%ld."),
3882                                 old_repos_relpath, old_rev,
3883                                 new_repos_relpath, new_rev,
3884                                 details->rev_author, details->deleted_rev);
3885         }
3886       else if (victim_node_kind == svn_node_file ||
3887                victim_node_kind == svn_node_symlink)
3888         {
3889           if (details->moves)
3890             {
3891               struct repos_move_info *move;
3892               const char *description;
3893
3894               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3895               description =
3896                 apr_psprintf(result_pool,
3897                              _("File switched from\n"
3898                                "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3899                                "moved to '^/%s' by %s in r%ld."),
3900                              old_repos_relpath, old_rev,
3901                              new_repos_relpath, new_rev,
3902                              get_moved_to_repos_relpath(details, scratch_pool),
3903                              details->rev_author, details->deleted_rev);
3904               return append_moved_to_chain_description(description,
3905                                                        move->next,
3906                                                        result_pool,
3907                                                        scratch_pool);
3908             }
3909           else
3910             return apr_psprintf(result_pool,
3911                                 _("File switched from\n"
3912                                   "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3913                                   "deleted by %s in r%ld."),
3914                                 old_repos_relpath, old_rev,
3915                                 new_repos_relpath, new_rev,
3916                                 details->rev_author, details->deleted_rev);
3917         }
3918       else
3919         {
3920           if (details->moves)
3921             {
3922               struct repos_move_info *move;
3923               const char *description;
3924
3925               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
3926               description =
3927                 apr_psprintf(result_pool,
3928                              _("Item switched from\n"
3929                                "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3930                                "moved to '^/%s' by %s in r%ld."),
3931                              old_repos_relpath, old_rev,
3932                              new_repos_relpath, new_rev,
3933                              get_moved_to_repos_relpath(details, scratch_pool),
3934                              details->rev_author, details->deleted_rev);
3935               return append_moved_to_chain_description(description,
3936                                                        move->next,
3937                                                        result_pool,
3938                                                        scratch_pool);
3939             }
3940           else
3941             return apr_psprintf(result_pool,
3942                                 _("Item switched from\n"
3943                                   "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3944                                   "deleted by %s in r%ld."),
3945                                 old_repos_relpath, old_rev,
3946                                 new_repos_relpath, new_rev,
3947                                 details->rev_author, details->deleted_rev);
3948         }
3949     }
3950 }
3951
3952 static const char *
3953 describe_incoming_reverse_addition_upon_switch(
3954   struct conflict_tree_incoming_delete_details *details,
3955   svn_node_kind_t victim_node_kind,
3956   const char *old_repos_relpath,
3957   svn_revnum_t old_rev,
3958   const char *new_repos_relpath,
3959   svn_revnum_t new_rev,
3960   apr_pool_t *result_pool)
3961 {
3962   if (details->replacing_node_kind == svn_node_file ||
3963       details->replacing_node_kind == svn_node_symlink)
3964     {
3965       if (victim_node_kind == svn_node_dir)
3966         return apr_psprintf(result_pool,
3967                             _("Directory switched from\n"
3968                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3969                               "was a file before the replacement made by %s "
3970                               "in r%ld."),
3971                             old_repos_relpath, old_rev,
3972                             new_repos_relpath, new_rev,
3973                             details->rev_author, details->added_rev);
3974       else if (victim_node_kind == svn_node_file ||
3975                victim_node_kind == svn_node_symlink)
3976         return apr_psprintf(result_pool,
3977                             _("File switched from\n"
3978                               "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas a "
3979                               "file from another line of history before the "
3980                               "replacement made by %s in r%ld."),
3981                             old_repos_relpath, old_rev,
3982                             new_repos_relpath, new_rev,
3983                             details->rev_author, details->added_rev);
3984       else
3985         return apr_psprintf(result_pool,
3986                             _("Item switched from\n"
3987                               "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
3988                               "replaced with a file by %s in r%ld."),
3989                             old_repos_relpath, old_rev,
3990                             new_repos_relpath, new_rev,
3991                             details->rev_author, details->added_rev);
3992     }
3993   else if (details->replacing_node_kind == svn_node_dir)
3994     {
3995       if (victim_node_kind == svn_node_dir)
3996         return apr_psprintf(result_pool,
3997                             _("Directory switched from\n"
3998                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
3999                               "was a directory from another line of history "
4000                               "before the replacement made by %s in r%ld."),
4001                             old_repos_relpath, old_rev,
4002                             new_repos_relpath, new_rev,
4003                             details->rev_author, details->added_rev);
4004       else if (victim_node_kind == svn_node_file ||
4005                victim_node_kind == svn_node_symlink)
4006         return apr_psprintf(result_pool,
4007                             _("Directory switched from\n"
4008                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4009                               "was a file before the replacement made by %s "
4010                               "in r%ld."),
4011                             old_repos_relpath, old_rev,
4012                             new_repos_relpath, new_rev,
4013                             details->rev_author, details->added_rev);
4014       else
4015         return apr_psprintf(result_pool,
4016                             _("Item switched from\n"
4017                               "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4018                               "replaced with a directory by %s in r%ld."),
4019                             old_repos_relpath, old_rev,
4020                             new_repos_relpath, new_rev,
4021                             details->rev_author, details->added_rev);
4022     }
4023   else
4024     {
4025       if (victim_node_kind == svn_node_dir)
4026         return apr_psprintf(result_pool,
4027                             _("Directory switched from\n"
4028                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4029                               "did not exist before it was added by %s in "
4030                               "r%ld."),
4031                             old_repos_relpath, old_rev,
4032                             new_repos_relpath, new_rev,
4033                             details->rev_author, details->added_rev);
4034       else if (victim_node_kind == svn_node_file ||
4035                victim_node_kind == svn_node_symlink)
4036         return apr_psprintf(result_pool,
4037                             _("File switched from\n"
4038                               "'^/%s@%ld'\nto\n'^/%s@%ld'\ndid "
4039                               "not exist before it was added by %s in "
4040                               "r%ld."),
4041                             old_repos_relpath, old_rev,
4042                             new_repos_relpath, new_rev,
4043                             details->rev_author, details->added_rev);
4044       else
4045         return apr_psprintf(result_pool,
4046                             _("Item switched from\n"
4047                               "'^/%s@%ld'\nto\n'^/%s@%ld'\ndid "
4048                               "not exist before it was added by %s in "
4049                               "r%ld."),
4050                             old_repos_relpath, old_rev,
4051                             new_repos_relpath, new_rev,
4052                             details->rev_author, details->added_rev);
4053     }
4054 }
4055
4056 static const char *
4057 describe_incoming_deletion_upon_merge(
4058   struct conflict_tree_incoming_delete_details *details,
4059   svn_node_kind_t victim_node_kind,
4060   const char *old_repos_relpath,
4061   svn_revnum_t old_rev,
4062   const char *new_repos_relpath,
4063   svn_revnum_t new_rev,
4064   apr_pool_t *result_pool,
4065   apr_pool_t *scratch_pool)
4066 {
4067   if (details->replacing_node_kind == svn_node_file ||
4068       details->replacing_node_kind == svn_node_symlink)
4069     {
4070       if (victim_node_kind == svn_node_dir)
4071         {
4072           const char *description =
4073             apr_psprintf(result_pool,
4074                          _("Directory merged from\n"
4075                            "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4076                            "was replaced with a file by %s in r%ld."),
4077                          old_repos_relpath, old_rev,
4078                          new_repos_relpath, new_rev,
4079                          details->rev_author, details->deleted_rev);
4080           if (details->moves)
4081             {
4082               struct repos_move_info *move;
4083
4084               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4085               description =
4086                 apr_psprintf(result_pool,
4087                              _("%s\nThe replaced directory was moved to "
4088                                "'^/%s'."), description,
4089                              get_moved_to_repos_relpath(details, scratch_pool));
4090               return append_moved_to_chain_description(description,
4091                                                        move->next,
4092                                                        result_pool,
4093                                                        scratch_pool);
4094             }
4095           return description;
4096         }
4097       else if (victim_node_kind == svn_node_file ||
4098                victim_node_kind == svn_node_symlink)
4099         {
4100           const char *description =
4101             apr_psprintf(result_pool,
4102                          _("File merged from\n"
4103                            "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4104                            "replaced with a file from another line of "
4105                            "history by %s in r%ld."),
4106                          old_repos_relpath, old_rev,
4107                          new_repos_relpath, new_rev,
4108                          details->rev_author, details->deleted_rev);
4109           if (details->moves)
4110             {
4111               struct repos_move_info *move;
4112
4113               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4114               description =
4115                 apr_psprintf(result_pool,
4116                              _("%s\nThe replaced file was moved to '^/%s'."),
4117                              description,
4118                              get_moved_to_repos_relpath(details, scratch_pool));
4119               return append_moved_to_chain_description(description,
4120                                                        move->next,
4121                                                        result_pool,
4122                                                        scratch_pool);
4123             }
4124           return description;
4125         }
4126       else
4127         return apr_psprintf(result_pool,
4128                             _("Item merged from\n"
4129                               "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4130                               "replaced with a file by %s in r%ld."),
4131                             old_repos_relpath, old_rev,
4132                             new_repos_relpath, new_rev,
4133                             details->rev_author, details->deleted_rev);
4134     }
4135   else if (details->replacing_node_kind == svn_node_dir)
4136     {
4137       if (victim_node_kind == svn_node_dir)
4138         {
4139           const char *description =
4140             apr_psprintf(result_pool,
4141                          _("Directory merged from\n"
4142                            "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4143                            "was replaced with a directory from another "
4144                            "line of history by %s in r%ld."),
4145                          old_repos_relpath, old_rev,
4146                          new_repos_relpath, new_rev,
4147                          details->rev_author, details->deleted_rev);
4148           if (details->moves)
4149             {
4150               struct repos_move_info *move;
4151
4152               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4153               description =
4154                 apr_psprintf(result_pool,
4155                              _("%s\nThe replaced directory was moved to "
4156                                "'^/%s'."), description,
4157                              get_moved_to_repos_relpath(details, scratch_pool));
4158               return append_moved_to_chain_description(description,
4159                                                        move->next,
4160                                                        result_pool,
4161                                                        scratch_pool);
4162             }
4163           return description;
4164         }
4165       else if (victim_node_kind == svn_node_file ||
4166                victim_node_kind == svn_node_symlink)
4167         {
4168           const char *description =
4169             apr_psprintf(result_pool,
4170                          _("File merged from\n"
4171                            "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4172                            "was replaced with a directory by %s in r%ld."),
4173                          old_repos_relpath, old_rev,
4174                          new_repos_relpath, new_rev,
4175                          details->rev_author, details->deleted_rev);
4176           if (details->moves)
4177             {
4178               struct repos_move_info *move;
4179
4180               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4181               description =
4182                 apr_psprintf(result_pool,
4183                              _("%s\nThe replaced file was moved to '^/%s'."),
4184                              description,
4185                              get_moved_to_repos_relpath(details, scratch_pool));
4186               return append_moved_to_chain_description(description,
4187                                                        move->next,
4188                                                        result_pool,
4189                                                        scratch_pool);
4190             }
4191           return description;
4192         }
4193       else
4194         {
4195           const char *description =
4196             apr_psprintf(result_pool,
4197                          _("Item merged from\n"
4198                            "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4199                            "replaced with a directory by %s in r%ld."),
4200                          old_repos_relpath, old_rev,
4201                          new_repos_relpath, new_rev,
4202                          details->rev_author, details->deleted_rev);
4203           if (details->moves)
4204             {
4205               struct repos_move_info *move;
4206
4207               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4208               description =
4209                 apr_psprintf(result_pool,
4210                              _("%s\nThe replaced item was moved to '^/%s'."),
4211                              description,
4212                              get_moved_to_repos_relpath(details, scratch_pool));
4213               return append_moved_to_chain_description(description,
4214                                                        move->next,
4215                                                        result_pool,
4216                                                        scratch_pool);
4217             }
4218           return description;
4219         }
4220     }
4221   else
4222     {
4223       if (victim_node_kind == svn_node_dir)
4224         {
4225           if (details->moves)
4226             {
4227               struct repos_move_info *move;
4228               const char *description;
4229
4230               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4231               description =
4232                 apr_psprintf(result_pool,
4233                              _("Directory merged from\n"
4234                                "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4235                                "moved to '^/%s' by %s in r%ld."),
4236                              old_repos_relpath, old_rev,
4237                              new_repos_relpath, new_rev,
4238                              get_moved_to_repos_relpath(details, scratch_pool),
4239                              details->rev_author, details->deleted_rev);
4240               return append_moved_to_chain_description(description,
4241                                                        move->next,
4242                                                        result_pool,
4243                                                        scratch_pool);
4244             }
4245           else
4246             return apr_psprintf(result_pool,
4247                                 _("Directory merged from\n"
4248                                   "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4249                                   "deleted by %s in r%ld."),
4250                                 old_repos_relpath, old_rev,
4251                                 new_repos_relpath, new_rev,
4252                                 details->rev_author, details->deleted_rev);
4253         }
4254       else if (victim_node_kind == svn_node_file ||
4255                victim_node_kind == svn_node_symlink)
4256         {
4257           if (details->moves)
4258             {
4259               struct repos_move_info *move;
4260               const char *description;
4261
4262               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4263               description =
4264                 apr_psprintf(result_pool,
4265                              _("File merged from\n"
4266                                "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4267                                "moved to '^/%s' by %s in r%ld."),
4268                              old_repos_relpath, old_rev,
4269                              new_repos_relpath, new_rev,
4270                              get_moved_to_repos_relpath(details, scratch_pool),
4271                              details->rev_author, details->deleted_rev);
4272               return append_moved_to_chain_description(description,
4273                                                        move->next,
4274                                                        result_pool,
4275                                                        scratch_pool);
4276             }
4277           else
4278             return apr_psprintf(result_pool,
4279                                 _("File merged from\n"
4280                                   "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4281                                   "deleted by %s in r%ld."),
4282                                 old_repos_relpath, old_rev,
4283                                 new_repos_relpath, new_rev,
4284                                 details->rev_author, details->deleted_rev);
4285         }
4286       else
4287         {
4288           if (details->moves)
4289             {
4290               struct repos_move_info *move;
4291               const char *description;
4292
4293               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
4294               description =
4295                 apr_psprintf(result_pool,
4296                              _("Item merged from\n"
4297                                "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4298                                "moved to '^/%s' by %s in r%ld."),
4299                              old_repos_relpath, old_rev,
4300                              new_repos_relpath, new_rev,
4301                              get_moved_to_repos_relpath(details, scratch_pool),
4302                              details->rev_author, details->deleted_rev);
4303               return append_moved_to_chain_description(description,
4304                                                        move->next,
4305                                                        result_pool,
4306                                                        scratch_pool);
4307             }
4308           else
4309             return apr_psprintf(result_pool,
4310                                 _("Item merged from\n"
4311                                   "'^/%s@%ld'\nto\n'^/%s@%ld'\nwas "
4312                                   "deleted by %s in r%ld."),
4313                                 old_repos_relpath, old_rev,
4314                                 new_repos_relpath, new_rev,
4315                                 details->rev_author, details->deleted_rev);
4316         }
4317     }
4318 }
4319
4320 static const char *
4321 describe_incoming_reverse_addition_upon_merge(
4322   struct conflict_tree_incoming_delete_details *details,
4323   svn_node_kind_t victim_node_kind,
4324   const char *old_repos_relpath,
4325   svn_revnum_t old_rev,
4326   const char *new_repos_relpath,
4327   svn_revnum_t new_rev,
4328   apr_pool_t *result_pool)
4329 {
4330   if (details->replacing_node_kind == svn_node_file ||
4331       details->replacing_node_kind == svn_node_symlink)
4332     {
4333       if (victim_node_kind == svn_node_dir)
4334         return apr_psprintf(result_pool,
4335                             _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4336                               "^/%s@%ld was a file before the replacement "
4337                               "made by %s in r%ld."),
4338                             old_repos_relpath, old_rev,
4339                             new_repos_relpath, new_rev,
4340                             details->rev_author, details->added_rev);
4341       else if (victim_node_kind == svn_node_file ||
4342                victim_node_kind == svn_node_symlink)
4343         return apr_psprintf(result_pool,
4344                             _("File reverse-merged from\n"
4345                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4346                               "was a file from another line of history before "
4347                               "the replacement made by %s in r%ld."),
4348                             old_repos_relpath, old_rev,
4349                             new_repos_relpath, new_rev,
4350                             details->rev_author, details->added_rev);
4351       else
4352         return apr_psprintf(result_pool,
4353                             _("Item reverse-merged from\n"
4354                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4355                               "was replaced with a file by %s in r%ld."),
4356                             old_repos_relpath, old_rev,
4357                             new_repos_relpath, new_rev,
4358                             details->rev_author, details->added_rev);
4359     }
4360   else if (details->replacing_node_kind == svn_node_dir)
4361     {
4362       if (victim_node_kind == svn_node_dir)
4363         return apr_psprintf(result_pool,
4364                             _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4365                               "^/%s@%ld was a directory from another line "
4366                               "of history before the replacement made by %s "
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 if (victim_node_kind == svn_node_file ||
4372                victim_node_kind == svn_node_symlink)
4373         return apr_psprintf(result_pool,
4374                             _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4375                               "^/%s@%ld was a file before the replacement "
4376                               "made by %s in r%ld."),
4377                             old_repos_relpath, old_rev,
4378                             new_repos_relpath, new_rev,
4379                             details->rev_author, details->added_rev);
4380       else
4381         return apr_psprintf(result_pool,
4382                             _("Item reverse-merged from\n"
4383                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4384                               "was replaced with a directory by %s in r%ld."),
4385                             old_repos_relpath, old_rev,
4386                             new_repos_relpath, new_rev,
4387                             details->rev_author, details->added_rev);
4388     }
4389   else
4390     {
4391       if (victim_node_kind == svn_node_dir)
4392         return apr_psprintf(result_pool,
4393                             _("Directory reverse-merged from\n'^/%s@%ld'\nto "
4394                               "^/%s@%ld did not exist before it was added "
4395                               "by %s in r%ld."),
4396                             old_repos_relpath, old_rev,
4397                             new_repos_relpath, new_rev,
4398                             details->rev_author, details->added_rev);
4399       else if (victim_node_kind == svn_node_file ||
4400                victim_node_kind == svn_node_symlink)
4401         return apr_psprintf(result_pool,
4402                             _("File reverse-merged from\n"
4403                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4404                               "did not exist before it was added by %s in "
4405                               "r%ld."),
4406                             old_repos_relpath, old_rev,
4407                             new_repos_relpath, new_rev,
4408                             details->rev_author, details->added_rev);
4409       else
4410         return apr_psprintf(result_pool,
4411                             _("Item reverse-merged from\n"
4412                               "'^/%s@%ld'\nto\n'^/%s@%ld'\n"
4413                               "did not exist before it was added by %s in "
4414                               "r%ld."),
4415                             old_repos_relpath, old_rev,
4416                             new_repos_relpath, new_rev,
4417                             details->rev_author, details->added_rev);
4418     }
4419 }
4420
4421 /* Implements tree_conflict_get_description_func_t. */
4422 static svn_error_t *
4423 conflict_tree_get_description_incoming_delete(
4424   const char **incoming_change_description,
4425   svn_client_conflict_t *conflict,
4426   svn_client_ctx_t *ctx,
4427   apr_pool_t *result_pool,
4428   apr_pool_t *scratch_pool)
4429 {
4430   const char *action;
4431   svn_node_kind_t victim_node_kind;
4432   svn_wc_operation_t conflict_operation;
4433   const char *old_repos_relpath;
4434   svn_revnum_t old_rev;
4435   const char *new_repos_relpath;
4436   svn_revnum_t new_rev;
4437   struct conflict_tree_incoming_delete_details *details;
4438
4439   if (conflict->tree_conflict_incoming_details == NULL)
4440     return svn_error_trace(conflict_tree_get_incoming_description_generic(
4441                              incoming_change_description,
4442                              conflict, ctx, result_pool, scratch_pool));
4443
4444   conflict_operation = svn_client_conflict_get_operation(conflict);
4445   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
4446   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
4447             &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
4448             scratch_pool));
4449   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
4450             &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
4451             scratch_pool));
4452
4453   details = conflict->tree_conflict_incoming_details;
4454
4455   if (conflict_operation == svn_wc_operation_update)
4456     {
4457       if (details->deleted_rev != SVN_INVALID_REVNUM)
4458         {
4459           action = describe_incoming_deletion_upon_update(details,
4460                                                           victim_node_kind,
4461                                                           old_rev,
4462                                                           new_rev,
4463                                                           result_pool,
4464                                                           scratch_pool);
4465         }
4466       else /* details->added_rev != SVN_INVALID_REVNUM */
4467         {
4468           /* This deletion is really the reverse change of an addition. */
4469           action = describe_incoming_reverse_addition_upon_update(
4470                      details, victim_node_kind, old_rev, new_rev, result_pool);
4471         }
4472     }
4473   else if (conflict_operation == svn_wc_operation_switch)
4474     {
4475       if (details->deleted_rev != SVN_INVALID_REVNUM)
4476         {
4477           action = describe_incoming_deletion_upon_switch(details,
4478                                                           victim_node_kind,
4479                                                           old_repos_relpath,
4480                                                           old_rev,
4481                                                           new_repos_relpath,
4482                                                           new_rev,
4483                                                           result_pool,
4484                                                           scratch_pool);
4485         }
4486       else /* details->added_rev != SVN_INVALID_REVNUM */
4487         {
4488           /* This deletion is really the reverse change of an addition. */
4489           action = describe_incoming_reverse_addition_upon_switch(
4490                      details, victim_node_kind, old_repos_relpath, old_rev,
4491                      new_repos_relpath, new_rev, result_pool);
4492             
4493         }
4494       }
4495   else if (conflict_operation == svn_wc_operation_merge)
4496     {
4497       if (details->deleted_rev != SVN_INVALID_REVNUM)
4498         {
4499           action = describe_incoming_deletion_upon_merge(details,
4500                                                          victim_node_kind,
4501                                                          old_repos_relpath,
4502                                                          old_rev,
4503                                                          new_repos_relpath,
4504                                                          new_rev,
4505                                                          result_pool,
4506                                                          scratch_pool);
4507         }
4508       else /* details->added_rev != SVN_INVALID_REVNUM */
4509         {
4510           /* This deletion is really the reverse change of an addition. */
4511           action = describe_incoming_reverse_addition_upon_merge(
4512                      details, victim_node_kind, old_repos_relpath, old_rev,
4513                      new_repos_relpath, new_rev, result_pool);
4514         }
4515       }
4516
4517   *incoming_change_description = apr_pstrdup(result_pool, action);
4518
4519   return SVN_NO_ERROR;
4520 }
4521
4522 /* Baton for find_added_rev(). */
4523 struct find_added_rev_baton
4524 {
4525   const char *victim_abspath;
4526   svn_client_ctx_t *ctx;
4527   svn_revnum_t added_rev;
4528   const char *repos_relpath;
4529   const char *parent_repos_relpath;
4530   apr_pool_t *pool;
4531 };
4532
4533 /* Implements svn_location_segment_receiver_t.
4534  * Finds the revision in which a node was added by tracing 'start'
4535  * revisions in location segments reported for the node.
4536  * If the PARENT_REPOS_RELPATH in the baton is not NULL, only consider
4537  * segments in which the node existed somwhere beneath this path. */
4538 static svn_error_t *
4539 find_added_rev(svn_location_segment_t *segment,
4540                void *baton,
4541                apr_pool_t *scratch_pool)
4542 {
4543   struct find_added_rev_baton *b = baton;
4544
4545   if (b->ctx->notify_func2)
4546     {
4547       svn_wc_notify_t *notify;
4548
4549       notify = svn_wc_create_notify(
4550                  b->victim_abspath,
4551                  svn_wc_notify_tree_conflict_details_progress,
4552                  scratch_pool),
4553       notify->revision = segment->range_start;
4554       b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
4555     }
4556
4557   if (segment->path) /* not interested in gaps */
4558     {
4559       if (b->parent_repos_relpath == NULL ||
4560           svn_relpath_skip_ancestor(b->parent_repos_relpath,
4561                                     segment->path) != NULL)
4562         {
4563           b->added_rev = segment->range_start;
4564           b->repos_relpath = apr_pstrdup(b->pool, segment->path);
4565         }
4566     }
4567
4568   return SVN_NO_ERROR;
4569 }
4570
4571 /* Find conflict details in the case where a revision which added a node was
4572  * applied in reverse, resulting in an incoming deletion. */
4573 static svn_error_t *
4574 get_incoming_delete_details_for_reverse_addition(
4575   struct conflict_tree_incoming_delete_details **details,
4576   const char *repos_root_url,
4577   const char *old_repos_relpath,
4578   svn_revnum_t old_rev,
4579   svn_revnum_t new_rev,
4580   svn_client_ctx_t *ctx,
4581   const char *victim_abspath,
4582   apr_pool_t *result_pool,
4583   apr_pool_t *scratch_pool)
4584 {
4585   svn_ra_session_t *ra_session;
4586   const char *url;
4587   const char *corrected_url;
4588   svn_string_t *author_revprop;
4589   struct find_added_rev_baton b = { 0 };
4590
4591   url = svn_path_url_add_component2(repos_root_url, old_repos_relpath,
4592                                     scratch_pool);
4593   SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
4594                                                &corrected_url,
4595                                                url, NULL, NULL,
4596                                                FALSE,
4597                                                FALSE,
4598                                                ctx,
4599                                                scratch_pool,
4600                                                scratch_pool));
4601
4602   *details = apr_pcalloc(result_pool, sizeof(**details));
4603   b.ctx = ctx;
4604   b.victim_abspath = victim_abspath;
4605   b.added_rev = SVN_INVALID_REVNUM;
4606   b.repos_relpath = NULL;
4607   b.parent_repos_relpath = NULL;
4608   b.pool = scratch_pool;
4609
4610   /* Figure out when this node was added. */
4611   SVN_ERR(svn_ra_get_location_segments(ra_session, "", old_rev,
4612                                        old_rev, new_rev,
4613                                        find_added_rev, &b,
4614                                        scratch_pool));
4615
4616   SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
4617                           SVN_PROP_REVISION_AUTHOR,
4618                           &author_revprop, scratch_pool));
4619   (*details)->deleted_rev = SVN_INVALID_REVNUM;
4620   (*details)->added_rev = b.added_rev;
4621   (*details)->repos_relpath = apr_pstrdup(result_pool, b.repos_relpath);
4622   if (author_revprop)
4623     (*details)->rev_author = apr_pstrdup(result_pool, author_revprop->data);
4624   else
4625     (*details)->rev_author = _("unknown author");
4626
4627   /* Check for replacement. */
4628   (*details)->replacing_node_kind = svn_node_none;
4629   if ((*details)->added_rev > 0)
4630     {
4631       svn_node_kind_t replaced_node_kind;
4632
4633       SVN_ERR(svn_ra_check_path(ra_session, "",
4634                                 rev_below((*details)->added_rev),
4635                                 &replaced_node_kind, scratch_pool));
4636       if (replaced_node_kind != svn_node_none)
4637         SVN_ERR(svn_ra_check_path(ra_session, "", (*details)->added_rev,
4638                                   &(*details)->replacing_node_kind,
4639                                   scratch_pool));
4640     }
4641
4642   return SVN_NO_ERROR;
4643 }
4644
4645 /* Follow each move chain starting a MOVE all the way to the end to find
4646  * the possible working copy locations for VICTIM_ABSPATH which corresponds
4647  * to VICTIM_REPOS_REPLATH@VICTIM_REVISION.
4648  * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the
4649  * repos_relpath which is the corresponding move destination in the repository.
4650  * This function is recursive. */
4651 static svn_error_t *
4652 follow_move_chains(apr_hash_t *wc_move_targets,
4653                    struct repos_move_info *move,
4654                    svn_client_ctx_t *ctx,
4655                    const char *victim_abspath,
4656                    svn_node_kind_t victim_node_kind,
4657                    const char *victim_repos_relpath,
4658                    svn_revnum_t victim_revision,
4659                    apr_pool_t *result_pool,
4660                    apr_pool_t *scratch_pool)
4661 {
4662   /* If this is the end of a move chain, look for matching paths in
4663    * the working copy and add them to our collection if found. */
4664   if (move->next == NULL)
4665     {
4666       apr_array_header_t *candidate_abspaths;
4667
4668       /* Gather candidate nodes which represent this moved_to_repos_relpath. */
4669       SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
4670                 &candidate_abspaths, ctx->wc_ctx,
4671                 victim_abspath, victim_node_kind,
4672                 move->moved_to_repos_relpath,
4673                 scratch_pool, scratch_pool));
4674       if (candidate_abspaths->nelts > 0)
4675         {
4676           apr_array_header_t *moved_to_abspaths;
4677           int i;
4678           apr_pool_t *iterpool = svn_pool_create(scratch_pool);
4679
4680           moved_to_abspaths = apr_array_make(result_pool, 1,
4681                                              sizeof (const char *));
4682
4683           for (i = 0; i < candidate_abspaths->nelts; i++)
4684             {
4685               const char *candidate_abspath;
4686               const char *repos_root_url;
4687               const char *repos_uuid;
4688               const char *candidate_repos_relpath;
4689               svn_revnum_t candidate_revision;
4690
4691               svn_pool_clear(iterpool);
4692
4693               candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
4694                                                 const char *);
4695               SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
4696                                               &candidate_repos_relpath,
4697                                               &repos_root_url,
4698                                               &repos_uuid,
4699                                               NULL, NULL,
4700                                               ctx->wc_ctx,
4701                                               candidate_abspath,
4702                                               FALSE,
4703                                               iterpool, iterpool));
4704
4705               if (candidate_revision == SVN_INVALID_REVNUM)
4706                 continue;
4707
4708               /* If the conflict victim and the move target candidate
4709                * are not from the same revision we must ensure that
4710                * they are related. */
4711                if (candidate_revision != victim_revision)
4712                 {
4713                   svn_client__pathrev_t *yca_loc;
4714                   svn_error_t *err;
4715
4716                   err = find_yca(&yca_loc, victim_repos_relpath,
4717                                  victim_revision,
4718                                  candidate_repos_relpath,
4719                                  candidate_revision,
4720                                  repos_root_url, repos_uuid,
4721                                  NULL, ctx, iterpool, iterpool);
4722                   if (err)
4723                     {
4724                       if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
4725                         {
4726                           svn_error_clear(err);
4727                           yca_loc = NULL;
4728                         }
4729                       else
4730                         return svn_error_trace(err);
4731                     }
4732
4733                   if (yca_loc == NULL)
4734                     continue;
4735                 }
4736
4737               APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
4738                 apr_pstrdup(result_pool, candidate_abspath);
4739             }
4740           svn_pool_destroy(iterpool);
4741
4742           svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
4743                         moved_to_abspaths);
4744         }
4745     }
4746   else
4747     {
4748       int i;
4749       apr_pool_t *iterpool;
4750
4751       /* Recurse into each of the possible move chains. */
4752       iterpool = svn_pool_create(scratch_pool);
4753       for (i = 0; i < move->next->nelts; i++)
4754         {
4755           struct repos_move_info *next_move;
4756
4757           svn_pool_clear(iterpool);
4758
4759           next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
4760           SVN_ERR(follow_move_chains(wc_move_targets, next_move,
4761                                      ctx, victim_abspath, victim_node_kind,
4762                                      victim_repos_relpath, victim_revision,
4763                                      result_pool, iterpool));
4764                                         
4765         }
4766       svn_pool_destroy(iterpool);
4767     }
4768
4769   return SVN_NO_ERROR;
4770 }
4771
4772 static svn_error_t *
4773 init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
4774                      svn_client_conflict_t *conflict,
4775                      svn_client_ctx_t *ctx,
4776                      apr_pool_t *scratch_pool)
4777 {
4778   int i;
4779   const char *victim_abspath;
4780   svn_node_kind_t victim_node_kind;
4781   const char *incoming_new_repos_relpath;
4782   svn_revnum_t incoming_new_pegrev;
4783   svn_wc_operation_t operation;
4784
4785   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
4786   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
4787   operation = svn_client_conflict_get_operation(conflict);
4788   /* ### Should we get the old location in case of reverse-merges? */
4789   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
4790             &incoming_new_repos_relpath, &incoming_new_pegrev,
4791             NULL, conflict,
4792             scratch_pool, scratch_pool));
4793   details->wc_move_targets = apr_hash_make(conflict->pool);
4794   for (i = 0; i < details->moves->nelts; i++)
4795     {
4796       struct repos_move_info *move;
4797
4798       move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
4799       SVN_ERR(follow_move_chains(details->wc_move_targets, move,
4800                                  ctx, victim_abspath,
4801                                  victim_node_kind,
4802                                  incoming_new_repos_relpath,
4803                                  incoming_new_pegrev,
4804                                  conflict->pool, scratch_pool));
4805     }
4806
4807   /* Initialize to the first possible move target. Hopefully,
4808    * in most cases there will only be one candidate anyway. */
4809   details->move_target_repos_relpath =
4810     get_moved_to_repos_relpath(details, scratch_pool);
4811   details->wc_move_target_idx = 0;
4812
4813   /* If only one move target exists after an update or switch,
4814    * recommend a resolution option which follows the incoming move. */
4815   if (apr_hash_count(details->wc_move_targets) == 1 &&
4816       (operation == svn_wc_operation_update ||
4817        operation == svn_wc_operation_switch))
4818     {
4819       apr_array_header_t *wc_abspaths;
4820
4821       wc_abspaths = svn_hash_gets(details->wc_move_targets,
4822                                   details->move_target_repos_relpath);
4823       if (wc_abspaths->nelts == 1)
4824         {
4825           svn_client_conflict_option_id_t recommended[] =
4826             {
4827               /* Only one of these will be present for any given conflict. */
4828               svn_client_conflict_option_incoming_move_file_text_merge,
4829               svn_client_conflict_option_incoming_move_dir_merge,
4830               svn_client_conflict_option_local_move_file_text_merge
4831             };
4832           apr_array_header_t *options;
4833
4834           SVN_ERR(svn_client_conflict_tree_get_resolution_options(
4835                     &options, conflict, ctx, scratch_pool, scratch_pool));
4836           for (i = 0; i < (sizeof(recommended) / sizeof(recommended[0])); i++)
4837             {
4838               svn_client_conflict_option_id_t option_id = recommended[i];
4839
4840               if (svn_client_conflict_option_find_by_id(options, option_id))
4841                 {
4842                   conflict->recommended_option_id = option_id;
4843                   break;
4844                 }
4845             }
4846         }
4847     }
4848
4849   return SVN_NO_ERROR;
4850 }
4851
4852 /* Implements tree_conflict_get_details_func_t.
4853  * Find the revision in which the victim was deleted in the repository. */
4854 static svn_error_t *
4855 conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict,
4856                                           svn_client_ctx_t *ctx,
4857                                           apr_pool_t *scratch_pool)
4858 {
4859   const char *old_repos_relpath;
4860   const char *new_repos_relpath;
4861   const char *repos_root_url;
4862   svn_revnum_t old_rev;
4863   svn_revnum_t new_rev;
4864   svn_node_kind_t old_kind;
4865   svn_node_kind_t new_kind;
4866   struct conflict_tree_incoming_delete_details *details;
4867   svn_wc_operation_t operation;
4868
4869   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
4870             &old_repos_relpath, &old_rev, &old_kind, conflict, scratch_pool,
4871             scratch_pool));
4872   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
4873             &new_repos_relpath, &new_rev, &new_kind, conflict, scratch_pool,
4874             scratch_pool));
4875   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
4876                                              conflict,
4877                                              scratch_pool, scratch_pool));
4878   operation = svn_client_conflict_get_operation(conflict);
4879
4880   if (operation == svn_wc_operation_update)
4881     {
4882       if (old_rev < new_rev)
4883         {
4884           const char *parent_repos_relpath;
4885           svn_revnum_t parent_peg_rev;
4886           svn_revnum_t deleted_rev;
4887           const char *deleted_rev_author;
4888           svn_node_kind_t replacing_node_kind;
4889           apr_array_header_t *moves;
4890           const char *related_repos_relpath;
4891           svn_revnum_t related_peg_rev;
4892
4893           /* The update operation went forward in history. */
4894           SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev,
4895                                               &parent_repos_relpath,
4896                                               NULL, NULL,
4897                                               ctx->wc_ctx,
4898                                               svn_dirent_dirname(
4899                                                 conflict->local_abspath,
4900                                                 scratch_pool),
4901                                               scratch_pool,
4902                                               scratch_pool));
4903           if (new_kind == svn_node_none)
4904             {
4905               SVN_ERR(find_related_node(&related_repos_relpath,
4906                                         &related_peg_rev,
4907                                         new_repos_relpath, new_rev,
4908                                         old_repos_relpath, old_rev,
4909                                         conflict, ctx,
4910                                         scratch_pool, scratch_pool));
4911             }
4912           else
4913             {
4914               /* related to self */
4915               related_repos_relpath = NULL;
4916               related_peg_rev = SVN_INVALID_REVNUM;
4917             }
4918
4919           SVN_ERR(find_revision_for_suspected_deletion(
4920                     &deleted_rev, &deleted_rev_author, &replacing_node_kind,
4921                     &moves, conflict,
4922                     svn_dirent_basename(conflict->local_abspath, scratch_pool),
4923                     parent_repos_relpath, parent_peg_rev,
4924                     new_kind == svn_node_none ? 0 : old_rev,
4925                     related_repos_relpath, related_peg_rev,
4926                     ctx, conflict->pool, scratch_pool));
4927           if (deleted_rev == SVN_INVALID_REVNUM)
4928             {
4929               /* We could not determine the revision in which the node was
4930                * deleted. We cannot provide the required details so the best
4931                * we can do is fall back to the default description. */
4932               return SVN_NO_ERROR;
4933             }
4934
4935           details = apr_pcalloc(conflict->pool, sizeof(*details));
4936           details->deleted_rev = deleted_rev;
4937           details->added_rev = SVN_INVALID_REVNUM;
4938           details->repos_relpath = apr_pstrdup(conflict->pool,
4939                                                new_repos_relpath);
4940           details->rev_author = deleted_rev_author;
4941           details->replacing_node_kind = replacing_node_kind;
4942           details->moves = moves;
4943         }
4944       else /* new_rev < old_rev */
4945         {
4946           /* The update operation went backwards in history.
4947            * Figure out when this node was added. */
4948           SVN_ERR(get_incoming_delete_details_for_reverse_addition(
4949                     &details, repos_root_url, old_repos_relpath,
4950                     old_rev, new_rev, ctx,
4951                     svn_client_conflict_get_local_abspath(conflict),
4952                     conflict->pool, scratch_pool));
4953         }
4954     }
4955   else if (operation == svn_wc_operation_switch ||
4956            operation == svn_wc_operation_merge)
4957     {
4958       if (old_rev < new_rev)
4959         {
4960           svn_revnum_t deleted_rev;
4961           const char *deleted_rev_author;
4962           svn_node_kind_t replacing_node_kind;
4963           apr_array_header_t *moves;
4964
4965           /* The switch/merge operation went forward in history.
4966            *
4967            * The deletion of the node happened on the branch we switched to
4968            * or merged from. Scan new_repos_relpath's parent's log to find
4969            * the revision which deleted the node. */
4970           SVN_ERR(find_revision_for_suspected_deletion(
4971                     &deleted_rev, &deleted_rev_author, &replacing_node_kind,
4972                     &moves, conflict,
4973                     svn_relpath_basename(new_repos_relpath, scratch_pool),
4974                     svn_relpath_dirname(new_repos_relpath, scratch_pool),
4975                     new_rev, old_rev, old_repos_relpath, old_rev, ctx,
4976                     conflict->pool, scratch_pool));
4977           if (deleted_rev == SVN_INVALID_REVNUM)
4978             {
4979               /* We could not determine the revision in which the node was
4980                * deleted. We cannot provide the required details so the best
4981                * we can do is fall back to the default description. */
4982               return SVN_NO_ERROR;
4983             }
4984
4985           details = apr_pcalloc(conflict->pool, sizeof(*details));
4986           details->deleted_rev = deleted_rev;
4987           details->added_rev = SVN_INVALID_REVNUM;
4988           details->repos_relpath = apr_pstrdup(conflict->pool,
4989                                                new_repos_relpath);
4990           details->rev_author = apr_pstrdup(conflict->pool,
4991                                             deleted_rev_author);
4992           details->replacing_node_kind = replacing_node_kind;
4993           details->moves = moves;
4994         }
4995       else /* new_rev < old_rev */
4996         {
4997           /* The switch/merge operation went backwards in history.
4998            * Figure out when the node we switched away from, or merged
4999            * from another branch, was added. */
5000           SVN_ERR(get_incoming_delete_details_for_reverse_addition(
5001                     &details, repos_root_url, old_repos_relpath,
5002                     old_rev, new_rev, ctx,
5003                     svn_client_conflict_get_local_abspath(conflict),
5004                     conflict->pool, scratch_pool));
5005         }
5006     }
5007   else
5008     {
5009       details = NULL;
5010     }
5011
5012   conflict->tree_conflict_incoming_details = details;
5013
5014   if (details && details->moves)
5015     SVN_ERR(init_wc_move_targets(details, conflict, ctx, scratch_pool));
5016
5017   return SVN_NO_ERROR;
5018 }
5019
5020 /* Details for tree conflicts involving incoming additions. */
5021 struct conflict_tree_incoming_add_details
5022 {
5023   /* If not SVN_INVALID_REVNUM, the node was added in ADDED_REV. */
5024   svn_revnum_t added_rev;
5025
5026   /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV.
5027    * Note that both ADDED_REV and DELETED_REV may be valid for update/switch.
5028    * See comment in conflict_tree_get_details_incoming_add() for details. */
5029   svn_revnum_t deleted_rev;
5030
5031   /* The path which was added/deleted relative to the repository root. */
5032   const char *repos_relpath;
5033
5034   /* Authors who committed ADDED_REV/DELETED_REV. */
5035   const char *added_rev_author;
5036   const char *deleted_rev_author;
5037
5038   /* Move information. If not NULL, this is an array of repos_move_info *
5039    * elements. Each element is the head of a move chain which starts in
5040    * ADDED_REV or in DELETED_REV (in which case moves should be interpreted
5041    * in reverse). */
5042   apr_array_header_t *moves;
5043 };
5044
5045 /* Implements tree_conflict_get_details_func_t.
5046  * Find the revision in which the victim was added in the repository. */
5047 static svn_error_t *
5048 conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
5049                                        svn_client_ctx_t *ctx,
5050                                        apr_pool_t *scratch_pool)
5051 {
5052   const char *old_repos_relpath;
5053   const char *new_repos_relpath;
5054   const char *repos_root_url;
5055   svn_revnum_t old_rev;
5056   svn_revnum_t new_rev;
5057   struct conflict_tree_incoming_add_details *details;
5058   svn_wc_operation_t operation;
5059
5060   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5061             &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
5062             scratch_pool));
5063   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5064             &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
5065             scratch_pool));
5066   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5067                                              conflict,
5068                                              scratch_pool, scratch_pool));
5069   operation = svn_client_conflict_get_operation(conflict);
5070
5071   if (operation == svn_wc_operation_update ||
5072       operation == svn_wc_operation_switch)
5073     {
5074       /* Only the new repository location is recorded for the node which
5075        * caused an incoming addition. There is no pre-update/pre-switch
5076        * revision to be recorded for the node since it does not exist in
5077        * the repository at that revision.
5078        * The implication is that we cannot know whether the operation went
5079        * forward or backwards in history. So always try to find an added
5080        * and a deleted revision for the node. Users must figure out by whether
5081        * the addition or deletion caused the conflict. */
5082       const char *url;
5083       const char *corrected_url;
5084       svn_string_t *author_revprop;
5085       struct find_added_rev_baton b = { 0 };
5086       svn_ra_session_t *ra_session;
5087       svn_revnum_t deleted_rev;
5088       svn_revnum_t head_rev;
5089
5090       url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5091                                         scratch_pool);
5092       SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5093                                                    &corrected_url,
5094                                                    url, NULL, NULL,
5095                                                    FALSE,
5096                                                    FALSE,
5097                                                    ctx,
5098                                                    scratch_pool,
5099                                                    scratch_pool));
5100
5101       details = apr_pcalloc(conflict->pool, sizeof(*details));
5102       b.ctx = ctx,
5103       b.victim_abspath = svn_client_conflict_get_local_abspath(conflict),
5104       b.added_rev = SVN_INVALID_REVNUM;
5105       b.repos_relpath = NULL;
5106       b.parent_repos_relpath = NULL;
5107       b.pool = scratch_pool;
5108
5109       /* Figure out when this node was added. */
5110       SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
5111                                            new_rev, SVN_INVALID_REVNUM,
5112                                            find_added_rev, &b,
5113                                            scratch_pool));
5114
5115       SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
5116                               SVN_PROP_REVISION_AUTHOR,
5117                               &author_revprop, scratch_pool));
5118       details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath);
5119       details->added_rev = b.added_rev;
5120       if (author_revprop)
5121         details->added_rev_author = apr_pstrdup(conflict->pool,
5122                                           author_revprop->data);
5123       else
5124         details->added_rev_author = _("unknown author");
5125       details->deleted_rev = SVN_INVALID_REVNUM;
5126       details->deleted_rev_author = NULL;
5127
5128       /* Figure out whether this node was deleted later.
5129        * ### Could probably optimize by infering both addition and deletion
5130        * ### from svn_ra_get_location_segments() call above. */
5131       SVN_ERR(svn_ra_get_latest_revnum(ra_session, &head_rev, scratch_pool));
5132       if (new_rev < head_rev)
5133         {
5134           SVN_ERR(svn_ra_get_deleted_rev(ra_session, "", new_rev, head_rev,
5135                                          &deleted_rev, scratch_pool));
5136           if (SVN_IS_VALID_REVNUM(deleted_rev))
5137            {
5138               SVN_ERR(svn_ra_rev_prop(ra_session, deleted_rev,
5139                                       SVN_PROP_REVISION_AUTHOR,
5140                                       &author_revprop, scratch_pool));
5141               details->deleted_rev = deleted_rev;
5142               if (author_revprop)
5143                 details->deleted_rev_author = apr_pstrdup(conflict->pool,
5144                                                           author_revprop->data);
5145               else
5146                 details->deleted_rev_author = _("unknown author");
5147             }
5148         }
5149     }
5150   else if (operation == svn_wc_operation_merge)
5151     {
5152       if (old_rev < new_rev)
5153         {
5154           /* The merge operation went forwards in history.
5155            * The addition of the node happened on the branch we merged form.
5156            * Scan the nodes's history to find the revision which added it. */
5157           const char *url;
5158           const char *corrected_url;
5159           svn_string_t *author_revprop;
5160           struct find_added_rev_baton b = { 0 };
5161           svn_ra_session_t *ra_session;
5162
5163           url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5164                                             scratch_pool);
5165           SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5166                                                        &corrected_url,
5167                                                        url, NULL, NULL,
5168                                                        FALSE,
5169                                                        FALSE,
5170                                                        ctx,
5171                                                        scratch_pool,
5172                                                        scratch_pool));
5173
5174           details = apr_pcalloc(conflict->pool, sizeof(*details));
5175           b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
5176           b.ctx = ctx;
5177           b.added_rev = SVN_INVALID_REVNUM;
5178           b.repos_relpath = NULL;
5179           b.parent_repos_relpath = NULL;
5180           b.pool = scratch_pool;
5181
5182           /* Figure out when this node was added. */
5183           SVN_ERR(svn_ra_get_location_segments(ra_session, "", new_rev,
5184                                                new_rev, old_rev,
5185                                                find_added_rev, &b,
5186                                                scratch_pool));
5187
5188           SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
5189                                   SVN_PROP_REVISION_AUTHOR,
5190                                   &author_revprop, scratch_pool));
5191           details->repos_relpath = apr_pstrdup(conflict->pool, b.repos_relpath);
5192           details->added_rev = b.added_rev;
5193           if (author_revprop)
5194             details->added_rev_author = apr_pstrdup(conflict->pool,
5195                                                     author_revprop->data);
5196           else
5197             details->added_rev_author = _("unknown author");
5198           details->deleted_rev = SVN_INVALID_REVNUM;
5199           details->deleted_rev_author = NULL;
5200         }
5201       else
5202         {
5203           /* The merge operation was a reverse-merge.
5204            * This addition is in fact a deletion, applied in reverse,
5205            * which happened on the branch we merged from.
5206            * Find the revision which deleted the node. */
5207           svn_revnum_t deleted_rev;
5208           const char *deleted_rev_author;
5209           svn_node_kind_t replacing_node_kind;
5210           apr_array_header_t *moves;
5211
5212           SVN_ERR(find_revision_for_suspected_deletion(
5213                     &deleted_rev, &deleted_rev_author, &replacing_node_kind,
5214                     &moves, conflict,
5215                     svn_relpath_basename(old_repos_relpath, scratch_pool),
5216                     svn_relpath_dirname(old_repos_relpath, scratch_pool),
5217                     old_rev, new_rev,
5218                     NULL, SVN_INVALID_REVNUM, /* related to self */
5219                     ctx,
5220                     conflict->pool, scratch_pool));
5221           if (deleted_rev == SVN_INVALID_REVNUM)
5222             {
5223               /* We could not determine the revision in which the node was
5224                * deleted. We cannot provide the required details so the best
5225                * we can do is fall back to the default description. */
5226               return SVN_NO_ERROR;
5227             }
5228
5229           details = apr_pcalloc(conflict->pool, sizeof(*details));
5230           details->repos_relpath = apr_pstrdup(conflict->pool,
5231                                                new_repos_relpath);
5232           details->deleted_rev = deleted_rev;
5233           details->deleted_rev_author = apr_pstrdup(conflict->pool,
5234                                                     deleted_rev_author);
5235
5236           details->added_rev = SVN_INVALID_REVNUM;
5237           details->added_rev_author = NULL;
5238           details->moves = moves;
5239         }
5240     }
5241   else
5242     {
5243       details = NULL;
5244     }
5245
5246   conflict->tree_conflict_incoming_details = details;
5247
5248   return SVN_NO_ERROR;
5249 }
5250
5251 static const char *
5252 describe_incoming_add_upon_update(
5253   struct conflict_tree_incoming_add_details *details,
5254   svn_node_kind_t new_node_kind,
5255   svn_revnum_t new_rev,
5256   apr_pool_t *result_pool)
5257 {
5258   if (new_node_kind == svn_node_dir)
5259     {
5260       if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5261           SVN_IS_VALID_REVNUM(details->deleted_rev))
5262         return apr_psprintf(result_pool,
5263                             _("A new directory appeared during update to r%ld; "
5264                               "it was added by %s in r%ld and later deleted "
5265                               "by %s in r%ld."), new_rev,
5266                             details->added_rev_author, details->added_rev,
5267                             details->deleted_rev_author, details->deleted_rev);
5268       else if (SVN_IS_VALID_REVNUM(details->added_rev))
5269         return apr_psprintf(result_pool,
5270                             _("A new directory appeared during update to r%ld; "
5271                               "it was added by %s in r%ld."), new_rev,
5272                             details->added_rev_author, details->added_rev);
5273       else
5274         return apr_psprintf(result_pool,
5275                             _("A new directory appeared during update to r%ld; "
5276                               "it was deleted by %s in r%ld."), new_rev,
5277                             details->deleted_rev_author, details->deleted_rev);
5278     }
5279   else if (new_node_kind == svn_node_file ||
5280            new_node_kind == svn_node_symlink)
5281     {
5282       if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5283           SVN_IS_VALID_REVNUM(details->deleted_rev))
5284         return apr_psprintf(result_pool,
5285                             _("A new file appeared during update to r%ld; "
5286                               "it was added by %s in r%ld and later deleted "
5287                               "by %s in r%ld."), new_rev,
5288                             details->added_rev_author, details->added_rev,
5289                             details->deleted_rev_author, details->deleted_rev);
5290       else if (SVN_IS_VALID_REVNUM(details->added_rev))
5291         return apr_psprintf(result_pool,
5292                             _("A new file appeared during update to r%ld; "
5293                               "it was added by %s in r%ld."), new_rev,
5294                             details->added_rev_author, details->added_rev);
5295       else
5296         return apr_psprintf(result_pool,
5297                             _("A new file appeared during update to r%ld; "
5298                               "it was deleted by %s in r%ld."), new_rev,
5299                             details->deleted_rev_author, details->deleted_rev);
5300     }
5301   else
5302     {
5303       if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5304           SVN_IS_VALID_REVNUM(details->deleted_rev))
5305         return apr_psprintf(result_pool,
5306                             _("A new item appeared during update to r%ld; "
5307                               "it was added by %s in r%ld and later deleted "
5308                               "by %s in r%ld."), new_rev,
5309                             details->added_rev_author, details->added_rev,
5310                             details->deleted_rev_author, details->deleted_rev);
5311       else if (SVN_IS_VALID_REVNUM(details->added_rev))
5312         return apr_psprintf(result_pool,
5313                             _("A new item appeared during update to r%ld; "
5314                               "it was added by %s in r%ld."), new_rev,
5315                             details->added_rev_author, details->added_rev);
5316       else
5317         return apr_psprintf(result_pool,
5318                             _("A new item appeared during update to r%ld; "
5319                               "it was deleted by %s in r%ld."), new_rev,
5320                             details->deleted_rev_author, details->deleted_rev);
5321     }
5322 }
5323
5324 static const char *
5325 describe_incoming_add_upon_switch(
5326   struct conflict_tree_incoming_add_details *details,
5327   svn_node_kind_t victim_node_kind,
5328   const char *new_repos_relpath,
5329   svn_revnum_t new_rev,
5330   apr_pool_t *result_pool)
5331 {
5332   if (victim_node_kind == svn_node_dir)
5333     {
5334       if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5335           SVN_IS_VALID_REVNUM(details->deleted_rev))
5336         return apr_psprintf(result_pool,
5337                             _("A new directory appeared during switch to\n"
5338                               "'^/%s@%ld'.\n"
5339                               "It was added by %s in r%ld and later deleted "
5340                               "by %s in r%ld."), new_repos_relpath, new_rev,
5341                             details->added_rev_author, details->added_rev,
5342                             details->deleted_rev_author, details->deleted_rev);
5343       else if (SVN_IS_VALID_REVNUM(details->added_rev))
5344         return apr_psprintf(result_pool,
5345                             _("A new directory appeared during switch to\n"
5346                              "'^/%s@%ld'.\nIt was added by %s in r%ld."),
5347                             new_repos_relpath, new_rev,
5348                             details->added_rev_author, details->added_rev);
5349       else
5350         return apr_psprintf(result_pool,
5351                             _("A new directory appeared during switch to\n"
5352                               "'^/%s@%ld'.\nIt was deleted by %s in r%ld."),
5353                             new_repos_relpath, new_rev,
5354                             details->deleted_rev_author, details->deleted_rev);
5355     }
5356   else if (victim_node_kind == svn_node_file ||
5357            victim_node_kind == svn_node_symlink)
5358     {
5359       if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5360           SVN_IS_VALID_REVNUM(details->deleted_rev))
5361         return apr_psprintf(result_pool,
5362                             _("A new file appeared during switch to\n"
5363                               "'^/%s@%ld'.\n"
5364                               "It was added by %s in r%ld and later deleted "
5365                               "by %s in r%ld."), new_repos_relpath, new_rev,
5366                             details->added_rev_author, details->added_rev,
5367                             details->deleted_rev_author, details->deleted_rev);
5368       else if (SVN_IS_VALID_REVNUM(details->added_rev))
5369         return apr_psprintf(result_pool,
5370                             _("A new file appeared during switch to\n"
5371                               "'^/%s@%ld'.\n"
5372                               "It was added by %s in r%ld."),
5373                             new_repos_relpath, new_rev,
5374                             details->added_rev_author, details->added_rev);
5375       else
5376         return apr_psprintf(result_pool,
5377                             _("A new file appeared during switch to\n"
5378                               "'^/%s@%ld'.\n"
5379                               "It was deleted by %s in r%ld."),
5380                             new_repos_relpath, new_rev,
5381                             details->deleted_rev_author, details->deleted_rev);
5382     }
5383   else
5384     {
5385       if (SVN_IS_VALID_REVNUM(details->added_rev) &&
5386           SVN_IS_VALID_REVNUM(details->deleted_rev))
5387         return apr_psprintf(result_pool,
5388                             _("A new item appeared during switch to\n"
5389                               "'^/%s@%ld'.\n"
5390                               "It was added by %s in r%ld and later deleted "
5391                               "by %s in r%ld."), new_repos_relpath, new_rev,
5392                             details->added_rev_author, details->added_rev,
5393                             details->deleted_rev_author, details->deleted_rev);
5394       else if (SVN_IS_VALID_REVNUM(details->added_rev))
5395         return apr_psprintf(result_pool,
5396                             _("A new item appeared during switch to\n"
5397                               "'^/%s@%ld'.\n"
5398                               "It was added by %s in r%ld."),
5399                             new_repos_relpath, new_rev,
5400                             details->added_rev_author, details->added_rev);
5401       else
5402         return apr_psprintf(result_pool,
5403                             _("A new item appeared during switch to\n"
5404                               "'^/%s@%ld'.\n"
5405                               "It was deleted by %s in r%ld."),
5406                             new_repos_relpath, new_rev,
5407                             details->deleted_rev_author, details->deleted_rev);
5408     }
5409 }
5410
5411 static const char *
5412 describe_incoming_add_upon_merge(
5413   struct conflict_tree_incoming_add_details *details,
5414   svn_node_kind_t new_node_kind,
5415   svn_revnum_t old_rev,
5416   const char *new_repos_relpath,
5417   svn_revnum_t new_rev,
5418   apr_pool_t *result_pool)
5419 {
5420   if (new_node_kind == svn_node_dir)
5421     {
5422       if (old_rev + 1 == new_rev)
5423         return apr_psprintf(result_pool,
5424                             _("A new directory appeared during merge of\n"
5425                               "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5426                             new_repos_relpath, new_rev,
5427                             details->added_rev_author, details->added_rev);
5428       else
5429         return apr_psprintf(result_pool,
5430                             _("A new directory appeared during merge of\n"
5431                               "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5432                             new_repos_relpath, old_rev + 1, new_rev,
5433                             details->added_rev_author, details->added_rev);
5434     }
5435   else if (new_node_kind == svn_node_file ||
5436            new_node_kind == svn_node_symlink)
5437     {
5438       if (old_rev + 1 == new_rev)
5439         return apr_psprintf(result_pool,
5440                             _("A new file appeared during merge of\n"
5441                               "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5442                             new_repos_relpath, new_rev,
5443                             details->added_rev_author, details->added_rev);
5444       else
5445         return apr_psprintf(result_pool,
5446                             _("A new file appeared during merge of\n"
5447                               "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5448                             new_repos_relpath, old_rev + 1, new_rev,
5449                             details->added_rev_author, details->added_rev);
5450     }
5451   else
5452     {
5453       if (old_rev + 1 == new_rev)
5454         return apr_psprintf(result_pool,
5455                             _("A new item appeared during merge of\n"
5456                               "'^/%s:%ld'.\nIt was added by %s in r%ld."),
5457                             new_repos_relpath, new_rev,
5458                             details->added_rev_author, details->added_rev);
5459       else
5460         return apr_psprintf(result_pool,
5461                             _("A new item appeared during merge of\n"
5462                               "'^/%s:%ld-%ld'.\nIt was added by %s in r%ld."),
5463                             new_repos_relpath, old_rev + 1, new_rev,
5464                             details->added_rev_author, details->added_rev);
5465     }
5466 }
5467
5468 static const char *
5469 describe_incoming_reverse_deletion_upon_merge(
5470   struct conflict_tree_incoming_add_details *details,
5471   svn_node_kind_t new_node_kind,
5472   const char *old_repos_relpath,
5473   svn_revnum_t old_rev,
5474   svn_revnum_t new_rev,
5475   apr_pool_t *result_pool)
5476 {
5477   if (new_node_kind == svn_node_dir)
5478     {
5479       if (new_rev + 1 == old_rev)
5480         return apr_psprintf(result_pool,
5481                             _("A new directory appeared during reverse-merge of"
5482                               "\n'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5483                             old_repos_relpath, old_rev,
5484                             details->deleted_rev_author,
5485                             details->deleted_rev);
5486       else
5487         return apr_psprintf(result_pool,
5488                             _("A new directory appeared during reverse-merge "
5489                               "of\n'^/%s:%ld-%ld'.\n"
5490                               "It was deleted by %s in r%ld."),
5491                             old_repos_relpath, new_rev, rev_below(old_rev),
5492                             details->deleted_rev_author,
5493                             details->deleted_rev);
5494     }
5495   else if (new_node_kind == svn_node_file ||
5496            new_node_kind == svn_node_symlink)
5497     {
5498       if (new_rev + 1 == old_rev)
5499         return apr_psprintf(result_pool,
5500                             _("A new file appeared during reverse-merge of\n"
5501                               "'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5502                             old_repos_relpath, old_rev,
5503                             details->deleted_rev_author,
5504                             details->deleted_rev);
5505       else
5506         return apr_psprintf(result_pool,
5507                             _("A new file appeared during reverse-merge of\n"
5508                               "'^/%s:%ld-%ld'.\nIt was deleted by %s in r%ld."),
5509                             old_repos_relpath, new_rev + 1, old_rev,
5510                             details->deleted_rev_author,
5511                             details->deleted_rev);
5512     }
5513   else
5514     {
5515       if (new_rev + 1 == old_rev)
5516         return apr_psprintf(result_pool,
5517                             _("A new item appeared during reverse-merge of\n"
5518                               "'^/%s:%ld'.\nIt was deleted by %s in r%ld."),
5519                             old_repos_relpath, old_rev,
5520                             details->deleted_rev_author,
5521                             details->deleted_rev);
5522       else
5523         return apr_psprintf(result_pool,
5524                             _("A new item appeared during reverse-merge of\n"
5525                               "'^/%s:%ld-%ld'.\nIt was deleted by %s in r%ld."),
5526                             old_repos_relpath, new_rev + 1, old_rev,
5527                             details->deleted_rev_author,
5528                             details->deleted_rev);
5529     }
5530 }
5531
5532 /* Implements tree_conflict_get_description_func_t. */
5533 static svn_error_t *
5534 conflict_tree_get_description_incoming_add(
5535   const char **incoming_change_description,
5536   svn_client_conflict_t *conflict,
5537   svn_client_ctx_t *ctx,
5538   apr_pool_t *result_pool,
5539   apr_pool_t *scratch_pool)
5540 {
5541   const char *action;
5542   svn_node_kind_t victim_node_kind;
5543   svn_wc_operation_t conflict_operation;
5544   const char *old_repos_relpath;
5545   svn_revnum_t old_rev;
5546   svn_node_kind_t old_node_kind;
5547   const char *new_repos_relpath;
5548   svn_revnum_t new_rev;
5549   svn_node_kind_t new_node_kind;
5550   struct conflict_tree_incoming_add_details *details;
5551
5552   if (conflict->tree_conflict_incoming_details == NULL)
5553     return svn_error_trace(conflict_tree_get_incoming_description_generic(
5554                              incoming_change_description, conflict, ctx,
5555                              result_pool, scratch_pool));
5556
5557   conflict_operation = svn_client_conflict_get_operation(conflict);
5558   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
5559
5560   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5561             &old_repos_relpath, &old_rev, &old_node_kind, conflict,
5562             scratch_pool, scratch_pool));
5563   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5564             &new_repos_relpath, &new_rev, &new_node_kind, conflict,
5565             scratch_pool, scratch_pool));
5566
5567   details = conflict->tree_conflict_incoming_details;
5568
5569   if (conflict_operation == svn_wc_operation_update)
5570     {
5571       action = describe_incoming_add_upon_update(details,
5572                                                  new_node_kind,
5573                                                  new_rev,
5574                                                  result_pool);
5575     }
5576   else if (conflict_operation == svn_wc_operation_switch)
5577     {
5578       action = describe_incoming_add_upon_switch(details,
5579                                                  victim_node_kind,
5580                                                  new_repos_relpath,
5581                                                  new_rev,
5582                                                  result_pool);
5583     }
5584   else if (conflict_operation == svn_wc_operation_merge)
5585     {
5586       if (old_rev < new_rev)
5587         action = describe_incoming_add_upon_merge(details,
5588                                                   new_node_kind,
5589                                                   old_rev,
5590                                                   new_repos_relpath,
5591                                                   new_rev,
5592                                                   result_pool);
5593       else
5594         action = describe_incoming_reverse_deletion_upon_merge(
5595                    details, new_node_kind, old_repos_relpath,
5596                    old_rev, new_rev, result_pool);
5597     }
5598
5599   *incoming_change_description = apr_pstrdup(result_pool, action);
5600
5601   return SVN_NO_ERROR;
5602 }
5603
5604 /* Details for tree conflicts involving incoming edits.
5605  * Note that we store an array of these. Each element corresponds to a
5606  * revision within the old/new range in which a modification occured. */
5607 struct conflict_tree_incoming_edit_details
5608 {
5609   /* The revision in which the edit ocurred. */
5610   svn_revnum_t rev;
5611
5612   /* The author of the revision. */
5613   const char *author;
5614
5615   /** Is the text modified? May be svn_tristate_unknown. */
5616   svn_tristate_t text_modified;
5617
5618   /** Are properties modified? May be svn_tristate_unknown. */
5619   svn_tristate_t props_modified;
5620
5621   /** For directories, are children modified?
5622    * May be svn_tristate_unknown. */
5623   svn_tristate_t children_modified;
5624
5625   /* The path which was edited, relative to the repository root. */
5626   const char *repos_relpath;
5627 };
5628
5629 /* Baton for find_modified_rev(). */
5630 struct find_modified_rev_baton {
5631   const char *victim_abspath;
5632   svn_client_ctx_t *ctx;
5633   apr_array_header_t *edits;
5634   const char *repos_relpath;
5635   svn_node_kind_t node_kind;
5636   apr_pool_t *result_pool;
5637   apr_pool_t *scratch_pool;
5638 };
5639
5640 /* Implements svn_log_entry_receiver_t. */
5641 static svn_error_t *
5642 find_modified_rev(void *baton,
5643                   svn_log_entry_t *log_entry,
5644                   apr_pool_t *scratch_pool)
5645 {
5646   struct find_modified_rev_baton *b = baton;
5647   struct conflict_tree_incoming_edit_details *details = NULL;
5648   svn_string_t *author;
5649   apr_hash_index_t *hi;
5650   apr_pool_t *iterpool;
5651
5652   if (b->ctx->notify_func2)
5653     {
5654       svn_wc_notify_t *notify;
5655
5656       notify = svn_wc_create_notify(
5657                  b->victim_abspath,
5658                  svn_wc_notify_tree_conflict_details_progress,
5659                  scratch_pool),
5660       notify->revision = log_entry->revision;
5661       b->ctx->notify_func2(b->ctx->notify_baton2, notify, scratch_pool);
5662     }
5663
5664   /* No paths were changed in this revision.  Nothing to do. */
5665   if (! log_entry->changed_paths2)
5666     return SVN_NO_ERROR;
5667
5668   details = apr_pcalloc(b->result_pool, sizeof(*details));
5669   details->rev = log_entry->revision;
5670   author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
5671   if (author)
5672     details->author = apr_pstrdup(b->result_pool, author->data);
5673   else
5674     details->author = _("unknown author");
5675
5676   details->text_modified = svn_tristate_unknown;
5677   details->props_modified = svn_tristate_unknown;
5678   details->children_modified = svn_tristate_unknown;
5679
5680   iterpool = svn_pool_create(scratch_pool);
5681   for (hi = apr_hash_first(scratch_pool, log_entry->changed_paths2);
5682        hi != NULL;
5683        hi = apr_hash_next(hi))
5684     {
5685       void *val;
5686       const char *path;
5687       svn_log_changed_path2_t *log_item;
5688
5689       svn_pool_clear(iterpool);
5690
5691       apr_hash_this(hi, (void *) &path, NULL, &val);
5692       log_item = val;
5693
5694       /* ### Remove leading slash from paths in log entries. */
5695       if (path[0] == '/')
5696           path = svn_relpath_canonicalize(path, iterpool);
5697
5698       if (svn_path_compare_paths(b->repos_relpath, path) == 0 &&
5699           (log_item->action == 'M' || log_item->action == 'A'))
5700         {
5701           details->text_modified = log_item->text_modified;
5702           details->props_modified = log_item->props_modified;
5703           details->repos_relpath = apr_pstrdup(b->result_pool, path);
5704
5705           if (log_item->copyfrom_path)
5706             b->repos_relpath = apr_pstrdup(b->scratch_pool,
5707                                            log_item->copyfrom_path);
5708         }
5709       else if (b->node_kind == svn_node_dir &&
5710                svn_relpath_skip_ancestor(b->repos_relpath, path) != NULL)
5711         details->children_modified = svn_tristate_true;
5712     }
5713
5714   if (b->node_kind == svn_node_dir &&
5715       details->children_modified == svn_tristate_unknown)
5716         details->children_modified = svn_tristate_false;
5717
5718   APR_ARRAY_PUSH(b->edits, struct conflict_tree_incoming_edit_details *) =
5719     details;
5720
5721   svn_pool_destroy(iterpool);
5722
5723   return SVN_NO_ERROR;
5724 }
5725
5726 /* Implements tree_conflict_get_details_func_t.
5727  * Find one or more revisions in which the victim was modified in the
5728  * repository. */
5729 static svn_error_t *
5730 conflict_tree_get_details_incoming_edit(svn_client_conflict_t *conflict,
5731                                         svn_client_ctx_t *ctx,
5732                                         apr_pool_t *scratch_pool)
5733 {
5734   const char *old_repos_relpath;
5735   const char *new_repos_relpath;
5736   const char *repos_root_url;
5737   svn_revnum_t old_rev;
5738   svn_revnum_t new_rev;
5739   svn_node_kind_t old_node_kind;
5740   svn_node_kind_t new_node_kind;
5741   svn_wc_operation_t operation;
5742   const char *url;
5743   const char *corrected_url;
5744   svn_ra_session_t *ra_session;
5745   apr_array_header_t *paths;
5746   apr_array_header_t *revprops;
5747   struct find_modified_rev_baton b = { 0 };
5748
5749   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5750             &old_repos_relpath, &old_rev, &old_node_kind, conflict,
5751             scratch_pool, scratch_pool));
5752   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5753             &new_repos_relpath, &new_rev, &new_node_kind, conflict,
5754             scratch_pool, scratch_pool));
5755   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
5756                                              conflict,
5757                                              scratch_pool, scratch_pool));
5758   operation = svn_client_conflict_get_operation(conflict);
5759   if (operation == svn_wc_operation_update)
5760     {
5761       b.node_kind = old_rev < new_rev ? new_node_kind : old_node_kind;
5762
5763       /* If there is no node then we cannot find any edits. */
5764       if (b.node_kind == svn_node_none)
5765         return SVN_NO_ERROR;
5766
5767       url = svn_path_url_add_component2(repos_root_url,
5768                                         old_rev < new_rev ? new_repos_relpath
5769                                                           : old_repos_relpath,
5770                                         scratch_pool);
5771
5772       b.repos_relpath = old_rev < new_rev ? new_repos_relpath
5773                                           : old_repos_relpath;
5774     }
5775   else if (operation == svn_wc_operation_switch ||
5776            operation == svn_wc_operation_merge)
5777     {
5778       url = svn_path_url_add_component2(repos_root_url, new_repos_relpath,
5779                                         scratch_pool);
5780
5781       b.repos_relpath = new_repos_relpath;
5782       b.node_kind = new_node_kind;
5783     }
5784
5785   SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
5786                                                &corrected_url,
5787                                                url, NULL, NULL,
5788                                                FALSE,
5789                                                FALSE,
5790                                                ctx,
5791                                                scratch_pool,
5792                                                scratch_pool));
5793
5794   paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
5795   APR_ARRAY_PUSH(paths, const char *) = "";
5796
5797   revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
5798   APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
5799
5800   b.ctx = ctx;
5801   b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
5802   b.result_pool = conflict->pool;
5803   b.scratch_pool = scratch_pool;
5804   b.edits = apr_array_make(
5805                conflict->pool, 0,
5806                sizeof(struct conflict_tree_incoming_edit_details *));
5807
5808   SVN_ERR(svn_ra_get_log2(ra_session, paths,
5809                           old_rev < new_rev ? old_rev : new_rev,
5810                           old_rev < new_rev ? new_rev : old_rev,
5811                           0, /* no limit */
5812                           TRUE, /* need the changed paths list */
5813                           FALSE, /* need to traverse copies */
5814                           FALSE, /* no need for merged revisions */
5815                           revprops,
5816                           find_modified_rev, &b,
5817                           scratch_pool));
5818
5819   conflict->tree_conflict_incoming_details = b.edits;
5820
5821   return SVN_NO_ERROR;
5822 }
5823
5824 static const char *
5825 describe_incoming_edit_upon_update(svn_revnum_t old_rev,
5826                                    svn_revnum_t new_rev,
5827                                    svn_node_kind_t old_node_kind,
5828                                    svn_node_kind_t new_node_kind,
5829                                    apr_pool_t *result_pool)
5830 {
5831   if (old_rev < new_rev)
5832     {
5833       if (new_node_kind == svn_node_dir)
5834         return apr_psprintf(result_pool,
5835                             _("Changes destined for a directory arrived "
5836                               "via the following revisions during update "
5837                               "from r%ld to r%ld."), old_rev, new_rev);
5838       else if (new_node_kind == svn_node_file ||
5839                new_node_kind == svn_node_symlink)
5840         return apr_psprintf(result_pool,
5841                             _("Changes destined for a file arrived "
5842                               "via the following revisions during update "
5843                               "from r%ld to r%ld"), old_rev, new_rev);
5844       else
5845         return apr_psprintf(result_pool,
5846                             _("Changes from the following revisions arrived "
5847                               "during update from r%ld to r%ld"),
5848                             old_rev, new_rev);
5849     }
5850   else
5851     {
5852       if (new_node_kind == svn_node_dir)
5853         return apr_psprintf(result_pool,
5854                             _("Changes destined for a directory arrived "
5855                               "via the following revisions during backwards "
5856                               "update from r%ld to r%ld"),
5857                             old_rev, new_rev);
5858       else if (new_node_kind == svn_node_file ||
5859                new_node_kind == svn_node_symlink)
5860         return apr_psprintf(result_pool,
5861                             _("Changes destined for a file arrived "
5862                               "via the following revisions during backwards "
5863                               "update from r%ld to r%ld"),
5864                             old_rev, new_rev);
5865       else
5866         return apr_psprintf(result_pool,
5867                             _("Changes from the following revisions arrived "
5868                               "during backwards update from r%ld to r%ld"),
5869                             old_rev, new_rev);
5870     }
5871 }
5872
5873 static const char *
5874 describe_incoming_edit_upon_switch(const char *new_repos_relpath,
5875                                    svn_revnum_t new_rev,
5876                                    svn_node_kind_t new_node_kind,
5877                                    apr_pool_t *result_pool)
5878 {
5879   if (new_node_kind == svn_node_dir)
5880     return apr_psprintf(result_pool,
5881                         _("Changes destined for a directory arrived via "
5882                           "the following revisions during switch to\n"
5883                           "'^/%s@r%ld'"),
5884                         new_repos_relpath, new_rev);
5885   else if (new_node_kind == svn_node_file ||
5886            new_node_kind == svn_node_symlink)
5887     return apr_psprintf(result_pool,
5888                         _("Changes destined for a directory arrived via "
5889                           "the following revisions during switch to\n"
5890                           "'^/%s@r%ld'"),
5891                         new_repos_relpath, new_rev);
5892   else
5893     return apr_psprintf(result_pool,
5894                         _("Changes from the following revisions arrived "
5895                           "during switch to\n'^/%s@r%ld'"),
5896                         new_repos_relpath, new_rev);
5897 }
5898
5899 /* Return a string showing the list of revisions in EDITS, ensuring
5900  * the string won't grow too large for display. */
5901 static const char *
5902 describe_incoming_edit_list_modified_revs(apr_array_header_t *edits,
5903                                           apr_pool_t *result_pool)
5904 {
5905   int num_revs_to_skip;
5906   static const int min_revs_for_skipping = 5;
5907   static const int max_revs_to_display = 8;
5908   const char *s = "";
5909   int i;
5910
5911   if (edits->nelts <= max_revs_to_display)
5912     num_revs_to_skip = 0;
5913   else
5914     {
5915       /* Check if we should insert a placeholder for some revisions because
5916        * the string would grow too long for display otherwise. */
5917       num_revs_to_skip = edits->nelts - max_revs_to_display;
5918       if (num_revs_to_skip < min_revs_for_skipping)
5919         {
5920           /* Don't bother with the placeholder. Just list all revisions. */
5921           num_revs_to_skip = 0;
5922         }
5923     }
5924
5925   for (i = 0; i < edits->nelts; i++)
5926     {
5927       struct conflict_tree_incoming_edit_details *details;
5928
5929       details = APR_ARRAY_IDX(edits, i,
5930                               struct conflict_tree_incoming_edit_details *);
5931       if (num_revs_to_skip > 0)
5932         {
5933           /* Insert a placeholder for revisions falling into the middle of
5934            * the range so we'll get something that looks like:
5935            * 1, 2, 3, 4, 5 [ placeholder ] 95, 96, 97, 98, 99 */
5936           if (i < max_revs_to_display / 2)
5937             s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
5938                              details->rev, details->author,
5939                              i < edits->nelts - 1 ? "," : "");
5940           else if (i >= max_revs_to_display / 2 &&
5941                    i < edits->nelts - (max_revs_to_display / 2))
5942               continue;
5943           else
5944             {
5945               if (i == edits->nelts - (max_revs_to_display / 2))
5946                   s = apr_psprintf(result_pool,
5947                                    _("%s\n [%d revisions omitted for "
5948                                      "brevity],\n"),
5949                                    s, num_revs_to_skip);
5950
5951               s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
5952                                details->rev, details->author,
5953                                i < edits->nelts - 1 ? "," : "");
5954             }
5955         } 
5956       else
5957         s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
5958                          details->rev, details->author,
5959                          i < edits->nelts - 1 ? "," : "");
5960     }
5961
5962   return s;
5963 }
5964
5965 /* Implements tree_conflict_get_description_func_t. */
5966 static svn_error_t *
5967 conflict_tree_get_description_incoming_edit(
5968   const char **incoming_change_description,
5969   svn_client_conflict_t *conflict,
5970   svn_client_ctx_t *ctx,
5971   apr_pool_t *result_pool,
5972   apr_pool_t *scratch_pool)
5973 {
5974   const char *action;
5975   svn_wc_operation_t conflict_operation;
5976   const char *old_repos_relpath;
5977   svn_revnum_t old_rev;
5978   svn_node_kind_t old_node_kind;
5979   const char *new_repos_relpath;
5980   svn_revnum_t new_rev;
5981   svn_node_kind_t new_node_kind;
5982   apr_array_header_t *edits;
5983
5984   if (conflict->tree_conflict_incoming_details == NULL)
5985     return svn_error_trace(conflict_tree_get_incoming_description_generic(
5986                              incoming_change_description, conflict, ctx,
5987                              result_pool, scratch_pool));
5988
5989   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
5990             &old_repos_relpath, &old_rev, &old_node_kind, conflict,
5991             scratch_pool, scratch_pool));
5992   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
5993             &new_repos_relpath, &new_rev, &new_node_kind, conflict,
5994             scratch_pool, scratch_pool));
5995
5996   conflict_operation = svn_client_conflict_get_operation(conflict);
5997
5998   edits = conflict->tree_conflict_incoming_details;
5999
6000   if (conflict_operation == svn_wc_operation_update)
6001     action = describe_incoming_edit_upon_update(old_rev, new_rev,
6002                                                 old_node_kind, new_node_kind,
6003                                                 scratch_pool);
6004   else if (conflict_operation == svn_wc_operation_switch)
6005     action = describe_incoming_edit_upon_switch(new_repos_relpath, new_rev,
6006                                                 new_node_kind, scratch_pool);
6007   else if (conflict_operation == svn_wc_operation_merge)
6008     {
6009       /* Handle merge inline because it returns early sometimes. */
6010       if (old_rev < new_rev)
6011         {
6012           if (old_rev + 1 == new_rev)
6013             {
6014               if (new_node_kind == svn_node_dir)
6015                 action = apr_psprintf(scratch_pool,
6016                                       _("Changes destined for a directory "
6017                                         "arrived during merge of\n"
6018                                         "'^/%s:%ld'."),
6019                                         new_repos_relpath, new_rev);
6020               else if (new_node_kind == svn_node_file ||
6021                        new_node_kind == svn_node_symlink)
6022                 action = apr_psprintf(scratch_pool,
6023                                       _("Changes destined for a file "
6024                                         "arrived during merge of\n"
6025                                         "'^/%s:%ld'."),
6026                                       new_repos_relpath, new_rev);
6027               else
6028                 action = apr_psprintf(scratch_pool,
6029                                       _("Changes arrived during merge of\n"
6030                                         "'^/%s:%ld'."),
6031                                       new_repos_relpath, new_rev);
6032
6033               *incoming_change_description = apr_pstrdup(result_pool, action);
6034
6035               return SVN_NO_ERROR;
6036             }
6037           else
6038             {
6039               if (new_node_kind == svn_node_dir)
6040                 action = apr_psprintf(scratch_pool,
6041                                       _("Changes destined for a directory "
6042                                         "arrived via the following revisions "
6043                                         "during merge of\n'^/%s:%ld-%ld'"),
6044                                       new_repos_relpath, old_rev + 1, new_rev);
6045               else if (new_node_kind == svn_node_file ||
6046                        new_node_kind == svn_node_symlink)
6047                 action = apr_psprintf(scratch_pool,
6048                                       _("Changes destined for a file "
6049                                         "arrived via the following revisions "
6050                                         "during merge of\n'^/%s:%ld-%ld'"),
6051                                       new_repos_relpath, old_rev + 1, new_rev);
6052               else
6053                 action = apr_psprintf(scratch_pool,
6054                                       _("Changes from the following revisions "
6055                                         "arrived during merge of\n"
6056                                         "'^/%s:%ld-%ld'"),
6057                                       new_repos_relpath, old_rev + 1, new_rev);
6058             }
6059         }
6060       else
6061         {
6062           if (new_rev + 1 == old_rev)
6063             {
6064               if (new_node_kind == svn_node_dir)
6065                 action = apr_psprintf(scratch_pool,
6066                                       _("Changes destined for a directory "
6067                                         "arrived during reverse-merge of\n"
6068                                         "'^/%s:%ld'."),
6069                                       new_repos_relpath, old_rev);
6070               else if (new_node_kind == svn_node_file ||
6071                        new_node_kind == svn_node_symlink)
6072                 action = apr_psprintf(scratch_pool,
6073                                       _("Changes destined for a file "
6074                                         "arrived during reverse-merge of\n"
6075                                         "'^/%s:%ld'."),
6076                                       new_repos_relpath, old_rev);
6077               else
6078                 action = apr_psprintf(scratch_pool,
6079                                       _("Changes arrived during reverse-merge "
6080                                         "of\n'^/%s:%ld'."),
6081                                       new_repos_relpath, old_rev);
6082
6083               *incoming_change_description = apr_pstrdup(result_pool, action);
6084
6085               return SVN_NO_ERROR;
6086             }
6087           else
6088             {
6089               if (new_node_kind == svn_node_dir)
6090                 action = apr_psprintf(scratch_pool,
6091                                       _("Changes destined for a directory "
6092                                         "arrived via the following revisions "
6093                                         "during reverse-merge of\n"
6094                                         "'^/%s:%ld-%ld'"),
6095                                       new_repos_relpath, new_rev + 1, old_rev);
6096               else if (new_node_kind == svn_node_file ||
6097                        new_node_kind == svn_node_symlink)
6098                 action = apr_psprintf(scratch_pool,
6099                                       _("Changes destined for a file "
6100                                         "arrived via the following revisions "
6101                                         "during reverse-merge of\n"
6102                                         "'^/%s:%ld-%ld'"),
6103                                       new_repos_relpath, new_rev + 1, old_rev);
6104                 
6105               else
6106                 action = apr_psprintf(scratch_pool,
6107                                       _("Changes from the following revisions "
6108                                         "arrived during reverse-merge of\n"
6109                                         "'^/%s:%ld-%ld'"),
6110                                       new_repos_relpath, new_rev + 1, old_rev);
6111             }
6112         }
6113     }
6114
6115   action = apr_psprintf(scratch_pool, "%s:\n%s", action,
6116                         describe_incoming_edit_list_modified_revs(
6117                           edits, scratch_pool));
6118   *incoming_change_description = apr_pstrdup(result_pool, action);
6119
6120   return SVN_NO_ERROR;
6121 }
6122
6123 svn_error_t *
6124 svn_client_conflict_tree_get_description(
6125   const char **incoming_change_description,
6126   const char **local_change_description,
6127   svn_client_conflict_t *conflict,
6128   svn_client_ctx_t *ctx,
6129   apr_pool_t *result_pool,
6130   apr_pool_t *scratch_pool)
6131 {
6132   SVN_ERR(conflict->tree_conflict_get_incoming_description_func(
6133             incoming_change_description,
6134             conflict, ctx, result_pool, scratch_pool));
6135
6136   SVN_ERR(conflict->tree_conflict_get_local_description_func(
6137             local_change_description,
6138             conflict, ctx, result_pool, scratch_pool));
6139   
6140   return SVN_NO_ERROR;
6141 }
6142
6143 void
6144 svn_client_conflict_option_set_merged_propval(
6145   svn_client_conflict_option_t *option,
6146   const svn_string_t *merged_propval)
6147 {
6148   option->type_data.prop.merged_propval = svn_string_dup(merged_propval,
6149                                                          option->pool);
6150 }
6151
6152 /* Implements conflict_option_resolve_func_t. */
6153 static svn_error_t *
6154 resolve_postpone(svn_client_conflict_option_t *option,
6155                  svn_client_conflict_t *conflict,
6156                  svn_client_ctx_t *ctx,
6157                  apr_pool_t *scratch_pool)
6158 {
6159   return SVN_NO_ERROR; /* Nothing to do. */
6160 }
6161
6162 /* Implements conflict_option_resolve_func_t. */
6163 static svn_error_t *
6164 resolve_text_conflict(svn_client_conflict_option_t *option,
6165                       svn_client_conflict_t *conflict,
6166                       svn_client_ctx_t *ctx,
6167                       apr_pool_t *scratch_pool)
6168 {
6169   svn_client_conflict_option_id_t option_id;
6170   const char *local_abspath;
6171   const char *lock_abspath;
6172   svn_wc_conflict_choice_t conflict_choice;
6173   svn_error_t *err;
6174
6175   option_id = svn_client_conflict_option_get_id(option);
6176   conflict_choice = conflict_option_id_to_wc_conflict_choice(option_id);
6177   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6178
6179   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6180                                                  local_abspath,
6181                                                  scratch_pool, scratch_pool));
6182   err = svn_wc__conflict_text_mark_resolved(ctx->wc_ctx,
6183                                             local_abspath,
6184                                             conflict_choice,
6185                                             ctx->cancel_func,
6186                                             ctx->cancel_baton,
6187                                             ctx->notify_func2,
6188                                             ctx->notify_baton2,
6189                                             scratch_pool);
6190   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6191                                                                  lock_abspath,
6192                                                                  scratch_pool));
6193   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6194   SVN_ERR(err);
6195
6196   conflict->resolution_text = option_id;
6197
6198   return SVN_NO_ERROR;
6199 }
6200
6201 /* Implements conflict_option_resolve_func_t. */
6202 static svn_error_t *
6203 resolve_prop_conflict(svn_client_conflict_option_t *option,
6204                       svn_client_conflict_t *conflict,
6205                       svn_client_ctx_t *ctx,
6206                       apr_pool_t *scratch_pool)
6207 {
6208   svn_client_conflict_option_id_t option_id;
6209   svn_wc_conflict_choice_t conflict_choice;
6210   const char *local_abspath;
6211   const char *lock_abspath;
6212   const char *propname = option->type_data.prop.propname;
6213   svn_error_t *err;
6214   const svn_string_t *merged_value;
6215
6216   option_id = svn_client_conflict_option_get_id(option);
6217   conflict_choice = conflict_option_id_to_wc_conflict_choice(option_id);
6218   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6219
6220   if (option_id == svn_client_conflict_option_merged_text)
6221     merged_value = option->type_data.prop.merged_propval;
6222   else
6223     merged_value = NULL;
6224
6225   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6226                                                  local_abspath,
6227                                                  scratch_pool, scratch_pool));
6228   err = svn_wc__conflict_prop_mark_resolved(ctx->wc_ctx, local_abspath,
6229                                             propname, conflict_choice,
6230                                             merged_value,
6231                                             ctx->notify_func2,
6232                                             ctx->notify_baton2,
6233                                             scratch_pool);
6234   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6235                                                                  lock_abspath,
6236                                                                  scratch_pool));
6237   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6238   SVN_ERR(err);
6239
6240   if (propname[0] == '\0')
6241     {
6242       apr_hash_index_t *hi;
6243
6244       /* All properties have been resolved to the same option. */
6245       for (hi = apr_hash_first(scratch_pool, conflict->prop_conflicts);
6246            hi;
6247            hi = apr_hash_next(hi))
6248         {
6249           const char *this_propname = apr_hash_this_key(hi);
6250
6251           svn_hash_sets(conflict->resolved_props,
6252                         apr_pstrdup(apr_hash_pool_get(conflict->resolved_props),
6253                                     this_propname),
6254                         option);
6255           svn_hash_sets(conflict->prop_conflicts, this_propname, NULL);
6256         }
6257
6258       conflict->legacy_prop_conflict_propname = NULL;
6259     }
6260   else
6261     {
6262       svn_hash_sets(conflict->resolved_props,
6263                     apr_pstrdup(apr_hash_pool_get(conflict->resolved_props),
6264                                 propname),
6265                    option);
6266       svn_hash_sets(conflict->prop_conflicts, propname, NULL);
6267
6268       if (apr_hash_count(conflict->prop_conflicts) > 0)
6269         conflict->legacy_prop_conflict_propname =
6270             apr_hash_this_key(apr_hash_first(scratch_pool,
6271                                              conflict->prop_conflicts));
6272       else
6273         conflict->legacy_prop_conflict_propname = NULL;
6274     }
6275
6276   return SVN_NO_ERROR;
6277 }
6278
6279 /* Implements conflict_option_resolve_func_t. */
6280 static svn_error_t *
6281 resolve_accept_current_wc_state(svn_client_conflict_option_t *option,
6282                                 svn_client_conflict_t *conflict,
6283                                 svn_client_ctx_t *ctx,
6284                                 apr_pool_t *scratch_pool)
6285 {
6286   svn_client_conflict_option_id_t option_id;
6287   const char *local_abspath;
6288   const char *lock_abspath;
6289   svn_error_t *err;
6290
6291   option_id = svn_client_conflict_option_get_id(option);
6292   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6293
6294   if (option_id != svn_client_conflict_option_accept_current_wc_state)
6295     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6296                              _("Tree conflict on '%s' can only be resolved "
6297                                "to the current working copy state"),
6298                              svn_dirent_local_style(local_abspath,
6299                                                     scratch_pool));
6300
6301   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6302                                                  local_abspath,
6303                                                  scratch_pool, scratch_pool));
6304
6305   /* Resolve to current working copy state. */
6306   err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
6307
6308   /* svn_wc__del_tree_conflict doesn't handle notification for us */
6309   if (ctx->notify_func2)
6310     ctx->notify_func2(ctx->notify_baton2,
6311                       svn_wc_create_notify(local_abspath,
6312                                            svn_wc_notify_resolved_tree,
6313                                            scratch_pool),
6314                       scratch_pool);
6315
6316   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6317                                                                  lock_abspath,
6318                                                                  scratch_pool));
6319   SVN_ERR(err);
6320
6321   conflict->resolution_tree = option_id;
6322
6323   return SVN_NO_ERROR;
6324 }
6325
6326 /* Implements conflict_option_resolve_func_t. */
6327 static svn_error_t *
6328 resolve_update_break_moved_away(svn_client_conflict_option_t *option,
6329                                 svn_client_conflict_t *conflict,
6330                                 svn_client_ctx_t *ctx,
6331                                 apr_pool_t *scratch_pool)
6332 {
6333   const char *local_abspath;
6334   const char *lock_abspath;
6335   svn_error_t *err;
6336
6337   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6338
6339   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6340                                                  local_abspath,
6341                                                  scratch_pool, scratch_pool));
6342   err = svn_wc__conflict_tree_update_break_moved_away(ctx->wc_ctx,
6343                                                       local_abspath,
6344                                                       ctx->cancel_func,
6345                                                       ctx->cancel_baton,
6346                                                       ctx->notify_func2,
6347                                                       ctx->notify_baton2,
6348                                                       scratch_pool);
6349   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6350                                                                  lock_abspath,
6351                                                                  scratch_pool));
6352   SVN_ERR(err);
6353
6354   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6355
6356   return SVN_NO_ERROR;
6357 }
6358
6359 /* Implements conflict_option_resolve_func_t. */
6360 static svn_error_t *
6361 resolve_update_raise_moved_away(svn_client_conflict_option_t *option,
6362                                 svn_client_conflict_t *conflict,
6363                                 svn_client_ctx_t *ctx,
6364                                 apr_pool_t *scratch_pool)
6365 {
6366   const char *local_abspath;
6367   const char *lock_abspath;
6368   svn_error_t *err;
6369
6370   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6371
6372   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6373                                                  local_abspath,
6374                                                  scratch_pool, scratch_pool));
6375   err = svn_wc__conflict_tree_update_raise_moved_away(ctx->wc_ctx,
6376                                                       local_abspath,
6377                                                       ctx->cancel_func,
6378                                                       ctx->cancel_baton,
6379                                                       ctx->notify_func2,
6380                                                       ctx->notify_baton2,
6381                                                       scratch_pool);
6382   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6383                                                                  lock_abspath,
6384                                                                  scratch_pool));
6385   SVN_ERR(err);
6386
6387   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6388
6389   return SVN_NO_ERROR;
6390 }
6391
6392 /* Implements conflict_option_resolve_func_t. */
6393 static svn_error_t *
6394 resolve_update_moved_away_node(svn_client_conflict_option_t *option,
6395                                svn_client_conflict_t *conflict,
6396                                svn_client_ctx_t *ctx,
6397                                apr_pool_t *scratch_pool)
6398 {
6399   const char *local_abspath;
6400   const char *lock_abspath;
6401   svn_error_t *err;
6402
6403   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6404
6405   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6406                                                  local_abspath,
6407                                                  scratch_pool, scratch_pool));
6408   err = svn_wc__conflict_tree_update_moved_away_node(ctx->wc_ctx,
6409                                                      local_abspath,
6410                                                      ctx->cancel_func,
6411                                                      ctx->cancel_baton,
6412                                                      ctx->notify_func2,
6413                                                      ctx->notify_baton2,
6414                                                      scratch_pool);
6415   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6416                                                                  lock_abspath,
6417                                                                  scratch_pool));
6418   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6419   SVN_ERR(err);
6420
6421   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6422
6423   return SVN_NO_ERROR;
6424 }
6425
6426 /* Verify the local working copy state matches what we expect when an
6427  * incoming add vs add tree conflict exists after an update operation.
6428  * We assume the update operation leaves the working copy in a state which
6429  * prefers the local change and cancels the incoming addition.
6430  * Run a quick sanity check and error out if it looks as if the
6431  * working copy was modified since, even though it's not easy to make
6432  * such modifications without also clearing the conflict marker. */
6433 static svn_error_t *
6434 verify_local_state_for_incoming_add_upon_update(
6435   svn_client_conflict_t *conflict,
6436   svn_client_conflict_option_t *option,
6437   svn_client_ctx_t *ctx,
6438   apr_pool_t *scratch_pool)
6439 {
6440   const char *local_abspath;
6441   svn_client_conflict_option_id_t option_id;
6442   const char *wcroot_abspath;
6443   svn_wc_operation_t operation;
6444   const char *incoming_new_repos_relpath;
6445   svn_revnum_t incoming_new_pegrev;
6446   svn_node_kind_t incoming_new_kind;
6447   const char *base_repos_relpath;
6448   svn_revnum_t base_rev;
6449   svn_node_kind_t base_kind;
6450   const char *local_style_relpath;
6451   svn_boolean_t is_added;
6452   svn_error_t *err;
6453
6454   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6455   option_id = svn_client_conflict_option_get_id(option);
6456   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
6457                              local_abspath, scratch_pool,
6458                              scratch_pool));
6459   operation = svn_client_conflict_get_operation(conflict);
6460   SVN_ERR_ASSERT(operation == svn_wc_operation_update);
6461
6462   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
6463             &incoming_new_repos_relpath, &incoming_new_pegrev,
6464             &incoming_new_kind, conflict, scratch_pool,
6465             scratch_pool));
6466
6467   local_style_relpath = svn_dirent_local_style(
6468                           svn_dirent_skip_ancestor(wcroot_abspath,
6469                                                    local_abspath),
6470                           scratch_pool);
6471
6472   /* Check if a local addition addition replaces the incoming new node. */
6473   err = svn_wc__node_get_base(&base_kind, &base_rev, &base_repos_relpath,
6474                               NULL, NULL, NULL, ctx->wc_ctx, local_abspath,
6475                               FALSE, scratch_pool, scratch_pool);
6476   if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
6477     {
6478       if (option_id == svn_client_conflict_option_incoming_add_ignore)
6479         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6480                                  _("Cannot resolve tree conflict on '%s' "
6481                                    "(expected a base node but found none)"),
6482                                  local_style_relpath);
6483       else if (option_id ==
6484                svn_client_conflict_option_incoming_added_dir_replace)
6485         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6486                                  _("Cannot resolve tree conflict on '%s' "
6487                                    "(expected a base node but found none)"),
6488                                  local_style_relpath);
6489       else
6490         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
6491                                  _("Unexpected option id '%d'"), option_id);
6492     }
6493   else if (err)
6494     return svn_error_trace(err);
6495
6496   if (base_kind != incoming_new_kind)
6497     {
6498       if (option_id == svn_client_conflict_option_incoming_add_ignore)
6499         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6500                                  _("Cannot resolve tree conflict on '%s' "
6501                                    "(expected base node kind '%s', "
6502                                    "but found '%s')"),
6503                                  local_style_relpath,
6504                                  svn_node_kind_to_word(incoming_new_kind),
6505                                  svn_node_kind_to_word(base_kind));
6506       else if (option_id ==
6507                svn_client_conflict_option_incoming_added_dir_replace)
6508         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6509                                  _("Cannot resolve tree conflict on '%s' "
6510                                    "(expected base node kind '%s', "
6511                                    "but found '%s')"),
6512                                   local_style_relpath,
6513                                  svn_node_kind_to_word(incoming_new_kind),
6514                                  svn_node_kind_to_word(base_kind));
6515       else
6516         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6517                                  _("Unexpected option id '%d'"), option_id);
6518     }
6519
6520   if (strcmp(base_repos_relpath, incoming_new_repos_relpath) != 0 ||
6521       base_rev != incoming_new_pegrev)
6522     {
6523       if (option_id == svn_client_conflict_option_incoming_add_ignore)
6524         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6525                                  _("Cannot resolve tree conflict on '%s' "
6526                                    "(expected base node from '^/%s@%ld', "
6527                                    "but found '^/%s@%ld')"),
6528                                  local_style_relpath,
6529                                  incoming_new_repos_relpath,
6530                                  incoming_new_pegrev,
6531                                  base_repos_relpath, base_rev);
6532       else if (option_id ==
6533                svn_client_conflict_option_incoming_added_dir_replace)
6534         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6535                                  _("Cannot resolve tree conflict on '%s' "
6536                                    "(expected base node from '^/%s@%ld', "
6537                                    "but found '^/%s@%ld')"),
6538                                  local_style_relpath,
6539                                  incoming_new_repos_relpath,
6540                                  incoming_new_pegrev,
6541                                  base_repos_relpath, base_rev);
6542       else
6543         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6544                                  _("Unexpected option id '%d'"), option_id);
6545     }
6546
6547   SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, local_abspath,
6548                                 scratch_pool));
6549   if (!is_added)
6550     {
6551       if (option_id == svn_client_conflict_option_incoming_add_ignore)
6552         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6553                                  _("Cannot resolve tree conflict on '%s' "
6554                                    "(expected an added item, but the item "
6555                                    "is not added)"),
6556                                  local_style_relpath);
6557
6558       else if (option_id ==
6559                svn_client_conflict_option_incoming_added_dir_replace)
6560         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6561                                  _("Cannot resolve tree conflict on '%s' "
6562                                    "(expected an added item, but the item "
6563                                    "is not added)"),
6564                                  local_style_relpath);
6565       else
6566         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
6567                                  _("Unexpected option id '%d'"), option_id);
6568     }
6569
6570   return SVN_NO_ERROR;
6571 }
6572
6573
6574 /* Implements conflict_option_resolve_func_t. */
6575 static svn_error_t *
6576 resolve_incoming_add_ignore(svn_client_conflict_option_t *option,
6577                             svn_client_conflict_t *conflict,
6578                             svn_client_ctx_t *ctx,
6579                             apr_pool_t *scratch_pool)
6580 {
6581   const char *local_abspath;
6582   const char *lock_abspath;
6583   svn_wc_operation_t operation;
6584   svn_error_t *err;
6585
6586   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6587   operation = svn_client_conflict_get_operation(conflict);
6588
6589   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6590                                                  local_abspath,
6591                                                  scratch_pool, scratch_pool));
6592
6593   if (operation == svn_wc_operation_update)
6594     {
6595       err = verify_local_state_for_incoming_add_upon_update(conflict, option,
6596                                                             ctx, scratch_pool);
6597       if (err)
6598         goto unlock_wc;
6599     }
6600
6601   /* All other options for this conflict actively fetch the incoming
6602    * new node. We can ignore the incoming new node by doing nothing. */
6603
6604   /* Resolve to current working copy state. */
6605   err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
6606
6607   /* svn_wc__del_tree_conflict doesn't handle notification for us */
6608   if (ctx->notify_func2)
6609     ctx->notify_func2(ctx->notify_baton2,
6610                       svn_wc_create_notify(local_abspath,
6611                                            svn_wc_notify_resolved_tree,
6612                                            scratch_pool),
6613                       scratch_pool);
6614
6615 unlock_wc:
6616   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6617                                                                  lock_abspath,
6618                                                                  scratch_pool));
6619   SVN_ERR(err);
6620
6621   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6622
6623   return SVN_NO_ERROR;
6624 }
6625
6626 /* Delete entry and wc props from a set of properties. */
6627 static void
6628 filter_props(apr_hash_t *props, apr_pool_t *scratch_pool)
6629 {
6630   apr_hash_index_t *hi;
6631
6632   for (hi = apr_hash_first(scratch_pool, props);
6633        hi != NULL;
6634        hi = apr_hash_next(hi))
6635     {
6636       const char *propname = apr_hash_this_key(hi);
6637
6638       if (!svn_wc_is_normal_prop(propname))
6639         svn_hash_sets(props, propname, NULL);
6640     }
6641 }
6642
6643 /* Implements conflict_option_resolve_func_t. */
6644 static svn_error_t *
6645 resolve_merge_incoming_added_file_text_update(
6646   svn_client_conflict_option_t *option,
6647   svn_client_conflict_t *conflict,
6648   svn_client_ctx_t *ctx,
6649   apr_pool_t *scratch_pool)
6650 {
6651   const char *wc_tmpdir;
6652   const char *local_abspath;
6653   const char *lock_abspath;
6654   svn_wc_merge_outcome_t merge_content_outcome;
6655   svn_wc_notify_state_t merge_props_outcome;
6656   const char *empty_file_abspath;
6657   const char *working_file_tmp_abspath;
6658   svn_stream_t *working_file_stream;
6659   svn_stream_t *working_file_tmp_stream;
6660   apr_hash_t *working_props;
6661   apr_array_header_t *propdiffs;
6662   svn_error_t *err;
6663
6664   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6665
6666   /* Set up tempory storage for the working version of file. */
6667   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
6668                              scratch_pool, scratch_pool));
6669   SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
6670                                  &working_file_tmp_abspath, wc_tmpdir,
6671                                  /* Don't delete automatically! */
6672                                  svn_io_file_del_none,
6673                                  scratch_pool, scratch_pool));
6674
6675   /* Copy the detranslated working file to temporary storage. */
6676   SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
6677                                     local_abspath, local_abspath,
6678                                     SVN_WC_TRANSLATE_TO_NF,
6679                                     scratch_pool, scratch_pool));
6680   SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
6681                            ctx->cancel_func, ctx->cancel_baton,
6682                            scratch_pool));
6683
6684   /* Get a copy of the working file's properties. */
6685   SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
6686                             scratch_pool, scratch_pool));
6687   filter_props(working_props, scratch_pool);
6688
6689   /* Create an empty file as fake "merge-base" for the two added files.
6690    * The files are not ancestrally related so this is the best we can do. */
6691   SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
6692                                    svn_io_file_del_on_pool_cleanup,
6693                                    scratch_pool, scratch_pool));
6694
6695   /* Create a property diff which shows all props as added. */
6696   SVN_ERR(svn_prop_diffs(&propdiffs, working_props,
6697                          apr_hash_make(scratch_pool), scratch_pool));
6698
6699   /* ### The following WC modifications should be atomic. */
6700   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6701                                                  local_abspath,
6702                                                  scratch_pool, scratch_pool));
6703
6704   /* Revert the path in order to restore the repository's line of
6705    * history, which is part of the BASE tree. This revert operation
6706    * is why are being careful about not losing the temporary copy. */
6707   err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_empty,
6708                        FALSE, NULL, TRUE, FALSE,
6709                        NULL, NULL, /* no cancellation */
6710                        ctx->notify_func2, ctx->notify_baton2,
6711                        scratch_pool);
6712   if (err)
6713     goto unlock_wc;
6714
6715   /* Perform the file merge. ### Merge into tempfile and then rename on top? */
6716   err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
6717                       ctx->wc_ctx, empty_file_abspath,
6718                       working_file_tmp_abspath, local_abspath,
6719                       NULL, NULL, NULL, /* labels */
6720                       NULL, NULL, /* conflict versions */
6721                       FALSE, /* dry run */
6722                       NULL, NULL, /* diff3_cmd, merge_options */
6723                       NULL, propdiffs,
6724                       NULL, NULL, /* conflict func/baton */
6725                       NULL, NULL, /* don't allow user to cancel here */
6726                       scratch_pool);
6727
6728 unlock_wc:
6729   if (err)
6730       err = svn_error_quick_wrapf(
6731               err, _("If needed, a backup copy of '%s' can be found at '%s'"),
6732               svn_dirent_local_style(local_abspath, scratch_pool),
6733               svn_dirent_local_style(working_file_tmp_abspath, scratch_pool));
6734   err = svn_error_compose_create(err,
6735                                  svn_wc__release_write_lock(ctx->wc_ctx,
6736                                                             lock_abspath,
6737                                                             scratch_pool));
6738   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6739   SVN_ERR(err);
6740   
6741   if (ctx->notify_func2)
6742     {
6743       svn_wc_notify_t *notify;
6744
6745       /* Tell the world about the file merge that just happened. */
6746       notify = svn_wc_create_notify(local_abspath,
6747                                     svn_wc_notify_update_update,
6748                                     scratch_pool);
6749       if (merge_content_outcome == svn_wc_merge_conflict)
6750         notify->content_state = svn_wc_notify_state_conflicted;
6751       else
6752         notify->content_state = svn_wc_notify_state_merged;
6753       notify->prop_state = merge_props_outcome;
6754       notify->kind = svn_node_file;
6755       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
6756
6757       /* And also about the successfully resolved tree conflict. */
6758       notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
6759                                     scratch_pool);
6760       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
6761     }
6762
6763   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6764
6765   /* All is good -- remove temporary copy of the working file. */
6766   SVN_ERR(svn_io_remove_file2(working_file_tmp_abspath, TRUE, scratch_pool));
6767
6768   return SVN_NO_ERROR;
6769 }
6770
6771 /* Implements conflict_option_resolve_func_t. */
6772 static svn_error_t *
6773 resolve_merge_incoming_added_file_text_merge(
6774   svn_client_conflict_option_t *option,
6775   svn_client_conflict_t *conflict,
6776   svn_client_ctx_t *ctx,
6777   apr_pool_t *scratch_pool)
6778 {
6779   svn_ra_session_t *ra_session;
6780   const char *url;
6781   const char *corrected_url;
6782   const char *repos_root_url;
6783   const char *wc_tmpdir;
6784   const char *incoming_new_repos_relpath;
6785   svn_revnum_t incoming_new_pegrev;
6786   const char *local_abspath;
6787   const char *lock_abspath;
6788   svn_wc_merge_outcome_t merge_content_outcome;
6789   svn_wc_notify_state_t merge_props_outcome;
6790   apr_file_t *incoming_new_file;
6791   const char *incoming_new_tmp_abspath;
6792   const char *empty_file_abspath;
6793   svn_stream_t *incoming_new_stream;
6794   apr_hash_t *incoming_new_props;
6795   apr_array_header_t *propdiffs;
6796   svn_error_t *err;
6797
6798   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6799
6800   /* Set up temporary storage for the repository version of file. */
6801   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
6802                              scratch_pool, scratch_pool));
6803   SVN_ERR(svn_io_open_unique_file3(&incoming_new_file,
6804                                    &incoming_new_tmp_abspath, wc_tmpdir,
6805                                    svn_io_file_del_on_pool_cleanup,
6806                                    scratch_pool, scratch_pool));
6807   incoming_new_stream = svn_stream_from_aprfile2(incoming_new_file, TRUE,
6808                                                  scratch_pool);
6809
6810   /* Fetch the incoming added file from the repository. */
6811   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
6812             &incoming_new_repos_relpath, &incoming_new_pegrev,
6813             NULL, conflict, scratch_pool,
6814             scratch_pool));
6815   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
6816                                              conflict, scratch_pool,
6817                                              scratch_pool));
6818   url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
6819                                     scratch_pool);
6820   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
6821                                                url, NULL, NULL, FALSE, FALSE,
6822                                                ctx, scratch_pool,
6823                                                scratch_pool));
6824   SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev,
6825                           incoming_new_stream, NULL, /* fetched_rev */
6826                           &incoming_new_props, scratch_pool));
6827
6828   /* Flush file to disk. */
6829   SVN_ERR(svn_stream_close(incoming_new_stream));
6830   SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool));
6831
6832   filter_props(incoming_new_props, scratch_pool);
6833
6834   /* Create an empty file as fake "merge-base" for the two added files.
6835    * The files are not ancestrally related so this is the best we can do. */
6836   SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
6837                                    svn_io_file_del_on_pool_cleanup,
6838                                    scratch_pool, scratch_pool));
6839
6840   /* Create a property diff which shows all props as added. */
6841   SVN_ERR(svn_prop_diffs(&propdiffs, incoming_new_props,
6842                          apr_hash_make(scratch_pool), scratch_pool));
6843
6844   /* ### The following WC modifications should be atomic. */
6845   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6846                                                  local_abspath,
6847                                                  scratch_pool, scratch_pool));
6848   /* Resolve to current working copy state. svn_wc_merge5() requires this. */
6849   err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
6850   if (err)
6851     return svn_error_compose_create(err,
6852                                     svn_wc__release_write_lock(ctx->wc_ctx,
6853                                                                lock_abspath,
6854                                                                scratch_pool));
6855   /* Perform the file merge. ### Merge into tempfile and then rename on top? */
6856   err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
6857                       ctx->wc_ctx, empty_file_abspath,
6858                       incoming_new_tmp_abspath, local_abspath,
6859                       NULL, NULL, NULL, /* labels */
6860                       NULL, NULL, /* conflict versions */
6861                       FALSE, /* dry run */
6862                       NULL, NULL, /* diff3_cmd, merge_options */
6863                       NULL, propdiffs,
6864                       NULL, NULL, /* conflict func/baton */
6865                       NULL, NULL, /* don't allow user to cancel here */
6866                       scratch_pool);
6867   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
6868                                                                  lock_abspath,
6869                                                                  scratch_pool));
6870   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
6871   SVN_ERR(err);
6872
6873   if (ctx->notify_func2)
6874     {
6875       svn_wc_notify_t *notify;
6876
6877       /* Tell the world about the file merge that just happened. */
6878       notify = svn_wc_create_notify(local_abspath,
6879                                     svn_wc_notify_update_update,
6880                                     scratch_pool);
6881       if (merge_content_outcome == svn_wc_merge_conflict)
6882         notify->content_state = svn_wc_notify_state_conflicted;
6883       else
6884         notify->content_state = svn_wc_notify_state_merged;
6885       notify->prop_state = merge_props_outcome;
6886       notify->kind = svn_node_file;
6887       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
6888
6889       /* And also about the successfully resolved tree conflict. */
6890       notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
6891                                     scratch_pool);
6892       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
6893     }
6894
6895   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
6896
6897   return SVN_NO_ERROR;
6898 }
6899
6900 /* Implements conflict_option_resolve_func_t. */
6901 static svn_error_t *
6902 resolve_merge_incoming_added_file_replace_and_merge(
6903   svn_client_conflict_option_t *option,
6904   svn_client_conflict_t *conflict,
6905   svn_client_ctx_t *ctx,
6906   apr_pool_t *scratch_pool)
6907 {
6908   svn_ra_session_t *ra_session;
6909   const char *url;
6910   const char *corrected_url;
6911   const char *repos_root_url;
6912   const char *incoming_new_repos_relpath;
6913   svn_revnum_t incoming_new_pegrev;
6914   apr_file_t *incoming_new_file;
6915   svn_stream_t *incoming_new_stream;
6916   apr_hash_t *incoming_new_props;
6917   const char *local_abspath;
6918   const char *lock_abspath;
6919   const char *wc_tmpdir;
6920   svn_stream_t *working_file_tmp_stream;
6921   const char *working_file_tmp_abspath;
6922   svn_stream_t *working_file_stream;
6923   apr_hash_t *working_props;
6924   svn_error_t *err;
6925   svn_wc_merge_outcome_t merge_content_outcome;
6926   svn_wc_notify_state_t merge_props_outcome;
6927   apr_file_t *empty_file;
6928   const char *empty_file_abspath;
6929   apr_array_header_t *propdiffs;
6930
6931   local_abspath = svn_client_conflict_get_local_abspath(conflict);
6932
6933   /* Set up tempory storage for the working version of file. */
6934   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
6935                              scratch_pool, scratch_pool));
6936   SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
6937                                  &working_file_tmp_abspath, wc_tmpdir,
6938                                  svn_io_file_del_on_pool_cleanup,
6939                                  scratch_pool, scratch_pool));
6940
6941   /* Copy the detranslated working file to temporary storage. */
6942   SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
6943                                     local_abspath, local_abspath,
6944                                     SVN_WC_TRANSLATE_TO_NF,
6945                                     scratch_pool, scratch_pool));
6946   SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
6947                            ctx->cancel_func, ctx->cancel_baton,
6948                            scratch_pool));
6949
6950   /* Get a copy of the working file's properties. */
6951   SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
6952                             scratch_pool, scratch_pool));
6953
6954   /* Fetch the incoming added file from the repository. */
6955   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
6956             &incoming_new_repos_relpath, &incoming_new_pegrev,
6957             NULL, conflict, scratch_pool,
6958             scratch_pool));
6959   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
6960                                              conflict, scratch_pool,
6961                                              scratch_pool));
6962   url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
6963                                     scratch_pool);
6964   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
6965                                                url, NULL, NULL, FALSE, FALSE,
6966                                                ctx, scratch_pool,
6967                                                scratch_pool));
6968   if (corrected_url)
6969     url = corrected_url;
6970   SVN_ERR(svn_io_open_unique_file3(&incoming_new_file, NULL, wc_tmpdir,
6971                                    svn_io_file_del_on_pool_cleanup,
6972                                    scratch_pool, scratch_pool));
6973   incoming_new_stream = svn_stream_from_aprfile2(incoming_new_file, TRUE,
6974                                                  scratch_pool);
6975   SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev,
6976                           incoming_new_stream, NULL, /* fetched_rev */
6977                           &incoming_new_props, scratch_pool));
6978   /* Flush file to disk. */
6979   SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool));
6980
6981   /* Reset the stream in preparation for adding its content to WC. */
6982   SVN_ERR(svn_stream_reset(incoming_new_stream));
6983
6984   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
6985                                                  local_abspath,
6986                                                  scratch_pool, scratch_pool));
6987
6988   /* ### The following WC modifications should be atomic. */
6989
6990   /* Replace the working file with the file from the repository. */
6991   err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
6992                        NULL, NULL, /* don't allow user to cancel here */
6993                        ctx->notify_func2, ctx->notify_baton2,
6994                        scratch_pool);
6995   if (err)
6996     goto unlock_wc;
6997   err = svn_wc_add_repos_file4(ctx->wc_ctx, local_abspath,
6998                                incoming_new_stream,
6999                                NULL, /* ### could we merge first, then set
7000                                         ### the merged content here? */
7001                                incoming_new_props,
7002                                NULL, /* ### merge props first, set here? */
7003                                url, incoming_new_pegrev,
7004                                NULL, NULL, /* don't allow user to cancel here */
7005                                scratch_pool);
7006   if (err)
7007     goto unlock_wc;
7008
7009   if (ctx->notify_func2)
7010     {
7011       svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
7012                                                      svn_wc_notify_add,
7013                                                      scratch_pool);
7014       notify->kind = svn_node_file;
7015       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7016     }
7017
7018   /* Resolve to current working copy state. svn_wc_merge5() requires this. */
7019   err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7020   if (err)
7021     goto unlock_wc;
7022
7023   /* Create an empty file as fake "merge-base" for the two added files.
7024    * The files are not ancestrally related so this is the best we can do. */
7025   err = svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
7026                                  svn_io_file_del_on_pool_cleanup,
7027                                  scratch_pool, scratch_pool);
7028   if (err)
7029     goto unlock_wc;
7030
7031   filter_props(incoming_new_props, scratch_pool);
7032
7033   /* Create a property diff for the files. */
7034   err = svn_prop_diffs(&propdiffs, incoming_new_props,
7035                        working_props, scratch_pool);
7036   if (err)
7037     goto unlock_wc;
7038
7039   /* Perform the file merge. */
7040   err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
7041                       ctx->wc_ctx, empty_file_abspath,
7042                       working_file_tmp_abspath, local_abspath,
7043                       NULL, NULL, NULL, /* labels */
7044                       NULL, NULL, /* conflict versions */
7045                       FALSE, /* dry run */
7046                       NULL, NULL, /* diff3_cmd, merge_options */
7047                       NULL, propdiffs,
7048                       NULL, NULL, /* conflict func/baton */
7049                       NULL, NULL, /* don't allow user to cancel here */
7050                       scratch_pool);
7051   if (err)
7052     goto unlock_wc;
7053
7054   if (ctx->notify_func2)
7055     {
7056       svn_wc_notify_t *notify = svn_wc_create_notify(
7057                                    local_abspath,
7058                                    svn_wc_notify_update_update,
7059                                    scratch_pool);
7060
7061       if (merge_content_outcome == svn_wc_merge_conflict)
7062         notify->content_state = svn_wc_notify_state_conflicted;
7063       else
7064         notify->content_state = svn_wc_notify_state_merged;
7065       notify->prop_state = merge_props_outcome;
7066       notify->kind = svn_node_file;
7067       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7068     }
7069
7070 unlock_wc:
7071   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7072                                                                  lock_abspath,
7073                                                                  scratch_pool));
7074   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7075   SVN_ERR(err);
7076
7077   SVN_ERR(svn_stream_close(incoming_new_stream));
7078
7079   if (ctx->notify_func2)
7080     {
7081       svn_wc_notify_t *notify = svn_wc_create_notify(
7082                                   local_abspath,
7083                                   svn_wc_notify_resolved_tree,
7084                                   scratch_pool);
7085
7086       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7087     }
7088
7089   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7090
7091   return SVN_NO_ERROR;
7092 }
7093
7094 static svn_error_t *
7095 raise_tree_conflict(const char *local_abspath,
7096                     svn_wc_conflict_action_t incoming_change,
7097                     svn_wc_conflict_reason_t local_change,
7098                     svn_node_kind_t local_node_kind,
7099                     svn_node_kind_t merge_left_kind,
7100                     svn_node_kind_t merge_right_kind,
7101                     const char *repos_root_url,
7102                     const char *repos_uuid,
7103                     const char *repos_relpath,
7104                     svn_revnum_t merge_left_rev,
7105                     svn_revnum_t merge_right_rev,
7106                     svn_wc_context_t *wc_ctx,
7107                     svn_wc_notify_func2_t notify_func2,
7108                     void *notify_baton2,
7109                     apr_pool_t *scratch_pool)
7110 {
7111   svn_wc_conflict_description2_t *conflict;
7112   const svn_wc_conflict_version_t *left_version;
7113   const svn_wc_conflict_version_t *right_version;
7114
7115   left_version = svn_wc_conflict_version_create2(repos_root_url,
7116                                                  repos_uuid,
7117                                                  repos_relpath,
7118                                                  merge_left_rev,
7119                                                  merge_left_kind,
7120                                                  scratch_pool);
7121   right_version = svn_wc_conflict_version_create2(repos_root_url,
7122                                                   repos_uuid,
7123                                                   repos_relpath,
7124                                                   merge_right_rev,
7125                                                   merge_right_kind,
7126                                                   scratch_pool);
7127   conflict = svn_wc_conflict_description_create_tree2(local_abspath,
7128                                                       local_node_kind,
7129                                                       svn_wc_operation_merge,
7130                                                       left_version,
7131                                                       right_version,
7132                                                       scratch_pool);
7133   conflict->action = incoming_change;
7134   conflict->reason = local_change;
7135
7136   SVN_ERR(svn_wc__add_tree_conflict(wc_ctx, conflict, scratch_pool));
7137
7138   if (notify_func2)
7139     {
7140       svn_wc_notify_t *notify;
7141
7142       notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
7143                                     scratch_pool);
7144       notify->kind = local_node_kind;
7145       notify_func2(notify_baton2, notify, scratch_pool);
7146     }
7147
7148   return SVN_NO_ERROR;
7149 }
7150
7151 struct merge_newly_added_dir_baton {
7152   const char *target_abspath;
7153   svn_client_ctx_t *ctx;
7154   const char *repos_root_url;
7155   const char *repos_uuid;
7156   const char *added_repos_relpath;
7157   svn_revnum_t merge_left_rev;
7158   svn_revnum_t merge_right_rev;
7159 };
7160
7161 static svn_error_t *
7162 merge_added_dir_props(const char *target_abspath,
7163                       const char *added_repos_relpath,
7164                       apr_hash_t *added_props,
7165                       const char *repos_root_url,
7166                       const char *repos_uuid,
7167                       svn_revnum_t merge_left_rev,
7168                       svn_revnum_t merge_right_rev,
7169                       svn_client_ctx_t *ctx,
7170                       apr_pool_t *scratch_pool)
7171 {
7172   svn_wc_notify_state_t property_state;
7173   apr_array_header_t *propchanges;
7174   const svn_wc_conflict_version_t *left_version;
7175   const svn_wc_conflict_version_t *right_version;
7176   apr_hash_index_t *hi;
7177
7178   left_version = svn_wc_conflict_version_create2(
7179                    repos_root_url, repos_uuid, added_repos_relpath,
7180                    merge_left_rev, svn_node_none, scratch_pool);
7181
7182   right_version = svn_wc_conflict_version_create2(
7183                     repos_root_url, repos_uuid, added_repos_relpath,
7184                     merge_right_rev, svn_node_dir, scratch_pool);
7185
7186   propchanges = apr_array_make(scratch_pool, apr_hash_count(added_props),
7187                                sizeof(svn_prop_t));
7188   for (hi = apr_hash_first(scratch_pool, added_props);
7189        hi;
7190        hi = apr_hash_next(hi))
7191     {
7192       svn_prop_t prop;
7193
7194       prop.name = apr_hash_this_key(hi);
7195       prop.value = apr_hash_this_val(hi);
7196
7197       if (svn_wc_is_normal_prop(prop.name))
7198         APR_ARRAY_PUSH(propchanges, svn_prop_t) = prop;
7199     }
7200
7201   SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx,
7202                               target_abspath,
7203                               left_version, right_version,
7204                               apr_hash_make(scratch_pool),
7205                               propchanges,
7206                               FALSE, /* not a dry-run */
7207                               NULL, NULL, NULL, NULL,
7208                               scratch_pool));
7209
7210   if (ctx->notify_func2)
7211     {
7212       svn_wc_notify_t *notify;
7213
7214       notify = svn_wc_create_notify(target_abspath,
7215                                     svn_wc_notify_update_update,
7216                                     scratch_pool);
7217       notify->kind = svn_node_dir;
7218       notify->content_state = svn_wc_notify_state_unchanged;;
7219       notify->prop_state = property_state;
7220       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7221     }
7222
7223   return SVN_NO_ERROR;
7224 }
7225
7226 /* An svn_diff_tree_processor_t callback. */
7227 static svn_error_t *
7228 diff_dir_added(const char *relpath,
7229                const svn_diff_source_t *copyfrom_source,
7230                const svn_diff_source_t *right_source,
7231                apr_hash_t *copyfrom_props,
7232                apr_hash_t *right_props,
7233                void *dir_baton,
7234                const struct svn_diff_tree_processor_t *processor,
7235                apr_pool_t *scratch_pool)
7236 {
7237   struct merge_newly_added_dir_baton *b = processor->baton;
7238   const char *local_abspath;
7239   const char *copyfrom_url;
7240   svn_node_kind_t db_kind;
7241   svn_node_kind_t on_disk_kind;
7242   apr_hash_index_t *hi;
7243
7244   /* Handle the root of the added directory tree. */
7245   if (relpath[0] == '\0')
7246     {
7247       /* ### svn_wc_merge_props3() requires this... */
7248       SVN_ERR(svn_wc__del_tree_conflict(b->ctx->wc_ctx, b->target_abspath,
7249                                         scratch_pool));
7250       SVN_ERR(merge_added_dir_props(b->target_abspath,
7251                                     b->added_repos_relpath, right_props,
7252                                     b->repos_root_url, b->repos_uuid,
7253                                     b->merge_left_rev, b->merge_right_rev,
7254                                     b->ctx, scratch_pool));
7255       return SVN_NO_ERROR;
7256
7257     }
7258
7259   local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
7260
7261   SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath,
7262                             FALSE, FALSE, scratch_pool));
7263   SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
7264
7265   if (db_kind == svn_node_dir && on_disk_kind == svn_node_dir)
7266     {
7267       SVN_ERR(merge_added_dir_props(svn_dirent_join(b->target_abspath, relpath,
7268                                                     scratch_pool),
7269                                     b->added_repos_relpath, right_props,
7270                                     b->repos_root_url, b->repos_uuid,
7271                                     b->merge_left_rev, b->merge_right_rev,
7272                                     b->ctx, scratch_pool));
7273       return SVN_NO_ERROR;
7274     }
7275
7276   if (db_kind != svn_node_none && db_kind != svn_node_unknown)
7277     {
7278       SVN_ERR(raise_tree_conflict(
7279                 local_abspath, svn_wc_conflict_action_add,
7280                 svn_wc_conflict_reason_obstructed,
7281                 db_kind, svn_node_none, svn_node_dir,
7282                 b->repos_root_url, b->repos_uuid,
7283                 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7284                 b->merge_left_rev, b->merge_right_rev,
7285                 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7286                 scratch_pool));
7287       return SVN_NO_ERROR;
7288     }
7289
7290   if (on_disk_kind != svn_node_none)
7291     {
7292       SVN_ERR(raise_tree_conflict(
7293                 local_abspath, svn_wc_conflict_action_add,
7294                 svn_wc_conflict_reason_obstructed, db_kind,
7295                 svn_node_none, svn_node_dir, b->repos_root_url, b->repos_uuid,
7296                 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7297                 b->merge_left_rev, b->merge_right_rev,
7298                 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7299                 scratch_pool));
7300       return SVN_NO_ERROR;
7301     }
7302
7303   SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
7304   copyfrom_url = apr_pstrcat(scratch_pool, b->repos_root_url, "/",
7305                              right_source->repos_relpath, SVN_VA_NULL);
7306   SVN_ERR(svn_wc_add4(b->ctx->wc_ctx, local_abspath, svn_depth_infinity,
7307                       copyfrom_url, right_source->revision,
7308                       NULL, NULL, /* cancel func/baton */
7309                       b->ctx->notify_func2, b->ctx->notify_baton2,
7310                       scratch_pool));
7311
7312   for (hi = apr_hash_first(scratch_pool, right_props);
7313        hi;
7314        hi = apr_hash_next(hi))
7315     {
7316       const char *propname = apr_hash_this_key(hi);
7317       const svn_string_t *propval = apr_hash_this_val(hi);
7318
7319       SVN_ERR(svn_wc_prop_set4(b->ctx->wc_ctx, local_abspath,
7320                                propname, propval, svn_depth_empty,
7321                                FALSE, NULL /* do not skip checks */,
7322                                NULL, NULL, /* cancel func/baton */
7323                                b->ctx->notify_func2, b->ctx->notify_baton2,
7324                                scratch_pool));
7325     }
7326
7327   return SVN_NO_ERROR;
7328 }
7329
7330 static svn_error_t *
7331 merge_added_files(const char *local_abspath,
7332                   const char *incoming_added_file_abspath,
7333                   apr_hash_t *incoming_added_file_props,
7334                   svn_client_ctx_t *ctx,
7335                   apr_pool_t *scratch_pool)
7336 {
7337   svn_wc_merge_outcome_t merge_content_outcome;
7338   svn_wc_notify_state_t merge_props_outcome;
7339   apr_file_t *empty_file;
7340   const char *empty_file_abspath;
7341   apr_array_header_t *propdiffs;
7342   apr_hash_t *working_props;
7343
7344   /* Create an empty file as fake "merge-base" for the two added files.
7345    * The files are not ancestrally related so this is the best we can do. */
7346   SVN_ERR(svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
7347                                    svn_io_file_del_on_pool_cleanup,
7348                                    scratch_pool, scratch_pool));
7349
7350   /* Get a copy of the working file's properties. */
7351   SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
7352                             scratch_pool, scratch_pool));
7353
7354   /* Create a property diff for the files. */
7355   SVN_ERR(svn_prop_diffs(&propdiffs, incoming_added_file_props,
7356                          working_props, scratch_pool));
7357
7358   /* Perform the file merge. */
7359   SVN_ERR(svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
7360                         ctx->wc_ctx, empty_file_abspath,
7361                         incoming_added_file_abspath, local_abspath,
7362                         NULL, NULL, NULL, /* labels */
7363                         NULL, NULL, /* conflict versions */
7364                         FALSE, /* dry run */
7365                         NULL, NULL, /* diff3_cmd, merge_options */
7366                         NULL, propdiffs,
7367                         NULL, NULL, /* conflict func/baton */
7368                         NULL, NULL, /* don't allow user to cancel here */
7369                         scratch_pool));
7370
7371   if (ctx->notify_func2)
7372     {
7373       svn_wc_notify_t *notify = svn_wc_create_notify(
7374                                    local_abspath,
7375                                    svn_wc_notify_update_update,
7376                                    scratch_pool);
7377
7378       if (merge_content_outcome == svn_wc_merge_conflict)
7379         notify->content_state = svn_wc_notify_state_conflicted;
7380       else
7381         notify->content_state = svn_wc_notify_state_merged;
7382       notify->prop_state = merge_props_outcome;
7383       notify->kind = svn_node_file;
7384       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7385     }
7386
7387   return SVN_NO_ERROR;
7388 }
7389
7390 /* An svn_diff_tree_processor_t callback. */
7391 static svn_error_t *
7392 diff_file_added(const char *relpath,
7393                 const svn_diff_source_t *copyfrom_source,
7394                 const svn_diff_source_t *right_source,
7395                 const char *copyfrom_file,
7396                 const char *right_file,
7397                 apr_hash_t *copyfrom_props,
7398                 apr_hash_t *right_props,
7399                 void *file_baton,
7400                 const struct svn_diff_tree_processor_t *processor,
7401                 apr_pool_t *scratch_pool)
7402 {
7403   struct merge_newly_added_dir_baton *b = processor->baton;
7404   const char *local_abspath;
7405   svn_node_kind_t db_kind;
7406   svn_node_kind_t on_disk_kind;
7407   apr_array_header_t *propsarray;
7408   apr_array_header_t *regular_props;
7409
7410   local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
7411
7412   SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath,
7413                             FALSE, FALSE, scratch_pool));
7414   SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
7415
7416   if (db_kind == svn_node_file && on_disk_kind == svn_node_file)
7417     {
7418       propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
7419       SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, &regular_props,
7420                                    scratch_pool));
7421       SVN_ERR(merge_added_files(local_abspath, right_file,
7422                                 svn_prop_array_to_hash(regular_props,
7423                                                        scratch_pool),
7424                                 b->ctx, scratch_pool));
7425       return SVN_NO_ERROR;
7426     }
7427
7428   if (db_kind != svn_node_none && db_kind != svn_node_unknown)
7429     {
7430       SVN_ERR(raise_tree_conflict(
7431                 local_abspath, svn_wc_conflict_action_add,
7432                 svn_wc_conflict_reason_obstructed,
7433                 db_kind, svn_node_none, svn_node_file,
7434                 b->repos_root_url, b->repos_uuid,
7435                 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7436                 b->merge_left_rev, b->merge_right_rev,
7437                 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7438                 scratch_pool));
7439       return SVN_NO_ERROR;
7440     }
7441
7442   if (on_disk_kind != svn_node_none)
7443     {
7444       SVN_ERR(raise_tree_conflict(
7445                 local_abspath, svn_wc_conflict_action_add,
7446                 svn_wc_conflict_reason_obstructed, db_kind,
7447                 svn_node_none, svn_node_file, b->repos_root_url, b->repos_uuid,
7448                 svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
7449                 b->merge_left_rev, b->merge_right_rev,
7450                 b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
7451                 scratch_pool));
7452       return SVN_NO_ERROR;
7453     }
7454
7455   propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
7456   SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, &regular_props,
7457                                scratch_pool));
7458   SVN_ERR(svn_io_copy_file(right_file, local_abspath, FALSE, scratch_pool));
7459   SVN_ERR(svn_wc_add_from_disk3(b->ctx->wc_ctx, local_abspath,
7460                                 svn_prop_array_to_hash(regular_props,
7461                                                        scratch_pool),
7462                                 FALSE, b->ctx->notify_func2,
7463                                 b->ctx->notify_baton2, scratch_pool));
7464
7465   return SVN_NO_ERROR;
7466 }
7467
7468 /* Merge a newly added directory into TARGET_ABSPATH in the working copy.
7469  *
7470  * This uses a diff-tree processor because our standard merge operation
7471  * is not set up for merges where the merge-source anchor is itself an
7472  * added directory (i.e. does not exist on one side of the diff).
7473  * The standard merge will only merge additions of children of a path
7474  * that exists across the entire revision range being merged.
7475  * But in our case, SOURCE1 does not yet exist in REV1, but SOURCE2
7476  * does exist in REV2. Thus we use a diff processor.
7477  */
7478 static svn_error_t *
7479 merge_newly_added_dir(const char *added_repos_relpath,
7480                       const char *source1,
7481                       svn_revnum_t rev1,
7482                       const char *source2,
7483                       svn_revnum_t rev2,
7484                       const char *target_abspath,
7485                       svn_boolean_t reverse_merge,
7486                       svn_client_ctx_t *ctx,
7487                       apr_pool_t *result_pool,
7488                       apr_pool_t *scratch_pool)
7489 {
7490   svn_diff_tree_processor_t *processor;
7491   struct merge_newly_added_dir_baton baton = { 0 };
7492   const svn_diff_tree_processor_t *diff_processor;
7493   svn_ra_session_t *ra_session;
7494   const char *corrected_url;
7495   svn_ra_session_t *extra_ra_session;
7496   const svn_ra_reporter3_t *reporter;
7497   void *reporter_baton;
7498   const svn_delta_editor_t *diff_editor;
7499   void *diff_edit_baton;
7500   const char *anchor1;
7501   const char *anchor2;
7502   const char *target1;
7503   const char *target2;
7504
7505   svn_uri_split(&anchor1, &target1, source1, scratch_pool);
7506   svn_uri_split(&anchor2, &target2, source2, scratch_pool);
7507
7508   baton.target_abspath = target_abspath;
7509   baton.ctx = ctx;
7510   baton.added_repos_relpath = added_repos_relpath;
7511   SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
7512                                       &baton.repos_root_url, &baton.repos_uuid,
7513                                       ctx->wc_ctx, target_abspath,
7514                                       scratch_pool, scratch_pool));
7515   baton.merge_left_rev = rev1;
7516   baton.merge_right_rev = rev2;
7517
7518   processor = svn_diff__tree_processor_create(&baton, scratch_pool);
7519   processor->dir_added = diff_dir_added;
7520   processor->file_added = diff_file_added;
7521
7522   diff_processor = processor;
7523   if (reverse_merge)
7524     diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
7525                                                              NULL,
7526                                                              scratch_pool);
7527
7528   /* Filter the first path component using a filter processor, until we fixed
7529      the diff processing to handle this directly */
7530   diff_processor = svn_diff__tree_processor_filter_create(
7531                      diff_processor, target1, scratch_pool);
7532
7533   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7534                                                anchor2, NULL, NULL, FALSE,
7535                                                FALSE, ctx,
7536                                                scratch_pool, scratch_pool));
7537   if (corrected_url)
7538     anchor2 = corrected_url;
7539
7540   /* Extra RA session is used during the editor calls to fetch file contents. */
7541   SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor2,
7542                               scratch_pool, scratch_pool));
7543
7544   /* Create a repos-repos diff editor. */
7545   SVN_ERR(svn_client__get_diff_editor2(
7546                 &diff_editor, &diff_edit_baton,
7547                 extra_ra_session, svn_depth_infinity, rev1, TRUE,
7548                 diff_processor, ctx->cancel_func, ctx->cancel_baton,
7549                 scratch_pool));
7550
7551   /* We want to switch our txn into URL2 */
7552   SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
7553                           rev2, target1, svn_depth_infinity, TRUE, TRUE,
7554                           source2, diff_editor, diff_edit_baton, scratch_pool));
7555
7556   /* Drive the reporter; do the diff. */
7557   SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
7558                              svn_depth_infinity,
7559                              FALSE, NULL,
7560                              scratch_pool));
7561
7562   SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
7563
7564   return SVN_NO_ERROR;
7565 }
7566
7567 /* Implements conflict_option_resolve_func_t. */
7568 static svn_error_t *
7569 resolve_merge_incoming_added_dir_merge(svn_client_conflict_option_t *option,
7570                                        svn_client_conflict_t *conflict,
7571                                        svn_client_ctx_t *ctx,
7572                                        apr_pool_t *scratch_pool)
7573 {
7574   const char *repos_root_url;
7575   const char *incoming_old_repos_relpath;
7576   svn_revnum_t incoming_old_pegrev;
7577   const char *incoming_new_repos_relpath;
7578   svn_revnum_t incoming_new_pegrev;
7579   const char *local_abspath;
7580   const char *lock_abspath;
7581   struct conflict_tree_incoming_add_details *details;
7582   const char *added_repos_relpath;
7583   const char *source1;
7584   svn_revnum_t rev1;
7585   const char *source2;
7586   svn_revnum_t rev2;
7587   svn_error_t *err;
7588
7589   local_abspath = svn_client_conflict_get_local_abspath(conflict);
7590
7591   details = conflict->tree_conflict_incoming_details;
7592   if (details == NULL)
7593     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7594                              _("Conflict resolution option '%d' requires "
7595                                "details for tree conflict at '%s' to be "
7596                                "fetched from the repository"),
7597                             option->id,
7598                             svn_dirent_local_style(local_abspath,
7599                                                    scratch_pool));
7600
7601   /* Set up merge sources to merge the entire incoming added directory tree. */
7602   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7603                                              conflict, scratch_pool,
7604                                              scratch_pool));
7605   source1 = svn_path_url_add_component2(repos_root_url,
7606                                         details->repos_relpath,
7607                                         scratch_pool);
7608   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
7609             &incoming_old_repos_relpath, &incoming_old_pegrev,
7610             NULL, conflict, scratch_pool, scratch_pool));
7611   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
7612             &incoming_new_repos_relpath, &incoming_new_pegrev,
7613             NULL, conflict, scratch_pool, scratch_pool));
7614   if (incoming_old_pegrev < incoming_new_pegrev) /* forward merge */
7615     {
7616       if (details->added_rev == SVN_INVALID_REVNUM)
7617         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7618                                  _("Could not determine when '%s' was "
7619                                    "added the repository"),
7620                                  svn_dirent_local_style(local_abspath,
7621                                                         scratch_pool));
7622       rev1 = rev_below(details->added_rev);
7623       source2 = svn_path_url_add_component2(repos_root_url,
7624                                             incoming_new_repos_relpath,
7625                                             scratch_pool);
7626       rev2 = incoming_new_pegrev;
7627       added_repos_relpath = incoming_new_repos_relpath;
7628     }
7629   else /* reverse-merge */
7630     {
7631       if (details->deleted_rev == SVN_INVALID_REVNUM)
7632         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7633                                  _("Could not determine when '%s' was "
7634                                    "deleted from the repository"),
7635                                  svn_dirent_local_style(local_abspath,
7636                                                         scratch_pool));
7637       rev1 = details->deleted_rev;
7638       source2 = svn_path_url_add_component2(repos_root_url,
7639                                             incoming_old_repos_relpath,
7640                                             scratch_pool);
7641       rev2 = incoming_old_pegrev;
7642       added_repos_relpath = incoming_new_repos_relpath;
7643     }
7644
7645   /* ### The following WC modifications should be atomic. */
7646   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7647                                                  local_abspath,
7648                                                  scratch_pool, scratch_pool));
7649
7650   /* ### wrap in a transaction */
7651   err = merge_newly_added_dir(added_repos_relpath,
7652                               source1, rev1, source2, rev2,
7653                               local_abspath,
7654                               (incoming_old_pegrev > incoming_new_pegrev),
7655                               ctx, scratch_pool, scratch_pool);
7656   if (!err)
7657     err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7658
7659   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7660                                                                  lock_abspath,
7661                                                                  scratch_pool));
7662   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7663   SVN_ERR(err);
7664
7665   if (ctx->notify_func2)
7666     ctx->notify_func2(ctx->notify_baton2,
7667                       svn_wc_create_notify(local_abspath,
7668                                            svn_wc_notify_resolved_tree,
7669                                            scratch_pool),
7670                       scratch_pool);
7671
7672   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7673
7674   return SVN_NO_ERROR;
7675 }
7676
7677 /* Implements conflict_option_resolve_func_t. */
7678 static svn_error_t *
7679 resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option,
7680                                        svn_client_conflict_t *conflict,
7681                                        svn_client_ctx_t *ctx,
7682                                        apr_pool_t *scratch_pool)
7683 {
7684   const char *local_abspath;
7685   const char *lock_abspath;
7686   svn_error_t *err;
7687
7688   local_abspath = svn_client_conflict_get_local_abspath(conflict);
7689
7690   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
7691             &lock_abspath, ctx->wc_ctx, local_abspath,
7692             scratch_pool, scratch_pool));
7693
7694   err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx,
7695                                                local_abspath,
7696                                                ctx->cancel_func,
7697                                                ctx->cancel_baton,
7698                                                ctx->notify_func2,
7699                                                ctx->notify_baton2,
7700                                                scratch_pool);
7701
7702   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7703                                                                  lock_abspath,
7704                                                                  scratch_pool));
7705   SVN_ERR(err);
7706
7707   return SVN_NO_ERROR;
7708 }
7709
7710 /* A baton for notification_adjust_func(). */
7711 struct notification_adjust_baton
7712 {
7713   svn_wc_notify_func2_t inner_func;
7714   void *inner_baton;
7715   const char *checkout_abspath;
7716   const char *final_abspath;
7717 };
7718
7719 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
7720  * baton is BATON->inner_baton) and adjusts the notification paths that
7721  * start with BATON->checkout_abspath to start instead with
7722  * BATON->final_abspath. */
7723 static void
7724 notification_adjust_func(void *baton,
7725                          const svn_wc_notify_t *notify,
7726                          apr_pool_t *pool)
7727 {
7728   struct notification_adjust_baton *nb = baton;
7729   svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
7730   const char *relpath;
7731
7732   relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
7733   inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
7734
7735   if (nb->inner_func)
7736     nb->inner_func(nb->inner_baton, inner_notify, pool);
7737 }
7738
7739 /* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by
7740  * replacing the local directory with the incoming directory.
7741  * If MERGE_DIRS is set, also merge the directories after replacing. */
7742 static svn_error_t *
7743 merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
7744                                  svn_client_conflict_t *conflict,
7745                                  svn_client_ctx_t *ctx,
7746                                  svn_boolean_t merge_dirs,
7747                                  apr_pool_t *scratch_pool)
7748 {
7749   svn_ra_session_t *ra_session;
7750   const char *url;
7751   const char *corrected_url;
7752   const char *repos_root_url;
7753   const char *incoming_new_repos_relpath;
7754   svn_revnum_t incoming_new_pegrev;
7755   const char *local_abspath;
7756   const char *lock_abspath;
7757   const char *tmpdir_abspath, *tmp_abspath;
7758   svn_error_t *err;
7759   svn_revnum_t copy_src_revnum;
7760   svn_opt_revision_t copy_src_peg_revision;
7761   svn_boolean_t timestamp_sleep;
7762   svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
7763   void *old_notify_baton2 = ctx->notify_baton2;
7764   struct notification_adjust_baton nb;
7765
7766   local_abspath = svn_client_conflict_get_local_abspath(conflict);
7767
7768   /* Find the URL of the incoming added directory in the repository. */
7769   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
7770             &incoming_new_repos_relpath, &incoming_new_pegrev,
7771             NULL, conflict, scratch_pool,
7772             scratch_pool));
7773   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
7774                                              conflict, scratch_pool,
7775                                              scratch_pool));
7776   url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
7777                                     scratch_pool);
7778   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
7779                                                url, NULL, NULL, FALSE, FALSE,
7780                                                ctx, scratch_pool,
7781                                                scratch_pool));
7782   if (corrected_url)
7783     url = corrected_url;
7784
7785
7786   /* Find a temporary location in which to check out the copy source. */
7787   SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, local_abspath,
7788                              scratch_pool, scratch_pool));
7789
7790   SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
7791                                    svn_io_file_del_on_close,
7792                                    scratch_pool, scratch_pool));
7793
7794   /* Make a new checkout of the requested source. While doing so,
7795    * resolve copy_src_revnum to an actual revision number in case it
7796    * was until now 'invalid' meaning 'head'.  Ask this function not to
7797    * sleep for timestamps, by passing a sleep_needed output param.
7798    * Send notifications for all nodes except the root node, and adjust
7799    * them to refer to the destination rather than this temporary path. */
7800
7801   nb.inner_func = ctx->notify_func2;
7802   nb.inner_baton = ctx->notify_baton2;
7803   nb.checkout_abspath = tmp_abspath;
7804   nb.final_abspath = local_abspath;
7805   ctx->notify_func2 = notification_adjust_func;
7806   ctx->notify_baton2 = &nb;
7807
7808   copy_src_peg_revision.kind = svn_opt_revision_number;
7809   copy_src_peg_revision.value.number = incoming_new_pegrev;
7810
7811   err = svn_client__checkout_internal(&copy_src_revnum, &timestamp_sleep,
7812                                       url, tmp_abspath,
7813                                       &copy_src_peg_revision,
7814                                       &copy_src_peg_revision,
7815                                       svn_depth_infinity,
7816                                       TRUE, /* we want to ignore externals */
7817                                       FALSE, /* we don't allow obstructions */
7818                                       ra_session, ctx, scratch_pool);
7819
7820   ctx->notify_func2 = old_notify_func2;
7821   ctx->notify_baton2 = old_notify_baton2;
7822
7823   SVN_ERR(err);
7824
7825   /* ### The following WC modifications should be atomic. */
7826
7827   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
7828                                                  svn_dirent_dirname(
7829                                                    local_abspath,
7830                                                    scratch_pool),
7831                                                  scratch_pool, scratch_pool));
7832
7833   /* Remove the working directory. */
7834   err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
7835                        NULL, NULL, /* don't allow user to cancel here */
7836                        ctx->notify_func2, ctx->notify_baton2,
7837                        scratch_pool);
7838   if (err)
7839     goto unlock_wc;
7840
7841   /* Schedule dst_path for addition in parent, with copy history.
7842      Don't send any notification here.
7843      Then remove the temporary checkout's .svn dir in preparation for
7844      moving the rest of it into the final destination. */
7845   err = svn_wc_copy3(ctx->wc_ctx, tmp_abspath, local_abspath,
7846                      TRUE /* metadata_only */,
7847                      NULL, NULL, /* don't allow user to cancel here */
7848                      NULL, NULL, scratch_pool);
7849   if (err)
7850     goto unlock_wc;
7851
7852   err = svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
7853                                    FALSE, scratch_pool, scratch_pool);
7854   if (err)
7855     goto unlock_wc;
7856   err = svn_wc_remove_from_revision_control2(ctx->wc_ctx,
7857                                              tmp_abspath,
7858                                              FALSE, FALSE,
7859                                              NULL, NULL, /* don't cancel */
7860                                              scratch_pool);
7861   if (err)
7862     goto unlock_wc;
7863
7864   /* Move the temporary disk tree into place. */
7865   err = svn_io_file_rename2(tmp_abspath, local_abspath, FALSE, scratch_pool);
7866   if (err)
7867     goto unlock_wc;
7868
7869   if (ctx->notify_func2)
7870     {
7871       svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
7872                                                      svn_wc_notify_add,
7873                                                      scratch_pool);
7874       notify->kind = svn_node_dir;
7875       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7876     }
7877
7878   /* Resolve to current working copy state.
7879    * svn_client__merge_locked() requires this. */
7880   err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
7881   if (err)
7882     goto unlock_wc;
7883
7884   if (merge_dirs)
7885     {
7886       svn_revnum_t base_revision;
7887       const char *base_repos_relpath;
7888       struct find_added_rev_baton b = { 0 };
7889
7890       /* Find the URL and revision of the directory we have just replaced. */
7891       err = svn_wc__node_get_base(NULL, &base_revision, &base_repos_relpath,
7892                                   NULL, NULL, NULL, ctx->wc_ctx, local_abspath,
7893                                   FALSE, scratch_pool, scratch_pool);
7894       if (err)
7895         goto unlock_wc;
7896
7897       url = svn_path_url_add_component2(repos_root_url, base_repos_relpath,
7898                                         scratch_pool);
7899
7900       /* Trace the replaced directory's history to its origin. */
7901       err = svn_ra_reparent(ra_session, url, scratch_pool);
7902       if (err)
7903         goto unlock_wc;
7904       b.victim_abspath = local_abspath;
7905       b.ctx = ctx;
7906       b.added_rev = SVN_INVALID_REVNUM;
7907       b.repos_relpath = NULL;
7908       b.parent_repos_relpath = svn_relpath_dirname(base_repos_relpath,
7909                                                    scratch_pool);
7910       b.pool = scratch_pool;
7911
7912       err = svn_ra_get_location_segments(ra_session, "", base_revision,
7913                                          base_revision, SVN_INVALID_REVNUM,
7914                                          find_added_rev, &b,
7915                                          scratch_pool);
7916       if (err)
7917         goto unlock_wc;
7918
7919       if (b.added_rev == SVN_INVALID_REVNUM)
7920         {
7921           err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
7922                                   _("Could not determine the revision in "
7923                                     "which '^/%s' was added to the "
7924                                     "repository.\n"),
7925                                   base_repos_relpath);
7926           goto unlock_wc;
7927         }
7928
7929       /* Merge the replaced directory into the directory which replaced it.
7930        * We do not need to consider a reverse-merge here since the source of
7931        * this merge was part of the merge target working copy, not a branch
7932        * in the repository. */
7933       err = merge_newly_added_dir(base_repos_relpath,
7934                                   url, rev_below(b.added_rev), url,
7935                                   base_revision, local_abspath, FALSE,
7936                                   ctx, scratch_pool, scratch_pool);
7937       if (err)
7938         goto unlock_wc;
7939     }
7940
7941 unlock_wc:
7942   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
7943                                                                  lock_abspath,
7944                                                                  scratch_pool));
7945   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
7946   SVN_ERR(err);
7947
7948   if (ctx->notify_func2)
7949     {
7950       svn_wc_notify_t *notify = svn_wc_create_notify(
7951                                   local_abspath,
7952                                   svn_wc_notify_resolved_tree,
7953                                   scratch_pool);
7954
7955       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
7956     }
7957
7958   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
7959
7960   return SVN_NO_ERROR;
7961 }
7962
7963 /* Implements conflict_option_resolve_func_t. */
7964 static svn_error_t *
7965 resolve_merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
7966                                          svn_client_conflict_t *conflict,
7967                                          svn_client_ctx_t *ctx,
7968                                          apr_pool_t *scratch_pool)
7969 {
7970   return svn_error_trace(merge_incoming_added_dir_replace(option,
7971                                                           conflict,
7972                                                           ctx,
7973                                                           FALSE,
7974                                                           scratch_pool));
7975 }
7976
7977 /* Implements conflict_option_resolve_func_t. */
7978 static svn_error_t *
7979 resolve_merge_incoming_added_dir_replace_and_merge(
7980   svn_client_conflict_option_t *option,
7981   svn_client_conflict_t *conflict,
7982   svn_client_ctx_t *ctx,
7983   apr_pool_t *scratch_pool)
7984 {
7985   return svn_error_trace(merge_incoming_added_dir_replace(option,
7986                                                           conflict,
7987                                                           ctx,
7988                                                           TRUE,
7989                                                           scratch_pool));
7990 }
7991
7992 /* Verify the local working copy state matches what we expect when an
7993  * incoming deletion tree conflict exists.
7994  * We assume update/merge/switch operations leave the working copy in a
7995  * state which prefers the local change and cancels the deletion.
7996  * Run a quick sanity check and error out if it looks as if the
7997  * working copy was modified since, even though it's not easy to make
7998  * such modifications without also clearing the conflict marker. */
7999 static svn_error_t *
8000 verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict,
8001                                        svn_client_conflict_option_t *option,
8002                                         svn_client_ctx_t *ctx,
8003                                        apr_pool_t *scratch_pool)
8004 {
8005   const char *local_abspath;
8006   const char *wcroot_abspath;
8007   svn_wc_operation_t operation;
8008
8009   local_abspath = svn_client_conflict_get_local_abspath(conflict);
8010   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
8011                              local_abspath, scratch_pool,
8012                              scratch_pool));
8013   operation = svn_client_conflict_get_operation(conflict);
8014
8015   if (operation == svn_wc_operation_update ||
8016       operation == svn_wc_operation_switch)
8017     {
8018       struct conflict_tree_incoming_delete_details *details;
8019       svn_boolean_t is_copy;
8020       svn_revnum_t copyfrom_rev;
8021       const char *copyfrom_repos_relpath;
8022
8023       details = conflict->tree_conflict_incoming_details;
8024       if (details == NULL)
8025         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8026                                  _("Conflict resolution option '%d' requires "
8027                                    "details for tree conflict at '%s' to be "
8028                                    "fetched from the repository."),
8029                                 option->id,
8030                                 svn_dirent_local_style(local_abspath,
8031                                                        scratch_pool));
8032
8033       /* Ensure that the item is a copy of itself from before it was deleted.
8034        * Update and switch are supposed to set this up when flagging the
8035        * conflict. */
8036       SVN_ERR(svn_wc__node_get_origin(&is_copy, &copyfrom_rev,
8037                                       &copyfrom_repos_relpath,
8038                                       NULL, NULL, NULL, NULL,
8039                                       ctx->wc_ctx, local_abspath, FALSE,
8040                                       scratch_pool, scratch_pool));
8041       if (!is_copy)
8042         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8043                                  _("Cannot resolve tree conflict on '%s' "
8044                                    "(expected a copied item, but the item "
8045                                    "is not a copy)"),
8046                                  svn_dirent_local_style(
8047                                    svn_dirent_skip_ancestor(
8048                                      wcroot_abspath,
8049                                      conflict->local_abspath),
8050                                  scratch_pool));
8051       else if (details->deleted_rev == SVN_INVALID_REVNUM &&
8052                details->added_rev == SVN_INVALID_REVNUM)
8053         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8054                                  _("Could not find the revision in which '%s' "
8055                                    "was deleted from the repository"),
8056                                  svn_dirent_local_style(
8057                                    svn_dirent_skip_ancestor(
8058                                      wcroot_abspath,
8059                                      conflict->local_abspath),
8060                                    scratch_pool));
8061       else if (details->deleted_rev != SVN_INVALID_REVNUM &&
8062                copyfrom_rev >= details->deleted_rev)
8063         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8064                                  _("Cannot resolve tree conflict on '%s' "
8065                                    "(expected an item copied from a revision "
8066                                    "smaller than r%ld, but the item was "
8067                                    "copied from r%ld)"),
8068                                  svn_dirent_local_style(
8069                                    svn_dirent_skip_ancestor(
8070                                      wcroot_abspath, conflict->local_abspath),
8071                                    scratch_pool),
8072                                  details->deleted_rev, copyfrom_rev);
8073
8074       else if (details->added_rev != SVN_INVALID_REVNUM &&
8075                copyfrom_rev < details->added_rev)
8076         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8077                                  _("Cannot resolve tree conflict on '%s' "
8078                                    "(expected an item copied from a revision "
8079                                    "larger than r%ld, but the item was "
8080                                    "copied from r%ld)"),
8081                                  svn_dirent_local_style(
8082                                    svn_dirent_skip_ancestor(
8083                                      wcroot_abspath, conflict->local_abspath),
8084                                    scratch_pool),
8085                                   details->added_rev, copyfrom_rev);
8086       else if (operation == svn_wc_operation_update)
8087         {
8088           const char *old_repos_relpath;
8089
8090           SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8091                     &old_repos_relpath, NULL, NULL, conflict,
8092                     scratch_pool, scratch_pool));
8093           if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 &&
8094               strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
8095             return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8096                                      _("Cannot resolve tree conflict on '%s' "
8097                                        "(expected an item copied from '^/%s' "
8098                                        "or from '^/%s' but the item was "
8099                                        "copied from '^/%s@%ld')"),
8100                                      svn_dirent_local_style(
8101                                        svn_dirent_skip_ancestor(
8102                                          wcroot_abspath, conflict->local_abspath),
8103                                        scratch_pool),
8104                                      details->repos_relpath,
8105                                      old_repos_relpath,
8106                                      copyfrom_repos_relpath, copyfrom_rev);
8107         }
8108       else if (operation == svn_wc_operation_switch)
8109         {
8110           const char *old_repos_relpath;
8111
8112           SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8113                     &old_repos_relpath, NULL, NULL, conflict,
8114                     scratch_pool, scratch_pool));
8115
8116           if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
8117             return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8118                                      _("Cannot resolve tree conflict on '%s' "
8119                                        "(expected an item copied from '^/%s', "
8120                                        "but the item was copied from "
8121                                         "'^/%s@%ld')"),
8122                                      svn_dirent_local_style(
8123                                        svn_dirent_skip_ancestor(
8124                                          wcroot_abspath,
8125                                          conflict->local_abspath),
8126                                        scratch_pool),
8127                                      old_repos_relpath,
8128                                      copyfrom_repos_relpath, copyfrom_rev);
8129         }
8130     }
8131   else if (operation == svn_wc_operation_merge)
8132     {
8133       svn_node_kind_t victim_node_kind;
8134       svn_node_kind_t on_disk_kind;
8135
8136       /* For merge, all we can do is ensure that the item still exists. */
8137       victim_node_kind =
8138         svn_client_conflict_tree_get_victim_node_kind(conflict);
8139       SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
8140
8141       if (victim_node_kind != on_disk_kind)
8142         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8143                                  _("Cannot resolve tree conflict on '%s' "
8144                                    "(expected node kind '%s' but found '%s')"),
8145                                  svn_dirent_local_style(
8146                                    svn_dirent_skip_ancestor(
8147                                      wcroot_abspath, conflict->local_abspath),
8148                                    scratch_pool),
8149                                  svn_node_kind_to_word(victim_node_kind),
8150                                  svn_node_kind_to_word(on_disk_kind));
8151     }
8152
8153   return SVN_NO_ERROR;
8154 }
8155
8156 /* Implements conflict_option_resolve_func_t. */
8157 static svn_error_t *
8158 resolve_incoming_delete_ignore(svn_client_conflict_option_t *option,
8159                                svn_client_conflict_t *conflict,
8160                                svn_client_ctx_t *ctx,
8161                                apr_pool_t *scratch_pool)
8162 {
8163   svn_client_conflict_option_id_t option_id;
8164   const char *local_abspath;
8165   const char *lock_abspath;
8166   svn_error_t *err;
8167
8168   option_id = svn_client_conflict_option_get_id(option);
8169   local_abspath = svn_client_conflict_get_local_abspath(conflict);
8170
8171   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8172                                                  local_abspath,
8173                                                  scratch_pool, scratch_pool));
8174
8175   err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8176                                                scratch_pool);
8177   if (err)
8178     goto unlock_wc;
8179
8180   /* Resolve to the current working copy state. */
8181   err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath, scratch_pool);
8182
8183   /* svn_wc__del_tree_conflict doesn't handle notification for us */
8184   if (ctx->notify_func2)
8185     ctx->notify_func2(ctx->notify_baton2,
8186                       svn_wc_create_notify(local_abspath,
8187                                            svn_wc_notify_resolved_tree,
8188                                            scratch_pool),
8189                       scratch_pool);
8190
8191 unlock_wc:
8192   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8193                                                                  lock_abspath,
8194                                                                  scratch_pool));
8195   SVN_ERR(err);
8196
8197   conflict->resolution_tree = option_id;
8198
8199   return SVN_NO_ERROR;
8200 }
8201
8202 /* Implements conflict_option_resolve_func_t. */
8203 static svn_error_t *
8204 resolve_incoming_delete_accept(svn_client_conflict_option_t *option,
8205                                svn_client_conflict_t *conflict,
8206                                svn_client_ctx_t *ctx,
8207                                apr_pool_t *scratch_pool)
8208 {
8209   svn_client_conflict_option_id_t option_id;
8210   const char *local_abspath;
8211   const char *parent_abspath;
8212   const char *lock_abspath;
8213   svn_error_t *err;
8214
8215   option_id = svn_client_conflict_option_get_id(option);
8216   local_abspath = svn_client_conflict_get_local_abspath(conflict);
8217
8218   /* Deleting a node requires a lock on the node's parent. */
8219   parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
8220   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
8221                                                  parent_abspath,
8222                                                  scratch_pool, scratch_pool));
8223
8224   err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8225                                                scratch_pool);
8226   if (err)
8227     goto unlock_wc;
8228
8229   /* Delete the tree conflict victim. Marks the conflict resolved. */
8230   err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
8231                        NULL, NULL, /* don't allow user to cancel here */
8232                        ctx->notify_func2, ctx->notify_baton2,
8233                        scratch_pool);
8234   if (err)
8235     {
8236       if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
8237         {
8238           /* Not a versioned path. This can happen if the victim has already
8239            * been deleted in our branche's history, for example. Either way,
8240            * the item is gone, which is what we want, so don't treat this as
8241            * a fatal error. */
8242           svn_error_clear(err);
8243
8244           /* Resolve to current working copy state. */
8245           err = svn_wc__del_tree_conflict(ctx->wc_ctx, local_abspath,
8246                                           scratch_pool);
8247         }
8248
8249       if (err)
8250         goto unlock_wc;
8251     }
8252
8253   if (ctx->notify_func2)
8254     ctx->notify_func2(ctx->notify_baton2,
8255                       svn_wc_create_notify(local_abspath,
8256                                            svn_wc_notify_resolved_tree,
8257                                            scratch_pool),
8258                       scratch_pool);
8259
8260 unlock_wc:
8261   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8262                                                                  lock_abspath,
8263                                                                  scratch_pool));
8264   SVN_ERR(err);
8265
8266   conflict->resolution_tree = option_id;
8267
8268   return SVN_NO_ERROR;
8269 }
8270
8271 /* Implements conflict_option_resolve_func_t. */
8272 static svn_error_t *
8273 resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
8274                                       svn_client_conflict_t *conflict,
8275                                       svn_client_ctx_t *ctx,
8276                                       apr_pool_t *scratch_pool)
8277 {
8278   svn_client_conflict_option_id_t option_id;
8279   const char *local_abspath;
8280   svn_wc_operation_t operation;
8281   const char *lock_abspath;
8282   svn_error_t *err;
8283   const char *repos_root_url;
8284   const char *incoming_old_repos_relpath;
8285   svn_revnum_t incoming_old_pegrev;
8286   const char *incoming_new_repos_relpath;
8287   svn_revnum_t incoming_new_pegrev;
8288   const char *wc_tmpdir;
8289   const char *ancestor_abspath;
8290   svn_stream_t *ancestor_stream;
8291   apr_hash_t *ancestor_props;
8292   apr_hash_t *victim_props;
8293   apr_hash_t *move_target_props;
8294   const char *ancestor_url;
8295   const char *corrected_url;
8296   svn_ra_session_t *ra_session;
8297   svn_wc_merge_outcome_t merge_content_outcome;
8298   svn_wc_notify_state_t merge_props_outcome;
8299   apr_array_header_t *propdiffs;
8300   struct conflict_tree_incoming_delete_details *details;
8301   apr_array_header_t *possible_moved_to_abspaths;
8302   const char *moved_to_abspath;
8303   const char *incoming_abspath = NULL;
8304
8305   local_abspath = svn_client_conflict_get_local_abspath(conflict);
8306   operation = svn_client_conflict_get_operation(conflict);
8307   details = conflict->tree_conflict_incoming_details;
8308   if (details == NULL || details->moves == NULL)
8309     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8310                              _("The specified conflict resolution option "
8311                                "requires details for tree conflict at '%s' "
8312                                "to be fetched from the repository first."),
8313                             svn_dirent_local_style(local_abspath,
8314                                                    scratch_pool));
8315   if (operation == svn_wc_operation_none)
8316     return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
8317                              _("Invalid operation code '%d' recorded for "
8318                                "conflict at '%s'"), operation,
8319                              svn_dirent_local_style(local_abspath,
8320                                                     scratch_pool));
8321
8322   option_id = svn_client_conflict_option_get_id(option);
8323   SVN_ERR_ASSERT(option_id ==
8324                  svn_client_conflict_option_incoming_move_file_text_merge ||
8325                  option_id ==
8326                  svn_client_conflict_option_incoming_move_dir_merge);
8327                   
8328   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8329                                              conflict, scratch_pool,
8330                                              scratch_pool));
8331   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8332             &incoming_old_repos_relpath, &incoming_old_pegrev,
8333             NULL, conflict, scratch_pool,
8334             scratch_pool));
8335   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8336             &incoming_new_repos_relpath, &incoming_new_pegrev,
8337             NULL, conflict, scratch_pool,
8338             scratch_pool));
8339
8340   /* Set up temporary storage for the common ancestor version of the file. */
8341   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
8342                              scratch_pool, scratch_pool));
8343   SVN_ERR(svn_stream_open_unique(&ancestor_stream,
8344                                  &ancestor_abspath, wc_tmpdir,
8345                                  svn_io_file_del_on_pool_cleanup,
8346                                  scratch_pool, scratch_pool));
8347
8348   /* Fetch the ancestor file's content. */
8349   ancestor_url = svn_path_url_add_component2(repos_root_url,
8350                                              incoming_old_repos_relpath,
8351                                              scratch_pool);
8352   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8353                                                ancestor_url, NULL, NULL,
8354                                                FALSE, FALSE, ctx,
8355                                                scratch_pool, scratch_pool));
8356   SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
8357                           ancestor_stream, NULL, /* fetched_rev */
8358                           &ancestor_props, scratch_pool));
8359   filter_props(ancestor_props, scratch_pool);
8360
8361   /* Close stream to flush ancestor file to disk. */
8362   SVN_ERR(svn_stream_close(ancestor_stream));
8363
8364   possible_moved_to_abspaths =
8365     svn_hash_gets(details->wc_move_targets,
8366                   get_moved_to_repos_relpath(details, scratch_pool));
8367   moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths,
8368                                    details->wc_move_target_idx,
8369                                    const char *);
8370
8371   /* ### The following WC modifications should be atomic. */
8372   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
8373             &lock_abspath, ctx->wc_ctx,
8374             svn_dirent_get_longest_ancestor(local_abspath,
8375                                             moved_to_abspath,
8376                                             scratch_pool),
8377             scratch_pool, scratch_pool));
8378
8379   err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8380                                                scratch_pool);
8381   if (err)
8382     goto unlock_wc;
8383
8384    /* Get a copy of the conflict victim's properties. */
8385   err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, local_abspath,
8386                           scratch_pool, scratch_pool);
8387   if (err)
8388     goto unlock_wc;
8389
8390   /* Get a copy of the move target's properties. */
8391   err = svn_wc_prop_list2(&move_target_props, ctx->wc_ctx,
8392                           moved_to_abspath,
8393                           scratch_pool, scratch_pool);
8394   if (err)
8395     goto unlock_wc;
8396
8397   /* Create a property diff for the files. */
8398   err = svn_prop_diffs(&propdiffs, move_target_props, victim_props,
8399                        scratch_pool);
8400   if (err)
8401     goto unlock_wc;
8402
8403   if (operation == svn_wc_operation_update ||
8404       operation == svn_wc_operation_switch)
8405     {
8406       svn_stream_t *working_stream;
8407       svn_stream_t *incoming_stream;
8408
8409       /* Create a temporary copy of the working file in repository-normal form.
8410        * Set up this temporary file to be automatically removed. */
8411       err = svn_stream_open_unique(&incoming_stream,
8412                                    &incoming_abspath, wc_tmpdir,
8413                                    svn_io_file_del_on_pool_cleanup,
8414                                    scratch_pool, scratch_pool);
8415       if (err)
8416         goto unlock_wc;
8417
8418       err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx,
8419                                       local_abspath, local_abspath,
8420                                       SVN_WC_TRANSLATE_TO_NF,
8421                                       scratch_pool, scratch_pool);
8422       if (err)
8423         goto unlock_wc;
8424
8425       err = svn_stream_copy3(working_stream, incoming_stream,
8426                              NULL, NULL, /* no cancellation */
8427                              scratch_pool);
8428       if (err)
8429         goto unlock_wc;
8430     }
8431   else if (operation == svn_wc_operation_merge)
8432     {
8433       svn_stream_t *incoming_stream;
8434       svn_stream_t *move_target_stream;
8435
8436       /* Set aside the current move target file. This is required to apply
8437        * the move, and only then perform a three-way text merge between
8438        * the ancestor's file, our working file (which we would move to
8439        * the destination), and the file that we have set aside, which
8440        * contains the incoming fulltext.
8441        * Set up this temporary file to NOT be automatically removed. */
8442       err = svn_stream_open_unique(&incoming_stream,
8443                                    &incoming_abspath, wc_tmpdir,
8444                                    svn_io_file_del_none,
8445                                    scratch_pool, scratch_pool);
8446       if (err)
8447         goto unlock_wc;
8448
8449       err = svn_wc__translated_stream(&move_target_stream, ctx->wc_ctx,
8450                                       moved_to_abspath, moved_to_abspath,
8451                                       SVN_WC_TRANSLATE_TO_NF,
8452                                       scratch_pool, scratch_pool);
8453       if (err)
8454         goto unlock_wc;
8455
8456       err = svn_stream_copy3(move_target_stream, incoming_stream,
8457                              NULL, NULL, /* no cancellation */
8458                              scratch_pool);
8459       if (err)
8460         goto unlock_wc;
8461
8462       /* Apply the incoming move. */
8463       err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool);
8464       if (err)
8465         goto unlock_wc;
8466       err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
8467                           FALSE, /* ordinary (not meta-data only) move */
8468                           FALSE, /* mixed-revisions don't apply to files */
8469                           NULL, NULL, /* don't allow user to cancel here */
8470                           NULL, NULL, /* no extra notification */
8471                           scratch_pool);
8472       if (err)
8473         goto unlock_wc;
8474     }
8475   else
8476     SVN_ERR_MALFUNCTION();
8477
8478   /* Perform the file merge. */
8479   err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
8480                       ctx->wc_ctx, ancestor_abspath,
8481                       incoming_abspath, moved_to_abspath,
8482                       NULL, NULL, NULL, /* labels */
8483                       NULL, NULL, /* conflict versions */
8484                       FALSE, /* dry run */
8485                       NULL, NULL, /* diff3_cmd, merge_options */
8486                       apr_hash_count(ancestor_props) ? ancestor_props : NULL,
8487                       propdiffs,
8488                       NULL, NULL, /* conflict func/baton */
8489                       NULL, NULL, /* don't allow user to cancel here */
8490                       scratch_pool);
8491   svn_io_sleep_for_timestamps(moved_to_abspath, scratch_pool);
8492   if (err)
8493     goto unlock_wc;
8494
8495   if (operation == svn_wc_operation_merge && incoming_abspath)
8496     {
8497       err = svn_io_remove_file2(incoming_abspath, TRUE, scratch_pool);
8498       if (err)
8499         goto unlock_wc;
8500       incoming_abspath = NULL;
8501     }
8502   
8503   if (ctx->notify_func2)
8504     {
8505       svn_wc_notify_t *notify;
8506
8507       /* Tell the world about the file merge that just happened. */
8508       notify = svn_wc_create_notify(moved_to_abspath,
8509                                     svn_wc_notify_update_update,
8510                                     scratch_pool);
8511       if (merge_content_outcome == svn_wc_merge_conflict)
8512         notify->content_state = svn_wc_notify_state_conflicted;
8513       else
8514         notify->content_state = svn_wc_notify_state_merged;
8515       notify->prop_state = merge_props_outcome;
8516       notify->kind = svn_node_file;
8517       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8518     }
8519
8520   if (operation == svn_wc_operation_update ||
8521       operation == svn_wc_operation_switch)
8522     {
8523       /* Delete the tree conflict victim (clears the tree conflict marker). */
8524       err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
8525                            NULL, NULL, /* don't allow user to cancel here */
8526                            NULL, NULL, /* no extra notification */
8527                            scratch_pool);
8528       if (err)
8529         goto unlock_wc;
8530     }
8531
8532   if (ctx->notify_func2)
8533     {
8534       svn_wc_notify_t *notify;
8535
8536       notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
8537                                     scratch_pool);
8538       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8539     }
8540
8541   conflict->resolution_tree = option_id;
8542
8543 unlock_wc:
8544   if (err && operation == svn_wc_operation_merge && incoming_abspath)
8545       err = svn_error_quick_wrapf(
8546               err, _("If needed, a backup copy of '%s' can be found at '%s'"),
8547               svn_dirent_local_style(moved_to_abspath, scratch_pool),
8548               svn_dirent_local_style(incoming_abspath, scratch_pool));
8549   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8550                                                                  lock_abspath,
8551                                                                  scratch_pool));
8552   SVN_ERR(err);
8553
8554   return SVN_NO_ERROR;
8555 }
8556
8557 /* Implements conflict_option_resolve_func_t. */
8558 static svn_error_t *
8559 resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
8560                                 svn_client_conflict_t *conflict,
8561                                 svn_client_ctx_t *ctx,
8562                                 apr_pool_t *scratch_pool)
8563 {
8564   svn_client_conflict_option_id_t option_id;
8565   const char *local_abspath;
8566   svn_wc_operation_t operation;
8567   const char *lock_abspath;
8568   svn_error_t *err;
8569   const char *repos_root_url;
8570   const char *repos_uuid;
8571   const char *incoming_old_repos_relpath;
8572   svn_revnum_t incoming_old_pegrev;
8573   const char *incoming_new_repos_relpath;
8574   svn_revnum_t incoming_new_pegrev;
8575   const char *victim_repos_relpath;
8576   svn_revnum_t victim_peg_rev;
8577   const char *moved_to_repos_relpath;
8578   svn_revnum_t moved_to_peg_rev;
8579   struct conflict_tree_incoming_delete_details *details;
8580   apr_array_header_t *possible_moved_to_abspaths;
8581   const char *moved_to_abspath;
8582   svn_client__pathrev_t *yca_loc;
8583   svn_opt_revision_t yca_opt_rev;
8584   svn_client__conflict_report_t *conflict_report;
8585   svn_boolean_t is_copy;
8586   svn_boolean_t is_modified;
8587
8588   local_abspath = svn_client_conflict_get_local_abspath(conflict);
8589   operation = svn_client_conflict_get_operation(conflict);
8590   details = conflict->tree_conflict_incoming_details;
8591   if (details == NULL || details->moves == NULL)
8592     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8593                              _("The specified conflict resolution option "
8594                                "requires details for tree conflict at '%s' "
8595                                "to be fetched from the repository first."),
8596                             svn_dirent_local_style(local_abspath,
8597                                                    scratch_pool));
8598
8599   option_id = svn_client_conflict_option_get_id(option);
8600   SVN_ERR_ASSERT(option_id ==
8601                  svn_client_conflict_option_incoming_move_dir_merge);
8602                   
8603   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
8604                                              conflict, scratch_pool,
8605                                              scratch_pool));
8606   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8607             &incoming_old_repos_relpath, &incoming_old_pegrev,
8608             NULL, conflict, scratch_pool,
8609             scratch_pool));
8610   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8611             &incoming_new_repos_relpath, &incoming_new_pegrev,
8612             NULL, conflict, scratch_pool,
8613             scratch_pool));
8614
8615   /* Get repository location of the moved-away node (the conflict victim). */
8616   if (operation == svn_wc_operation_update ||
8617       operation == svn_wc_operation_switch)
8618     {
8619       victim_repos_relpath = incoming_old_repos_relpath;
8620       victim_peg_rev = incoming_old_pegrev;
8621     }
8622   else if (operation == svn_wc_operation_merge)
8623     SVN_ERR(svn_wc__node_get_repos_info(&victim_peg_rev, &victim_repos_relpath,
8624                                         NULL, NULL, ctx->wc_ctx, local_abspath,
8625                                         scratch_pool, scratch_pool));
8626
8627   /* Get repository location of the moved-here node (incoming move). */
8628   possible_moved_to_abspaths =
8629     svn_hash_gets(details->wc_move_targets,
8630                   get_moved_to_repos_relpath(details, scratch_pool));
8631   moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths,
8632                                    details->wc_move_target_idx,
8633                                    const char *);
8634
8635   /* ### The following WC modifications should be atomic. */
8636
8637   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
8638             &lock_abspath, ctx->wc_ctx,
8639             svn_dirent_get_longest_ancestor(local_abspath,
8640                                             moved_to_abspath,
8641                                             scratch_pool),
8642             scratch_pool, scratch_pool));
8643
8644   err = svn_wc__node_get_origin(&is_copy, &moved_to_peg_rev,
8645                                 &moved_to_repos_relpath,
8646                                 NULL, NULL, NULL, NULL,
8647                                 ctx->wc_ctx, moved_to_abspath, FALSE,
8648                                 scratch_pool, scratch_pool);
8649   if (err)
8650     goto unlock_wc;
8651   if (!is_copy && operation == svn_wc_operation_merge)
8652     {
8653       err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8654                               _("Cannot resolve tree conflict on '%s' "
8655                                 "(expected a copied item at '%s', but the "
8656                                 "item is not a copy)"),
8657                               svn_dirent_local_style(local_abspath,
8658                                                      scratch_pool),
8659                               svn_dirent_local_style(moved_to_abspath,
8660                                                      scratch_pool));
8661       goto unlock_wc;
8662     }
8663
8664   if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM)
8665     {
8666       err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8667                               _("Cannot resolve tree conflict on '%s' "
8668                                 "(could not determine origin of '%s')"),
8669                               svn_dirent_local_style(local_abspath,
8670                                                      scratch_pool),
8671                               svn_dirent_local_style(moved_to_abspath,
8672                                                      scratch_pool));
8673       goto unlock_wc;
8674     }
8675
8676   /* Now find the youngest common ancestor of these nodes. */
8677   err = find_yca(&yca_loc, victim_repos_relpath, victim_peg_rev,
8678                  moved_to_repos_relpath, moved_to_peg_rev,
8679                  repos_root_url, repos_uuid,
8680                  NULL, ctx, scratch_pool, scratch_pool);
8681   if (err)
8682     goto unlock_wc;
8683
8684   if (yca_loc == NULL)
8685     {
8686       err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
8687                               _("Cannot resolve tree conflict on '%s' "
8688                                 "(could not find common ancestor of '^/%s@%ld' "
8689                                 " and '^/%s@%ld')"),
8690                               svn_dirent_local_style(local_abspath,
8691                                                      scratch_pool),
8692                               victim_repos_relpath, victim_peg_rev,
8693                               moved_to_repos_relpath, moved_to_peg_rev);
8694       goto unlock_wc;
8695     }
8696
8697   yca_opt_rev.kind = svn_opt_revision_number;
8698   yca_opt_rev.value.number = yca_loc->rev;
8699
8700   err = verify_local_state_for_incoming_delete(conflict, option, ctx,
8701                                                scratch_pool);
8702   if (err)
8703     goto unlock_wc;
8704
8705   if (operation == svn_wc_operation_merge)
8706     {
8707       const char *move_target_url;
8708       svn_opt_revision_t incoming_new_opt_rev;
8709
8710       /* Revert the incoming move target directory. */
8711       SVN_ERR(svn_wc_revert5(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity,
8712                              FALSE, NULL, TRUE, FALSE,
8713                              NULL, NULL, /* no cancellation */
8714                              ctx->notify_func2, ctx->notify_baton2,
8715                              scratch_pool));
8716
8717       /* The move operation is not part of natural history. We must replicate
8718        * this move in our history. Record a move in the working copy. */
8719       err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
8720                           FALSE, /* this is not a meta-data only move */
8721                           TRUE, /* allow mixed-revisions just in case */
8722                           NULL, NULL, /* don't allow user to cancel here */
8723                           ctx->notify_func2, ctx->notify_baton2,
8724                           scratch_pool);
8725       if (err)
8726         goto unlock_wc;
8727
8728       /* Merge YCA_URL@YCA_REV->MOVE_TARGET_URL@MERGE_RIGHT into move target. */
8729       move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
8730                                     get_moved_to_repos_relpath(details,
8731                                                                scratch_pool),
8732                                     SVN_VA_NULL);
8733       incoming_new_opt_rev.kind = svn_opt_revision_number;
8734       incoming_new_opt_rev.value.number = incoming_new_pegrev;
8735       err = svn_client__merge_locked(&conflict_report,
8736                                      yca_loc->url, &yca_opt_rev,
8737                                      move_target_url, &incoming_new_opt_rev,
8738                                      moved_to_abspath, svn_depth_infinity,
8739                                      TRUE, TRUE, /* do a no-ancestry merge */
8740                                      FALSE, FALSE, FALSE,
8741                                      TRUE, /* Allow mixed-rev just in case,
8742                                             * since conflict victims can't be
8743                                             * updated to straighten out
8744                                             * mixed-rev trees. */
8745                                      NULL, ctx, scratch_pool, scratch_pool);
8746       if (err)
8747         goto unlock_wc;
8748     }
8749   else
8750     {
8751       SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
8752                      operation == svn_wc_operation_switch);
8753
8754       /* Merge local modifications into the incoming move target dir. */
8755       err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath,
8756                                    TRUE, ctx->cancel_func, ctx->cancel_baton,
8757                                    scratch_pool);
8758       if (err)
8759         goto unlock_wc;
8760
8761       if (is_modified)
8762         {
8763           err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx,
8764                                                            local_abspath,
8765                                                            moved_to_abspath,
8766                                                            ctx->cancel_func,
8767                                                            ctx->cancel_baton,
8768                                                            ctx->notify_func2,
8769                                                            ctx->notify_baton2,
8770                                                            scratch_pool);
8771           if (err)
8772             goto unlock_wc;
8773         }
8774
8775       /* The move operation is part of our natural history.
8776        * Delete the tree conflict victim (clears the tree conflict marker). */
8777       err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
8778                            NULL, NULL, /* don't allow user to cancel here */
8779                            NULL, NULL, /* no extra notification */
8780                            scratch_pool);
8781       if (err)
8782         goto unlock_wc;
8783     }
8784
8785   if (ctx->notify_func2)
8786     {
8787       svn_wc_notify_t *notify;
8788
8789       notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
8790                                     scratch_pool);
8791       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8792     }
8793
8794   conflict->resolution_tree = option_id;
8795
8796 unlock_wc:
8797   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
8798                                                                  lock_abspath,
8799                                                                  scratch_pool));
8800   SVN_ERR(err);
8801
8802   return SVN_NO_ERROR;
8803 }
8804
8805 /* Implements conflict_option_resolve_func_t. */
8806 static svn_error_t *
8807 resolve_local_move_file_merge(svn_client_conflict_option_t *option,
8808                               svn_client_conflict_t *conflict,
8809                               svn_client_ctx_t *ctx,
8810                               apr_pool_t *scratch_pool)
8811 {
8812   const char *lock_abspath;
8813   svn_error_t *err;
8814   const char *repos_root_url;
8815   const char *incoming_old_repos_relpath;
8816   svn_revnum_t incoming_old_pegrev;
8817   const char *incoming_new_repos_relpath;
8818   svn_revnum_t incoming_new_pegrev;
8819   const char *wc_tmpdir;
8820   const char *ancestor_tmp_abspath;
8821   const char *incoming_tmp_abspath;
8822   apr_hash_t *ancestor_props;
8823   apr_hash_t *incoming_props;
8824   svn_stream_t *stream;
8825   const char *url;
8826   const char *corrected_url;
8827   const char *old_session_url;
8828   svn_ra_session_t *ra_session;
8829   svn_wc_merge_outcome_t merge_content_outcome;
8830   svn_wc_notify_state_t merge_props_outcome;
8831   apr_array_header_t *propdiffs;
8832   struct conflict_tree_local_missing_details *details;
8833
8834   details = conflict->tree_conflict_local_details;
8835
8836   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
8837                                              conflict, scratch_pool,
8838                                              scratch_pool));
8839   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
8840             &incoming_old_repos_relpath, &incoming_old_pegrev,
8841             NULL, conflict, scratch_pool,
8842             scratch_pool));
8843   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
8844             &incoming_new_repos_relpath, &incoming_new_pegrev,
8845             NULL, conflict, scratch_pool,
8846             scratch_pool));
8847
8848   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx,
8849                              details->moved_to_abspath,
8850                              scratch_pool, scratch_pool));
8851
8852   /* Fetch the common ancestor file's content. */
8853   SVN_ERR(svn_stream_open_unique(&stream, &ancestor_tmp_abspath, wc_tmpdir,
8854                                  svn_io_file_del_on_pool_cleanup,
8855                                  scratch_pool, scratch_pool));
8856   url = svn_path_url_add_component2(repos_root_url,
8857                                     incoming_old_repos_relpath,
8858                                     scratch_pool);
8859   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
8860                                                url, NULL, NULL,
8861                                                FALSE, FALSE, ctx,
8862                                                scratch_pool, scratch_pool));
8863   SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, stream, NULL,
8864                           &ancestor_props, scratch_pool));
8865   filter_props(ancestor_props, scratch_pool);
8866
8867   /* Close stream to flush the file to disk. */
8868   SVN_ERR(svn_stream_close(stream));
8869
8870   /* Do the same for the incoming file's content. */
8871   SVN_ERR(svn_stream_open_unique(&stream, &incoming_tmp_abspath, wc_tmpdir,
8872                                  svn_io_file_del_on_pool_cleanup,
8873                                  scratch_pool, scratch_pool));
8874   url = svn_path_url_add_component2(repos_root_url,
8875                                     incoming_new_repos_relpath,
8876                                     scratch_pool);
8877   SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
8878                                             url, scratch_pool));
8879   SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, stream, NULL,
8880                           &incoming_props, scratch_pool));
8881   /* Close stream to flush the file to disk. */
8882   SVN_ERR(svn_stream_close(stream));
8883
8884   filter_props(incoming_props, scratch_pool);
8885
8886   /* Create a property diff for the files. */
8887   SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props,
8888                          scratch_pool));
8889
8890   /* ### The following WC modifications should be atomic. */
8891   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
8892             &lock_abspath, ctx->wc_ctx,
8893             svn_dirent_get_longest_ancestor(conflict->local_abspath,
8894                                             details->moved_to_abspath,
8895                                             scratch_pool),
8896             scratch_pool, scratch_pool));
8897
8898   /* Perform the file merge. */
8899   err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
8900                       ctx->wc_ctx,
8901                       ancestor_tmp_abspath, incoming_tmp_abspath,
8902                       details->moved_to_abspath,
8903                       NULL, NULL, NULL, /* labels */
8904                       NULL, NULL, /* conflict versions */
8905                       FALSE, /* dry run */
8906                       NULL, NULL, /* diff3_cmd, merge_options */
8907                       apr_hash_count(ancestor_props) ? ancestor_props : NULL,
8908                       propdiffs,
8909                       NULL, NULL, /* conflict func/baton */
8910                       NULL, NULL, /* don't allow user to cancel here */
8911                       scratch_pool);
8912   svn_io_sleep_for_timestamps(details->moved_to_abspath, scratch_pool);
8913   if (err)
8914     return svn_error_compose_create(err,
8915                                     svn_wc__release_write_lock(ctx->wc_ctx,
8916                                                                lock_abspath,
8917                                                                scratch_pool));
8918
8919   err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
8920                                   scratch_pool);
8921   err = svn_error_compose_create(err,
8922                                  svn_wc__release_write_lock(ctx->wc_ctx,
8923                                                             lock_abspath,
8924                                                             scratch_pool));
8925   if (err)
8926     return svn_error_trace(err);
8927
8928   if (ctx->notify_func2)
8929     {
8930       svn_wc_notify_t *notify;
8931
8932       /* Tell the world about the file merge that just happened. */
8933       notify = svn_wc_create_notify(details->moved_to_abspath,
8934                                     svn_wc_notify_update_update,
8935                                     scratch_pool);
8936       if (merge_content_outcome == svn_wc_merge_conflict)
8937         notify->content_state = svn_wc_notify_state_conflicted;
8938       else
8939         notify->content_state = svn_wc_notify_state_merged;
8940       notify->prop_state = merge_props_outcome;
8941       notify->kind = svn_node_file;
8942       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8943
8944       /* And also about the successfully resolved tree conflict. */
8945       notify = svn_wc_create_notify(conflict->local_abspath,
8946                                     svn_wc_notify_resolved_tree,
8947                                     scratch_pool);
8948       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
8949     }
8950
8951   conflict->resolution_tree = svn_client_conflict_option_get_id(option);
8952
8953   return SVN_NO_ERROR;
8954 }
8955
8956 static svn_error_t *
8957 assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
8958 {
8959   svn_boolean_t text_conflicted;
8960
8961   SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL,
8962                                              conflict, scratch_pool,
8963                                              scratch_pool));
8964
8965   SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */
8966
8967   return SVN_NO_ERROR;
8968 }
8969
8970 static svn_error_t *
8971 assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
8972 {
8973   apr_array_header_t *props_conflicted;
8974
8975   SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
8976                                              conflict, scratch_pool,
8977                                              scratch_pool));
8978
8979   /* ### return proper error? */
8980   SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0);
8981
8982   return SVN_NO_ERROR;
8983 }
8984
8985 static svn_error_t *
8986 assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
8987 {
8988   svn_boolean_t tree_conflicted;
8989
8990   SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
8991                                              conflict, scratch_pool,
8992                                              scratch_pool));
8993
8994   SVN_ERR_ASSERT(tree_conflicted); /* ### return proper error? */
8995
8996   return SVN_NO_ERROR;
8997 }
8998
8999 /* Helper to add to conflict resolution option to array of OPTIONS.
9000  * Resolution option object will be allocated from OPTIONS->POOL
9001  * and DESCRIPTION will be copied to this pool.
9002  * Returns pointer to the created conflict resolution option. */
9003 static svn_client_conflict_option_t *
9004 add_resolution_option(apr_array_header_t *options,
9005                       svn_client_conflict_t *conflict,
9006                       svn_client_conflict_option_id_t id,
9007                       const char *label,
9008                       const char *description,
9009                       conflict_option_resolve_func_t resolve_func)
9010 {
9011     svn_client_conflict_option_t *option;
9012
9013     option = apr_pcalloc(options->pool, sizeof(*option));
9014     option->pool = options->pool;
9015     option->id = id;
9016     option->label = apr_pstrdup(option->pool, label);
9017     option->description = apr_pstrdup(option->pool, description);
9018     option->conflict = conflict;
9019     option->do_resolve_func = resolve_func;
9020
9021     APR_ARRAY_PUSH(options, const svn_client_conflict_option_t *) = option;
9022
9023     return option;
9024 }
9025
9026 svn_error_t *
9027 svn_client_conflict_text_get_resolution_options(apr_array_header_t **options,
9028                                                 svn_client_conflict_t *conflict,
9029                                                 svn_client_ctx_t *ctx,
9030                                                 apr_pool_t *result_pool,
9031                                                 apr_pool_t *scratch_pool)
9032 {
9033   const char *mime_type;
9034
9035   SVN_ERR(assert_text_conflict(conflict, scratch_pool));
9036
9037   *options = apr_array_make(result_pool, 7,
9038                             sizeof(svn_client_conflict_option_t *));
9039
9040   add_resolution_option(*options, conflict,
9041       svn_client_conflict_option_postpone,
9042       _("Postpone"),
9043       _("skip this conflict and leave it unresolved"),
9044       resolve_postpone);
9045
9046   mime_type = svn_client_conflict_text_get_mime_type(conflict);
9047   if (mime_type && svn_mime_type_is_binary(mime_type))
9048     {
9049       /* Resolver options for a binary file conflict. */
9050       add_resolution_option(*options, conflict,
9051         svn_client_conflict_option_base_text,
9052         _("Accept base"),
9053         _("discard local and incoming changes for this binary file"),
9054         resolve_text_conflict);
9055
9056       add_resolution_option(*options, conflict,
9057         svn_client_conflict_option_incoming_text,
9058         _("Accept incoming"),
9059         _("accept incoming version of binary file"),
9060         resolve_text_conflict);
9061
9062       add_resolution_option(*options, conflict,
9063         svn_client_conflict_option_working_text,
9064         _("Mark as resolved"),
9065         _("accept binary file as it appears in the working copy"),
9066         resolve_text_conflict);
9067   }
9068   else
9069     {
9070       /* Resolver options for a text file conflict. */
9071       add_resolution_option(*options, conflict,
9072         svn_client_conflict_option_base_text,
9073         _("Accept base"),
9074         _("discard local and incoming changes for this file"),
9075         resolve_text_conflict);
9076
9077       add_resolution_option(*options, conflict,
9078         svn_client_conflict_option_incoming_text,
9079         _("Accept incoming"),
9080         _("accept incoming version of entire file"),
9081         resolve_text_conflict);
9082
9083       add_resolution_option(*options, conflict,
9084         svn_client_conflict_option_working_text,
9085         _("Reject incoming"),
9086         _("reject all incoming changes for this file"),
9087         resolve_text_conflict);
9088
9089       add_resolution_option(*options, conflict,
9090         svn_client_conflict_option_incoming_text_where_conflicted,
9091         _("Accept incoming for conflicts"),
9092         _("accept changes only where they conflict"),
9093         resolve_text_conflict);
9094
9095       add_resolution_option(*options, conflict,
9096         svn_client_conflict_option_working_text_where_conflicted,
9097         _("Reject conflicts"),
9098         _("reject changes which conflict and accept the rest"),
9099         resolve_text_conflict);
9100
9101       add_resolution_option(*options, conflict,
9102         svn_client_conflict_option_merged_text,
9103         _("Mark as resolved"),
9104         _("accept the file as it appears in the working copy"),
9105         resolve_text_conflict);
9106     }
9107
9108   return SVN_NO_ERROR;
9109 }
9110
9111 svn_error_t *
9112 svn_client_conflict_prop_get_resolution_options(apr_array_header_t **options,
9113                                                 svn_client_conflict_t *conflict,
9114                                                 svn_client_ctx_t *ctx,
9115                                                 apr_pool_t *result_pool,
9116                                                 apr_pool_t *scratch_pool)
9117 {
9118   SVN_ERR(assert_prop_conflict(conflict, scratch_pool));
9119
9120   *options = apr_array_make(result_pool, 7,
9121                             sizeof(svn_client_conflict_option_t *));
9122
9123   add_resolution_option(*options, conflict,
9124     svn_client_conflict_option_postpone,
9125     _("Postpone"),
9126     _("skip this conflict and leave it unresolved"),
9127     resolve_postpone);
9128
9129   add_resolution_option(*options, conflict,
9130     svn_client_conflict_option_base_text,
9131     _("Accept base"),
9132     _("discard local and incoming changes for this property"),
9133     resolve_prop_conflict);
9134
9135   add_resolution_option(*options, conflict,
9136     svn_client_conflict_option_incoming_text,
9137     _("Accept incoming"),
9138     _("accept incoming version of entire property value"),
9139     resolve_prop_conflict);
9140
9141   add_resolution_option(*options, conflict,
9142     svn_client_conflict_option_working_text,
9143     _("Mark as resolved"),
9144     _("accept working copy version of entire property value"),
9145     resolve_prop_conflict);
9146
9147   add_resolution_option(*options, conflict,
9148     svn_client_conflict_option_incoming_text_where_conflicted,
9149     _("Accept incoming for conflicts"),
9150     _("accept incoming changes only where they conflict"),
9151     resolve_prop_conflict);
9152
9153   add_resolution_option(*options, conflict,
9154     svn_client_conflict_option_working_text_where_conflicted,
9155     _("Reject conflicts"),
9156     _("reject changes which conflict and accept the rest"),
9157     resolve_prop_conflict);
9158
9159   add_resolution_option(*options, conflict,
9160     svn_client_conflict_option_merged_text,
9161     _("Accept merged"),
9162     _("accept merged version of property value"),
9163     resolve_prop_conflict);
9164
9165   return SVN_NO_ERROR;
9166 }
9167
9168 /* Configure 'accept current wc state' resolution option for a tree conflict. */
9169 static svn_error_t *
9170 configure_option_accept_current_wc_state(svn_client_conflict_t *conflict,
9171                                          apr_array_header_t *options)
9172 {
9173   svn_wc_operation_t operation;
9174   svn_wc_conflict_action_t incoming_change;
9175   svn_wc_conflict_reason_t local_change;
9176   conflict_option_resolve_func_t do_resolve_func;
9177
9178   operation = svn_client_conflict_get_operation(conflict);
9179   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9180   local_change = svn_client_conflict_get_local_change(conflict);
9181
9182   if ((operation == svn_wc_operation_update ||
9183        operation == svn_wc_operation_switch) &&
9184       (local_change == svn_wc_conflict_reason_moved_away ||
9185        local_change == svn_wc_conflict_reason_deleted ||
9186        local_change == svn_wc_conflict_reason_replaced) &&
9187       incoming_change == svn_wc_conflict_action_edit)
9188     {
9189       /* We must break moves if the user accepts the current working copy
9190        * state instead of updating a moved-away node or updating children
9191        * moved outside of deleted or replaced directory nodes.
9192        * Else such moves would be left in an invalid state. */
9193       do_resolve_func = resolve_update_break_moved_away;
9194     }
9195   else
9196     do_resolve_func = resolve_accept_current_wc_state;
9197
9198   add_resolution_option(options, conflict,
9199                         svn_client_conflict_option_accept_current_wc_state,
9200                         _("Mark as resolved"),
9201                         _("accept current working copy state"),
9202                         do_resolve_func);
9203
9204   return SVN_NO_ERROR;
9205 }
9206
9207 /* Configure 'update move destination' resolution option for a tree conflict. */
9208 static svn_error_t *
9209 configure_option_update_move_destination(svn_client_conflict_t *conflict,
9210                                          apr_array_header_t *options)
9211 {
9212   svn_wc_operation_t operation;
9213   svn_wc_conflict_action_t incoming_change;
9214   svn_wc_conflict_reason_t local_change;
9215
9216   operation = svn_client_conflict_get_operation(conflict);
9217   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9218   local_change = svn_client_conflict_get_local_change(conflict);
9219
9220   if ((operation == svn_wc_operation_update ||
9221        operation == svn_wc_operation_switch) &&
9222       incoming_change == svn_wc_conflict_action_edit &&
9223       local_change == svn_wc_conflict_reason_moved_away)
9224     {
9225       add_resolution_option(
9226         options, conflict,
9227         svn_client_conflict_option_update_move_destination,
9228         _("Update move destination"),
9229         _("apply incoming changes to move destination"),
9230         resolve_update_moved_away_node);
9231     }
9232
9233   return SVN_NO_ERROR;
9234 }
9235
9236 /* Configure 'update raise moved away children' resolution option for a tree
9237  * conflict. */
9238 static svn_error_t *
9239 configure_option_update_raise_moved_away_children(
9240   svn_client_conflict_t *conflict,
9241   apr_array_header_t *options)
9242 {
9243   svn_wc_operation_t operation;
9244   svn_wc_conflict_action_t incoming_change;
9245   svn_wc_conflict_reason_t local_change;
9246   svn_node_kind_t victim_node_kind;
9247
9248   operation = svn_client_conflict_get_operation(conflict);
9249   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9250   local_change = svn_client_conflict_get_local_change(conflict);
9251   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9252
9253   if ((operation == svn_wc_operation_update ||
9254        operation == svn_wc_operation_switch) &&
9255       incoming_change == svn_wc_conflict_action_edit &&
9256       (local_change == svn_wc_conflict_reason_deleted ||
9257        local_change == svn_wc_conflict_reason_replaced) &&
9258       victim_node_kind == svn_node_dir)
9259     {
9260       add_resolution_option(
9261         options, conflict,
9262         svn_client_conflict_option_update_any_moved_away_children,
9263         _("Update any moved-away children"),
9264         _("prepare for updating moved-away children, if any"),
9265         resolve_update_raise_moved_away);
9266     }
9267
9268   return SVN_NO_ERROR;
9269 }
9270
9271 /* Configure 'incoming add ignore' resolution option for a tree conflict. */
9272 static svn_error_t *
9273 configure_option_incoming_add_ignore(svn_client_conflict_t *conflict,
9274                                      svn_client_ctx_t *ctx,
9275                                      apr_array_header_t *options,
9276                                      apr_pool_t *scratch_pool)
9277 {
9278   svn_wc_operation_t operation;
9279   svn_wc_conflict_action_t incoming_change;
9280   svn_wc_conflict_reason_t local_change;
9281   const char *incoming_new_repos_relpath;
9282   svn_revnum_t incoming_new_pegrev;
9283   svn_node_kind_t victim_node_kind;
9284
9285   operation = svn_client_conflict_get_operation(conflict);
9286   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9287   local_change = svn_client_conflict_get_local_change(conflict);
9288   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9289   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9290             &incoming_new_repos_relpath, &incoming_new_pegrev,
9291             NULL, conflict, scratch_pool,
9292             scratch_pool));
9293
9294   /* This option is only available for directories. */
9295   if (victim_node_kind == svn_node_dir &&
9296       incoming_change == svn_wc_conflict_action_add &&
9297       (local_change == svn_wc_conflict_reason_obstructed ||
9298        local_change == svn_wc_conflict_reason_added))
9299     {
9300       const char *description;
9301       const char *wcroot_abspath;
9302
9303       SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9304                                  conflict->local_abspath, scratch_pool,
9305                                  scratch_pool));
9306       if (operation == svn_wc_operation_merge)
9307         description =
9308           apr_psprintf(scratch_pool,
9309                        _("ignore and do not add '^/%s@%ld' here"),
9310                        incoming_new_repos_relpath, incoming_new_pegrev);
9311       else if (operation == svn_wc_operation_update ||
9312                operation == svn_wc_operation_switch)
9313         {
9314           if (victim_node_kind == svn_node_file)
9315             description =
9316               apr_psprintf(scratch_pool,
9317                            _("replace '^/%s@%ld' with the locally added file"),
9318                            incoming_new_repos_relpath, incoming_new_pegrev);
9319           else if (victim_node_kind == svn_node_dir)
9320             description =
9321               apr_psprintf(scratch_pool,
9322                            _("replace '^/%s@%ld' with the locally added "
9323                              "directory"),
9324                            incoming_new_repos_relpath, incoming_new_pegrev);
9325           else
9326             description =
9327               apr_psprintf(scratch_pool,
9328                            _("replace '^/%s@%ld' with the locally added item"),
9329                            incoming_new_repos_relpath, incoming_new_pegrev);
9330         }
9331       else
9332         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
9333                                  _("unexpected operation code '%d'"),
9334                                  operation);
9335       add_resolution_option(
9336         options, conflict, svn_client_conflict_option_incoming_add_ignore,
9337         _("Ignore incoming addition"), description, resolve_incoming_add_ignore);
9338     }
9339
9340   return SVN_NO_ERROR;
9341 }
9342
9343 /* Configure 'incoming added file text merge' resolution option for a tree
9344  * conflict. */
9345 static svn_error_t *
9346 configure_option_incoming_added_file_text_merge(svn_client_conflict_t *conflict,
9347                                                 svn_client_ctx_t *ctx,
9348                                                 apr_array_header_t *options,
9349                                                 apr_pool_t *scratch_pool)
9350 {
9351   svn_wc_operation_t operation;
9352   svn_wc_conflict_action_t incoming_change;
9353   svn_wc_conflict_reason_t local_change;
9354   svn_node_kind_t victim_node_kind;
9355   const char *incoming_new_repos_relpath;
9356   svn_revnum_t incoming_new_pegrev;
9357   svn_node_kind_t incoming_new_kind;
9358
9359   operation = svn_client_conflict_get_operation(conflict);
9360   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9361   local_change = svn_client_conflict_get_local_change(conflict);
9362   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9363   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9364             &incoming_new_repos_relpath, &incoming_new_pegrev,
9365             &incoming_new_kind, conflict, scratch_pool,
9366             scratch_pool));
9367
9368   if (victim_node_kind == svn_node_file &&
9369       incoming_new_kind == svn_node_file &&
9370       incoming_change == svn_wc_conflict_action_add &&
9371       (local_change == svn_wc_conflict_reason_obstructed ||
9372        local_change == svn_wc_conflict_reason_added))
9373     {
9374       const char *description;
9375       const char *wcroot_abspath;
9376
9377       SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9378                                  conflict->local_abspath, scratch_pool,
9379                                  scratch_pool));
9380
9381       if (operation == svn_wc_operation_merge)
9382         description =
9383           apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"),
9384             incoming_new_repos_relpath, incoming_new_pegrev,
9385             svn_dirent_local_style(
9386               svn_dirent_skip_ancestor(wcroot_abspath,
9387                                        conflict->local_abspath),
9388               scratch_pool));
9389       else
9390         description =
9391           apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"),
9392             svn_dirent_local_style(
9393               svn_dirent_skip_ancestor(wcroot_abspath,
9394                                        conflict->local_abspath),
9395               scratch_pool),
9396             incoming_new_repos_relpath, incoming_new_pegrev);
9397
9398       add_resolution_option(
9399         options, conflict,
9400         svn_client_conflict_option_incoming_added_file_text_merge,
9401         _("Merge the files"), description,
9402         operation == svn_wc_operation_merge
9403           ? resolve_merge_incoming_added_file_text_merge
9404           : resolve_merge_incoming_added_file_text_update);
9405     }
9406
9407   return SVN_NO_ERROR;
9408 }
9409
9410 /* Configure 'incoming added file replace and merge' resolution option for a
9411  * tree conflict. */
9412 static svn_error_t *
9413 configure_option_incoming_added_file_replace_and_merge(
9414   svn_client_conflict_t *conflict,
9415   svn_client_ctx_t *ctx,
9416   apr_array_header_t *options,
9417   apr_pool_t *scratch_pool)
9418 {
9419   svn_wc_operation_t operation;
9420   svn_wc_conflict_action_t incoming_change;
9421   svn_wc_conflict_reason_t local_change;
9422   svn_node_kind_t victim_node_kind;
9423   const char *incoming_new_repos_relpath;
9424   svn_revnum_t incoming_new_pegrev;
9425   svn_node_kind_t incoming_new_kind;
9426
9427   operation = svn_client_conflict_get_operation(conflict);
9428   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9429   local_change = svn_client_conflict_get_local_change(conflict);
9430   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9431   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9432             &incoming_new_repos_relpath, &incoming_new_pegrev,
9433             &incoming_new_kind, conflict, scratch_pool,
9434             scratch_pool));
9435
9436   if (operation == svn_wc_operation_merge &&
9437       victim_node_kind == svn_node_file &&
9438       incoming_new_kind == svn_node_file &&
9439       incoming_change == svn_wc_conflict_action_add &&
9440       local_change == svn_wc_conflict_reason_obstructed)
9441     {
9442       const char *wcroot_abspath;
9443       const char *description;
9444
9445       SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9446                                  conflict->local_abspath, scratch_pool,
9447                                  scratch_pool));
9448       description =
9449         apr_psprintf(scratch_pool,
9450           _("delete '%s', copy '^/%s@%ld' here, and merge the files"),
9451           svn_dirent_local_style(
9452             svn_dirent_skip_ancestor(wcroot_abspath,
9453                                      conflict->local_abspath),
9454             scratch_pool),
9455           incoming_new_repos_relpath, incoming_new_pegrev);
9456
9457       add_resolution_option(
9458         options, conflict,
9459         svn_client_conflict_option_incoming_added_file_replace_and_merge,
9460         _("Replace and merge"),
9461         description, resolve_merge_incoming_added_file_replace_and_merge);
9462     }
9463
9464   return SVN_NO_ERROR;
9465 }
9466
9467 /* Configure 'incoming added dir merge' resolution option for a tree
9468  * conflict. */
9469 static svn_error_t *
9470 configure_option_incoming_added_dir_merge(svn_client_conflict_t *conflict,
9471                                           svn_client_ctx_t *ctx,
9472                                           apr_array_header_t *options,
9473                                           apr_pool_t *scratch_pool)
9474 {
9475   svn_wc_operation_t operation;
9476   svn_wc_conflict_action_t incoming_change;
9477   svn_wc_conflict_reason_t local_change;
9478   svn_node_kind_t victim_node_kind;
9479   const char *incoming_new_repos_relpath;
9480   svn_revnum_t incoming_new_pegrev;
9481   svn_node_kind_t incoming_new_kind;
9482
9483   operation = svn_client_conflict_get_operation(conflict);
9484   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9485   local_change = svn_client_conflict_get_local_change(conflict);
9486   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9487   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9488             &incoming_new_repos_relpath, &incoming_new_pegrev,
9489             &incoming_new_kind, conflict, scratch_pool,
9490             scratch_pool));
9491
9492   if (victim_node_kind == svn_node_dir &&
9493       incoming_new_kind == svn_node_dir &&
9494       incoming_change == svn_wc_conflict_action_add &&
9495       (local_change == svn_wc_conflict_reason_added ||
9496        (operation == svn_wc_operation_merge &&
9497        local_change == svn_wc_conflict_reason_obstructed)))
9498
9499     {
9500       const char *description;
9501       const char *wcroot_abspath;
9502
9503       SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9504                                  conflict->local_abspath, scratch_pool,
9505                                  scratch_pool));
9506       if (operation == svn_wc_operation_merge)
9507         description =
9508           apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"),
9509             incoming_new_repos_relpath, incoming_new_pegrev,
9510             svn_dirent_local_style(
9511               svn_dirent_skip_ancestor(wcroot_abspath,
9512                                        conflict->local_abspath),
9513               scratch_pool));
9514       else
9515         description =
9516           apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"),
9517             svn_dirent_local_style(
9518               svn_dirent_skip_ancestor(wcroot_abspath,
9519                                        conflict->local_abspath),
9520               scratch_pool),
9521             incoming_new_repos_relpath, incoming_new_pegrev);
9522
9523       add_resolution_option(options, conflict,
9524                             svn_client_conflict_option_incoming_added_dir_merge,
9525                             _("Merge the directories"), description,
9526                             operation == svn_wc_operation_merge
9527                               ? resolve_merge_incoming_added_dir_merge
9528                               : resolve_update_incoming_added_dir_merge);
9529     }
9530
9531   return SVN_NO_ERROR;
9532 }
9533
9534 /* Configure 'incoming added dir replace' resolution option for a tree
9535  * conflict. */
9536 static svn_error_t *
9537 configure_option_incoming_added_dir_replace(svn_client_conflict_t *conflict,
9538                                             svn_client_ctx_t *ctx,
9539                                             apr_array_header_t *options,
9540                                             apr_pool_t *scratch_pool)
9541 {
9542   svn_wc_operation_t operation;
9543   svn_wc_conflict_action_t incoming_change;
9544   svn_wc_conflict_reason_t local_change;
9545   svn_node_kind_t victim_node_kind;
9546   const char *incoming_new_repos_relpath;
9547   svn_revnum_t incoming_new_pegrev;
9548   svn_node_kind_t incoming_new_kind;
9549
9550   operation = svn_client_conflict_get_operation(conflict);
9551   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9552   local_change = svn_client_conflict_get_local_change(conflict);
9553   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9554   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9555             &incoming_new_repos_relpath, &incoming_new_pegrev,
9556             &incoming_new_kind, conflict, scratch_pool,
9557             scratch_pool));
9558
9559   if (operation == svn_wc_operation_merge &&
9560       victim_node_kind == svn_node_dir &&
9561       incoming_new_kind == svn_node_dir &&
9562       incoming_change == svn_wc_conflict_action_add &&
9563       local_change == svn_wc_conflict_reason_obstructed)
9564     {
9565       const char *description;
9566       const char *wcroot_abspath;
9567
9568       SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9569                                  conflict->local_abspath, scratch_pool,
9570                                  scratch_pool));
9571       description =
9572         apr_psprintf(scratch_pool, _("delete '%s' and copy '^/%s@%ld' here"),
9573           svn_dirent_local_style(
9574             svn_dirent_skip_ancestor(wcroot_abspath,
9575                                      conflict->local_abspath),
9576             scratch_pool),
9577           incoming_new_repos_relpath, incoming_new_pegrev);
9578       add_resolution_option(
9579         options, conflict,
9580         svn_client_conflict_option_incoming_added_dir_replace,
9581         _("Delete my directory and replace it with incoming directory"),
9582         description, resolve_merge_incoming_added_dir_replace);
9583     }
9584
9585   return SVN_NO_ERROR;
9586 }
9587
9588 /* Configure 'incoming added dir replace and merge' resolution option
9589  * for a tree conflict. */
9590 static svn_error_t *
9591 configure_option_incoming_added_dir_replace_and_merge(
9592   svn_client_conflict_t *conflict,
9593   svn_client_ctx_t *ctx,
9594   apr_array_header_t *options,
9595   apr_pool_t *scratch_pool)
9596 {
9597   svn_wc_operation_t operation;
9598   svn_wc_conflict_action_t incoming_change;
9599   svn_wc_conflict_reason_t local_change;
9600   svn_node_kind_t victim_node_kind;
9601   const char *incoming_new_repos_relpath;
9602   svn_revnum_t incoming_new_pegrev;
9603   svn_node_kind_t incoming_new_kind;
9604
9605   operation = svn_client_conflict_get_operation(conflict);
9606   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9607   local_change = svn_client_conflict_get_local_change(conflict);
9608   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9609   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9610             &incoming_new_repos_relpath, &incoming_new_pegrev,
9611             &incoming_new_kind, conflict, scratch_pool,
9612             scratch_pool));
9613
9614   if (operation == svn_wc_operation_merge &&
9615       victim_node_kind == svn_node_dir &&
9616       incoming_new_kind == svn_node_dir &&
9617       incoming_change == svn_wc_conflict_action_add &&
9618       local_change == svn_wc_conflict_reason_obstructed)
9619     {
9620       const char *description;
9621       const char *wcroot_abspath;
9622
9623       SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9624                                  conflict->local_abspath, scratch_pool,
9625                                  scratch_pool));
9626       description =
9627         apr_psprintf(scratch_pool,
9628           _("delete '%s', copy '^/%s@%ld' here, and merge the directories"),
9629           svn_dirent_local_style(
9630             svn_dirent_skip_ancestor(wcroot_abspath,
9631                                      conflict->local_abspath),
9632             scratch_pool),
9633           incoming_new_repos_relpath, incoming_new_pegrev);
9634
9635       add_resolution_option(
9636         options, conflict,
9637         svn_client_conflict_option_incoming_added_dir_replace_and_merge,
9638         _("Replace and merge"),
9639         description, resolve_merge_incoming_added_dir_replace_and_merge);
9640     }
9641
9642   return SVN_NO_ERROR;
9643 }
9644
9645 /* Configure 'incoming delete ignore' resolution option for a tree conflict. */
9646 static svn_error_t *
9647 configure_option_incoming_delete_ignore(svn_client_conflict_t *conflict,
9648                                         svn_client_ctx_t *ctx,
9649                                         apr_array_header_t *options,
9650                                         apr_pool_t *scratch_pool)
9651 {
9652   svn_wc_operation_t operation;
9653   svn_wc_conflict_action_t incoming_change;
9654   svn_wc_conflict_reason_t local_change;
9655   const char *incoming_new_repos_relpath;
9656   svn_revnum_t incoming_new_pegrev;
9657
9658   operation = svn_client_conflict_get_operation(conflict);
9659   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9660   local_change = svn_client_conflict_get_local_change(conflict);
9661   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9662             &incoming_new_repos_relpath, &incoming_new_pegrev,
9663             NULL, conflict, scratch_pool,
9664             scratch_pool));
9665
9666   if (incoming_change == svn_wc_conflict_action_delete)
9667     {
9668       const char *description;
9669       struct conflict_tree_incoming_delete_details *incoming_details;
9670       svn_boolean_t is_incoming_move;
9671
9672       incoming_details = conflict->tree_conflict_incoming_details;
9673       is_incoming_move = (incoming_details != NULL &&
9674                           incoming_details->moves != NULL);
9675       if (local_change == svn_wc_conflict_reason_moved_away ||
9676           local_change == svn_wc_conflict_reason_edited)
9677         {
9678           /* An option which ignores the incoming deletion makes no sense
9679            * if we know there was a local move and/or an incoming move. */
9680           if (is_incoming_move)
9681             return SVN_NO_ERROR;
9682         }
9683       else if (local_change == svn_wc_conflict_reason_deleted)
9684         {
9685           /* If the local item was deleted and conflict details were fetched
9686            * and indicate that there was no move, then this is an actual
9687            * 'delete vs delete' situation. An option which ignores the incoming
9688            * deletion makes no sense in that case because there is no local
9689            * node to preserve. */
9690           if (!is_incoming_move)
9691             return SVN_NO_ERROR;
9692         }
9693       else if (local_change == svn_wc_conflict_reason_missing &&
9694                operation == svn_wc_operation_merge)
9695         {
9696           struct conflict_tree_local_missing_details *local_details;
9697           svn_boolean_t is_local_move; /* "local" to branch history */
9698
9699           local_details = conflict->tree_conflict_local_details;
9700           is_local_move = (local_details != NULL &&
9701                            local_details->moves != NULL);
9702
9703           if (!is_incoming_move && !is_local_move)
9704             return SVN_NO_ERROR;
9705         }
9706
9707       description =
9708         apr_psprintf(scratch_pool, _("ignore the deletion of '^/%s@%ld'"),
9709           incoming_new_repos_relpath, incoming_new_pegrev);
9710
9711       add_resolution_option(options, conflict,
9712                             svn_client_conflict_option_incoming_delete_ignore,
9713                             _("Ignore incoming deletion"), description,
9714                             resolve_incoming_delete_ignore);
9715     }
9716
9717   return SVN_NO_ERROR;
9718 }
9719
9720 /* Configure 'incoming delete accept' resolution option for a tree conflict. */
9721 static svn_error_t *
9722 configure_option_incoming_delete_accept(svn_client_conflict_t *conflict,
9723                                         svn_client_ctx_t *ctx,
9724                                         apr_array_header_t *options,
9725                                         apr_pool_t *scratch_pool)
9726 {
9727   svn_wc_conflict_action_t incoming_change;
9728   svn_wc_conflict_reason_t local_change;
9729   const char *incoming_new_repos_relpath;
9730   svn_revnum_t incoming_new_pegrev;
9731
9732   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9733   local_change = svn_client_conflict_get_local_change(conflict);
9734   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9735             &incoming_new_repos_relpath, &incoming_new_pegrev,
9736             NULL, conflict, scratch_pool,
9737             scratch_pool));
9738
9739   if (incoming_change == svn_wc_conflict_action_delete)
9740     {
9741       struct conflict_tree_incoming_delete_details *incoming_details;
9742       svn_boolean_t is_incoming_move;
9743
9744       incoming_details = conflict->tree_conflict_incoming_details;
9745       is_incoming_move = (incoming_details != NULL &&
9746                           incoming_details->moves != NULL);
9747       if (is_incoming_move &&
9748           (local_change == svn_wc_conflict_reason_edited ||
9749           local_change == svn_wc_conflict_reason_moved_away))
9750         {
9751           /* An option which accepts the incoming deletion makes no sense
9752            * if we know there was a local move and/or an incoming move. */
9753           return SVN_NO_ERROR;
9754         }
9755       else
9756         {
9757           const char *description;
9758           const char *wcroot_abspath;
9759           const char *local_abspath;
9760
9761           SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9762                                      conflict->local_abspath, scratch_pool,
9763                                      scratch_pool));
9764           local_abspath = svn_client_conflict_get_local_abspath(conflict);
9765           description =
9766             apr_psprintf(scratch_pool, _("accept the deletion of '%s'"),
9767               svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9768                                                               local_abspath),
9769                                      scratch_pool));
9770           add_resolution_option(
9771             options, conflict,
9772             svn_client_conflict_option_incoming_delete_accept,
9773             _("Accept incoming deletion"), description,
9774             resolve_incoming_delete_accept);
9775         }
9776     }
9777
9778   return SVN_NO_ERROR;
9779 }
9780
9781 static svn_error_t *
9782 describe_incoming_move_merge_conflict_option(
9783   const char **description,
9784   svn_client_conflict_t *conflict,
9785   svn_client_ctx_t *ctx,
9786   struct conflict_tree_incoming_delete_details *details,
9787   apr_pool_t *result_pool,
9788   apr_pool_t *scratch_pool)
9789 {
9790   apr_array_header_t *move_target_wc_abspaths;
9791   svn_wc_operation_t operation;
9792   const char *victim_abspath;
9793   const char *moved_to_abspath;
9794   const char *wcroot_abspath;
9795
9796   move_target_wc_abspaths =
9797     svn_hash_gets(details->wc_move_targets,
9798                   get_moved_to_repos_relpath(details, scratch_pool));
9799   moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
9800                                    details->wc_move_target_idx,
9801                                    const char *);
9802
9803   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
9804   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
9805                              victim_abspath, scratch_pool,
9806                              scratch_pool));
9807
9808   operation = svn_client_conflict_get_operation(conflict);
9809   if (operation == svn_wc_operation_merge)
9810     *description =
9811       apr_psprintf(
9812         result_pool, _("move '%s' to '%s' and merge"),
9813         svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9814                                                         victim_abspath),
9815                                scratch_pool),
9816         svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9817                                                         moved_to_abspath),
9818                                scratch_pool));
9819   else
9820     *description =
9821       apr_psprintf(
9822         result_pool, _("move and merge local changes from '%s' into '%s'"),
9823         svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9824                                                         victim_abspath),
9825                                scratch_pool),
9826         svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
9827                                                         moved_to_abspath),
9828                                scratch_pool));
9829
9830   return SVN_NO_ERROR;
9831 }
9832
9833 /* Configure 'incoming move file merge' resolution option for
9834  * a tree conflict. */
9835 static svn_error_t *
9836 configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
9837                                           svn_client_ctx_t *ctx,
9838                                           apr_array_header_t *options,
9839                                           apr_pool_t *scratch_pool)
9840 {
9841   svn_node_kind_t victim_node_kind;
9842   svn_wc_conflict_action_t incoming_change;
9843   const char *incoming_old_repos_relpath;
9844   svn_revnum_t incoming_old_pegrev;
9845   svn_node_kind_t incoming_old_kind;
9846   const char *incoming_new_repos_relpath;
9847   svn_revnum_t incoming_new_pegrev;
9848   svn_node_kind_t incoming_new_kind;
9849   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9850   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9851   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9852             &incoming_old_repos_relpath, &incoming_old_pegrev,
9853             &incoming_old_kind, conflict, scratch_pool,
9854             scratch_pool));
9855   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9856             &incoming_new_repos_relpath, &incoming_new_pegrev,
9857             &incoming_new_kind, conflict, scratch_pool,
9858             scratch_pool));
9859
9860   if (victim_node_kind == svn_node_file &&
9861       incoming_old_kind == svn_node_file &&
9862       incoming_new_kind == svn_node_none &&
9863       incoming_change == svn_wc_conflict_action_delete)
9864     {
9865       struct conflict_tree_incoming_delete_details *details;
9866       const char *description;
9867
9868       details = conflict->tree_conflict_incoming_details;
9869       if (details == NULL || details->moves == NULL)
9870         return SVN_NO_ERROR;
9871
9872       if (apr_hash_count(details->wc_move_targets) == 0)
9873         return SVN_NO_ERROR;
9874
9875       SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
9876                                                            conflict, ctx,
9877                                                            details,
9878                                                            scratch_pool,
9879                                                            scratch_pool));
9880       add_resolution_option(
9881         options, conflict,
9882         svn_client_conflict_option_incoming_move_file_text_merge,
9883         _("Move and merge"), description,
9884         resolve_incoming_move_file_text_merge);
9885     }
9886
9887   return SVN_NO_ERROR;
9888 }
9889
9890 /* Configure 'incoming move dir merge' resolution option for
9891  * a tree conflict. */
9892 static svn_error_t *
9893 configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
9894                                     svn_client_ctx_t *ctx,
9895                                     apr_array_header_t *options,
9896                                     apr_pool_t *scratch_pool)
9897 {
9898   svn_node_kind_t victim_node_kind;
9899   svn_wc_conflict_action_t incoming_change;
9900   svn_wc_conflict_reason_t local_change;
9901   const char *incoming_old_repos_relpath;
9902   svn_revnum_t incoming_old_pegrev;
9903   svn_node_kind_t incoming_old_kind;
9904   const char *incoming_new_repos_relpath;
9905   svn_revnum_t incoming_new_pegrev;
9906   svn_node_kind_t incoming_new_kind;
9907
9908   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9909   local_change = svn_client_conflict_get_local_change(conflict);
9910   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
9911   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
9912             &incoming_old_repos_relpath, &incoming_old_pegrev,
9913             &incoming_old_kind, conflict, scratch_pool,
9914             scratch_pool));
9915   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9916             &incoming_new_repos_relpath, &incoming_new_pegrev,
9917             &incoming_new_kind, conflict, scratch_pool,
9918             scratch_pool));
9919
9920   if (victim_node_kind == svn_node_dir &&
9921       incoming_old_kind == svn_node_dir &&
9922       incoming_new_kind == svn_node_none &&
9923       incoming_change == svn_wc_conflict_action_delete &&
9924       local_change == svn_wc_conflict_reason_edited)
9925     {
9926       struct conflict_tree_incoming_delete_details *details;
9927       const char *description;
9928
9929       details = conflict->tree_conflict_incoming_details;
9930       if (details == NULL || details->moves == NULL)
9931         return SVN_NO_ERROR;
9932
9933       if (apr_hash_count(details->wc_move_targets) == 0)
9934         return SVN_NO_ERROR;
9935
9936       SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
9937                                                            conflict, ctx,
9938                                                            details,
9939                                                            scratch_pool,
9940                                                            scratch_pool));
9941       add_resolution_option(options, conflict,
9942                             svn_client_conflict_option_incoming_move_dir_merge,
9943                             _("Move and merge"), description,
9944                             resolve_incoming_move_dir_merge);
9945     }
9946
9947   return SVN_NO_ERROR;
9948 }
9949
9950 /* Configure 'local move file merge' resolution option for
9951  * a tree conflict. */
9952 static svn_error_t *
9953 configure_option_local_move_file_merge(svn_client_conflict_t *conflict,
9954                                        svn_client_ctx_t *ctx,
9955                                        apr_array_header_t *options,
9956                                        apr_pool_t *scratch_pool)
9957 {
9958   svn_wc_operation_t operation;
9959   svn_wc_conflict_action_t incoming_change;
9960   svn_wc_conflict_reason_t local_change;
9961   const char *incoming_new_repos_relpath;
9962   svn_revnum_t incoming_new_pegrev;
9963
9964   operation = svn_client_conflict_get_operation(conflict);
9965   incoming_change = svn_client_conflict_get_incoming_change(conflict);
9966   local_change = svn_client_conflict_get_local_change(conflict);
9967   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
9968             &incoming_new_repos_relpath, &incoming_new_pegrev,
9969             NULL, conflict, scratch_pool,
9970             scratch_pool));
9971
9972   if (operation == svn_wc_operation_merge &&
9973       incoming_change == svn_wc_conflict_action_edit &&
9974       local_change == svn_wc_conflict_reason_missing)
9975     {
9976       struct conflict_tree_local_missing_details *details;
9977
9978       details = conflict->tree_conflict_local_details;
9979       if (details != NULL && details->moves != NULL)
9980         {
9981           apr_hash_t *wc_move_targets = apr_hash_make(scratch_pool);
9982           apr_pool_t *iterpool;
9983           int i;
9984
9985           iterpool = svn_pool_create(scratch_pool);
9986           for (i = 0; i < details->moves->nelts; i++)
9987             {
9988               struct repos_move_info *move;
9989
9990               svn_pool_clear(iterpool);
9991               move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
9992               SVN_ERR(follow_move_chains(wc_move_targets, move, ctx,
9993                                          conflict->local_abspath,
9994                                          svn_node_file,
9995                                          incoming_new_repos_relpath,
9996                                          incoming_new_pegrev,
9997                                          scratch_pool, iterpool));
9998             }
9999           svn_pool_destroy(iterpool);
10000
10001           if (apr_hash_count(wc_move_targets) > 0)
10002             {
10003               apr_array_header_t *move_target_repos_relpaths;
10004               const svn_sort__item_t *item;
10005               apr_array_header_t *moved_to_abspaths;
10006               const char *description;
10007               const char *wcroot_abspath;
10008
10009               /* Initialize to the first possible move target. Hopefully,
10010                * in most cases there will only be one candidate anyway. */
10011               move_target_repos_relpaths = svn_sort__hash(
10012                                              wc_move_targets,
10013                                              svn_sort_compare_items_as_paths,
10014                                              scratch_pool);
10015               item = &APR_ARRAY_IDX(move_target_repos_relpaths,
10016                                     0, svn_sort__item_t);
10017               moved_to_abspaths = item->value;
10018               details->moved_to_abspath =
10019                 apr_pstrdup(conflict->pool,
10020                             APR_ARRAY_IDX(moved_to_abspaths, 0, const char *));
10021
10022               SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
10023                                          conflict->local_abspath,
10024                                          scratch_pool, scratch_pool));
10025               description =
10026                 apr_psprintf(
10027                   scratch_pool, _("apply changes to move destination '%s'"),
10028                   svn_dirent_local_style(
10029                     svn_dirent_skip_ancestor(wcroot_abspath,
10030                                              details->moved_to_abspath),
10031                     scratch_pool));
10032
10033               add_resolution_option(
10034                 options, conflict,
10035                 svn_client_conflict_option_local_move_file_text_merge,
10036                 _("Apply to move destination"),
10037                 description, resolve_local_move_file_merge);
10038             }
10039           else
10040             details->moved_to_abspath = NULL;
10041         }
10042     }
10043
10044   return SVN_NO_ERROR;
10045 }
10046
10047 svn_error_t *
10048 svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
10049   apr_array_header_t **possible_moved_to_repos_relpaths,
10050   svn_client_conflict_option_t *option,
10051   apr_pool_t *result_pool,
10052   apr_pool_t *scratch_pool)
10053 {
10054   svn_client_conflict_t *conflict = option->conflict;
10055   struct conflict_tree_incoming_delete_details *details;
10056   const char *victim_abspath;
10057   apr_array_header_t *sorted_repos_relpaths;
10058   int i;
10059
10060   SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
10061                  svn_client_conflict_option_incoming_move_file_text_merge ||
10062                  svn_client_conflict_option_get_id(option) ==
10063                  svn_client_conflict_option_incoming_move_dir_merge);
10064
10065   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10066   details = conflict->tree_conflict_incoming_details;
10067   if (details == NULL || details->wc_move_targets == NULL)
10068     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10069                              _("Getting a list of possible move targets "
10070                                "requires details for tree conflict at '%s' "
10071                                "to be fetched from the repository first"),
10072                             svn_dirent_local_style(victim_abspath,
10073                                                    scratch_pool));
10074
10075   /* Return a copy of the repos replath candidate list. */
10076   sorted_repos_relpaths = svn_sort__hash(details->wc_move_targets,
10077                                          svn_sort_compare_items_as_paths,
10078                                          scratch_pool);
10079
10080   *possible_moved_to_repos_relpaths = apr_array_make(
10081                                         result_pool,
10082                                         sorted_repos_relpaths->nelts,
10083                                         sizeof (const char *));
10084   for (i = 0; i < sorted_repos_relpaths->nelts; i++)
10085     {
10086       svn_sort__item_t item;
10087       const char *repos_relpath;
10088
10089       item = APR_ARRAY_IDX(sorted_repos_relpaths, i, svn_sort__item_t);
10090       repos_relpath = item.key;
10091       APR_ARRAY_PUSH(*possible_moved_to_repos_relpaths, const char *) =
10092         apr_pstrdup(result_pool, repos_relpath);
10093     }
10094
10095   return SVN_NO_ERROR;
10096 }
10097
10098 svn_error_t *
10099 svn_client_conflict_option_set_moved_to_repos_relpath(
10100   svn_client_conflict_option_t *option,
10101   int preferred_move_target_idx,
10102   svn_client_ctx_t *ctx,
10103   apr_pool_t *scratch_pool)
10104 {
10105   svn_client_conflict_t *conflict = option->conflict;
10106   struct conflict_tree_incoming_delete_details *details;
10107   const char *victim_abspath;
10108   apr_array_header_t *move_target_repos_relpaths;
10109   svn_sort__item_t item;
10110   const char *move_target_repos_relpath;
10111   apr_hash_index_t *hi;
10112
10113   SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
10114                  svn_client_conflict_option_incoming_move_file_text_merge ||
10115                  svn_client_conflict_option_get_id(option) ==
10116                  svn_client_conflict_option_incoming_move_dir_merge);
10117
10118   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10119   details = conflict->tree_conflict_incoming_details;
10120   if (details == NULL || details->wc_move_targets == NULL)
10121     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10122                              _("Setting a move target requires details "
10123                                "for tree conflict at '%s' to be fetched "
10124                                "from the repository first"),
10125                             svn_dirent_local_style(victim_abspath,
10126                                                    scratch_pool));
10127
10128   if (preferred_move_target_idx < 0 ||
10129       preferred_move_target_idx >= apr_hash_count(details->wc_move_targets))
10130     return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
10131                              _("Index '%d' is out of bounds of the possible "
10132                                "move target list for '%s'"),
10133                             preferred_move_target_idx,
10134                             svn_dirent_local_style(victim_abspath,
10135                                                    scratch_pool));
10136
10137   /* Translate the index back into a hash table key. */
10138   move_target_repos_relpaths =
10139     svn_sort__hash(details->wc_move_targets,
10140                    svn_sort_compare_items_as_paths,
10141                    scratch_pool);
10142   item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx,
10143                        svn_sort__item_t);
10144   move_target_repos_relpath = item.key;
10145   /* Find our copy of the hash key and remember the user's preference. */
10146   for (hi = apr_hash_first(scratch_pool, details->wc_move_targets);
10147        hi != NULL;
10148        hi = apr_hash_next(hi))
10149     {
10150       const char *repos_relpath = apr_hash_this_key(hi);
10151
10152       if (strcmp(move_target_repos_relpath, repos_relpath) == 0)
10153         {
10154           details->move_target_repos_relpath = repos_relpath;
10155           /* Update option description. */
10156           SVN_ERR(describe_incoming_move_merge_conflict_option(
10157                     &option->description,
10158                     conflict, ctx,
10159                     details,
10160                     conflict->pool,
10161                     scratch_pool));
10162
10163           return SVN_NO_ERROR;
10164         }
10165     }
10166
10167   return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
10168                            _("Repository path '%s' not found in list of "
10169                              "possible move targets for '%s'"),
10170                            move_target_repos_relpath,
10171                            svn_dirent_local_style(victim_abspath,
10172                                                   scratch_pool));
10173 }
10174
10175 svn_error_t *
10176 svn_client_conflict_option_get_moved_to_abspath_candidates(
10177   apr_array_header_t **possible_moved_to_abspaths,
10178   svn_client_conflict_option_t *option,
10179   apr_pool_t *result_pool,
10180   apr_pool_t *scratch_pool)
10181 {
10182   svn_client_conflict_t *conflict = option->conflict;
10183   struct conflict_tree_incoming_delete_details *details;
10184   const char *victim_abspath;
10185   apr_array_header_t *move_target_wc_abspaths;
10186   int i;
10187
10188   SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
10189                  svn_client_conflict_option_incoming_move_file_text_merge ||
10190                  svn_client_conflict_option_get_id(option) ==
10191                  svn_client_conflict_option_incoming_move_dir_merge);
10192
10193   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10194   details = conflict->tree_conflict_incoming_details;
10195   if (details == NULL || details->wc_move_targets == NULL)
10196     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10197                              _("Getting a list of possible move targets "
10198                                "requires details for tree conflict at '%s' "
10199                                "to be fetched from the repository first"),
10200                             svn_dirent_local_style(victim_abspath,
10201                                                    scratch_pool));
10202
10203   move_target_wc_abspaths =
10204     svn_hash_gets(details->wc_move_targets,
10205                   get_moved_to_repos_relpath(details, scratch_pool));
10206
10207   /* Return a copy of the option's move target candidate list. */
10208   *possible_moved_to_abspaths =
10209     apr_array_make(result_pool, move_target_wc_abspaths->nelts,
10210                    sizeof (const char *));
10211   for (i = 0; i < move_target_wc_abspaths->nelts; i++)
10212     {
10213       const char *moved_to_abspath;
10214
10215       moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
10216                                        const char *);
10217       APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
10218         apr_pstrdup(result_pool, moved_to_abspath);
10219     }
10220
10221   return SVN_NO_ERROR;
10222 }
10223
10224 svn_error_t *
10225 svn_client_conflict_option_set_moved_to_abspath(
10226   svn_client_conflict_option_t *option,
10227   int preferred_move_target_idx,
10228   svn_client_ctx_t *ctx,
10229   apr_pool_t *scratch_pool)
10230 {
10231   svn_client_conflict_t *conflict = option->conflict;
10232   struct conflict_tree_incoming_delete_details *details;
10233   const char *victim_abspath;
10234   apr_array_header_t *move_target_wc_abspaths;
10235
10236   SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
10237                  svn_client_conflict_option_incoming_move_file_text_merge ||
10238                  svn_client_conflict_option_get_id(option) ==
10239                  svn_client_conflict_option_incoming_move_dir_merge);
10240
10241   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
10242   details = conflict->tree_conflict_incoming_details;
10243   if (details == NULL || details->wc_move_targets == NULL)
10244     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10245                              _("Setting a move target requires details "
10246                                "for tree conflict at '%s' to be fetched "
10247                                "from the repository first"),
10248                             svn_dirent_local_style(victim_abspath,
10249                                                    scratch_pool));
10250
10251   move_target_wc_abspaths =
10252     svn_hash_gets(details->wc_move_targets,
10253                   get_moved_to_repos_relpath(details, scratch_pool));
10254
10255   if (preferred_move_target_idx < 0 ||
10256       preferred_move_target_idx > move_target_wc_abspaths->nelts)
10257     return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
10258                              _("Index '%d' is out of bounds of the possible "
10259                                "move target list for '%s'"),
10260                             preferred_move_target_idx,
10261                             svn_dirent_local_style(victim_abspath,
10262                                                    scratch_pool));
10263
10264   /* Record the user's preference. */
10265   details->wc_move_target_idx = preferred_move_target_idx;
10266
10267   /* Update option description. */
10268   SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description,
10269                                                        conflict, ctx,
10270                                                        details,
10271                                                        conflict->pool,
10272                                                        scratch_pool));
10273   return SVN_NO_ERROR;
10274 }
10275
10276 svn_error_t *
10277 svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options,
10278                                                 svn_client_conflict_t *conflict,
10279                                                 svn_client_ctx_t *ctx,
10280                                                 apr_pool_t *result_pool,
10281                                                 apr_pool_t *scratch_pool)
10282 {
10283   SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
10284
10285   *options = apr_array_make(result_pool, 2,
10286                             sizeof(svn_client_conflict_option_t *));
10287
10288   /* Add postpone option. */
10289   add_resolution_option(*options, conflict,
10290                         svn_client_conflict_option_postpone,
10291                         _("Postpone"),
10292                         _("skip this conflict and leave it unresolved"),
10293                         resolve_postpone);
10294
10295   /* Add an option which marks the conflict resolved. */
10296   SVN_ERR(configure_option_accept_current_wc_state(conflict, *options));
10297
10298   /* Configure options which offer automatic resolution. */
10299   SVN_ERR(configure_option_update_move_destination(conflict, *options));
10300   SVN_ERR(configure_option_update_raise_moved_away_children(conflict,
10301                                                             *options));
10302   SVN_ERR(configure_option_incoming_add_ignore(conflict, ctx, *options,
10303                                                scratch_pool));
10304   SVN_ERR(configure_option_incoming_added_file_text_merge(conflict, ctx,
10305                                                           *options,
10306                                                           scratch_pool));
10307   SVN_ERR(configure_option_incoming_added_file_replace_and_merge(conflict,
10308                                                                  ctx,
10309                                                                  *options,
10310                                                                  scratch_pool));
10311   SVN_ERR(configure_option_incoming_added_dir_merge(conflict, ctx,
10312                                                     *options,
10313                                                     scratch_pool));
10314   SVN_ERR(configure_option_incoming_added_dir_replace(conflict, ctx,
10315                                                       *options,
10316                                                       scratch_pool));
10317   SVN_ERR(configure_option_incoming_added_dir_replace_and_merge(conflict,
10318                                                                 ctx,
10319                                                                 *options,
10320                                                                 scratch_pool));
10321   SVN_ERR(configure_option_incoming_delete_ignore(conflict, ctx, *options,
10322                                                   scratch_pool));
10323   SVN_ERR(configure_option_incoming_delete_accept(conflict, ctx, *options,
10324                                                   scratch_pool));
10325   SVN_ERR(configure_option_incoming_move_file_merge(conflict, ctx, *options,
10326                                                     scratch_pool));
10327   SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options,
10328                                               scratch_pool));
10329   SVN_ERR(configure_option_local_move_file_merge(conflict, ctx, *options,
10330                                                  scratch_pool));
10331
10332   return SVN_NO_ERROR;
10333 }
10334
10335 /* Swallow authz failures and return SVN_NO_ERROR in that case.
10336  * Otherwise, return ERR unchanged. */
10337 static svn_error_t *
10338 ignore_authz_failures(svn_error_t *err)
10339 {
10340   if (err && (   svn_error_find_cause(err, SVN_ERR_AUTHZ_UNREADABLE)
10341               || svn_error_find_cause(err, SVN_ERR_RA_NOT_AUTHORIZED)
10342               || svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)))
10343     {
10344       svn_error_clear(err);
10345       err = SVN_NO_ERROR;
10346     }
10347
10348   return err;
10349 }
10350
10351 svn_error_t *
10352 svn_client_conflict_tree_get_details(svn_client_conflict_t *conflict,
10353                                      svn_client_ctx_t *ctx,
10354                                      apr_pool_t *scratch_pool)
10355 {
10356   SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
10357
10358   if (ctx->notify_func2)
10359     {
10360       svn_wc_notify_t *notify;
10361
10362       notify = svn_wc_create_notify(
10363                  svn_client_conflict_get_local_abspath(conflict),
10364                  svn_wc_notify_begin_search_tree_conflict_details,
10365                  scratch_pool),
10366       ctx->notify_func2(ctx->notify_baton2, notify,
10367                                   scratch_pool);
10368     }
10369
10370   /* Collecting conflict details may fail due to insufficient access rights.
10371    * This is not a failure but simply restricts our future options. */
10372   if (conflict->tree_conflict_get_incoming_details_func)
10373     SVN_ERR(ignore_authz_failures(
10374       conflict->tree_conflict_get_incoming_details_func(conflict, ctx,
10375                                                         scratch_pool)));
10376
10377
10378   if (conflict->tree_conflict_get_local_details_func)
10379     SVN_ERR(ignore_authz_failures(
10380       conflict->tree_conflict_get_local_details_func(conflict, ctx,
10381                                                     scratch_pool)));
10382
10383   if (ctx->notify_func2)
10384     {
10385       svn_wc_notify_t *notify;
10386
10387       notify = svn_wc_create_notify(
10388                  svn_client_conflict_get_local_abspath(conflict),
10389                  svn_wc_notify_end_search_tree_conflict_details,
10390                  scratch_pool),
10391       ctx->notify_func2(ctx->notify_baton2, notify,
10392                                   scratch_pool);
10393     }
10394
10395   return SVN_NO_ERROR;
10396 }
10397
10398 svn_client_conflict_option_id_t
10399 svn_client_conflict_option_get_id(svn_client_conflict_option_t *option)
10400 {
10401   return option->id;
10402 }
10403
10404 const char *
10405 svn_client_conflict_option_get_label(svn_client_conflict_option_t *option,
10406                                      apr_pool_t *result_pool)
10407 {
10408   return apr_pstrdup(result_pool, option->label);
10409 }
10410
10411 const char *
10412 svn_client_conflict_option_get_description(svn_client_conflict_option_t *option,
10413                                            apr_pool_t *result_pool)
10414 {
10415   return apr_pstrdup(result_pool, option->description);
10416 }
10417
10418 svn_client_conflict_option_id_t
10419 svn_client_conflict_get_recommended_option_id(svn_client_conflict_t *conflict)
10420 {
10421   return conflict->recommended_option_id;
10422 }
10423                                     
10424 svn_error_t *
10425 svn_client_conflict_text_resolve(svn_client_conflict_t *conflict,
10426                                  svn_client_conflict_option_t *option,
10427                                  svn_client_ctx_t *ctx,
10428                                  apr_pool_t *scratch_pool)
10429 {
10430   SVN_ERR(assert_text_conflict(conflict, scratch_pool));
10431   SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
10432
10433   return SVN_NO_ERROR;
10434 }
10435
10436 svn_client_conflict_option_t *
10437 svn_client_conflict_option_find_by_id(apr_array_header_t *options,
10438                                       svn_client_conflict_option_id_t option_id)
10439 {
10440   int i;
10441
10442   for (i = 0; i < options->nelts; i++)
10443     {
10444       svn_client_conflict_option_t *this_option;
10445       svn_client_conflict_option_id_t this_option_id;
10446       
10447       this_option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
10448       this_option_id = svn_client_conflict_option_get_id(this_option);
10449
10450       if (this_option_id == option_id)
10451         return this_option;
10452     }
10453
10454   return NULL;
10455 }
10456
10457 svn_error_t *
10458 svn_client_conflict_text_resolve_by_id(
10459   svn_client_conflict_t *conflict,
10460   svn_client_conflict_option_id_t option_id,
10461   svn_client_ctx_t *ctx,
10462   apr_pool_t *scratch_pool)
10463 {
10464   apr_array_header_t *resolution_options;
10465   svn_client_conflict_option_t *option;
10466
10467   SVN_ERR(svn_client_conflict_text_get_resolution_options(
10468             &resolution_options, conflict, ctx,
10469             scratch_pool, scratch_pool));
10470   option = svn_client_conflict_option_find_by_id(resolution_options,
10471                                                  option_id);
10472   if (option == NULL)
10473     return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
10474                              NULL,
10475                              _("Inapplicable conflict resolution option "
10476                                "given for conflicted path '%s'"),
10477                              svn_dirent_local_style(conflict->local_abspath,
10478                                                     scratch_pool));
10479
10480   SVN_ERR(svn_client_conflict_text_resolve(conflict, option, ctx, scratch_pool));
10481
10482   return SVN_NO_ERROR;
10483 }
10484
10485 svn_client_conflict_option_id_t
10486 svn_client_conflict_text_get_resolution(svn_client_conflict_t *conflict)
10487 {
10488   return conflict->resolution_text;
10489 }
10490
10491 svn_error_t *
10492 svn_client_conflict_prop_resolve(svn_client_conflict_t *conflict,
10493                                  const char *propname,
10494                                  svn_client_conflict_option_t *option,
10495                                  svn_client_ctx_t *ctx,
10496                                  apr_pool_t *scratch_pool)
10497 {
10498   SVN_ERR(assert_prop_conflict(conflict, scratch_pool));
10499   option->type_data.prop.propname = propname;
10500   SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
10501
10502   return SVN_NO_ERROR;
10503 }
10504
10505 svn_error_t *
10506 svn_client_conflict_prop_resolve_by_id(
10507   svn_client_conflict_t *conflict,
10508   const char *propname,
10509   svn_client_conflict_option_id_t option_id,
10510   svn_client_ctx_t *ctx,
10511   apr_pool_t *scratch_pool)
10512 {
10513   apr_array_header_t *resolution_options;
10514   svn_client_conflict_option_t *option;
10515
10516   SVN_ERR(svn_client_conflict_prop_get_resolution_options(
10517             &resolution_options, conflict, ctx,
10518             scratch_pool, scratch_pool));
10519   option = svn_client_conflict_option_find_by_id(resolution_options,
10520                                                  option_id);
10521   if (option == NULL)
10522     return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
10523                              NULL,
10524                              _("Inapplicable conflict resolution option "
10525                                "given for conflicted path '%s'"),
10526                              svn_dirent_local_style(conflict->local_abspath,
10527                                                     scratch_pool));
10528   SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option, ctx,
10529                                            scratch_pool));
10530
10531   return SVN_NO_ERROR;
10532 }
10533
10534 svn_client_conflict_option_id_t
10535 svn_client_conflict_prop_get_resolution(svn_client_conflict_t *conflict,
10536                                         const char *propname)
10537 {
10538   svn_client_conflict_option_t *option;
10539
10540   option = svn_hash_gets(conflict->resolved_props, propname);
10541   if (option == NULL)
10542     return svn_client_conflict_option_unspecified;
10543
10544   return svn_client_conflict_option_get_id(option);
10545 }
10546
10547 svn_error_t *
10548 svn_client_conflict_tree_resolve(svn_client_conflict_t *conflict,
10549                                  svn_client_conflict_option_t *option,
10550                                  svn_client_ctx_t *ctx,
10551                                  apr_pool_t *scratch_pool)
10552 {
10553   SVN_ERR(assert_tree_conflict(conflict, scratch_pool));
10554   SVN_ERR(option->do_resolve_func(option, conflict, ctx, scratch_pool));
10555
10556   return SVN_NO_ERROR;
10557 }
10558
10559 svn_error_t *
10560 svn_client_conflict_tree_resolve_by_id(
10561   svn_client_conflict_t *conflict,
10562   svn_client_conflict_option_id_t option_id,
10563   svn_client_ctx_t *ctx,
10564   apr_pool_t *scratch_pool)
10565 {
10566   apr_array_header_t *resolution_options;
10567   svn_client_conflict_option_t *option;
10568
10569   SVN_ERR(svn_client_conflict_tree_get_resolution_options(
10570             &resolution_options, conflict, ctx,
10571             scratch_pool, scratch_pool));
10572   option = svn_client_conflict_option_find_by_id(resolution_options,
10573                                                  option_id);
10574   if (option == NULL)
10575     return svn_error_createf(SVN_ERR_CLIENT_CONFLICT_OPTION_NOT_APPLICABLE,
10576                              NULL,
10577                              _("Inapplicable conflict resolution option "
10578                                "given for conflicted path '%s'"),
10579                              svn_dirent_local_style(conflict->local_abspath,
10580                                                     scratch_pool));
10581   SVN_ERR(svn_client_conflict_tree_resolve(conflict, option, ctx, scratch_pool));
10582
10583   return SVN_NO_ERROR;
10584 }
10585
10586 svn_client_conflict_option_id_t
10587 svn_client_conflict_tree_get_resolution(svn_client_conflict_t *conflict)
10588 {
10589   return conflict->resolution_tree;
10590 }
10591
10592 /* Return the legacy conflict descriptor which is wrapped by CONFLICT. */
10593 static const svn_wc_conflict_description2_t *
10594 get_conflict_desc2_t(svn_client_conflict_t *conflict)
10595 {
10596   if (conflict->legacy_text_conflict)
10597     return conflict->legacy_text_conflict;
10598
10599   if (conflict->legacy_tree_conflict)
10600     return conflict->legacy_tree_conflict;
10601
10602   if (conflict->prop_conflicts && conflict->legacy_prop_conflict_propname)
10603     return svn_hash_gets(conflict->prop_conflicts,
10604                          conflict->legacy_prop_conflict_propname);
10605
10606   return NULL;
10607 }
10608
10609 svn_error_t *
10610 svn_client_conflict_get_conflicted(svn_boolean_t *text_conflicted,
10611                                    apr_array_header_t **props_conflicted,
10612                                    svn_boolean_t *tree_conflicted,
10613                                    svn_client_conflict_t *conflict,
10614                                    apr_pool_t *result_pool,
10615                                    apr_pool_t *scratch_pool)
10616 {
10617   if (text_conflicted)
10618     *text_conflicted = (conflict->legacy_text_conflict != NULL);
10619
10620   if (props_conflicted)
10621     {
10622       if (conflict->prop_conflicts)
10623         SVN_ERR(svn_hash_keys(props_conflicted, conflict->prop_conflicts,
10624                               result_pool));
10625       else
10626         *props_conflicted = apr_array_make(result_pool, 0,
10627                                            sizeof(const char*));
10628     }
10629
10630   if (tree_conflicted)
10631     *tree_conflicted = (conflict->legacy_tree_conflict != NULL);
10632
10633   return SVN_NO_ERROR;
10634 }
10635
10636 const char *
10637 svn_client_conflict_get_local_abspath(svn_client_conflict_t *conflict)
10638 {
10639   return conflict->local_abspath;
10640 }
10641
10642 svn_wc_operation_t
10643 svn_client_conflict_get_operation(svn_client_conflict_t *conflict)
10644 {
10645   return get_conflict_desc2_t(conflict)->operation;
10646 }
10647
10648 svn_wc_conflict_action_t
10649 svn_client_conflict_get_incoming_change(svn_client_conflict_t *conflict)
10650 {
10651   return get_conflict_desc2_t(conflict)->action;
10652 }
10653
10654 svn_wc_conflict_reason_t
10655 svn_client_conflict_get_local_change(svn_client_conflict_t *conflict)
10656 {
10657   return get_conflict_desc2_t(conflict)->reason;
10658 }
10659
10660 svn_error_t *
10661 svn_client_conflict_get_repos_info(const char **repos_root_url,
10662                                    const char **repos_uuid,
10663                                    svn_client_conflict_t *conflict,
10664                                    apr_pool_t *result_pool,
10665                                    apr_pool_t *scratch_pool)
10666 {
10667   if (repos_root_url)
10668     {
10669       if (get_conflict_desc2_t(conflict)->src_left_version)
10670         *repos_root_url =
10671           get_conflict_desc2_t(conflict)->src_left_version->repos_url;
10672       else if (get_conflict_desc2_t(conflict)->src_right_version)
10673         *repos_root_url =
10674           get_conflict_desc2_t(conflict)->src_right_version->repos_url;
10675       else
10676         *repos_root_url = NULL;
10677     }
10678
10679   if (repos_uuid)
10680     {
10681       if (get_conflict_desc2_t(conflict)->src_left_version)
10682         *repos_uuid =
10683           get_conflict_desc2_t(conflict)->src_left_version->repos_uuid;
10684       else if (get_conflict_desc2_t(conflict)->src_right_version)
10685         *repos_uuid =
10686           get_conflict_desc2_t(conflict)->src_right_version->repos_uuid;
10687       else
10688         *repos_uuid = NULL;
10689     }
10690
10691   return SVN_NO_ERROR;
10692 }
10693
10694 svn_error_t *
10695 svn_client_conflict_get_incoming_old_repos_location(
10696   const char **incoming_old_repos_relpath,
10697   svn_revnum_t *incoming_old_pegrev,
10698   svn_node_kind_t *incoming_old_node_kind,
10699   svn_client_conflict_t *conflict,
10700   apr_pool_t *result_pool,
10701   apr_pool_t *scratch_pool)
10702 {
10703   if (incoming_old_repos_relpath)
10704     {
10705       if (get_conflict_desc2_t(conflict)->src_left_version)
10706         *incoming_old_repos_relpath =
10707           get_conflict_desc2_t(conflict)->src_left_version->path_in_repos;
10708       else
10709         *incoming_old_repos_relpath = NULL;
10710     }
10711
10712   if (incoming_old_pegrev)
10713     {
10714       if (get_conflict_desc2_t(conflict)->src_left_version)
10715         *incoming_old_pegrev =
10716           get_conflict_desc2_t(conflict)->src_left_version->peg_rev;
10717       else
10718         *incoming_old_pegrev = SVN_INVALID_REVNUM;
10719     }
10720
10721   if (incoming_old_node_kind)
10722     {
10723       if (get_conflict_desc2_t(conflict)->src_left_version)
10724         *incoming_old_node_kind =
10725           get_conflict_desc2_t(conflict)->src_left_version->node_kind;
10726       else
10727         *incoming_old_node_kind = svn_node_none;
10728     }
10729
10730   return SVN_NO_ERROR;
10731 }
10732
10733 svn_error_t *
10734 svn_client_conflict_get_incoming_new_repos_location(
10735   const char **incoming_new_repos_relpath,
10736   svn_revnum_t *incoming_new_pegrev,
10737   svn_node_kind_t *incoming_new_node_kind,
10738   svn_client_conflict_t *conflict,
10739   apr_pool_t *result_pool,
10740   apr_pool_t *scratch_pool)
10741 {
10742   if (incoming_new_repos_relpath)
10743     {
10744       if (get_conflict_desc2_t(conflict)->src_right_version)
10745         *incoming_new_repos_relpath =
10746           get_conflict_desc2_t(conflict)->src_right_version->path_in_repos;
10747       else
10748         *incoming_new_repos_relpath = NULL;
10749     }
10750
10751   if (incoming_new_pegrev)
10752     {
10753       if (get_conflict_desc2_t(conflict)->src_right_version)
10754         *incoming_new_pegrev =
10755           get_conflict_desc2_t(conflict)->src_right_version->peg_rev;
10756       else
10757         *incoming_new_pegrev = SVN_INVALID_REVNUM;
10758     }
10759
10760   if (incoming_new_node_kind)
10761     {
10762       if (get_conflict_desc2_t(conflict)->src_right_version)
10763         *incoming_new_node_kind =
10764           get_conflict_desc2_t(conflict)->src_right_version->node_kind;
10765       else
10766         *incoming_new_node_kind = svn_node_none;
10767     }
10768
10769   return SVN_NO_ERROR;
10770 }
10771
10772 svn_node_kind_t
10773 svn_client_conflict_tree_get_victim_node_kind(svn_client_conflict_t *conflict)
10774 {
10775   SVN_ERR_ASSERT_NO_RETURN(assert_tree_conflict(conflict, conflict->pool)
10776                            == SVN_NO_ERROR);
10777
10778   return get_conflict_desc2_t(conflict)->node_kind;
10779 }
10780
10781 svn_error_t *
10782 svn_client_conflict_prop_get_propvals(const svn_string_t **base_propval,
10783                                       const svn_string_t **working_propval,
10784                                       const svn_string_t **incoming_old_propval,
10785                                       const svn_string_t **incoming_new_propval,
10786                                       svn_client_conflict_t *conflict,
10787                                       const char *propname,
10788                                       apr_pool_t *result_pool)
10789 {
10790   const svn_wc_conflict_description2_t *desc;
10791
10792   SVN_ERR(assert_prop_conflict(conflict, conflict->pool));
10793
10794   desc = svn_hash_gets(conflict->prop_conflicts, propname);
10795   if (desc == NULL)
10796     return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
10797                              _("Property '%s' is not in conflict."), propname);
10798
10799   if (base_propval)
10800     *base_propval =
10801       svn_string_dup(desc->prop_value_base, result_pool);
10802
10803   if (working_propval)
10804     *working_propval =
10805       svn_string_dup(desc->prop_value_working, result_pool);
10806
10807   if (incoming_old_propval)
10808     *incoming_old_propval =
10809       svn_string_dup(desc->prop_value_incoming_old, result_pool);
10810
10811   if (incoming_new_propval)
10812     *incoming_new_propval =
10813       svn_string_dup(desc->prop_value_incoming_new, result_pool);
10814
10815   return SVN_NO_ERROR;
10816 }
10817
10818 const char *
10819 svn_client_conflict_prop_get_reject_abspath(svn_client_conflict_t *conflict)
10820 {
10821   SVN_ERR_ASSERT_NO_RETURN(assert_prop_conflict(conflict, conflict->pool)
10822                            == SVN_NO_ERROR);
10823
10824   /* svn_wc_conflict_description2_t stores this path in 'their_abspath' */
10825   return get_conflict_desc2_t(conflict)->their_abspath;
10826 }
10827
10828 const char *
10829 svn_client_conflict_text_get_mime_type(svn_client_conflict_t *conflict)
10830 {
10831   SVN_ERR_ASSERT_NO_RETURN(assert_text_conflict(conflict, conflict->pool)
10832                            == SVN_NO_ERROR);
10833
10834   return get_conflict_desc2_t(conflict)->mime_type;
10835 }
10836
10837 svn_error_t *
10838 svn_client_conflict_text_get_contents(const char **base_abspath,
10839                                       const char **working_abspath,
10840                                       const char **incoming_old_abspath,
10841                                       const char **incoming_new_abspath,
10842                                       svn_client_conflict_t *conflict,
10843                                       apr_pool_t *result_pool,
10844                                       apr_pool_t *scratch_pool)
10845 {
10846   SVN_ERR(assert_text_conflict(conflict, scratch_pool));
10847
10848   if (base_abspath)
10849     {
10850       if (svn_client_conflict_get_operation(conflict) ==
10851           svn_wc_operation_merge)
10852         *base_abspath = NULL; /* ### WC base contents not available yet */
10853       else /* update/switch */
10854         *base_abspath = get_conflict_desc2_t(conflict)->base_abspath;
10855     }
10856
10857   if (working_abspath)
10858     *working_abspath = get_conflict_desc2_t(conflict)->my_abspath;
10859
10860   if (incoming_old_abspath)
10861     *incoming_old_abspath = get_conflict_desc2_t(conflict)->base_abspath;
10862
10863   if (incoming_new_abspath)
10864     *incoming_new_abspath = get_conflict_desc2_t(conflict)->their_abspath;
10865
10866   return SVN_NO_ERROR;
10867 }
10868
10869 /* Set up type-specific data for a new conflict object. */
10870 static svn_error_t *
10871 conflict_type_specific_setup(svn_client_conflict_t *conflict,
10872                              apr_pool_t *scratch_pool)
10873 {
10874   svn_boolean_t tree_conflicted;
10875   svn_wc_conflict_action_t incoming_change;
10876   svn_wc_conflict_reason_t local_change;
10877
10878   /* For now, we only deal with tree conflicts here. */
10879   SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
10880                                              conflict, scratch_pool,
10881                                              scratch_pool));
10882   if (!tree_conflicted)
10883     return SVN_NO_ERROR;
10884
10885   /* Set a default description function. */
10886   conflict->tree_conflict_get_incoming_description_func =
10887     conflict_tree_get_incoming_description_generic;
10888   conflict->tree_conflict_get_local_description_func =
10889     conflict_tree_get_local_description_generic;
10890
10891   incoming_change = svn_client_conflict_get_incoming_change(conflict);
10892   local_change = svn_client_conflict_get_local_change(conflict);
10893
10894   /* Set type-specific description and details functions. */
10895   if (incoming_change == svn_wc_conflict_action_delete ||
10896       incoming_change == svn_wc_conflict_action_replace)
10897     {
10898       conflict->tree_conflict_get_incoming_description_func =
10899         conflict_tree_get_description_incoming_delete;
10900       conflict->tree_conflict_get_incoming_details_func =
10901         conflict_tree_get_details_incoming_delete;
10902     }
10903   else if (incoming_change == svn_wc_conflict_action_add)
10904     {
10905       conflict->tree_conflict_get_incoming_description_func =
10906         conflict_tree_get_description_incoming_add;
10907       conflict->tree_conflict_get_incoming_details_func =
10908         conflict_tree_get_details_incoming_add;
10909     }
10910   else if (incoming_change == svn_wc_conflict_action_edit)
10911     {
10912       conflict->tree_conflict_get_incoming_description_func =
10913         conflict_tree_get_description_incoming_edit;
10914       conflict->tree_conflict_get_incoming_details_func =
10915         conflict_tree_get_details_incoming_edit;
10916     }
10917
10918   if (local_change == svn_wc_conflict_reason_missing)
10919     {
10920       conflict->tree_conflict_get_local_description_func =
10921         conflict_tree_get_description_local_missing;
10922       conflict->tree_conflict_get_local_details_func =
10923         conflict_tree_get_details_local_missing;
10924     }
10925
10926   return SVN_NO_ERROR;
10927 }
10928
10929 svn_error_t *
10930 svn_client_conflict_get(svn_client_conflict_t **conflict,
10931                         const char *local_abspath,
10932                         svn_client_ctx_t *ctx,
10933                         apr_pool_t *result_pool,
10934                         apr_pool_t *scratch_pool)
10935 {
10936   const apr_array_header_t *descs;
10937   int i;
10938
10939   *conflict = apr_pcalloc(result_pool, sizeof(**conflict));
10940
10941   (*conflict)->local_abspath = apr_pstrdup(result_pool, local_abspath);
10942   (*conflict)->resolution_text = svn_client_conflict_option_unspecified;
10943   (*conflict)->resolution_tree = svn_client_conflict_option_unspecified;
10944   (*conflict)->resolved_props = apr_hash_make(result_pool);
10945   (*conflict)->recommended_option_id = svn_client_conflict_option_unspecified;
10946   (*conflict)->pool = result_pool;
10947
10948   /* Add all legacy conflict descriptors we can find. Eventually, this code
10949    * path should stop relying on svn_wc_conflict_description2_t entirely. */
10950   SVN_ERR(svn_wc__read_conflict_descriptions2_t(&descs, ctx->wc_ctx,
10951                                                 local_abspath,
10952                                                 result_pool, scratch_pool));
10953   for (i = 0; i < descs->nelts; i++)
10954     {
10955       const svn_wc_conflict_description2_t *desc;
10956
10957       desc = APR_ARRAY_IDX(descs, i, const svn_wc_conflict_description2_t *);
10958       add_legacy_desc_to_conflict(desc, *conflict, result_pool);
10959     }
10960
10961   SVN_ERR(conflict_type_specific_setup(*conflict, scratch_pool));
10962
10963   return SVN_NO_ERROR;
10964 }
10965
10966 /* Baton for conflict_status_walker */
10967 struct conflict_status_walker_baton
10968 {
10969   svn_client_conflict_walk_func_t conflict_walk_func;
10970   void *conflict_walk_func_baton;
10971   svn_client_ctx_t *ctx;
10972   svn_wc_notify_func2_t notify_func;
10973   void *notify_baton;
10974   svn_boolean_t resolved_a_tree_conflict;
10975   apr_hash_t *unresolved_tree_conflicts;
10976 };
10977
10978 /* Implements svn_wc_notify_func2_t to collect new conflicts caused by
10979    resolving a tree conflict. */
10980 static void
10981 tree_conflict_collector(void *baton,
10982                         const svn_wc_notify_t *notify,
10983                         apr_pool_t *pool)
10984 {
10985   struct conflict_status_walker_baton *cswb = baton;
10986
10987   if (cswb->notify_func)
10988     cswb->notify_func(cswb->notify_baton, notify, pool);
10989
10990   if (cswb->unresolved_tree_conflicts
10991       && (notify->action == svn_wc_notify_tree_conflict
10992           || notify->prop_state == svn_wc_notify_state_conflicted
10993           || notify->content_state == svn_wc_notify_state_conflicted))
10994     {
10995       if (!svn_hash_gets(cswb->unresolved_tree_conflicts, notify->path))
10996         {
10997           const char *tc_abspath;
10998           apr_pool_t *hash_pool;
10999  
11000           hash_pool = apr_hash_pool_get(cswb->unresolved_tree_conflicts);
11001           tc_abspath = apr_pstrdup(hash_pool, notify->path);
11002           svn_hash_sets(cswb->unresolved_tree_conflicts, tc_abspath, "");
11003         }
11004     }
11005 }
11006
11007 /* 
11008  * Record a tree conflict resolution failure due to error condition ERR
11009  * in the RESOLVE_LATER hash table. If the hash table is not available
11010  * (meaning the caller does not wish to retry resolution later), or if
11011  * the error condition does not indicate circumstances where another
11012  * existing tree conflict is blocking the resolution attempt, then
11013  * return the error ERR itself.
11014  */
11015 static svn_error_t *
11016 handle_tree_conflict_resolution_failure(const char *local_abspath,
11017                                         svn_error_t *err,
11018                                         apr_hash_t *unresolved_tree_conflicts)
11019 {
11020   const char *tc_abspath;
11021
11022   if (!unresolved_tree_conflicts
11023       || (err->apr_err != SVN_ERR_WC_OBSTRUCTED_UPDATE
11024           && err->apr_err != SVN_ERR_WC_FOUND_CONFLICT))
11025     return svn_error_trace(err); /* Give up. Do not retry resolution later. */
11026
11027   svn_error_clear(err);
11028   tc_abspath = apr_pstrdup(apr_hash_pool_get(unresolved_tree_conflicts),
11029                            local_abspath);
11030
11031   svn_hash_sets(unresolved_tree_conflicts, tc_abspath, "");
11032
11033   return SVN_NO_ERROR; /* Caller may retry after resolving other conflicts. */
11034 }
11035
11036 /* Implements svn_wc_status4_t to walk all conflicts to resolve.
11037  */
11038 static svn_error_t *
11039 conflict_status_walker(void *baton,
11040                        const char *local_abspath,
11041                        const svn_wc_status3_t *status,
11042                        apr_pool_t *scratch_pool)
11043 {
11044   struct conflict_status_walker_baton *cswb = baton;
11045   svn_client_conflict_t *conflict;
11046   svn_error_t *err;
11047   svn_boolean_t tree_conflicted;
11048
11049   if (!status->conflicted)
11050     return SVN_NO_ERROR;
11051
11052   SVN_ERR(svn_client_conflict_get(&conflict, local_abspath, cswb->ctx,
11053                                   scratch_pool, scratch_pool));
11054   SVN_ERR(svn_client_conflict_get_conflicted(NULL, NULL, &tree_conflicted,
11055                                              conflict, scratch_pool,
11056                                              scratch_pool));
11057   err = cswb->conflict_walk_func(cswb->conflict_walk_func_baton,
11058                                  conflict, scratch_pool);
11059   if (err)
11060     {
11061       if (tree_conflicted)
11062         SVN_ERR(handle_tree_conflict_resolution_failure(
11063                   local_abspath, err, cswb->unresolved_tree_conflicts));
11064
11065       else
11066         return svn_error_trace(err);
11067     }
11068
11069   if (tree_conflicted)
11070     {
11071       svn_client_conflict_option_id_t resolution;
11072
11073       resolution = svn_client_conflict_tree_get_resolution(conflict);
11074       if (resolution != svn_client_conflict_option_unspecified &&
11075           resolution != svn_client_conflict_option_postpone)
11076         cswb->resolved_a_tree_conflict = TRUE;
11077     }
11078
11079   return SVN_NO_ERROR;
11080 }
11081
11082 svn_error_t *
11083 svn_client_conflict_walk(const char *local_abspath,
11084                          svn_depth_t depth,
11085                          svn_client_conflict_walk_func_t conflict_walk_func,
11086                          void *conflict_walk_func_baton,
11087                          svn_client_ctx_t *ctx,
11088                          apr_pool_t *scratch_pool)
11089 {
11090   struct conflict_status_walker_baton cswb;
11091   apr_pool_t *iterpool = NULL;
11092   svn_error_t *err = SVN_NO_ERROR;
11093
11094   if (depth == svn_depth_unknown)
11095     depth = svn_depth_infinity;
11096
11097   cswb.conflict_walk_func = conflict_walk_func;
11098   cswb.conflict_walk_func_baton = conflict_walk_func_baton;
11099   cswb.ctx = ctx;
11100   cswb.resolved_a_tree_conflict = FALSE;
11101   cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool);
11102
11103   if (ctx->notify_func2)
11104     ctx->notify_func2(ctx->notify_baton2,
11105                       svn_wc_create_notify(
11106                         local_abspath,
11107                         svn_wc_notify_conflict_resolver_starting,
11108                         scratch_pool),
11109                       scratch_pool);
11110
11111   /* Swap in our notify_func wrapper. We must revert this before returning! */
11112   cswb.notify_func = ctx->notify_func2;
11113   cswb.notify_baton = ctx->notify_baton2;
11114   ctx->notify_func2 = tree_conflict_collector;
11115   ctx->notify_baton2 = &cswb;
11116
11117   err = svn_wc_walk_status(ctx->wc_ctx,
11118                            local_abspath,
11119                            depth,
11120                            FALSE /* get_all */,
11121                            FALSE /* no_ignore */,
11122                            TRUE /* ignore_text_mods */,
11123                            NULL /* ignore_patterns */,
11124                            conflict_status_walker, &cswb,
11125                            ctx->cancel_func, ctx->cancel_baton,
11126                            scratch_pool);
11127
11128   /* If we got new tree conflicts (or delayed conflicts) during the initial
11129      walk, we now walk them one by one as closure. */
11130   while (!err && cswb.unresolved_tree_conflicts &&
11131          apr_hash_count(cswb.unresolved_tree_conflicts))
11132     {
11133       apr_hash_index_t *hi;
11134       svn_wc_status3_t *status = NULL;
11135       const char *tc_abspath = NULL;
11136
11137       if (iterpool)
11138         svn_pool_clear(iterpool);
11139       else
11140         iterpool = svn_pool_create(scratch_pool);
11141
11142       hi = apr_hash_first(scratch_pool, cswb.unresolved_tree_conflicts);
11143       cswb.unresolved_tree_conflicts = apr_hash_make(scratch_pool);
11144       cswb.resolved_a_tree_conflict = FALSE;
11145
11146       for (; hi && !err; hi = apr_hash_next(hi))
11147         {
11148           svn_pool_clear(iterpool);
11149
11150           tc_abspath = apr_hash_this_key(hi);
11151
11152           if (ctx->cancel_func)
11153             {
11154               err = ctx->cancel_func(ctx->cancel_baton);
11155               if (err)
11156                 break;
11157             }
11158
11159           err = svn_error_trace(svn_wc_status3(&status, ctx->wc_ctx,
11160                                                tc_abspath,
11161                                                iterpool, iterpool));
11162           if (err)
11163             break;
11164
11165           err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
11166                                                        status, scratch_pool));
11167           if (err)
11168             break;
11169         }
11170   
11171       if (!err && !cswb.resolved_a_tree_conflict && tc_abspath &&
11172           apr_hash_count(cswb.unresolved_tree_conflicts))
11173         {
11174           /* None of the remaining conflicts got resolved, without any error.
11175            * Disable the 'unresolved_tree_conflicts' cache and try again. */
11176           cswb.unresolved_tree_conflicts = NULL;
11177
11178           /* Run the most recent resolve operation again.
11179            * We still have status and tc_abspath for that one.
11180            * This should uncover the error which prevents resolution. */
11181           err = svn_error_trace(conflict_status_walker(&cswb, tc_abspath,
11182                                                        status, scratch_pool));
11183           SVN_ERR_ASSERT(err != NULL);
11184
11185           err = svn_error_createf(
11186                     SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
11187                     _("Unable to resolve pending conflict on '%s'"),
11188                     svn_dirent_local_style(tc_abspath, scratch_pool));
11189           break;
11190         }
11191     }
11192
11193   if (iterpool)
11194     svn_pool_destroy(iterpool);
11195
11196   ctx->notify_func2 = cswb.notify_func;
11197   ctx->notify_baton2 = cswb.notify_baton;
11198
11199   if (!err && ctx->notify_func2)
11200     ctx->notify_func2(ctx->notify_baton2,
11201                       svn_wc_create_notify(local_abspath,
11202                                           svn_wc_notify_conflict_resolver_done,
11203                                           scratch_pool),
11204                       scratch_pool);
11205
11206   return svn_error_trace(err);
11207 }