Пример #1
0
/* Remove leading and trailing white space from a C string, in place. */
static void
trim_string(char **pstr)
{
  char *str = *pstr;
  size_t i;

  while (svn_ctype_isspace(*str))
    str++;
  *pstr = str;
  i = strlen(str);
  while ((i > 0) && svn_ctype_isspace(str[i-1]))
    i--;
  str[i] = '\0';
}
Пример #2
0
static APR_INLINE apr_size_t
string_first_non_whitespace(const char *str, apr_size_t len)
{
  apr_size_t i;

  for (i = 0; i < len; i++)
    {
      if (! svn_ctype_isspace(str[i]))
        return i;
    }

  /* if we get here, then the string must be entirely whitespace */
  return len;
}
Пример #3
0
void
svn_cstring_split_append(apr_array_header_t *array,
                         const char *input,
                         const char *sep_chars,
                         svn_boolean_t chop_whitespace,
                         apr_pool_t *pool)
{
  char *last;
  char *pats;
  char *p;

  pats = apr_pstrdup(pool, input);  /* strtok wants non-const data */
  p = apr_strtok(pats, sep_chars, &last);

  while (p)
    {
      if (chop_whitespace)
        {
          while (svn_ctype_isspace(*p))
            p++;

          {
            char *e = p + (strlen(p) - 1);
            while ((e >= p) && (svn_ctype_isspace(*e)))
              e--;
            *(++e) = '\0';
          }
        }

      if (p[0] != '\0')
        APR_ARRAY_PUSH(array, const char *) = p;

      p = apr_strtok(NULL, sep_chars, &last);
    }

  return;
}
Пример #4
0
void
svn_stringbuf_strip_whitespace(svn_stringbuf_t *str)
{
  /* Find first non-whitespace character */
  apr_size_t offset = svn_stringbuf_first_non_whitespace(str);

  /* Go ahead!  Waste some RAM, we've got pools! :)  */
  str->data += offset;
  str->len -= offset;
  str->blocksize -= offset;

  /* Now that we've trimmed the front, trim the end, wasting more RAM. */
  while ((str->len > 0) && svn_ctype_isspace(str->data[str->len - 1]))
    str->len--;
  str->data[str->len] = '\0';
}
Пример #5
0
svn_error_t *
svn_repos_parse_dumpstream3(svn_stream_t *stream,
                            const svn_repos_parse_fns3_t *parse_fns,
                            void *parse_baton,
                            svn_boolean_t deltas_are_text,
                            svn_cancel_func_t cancel_func,
                            void *cancel_baton,
                            apr_pool_t *pool)
{
    svn_boolean_t eof;
    svn_stringbuf_t *linebuf;
    void *rev_baton = NULL;
    char *buffer = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
    apr_size_t buflen = SVN__STREAM_CHUNK_SIZE;
    apr_pool_t *linepool = svn_pool_create(pool);
    apr_pool_t *revpool = svn_pool_create(pool);
    apr_pool_t *nodepool = svn_pool_create(pool);
    int version;

    SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));
    if (eof)
        return stream_ran_dry();

    /* The first two lines of the stream are the dumpfile-format version
       number, and a blank line.  To preserve backward compatibility,
       don't assume the existence of newer parser-vtable functions. */
    SVN_ERR(parse_format_version(&version, linebuf->data));
    if (parse_fns->magic_header_record != NULL)
        SVN_ERR(parse_fns->magic_header_record(version, parse_baton, pool));

    /* A dumpfile "record" is defined to be a header-block of
       rfc822-style headers, possibly followed by a content-block.

         - A header-block is always terminated by a single blank line (\n\n)

         - We know whether the record has a content-block by looking for
           a 'Content-length:' header.  The content-block will always be
           of a specific length, plus an extra newline.

       Once a record is fully sucked from the stream, an indeterminate
       number of blank lines (or lines that begin with whitespace) may
       follow before the next record (or the end of the stream.)
    */

    while (1)
    {
        apr_hash_t *headers;
        void *node_baton;
        svn_boolean_t found_node = FALSE;
        svn_boolean_t old_v1_with_cl = FALSE;
        const char *content_length;
        const char *prop_cl;
        const char *text_cl;
        const char *value;
        svn_filesize_t actual_prop_length;

        /* Clear our per-line pool. */
        svn_pool_clear(linepool);

        /* Check for cancellation. */
        if (cancel_func)
            SVN_ERR(cancel_func(cancel_baton));

        /* Keep reading blank lines until we discover a new record, or until
           the stream runs out. */
        SVN_ERR(svn_stream_readline(stream, &linebuf, "\n", &eof, linepool));

        if (eof)
        {
            if (svn_stringbuf_isempty(linebuf))
                break;   /* end of stream, go home. */
            else
                return stream_ran_dry();
        }

        if ((linebuf->len == 0) || (svn_ctype_isspace(linebuf->data[0])))
            continue; /* empty line ... loop */

        /*** Found the beginning of a new record. ***/

        /* The last line we read better be a header of some sort.
           Read the whole header-block into a hash. */
        SVN_ERR(read_header_block(stream, linebuf, &headers, linepool));

        /*** Handle the various header blocks. ***/

        /* Is this a revision record? */
        if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER))
        {
            /* If we already have a rev_baton open, we need to close it
               and clear the per-revision subpool. */
            if (rev_baton != NULL)
            {
                SVN_ERR(parse_fns->close_revision(rev_baton));
                svn_pool_clear(revpool);
            }

            SVN_ERR(parse_fns->new_revision_record(&rev_baton,
                                                   headers, parse_baton,
                                                   revpool));
        }
        /* Or is this, perhaps, a node record? */
        else if (svn_hash_gets(headers, SVN_REPOS_DUMPFILE_NODE_PATH))
        {
            SVN_ERR(parse_fns->new_node_record(&node_baton,
                                               headers,
                                               rev_baton,
                                               nodepool));
            found_node = TRUE;
        }
        /* Or is this the repos UUID? */
        else if ((value = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_UUID)))
        {
            SVN_ERR(parse_fns->uuid_record(value, parse_baton, pool));
        }
        /* Or perhaps a dumpfile format? */
        /* ### TODO: use parse_format_version */
        else if ((value = svn_hash_gets(headers,
                                        SVN_REPOS_DUMPFILE_MAGIC_HEADER)))
        {
            /* ### someday, switch modes of operation here. */
            SVN_ERR(svn_cstring_atoi(&version, value));
        }
        /* Or is this bogosity?! */
        else
        {
            /* What the heck is this record?!? */
            return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
                                    _("Unrecognized record type in stream"));
        }

        /* Need 3 values below to determine v1 dump type

           Old (pre 0.14?) v1 dumps don't have Prop-content-length
           and Text-content-length fields, but always have a properties
           block in a block with Content-Length > 0 */

        content_length = svn_hash_gets(headers,
                                       SVN_REPOS_DUMPFILE_CONTENT_LENGTH);
        prop_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH);
        text_cl = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH);
        old_v1_with_cl =
            version == 1 && content_length && ! prop_cl && ! text_cl;

        /* Is there a props content-block to parse? */
        if (prop_cl || old_v1_with_cl)
        {
            const char *delta = svn_hash_gets(headers,
                                              SVN_REPOS_DUMPFILE_PROP_DELTA);
            svn_boolean_t is_delta = (delta && strcmp(delta, "true") == 0);

            /* First, remove all node properties, unless this is a delta
               property block. */
            if (found_node && !is_delta)
                SVN_ERR(parse_fns->remove_node_props(node_baton));

            SVN_ERR(parse_property_block
                    (stream,
                     svn__atoui64(prop_cl ? prop_cl : content_length),
                     parse_fns,
                     found_node ? node_baton : rev_baton,
                     parse_baton,
                     found_node,
                     &actual_prop_length,
                     found_node ? nodepool : revpool));
        }

        /* Is there a text content-block to parse? */
        if (text_cl)
        {
            const char *delta = svn_hash_gets(headers,
                                              SVN_REPOS_DUMPFILE_TEXT_DELTA);
            svn_boolean_t is_delta = FALSE;
            if (! deltas_are_text)
                is_delta = (delta && strcmp(delta, "true") == 0);

            SVN_ERR(parse_text_block(stream,
                                     svn__atoui64(text_cl),
                                     is_delta,
                                     parse_fns,
                                     found_node ? node_baton : rev_baton,
                                     buffer,
                                     buflen,
                                     found_node ? nodepool : revpool));
        }
        else if (old_v1_with_cl)
        {
            /* An old-v1 block with a Content-length might have a text block.
               If the property block did not consume all the bytes of the
               Content-length, then it clearly does have a text block.
               If not, then we must deduce whether we have an *empty* text
               block or an *absent* text block.  The rules are:
               - "Node-kind: file" blocks have an empty (i.e. present, but
                 zero-length) text block, since they represent a file
                 modification.  Note that file-copied-text-unmodified blocks
                 have no Content-length - even if they should have contained
                 a modified property block, the pre-0.14 dumper forgets to
                 dump the modified properties.
               - If it is not a file node, then it is a revision or directory,
                 and so has an absent text block.
            */
            const char *node_kind;
            svn_filesize_t cl_value = svn__atoui64(content_length)
                                      - actual_prop_length;

            if (cl_value ||
                    ((node_kind = svn_hash_gets(headers,
                                                SVN_REPOS_DUMPFILE_NODE_KIND))
                     && strcmp(node_kind, "file") == 0)
               )
                SVN_ERR(parse_text_block(stream,
                                         cl_value,
                                         FALSE,
                                         parse_fns,
                                         found_node ? node_baton : rev_baton,
                                         buffer,
                                         buflen,
                                         found_node ? nodepool : revpool));
        }

        /* if we have a content-length header, did we read all of it?
           in case of an old v1, we *always* read all of it, because
           text-content-length == content-length - prop-content-length
        */
        if (content_length && ! old_v1_with_cl)
        {
            apr_size_t rlen, num_to_read;
            svn_filesize_t remaining =
                svn__atoui64(content_length) -
                (prop_cl ? svn__atoui64(prop_cl) : 0) -
                (text_cl ? svn__atoui64(text_cl) : 0);


            if (remaining < 0)
                return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL,
                                        _("Sum of subblock sizes larger than "
                                          "total block content length"));

            /* Consume remaining bytes in this content block */
            while (remaining > 0)
            {
                if (remaining >= (svn_filesize_t)buflen)
                    rlen = buflen;
                else
                    rlen = (apr_size_t) remaining;

                num_to_read = rlen;
                SVN_ERR(svn_stream_read(stream, buffer, &rlen));
                remaining -= rlen;
                if (rlen != num_to_read)
                    return stream_ran_dry();
            }
        }

        /* If we just finished processing a node record, we need to
           close the node record and clear the per-node subpool. */
        if (found_node)
        {
            SVN_ERR(parse_fns->close_node(node_baton));
            svn_pool_clear(nodepool);
        }

        /*** End of processing for one record. ***/

    } /* end of stream */

    /* Close out whatever revision we're in. */
    if (rev_baton != NULL)
        SVN_ERR(parse_fns->close_revision(rev_baton));

    svn_pool_destroy(linepool);
    svn_pool_destroy(revpool);
    svn_pool_destroy(nodepool);
    return SVN_NO_ERROR;
}
Пример #6
0
svn_error_t *
svn_cl__get_log_message(const char **log_msg,
                        const char **tmp_file,
                        const apr_array_header_t *commit_items,
                        void *baton,
                        apr_pool_t *pool)
{
  svn_stringbuf_t *default_msg = NULL;
  struct log_msg_baton *lmb = baton;
  svn_stringbuf_t *message = NULL;
  svn_config_t *cfg;
  const char *mfc_after, *sponsored_by;

  cfg = lmb->config ? svn_hash_gets(lmb->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;

  /* Set default message.  */
  default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Reported by:\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "MFC after:\t");
  svn_config_get(cfg, &mfc_after, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-mfc-after", NULL);
  if (mfc_after != NULL)
	  svn_stringbuf_appendcstr(default_msg, mfc_after);
  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "MFH:\t\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t");
  svn_config_get(cfg, &sponsored_by, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-sponsored-by",
#ifdef HAS_ORGANIZATION_NAME
  	ORGANIZATION_NAME);
#else
	NULL);
