Exemplo n.º 1
0
/* If PATH needs to be escaped, return an escaped version of it, allocated
 * from RESULT_POOL. Otherwise, return PATH directly. */
static const char *
auto_escape_path(const char *path,
                 apr_pool_t *result_pool)
{
  apr_size_t len = strlen(path);
  apr_size_t i;
  const char esc = '\x1b';

  for (i = 0; i < len; ++i)
    if (path[i] < ' ')
      {
        svn_stringbuf_t *escaped = svn_stringbuf_create_ensure(2 * len,
                                                               result_pool);
        for (i = 0; i < len; ++i)
          if (path[i] < ' ')
            {
              svn_stringbuf_appendbyte(escaped, esc);
              svn_stringbuf_appendbyte(escaped, path[i] + 'A' - 1);
            }
          else
            {
              svn_stringbuf_appendbyte(escaped, path[i]);
            }

        return escaped->data;
      }

   return path;
}
Exemplo n.º 2
0
/* Append the concrete representation of SKEL to the string STR. */
static svn_stringbuf_t *
unparse(const svn_skel_t *skel, svn_stringbuf_t *str)
{
  if (skel->is_atom)
    {
      /* Append an atom to STR.  */
      if (use_implicit(skel))
        svn_stringbuf_appendbytes(str, skel->data, skel->len);
      else
        {
          /* Append the length to STR.  Ensure enough space for at least
           * one 64 bit int. */
          char buf[200 + SVN_INT64_BUFFER_SIZE];
          apr_size_t length_len;

          length_len = svn__ui64toa(buf, skel->len);

          SVN_ERR_ASSERT_NO_RETURN(length_len > 0);

          /* Make sure we have room for the length, the space, and the
             atom's contents.  */
          svn_stringbuf_ensure(str, str->len + length_len + 1 + skel->len);
          svn_stringbuf_appendbytes(str, buf, length_len);
          svn_stringbuf_appendbyte(str, ' ');
          svn_stringbuf_appendbytes(str, skel->data, skel->len);
        }
    }
  else
    {
      /* Append a list to STR: an opening parenthesis, the list elements
       * separated by a space, and a closing parenthesis.  */
      svn_skel_t *child;

      svn_stringbuf_appendbyte(str, '(');

      for (child = skel->children; child; child = child->next)
        {
          unparse(child, str);
          if (child->next)
            svn_stringbuf_appendbyte(str, ' ');
        }

      svn_stringbuf_appendbyte(str, ')');
    }

  return str;
}
Exemplo n.º 3
0
/* Write a single change entry, path PATH, change CHANGE, to STREAM.

   All temporary allocations are in SCRATCH_POOL. */
static svn_error_t *
write_change_entry(svn_stream_t *stream,
                   svn_fs_x__change_t *change,
                   apr_pool_t *scratch_pool)
{
  const char *change_string = NULL;
  const char *kind_string = "";
  svn_stringbuf_t *buf;
  apr_size_t len;

  switch (change->change_kind)
    {
    case svn_fs_path_change_modify:
      change_string = ACTION_MODIFY;
      break;
    case svn_fs_path_change_add:
      change_string = ACTION_ADD;
      break;
    case svn_fs_path_change_delete:
      change_string = ACTION_DELETE;
      break;
    case svn_fs_path_change_replace:
      change_string = ACTION_REPLACE;
      break;
    default:
      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
                               _("Invalid change type %d"),
                               change->change_kind);
    }

  SVN_ERR_ASSERT(change->node_kind == svn_node_dir
                 || change->node_kind == svn_node_file);
  kind_string = apr_psprintf(scratch_pool, "-%s",
                             change->node_kind == svn_node_dir
                             ? SVN_FS_X__KIND_DIR
                             : SVN_FS_X__KIND_FILE);

  buf = svn_stringbuf_createf(scratch_pool, "%s%s %s %s %s %s\n",
                              change_string, kind_string,
                              change->text_mod ? FLAG_TRUE : FLAG_FALSE,
                              change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
                              change->mergeinfo_mod == svn_tristate_true
                                               ? FLAG_TRUE : FLAG_FALSE,
                              auto_escape_path(change->path.data, scratch_pool));

  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
    {
      svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
                               change->copyfrom_rev,
                               auto_escape_path(change->copyfrom_path,
                                                scratch_pool)));
    }

  svn_stringbuf_appendbyte(buf, '\n');

  /* Write all change info in one write call. */
  len = buf->len;
  return svn_error_trace(svn_stream_write(stream, buf->data, &len));
}
Exemplo n.º 4
0
/* Create a separator string of the appropriate length. */
static const char *
get_sep_string(apr_pool_t *result_pool)
{
  int line_width = LINE_DISPLAY_WIDTH;
  int i;
  svn_stringbuf_t *buf;

  buf = svn_stringbuf_create_empty(result_pool);
  for (i = 0; i < line_width; i++)
    svn_stringbuf_appendbyte(buf, '-');
  svn_stringbuf_appendbyte(buf, '+');
  for (i = 0; i < line_width; i++)
    svn_stringbuf_appendbyte(buf, '-');
  svn_stringbuf_appendbyte(buf, '\n');

  return buf->data;
}
Exemplo n.º 5
0
/* Append the concrete representation of SKEL to the string STR.
   Grow S with new space from POOL as necessary.  */
