static void read_patch_file(struct strbuf *sb, int fd)
{
if (strbuf_read(sb, fd, 0) < 0)
- die("git apply: read returned %s", strerror(errno));
+ die_errno("git apply: failed to read");
/*
* Make sure that we have some slop in the buffer
return 1;
}
+/* remove double slashes to make --index work with such filenames */
+static char *squash_slash(char *name)
+{
+ int i = 0, j = 0;
+
+ while (name[i]) {
+ if ((name[j++] = name[i++]) == '/')
+ while (name[i] == '/')
+ i++;
+ }
+ name[j] = '\0';
+ return name;
+}
+
static char *find_name(const char *line, char *def, int p_value, int terminate)
{
int len;
free(def);
if (root)
strbuf_insert(&name, 0, root, root_len);
- return strbuf_detach(&name, NULL);
+ return squash_slash(strbuf_detach(&name, NULL));
}
}
strbuf_release(&name);
start = line;
}
if (!start)
- return def;
+ return squash_slash(def);
len = line - start;
if (!len)
- return def;
+ return squash_slash(def);
/*
* Generally we prefer the shorter name, especially
if (def) {
int deflen = strlen(def);
if (deflen < len && !strncmp(start, def, deflen))
- return def;
+ return squash_slash(def);
free(def);
}
strcpy(ret, root);
memcpy(ret + root_len, start, len);
ret[root_len + len] = '\0';
- return ret;
+ return squash_slash(ret);
}
- return xmemdupz(start, len);
+ return squash_slash(xmemdupz(start, len));
}
static int count_slashes(const char *cp)
return val;
}
+/*
+ * Does the ---/+++ line has 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;
+ 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, NULL, 10);
+ 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
*
} else {
name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
- patch->old_name = patch->new_name = 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 = patch->new_name = name;
+ }
}
if (!name)
die("unable to find filename in patch at line %d", linenr);
return NULL;
}
+/*
+ * item->util in the filename table records the status of the path.
+ * Usually it points at a patch (whose result records the contents
+ * of it after applying it), but it could be PATH_WAS_DELETED for a
+ * path that a previously applied patch has already removed.
+ */
+ #define PATH_TO_BE_DELETED ((struct patch *) -2)
+#define PATH_WAS_DELETED ((struct patch *) -1)
+
+static int to_be_deleted(struct patch *patch)
+{
+ return patch == PATH_TO_BE_DELETED;
+}
+
+static int was_deleted(struct patch *patch)
+{
+ return patch == PATH_WAS_DELETED;
+}
+
static void add_to_fn_table(struct patch *patch)
{
struct string_list_item *item;
*/
if ((patch->new_name == NULL) || (patch->is_rename)) {
item = string_list_insert(patch->old_name, &fn_table);
- item->util = (struct patch *) -1;
+ item->util = PATH_WAS_DELETED;
+ }
+}
+
+static void prepare_fn_table(struct patch *patch)
+{
+ /*
+ * store information about incoming file deletion
+ */
+ while (patch) {
+ if ((patch->new_name == NULL) || (patch->is_rename)) {
+ struct string_list_item *item;
+ item = string_list_insert(patch->old_name, &fn_table);
+ item->util = PATH_TO_BE_DELETED;
+ }
+ patch = patch->next;
}
}
struct patch *tpatch;
if (!(patch->is_copy || patch->is_rename) &&
- ((tpatch = in_fn_table(patch->old_name)) != NULL)) {
- if (tpatch == (struct patch *) -1) {
+ (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
+ if (was_deleted(tpatch)) {
return error("patch %s has been renamed/deleted",
patch->old_name);
}
assert(patch->is_new <= 0);
if (!(patch->is_copy || patch->is_rename) &&
- (tpatch = in_fn_table(old_name)) != NULL) {
- if (tpatch == (struct patch *) -1) {
+ (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
+ if (was_deleted(tpatch))
return error("%s: has been deleted/renamed", old_name);
- }
st_mode = tpatch->new_mode;
} else if (!cached) {
stat_ret = lstat(old_name, st);
return error("%s: %s", old_name, strerror(errno));
}
+ if (to_be_deleted(tpatch))
+ tpatch = NULL;
+
if (check_index && !tpatch) {
int pos = cache_name_pos(old_name, strlen(old_name));
if (pos < 0) {
const char *new_name = patch->new_name;
const char *name = old_name ? old_name : new_name;
struct cache_entry *ce = NULL;
+ struct patch *tpatch;
int ok_if_exists;
int status;
return status;
old_name = patch->old_name;
- if (in_fn_table(new_name) == (struct patch *) -1)
+ if ((tpatch = in_fn_table(new_name)) &&
+ (was_deleted(tpatch) || to_be_deleted(tpatch)))
/*
* A type-change diff is always split into a patch to
* delete old, immediately followed by a patch to
{
int err = 0;
+ prepare_fn_table(patch);
while (patch) {
if (apply_verbosely)
say_patch_name(stderr,
static void build_fake_ancestor(struct patch *list, const char *filename)
{
struct patch *patch;
- struct index_state result = { 0 };
+ struct index_state result = { NULL };
int fd;
/* Once we start supporting the reverse patch, it may be
if (rmdir(patch->old_name))
warning("unable to remove submodule %s",
patch->old_name);
- } else if (!unlink(patch->old_name) && rmdir_empty) {
+ } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) {
remove_path(patch->old_name);
}
}
} else {
if (!cached) {
if (lstat(path, &st) < 0)
- die("unable to stat newly created file %s",
- path);
+ die_errno("unable to stat newly created file '%s'",
+ path);
fill_stat_cache_info(ce, &st);
}
if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
strbuf_release(&nbuf);
if (close(fd) < 0)
- die("closing file %s: %s", path, strerror(errno));
+ die_errno("closing file '%s'", path);
return 0;
}
if (!try_create_file(newpath, mode, buf, size)) {
if (!rename(newpath, path))
return;
- unlink(newpath);
+ unlink_or_warn(newpath);
break;
}
if (errno != EEXIST)
++nr;
}
}
- die("unable to write file %s mode %o", path, mode);
+ die_errno("unable to write file '%s' mode %o", path, mode);
}
static void create_file(struct patch *patch)
OPT_BOOLEAN(0, "stat", &diffstat,
"instead of applying the patch, output diffstat for the input"),
{ OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
- NULL, "old option, now no-op", PARSE_OPT_HIDDEN },
+ NULL, "old option, now no-op",
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
{ OPTION_BOOLEAN, 0, "binary", &binary,
- NULL, "old option, now no-op", PARSE_OPT_HIDDEN },
+ NULL, "old option, now no-op",
+ PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
OPT_BOOLEAN(0, "numstat", &numstat,
"shows number of added and deleted lines in decimal notation"),
OPT_BOOLEAN(0, "summary", &summary,
"apply a patch without touching the working tree"),
OPT_BOOLEAN(0, "apply", &force_apply,
"also apply the patch (use with --stat/--summary/--check)"),
- OPT_STRING(0, "build-fake-ancestor", &fake_ancestor, "file",
+ OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
"build a temporary index based on embedded index information"),
{ OPTION_CALLBACK, 'z', NULL, NULL, NULL,
"paths are separated with NUL character",
if (apply_default_whitespace)
parse_whitespace_option(apply_default_whitespace);
- argc = parse_options(argc, argv, builtin_apply_options,
+ argc = parse_options(argc, argv, prefix, builtin_apply_options,
apply_usage, 0);
+
if (apply_with_reject)
apply = apply_verbosely = 1;
if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
fd = open(arg, O_RDONLY);
if (fd < 0)
- die("can't open patch '%s': %s", arg, strerror(errno));
+ die_errno("can't open patch '%s'", arg);
read_stdin = 0;
set_default_whitespace_mode(whitespace_option);
errs |= apply_patch(fd, arg, options);