
The get_one_patchid() function reads input lines until it finds a patch header (the line that begins a patch), whose beginning is one of: (1) an "<object name>", which is what "git diff-tree --stdin" shows; (2) "commit <object name>", which is what "git log" shows; or (3) "From <object name>", which is what "git log --format=email" shows. When it finds such a line, it returns to the caller, reporting the <object name> it found, and the size of the "patch" it processed. The caller then calls the function again, which then ignores the commit log message, and then processes the lines in the patch part until it hits another "beginning of a patch". The above logic was fairly easy to see until2bb73ae8
(patch-id: use starts_with() and skip_prefix(), 2016-05-28) reorganized the code, which made another logic that has nothing to do with the "where does the next patch begin?" logic, which came from2485eab5
(git-patch-id: do not trip over "no newline" markers, 2011-02-17) that ignores the "\ No newline at the end", rolled into the same single if() statement. Let's split it out. The "\ No newline at the end" marker is part of the patch, should not appear before we start reading the patch part, and does not belong to the detection of patch header. Signed-off-by: Junio C Hamano <gitster@pobox.com>
272 lines
6.2 KiB
C
272 lines
6.2 KiB
C
#include "builtin.h"
|
|
#include "config.h"
|
|
#include "diff.h"
|
|
#include "gettext.h"
|
|
#include "hash.h"
|
|
#include "hex.h"
|
|
#include "parse-options.h"
|
|
|
|
static void flush_current_id(struct object_id *id, struct object_id *result)
|
|
{
|
|
printf("%s %s\n", oid_to_hex(result), oid_to_hex(id));
|
|
}
|
|
|
|
static int remove_space(char *line)
|
|
{
|
|
char *src = line;
|
|
char *dst = line;
|
|
unsigned char c;
|
|
|
|
while ((c = *src++) != '\0') {
|
|
if (!isspace(c))
|
|
*dst++ = c;
|
|
}
|
|
return dst - line;
|
|
}
|
|
|
|
static int scan_hunk_header(const char *p, int *p_before, int *p_after)
|
|
{
|
|
static const char digits[] = "0123456789";
|
|
const char *q, *r;
|
|
int n;
|
|
|
|
q = p + 4;
|
|
n = strspn(q, digits);
|
|
if (q[n] == ',') {
|
|
q += n + 1;
|
|
*p_before = atoi(q);
|
|
n = strspn(q, digits);
|
|
} else {
|
|
*p_before = 1;
|
|
}
|
|
|
|
if (n == 0 || q[n] != ' ' || q[n+1] != '+')
|
|
return 0;
|
|
|
|
r = q + n + 2;
|
|
n = strspn(r, digits);
|
|
if (r[n] == ',') {
|
|
r += n + 1;
|
|
*p_after = atoi(r);
|
|
n = strspn(r, digits);
|
|
} else {
|
|
*p_after = 1;
|
|
}
|
|
if (n == 0)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* flag bits to control get_one_patchid()'s behaviour.
|
|
*/
|
|
enum {
|
|
GOPID_STABLE = (1<<0), /* --stable */
|
|
GOPID_VERBATIM = (1<<1), /* --verbatim */
|
|
};
|
|
|
|
static int get_one_patchid(struct object_id *next_oid, struct object_id *result,
|
|
struct strbuf *line_buf, unsigned flags)
|
|
{
|
|
int stable = flags & GOPID_STABLE;
|
|
int verbatim = flags & GOPID_VERBATIM;
|
|
int patchlen = 0, found_next = 0;
|
|
int before = -1, after = -1;
|
|
int diff_is_binary = 0;
|
|
char pre_oid_str[GIT_MAX_HEXSZ + 1], post_oid_str[GIT_MAX_HEXSZ + 1];
|
|
git_hash_ctx ctx;
|
|
|
|
the_hash_algo->init_fn(&ctx);
|
|
oidclr(result);
|
|
|
|
while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) {
|
|
char *line = line_buf->buf;
|
|
const char *p = line;
|
|
int len;
|
|
|
|
/*
|
|
* If we see a line that begins with "<object name>",
|
|
* "commit <object name>" or "From <object name>", it is
|
|
* the beginning of a patch. Return to the caller, as
|
|
* we are done with the one we have been processing.
|
|
*/
|
|
if (skip_prefix(line, "commit ", &p))
|
|
;
|
|
else if (skip_prefix(line, "From ", &p))
|
|
;
|
|
if (!get_oid_hex(p, next_oid)) {
|
|
if (verbatim)
|
|
the_hash_algo->update_fn(&ctx, line, strlen(line));
|
|
found_next = 1;
|
|
break;
|
|
}
|
|
|
|
/* Ignore commit comments */
|
|
if (!patchlen && !starts_with(line, "diff "))
|
|
continue;
|
|
|
|
/* Parsing diff header? */
|
|
if (before == -1) {
|
|
if (starts_with(line, "GIT binary patch") ||
|
|
starts_with(line, "Binary files")) {
|
|
diff_is_binary = 1;
|
|
before = 0;
|
|
the_hash_algo->update_fn(&ctx, pre_oid_str,
|
|
strlen(pre_oid_str));
|
|
the_hash_algo->update_fn(&ctx, post_oid_str,
|
|
strlen(post_oid_str));
|
|
if (stable)
|
|
flush_one_hunk(result, &ctx);
|
|
continue;
|
|
} else if (skip_prefix(line, "index ", &p)) {
|
|
char *oid1_end = strstr(line, "..");
|
|
char *oid2_end = NULL;
|
|
if (oid1_end)
|
|
oid2_end = strstr(oid1_end, " ");
|
|
if (!oid2_end)
|
|
oid2_end = line + strlen(line) - 1;
|
|
if (oid1_end != NULL && oid2_end != NULL) {
|
|
*oid1_end = *oid2_end = '\0';
|
|
strlcpy(pre_oid_str, p, GIT_MAX_HEXSZ + 1);
|
|
strlcpy(post_oid_str, oid1_end + 2, GIT_MAX_HEXSZ + 1);
|
|
}
|
|
continue;
|
|
} else if (starts_with(line, "--- "))
|
|
before = after = 1;
|
|
else if (!isalpha(line[0]))
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* A hunk about an incomplete line may have this
|
|
* marker at the end, which should just be ignored.
|
|
*/
|
|
if (starts_with(line, "\\ ") && 12 < strlen(line)) {
|
|
if (verbatim)
|
|
the_hash_algo->update_fn(&ctx, line, strlen(line));
|
|
continue;
|
|
}
|
|
|
|
if (diff_is_binary) {
|
|
if (starts_with(line, "diff ")) {
|
|
diff_is_binary = 0;
|
|
before = -1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Looking for a valid hunk header? */
|
|
if (before == 0 && after == 0) {
|
|
if (starts_with(line, "@@ -")) {
|
|
/* Parse next hunk, but ignore line numbers. */
|
|
scan_hunk_header(line, &before, &after);
|
|
continue;
|
|
}
|
|
|
|
/* Split at the end of the patch. */
|
|
if (!starts_with(line, "diff "))
|
|
break;
|
|
|
|
/* Else we're parsing another header. */
|
|
if (stable)
|
|
flush_one_hunk(result, &ctx);
|
|
before = after = -1;
|
|
}
|
|
|
|
/* If we get here, we're inside a hunk. */
|
|
if (line[0] == '-' || line[0] == ' ')
|
|
before--;
|
|
if (line[0] == '+' || line[0] == ' ')
|
|
after--;
|
|
|
|
/* Add line to hash algo (possibly removing whitespace) */
|
|
len = verbatim ? strlen(line) : remove_space(line);
|
|
patchlen += len;
|
|
the_hash_algo->update_fn(&ctx, line, len);
|
|
}
|
|
|
|
if (!found_next)
|
|
oidclr(next_oid);
|
|
|
|
flush_one_hunk(result, &ctx);
|
|
|
|
return patchlen;
|
|
}
|
|
|
|
static void generate_id_list(unsigned flags)
|
|
{
|
|
struct object_id oid, n, result;
|
|
int patchlen;
|
|
struct strbuf line_buf = STRBUF_INIT;
|
|
|
|
oidclr(&oid);
|
|
while (!feof(stdin)) {
|
|
patchlen = get_one_patchid(&n, &result, &line_buf, flags);
|
|
if (patchlen)
|
|
flush_current_id(&oid, &result);
|
|
oidcpy(&oid, &n);
|
|
}
|
|
strbuf_release(&line_buf);
|
|
}
|
|
|
|
static const char *const patch_id_usage[] = {
|
|
N_("git patch-id [--stable | --unstable | --verbatim]"), NULL
|
|
};
|
|
|
|
struct patch_id_opts {
|
|
int stable;
|
|
int verbatim;
|
|
};
|
|
|
|
static int git_patch_id_config(const char *var, const char *value,
|
|
const struct config_context *ctx, void *cb)
|
|
{
|
|
struct patch_id_opts *opts = cb;
|
|
|
|
if (!strcmp(var, "patchid.stable")) {
|
|
opts->stable = git_config_bool(var, value);
|
|
return 0;
|
|
}
|
|
if (!strcmp(var, "patchid.verbatim")) {
|
|
opts->verbatim = git_config_bool(var, value);
|
|
return 0;
|
|
}
|
|
|
|
return git_default_config(var, value, ctx, cb);
|
|
}
|
|
|
|
int cmd_patch_id(int argc, const char **argv, const char *prefix)
|
|
{
|
|
/* if nothing is set, default to unstable */
|
|
struct patch_id_opts config = {0, 0};
|
|
int opts = 0;
|
|
unsigned flags = 0;
|
|
struct option builtin_patch_id_options[] = {
|
|
OPT_CMDMODE(0, "unstable", &opts,
|
|
N_("use the unstable patch-id algorithm"), 1),
|
|
OPT_CMDMODE(0, "stable", &opts,
|
|
N_("use the stable patch-id algorithm"), 2),
|
|
OPT_CMDMODE(0, "verbatim", &opts,
|
|
N_("don't strip whitespace from the patch"), 3),
|
|
OPT_END()
|
|
};
|
|
|
|
git_config(git_patch_id_config, &config);
|
|
|
|
/* verbatim implies stable */
|
|
if (config.verbatim)
|
|
config.stable = 1;
|
|
|
|
argc = parse_options(argc, argv, prefix, builtin_patch_id_options,
|
|
patch_id_usage, 0);
|
|
|
|
if (opts ? opts > 1 : config.stable)
|
|
flags |= GOPID_STABLE;
|
|
if (opts ? opts == 3 : config.verbatim)
|
|
flags |= GOPID_VERBATIM;
|
|
generate_id_list(flags);
|
|
|
|
return 0;
|
|
}
|