summaryrefslogtreecommitdiff
path: root/subversion/libsvn_diff/parse-diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_diff/parse-diff.c')
-rw-r--r--subversion/libsvn_diff/parse-diff.c223
1 files changed, 181 insertions, 42 deletions
diff --git a/subversion/libsvn_diff/parse-diff.c b/subversion/libsvn_diff/parse-diff.c
index e269ef9..3f794b8 100644
--- a/subversion/libsvn_diff/parse-diff.c
+++ b/subversion/libsvn_diff/parse-diff.c
@@ -35,9 +35,12 @@
#include "svn_utf.h"
#include "svn_dirent_uri.h"
#include "svn_diff.h"
+#include "svn_ctype.h"
+#include "svn_mergeinfo.h"
#include "private/svn_eol_private.h"
#include "private/svn_dep_compat.h"
+#include "private/svn_sorts_private.h"
/* Helper macro for readability */
#define starts_with(str, start) \
@@ -385,7 +388,6 @@ svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- svn_diff_hunk_t dummy;
svn_stringbuf_t *line;
apr_size_t max_len;
apr_off_t pos;
@@ -415,33 +417,10 @@ svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk,
if (hunk->patch->reverse)
{
- if (parse_hunk_header(line->data, &dummy, "@@", scratch_pool))
- {
- /* Line is a hunk header, reverse it. */
- line = svn_stringbuf_createf(result_pool,
- "@@ -%lu,%lu +%lu,%lu @@",
- hunk->modified_start,
- hunk->modified_length,
- hunk->original_start,
- hunk->original_length);
- }
- else if (parse_hunk_header(line->data, &dummy, "##", scratch_pool))
- {
- /* Line is a hunk header, reverse it. */
- line = svn_stringbuf_createf(result_pool,
- "## -%lu,%lu +%lu,%lu ##",
- hunk->modified_start,
- hunk->modified_length,
- hunk->original_start,
- hunk->original_length);
- }
- else
- {
- if (line->data[0] == '+')
- line->data[0] = '-';
- else if (line->data[0] == '-')
- line->data[0] = '+';
- }
+ if (line->data[0] == '+')
+ line->data[0] = '-';
+ else if (line->data[0] == '-')
+ line->data[0] = '+';
}
*stringbuf = line;
@@ -471,6 +450,147 @@ parse_prop_name(const char **prop_name, const char *header,
return SVN_NO_ERROR;
}
+
+/* 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;
+}
+
/* Return the next *HUNK from a PATCH in APR_FILE.
* If no hunk can be found, set *HUNK to NULL.
* Set IS_PROPERTY to TRUE if we have a property hunk. If the returned HUNK
@@ -600,6 +720,17 @@ parse_next_hunk(svn_diff_hunk_t **hunk,
continue;
}
+ if (in_hunk && *is_property && *prop_name &&
+ strcmp(*prop_name, SVN_PROP_MERGEINFO) == 0)
+ {
+ svn_boolean_t found_mergeinfo;
+
+ SVN_ERR(parse_mergeinfo(&found_mergeinfo, line, *hunk, patch,
+ result_pool, iterpool));
+ if (found_mergeinfo)
+ continue; /* Proceed to the next line in the patch. */
+ }
+
if (in_hunk)
{
char c;
@@ -1192,6 +1323,13 @@ parse_hunks(svn_patch_t *patch, apr_file_t *apr_file,
prop_name = last_prop_name;
else
last_prop_name = prop_name;
+
+ /* Skip svn:mergeinfo properties.
+ * Mergeinfo data cannot be represented as a hunk and
+ * is therefore stored in PATCH itself. */
+ if (strcmp(prop_name, SVN_PROP_MERGEINFO) == 0)
+ continue;
+
SVN_ERR(add_property_hunk(patch, prop_name, hunk, prop_operation,
result_pool));
}
@@ -1229,7 +1367,7 @@ static struct transition transitions[] =
};
svn_error_t *
-svn_diff_parse_next_patch(svn_patch_t **patch,
+svn_diff_parse_next_patch(svn_patch_t **patch_p,
svn_patch_file_t *patch_file,
svn_boolean_t reverse,
svn_boolean_t ignore_whitespace,
@@ -1240,16 +1378,17 @@ svn_diff_parse_next_patch(svn_patch_t **patch,
svn_boolean_t eof;
svn_boolean_t line_after_tree_header_read = FALSE;
apr_pool_t *iterpool;
+ svn_patch_t *patch;
enum parse_state state = state_start;
if (apr_file_eof(patch_file->apr_file) == APR_EOF)
{
/* No more patches here. */
- *patch = NULL;
+ *patch_p = NULL;
return SVN_NO_ERROR;
}
- *patch = apr_pcalloc(result_pool, sizeof(**patch));
+ patch = apr_pcalloc(result_pool, sizeof(*patch));
pos = patch_file->next_patch_offset;
SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &pos, scratch_pool));
@@ -1282,7 +1421,7 @@ svn_diff_parse_next_patch(svn_patch_t **patch,
if (starts_with(line->data, transitions[i].expected_input)
&& state == transitions[i].required_state)
{
- SVN_ERR(transitions[i].fn(&state, line->data, *patch,
+ SVN_ERR(transitions[i].fn(&state, line->data, patch,
result_pool, iterpool));
valid_header_line = TRUE;
break;
@@ -1328,22 +1467,22 @@ svn_diff_parse_next_patch(svn_patch_t **patch,
}
while (! eof);
- (*patch)->reverse = reverse;
+ patch->reverse = reverse;
if (reverse)
{
const char *temp;
- temp = (*patch)->old_filename;
- (*patch)->old_filename = (*patch)->new_filename;
- (*patch)->new_filename = temp;
+ temp = patch->old_filename;
+ patch->old_filename = patch->new_filename;
+ patch->new_filename = temp;
}
- if ((*patch)->old_filename == NULL || (*patch)->new_filename == NULL)
+ if (patch->old_filename == NULL || patch->new_filename == NULL)
{
/* Something went wrong, just discard the result. */
- *patch = NULL;
+ patch = NULL;
}
else
- SVN_ERR(parse_hunks(*patch, patch_file->apr_file, ignore_whitespace,
+ SVN_ERR(parse_hunks(patch, patch_file->apr_file, ignore_whitespace,
result_pool, iterpool));
svn_pool_destroy(iterpool);
@@ -1352,16 +1491,16 @@ svn_diff_parse_next_patch(svn_patch_t **patch,
SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_CUR,
&patch_file->next_patch_offset, scratch_pool));
- if (*patch)
+ if (patch)
{
/* Usually, hunks appear in the patch sorted by their original line
* offset. But just in case they weren't parsed in this order for
* some reason, we sort them so that our caller can assume that hunks
* are sorted as if parsed from a usual patch. */
- qsort((*patch)->hunks->elts, (*patch)->hunks->nelts,
- (*patch)->hunks->elt_size, compare_hunks);
+ svn_sort__array(patch->hunks, compare_hunks);
}
+ *patch_p = patch;
return SVN_NO_ERROR;
}