2 * status.c: the command-line's portion of the "svn status" command
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
30 #include "svn_cmdline.h"
32 #include "svn_dirent_uri.h"
36 #include "svn_private_config.h"
37 #include "cl-conflicts.h"
38 #include "private/svn_wc_private.h"
40 /* Return the single character representation of STATUS */
42 generate_status_code(enum svn_wc_status_kind status)
46 case svn_wc_status_none: return ' ';
47 case svn_wc_status_normal: return ' ';
48 case svn_wc_status_added: return 'A';
49 case svn_wc_status_missing: return '!';
50 case svn_wc_status_incomplete: return '!';
51 case svn_wc_status_deleted: return 'D';
52 case svn_wc_status_replaced: return 'R';
53 case svn_wc_status_modified: return 'M';
54 case svn_wc_status_conflicted: return 'C';
55 case svn_wc_status_obstructed: return '~';
56 case svn_wc_status_ignored: return 'I';
57 case svn_wc_status_external: return 'X';
58 case svn_wc_status_unversioned: return '?';
63 /* Return the combined STATUS as shown in 'svn status' based
64 on the node status and text status */
65 static enum svn_wc_status_kind
66 combined_status(const svn_client_status_t *status)
68 enum svn_wc_status_kind new_status = status->node_status;
70 switch (status->node_status)
72 case svn_wc_status_conflicted:
73 if (!status->versioned && status->conflicted)
75 /* Report unversioned tree conflict victims as missing: '!' */
76 new_status = svn_wc_status_missing;
80 case svn_wc_status_modified:
81 /* This value might be the property status */
82 new_status = status->text_status;
91 /* Return the combined repository STATUS as shown in 'svn status' based
92 on the repository node status and repository text status */
93 static enum svn_wc_status_kind
94 combined_repos_status(const svn_client_status_t *status)
96 if (status->repos_node_status == svn_wc_status_modified)
97 return status->repos_text_status;
99 return status->repos_node_status;
102 /* Return the single character representation of the switched column
105 generate_switch_column_code(const svn_client_status_t *status)
107 if (status->switched)
109 else if (status->file_external)
115 /* Return the detailed string representation of STATUS */
117 generate_status_desc(enum svn_wc_status_kind status)
121 case svn_wc_status_none: return "none";
122 case svn_wc_status_normal: return "normal";
123 case svn_wc_status_added: return "added";
124 case svn_wc_status_missing: return "missing";
125 case svn_wc_status_incomplete: return "incomplete";
126 case svn_wc_status_deleted: return "deleted";
127 case svn_wc_status_replaced: return "replaced";
128 case svn_wc_status_modified: return "modified";
129 case svn_wc_status_conflicted: return "conflicted";
130 case svn_wc_status_obstructed: return "obstructed";
131 case svn_wc_status_ignored: return "ignored";
132 case svn_wc_status_external: return "external";
133 case svn_wc_status_unversioned: return "unversioned";
135 SVN_ERR_MALFUNCTION_NO_RETURN();
139 /* Make a relative path containing '..' elements as needed.
140 TARGET_ABSPATH shall be the absolute version of TARGET_PATH.
141 TARGET_ABSPATH, TARGET_PATH and LOCAL_ABSPATH shall be canonical
143 If above conditions are met, a relative path that leads to PATH
144 from TARGET_PATH is returned, but there is no error checking involved.
146 The returned path is allocated from RESULT_POOL, all other
147 allocations are made in SCRATCH_POOL.
149 Note that it is not possible to just join the resulting path to
150 reconstruct the real path as the "../" paths are relative from
151 a different base than the normal relative paths!
154 make_relpath(const char *target_abspath,
155 const char *target_path,
156 const char *local_abspath,
157 apr_pool_t *result_pool,
158 apr_pool_t *scratch_pool)
161 const char *parent_dir_els = "";
162 const char *t_relpath;
163 const char *p_relpath;
166 SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath));
169 t_relpath = svn_dirent_skip_ancestor(target_abspath, local_abspath);
172 return svn_dirent_join(target_path, t_relpath, result_pool);
175 * relative_to_path = /a/b/c
177 * result = ../../x/y/z
179 * Another example (Windows specific):
180 * relative_to_path = F:/wc
184 /* Skip the common ancestor of both paths, here '/a'. */
185 la = svn_dirent_get_longest_ancestor(target_abspath, local_abspath,
189 /* Nothing in common: E.g. C:/ vs F:/ on Windows */
190 return apr_pstrdup(result_pool, local_abspath);
192 t_relpath = svn_dirent_skip_ancestor(la, target_abspath);
193 p_relpath = svn_dirent_skip_ancestor(la, local_abspath);
195 /* In above example, we'd now have:
196 * relative_to_path = b/c
199 /* Count the elements of relative_to_path and prepend as many '..' elements
203 t_relpath = svn_dirent_dirname(t_relpath, scratch_pool);
204 parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool);
207 /* This returns a ../ style path relative from the status target */
208 return svn_dirent_join(parent_dir_els, p_relpath, result_pool);
212 /* Print STATUS and PATH in a format determined by DETAILED and
213 SHOW_LAST_COMMITTED. */
215 print_status(const char *target_abspath,
216 const char *target_path,
218 svn_boolean_t detailed,
219 svn_boolean_t show_last_committed,
220 svn_boolean_t repos_locks,
221 const svn_client_status_t *status,
222 unsigned int *text_conflicts,
223 unsigned int *prop_conflicts,
224 unsigned int *tree_conflicts,
225 svn_client_ctx_t *ctx,
228 enum svn_wc_status_kind node_status = status->node_status;
229 enum svn_wc_status_kind prop_status = status->prop_status;
230 char tree_status_code = ' ';
231 const char *tree_desc_line = "";
232 const char *moved_from_line = "";
233 const char *moved_to_line = "";
235 /* For historic reasons svn ignores the property status for added nodes, even
236 if these nodes were copied and have local property changes.
238 Note that it doesn't do this on replacements, or children of copies.
240 ### Our test suite would catch more errors if we reported property
241 changes on copies. */
242 if (node_status == svn_wc_status_added)
243 prop_status = svn_wc_status_none;
245 /* To indicate this node is the victim of a tree conflict, we show
246 'C' in the tree-conflict column, overriding any other status.
247 We also print a separate line describing the nature of the tree
249 if (status->conflicted)
252 const char *local_abspath = status->local_abspath;
253 svn_boolean_t text_conflicted;
254 svn_boolean_t prop_conflicted;
255 svn_boolean_t tree_conflicted;
257 if (status->versioned)
261 err = svn_wc_conflicted_p3(&text_conflicted,
263 &tree_conflicted, ctx->wc_ctx,
264 local_abspath, pool);
266 if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
268 svn_error_clear(err);
269 text_conflicted = FALSE;
270 prop_conflicted = FALSE;
271 tree_conflicted = FALSE;
278 text_conflicted = FALSE;
279 prop_conflicted = FALSE;
280 tree_conflicted = TRUE;
285 svn_client_conflict_t *tree_conflict;
287 SVN_ERR(svn_client_conflict_get(&tree_conflict, local_abspath,
289 tree_status_code = 'C';
290 SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
291 &desc, tree_conflict, pool));
292 tree_desc_line = apr_psprintf(pool, "\n > %s", desc);
295 else if (text_conflicted)
297 else if (prop_conflicted)
301 /* Note that moved-from and moved-to information is only available in STATUS
302 * for (op-)roots of a move. Those are exactly the nodes we want to show
303 * move info for in 'svn status'. See also comments in svn_wc_status3_t. */
304 if (status->moved_from_abspath && status->moved_to_abspath &&
305 strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0)
309 relpath = make_relpath(target_abspath, target_path,
310 status->moved_from_abspath,
312 relpath = svn_dirent_local_style(relpath, pool);
313 moved_from_line = apr_pstrcat(pool, "\n > ",
315 _("swapped places with %s"),
319 else if (status->moved_from_abspath || status->moved_to_abspath)
323 if (status->moved_from_abspath)
325 relpath = make_relpath(target_abspath, target_path,
326 status->moved_from_abspath,
328 relpath = svn_dirent_local_style(relpath, pool);
329 moved_from_line = apr_pstrcat(pool, "\n > ",
330 apr_psprintf(pool, _("moved from %s"),
335 if (status->moved_to_abspath)
337 relpath = make_relpath(target_abspath, target_path,
338 status->moved_to_abspath,
340 relpath = svn_dirent_local_style(relpath, pool);
341 moved_to_line = apr_pstrcat(pool, "\n > ",
342 apr_psprintf(pool, _("moved to %s"),
348 path = svn_dirent_local_style(path, pool);
352 char ood_status, lock_status;
353 const char *working_rev;
355 if (! status->versioned)
357 else if (status->copied
358 || ! SVN_IS_VALID_REVNUM(status->revision))
361 working_rev = apr_psprintf(pool, "%ld", status->revision);
363 if (status->repos_node_status != svn_wc_status_none)
370 if (status->repos_lock)
374 if (strcmp(status->repos_lock->token, status->lock->token)
383 else if (status->lock)
389 lock_status = (status->lock) ? 'K' : ' ';
391 if (show_last_committed)
393 const char *commit_rev;
394 const char *commit_author;
396 if (SVN_IS_VALID_REVNUM(status->changed_rev))
397 commit_rev = apr_psprintf(pool, "%ld", status->changed_rev);
398 else if (status->versioned)
403 if (status->changed_author)
404 commit_author = status->changed_author;
405 else if (status->versioned)
406 commit_author = " ? ";
411 (svn_cmdline_printf(pool,
412 "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n",
413 generate_status_code(combined_status(status)),
414 generate_status_code(prop_status),
415 status->wc_is_locked ? 'L' : ' ',
416 status->copied ? '+' : ' ',
417 generate_switch_column_code(status),
431 svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%s%s\n",
432 generate_status_code(combined_status(status)),
433 generate_status_code(prop_status),
434 status->wc_is_locked ? 'L' : ' ',
435 status->copied ? '+' : ' ',
436 generate_switch_column_code(status),
448 svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n",
449 generate_status_code(combined_status(status)),
450 generate_status_code(prop_status),
451 status->wc_is_locked ? 'L' : ' ',
452 status->copied ? '+' : ' ',
453 generate_switch_column_code(status),
462 return svn_cmdline_fflush(stdout);
467 svn_cl__print_status_xml(const char *target_abspath,
468 const char *target_path,
470 const svn_client_status_t *status,
471 svn_client_ctx_t *ctx,
474 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
475 apr_hash_t *att_hash;
476 const char *local_abspath = status->local_abspath;
477 svn_boolean_t tree_conflicted = FALSE;
479 if (status->node_status == svn_wc_status_none
480 && status->repos_node_status == svn_wc_status_none)
483 if (status->conflicted)
484 SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
485 ctx->wc_ctx, local_abspath, pool));
487 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
488 "path", svn_dirent_local_style(path, pool),
491 att_hash = apr_hash_make(pool);
492 svn_hash_sets(att_hash, "item",
493 generate_status_desc(combined_status(status)));
495 svn_hash_sets(att_hash, "props",
496 generate_status_desc(
497 (status->node_status != svn_wc_status_deleted)
498 ? status->prop_status
499 : svn_wc_status_none));
500 if (status->wc_is_locked)
501 svn_hash_sets(att_hash, "wc-locked", "true");
503 svn_hash_sets(att_hash, "copied", "true");
504 if (status->switched)
505 svn_hash_sets(att_hash, "switched", "true");
506 if (status->file_external)
507 svn_hash_sets(att_hash, "file-external", "true");
508 if (status->versioned && ! status->copied)
509 svn_hash_sets(att_hash, "revision",
510 apr_psprintf(pool, "%ld", status->revision));
512 svn_hash_sets(att_hash, "tree-conflicted", "true");
513 if (status->moved_from_abspath || status->moved_to_abspath)
517 if (status->moved_from_abspath)
519 relpath = make_relpath(target_abspath, target_path,
520 status->moved_from_abspath,
522 relpath = svn_dirent_local_style(relpath, pool);
523 svn_hash_sets(att_hash, "moved-from", relpath);
525 if (status->moved_to_abspath)
527 relpath = make_relpath(target_abspath, target_path,
528 status->moved_to_abspath,
530 relpath = svn_dirent_local_style(relpath, pool);
531 svn_hash_sets(att_hash, "moved-to", relpath);
534 svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status",
537 if (SVN_IS_VALID_REVNUM(status->changed_rev))
539 svn_cl__print_xml_commit(&sb, status->changed_rev,
540 status->changed_author,
541 svn_time_to_cstring(status->changed_date,
547 svn_cl__print_xml_lock(&sb, status->lock, pool);
549 svn_xml_make_close_tag(&sb, pool, "wc-status");
551 if (status->repos_node_status != svn_wc_status_none
552 || status->repos_lock)
554 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status",
556 generate_status_desc(combined_repos_status(status)),
558 generate_status_desc(status->repos_prop_status),
560 if (status->repos_lock)
561 svn_cl__print_xml_lock(&sb, status->repos_lock, pool);
563 svn_xml_make_close_tag(&sb, pool, "repos-status");
566 svn_xml_make_close_tag(&sb, pool, "entry");
568 return svn_cl__error_checked_fputs(sb->data, stdout);
571 /* Called by status-cmd.c */
573 svn_cl__print_status(const char *target_abspath,
574 const char *target_path,
576 const svn_client_status_t *status,
577 svn_boolean_t suppress_externals_placeholders,
578 svn_boolean_t detailed,
579 svn_boolean_t show_last_committed,
580 svn_boolean_t skip_unrecognized,
581 svn_boolean_t repos_locks,
582 unsigned int *text_conflicts,
583 unsigned int *prop_conflicts,
584 unsigned int *tree_conflicts,
585 svn_client_ctx_t *ctx,
589 || (skip_unrecognized
590 && !(status->versioned
591 || status->conflicted
592 || status->node_status == svn_wc_status_external))
593 || (status->node_status == svn_wc_status_none
594 && status->repos_node_status == svn_wc_status_none))
597 /* If we're trying not to print boring "X /path/to/external"
599 if (suppress_externals_placeholders)
601 /* ... skip regular externals unmodified in the repository. */
602 if ((status->node_status == svn_wc_status_external)
603 && (status->repos_node_status == svn_wc_status_none)
604 && (! status->conflicted))
607 /* ... skip file externals that aren't modified locally or
608 remotely, changelisted, or locked (in either sense of the
610 if ((status->file_external)
611 && (status->repos_node_status == svn_wc_status_none)
612 && ((status->node_status == svn_wc_status_normal)
613 || (status->node_status == svn_wc_status_none))
614 && ((status->prop_status == svn_wc_status_normal)
615 || (status->prop_status == svn_wc_status_none))
616 && (! status->changelist)
618 && (! status->wc_is_locked)
619 && (! status->conflicted))
623 return print_status(target_abspath, target_path, path,
624 detailed, show_last_committed, repos_locks, status,
625 text_conflicts, prop_conflicts, tree_conflicts,