Example #1
0
static svn_error_t *
change_dir_prop(void *dir_baton,
                const char *name,
                const svn_string_t *value,
                apr_pool_t *pool)
{
  struct dir_baton *db = dir_baton;
  struct edit_baton *eb = db->edit_baton;

  /* Check for write authorization. */
  SVN_ERR(check_authz(eb, db->path, eb->txn_root,
                      svn_authz_write, pool));

  if (SVN_IS_VALID_REVNUM(db->base_rev))
    {
      /* Subversion rule:  propchanges can only happen on a directory
         which is up-to-date. */
      svn_revnum_t created_rev;
      SVN_ERR(svn_fs_node_created_rev(&created_rev,
                                      eb->txn_root, db->path, pool));

      if (db->base_rev < created_rev)
        return out_of_date(db->path, svn_node_dir);
    }

  return svn_repos_fs_change_node_prop(eb->txn_root, db->path,
                                       name, value, pool);
}
Example #2
0
/* Can we create a node at FSPATH in TXN_ROOT? If something already exists
   at that path, then the client MAY be out of date. We then have to see if
   the path was created/modified in this transaction. IOW, it is new and
   can be replaced without problem.

   Note: the editor protocol disallows double-modifications. This is to
   ensure somebody does not accidentally overwrite another file due to
   being out-of-date.  */
