* This applies patches on top of some (arbitrary) version of the SCM.
*
*/
-#include <fnmatch.h>
#include "cache.h"
#include "cache-tree.h"
#include "quote.h"
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 p_value_known;
static int check_index;
static int write_index;
static int cached;
static int check;
static int apply = 1;
static int apply_in_reverse;
+static int apply_with_reject;
+static int apply_verbosely;
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] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
+"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>...";
static enum whitespace_eol {
nowarn_whitespace,
unsigned long newpos, newlines;
const char *patch;
int size;
+ int rejected;
struct fragment *next;
};
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 is_toplevel_relative: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;
struct patch *next;
};
+static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post)
+{
+ fputs(pre, output);
+ if (patch->old_name && patch->new_name &&
+ strcmp(patch->old_name, patch->new_name)) {
+ write_name_quoted(NULL, 0, patch->old_name, 1, output);
+ fputs(" => ", output);
+ write_name_quoted(NULL, 0, patch->new_name, 1, output);
+ }
+ else {
+ const char *n = patch->new_name;
+ if (!n)
+ n = patch->old_name;
+ write_name_quoted(NULL, 0, n, 1, output);
+ }
+ fputs(post, output);
+}
+
#define CHUNKSIZE (8192)
#define SLOP (16)
return 1;
}
-static char * find_name(const char *line, char *def, int p_value, int terminate)
+static char *find_name(const char *line, char *def, int p_value, int terminate)
{
int len;
const char *start = line;
return name;
}
+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(const char *nameline)
+{
+ char *name, *cp;
+ int val = -1;
+
+ if (is_dev_null(nameline))
+ return -1;
+ name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB);
+ if (!name)
+ return -1;
+ cp = strchr(name, '/');
+ if (!cp)
+ val = 0;
+ else if (prefix) {
+ /*
+ * Does it begin with "a/$our-prefix" and such? Then this is
+ * very likely to apply to our directory.
+ */
+ if (!strncmp(name, prefix, prefix_length))
+ val = count_slashes(prefix);
+ else {
+ cp++;
+ if (!strncmp(cp, prefix, prefix_length))
+ val = count_slashes(prefix) + 1;
+ }
+ }
+ free(name);
+ return val;
+}
+
/*
* Get the name etc info from the --/+++ lines of a traditional patch header
*
- * NOTE! This hardcodes "-p1" behaviour in filename detection.
- *
* 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.
first += 4; /* skip "--- " */
second += 4; /* skip "+++ " */
+ if (!p_value_known) {
+ int p, q;
+ p = guess_p_value(first);
+ q = guess_p_value(second);
+ if (p < 0) p = q;
+ if (0 <= p && p == q) {
+ p_value = p;
+ p_value_known = 1;
+ }
+ }
if (is_dev_null(first)) {
patch->is_new = 1;
patch->is_delete = 0;
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);
* form.
*/
for (len = 0 ; ; len++) {
- char c = name[len];
-
- switch (c) {
+ switch (name[len]) {
default:
continue;
case '\n':
{
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;
struct fragment dummy;
if (parse_fragment_header(line, len, &dummy) < 0)
continue;
- error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line);
+ die("patch fragment without header at line %d: %.*s",
+ linenr, (int)len-1, line);
}
if (size < len + 6)
die("git diff header lacks filename information (line %d)", linenr);
patch->old_name = patch->new_name = patch->def_name;
}
+ patch->is_toplevel_relative = 1;
*hdrsize = git_hdr_len;
return offset;
}
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;
}
*status_p = 0;
- if (!strncmp(buffer, "delta ", 6)) {
+ if (!prefixcmp(buffer, "delta ")) {
patch_method = BINARY_DELTA_DEFLATED;
origlen = strtoul(buffer + 6, NULL, 10);
}
- else if (!strncmp(buffer, "literal ", 8)) {
+ else if (!prefixcmp(buffer, "literal ")) {
patch_method = BINARY_LITERAL_DEFLATED;
origlen = strtoul(buffer + 8, NULL, 10);
}
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)
+static int read_old_data(struct stat *st, const char *path, char **buf_p, unsigned long *alloc_p, unsigned long *size_p)
{
int fd;
unsigned long got;
+ unsigned long nsize;
+ char *nbuf;
+ unsigned long size = *size_p;
+ char *buf = *buf_p;
switch (st->st_mode & S_IFMT) {
case S_IFLNK:
- return readlink(path, buf, size);
+ return readlink(path, buf, size) != size;
case S_IFREG:
fd = open(path, O_RDONLY);
if (fd < 0)
return error("unable to open %s", path);
got = 0;
for (;;) {
- int ret = xread(fd, (char *) buf + got, size - got);
+ int ret = xread(fd, buf + got, size - got);
if (ret <= 0)
break;
got += ret;
}
close(fd);
- return got;
-
+ nsize = got;
+ nbuf = buf;
+ if (convert_to_git(path, &nbuf, &nsize)) {
+ free(buf);
+ *buf_p = nbuf;
+ *alloc_p = nsize;
+ *size_p = nsize;
+ }
+ return got != size;
default:
return -1;
}
{
/* 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 ||
+ *patch != '+') {
+ 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);
/* Ignore it, we already handled it */
break;
default:
+ if (apply_verbosely)
+ error("invalid start of line: '%c'", first);
return -1;
}
patch += len;
/*
* 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;
for (;;) {
- offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines);
+ offset = find_offset(buf, desc->size,
+ oldlines, oldsize, pos, &lines);
if (match_end && offset + oldsize != desc->size)
offset = -1;
if (match_beginning && offset)
/* Warn if it was necessary to reduce the number
* of context lines.
*/
- if ((leading != frag->leading) || (trailing != frag->trailing))
- fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n",
+ if ((leading != frag->leading) ||
+ (trailing != frag->trailing))
+ fprintf(stderr, "Context reduced to (%ld/%ld)"
+ " to apply fragment at %d\n",
leading, trailing, pos + lines);
if (size > alloc) {
desc->buffer = buf;
}
desc->size = size;
- memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize);
+ memmove(buf + offset + newsize,
+ buf + offset + oldsize,
+ size - offset - newsize);
memcpy(buf + offset, newlines, newsize);
offset = 0;
}
}
+ if (offset && apply_verbosely)
+ error("while searching for:\n%.*s", oldsize, oldlines);
+
free(old);
free(new);
return offset;
{
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 "
if (has_sha1_file(sha1)) {
/* We already have the postimage */
- char type[10];
+ enum object_type type;
unsigned long size;
free(desc->buffer);
- desc->buffer = read_sha1_file(sha1, type, &size);
+ desc->buffer = read_sha1_file(sha1, &type, &size);
if (!desc->buffer)
return error("the necessary postimage %s for "
"'%s' cannot be read",
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));
}
return apply_binary(desc, patch);
while (frag) {
- if (apply_one_fragment(desc, frag, patch->inaccurate_eof) < 0)
- return error("patch failed: %s:%ld",
- name, frag->oldpos);
+ if (apply_one_fragment(desc, frag, patch->inaccurate_eof)) {
+ error("patch failed: %s:%ld", name, frag->oldpos);
+ if (!apply_with_reject)
+ return -1;
+ frag->rejected = 1;
+ }
frag = frag->next;
}
return 0;
buf = NULL;
if (cached) {
if (ce) {
- char type[20];
- buf = read_sha1_file(ce->sha1, type, &size);
+ enum object_type type;
+ buf = read_sha1_file(ce->sha1, &type, &size);
if (!buf)
return error("read of %s failed",
patch->old_name);
size = st->st_size;
alloc = size + 8192;
buf = xmalloc(alloc);
- if (read_old_data(st, patch->old_name, buf, alloc) != size)
+ if (read_old_data(st, patch->old_name, &buf, &alloc, &size))
return error("read of %s failed", patch->old_name);
}
desc.size = size;
desc.alloc = alloc;
desc.buffer = buf;
+
if (apply_fragments(&desc, patch) < 0)
- return -1;
+ return -1; /* note with --reject this succeeds. */
/* NUL terminate the result */
if (desc.alloc <= desc.size)
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;
struct cache_entry *ce = NULL;
int ok_if_exists;
+ patch->rejected = 1; /* we will drop this after we succeed */
if (old_name) {
int changed = 0;
int stat_ret = 0;
return error("%s: %s", old_name, strerror(errno));
if (!cached)
- st_mode = ntohl(create_ce_mode(st.st_mode));
+ st_mode = ntohl(ce_mode_from_stat(ce, st.st_mode));
if (patch->is_new < 0)
patch->is_new = 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;
if (apply_data(patch, &st, ce) < 0)
return error("%s: patch does not apply", name);
+ patch->rejected = 0;
return 0;
}
static int check_patch_list(struct patch *patch)
{
struct patch *prev_patch = NULL;
- int error = 0;
+ int err = 0;
for (prev_patch = NULL; patch ; patch = patch->next) {
- error |= check_patch(patch, prev_patch);
+ if (apply_verbosely)
+ say_patch_name(stderr,
+ "Checking patch ", patch, "...\n");
+ err |= check_patch(patch, prev_patch);
prev_patch = patch;
}
- return error;
+ return err;
}
static void show_index_list(struct patch *list)
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 void remove_file(struct patch *patch)
+static void remove_file(struct patch *patch, int rmdir_empty)
{
if (write_index) {
if (remove_file_from_cache(patch->old_name) < 0)
die("unable to remove %s from index", patch->old_name);
cache_tree_invalidate_path(active_cache_tree, patch->old_name);
}
- if (!cached)
- unlink(patch->old_name);
+ if (!cached) {
+ if (!unlink(patch->old_name) && rmdir_empty) {
+ char *name = xstrdup(patch->old_name);
+ char *end = strrchr(name, '/');
+ while (end) {
+ *end = 0;
+ if (rmdir(name))
+ break;
+ end = strrchr(name, '/');
+ }
+ free(name);
+ }
+ }
}
static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
{
int fd;
+ char *nbuf;
+ unsigned long nsize;
if (S_ISLNK(mode))
/* Although buf:size is counted string, it also is NUL
* terminated.
*/
return symlink(buf, path);
+ nsize = size;
+ nbuf = (char *) buf;
+ if (convert_to_working_tree(path, &nbuf, &nsize)) {
+ free((char *) buf);
+ buf = nbuf;
+ size = nsize;
+ }
+
fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
if (fd < 0)
return -1;
{
if (patch->is_delete > 0) {
if (phase == 0)
- remove_file(patch);
+ remove_file(patch, 1);
return;
}
if (patch->is_new > 0 || patch->is_copy) {
* thing: remove the old, write the new
*/
if (phase == 0)
- remove_file(patch);
+ remove_file(patch, 0);
if (phase == 1)
- create_file(patch);
+ create_file(patch);
+}
+
+static int write_out_one_reject(struct patch *patch)
+{
+ FILE *rej;
+ char namebuf[PATH_MAX];
+ struct fragment *frag;
+ int cnt = 0;
+
+ for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
+ if (!frag->rejected)
+ continue;
+ cnt++;
+ }
+
+ if (!cnt) {
+ if (apply_verbosely)
+ say_patch_name(stderr,
+ "Applied patch ", patch, " cleanly.\n");
+ return 0;
+ }
+
+ /* This should not happen, because a removal patch that leaves
+ * contents are marked "rejected" at the patch level.
+ */
+ if (!patch->new_name)
+ die("internal error");
+
+ /* Say this even without --verbose */
+ say_patch_name(stderr, "Applying patch ", patch, " with");
+ fprintf(stderr, " %d rejects...\n", cnt);
+
+ cnt = strlen(patch->new_name);
+ if (ARRAY_SIZE(namebuf) <= cnt + 5) {
+ cnt = ARRAY_SIZE(namebuf) - 5;
+ fprintf(stderr,
+ "warning: truncating .rej filename to %.*s.rej",
+ cnt - 1, patch->new_name);
+ }
+ memcpy(namebuf, patch->new_name, cnt);
+ memcpy(namebuf + cnt, ".rej", 5);
+
+ rej = fopen(namebuf, "w");
+ if (!rej)
+ return error("cannot open %s: %s", namebuf, strerror(errno));
+
+ /* Normal git tools never deal with .rej, so do not pretend
+ * this is a git patch by saying --git nor give extended
+ * headers. While at it, maybe please "kompare" that wants
+ * the trailing TAB and some garbage at the end of line ;-).
+ */
+ fprintf(rej, "diff a/%s b/%s\t(rejected hunks)\n",
+ patch->new_name, patch->new_name);
+ for (cnt = 1, frag = patch->fragments;
+ frag;
+ cnt++, frag = frag->next) {
+ if (!frag->rejected) {
+ fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
+ continue;
+ }
+ fprintf(stderr, "Rejected hunk #%d.\n", cnt);
+ fprintf(rej, "%.*s", frag->size, frag->patch);
+ if (frag->patch[frag->size-1] != '\n')
+ fputc('\n', rej);
+ }
+ fclose(rej);
+ return -1;
}
-static void write_out_results(struct patch *list, int skipped_patch)
+static int write_out_results(struct patch *list, int skipped_patch)
{
int phase;
+ int errs = 0;
+ struct patch *l;
if (!list && !skipped_patch)
- die("No changes");
+ return error("No changes");
for (phase = 0; phase < 2; phase++) {
- struct patch *l = list;
+ l = list;
while (l) {
- write_out_one_result(l, phase);
+ if (l->rejected)
+ errs = 1;
+ else {
+ write_out_one_result(l, phase);
+ if (phase == 1 && write_out_one_reject(l))
+ errs = 1;
+ }
l = l->next;
}
}
+ return errs;
}
static struct lock_file lock_file;
return 1;
}
+static void prefix_one(char **name)
+{
+ char *old_name = *name;
+ if (!old_name)
+ return;
+ *name = xstrdup(prefix_filename(prefix, prefix_length, *name));
+ free(old_name);
+}
+
+static void prefix_patches(struct patch *p)
+{
+ if (!prefix || p->is_toplevel_relative)
+ return;
+ for ( ; p; p = p->next) {
+ if (p->new_name == p->old_name) {
+ char *prefixed = p->new_name;
+ prefix_one(&prefixed);
+ p->new_name = p->old_name = prefixed;
+ }
+ else {
+ prefix_one(&p->new_name);
+ prefix_one(&p->old_name);
+ }
+ }
+}
+
static int apply_patch(int fd, const char *filename, int inaccurate_eof)
{
unsigned long offset, size;
break;
if (apply_in_reverse)
reverse_patches(patch);
+ if (prefix)
+ prefix_patches(patch);
if (use_patch(patch)) {
patch_stats(patch);
*listp = patch;
listp = &patch->next;
- } else {
+ }
+ else {
/* perhaps free it a bit better? */
free(patch);
skipped_patch++;
die("unable to read index file");
}
- if ((check || apply) && check_patch_list(list) < 0)
+ if ((check || apply) &&
+ check_patch_list(list) < 0 &&
+ !apply_with_reject)
exit(1);
- if (apply)
- write_out_results(list, skipped_patch);
+ if (apply && write_out_results(list, skipped_patch))
+ exit(1);
if (show_index_info)
show_index_list(list);
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);
}
-int cmd_apply(int argc, const char **argv, const char *prefix)
+int cmd_apply(int argc, const char **argv, const char *unused_prefix)
{
int i;
int read_stdin = 1;
int inaccurate_eof = 0;
+ int errs = 0;
+ int is_not_gitdir = 0;
const char *whitespace_option = NULL;
+ prefix = setup_git_directory_gently(&is_not_gitdir);
+ prefix_length = prefix ? strlen(prefix) : 0;
+ git_config(git_apply_config);
+ if (apply_default_whitespace)
+ parse_whitespace_option(apply_default_whitespace);
+
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
char *end;
int fd;
if (!strcmp(arg, "-")) {
- apply_patch(0, "<stdin>", inaccurate_eof);
+ errs |= apply_patch(0, "<stdin>", inaccurate_eof);
read_stdin = 0;
continue;
}
- if (!strncmp(arg, "--exclude=", 10)) {
+ if (!prefixcmp(arg, "--exclude=")) {
struct excludes *x = xmalloc(sizeof(*x));
x->path = arg + 10;
x->next = excludes;
excludes = x;
continue;
}
- if (!strncmp(arg, "-p", 2)) {
+ if (!prefixcmp(arg, "-p")) {
p_value = atoi(arg + 2);
+ p_value_known = 1;
continue;
}
if (!strcmp(arg, "--no-add")) {
}
if (!strcmp(arg, "--allow-binary-replacement") ||
!strcmp(arg, "--binary")) {
- allow_binary_replacement = 1;
- continue;
+ continue; /* now no-op */
}
if (!strcmp(arg, "--numstat")) {
apply = 0;
continue;
}
if (!strcmp(arg, "--index")) {
+ if (is_not_gitdir)
+ die("--index outside a repository");
check_index = 1;
continue;
}
if (!strcmp(arg, "--cached")) {
+ if (is_not_gitdir)
+ die("--cached outside a repository");
check_index = 1;
cached = 1;
continue;
line_termination = 0;
continue;
}
- if (!strncmp(arg, "-C", 2)) {
+ if (!prefixcmp(arg, "-C")) {
p_context = strtoul(arg + 2, &end, 0);
if (*end != '\0')
die("unrecognized context count '%s'", arg + 2);
continue;
}
- if (!strncmp(arg, "--whitespace=", 13)) {
+ if (!prefixcmp(arg, "--whitespace=")) {
whitespace_option = arg + 13;
parse_whitespace_option(arg + 13);
continue;
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;
+ }
+ if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) {
+ apply_verbosely = 1;
+ continue;
+ }
if (!strcmp(arg, "--inaccurate-eof")) {
inaccurate_eof = 1;
continue;
}
-
- if (check_index && prefix_length < 0) {
- prefix = setup_git_directory();
- prefix_length = prefix ? strlen(prefix) : 0;
- git_config(git_apply_config);
- if (!whitespace_option && apply_default_whitespace)
- parse_whitespace_option(apply_default_whitespace);
- }
if (0 < prefix_length)
arg = prefix_filename(prefix, prefix_length, arg);
usage(apply_usage);
read_stdin = 0;
set_default_whitespace_mode(whitespace_option);
- apply_patch(fd, arg, inaccurate_eof);
+ errs |= apply_patch(fd, arg, inaccurate_eof);
close(fd);
}
set_default_whitespace_mode(whitespace_option);
if (read_stdin)
- apply_patch(0, "<stdin>", inaccurate_eof);
+ errs |= apply_patch(0, "<stdin>", inaccurate_eof);
if (whitespace_error) {
if (squelch_whitespace_errors &&
squelch_whitespace_errors < whitespace_error) {
int squelched =
whitespace_error - squelch_whitespace_errors;
- fprintf(stderr, "warning: squelched %d whitespace error%s\n",
+ fprintf(stderr, "warning: squelched %d "
+ "whitespace error%s\n",
squelched,
squelched == 1 ? "" : "s");
}
die("Unable to write new index file");
}
- return 0;
+ return !!errs;
}