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 RELATIVE_TO_PATH must be the path to a directory (not a file!) and
141 TARGET_PATH must be the path to any file or directory. Both
142 RELATIVE_TO_PATH and TARGET_PATH must be based on the same parent path,
143 i.e. they can either both be absolute or they can both be relative to the
144 same parent directory. Both paths are expected to be canonical.
146 If above conditions are met, a relative path that leads to TARGET_ABSPATH
147 from RELATIVE_TO_PATH is returned, but there is no error checking involved.
149 The returned path is allocated from RESULT_POOL, all other allocations are
150 made in SCRATCH_POOL. */
152 make_relpath(const char *relative_to_path,
153 const char *target_path,
154 apr_pool_t *result_pool,
155 apr_pool_t *scratch_pool)
158 const char *parent_dir_els = "";
161 * relative_to_path = /a/b/c
162 * target_path = /a/x/y/z
163 * result = ../../x/y/z
165 * Another example (Windows specific):
166 * relative_to_path = F:/wc
167 * target_path = C:/wc
171 /* Skip the common ancestor of both paths, here '/a'. */
172 la = svn_dirent_get_longest_ancestor(relative_to_path, target_path,
176 /* Nothing in common: E.g. C:/ vs F:/ on Windows */
177 return apr_pstrdup(result_pool, target_path);
179 relative_to_path = svn_dirent_skip_ancestor(la, relative_to_path);
180 target_path = svn_dirent_skip_ancestor(la, target_path);
182 /* In above example, we'd now have:
183 * relative_to_path = b/c
184 * target_path = x/y/z */
186 /* Count the elements of relative_to_path and prepend as many '..' elements
188 while (*relative_to_path)
190 svn_dirent_split(&relative_to_path, NULL, relative_to_path,
192 parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool);
195 return svn_dirent_join(parent_dir_els, target_path, result_pool);
199 /* Print STATUS and PATH in a format determined by DETAILED and
200 SHOW_LAST_COMMITTED. */
202 print_status(const char *cwd_abspath, const char *path,
203 svn_boolean_t detailed,
204 svn_boolean_t show_last_committed,
205 svn_boolean_t repos_locks,
206 const svn_client_status_t *status,
207 unsigned int *text_conflicts,
208 unsigned int *prop_conflicts,
209 unsigned int *tree_conflicts,
210 svn_client_ctx_t *ctx,
213 enum svn_wc_status_kind node_status = status->node_status;
214 enum svn_wc_status_kind prop_status = status->prop_status;
215 char tree_status_code = ' ';
216 const char *tree_desc_line = "";
217 const char *moved_from_line = "";
218 const char *moved_to_line = "";
220 path = make_relpath(cwd_abspath, path, pool, pool);
222 /* For historic reasons svn ignores the property status for added nodes, even
223 if these nodes were copied and have local property changes.
225 Note that it doesn't do this on replacements, or children of copies.
227 ### Our test suite would catch more errors if we reported property
228 changes on copies. */
229 if (node_status == svn_wc_status_added)
230 prop_status = svn_wc_status_none;
232 /* To indicate this node is the victim of a tree conflict, we show
233 'C' in the tree-conflict column, overriding any other status.
234 We also print a separate line describing the nature of the tree
236 if (status->conflicted)
239 const char *local_abspath = status->local_abspath;
240 svn_boolean_t text_conflicted;
241 svn_boolean_t prop_conflicted;
242 svn_boolean_t tree_conflicted;
244 if (status->versioned)
248 err = svn_wc_conflicted_p3(&text_conflicted,
250 &tree_conflicted, ctx->wc_ctx,
251 local_abspath, pool);
253 if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
255 svn_error_clear(err);
256 text_conflicted = FALSE;
257 prop_conflicted = FALSE;
258 tree_conflicted = FALSE;
265 text_conflicted = FALSE;
266 prop_conflicted = FALSE;
267 tree_conflicted = TRUE;
272 const svn_wc_conflict_description2_t *tree_conflict;
273 SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx,
274 local_abspath, pool, pool));
275 SVN_ERR_ASSERT(tree_conflict != NULL);
277 tree_status_code = 'C';
278 SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
279 &desc, tree_conflict, pool));
280 tree_desc_line = apr_psprintf(pool, "\n > %s", desc);
283 else if (text_conflicted)
285 else if (prop_conflicted)
289 /* Note that moved-from and moved-to information is only available in STATUS
290 * for (op-)roots of a move. Those are exactly the nodes we want to show
291 * move info for in 'svn status'. See also comments in svn_wc_status3_t. */
292 if (status->moved_from_abspath && status->moved_to_abspath &&
293 strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0)
297 relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
299 relpath = svn_dirent_local_style(relpath, pool);
300 moved_from_line = apr_pstrcat(pool, "\n > ",
302 _("swapped places with %s"),
306 else if (status->moved_from_abspath || status->moved_to_abspath)
310 if (status->moved_from_abspath)
312 relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
314 relpath = svn_dirent_local_style(relpath, pool);
315 moved_from_line = apr_pstrcat(pool, "\n > ",
316 apr_psprintf(pool, _("moved from %s"),
321 if (status->moved_to_abspath)
323 relpath = make_relpath(cwd_abspath, status->moved_to_abspath,
325 relpath = svn_dirent_local_style(relpath, pool);
326 moved_to_line = apr_pstrcat(pool, "\n > ",
327 apr_psprintf(pool, _("moved to %s"),
335 char ood_status, lock_status;
336 const char *working_rev;
338 if (! status->versioned)
340 else if (status->copied
341 || ! SVN_IS_VALID_REVNUM(status->revision))
344 working_rev = apr_psprintf(pool, "%ld", status->revision);
346 if (status->repos_node_status != svn_wc_status_none)
353 if (status->repos_lock)
357 if (strcmp(status->repos_lock->token, status->lock->token)
366 else if (status->lock)
372 lock_status = (status->lock) ? 'K' : ' ';
374 if (show_last_committed)
376 const char *commit_rev;
377 const char *commit_author;
379 if (SVN_IS_VALID_REVNUM(status->changed_rev))
380 commit_rev = apr_psprintf(pool, "%ld", status->changed_rev);
381 else if (status->versioned)
386 if (status->changed_author)
387 commit_author = status->changed_author;
388 else if (status->versioned)
389 commit_author = " ? ";
394 (svn_cmdline_printf(pool,
395 "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n",
396 generate_status_code(combined_status(status)),
397 generate_status_code(prop_status),
398 status->wc_is_locked ? 'L' : ' ',
399 status->copied ? '+' : ' ',
400 generate_switch_column_code(status),
414 svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%s%s\n",
415 generate_status_code(combined_status(status)),
416 generate_status_code(prop_status),
417 status->wc_is_locked ? 'L' : ' ',
418 status->copied ? '+' : ' ',
419 generate_switch_column_code(status),
431 svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %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),
445 return svn_cmdline_fflush(stdout);
450 svn_cl__print_status_xml(const char *cwd_abspath,
452 const svn_client_status_t *status,
453 svn_client_ctx_t *ctx,
456 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
457 apr_hash_t *att_hash;
458 const char *local_abspath = status->local_abspath;
459 svn_boolean_t tree_conflicted = FALSE;
461 if (status->node_status == svn_wc_status_none
462 && status->repos_node_status == svn_wc_status_none)
465 if (status->conflicted)
466 SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
467 ctx->wc_ctx, local_abspath, pool));
469 path = make_relpath(cwd_abspath, path, pool, pool);
471 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
472 "path", svn_dirent_local_style(path, pool), NULL);
474 att_hash = apr_hash_make(pool);
475 svn_hash_sets(att_hash, "item",
476 generate_status_desc(combined_status(status)));
478 svn_hash_sets(att_hash, "props",
479 generate_status_desc(
480 (status->node_status != svn_wc_status_deleted)
481 ? status->prop_status
482 : svn_wc_status_none));
483 if (status->wc_is_locked)
484 svn_hash_sets(att_hash, "wc-locked", "true");
486 svn_hash_sets(att_hash, "copied", "true");
487 if (status->switched)
488 svn_hash_sets(att_hash, "switched", "true");
489 if (status->file_external)
490 svn_hash_sets(att_hash, "file-external", "true");
491 if (status->versioned && ! status->copied)
492 svn_hash_sets(att_hash, "revision",
493 apr_psprintf(pool, "%ld", status->revision));
495 svn_hash_sets(att_hash, "tree-conflicted", "true");
496 if (status->moved_from_abspath || status->moved_to_abspath)
500 if (status->moved_from_abspath)
502 relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
504 relpath = svn_dirent_local_style(relpath, pool);
505 svn_hash_sets(att_hash, "moved-from", relpath);
507 if (status->moved_to_abspath)
509 relpath = make_relpath(cwd_abspath, status->moved_to_abspath,
511 relpath = svn_dirent_local_style(relpath, pool);
512 svn_hash_sets(att_hash, "moved-to", relpath);
515 svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status",
518 if (SVN_IS_VALID_REVNUM(status->changed_rev))
520 svn_cl__print_xml_commit(&sb, status->changed_rev,
521 status->changed_author,
522 svn_time_to_cstring(status->changed_date,
528 svn_cl__print_xml_lock(&sb, status->lock, pool);
530 svn_xml_make_close_tag(&sb, pool, "wc-status");
532 if (status->repos_node_status != svn_wc_status_none
533 || status->repos_lock)
535 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status",
537 generate_status_desc(combined_repos_status(status)),
539 generate_status_desc(status->repos_prop_status),
541 if (status->repos_lock)
542 svn_cl__print_xml_lock(&sb, status->repos_lock, pool);
544 svn_xml_make_close_tag(&sb, pool, "repos-status");
547 svn_xml_make_close_tag(&sb, pool, "entry");
549 return svn_cl__error_checked_fputs(sb->data, stdout);
552 /* Called by status-cmd.c */
554 svn_cl__print_status(const char *cwd_abspath,
556 const svn_client_status_t *status,
557 svn_boolean_t suppress_externals_placeholders,
558 svn_boolean_t detailed,
559 svn_boolean_t show_last_committed,
560 svn_boolean_t skip_unrecognized,
561 svn_boolean_t repos_locks,
562 unsigned int *text_conflicts,
563 unsigned int *prop_conflicts,
564 unsigned int *tree_conflicts,
565 svn_client_ctx_t *ctx,
569 || (skip_unrecognized
570 && !(status->versioned
571 || status->conflicted
572 || status->node_status == svn_wc_status_external))
573 || (status->node_status == svn_wc_status_none
574 && status->repos_node_status == svn_wc_status_none))
577 /* If we're trying not to print boring "X /path/to/external"
579 if (suppress_externals_placeholders)
581 /* ... skip regular externals unmodified in the repository. */
582 if ((status->node_status == svn_wc_status_external)
583 && (status->repos_node_status == svn_wc_status_none)
584 && (! status->conflicted))
587 /* ... skip file externals that aren't modified locally or
588 remotely, changelisted, or locked (in either sense of the
590 if ((status->file_external)
591 && (status->repos_node_status == svn_wc_status_none)
592 && ((status->node_status == svn_wc_status_normal)
593 || (status->node_status == svn_wc_status_none))
594 && ((status->prop_status == svn_wc_status_normal)
595 || (status->prop_status == svn_wc_status_none))
596 && (! status->changelist)
598 && (! status->wc_is_locked)
599 && (! status->conflicted))
603 return print_status(cwd_abspath, svn_dirent_local_style(path, pool),
604 detailed, show_last_committed, repos_locks, status,
605 text_conflicts, prop_conflicts, tree_conflicts,