/* 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; }
/* 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; }
/* 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)); }
/* 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; }
/* 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; }
/* 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; }
/* 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; }
/* 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; }
/* 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; }
/* 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; }
/* 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; }
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; }
/* 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)); }
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; }
/* 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; }
/* 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; }
/* 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; }
/* 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; }