static svn_error_t *
can_create(svn_fs_root_t *txn_root,
           const char *fspath,
           apr_pool_t *scratch_pool)
{
    svn_node_kind_t kind;
    const char *cur_fspath;

    SVN_ERR(svn_fs_check_path(&kind, txn_root, fspath, scratch_pool));
    if (kind == svn_node_none)
        return SVN_NO_ERROR;

    /* ### I'm not sure if this works perfectly. We might have an ancestor
       ### that was modified as a result of a change on a cousin. We might
       ### misinterpret that as a *-here node which brought along this
       ### child. Need to write a test to verify. We may also be able to
       ### test the ancestor to determine if it has been *-here in this
       ### txn, or just a simple modification.  */

    /* Are any of the parents copied/moved/rotated-here?  */
    for (cur_fspath = fspath;
            strlen(cur_fspath) > 1;  /* not the root  */
            cur_fspath = svn_fspath__dirname(cur_fspath, scratch_pool))
    {
        svn_revnum_t created_rev;

        SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, cur_fspath,
                                        scratch_pool));
        if (!SVN_IS_VALID_REVNUM(created_rev))
        {
            /* The node has no created revision, meaning it is uncommitted.
               Thus, it was created in this transaction, or it has already
               been modified in some way (implying it has already passed a
               modification check.  */
            /* ### verify the node has been *-here ??  */
            return SVN_NO_ERROR;
        }
    }

    return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
                             _("'%s' already exists, so may be out"
                               " of date; try updating"),
                             fspath);
}
Example #3
0
static svn_error_t *
delete_entry(const char *path,
             svn_revnum_t revision,
             void *parent_baton,
             apr_pool_t *pool)
{
  struct dir_baton *parent = parent_baton;
  struct edit_baton *eb = parent->edit_baton;
  svn_node_kind_t kind;
  svn_revnum_t cr_rev;
  svn_repos_authz_access_t required = svn_authz_write;
  const char *full_path;

  full_path = svn_fspath__join(eb->base_path,
                               svn_relpath_canonicalize(path, pool), pool);

  /* Check PATH in our transaction.  */
  SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool));

  /* Deletion requires a recursive write access, as well as write
     access to the parent directory. */
  if (kind == svn_node_dir)
    required |= svn_authz_recursive;
  SVN_ERR(check_authz(eb, full_path, eb->txn_root,
                      required, pool));
  SVN_ERR(check_authz(eb, parent->path, eb->txn_root,
                      svn_authz_write, pool));

  /* If PATH doesn't exist in the txn, the working copy is out of date. */
  if (kind == svn_node_none)
    return out_of_date(full_path, kind);

  /* Now, make sure we're deleting the node we *think* we're
     deleting, else return an out-of-dateness error. */
  SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool));
  if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev))
    return out_of_date(full_path, kind);

  /* This routine is a mindless wrapper.  We call svn_fs_delete()
     because that will delete files and recursively delete
     directories.  */
  return svn_fs_delete(eb->txn_root, full_path, pool);
}
Example #4
0
static svn_error_t *
open_file(const char *path,
          void *parent_baton,
          svn_revnum_t base_revision,
          apr_pool_t *pool,
          void **file_baton)
{
  struct file_baton *new_fb;
  struct dir_baton *pb = parent_baton;
  struct edit_baton *eb = pb->edit_baton;
  svn_revnum_t cr_rev;
  apr_pool_t *subpool = svn_pool_create(pool);
  const char *full_path;

  full_path = svn_fspath__join(eb->base_path,
                               svn_relpath_canonicalize(path, pool), pool);

  /* Check for read authorization. */
  SVN_ERR(check_authz(eb, full_path, eb->txn_root,
                      svn_authz_read, subpool));

  /* Get this node's creation revision (doubles as an existence check). */
  SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path,
                                  subpool));

  /* If the node our caller has is an older revision number than the
     one in our transaction, return an out-of-dateness error. */
  if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev))
    return out_of_date(full_path, svn_node_file);

  /* Build a new file baton */
  new_fb = apr_pcalloc(pool, sizeof(*new_fb));
  new_fb->edit_baton = eb;
  new_fb->path = full_path;

  *file_baton = new_fb;

  /* Destory the work subpool. */
  svn_pool_destroy(subpool);

  return SVN_NO_ERROR;
}
static dav_prop_insert
insert_prop_internal(const dav_resource *resource,
                     int propid,
                     dav_prop_insert what,
                     apr_text_header *phdr,
                     apr_pool_t *scratch_pool,
                     apr_pool_t *result_pool)
{
  const char *value = NULL;
  const char *s;
  const dav_liveprop_spec *info;
  int global_ns;
  svn_error_t *serr;

  /*
  ** Almost none of the SVN provider properties are defined if the
  ** resource does not exist.  We do need to return the one VCC
  ** property and baseline-relative-path on lock-null resources,
  ** however, so that svn clients can run 'svn unlock' and 'svn info'
  ** on these things.
  **
  ** Even though we state that the SVN properties are not defined, the
  ** client cannot store dead values -- we deny that thru the is_writable
  ** hook function.
  */
  if ((! resource->exists)
      && (propid != DAV_PROPID_version_controlled_configuration)
      && (propid != SVN_PROPID_baseline_relative_path))
    return DAV_PROP_INSERT_NOTSUPP;

  /* ### we may want to respond to DAV_PROPID_resourcetype for PRIVATE
     ### resources. need to think on "proper" interaction with mod_dav */

  switch (propid)
    {
    case DAV_PROPID_getlastmodified:
    case DAV_PROPID_creationdate:
      {
        /* In subversion terms, the date attached to a file's CR is
           the true "last modified" time.  However, we're defining
           creationdate in the same way.  IMO, the "creationdate" is
           really the date attached to the revision in which the item
           *first* came into existence; this would found by tracing
           back through the log of the file -- probably via
           svn_fs_revisions_changed.  gstein, is it a bad thing that
           we're currently using 'creationdate' to mean the same thing
           as 'last modified date'?  */
        const char *datestring;
        apr_time_t timeval;
        enum time_format format;

        /* ### for now, our global VCC has no such property. */
        if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
            && (resource->info->restype == DAV_SVN_RESTYPE_VCC
                || resource->info->restype == DAV_SVN_RESTYPE_ME))
          {
            return DAV_PROP_INSERT_NOTSUPP;
          }

        if (propid == DAV_PROPID_creationdate)
          {
            /* Return an ISO8601 date; this is what the svn client
               expects, and rfc2518 demands it. */
            format = time_format_iso8601;
          }
        else /* propid == DAV_PROPID_getlastmodified */
          {
            format = time_format_rfc1123;
          }

        if (0 != get_last_modified_time(&datestring, &timeval,
                                        resource, format, scratch_pool))
          {
            return DAV_PROP_INSERT_NOTDEF;
          }

        value = apr_xml_quote_string(scratch_pool, datestring, 1);
        break;
      }

    case DAV_PROPID_creator_displayname:
      {
        svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
        svn_string_t *last_author = NULL;

        /* ### for now, our global VCC has no such property. */
        if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
            && (resource->info->restype == DAV_SVN_RESTYPE_VCC
                || resource->info->restype == DAV_SVN_RESTYPE_ME))
          {
            return DAV_PROP_INSERT_NOTSUPP;
          }

        if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
          {
            /* A baseline URI. */
            committed_rev = resource->info->root.rev;
          }
        else if (resource->type == DAV_RESOURCE_TYPE_REGULAR
                 || resource->type == DAV_RESOURCE_TYPE_WORKING
                 || resource->type == DAV_RESOURCE_TYPE_VERSION)
          {
            /* Get the CR field out of the node's skel.  Notice that the
               root object might be an ID root -or- a revision root. */
            serr = svn_fs_node_created_rev(&committed_rev,
                                           resource->info->root.root,
                                           resource->info->repos_path,
                                           scratch_pool);
            if (serr != NULL)
              {
                /* ### what to do? */
                svn_error_clear(serr);
                value = "###error###";
                break;
              }
          }
        else
          {
            return DAV_PROP_INSERT_NOTSUPP;
          }

        serr = get_path_revprop(&last_author,
                                resource,
                                committed_rev,
                                SVN_PROP_REVISION_AUTHOR,
                                scratch_pool);
        if (serr)
          {
            /* ### what to do? */
            svn_error_clear(serr);
            value = "###error###";
            break;
          }

        if (last_author == NULL)
          return DAV_PROP_INSERT_NOTDEF;

        value = apr_xml_quote_string(scratch_pool, last_author->data, 1);
        break;
      }

    case DAV_PROPID_getcontentlanguage:
      /* ### need something here */
      return DAV_PROP_INSERT_NOTSUPP;
      break;

    case DAV_PROPID_getcontentlength:
      {
        svn_filesize_t len = 0;

        /* our property, but not defined on collection resources */
        if (resource->collection || resource->baselined)
          return DAV_PROP_INSERT_NOTSUPP;

        serr = svn_fs_file_length(&len, resource->info->root.root,
                                  resource->info->repos_path, scratch_pool);
        if (serr != NULL)
          {
            svn_error_clear(serr);
            value = "0";  /* ### what to do? */
            break;
          }

        value = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, len);
        break;
      }

    case DAV_PROPID_getcontenttype:
      {
        /* The subversion client assumes that any file without an
           svn:mime-type property is of type text/plain.  So it seems
           safe (and consistent) to assume the same on the server.  */
        svn_string_t *pval;
        const char *mime_type = NULL;

        if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
          return DAV_PROP_INSERT_NOTSUPP;

        if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
            && (resource->info->restype == DAV_SVN_RESTYPE_VCC
                || resource->info->restype == DAV_SVN_RESTYPE_ME))
          {
            return DAV_PROP_INSERT_NOTSUPP;
          }

        if (resource->collection) /* defaults for directories */
          {
            if (resource->info->repos->xslt_uri)
              mime_type = "text/xml";
            else
              mime_type = "text/html; charset=UTF-8";
          }
        else
          {
            if ((serr = svn_fs_node_prop(&pval, resource->info->root.root,
                                         resource->info->repos_path,
                                         SVN_PROP_MIME_TYPE, scratch_pool)))
              {
                svn_error_clear(serr);
                pval = NULL;
              }

            if (pval)
              mime_type = pval->data;
            else if ((! resource->info->repos->is_svn_client)
                     && resource->info->r->content_type)
              mime_type = resource->info->r->content_type;
            else
              mime_type = "text/plain";

            if ((serr = svn_mime_type_validate(mime_type, scratch_pool)))
              {
                /* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but
                   there's no point even checking.  No matter what the
                   error is, we can't claim to have a mime type for
                   this resource. */
                ap_log_rerror(APLOG_MARK, APLOG_WARNING, serr->apr_err, 
                              resource->info->r, "%s", serr->message);
                svn_error_clear(serr);
                return DAV_PROP_INSERT_NOTDEF;
              }
          }

        value = mime_type;
        break;
      }

    case DAV_PROPID_getetag:
      if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
          && (resource->info->restype == DAV_SVN_RESTYPE_VCC
              || resource->info->restype == DAV_SVN_RESTYPE_ME))
        {
          return DAV_PROP_INSERT_NOTSUPP;
        }

      value = dav_svn__getetag(resource, scratch_pool);
      break;

    case DAV_PROPID_auto_version:
      /* we only support one autoversioning behavior, and thus only
         return this one static value; someday when we support
         locking, there are other possible values/behaviors for this. */
      if (resource->info->repos->autoversioning)
        value = "DAV:checkout-checkin";
      else
        return DAV_PROP_INSERT_NOTDEF;
      break;

    case DAV_PROPID_baseline_collection:
      /* only defined for Baselines */
      /* ### whoops. also defined for a VCC. deal with it later. */
      if (resource->type != DAV_RESOURCE_TYPE_VERSION || !resource->baselined)
        return DAV_PROP_INSERT_NOTSUPP;
      value = dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_BC,
                                 resource->info->root.rev, NULL,
                                 1 /* add_href */, scratch_pool);
      break;

    case DAV_PROPID_checked_in:
      /* only defined for VCRs (in the public space and in a BC space) */
      /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
      if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
          && (resource->info->restype == DAV_SVN_RESTYPE_VCC
              || resource->info->restype == DAV_SVN_RESTYPE_ME))
        {
          svn_revnum_t revnum;

          serr = svn_fs_youngest_rev(&revnum, resource->info->repos->fs,
                                     scratch_pool);
          if (serr != NULL)
            {
              /* ### what to do? */
              svn_error_clear(serr);
              value = "###error###";
              break;
            }
          s = dav_svn__build_uri(resource->info->repos,
                                 DAV_SVN__BUILD_URI_BASELINE,
                                 revnum, NULL, 0 /* add_href */, scratch_pool);
          value = apr_psprintf(scratch_pool, "<D:href>%s</D:href>",
                               apr_xml_quote_string(scratch_pool, s, 1));
        }
      else if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
        {
          /* not defined for this resource type */
          return DAV_PROP_INSERT_NOTSUPP;
        }
      else
        {
          svn_revnum_t rev_to_use =
            dav_svn__get_safe_cr(resource->info->root.root,
                                 resource->info->repos_path, scratch_pool);

          s = dav_svn__build_uri(resource->info->repos,
                                 DAV_SVN__BUILD_URI_VERSION,
                                 rev_to_use, resource->info->repos_path,
                                0 /* add_href */, scratch_pool);
          value = apr_psprintf(scratch_pool, "<D:href>%s</D:href>",
                               apr_xml_quote_string(scratch_pool, s, 1));
        }
      break;

    case DAV_PROPID_version_controlled_configuration:
      /* only defined for VCRs */
      /* ### VCRs within the BC should not have this property! */
      /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
        return DAV_PROP_INSERT_NOTSUPP;
      value = dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_VCC,
                                 SVN_IGNORED_REVNUM, NULL,
                                 1 /* add_href */, scratch_pool);
      break;

    case DAV_PROPID_version_name:
      /* only defined for Version Resources and Baselines */
      /* ### whoops. also defined for VCRs. deal with it later. */
      if ((resource->type != DAV_RESOURCE_TYPE_VERSION)
          && (! resource->versioned))
        return DAV_PROP_INSERT_NOTSUPP;

      if (resource->type == DAV_RESOURCE_TYPE_PRIVATE
          && (resource->info->restype == DAV_SVN_RESTYPE_VCC
              || resource->info->restype == DAV_SVN_RESTYPE_ME))
        {
          return DAV_PROP_INSERT_NOTSUPP;
        }

      if (resource->baselined)
        {
          /* just the revision number for baselines */
          value = apr_psprintf(scratch_pool, "%ld",
                               resource->info->root.rev);
        }
      else
        {
          svn_revnum_t committed_rev = SVN_INVALID_REVNUM;

          /* Get the CR field out of the node's skel.  Notice that the
             root object might be an ID root -or- a revision root. */
          serr = svn_fs_node_created_rev(&committed_rev,
                                         resource->info->root.root,
                                         resource->info->repos_path,
                                         scratch_pool);
          if (serr != NULL)
            {
              /* ### what to do? */
              svn_error_clear(serr);
              value = "###error###";
              break;
            }

          /* Convert the revision into a quoted string */
          s = apr_psprintf(scratch_pool, "%ld", committed_rev);
          value = apr_xml_quote_string(scratch_pool, s, 1);
        }
      break;

    case SVN_PROPID_baseline_relative_path:
      /* only defined for VCRs */
      /* ### VCRs within the BC should not have this property! */
      /* ### note that a VCC (a special VCR) is defined as _PRIVATE for now */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
        return DAV_PROP_INSERT_NOTSUPP;

      /* drop the leading slash, so it is relative */
      s = resource->info->repos_path + 1;
      value = apr_xml_quote_string(scratch_pool, s, 1);
      break;

    case SVN_PROPID_md5_checksum:
      if ((! resource->collection)
          && (! resource->baselined)
          && (resource->type == DAV_RESOURCE_TYPE_REGULAR
              || resource->type == DAV_RESOURCE_TYPE_WORKING
              || resource->type == DAV_RESOURCE_TYPE_VERSION))
        {
          svn_checksum_t *checksum;

          serr = svn_fs_file_checksum(&checksum, svn_checksum_md5,
                                      resource->info->root.root,
                                      resource->info->repos_path, TRUE,
                                      scratch_pool);
          if (serr != NULL)
            {
              /* ### what to do? */
              svn_error_clear(serr);
              value = "###error###";
              break;
            }

          value = svn_checksum_to_cstring(checksum, scratch_pool);

          if (! value)
            return DAV_PROP_INSERT_NOTSUPP;
        }
      else
        return DAV_PROP_INSERT_NOTSUPP;

      break;

    case SVN_PROPID_repository_uuid:
      serr = svn_fs_get_uuid(resource->info->repos->fs, &value, scratch_pool);
      if (serr != NULL)
        {
          /* ### what to do? */
          svn_error_clear(serr);
          value = "###error###";
          break;
        }
      break;

    case SVN_PROPID_deadprop_count:
      {
        unsigned int propcount;
        apr_hash_t *proplist;

        if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
          return DAV_PROP_INSERT_NOTSUPP;

        serr = svn_fs_node_proplist(&proplist,
                                    resource->info->root.root,
                                    resource->info->repos_path, scratch_pool);
        if (serr != NULL)
          {
            /* ### what to do? */
            svn_error_clear(serr);
            value = "###error###";
            break;
          }

        propcount = apr_hash_count(proplist);
        value = apr_psprintf(scratch_pool, "%u", propcount);
        break;
      }

    default:
      /* ### what the heck was this property? */
      return DAV_PROP_INSERT_NOTDEF;
    }

  /* assert: value != NULL */

  /* get the information and global NS index for the property */
  global_ns = dav_get_liveprop_info(propid, &dav_svn__liveprop_group, &info);

  /* assert: info != NULL && info->name != NULL */

  if (what == DAV_PROP_INSERT_NAME
      || (what == DAV_PROP_INSERT_VALUE && *value == '\0')) {
    s = apr_psprintf(result_pool, "<lp%d:%s/>" DEBUG_CR, global_ns,
                     info->name);
  }
  else if (what == DAV_PROP_INSERT_VALUE) {
    s = apr_psprintf(result_pool, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
                     global_ns, info->name, value, global_ns, info->name);
  }
  else {
    /* assert: what == DAV_PROP_INSERT_SUPPORTED */
    s = apr_psprintf(result_pool,
                     "<D:supported-live-property D:name=\"%s\" "
                     "D:namespace=\"%s\"/>" DEBUG_CR,
                     info->name, namespace_uris[info->ns]);
  }
  apr_text_append(result_pool, phdr, s);

  /* we inserted whatever was asked for */
  return what;
}
/* Given a mod_dav_svn @a resource, set @a *timeval and @a *datestring
   to the last-modified-time of the resource.  The datestring will be
   formatted according to @a format.  Use @a pool for both
   scratchwork, and to allocate @a *datestring.

   If @a timeval or @a datestring is NULL, don't touch it.

   Return zero on success, non-zero if an error occurs. */
