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