#endif
  if (sponsored_by != NULL)
	  svn_stringbuf_appendcstr(default_msg, sponsored_by);
  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "Differential Revision:\t" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above:                     76 columns --|" APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> PR:                       If and which Problem Report is related." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Submitted by:             If someone else sent in the change." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Reported by:              If someone else reported the issue." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Reviewed by:              If someone else reviewed your modification." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Approved by:              If you needed approval for this commit." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Obtained from:            If the change is from a third party." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> MFC after:                N [day[s]|week[s]|month[s]].  Request a reminder email." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> MFH:                      Ports tree branch name.  Request approval for merge." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Relnotes:                 Set to 'yes' for mention in release notes." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Security:                 Vulnerability reference (one per line) or description." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Sponsored by:             If the change was sponsored by an organization." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Differential Revision:    https://reviews.freebsd.org/D### (*full* phabric URL needed)." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);

  *tmp_file = NULL;
  if (lmb->message)
    {
      svn_string_t *log_msg_str = svn_string_create(lmb->message, pool);

      SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, NULL, NULL,
                                            log_msg_str, lmb->message_encoding,
                                            FALSE, pool, pool),
                _("Error normalizing log message to internal format"));

      /* Strip off the EOF marker text and the junk that follows it. */
      truncate_buffer_at_prefix(&(log_msg_str->len), (char *)log_msg_str->data,
                                EDITOR_EOF_PREFIX);

      cleanmsg(&(log_msg_str->len), (char*)log_msg_str->data);

      *log_msg = log_msg_str->data;
      return SVN_NO_ERROR;
    }

  if (! commit_items->nelts)
    {
      *log_msg = "";
      return SVN_NO_ERROR;
    }

  while (! message)
    {
      /* We still don't have a valid commit message.  Use $EDITOR to
         get one.  Note that svn_cl__edit_string_externally will still
         return a UTF-8'ized log message. */
      int i;
      svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
      svn_error_t *err = SVN_NO_ERROR;
      svn_string_t *msg_string = svn_string_create_empty(pool);

      for (i = 0; i < commit_items->nelts; i++)
        {
          svn_client_commit_item3_t *item
            = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
          const char *path = item->path;
          char text_mod = '_', prop_mod = ' ', unlock = ' ';

          if (! path)
            path = item->url;
          else if (lmb->base_dir)
            path = svn_dirent_is_child(lmb->base_dir, path, pool);

          /* If still no path, then just use current directory. */
          if (! path || !*path)
            path = ".";

          if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
              && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
            text_mod = 'R';
          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
            text_mod = 'A';
          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
            text_mod = 'D';
          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
            text_mod = 'M';

          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
            prop_mod = 'M';

          if (! lmb->keep_locks
              && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
            unlock = 'U';

          svn_stringbuf_appendbyte(tmp_message, text_mod);
          svn_stringbuf_appendbyte(tmp_message, prop_mod);
          svn_stringbuf_appendbyte(tmp_message, unlock);
          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
            /* History included via copy/move. */
            svn_stringbuf_appendcstr(tmp_message, "+ ");
          else
            svn_stringbuf_appendcstr(tmp_message, "  ");
          svn_stringbuf_appendcstr(tmp_message, path);
          svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
        }

      msg_string->data = tmp_message->data;
      msg_string->len = tmp_message->len;

      /* Use the external edit to get a log message. */
      if (! lmb->non_interactive)
        {
          err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
                                                    lmb->editor_cmd,
                                                    lmb->base_dir ? lmb->base_dir : "",
                                                    msg_string, "svn-commit",
                                                    lmb->config, TRUE,
                                                    lmb->message_encoding,
                                                    pool);
        }
      else /* non_interactive flag says we can't pop up an editor, so error */
        {
          return svn_error_create
            (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
             _("Cannot invoke editor to get log message "
               "when non-interactive"));
        }

      /* Dup the tmpfile path into its baton's pool. */
      *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
                                                  lmb->tmpfile_left);

      /* If the edit returned an error, handle it. */
      if (err)
        {
          if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
            err = svn_error_quick_wrap
              (err, _("Could not use external editor to fetch log message; "
                      "consider setting the $SVN_EDITOR environment variable "
                      "or using the --message (-m) or --file (-F) options"));
          return svn_error_trace(err);
        }

      if (msg_string)
        message = svn_stringbuf_create_from_string(msg_string, pool);

      /* Strip off the EOF marker text and the junk that follows it. */
      if (message)
        truncate_buffer_at_prefix(&message->len, message->data,
                                  EDITOR_EOF_PREFIX);
      /*
       * Since we're adding freebsd-specific tokens to the log message,
       * clean out any leftovers to avoid accidently sending them to other
       * projects that won't be expecting them.
       */
      if (message)
	cleanmsg(&message->len, message->data);

      if (message)
        {
          /* We did get message, now check if it is anything more than just
             white space as we will consider white space only as empty */
          apr_size_t len;

          for (len = 0; len < message->len; len++)
            {
              /* FIXME: should really use an UTF-8 whitespace test
                 rather than svn_ctype_isspace, which is ASCII only */
              if (! svn_ctype_isspace(message->data[len]))
                break;
            }
          if (len == message->len)
            message = NULL;
        }

      if (! message)
        {
          const char *reply;
          SVN_ERR(svn_cmdline_prompt_user2
                  (&reply,
                   _("\nLog message unchanged or not specified\n"
                     "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
          if (reply)
            {
              int letter = apr_tolower(reply[0]);

              /* If the user chooses to abort, we cleanup the
                 temporary file and exit the loop with a NULL
                 message. */
              if ('a' == letter)
                {
                  SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
                  *tmp_file = lmb->tmpfile_left = NULL;
                  break;
                }

              /* If the user chooses to continue, we make an empty
                 message, which will cause us to exit the loop.  We
                 also cleanup the temporary file. */
              if ('c' == letter)
                {
                  SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
                  *tmp_file = lmb->tmpfile_left = NULL;
                  message = svn_stringbuf_create_empty(pool);
                }

              /* If the user chooses anything else, the loop will
                 continue on the NULL message. */
            }
        }
    }

  *log_msg = message ? message->data : NULL;
  return SVN_NO_ERROR;
}
Пример #7
0
svn_error_t *
svn_cl__merge_file_externally(const char *base_path,
                              const char *their_path,
                              const char *my_path,
                              const char *merged_path,
                              const char *wc_path,
                              apr_hash_t *config,
                              svn_boolean_t *remains_in_conflict,
                              apr_pool_t *pool)
{
  char *merge_tool;
  /* Error if there is no editor specified */
  if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS)
    {
      struct svn_config_t *cfg;
      merge_tool = NULL;
      cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
      /* apr_env_get wants char **, this wants const char ** */
      svn_config_get(cfg, (const char **)&merge_tool,
                     SVN_CONFIG_SECTION_HELPERS,
                     SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL);
    }

  if (merge_tool)
    {
      const char *c;

      for (c = merge_tool; *c; c++)
        if (!svn_ctype_isspace(*c))
          break;

      if (! *c)
        return svn_error_create
          (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
           _("The SVN_MERGE environment variable is empty or "
             "consists solely of whitespace. Expected a shell command.\n"));
    }
  else
      return svn_error_create
        (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
         _("The environment variable SVN_MERGE and the merge-tool-cmd run-time "
           "configuration option were not set.\n"));

  {
    const char *arguments[7] = { 0 };
    char *cwd;
    int exitcode;

    apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool);
    if (status != 0)
      return svn_error_wrap_apr(status, NULL);

    arguments[0] = merge_tool;
    arguments[1] = base_path;
    arguments[2] = their_path;
    arguments[3] = my_path;
    arguments[4] = merged_path;
    arguments[5] = wc_path;
    arguments[6] = NULL;

    SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool,
                           arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL,
                           pool));
    /* Exit code 0 means the merge was successful.
     * Exit code 1 means the file was left in conflict but it
     * is OK to continue with the merge.
     * Any other exit code means there was a real problem. */
    if (exitcode != 0 && exitcode != 1)
      return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
        _("The external merge tool '%s' exited with exit code %d."),
        merge_tool, exitcode);
    else if (remains_in_conflict)
      *remains_in_conflict = exitcode == 1;
  }
  return SVN_NO_ERROR;
}
Пример #8
0
void
svn_diff__normalize_buffer(char **tgt,
                           apr_off_t *lengthp,
                           svn_diff__normalize_state_t *statep,
                           const char *buf,
                           const svn_diff_file_options_t *opts)
{
  /* Variables for looping through BUF */
  const char *curp, *endp;

  /* Variable to record normalizing state */
  svn_diff__normalize_state_t state = *statep;

  /* Variables to track what needs copying into the target buffer */
  const char *start = buf;
  apr_size_t include_len = 0;
  svn_boolean_t last_skipped = FALSE; /* makes sure we set 'start' */

  /* Variable to record the state of the target buffer */
  char *tgt_newend = *tgt;

  /* If this is a noop, then just get out of here. */
  if (! opts->ignore_space && ! opts->ignore_eol_style)
    {
      *tgt = (char *)buf;
      return;
    }


  /* It only took me forever to get this routine right,
     so here my thoughts go:

    Below, we loop through the data, doing 2 things:

     - Normalizing
     - Copying other data

     The routine tries its hardest *not* to copy data, but instead
     returning a pointer into already normalized existing data.

     To this end, a block 'other data' shouldn't be copied when found,
     but only as soon as it can't be returned in-place.

     On a character level, there are 3 possible operations:

     - Skip the character (don't include in the normalized data)
     - Include the character (do include in the normalizad data)
     - Include as another character
       This is essentially the same as skipping the current character
       and inserting a given character in the output data.

    The macros below (SKIP, INCLUDE and INCLUDE_AS) are defined to
    handle the character based operations.  The macros themselves
    collect character level data into blocks.

    At all times designate the START, INCLUDED_LEN and CURP pointers
    an included and and skipped block like this:

      [ start, start + included_len ) [ start + included_len, curp )
             INCLUDED                        EXCLUDED

    When the routine flips from skipping to including, the last
    included block has to be flushed to the output buffer.
  */

  /* Going from including to skipping; only schedules the current
     included section for flushing.
     Also, simply chop off the character if it's the first in the buffer,
     so we can possibly just return the remainder of the buffer */
#define SKIP             \
  do {                   \
    if (start == curp)   \
       ++start;          \
    last_skipped = TRUE; \
  } while (0)

#define INCLUDE                \
  do {                         \
    if (last_skipped)          \
      COPY_INCLUDED_SECTION;   \
    ++include_len;             \
    last_skipped = FALSE;      \
  } while (0)

#define COPY_INCLUDED_SECTION                     \
  do {                                            \
    if (include_len > 0)                          \
      {                                           \
         memmove(tgt_newend, start, include_len); \
         tgt_newend += include_len;               \
         include_len = 0;                         \
      }                                           \
    start = curp;                                 \
  } while (0)

  /* Include the current character as character X.
     If the current character already *is* X, add it to the
     currently included region, increasing chances for consecutive
     fully normalized blocks. */
#define INCLUDE_AS(x)          \
  do {                         \
    if (*curp == (x))          \
      INCLUDE;                 \
    else                       \
      {                        \
        INSERT((x));           \
        SKIP;                  \
      }                        \
  } while (0)

  /* Insert character X in the output buffer */
#define INSERT(x)              \
  do {                         \
    COPY_INCLUDED_SECTION;     \
    *tgt_newend++ = (x);       \
  } while (0)

  for (curp = buf, endp = buf + *lengthp; curp != endp; ++curp)
    {
      switch (*curp)
        {
        case '\r':
          if (opts->ignore_eol_style)
            INCLUDE_AS('\n');
          else
            INCLUDE;
          state = svn_diff__normalize_state_cr;
          break;

        case '\n':
          if (state == svn_diff__normalize_state_cr
              && opts->ignore_eol_style)
            SKIP;
          else
            INCLUDE;
          state = svn_diff__normalize_state_normal;
          break;

        default:
          if (svn_ctype_isspace(*curp)
              && opts->ignore_space != svn_diff_file_ignore_space_none)
            {
              /* Whitespace but not '\r' or '\n' */
              if (state != svn_diff__normalize_state_whitespace
                  && opts->ignore_space
                     == svn_diff_file_ignore_space_change)
                /*### If we can postpone insertion of the space
                  until the next non-whitespace character,
                  we have a potential of reducing the number of copies:
                  If this space is followed by more spaces,
                  this will cause a block-copy.
                  If the next non-space block is considered normalized
                  *and* preceded by a space, we can take advantage of that. */
                /* Note, the above optimization applies to 90% of the source
                   lines in our own code, since it (generally) doesn't use
                   more than one space per blank section, except for the
                   beginning of a line. */
                INCLUDE_AS(' ');
              else
                SKIP;
              state = svn_diff__normalize_state_whitespace;
            }
          else
            {
              /* Non-whitespace character, or whitespace character in
                 svn_diff_file_ignore_space_none mode. */
              INCLUDE;
              state = svn_diff__normalize_state_normal;
            }
        }
    }

  /* If we're not in whitespace, flush the last chunk of data.
   * Note that this will work correctly when this is the last chunk of the
   * file:
   * * If there is an eol, it will either have been output when we entered
   *   the state_cr, or it will be output now.
   * * If there is no eol and we're not in whitespace, then we just output
   *   everything below.
   * * If there's no eol and we are in whitespace, we want to ignore
   *   whitespace unconditionally. */

  if (*tgt == tgt_newend)
    {
      /* we haven't copied any data in to *tgt and our chunk consists
         only of one block of (already normalized) data.
         Just return the block. */
      *tgt = (char *)start;
      *lengthp = include_len;
    }
  else
    {
      COPY_INCLUDED_SECTION;
      *lengthp = tgt_newend - *tgt;
    }

  *statep = state;

#undef SKIP
#undef INCLUDE
#undef INCLUDE_AS
#undef INSERT
#undef COPY_INCLUDED_SECTION
}
Пример #9
0
svn_error_t *
svn_cl__get_log_message(const char **log_msg,
                        const char **tmp_file,
                        const apr_array_header_t *commit_items,
                        void *baton,
                        apr_pool_t *pool)
{
  svn_stringbuf_t *default_msg = NULL;
  struct log_msg_baton *lmb = baton;
  svn_stringbuf_t *message = NULL;

  /* Set default message.  */
  default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
  svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
  svn_stringbuf_appendcstr(default_msg, APR_EOL_STR APR_EOL_STR);

  *tmp_file = NULL;
  if (lmb->message)
    {
      svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool);
      svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str));

      /* Trim incoming messages of the EOF marker text and the junk
         that follows it.  */
      truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data,
                                EDITOR_EOF_PREFIX);

      /* Make a string from a stringbuf, sharing the data allocation. */
      log_msg_str->data = log_msg_buf->data;
      log_msg_str->len = log_msg_buf->len;
      SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE,
                                            log_msg_str, lmb->message_encoding,
                                            FALSE, pool, pool),
                _("Error normalizing log message to internal format"));

      *log_msg = log_msg_str->data;
      return SVN_NO_ERROR;
    }

  if (! commit_items->nelts)
    {
      *log_msg = "";
      return SVN_NO_ERROR;
    }

  while (! message)
    {
      /* We still don't have a valid commit message.  Use $EDITOR to
         get one.  Note that svn_cl__edit_externally will still return
         a UTF-8'ized log message. */
      int i;
      svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
      svn_error_t *err = SVN_NO_ERROR;
      svn_string_t *msg_string = svn_string_create("", pool);

      for (i = 0; i < commit_items->nelts; i++)
        {
          svn_client_commit_item3_t *item
            = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
          const char *path = item->path;
          char text_mod = '_', prop_mod = ' ', unlock = ' ';

          if (! path)
            path = item->url;
          else if (! *path)
            path = ".";

          if (! svn_path_is_url(path) && lmb->base_dir)
            path = svn_dirent_is_child(lmb->base_dir, path, pool);

          /* If still no path, then just use current directory. */
          if (! path)
            path = ".";

          if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
              && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
            text_mod = 'R';
          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
            text_mod = 'A';
          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
            text_mod = 'D';
          else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
            text_mod = 'M';

          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
            prop_mod = 'M';

          if (! lmb->keep_locks
              && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
            unlock = 'U';

          svn_stringbuf_appendbyte(tmp_message, text_mod);
          svn_stringbuf_appendbyte(tmp_message, prop_mod);
          svn_stringbuf_appendbyte(tmp_message, unlock);
          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
            /* History included via copy/move. */
            svn_stringbuf_appendcstr(tmp_message, "+ ");
          else
            svn_stringbuf_appendcstr(tmp_message, "  ");
          svn_stringbuf_appendcstr(tmp_message, path);
          svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
        }

      msg_string->data = tmp_message->data;
      msg_string->len = tmp_message->len;

      /* Use the external edit to get a log message. */
      if (! lmb->non_interactive)
        {
          err = svn_cl__edit_string_externally(&msg_string, &lmb->tmpfile_left,
                                               lmb->editor_cmd, lmb->base_dir,
                                               msg_string, "svn-commit",
                                               lmb->config, TRUE,
                                               lmb->message_encoding,
                                               pool);
        }
      else /* non_interactive flag says we can't pop up an editor, so error */
        {
          return svn_error_create
            (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
             _("Cannot invoke editor to get log message "
               "when non-interactive"));
        }

      /* Dup the tmpfile path into its baton's pool. */
      *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
                                                  lmb->tmpfile_left);

      /* If the edit returned an error, handle it. */
      if (err)
        {
          if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
            err = svn_error_quick_wrap
              (err, _("Could not use external editor to fetch log message; "
                      "consider setting the $SVN_EDITOR environment variable "
                      "or using the --message (-m) or --file (-F) options"));
          return svn_error_trace(err);
        }

      if (msg_string)
        message = svn_stringbuf_create_from_string(msg_string, pool);

      /* Strip the prefix from the buffer. */
      if (message)
        truncate_buffer_at_prefix(&message->len, message->data,
                                  EDITOR_EOF_PREFIX);

      if (message)
        {
          /* We did get message, now check if it is anything more than just
             white space as we will consider white space only as empty */
          apr_size_t len;

          for (len = 0; len < message->len; len++)
            {
              /* FIXME: should really use an UTF-8 whitespace test
                 rather than svn_ctype_isspace, which is ASCII only */
              if (! svn_ctype_isspace(message->data[len]))
                break;
            }
          if (len == message->len)
            message = NULL;
        }

      if (! message)
        {
          const char *reply;
          SVN_ERR(svn_cmdline_prompt_user2
                  (&reply,
                   _("\nLog message unchanged or not specified\n"
                     "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
          if (reply)
            {
              int letter = apr_tolower(reply[0]);

              /* If the user chooses to abort, we cleanup the
                 temporary file and exit the loop with a NULL
                 message. */
              if ('a' == letter)
                {
                  SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
                  *tmp_file = lmb->tmpfile_left = NULL;
                  break;
                }

              /* If the user chooses to continue, we make an empty
                 message, which will cause us to exit the loop.  We
                 also cleanup the temporary file. */
              if ('c' == letter)
                {
                  SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
                  *tmp_file = lmb->tmpfile_left = NULL;
                  message = svn_stringbuf_create("", pool);
                }

              /* If the user chooses anything else, the loop will
                 continue on the NULL message. */
            }
        }
    }

  *log_msg = message ? message->data : NULL;
  return SVN_NO_ERROR;
}
Пример #10
0
/* Helper for the next two functions.  Set *EDITOR to some path to an
   editor binary.  Sources to search include: the EDITOR_CMD argument
   (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
   is not NULL), $VISUAL, $EDITOR.  Return
   SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
static svn_error_t *
find_editor_binary(const char **editor,
                   const char *editor_cmd,
                   apr_hash_t *config)
{
  const char *e;
  struct svn_config_t *cfg;

  /* Use the editor specified on the command line via --editor-cmd, if any. */
  e = editor_cmd;

  /* Otherwise look for the Subversion-specific environment variable. */
  if (! e)
    e = getenv("SVN_EDITOR");

  /* If not found then fall back on the config file. */
  if (! e)
    {
      cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
                                  APR_HASH_KEY_STRING) : NULL;
      svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS,
                     SVN_CONFIG_OPTION_EDITOR_CMD, NULL);
    }

  /* If not found yet then try general purpose environment variables. */
  if (! e)
    e = getenv("VISUAL");
  if (! e)
    e = getenv("EDITOR");