static svn_stringbuf_t *
unparse(const svn_skel_t *skel, svn_stringbuf_t *str, apr_pool_t *pool)
{
  if (skel->is_atom)
    {
      /* Append an atom to STR.  */
      if (use_implicit(skel))
        svn_stringbuf_appendbytes(str, skel->data, skel->len);
      else
        {
          /* Append the length to STR.  */
          char buf[200];
          int length_len;

          length_len = putsize(buf, sizeof(buf), skel->len);

          SVN_ERR_ASSERT_NO_RETURN(length_len > 0);

          /* Make sure we have room for the length, the space, and the
             atom's contents.  */
          svn_stringbuf_ensure(str, str->len + length_len + 1 + skel->len);
          svn_stringbuf_appendbytes(str, buf, length_len);
          str->data[str->len++] = ' ';
          svn_stringbuf_appendbytes(str, skel->data, skel->len);
        }
    }
  else
    {
      /* Append a list to STR.  */
      svn_skel_t *child;

      /* Emit an opening parenthesis.  */
      svn_stringbuf_ensure(str, str->len + 1);
      str->data[str->len++] = '(';

      /* Append each element.  Emit a space between each pair of elements.  */
      for (child = skel->children; child; child = child->next)
        {
          unparse(child, str, pool);
          if (child->next)
            {
              svn_stringbuf_ensure(str, str->len + 1);
              str->data[str->len++] = ' ';
            }
        }

      /* Emit a closing parenthesis.  */
      svn_stringbuf_appendbyte(str, ')');
    }

  return str;
}
Exemplo n.º 6
0
/* Read a field, possibly with escaped bytes, from [*BUF, END),
   stopping at the terminator.  Place the read string in *RESULT, or set
   *RESULT to NULL if it is the empty string.  Allocate the returned string
   in POOL.  Advance *BUF to point after the terminator. */
static svn_error_t *
read_str(const char **result,
         char **buf, const char *end,
         apr_pool_t *pool)
{
    svn_stringbuf_t *s = NULL;
    const char *start;
    if (*buf == end)
        return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
                                _("Unexpected end of entry"));
    if (**buf == '\n')
    {
        *result = NULL;
        (*buf)++;
        return SVN_NO_ERROR;
    }

    start = *buf;
    while (*buf != end && **buf != '\n')
    {
        if (**buf == '\\')
        {
            char c;
            if (! s)
                s = svn_stringbuf_ncreate(start, *buf - start, pool);
            else
                svn_stringbuf_appendbytes(s, start, *buf - start);
            (*buf)++;
            SVN_ERR(read_escaped(&c, buf, end));
            svn_stringbuf_appendbyte(s, c);
            start = *buf;
        }
        else
            (*buf)++;
    }

    if (*buf == end)
        return svn_error_create(SVN_ERR_WC_CORRUPT, NULL,
                                _("Unexpected end of entry"));

    if (s)
    {
        svn_stringbuf_appendbytes(s, start, *buf - start);
        *result = s->data;
    }
    else
        *result = apr_pstrndup(pool, start, *buf - start);
    (*buf)++;
    return SVN_NO_ERROR;
}
Exemplo n.º 7
0
/* If PATH has been escaped, return the un-escaped version of it, allocated
 * from RESULT_POOL. Otherwise, return PATH directly. */
