diff options
Diffstat (limited to 'subversion/svn/status.c')
-rw-r--r-- | subversion/svn/status.c | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/subversion/svn/status.c b/subversion/svn/status.c new file mode 100644 index 0000000000000..3679bfff9f8c1 --- /dev/null +++ b/subversion/svn/status.c @@ -0,0 +1,607 @@ +/* + * status.c: the command-line's portion of the "svn status" command + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ +#include "svn_hash.h" +#include "svn_cmdline.h" +#include "svn_wc.h" +#include "svn_dirent_uri.h" +#include "svn_xml.h" +#include "svn_time.h" +#include "cl.h" +#include "svn_private_config.h" +#include "cl-conflicts.h" +#include "private/svn_wc_private.h" + +/* Return the single character representation of STATUS */ +static char +generate_status_code(enum svn_wc_status_kind status) +{ + switch (status) + { + case svn_wc_status_none: return ' '; + case svn_wc_status_normal: return ' '; + case svn_wc_status_added: return 'A'; + case svn_wc_status_missing: return '!'; + case svn_wc_status_incomplete: return '!'; + case svn_wc_status_deleted: return 'D'; + case svn_wc_status_replaced: return 'R'; + case svn_wc_status_modified: return 'M'; + case svn_wc_status_conflicted: return 'C'; + case svn_wc_status_obstructed: return '~'; + case svn_wc_status_ignored: return 'I'; + case svn_wc_status_external: return 'X'; + case svn_wc_status_unversioned: return '?'; + default: return '?'; + } +} + +/* Return the combined STATUS as shown in 'svn status' based + on the node status and text status */ +static enum svn_wc_status_kind +combined_status(const svn_client_status_t *status) +{ + enum svn_wc_status_kind new_status = status->node_status; + + switch (status->node_status) + { + case svn_wc_status_conflicted: + if (!status->versioned && status->conflicted) + { + /* Report unversioned tree conflict victims as missing: '!' */ + new_status = svn_wc_status_missing; + break; + } + /* fall through */ + case svn_wc_status_modified: + /* This value might be the property status */ + new_status = status->text_status; + break; + default: + break; + } + + return new_status; +} + +/* Return the combined repository STATUS as shown in 'svn status' based + on the repository node status and repository text status */ +static enum svn_wc_status_kind +combined_repos_status(const svn_client_status_t *status) +{ + if (status->repos_node_status == svn_wc_status_modified) + return status->repos_text_status; + + return status->repos_node_status; +} + +/* Return the single character representation of the switched column + status. */ +static char +generate_switch_column_code(const svn_client_status_t *status) +{ + if (status->switched) + return 'S'; + else if (status->file_external) + return 'X'; + else + return ' '; +} + +/* Return the detailed string representation of STATUS */ +static const char * +generate_status_desc(enum svn_wc_status_kind status) +{ + switch (status) + { + case svn_wc_status_none: return "none"; + case svn_wc_status_normal: return "normal"; + case svn_wc_status_added: return "added"; + case svn_wc_status_missing: return "missing"; + case svn_wc_status_incomplete: return "incomplete"; + case svn_wc_status_deleted: return "deleted"; + case svn_wc_status_replaced: return "replaced"; + case svn_wc_status_modified: return "modified"; + case svn_wc_status_conflicted: return "conflicted"; + case svn_wc_status_obstructed: return "obstructed"; + case svn_wc_status_ignored: return "ignored"; + case svn_wc_status_external: return "external"; + case svn_wc_status_unversioned: return "unversioned"; + default: + SVN_ERR_MALFUNCTION_NO_RETURN(); + } +} + +/* Make a relative path containing '..' elements as needed. + RELATIVE_TO_PATH must be the path to a directory (not a file!) and + TARGET_PATH must be the path to any file or directory. Both + RELATIVE_TO_PATH and TARGET_PATH must be based on the same parent path, + i.e. they can either both be absolute or they can both be relative to the + same parent directory. Both paths are expected to be canonical. + + If above conditions are met, a relative path that leads to TARGET_ABSPATH + from RELATIVE_TO_PATH is returned, but there is no error checking involved. + + The returned path is allocated from RESULT_POOL, all other allocations are + made in SCRATCH_POOL. */ +static const char * +make_relpath(const char *relative_to_path, + const char *target_path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *la; + const char *parent_dir_els = ""; + + /* An example: + * relative_to_path = /a/b/c + * target_path = /a/x/y/z + * result = ../../x/y/z + * + * Another example (Windows specific): + * relative_to_path = F:/wc + * target_path = C:/wc + * result = C:/wc + */ + + /* Skip the common ancestor of both paths, here '/a'. */ + la = svn_dirent_get_longest_ancestor(relative_to_path, target_path, + scratch_pool); + if (*la == '\0') + { + /* Nothing in common: E.g. C:/ vs F:/ on Windows */ + return apr_pstrdup(result_pool, target_path); + } + relative_to_path = svn_dirent_skip_ancestor(la, relative_to_path); + target_path = svn_dirent_skip_ancestor(la, target_path); + + /* In above example, we'd now have: + * relative_to_path = b/c + * target_path = x/y/z */ + + /* Count the elements of relative_to_path and prepend as many '..' elements + * to target_path. */ + while (*relative_to_path) + { + svn_dirent_split(&relative_to_path, NULL, relative_to_path, + scratch_pool); + parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool); + } + + return svn_dirent_join(parent_dir_els, target_path, result_pool); +} + + +/* Print STATUS and PATH in a format determined by DETAILED and + SHOW_LAST_COMMITTED. */ +static svn_error_t * +print_status(const char *cwd_abspath, const char *path, + svn_boolean_t detailed, + svn_boolean_t show_last_committed, + svn_boolean_t repos_locks, + const svn_client_status_t *status, + unsigned int *text_conflicts, + unsigned int *prop_conflicts, + unsigned int *tree_conflicts, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + enum svn_wc_status_kind node_status = status->node_status; + enum svn_wc_status_kind prop_status = status->prop_status; + char tree_status_code = ' '; + const char *tree_desc_line = ""; + const char *moved_from_line = ""; + const char *moved_to_line = ""; + + path = make_relpath(cwd_abspath, path, pool, pool); + + /* For historic reasons svn ignores the property status for added nodes, even + if these nodes were copied and have local property changes. + + Note that it doesn't do this on replacements, or children of copies. + + ### Our test suite would catch more errors if we reported property + changes on copies. */ + if (node_status == svn_wc_status_added) + prop_status = svn_wc_status_none; + + /* To indicate this node is the victim of a tree conflict, we show + 'C' in the tree-conflict column, overriding any other status. + We also print a separate line describing the nature of the tree + conflict. */ + if (status->conflicted) + { + const char *desc; + const char *local_abspath = status->local_abspath; + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + + if (status->versioned) + { + svn_error_t *err; + + err = svn_wc_conflicted_p3(&text_conflicted, + &prop_conflicted, + &tree_conflicted, ctx->wc_ctx, + local_abspath, pool); + + if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) + { + svn_error_clear(err); + text_conflicted = FALSE; + prop_conflicted = FALSE; + tree_conflicted = FALSE; + } + else + SVN_ERR(err); + } + else + { + text_conflicted = FALSE; + prop_conflicted = FALSE; + tree_conflicted = TRUE; + } + + if (tree_conflicted) + { + const svn_wc_conflict_description2_t *tree_conflict; + SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx, + local_abspath, pool, pool)); + SVN_ERR_ASSERT(tree_conflict != NULL); + + tree_status_code = 'C'; + SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( + &desc, tree_conflict, pool)); + tree_desc_line = apr_psprintf(pool, "\n > %s", desc); + (*tree_conflicts)++; + } + else if (text_conflicted) + (*text_conflicts)++; + else if (prop_conflicted) + (*prop_conflicts)++; + } + + /* Note that moved-from and moved-to information is only available in STATUS + * for (op-)roots of a move. Those are exactly the nodes we want to show + * move info for in 'svn status'. See also comments in svn_wc_status3_t. */ + if (status->moved_from_abspath && status->moved_to_abspath && + strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0) + { + const char *relpath; + + relpath = make_relpath(cwd_abspath, status->moved_from_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + moved_from_line = apr_pstrcat(pool, "\n > ", + apr_psprintf(pool, + _("swapped places with %s"), + relpath), + (char *)NULL); + } + else if (status->moved_from_abspath || status->moved_to_abspath) + { + const char *relpath; + + if (status->moved_from_abspath) + { + relpath = make_relpath(cwd_abspath, status->moved_from_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + moved_from_line = apr_pstrcat(pool, "\n > ", + apr_psprintf(pool, _("moved from %s"), + relpath), + (char *)NULL); + } + + if (status->moved_to_abspath) + { + relpath = make_relpath(cwd_abspath, status->moved_to_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + moved_to_line = apr_pstrcat(pool, "\n > ", + apr_psprintf(pool, _("moved to %s"), + relpath), + (char *)NULL); + } + } + + if (detailed) + { + char ood_status, lock_status; + const char *working_rev; + + if (! status->versioned) + working_rev = ""; + else if (status->copied + || ! SVN_IS_VALID_REVNUM(status->revision)) + working_rev = "-"; + else + working_rev = apr_psprintf(pool, "%ld", status->revision); + + if (status->repos_node_status != svn_wc_status_none) + ood_status = '*'; + else + ood_status = ' '; + + if (repos_locks) + { + if (status->repos_lock) + { + if (status->lock) + { + if (strcmp(status->repos_lock->token, status->lock->token) + == 0) + lock_status = 'K'; + else + lock_status = 'T'; + } + else + lock_status = 'O'; + } + else if (status->lock) + lock_status = 'B'; + else + lock_status = ' '; + } + else + lock_status = (status->lock) ? 'K' : ' '; + + if (show_last_committed) + { + const char *commit_rev; + const char *commit_author; + + if (SVN_IS_VALID_REVNUM(status->changed_rev)) + commit_rev = apr_psprintf(pool, "%ld", status->changed_rev); + else if (status->versioned) + commit_rev = " ? "; + else + commit_rev = ""; + + if (status->changed_author) + commit_author = status->changed_author; + else if (status->versioned) + commit_author = " ? "; + else + commit_author = ""; + + SVN_ERR + (svn_cmdline_printf(pool, + "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n", + generate_status_code(combined_status(status)), + generate_status_code(prop_status), + status->wc_is_locked ? 'L' : ' ', + status->copied ? '+' : ' ', + generate_switch_column_code(status), + lock_status, + tree_status_code, + ood_status, + working_rev, + commit_rev, + commit_author, + path, + moved_to_line, + moved_from_line, + tree_desc_line)); + } + else + SVN_ERR( + svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%s%s\n", + generate_status_code(combined_status(status)), + generate_status_code(prop_status), + status->wc_is_locked ? 'L' : ' ', + status->copied ? '+' : ' ', + generate_switch_column_code(status), + lock_status, + tree_status_code, + ood_status, + working_rev, + path, + moved_to_line, + moved_from_line, + tree_desc_line)); + } + else + SVN_ERR( + svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n", + generate_status_code(combined_status(status)), + generate_status_code(prop_status), + status->wc_is_locked ? 'L' : ' ', + status->copied ? '+' : ' ', + generate_switch_column_code(status), + ((status->lock) + ? 'K' : ' '), + tree_status_code, + path, + moved_to_line, + moved_from_line, + tree_desc_line)); + + return svn_cmdline_fflush(stdout); +} + + +svn_error_t * +svn_cl__print_status_xml(const char *cwd_abspath, + const char *path, + const svn_client_status_t *status, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); + apr_hash_t *att_hash; + const char *local_abspath = status->local_abspath; + svn_boolean_t tree_conflicted = FALSE; + + if (status->node_status == svn_wc_status_none + && status->repos_node_status == svn_wc_status_none) + return SVN_NO_ERROR; + + if (status->conflicted) + SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, + ctx->wc_ctx, local_abspath, pool)); + + path = make_relpath(cwd_abspath, path, pool, pool); + + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", + "path", svn_dirent_local_style(path, pool), NULL); + + att_hash = apr_hash_make(pool); + svn_hash_sets(att_hash, "item", + generate_status_desc(combined_status(status))); + + svn_hash_sets(att_hash, "props", + generate_status_desc( + (status->node_status != svn_wc_status_deleted) + ? status->prop_status + : svn_wc_status_none)); + if (status->wc_is_locked) + svn_hash_sets(att_hash, "wc-locked", "true"); + if (status->copied) + svn_hash_sets(att_hash, "copied", "true"); + if (status->switched) + svn_hash_sets(att_hash, "switched", "true"); + if (status->file_external) + svn_hash_sets(att_hash, "file-external", "true"); + if (status->versioned && ! status->copied) + svn_hash_sets(att_hash, "revision", + apr_psprintf(pool, "%ld", status->revision)); + if (tree_conflicted) + svn_hash_sets(att_hash, "tree-conflicted", "true"); + if (status->moved_from_abspath || status->moved_to_abspath) + { + const char *relpath; + + if (status->moved_from_abspath) + { + relpath = make_relpath(cwd_abspath, status->moved_from_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + svn_hash_sets(att_hash, "moved-from", relpath); + } + if (status->moved_to_abspath) + { + relpath = make_relpath(cwd_abspath, status->moved_to_abspath, + pool, pool); + relpath = svn_dirent_local_style(relpath, pool); + svn_hash_sets(att_hash, "moved-to", relpath); + } + } + svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status", + att_hash); + + if (SVN_IS_VALID_REVNUM(status->changed_rev)) + { + svn_cl__print_xml_commit(&sb, status->changed_rev, + status->changed_author, + svn_time_to_cstring(status->changed_date, + pool), + pool); + } + + if (status->lock) + svn_cl__print_xml_lock(&sb, status->lock, pool); + + svn_xml_make_close_tag(&sb, pool, "wc-status"); + + if (status->repos_node_status != svn_wc_status_none + || status->repos_lock) + { + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status", + "item", + generate_status_desc(combined_repos_status(status)), + "props", + generate_status_desc(status->repos_prop_status), + NULL); + if (status->repos_lock) + svn_cl__print_xml_lock(&sb, status->repos_lock, pool); + + svn_xml_make_close_tag(&sb, pool, "repos-status"); + } + + svn_xml_make_close_tag(&sb, pool, "entry"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + +/* Called by status-cmd.c */ +svn_error_t * +svn_cl__print_status(const char *cwd_abspath, + const char *path, + const svn_client_status_t *status, + svn_boolean_t suppress_externals_placeholders, + svn_boolean_t detailed, + svn_boolean_t show_last_committed, + svn_boolean_t skip_unrecognized, + svn_boolean_t repos_locks, + unsigned int *text_conflicts, + unsigned int *prop_conflicts, + unsigned int *tree_conflicts, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + if (! status + || (skip_unrecognized + && !(status->versioned + || status->conflicted + || status->node_status == svn_wc_status_external)) + || (status->node_status == svn_wc_status_none + && status->repos_node_status == svn_wc_status_none)) + return SVN_NO_ERROR; + + /* If we're trying not to print boring "X /path/to/external" + lines..." */ + if (suppress_externals_placeholders) + { + /* ... skip regular externals unmodified in the repository. */ + if ((status->node_status == svn_wc_status_external) + && (status->repos_node_status == svn_wc_status_none) + && (! status->conflicted)) + return SVN_NO_ERROR; + + /* ... skip file externals that aren't modified locally or + remotely, changelisted, or locked (in either sense of the + word). */ + if ((status->file_external) + && (status->repos_node_status == svn_wc_status_none) + && ((status->node_status == svn_wc_status_normal) + || (status->node_status == svn_wc_status_none)) + && ((status->prop_status == svn_wc_status_normal) + || (status->prop_status == svn_wc_status_none)) + && (! status->changelist) + && (! status->lock) + && (! status->wc_is_locked) + && (! status->conflicted)) + return SVN_NO_ERROR; + } + + return print_status(cwd_abspath, svn_dirent_local_style(path, pool), + detailed, show_last_committed, repos_locks, status, + text_conflicts, prop_conflicts, tree_conflicts, + ctx, pool); +} |