diff options
author | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | d25dac7fcc6acc838b71bbda8916fd9665c709ab (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_diff/util.c | |
download | FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz |
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_diff/util.c')
-rw-r--r-- | subversion/libsvn_diff/util.c | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/subversion/libsvn_diff/util.c b/subversion/libsvn_diff/util.c new file mode 100644 index 0000000..9e1f411 --- /dev/null +++ b/subversion/libsvn_diff/util.c @@ -0,0 +1,591 @@ +/* + * util.c : routines for doing diffs + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <apr.h> +#include <apr_general.h> + +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_props.h" +#include "svn_mergeinfo.h" +#include "svn_error.h" +#include "svn_diff.h" +#include "svn_types.h" +#include "svn_ctype.h" +#include "svn_utf.h" +#include "svn_version.h" + +#include "private/svn_diff_private.h" +#include "diff.h" + +#include "svn_private_config.h" + + +svn_boolean_t +svn_diff_contains_conflicts(svn_diff_t *diff) +{ + while (diff != NULL) + { + if (diff->type == svn_diff__type_conflict) + { + return TRUE; + } + + diff = diff->next; + } + + return FALSE; +} + +svn_boolean_t +svn_diff_contains_diffs(svn_diff_t *diff) +{ + while (diff != NULL) + { + if (diff->type != svn_diff__type_common) + { + return TRUE; + } + + diff = diff->next; + } + + return FALSE; +} + +svn_error_t * +svn_diff_output(svn_diff_t *diff, + void *output_baton, + const svn_diff_output_fns_t *vtable) +{ + svn_error_t *(*output_fn)(void *, + apr_off_t, apr_off_t, + apr_off_t, apr_off_t, + apr_off_t, apr_off_t); + + while (diff != NULL) + { + switch (diff->type) + { + case svn_diff__type_common: + output_fn = vtable->output_common; + break; + + case svn_diff__type_diff_common: + output_fn = vtable->output_diff_common; + break; + + case svn_diff__type_diff_modified: + output_fn = vtable->output_diff_modified; + break; + + case svn_diff__type_diff_latest: + output_fn = vtable->output_diff_latest; + break; + + case svn_diff__type_conflict: + output_fn = NULL; + if (vtable->output_conflict != NULL) + { + SVN_ERR(vtable->output_conflict(output_baton, + diff->original_start, diff->original_length, + diff->modified_start, diff->modified_length, + diff->latest_start, diff->latest_length, + diff->resolved_diff)); + } + break; + + default: + output_fn = NULL; + break; + } + + if (output_fn != NULL) + { + SVN_ERR(output_fn(output_baton, + diff->original_start, diff->original_length, + diff->modified_start, diff->modified_length, + diff->latest_start, diff->latest_length)); + } + + diff = diff->next; + } + + return SVN_NO_ERROR; +} + + +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 +} + +svn_error_t * +svn_diff__unified_append_no_newline_msg(svn_stringbuf_t *stringbuf, + const char *header_encoding, + apr_pool_t *scratch_pool) +{ + const char *out_str; + + SVN_ERR(svn_utf_cstring_from_utf8_ex2( + &out_str, + APR_EOL_STR + SVN_DIFF__NO_NEWLINE_AT_END_OF_FILE APR_EOL_STR, + header_encoding, scratch_pool)); + svn_stringbuf_appendcstr(stringbuf, out_str); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_diff__unified_write_hunk_header(svn_stream_t *output_stream, + const char *header_encoding, + const char *hunk_delimiter, + apr_off_t old_start, + apr_off_t old_length, + apr_off_t new_start, + apr_off_t new_length, + const char *hunk_extra_context, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, + scratch_pool, + "%s -%" APR_OFF_T_FMT, + hunk_delimiter, old_start)); + /* If the hunk length is 1, suppress the number of lines in the hunk + * (it is 1 implicitly) */ + if (old_length != 1) + { + SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, + scratch_pool, + ",%" APR_OFF_T_FMT, old_length)); + } + + SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, + scratch_pool, + " +%" APR_OFF_T_FMT, new_start)); + if (new_length != 1) + { + SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, + scratch_pool, + ",%" APR_OFF_T_FMT, new_length)); + } + + if (hunk_extra_context == NULL) + hunk_extra_context = ""; + SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, + scratch_pool, + " %s%s%s" APR_EOL_STR, + hunk_delimiter, + hunk_extra_context[0] ? " " : "", + hunk_extra_context)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_diff__unidiff_write_header(svn_stream_t *output_stream, + const char *header_encoding, + const char *old_header, + const char *new_header, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_stream_printf_from_utf8(output_stream, header_encoding, + scratch_pool, + "--- %s" APR_EOL_STR + "+++ %s" APR_EOL_STR, + old_header, + new_header)); + return SVN_NO_ERROR; +} + +/* A helper function for display_prop_diffs. Output the differences between + the mergeinfo stored in ORIG_MERGEINFO_VAL and NEW_MERGEINFO_VAL in a + human-readable form to OUTSTREAM, using ENCODING. Use POOL for temporary + allocations. */ +static svn_error_t * +display_mergeinfo_diff(const char *old_mergeinfo_val, + const char *new_mergeinfo_val, + const char *encoding, + svn_stream_t *outstream, + apr_pool_t *pool) +{ + apr_hash_t *old_mergeinfo_hash, *new_mergeinfo_hash, *added, *deleted; + apr_pool_t *iterpool = svn_pool_create(pool); + apr_hash_index_t *hi; + + if (old_mergeinfo_val) + SVN_ERR(svn_mergeinfo_parse(&old_mergeinfo_hash, old_mergeinfo_val, pool)); + else + old_mergeinfo_hash = NULL; + + if (new_mergeinfo_val) + SVN_ERR(svn_mergeinfo_parse(&new_mergeinfo_hash, new_mergeinfo_val, pool)); + else + new_mergeinfo_hash = NULL; + + SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, old_mergeinfo_hash, + new_mergeinfo_hash, + TRUE, pool, pool)); + + for (hi = apr_hash_first(pool, deleted); + hi; hi = apr_hash_next(hi)) + { + const char *from_path = svn__apr_hash_index_key(hi); + svn_rangelist_t *merge_revarray = svn__apr_hash_index_val(hi); + svn_string_t *merge_revstr; + + svn_pool_clear(iterpool); + SVN_ERR(svn_rangelist_to_string(&merge_revstr, merge_revarray, + iterpool)); + + SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool, + _(" Reverse-merged %s:r%s%s"), + from_path, merge_revstr->data, + APR_EOL_STR)); + } + + for (hi = apr_hash_first(pool, added); + hi; hi = apr_hash_next(hi)) + { + const char *from_path = svn__apr_hash_index_key(hi); + svn_rangelist_t *merge_revarray = svn__apr_hash_index_val(hi); + svn_string_t *merge_revstr; + + svn_pool_clear(iterpool); + SVN_ERR(svn_rangelist_to_string(&merge_revstr, merge_revarray, + iterpool)); + + SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool, + _(" Merged %s:r%s%s"), + from_path, merge_revstr->data, + APR_EOL_STR)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_diff__display_prop_diffs(svn_stream_t *outstream, + const char *encoding, + const apr_array_header_t *propchanges, + apr_hash_t *original_props, + svn_boolean_t pretty_print_mergeinfo, + apr_pool_t *pool) +{ + apr_pool_t *iterpool = svn_pool_create(pool); + int i; + + for (i = 0; i < propchanges->nelts; i++) + { + const char *action; + const svn_string_t *original_value; + const svn_prop_t *propchange + = &APR_ARRAY_IDX(propchanges, i, svn_prop_t); + + if (original_props) + original_value = svn_hash_gets(original_props, propchange->name); + else + original_value = NULL; + + /* If the property doesn't exist on either side, or if it exists + with the same value, skip it. This can happen if the client is + hitting an old mod_dav_svn server that doesn't understand the + "send-all" REPORT style. */ + if ((! (original_value || propchange->value)) + || (original_value && propchange->value + && svn_string_compare(original_value, propchange->value))) + continue; + + svn_pool_clear(iterpool); + + if (! original_value) + action = "Added"; + else if (! propchange->value) + action = "Deleted"; + else + action = "Modified"; + SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, iterpool, + "%s: %s%s", action, + propchange->name, APR_EOL_STR)); + + if (pretty_print_mergeinfo + && strcmp(propchange->name, SVN_PROP_MERGEINFO) == 0) + { + const char *orig = original_value ? original_value->data : NULL; + const char *val = propchange->value ? propchange->value->data : NULL; + svn_error_t *err = display_mergeinfo_diff(orig, val, encoding, + outstream, iterpool); + + /* Issue #3896: If we can't pretty-print mergeinfo differences + because invalid mergeinfo is present, then don't let the diff + fail, just print the diff as any other property. */ + if (err && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + { + svn_error_clear(err); + } + else + { + SVN_ERR(err); + continue; + } + } + + { + svn_diff_t *diff; + svn_diff_file_options_t options = { 0 }; + const svn_string_t *orig + = original_value ? original_value + : svn_string_create_empty(iterpool); + const svn_string_t *val + = propchange->value ? propchange->value + : svn_string_create_empty(iterpool); + + SVN_ERR(svn_diff_mem_string_diff(&diff, orig, val, &options, + iterpool)); + + /* UNIX patch will try to apply a diff even if the diff header + * is missing. It tries to be helpful by asking the user for a + * target filename when it can't determine the target filename + * from the diff header. But there usually are no files which + * UNIX patch could apply the property diff to, so we use "##" + * instead of "@@" as the default hunk delimiter for property diffs. + * We also supress the diff header. */ + SVN_ERR(svn_diff_mem_string_output_unified2( + outstream, diff, FALSE /* no header */, "##", NULL, NULL, + encoding, orig, val, iterpool)); + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Return the library version number. */ +const svn_version_t * +svn_diff_version(void) +{ + SVN_VERSION_BODY; +} |