static const char *
auto_unescape_path(const char *path,
                   apr_pool_t *result_pool)
{
  const char esc = '\x1b';
  if (strchr(path, esc))
    {
      apr_size_t len = strlen(path);
      apr_size_t i;

      svn_stringbuf_t *unescaped = svn_stringbuf_create_ensure(len,
                                                               result_pool);
      for (i = 0; i < len; ++i)
        if (path[i] == esc)
          svn_stringbuf_appendbyte(unescaped, path[++i] + 1 - 'A');
        else
          svn_stringbuf_appendbyte(unescaped, path[i]);

      return unescaped->data;
    }

   return path;
}
Exemplo n.º 8
0
/* Guts of svn_stream_readline().
 * Returns the line read from STREAM in *STRINGBUF, and indicates
 * end-of-file in *EOF.  If DETECT_EOL is TRUE, the end-of-line indicator
 * is detected automatically and returned in *EOL.
 * If DETECT_EOL is FALSE, *EOL must point to the desired end-of-line
 * indicator.  STRINGBUF is allocated in POOL. */
static svn_error_t *
stream_readline_bytewise(svn_stringbuf_t **stringbuf,
                         svn_boolean_t *eof,
                         const char *eol,
                         svn_stream_t *stream,
                         apr_pool_t *pool)
{
  svn_stringbuf_t *str;
  apr_size_t numbytes;
  const char *match;
  char c;

  /* Since we're reading one character at a time, let's at least
     optimize for the 90% case.  90% of the time, we can avoid the
     stringbuf ever having to realloc() itself if we start it out at
     80 chars.  */
  str = svn_stringbuf_create_ensure(LINE_CHUNK_SIZE, pool);

  /* Read into STR up to and including the next EOL sequence. */
  match = eol;
  while (*match)
    {
      numbytes = 1;
      SVN_ERR(svn_stream_read(stream, &c, &numbytes));
      if (numbytes != 1)
        {
          /* a 'short' read means the stream has run out. */
          *eof = TRUE;
          *stringbuf = str;
          return SVN_NO_ERROR;
        }

      if (c == *match)
        match++;
      else
        match = eol;

      svn_stringbuf_appendbyte(str, c);
    }

  *eof = FALSE;
  svn_stringbuf_chop(str, match - eol);
  *stringbuf = str;

  return SVN_NO_ERROR;
}
Exemplo n.º 9
0
/* generate some poorly compressable data */
static svn_stringbuf_t *
generate_test_bytes(int num_bytes, apr_pool_t *pool)
{
  svn_stringbuf_t *buffer = svn_stringbuf_create_empty(pool);
  int total, repeat, repeat_iter;
  char c;

  for (total = 0, repeat = repeat_iter = 1, c = 0; total < num_bytes; total++)
    {
      svn_stringbuf_appendbyte(buffer, c);

      repeat_iter--;
      if (repeat_iter == 0)
        {
          if (c == 127)
            repeat++;
          c = (char)((c + 1) % 127);
          repeat_iter = repeat;
        }
    }

  return buffer;
}
Exemplo n.º 10
0
/* Take the ORIGINAL string and replace all occurrences of ":" without
 * limiting the key space.  Allocate the result in POOL.
 */
static const char *
normalize_key_part(const char *original,
                   apr_pool_t *pool)
{
  apr_size_t i;
  apr_size_t len = strlen(original);
  svn_stringbuf_t *normalized = svn_stringbuf_create_ensure(len, pool);

  for (i = 0; i < len; ++i)
    {
      char c = original[i];
      switch (c)
        {
        case ':': svn_stringbuf_appendbytes(normalized, "%_", 2);
                  break;
        case '%': svn_stringbuf_appendbytes(normalized, "%%", 2);
                  break;
        default : svn_stringbuf_appendbyte(normalized, c);
        }
    }

  return normalized->data;
}
Exemplo n.º 11
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;
}
Exemplo n.º 12
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;
}
Exemplo n.º 13
0
/* Write a single change entry, path PATH, change CHANGE, to STREAM.

   Only include the node kind field if INCLUDE_NODE_KIND is true.  Only
   include the mergeinfo-mod field if INCLUDE_MERGEINFO_MODS is true.
   All temporary allocations are in SCRATCH_POOL. */
