1/*
2 * tree_conflicts.c: Storage of tree conflict descriptions in the WC.
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#include "svn_dirent_uri.h"
25#include "svn_path.h"
26#include "svn_types.h"
27#include "svn_pools.h"
28
29#include "tree_conflicts.h"
30#include "conflicts.h"
31#include "wc.h"
32
33#include "private/svn_skel.h"
34#include "private/svn_wc_private.h"
35#include "private/svn_token.h"
36
37#include "svn_private_config.h"
38
39/* ### this should move to a more general location...  */
40/* A map for svn_node_kind_t values. */
41/* FIXME: this mapping defines a different representation of
42          svn_node_unknown than the one defined in token-map.h */
43static const svn_token_map_t node_kind_map[] =
44{
45  { "none", svn_node_none },
46  { "file", svn_node_file },
47  { "dir",  svn_node_dir },
48  { "",     svn_node_unknown },
49  { NULL }
50};
51
52/* A map for svn_wc_operation_t values. */
53const svn_token_map_t svn_wc__operation_map[] =
54{
55  { "none",   svn_wc_operation_none },
56  { "update", svn_wc_operation_update },
57  { "switch", svn_wc_operation_switch },
58  { "merge",  svn_wc_operation_merge },
59  { NULL }
60};
61
62/* A map for svn_wc_conflict_action_t values. */
63const svn_token_map_t svn_wc__conflict_action_map[] =
64{
65  { "edited",   svn_wc_conflict_action_edit },
66  { "deleted",  svn_wc_conflict_action_delete },
67  { "added",    svn_wc_conflict_action_add },
68  { "replaced", svn_wc_conflict_action_replace },
69  { NULL }
70};
71
72/* A map for svn_wc_conflict_reason_t values. */
73const svn_token_map_t svn_wc__conflict_reason_map[] =
74{
75  { "edited",      svn_wc_conflict_reason_edited },
76  { "deleted",     svn_wc_conflict_reason_deleted },
77  { "missing",     svn_wc_conflict_reason_missing },
78  { "obstructed",  svn_wc_conflict_reason_obstructed },
79  { "added",       svn_wc_conflict_reason_added },
80  { "replaced",    svn_wc_conflict_reason_replaced },
81  { "unversioned", svn_wc_conflict_reason_unversioned },
82  { "moved-away", svn_wc_conflict_reason_moved_away },
83  { "moved-here", svn_wc_conflict_reason_moved_here },
84  { NULL }
85};
86
87
88/* */
89static svn_boolean_t
90is_valid_version_info_skel(const svn_skel_t *skel)
91{
92  return (svn_skel__list_length(skel) == 5
93          && svn_skel__matches_atom(skel->children, "version")
94          && skel->children->next->is_atom
95          && skel->children->next->next->is_atom
96          && skel->children->next->next->next->is_atom
97          && skel->children->next->next->next->next->is_atom);
98}
99
100
101/* */
102static svn_boolean_t
103is_valid_conflict_skel(const svn_skel_t *skel)
104{
105  int i;
106
107  if (svn_skel__list_length(skel) != 8
108      || !svn_skel__matches_atom(skel->children, "conflict"))
109    return FALSE;
110
111  /* 5 atoms ... */
112  skel = skel->children->next;
113  for (i = 5; i--; skel = skel->next)
114    if (!skel->is_atom)
115      return FALSE;
116
117  /* ... and 2 version info skels. */
118  return (is_valid_version_info_skel(skel)
119          && is_valid_version_info_skel(skel->next));
120}
121
122
123/* Parse the enumeration value in VALUE into a plain
124 * 'int', using MAP to convert from strings to enumeration values.
125 * In MAP, a null .str field marks the end of the map.
126 */
127static svn_error_t *
128read_enum_field(int *result,
129                const svn_token_map_t *map,
130                const svn_skel_t *skel)
131{
132  int value = svn_token__from_mem(map, skel->data, skel->len);
133
134  if (value == SVN_TOKEN_UNKNOWN)
135    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
136                            _("Unknown enumeration value in tree conflict "
137                              "description"));
138
139  *result = value;
140
141  return SVN_NO_ERROR;
142}
143
144
145/* Parse the conflict info fields from SKEL into *VERSION_INFO. */
146static svn_error_t *
147read_node_version_info(const svn_wc_conflict_version_t **version_info,
148                       const svn_skel_t *skel,
149                       apr_pool_t *result_pool,
150                       apr_pool_t *scratch_pool)
151{
152  int n;
153  const char *repos_root;
154  const char *repos_relpath;
155  svn_revnum_t peg_rev;
156  svn_node_kind_t kind;
157
158  if (!is_valid_version_info_skel(skel))
159    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
160                            _("Invalid version info in tree conflict "
161                              "description"));
162
163  repos_root = apr_pstrmemdup(scratch_pool,
164                              skel->children->next->data,
165                              skel->children->next->len);
166  if (*repos_root == '\0')
167    {
168      *version_info = NULL;
169      return SVN_NO_ERROR;
170    }
171
172  /* Apply the Subversion 1.7+ url canonicalization rules to a pre 1.7 url */
173  repos_root = svn_uri_canonicalize(repos_root, result_pool);
174
175  peg_rev = SVN_STR_TO_REV(apr_pstrmemdup(scratch_pool,
176                                          skel->children->next->next->data,
177                                          skel->children->next->next->len));
178
179  repos_relpath = apr_pstrmemdup(result_pool,
180                                 skel->children->next->next->next->data,
181                                 skel->children->next->next->next->len);
182
183  SVN_ERR(read_enum_field(&n, node_kind_map,
184                          skel->children->next->next->next->next));
185  kind = (svn_node_kind_t)n;
186
187  *version_info = svn_wc_conflict_version_create2(repos_root,
188                                                  NULL,
189                                                  repos_relpath,
190                                                  peg_rev,
191                                                  kind,
192                                                  result_pool);
193
194  return SVN_NO_ERROR;
195}
196
197
198svn_error_t *
199svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict,
200                             const svn_skel_t *skel,
201                             const char *dir_path,
202                             apr_pool_t *result_pool,
203                             apr_pool_t *scratch_pool)
204{
205  const char *victim_basename;
206  const char *victim_abspath;
207  svn_node_kind_t node_kind;
208  svn_wc_operation_t operation;
209  svn_wc_conflict_action_t action;
210  svn_wc_conflict_reason_t reason;
211  const svn_wc_conflict_version_t *src_left_version;
212  const svn_wc_conflict_version_t *src_right_version;
213  int n;
214  svn_wc_conflict_description2_t *new_conflict;
215
216  if (!is_valid_conflict_skel(skel))
217    return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
218                             _("Invalid conflict info '%s' in tree conflict "
219                               "description"),
220                             skel ? svn_skel__unparse(skel, scratch_pool)->data
221                                  : "(null)");
222
223  /* victim basename */
224  victim_basename = apr_pstrmemdup(scratch_pool,
225                                   skel->children->next->data,
226                                   skel->children->next->len);
227  if (victim_basename[0] == '\0')
228    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
229                            _("Empty 'victim' field in tree conflict "
230                              "description"));
231
232  /* node_kind */
233  SVN_ERR(read_enum_field(&n, node_kind_map, skel->children->next->next));
234  node_kind = (svn_node_kind_t)n;
235  if (node_kind != svn_node_file && node_kind != svn_node_dir)
236    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
237             _("Invalid 'node_kind' field in tree conflict description"));
238
239  /* operation */
240  SVN_ERR(read_enum_field(&n, svn_wc__operation_map,
241                          skel->children->next->next->next));
242  operation = (svn_wc_operation_t)n;
243
244  SVN_ERR(svn_dirent_get_absolute(&victim_abspath,
245                    svn_dirent_join(dir_path, victim_basename, scratch_pool),
246                    scratch_pool));
247
248  /* action */
249  SVN_ERR(read_enum_field(&n, svn_wc__conflict_action_map,
250                          skel->children->next->next->next->next));
251  action = n;
252
253  /* reason */
254  SVN_ERR(read_enum_field(&n, svn_wc__conflict_reason_map,
255                          skel->children->next->next->next->next->next));
256  reason = n;
257
258  /* Let's just make it a bit easier on ourself here... */
259  skel = skel->children->next->next->next->next->next->next;
260
261  /* src_left_version */
262  SVN_ERR(read_node_version_info(&src_left_version, skel,
263                                 result_pool, scratch_pool));
264
265  /* src_right_version */
266  SVN_ERR(read_node_version_info(&src_right_version, skel->next,
267                                 result_pool, scratch_pool));
268
269  new_conflict = svn_wc_conflict_description_create_tree2(victim_abspath,
270    node_kind, operation, src_left_version, src_right_version,
271    result_pool);
272  new_conflict->action = action;
273  new_conflict->reason = reason;
274
275  *conflict = new_conflict;
276
277  return SVN_NO_ERROR;
278}
279
280
281/* Prepend to SKEL the string corresponding to enumeration value N, as found
282 * in MAP. */
283static void
284skel_prepend_enum(svn_skel_t *skel,
285                  const svn_token_map_t *map,
286                  int n,
287                  apr_pool_t *result_pool)
288{
289  svn_skel__prepend(svn_skel__str_atom(svn_token__to_word(map, n),
290                                       result_pool), skel);
291}
292
293
294/* Prepend to PARENT_SKEL the several fields that represent VERSION_INFO, */
295static svn_error_t *
296prepend_version_info_skel(svn_skel_t *parent_skel,
297                          const svn_wc_conflict_version_t *version_info,
298                          apr_pool_t *pool)
299{
300  svn_skel_t *skel = svn_skel__make_empty_list(pool);
301
302  /* node_kind */
303  skel_prepend_enum(skel, node_kind_map, version_info->node_kind, pool);
304
305  /* path_in_repos */
306  svn_skel__prepend(svn_skel__str_atom(version_info->path_in_repos
307                                       ? version_info->path_in_repos
308                                       : "", pool), skel);
309
310  /* peg_rev */
311  svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld",
312                                                    version_info->peg_rev),
313                                       pool), skel);
314
315  /* repos_url */
316  svn_skel__prepend(svn_skel__str_atom(version_info->repos_url
317                                       ? version_info->repos_url
318                                       : "", pool), skel);
319
320  svn_skel__prepend(svn_skel__str_atom("version", pool), skel);
321
322  SVN_ERR_ASSERT(is_valid_version_info_skel(skel));
323
324  svn_skel__prepend(skel, parent_skel);
325
326  return SVN_NO_ERROR;
327}
328
329
330svn_error_t *
331svn_wc__serialize_conflict(svn_skel_t **skel,
332                           const svn_wc_conflict_description2_t *conflict,
333                           apr_pool_t *result_pool,
334                           apr_pool_t *scratch_pool)
335{
336  /* A conflict version struct with all fields null/invalid. */
337  static const svn_wc_conflict_version_t null_version = {
338    NULL, SVN_INVALID_REVNUM, NULL, svn_node_unknown };
339  svn_skel_t *c_skel = svn_skel__make_empty_list(result_pool);
340  const char *victim_basename;
341
342  /* src_right_version */
343  if (conflict->src_right_version)
344    SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_right_version,
345                                      result_pool));
346  else
347    SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool));
348
349  /* src_left_version */
350  if (conflict->src_left_version)
351    SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_left_version,
352                                      result_pool));
353  else
354    SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool));
355
356  /* reason */
357  skel_prepend_enum(c_skel, svn_wc__conflict_reason_map, conflict->reason,
358                    result_pool);
359
360  /* action */
361  skel_prepend_enum(c_skel, svn_wc__conflict_action_map, conflict->action,
362                    result_pool);
363
364  /* operation */
365  skel_prepend_enum(c_skel, svn_wc__operation_map, conflict->operation,
366                    result_pool);
367
368  /* node_kind */
369  SVN_ERR_ASSERT(conflict->node_kind == svn_node_dir
370                 || conflict->node_kind == svn_node_file);
371  skel_prepend_enum(c_skel, node_kind_map, conflict->node_kind, result_pool);
372
373  /* Victim path (escaping separator chars). */
374  victim_basename = svn_dirent_basename(conflict->local_abspath, result_pool);
375  SVN_ERR_ASSERT(victim_basename[0]);
376  svn_skel__prepend(svn_skel__str_atom(victim_basename, result_pool), c_skel);
377
378  svn_skel__prepend(svn_skel__str_atom("conflict", result_pool), c_skel);
379
380  SVN_ERR_ASSERT(is_valid_conflict_skel(c_skel));
381
382  *skel = c_skel;
383
384  return SVN_NO_ERROR;
385}
386
387
388svn_error_t *
389svn_wc__del_tree_conflict(svn_wc_context_t *wc_ctx,
390                          const char *victim_abspath,
391                          apr_pool_t *scratch_pool)
392{
393  SVN_ERR_ASSERT(svn_dirent_is_absolute(victim_abspath));
394
395  SVN_ERR(svn_wc__db_op_mark_resolved(wc_ctx->db, victim_abspath,
396                                      FALSE, FALSE, TRUE, NULL,
397                                      scratch_pool));
398
399   return SVN_NO_ERROR;
400 }
401
402svn_error_t *
403svn_wc__add_tree_conflict(svn_wc_context_t *wc_ctx,
404                          const svn_wc_conflict_description2_t *conflict,
405                          apr_pool_t *scratch_pool)
406{
407  svn_boolean_t existing_conflict;
408  svn_skel_t *conflict_skel;
409  svn_error_t *err;
410
411  SVN_ERR_ASSERT(conflict != NULL);
412  SVN_ERR_ASSERT(conflict->operation == svn_wc_operation_merge
413                 || (conflict->reason != svn_wc_conflict_reason_moved_away
414                     && conflict->reason != svn_wc_conflict_reason_moved_here)
415                );
416
417  /* Re-adding an existing tree conflict victim is an error. */
418  err = svn_wc__internal_conflicted_p(NULL, NULL, &existing_conflict,
419                                      wc_ctx->db, conflict->local_abspath,
420                                      scratch_pool);
421  if (err)
422    {
423      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
424        return svn_error_trace(err);
425
426      svn_error_clear(err);
427    }
428  else if (existing_conflict)
429    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
430                             _("Attempt to add tree conflict that already "
431                               "exists at '%s'"),
432                             svn_dirent_local_style(conflict->local_abspath,
433                                                    scratch_pool));
434  else if (!conflict)
435    return SVN_NO_ERROR;
436
437  conflict_skel = svn_wc__conflict_skel_create(scratch_pool);
438
439  SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_skel, wc_ctx->db,
440                                                  conflict->local_abspath,
441                                                  conflict->reason,
442                                                  conflict->action,
443                                                  NULL,
444                                                  scratch_pool, scratch_pool));
445
446  switch(conflict->operation)
447    {
448      case svn_wc_operation_update:
449      default:
450        SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel,
451                                                    conflict->src_left_version,
452                                                    conflict->src_right_version,
453                                                    scratch_pool, scratch_pool));
454        break;
455      case svn_wc_operation_switch:
456        SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_skel,
457                                                    conflict->src_left_version,
458                                                    conflict->src_right_version,
459                                                    scratch_pool, scratch_pool));
460        break;
461      case svn_wc_operation_merge:
462        SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
463                                                   conflict->src_left_version,
464                                                   conflict->src_right_version,
465                                                   scratch_pool, scratch_pool));
466        break;
467    }
468
469  return svn_error_trace(
470                svn_wc__db_op_mark_conflict(wc_ctx->db, conflict->local_abspath,
471                                            conflict_skel, NULL, scratch_pool));
472}
473
474
475svn_error_t *
476svn_wc__get_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict,
477                          svn_wc_context_t *wc_ctx,
478                          const char *local_abspath,
479                          apr_pool_t *result_pool,
480                          apr_pool_t *scratch_pool)
481{
482  const apr_array_header_t *conflicts;
483  int i;
484  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
485
486  SVN_ERR(svn_wc__read_conflicts(&conflicts,
487                                 wc_ctx->db, local_abspath, FALSE,
488                                 scratch_pool, scratch_pool));
489
490  if (!conflicts || conflicts->nelts == 0)
491    {
492      *tree_conflict = NULL;
493      return SVN_NO_ERROR;
494    }
495
496  for (i = 0; i < conflicts->nelts; i++)
497    {
498      const svn_wc_conflict_description2_t *desc;
499
500      desc = APR_ARRAY_IDX(conflicts, i, svn_wc_conflict_description2_t *);
501
502      if (desc->kind == svn_wc_conflict_kind_tree)
503        {
504          *tree_conflict = svn_wc__conflict_description2_dup(desc,
505                                                             result_pool);
506          return SVN_NO_ERROR;
507        }
508    }
509
510  *tree_conflict = NULL;
511  return SVN_NO_ERROR;
512}
513
514