static int
get_last_modified_time(const char **datestring,
                       apr_time_t *timeval,
                       const dav_resource *resource,
                       enum time_format format,
                       apr_pool_t *pool)
{
  svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
  svn_string_t *committed_date = NULL;
  svn_error_t *serr;
  apr_time_t timeval_tmp;

  if ((datestring == NULL) && (timeval == NULL))
    return 0;

  if (resource->baselined && resource->type == DAV_RESOURCE_TYPE_VERSION)
    {
      /* A baseline URI. */
      committed_rev = resource->info->root.rev;
    }
  else if (resource->type == DAV_RESOURCE_TYPE_REGULAR
           || resource->type == DAV_RESOURCE_TYPE_WORKING
           || resource->type == DAV_RESOURCE_TYPE_VERSION)
    {
      serr = svn_fs_node_created_rev(&committed_rev,
                                     resource->info->root.root,
                                     resource->info->repos_path, pool);
      if (serr != NULL)
        {
          svn_error_clear(serr);
          return 1;
        }
    }
  else
    {
      /* unsupported resource kind -- has no mod-time */
      return 1;
    }

  serr = get_path_revprop(&committed_date,
                          resource,
                          committed_rev,
                          SVN_PROP_REVISION_DATE,
                          pool);
  if (serr)
    {
      svn_error_clear(serr);
      return 1;
    }

  if (committed_date == NULL)
    return 1;

  /* return the ISO8601 date as an apr_time_t */
  serr = svn_time_from_cstring(&timeval_tmp, committed_date->data, pool);
  if (serr != NULL)
    {
      svn_error_clear(serr);
      return 1;
    }

  if (timeval)
    memcpy(timeval, &timeval_tmp, sizeof(*timeval));

  if (! datestring)
    return 0;

  if (format == time_format_iso8601)
    {
      *datestring = committed_date->data;
    }
  else if (format == time_format_rfc1123)
    {
      apr_time_exp_t tms;
      apr_status_t status;

      /* convert the apr_time_t into an apr_time_exp_t */
      status = apr_time_exp_gmt(&tms, timeval_tmp);
      if (status != APR_SUCCESS)
        return 1;

      /* stolen from dav/fs/repos.c   :-)  */
      *datestring = apr_psprintf(pool, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
                                 apr_day_snames[tms.tm_wday],
                                 tms.tm_mday, apr_month_snames[tms.tm_mon],
                                 tms.tm_year + 1900,
                                 tms.tm_hour, tms.tm_min, tms.tm_sec);
    }
  else /* unknown time format */
    {
      return 1;
    }

  return 0;
}
Example #7
0
/* The caller wants to modify REVISION of FSPATH. Is that allowed?  */
static svn_error_t *
can_modify(svn_fs_root_t *txn_root,
           const char *fspath,
           svn_revnum_t revision,
           apr_pool_t *scratch_pool)
{
    svn_revnum_t created_rev;

    /* Out-of-dateness check:  compare the created-rev of the node
       in the txn against the created-rev of FSPATH.  */
    SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, fspath,
                                    scratch_pool));

    /* Uncommitted nodes (eg. a descendent of a copy/move/rotate destination)
       have no (committed) revision number. Let the caller go ahead and
       modify these nodes.

       Note: strictly speaking, they might be performing an "illegal" edit
       in certain cases, but let's just assume they're Good Little Boys.

       If CREATED_REV is invalid, that means it's already mutable in the
       txn, which means it has already passed this out-of-dateness check.
       (Usually, this happens when looking at a parent directory of an
       already-modified node)  */
    if (!SVN_IS_VALID_REVNUM(created_rev))
        return SVN_NO_ERROR;

    /* If the node is immutable (has a revision), then the caller should
       have supplied a valid revision number [that they expect to change].
       The checks further below will determine the out-of-dateness of the
       specified revision.  */
    /* ### ugh. descendents of copy/move/rotate destinations carry along
       ### their original immutable state and (thus) a valid CREATED_REV.
       ### but they are logically uncommitted, so the caller will pass
       ### SVN_INVALID_REVNUM. (technically, the caller could provide
       ### ORIGINAL_REV, but that is semantically incorrect for the Ev2
       ### API).
       ###
       ### for now, we will assume the caller knows what they are doing
       ### and an invalid revision implies such a descendent. in the
       ### future, we could examine the ancestor chain looking for a
       ### copy/move/rotate-here node and allow the modification (and the
       ### converse: if no such ancestor, the caller must specify the
       ### correct/intended revision to modify).
    */