static svn_error_t *
write_change_entry(svn_stream_t *stream,
                   const char *path,
                   svn_fs_path_change2_t *change,
                   svn_boolean_t include_node_kind,
                   svn_boolean_t include_mergeinfo_mods,
                   apr_pool_t *scratch_pool)
{
  const char *idstr;
  const char *change_string = NULL;
  const char *kind_string = "";
  const char *mergeinfo_string = "";
  svn_stringbuf_t *buf;
  apr_size_t len;

  switch (change->change_kind)
    {
    case svn_fs_path_change_modify:
      change_string = ACTION_MODIFY;
      break;
    case svn_fs_path_change_add:
      change_string = ACTION_ADD;
      break;
    case svn_fs_path_change_delete:
      change_string = ACTION_DELETE;
      break;
    case svn_fs_path_change_replace:
      change_string = ACTION_REPLACE;
      break;
    case svn_fs_path_change_reset:
      change_string = ACTION_RESET;
      break;
    default:
      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
                               _("Invalid change type %d"),
                               change->change_kind);
    }

  if (change->node_rev_id)
    idstr = svn_fs_fs__id_unparse(change->node_rev_id, scratch_pool)->data;
  else
    idstr = ACTION_RESET;

  if (include_node_kind)
    {
      SVN_ERR_ASSERT(change->node_kind == svn_node_dir
                     || change->node_kind == svn_node_file);
      kind_string = apr_psprintf(scratch_pool, "-%s",
                                 change->node_kind == svn_node_dir
                                 ? SVN_FS_FS__KIND_DIR
                                  : SVN_FS_FS__KIND_FILE);
    }

  if (include_mergeinfo_mods && change->mergeinfo_mod != svn_tristate_unknown)
    mergeinfo_string = apr_psprintf(scratch_pool, " %s",
                                    change->mergeinfo_mod == svn_tristate_true
                                      ? FLAG_TRUE
                                      : FLAG_FALSE);

  buf = svn_stringbuf_createf(scratch_pool, "%s %s%s %s %s%s %s\n",
                              idstr, change_string, kind_string,
                              change->text_mod ? FLAG_TRUE : FLAG_FALSE,
                              change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
                              mergeinfo_string,
                              path);

  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
    {
      svn_stringbuf_appendcstr(buf, apr_psprintf(scratch_pool, "%ld %s",
                                                 change->copyfrom_rev,
                                                 change->copyfrom_path));
    }

   svn_stringbuf_appendbyte(buf, '\n');

   /* Write all change info in one write call. */
   len = buf->len;
   return svn_error_trace(svn_stream_write(stream, buf->data, &len));
}
Exemplo n.º 14
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;
}
Exemplo n.º 15
0
/* Remove r0 references from the mergeinfo string *STR.
 *
 * r0 was never a valid mergeinfo reference and cannot be committed with
 * recent servers, but can be committed through a server older than 1.6.18
 * for HTTP or older than 1.6.17 for the other protocols. See issue #4476
 * "Mergeinfo containing r0 makes svnsync and dump and load fail".
 *
 * Set *WAS_CHANGED to TRUE if *STR was changed, otherwise to FALSE.
 */
static svn_error_t *
remove_r0_mergeinfo(const svn_string_t **str,
                    svn_boolean_t *was_changed,
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
{
  svn_stringbuf_t *new_str = svn_stringbuf_create_empty(result_pool);
  apr_array_header_t *lines;
  int i;

  SVN_ERR_ASSERT(*str && (*str)->data);

  *was_changed = FALSE;

  /* for each line */
  lines = svn_cstring_split((*str)->data, "\n", FALSE, scratch_pool);

  for (i = 0; i < lines->nelts; i++)
    {
      char *line = APR_ARRAY_IDX(lines, i, char *);
      char *colon;
      char *rangelist;

      /* split at the last colon */
      colon = strrchr(line, ':');

      if (! colon)
        return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
                                 _("Missing colon in svn:mergeinfo "
                                   "property"));

      rangelist = colon + 1;

      /* remove r0 */
      if (colon[1] == '0')
        {
          if (strncmp(rangelist, "0*,", 3) == 0)
            {
              rangelist += 3;
            }
          else if (strcmp(rangelist, "0*") == 0
                   || strncmp(rangelist, "0,", 2) == 0
                   || strncmp(rangelist, "0-1*", 4) == 0
                   || strncmp(rangelist, "0-1,", 4) == 0
                   || strcmp(rangelist, "0-1") == 0)
            {
              rangelist += 2;
            }
          else if (strcmp(rangelist, "0") == 0)
            {
              rangelist += 1;
            }
          else if (strncmp(rangelist, "0-", 2) == 0)
            {
              rangelist[0] = '1';
            }
        }

      /* reassemble */
      if (rangelist[0])
        {
          if (new_str->len)
            svn_stringbuf_appendbyte(new_str, '\n');
          svn_stringbuf_appendbytes(new_str, line, colon + 1 - line);
          svn_stringbuf_appendcstr(new_str, rangelist);
        }
    }

  if (strcmp((*str)->data, new_str->data) != 0)
    {
      *was_changed = TRUE;
    }

  *str = svn_stringbuf__morph_into_string(new_str);
  return SVN_NO_ERROR;
}
Exemplo n.º 16
0
/* Write to DIGEST_PATH a representation of CHILDREN (which may be
   empty, if the versioned path in FS represented by DIGEST_PATH has
   no children) and LOCK (which may be NULL if that versioned path is
   lock itself locked).  Set the permissions of DIGEST_PATH to those of
   PERMS_REFERENCE.  Use POOL for all allocations.
 */
