*/
#include <fnmatch.h>
#include "cache.h"
+#include "cache-tree.h"
#include "quote.h"
+#include "blob.h"
+
+static unsigned char active_cache_sha1[20];
+static struct cache_tree *active_cache_tree;
+
// --check turns on checking that the working tree matches the
// files that are being modified, but doesn't apply the patch
static const char *prefix;
static int prefix_length = -1;
+static int p_value = 1;
static int allow_binary_replacement = 0;
static int check_index = 0;
static int write_index = 0;
static int no_add = 0;
static int show_index_info = 0;
static int line_termination = '\n';
+static unsigned long p_context = -1;
static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
+
+static enum whitespace_eol {
+ nowarn_whitespace,
+ warn_on_whitespace,
+ error_on_whitespace,
+ strip_whitespace,
+} new_whitespace = warn_on_whitespace;
+static int whitespace_error = 0;
+static int squelch_whitespace_errors = 5;
+static int applied_after_stripping = 0;
+static const char *patch_input_file = NULL;
+
+static void parse_whitespace_option(const char *option)
+{
+ if (!option) {
+ new_whitespace = warn_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "warn")) {
+ new_whitespace = warn_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "nowarn")) {
+ new_whitespace = nowarn_whitespace;
+ return;
+ }
+ if (!strcmp(option, "error")) {
+ new_whitespace = error_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "error-all")) {
+ new_whitespace = error_on_whitespace;
+ squelch_whitespace_errors = 0;
+ return;
+ }
+ if (!strcmp(option, "strip")) {
+ new_whitespace = strip_whitespace;
+ return;
+ }
+ die("unrecognized whitespace option '%s'", option);
+}
+
+static void set_default_whitespace_mode(const char *whitespace_option)
+{
+ if (!whitespace_option && !apply_default_whitespace) {
+ new_whitespace = (apply
+ ? warn_on_whitespace
+ : nowarn_whitespace);
+ }
+}
/*
* For "diff-stat" like behaviour, we keep track of the biggest change
static int linenr = 1;
struct fragment {
+ unsigned long leading, trailing;
unsigned long oldpos, oldlines;
unsigned long newpos, newlines;
const char *patch;
*/
static void parse_traditional_patch(const char *first, const char *second, struct patch *patch)
{
- int p_value = 1;
char *name;
first += 4; // skip "--- "
len = linelen(line, size);
if (!len || line[len-1] != '\n')
break;
- for (i = 0; i < sizeof(optable) / sizeof(optable[0]); i++) {
+ for (i = 0; i < ARRAY_SIZE(optable); i++) {
const struct opentry *p = optable + i;
int oplen = strlen(p->str);
if (len < oplen || memcmp(p->str, line, oplen))
line += digits;
len -= digits;
- *p2 = *p1;
+ *p2 = 1;
if (*line == ',') {
digits = parse_num(line+1, p2);
if (!digits)
int added, deleted;
int len = linelen(line, size), offset;
unsigned long oldlines, newlines;
+ unsigned long leading, trailing;
offset = parse_fragment_header(line, len, fragment);
if (offset < 0)
return -1;
oldlines = fragment->oldlines;
newlines = fragment->newlines;
+ leading = 0;
+ trailing = 0;
if (patch->is_new < 0) {
patch->is_new = !oldlines;
patch->new_name = NULL;
}
- if (patch->is_new != !oldlines)
+ if (patch->is_new && oldlines)
return error("new file depends on old contents");
if (patch->is_delete != !newlines) {
if (newlines)
case ' ':
oldlines--;
newlines--;
+ if (!deleted && !added)
+ leading++;
+ trailing++;
break;
case '-':
deleted++;
oldlines--;
+ 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);
+ }
+ }
added++;
newlines--;
+ trailing = 0;
break;
/* We allow "\ No newline at end of file". Depending
break;
}
}
+ if (oldlines || newlines)
+ return -1;
+ fragment->leading = leading;
+ fragment->trailing = trailing;
+
/* If a fragment ends with an incomplete line, we failed to include
* it in the above loop because we hit oldlines == newlines == 0
* before seeing it.
struct fragment *fragment;
int len;
- fragment = xmalloc(sizeof(*fragment));
- memset(fragment, 0, sizeof(*fragment));
+ fragment = xcalloc(1, sizeof(*fragment));
len = parse_fragment(line, size, patch, fragment);
if (len <= 0)
die("corrupt patch at line %d", linenr);
}
}
-static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line)
+static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line, int *lines)
{
int i;
unsigned long start, backwards, forwards;
n = (i >> 1)+1;
if (i & 1)
n = -n;
+ *lines = n;
return try;
}
return -1;
}
+static void remove_first_line(const char **rbuf, int *rsize)
+{
+ const char *buf = *rbuf;
+ int size = *rsize;
+ unsigned long offset;
+ offset = 0;
+ while (offset <= size) {
+ if (buf[offset++] == '\n')
+ break;
+ }
+ *rsize = size - offset;
+ *rbuf = buf + offset;
+}
+
+static void remove_last_line(const char **rbuf, int *rsize)
+{
+ const char *buf = *rbuf;
+ int size = *rsize;
+ unsigned long offset;
+ offset = size - 1;
+ while (offset > 0) {
+ if (buf[--offset] == '\n')
+ break;
+ }
+ *rsize = offset + 1;
+}
+
struct buffer_desc {
char *buffer;
unsigned long size;
unsigned long alloc;
};
+static int apply_line(char *output, const char *patch, int plen)
+{
+ /* plen is number of bytes to be copied from patch,
+ * starting at patch+1 (patch[0] is '+'). Typically
+ * patch[plen] is '\n'.
+ */
+ int add_nl_to_tail = 0;
+ if ((new_whitespace == strip_whitespace) &&
+ 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++;
+ }
+ memcpy(output, patch + 1, plen);
+ if (add_nl_to_tail)
+ output[plen++] = '\n';
+ return plen;
+}
+
static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
{
char *buf = desc->buffer;
int offset, size = frag->size;
char *old = xmalloc(size);
char *new = xmalloc(size);
+ const char *oldlines, *newlines;
int oldsize = 0, newsize = 0;
+ unsigned long leading, trailing;
+ int pos, lines;
while (size > 0) {
int len = linelen(patch, size);
break;
/* Fall-through for ' ' */
case '+':
- if (*patch != '+' || !no_add) {
- memcpy(new + newsize, patch + 1, plen);
- newsize += plen;
- }
+ if (*patch != '+' || !no_add)
+ newsize += apply_line(new + newsize, patch,
+ plen);
break;
case '@': case '\\':
/* Ignore it, we already handled it */
size -= len;
}
- offset = find_offset(buf, desc->size, old, oldsize, frag->newpos);
- if (offset >= 0) {
- int diff = newsize - oldsize;
- unsigned long size = desc->size + diff;
- unsigned long alloc = desc->alloc;
+#ifdef NO_ACCURATE_DIFF
+ if (oldsize > 0 && old[oldsize - 1] == '\n' &&
+ newsize > 0 && new[newsize - 1] == '\n') {
+ oldsize--;
+ newsize--;
+ }
+#endif
+
+ oldlines = old;
+ newlines = new;
+ leading = frag->leading;
+ trailing = frag->trailing;
+ lines = 0;
+ pos = frag->newpos;
+ for (;;) {
+ offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines);
+ if (offset >= 0) {
+ int diff = newsize - oldsize;
+ unsigned long size = desc->size + diff;
+ unsigned long alloc = desc->alloc;
+
+ /* 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",
+ leading, trailing, pos + lines);
+
+ if (size > alloc) {
+ alloc = size + 8192;
+ desc->alloc = alloc;
+ buf = xrealloc(buf, alloc);
+ desc->buffer = buf;
+ }
+ desc->size = size;
+ memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize);
+ memcpy(buf + offset, newlines, newsize);
+ offset = 0;
+
+ break;
+ }
- if (size > alloc) {
- alloc = size + 8192;
- desc->alloc = alloc;
- buf = xrealloc(buf, alloc);
- desc->buffer = buf;
+ /* Am I at my context limits? */
+ if ((leading <= p_context) && (trailing <= p_context))
+ break;
+ /* Reduce the number of context lines
+ * Reduce both leading and trailing if they are equal
+ * otherwise just reduce the larger context.
+ */
+ if (leading >= trailing) {
+ remove_first_line(&oldlines, &oldsize);
+ remove_first_line(&newlines, &newsize);
+ pos--;
+ leading--;
+ }
+ if (trailing > leading) {
+ remove_last_line(&oldlines, &oldsize);
+ remove_last_line(&newlines, &newsize);
+ trailing--;
}
- desc->size = size;
- memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize);
- memcpy(buf + offset, new, newsize);
- offset = 0;
}
free(old);
* applies to.
*/
write_sha1_file_prepare(desc->buffer, desc->size,
- "blob", sha1, hdr, &hdrlen);
+ blob_type, sha1, hdr, &hdrlen);
if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
return error("the patch applies to '%s' (%s), "
"which does not match the "
costate.not_new = 0;
costate.refresh_cache = 1;
if (checkout_entry(active_cache[pos],
- &costate) ||
+ &costate,
+ NULL) ||
lstat(old_name, &st))
return -1;
}
- changed = ce_match_stat(active_cache[pos], &st);
+ changed = ce_match_stat(active_cache[pos], &st, 1);
if (changed)
return error("%s: does not match index",
old_name);
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);
}
unlink(patch->old_name);
}
if (!write_index)
return;
- ce = xmalloc(ce_size);
- memset(ce, 0, ce_size);
+ ce = xcalloc(1, ce_size);
memcpy(ce->name, path, namelen);
ce->ce_mode = create_ce_mode(mode);
ce->ce_flags = htons(namelen);
if (lstat(path, &st) < 0)
die("unable to stat newly created file %s", path);
fill_stat_cache_info(ce, &st);
- if (write_sha1_file(buf, size, "blob", ce->sha1) < 0)
+ if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
die("unable to create backing store for newly created file %s", path);
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
die("unable to add cache entry for %s", path);
}
-static void create_subdirectories(const char *path)
-{
- int len = strlen(path);
- char *buf = xmalloc(len + 1);
- const char *slash = path;
-
- while ((slash = strchr(slash+1, '/')) != NULL) {
- len = slash - path;
- memcpy(buf, path, len);
- buf[len] = 0;
- if (mkdir(buf, 0777) < 0) {
- if (errno != EEXIST)
- break;
- }
- }
- free(buf);
-}
-
static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
{
int fd;
* which is true 99% of the time anyway. If they don't,
* we create them and try again.
*/
-static void create_one_file(const char *path, unsigned mode, const char *buf, unsigned long size)
+static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size)
{
if (!try_create_file(path, mode, buf, size))
return;
if (errno == ENOENT) {
- create_subdirectories(path);
+ if (safe_create_leading_directories(path))
+ return;
if (!try_create_file(path, mode, buf, size))
return;
}
static void create_file(struct patch *patch)
{
- const char *path = patch->new_name;
+ char *path = patch->new_name;
unsigned mode = patch->new_mode;
unsigned long size = patch->resultsize;
char *buf = patch->result;
if (!mode)
mode = S_IFREG | 0644;
- create_one_file(path, mode, buf, size);
+ create_one_file(path, mode, buf, size);
add_index_file(path, mode, buf, size);
+ cache_tree_invalidate_path(active_cache_tree, path);
}
static void write_out_one_result(struct patch *patch)
return 1;
}
-static int apply_patch(int fd)
+static int apply_patch(int fd, const char *filename)
{
int newfd;
unsigned long offset, size;
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
+ patch_input_file = filename;
if (!buffer)
return -1;
offset = 0;
struct patch *patch;
int nr;
- patch = xmalloc(sizeof(*patch));
- memset(patch, 0, sizeof(*patch));
+ patch = xcalloc(1, sizeof(*patch));
nr = parse_chunk(buffer + offset, size, patch);
if (nr < 0)
break;
}
newfd = -1;
+ if (whitespace_error && (new_whitespace == error_on_whitespace))
+ apply = 0;
+
write_index = check_index && apply;
if (write_index)
newfd = hold_index_file_for_update(&cache_file, get_index_file());
if (check_index) {
- if (read_cache() < 0)
+ if (read_cache_1(active_cache_sha1) < 0)
die("unable to read index file");
+ active_cache_tree = read_cache_tree(active_cache_sha1);
}
if ((check || apply) && check_patch_list(list) < 0)
write_out_results(list, skipped_patch);
if (write_index) {
- if (write_cache(newfd, active_cache, active_nr) ||
+ if (write_cache_1(newfd, active_cache, active_nr,
+ active_cache_sha1) ||
commit_index_file(&cache_file))
die("Unable to write new cachefile");
+ write_cache_tree(active_cache_sha1, active_cache_tree);
}
if (show_index_info)
return 0;
}
+static int git_apply_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "apply.whitespace")) {
+ apply_default_whitespace = strdup(value);
+ return 0;
+ }
+ return git_default_config(var, value);
+}
+
+
int main(int argc, char **argv)
{
int i;
int read_stdin = 1;
+ const char *whitespace_option = NULL;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+ char *end;
int fd;
if (!strcmp(arg, "-")) {
- apply_patch(0);
+ apply_patch(0, "<stdin>");
read_stdin = 0;
continue;
}
excludes = x;
continue;
}
+ if (!strncmp(arg, "-p", 2)) {
+ p_value = atoi(arg + 2);
+ continue;
+ }
if (!strcmp(arg, "--no-add")) {
no_add = 1;
continue;
line_termination = 0;
continue;
}
+ if (!strncmp(arg, "-C", 2)) {
+ p_context = strtoul(arg + 2, &end, 0);
+ if (*end != '\0')
+ die("unrecognized context count '%s'", arg + 2);
+ continue;
+ }
+ if (!strncmp(arg, "--whitespace=", 13)) {
+ whitespace_option = arg + 13;
+ parse_whitespace_option(arg + 13);
+ continue;
+ }
if (check_index && prefix_length < 0) {
prefix = setup_git_directory();
prefix_length = prefix ? strlen(prefix) : 0;
- git_config(git_default_config);
+ 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);
if (fd < 0)
usage(apply_usage);
read_stdin = 0;
- apply_patch(fd);
+ set_default_whitespace_mode(whitespace_option);
+ apply_patch(fd, arg);
close(fd);
}
+ set_default_whitespace_mode(whitespace_option);
if (read_stdin)
- apply_patch(0);
+ apply_patch(0, "<stdin>");
+ 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",
+ squelched,
+ squelched == 1 ? "" : "s");
+ }
+ if (new_whitespace == error_on_whitespace)
+ die("%d line%s add%s trailing whitespaces.",
+ whitespace_error,
+ whitespace_error == 1 ? "" : "s",
+ whitespace_error == 1 ? "s" : "");
+ if (applied_after_stripping)
+ fprintf(stderr, "warning: %d line%s applied after"
+ " stripping trailing whitespaces.\n",
+ applied_after_stripping,
+ applied_after_stripping == 1 ? "" : "s");
+ else if (whitespace_error)
+ fprintf(stderr, "warning: %d line%s add%s trailing"
+ " whitespaces.\n",
+ whitespace_error,
+ whitespace_error == 1 ? "" : "s",
+ whitespace_error == 1 ? "s" : "");
+ }
return 0;
}