commit_util.c

来自「linux subdivision ying gai ke yi le ba」· C语言 代码 · 共 1,546 行 · 第 1/4 页

C
1,546
字号
/*
 * commit_util.c:  Driver for the WC commit process.
 *
 * ====================================================================
 * Copyright (c) 2000-2004 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/.
 * ====================================================================
 */

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


#include <string.h>

#include <apr_pools.h>
#include <apr_hash.h>

#include "client.h"
#include "svn_path.h"
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_wc.h"
#include "svn_sorts.h"

#include <assert.h>
#include <stdlib.h>  /* for qsort() */

#include "svn_private_config.h"

/*** Uncomment this to turn on commit driver debugging. ***/
/*
#define SVN_CLIENT_COMMIT_DEBUG
*/



/*** Harvesting Commit Candidates ***/


/* Add a new commit candidate (described by all parameters except
   `COMMITTABLES') to the COMMITABLES hash. */
static void
add_committable (apr_hash_t *committables,
                 const char *path,
                 svn_node_kind_t kind,
                 const char *url,
                 svn_revnum_t revision,
                 const char *copyfrom_url,
                 apr_byte_t state_flags)
{
  apr_pool_t *pool = apr_hash_pool_get (committables);
  const char *repos_name = SVN_CLIENT__SINGLE_REPOS_NAME;
  apr_array_header_t *array;
  svn_client_commit_item_t *new_item;

  /* Sanity checks. */
  assert (path && url);

  /* ### todo: Get the canonical repository for this item, which will
     be the real key for the COMMITTABLES hash, instead of the above
     bogosity. */
  array = apr_hash_get (committables, repos_name, APR_HASH_KEY_STRING);

  /* E-gads!  There is no array for this repository yet!  Oh, no
     problem, we'll just create (and add to the hash) one. */
  if (array == NULL)
    {
      array = apr_array_make (pool, 1, sizeof (new_item));
      apr_hash_set (committables, repos_name, APR_HASH_KEY_STRING, array);
    }

  /* Now update pointer values, ensuring that their allocations live
     in POOL. */
  new_item = apr_pcalloc (pool, sizeof (*new_item));
  new_item->path           = apr_pstrdup (pool, path);
  new_item->kind           = kind;
  new_item->url            = apr_pstrdup (pool, url);
  new_item->revision       = revision;
  new_item->copyfrom_url   = copyfrom_url 
                             ? apr_pstrdup (pool, copyfrom_url) : NULL;
  new_item->state_flags    = state_flags;
  new_item->wcprop_changes = apr_array_make (pool, 1, sizeof (svn_prop_t *));
   
  /* Now, add the commit item to the array. */
  (*((svn_client_commit_item_t **) apr_array_push (array))) = new_item;
}


static svn_error_t *
check_prop_mods (svn_boolean_t *props_changed,
                 svn_boolean_t *eol_prop_changed,
                 const char *path,
                 svn_wc_adm_access_t *adm_access,
                 apr_pool_t *pool)
{
  apr_array_header_t *prop_mods;
  int i;

  *eol_prop_changed = *props_changed = FALSE;
  SVN_ERR (svn_wc_props_modified_p (props_changed, path, adm_access, pool));
  if (! props_changed)
    return SVN_NO_ERROR;
  SVN_ERR (svn_wc_get_prop_diffs (&prop_mods, NULL, path, adm_access, pool));
  for (i = 0; i < prop_mods->nelts; i++)
    {
      svn_prop_t *prop_mod = &APR_ARRAY_IDX (prop_mods, i, svn_prop_t);
      if (strcmp (prop_mod->name, SVN_PROP_EOL_STYLE) == 0)
        *eol_prop_changed = TRUE;
    }
  return SVN_NO_ERROR;
}


/* If there is a commit item for PATH in COMMITTABLES, return it, else
   return NULL.  Use POOL for temporary allocation only. */
static svn_client_commit_item_t *
look_up_committable (apr_hash_t *committables,
                     const char *path,
                     apr_pool_t *pool)
{
  apr_hash_index_t *hi;

  for (hi = apr_hash_first (pool, committables); hi; hi = apr_hash_next (hi))
    {
      const void *key;
      void *val;
      apr_array_header_t *these_committables;
      int i;
      
      apr_hash_this (hi, &key, NULL, &val);
      these_committables = val;
      
      for (i = 0; i < these_committables->nelts; i++)
        {
          svn_client_commit_item_t *this_committable
            = APR_ARRAY_IDX (these_committables, i,
                             svn_client_commit_item_t *);
          
          if (strcmp (this_committable->path, path) == 0)
            return this_committable;
        }
    }

  return NULL;
}