static svn_error_t *
write_digest_file(apr_hash_t *children,
                  svn_lock_t *lock,
                  const char *fs_path,
                  const char *digest_path,
                  const char *perms_reference,
                  apr_pool_t *pool)
{
  svn_error_t *err = SVN_NO_ERROR;
  svn_stream_t *stream;
  apr_hash_index_t *hi;
  apr_hash_t *hash = apr_hash_make(pool);
  const char *tmp_path;

  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs_path, PATH_LOCKS_DIR,
                                                       pool), fs_path, pool));
  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_dirname(digest_path, pool),
                                       fs_path, pool));

  if (lock)
    {
      const char *creation_date = NULL, *expiration_date = NULL;
      if (lock->creation_date)
        creation_date = svn_time_to_cstring(lock->creation_date, pool);
      if (lock->expiration_date)
        expiration_date = svn_time_to_cstring(lock->expiration_date, pool);
      hash_store(hash, PATH_KEY, sizeof(PATH_KEY)-1,
                 lock->path, APR_HASH_KEY_STRING, pool);
      hash_store(hash, TOKEN_KEY, sizeof(TOKEN_KEY)-1,
                 lock->token, APR_HASH_KEY_STRING, pool);
      hash_store(hash, OWNER_KEY, sizeof(OWNER_KEY)-1,
                 lock->owner, APR_HASH_KEY_STRING, pool);
      hash_store(hash, COMMENT_KEY, sizeof(COMMENT_KEY)-1,
                 lock->comment, APR_HASH_KEY_STRING, pool);
      hash_store(hash, IS_DAV_COMMENT_KEY, sizeof(IS_DAV_COMMENT_KEY)-1,
                 lock->is_dav_comment ? "1" : "0", 1, pool);
      hash_store(hash, CREATION_DATE_KEY, sizeof(CREATION_DATE_KEY)-1,
                 creation_date, APR_HASH_KEY_STRING, pool);
      hash_store(hash, EXPIRATION_DATE_KEY, sizeof(EXPIRATION_DATE_KEY)-1,
                 expiration_date, APR_HASH_KEY_STRING, pool);
    }
  if (apr_hash_count(children))
    {
      svn_stringbuf_t *children_list = svn_stringbuf_create("", pool);
      for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
        {
          svn_stringbuf_appendbytes(children_list,
                                    svn__apr_hash_index_key(hi),
                                    svn__apr_hash_index_klen(hi));
          svn_stringbuf_appendbyte(children_list, '\n');
        }
      hash_store(hash, CHILDREN_KEY, sizeof(CHILDREN_KEY)-1,
                 children_list->data, children_list->len, pool);
    }

  SVN_ERR(svn_stream_open_unique(&stream, &tmp_path,
                                 svn_dirent_dirname(digest_path, pool),
                                 svn_io_file_del_none, pool, pool));
  if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
    {
      svn_error_clear(svn_stream_close(stream));
      return svn_error_createf(err->apr_err,
                               err,
                               _("Cannot write lock/entries hashfile '%s'"),
                               svn_dirent_local_style(tmp_path, pool));
    }

  SVN_ERR(svn_stream_close(stream));
  SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
  SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool));
  return SVN_NO_ERROR;
}
Exemplo n.º 17
0
/* Prepare LINE for display, pruning or extending it to an appropriate
 * display width, and stripping the EOL marker, if any.
 * This function assumes that the data in LINE is encoded in UTF-8. */