#if 1
    if (!SVN_IS_VALID_REVNUM(revision))
        return SVN_NO_ERROR;
#else
    if (!SVN_IS_VALID_REVNUM(revision))
        /* ### use a custom error code?  */
        return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
                                 _("Revision for modifying '%s' is required"),
                                 fspath);
#endif

    if (revision < created_rev)
    {
        /* We asked to change a node that is *older* than what we found
           in the transaction. The client is out of date.  */
        return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
                                 _("'%s' is out of date; try updating"),
                                 fspath);
    }

    if (revision > created_rev)
    {
        /* We asked to change a node that is *newer* than what we found
           in the transaction. Given that the transaction was based off
           of 'youngest', then either:
           - the caller asked to modify a future node
           - the caller has committed more revisions since this txn
           was constructed, and is asking to modify a node in one
           of those new revisions.
           In either case, the node may not have changed in those new
           revisions; use the node's ID to determine this case.  */
        const svn_fs_id_t *txn_noderev_id;
        svn_fs_root_t *rev_root;
        const svn_fs_id_t *new_noderev_id;

        /* The ID of the node that we would be modifying in the txn  */
        SVN_ERR(svn_fs_node_id(&txn_noderev_id, txn_root, fspath,
                               scratch_pool));

        /* Get the ID from the future/new revision.  */
        SVN_ERR(svn_fs_revision_root(&rev_root, svn_fs_root_fs(txn_root),
                                     revision, scratch_pool));
        SVN_ERR(svn_fs_node_id(&new_noderev_id, rev_root, fspath,
                               scratch_pool));
        svn_fs_close_root(rev_root);

        /* Has the target node changed in the future?  */
        if (svn_fs_compare_ids(txn_noderev_id, new_noderev_id) != 0)
        {
            /* Restarting the commit will base the txn on the future/new
               revision, allowing the modification at REVISION.  */
            /* ### use a custom error code  */
            return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
                                     _("'%s' has been modified since the "
                                       "commit began (restart the commit)"),
                                     fspath);
        }
    }

    return SVN_NO_ERROR;
}