]> git.proxmox.com Git - libgit2.git/blame - src/commit.c
Merge pull request #3759 from libgit2/cmn/faster-header
[libgit2.git] / src / commit.c
CommitLineData
06160502 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
06160502 3 *
bb742ede
VM
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
06160502
SP
6 */
7
44908fe7
VM
8#include "git2/common.h"
9#include "git2/object.h"
10#include "git2/repository.h"
638c2ca4 11#include "git2/signature.h"
9233b3de 12#include "git2/sys/commit.h"
58519018 13
64a47c01 14#include "common.h"
72a3fe42 15#include "odb.h"
4f0adcd0 16#include "commit.h"
638c2ca4 17#include "signature.h"
458b9450 18#include "message.h"
a612a25f 19#include "refs.h"
ef63bab3 20#include "object.h"
47cb42da 21#include "oidarray.h"
06160502 22
78606263 23void git_commit__free(void *_commit)
40721f6b 24{
78606263
RB
25 git_commit *commit = _commit;
26
9abc78ae 27 git_array_clear(commit->parent_ids);
52f2390b 28
638c2ca4
VM
29 git_signature_free(commit->author);
30 git_signature_free(commit->committer);
58519018 31
f094f905 32 git__free(commit->raw_header);
598f069b 33 git__free(commit->raw_message);
3286c408 34 git__free(commit->message_encoding);
300d192f 35 git__free(commit->summary);
7f8fe1d4 36 git__free(commit->body);
9abc78ae 37
3286c408 38 git__free(commit);
40721f6b
VM
39}
40
47cb42da
CMN
41static int git_commit__create_buffer_internal(
42 git_buf *out,
9233b3de 43 git_repository *repo,
9233b3de
RB
44 const git_signature *author,
45 const git_signature *committer,
46 const char *message_encoding,
47 const char *message,
48 const git_oid *tree,
47cb42da 49 git_array_oid_t *parents)
72a3fe42 50{
80c29fe9 51 size_t i = 0;
80c29fe9 52 const git_oid *parent;
72a3fe42 53
47cb42da 54 assert(out && repo && tree);
9233b3de 55
47cb42da 56 git_oid__writebuf(out, "tree ", tree);
ef63bab3 57
47cb42da
CMN
58 for (i = 0; i < git_array_size(*parents); i++) {
59 parent = git_array_get(*parents, i);
60 git_oid__writebuf(out, "parent ", parent);
217c029b 61 }
217c029b 62
47cb42da
CMN
63 git_signature__writebuf(out, "author ", author);
64 git_signature__writebuf(out, "committer ", committer);
65
66 if (message_encoding != NULL)
67 git_buf_printf(out, "encoding %s\n", message_encoding);
68
69 git_buf_putc(out, '\n');
217c029b 70
47cb42da
CMN
71 if (git_buf_puts(out, message) < 0)
72 goto on_error;
73
74 return 0;
75
76on_error:
77 git_buf_free(out);
78 return -1;
79}
d5afc039 80
47cb42da
CMN
81static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree,
82 git_commit_parent_callback parent_cb, void *parent_payload,
83 const git_oid *current_id, bool validate)
84{
85 size_t i;
86 int error;
87 git_oid *parent_cpy;
88 const git_oid *parent;
89
90 if (validate && !git_object__is_valid(repo, tree, GIT_OBJ_TREE))
91 return -1;
92
93 i = 0;
217c029b 94 while ((parent = parent_cb(i, parent_payload)) != NULL) {
ef63bab3
ET
95 if (validate && !git_object__is_valid(repo, parent, GIT_OBJ_COMMIT)) {
96 error = -1;
97 goto on_error;
98 }
99
47cb42da
CMN
100 parent_cpy = git_array_alloc(*parents);
101 GITERR_CHECK_ALLOC(parent_cpy);
102
103 git_oid_cpy(parent_cpy, parent);
217c029b
CMN
104 i++;
105 }
106
47cb42da 107 if (current_id && git_oid_cmp(current_id, git_array_get(*parents, 0))) {
217c029b 108 giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent");
47cb42da
CMN
109 error = GIT_EMODIFIED;
110 goto on_error;
217c029b 111 }
0c3596f1 112
47cb42da 113 return 0;
0c3596f1 114
47cb42da
CMN
115on_error:
116 git_array_clear(*parents);
117 return error;
118}
5ae2f0c0 119
47cb42da
CMN
120static int git_commit__create_internal(
121 git_oid *id,
122 git_repository *repo,
123 const char *update_ref,
124 const git_signature *author,
125 const git_signature *committer,
126 const char *message_encoding,
127 const char *message,
128 const git_oid *tree,
129 git_commit_parent_callback parent_cb,
130 void *parent_payload,
131 bool validate)
132{
133 int error;
134 git_odb *odb;
135 git_reference *ref = NULL;
136 git_buf buf = GIT_BUF_INIT;
137 const git_oid *current_id = NULL;
138 git_array_oid_t parents = GIT_ARRAY_INIT;
0c3596f1 139
47cb42da
CMN
140 if (update_ref) {
141 error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
142 if (error < 0 && error != GIT_ENOTFOUND)
143 return error;
144 }
145 giterr_clear();
146
147 if (ref)
148 current_id = git_reference_target(ref);
149
150 if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0)
151 goto cleanup;
152
153 error = git_commit__create_buffer_internal(&buf, repo, author, committer,
154 message_encoding, message, tree,
155 &parents);
156
157 if (error < 0)
158 goto cleanup;
458b9450 159
73fe6a8e 160 if (git_repository_odb__weakptr(&odb, repo) < 0)
47cb42da 161 goto cleanup;
75abd2b9 162
47cb42da
CMN
163 if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJ_COMMIT) < 0)
164 goto cleanup;
72a3fe42 165
57450775 166
0adb0606 167 if (update_ref != NULL) {
a612a25f 168 error = git_reference__update_for_commit(
659cf202 169 repo, ref, update_ref, id, "commit");
47cb42da 170 goto cleanup;
0adb0606 171 }
4c7a5e9e 172
47cb42da
CMN
173cleanup:
174 git_array_clear(parents);
175 git_reference_free(ref);
176 git_buf_free(&buf);
177 return error;
0c3596f1
VM
178}
179
ef63bab3
ET
180int git_commit_create_from_callback(
181 git_oid *id,
182 git_repository *repo,
183 const char *update_ref,
184 const git_signature *author,
185 const git_signature *committer,
186 const char *message_encoding,
187 const char *message,
188 const git_oid *tree,
189 git_commit_parent_callback parent_cb,
190 void *parent_payload)
191{
192 return git_commit__create_internal(
193 id, repo, update_ref, author, committer, message_encoding, message,
194 tree, parent_cb, parent_payload, true);
195}
196
80c29fe9
RB
197typedef struct {
198 size_t total;
199 va_list args;
200} commit_parent_varargs;
201
202static const git_oid *commit_parent_from_varargs(size_t curr, void *payload)
203{
204 commit_parent_varargs *data = payload;
205 const git_commit *commit;
206 if (curr >= data->total)
207 return NULL;
208 commit = va_arg(data->args, const git_commit *);
209 return commit ? git_commit_id(commit) : NULL;
210}
211
212int git_commit_create_v(
213 git_oid *id,
214 git_repository *repo,
215 const char *update_ref,
216 const git_signature *author,
217 const git_signature *committer,
218 const char *message_encoding,
219 const char *message,
220 const git_tree *tree,
221 size_t parent_count,
222 ...)
223{
224 int error = 0;
225 commit_parent_varargs data;
226
227 assert(tree && git_tree_owner(tree) == repo);
228
229 data.total = parent_count;
230 va_start(data.args, parent_count);
231
ef63bab3 232 error = git_commit__create_internal(
80c29fe9
RB
233 id, repo, update_ref, author, committer,
234 message_encoding, message, git_tree_id(tree),
ef63bab3 235 commit_parent_from_varargs, &data, false);
80c29fe9
RB
236
237 va_end(data.args);
238 return error;
239}
240
241typedef struct {
242 size_t total;
243 const git_oid **parents;
244} commit_parent_oids;
245
246static const git_oid *commit_parent_from_ids(size_t curr, void *payload)
247{
248 commit_parent_oids *data = payload;
249 return (curr < data->total) ? data->parents[curr] : NULL;
250}
251
252int git_commit_create_from_ids(
253 git_oid *id,
254 git_repository *repo,
255 const char *update_ref,
256 const git_signature *author,
257 const git_signature *committer,
258 const char *message_encoding,
259 const char *message,
260 const git_oid *tree,
261 size_t parent_count,
262 const git_oid *parents[])
263{
264 commit_parent_oids data = { parent_count, parents };
265
ef63bab3 266 return git_commit__create_internal(
80c29fe9
RB
267 id, repo, update_ref, author, committer,
268 message_encoding, message, tree,
ef63bab3 269 commit_parent_from_ids, &data, true);
80c29fe9
RB
270}
271
272typedef struct {
273 size_t total;
274 const git_commit **parents;
275 git_repository *repo;
276} commit_parent_data;
277
278static const git_oid *commit_parent_from_array(size_t curr, void *payload)
279{
280 commit_parent_data *data = payload;
281 const git_commit *commit;
282 if (curr >= data->total)
283 return NULL;
284 commit = data->parents[curr];
285 if (git_commit_owner(commit) != data->repo)
286 return NULL;
287 return git_commit_id(commit);
288}
289
92550398 290int git_commit_create(
80c29fe9 291 git_oid *id,
9233b3de
RB
292 git_repository *repo,
293 const char *update_ref,
294 const git_signature *author,
295 const git_signature *committer,
296 const char *message_encoding,
297 const char *message,
298 const git_tree *tree,
80c29fe9 299 size_t parent_count,
9233b3de 300 const git_commit *parents[])
92550398 301{
80c29fe9 302 commit_parent_data data = { parent_count, parents, repo };
92550398 303
80c29fe9 304 assert(tree && git_tree_owner(tree) == repo);
92550398 305
ef63bab3 306 return git_commit__create_internal(
80c29fe9
RB
307 id, repo, update_ref, author, committer,
308 message_encoding, message, git_tree_id(tree),
ef63bab3 309 commit_parent_from_array, &data, false);
80c29fe9 310}
92550398 311
80c29fe9
RB
312static const git_oid *commit_parent_for_amend(size_t curr, void *payload)
313{
314 const git_commit *commit_to_amend = payload;
315 if (curr >= git_array_size(commit_to_amend->parent_ids))
316 return NULL;
317 return git_array_get(commit_to_amend->parent_ids, curr);
318}
92550398 319
80c29fe9
RB
320int git_commit_amend(
321 git_oid *id,
322 const git_commit *commit_to_amend,
323 const char *update_ref,
324 const git_signature *author,
325 const git_signature *committer,
326 const char *message_encoding,
327 const char *message,
328 const git_tree *tree)
329{
330 git_repository *repo;
331 git_oid tree_id;
217c029b
CMN
332 git_reference *ref;
333 int error;
80c29fe9
RB
334
335 assert(id && commit_to_amend);
336
337 repo = git_commit_owner(commit_to_amend);
338
339 if (!author)
340 author = git_commit_author(commit_to_amend);
341 if (!committer)
342 committer = git_commit_committer(commit_to_amend);
343 if (!message_encoding)
344 message_encoding = git_commit_message_encoding(commit_to_amend);
345 if (!message)
346 message = git_commit_message(commit_to_amend);
347
348 if (!tree) {
349 git_tree *old_tree;
350 GITERR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) );
351 git_oid_cpy(&tree_id, git_tree_id(old_tree));
352 git_tree_free(old_tree);
353 } else {
354 assert(git_tree_owner(tree) == repo);
355 git_oid_cpy(&tree_id, git_tree_id(tree));
356 }
9233b3de 357
217c029b
CMN
358 if (update_ref) {
359 if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0)
360 return error;
361
362 if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) {
363 git_reference_free(ref);
364 giterr_set(GITERR_REFERENCE, "commit to amend is not the tip of the given branch");
365 return -1;
366 }
367 }
368
ef63bab3 369 error = git_commit__create_internal(
217c029b 370 id, repo, NULL, author, committer, message_encoding, message,
ef63bab3 371 &tree_id, commit_parent_for_amend, (void *)commit_to_amend, false);
217c029b
CMN
372
373 if (!error && update_ref) {
a612a25f 374 error = git_reference__update_for_commit(
659cf202 375 repo, ref, NULL, id, "commit");
217c029b
CMN
376 git_reference_free(ref);
377 }
378
379 return error;
92550398
JW
380}
381
3f27127d 382int git_commit__parse(void *_commit, git_odb_object *odb_obj)
417f0abc 383{
78606263 384 git_commit *commit = _commit;
f094f905
RB
385 const char *buffer_start = git_odb_object_data(odb_obj), *buffer;
386 const char *buffer_end = buffer_start + git_odb_object_size(odb_obj);
cfbe4be3 387 git_oid parent_id;
584f2d30 388 size_t header_len;
65d69fe8 389 git_signature dummy_sig;
417f0abc 390
a6563619 391 buffer = buffer_start;
f094f905 392
a6563619
CMN
393 /* Allocate for one, which will allow not to realloc 90% of the time */
394 git_array_init_to_size(commit->parent_ids, 1);
9abc78ae 395 GITERR_CHECK_ARRAY(commit->parent_ids);
eec95235 396
a6563619 397 /* The tree is always the first field */
cfbe4be3 398 if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
73fe6a8e 399 goto bad_buffer;
225fe215 400
9b3577ed 401 /*
9b3577ed
VM
402 * TODO: commit grafts!
403 */
417f0abc 404
cfbe4be3 405 while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
9abc78ae 406 git_oid *new_id = git_array_alloc(commit->parent_ids);
cfbe4be3 407 GITERR_CHECK_ALLOC(new_id);
73fe6a8e 408
cfbe4be3 409 git_oid_cpy(new_id, &parent_id);
9b3577ed 410 }
417f0abc 411
6b2a1941 412 commit->author = git__malloc(sizeof(git_signature));
73fe6a8e
VM
413 GITERR_CHECK_ALLOC(commit->author);
414
415 if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0)
416 return -1;
52f2390b 417
65d69fe8
CMN
418 /* Some tools create multiple author fields, ignore the extra ones */
419 while ((size_t)(buffer_end - buffer) >= strlen("author ") && !git__prefixcmp(buffer, "author ")) {
420 if (git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n') < 0)
421 return -1;
422
423 git__free(dummy_sig.name);
424 git__free(dummy_sig.email);
425 }
426
58519018 427 /* Always parse the committer; we need the commit time */
638c2ca4 428 commit->committer = git__malloc(sizeof(git_signature));
73fe6a8e
VM
429 GITERR_CHECK_ALLOC(commit->committer);
430
431 if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
432 return -1;
5ae2f0c0 433
f094f905
RB
434 /* Parse add'l header entries */
435 while (buffer < buffer_end) {
291090a0 436 const char *eoln = buffer;
a6563619
CMN
437 if (buffer[-1] == '\n' && buffer[0] == '\n')
438 break;
439
291090a0
RB
440 while (eoln < buffer_end && *eoln != '\n')
441 ++eoln;
5ae2f0c0 442
291090a0
RB
443 if (git__prefixcmp(buffer, "encoding ") == 0) {
444 buffer += strlen("encoding ");
5ae2f0c0 445
291090a0
RB
446 commit->message_encoding = git__strndup(buffer, eoln - buffer);
447 GITERR_CHECK_ALLOC(commit->message_encoding);
448 }
5ae2f0c0 449
d47c6aab
CMN
450 if (eoln < buffer_end && *eoln == '\n')
451 ++eoln;
291090a0 452 buffer = eoln;
5ae2f0c0 453 }
58519018 454
a6563619
CMN
455 header_len = buffer - buffer_start;
456 commit->raw_header = git__strndup(buffer_start, header_len);
457 GITERR_CHECK_ALLOC(commit->raw_header);
f094f905 458
a6563619
CMN
459 /* point "buffer" to data after header, +1 for the final LF */
460 buffer = buffer_start + header_len + 1;
52f2390b 461
f094f905 462 /* extract commit message */
04f78802 463 if (buffer <= buffer_end) {
598f069b 464 commit->raw_message = git__strndup(buffer, buffer_end - buffer);
465 GITERR_CHECK_ALLOC(commit->raw_message);
52f2390b 466 }
417f0abc 467
73fe6a8e
VM
468 return 0;
469
470bad_buffer:
471 giterr_set(GITERR_OBJECT, "Failed to parse bad commit object");
472 return -1;
417f0abc 473}
4caa8962 474
6b2a1941 475#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
cfbe4be3 476 _rvalue git_commit_##_name(const git_commit *commit) \
0c3596f1 477 {\
58519018 478 assert(commit); \
6b2a1941 479 return _return; \
0c3596f1
VM
480 }
481
6b2a1941
VM
482GIT_COMMIT_GETTER(const git_signature *, author, commit->author)
483GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer)
598f069b 484GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message)
5ae2f0c0 485GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
f094f905 486GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header)
56d8ca26 487GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
6b2a1941 488GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
9abc78ae 489GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids))
c8e02b87 490GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id)
6b2a1941 491
598f069b 492const char *git_commit_message(const git_commit *commit)
493{
be0a1a79 494 const char *message;
598f069b 495
496 assert(commit);
497
be0a1a79
PH
498 message = commit->raw_message;
499
598f069b 500 /* trim leading newlines from raw message */
501 while (*message && *message == '\n')
502 ++message;
503
504 return message;
505}
506
300d192f
ET
507const char *git_commit_summary(git_commit *commit)
508{
509 git_buf summary = GIT_BUF_INIT;
510 const char *msg, *space;
f5f96a23 511 bool space_contains_newline = false;
300d192f
ET
512
513 assert(commit);
514
515 if (!commit->summary) {
516 for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) {
f5f96a23
SR
517 char next_character = msg[0];
518 /* stop processing at the end of the first paragraph */
519 if (next_character == '\n' && (!msg[1] || msg[1] == '\n'))
300d192f 520 break;
f5f96a23
SR
521 /* record the beginning of contiguous whitespace runs */
522 else if (git__isspace(next_character)) {
523 if(space == NULL) {
524 space = msg;
525 space_contains_newline = false;
526 }
527 space_contains_newline |= next_character == '\n';
528 }
529 /* the next character is non-space */
530 else {
531 /* process any recorded whitespace */
532 if (space) {
533 if(space_contains_newline)
534 git_buf_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */
535 else
536 git_buf_put(&summary, space, (msg - space)); /* otherwise copy it */
537 space = NULL;
538 }
539 /* copy the next character */
540 git_buf_putc(&summary, next_character);
541 }
300d192f
ET
542 }
543
80c29fe9
RB
544 commit->summary = git_buf_detach(&summary);
545 if (!commit->summary)
238e8149 546 commit->summary = git__strdup("");
300d192f
ET
547 }
548
549 return commit->summary;
550}
551
7f8fe1d4
PS
552const char *git_commit_body(git_commit *commit)
553{
554 const char *msg, *end;
555
556 assert(commit);
557
558 if (!commit->body) {
559 /* search for end of summary */
560 for (msg = git_commit_message(commit); *msg; ++msg)
561 if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n'))
562 break;
563
564 /* trim leading and trailing whitespace */
565 for (; *msg; ++msg)
566 if (!git__isspace(*msg))
567 break;
568 for (end = msg + strlen(msg) - 1; msg <= end; --end)
569 if (!git__isspace(*end))
570 break;
571
572 if (*msg)
573 commit->body = git__strndup(msg, end - msg + 1);
574 }
575
576 return commit->body;
577}
578
cfbe4be3 579int git_commit_tree(git_tree **tree_out, const git_commit *commit)
6b2a1941
VM
580{
581 assert(commit);
cfbe4be3 582 return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
6b2a1941 583}
57450775 584
58206c9a
RB
585const git_oid *git_commit_parent_id(
586 const git_commit *commit, unsigned int n)
2b92a154 587{
588 assert(commit);
589
9abc78ae 590 return git_array_get(commit->parent_ids, n);
2b92a154 591}
592
58206c9a
RB
593int git_commit_parent(
594 git_commit **parent, const git_commit *commit, unsigned int n)
48c27f86 595{
cfbe4be3 596 const git_oid *parent_id;
48c27f86
VM
597 assert(commit);
598
cfbe4be3
VM
599 parent_id = git_commit_parent_id(commit, n);
600 if (parent_id == NULL) {
3aa351ea
CMN
601 giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
602 return GIT_ENOTFOUND;
603 }
48c27f86 604
cfbe4be3 605 return git_commit_lookup(parent, commit->object.repo, parent_id);
48c27f86 606}
b1aca6ea 607
608int git_commit_nth_gen_ancestor(
609 git_commit **ancestor,
610 const git_commit *commit,
611 unsigned int n)
612{
e583334c 613 git_commit *current, *parent = NULL;
b1aca6ea 614 int error;
615
616 assert(ancestor && commit);
617
f0224772 618 if (git_commit_dup(&current, (git_commit *)commit) < 0)
7c1ee212 619 return -1;
b1aca6ea 620
7c1ee212
CMN
621 if (n == 0) {
622 *ancestor = current;
623 return 0;
624 }
b1aca6ea 625
626 while (n--) {
7c1ee212 627 error = git_commit_parent(&parent, current, 0);
b1aca6ea 628
7c1ee212 629 git_commit_free(current);
b1aca6ea 630
631 if (error < 0)
632 return error;
633
634 current = parent;
635 }
636
637 *ancestor = parent;
638 return 0;
639}
a3f42fe8
CMN
640
641int git_commit_header_field(git_buf *out, const git_commit *commit, const char *field)
642{
f55eca16 643 const char *eol, *buf = commit->raw_header;
a3f42fe8
CMN
644
645 git_buf_sanitize(out);
f55eca16 646
460ae11f 647 while ((eol = strchr(buf, '\n'))) {
f55eca16
CMN
648 /* We can skip continuations here */
649 if (buf[0] == ' ') {
650 buf = eol + 1;
651 continue;
652 }
653
654 /* Skip until we find the field we're after */
655 if (git__prefixcmp(buf, field)) {
656 buf = eol + 1;
a3f42fe8
CMN
657 continue;
658 }
659
f55eca16
CMN
660 buf += strlen(field);
661 /* Check that we're not matching a prefix but the field itself */
662 if (buf[0] != ' ') {
663 buf = eol + 1;
a3f42fe8
CMN
664 continue;
665 }
a3f42fe8 666
f55eca16 667 buf++; /* skip the SP */
a3f42fe8 668
f55eca16 669 git_buf_put(out, buf, eol - buf);
a3f42fe8
CMN
670 if (git_buf_oom(out))
671 goto oom;
672
673 /* If the next line starts with SP, it's multi-line, we must continue */
674 while (eol[1] == ' ') {
675 git_buf_putc(out, '\n');
f55eca16
CMN
676 buf = eol + 2;
677 eol = strchr(buf, '\n');
a3f42fe8
CMN
678 if (!eol)
679 goto malformed;
680
f55eca16 681 git_buf_put(out, buf, eol - buf);
a3f42fe8
CMN
682 }
683
684 if (git_buf_oom(out))
685 goto oom;
686
687 return 0;
688 }
689
f55eca16 690 giterr_set(GITERR_OBJECT, "no such field '%s'", field);
a3f42fe8
CMN
691 return GIT_ENOTFOUND;
692
693malformed:
694 giterr_set(GITERR_OBJECT, "malformed header");
695 return -1;
696oom:
697 giterr_set_oom();
698 return -1;
699}
a65afb75
CMN
700
701int git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field)
702{
703 git_odb_object *obj;
704 git_odb *odb;
705 const char *buf;
706 const char *h, *eol;
707 int error;
708
709 git_buf_sanitize(signature);
710 git_buf_sanitize(signed_data);
711
712 if (!field)
713 field = "gpgsig";
714
715 if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
716 return error;
717
718 if ((error = git_odb_read(&obj, odb, commit_id)) < 0)
719 return error;
720
eadd0f05
CMN
721 if (obj->cached.type != GIT_OBJ_COMMIT) {
722 giterr_set(GITERR_INVALID, "the requested type does not match the type in ODB");
723 error = GIT_ENOTFOUND;
724 goto cleanup;
725 }
726
a65afb75
CMN
727 buf = git_odb_object_data(obj);
728
bf804d40 729 while ((h = strchr(buf, '\n')) && h[1] != '\0') {
a65afb75
CMN
730 h++;
731 if (git__prefixcmp(buf, field)) {
732 if (git_buf_put(signed_data, buf, h - buf) < 0)
733 return -1;
734
735 buf = h;
736 continue;
737 }
738
739 h = buf;
740 h += strlen(field);
741 eol = strchr(h, '\n');
742 if (h[0] != ' ') {
743 buf = h;
744 continue;
745 }
746 if (!eol)
747 goto malformed;
748
749 h++; /* skip the SP */
750
751 git_buf_put(signature, h, eol - h);
752 if (git_buf_oom(signature))
753 goto oom;
754
755 /* If the next line starts with SP, it's multi-line, we must continue */
756 while (eol[1] == ' ') {
757 git_buf_putc(signature, '\n');
758 h = eol + 2;
759 eol = strchr(h, '\n');
760 if (!eol)
761 goto malformed;
762
763 git_buf_put(signature, h, eol - h);
764 }
765
766 if (git_buf_oom(signature))
767 goto oom;
768
769 git_odb_object_free(obj);
770 return git_buf_puts(signed_data, eol+1);
771 }
772
eadd0f05 773 giterr_set(GITERR_OBJECT, "this commit is not signed");
a65afb75
CMN
774 error = GIT_ENOTFOUND;
775 goto cleanup;
776
777malformed:
778 giterr_set(GITERR_OBJECT, "malformed header");
779 error = -1;
780 goto cleanup;
781oom:
782 giterr_set_oom();
783 error = -1;
784 goto cleanup;
785
786cleanup:
787 git_odb_object_free(obj);
788 git_buf_clear(signature);
789 git_buf_clear(signed_data);
790 return error;
791}
47cb42da
CMN
792
793int git_commit_create_buffer(git_buf *out,
794 git_repository *repo,
795 const git_signature *author,
796 const git_signature *committer,
797 const char *message_encoding,
798 const char *message,
799 const git_tree *tree,
800 size_t parent_count,
801 const git_commit *parents[])
802{
803 int error;
804 commit_parent_data data = { parent_count, parents, repo };
805 git_array_oid_t parents_arr = GIT_ARRAY_INIT;
806 const git_oid *tree_id;
807
808 assert(tree && git_tree_owner(tree) == repo);
809
810 tree_id = git_tree_id(tree);
811
812 if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0)
813 return error;
814
815 error = git_commit__create_buffer_internal(
816 out, repo, author, committer,
817 message_encoding, message, tree_id,
818 &parents_arr);
819
820 git_array_clear(parents_arr);
821 return error;
822}
02d61a3b
CMN
823
824/**
825 * Append to 'out' properly marking continuations when there's a newline in 'content'
826 */
827static void format_header_field(git_buf *out, const char *field, const char *content)
828{
829 const char *lf;
830
831 assert(out && field && content);
832
833 git_buf_puts(out, field);
834 git_buf_putc(out, ' ');
835
836 while ((lf = strchr(content, '\n')) != NULL) {
837 git_buf_put(out, content, lf - content);
838 git_buf_puts(out, "\n ");
839 content = lf + 1;
840 }
841
842 git_buf_puts(out, content);
843 git_buf_putc(out, '\n');
844}
845
846int git_commit_create_with_signature(
847 git_oid *out,
848 git_repository *repo,
849 const char *commit_content,
850 const char *signature,
851 const char *signature_field)
852{
853 git_odb *odb;
854 int error = 0;
855 const char *field;
856 const char *header_end;
857 git_buf commit = GIT_BUF_INIT;
858
859 /* We start by identifying the end of the commit header */
860 header_end = strstr(commit_content, "\n\n");
861 if (!header_end) {
862 giterr_set(GITERR_INVALID, "malformed commit contents");
863 return -1;
864 }
865
866 field = signature_field ? signature_field : "gpgsig";
867
868 /* The header ends after the first LF */
869 header_end++;
870 git_buf_put(&commit, commit_content, header_end - commit_content);
871 format_header_field(&commit, field, signature);
872 git_buf_puts(&commit, header_end);
873
874 if (git_buf_oom(&commit))
875 return -1;
876
877 if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
878 goto cleanup;
879
880 if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT)) < 0)
881 goto cleanup;
882
883cleanup:
884 git_buf_free(&commit);
885 return error;
886}