1 /*
2 * conflicts.c: Tree conflicts.
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 "cl-conflicts.h"
25 #include "svn_hash.h"
26 #include "svn_xml.h"
27 #include "svn_dirent_uri.h"
28 #include "svn_path.h"
29 #include "private/svn_token.h"
30
31 #include "cl.h"
32
33 #include "svn_private_config.h"
34
35
36 /* A map for svn_wc_conflict_action_t values to XML strings */
37 static const svn_token_map_t map_conflict_action_xml[] =
38 {
39 { "edit", svn_wc_conflict_action_edit },
40 { "delete", svn_wc_conflict_action_delete },
41 { "add", svn_wc_conflict_action_add },
42 { "replace", svn_wc_conflict_action_replace },
43 { NULL, 0 }
44 };
45
46 /* A map for svn_wc_conflict_reason_t values to XML strings */
47 static const svn_token_map_t map_conflict_reason_xml[] =
48 {
49 { "edit", svn_wc_conflict_reason_edited },
50 { "delete", svn_wc_conflict_reason_deleted },
51 { "missing", svn_wc_conflict_reason_missing },
52 { "obstruction", svn_wc_conflict_reason_obstructed },
53 { "add", svn_wc_conflict_reason_added },
54 { "replace", svn_wc_conflict_reason_replaced },
55 { "unversioned", svn_wc_conflict_reason_unversioned },
56 { "moved-away", svn_wc_conflict_reason_moved_away },
57 { "moved-here", svn_wc_conflict_reason_moved_here },
58 { NULL, 0 }
59 };
60
61 static const svn_token_map_t map_conflict_kind_xml[] =
62 {
63 { "text", svn_wc_conflict_kind_text },
64 { "property", svn_wc_conflict_kind_property },
65 { "tree", svn_wc_conflict_kind_tree },
66 { NULL, 0 }
67 };
68
69 /* Return a localised string representation of the local part of a conflict;
70 NULL for non-localised odd cases. */
71 static const char *
local_reason_str(svn_node_kind_t kind,svn_wc_conflict_reason_t reason,svn_wc_operation_t operation)72 local_reason_str(svn_node_kind_t kind, svn_wc_conflict_reason_t reason,
73 svn_wc_operation_t operation)
74 {
75 switch (kind)
76 {
77 case svn_node_file:
78 case svn_node_symlink:
79 switch (reason)
80 {
81 case svn_wc_conflict_reason_edited:
82 return _("local file edit");
83 case svn_wc_conflict_reason_obstructed:
84 return _("local file obstruction");
85 case svn_wc_conflict_reason_deleted:
86 return _("local file delete");
87 case svn_wc_conflict_reason_missing:
88 if (operation == svn_wc_operation_merge)
89 return _("local file missing or deleted or moved away");
90 else
91 return _("local file missing");
92 case svn_wc_conflict_reason_unversioned:
93 return _("local file unversioned");
94 case svn_wc_conflict_reason_added:
95 return _("local file add");
96 case svn_wc_conflict_reason_replaced:
97 return _("local file replace");
98 case svn_wc_conflict_reason_moved_away:
99 return _("local file moved away");
100 case svn_wc_conflict_reason_moved_here:
101 return _("local file moved here");
102 }
103 break;
104 case svn_node_dir:
105 switch (reason)
106 {
107 case svn_wc_conflict_reason_edited:
108 return _("local dir edit");
109 case svn_wc_conflict_reason_obstructed:
110 return _("local dir obstruction");
111 case svn_wc_conflict_reason_deleted:
112 return _("local dir delete");
113 case svn_wc_conflict_reason_missing:
114 if (operation == svn_wc_operation_merge)
115 return _("local dir missing or deleted or moved away");
116 else
117 return _("local dir missing");
118 case svn_wc_conflict_reason_unversioned:
119 return _("local dir unversioned");
120 case svn_wc_conflict_reason_added:
121 return _("local dir add");
122 case svn_wc_conflict_reason_replaced:
123 return _("local dir replace");
124 case svn_wc_conflict_reason_moved_away:
125 return _("local dir moved away");
126 case svn_wc_conflict_reason_moved_here:
127 return _("local dir moved here");
128 }
129 break;
130 case svn_node_none:
131 case svn_node_unknown:
132 switch (reason)
133 {
134 case svn_wc_conflict_reason_edited:
135 return _("local edit");
136 case svn_wc_conflict_reason_obstructed:
137 return _("local obstruction");
138 case svn_wc_conflict_reason_deleted:
139 return _("local delete");
140 case svn_wc_conflict_reason_missing:
141 if (operation == svn_wc_operation_merge)
142 return _("local missing or deleted or moved away");
143 else
144 return _("local missing");
145 case svn_wc_conflict_reason_unversioned:
146 return _("local unversioned");
147 case svn_wc_conflict_reason_added:
148 return _("local add");
149 case svn_wc_conflict_reason_replaced:
150 return _("local replace");
151 case svn_wc_conflict_reason_moved_away:
152 return _("local moved away");
153 case svn_wc_conflict_reason_moved_here:
154 return _("local moved here");
155 }
156 break;
157 }
158 return NULL;
159 }
160
161 /* Return a localised string representation of the incoming part of a
162 conflict; NULL for non-localised odd cases. */
163 static const char *
incoming_action_str(svn_node_kind_t kind,svn_wc_conflict_action_t action)164 incoming_action_str(svn_node_kind_t kind, svn_wc_conflict_action_t action)
165 {
166 switch (kind)
167 {
168 case svn_node_file:
169 case svn_node_symlink:
170 switch (action)
171 {
172 case svn_wc_conflict_action_edit:
173 return _("incoming file edit");
174 case svn_wc_conflict_action_add:
175 return _("incoming file add");
176 case svn_wc_conflict_action_delete:
177 return _("incoming file delete or move");
178 case svn_wc_conflict_action_replace:
179 return _("incoming replace with file");
180 }
181 break;
182 case svn_node_dir:
183 switch (action)
184 {
185 case svn_wc_conflict_action_edit:
186 return _("incoming dir edit");
187 case svn_wc_conflict_action_add:
188 return _("incoming dir add");
189 case svn_wc_conflict_action_delete:
190 return _("incoming dir delete or move");
191 case svn_wc_conflict_action_replace:
192 return _("incoming replace with dir");
193 }
194 break;
195 case svn_node_none:
196 case svn_node_unknown:
197 switch (action)
198 {
199 case svn_wc_conflict_action_edit:
200 return _("incoming edit");
201 case svn_wc_conflict_action_add:
202 return _("incoming add");
203 case svn_wc_conflict_action_delete:
204 return _("incoming delete or move");
205 case svn_wc_conflict_action_replace:
206 return _("incoming replace");
207 }
208 break;
209 }
210 return NULL;
211 }
212
213 /* Return a localised string representation of the operation part of a
214 conflict. */
215 static const char *
operation_str(svn_wc_operation_t operation)216 operation_str(svn_wc_operation_t operation)
217 {
218 switch (operation)
219 {
220 case svn_wc_operation_update: return _("upon update");
221 case svn_wc_operation_switch: return _("upon switch");
222 case svn_wc_operation_merge: return _("upon merge");
223 case svn_wc_operation_none: return _("upon none");
224 }
225 SVN_ERR_MALFUNCTION_NO_RETURN();
226 return NULL;
227 }
228
229 svn_error_t *
svn_cl__get_human_readable_prop_conflict_description(const char ** desc,const svn_wc_conflict_description2_t * conflict,apr_pool_t * pool)230 svn_cl__get_human_readable_prop_conflict_description(
231 const char **desc,
232 const svn_wc_conflict_description2_t *conflict,
233 apr_pool_t *pool)
234 {
235 const char *reason_str, *action_str;
236
237 /* We provide separately translatable strings for the values that we
238 * know about, and a fall-back in case any other values occur. */
239 switch (conflict->reason)
240 {
241 case svn_wc_conflict_reason_edited:
242 reason_str = _("local edit");
243 break;
244 case svn_wc_conflict_reason_added:
245 reason_str = _("local add");
246 break;
247 case svn_wc_conflict_reason_deleted:
248 reason_str = _("local delete");
249 break;
250 case svn_wc_conflict_reason_obstructed:
251 reason_str = _("local obstruction");
252 break;
253 default:
254 reason_str = apr_psprintf(pool, _("local %s"),
255 svn_token__to_word(map_conflict_reason_xml,
256 conflict->reason));
257 break;
258 }
259 switch (conflict->action)
260 {
261 case svn_wc_conflict_action_edit:
262 action_str = _("incoming edit");
263 break;
264 case svn_wc_conflict_action_add:
265 action_str = _("incoming add");
266 break;
267 case svn_wc_conflict_action_delete:
268 action_str = _("incoming delete");
269 break;
270 default:
271 action_str = apr_psprintf(pool, _("incoming %s"),
272 svn_token__to_word(map_conflict_action_xml,
273 conflict->action));
274 break;
275 }
276 SVN_ERR_ASSERT(reason_str && action_str);
277 *desc = apr_psprintf(pool, _("%s, %s %s"),
278 reason_str, action_str,
279 operation_str(conflict->operation));
280 return SVN_NO_ERROR;
281 }
282
283 svn_error_t *
svn_cl__get_human_readable_tree_conflict_description(const char ** desc,const svn_wc_conflict_description2_t * conflict,apr_pool_t * pool)284 svn_cl__get_human_readable_tree_conflict_description(
285 const char **desc,
286 const svn_wc_conflict_description2_t *conflict,
287 apr_pool_t *pool)
288 {
289 const char *action, *reason, *operation;
290 svn_node_kind_t incoming_kind;
291
292 /* Determine the node kind of the incoming change. */
293 incoming_kind = svn_node_unknown;
294 if (conflict->action == svn_wc_conflict_action_edit ||
295 conflict->action == svn_wc_conflict_action_delete)
296 {
297 /* Change is acting on 'src_left' version of the node. */
298 if (conflict->src_left_version)
299 incoming_kind = conflict->src_left_version->node_kind;
300 }
301 else if (conflict->action == svn_wc_conflict_action_add ||
302 conflict->action == svn_wc_conflict_action_replace)
303 {
304 /* Change is acting on 'src_right' version of the node.
305 *
306 * ### For 'replace', the node kind is ambiguous. However, src_left
307 * ### is NULL for replace, so we must use src_right. */
308 if (conflict->src_right_version)
309 incoming_kind = conflict->src_right_version->node_kind;
310 }
311
312 reason = local_reason_str(conflict->node_kind, conflict->reason,
313 conflict->operation);
314 action = incoming_action_str(incoming_kind, conflict->action);
315 operation = operation_str(conflict->operation);
316 SVN_ERR_ASSERT(operation);
317
318 if (action && reason)
319 {
320 *desc = apr_psprintf(pool, _("%s, %s %s"),
321 reason, action, operation);
322 }
323 else
324 {
325 /* A catch-all message for very rare or nominally impossible cases.
326 It will not be pretty, but is closer to an internal error than
327 an ordinary user-facing string. */
328 *desc = apr_psprintf(pool, _("local: %s %s incoming: %s %s %s"),
329 svn_node_kind_to_word(conflict->node_kind),
330 svn_token__to_word(map_conflict_reason_xml,
331 conflict->reason),
332 svn_node_kind_to_word(incoming_kind),
333 svn_token__to_word(map_conflict_action_xml,
334 conflict->action),
335 operation);
336 }
337 return SVN_NO_ERROR;
338 }
339
340 svn_error_t *
svn_cl__get_human_readable_action_description(const char ** desc,svn_wc_conflict_action_t action,svn_wc_operation_t operation,svn_node_kind_t kind,apr_pool_t * pool)341 svn_cl__get_human_readable_action_description(
342 const char **desc,
343 svn_wc_conflict_action_t action,
344 svn_wc_operation_t operation,
345 svn_node_kind_t kind,
346 apr_pool_t *pool)
347 {
348 const char *action_s, *operation_s;
349
350 action_s = incoming_action_str(kind, action);
351 operation_s = operation_str(operation);
352
353 SVN_ERR_ASSERT(operation_s);
354
355 *desc = apr_psprintf(pool, _("%s %s"),
356 action_s, operation_s);
357
358 return SVN_NO_ERROR;
359 }
360
361
362 /* Helper for svn_cl__append_tree_conflict_info_xml().
363 * Appends the attributes of the given VERSION to ATT_HASH.
364 * SIDE is the content of the version tag's side="..." attribute,
365 * currently one of "source-left" or "source-right".*/
366 static svn_error_t *
add_conflict_version_xml(svn_stringbuf_t ** pstr,const char * side,const svn_wc_conflict_version_t * version,apr_pool_t * pool)367 add_conflict_version_xml(svn_stringbuf_t **pstr,
368 const char *side,
369 const svn_wc_conflict_version_t *version,
370 apr_pool_t *pool)
371 {
372 apr_hash_t *att_hash = apr_hash_make(pool);
373
374
375 svn_hash_sets(att_hash, "side", side);
376
377 if (version->repos_url)
378 svn_hash_sets(att_hash, "repos-url", version->repos_url);
379
380 if (version->path_in_repos)
381 svn_hash_sets(att_hash, "path-in-repos", version->path_in_repos);
382
383 if (SVN_IS_VALID_REVNUM(version->peg_rev))
384 svn_hash_sets(att_hash, "revision", apr_ltoa(pool, version->peg_rev));
385
386 if (version->node_kind != svn_node_unknown)
387 svn_hash_sets(att_hash, "kind",
388 svn_cl__node_kind_str_xml(version->node_kind));
389
390 svn_xml_make_open_tag_hash(pstr, pool, svn_xml_self_closing,
391 "version", att_hash);
392 return SVN_NO_ERROR;
393 }
394
395
396 static svn_error_t *
append_tree_conflict_info_xml(svn_stringbuf_t * str,const svn_wc_conflict_description2_t * conflict,apr_pool_t * pool)397 append_tree_conflict_info_xml(svn_stringbuf_t *str,
398 const svn_wc_conflict_description2_t *conflict,
399 apr_pool_t *pool)
400 {
401 apr_hash_t *att_hash = apr_hash_make(pool);
402 const char *tmp;
403
404 svn_hash_sets(att_hash, "victim",
405 svn_dirent_basename(conflict->local_abspath, pool));
406
407 svn_hash_sets(att_hash, "kind",
408 svn_cl__node_kind_str_xml(conflict->node_kind));
409
410 svn_hash_sets(att_hash, "operation",
411 svn_cl__operation_str_xml(conflict->operation, pool));
412
413 tmp = svn_token__to_word(map_conflict_action_xml, conflict->action);
414 svn_hash_sets(att_hash, "action", tmp);
415
416 tmp = svn_token__to_word(map_conflict_reason_xml, conflict->reason);
417 svn_hash_sets(att_hash, "reason", tmp);
418
419 /* Open the tree-conflict tag. */
420 svn_xml_make_open_tag_hash(&str, pool, svn_xml_normal,
421 "tree-conflict", att_hash);
422
423 /* Add child tags for OLDER_VERSION and THEIR_VERSION. */
424
425 if (conflict->src_left_version)
426 SVN_ERR(add_conflict_version_xml(&str,
427 "source-left",
428 conflict->src_left_version,
429 pool));
430
431 if (conflict->src_right_version)
432 SVN_ERR(add_conflict_version_xml(&str,
433 "source-right",
434 conflict->src_right_version,
435 pool));
436
437 svn_xml_make_close_tag(&str, pool, "tree-conflict");
438
439 return SVN_NO_ERROR;
440 }
441
442 svn_error_t *
svn_cl__append_conflict_info_xml(svn_stringbuf_t * str,const svn_wc_conflict_description2_t * conflict,apr_pool_t * scratch_pool)443 svn_cl__append_conflict_info_xml(svn_stringbuf_t *str,
444 const svn_wc_conflict_description2_t *conflict,
445 apr_pool_t *scratch_pool)
446 {
447 apr_hash_t *att_hash;
448 const char *kind;
449 if (conflict->kind == svn_wc_conflict_kind_tree)
450 {
451 /* Uses other element type */
452 return svn_error_trace(
453 append_tree_conflict_info_xml(str, conflict, scratch_pool));
454 }
455
456 att_hash = apr_hash_make(scratch_pool);
457
458 svn_hash_sets(att_hash, "operation",
459 svn_cl__operation_str_xml(conflict->operation, scratch_pool));
460
461
462 kind = svn_token__to_word(map_conflict_kind_xml, conflict->kind);
463 svn_hash_sets(att_hash, "type", kind);
464
465 svn_hash_sets(att_hash, "operation",
466 svn_cl__operation_str_xml(conflict->operation, scratch_pool));
467
468
469 /* "<conflict>" */
470 svn_xml_make_open_tag_hash(&str, scratch_pool,
471 svn_xml_normal, "conflict", att_hash);
472
473 if (conflict->src_left_version)
474 SVN_ERR(add_conflict_version_xml(&str,
475 "source-left",
476 conflict->src_left_version,
477 scratch_pool));
478
479 if (conflict->src_right_version)
480 SVN_ERR(add_conflict_version_xml(&str,
481 "source-right",
482 conflict->src_right_version,
483 scratch_pool));
484
485 switch (conflict->kind)
486 {
487 case svn_wc_conflict_kind_text:
488 /* "<prev-base-file> xx </prev-base-file>" */
489 svn_cl__xml_tagged_cdata(&str, scratch_pool, "prev-base-file",
490 conflict->base_abspath);
491
492 /* "<prev-wc-file> xx </prev-wc-file>" */
493 svn_cl__xml_tagged_cdata(&str, scratch_pool, "prev-wc-file",
494 conflict->my_abspath);
495
496 /* "<cur-base-file> xx </cur-base-file>" */
497 svn_cl__xml_tagged_cdata(&str, scratch_pool, "cur-base-file",
498 conflict->their_abspath);
499
500 break;
501
502 case svn_wc_conflict_kind_property:
503 /* "<prop-file> xx </prop-file>" */
504 svn_cl__xml_tagged_cdata(&str, scratch_pool, "prop-file",
505 conflict->their_abspath);
506 break;
507
508 default:
509 case svn_wc_conflict_kind_tree:
510 SVN_ERR_MALFUNCTION(); /* Handled separately */
511 break;
512 }
513
514 /* "</conflict>" */
515 svn_xml_make_close_tag(&str, scratch_pool, "conflict");
516
517 return SVN_NO_ERROR;
518 }
519