The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * status.c:  the command-line's portion of the "svn status" command
 *
 * ====================================================================
 * Copyright (c) 2000-2004,2008 CollabNet.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://subversion.tigris.org/license-1.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 *
 * This software consists of voluntary contributions made by many
 * individuals.  For exact contribution history, see the revision
 * history and logs, available at http://subversion.tigris.org/.
 * ====================================================================
 */

/* ==================================================================== */



/*** Includes. ***/
#include "svn_cmdline.h"
#include "svn_wc.h"
#include "svn_path.h"
#include "svn_xml.h"
#include "svn_time.h"
#include "cl.h"
#include "svn_private_config.h"
#include "tree-conflicts.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_merged:      return 'G';
    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 single character representation of the switched column
   status. */
static char
generate_switch_column_code(const svn_wc_status2_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_merged:      return "merged";
    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:                        abort();
    }
}


/* Print STATUS and PATH in a format determined by DETAILED and
   SHOW_LAST_COMMITTED. */
static svn_error_t *
print_status(const char *path,
             svn_boolean_t detailed,
             svn_boolean_t show_last_committed,
             svn_boolean_t repos_locks,
             const svn_wc_status2_t *status,
             apr_pool_t *pool)
{
  enum svn_wc_status_kind text_status = status->text_status;
  char tree_status_code = ' ';
  const char *tree_desc_line = "";

  /* 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->tree_conflict)
    {
      const char *desc;
      tree_status_code = 'C';
      svn_cl__get_human_readable_tree_conflict_description(
        &desc, status->tree_conflict, pool);
      tree_desc_line = apr_psprintf(pool, "\n      >   %s", desc);
    }

  if (detailed)
    {
      char ood_status, lock_status;
      const char *working_rev;

      if (! status->entry)
        working_rev = "";
      else if (! SVN_IS_VALID_REVNUM(status->entry->revision))
        working_rev = " ? ";
      else if (status->copied)
        working_rev = "-";
      else
        working_rev = apr_psprintf(pool, "%ld", status->entry->revision);

      if (status->repos_text_status != svn_wc_status_none
          || status->repos_prop_status != svn_wc_status_none)
        ood_status = '*';
      else
        ood_status = ' ';

      if (repos_locks)
        {
          if (status->repos_lock)
            {
              if (status->entry && status->entry->lock_token)
                {
                  if (strcmp(status->repos_lock->token, status->entry->lock_token)
                      == 0)
                    lock_status = 'K';
                  else
                    lock_status = 'T';
                }
              else
                lock_status = 'O';
            }
          else if (status->entry && status->entry->lock_token)
            lock_status = 'B';
          else
            lock_status = ' ';
        }
      else
        lock_status = (status->entry && status->entry->lock_token) ? 'K' : ' ';

      if (show_last_committed)
        {
          const char *commit_rev;
          const char *commit_author;

          if (status->entry && SVN_IS_VALID_REVNUM(status->entry->cmt_rev))
            commit_rev = apr_psprintf(pool, "%ld", status->entry->cmt_rev);
          else if (status->entry)
            commit_rev = " ? ";
          else
            commit_rev = "";

          if (status->entry && status->entry->cmt_author)
            commit_author = status->entry->cmt_author;
          else if (status->entry)
            commit_author = " ? ";
          else
            commit_author = "";

          SVN_ERR
            (svn_cmdline_printf(pool,
                                "%c%c%c%c%c%c%c %c   %6s   %6s %-12s %s%s\n",
                                generate_status_code(text_status),
                                generate_status_code(status->prop_status),
                                status->locked ? 'L' : ' ',
                                status->copied ? '+' : ' ',
                                generate_switch_column_code(status),
                                lock_status,
                                tree_status_code,
                                ood_status,
                                working_rev,
                                commit_rev,
                                commit_author,
                                path,
                                tree_desc_line));
        }
      else
        SVN_ERR
          (svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c   %6s   %s%s\n",
                              generate_status_code(text_status),
                              generate_status_code(status->prop_status),
                              status->locked ? 'L' : ' ',
                              status->copied ? '+' : ' ',
                              generate_switch_column_code(status),
                              lock_status,
                              tree_status_code,
                              ood_status,
                              working_rev,
                              path,
                              tree_desc_line));
    }
  else
    SVN_ERR
      (svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s\n",
                          generate_status_code(text_status),
                          generate_status_code(status->prop_status),
                          status->locked ? 'L' : ' ',
                          status->copied ? '+' : ' ',
                          generate_switch_column_code(status),
                          ((status->entry && status->entry->lock_token)
                           ? 'K' : ' '),
                          tree_status_code,
                          path,
                          tree_desc_line));

  return svn_cmdline_fflush(stdout);
}


svn_error_t *
svn_cl__print_status_xml(const char *path,
                         const svn_wc_status2_t *status,
                         apr_pool_t *pool)
{
  svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
  apr_hash_t *att_hash;

  if (status->text_status == svn_wc_status_none
      && status->repos_text_status == svn_wc_status_none)
    return SVN_NO_ERROR;

  svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
                        "path", svn_path_local_style(path, pool), NULL);

  att_hash = apr_hash_make(pool);
  apr_hash_set(att_hash, "item", APR_HASH_KEY_STRING,
               generate_status_desc(status->text_status));
  apr_hash_set(att_hash, "props", APR_HASH_KEY_STRING,
               generate_status_desc(status->prop_status));
  if (status->locked)
    apr_hash_set(att_hash, "wc-locked", APR_HASH_KEY_STRING, "true");
  if (status->copied)
    apr_hash_set(att_hash, "copied", APR_HASH_KEY_STRING, "true");
  if (status->switched)
    apr_hash_set(att_hash, "switched", APR_HASH_KEY_STRING, "true");
  if (status->file_external)
    apr_hash_set(att_hash, "file-external", APR_HASH_KEY_STRING, "true");
  if (status->entry && ! status->entry->copied)
    apr_hash_set(att_hash, "revision", APR_HASH_KEY_STRING,
                 apr_psprintf(pool, "%ld", status->entry->revision));
  if (status->tree_conflict)
    apr_hash_set(att_hash, "tree-conflicted", APR_HASH_KEY_STRING,
                 "true");
  svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status",
                             att_hash);

  if (status->entry && SVN_IS_VALID_REVNUM(status->entry->cmt_rev))
    {
      svn_cl__print_xml_commit(&sb, status->entry->cmt_rev,
                               status->entry->cmt_author,
                               svn_time_to_cstring(status->entry->cmt_date,
                                                   pool),
                               pool);
    }

  if (status->entry && status->entry->lock_token)
    {
      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "lock", NULL);

      svn_cl__xml_tagged_cdata(&sb, pool, "token", status->entry->lock_token);

      /* If lock_owner is NULL, assume WC is corrupt. */
      if (status->entry->lock_owner)
        svn_cl__xml_tagged_cdata(&sb, pool, "owner",
                                 status->entry->lock_owner);
      else
        return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
                                 _("'%s' has lock token, but no lock owner"),
                                 svn_path_local_style(path, pool));

      svn_cl__xml_tagged_cdata(&sb, pool, "comment",
                               status->entry->lock_comment);

      svn_cl__xml_tagged_cdata(&sb, pool, "created",
                               svn_time_to_cstring
                               (status->entry->lock_creation_date, pool));

      svn_xml_make_close_tag(&sb, pool, "lock");
    }

  svn_xml_make_close_tag(&sb, pool, "wc-status");

  if (status->repos_text_status != svn_wc_status_none
      || status->repos_prop_status != svn_wc_status_none
      || status->repos_lock)
    {
      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status",
                            "item",
                            generate_status_desc(status->repos_text_status),
                            "props",
                            generate_status_desc(status->repos_prop_status),
                            NULL);
      if (status->repos_lock)
        {
          svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "lock", NULL);

          svn_cl__xml_tagged_cdata(&sb, pool, "token",
                                   status->repos_lock->token);

          svn_cl__xml_tagged_cdata(&sb, pool, "owner",
                                   status->repos_lock->owner);

          svn_cl__xml_tagged_cdata(&sb, pool, "comment",
                                   status->repos_lock->comment);

          svn_cl__xml_tagged_cdata(&sb, pool, "created",
                                   svn_time_to_cstring
                                   (status->repos_lock->creation_date,
                                    pool));

          if (status->repos_lock->expiration_date != 0)
            {
              svn_cl__xml_tagged_cdata(&sb, pool, "expires",
                                       svn_time_to_cstring
                                       (status->repos_lock->expiration_date,
                                        pool));
            }

          svn_xml_make_close_tag(&sb, pool, "lock");
        }
      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 *path,
                     const svn_wc_status2_t *status,
                     svn_boolean_t detailed,
                     svn_boolean_t show_last_committed,
                     svn_boolean_t skip_unrecognized,
                     svn_boolean_t repos_locks,
                     apr_pool_t *pool)
{
  if (! status
      || (skip_unrecognized && !(status->entry || status->tree_conflict))
      || (status->text_status == svn_wc_status_none
          && status->repos_text_status == svn_wc_status_none))
    return SVN_NO_ERROR;

  return print_status(svn_path_local_style(path, pool),
                      detailed, show_last_committed, repos_locks, status,
                      pool);
}