2016-04-22 20:55:46 +02:00
|
|
|
/*
|
|
|
|
* apply.c
|
|
|
|
*
|
|
|
|
* Copyright (C) Linus Torvalds, 2005
|
|
|
|
*
|
|
|
|
* This applies patches on top of some (arbitrary) version of the SCM.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2016-08-08 23:03:07 +02:00
|
|
|
#include "cache.h"
|
2017-06-14 20:07:36 +02:00
|
|
|
#include "config.h"
|
2016-04-22 20:55:46 +02:00
|
|
|
#include "blob.h"
|
|
|
|
#include "delta.h"
|
|
|
|
#include "diff.h"
|
|
|
|
#include "dir.h"
|
|
|
|
#include "xdiff-interface.h"
|
|
|
|
#include "ll-merge.h"
|
2016-08-08 23:03:07 +02:00
|
|
|
#include "lockfile.h"
|
2016-04-22 20:55:46 +02:00
|
|
|
#include "parse-options.h"
|
|
|
|
#include "quote.h"
|
|
|
|
#include "rerere.h"
|
2016-08-08 23:03:07 +02:00
|
|
|
#include "apply.h"
|
|
|
|
|
|
|
|
static void git_apply_config(void)
|
|
|
|
{
|
|
|
|
git_config_get_string_const("apply.whitespace", &apply_default_whitespace);
|
|
|
|
git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace);
|
|
|
|
git_config(git_default_config, NULL);
|
|
|
|
}
|
|
|
|
|
2016-09-04 22:18:23 +02:00
|
|
|
static int parse_whitespace_option(struct apply_state *state, const char *option)
|
2016-08-08 23:03:07 +02:00
|
|
|
{
|
|
|
|
if (!option) {
|
|
|
|
state->ws_error_action = warn_on_ws_error;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strcmp(option, "warn")) {
|
|
|
|
state->ws_error_action = warn_on_ws_error;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strcmp(option, "nowarn")) {
|
|
|
|
state->ws_error_action = nowarn_ws_error;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strcmp(option, "error")) {
|
|
|
|
state->ws_error_action = die_on_ws_error;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strcmp(option, "error-all")) {
|
|
|
|
state->ws_error_action = die_on_ws_error;
|
|
|
|
state->squelch_whitespace_errors = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
|
|
|
|
state->ws_error_action = correct_ws_error;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return error(_("unrecognized whitespace option '%s'"), option);
|
|
|
|
}
|
|
|
|
|
2016-09-04 22:18:23 +02:00
|
|
|
static int parse_ignorewhitespace_option(struct apply_state *state,
|
|
|
|
const char *option)
|
2016-08-08 23:03:07 +02:00
|
|
|
{
|
|
|
|
if (!option || !strcmp(option, "no") ||
|
|
|
|
!strcmp(option, "false") || !strcmp(option, "never") ||
|
|
|
|
!strcmp(option, "none")) {
|
|
|
|
state->ws_ignore_action = ignore_ws_none;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strcmp(option, "change")) {
|
|
|
|
state->ws_ignore_action = ignore_ws_change;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return error(_("unrecognized whitespace ignore option '%s'"), option);
|
|
|
|
}
|
|
|
|
|
2016-08-08 23:03:08 +02:00
|
|
|
int init_apply_state(struct apply_state *state,
|
|
|
|
const char *prefix,
|
|
|
|
struct lock_file *lock_file)
|
2016-08-08 23:03:07 +02:00
|
|
|
{
|
|
|
|
memset(state, 0, sizeof(*state));
|
|
|
|
state->prefix = prefix;
|
|
|
|
state->prefix_length = state->prefix ? strlen(state->prefix) : 0;
|
|
|
|
state->lock_file = lock_file;
|
|
|
|
state->newfd = -1;
|
|
|
|
state->apply = 1;
|
|
|
|
state->line_termination = '\n';
|
|
|
|
state->p_value = 1;
|
|
|
|
state->p_context = UINT_MAX;
|
|
|
|
state->squelch_whitespace_errors = 5;
|
|
|
|
state->ws_error_action = warn_on_ws_error;
|
|
|
|
state->ws_ignore_action = ignore_ws_none;
|
|
|
|
state->linenr = 1;
|
|
|
|
string_list_init(&state->fn_table, 0);
|
|
|
|
string_list_init(&state->limit_by_name, 0);
|
|
|
|
string_list_init(&state->symlink_changes, 0);
|
|
|
|
strbuf_init(&state->root, 0);
|
|
|
|
|
|
|
|
git_apply_config();
|
|
|
|
if (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))
|
2016-08-08 23:03:08 +02:00
|
|
|
return -1;
|
2016-08-08 23:03:07 +02:00
|
|
|
if (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))
|
2016-08-08 23:03:08 +02:00
|
|
|
return -1;
|
|
|
|
return 0;
|
2016-08-08 23:03:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void clear_apply_state(struct apply_state *state)
|
|
|
|
{
|
|
|
|
string_list_clear(&state->limit_by_name, 0);
|
|
|
|
string_list_clear(&state->symlink_changes, 0);
|
|
|
|
strbuf_release(&state->root);
|
|
|
|
|
|
|
|
/* &state->fn_table is cleared at the end of apply_patch() */
|
|
|
|
}
|
2016-08-08 23:03:10 +02:00
|
|
|
|
2016-09-04 22:18:29 +02:00
|
|
|
static void mute_routine(const char *msg, va_list params)
|
|
|
|
{
|
|
|
|
/* do nothing */
|
|
|
|
}
|
|
|
|
|
2016-08-08 23:03:10 +02:00
|
|
|
int check_apply_state(struct apply_state *state, int force_apply)
|
|
|
|
{
|
|
|
|
int is_not_gitdir = !startup_info->have_repository;
|
|
|
|
|
|
|
|
if (state->apply_with_reject && state->threeway)
|
2016-10-14 13:43:37 +02:00
|
|
|
return error(_("--reject and --3way cannot be used together."));
|
2016-08-08 23:03:10 +02:00
|
|
|
if (state->cached && state->threeway)
|
2016-10-14 13:43:37 +02:00
|
|
|
return error(_("--cached and --3way cannot be used together."));
|
2016-08-08 23:03:10 +02:00
|
|
|
if (state->threeway) {
|
|
|
|
if (is_not_gitdir)
|
|
|
|
return error(_("--3way outside a repository"));
|
|
|
|
state->check_index = 1;
|
|
|
|
}
|
2016-09-04 22:18:25 +02:00
|
|
|
if (state->apply_with_reject) {
|
|
|
|
state->apply = 1;
|
|
|
|
if (state->apply_verbosity == verbosity_normal)
|
|
|
|
state->apply_verbosity = verbosity_verbose;
|
|
|
|
}
|
2016-08-08 23:03:10 +02:00
|
|
|
if (!force_apply && (state->diffstat || state->numstat || state->summary || state->check || state->fake_ancestor))
|
|
|
|
state->apply = 0;
|
|
|
|
if (state->check_index && is_not_gitdir)
|
|
|
|
return error(_("--index outside a repository"));
|
|
|
|
if (state->cached) {
|
|
|
|
if (is_not_gitdir)
|
|
|
|
return error(_("--cached outside a repository"));
|
|
|
|
state->check_index = 1;
|
|
|
|
}
|
|
|
|
if (state->check_index)
|
|
|
|
state->unsafe_paths = 0;
|
|
|
|
if (!state->lock_file)
|
|
|
|
return error("BUG: state->lock_file should not be NULL");
|
|
|
|
|
2016-09-04 22:18:29 +02:00
|
|
|
if (state->apply_verbosity <= verbosity_silent) {
|
|
|
|
state->saved_error_routine = get_error_routine();
|
|
|
|
state->saved_warn_routine = get_warn_routine();
|
|
|
|
set_error_routine(mute_routine);
|
|
|
|
set_warn_routine(mute_routine);
|
|
|
|
}
|
|
|
|
|
2016-08-08 23:03:10 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2016-04-22 20:55:46 +02:00
|
|
|
|
|
|
|
static void set_default_whitespace_mode(struct apply_state *state)
|
|
|
|
{
|
|
|
|
if (!state->whitespace_option && !apply_default_whitespace)
|
|
|
|
state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This represents one "hunk" from a patch, starting with
|
|
|
|
* "@@ -oldpos,oldlines +newpos,newlines @@" marker. The
|
|
|
|
* patch text is pointed at by patch, and its byte length
|
|
|
|
* is stored in size. leading and trailing are the number
|
|
|
|
* of context lines.
|
|
|
|
*/
|
|
|
|
struct fragment {
|
|
|
|
unsigned long leading, trailing;
|
|
|
|
unsigned long oldpos, oldlines;
|
|
|
|
unsigned long newpos, newlines;
|
|
|
|
/*
|
|
|
|
* 'patch' is usually borrowed from buf in apply_patch(),
|
|
|
|
* but some codepaths store an allocated buffer.
|
|
|
|
*/
|
|
|
|
const char *patch;
|
|
|
|
unsigned free_patch:1,
|
|
|
|
rejected:1;
|
|
|
|
int size;
|
|
|
|
int linenr;
|
|
|
|
struct fragment *next;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When dealing with a binary patch, we reuse "leading" field
|
|
|
|
* to store the type of the binary hunk, either deflated "delta"
|
|
|
|
* or deflated "literal".
|
|
|
|
*/
|
|
|
|
#define binary_patch_method leading
|
|
|
|
#define BINARY_DELTA_DEFLATED 1
|
|
|
|
#define BINARY_LITERAL_DEFLATED 2
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This represents a "patch" to a file, both metainfo changes
|
|
|
|
* such as creation/deletion, filemode and content changes represented
|
|
|
|
* as a series of fragments.
|
|
|
|
*/
|
|
|
|
struct patch {
|
|
|
|
char *new_name, *old_name, *def_name;
|
|
|
|
unsigned int old_mode, new_mode;
|
|
|
|
int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
|
|
|
|
int rejected;
|
|
|
|
unsigned ws_rule;
|
|
|
|
int lines_added, lines_deleted;
|
|
|
|
int score;
|
2017-06-27 19:03:39 +02:00
|
|
|
int extension_linenr; /* first line specifying delete/new/rename/copy */
|
2016-04-22 20:55:46 +02:00
|
|
|
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;
|
|
|
|
unsigned int recount:1;
|
|
|
|
unsigned int conflicted_threeway:1;
|
|
|
|
unsigned int direct_to_threeway:1;
|
|
|
|
struct fragment *fragments;
|
|
|
|
char *result;
|
|
|
|
size_t resultsize;
|
|
|
|
char old_sha1_prefix[41];
|
|
|
|
char new_sha1_prefix[41];
|
|
|
|
struct patch *next;
|
|
|
|
|
|
|
|
/* three-way fallback result */
|
|
|
|
struct object_id threeway_stage[3];
|
|
|
|
};
|
|
|
|
|
|
|
|
static void free_fragment_list(struct fragment *list)
|
|
|
|
{
|
|
|
|
while (list) {
|
|
|
|
struct fragment *next = list->next;
|
|
|
|
if (list->free_patch)
|
|
|
|
free((char *)list->patch);
|
|
|
|
free(list);
|
|
|
|
list = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void free_patch(struct patch *patch)
|
|
|
|
{
|
|
|
|
free_fragment_list(patch->fragments);
|
|
|
|
free(patch->def_name);
|
|
|
|
free(patch->old_name);
|
|
|
|
free(patch->new_name);
|
|
|
|
free(patch->result);
|
|
|
|
free(patch);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void free_patch_list(struct patch *list)
|
|
|
|
{
|
|
|
|
while (list) {
|
|
|
|
struct patch *next = list->next;
|
|
|
|
free_patch(list);
|
|
|
|
list = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A line in a file, len-bytes long (includes the terminating LF,
|
|
|
|
* except for an incomplete line at the end if the file ends with
|
|
|
|
* one), and its contents hashes to 'hash'.
|
|
|
|
*/
|
|
|
|
struct line {
|
|
|
|
size_t len;
|
|
|
|
unsigned hash : 24;
|
|
|
|
unsigned flag : 8;
|
|
|
|
#define LINE_COMMON 1
|
|
|
|
#define LINE_PATCHED 2
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This represents a "file", which is an array of "lines".
|
|
|
|
*/
|
|
|
|
struct image {
|
|
|
|
char *buf;
|
|
|
|
size_t len;
|
|
|
|
size_t nr;
|
|
|
|
size_t alloc;
|
|
|
|
struct line *line_allocated;
|
|
|
|
struct line *line;
|
|
|
|
};
|
|
|
|
|
|
|
|
static uint32_t hash_line(const char *cp, size_t len)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
uint32_t h;
|
|
|
|
for (i = 0, h = 0; i < len; i++) {
|
|
|
|
if (!isspace(cp[i])) {
|
|
|
|
h = h * 3 + (cp[i] & 0xff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare lines s1 of length n1 and s2 of length n2, ignoring
|
|
|
|
* whitespace difference. Returns 1 if they match, 0 otherwise
|
|
|
|
*/
|
|
|
|
static int fuzzy_matchlines(const char *s1, size_t n1,
|
|
|
|
const char *s2, size_t n2)
|
|
|
|
{
|
|
|
|
const char *last1 = s1 + n1 - 1;
|
|
|
|
const char *last2 = s2 + n2 - 1;
|
|
|
|
int result = 0;
|
|
|
|
|
|
|
|
/* ignore line endings */
|
|
|
|
while ((*last1 == '\r') || (*last1 == '\n'))
|
|
|
|
last1--;
|
|
|
|
while ((*last2 == '\r') || (*last2 == '\n'))
|
|
|
|
last2--;
|
|
|
|
|
|
|
|
/* skip leading whitespaces, if both begin with whitespace */
|
|
|
|
if (s1 <= last1 && s2 <= last2 && isspace(*s1) && isspace(*s2)) {
|
|
|
|
while (isspace(*s1) && (s1 <= last1))
|
|
|
|
s1++;
|
|
|
|
while (isspace(*s2) && (s2 <= last2))
|
|
|
|
s2++;
|
|
|
|
}
|
|
|
|
/* early return if both lines are empty */
|
|
|
|
if ((s1 > last1) && (s2 > last2))
|
|
|
|
return 1;
|
|
|
|
while (!result) {
|
|
|
|
result = *s1++ - *s2++;
|
|
|
|
/*
|
|
|
|
* Skip whitespace inside. We check for whitespace on
|
|
|
|
* both buffers because we don't want "a b" to match
|
|
|
|
* "ab"
|
|
|
|
*/
|
|
|
|
if (isspace(*s1) && isspace(*s2)) {
|
|
|
|
while (isspace(*s1) && s1 <= last1)
|
|
|
|
s1++;
|
|
|
|
while (isspace(*s2) && s2 <= last2)
|
|
|
|
s2++;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* If we reached the end on one side only,
|
|
|
|
* lines don't match
|
|
|
|
*/
|
|
|
|
if (
|
|
|
|
((s2 > last2) && (s1 <= last1)) ||
|
|
|
|
((s1 > last1) && (s2 <= last2)))
|
|
|
|
return 0;
|
|
|
|
if ((s1 > last1) && (s2 > last2))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
|
|
|
|
{
|
|
|
|
ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
|
|
|
|
img->line_allocated[img->nr].len = len;
|
|
|
|
img->line_allocated[img->nr].hash = hash_line(bol, len);
|
|
|
|
img->line_allocated[img->nr].flag = flag;
|
|
|
|
img->nr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "buf" has the file contents to be patched (read from various sources).
|
|
|
|
* attach it to "image" and add line-based index to it.
|
|
|
|
* "image" now owns the "buf".
|
|
|
|
*/
|
|
|
|
static void prepare_image(struct image *image, char *buf, size_t len,
|
|
|
|
int prepare_linetable)
|
|
|
|
{
|
|
|
|
const char *cp, *ep;
|
|
|
|
|
|
|
|
memset(image, 0, sizeof(*image));
|
|
|
|
image->buf = buf;
|
|
|
|
image->len = len;
|
|
|
|
|
|
|
|
if (!prepare_linetable)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ep = image->buf + image->len;
|
|
|
|
cp = image->buf;
|
|
|
|
while (cp < ep) {
|
|
|
|
const char *next;
|
|
|
|
for (next = cp; next < ep && *next != '\n'; next++)
|
|
|
|
;
|
|
|
|
if (next < ep)
|
|
|
|
next++;
|
|
|
|
add_line_info(image, cp, next - cp, 0);
|
|
|
|
cp = next;
|
|
|
|
}
|
|
|
|
image->line = image->line_allocated;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void clear_image(struct image *image)
|
|
|
|
{
|
|
|
|
free(image->buf);
|
|
|
|
free(image->line_allocated);
|
|
|
|
memset(image, 0, sizeof(*image));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fmt must contain _one_ %s and no other substitution */
|
|
|
|
static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
|
|
|
|
{
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
|
|
|
|
if (patch->old_name && patch->new_name &&
|
|
|
|
strcmp(patch->old_name, patch->new_name)) {
|
|
|
|
quote_c_style(patch->old_name, &sb, NULL, 0);
|
|
|
|
strbuf_addstr(&sb, " => ");
|
|
|
|
quote_c_style(patch->new_name, &sb, NULL, 0);
|
|
|
|
} else {
|
|
|
|
const char *n = patch->new_name;
|
|
|
|
if (!n)
|
|
|
|
n = patch->old_name;
|
|
|
|
quote_c_style(n, &sb, NULL, 0);
|
|
|
|
}
|
|
|
|
fprintf(output, fmt, sb.buf);
|
|
|
|
fputc('\n', output);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SLOP (16)
|
|
|
|
|
|
|
|
static int read_patch_file(struct strbuf *sb, int fd)
|
|
|
|
{
|
|
|
|
if (strbuf_read(sb, fd, 0) < 0)
|
|
|
|
return error_errno("git apply: failed to read");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure that we have some slop in the buffer
|
|
|
|
* so that we can do speculative "memcmp" etc, and
|
|
|
|
* see to it that it is NUL-filled.
|
|
|
|
*/
|
|
|
|
strbuf_grow(sb, SLOP);
|
|
|
|
memset(sb->buf + sb->len, 0, SLOP);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long linelen(const char *buffer, unsigned long size)
|
|
|
|
{
|
|
|
|
unsigned long len = 0;
|
|
|
|
while (size--) {
|
|
|
|
len++;
|
|
|
|
if (*buffer++ == '\n')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int is_dev_null(const char *str)
|
|
|
|
{
|
|
|
|
return skip_prefix(str, "/dev/null", &str) && isspace(*str);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define TERM_SPACE 1
|
|
|
|
#define TERM_TAB 2
|
|
|
|
|
|
|
|
static int name_terminate(int c, int terminate)
|
|
|
|
{
|
|
|
|
if (c == ' ' && !(terminate & TERM_SPACE))
|
|
|
|
return 0;
|
|
|
|
if (c == '\t' && !(terminate & TERM_TAB))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove double slashes to make --index work with such filenames */
|
|
|
|
static char *squash_slash(char *name)
|
|
|
|
{
|
|
|
|
int i = 0, j = 0;
|
|
|
|
|
|
|
|
if (!name)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
while (name[i]) {
|
|
|
|
if ((name[j++] = name[i++]) == '/')
|
|
|
|
while (name[i] == '/')
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
name[j] = '\0';
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *find_name_gnu(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
const char *def,
|
|
|
|
int p_value)
|
|
|
|
{
|
|
|
|
struct strbuf name = STRBUF_INIT;
|
|
|
|
char *cp;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Proposed "new-style" GNU patch/diff format; see
|
|
|
|
* http://marc.info/?l=git&m=112927316408690&w=2
|
|
|
|
*/
|
|
|
|
if (unquote_c_style(&name, line, NULL)) {
|
|
|
|
strbuf_release(&name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (cp = name.buf; p_value; p_value--) {
|
|
|
|
cp = strchr(cp, '/');
|
|
|
|
if (!cp) {
|
|
|
|
strbuf_release(&name);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
cp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_remove(&name, 0, cp - name.buf);
|
|
|
|
if (state->root.len)
|
|
|
|
strbuf_insert(&name, 0, state->root.buf, state->root.len);
|
|
|
|
return squash_slash(strbuf_detach(&name, NULL));
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t sane_tz_len(const char *line, size_t len)
|
|
|
|
{
|
|
|
|
const char *tz, *p;
|
|
|
|
|
|
|
|
if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ')
|
|
|
|
return 0;
|
|
|
|
tz = line + len - strlen(" +0500");
|
|
|
|
|
|
|
|
if (tz[1] != '+' && tz[1] != '-')
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (p = tz + 2; p != line + len; p++)
|
|
|
|
if (!isdigit(*p))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return line + len - tz;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t tz_with_colon_len(const char *line, size_t len)
|
|
|
|
{
|
|
|
|
const char *tz, *p;
|
|
|
|
|
|
|
|
if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':')
|
|
|
|
return 0;
|
|
|
|
tz = line + len - strlen(" +08:00");
|
|
|
|
|
|
|
|
if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-'))
|
|
|
|
return 0;
|
|
|
|
p = tz + 2;
|
|
|
|
if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
|
|
|
|
!isdigit(*p++) || !isdigit(*p++))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return line + len - tz;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t date_len(const char *line, size_t len)
|
|
|
|
{
|
|
|
|
const char *date, *p;
|
|
|
|
|
|
|
|
if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-')
|
|
|
|
return 0;
|
|
|
|
p = date = line + len - strlen("72-02-05");
|
|
|
|
|
|
|
|
if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
|
|
|
|
!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
|
|
|
|
!isdigit(*p++) || !isdigit(*p++)) /* Not a date. */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (date - line >= strlen("19") &&
|
|
|
|
isdigit(date[-1]) && isdigit(date[-2])) /* 4-digit year */
|
|
|
|
date -= strlen("19");
|
|
|
|
|
|
|
|
return line + len - date;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t short_time_len(const char *line, size_t len)
|
|
|
|
{
|
|
|
|
const char *time, *p;
|
|
|
|
|
|
|
|
if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':')
|
|
|
|
return 0;
|
|
|
|
p = time = line + len - strlen(" 07:01:32");
|
|
|
|
|
|
|
|
/* Permit 1-digit hours? */
|
|
|
|
if (*p++ != ' ' ||
|
|
|
|
!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
|
|
|
|
!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
|
|
|
|
!isdigit(*p++) || !isdigit(*p++)) /* Not a time. */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return line + len - time;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t fractional_time_len(const char *line, size_t len)
|
|
|
|
{
|
|
|
|
const char *p;
|
|
|
|
size_t n;
|
|
|
|
|
|
|
|
/* Expected format: 19:41:17.620000023 */
|
|
|
|
if (!len || !isdigit(line[len - 1]))
|
|
|
|
return 0;
|
|
|
|
p = line + len - 1;
|
|
|
|
|
|
|
|
/* Fractional seconds. */
|
|
|
|
while (p > line && isdigit(*p))
|
|
|
|
p--;
|
|
|
|
if (*p != '.')
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Hours, minutes, and whole seconds. */
|
|
|
|
n = short_time_len(line, p - line);
|
|
|
|
if (!n)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return line + len - p + n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t trailing_spaces_len(const char *line, size_t len)
|
|
|
|
{
|
|
|
|
const char *p;
|
|
|
|
|
|
|
|
/* Expected format: ' ' x (1 or more) */
|
|
|
|
if (!len || line[len - 1] != ' ')
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
p = line + len;
|
|
|
|
while (p != line) {
|
|
|
|
p--;
|
|
|
|
if (*p != ' ')
|
|
|
|
return line + len - (p + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* All spaces! */
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t diff_timestamp_len(const char *line, size_t len)
|
|
|
|
{
|
|
|
|
const char *end = line + len;
|
|
|
|
size_t n;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Posix: 2010-07-05 19:41:17
|
|
|
|
* GNU: 2010-07-05 19:41:17.620000023 -0500
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!isdigit(end[-1]))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
n = sane_tz_len(line, end - line);
|
|
|
|
if (!n)
|
|
|
|
n = tz_with_colon_len(line, end - line);
|
|
|
|
end -= n;
|
|
|
|
|
|
|
|
n = short_time_len(line, end - line);
|
|
|
|
if (!n)
|
|
|
|
n = fractional_time_len(line, end - line);
|
|
|
|
end -= n;
|
|
|
|
|
|
|
|
n = date_len(line, end - line);
|
|
|
|
if (!n) /* No date. Too bad. */
|
|
|
|
return 0;
|
|
|
|
end -= n;
|
|
|
|
|
|
|
|
if (end == line) /* No space before date. */
|
|
|
|
return 0;
|
|
|
|
if (end[-1] == '\t') { /* Success! */
|
|
|
|
end--;
|
|
|
|
return line + len - end;
|
|
|
|
}
|
|
|
|
if (end[-1] != ' ') /* No space before date. */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Whitespace damage. */
|
|
|
|
end -= trailing_spaces_len(line, end - line);
|
|
|
|
return line + len - end;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *find_name_common(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
const char *def,
|
|
|
|
int p_value,
|
|
|
|
const char *end,
|
|
|
|
int terminate)
|
|
|
|
{
|
|
|
|
int len;
|
|
|
|
const char *start = NULL;
|
|
|
|
|
|
|
|
if (p_value == 0)
|
|
|
|
start = line;
|
|
|
|
while (line != end) {
|
|
|
|
char c = *line;
|
|
|
|
|
|
|
|
if (!end && isspace(c)) {
|
|
|
|
if (c == '\n')
|
|
|
|
break;
|
|
|
|
if (name_terminate(c, terminate))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
line++;
|
|
|
|
if (c == '/' && !--p_value)
|
|
|
|
start = line;
|
|
|
|
}
|
|
|
|
if (!start)
|
|
|
|
return squash_slash(xstrdup_or_null(def));
|
|
|
|
len = line - start;
|
|
|
|
if (!len)
|
|
|
|
return squash_slash(xstrdup_or_null(def));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generally we prefer the shorter name, especially
|
|
|
|
* if the other one is just a variation of that with
|
|
|
|
* something else tacked on to the end (ie "file.orig"
|
|
|
|
* or "file~").
|
|
|
|
*/
|
|
|
|
if (def) {
|
|
|
|
int deflen = strlen(def);
|
|
|
|
if (deflen < len && !strncmp(start, def, deflen))
|
|
|
|
return squash_slash(xstrdup(def));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->root.len) {
|
|
|
|
char *ret = xstrfmt("%s%.*s", state->root.buf, len, start);
|
|
|
|
return squash_slash(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
return squash_slash(xmemdupz(start, len));
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *find_name(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
char *def,
|
|
|
|
int p_value,
|
|
|
|
int terminate)
|
|
|
|
{
|
|
|
|
if (*line == '"') {
|
|
|
|
char *name = find_name_gnu(state, line, def, p_value);
|
|
|
|
if (name)
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
return find_name_common(state, line, def, p_value, NULL, terminate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *find_name_traditional(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
char *def,
|
|
|
|
int p_value)
|
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
size_t date_len;
|
|
|
|
|
|
|
|
if (*line == '"') {
|
|
|
|
char *name = find_name_gnu(state, line, def, p_value);
|
|
|
|
if (name)
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
len = strchrnul(line, '\n') - line;
|
|
|
|
date_len = diff_timestamp_len(line, len);
|
|
|
|
if (!date_len)
|
|
|
|
return find_name_common(state, line, def, p_value, NULL, TERM_TAB);
|
|
|
|
len -= date_len;
|
|
|
|
|
|
|
|
return find_name_common(state, line, def, p_value, line + len, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Given the string after "--- " or "+++ ", guess the appropriate
|
|
|
|
* p_value for the given patch.
|
|
|
|
*/
|
|
|
|
static int guess_p_value(struct apply_state *state, const char *nameline)
|
|
|
|
{
|
|
|
|
char *name, *cp;
|
|
|
|
int val = -1;
|
|
|
|
|
|
|
|
if (is_dev_null(nameline))
|
|
|
|
return -1;
|
|
|
|
name = find_name_traditional(state, nameline, NULL, 0);
|
|
|
|
if (!name)
|
|
|
|
return -1;
|
|
|
|
cp = strchr(name, '/');
|
|
|
|
if (!cp)
|
|
|
|
val = 0;
|
|
|
|
else if (state->prefix) {
|
|
|
|
/*
|
|
|
|
* Does it begin with "a/$our-prefix" and such? Then this is
|
|
|
|
* very likely to apply to our directory.
|
|
|
|
*/
|
|
|
|
if (!strncmp(name, state->prefix, state->prefix_length))
|
|
|
|
val = count_slashes(state->prefix);
|
|
|
|
else {
|
|
|
|
cp++;
|
|
|
|
if (!strncmp(cp, state->prefix, state->prefix_length))
|
|
|
|
val = count_slashes(state->prefix) + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(name);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Does the ---/+++ line have 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, *colon;
|
|
|
|
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, (char **) &colon, 10);
|
|
|
|
if (*colon == ':')
|
|
|
|
zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10);
|
|
|
|
else
|
|
|
|
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
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
static int parse_traditional_patch(struct apply_state *state,
|
|
|
|
const char *first,
|
|
|
|
const char *second,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
|
|
|
char *name;
|
|
|
|
|
|
|
|
first += 4; /* skip "--- " */
|
|
|
|
second += 4; /* skip "+++ " */
|
|
|
|
if (!state->p_value_known) {
|
|
|
|
int p, q;
|
|
|
|
p = guess_p_value(state, first);
|
|
|
|
q = guess_p_value(state, second);
|
|
|
|
if (p < 0) p = q;
|
|
|
|
if (0 <= p && p == q) {
|
|
|
|
state->p_value = p;
|
|
|
|
state->p_value_known = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (is_dev_null(first)) {
|
|
|
|
patch->is_new = 1;
|
|
|
|
patch->is_delete = 0;
|
|
|
|
name = find_name_traditional(state, second, NULL, state->p_value);
|
|
|
|
patch->new_name = name;
|
|
|
|
} else if (is_dev_null(second)) {
|
|
|
|
patch->is_new = 0;
|
|
|
|
patch->is_delete = 1;
|
|
|
|
name = find_name_traditional(state, first, NULL, state->p_value);
|
|
|
|
patch->old_name = name;
|
|
|
|
} else {
|
|
|
|
char *first_name;
|
|
|
|
first_name = find_name_traditional(state, first, NULL, state->p_value);
|
|
|
|
name = find_name_traditional(state, second, first_name, state->p_value);
|
|
|
|
free(first_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 = name;
|
|
|
|
patch->new_name = xstrdup_or_null(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!name)
|
|
|
|
return error(_("unable to find filename in patch at line %d"), state->linenr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_hdrend(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We're anal about diff header consistency, to make
|
|
|
|
* sure that we don't end up having strange ambiguous
|
|
|
|
* patches floating around.
|
|
|
|
*
|
|
|
|
* As a result, gitdiff_{old|new}name() will check
|
|
|
|
* their names against any previous information, just
|
|
|
|
* to make sure..
|
|
|
|
*/
|
|
|
|
#define DIFF_OLD_NAME 0
|
|
|
|
#define DIFF_NEW_NAME 1
|
|
|
|
|
|
|
|
static int gitdiff_verify_name(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
int isnull,
|
|
|
|
char **name,
|
|
|
|
int side)
|
|
|
|
{
|
|
|
|
if (!*name && !isnull) {
|
|
|
|
*name = find_name(state, line, NULL, state->p_value, TERM_TAB);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*name) {
|
|
|
|
char *another;
|
|
|
|
if (isnull)
|
|
|
|
return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
|
|
|
|
*name, state->linenr);
|
|
|
|
another = find_name(state, line, NULL, state->p_value, TERM_TAB);
|
2017-07-08 10:58:42 +02:00
|
|
|
if (!another || strcmp(another, *name)) {
|
2016-04-22 20:55:46 +02:00
|
|
|
free(another);
|
|
|
|
return error((side == DIFF_NEW_NAME) ?
|
|
|
|
_("git apply: bad git-diff - inconsistent new filename on line %d") :
|
|
|
|
_("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr);
|
|
|
|
}
|
|
|
|
free(another);
|
|
|
|
} else {
|
2017-07-01 11:10:07 +02:00
|
|
|
if (!starts_with(line, "/dev/null\n"))
|
2016-04-22 20:55:46 +02:00
|
|
|
return error(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_oldname(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
|
|
|
return gitdiff_verify_name(state, line,
|
|
|
|
patch->is_new, &patch->old_name,
|
|
|
|
DIFF_OLD_NAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_newname(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
|
|
|
return gitdiff_verify_name(state, line,
|
|
|
|
patch->is_delete, &patch->new_name,
|
|
|
|
DIFF_NEW_NAME);
|
|
|
|
}
|
|
|
|
|
2017-06-27 19:03:47 +02:00
|
|
|
static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
|
|
|
|
{
|
|
|
|
char *end;
|
|
|
|
*mode = strtoul(line, &end, 8);
|
|
|
|
if (end == line || !isspace(*end))
|
|
|
|
return error(_("invalid mode on line %d: %s"), linenr, line);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-04-22 20:55:46 +02:00
|
|
|
static int gitdiff_oldmode(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
2017-06-27 19:03:47 +02:00
|
|
|
return parse_mode_line(line, state->linenr, &patch->old_mode);
|
2016-04-22 20:55:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_newmode(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
2017-06-27 19:03:47 +02:00
|
|
|
return parse_mode_line(line, state->linenr, &patch->new_mode);
|
2016-04-22 20:55:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_delete(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
|
|
|
patch->is_delete = 1;
|
|
|
|
free(patch->old_name);
|
|
|
|
patch->old_name = xstrdup_or_null(patch->def_name);
|
|
|
|
return gitdiff_oldmode(state, line, patch);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_newfile(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
|
|
|
patch->is_new = 1;
|
|
|
|
free(patch->new_name);
|
|
|
|
patch->new_name = xstrdup_or_null(patch->def_name);
|
|
|
|
return gitdiff_newmode(state, line, patch);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_copysrc(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
|
|
|
patch->is_copy = 1;
|
|
|
|
free(patch->old_name);
|
|
|
|
patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_copydst(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|
{
|
|
|
|
patch->is_copy = 1;
|
|
|
|
free(patch->new_name);
|
|
|
|
patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gitdiff_renamesrc(struct apply_state *state,
|
|
|
|
const char *line,
|
|
|
|
struct patch *patch)
|
|
|
|