* uses the working tree as a "branch" for a 3-way merge.
*/
#include <ctype.h>
-
+#include <fnmatch.h>
#include "cache.h"
// We default to the merge behaviour, since that's what most people would
//
static int merge_patch = 1;
static int check_index = 0;
+static int write_index = 0;
static int diffstat = 0;
+static int summary = 0;
static int check = 0;
static int apply = 1;
static int show_files = 0;
-static const char apply_usage[] = "git-apply [--stat] [--check] [--show-files] <patch>";
+static const char apply_usage[] =
+"git-apply [--no-merge] [--stat] [--summary] [--check] [--index] [--apply] [--show-files] <patch>...";
/*
* For "diff-stat" like behaviour, we keep track of the biggest change
unsigned int old_mode, new_mode;
int is_rename, is_copy, is_new, is_delete;
int lines_added, lines_deleted;
+ int score;
struct fragment *fragments;
- const char *result;
+ char *result;
unsigned long resultsize;
struct patch *next;
};
static int gitdiff_similarity(const char *line, struct patch *patch)
{
+ if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+ patch->score = 0;
return 0;
}
static int gitdiff_dissimilarity(const char *line, struct patch *patch)
{
+ if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+ patch->score = 0;
return 0;
}
default:
continue;
case '\n':
- break;
+ return NULL;
case '\t': case ' ':
second = name+len;
for (;;) {
{ "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 },
struct fragment dummy;
if (parse_fragment_header(line, len, &dummy) < 0)
continue;
- error("patch fragment without header at line %d: %.*s", linenr, len-1, line);
+ error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line);
}
if (size < len + 6)
*/
if (!memcmp("diff --git ", line, 11)) {
int git_hdr_len = parse_git_header(line, len, size, patch);
- if (git_hdr_len < 0)
+ if (git_hdr_len <= len)
continue;
- if (!patch->old_name && !patch->new_name)
- die("git diff header lacks filename information");
+ if (!patch->old_name && !patch->new_name) {
+ if (!patch->def_name)
+ die("git diff header lacks filename information (line %d)", linenr);
+ patch->old_name = patch->new_name = patch->def_name;
+ }
*hdrsize = git_hdr_len;
return offset;
}
if (patch->is_new != !oldlines)
return error("new file depends on old contents");
- if (patch->is_delete != !newlines)
- return error("deleted file still has 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;
added++;
newlines--;
break;
- /* We allow "\ No newline at end of file" */
+
+ /* We allow "\ No newline at end of file". Depending
+ * on locale settings when the patch was produced we
+ * don't know what this line looks like. The only
+ * thing we do know is that it begins with "\ ". */
case '\\':
- if (len < 12 || memcmp(line, "\\ No newline", 12))
+ if (len < 12 || memcmp(line, "\\ ", 2))
return -1;
break;
}
}
+ /* 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.
+ */
+ if (12 < size && !memcmp(line, "\\ ", 2))
+ offset += linelen(line, size);
+
patch->lines_added += added;
patch->lines_deleted += deleted;
return offset;
return offset + hdrsize + patchsize;
}
-const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-const char minuses[]= "----------------------------------------------------------------------";
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
static void show_stats(struct patch *patch)
{
- char *name = patch->old_name;
+ const char *prefix = "";
+ char *name = patch->new_name;
int len, max, add, del, total;
if (!name)
- name = patch->new_name;
+ name = patch->old_name;
/*
* "scale" the filename
max = max_len;
if (max > 50)
max = 50;
- if (len > max)
+ if (len > max) {
+ char *slash;
+ prefix = "...";
+ max -= 3;
name += len - max;
+ slash = strchr(name, '/');
+ if (slash)
+ name = slash;
+ }
len = max;
/*
del = patch->lines_deleted;
total = add + del;
- total = (total * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
- del = total - add;
- printf(" %-*s |%5d %.*s%.*s\n",
+ if (max_change > 0) {
+ total = (total * max + max_change / 2) / max_change;
+ add = (add * max + max_change / 2) / max_change;
+ del = total - add;
+ }
+ printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
len, name, patch->lines_added + patch->lines_deleted,
add, pluses, del, minuses);
}
n = (i >> 1)+1;
if (i & 1)
n = -n;
- fprintf(stderr, "Fragment applied at offset %d\n", n);
return try;
}
* last one (which is the newline, of course).
*/
plen = len-1;
- if (len > size && patch[len] == '\\')
+ if (len < size && patch[len] == '\\')
plen--;
switch (*patch) {
case ' ':
while (frag) {
if (apply_one_fragment(desc, frag) < 0)
- return error("patch failed: %s:%d", patch->old_name, frag->oldpos);
+ return error("patch failed: %s:%ld", patch->old_name, frag->oldpos);
frag = frag->next;
}
return 0;
return -1;
patch->result = desc.buffer;
patch->resultsize = desc.size;
+
+ if (patch->is_delete && patch->resultsize)
+ return error("removal patch leaves file contents");
+
return 0;
}
int changed;
if (lstat(old_name, &st) < 0)
- return error("%s: %s\n", strerror(errno));
+ return error("%s: %s", old_name, strerror(errno));
if (check_index) {
int pos = cache_name_pos(old_name, strlen(old_name));
if (pos < 0)
}
if (patch->is_new < 0)
patch->is_new = 0;
+ st.st_mode = ntohl(create_ce_mode(st.st_mode));
if (!patch->old_mode)
patch->old_mode = st.st_mode;
if ((st.st_mode ^ patch->old_mode) & S_IFMT)
return error("%s: already exists in working directory", new_name);
if (errno != ENOENT)
return error("%s: %s", new_name, strerror(errno));
+ if (!patch->new_mode) {
+ if (patch->is_new)
+ patch->new_mode = S_IFREG | 0644;
+ else
+ patch->new_mode = patch->old_mode;
+ }
}
if (new_name && old_name) {
printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
}
+static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
+{
+ if (mode)
+ printf(" %s mode %06o %s\n", newdelete, mode, name);
+ else
+ printf(" %s %s\n", newdelete, name);
+}
+
+static void show_mode_change(struct patch *p, int show_name)
+{
+ if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
+ if (show_name)
+ printf(" mode change %06o => %06o %s\n",
+ p->old_mode, p->new_mode, p->new_name);
+ else
+ printf(" mode change %06o => %06o\n",
+ p->old_mode, p->new_mode);
+ }
+}
+
+static void show_rename_copy(struct patch *p)
+{
+ const char *renamecopy = p->is_rename ? "rename" : "copy";
+ const char *old, *new;
+
+ /* Find common prefix */
+ old = p->old_name;
+ new = p->new_name;
+ while (1) {
+ const char *slash_old, *slash_new;
+ slash_old = strchr(old, '/');
+ slash_new = strchr(new, '/');
+ if (!slash_old ||
+ !slash_new ||
+ slash_old - old != slash_new - new ||
+ memcmp(old, new, slash_new - new))
+ break;
+ old = slash_old + 1;
+ new = slash_new + 1;
+ }
+ /* p->old_name thru old is the common prefix, and old and new
+ * through the end of names are renames
+ */
+ if (old != p->old_name)
+ printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+ (int)(old - p->old_name), p->old_name,
+ old, new, p->score);
+ else
+ printf(" %s %s => %s (%d%%)\n", renamecopy,
+ p->old_name, p->new_name, p->score);
+ show_mode_change(p, 0);
+}
+
+static void summary_patch_list(struct patch *patch)
+{
+ struct patch *p;
+
+ for (p = patch; p; p = p->next) {
+ if (p->is_new)
+ show_file_mode_name("create", p->new_mode, p->new_name);
+ else if (p->is_delete)
+ show_file_mode_name("delete", p->old_mode, p->old_name);
+ else {
+ if (p->is_rename || p->is_copy)
+ show_rename_copy(p);
+ else {
+ if (p->score) {
+ printf(" rewrite %s (%d%%)\n",
+ p->new_name, p->score);
+ show_mode_change(p, 0);
+ }
+ else
+ show_mode_change(p, 1);
+ }
+ }
+ }
+}
+
static void patch_stats(struct patch *patch)
{
int lines = patch->lines_added + patch->lines_deleted;
}
}
+static void remove_file(struct patch *patch)
+{
+ if (write_index) {
+ if (remove_file_from_cache(patch->old_name) < 0)
+ die("unable to remove %s from index", patch->old_name);
+ }
+ unlink(patch->old_name);
+}
+
+static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
+{
+ struct stat st;
+ struct cache_entry *ce;
+ int namelen = strlen(path);
+ unsigned ce_size = cache_entry_size(namelen);
+
+ if (!write_index)
+ return;
+
+ ce = xmalloc(ce_size);
+ memset(ce, 0, 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)
+ 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;
+
+ if (S_ISLNK(mode))
+ return symlink(buf, path);
+ fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_TRUNC, (mode & 0100) ? 0777 : 0666);
+ if (fd < 0)
+ return -1;
+ while (size) {
+ int written = write(fd, buf, size);
+ if (written < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ die("writing file %s: %s", path, strerror(errno));
+ }
+ if (!written)
+ die("out of space writing file %s", path);
+ buf += written;
+ size -= written;
+ }
+ if (close(fd) < 0)
+ die("closing file %s: %s", path, strerror(errno));
+ return 0;
+}
+
+/*
+ * We optimistically assume that the directories exist,
+ * 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)
+{
+ if (!try_create_file(path, mode, buf, size))
+ return;
+
+ if (errno == ENOENT) {
+ create_subdirectories(path);
+ if (!try_create_file(path, mode, buf, size))
+ return;
+ }
+
+ if (errno == EEXIST) {
+ unsigned int nr = getpid();
+
+ for (;;) {
+ const char *newpath;
+ newpath = mkpath("%s~%u", path, nr);
+ if (!try_create_file(newpath, mode, buf, size)) {
+ if (!rename(newpath, path))
+ return;
+ unlink(newpath);
+ break;
+ }
+ if (errno != EEXIST)
+ break;
+ }
+ }
+ die("unable to write file %s mode %o", path, mode);
+}
+
+static void create_file(struct patch *patch)
+{
+ const 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);
+ add_index_file(path, mode, buf, size);
+}
+
+static void write_out_one_result(struct patch *patch)
+{
+ if (patch->is_delete > 0) {
+ remove_file(patch);
+ return;
+ }
+ if (patch->is_new > 0 || patch->is_copy) {
+ create_file(patch);
+ return;
+ }
+ /*
+ * Rename or modification boils down to the same
+ * thing: remove the old, write the new
+ */
+ remove_file(patch);
+ create_file(patch);
+}
+
+static void write_out_results(struct patch *list, int skipped_patch)
+{
+ if (!list && !skipped_patch)
+ die("No changes");
+
+ while (list) {
+ write_out_one_result(list);
+ list = list->next;
+ }
+}
+
+static struct cache_file cache_file;
+
+static struct excludes {
+ struct excludes *next;
+ const char *path;
+} *excludes;
+
+static int use_patch(struct patch *p)
+{
+ const char *pathname = p->new_name ? p->new_name : p->old_name;
+ struct excludes *x = excludes;
+ while (x) {
+ if (fnmatch(x->path, pathname, 0) == 0)
+ return 0;
+ x = x->next;
+ }
+ return 1;
+}
+
static int apply_patch(int fd)
{
+ int newfd;
unsigned long offset, size;
char *buffer = read_patch_file(fd, &size);
struct patch *list = NULL, **listp = &list;
+ int skipped_patch = 0;
if (!buffer)
return -1;
nr = parse_chunk(buffer + offset, size, patch);
if (nr < 0)
break;
- patch_stats(patch);
- *listp = patch;
- listp = &patch->next;
+ if (use_patch(patch)) {
+ patch_stats(patch);
+ *listp = patch;
+ listp = &patch->next;
+ } else {
+ /* perhaps free it a bit better? */
+ free(patch);
+ skipped_patch++;
+ }
offset += nr;
size -= nr;
}
+ newfd = -1;
+ 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)
+ die("unable to read index file");
+ }
+
if ((check || apply) && check_patch_list(list) < 0)
exit(1);
+ if (apply)
+ write_out_results(list, skipped_patch);
+
+ if (write_index) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_index_file(&cache_file))
+ die("Unable to write new cachefile");
+ }
+
if (show_files)
show_file_list(list);
if (diffstat)
stat_patch_list(list);
+ if (summary)
+ summary_patch_list(list);
+
free(buffer);
return 0;
}
int i;
int read_stdin = 1;
- if (read_cache() < 0)
- die("unable to read index file");
-
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
int fd;
read_stdin = 0;
continue;
}
+ if (!strncmp(arg, "--exclude=", 10)) {
+ struct excludes *x = xmalloc(sizeof(*x));
+ x->path = arg + 10;
+ x->next = excludes;
+ excludes = x;
+ continue;
+ }
+ /* NEEDSWORK: this does not do anything at this moment. */
if (!strcmp(arg, "--no-merge")) {
merge_patch = 0;
continue;
diffstat = 1;
continue;
}
+ if (!strcmp(arg, "--summary")) {
+ apply = 0;
+ summary = 1;
+ continue;
+ }
if (!strcmp(arg, "--check")) {
apply = 0;
check = 1;
check_index = 1;
continue;
}
+ if (!strcmp(arg, "--apply")) {
+ apply = 1;
+ continue;
+ }
if (!strcmp(arg, "--show-files")) {
show_files = 1;
continue;