#ifdef SVN_CLIENT_EDITOR
  /* If still not found then fall back on the hard-coded default. */
  if (! e)
    e = SVN_CLIENT_EDITOR;
#endif

  /* Error if there is no editor specified */
  if (e)
    {
      const char *c;

      for (c = e; *c; c++)
        if (!svn_ctype_isspace(*c))
          break;

      if (! *c)
        return svn_error_create
          (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
           _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
             "'editor-cmd' run-time configuration option is empty or "
             "consists solely of whitespace. Expected a shell command."));
    }
  else
    return svn_error_create
      (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
       _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
         "set, and no 'editor-cmd' run-time configuration option was found"));

  *editor = e;
  return SVN_NO_ERROR;
}
Пример #11
0
/* Callback to perform some simple sanity checks on an authz rule.
 *
 * - If RULE_MATCH_STRING references a group or an alias, verify that
 *   the group or alias definition exists.
 * - If RULE_MATCH_STRING specifies a token (starts with $), verify
 *   that the token name is valid.
 * - If RULE_MATCH_STRING is using inversion, verify that it isn't
 *   doing it more than once within the one rule, and that it isn't
 *   "~*", as that would never match.
 * - Check that VALUE part of the rule specifies only allowed rule
 *   flag characters ('r' and 'w').
 *
 * Return TRUE if the rule has no errors. Use BATON for context and
 * error reporting.
 */
