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