mirror of https://github.com/git/git.git
apply: move libified code from builtin/apply.c to apply.{c,h}
As most of the apply code in builtin/apply.c has been libified by a number of previous commits, it can now be moved to apply.{c,h}, so that more code can use it. Helped-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Helped-by: Ramsay Jones <ramsay@ramsayjones.plus.com> Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
803bf4e012
commit
13b5af22f3
4731
apply.c
4731
apply.c
|
@ -1,5 +1,23 @@
|
|||
/*
|
||||
* apply.c
|
||||
*
|
||||
* Copyright (C) Linus Torvalds, 2005
|
||||
*
|
||||
* This applies patches on top of some (arbitrary) version of the SCM.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "cache.h"
|
||||
#include "blob.h"
|
||||
#include "delta.h"
|
||||
#include "diff.h"
|
||||
#include "dir.h"
|
||||
#include "xdiff-interface.h"
|
||||
#include "ll-merge.h"
|
||||
#include "lockfile.h"
|
||||
#include "parse-options.h"
|
||||
#include "quote.h"
|
||||
#include "rerere.h"
|
||||
#include "apply.h"
|
||||
|
||||
static void git_apply_config(void)
|
||||
|
@ -125,3 +143,4716 @@ int check_apply_state(struct apply_state *state, int force_apply)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_default_whitespace_mode(struct apply_state *state)
|
||||
{
|
||||
if (!state->whitespace_option && !apply_default_whitespace)
|
||||
state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);
|
||||
}
|
||||
|
||||
/*
|
||||
* This represents one "hunk" from a patch, starting with
|
||||
* "@@ -oldpos,oldlines +newpos,newlines @@" marker. The
|
||||
* patch text is pointed at by patch, and its byte length
|
||||
* is stored in size. leading and trailing are the number
|
||||
* of context lines.
|
||||
*/
|
||||
struct fragment {
|
||||
unsigned long leading, trailing;
|
||||
unsigned long oldpos, oldlines;
|
||||
unsigned long newpos, newlines;
|
||||
/*
|
||||
* 'patch' is usually borrowed from buf in apply_patch(),
|
||||
* but some codepaths store an allocated buffer.
|
||||
*/
|
||||
const char *patch;
|
||||
unsigned free_patch:1,
|
||||
rejected:1;
|
||||
int size;
|
||||
int linenr;
|
||||
struct fragment *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* When dealing with a binary patch, we reuse "leading" field
|
||||
* to store the type of the binary hunk, either deflated "delta"
|
||||
* or deflated "literal".
|
||||
*/
|
||||
#define binary_patch_method leading
|
||||
#define BINARY_DELTA_DEFLATED 1
|
||||
#define BINARY_LITERAL_DEFLATED 2
|
||||
|
||||
/*
|
||||
* This represents a "patch" to a file, both metainfo changes
|
||||
* such as creation/deletion, filemode and content changes represented
|
||||
* as a series of fragments.
|
||||
*/
|
||||
struct patch {
|
||||
char *new_name, *old_name, *def_name;
|
||||
unsigned int old_mode, new_mode;
|
||||
int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
|
||||
int rejected;
|
||||
unsigned ws_rule;
|
||||
int lines_added, lines_deleted;
|
||||
int score;
|
||||
unsigned int is_toplevel_relative:1;
|
||||
unsigned int inaccurate_eof:1;
|
||||
unsigned int is_binary:1;
|
||||
unsigned int is_copy:1;
|
||||
unsigned int is_rename:1;
|
||||
unsigned int recount:1;
|
||||
unsigned int conflicted_threeway:1;
|
||||
unsigned int direct_to_threeway:1;
|
||||
struct fragment *fragments;
|
||||
char *result;
|
||||
size_t resultsize;
|
||||
char old_sha1_prefix[41];
|
||||
char new_sha1_prefix[41];
|
||||
struct patch *next;
|
||||
|
||||
/* three-way fallback result */
|
||||
struct object_id threeway_stage[3];
|
||||
};
|
||||
|
||||
static void free_fragment_list(struct fragment *list)
|
||||
{
|
||||
while (list) {
|
||||
struct fragment *next = list->next;
|
||||
if (list->free_patch)
|
||||
free((char *)list->patch);
|
||||
free(list);
|
||||
list = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void free_patch(struct patch *patch)
|
||||
{
|
||||
free_fragment_list(patch->fragments);
|
||||
free(patch->def_name);
|
||||
free(patch->old_name);
|
||||
free(patch->new_name);
|
||||
free(patch->result);
|
||||
free(patch);
|
||||
}
|
||||
|
||||
static void free_patch_list(struct patch *list)
|
||||
{
|
||||
while (list) {
|
||||
struct patch *next = list->next;
|
||||
free_patch(list);
|
||||
list = next;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A line in a file, len-bytes long (includes the terminating LF,
|
||||
* except for an incomplete line at the end if the file ends with
|
||||
* one), and its contents hashes to 'hash'.
|
||||
*/
|
||||
struct line {
|
||||
size_t len;
|
||||
unsigned hash : 24;
|
||||
unsigned flag : 8;
|
||||
#define LINE_COMMON 1
|
||||
#define LINE_PATCHED 2
|
||||
};
|
||||
|
||||
/*
|
||||
* This represents a "file", which is an array of "lines".
|
||||
*/
|
||||
struct image {
|
||||
char *buf;
|
||||
size_t len;
|
||||
size_t nr;
|
||||
size_t alloc;
|
||||
struct line *line_allocated;
|
||||
struct line *line;
|
||||
};
|
||||
|
||||
static uint32_t hash_line(const char *cp, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
uint32_t h;
|
||||
for (i = 0, h = 0; i < len; i++) {
|
||||
if (!isspace(cp[i])) {
|
||||
h = h * 3 + (cp[i] & 0xff);
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare lines s1 of length n1 and s2 of length n2, ignoring
|
||||
* whitespace difference. Returns 1 if they match, 0 otherwise
|
||||
*/
|
||||
static int fuzzy_matchlines(const char *s1, size_t n1,
|
||||
const char *s2, size_t n2)
|
||||
{
|
||||
const char *last1 = s1 + n1 - 1;
|
||||
const char *last2 = s2 + n2 - 1;
|
||||
int result = 0;
|
||||
|
||||
/* ignore line endings */
|
||||
while ((*last1 == '\r') || (*last1 == '\n'))
|
||||
last1--;
|
||||
while ((*last2 == '\r') || (*last2 == '\n'))
|
||||
last2--;
|
||||
|
||||
/* skip leading whitespaces, if both begin with whitespace */
|
||||
if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) {
|
||||
while (isspace(*s1) && (s1 <= last1))
|
||||
s1++;
|
||||
while (isspace(*s2) && (s2 <= last2))
|
||||
s2++;
|
||||
}
|
||||
/* early return if both lines are empty */
|
||||
if ((s1 > last1) && (s2 > last2))
|
||||
return 1;
|
||||
while (!result) {
|
||||
result = *s1++ - *s2++;
|
||||
/*
|
||||
* Skip whitespace inside. We check for whitespace on
|
||||
* both buffers because we don't want "a b" to match
|
||||
* "ab"
|
||||
*/
|
||||
if (isspace(*s1) && isspace(*s2)) {
|
||||
while (isspace(*s1) && s1 <= last1)
|
||||
s1++;
|
||||
while (isspace(*s2) && s2 <= last2)
|
||||
s2++;
|
||||
}
|
||||
/*
|
||||
* If we reached the end on one side only,
|
||||
* lines don't match
|
||||
*/
|
||||
if (
|
||||
((s2 > last2) && (s1 <= last1)) ||
|
||||
((s1 > last1) && (s2 <= last2)))
|
||||
return 0;
|
||||
if ((s1 > last1) && (s2 > last2))
|
||||
break;
|
||||
}
|
||||
|
||||
return !result;
|
||||
}
|
||||
|
||||
static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
|
||||
{
|
||||
ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
|
||||
img->line_allocated[img->nr].len = len;
|
||||
img->line_allocated[img->nr].hash = hash_line(bol, len);
|
||||
img->line_allocated[img->nr].flag = flag;
|
||||
img->nr++;
|
||||
}
|
||||
|
||||
/*
|
||||
* "buf" has the file contents to be patched (read from various sources).
|
||||
* attach it to "image" and add line-based index to it.
|
||||
* "image" now owns the "buf".
|
||||
*/
|
||||
static void prepare_image(struct image *image, char *buf, size_t len,
|
||||
int prepare_linetable)
|
||||
{
|
||||
const char *cp, *ep;
|
||||
|
||||
memset(image, 0, sizeof(*image));
|
||||
image->buf = buf;
|
||||
image->len = len;
|
||||
|
||||
if (!prepare_linetable)
|
||||
return;
|
||||
|
||||
ep = image->buf + image->len;
|
||||
cp = image->buf;
|
||||
while (cp < ep) {
|
||||
const char *next;
|
||||
for (next = cp; next < ep && *next != '\n'; next++)
|
||||
;
|
||||
if (next < ep)
|
||||
next++;
|
||||
add_line_info(image, cp, next - cp, 0);
|
||||
cp = next;
|
||||
}
|
||||
image->line = image->line_allocated;
|
||||
}
|
||||
|
||||
static void clear_image(struct image *image)
|
||||
{
|
||||
free(image->buf);
|
||||
free(image->line_allocated);
|
||||
memset(image, 0, sizeof(*image));
|
||||
}
|
||||
|
||||
/* fmt must contain _one_ %s and no other substitution */
|
||||
static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
if (patch->old_name && patch->new_name &&
|
||||
strcmp(patch->old_name, patch->new_name)) {
|
||||
quote_c_style(patch->old_name, &sb, NULL, 0);
|
||||
strbuf_addstr(&sb, " => ");
|
||||
quote_c_style(patch->new_name, &sb, NULL, 0);
|
||||
} else {
|
||||
const char *n = patch->new_name;
|
||||
if (!n)
|
||||
n = patch->old_name;
|
||||
quote_c_style(n, &sb, NULL, 0);
|
||||
}
|
||||
fprintf(output, fmt, sb.buf);
|
||||
fputc('\n', output);
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
||||
#define SLOP (16)
|
||||
|
||||
static int read_patch_file(struct strbuf *sb, int fd)
|
||||
{
|
||||
if (strbuf_read(sb, fd, 0) < 0)
|
||||
return error_errno("git apply: failed to read");
|
||||
|
||||
/*
|
||||
* Make sure that we have some slop in the buffer
|
||||
* so that we can do speculative "memcmp" etc, and
|
||||
* see to it that it is NUL-filled.
|
||||
*/
|
||||
strbuf_grow(sb, SLOP);
|
||||
memset(sb->buf + sb->len, 0, SLOP);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long linelen(const char *buffer, unsigned long size)
|
||||
{
|
||||
unsigned long len = 0;
|
||||
while (size--) {
|
||||
len++;
|
||||
if (*buffer++ == '\n')
|
||||
break;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int is_dev_null(const char *str)
|
||||
{
|
||||
return skip_prefix(str, "/dev/null", &str) && isspace(*str);
|
||||
}
|
||||
|
||||
#define TERM_SPACE 1
|
||||
#define TERM_TAB 2
|
||||
|
||||
static int name_terminate(int c, int terminate)
|
||||
{
|
||||
if (c == ' ' && !(terminate & TERM_SPACE))
|
||||
return 0;
|
||||
if (c == '\t' && !(terminate & TERM_TAB))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* remove double slashes to make --index work with such filenames */
|
||||
static char *squash_slash(char *name)
|
||||
{
|
||||
int i = 0, j = 0;
|
||||
|
||||
if (!name)
|
||||
return NULL;
|
||||
|
||||
while (name[i]) {
|
||||
if ((name[j++] = name[i++]) == '/')
|
||||
while (name[i] == '/')
|
||||
i++;
|
||||
}
|
||||
name[j] = '\0';
|
||||
return name;
|
||||
}
|
||||
|
||||
static char *find_name_gnu(struct apply_state *state,
|
||||
const char *line,
|
||||
const char *def,
|
||||
int p_value)
|
||||
{
|
||||
struct strbuf name = STRBUF_INIT;
|
||||
char *cp;
|
||||
|
||||
/*
|
||||
* Proposed "new-style" GNU patch/diff format; see
|
||||
* http://marc.info/?l=git&m=112927316408690&w=2
|
||||
*/
|
||||
if (unquote_c_style(&name, line, NULL)) {
|
||||
strbuf_release(&name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (cp = name.buf; p_value; p_value--) {
|
||||
cp = strchr(cp, '/');
|
||||
if (!cp) {
|
||||
strbuf_release(&name);
|
||||
return NULL;
|
||||
}
|
||||
cp++;
|
||||
}
|
||||
|
||||
strbuf_remove(&name, 0, cp - name.buf);
|
||||
if (state->root.len)
|
||||
strbuf_insert(&name, 0, state->root.buf, state->root.len);
|
||||
return squash_slash(strbuf_detach(&name, NULL));
|
||||
}
|
||||
|
||||
static size_t sane_tz_len(const char *line, size_t len)
|
||||
{
|
||||
const char *tz, *p;
|
||||
|
||||
if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ')
|
||||
return 0;
|
||||
tz = line + len - strlen(" +0500");
|
||||
|
||||
if (tz[1] != '+' && tz[1] != '-')
|
||||
return 0;
|
||||
|
||||
for (p = tz + 2; p != line + len; p++)
|
||||
if (!isdigit(*p))
|
||||
return 0;
|
||||
|
||||
return line + len - tz;
|
||||
}
|
||||
|
||||
static size_t tz_with_colon_len(const char *line, size_t len)
|
||||
{
|
||||
const char *tz, *p;
|
||||
|
||||
if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':')
|
||||
return 0;
|
||||
tz = line + len - strlen(" +08:00");
|
||||
|
||||
if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-'))
|
||||
return 0;
|
||||
p = tz + 2;
|
||||
if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
|
||||
!isdigit(*p++) || !isdigit(*p++))
|
||||
return 0;
|
||||
|
||||
return line + len - tz;
|
||||
}
|
||||
|
||||
static size_t date_len(const char *line, size_t len)
|
||||
{
|
||||
const char *date, *p;
|
||||
|
||||
if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-')
|
||||
return 0;
|
||||
p = date = line + len - strlen("72-02-05");
|
||||
|
||||
if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
|
||||
!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
|
||||
!isdigit(*p++) || !isdigit(*p++)) /* Not a date. */
|
||||
return 0;
|
||||
|
||||
if (date - line >= strlen("19") &&
|
||||
isdigit(date[-1]) && isdigit(date[-2])) /* 4-digit year */
|
||||
date -= strlen("19");
|
||||
|
||||
return line + len - date;
|
||||
}
|
||||
|
||||
static size_t short_time_len(const char *line, size_t len)
|
||||
{
|
||||
const char *time, *p;
|
||||
|
||||
if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':')
|
||||
return 0;
|
||||
p = time = line + len - strlen(" 07:01:32");
|
||||
|
||||
/* Permit 1-digit hours? */
|
||||
if (*p++ != ' ' ||
|
||||
!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
|
||||
!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
|
||||
!isdigit(*p++) || !isdigit(*p++)) /* Not a time. */
|
||||
return 0;
|
||||
|
||||
return line + len - time;
|
||||
}
|
||||
|
||||
static size_t fractional_time_len(const char *line, size_t len)
|
||||
{
|
||||
const char *p;
|
||||
size_t n;
|
||||
|
||||
/* Expected format: 19:41:17.620000023 */
|
||||
if (!len || !isdigit(line[len - 1]))
|
||||
return 0;
|
||||
p = line + len - 1;
|
||||
|
||||
/* Fractional seconds. */
|
||||
while (p > line && isdigit(*p))
|
||||
p--;
|
||||
if (*p != '.')
|
||||
return 0;
|
||||
|
||||
/* Hours, minutes, and whole seconds. */
|
||||
n = short_time_len(line, p - line);
|
||||
if (!n)
|
||||
return 0;
|
||||
|
||||
return line + len - p + n;
|
||||
}
|
||||
|
||||
static size_t trailing_spaces_len(const char *line, size_t len)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
/* Expected format: ' ' x (1 or more) */
|
||||
if (!len || line[len - 1] != ' ')
|
||||
return 0;
|
||||
|
||||
p = line + len;
|
||||
while (p != line) {
|
||||
p--;
|
||||
if (*p != ' ')
|
||||
return line + len - (p + 1);
|
||||
}
|
||||
|
||||
/* All spaces! */
|
||||
return len;
|
||||
}
|
||||
|
||||
static size_t diff_timestamp_len(const char *line, size_t len)
|
||||
{
|
||||
const char *end = line + len;
|
||||
size_t n;
|
||||
|
||||
/*
|
||||
* Posix: 2010-07-05 19:41:17
|
||||
* GNU: 2010-07-05 19:41:17.620000023 -0500
|
||||
*/
|
||||
|
||||
if (!isdigit(end[-1]))
|
||||
return 0;
|
||||
|
||||
n = sane_tz_len(line, end - line);
|
||||
if (!n)
|
||||
n = tz_with_colon_len(line, end - line);
|
||||
end -= n;
|
||||
|
||||
n = short_time_len(line, end - line);
|
||||
if (!n)
|
||||
n = fractional_time_len(line, end - line);
|
||||
end -= n;
|
||||
|
||||
n = date_len(line, end - line);
|
||||
if (!n) /* No date. Too bad. */
|
||||
return 0;
|
||||
end -= n;
|
||||
|
||||
if (end == line) /* No space before date. */
|
||||
return 0;
|
||||
if (end[-1] == '\t') { /* Success! */
|
||||
end--;
|
||||
return line + len - end;
|
||||
}
|
||||
if (end[-1] != ' ') /* No space before date. */
|
||||
return 0;
|
||||
|
||||
/* Whitespace damage. */
|
||||
end -= trailing_spaces_len(line, end - line);
|
||||
return line + len - end;
|
||||
}
|
||||
|
||||
static char *find_name_common(struct apply_state *state,
|
||||
const char *line,
|
||||
const char *def,
|
||||
int p_value,
|
||||
const char *end,
|
||||
int terminate)
|
||||
{
|
||||
int len;
|
||||
const char *start = NULL;
|
||||
|
||||
if (p_value == 0)
|
||||
start = line;
|
||||
while (line != end) {
|
||||
char c = *line;
|
||||
|
||||
if (!end && isspace(c)) {
|
||||
if (c == '\n')
|
||||
break;
|
||||
if (name_terminate(c, terminate))
|
||||
break;
|
||||
}
|
||||
line++;
|
||||
if (c == '/' && !--p_value)
|
||||
start = line;
|
||||
}
|
||||
if (!start)
|
||||
return squash_slash(xstrdup_or_null(def));
|
||||
len = line - start;
|
||||
if (!len)
|
||||
return squash_slash(xstrdup_or_null(def));
|
||||
|
||||
/*
|
||||
* Generally we prefer the shorter name, especially
|
||||
* if the other one is just a variation of that with
|
||||
* something else tacked on to the end (ie "file.orig"
|
||||
* or "file~").
|
||||
*/
|
||||
if (def) {
|
||||
int deflen = strlen(def);
|
||||
if (deflen < len && !strncmp(start, def, deflen))
|
||||
return squash_slash(xstrdup(def));
|
||||
}
|
||||
|
||||
if (state->root.len) {
|
||||
char *ret = xstrfmt("%s%.*s", state->root.buf, len, start);
|
||||
return squash_slash(ret);
|
||||
}
|
||||
|
||||
return squash_slash(xmemdupz(start, len));
|
||||
}
|
||||
|
||||
static char *find_name(struct apply_state *state,
|
||||
const char *line,
|
||||
char *def,
|
||||
int p_value,
|
||||
int terminate)
|
||||
{
|
||||
if (*line == '"') {
|
||||
char *name = find_name_gnu(state, line, def, p_value);
|
||||
if (name)
|
||||
return name;
|
||||
}
|
||||
|
||||
return find_name_common(state, line, def, p_value, NULL, terminate);
|
||||
}
|
||||
|
||||
static char *find_name_traditional(struct apply_state *state,
|
||||
const char *line,
|
||||
char *def,
|
||||
int p_value)
|
||||
{
|
||||
size_t len;
|
||||
size_t date_len;
|
||||
|
||||
if (*line == '"') {
|
||||
char *name = find_name_gnu(state, line, def, p_value);
|
||||
if (name)
|
||||
return name;
|
||||
}
|
||||
|
||||
len = strchrnul(line, '\n') - line;
|
||||
date_len = diff_timestamp_len(line, len);
|
||||
if (!date_len)
|
||||
return find_name_common(state, line, def, p_value, NULL, TERM_TAB);
|
||||
len -= date_len;
|
||||
|
||||
return find_name_common(state, line, def, p_value, line + len, 0);
|
||||
}
|
||||
|
||||
static int count_slashes(const char *cp)
|
||||
{
|
||||
int cnt = 0;
|
||||
char ch;
|
||||
|
||||
while ((ch = *cp++))
|
||||
if (ch == '/')
|
||||
cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given the string after "--- " or "+++ ", guess the appropriate
|
||||
* p_value for the given patch.
|
||||
*/
|
||||
static int guess_p_value(struct apply_state *state, const char *nameline)
|
||||
{
|
||||
char *name, *cp;
|
||||
int val = -1;
|
||||
|
||||
if (is_dev_null(nameline))
|
||||
return -1;
|
||||
name = find_name_traditional(state, nameline, NULL, 0);
|
||||
if (!name)
|
||||
return -1;
|
||||
cp = strchr(name, '/');
|
||||
if (!cp)
|
||||
val = 0;
|
||||
else if (state->prefix) {
|
||||
/*
|
||||
* Does it begin with "a/$our-prefix" and such? Then this is
|
||||
* very likely to apply to our directory.
|
||||
*/
|
||||
if (!strncmp(name, state->prefix, state->prefix_length))
|
||||
val = count_slashes(state->prefix);
|
||||
else {
|
||||
cp++;
|
||||
if (!strncmp(cp, state->prefix, state->prefix_length))
|
||||
val = count_slashes(state->prefix) + 1;
|
||||
}
|
||||
}
|
||||
free(name);
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Does the ---/+++ line have the POSIX timestamp after the last HT?
|
||||
* GNU diff puts epoch there to signal a creation/deletion event. Is
|
||||
* this such a timestamp?
|
||||
*/
|
||||
static int has_epoch_timestamp(const char *nameline)
|
||||
{
|
||||
/*
|
||||
* We are only interested in epoch timestamp; any non-zero
|
||||
* fraction cannot be one, hence "(\.0+)?" in the regexp below.
|
||||
* For the same reason, the date must be either 1969-12-31 or
|
||||
* 1970-01-01, and the seconds part must be "00".
|
||||
*/
|
||||
const char stamp_regexp[] =
|
||||
"^(1969-12-31|1970-01-01)"
|
||||
" "
|
||||
"[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
|
||||
" "
|
||||
"([-+][0-2][0-9]:?[0-5][0-9])\n";
|
||||
const char *timestamp = NULL, *cp, *colon;
|
||||
static regex_t *stamp;
|
||||
regmatch_t m[10];
|
||||
int zoneoffset;
|
||||
int hourminute;
|
||||
int status;
|
||||
|
||||
for (cp = nameline; *cp != '\n'; cp++) {
|
||||
if (*cp == '\t')
|
||||
timestamp = cp + 1;
|
||||
}
|
||||
if (!timestamp)
|
||||
return 0;
|
||||
if (!stamp) {
|
||||
stamp = xmalloc(sizeof(*stamp));
|
||||
if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
|
||||
warning(_("Cannot prepare timestamp regexp %s"),
|
||||
stamp_regexp);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
|
||||
if (status) {
|
||||
if (status != REG_NOMATCH)
|
||||
warning(_("regexec returned %d for input: %s"),
|
||||
status, timestamp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10);
|
||||
if (*colon == ':')
|
||||
zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10);
|
||||
else
|
||||
zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
|
||||
if (timestamp[m[3].rm_so] == '-')
|
||||
zoneoffset = -zoneoffset;
|
||||
|
||||
/*
|
||||
* YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
|
||||
* (west of GMT) or 1970-01-01 (east of GMT)
|
||||
*/
|
||||
if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) ||
|
||||
(0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10)))
|
||||
return 0;
|
||||
|
||||
hourminute = (strtol(timestamp + 11, NULL, 10) * 60 +
|
||||
strtol(timestamp + 14, NULL, 10) -
|
||||
zoneoffset);
|
||||
|
||||
return ((zoneoffset < 0 && hourminute == 1440) ||
|
||||
(0 <= zoneoffset && !hourminute));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the name etc info from the ---/+++ lines of a traditional patch header
|
||||
*
|
||||
* FIXME! The end-of-filename heuristics are kind of screwy. For existing
|
||||
* files, we can happily check the index for a match, but for creating a
|
||||
* new file we should try to match whatever "patch" does. I have no idea.
|
||||
*/
|
||||
static int parse_traditional_patch(struct apply_state *state,
|
||||
const char *first,
|
||||
const char *second,
|
||||
struct patch *patch)
|
||||
{
|
||||
char *name;
|
||||
|
||||
first += 4; /* skip "--- " */
|
||||
second += 4; /* skip "+++ " */
|
||||
if (!state->p_value_known) {
|
||||
int p, q;
|
||||
p = guess_p_value(state, first);
|
||||
q = guess_p_value(state, second);
|
||||
if (p < 0) p = q;
|
||||
if (0 <= p && p == q) {
|
||||
state->p_value = p;
|
||||
state->p_value_known = 1;
|
||||
}
|
||||
}
|
||||
if (is_dev_null(first)) {
|
||||
patch->is_new = 1;
|
||||
patch->is_delete = 0;
|
||||
name = find_name_traditional(state, second, NULL, state->p_value);
|
||||
patch->new_name = name;
|
||||
} else if (is_dev_null(second)) {
|
||||
patch->is_new = 0;
|
||||
patch->is_delete = 1;
|
||||
name = find_name_traditional(state, first, NULL, state->p_value);
|
||||
patch->old_name = name;
|
||||
} else {
|
||||
char *first_name;
|
||||
first_name = find_name_traditional(state, first, NULL, state->p_value);
|
||||
name = find_name_traditional(state, second, first_name, state->p_value);
|
||||
free(first_name);
|
||||
if (has_epoch_timestamp(first)) {
|
||||
patch->is_new = 1;
|
||||
patch->is_delete = 0;
|
||||
patch->new_name = name;
|
||||
} else if (has_epoch_timestamp(second)) {
|
||||
patch->is_new = 0;
|
||||
patch->is_delete = 1;
|
||||
patch->old_name = name;
|
||||
} else {
|
||||
patch->old_name = name;
|
||||
patch->new_name = xstrdup_or_null(name);
|
||||
}
|
||||
}
|
||||
if (!name)
|
||||
return error(_("unable to find filename in patch at line %d"), state->linenr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_hdrend(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're anal about diff header consistency, to make
|
||||
* sure that we don't end up having strange ambiguous
|
||||
* patches floating around.
|
||||
*
|
||||
* As a result, gitdiff_{old|new}name() will check
|
||||
* their names against any previous information, just
|
||||
* to make sure..
|
||||
*/
|
||||
#define DIFF_OLD_NAME 0
|
||||
#define DIFF_NEW_NAME 1
|
||||
|
||||
static int gitdiff_verify_name(struct apply_state *state,
|
||||
const char *line,
|
||||
int isnull,
|
||||
char **name,
|
||||
int side)
|
||||
{
|
||||
if (!*name && !isnull) {
|
||||
*name = find_name(state, line, NULL, state->p_value, TERM_TAB);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*name) {
|
||||
int len = strlen(*name);
|
||||
char *another;
|
||||
if (isnull)
|
||||
return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
|
||||
*name, state->linenr);
|
||||
another = find_name(state, line, NULL, state->p_value, TERM_TAB);
|
||||
if (!another || memcmp(another, *name, len + 1)) {
|
||||
free(another);
|
||||
return error((side == DIFF_NEW_NAME) ?
|
||||
_("git apply: bad git-diff - inconsistent new filename on line %d") :
|
||||
_("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr);
|
||||
}
|
||||
free(another);
|
||||
} else {
|
||||
/* expect "/dev/null" */
|
||||
if (memcmp("/dev/null", line, 9) || line[9] != '\n')
|
||||
return error(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_oldname(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
return gitdiff_verify_name(state, line,
|
||||
patch->is_new, &patch->old_name,
|
||||
DIFF_OLD_NAME);
|
||||
}
|
||||
|
||||
static int gitdiff_newname(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
return gitdiff_verify_name(state, line,
|
||||
patch->is_delete, &patch->new_name,
|
||||
DIFF_NEW_NAME);
|
||||
}
|
||||
|
||||
static int gitdiff_oldmode(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
patch->old_mode = strtoul(line, NULL, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_newmode(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
patch->new_mode = strtoul(line, NULL, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_delete(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
patch->is_delete = 1;
|
||||
free(patch->old_name);
|
||||
patch->old_name = xstrdup_or_null(patch->def_name);
|
||||
return gitdiff_oldmode(state, line, patch);
|
||||
}
|
||||
|
||||
static int gitdiff_newfile(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
patch->is_new = 1;
|
||||
free(patch->new_name);
|
||||
patch->new_name = xstrdup_or_null(patch->def_name);
|
||||
return gitdiff_newmode(state, line, patch);
|
||||
}
|
||||
|
||||
static int gitdiff_copysrc(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
patch->is_copy = 1;
|
||||
free(patch->old_name);
|
||||
patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_copydst(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
patch->is_copy = 1;
|
||||
free(patch->new_name);
|
||||
patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_renamesrc(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
patch->is_rename = 1;
|
||||
free(patch->old_name);
|
||||
patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_renamedst(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
patch->is_rename = 1;
|
||||
free(patch->new_name);
|
||||
patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_similarity(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
unsigned long val = strtoul(line, NULL, 10);
|
||||
if (val <= 100)
|
||||
patch->score = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_dissimilarity(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
unsigned long val = strtoul(line, NULL, 10);
|
||||
if (val <= 100)
|
||||
patch->score = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gitdiff_index(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
/*
|
||||
* index line is N hexadecimal, "..", N hexadecimal,
|
||||
* and optional space with octal mode.
|
||||
*/
|
||||
const char *ptr, *eol;
|
||||
int len;
|
||||
|
||||
ptr = strchr(line, '.');
|
||||
if (!ptr || ptr[1] != '.' || 40 < ptr - line)
|
||||
return 0;
|
||||
len = ptr - line;
|
||||
memcpy(patch->old_sha1_prefix, line, len);
|
||||
patch->old_sha1_prefix[len] = 0;
|
||||
|
||||
line = ptr + 2;
|
||||
ptr = strchr(line, ' ');
|
||||
eol = strchrnul(line, '\n');
|
||||
|
||||
if (!ptr || eol < ptr)
|
||||
ptr = eol;
|
||||
len = ptr - line;
|
||||
|
||||
if (40 < len)
|
||||
return 0;
|
||||
memcpy(patch->new_sha1_prefix, line, len);
|
||||
patch->new_sha1_prefix[len] = 0;
|
||||
if (*ptr == ' ')
|
||||
patch->old_mode = strtoul(ptr+1, NULL, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is normal for a diff that doesn't change anything: we'll fall through
|
||||
* into the next diff. Tell the parser to break out.
|
||||
*/
|
||||
static int gitdiff_unrecognized(struct apply_state *state,
|
||||
const char *line,
|
||||
struct patch *patch)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip p_value leading components from "line"; as we do not accept
|
||||
* absolute paths, return NULL in that case.
|
||||
*/
|
||||
static const char *skip_tree_prefix(struct apply_state *state,
|
||||
const char *line,
|
||||
int llen)
|
||||
{
|
||||
int nslash;
|
||||
int i;
|
||||
|
||||
if (!state->p_value)
|
||||
return (llen && line[0] == '/') ? NULL : line;
|
||||
|
||||
nslash = state->p_value;
|
||||
for (i = 0; i < llen; i++) {
|
||||
int ch = line[i];
|
||||
if (ch == '/' && --nslash <= 0)
|
||||
return (i == 0) ? NULL : &line[i + 1];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is to extract the same name that appears on "diff --git"
|
||||
* line. We do not find and return anything if it is a rename
|
||||
* patch, and it is OK because we will find the name elsewhere.
|
||||
* We need to reliably find name only when it is mode-change only,
|
||||
* creation or deletion of an empty file. In any of these cases,
|
||||
* both sides are the same name under a/ and b/ respectively.
|
||||
*/
|
||||
static char *git_header_name(struct apply_state *state,
|
||||
const char *line,
|
||||
int llen)
|
||||
{
|
||||
const char *name;
|
||||
const char *second = NULL;
|
||||
size_t len, line_len;
|
||||
|
||||
line += strlen("diff --git ");
|
||||
llen -= strlen("diff --git ");
|
||||
|
||||
if (*line == '"') {
|
||||
const char *cp;
|
||||
struct strbuf first = STRBUF_INIT;
|
||||
struct strbuf sp = STRBUF_INIT;
|
||||
|
||||
if (unquote_c_style(&first, line, &second))
|
||||
goto free_and_fail1;
|
||||
|
||||
/* strip the a/b prefix including trailing slash */
|
||||
cp = skip_tree_prefix(state, first.buf, first.len);
|
||||
if (!cp)
|
||||
goto free_and_fail1;
|
||||
strbuf_remove(&first, 0, cp - first.buf);
|
||||
|
||||
/*
|
||||
* second points at one past closing dq of name.
|
||||
* find the second name.
|
||||
*/
|
||||
while ((second < line + llen) && isspace(*second))
|
||||
second++;
|
||||
|
||||
if (line + llen <= second)
|
||||
goto free_and_fail1;
|
||||
if (*second == '"') {
|
||||
if (unquote_c_style(&sp, second, NULL))
|
||||
goto free_and_fail1;
|
||||
cp = skip_tree_prefix(state, sp.buf, sp.len);
|
||||
if (!cp)
|
||||
goto free_and_fail1;
|
||||
/* They must match, otherwise ignore */
|
||||
if (strcmp(cp, first.buf))
|
||||
goto free_and_fail1;
|
||||
strbuf_release(&sp);
|
||||
return strbuf_detach(&first, NULL);
|
||||
}
|
||||
|
||||
/* unquoted second */
|
||||
cp = skip_tree_prefix(state, second, line + llen - second);
|
||||
if (!cp)
|
||||
goto free_and_fail1;
|
||||
if (line + llen - cp != first.len ||
|
||||
memcmp(first.buf, cp, first.len))
|
||||
goto free_and_fail1;
|
||||
return strbuf_detach(&first, NULL);
|
||||
|
||||
free_and_fail1:
|
||||
strbuf_release(&first);
|
||||
strbuf_release(&sp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* unquoted first name */
|
||||
name = skip_tree_prefix(state, line, llen);
|
||||
if (!name)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* since the first name is unquoted, a dq if exists must be
|
||||
* the beginning of the second name.
|
||||
*/
|
||||
for (second = name; second < line + llen; second++) {
|
||||
if (*second == '"') {
|
||||
struct strbuf sp = STRBUF_INIT;
|
||||
const char *np;
|
||||
|
||||
if (unquote_c_style(&sp, second, NULL))
|
||||
goto free_and_fail2;
|
||||
|
||||
np = skip_tree_prefix(state, sp.buf, sp.len);
|
||||
if (!np)
|
||||
goto free_and_fail2;
|
||||
|
||||
len = sp.buf + sp.len - np;
|
||||
if (len < second - name &&
|
||||
!strncmp(np, name, len) &&
|
||||
isspace(name[len])) {
|
||||
/* Good */
|
||||
strbuf_remove(&sp, 0, np - sp.buf);
|
||||
return strbuf_detach(&sp, NULL);
|
||||
}
|
||||
|
||||
free_and_fail2:
|
||||
strbuf_release(&sp);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Accept a name only if it shows up twice, exactly the same
|
||||
* form.
|
||||
*/
|
||||
second = strchr(name, '\n');
|
||||
if (!second)
|
||||
return NULL;
|
||||
line_len = second - name;
|
||||
for (len = 0 ; ; len++) {
|
||||
switch (name[len]) {
|
||||
default:
|
||||
continue;
|
||||
case '\n':
|
||||
return NULL;
|
||||
case '\t': case ' ':
|
||||
/*
|
||||
* Is this the separator between the preimage
|
||||
* and the postimage pathname? Again, we are
|
||||
* only interested in the case where there is
|
||||
* no rename, as this is only to set def_name
|
||||
* and a rename patch has the names elsewhere
|
||||
* in an unambiguous form.
|
||||
*/
|
||||
if (!name[len + 1])
|
||||
return NULL; /* no postimage name */
|
||||
second = skip_tree_prefix(state, name + len + 1,
|
||||
line_len - (len + 1));
|
||||
if (!second)
|
||||
return NULL;
|
||||
/*
|
||||
* Does len bytes starting at "name" and "second"
|
||||
* (that are separated by one HT or SP we just
|
||||
* found) exactly match?
|
||||
*/
|
||||
if (second[len] == '\n' && !strncmp(name, second, len))
|
||||
return xmemdupz(name, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify that we recognize the lines following a git header */
|
||||
static int parse_git_header(struct apply_state *state,
|
||||
const char *line,
|
||||
int len,
|
||||
unsigned int size,
|
||||
struct patch *patch)
|
||||
{
|
||||
unsigned long offset;
|
||||
|
||||
/* A git diff has explicit new/delete information, so we don't guess */
|
||||
patch->is_new = 0;
|
||||
patch->is_delete = 0;
|
||||
|
||||
/*
|
||||
* Some things may not have the old name in the
|
||||
* rest of the headers anywhere (pure mode changes,
|
||||
* or removing or adding empty files), so we get
|
||||
* the default name from the header.
|
||||
*/
|
||||
patch->def_name = git_header_name(state, line, len);
|
||||
if (patch->def_name && state->root.len) {
|
||||
char *s = xstrfmt("%s%s", state->root.buf, patch->def_name);
|
||||
free(patch->def_name);
|
||||
patch->def_name = s;
|
||||
}
|
||||
|
||||
line += len;
|
||||
size -= len;
|
||||
state->linenr++;
|
||||
for (offset = len ; size > 0 ; offset += len, size -= len, line += len, state->linenr++) {
|
||||
static const struct opentry {
|
||||
const char *str;
|
||||
int (*fn)(struct apply_state *, const char *, struct patch *);
|
||||
} optable[] = {
|
||||
{ "@@ -", gitdiff_hdrend },
|
||||
{ "--- ", gitdiff_oldname },
|
||||
{ "+++ ", gitdiff_newname },
|
||||
{ "old mode ", gitdiff_oldmode },
|
||||
{ "new mode ", gitdiff_newmode },
|
||||
{ "deleted file mode ", gitdiff_delete },
|
||||
{ "new file mode ", gitdiff_newfile },
|
||||
{ "copy from ", gitdiff_copysrc },
|
||||
{ "copy to ", gitdiff_copydst },
|
||||
{ "rename old ", gitdiff_renamesrc },
|
||||
{ "rename new ", gitdiff_renamedst },
|
||||
{ "rename from ", gitdiff_renamesrc },
|
||||
{ "rename to ", gitdiff_renamedst },
|
||||
{ "similarity index ", gitdiff_similarity },
|
||||
{ "dissimilarity index ", gitdiff_dissimilarity },
|
||||
{ "index ", gitdiff_index },
|
||||
{ "", gitdiff_unrecognized },
|
||||
};
|
||||
int i;
|
||||
|
||||
len = linelen(line, size);
|
||||
if (!len || line[len-1] != '\n')
|
||||
break;
|
||||
for (i = 0; i < ARRAY_SIZE(optable); i++) {
|
||||
const struct opentry *p = optable + i;
|
||||
int oplen = strlen(p->str);
|
||||
int res;
|
||||
if (len < oplen || memcmp(p->str, line, oplen))
|
||||
continue;
|
||||
res = p->fn(state, line + oplen, patch);
|
||||
if (res < 0)
|
||||
return -1;
|
||||
if (res > 0)
|
||||
return offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int parse_num(const char *line, unsigned long *p)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
if (!isdigit(*line))
|
||||
return 0;
|
||||
*p = strtoul(line, &ptr, 10);
|
||||
return ptr - line;
|
||||
}
|
||||
|
||||
static int parse_range(const char *line, int len, int offset, const char *expect,
|
||||
unsigned long *p1, unsigned long *p2)
|
||||
{
|
||||
int digits, ex;
|
||||
|
||||
if (offset < 0 || offset >= len)
|
||||
return -1;
|
||||
line += offset;
|
||||
len -= offset;
|
||||
|
||||
digits = parse_num(line, p1);
|
||||
if (!digits)
|
||||
return -1;
|
||||
|
||||
offset += digits;
|
||||
line += digits;
|
||||
len -= digits;
|
||||
|
||||
*p2 = 1;
|
||||
if (*line == ',') {
|
||||
digits = parse_num(line+1, p2);
|
||||
if (!digits)
|
||||
return -1;
|
||||
|
||||
offset += digits+1;
|
||||
line += digits+1;
|
||||
len -= digits+1;
|
||||
}
|
||||
|
||||
ex = strlen(expect);
|
||||
if (ex > len)
|
||||
return -1;
|
||||
if (memcmp(line, expect, ex))
|
||||
return -1;
|
||||
|
||||
return offset + ex;
|
||||
}
|
||||
|
||||
static void recount_diff(const char *line, int size, struct fragment *fragment)
|
||||
{
|
||||
int oldlines = 0, newlines = 0, ret = 0;
|
||||
|
||||
if (size < 1) {
|
||||
warning("recount: ignore empty hunk");
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int len = linelen(line, size);
|
||||
size -= len;
|
||||
line += len;
|
||||
|
||||
if (size < 1)
|
||||
break;
|
||||
|
||||
switch (*line) {
|
||||
case ' ': case '\n':
|
||||
newlines++;
|
||||
/* fall through */
|
||||
case '-':
|
||||
oldlines++;
|
||||
continue;
|
||||
case '+':
|
||||
newlines++;
|
||||
continue;
|
||||
case '\\':
|
||||
continue;
|
||||
case '@':
|
||||
ret = size < 3 || !starts_with(line, "@@ ");
|
||||
break;
|
||||
case 'd':
|
||||
ret = size < 5 || !starts_with(line, "diff ");
|
||||
break;
|
||||
default:
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
if (ret) {
|
||||
warning(_("recount: unexpected line: %.*s"),
|
||||
(int)linelen(line, size), line);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
fragment->oldlines = oldlines;
|
||||
fragment->newlines = newlines;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a unified diff fragment header of the
|
||||
* form "@@ -a,b +c,d @@"
|
||||
*/
|
||||
static int parse_fragment_header(const char *line, int len, struct fragment *fragment)
|
||||
{
|
||||
int offset;
|
||||
|
||||
if (!len || line[len-1] != '\n')
|
||||
return -1;
|
||||
|
||||
/* Figure out the number of lines in a fragment */
|
||||
offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines);
|
||||
offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find file diff header
|
||||
*
|
||||
* Returns:
|
||||
* -1 if no header was found
|
||||
* -128 in case of error
|
||||
* the size of the header in bytes (called "offset") otherwise
|
||||
*/
|
||||
static int find_header(struct apply_state *state,
|
||||
const char *line,
|
||||
unsigned long size,
|
||||
int *hdrsize,
|
||||
struct patch *patch)
|
||||
{
|
||||
unsigned long offset, len;
|
||||
|
||||
patch->is_toplevel_relative = 0;
|
||||
patch->is_rename = patch->is_copy = 0;
|
||||
patch->is_new = patch->is_delete = -1;
|
||||
patch->old_mode = patch->new_mode = 0;
|
||||
patch->old_name = patch->new_name = NULL;
|
||||
for (offset = 0; size > 0; offset += len, size -= len, line += len, state->linenr++) {
|
||||
unsigned long nextlen;
|
||||
|
||||
len = linelen(line, size);
|
||||
if (!len)
|
||||
break;
|
||||
|
||||
/* Testing this early allows us to take a few shortcuts.. */
|
||||
if (len < 6)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Make sure we don't find any unconnected patch fragments.
|
||||
* That's a sign that we didn't find a header, and that a
|
||||
* patch has become corrupted/broken up.
|
||||
*/
|
||||
if (!memcmp("@@ -", line, 4)) {
|
||||
struct fragment dummy;
|
||||
if (parse_fragment_header(line, len, &dummy) < 0)
|
||||
continue;
|
||||
error(_("patch fragment without header at line %d: %.*s"),
|
||||
state->linenr, (int)len-1, line);
|
||||
return -128;
|
||||
}
|
||||
|
||||
if (size < len + 6)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Git patch? It might not have a real patch, just a rename
|
||||
* or mode change, so we handle that specially
|
||||
*/
|
||||
if (!memcmp("diff --git ", line, 11)) {
|
||||
int git_hdr_len = parse_git_header(state, line, len, size, patch);
|
||||
if (git_hdr_len < 0)
|
||||
return -128;
|
||||
if (git_hdr_len <= len)
|
||||
continue;
|
||||
if (!patch->old_name && !patch->new_name) {
|
||||
if (!patch->def_name) {
|
||||
error(Q_("git diff header lacks filename information when removing "
|
||||
"%d leading pathname component (line %d)",
|
||||
"git diff header lacks filename information when removing "
|
||||
"%d leading pathname components (line %d)",
|
||||
state->p_value),
|
||||
state->p_value, state->linenr);
|
||||
return -128;
|
||||
}
|
||||
patch->old_name = xstrdup(patch->def_name);
|
||||
patch->new_name = xstrdup(patch->def_name);
|
||||
}
|
||||
if (!patch->is_delete && !patch->new_name) {
|
||||
error("git diff header lacks filename information "
|
||||
"(line %d)", state->linenr);
|
||||
return -128;
|
||||
}
|
||||
patch->is_toplevel_relative = 1;
|
||||
*hdrsize = git_hdr_len;
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* --- followed by +++ ? */
|
||||
if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* We only accept unified patches, so we want it to
|
||||
* at least have "@@ -a,b +c,d @@\n", which is 14 chars
|
||||
* minimum ("@@ -0,0 +1 @@\n" is the shortest).
|
||||
*/
|
||||
nextlen = linelen(line + len, size - len);
|
||||
if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
|
||||
continue;
|
||||
|
||||
/* Ok, we'll consider it a patch */
|
||||
if (parse_traditional_patch(state, line, line+len, patch))
|
||||
return -128;
|
||||
*hdrsize = len + nextlen;
|
||||
state->linenr += 2;
|
||||
return offset;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void record_ws_error(struct apply_state *state,
|
||||
unsigned result,
|
||||
const char *line,
|
||||
int len,
|
||||
int linenr)
|
||||
{
|
||||
char *err;
|
||||
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
state->whitespace_error++;
|
||||
if (state->squelch_whitespace_errors &&
|
||||
state->squelch_whitespace_errors < state->whitespace_error)
|
||||
return;
|
||||
|
||||