tree_conflicts.c revision 362181
1141296Sdas/*
2141296Sdas * tree_conflicts.c: Storage of tree conflict descriptions in the WC.
32116Sjkh *
42116Sjkh * ====================================================================
52116Sjkh *    Licensed to the Apache Software Foundation (ASF) under one
62116Sjkh *    or more contributor license agreements.  See the NOTICE file
7141296Sdas *    distributed with this work for additional information
82116Sjkh *    regarding copyright ownership.  The ASF licenses this file
9141296Sdas *    to you under the Apache License, Version 2.0 (the
102116Sjkh *    "License"); you may not use this file except in compliance
112116Sjkh *    with the License.  You may obtain a copy of the License at
122116Sjkh *
132116Sjkh *      http://www.apache.org/licenses/LICENSE-2.0
14176451Sdas *
15176451Sdas *    Unless required by applicable law or agreed to in writing,
162116Sjkh *    software distributed under the License is distributed on an
172116Sjkh *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18141296Sdas *    KIND, either express or implied.  See the License for the
192116Sjkh *    specific language governing permissions and limitations
20141296Sdas *    under the License.
21141296Sdas * ====================================================================
222116Sjkh */
232116Sjkh
242116Sjkh#include "svn_dirent_uri.h"
252116Sjkh#include "svn_path.h"
26141296Sdas#include "svn_types.h"
272116Sjkh#include "svn_pools.h"
282116Sjkh
292116Sjkh#include "tree_conflicts.h"
302116Sjkh#include "conflicts.h"
312116Sjkh#include "wc.h"
322116Sjkh
332116Sjkh#include "private/svn_skel.h"
342116Sjkh#include "private/svn_wc_private.h"
35260067Skargl#include "private/svn_token.h"
36260067Skargl
372116Sjkh#include "svn_private_config.h"
382116Sjkh
392116Sjkh/* ### this should move to a more general location...  */
402116Sjkh/* A map for svn_node_kind_t values. */
412116Sjkh/* FIXME: this mapping defines a different representation of
4297413Salfred          svn_node_unknown than the one defined in token-map.h */
4397413Salfredstatic const svn_token_map_t node_kind_map[] =
448870Srgrimes{
45226598Sdas  { "none", svn_node_none },
462116Sjkh  { "file", svn_node_file },
472116Sjkh  { "dir",  svn_node_dir },
482116Sjkh  { "",     svn_node_unknown },
492116Sjkh  /* ### should also map svn_node_symlink */
502116Sjkh  { NULL }
512116Sjkh};
522116Sjkh
53141296Sdas/* A map for svn_wc_operation_t values. */
542116Sjkhconst svn_token_map_t svn_wc__operation_map[] =
552116Sjkh{
562116Sjkh  { "none",   svn_wc_operation_none },
572116Sjkh  { "update", svn_wc_operation_update },
582116Sjkh  { "switch", svn_wc_operation_switch },
592116Sjkh  { "merge",  svn_wc_operation_merge },
602116Sjkh  { NULL }
612116Sjkh};
622116Sjkh
632116Sjkh/* A map for svn_wc_conflict_action_t values. */
642116Sjkhconst svn_token_map_t svn_wc__conflict_action_map[] =
652116Sjkh{
662116Sjkh  { "edited",   svn_wc_conflict_action_edit },
672116Sjkh  { "deleted",  svn_wc_conflict_action_delete },
682116Sjkh  { "added",    svn_wc_conflict_action_add },
692116Sjkh  { "replaced", svn_wc_conflict_action_replace },
70226598Sdas  { NULL }
71226598Sdas};
722116Sjkh
732116Sjkh/* A map for svn_wc_conflict_reason_t values. */
742116Sjkhconst svn_token_map_t svn_wc__conflict_reason_map[] =
752116Sjkh{
76260067Skargl  { "edited",      svn_wc_conflict_reason_edited },
77260067Skargl  { "deleted",     svn_wc_conflict_reason_deleted },
78260067Skargl  { "missing",     svn_wc_conflict_reason_missing },
79260067Skargl  { "obstructed",  svn_wc_conflict_reason_obstructed },
80  { "added",       svn_wc_conflict_reason_added },
81  { "replaced",    svn_wc_conflict_reason_replaced },
82  { "unversioned", svn_wc_conflict_reason_unversioned },
83  { "moved-away", svn_wc_conflict_reason_moved_away },
84  { "moved-here", svn_wc_conflict_reason_moved_here },
85  { NULL }
86};
87
88
89/* */
90static svn_boolean_t
91is_valid_version_info_skel(const svn_skel_t *skel)
92{
93  return (svn_skel__list_length(skel) == 5
94          && svn_skel__matches_atom(skel->children, "version")
95          && skel->children->next->is_atom
96          && skel->children->next->next->is_atom
97          && skel->children->next->next->next->is_atom
98          && skel->children->next->next->next->next->is_atom);
99}
100
101
102/* */
103static svn_boolean_t
104is_valid_conflict_skel(const svn_skel_t *skel)
105{
106  int i;
107
108  if (svn_skel__list_length(skel) != 8
109      || !svn_skel__matches_atom(skel->children, "conflict"))
110    return FALSE;
111
112  /* 5 atoms ... */
113  skel = skel->children->next;
114  for (i = 5; i--; skel = skel->next)
115    if (!skel->is_atom)
116      return FALSE;
117
118  /* ... and 2 version info skels. */
119  return (is_valid_version_info_skel(skel)
120          && is_valid_version_info_skel(skel->next));
121}
122
123
124/* Parse the enumeration value in VALUE into a plain
125 * 'int', using MAP to convert from strings to enumeration values.
126 * In MAP, a null .str field marks the end of the map.
127 */
128static svn_error_t *
129read_enum_field(int *result,
130                const svn_token_map_t *map,
131                const svn_skel_t *skel)
132{
133  int value = svn_token__from_mem(map, skel->data, skel->len);
134
135  if (value == SVN_TOKEN_UNKNOWN)
136    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
137                            _("Unknown enumeration value in tree conflict "
138                              "description"));
139
140  *result = value;
141
142  return SVN_NO_ERROR;
143}
144
145
146/* Parse the conflict info fields from SKEL into *VERSION_INFO. */
147static svn_error_t *
148read_node_version_info(const svn_wc_conflict_version_t **version_info,
149                       const svn_skel_t *skel,
150                       apr_pool_t *result_pool,
151                       apr_pool_t *scratch_pool)
152{
153  int n;
154  const char *repos_root;
155  const char *repos_relpath;
156  svn_revnum_t peg_rev;
157  svn_node_kind_t kind;
158
159  if (!is_valid_version_info_skel(skel))
160    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
161                            _("Invalid version info in tree conflict "
162                              "description"));
163
164  repos_root = apr_pstrmemdup(scratch_pool,
165                              skel->children->next->data,
166                              skel->children->next->len);
167  if (*repos_root == '\0')
168    {
169      *version_info = NULL;
170      return SVN_NO_ERROR;
171    }
172
173  /* Apply the Subversion 1.7+ url canonicalization rules to a pre 1.7 url */
174  repos_root = svn_uri_canonicalize(repos_root, result_pool);
175
176  peg_rev = SVN_STR_TO_REV(apr_pstrmemdup(scratch_pool,
177                                          skel->children->next->next->data,
178                                          skel->children->next->next->len));
179
180  repos_relpath = apr_pstrmemdup(result_pool,
181                                 skel->children->next->next->next->data,
182                                 skel->children->next->next->next->len);
183
184  SVN_ERR(read_enum_field(&n, node_kind_map,
185                          skel->children->next->next->next->next));
186  kind = (svn_node_kind_t)n;
187
188  *version_info = svn_wc_conflict_version_create2(repos_root,
189                                                  NULL,
190                                                  repos_relpath,
191                                                  peg_rev,
192                                                  kind,
193                                                  result_pool);
194
195  return SVN_NO_ERROR;
196}
197
198
199svn_error_t *
200svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict,
201                             const svn_skel_t *skel,
202                             const char *dir_path,
203                             apr_pool_t *result_pool,
204                             apr_pool_t *scratch_pool)
205{
206  const char *victim_basename;
207  const char *victim_abspath;
208  svn_node_kind_t node_kind;
209  svn_wc_operation_t operation;
210  svn_wc_conflict_action_t action;
211  svn_wc_conflict_reason_t reason;
212  const svn_wc_conflict_version_t *src_left_version;
213  const svn_wc_conflict_version_t *src_right_version;
214  int n;
215  svn_wc_conflict_description2_t *new_conflict;
216
217  if (!is_valid_conflict_skel(skel))
218    return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
219                             _("Invalid conflict info '%s' in tree conflict "
220                               "description"),
221                             skel ? svn_skel__unparse(skel, scratch_pool)->data
222                                  : "(null)");
223
224  /* victim basename */
225  victim_basename = apr_pstrmemdup(scratch_pool,
226                                   skel->children->next->data,
227                                   skel->children->next->len);
228  if (victim_basename[0] == '\0')
229    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
230                            _("Empty 'victim' field in tree conflict "
231                              "description"));
232
233  /* node_kind */
234  SVN_ERR(read_enum_field(&n, node_kind_map, skel->children->next->next));
235  node_kind = (svn_node_kind_t)n;
236  if (node_kind != svn_node_file && node_kind != svn_node_dir)
237    return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
238             _("Invalid 'node_kind' field in tree conflict description"));
239
240  /* operation */
241  SVN_ERR(read_enum_field(&n, svn_wc__operation_map,
242                          skel->children->next->next->next));
243  operation = (svn_wc_operation_t)n;
244
245  SVN_ERR(svn_dirent_get_absolute(&victim_abspath,
246                    svn_dirent_join(dir_path, victim_basename, scratch_pool),
247                    scratch_pool));
248
249  /* action */
250  SVN_ERR(read_enum_field(&n, svn_wc__conflict_action_map,
251                          skel->children->next->next->next->next));
252  action = n;
253
254  /* reason */
255  SVN_ERR(read_enum_field(&n, svn_wc__conflict_reason_map,
256                          skel->children->next->next->next->next->next));
257  reason = n;
258
259  /* Let's just make it a bit easier on ourself here... */
260  skel = skel->children->next->next->next->next->next->next;
261
262  /* src_left_version */
263  SVN_ERR(read_node_version_info(&src_left_version, skel,
264                                 result_pool, scratch_pool));
265
266  /* src_right_version */
267  SVN_ERR(read_node_version_info(&src_right_version, skel->next,
268                                 result_pool, scratch_pool));
269
270  new_conflict = svn_wc_conflict_description_create_tree2(victim_abspath,
271    node_kind, operation, src_left_version, src_right_version,
272    result_pool);
273  new_conflict->action = action;
274  new_conflict->reason = reason;
275
276  *conflict = new_conflict;
277
278  return SVN_NO_ERROR;
279}
280
281
282/* Prepend to SKEL the string corresponding to enumeration value N, as found
283 * in MAP. */
284static void
285skel_prepend_enum(svn_skel_t *skel,
286                  const svn_token_map_t *map,
287                  int n,
288                  apr_pool_t *result_pool)
289{
290  svn_skel__prepend(svn_skel__str_atom(svn_token__to_word(map, n),
291                                       result_pool), skel);
292}
293
294
295/* Prepend to PARENT_SKEL the several fields that represent VERSION_INFO, */
296static svn_error_t *
297prepend_version_info_skel(svn_skel_t *parent_skel,
298                          const svn_wc_conflict_version_t *version_info,
299                          apr_pool_t *pool)
300{
301  svn_skel_t *skel = svn_skel__make_empty_list(pool);
302
303  /* node_kind */
304  skel_prepend_enum(skel, node_kind_map, version_info->node_kind, pool);
305
306  /* path_in_repos */
307  svn_skel__prepend(svn_skel__str_atom(version_info->path_in_repos
308                                       ? version_info->path_in_repos
309                                       : "", pool), skel);
310
311  /* peg_rev */
312  svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld",
313                                                    version_info->peg_rev),
314                                       pool), skel);
315
316  /* repos_url */
317  svn_skel__prepend(svn_skel__str_atom(version_info->repos_url
318                                       ? version_info->repos_url
319                                       : "", pool), skel);
320
321  svn_skel__prepend(svn_skel__str_atom("version", pool), skel);
322
323  SVN_ERR_ASSERT(is_valid_version_info_skel(skel));
324
325  svn_skel__prepend(skel, parent_skel);
326
327  return SVN_NO_ERROR;
328}
329
330
331svn_error_t *
332svn_wc__serialize_conflict(svn_skel_t **skel,
333                           const svn_wc_conflict_description2_t *conflict,
334                           apr_pool_t *result_pool,
335                           apr_pool_t *scratch_pool)
336{
337  /* A conflict version struct with all fields null/invalid. */
338  static const svn_wc_conflict_version_t null_version = {
339    NULL, SVN_INVALID_REVNUM, NULL, svn_node_unknown };
340  svn_skel_t *c_skel = svn_skel__make_empty_list(result_pool);
341  const char *victim_basename;
342
343  /* src_right_version */
344  if (conflict->src_right_version)
345    SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_right_version,
346                                      result_pool));
347  else
348    SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool));
349
350  /* src_left_version */
351  if (conflict->src_left_version)
352    SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_left_version,
353                                      result_pool));
354  else
355    SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool));
356
357  /* local change */
358  skel_prepend_enum(c_skel, svn_wc__conflict_reason_map,
359                    conflict->reason, result_pool);
360
361  /* incoming change */
362  skel_prepend_enum(c_skel, svn_wc__conflict_action_map,
363                    conflict->action, result_pool);
364
365  /* operation */
366  skel_prepend_enum(c_skel, svn_wc__operation_map, conflict->operation,
367                    result_pool);
368
369  /* node_kind */
370  SVN_ERR_ASSERT(conflict->node_kind == svn_node_dir
371                 || conflict->node_kind == svn_node_file
372                 || conflict->node_kind == svn_node_none);
373  skel_prepend_enum(c_skel, node_kind_map, conflict->node_kind,
374                    result_pool);
375
376  /* Victim path (escaping separator chars). */
377  victim_basename = svn_dirent_basename(conflict->local_abspath, result_pool);
378  SVN_ERR_ASSERT(victim_basename[0]);
379  svn_skel__prepend(svn_skel__str_atom(victim_basename, result_pool), c_skel);
380
381  svn_skel__prepend(svn_skel__str_atom("conflict", result_pool), c_skel);
382
383  SVN_ERR_ASSERT(is_valid_conflict_skel(c_skel));
384
385  *skel = c_skel;
386
387  return SVN_NO_ERROR;
388}
389
390
391svn_error_t *
392svn_wc__del_tree_conflict(svn_wc_context_t *wc_ctx,
393                          const char *victim_abspath,
394                          apr_pool_t *scratch_pool)
395{
396  SVN_ERR_ASSERT(svn_dirent_is_absolute(victim_abspath));
397
398  SVN_ERR(svn_wc__db_op_mark_resolved(wc_ctx->db, victim_abspath,
399                                      FALSE, FALSE, TRUE, NULL,
400                                      scratch_pool));
401
402   return SVN_NO_ERROR;
403 }
404
405svn_error_t *
406svn_wc__add_tree_conflict(svn_wc_context_t *wc_ctx,
407                          const svn_wc_conflict_description2_t *conflict,
408                          apr_pool_t *scratch_pool)
409{
410  svn_boolean_t existing_conflict;
411  svn_skel_t *conflict_skel;
412  svn_error_t *err;
413
414  SVN_ERR_ASSERT(conflict != NULL);
415  SVN_ERR_ASSERT(conflict->operation == svn_wc_operation_merge ||
416                 (conflict->reason != svn_wc_conflict_reason_moved_away &&
417                  conflict->reason != svn_wc_conflict_reason_moved_here));
418
419  /* Re-adding an existing tree conflict victim is an error. */
420  err = svn_wc__internal_conflicted_p(NULL, NULL, &existing_conflict,
421                                      wc_ctx->db, conflict->local_abspath,
422                                      scratch_pool);
423  if (err)
424    {
425      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
426        return svn_error_trace(err);
427
428      svn_error_clear(err);
429    }
430  else if (existing_conflict)
431    return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
432                             _("Attempt to add tree conflict that already "
433                               "exists at '%s'"),
434                             svn_dirent_local_style(conflict->local_abspath,
435                                                    scratch_pool));
436  else if (!conflict)
437    return SVN_NO_ERROR;
438
439  conflict_skel = svn_wc__conflict_skel_create(scratch_pool);
440
441  SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_skel, wc_ctx->db,
442                                                  conflict->local_abspath,
443                                                  conflict->reason,
444                                                  conflict->action,
445                                                  NULL, NULL,
446                                                  scratch_pool, scratch_pool));
447
448  switch (conflict->operation)
449    {
450      case svn_wc_operation_update:
451      default:
452        SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel,
453                                                    conflict->src_left_version,
454                                                    conflict->src_right_version,
455                                                    scratch_pool, scratch_pool));
456        break;
457      case svn_wc_operation_switch:
458        SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_skel,
459                                                    conflict->src_left_version,
460                                                    conflict->src_right_version,
461                                                    scratch_pool, scratch_pool));
462        break;
463      case svn_wc_operation_merge:
464        SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
465                                                   conflict->src_left_version,
466                                                   conflict->src_right_version,
467                                                   scratch_pool, scratch_pool));
468        break;
469    }
470
471  return svn_error_trace(
472                svn_wc__db_op_mark_conflict(wc_ctx->db, conflict->local_abspath,
473                                            conflict_skel, NULL, scratch_pool));
474}
475
476
477svn_error_t *
478svn_wc__get_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict,
479                          svn_wc_context_t *wc_ctx,
480                          const char *local_abspath,
481                          apr_pool_t *result_pool,
482                          apr_pool_t *scratch_pool)
483{
484  const apr_array_header_t *conflicts;
485  int i;
486  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
487
488  SVN_ERR(svn_wc__read_conflicts(&conflicts, NULL,
489                                 wc_ctx->db, local_abspath,
490                                 FALSE /* temp files */,
491                                 TRUE /* only tree conflicts */,
492                                 scratch_pool, scratch_pool));
493
494  if (!conflicts || conflicts->nelts == 0)
495    {
496      *tree_conflict = NULL;
497      return SVN_NO_ERROR;
498    }
499
500  for (i = 0; i < conflicts->nelts; i++)
501    {
502      const svn_wc_conflict_description2_t *desc;
503
504      desc = APR_ARRAY_IDX(conflicts, i, svn_wc_conflict_description2_t *);
505
506      if (desc->kind == svn_wc_conflict_kind_tree)
507        {
508          *tree_conflict = svn_wc_conflict_description2_dup(desc, result_pool);
509          return SVN_NO_ERROR;
510        }
511    }
512
513  *tree_conflict = NULL;
514  return SVN_NO_ERROR;
515}
516
517