static int parse_patch_hunks( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { git_patch_hunk *hunk; int error = 0; while (git_parse_ctx_contains_s(&ctx->parse_ctx, "@@ -")) { hunk = git_array_alloc(patch->base.hunks); GITERR_CHECK_ALLOC(hunk); memset(hunk, 0, sizeof(git_patch_hunk)); hunk->line_start = git_array_size(patch->base.lines); hunk->line_count = 0; if ((error = parse_hunk_header(hunk, ctx)) < 0 || (error = parse_hunk_body(patch, hunk, ctx)) < 0) goto done; } patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; done: return error; }
static int parse_patch_header( git_patch_parsed *patch, git_patch_parse_ctx *ctx) { int error = 0; for (ctx->line = ctx->remain; ctx->remain_len > 0; parse_advance_line(ctx)) { /* This line is too short to be a patch header. */ if (ctx->line_len < 6) continue; /* This might be a hunk header without a patch header, provide a * sensible error message. */ if (parse_ctx_contains_s(ctx, "@@ -")) { size_t line_num = ctx->line_num; git_patch_hunk hunk; /* If this cannot be parsed as a hunk header, it's just leading * noise, continue. */ if (parse_hunk_header(&hunk, ctx) < 0) { giterr_clear(); continue; } error = parse_err("invalid hunk header outside patch at line %d", line_num); goto done; } /* This buffer is too short to contain a patch. */ if (ctx->remain_len < ctx->line_len + 6) break; /* A proper git patch */ if (parse_ctx_contains_s(ctx, "diff --git ")) { error = parse_header_git(patch, ctx); goto done; } error = 0; continue; } giterr_set(GITERR_PATCH, "no patch found"); error = GIT_ENOTFOUND; done: return error; }
static void consume_line(void *state_, char *line, unsigned long len) { struct combine_diff_state *state = state_; if (5 < len && !memcmp("@@ -", line, 4)) { if (parse_hunk_header(line, len, &state->ob, &state->on, &state->nb, &state->nn)) return; state->lno = state->nb; if (state->nn == 0) { /* @@ -X,Y +N,0 @@ removed Y lines * that would have come *after* line N * in the result. Our lost buckets hang * to the line after the removed lines, * * Note that this is correct even when N == 0, * in which case the hunk removes the first * line in the file. */ state->lost_bucket = &state->sline[state->nb]; if (!state->nb) state->nb = 1; } else { state->lost_bucket = &state->sline[state->nb-1]; } if (!state->sline[state->nb-1].p_lno) state->sline[state->nb-1].p_lno = xcalloc(state->num_parent, sizeof(unsigned long)); state->sline[state->nb-1].p_lno[state->n] = state->ob; state->lost_bucket->next_lost = state->lost_bucket->lost_head; return; } if (!state->lost_bucket) return; /* not in any hunk yet */ switch (line[0]) { case '-': append_lost(state->lost_bucket, state->n, line+1, len-1); break; case '+': state->sline[state->lno-1].flag |= state->nmask; state->lno++; break; } }
/* 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 * is the first belonging to a certain property, then PROP_NAME and * PROP_OPERATION will be set too. If we have a text hunk, PROP_NAME will be * NULL. If IGNORE_WHITESPACE is TRUE, lines without leading spaces will be * treated as context lines. Allocate results in RESULT_POOL. * Use SCRATCH_POOL for all other allocations. */ static svn_error_t * parse_next_hunk(svn_diff_hunk_t **hunk, svn_boolean_t *is_property, const char **prop_name, svn_diff_operation_kind_t *prop_operation, svn_patch_t *patch, apr_file_t *apr_file, svn_boolean_t ignore_whitespace, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { static const char * const minus = "--- "; static const char * const text_atat = "@@"; static const char * const prop_atat = "##"; svn_stringbuf_t *line; svn_boolean_t eof, in_hunk, hunk_seen; apr_off_t pos, last_line; apr_off_t start, end; apr_off_t original_end; apr_off_t modified_end; svn_linenum_t original_lines; svn_linenum_t modified_lines; svn_linenum_t leading_context; svn_linenum_t trailing_context; svn_boolean_t changed_line_seen; enum { noise_line, original_line, modified_line, context_line } last_line_type; apr_pool_t *iterpool; *prop_operation = svn_diff_op_unchanged; /* We only set this if we have a property hunk header. */ *prop_name = NULL; *is_property = FALSE; if (apr_file_eof(apr_file) == APR_EOF) { /* No more hunks here. */ *hunk = NULL; return SVN_NO_ERROR; } in_hunk = FALSE; hunk_seen = FALSE; leading_context = 0; trailing_context = 0; changed_line_seen = FALSE; original_end = 0; modified_end = 0; *hunk = apr_pcalloc(result_pool, sizeof(**hunk)); /* Get current seek position -- APR has no ftell() :( */ pos = 0; SVN_ERR(svn_io_file_seek(apr_file, APR_CUR, &pos, scratch_pool)); /* Start out assuming noise. */ last_line_type = noise_line; iterpool = svn_pool_create(scratch_pool); do { svn_pool_clear(iterpool); /* Remember the current line's offset, and read the line. */ last_line = pos; SVN_ERR(readline(apr_file, &line, NULL, &eof, APR_SIZE_MAX, iterpool, iterpool)); /* Update line offset for next iteration. */ pos = 0; SVN_ERR(svn_io_file_seek(apr_file, APR_CUR, &pos, iterpool)); /* Lines starting with a backslash are comments, such as * "\ No newline at end of file". */ if (line->data[0] == '\\') { if (in_hunk && ((!*is_property && strcmp(line->data, "\\ No newline at end of file") == 0) || (*is_property && strcmp(line->data, "\\ No newline at end of property") == 0))) { char eolbuf[2]; apr_size_t len; apr_off_t off; apr_off_t hunk_text_end; /* Comment terminates the hunk text and says the hunk text * has no trailing EOL. Snip off trailing EOL which is part * of the patch file but not part of the hunk text. */ off = last_line - 2; SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &off, iterpool)); len = sizeof(eolbuf); SVN_ERR(svn_io_file_read_full2(apr_file, eolbuf, len, &len, &eof, iterpool)); if (eolbuf[0] == '\r' && eolbuf[1] == '\n') hunk_text_end = last_line - 2; else if (eolbuf[1] == '\n' || eolbuf[1] == '\r') hunk_text_end = last_line - 1; else hunk_text_end = last_line; if (last_line_type == original_line && original_end == 0) original_end = hunk_text_end; else if (last_line_type == modified_line && modified_end == 0) modified_end = hunk_text_end; else if (last_line_type == context_line) { if (original_end == 0) original_end = hunk_text_end; if (modified_end == 0) modified_end = hunk_text_end; break; } SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &pos, iterpool)); } continue; } if (in_hunk) { char c; static const char add = '+'; static const char del = '-'; if (! hunk_seen) { /* We're reading the first line of the hunk, so the start * of the line just read is the hunk text's byte offset. */ start = last_line; } c = line->data[0]; if (original_lines > 0 && modified_lines > 0 && ((c == ' ') /* Tolerate chopped leading spaces on empty lines. */ || (! eof && line->len == 0) /* Maybe tolerate chopped leading spaces on non-empty lines. */ || (ignore_whitespace && c != del && c != add))) { /* It's a "context" line in the hunk. */ hunk_seen = TRUE; original_lines--; modified_lines--; if (changed_line_seen) trailing_context++; else leading_context++; last_line_type = context_line; } else if (original_lines > 0 && c == del) { /* It's a "deleted" line in the hunk. */ hunk_seen = TRUE; changed_line_seen = TRUE; /* A hunk may have context in the middle. We only want trailing lines of context. */ if (trailing_context > 0) trailing_context = 0; original_lines--; last_line_type = original_line; } else if (modified_lines > 0 && c == add) { /* It's an "added" line in the hunk. */ hunk_seen = TRUE; changed_line_seen = TRUE; /* A hunk may have context in the middle. We only want trailing lines of context. */ if (trailing_context > 0) trailing_context = 0; modified_lines--; last_line_type = modified_line; } else { if (eof) { /* The hunk ends at EOF. */ end = pos; } else { /* The start of the current line marks the first byte * after the hunk text. */ end = last_line; } if (original_end == 0) original_end = end; if (modified_end == 0) modified_end = end; break; /* Hunk was empty or has been read. */ } } else { if (starts_with(line->data, text_atat)) { /* Looks like we have a hunk header, try to rip it apart. */ in_hunk = parse_hunk_header(line->data, *hunk, text_atat, iterpool); if (in_hunk) { original_lines = (*hunk)->original_length; modified_lines = (*hunk)->modified_length; *is_property = FALSE; } } else if (starts_with(line->data, prop_atat)) { /* Looks like we have a property hunk header, try to rip it * apart. */ in_hunk = parse_hunk_header(line->data, *hunk, prop_atat, iterpool); if (in_hunk) { original_lines = (*hunk)->original_length; modified_lines = (*hunk)->modified_length; *is_property = TRUE; } } else if (starts_with(line->data, "Added: ")) { SVN_ERR(parse_prop_name(prop_name, line->data, "Added: ", result_pool)); if (*prop_name) *prop_operation = svn_diff_op_added; } else if (starts_with(line->data, "Deleted: ")) { SVN_ERR(parse_prop_name(prop_name, line->data, "Deleted: ", result_pool)); if (*prop_name) *prop_operation = svn_diff_op_deleted; } else if (starts_with(line->data, "Modified: ")) { SVN_ERR(parse_prop_name(prop_name, line->data, "Modified: ", result_pool)); if (*prop_name) *prop_operation = svn_diff_op_modified; } else if (starts_with(line->data, minus) || starts_with(line->data, "diff --git ")) /* This could be a header of another patch. Bail out. */ break; } } /* Check for the line length since a file may not have a newline at the * end and we depend upon the last line to be an empty one. */ while (! eof || line->len > 0); svn_pool_destroy(iterpool); if (! eof) /* Rewind to the start of the line just read, so subsequent calls * to this function or svn_diff_parse_next_patch() don't end * up skipping the line -- it may contain a patch or hunk header. */ SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &last_line, scratch_pool)); if (hunk_seen && start < end) { (*hunk)->patch = patch; (*hunk)->apr_file = apr_file; (*hunk)->leading_context = leading_context; (*hunk)->trailing_context = trailing_context; (*hunk)->diff_text_range.start = start; (*hunk)->diff_text_range.current = start; (*hunk)->diff_text_range.end = end; (*hunk)->original_text_range.start = start; (*hunk)->original_text_range.current = start; (*hunk)->original_text_range.end = original_end; (*hunk)->modified_text_range.start = start; (*hunk)->modified_text_range.current = start; (*hunk)->modified_text_range.end = modified_end; } else /* Something went wrong, just discard the result. */ *hunk = NULL; return SVN_NO_ERROR; }
svn_error_t * svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk, svn_stringbuf_t **stringbuf, const char **eol, svn_boolean_t *eof, 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; if (hunk->diff_text_range.current >= hunk->diff_text_range.end) { /* We're past the range. Indicate that no bytes can be read. */ *eof = TRUE; if (eol) *eol = NULL; *stringbuf = svn_stringbuf_create("", result_pool); return SVN_NO_ERROR; } pos = 0; SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, &pos, scratch_pool)); SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &hunk->diff_text_range.current, scratch_pool)); max_len = hunk->diff_text_range.end - hunk->diff_text_range.current; SVN_ERR(readline(hunk->apr_file, &line, eol, eof, max_len, result_pool, scratch_pool)); hunk->diff_text_range.current = 0; SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, &hunk->diff_text_range.current, scratch_pool)); SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &pos, scratch_pool)); 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] = '+'; } } *stringbuf = line; return SVN_NO_ERROR; }