static const char *
prepare_line_for_display(const char *line, apr_pool_t *pool)
{
  svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
  size_t width;
  size_t line_width = LINE_DISPLAY_WIDTH;
  apr_pool_t *iterpool;

  /* Trim EOL. */
  if (buf->len >= 2 &&
      buf->data[buf->len - 2] == '\r' &&
      buf->data[buf->len - 1] == '\n')
    svn_stringbuf_chop(buf, 2);
  else if (buf->len >= 1 &&
           (buf->data[buf->len - 1] == '\n' ||
            buf->data[buf->len - 1] == '\r'))
    svn_stringbuf_chop(buf, 1);

  /* Determine the on-screen width of the line. */
  width = svn_utf_cstring_utf8_width(buf->data);
  if (width == -1)
    {
      /* Determining the width failed. Try to get rid of unprintable
       * characters in the line buffer. */
      buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool);
      width = svn_utf_cstring_utf8_width(buf->data);
      if (width == -1)
        width = buf->len; /* fallback: buffer length */
    }

  /* Trim further in case line is still too long, or add padding in case
   * it is too short. */
  iterpool = svn_pool_create(pool);
  while (width > line_width)
    {
      const char *last_valid;

      svn_pool_clear(iterpool);

      svn_stringbuf_chop(buf, 1);

      /* Be careful not to invalidate the UTF-8 string by trimming
       * just part of a character. */
      last_valid = svn_utf__last_valid(buf->data, buf->len);
      if (last_valid < buf->data + buf->len)
        svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid);

      width = svn_utf_cstring_utf8_width(buf->data);
      if (width == -1)
        width = buf->len; /* fallback: buffer length */
    }
  svn_pool_destroy(iterpool);

  while (width == 0 || width < line_width)
    {
      svn_stringbuf_appendbyte(buf, ' ');
      width++;
    }

  SVN_ERR_ASSERT_NO_RETURN(width == line_width);
  return buf->data;
}
Exemplo n.º 18
0
/* A helper for reading a line of text from a range in the patch file.
 *
 * Allocate *STRINGBUF in RESULT_POOL, and read into it one line from FILE.
 * Reading stops either after a line-terminator was found or after MAX_LEN
 * bytes have been read. The line-terminator is not stored in *STRINGBUF.
 *
 * The line-terminator is detected automatically and stored in *EOL
 * if EOL is not NULL. If EOF is reached and FILE does not end
 * with a newline character, and EOL is not NULL, *EOL is set to NULL.
 *
 * SCRATCH_POOL is used for temporary allocations.
 */
static svn_error_t *
readline(apr_file_t *file,
         svn_stringbuf_t **stringbuf,
         const char **eol,
         svn_boolean_t *eof,
         apr_size_t max_len,
         apr_pool_t *result_pool,
         apr_pool_t *scratch_pool)
{
  svn_stringbuf_t *str;
  const char *eol_str;
  apr_size_t numbytes;
  char c;
  apr_size_t len;
  svn_boolean_t found_eof;

  str = svn_stringbuf_create_ensure(80, result_pool);

  /* Read bytes into STR up to and including, but not storing,
   * the next EOL sequence. */
  eol_str = NULL;
  numbytes = 1;
  len = 0;
  found_eof = FALSE;
  while (!found_eof)
    {
      if (len < max_len)
        SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
                                       &found_eof, scratch_pool));
      len++;
      if (numbytes != 1 || len > max_len)
        {
          found_eof = TRUE;
          break;
        }

      if (c == '\n')
        {
          eol_str = "\n";
        }
      else if (c == '\r')
        {
          eol_str = "\r";

          if (!found_eof && len < max_len)
            {
              apr_off_t pos;

              /* Check for "\r\n" by peeking at the next byte. */
              pos = 0;
              SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
              SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
                                             &found_eof, scratch_pool));
              if (numbytes == 1 && c == '\n')
                {
                  eol_str = "\r\n";
                  len++;
                }
              else
                {
                  /* Pretend we never peeked. */
                  SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
                  found_eof = FALSE;
                  numbytes = 1;
                }
            }
        }
      else
        svn_stringbuf_appendbyte(str, c);

      if (eol_str)
        break;
    }

  if (eol)
    *eol = eol_str;
  if (eof)
    *eof = found_eof;
  *stringbuf = str;

  return SVN_NO_ERROR;
}