/* Recursively search for commit candidates in (and under) PATH (with
   entry ENTRY and ancestry URL), and add those candidates to
   COMMITTABLES.  If in ADDS_ONLY modes, only new additions are
   recognized.  COPYFROM_URL is the default copyfrom-url for children
   of copied directories.  NONRECURSIVE indicates that this function
   will not recurse into subdirectories of PATH when PATH is itself a
   directory.

   If in COPY_MODE, the entry is treated as if it is destined to be
   added with history as URL.

   If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to see 
   if the user has cancelled the operation.  */
static svn_error_t *
harvest_committables (apr_hash_t *committables,
                      const char *path,
                      svn_wc_adm_access_t *adm_access,
                      const char *url,
                      const char *copyfrom_url,
                      const svn_wc_entry_t *entry,
                      const svn_wc_entry_t *parent_entry,
                      svn_boolean_t adds_only,
                      svn_boolean_t copy_mode,
                      svn_boolean_t nonrecursive,
                      svn_client_ctx_t *ctx,
                      apr_pool_t *pool)
{
  apr_hash_t *entries = NULL;
  svn_boolean_t text_mod = FALSE, prop_mod = FALSE;
  apr_byte_t state_flags = 0;
  svn_node_kind_t kind;
  const char *p_path;
  svn_boolean_t tc, pc;
  const char *cf_url = NULL;
  svn_revnum_t cf_rev = entry->copyfrom_rev;
  const svn_string_t *propval;
  svn_boolean_t is_special;

  /* Early out if the item is already marked as committable. */
  if (look_up_committable (committables, path, pool))
    return SVN_NO_ERROR;

  assert (entry);
  assert (url);

  if (ctx->cancel_func)
    SVN_ERR (ctx->cancel_func (ctx->cancel_baton));

  /* Make P_PATH the parent dir. */
  p_path = svn_path_dirname (path, pool);

  /* Return error on unknown path kinds.  We check both the entry and
     the node itself, since a path might have changed kind since its
     entry was written. */
  if ((entry->kind != svn_node_file) && (entry->kind != svn_node_dir))
    return svn_error_createf
      (SVN_ERR_NODE_UNKNOWN_KIND, NULL, _("Unknown entry kind for '%s'"), path);

  SVN_ERR (svn_io_check_special_path (path, &kind, &is_special, pool));

  if ((kind != svn_node_file)
      && (kind != svn_node_dir)
      && (kind != svn_node_none))
    {
      return svn_error_createf
        (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
         _("Unknown entry kind for '%s'"), path);
    }

  /* Verify that the node's type has not changed before attempting to
     commit. */
  SVN_ERR (svn_wc_prop_get (&propval, SVN_PROP_SPECIAL, path, adm_access,
                            pool));

  if ((((! propval) && (is_special))
#ifdef HAVE_SYMLINK  
       || ((propval) && (! is_special))
#endif /* HAVE_SYMLINK */
       ) && (kind != svn_node_none))
    {
      return svn_error_createf
        (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
         _("Entry '%s' has unexpectedly changed special status"), path);
    }

  /* Get a fully populated entry for PATH if we can, and check for
     conflicts. If this is a directory ... */
  if (entry->kind == svn_node_dir)
    { 
      /* ... then try to read its own entries file so we have a full
         entry for it (we were going to have to do this eventually to
         recurse anyway, so... ) */
      svn_error_t *err;
      const svn_wc_entry_t *e = NULL;
      err = svn_wc_entries_read (&entries, adm_access, FALSE, pool);

      /* If we failed to get an entries hash for the directory, no
         sweat.  Cleanup and move along.  */
      if (err)
        {
          svn_error_clear (err);
          entries = NULL;
        }
      
      /* If we got an entries hash, and the "this dir" entry is
         present, override our current ENTRY with it, and check for
         conflicts. */
      if ((entries) && ((e = apr_hash_get (entries, SVN_WC_ENTRY_THIS_DIR, 
                                           APR_HASH_KEY_STRING))))
        {
          entry = e;
          SVN_ERR (svn_wc_conflicted_p (&tc, &pc, path, entry, pool));
        }

      /* No new entry?  Just check the parent's pointer for
         conflicts. */
      else
        {
          SVN_ERR (svn_wc_conflicted_p (&tc, &pc, p_path, entry, pool));
        }
    }

  /* If this is not a directory, check for conflicts using the
     parent's path. */
  else
    {
      SVN_ERR (svn_wc_conflicted_p (&tc, &pc, p_path, entry, pool));
    }

  /* Bail now if any conflicts exist for the ENTRY. */
  if (tc || pc)
    return svn_error_createf (SVN_ERR_WC_FOUND_CONFLICT, NULL,
                              _("Aborting commit: '%s' remains in conflict"),
                              path);

  /* If we have our own URL, and we're NOT in COPY_MODE, it wins over
     the telescoping one(s).  In COPY_MODE, URL will always be the
     URL-to-be of the copied item.  */
  if ((entry->url) && (! copy_mode))
    url = entry->url;

  /* Check for the deletion case.  Deletes can occur only when we are
     not in "adds-only mode".  They can be either explicit
     (schedule == delete) or implicit (schedule == replace ::= delete+add).  */
  if ((! adds_only)
      && ((entry->schedule == svn_wc_schedule_delete)
          || (entry->schedule == svn_wc_schedule_replace)))
    {
      state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
    }

  /* Check for the trivial addition case.  Adds can be explicit
     (schedule == add) or implicit (schedule == replace ::= delete+add).  
     We also note whether or not this is an add with history here.  */
  if ((entry->schedule == svn_wc_schedule_add)
      || (entry->schedule == svn_wc_schedule_replace))
    {
      state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
      if (entry->copyfrom_url)
        {
          state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
          cf_url = entry->copyfrom_url;
          adds_only = FALSE;
        }
      else
        {
          adds_only = TRUE;
        }
    }

  /* Check for the copied-subtree addition case.  */
  if ((entry->copied || copy_mode) 
      && (entry->schedule == svn_wc_schedule_normal))
    {
      svn_revnum_t p_rev = entry->revision - 1; /* arbitrary non-equal value */
      svn_boolean_t wc_root = FALSE;

      /* If this is not a WC root then its parent's revision is
         admissible for comparitive purposes. */
      SVN_ERR (svn_wc_is_wc_root (&wc_root, path, adm_access, pool));
      if (! wc_root)
        {
          if (parent_entry)
            p_rev = parent_entry->revision;
        }
      else if (! copy_mode)
        return svn_error_createf 
          (SVN_ERR_WC_CORRUPT, NULL,
           _("Did not expect '%s' to be a working copy root"), path);

      /* If the ENTRY's revision differs from that of its parent, we
         have to explicitly commit ENTRY as a copy. */
      if (entry->revision != p_rev)
        {
          state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
          state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
          adds_only = FALSE;
          cf_rev = entry->revision;
          if (copy_mode)
            cf_url = entry->url;
          else if (copyfrom_url)
            cf_url = copyfrom_url;
          else /* ### See issue #830 */
            return svn_error_createf 
              (SVN_ERR_BAD_URL, NULL,
               _("Commit item '%s' has copy flag but no copyfrom URL\n"), path);
        }
    }

  /* If an add is scheduled to occur, dig around for some more
     information about it. */
  if (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
    {
      svn_boolean_t eol_prop_changed;

      /* See if there are property modifications to send. */
      SVN_ERR (check_prop_mods (&prop_mod, &eol_prop_changed, path, 
                                adm_access, pool));

      /* Regular adds of files have text mods, but for copies we have
         to test for textual mods.  Directories simply don't have text! */
      if (entry->kind == svn_node_file)
        {
          /* Check for text mods.  If EOL_PROP_CHANGED is TRUE, then
             we need to force a translated byte-for-byte comparison
             against the text-base so that a timestamp comparison
             won't bail out early.  Depending on how the svn:eol-style
             prop was changed, we might have to send new text to the
             server to match the new newline style.  */
          if (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
            SVN_ERR (svn_wc_text_modified_p (&text_mod, path, eol_prop_changed,

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?