aboutsummaryrefslogtreecommitdiffstats
path: root/commit.c
diff options
context:
space:
mode:
Diffstat (limited to 'commit.c')
-rw-r--r--commit.c581
1 files changed, 447 insertions, 134 deletions
diff --git a/commit.c b/commit.c
index 32d1234bd7..7b63d3b0e1 100644
--- a/commit.c
+++ b/commit.c
@@ -2,6 +2,7 @@
#include "tag.h"
#include "commit.h"
#include "commit-graph.h"
+#include "hex.h"
#include "repository.h"
#include "object-store.h"
#include "pkt-line.h"
@@ -14,13 +15,19 @@
#include "mergesort.h"
#include "commit-slab.h"
#include "prio-queue.h"
-#include "sha1-lookup.h"
+#include "hash-lookup.h"
#include "wt-status.h"
#include "advice.h"
+#include "refs.h"
+#include "commit-reach.h"
+#include "run-command.h"
+#include "shallow.h"
+#include "hook.h"
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
int save_commit_buffer = 1;
+int no_graft_file_deprecated_advice;
const char *commit_type = "commit";
@@ -33,7 +40,7 @@ struct commit *lookup_commit_reference_gently(struct repository *r,
if (!obj)
return NULL;
- return object_as_type(r, obj, OBJ_COMMIT, quiet);
+ return object_as_type(obj, OBJ_COMMIT, quiet);
}
struct commit *lookup_commit_reference(struct repository *r, const struct object_id *oid)
@@ -46,20 +53,27 @@ struct commit *lookup_commit_or_die(const struct object_id *oid, const char *ref
struct commit *c = lookup_commit_reference(the_repository, oid);
if (!c)
die(_("could not parse %s"), ref_name);
- if (oidcmp(oid, &c->object.oid)) {
+ if (!oideq(oid, &c->object.oid)) {
warning(_("%s %s is not a commit!"),
ref_name, oid_to_hex(oid));
}
return c;
}
+struct commit *lookup_commit_object(struct repository *r,
+ const struct object_id *oid)
+{
+ struct object *obj = parse_object(r, oid);
+ return obj ? object_as_type(obj, OBJ_COMMIT, 0) : NULL;
+
+}
+
struct commit *lookup_commit(struct repository *r, const struct object_id *oid)
{
- struct object *obj = lookup_object(r, oid->hash);
+ struct object *obj = lookup_object(r, oid);
if (!obj)
- return create_object(r, oid->hash,
- alloc_commit_node(r));
- return object_as_type(r, obj, OBJ_COMMIT, 0);
+ return create_object(r, oid, alloc_commit_node(r));
+ return object_as_type(obj, OBJ_COMMIT, 0);
}
struct commit *lookup_commit_reference_by_name(const char *name)
@@ -102,23 +116,34 @@ static timestamp_t parse_commit_date(const char *buf, const char *tail)
return parse_timestamp(dateptr, NULL, 10);
}
-static const unsigned char *commit_graft_sha1_access(size_t index, void *table)
+static const struct object_id *commit_graft_oid_access(size_t index, const void *table)
{
- struct commit_graft **commit_graft_table = table;
- return commit_graft_table[index]->oid.hash;
+ const struct commit_graft * const *commit_graft_table = table;
+ return &commit_graft_table[index]->oid;
}
-static int commit_graft_pos(struct repository *r, const unsigned char *sha1)
+int commit_graft_pos(struct repository *r, const struct object_id *oid)
{
- return sha1_pos(sha1, r->parsed_objects->grafts,
- r->parsed_objects->grafts_nr,
- commit_graft_sha1_access);
+ return oid_pos(oid, r->parsed_objects->grafts,
+ r->parsed_objects->grafts_nr,
+ commit_graft_oid_access);
+}
+
+static void unparse_commit(struct repository *r, const struct object_id *oid)
+{
+ struct commit *c = lookup_commit(r, oid);
+
+ if (!c->object.parsed)
+ return;
+ free_commit_list(c->parents);
+ c->parents = NULL;
+ c->object.parsed = 0;
}
int register_commit_graft(struct repository *r, struct commit_graft *graft,
int ignore_dups)
{
- int pos = commit_graft_pos(r, graft->oid.hash);
+ int pos = commit_graft_pos(r, &graft->oid);
if (0 <= pos) {
if (ignore_dups)
@@ -140,6 +165,7 @@ int register_commit_graft(struct repository *r, struct commit_graft *graft,
(r->parsed_objects->grafts_nr - pos - 1) *
sizeof(*r->parsed_objects->grafts));
r->parsed_objects->grafts[pos] = graft;
+ unparse_commit(r, &graft->oid);
return 0;
}
@@ -187,7 +213,8 @@ static int read_graft_file(struct repository *r, const char *graft_file)
struct strbuf buf = STRBUF_INIT;
if (!fp)
return -1;
- if (advice_graft_file_deprecated)
+ if (!no_graft_file_deprecated_advice &&
+ advice_enabled(ADVICE_GRAFT_FILE_DEPRECATED))
advise(_("Support for <GIT_DIR>/info/grafts is deprecated\n"
"and will be removed in a future Git version.\n"
"\n"
@@ -209,7 +236,7 @@ static int read_graft_file(struct repository *r, const char *graft_file)
return 0;
}
-static void prepare_commit_graft(struct repository *r)
+void prepare_commit_graft(struct repository *r)
{
char *graft_file;
@@ -229,7 +256,7 @@ struct commit_graft *lookup_commit_graft(struct repository *r, const struct obje
{
int pos;
prepare_commit_graft(r);
- pos = commit_graft_pos(r, oid->hash);
+ pos = commit_graft_pos(r, oid);
if (pos < 0)
return NULL;
return r->parsed_objects->grafts[pos];
@@ -243,17 +270,16 @@ int for_each_commit_graft(each_commit_graft_fn fn, void *cb_data)
return ret;
}
-int unregister_shallow(const struct object_id *oid)
+void reset_commit_grafts(struct repository *r)
{
- int pos = commit_graft_pos(the_repository, oid->hash);
- if (pos < 0)
- return -1;
- if (pos + 1 < the_repository->parsed_objects->grafts_nr)
- MOVE_ARRAY(the_repository->parsed_objects->grafts + pos,
- the_repository->parsed_objects->grafts + pos + 1,
- the_repository->parsed_objects->grafts_nr - pos - 1);
- the_repository->parsed_objects->grafts_nr--;
- return 0;
+ int i;
+
+ for (i = 0; i < r->parsed_objects->grafts_nr; i++) {
+ unparse_commit(r, &r->parsed_objects->grafts[i]->oid);
+ free(r->parsed_objects->grafts[i]);
+ }
+ r->parsed_objects->grafts_nr = 0;
+ r->parsed_objects->commit_graft_prepared = 0;
}
struct commit_buffer {
@@ -297,13 +323,15 @@ const void *get_cached_commit_buffer(struct repository *r, const struct commit *
return v->buffer;
}
-const void *get_commit_buffer(const struct commit *commit, unsigned long *sizep)
+const void *repo_get_commit_buffer(struct repository *r,
+ const struct commit *commit,
+ unsigned long *sizep)
{
- const void *ret = get_cached_commit_buffer(the_repository, commit, sizep);
+ const void *ret = get_cached_commit_buffer(r, commit, sizep);
if (!ret) {
enum object_type type;
unsigned long size;
- ret = read_object_file(&commit->object.oid, &type, &size);
+ ret = repo_read_object_file(r, &commit->object.oid, &type, &size);
if (!ret)
die("cannot read commit object %s",
oid_to_hex(&commit->object.oid));
@@ -316,47 +344,55 @@ const void *get_commit_buffer(const struct commit *commit, unsigned long *sizep)
return ret;
}
-void unuse_commit_buffer(const struct commit *commit, const void *buffer)
+void repo_unuse_commit_buffer(struct repository *r,
+ const struct commit *commit,
+ const void *buffer)
{
struct commit_buffer *v = buffer_slab_peek(
- the_repository->parsed_objects->buffer_slab, commit);
+ r->parsed_objects->buffer_slab, commit);
if (!(v && v->buffer == buffer))
free((void *)buffer);
}
-void free_commit_buffer(struct commit *commit)
+void free_commit_buffer(struct parsed_object_pool *pool, struct commit *commit)
{
struct commit_buffer *v = buffer_slab_peek(
- the_repository->parsed_objects->buffer_slab, commit);
+ pool->buffer_slab, commit);
if (v) {
FREE_AND_NULL(v->buffer);
v->size = 0;
}
}
-struct tree *get_commit_tree(const struct commit *commit)
+static inline void set_commit_tree(struct commit *c, struct tree *t)
+{
+ c->maybe_tree = t;
+}
+
+struct tree *repo_get_commit_tree(struct repository *r,
+ const struct commit *commit)
{
if (commit->maybe_tree || !commit->object.parsed)
return commit->maybe_tree;
- if (commit->graph_pos == COMMIT_NOT_FROM_GRAPH)
- BUG("commit has NULL tree, but was not loaded from commit-graph");
+ if (commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
+ return get_commit_tree_in_graph(r, commit);
- return get_commit_tree_in_graph(the_repository, commit);
+ return NULL;
}
struct object_id *get_commit_tree_oid(const struct commit *commit)
{
- return &get_commit_tree(commit)->object.oid;
+ struct tree *tree = get_commit_tree(commit);
+ return tree ? &tree->object.oid : NULL;
}
-void release_commit_memory(struct commit *c)
+void release_commit_memory(struct parsed_object_pool *pool, struct commit *c)
{
- c->maybe_tree = NULL;
+ set_commit_tree(c, NULL);
+ free_commit_buffer(pool, c);
c->index = 0;
- free_commit_buffer(c);
free_commit_list(c->parents);
- /* TODO: what about commit->util? */
c->object.parsed = 0;
}
@@ -388,12 +424,21 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b
struct object_id parent;
struct commit_list **pptr;
struct commit_graft *graft;
- const int tree_entry_len = GIT_SHA1_HEXSZ + 5;
- const int parent_entry_len = GIT_SHA1_HEXSZ + 7;
+ const int tree_entry_len = the_hash_algo->hexsz + 5;
+ const int parent_entry_len = the_hash_algo->hexsz + 7;
+ struct tree *tree;
if (item->object.parsed)
return 0;
- item->object.parsed = 1;
+ /*
+ * Presumably this is leftover from an earlier failed parse;
+ * clear it out in preparation for us re-parsing (we'll hit the
+ * same error, but that's good, since it lets our caller know
+ * the result cannot be trusted.
+ */
+ free_commit_list(item->parents);
+ item->parents = NULL;
+
tail += size;
if (tail <= bufptr + tree_entry_len + 1 || memcmp(bufptr, "tree ", 5) ||
bufptr[tree_entry_len] != '\n')
@@ -401,11 +446,18 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b
if (get_oid_hex(bufptr + 5, &parent) < 0)
return error("bad tree pointer in commit %s",
oid_to_hex(&item->object.oid));
- item->maybe_tree = lookup_tree(r, &parent);
+ tree = lookup_tree(r, &parent);
+ if (!tree)
+ return error("bad tree pointer %s in commit %s",
+ oid_to_hex(&parent),
+ oid_to_hex(&item->object.oid));
+ set_commit_tree(item, tree);
bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */
pptr = &item->parents;
graft = lookup_commit_graft(r, &item->object.oid);
+ if (graft)
+ r->parsed_objects->substituted_parent = 1;
while (bufptr + parent_entry_len < tail && !memcmp(bufptr, "parent ", 7)) {
struct commit *new_parent;
@@ -421,8 +473,11 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b
if (graft && (graft->nr_parent < 0 || grafts_replace_parents))
continue;
new_parent = lookup_commit(r, &parent);
- if (new_parent)
- pptr = &commit_list_insert(new_parent, pptr)->next;
+ if (!new_parent)
+ return error("bad parent %s in commit %s",
+ oid_to_hex(&parent),
+ oid_to_hex(&item->object.oid));
+ pptr = &commit_list_insert(new_parent, pptr)->next;
}
if (graft) {
int i;
@@ -431,33 +486,50 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b
new_parent = lookup_commit(r,
&graft->parent[i]);
if (!new_parent)
- continue;
+ return error("bad graft parent %s in commit %s",
+ oid_to_hex(&graft->parent[i]),
+ oid_to_hex(&item->object.oid));
pptr = &commit_list_insert(new_parent, pptr)->next;
}
}
item->date = parse_commit_date(bufptr, tail);
if (check_graph)
- load_commit_graph_info(the_repository, item);
+ load_commit_graph_info(r, item);
+ item->object.parsed = 1;
return 0;
}
-int parse_commit_internal(struct commit *item, int quiet_on_missing, int use_commit_graph)
+int repo_parse_commit_internal(struct repository *r,
+ struct commit *item,
+ int quiet_on_missing,
+ int use_commit_graph)
{
enum object_type type;
void *buffer;
unsigned long size;
+ struct object_info oi = {
+ .typep = &type,
+ .sizep = &size,
+ .contentp = &buffer,
+ };
+ /*
+ * Git does not support partial clones that exclude commits, so set
+ * OBJECT_INFO_SKIP_FETCH_OBJECT to fail fast when an object is missing.
+ */
+ int flags = OBJECT_INFO_LOOKUP_REPLACE | OBJECT_INFO_SKIP_FETCH_OBJECT |
+ OBJECT_INFO_DIE_IF_CORRUPT;
int ret;
if (!item)
return -1;
if (item->object.parsed)
return 0;
- if (use_commit_graph && parse_commit_in_graph(the_repository, item))
+ if (use_commit_graph && parse_commit_in_graph(r, item))
return 0;
- buffer = read_object_file(&item->object.oid, &type, &size);
- if (!buffer)
+
+ if (oid_object_info_extended(r, &item->object.oid, &oi, flags) < 0)
return quiet_on_missing ? -1 :
error("Could not read %s",
oid_to_hex(&item->object.oid));
@@ -467,18 +539,19 @@ int parse_commit_internal(struct commit *item, int quiet_on_missing, int use_com
oid_to_hex(&item->object.oid));
}
- ret = parse_commit_buffer(the_repository, item, buffer, size, 0);
+ ret = parse_commit_buffer(r, item, buffer, size, 0);
if (save_commit_buffer && !ret) {
- set_commit_buffer(the_repository, item, buffer, size);
+ set_commit_buffer(r, item, buffer, size);
return 0;
}
free(buffer);
return ret;
}
-int parse_commit_gently(struct commit *item, int quiet_on_missing)
+int repo_parse_commit_gently(struct repository *r,
+ struct commit *item, int quiet_on_missing)
{
- return parse_commit_internal(item, quiet_on_missing, 1);
+ return repo_parse_commit_internal(r, item, quiet_on_missing, 1);
}
void parse_commit_or_die(struct commit *item)
@@ -506,6 +579,20 @@ int find_commit_subject(const char *commit_buffer, const char **subject)
return eol - p;
}
+size_t commit_subject_length(const char *body)
+{
+ const char *p = body;
+ while (*p) {
+ const char *next = skip_blank_lines(p);
+ if (next != p)
+ break;
+ p = strchrnul(p, '\n');
+ if (*p)
+ p++;
+ }
+ return p - body;
+}
+
struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
{
struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
@@ -515,6 +602,17 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
return new_list;
}
+int commit_list_contains(struct commit *item, struct commit_list *list)
+{
+ while (list) {
+ if (list->item == item)
+ return 1;
+ list = list->next;
+ }
+
+ return 0;
+}
+
unsigned commit_list_count(const struct commit_list *l)
{
unsigned c = 0;
@@ -534,6 +632,17 @@ struct commit_list *copy_commit_list(struct commit_list *list)
return head;
}
+struct commit_list *reverse_commit_list(struct commit_list *list)
+{
+ struct commit_list *next = NULL, *current, *backup;
+ for (current = list; current; current = backup) {
+ backup = current->next;
+ current->next = next;
+ next = current;
+ }
+ return next;
+}
+
void free_commit_list(struct commit_list *list)
{
while (list)
@@ -553,10 +662,11 @@ struct commit_list * commit_list_insert_by_date(struct commit *item, struct comm
return commit_list_insert(item, pp);
}
-static int commit_list_compare_by_date(const void *a, const void *b)
+static int commit_list_compare_by_date(const struct commit_list *a,
+ const struct commit_list *b)
{
- timestamp_t a_date = ((const struct commit_list *)a)->item->date;
- timestamp_t b_date = ((const struct commit_list *)b)->item->date;
+ timestamp_t a_date = a->item->date;
+ timestamp_t b_date = b->item->date;
if (a_date < b_date)
return 1;
if (a_date > b_date)
@@ -564,20 +674,11 @@ static int commit_list_compare_by_date(const void *a, const void *b)
return 0;
}
-static void *commit_list_get_next(const void *a)
-{
- return ((const struct commit_list *)a)->next;
-}
-
-static void commit_list_set_next(void *a, void *next)
-{
- ((struct commit_list *)a)->next = next;
-}
+DEFINE_LIST_SORT(static, commit_list_sort, struct commit_list, next);
void commit_list_sort_by_date(struct commit_list **list)
{
- *list = llist_mergesort(*list, commit_list_get_next, commit_list_set_next,
- commit_list_compare_by_date);
+ commit_list_sort(list, commit_list_compare_by_date);
}
struct commit *pop_most_recent_commit(struct commit_list **list,
@@ -612,8 +713,10 @@ static void clear_commit_marks_1(struct commit_list **plist,
if (!parents)
return;
- while ((parents = parents->next))
- commit_list_insert(parents->item, plist);
+ while ((parents = parents->next)) {
+ if (parents->item->object.flags & mark)
+ commit_list_insert(parents->item, plist);
+ }
commit = commit->parents->item;
}
@@ -655,11 +758,10 @@ struct commit *pop_commit(struct commit_list **stack)
/* count number of children that have not been emitted */
define_commit_slab(indegree_slab, int);
-/* record author-date for each commit object */
-define_commit_slab(author_date_slab, unsigned long);
+define_commit_slab(author_date_slab, timestamp_t);
-static void record_author_date(struct author_date_slab *author_date,
- struct commit *commit)
+void record_author_date(struct author_date_slab *author_date,
+ struct commit *commit)
{
const char *buffer = get_commit_buffer(commit, NULL);
struct ident_split ident;
@@ -684,8 +786,8 @@ fail_exit:
unuse_commit_buffer(commit, buffer);
}
-static int compare_commits_by_author_date(const void *a_, const void *b_,
- void *cb_data)
+int compare_commits_by_author_date(const void *a_, const void *b_,
+ void *cb_data)
{
const struct commit *a = a_, *b = b_;
struct author_date_slab *author_date = cb_data;
@@ -700,14 +802,17 @@ static int compare_commits_by_author_date(const void *a_, const void *b_,
return 0;
}
-int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused)
+int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_,
+ void *unused UNUSED)
{
const struct commit *a = a_, *b = b_;
+ const timestamp_t generation_a = commit_graph_generation(a),
+ generation_b = commit_graph_generation(b);
/* newer commits first */
- if (a->generation < b->generation)
+ if (generation_a < generation_b)
return 1;
- else if (a->generation > b->generation)
+ else if (generation_a > generation_b)
return -1;
/* use date as a heuristic when generations are equal */
@@ -718,7 +823,8 @@ int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void
return 0;
}
-int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)
+int compare_commits_by_commit_date(const void *a_, const void *b_,
+ void *unused UNUSED)
{
const struct commit *a = a_, *b = b_;
/* newer commits with larger date first */
@@ -843,14 +949,115 @@ void sort_in_topological_order(struct commit_list **list, enum rev_sort_order so
clear_author_date_slab(&author_date);
}
-static const char gpg_sig_header[] = "gpgsig";
-static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+struct rev_collect {
+ struct commit **commit;
+ int nr;
+ int alloc;
+ unsigned int initial : 1;
+};
-static int do_sign_commit(struct strbuf *buf, const char *keyid)
+static void add_one_commit(struct object_id *oid, struct rev_collect *revs)
+{
+ struct commit *commit;
+
+ if (is_null_oid(oid))
+ return;
+
+ commit = lookup_commit(the_repository, oid);
+ if (!commit ||
+ (commit->object.flags & TMP_MARK) ||
+ parse_commit(commit))
+ return;
+
+ ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc);
+ revs->commit[revs->nr++] = commit;
+ commit->object.flags |= TMP_MARK;
+}
+
+static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
+ const char *ident UNUSED,
+ timestamp_t timestamp UNUSED, int tz UNUSED,
+ const char *message UNUSED, void *cbdata)
+{
+ struct rev_collect *revs = cbdata;
+
+ if (revs->initial) {
+ revs->initial = 0;
+ add_one_commit(ooid, revs);
+ }
+ add_one_commit(noid, revs);
+ return 0;
+}
+
+struct commit *get_fork_point(const char *refname, struct commit *commit)
+{
+ struct object_id oid;
+ struct rev_collect revs;
+ struct commit_list *bases;
+ int i;
+ struct commit *ret = NULL;
+ char *full_refname;
+
+ switch (dwim_ref(refname, strlen(refname), &oid, &full_refname, 0)) {
+ case 0:
+ die("No such ref: '%s'", refname);
+ case 1:
+ break; /* good */
+ default:
+ die("Ambiguous refname: '%s'", refname);
+ }
+
+ memset(&revs, 0, sizeof(revs));
+ revs.initial = 1;
+ for_each_reflog_ent(full_refname, collect_one_reflog_ent, &revs);
+
+ if (!revs.nr)
+ add_one_commit(&oid, &revs);
+
+ for (i = 0; i < revs.nr; i++)
+ revs.commit[i]->object.flags &= ~TMP_MARK;
+
+ bases = get_merge_bases_many(commit, revs.nr, revs.commit);
+
+ /*
+ * There should be one and only one merge base, when we found
+ * a common ancestor among reflog entries.
+ */
+ if (!bases || bases->next)
+ goto cleanup_return;
+
+ /* And the found one must be one of the reflog entries */
+ for (i = 0; i < revs.nr; i++)
+ if (&bases->item->object == &revs.commit[i]->object)
+ break; /* found */
+ if (revs.nr <= i)
+ goto cleanup_return;
+
+ ret = bases->item;
+
+cleanup_return:
+ free(revs.commit);
+ free_commit_list(bases);
+ free(full_refname);
+ return ret;
+}
+
+/*
+ * Indexed by hash algorithm identifier.
+ */
+static const char *gpg_sig_headers[] = {
+ NULL,
+ "gpgsig",
+ "gpgsig-sha256",
+};
+
+int sign_with_header(struct strbuf *buf, const char *keyid)
{
struct strbuf sig = STRBUF_INIT;
int inspos, copypos;
const char *eoh;
+ const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)];
+ int gpg_sig_header_len = strlen(gpg_sig_header);
/* find the end of the header */
eoh = strstr(buf->buf, "\n\n");
@@ -875,7 +1082,7 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid)
strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
inspos += gpg_sig_header_len;
}
- strbuf_insert(buf, inspos++, " ", 1);
+ strbuf_insertstr(buf, inspos++, " ");
strbuf_insert(buf, inspos, bol, len);
inspos += len;
copypos += len;
@@ -884,19 +1091,32 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid)
return 0;
}
+
+
int parse_signed_commit(const struct commit *commit,
- struct strbuf *payload, struct strbuf *signature)
+ struct strbuf *payload, struct strbuf *signature,
+ const struct git_hash_algo *algop)
{
-
unsigned long size;
const char *buffer = get_commit_buffer(commit, &size);
- int in_signature, saw_signature = -1;
- const char *line, *tail;
+ int ret = parse_buffer_signed_by_header(buffer, size, payload, signature, algop);
+
+ unuse_commit_buffer(commit, buffer);
+ return ret;
+}
+
+int parse_buffer_signed_by_header(const char *buffer,
+ unsigned long size,
+ struct strbuf *payload,
+ struct strbuf *signature,
+ const struct git_hash_algo *algop)
+{
+ int in_signature = 0, saw_signature = 0, other_signature = 0;
+ const char *line, *tail, *p;
+ const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(algop)];
line = buffer;
tail = buffer + size;
- in_signature = 0;
- saw_signature = 0;
while (line < tail) {
const char *sig = NULL;
const char *next = memchr(line, '\n', tail - line);
@@ -904,9 +1124,15 @@ int parse_signed_commit(const struct commit *commit,
next = next ? next + 1 : tail;
if (in_signature && line[0] == ' ')
sig = line + 1;
- else if (starts_with(line, gpg_sig_header) &&
- line[gpg_sig_header_len] == ' ')
- sig = line + gpg_sig_header_len + 1;
+ else if (skip_prefix(line, gpg_sig_header, &p) &&
+ *p == ' ') {
+ sig = line + strlen(gpg_sig_header) + 1;
+ other_signature = 0;
+ }
+ else if (starts_with(line, "gpgsig"))
+ other_signature = 1;
+ else if (other_signature && line[0] != ' ')
+ other_signature = 0;
if (sig) {
strbuf_add(signature, sig, next - sig);
saw_signature = 1;
@@ -915,12 +1141,12 @@ int parse_signed_commit(const struct commit *commit,
if (*line == '\n')
/* dump the whole remainder of the buffer */
next = tail;
- strbuf_add(payload, line, next - line);
+ if (!other_signature)
+ strbuf_add(payload, line, next - line);
in_signature = 0;
}
line = next;
}
- unuse_commit_buffer(commit, buffer);
return saw_signature;
}
@@ -929,33 +1155,48 @@ int remove_signature(struct strbuf *buf)
const char *line = buf->buf;
const char *tail = buf->buf + buf->len;
int in_signature = 0;
- const char *sig_start = NULL;
- const char *sig_end = NULL;
+ struct sigbuf {
+ const char *start;
+ const char *end;
+ } sigs[2], *sigp = &sigs[0];
+ int i;
+ const char *orig_buf = buf->buf;
+
+ memset(sigs, 0, sizeof(sigs));
while (line < tail) {
const char *next = memchr(line, '\n', tail - line);
next = next ? next + 1 : tail;
if (in_signature && line[0] == ' ')
- sig_end = next;
- else if (starts_with(line, gpg_sig_header) &&
- line[gpg_sig_header_len] == ' ') {
- sig_start = line;
- sig_end = next;
- in_signature = 1;
+ sigp->end = next;
+ else if (starts_with(line, "gpgsig")) {
+ int i;
+ for (i = 1; i < GIT_HASH_NALGOS; i++) {
+ const char *p;
+ if (skip_prefix(line, gpg_sig_headers[i], &p) &&
+ *p == ' ') {
+ sigp->start = line;
+ sigp->end = next;
+ in_signature = 1;
+ }
+ }
} else {
if (*line == '\n')
/* dump the whole remainder of the buffer */
next = tail;
+ if (in_signature && sigp - sigs != ARRAY_SIZE(sigs))
+ sigp++;
in_signature = 0;
}
line = next;
}
- if (sig_start)
- strbuf_remove(buf, sig_start - buf->buf, sig_end - sig_start);
+ for (i = ARRAY_SIZE(sigs) - 1; i >= 0; i--)
+ if (sigs[i].start)
+ strbuf_remove(buf, sigs[i].start - orig_buf, sigs[i].end - sigs[i].start);
- return sig_start != NULL;
+ return sigs[0].start != NULL;
}
static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
@@ -963,8 +1204,10 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header
struct merge_remote_desc *desc;
struct commit_extra_header *mergetag;
char *buf;
- unsigned long size, len;
+ unsigned long size;
enum object_type type;
+ struct strbuf payload = STRBUF_INIT;
+ struct strbuf signature = STRBUF_INIT;
desc = merge_remote_util(parent);
if (!desc || !desc->obj)
@@ -972,26 +1215,27 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header
buf = read_object_file(&desc->obj->oid, &type, &size);
if (!buf || type != OBJ_TAG)
goto free_return;
- len = parse_signature(buf, size);
- if (size == len)
+ if (!parse_signature(buf, size, &payload, &signature))
goto free_return;
/*
* We could verify this signature and either omit the tag when
* it does not validate, but the integrator may not have the
- * public key of the signer of the tag he is merging, while a
+ * public key of the signer of the tag being merged, while a
* later auditor may have it while auditing, so let's not run
* verify-signed-buffer here for now...
*
* if (verify_signed_buffer(buf, len, buf + len, size - len, ...))
* warn("warning: signed tag unverified.");
*/
- mergetag = xcalloc(1, sizeof(*mergetag));
+ CALLOC_ARRAY(mergetag, 1);
mergetag->key = xstrdup("mergetag");
mergetag->value = buf;
mergetag->len = size;
**tail = mergetag;
*tail = &mergetag->next;
+ strbuf_release(&payload);
+ strbuf_release(&signature);
return;
free_return:
@@ -1006,10 +1250,12 @@ int check_commit_signature(const struct commit *commit, struct signature_check *
sigc->result = 'N';
- if (parse_signed_commit(commit, &payload, &signature) <= 0)
+ if (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0)
goto out;
- ret = check_signature(payload.buf, payload.len, signature.buf,
- signature.len, sigc);
+
+ sigc->payload_type = SIGNATURE_PAYLOAD_COMMIT;
+ sigc->payload = strbuf_detach(&payload, &sigc->payload_len);
+ ret = check_signature(sigc, signature.buf, signature.len);
out:
strbuf_release(&payload);
@@ -1018,7 +1264,35 @@ int check_commit_signature(const struct commit *commit, struct signature_check *
return ret;
}
+void verify_merge_signature(struct commit *commit, int verbosity,
+ int check_trust)
+{
+ char hex[GIT_MAX_HEXSZ + 1];
+ struct signature_check signature_check;
+ int ret;
+ memset(&signature_check, 0, sizeof(signature_check));
+
+ ret = check_commit_signature(commit, &signature_check);
+ find_unique_abbrev_r(hex, &commit->object.oid, DEFAULT_ABBREV);
+ switch (signature_check.result) {
+ case 'G':
+ if (ret || (check_trust && signature_check.trust_level < TRUST_MARGINAL))
+ die(_("Commit %s has an untrusted GPG signature, "
+ "allegedly by %s."), hex, signature_check.signer);
+ break;
+ case 'B':
+ die(_("Commit %s has a bad GPG signature "
+ "allegedly by %s."), hex, signature_check.signer);
+ default: /* 'N' */
+ die(_("Commit %s does not have a GPG signature."), hex);
+ }
+ if (verbosity >= 0 && signature_check.result == 'G')
+ printf(_("Commit %s has a good GPG signature by %s\n"),
+ hex, signature_check.signer);
+
+ signature_check_clear(&signature_check);
+}
void append_merge_tag_headers(struct commit_list *parents,
struct commit_extra_header ***tail)
@@ -1120,7 +1394,7 @@ static struct commit_extra_header *read_commit_extra_header_lines(
excluded_header_field(line, eof - line, exclude))
continue;
- it = xcalloc(1, sizeof(*it));
+ CALLOC_ARRAY(it, 1);
it->key = xmemdupz(line, eof-line);
*tail = it;
tail = &it->next;
@@ -1151,8 +1425,8 @@ int commit_tree(const char *msg, size_t msg_len, const struct object_id *tree,
int result;
append_merge_tag_headers(parents, &tail);
- result = commit_tree_extended(msg, msg_len, tree, parents, ret,
- author, sign_commit, extra);
+ result = commit_tree_extended(msg, msg_len, tree, parents, ret, author,
+ NULL, sign_commit, extra);
free_commit_extra_headers(extra);
return result;
}
@@ -1270,12 +1544,13 @@ static int verify_utf8(struct strbuf *buf)
static const char commit_utf8_warn[] =
N_("Warning: commit message did not conform to UTF-8.\n"
"You may want to amend it after fixing the message, or set the config\n"
- "variable i18n.commitencoding to the encoding your project uses.\n");
+ "variable i18n.commitEncoding to the encoding your project uses.\n");
int commit_tree_extended(const char *msg, size_t msg_len,
const struct object_id *tree,
struct commit_list *parents, struct object_id *ret,
- const char *author, const char *sign_commit,
+ const char *author, const char *committer,
+ const char *sign_commit,
struct commit_extra_header *extra)
{
int result;
@@ -1308,7 +1583,9 @@ int commit_tree_extended(const char *msg, size_t msg_len,
if (!author)
author = git_author_info(IDENT_STRICT);
strbuf_addf(&buffer, "author %s\n", author);
- strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_STRICT));
+ if (!committer)
+ committer = git_committer_info(IDENT_STRICT);
+ strbuf_addf(&buffer, "committer %s\n", committer);
if (!encoding_is_utf8)
strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding);
@@ -1325,12 +1602,12 @@ int commit_tree_extended(const char *msg, size_t msg_len,
if (encoding_is_utf8 && !verify_utf8(&buffer))
fprintf(stderr, _(commit_utf8_warn));
- if (sign_commit && do_sign_commit(&buffer, sign_commit)) {
+ if (sign_commit && sign_with_header(&buffer, sign_commit)) {
result = -1;
goto out;
}
- result = write_object_file(buffer.buf, buffer.len, commit_type, ret);
+ result = write_object_file(buffer.buf, buffer.len, OBJ_COMMIT, ret);
out:
strbuf_release(&buffer);
return result;
@@ -1394,12 +1671,20 @@ struct commit_list **commit_list_append(struct commit *commit,
return &new_commit->next;
}
-const char *find_commit_header(const char *msg, const char *key, size_t *out_len)
+const char *find_header_mem(const char *msg, size_t len,
+ const char *key, size_t *out_len)
{
int key_len = strlen(key);
const char *line = msg;
- while (line) {
+ /*
+ * NEEDSWORK: It's possible for strchrnul() to scan beyond the range
+ * given by len. However, current callers are safe because they compute
+ * len by scanning a NUL-terminated block of memory starting at msg.
+ * Nonetheless, it would be better to ensure the function does not look
+ * at msg beyond the len provided by the caller.
+ */
+ while (line && line < msg + len) {
const char *eol = strchrnul(line, '\n');
if (line == eol)
@@ -1416,9 +1701,13 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len
return NULL;
}
+const char *find_commit_header(const char *msg, const char *key, size_t *out_len)
+{
+ return find_header_mem(msg, strlen(msg), key, out_len);
+}
/*
* Inspect the given string and determine the true "end" of the log message, in
- * order to find where to put a new Signed-off-by: line. Ignored are
+ * order to find where to put a new Signed-off-by trailer. Ignored are
* trailing comment lines and blank lines. To support "git commit -s
* --amend" on an existing commit, we also ignore "Conflicts:". To
* support "git commit -v", we truncate at cut lines.
@@ -1426,10 +1715,10 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len
* Returns the number of bytes from the tail to ignore, to be fed as
* the second parameter to append_signoff().
*/
-int ignore_non_trailer(const char *buf, size_t len)
+size_t ignore_non_trailer(const char *buf, size_t len)
{
- int boc = 0;
- int bol = 0;
+ size_t boc = 0;
+ size_t bol = 0;
int in_old_conflicts_block = 0;
size_t cutoff = wt_status_locate_end(buf, len);
@@ -1461,3 +1750,27 @@ int ignore_non_trailer(const char *buf, size_t len)
}
return boc ? len - boc : len - cutoff;
}
+
+int run_commit_hook(int editor_is_used, const char *index_file,
+ int *invoked_hook, const char *name, ...)
+{
+ struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
+ va_list args;
+ const char *arg;
+
+ strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file);
+
+ /*
+ * Let the hook know that no editor will be launched.
+ */
+ if (!editor_is_used)
+ strvec_push(&opt.env, "GIT_EDITOR=:");
+
+ va_start(args, name);
+ while ((arg = va_arg(args, const char *)))
+ strvec_push(&opt.args, arg);
+ va_end(args);
+
+ opt.invoked_hook = invoked_hook;
+ return run_hooks_opt(name, &opt);
+}