static svn_boolean_t authz_validate_rule(const char *rule_match_string,
                                         const char *value,
                                         void *baton,
                                         apr_pool_t *pool)
{
  const char *val;
  const char *match = rule_match_string;
  struct authz_validate_baton *b = baton;

  /* Make sure the user isn't using double-negatives. */
  if (match[0] == '~')
    {
      /* Bump the pointer past the inversion for the other checks. */
      match++;

      /* Another inversion is a double negative; we can't not stop. */
      if (match[0] == '~')
        {
          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
                                     "Rule '%s' has more than one "
                                     "inversion; double negatives are "
                                     "not permitted.",
                                     rule_match_string);
          return FALSE;
        }

      /* Make sure that the rule isn't "~*", which won't ever match. */
      if (strcmp(match, "*") == 0)
        {
          b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
                                    "Authz rules with match string '~*' "
                                    "are not allowed, because they never "
                                    "match anyone.");
          return FALSE;
        }
    }

  /* If the rule applies to a group, check its existence. */
  if (match[0] == '@')
    {
      const char *group = &match[1];

      svn_config_get(b->config, &val, "groups", group, NULL);

      /* Having a non-existent group in the ACL configuration might be
         the sign of a typo.  Refuse to perform authz on uncertain
         rules. */
      if (!val)
        {
          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
                                     "An authz rule refers to group "
                                     "'%s', which is undefined",
                                     rule_match_string);
          return FALSE;
        }
    }

  /* If the rule applies to an alias, check its existence. */
  if (match[0] == '&')
    {
      const char *alias = &match[1];

      svn_config_get(b->config, &val, "aliases", alias, NULL);

      if (!val)
        {
          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
                                     "An authz rule refers to alias "
                                     "'%s', which is undefined",
                                     rule_match_string);
          return FALSE;
        }
     }

  /* If the rule specifies a token, check its validity. */
  if (match[0] == '$')
    {
      const char *token_name = &match[1];

      if ((strcmp(token_name, "anonymous") != 0)
       && (strcmp(token_name, "authenticated") != 0))
        {
          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
                                     "Unrecognized authz token '%s'.",
                                     rule_match_string);
          return FALSE;
        }
    }

  val = value;

  while (*val)
    {
      if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val))
        {
          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
                                     "The character '%c' in rule '%s' is not "
                                     "allowed in authz rules", *val,
                                     rule_match_string);
          return FALSE;
        }

      ++val;
    }

  return TRUE;
}
Пример #12
0
/* A helper function to parse svn:mergeinfo diffs.
 *
 * These diffs use a special pretty-print format, for instance:
 *
 * Added: svn:mergeinfo
 * ## -0,0 +0,1 ##
 *   Merged /trunk:r2-3
 *
 * The hunk header has the following format:
 * ## -0,NUMBER_OF_REVERSE_MERGES +0,NUMBER_OF_FORWARD_MERGES ##
 *
 * At this point, the number of reverse merges has already been
 * parsed into HUNK->ORIGINAL_LENGTH, and the number of forward
 * merges has been parsed into HUNK->MODIFIED_LENGTH.
 *
 * The header is followed by a list of mergeinfo, one path per line.
 * This function parses such lines. Lines describing reverse merges
 * appear first, and then all lines describing forward merges appear.
 *
 * Parts of the line are affected by i18n. The words 'Merged'
 * and 'Reverse-merged' can appear in any language and at any
 * position within the line. We can only assume that a leading
 * '/' starts the merge source path, the path is followed by
 * ":r", which in turn is followed by a mergeinfo revision range,
 *  which is terminated by whitespace or end-of-string.
 *
 * If the current line meets the above criteria and we're able
 * to parse valid mergeinfo from it, the resulting mergeinfo
 * is added to patch->mergeinfo or patch->reverse_mergeinfo,
 * and we proceed to the next line.
 */
