static int prefix_length = -1;
static int newfd = -1;
+static int unidiff_zero;
static int p_value = 1;
-static int allow_binary_replacement;
static int check_index;
static int write_index;
static int cached;
static int no_add;
static int show_index_info;
static int line_termination = '\n';
-static unsigned long p_context = -1;
+static unsigned long p_context = ULONG_MAX;
static const char apply_usage[] =
"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
struct patch {
char *new_name, *old_name, *def_name;
unsigned int old_mode, new_mode;
- int is_rename, is_copy, is_new, is_delete, is_binary;
+ int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
int rejected;
unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
- int inaccurate_eof:1;
+ unsigned int inaccurate_eof:1;
+ unsigned int is_binary:1;
+ unsigned int is_copy:1;
+ unsigned int is_rename:1;
struct fragment *fragments;
char *result;
unsigned long resultsize;
static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
{
if (!orig_name && !isnull)
- return find_name(line, NULL, 1, 0);
+ return find_name(line, NULL, 1, TERM_TAB);
if (orig_name) {
int len;
len = strlen(name);
if (isnull)
die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
- another = find_name(line, NULL, 1, 0);
+ another = find_name(line, NULL, 1, TERM_TAB);
if (!another || memcmp(another, name, len))
die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
free(another);
return -1;
}
+static void check_whitespace(const char *line, int len)
+{
+ const char *err = "Adds trailing whitespace";
+ int seen_space = 0;
+ int i;
+
+ /*
+ * We know len is at least two, since we have a '+' and we
+ * checked that the last character was a '\n' before calling
+ * this function. That is, an addition of an empty line would
+ * check the '+' here. Sneaky...
+ */
+ if (isspace(line[len-2]))
+ goto error;
+
+ /*
+ * Make sure that there is no space followed by a tab in
+ * indentation.
+ */
+ err = "Space in indent is followed by a tab";
+ for (i = 1; i < len; i++) {
+ if (line[i] == '\t') {
+ if (seen_space)
+ goto error;
+ }
+ else if (line[i] == ' ')
+ seen_space = 1;
+ else
+ break;
+ }
+ return;
+
+ error:
+ whitespace_error++;
+ if (squelch_whitespace_errors &&
+ squelch_whitespace_errors < whitespace_error)
+ ;
+ else
+ fprintf(stderr, "%s.\n%s:%d:%.*s\n",
+ err, patch_input_file, linenr, len-2, line+1);
+}
+
+
/*
- * Parse a unified diff. Note that this really needs
- * to parse each fragment separately, since the only
- * way to know the difference between a "---" that is
- * part of a patch, and a "---" that starts the next
- * patch is to look at the line counts..
+ * Parse a unified diff. Note that this really needs to parse each
+ * fragment separately, since the only way to know the difference
+ * between a "---" that is part of a patch, and a "---" that starts
+ * the next patch is to look at the line counts..
*/
static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
{
leading = 0;
trailing = 0;
- if (patch->is_new < 0) {
- patch->is_new = !oldlines;
- if (!oldlines)
- patch->old_name = NULL;
- }
- if (patch->is_delete < 0) {
- patch->is_delete = !newlines;
- if (!newlines)
- patch->new_name = NULL;
- }
-
- if (patch->is_new && oldlines)
- return error("new file depends on old contents");
- if (patch->is_delete != !newlines) {
- if (newlines)
- return error("deleted file still has contents");
- fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
- }
-
/* Parse the thing.. */
line += len;
size -= len;
linenr++;
added = deleted = 0;
- for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
+ for (offset = len;
+ 0 < size;
+ offset += len, size -= len, line += len, linenr++) {
if (!oldlines && !newlines)
break;
len = linelen(line, size);
switch (*line) {
default:
return -1;
+ case '\n': /* newer GNU diff, an empty context line */
case ' ':
oldlines--;
newlines--;
trailing = 0;
break;
case '+':
- /*
- * We know len is at least two, since we have a '+' and
- * we checked that the last character was a '\n' above.
- * That is, an addition of an empty line would check
- * the '+' here. Sneaky...
- */
- if ((new_whitespace != nowarn_whitespace) &&
- isspace(line[len-2])) {
- whitespace_error++;
- if (squelch_whitespace_errors &&
- squelch_whitespace_errors <
- whitespace_error)
- ;
- else {
- fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n",
- patch_input_file,
- linenr, len-2, line+1);
- }
- }
+ if (new_whitespace != nowarn_whitespace)
+ check_whitespace(line, len);
added++;
newlines--;
trailing = 0;
patch->lines_added += added;
patch->lines_deleted += deleted;
+
+ if (0 < patch->is_new && oldlines)
+ return error("new file depends on old contents");
+ if (0 < patch->is_delete && newlines)
+ return error("deleted file still has contents");
return offset;
}
static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
{
unsigned long offset = 0;
+ unsigned long oldlines = 0, newlines = 0, context = 0;
struct fragment **fragp = &patch->fragments;
while (size > 4 && !memcmp(line, "@@ -", 4)) {
len = parse_fragment(line, size, patch, fragment);
if (len <= 0)
die("corrupt patch at line %d", linenr);
-
fragment->patch = line;
fragment->size = len;
+ oldlines += fragment->oldlines;
+ newlines += fragment->newlines;
+ context += fragment->leading + fragment->trailing;
*fragp = fragment;
fragp = &fragment->next;
line += len;
size -= len;
}
+
+ /*
+ * If something was removed (i.e. we have old-lines) it cannot
+ * be creation, and if something was added it cannot be
+ * deletion. However, the reverse is not true; --unified=0
+ * patches that only add are not necessarily creation even
+ * though they do not have any old lines, and ones that only
+ * delete are not necessarily deletion.
+ *
+ * Unfortunately, a real creation/deletion patch do _not_ have
+ * any context line by definition, so we cannot safely tell it
+ * apart with --unified=0 insanity. At least if the patch has
+ * more than one hunk it is not creation or deletion.
+ */
+ if (patch->is_new < 0 &&
+ (oldlines || (patch->fragments && patch->fragments->next)))
+ patch->is_new = 0;
+ if (patch->is_delete < 0 &&
+ (newlines || (patch->fragments && patch->fragments->next)))
+ patch->is_delete = 0;
+ if (!unidiff_zero || context) {
+ /* If the user says the patch is not generated with
+ * --unified=0, or if we have seen context lines,
+ * then not having oldlines means the patch is creation,
+ * and not having newlines means the patch is deletion.
+ */
+ if (patch->is_new < 0 && !oldlines) {
+ patch->is_new = 1;
+ patch->old_name = NULL;
+ }
+ if (patch->is_delete < 0 && !newlines) {
+ patch->is_delete = 1;
+ patch->new_name = NULL;
+ }
+ }
+
+ if (0 < patch->is_new && oldlines)
+ die("new file %s depends on old contents", patch->new_name);
+ if (0 < patch->is_delete && newlines)
+ die("deleted file %s still has contents", patch->old_name);
+ if (!patch->is_delete && !newlines && context)
+ fprintf(stderr, "** warning: file %s becomes empty but "
+ "is not deleted\n", patch->new_name);
+
return offset;
}
return frag;
corrupt:
- if (data)
- free(data);
+ free(data);
*status_p = -1;
error("corrupt binary patch at line %d: %.*s",
linenr-1, llen-1, buffer);
}
}
- /* Empty patch cannot be applied if:
- * - it is a binary patch and we do not do binary_replace, or
- * - text patch without metadata change
+ /* Empty patch cannot be applied if it is a text patch
+ * without metadata change. A binary patch appears
+ * empty to us here.
*/
if ((apply || check) &&
- (patch->is_binary
- ? !allow_binary_replacement
- : !metadata_changes(patch)))
+ (!patch->is_binary && !metadata_changes(patch)))
die("patch with only garbage at line %d", linenr);
}
printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
len, name, patch->lines_added + patch->lines_deleted,
add, pluses, del, minuses);
- if (qname)
- free(qname);
+ free(qname);
}
static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
{
/* plen is number of bytes to be copied from patch,
* starting at patch+1 (patch[0] is '+'). Typically
- * patch[plen] is '\n'.
+ * patch[plen] is '\n', unless this is the incomplete
+ * last line.
*/
+ int i;
int add_nl_to_tail = 0;
- if ((new_whitespace == strip_whitespace) &&
- 1 < plen && isspace(patch[plen-1])) {
+ int fixed = 0;
+ int last_tab_in_indent = -1;
+ int last_space_in_indent = -1;
+ int need_fix_leading_space = 0;
+ char *buf;
+
+ if ((new_whitespace != strip_whitespace) || !whitespace_error) {
+ memcpy(output, patch + 1, plen);
+ return plen;
+ }
+
+ if (1 < plen && isspace(patch[plen-1])) {
if (patch[plen] == '\n')
add_nl_to_tail = 1;
plen--;
while (0 < plen && isspace(patch[plen]))
plen--;
- applied_after_stripping++;
+ fixed = 1;
+ }
+
+ for (i = 1; i < plen; i++) {
+ char ch = patch[i];
+ if (ch == '\t') {
+ last_tab_in_indent = i;
+ if (0 <= last_space_in_indent)
+ need_fix_leading_space = 1;
+ }
+ else if (ch == ' ')
+ last_space_in_indent = i;
+ else
+ break;
}
- memcpy(output, patch + 1, plen);
+
+ buf = output;
+ if (need_fix_leading_space) {
+ /* between patch[1..last_tab_in_indent] strip the
+ * funny spaces, updating them to tab as needed.
+ */
+ for (i = 1; i < last_tab_in_indent; i++, plen--) {
+ char ch = patch[i];
+ if (ch != ' ')
+ *output++ = ch;
+ else if ((i % 8) == 0)
+ *output++ = '\t';
+ }
+ fixed = 1;
+ i = last_tab_in_indent;
+ }
+ else
+ i = 1;
+
+ memcpy(output, patch + i, plen);
if (add_nl_to_tail)
output[plen++] = '\n';
- return plen;
+ if (fixed)
+ applied_after_stripping++;
+ return output + plen - buf;
}
static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof)
first = '-';
}
switch (first) {
+ case '\n':
+ /* Newer GNU diff, empty context line */
+ if (plen < 0)
+ /* ... followed by '\No newline'; nothing */
+ break;
+ old[oldsize++] = '\n';
+ new[newsize++] = '\n';
+ break;
case ' ':
case '-':
memcpy(old + oldsize, patch + 1, plen);
/*
* If we don't have any leading/trailing data in the patch,
* we want it to match at the beginning/end of the file.
+ *
+ * But that would break if the patch is generated with
+ * --unified=0; sane people wouldn't do that to cause us
+ * trouble, but we try to please not so sane ones as well.
*/
- match_beginning = !leading && (frag->oldpos == 1);
- match_end = !trailing;
+ if (unidiff_zero) {
+ match_beginning = (!leading && !frag->oldpos);
+ match_end = 0;
+ }
+ else {
+ match_beginning = !leading && (frag->oldpos == 1);
+ match_end = !trailing;
+ }
lines = 0;
pos = frag->newpos;
{
const char *name = patch->old_name ? patch->old_name : patch->new_name;
unsigned char sha1[20];
- unsigned char hdr[50];
- int hdrlen;
-
- if (!allow_binary_replacement)
- return error("cannot apply binary patch to '%s' "
- "without --allow-binary-replacement",
- name);
/* For safety, we require patch index line to contain
* full 40-byte textual SHA1 for old and new, at least for now.
/* See if the old one matches what the patch
* applies to.
*/
- write_sha1_file_prepare(desc->buffer, desc->size,
- blob_type, sha1, hdr, &hdrlen);
+ hash_sha1_file(desc->buffer, desc->size, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
return error("the patch applies to '%s' (%s), "
"which does not match the "
name);
/* verify that the result matches */
- write_sha1_file_prepare(desc->buffer, desc->size, blob_type,
- sha1, hdr, &hdrlen);
+ hash_sha1_file(desc->buffer, desc->size, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1));
}
patch->result = desc.buffer;
patch->resultsize = desc.size;
- if (patch->is_delete && patch->resultsize)
+ if (0 < patch->is_delete && patch->resultsize)
return error("removal patch leaves file contents");
return 0;
old_name, st_mode, patch->old_mode);
}
- if (new_name && prev_patch && prev_patch->is_delete &&
+ if (new_name && prev_patch && 0 < prev_patch->is_delete &&
!strcmp(prev_patch->old_name, new_name))
/* A type-change diff is always split into a patch to
* delete old, immediately followed by a patch to
else
ok_if_exists = 0;
- if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
+ if (new_name &&
+ ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
if (check_index &&
cache_name_pos(new_name, strlen(new_name)) >= 0 &&
!ok_if_exists)
return error("%s: %s", new_name, strerror(errno));
}
if (!patch->new_mode) {
- if (patch->is_new)
+ if (0 < patch->is_new)
patch->new_mode = S_IFREG | 0644;
else
patch->new_mode = patch->old_mode;
const char *name;
name = patch->old_name ? patch->old_name : patch->new_name;
- if (patch->is_new)
+ if (0 < patch->is_new)
sha1_ptr = null_sha1;
else if (get_sha1(patch->old_sha1_prefix, sha1))
die("sha1 information is lacking or useless (%s).",
for ( ; patch; patch = patch->next) {
const char *name;
name = patch->new_name ? patch->new_name : patch->old_name;
- printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+ if (patch->is_binary)
+ printf("-\t-\t");
+ else
+ printf("%d\t%d\t",
+ patch->lines_added, patch->lines_deleted);
if (line_termination && quote_c_style(name, NULL, NULL, 0))
quote_c_style(name, NULL, stdout, 0);
else
fputs(name, stdout);
- putchar('\n');
+ putchar(line_termination);
}
}
static int git_apply_config(const char *var, const char *value)
{
if (!strcmp(var, "apply.whitespace")) {
- apply_default_whitespace = strdup(value);
+ apply_default_whitespace = xstrdup(value);
return 0;
}
return git_default_config(var, value);
}
if (!strcmp(arg, "--allow-binary-replacement") ||
!strcmp(arg, "--binary")) {
- allow_binary_replacement = 1;
- continue;
+ continue; /* now no-op */
}
if (!strcmp(arg, "--numstat")) {
apply = 0;
apply_in_reverse = 1;
continue;
}
+ if (!strcmp(arg, "--unidiff-zero")) {
+ unidiff_zero = 1;
+ continue;
+ }
if (!strcmp(arg, "--reject")) {
apply = apply_with_reject = apply_verbosely = 1;
continue;