static svn_error_t *
parse_mergeinfo(svn_boolean_t *found_mergeinfo,
                svn_stringbuf_t *line,
                svn_diff_hunk_t *hunk,
                svn_patch_t *patch,
                apr_pool_t *result_pool,
                apr_pool_t *scratch_pool)
{
  char *slash = strchr(line->data, '/');
  char *colon = strrchr(line->data, ':');

  *found_mergeinfo = FALSE;

  if (slash && colon && colon[1] == 'r' && slash < colon)
    {
      svn_stringbuf_t *input;
      svn_mergeinfo_t mergeinfo = NULL;
      char *s;
      svn_error_t *err;

      input = svn_stringbuf_create_ensure(line->len, scratch_pool);

      /* Copy the merge source path + colon */
      s = slash;
      while (s <= colon)
        {
          svn_stringbuf_appendbyte(input, *s);
          s++;
        }

      /* skip 'r' after colon */
      s++;

      /* Copy the revision range. */
      while (s < line->data + line->len)
        {
          if (svn_ctype_isspace(*s))
            break;
          svn_stringbuf_appendbyte(input, *s);
          s++;
        }

      err = svn_mergeinfo_parse(&mergeinfo, input->data, result_pool);
      if (err && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
        {
          svn_error_clear(err);
          mergeinfo = NULL;
        }
      else
        SVN_ERR(err);

      if (mergeinfo)
        {
          if (hunk->original_length > 0) /* reverse merges */
            {
              if (patch->reverse)
                {
                  if (patch->mergeinfo == NULL)
                    patch->mergeinfo = mergeinfo;
                  else
                    SVN_ERR(svn_mergeinfo_merge2(patch->mergeinfo,
                                                 mergeinfo,
                                                 result_pool,
                                                 scratch_pool));
                }
              else
                {
                  if (patch->reverse_mergeinfo == NULL)
                    patch->reverse_mergeinfo = mergeinfo;
                  else
                    SVN_ERR(svn_mergeinfo_merge2(patch->reverse_mergeinfo,
                                                 mergeinfo,
                                                 result_pool,
                                                 scratch_pool));
                }
              hunk->original_length--;
            }
          else if (hunk->modified_length > 0) /* forward merges */
            {
              if (patch->reverse)
                {
                  if (patch->reverse_mergeinfo == NULL)
                    patch->reverse_mergeinfo = mergeinfo;
                  else
                    SVN_ERR(svn_mergeinfo_merge2(patch->reverse_mergeinfo,
                                                 mergeinfo,
                                                 result_pool,
                                                 scratch_pool));
                }
              else
                {
                  if (patch->mergeinfo == NULL)
                    patch->mergeinfo = mergeinfo;
                  else
                    SVN_ERR(svn_mergeinfo_merge2(patch->mergeinfo,
                                                 mergeinfo,
                                                 result_pool,
                                                 scratch_pool));
                }
              hunk->modified_length--;
            }

          *found_mergeinfo = TRUE;
        }
    }

  return SVN_NO_ERROR;
}