]>
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 | |
e579e0f7 | 17 | #include "buf.h" |
72a3fe42 | 18 | #include "odb.h" |
4f0adcd0 | 19 | #include "commit.h" |
638c2ca4 | 20 | #include "signature.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 | 44 | static int git_commit__create_buffer_internal( |
e579e0f7 | 45 | git_str *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) | |
e579e0f7 | 70 | git_str_printf(out, "encoding %s\n", message_encoding); |
47cb42da | 71 | |
e579e0f7 | 72 | git_str_putc(out, '\n'); |
217c029b | 73 | |
e579e0f7 | 74 | if (git_str_puts(out, message) < 0) |
47cb42da CMN |
75 | goto on_error; |
76 | ||
77 | return 0; | |
78 | ||
79 | on_error: | |
e579e0f7 | 80 | git_str_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; | |
e579e0f7 | 139 | git_str buf = GIT_STR_INIT; |
47cb42da CMN |
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); | |
e579e0f7 | 182 | git_str_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; |
e579e0f7 | 398 | int error; |
417f0abc | 399 | |
c25aa7cd PP |
400 | GIT_ASSERT_ARG(commit); |
401 | GIT_ASSERT_ARG(data); | |
22a2d3d5 | 402 | |
a6563619 | 403 | buffer = buffer_start; |
f094f905 | 404 | |
a6563619 CMN |
405 | /* Allocate for one, which will allow not to realloc 90% of the time */ |
406 | git_array_init_to_size(commit->parent_ids, 1); | |
ac3d33df | 407 | GIT_ERROR_CHECK_ARRAY(commit->parent_ids); |
eec95235 | 408 | |
a6563619 | 409 | /* The tree is always the first field */ |
22a2d3d5 UG |
410 | if (!(flags & GIT_COMMIT_PARSE_QUICK)) { |
411 | if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) | |
412 | goto bad_buffer; | |
413 | } else { | |
414 | size_t tree_len = strlen("tree ") + GIT_OID_HEXSZ + 1; | |
415 | if (buffer + tree_len > buffer_end) | |
416 | goto bad_buffer; | |
417 | buffer += tree_len; | |
418 | } | |
225fe215 | 419 | |
9b3577ed | 420 | /* |
9b3577ed VM |
421 | * TODO: commit grafts! |
422 | */ | |
417f0abc | 423 | |
cfbe4be3 | 424 | while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { |
9abc78ae | 425 | git_oid *new_id = git_array_alloc(commit->parent_ids); |
ac3d33df | 426 | GIT_ERROR_CHECK_ALLOC(new_id); |
73fe6a8e | 427 | |
cfbe4be3 | 428 | git_oid_cpy(new_id, &parent_id); |
9b3577ed | 429 | } |
417f0abc | 430 | |
22a2d3d5 UG |
431 | if (!(flags & GIT_COMMIT_PARSE_QUICK)) { |
432 | commit->author = git__malloc(sizeof(git_signature)); | |
433 | GIT_ERROR_CHECK_ALLOC(commit->author); | |
73fe6a8e | 434 | |
e579e0f7 MB |
435 | if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n')) < 0) |
436 | return error; | |
22a2d3d5 | 437 | } |
52f2390b | 438 | |
65d69fe8 | 439 | /* Some tools create multiple author fields, ignore the extra ones */ |
ac3d33df | 440 | while (!git__prefixncmp(buffer, buffer_end - buffer, "author ")) { |
e579e0f7 MB |
441 | if ((error = git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n')) < 0) |
442 | return error; | |
65d69fe8 CMN |
443 | |
444 | git__free(dummy_sig.name); | |
445 | git__free(dummy_sig.email); | |
446 | } | |
447 | ||
58519018 | 448 | /* Always parse the committer; we need the commit time */ |
638c2ca4 | 449 | commit->committer = git__malloc(sizeof(git_signature)); |
ac3d33df | 450 | GIT_ERROR_CHECK_ALLOC(commit->committer); |
73fe6a8e | 451 | |
e579e0f7 MB |
452 | if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n')) < 0) |
453 | return error; | |
5ae2f0c0 | 454 | |
22a2d3d5 UG |
455 | if (flags & GIT_COMMIT_PARSE_QUICK) |
456 | return 0; | |
457 | ||
f094f905 RB |
458 | /* Parse add'l header entries */ |
459 | while (buffer < buffer_end) { | |
291090a0 | 460 | const char *eoln = buffer; |
a6563619 CMN |
461 | if (buffer[-1] == '\n' && buffer[0] == '\n') |
462 | break; | |
463 | ||
291090a0 RB |
464 | while (eoln < buffer_end && *eoln != '\n') |
465 | ++eoln; | |
5ae2f0c0 | 466 | |
6c7cee42 | 467 | if (git__prefixncmp(buffer, buffer_end - buffer, "encoding ") == 0) { |
291090a0 | 468 | buffer += strlen("encoding "); |
5ae2f0c0 | 469 | |
291090a0 | 470 | commit->message_encoding = git__strndup(buffer, eoln - buffer); |
ac3d33df | 471 | GIT_ERROR_CHECK_ALLOC(commit->message_encoding); |
291090a0 | 472 | } |
5ae2f0c0 | 473 | |
d47c6aab CMN |
474 | if (eoln < buffer_end && *eoln == '\n') |
475 | ++eoln; | |
291090a0 | 476 | buffer = eoln; |
5ae2f0c0 | 477 | } |
58519018 | 478 | |
a6563619 CMN |
479 | header_len = buffer - buffer_start; |
480 | commit->raw_header = git__strndup(buffer_start, header_len); | |
ac3d33df | 481 | GIT_ERROR_CHECK_ALLOC(commit->raw_header); |
f094f905 | 482 | |
a6563619 CMN |
483 | /* point "buffer" to data after header, +1 for the final LF */ |
484 | buffer = buffer_start + header_len + 1; | |
52f2390b | 485 | |
f094f905 | 486 | /* extract commit message */ |
a719ef5e | 487 | if (buffer <= buffer_end) |
598f069b | 488 | commit->raw_message = git__strndup(buffer, buffer_end - buffer); |
a719ef5e PS |
489 | else |
490 | commit->raw_message = git__strdup(""); | |
ac3d33df | 491 | GIT_ERROR_CHECK_ALLOC(commit->raw_message); |
417f0abc | 492 | |
73fe6a8e VM |
493 | return 0; |
494 | ||
495 | bad_buffer: | |
ac3d33df | 496 | git_error_set(GIT_ERROR_OBJECT, "failed to parse bad commit object"); |
e579e0f7 | 497 | return GIT_EINVALID; |
417f0abc | 498 | } |
4caa8962 | 499 | |
22a2d3d5 UG |
500 | int git_commit__parse_raw(void *commit, const char *data, size_t size) |
501 | { | |
502 | return commit_parse(commit, data, size, 0); | |
503 | } | |
504 | ||
505 | int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags) | |
506 | { | |
507 | return commit_parse(commit, git_odb_object_data(odb_obj), git_odb_object_size(odb_obj), flags); | |
508 | } | |
509 | ||
ac3d33df JK |
510 | int git_commit__parse(void *_commit, git_odb_object *odb_obj) |
511 | { | |
22a2d3d5 | 512 | return git_commit__parse_ext(_commit, odb_obj, 0); |
ac3d33df JK |
513 | } |
514 | ||
c25aa7cd | 515 | #define GIT_COMMIT_GETTER(_rvalue, _name, _return, _invalid) \ |
cfbe4be3 | 516 | _rvalue git_commit_##_name(const git_commit *commit) \ |
0c3596f1 | 517 | {\ |
c25aa7cd | 518 | GIT_ASSERT_ARG_WITH_RETVAL(commit, _invalid); \ |
6b2a1941 | 519 | return _return; \ |
0c3596f1 VM |
520 | } |
521 | ||
c25aa7cd PP |
522 | GIT_COMMIT_GETTER(const git_signature *, author, commit->author, NULL) |
523 | GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer, NULL) | |
524 | GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message, NULL) | |
525 | GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding, NULL) | |
526 | GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header, NULL) | |
527 | GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time, INT64_MIN) | |
528 | GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset, -1) | |
529 | GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids), 0) | |
530 | GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id, NULL) | |
6b2a1941 | 531 | |
598f069b | 532 | const char *git_commit_message(const git_commit *commit) |
533 | { | |
be0a1a79 | 534 | const char *message; |
598f069b | 535 | |
c25aa7cd | 536 | GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); |
598f069b | 537 | |
be0a1a79 PH |
538 | message = commit->raw_message; |
539 | ||
598f069b | 540 | /* trim leading newlines from raw message */ |
541 | while (*message && *message == '\n') | |
542 | ++message; | |
543 | ||
544 | return message; | |
545 | } | |
546 | ||
300d192f ET |
547 | const char *git_commit_summary(git_commit *commit) |
548 | { | |
e579e0f7 MB |
549 | git_str summary = GIT_STR_INIT; |
550 | const char *msg, *space, *next; | |
f5f96a23 | 551 | bool space_contains_newline = false; |
300d192f | 552 | |
c25aa7cd | 553 | GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); |
300d192f ET |
554 | |
555 | if (!commit->summary) { | |
556 | for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) { | |
f5f96a23 SR |
557 | char next_character = msg[0]; |
558 | /* stop processing at the end of the first paragraph */ | |
e579e0f7 MB |
559 | if (next_character == '\n') { |
560 | if (!msg[1]) | |
561 | break; | |
562 | if (msg[1] == '\n') | |
563 | break; | |
564 | /* stop processing if next line contains only whitespace */ | |
565 | next = msg + 1; | |
566 | while (*next && git__isspace_nonlf(*next)) { | |
567 | ++next; | |
568 | } | |
569 | if (!*next || *next == '\n') | |
570 | break; | |
571 | } | |
f5f96a23 | 572 | /* record the beginning of contiguous whitespace runs */ |
e579e0f7 | 573 | if (git__isspace(next_character)) { |
f5f96a23 SR |
574 | if(space == NULL) { |
575 | space = msg; | |
576 | space_contains_newline = false; | |
577 | } | |
578 | space_contains_newline |= next_character == '\n'; | |
579 | } | |
580 | /* the next character is non-space */ | |
581 | else { | |
582 | /* process any recorded whitespace */ | |
583 | if (space) { | |
584 | if(space_contains_newline) | |
e579e0f7 | 585 | git_str_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */ |
f5f96a23 | 586 | else |
e579e0f7 | 587 | git_str_put(&summary, space, (msg - space)); /* otherwise copy it */ |
f5f96a23 SR |
588 | space = NULL; |
589 | } | |
590 | /* copy the next character */ | |
e579e0f7 | 591 | git_str_putc(&summary, next_character); |
f5f96a23 | 592 | } |
300d192f ET |
593 | } |
594 | ||
e579e0f7 | 595 | commit->summary = git_str_detach(&summary); |
80c29fe9 | 596 | if (!commit->summary) |
238e8149 | 597 | commit->summary = git__strdup(""); |
300d192f ET |
598 | } |
599 | ||
600 | return commit->summary; | |
601 | } | |
602 | ||
7f8fe1d4 PS |
603 | const char *git_commit_body(git_commit *commit) |
604 | { | |
605 | const char *msg, *end; | |
606 | ||
c25aa7cd | 607 | GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); |
7f8fe1d4 PS |
608 | |
609 | if (!commit->body) { | |
610 | /* search for end of summary */ | |
611 | for (msg = git_commit_message(commit); *msg; ++msg) | |
612 | if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n')) | |
613 | break; | |
614 | ||
615 | /* trim leading and trailing whitespace */ | |
616 | for (; *msg; ++msg) | |
617 | if (!git__isspace(*msg)) | |
618 | break; | |
619 | for (end = msg + strlen(msg) - 1; msg <= end; --end) | |
620 | if (!git__isspace(*end)) | |
621 | break; | |
622 | ||
623 | if (*msg) | |
22a2d3d5 | 624 | commit->body = git__strndup(msg, end - msg + 1); |
7f8fe1d4 PS |
625 | } |
626 | ||
627 | return commit->body; | |
628 | } | |
629 | ||
cfbe4be3 | 630 | int git_commit_tree(git_tree **tree_out, const git_commit *commit) |
6b2a1941 | 631 | { |
c25aa7cd | 632 | GIT_ASSERT_ARG(commit); |
cfbe4be3 | 633 | return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id); |
6b2a1941 | 634 | } |
57450775 | 635 | |
58206c9a RB |
636 | const git_oid *git_commit_parent_id( |
637 | const git_commit *commit, unsigned int n) | |
2b92a154 | 638 | { |
c25aa7cd | 639 | GIT_ASSERT_ARG_WITH_RETVAL(commit, NULL); |
2b92a154 | 640 | |
9abc78ae | 641 | return git_array_get(commit->parent_ids, n); |
2b92a154 | 642 | } |
643 | ||
58206c9a RB |
644 | int git_commit_parent( |
645 | git_commit **parent, const git_commit *commit, unsigned int n) | |
48c27f86 | 646 | { |
cfbe4be3 | 647 | const git_oid *parent_id; |
c25aa7cd | 648 | GIT_ASSERT_ARG(commit); |
48c27f86 | 649 | |
cfbe4be3 VM |
650 | parent_id = git_commit_parent_id(commit, n); |
651 | if (parent_id == NULL) { | |
ac3d33df | 652 | git_error_set(GIT_ERROR_INVALID, "parent %u does not exist", n); |
3aa351ea CMN |
653 | return GIT_ENOTFOUND; |
654 | } | |
48c27f86 | 655 | |
cfbe4be3 | 656 | return git_commit_lookup(parent, commit->object.repo, parent_id); |
48c27f86 | 657 | } |
b1aca6ea | 658 | |
659 | int git_commit_nth_gen_ancestor( | |
660 | git_commit **ancestor, | |
661 | const git_commit *commit, | |
662 | unsigned int n) | |
663 | { | |
e583334c | 664 | git_commit *current, *parent = NULL; |
b1aca6ea | 665 | int error; |
666 | ||
c25aa7cd PP |
667 | GIT_ASSERT_ARG(ancestor); |
668 | GIT_ASSERT_ARG(commit); | |
b1aca6ea | 669 | |
f0224772 | 670 | if (git_commit_dup(¤t, (git_commit *)commit) < 0) |
7c1ee212 | 671 | return -1; |
b1aca6ea | 672 | |
7c1ee212 CMN |
673 | if (n == 0) { |
674 | *ancestor = current; | |
675 | return 0; | |
676 | } | |
b1aca6ea | 677 | |
678 | while (n--) { | |
7c1ee212 | 679 | error = git_commit_parent(&parent, current, 0); |
b1aca6ea | 680 | |
7c1ee212 | 681 | git_commit_free(current); |
b1aca6ea | 682 | |
683 | if (error < 0) | |
684 | return error; | |
685 | ||
686 | current = parent; | |
687 | } | |
688 | ||
689 | *ancestor = parent; | |
690 | return 0; | |
691 | } | |
a3f42fe8 | 692 | |
e579e0f7 MB |
693 | int git_commit_header_field( |
694 | git_buf *out, | |
695 | const git_commit *commit, | |
696 | const char *field) | |
697 | { | |
698 | GIT_BUF_WRAP_PRIVATE(out, git_commit__header_field, commit, field); | |
699 | } | |
700 | ||
701 | int git_commit__header_field( | |
702 | git_str *out, | |
703 | const git_commit *commit, | |
704 | const char *field) | |
a3f42fe8 | 705 | { |
f55eca16 | 706 | const char *eol, *buf = commit->raw_header; |
a3f42fe8 | 707 | |
e579e0f7 | 708 | git_str_clear(out); |
f55eca16 | 709 | |
460ae11f | 710 | while ((eol = strchr(buf, '\n'))) { |
f55eca16 CMN |
711 | /* We can skip continuations here */ |
712 | if (buf[0] == ' ') { | |
713 | buf = eol + 1; | |
714 | continue; | |
715 | } | |
716 | ||
717 | /* Skip until we find the field we're after */ | |
718 | if (git__prefixcmp(buf, field)) { | |
719 | buf = eol + 1; | |
a3f42fe8 CMN |
720 | continue; |
721 | } | |
722 | ||
f55eca16 CMN |
723 | buf += strlen(field); |
724 | /* Check that we're not matching a prefix but the field itself */ | |
725 | if (buf[0] != ' ') { | |
726 | buf = eol + 1; | |
a3f42fe8 CMN |
727 | continue; |
728 | } | |
a3f42fe8 | 729 | |
f55eca16 | 730 | buf++; /* skip the SP */ |
a3f42fe8 | 731 | |
e579e0f7 MB |
732 | git_str_put(out, buf, eol - buf); |
733 | if (git_str_oom(out)) | |
a3f42fe8 CMN |
734 | goto oom; |
735 | ||
736 | /* If the next line starts with SP, it's multi-line, we must continue */ | |
737 | while (eol[1] == ' ') { | |
e579e0f7 | 738 | git_str_putc(out, '\n'); |
f55eca16 CMN |
739 | buf = eol + 2; |
740 | eol = strchr(buf, '\n'); | |
a3f42fe8 CMN |
741 | if (!eol) |
742 | goto malformed; | |
743 | ||
e579e0f7 | 744 | git_str_put(out, buf, eol - buf); |
a3f42fe8 CMN |
745 | } |
746 | ||
e579e0f7 | 747 | if (git_str_oom(out)) |
a3f42fe8 CMN |
748 | goto oom; |
749 | ||
750 | return 0; | |
751 | } | |
752 | ||
ac3d33df | 753 | git_error_set(GIT_ERROR_OBJECT, "no such field '%s'", field); |
a3f42fe8 CMN |
754 | return GIT_ENOTFOUND; |
755 | ||
756 | malformed: | |
ac3d33df | 757 | git_error_set(GIT_ERROR_OBJECT, "malformed header"); |
a3f42fe8 CMN |
758 | return -1; |
759 | oom: | |
ac3d33df | 760 | git_error_set_oom(); |
a3f42fe8 CMN |
761 | return -1; |
762 | } | |
a65afb75 | 763 | |
e579e0f7 MB |
764 | int git_commit_extract_signature( |
765 | git_buf *signature_out, | |
766 | git_buf *signed_data_out, | |
767 | git_repository *repo, | |
768 | git_oid *commit_id, | |
769 | const char *field) | |
770 | { | |
771 | git_str signature = GIT_STR_INIT, signed_data = GIT_STR_INIT; | |
772 | int error; | |
773 | ||
774 | if ((error = git_buf_tostr(&signature, signature_out)) < 0 || | |
775 | (error = git_buf_tostr(&signed_data, signed_data_out)) < 0 || | |
776 | (error = git_commit__extract_signature(&signature, &signed_data, repo, commit_id, field)) < 0 || | |
777 | (error = git_buf_fromstr(signature_out, &signature)) < 0 || | |
778 | (error = git_buf_fromstr(signed_data_out, &signed_data)) < 0) | |
779 | goto done; | |
780 | ||
781 | done: | |
782 | git_str_dispose(&signature); | |
783 | git_str_dispose(&signed_data); | |
784 | return error; | |
785 | } | |
786 | ||
787 | int git_commit__extract_signature( | |
788 | git_str *signature, | |
789 | git_str *signed_data, | |
790 | git_repository *repo, | |
791 | git_oid *commit_id, | |
792 | const char *field) | |
a65afb75 CMN |
793 | { |
794 | git_odb_object *obj; | |
795 | git_odb *odb; | |
796 | const char *buf; | |
797 | const char *h, *eol; | |
798 | int error; | |
799 | ||
e579e0f7 MB |
800 | git_str_clear(signature); |
801 | git_str_clear(signed_data); | |
a65afb75 CMN |
802 | |
803 | if (!field) | |
804 | field = "gpgsig"; | |
805 | ||
806 | if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) | |
807 | return error; | |
808 | ||
809 | if ((error = git_odb_read(&obj, odb, commit_id)) < 0) | |
810 | return error; | |
811 | ||
ac3d33df | 812 | if (obj->cached.type != GIT_OBJECT_COMMIT) { |
22a2d3d5 | 813 | git_error_set(GIT_ERROR_INVALID, "the requested type does not match the type in the ODB"); |
eadd0f05 CMN |
814 | error = GIT_ENOTFOUND; |
815 | goto cleanup; | |
816 | } | |
817 | ||
a65afb75 CMN |
818 | buf = git_odb_object_data(obj); |
819 | ||
bf804d40 | 820 | while ((h = strchr(buf, '\n')) && h[1] != '\0') { |
a65afb75 CMN |
821 | h++; |
822 | if (git__prefixcmp(buf, field)) { | |
e579e0f7 | 823 | if (git_str_put(signed_data, buf, h - buf) < 0) |
a65afb75 CMN |
824 | return -1; |
825 | ||
826 | buf = h; | |
827 | continue; | |
828 | } | |
829 | ||
830 | h = buf; | |
831 | h += strlen(field); | |
832 | eol = strchr(h, '\n'); | |
833 | if (h[0] != ' ') { | |
834 | buf = h; | |
835 | continue; | |
836 | } | |
837 | if (!eol) | |
838 | goto malformed; | |
839 | ||
840 | h++; /* skip the SP */ | |
841 | ||
e579e0f7 MB |
842 | git_str_put(signature, h, eol - h); |
843 | if (git_str_oom(signature)) | |
a65afb75 CMN |
844 | goto oom; |
845 | ||
846 | /* If the next line starts with SP, it's multi-line, we must continue */ | |
847 | while (eol[1] == ' ') { | |
e579e0f7 | 848 | git_str_putc(signature, '\n'); |
a65afb75 CMN |
849 | h = eol + 2; |
850 | eol = strchr(h, '\n'); | |
851 | if (!eol) | |
852 | goto malformed; | |
853 | ||
e579e0f7 | 854 | git_str_put(signature, h, eol - h); |
a65afb75 CMN |
855 | } |
856 | ||
e579e0f7 | 857 | if (git_str_oom(signature)) |
a65afb75 CMN |
858 | goto oom; |
859 | ||
e579e0f7 | 860 | error = git_str_puts(signed_data, eol+1); |
a65afb75 | 861 | git_odb_object_free(obj); |
ade0d9c6 | 862 | return error; |
a65afb75 CMN |
863 | } |
864 | ||
ac3d33df | 865 | git_error_set(GIT_ERROR_OBJECT, "this commit is not signed"); |
a65afb75 CMN |
866 | error = GIT_ENOTFOUND; |
867 | goto cleanup; | |
868 | ||
869 | malformed: | |
ac3d33df | 870 | git_error_set(GIT_ERROR_OBJECT, "malformed header"); |
a65afb75 CMN |
871 | error = -1; |
872 | goto cleanup; | |
873 | oom: | |
ac3d33df | 874 | git_error_set_oom(); |
a65afb75 CMN |
875 | error = -1; |
876 | goto cleanup; | |
877 | ||
878 | cleanup: | |
879 | git_odb_object_free(obj); | |
e579e0f7 MB |
880 | git_str_clear(signature); |
881 | git_str_clear(signed_data); | |
a65afb75 CMN |
882 | return error; |
883 | } | |
47cb42da | 884 | |
e579e0f7 MB |
885 | int git_commit_create_buffer( |
886 | git_buf *out, | |
887 | git_repository *repo, | |
888 | const git_signature *author, | |
889 | const git_signature *committer, | |
890 | const char *message_encoding, | |
891 | const char *message, | |
892 | const git_tree *tree, | |
893 | size_t parent_count, | |
894 | const git_commit *parents[]) | |
895 | { | |
896 | GIT_BUF_WRAP_PRIVATE(out, git_commit__create_buffer, repo, | |
897 | author, committer, message_encoding, message, | |
898 | tree, parent_count, parents); | |
899 | } | |
900 | ||
901 | int git_commit__create_buffer( | |
902 | git_str *out, | |
47cb42da CMN |
903 | git_repository *repo, |
904 | const git_signature *author, | |
905 | const git_signature *committer, | |
906 | const char *message_encoding, | |
907 | const char *message, | |
908 | const git_tree *tree, | |
909 | size_t parent_count, | |
910 | const git_commit *parents[]) | |
911 | { | |
912 | int error; | |
913 | commit_parent_data data = { parent_count, parents, repo }; | |
914 | git_array_oid_t parents_arr = GIT_ARRAY_INIT; | |
915 | const git_oid *tree_id; | |
916 | ||
c25aa7cd PP |
917 | GIT_ASSERT_ARG(tree); |
918 | GIT_ASSERT_ARG(git_tree_owner(tree) == repo); | |
47cb42da CMN |
919 | |
920 | tree_id = git_tree_id(tree); | |
921 | ||
922 | if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0) | |
923 | return error; | |
924 | ||
925 | error = git_commit__create_buffer_internal( | |
0d77a56f | 926 | out, author, committer, |
47cb42da CMN |
927 | message_encoding, message, tree_id, |
928 | &parents_arr); | |
929 | ||
930 | git_array_clear(parents_arr); | |
931 | return error; | |
932 | } | |
02d61a3b CMN |
933 | |
934 | /** | |
935 | * Append to 'out' properly marking continuations when there's a newline in 'content' | |
936 | */ | |
e579e0f7 | 937 | static int format_header_field(git_str *out, const char *field, const char *content) |
02d61a3b CMN |
938 | { |
939 | const char *lf; | |
940 | ||
c25aa7cd PP |
941 | GIT_ASSERT_ARG(out); |
942 | GIT_ASSERT_ARG(field); | |
943 | GIT_ASSERT_ARG(content); | |
02d61a3b | 944 | |
e579e0f7 MB |
945 | git_str_puts(out, field); |
946 | git_str_putc(out, ' '); | |
02d61a3b CMN |
947 | |
948 | while ((lf = strchr(content, '\n')) != NULL) { | |
e579e0f7 MB |
949 | git_str_put(out, content, lf - content); |
950 | git_str_puts(out, "\n "); | |
02d61a3b CMN |
951 | content = lf + 1; |
952 | } | |
953 | ||
e579e0f7 MB |
954 | git_str_puts(out, content); |
955 | git_str_putc(out, '\n'); | |
c25aa7cd | 956 | |
e579e0f7 | 957 | return git_str_oom(out) ? -1 : 0; |
02d61a3b CMN |
958 | } |
959 | ||
22a2d3d5 UG |
960 | static const git_oid *commit_parent_from_commit(size_t n, void *payload) |
961 | { | |
962 | const git_commit *commit = (const git_commit *) payload; | |
963 | ||
964 | return git_array_get(commit->parent_ids, n); | |
965 | ||
966 | } | |
967 | ||
02d61a3b CMN |
968 | int git_commit_create_with_signature( |
969 | git_oid *out, | |
970 | git_repository *repo, | |
971 | const char *commit_content, | |
972 | const char *signature, | |
973 | const char *signature_field) | |
974 | { | |
975 | git_odb *odb; | |
976 | int error = 0; | |
977 | const char *field; | |
978 | const char *header_end; | |
e579e0f7 | 979 | git_str commit = GIT_STR_INIT; |
22a2d3d5 UG |
980 | git_commit *parsed; |
981 | git_array_oid_t parents = GIT_ARRAY_INIT; | |
982 | ||
983 | /* The first step is to verify that all the tree and parents exist */ | |
984 | parsed = git__calloc(1, sizeof(git_commit)); | |
985 | GIT_ERROR_CHECK_ALLOC(parsed); | |
e579e0f7 MB |
986 | if (commit_parse(parsed, commit_content, strlen(commit_content), 0) < 0) { |
987 | error = -1; | |
22a2d3d5 | 988 | goto cleanup; |
e579e0f7 | 989 | } |
22a2d3d5 UG |
990 | |
991 | if ((error = validate_tree_and_parents(&parents, repo, &parsed->tree_id, commit_parent_from_commit, parsed, NULL, true)) < 0) | |
992 | goto cleanup; | |
02d61a3b | 993 | |
22a2d3d5 UG |
994 | git_array_clear(parents); |
995 | ||
996 | /* Then we start appending by identifying the end of the commit header */ | |
02d61a3b CMN |
997 | header_end = strstr(commit_content, "\n\n"); |
998 | if (!header_end) { | |
ac3d33df | 999 | git_error_set(GIT_ERROR_INVALID, "malformed commit contents"); |
22a2d3d5 UG |
1000 | error = -1; |
1001 | goto cleanup; | |
02d61a3b CMN |
1002 | } |
1003 | ||
02d61a3b CMN |
1004 | /* The header ends after the first LF */ |
1005 | header_end++; | |
e579e0f7 | 1006 | git_str_put(&commit, commit_content, header_end - commit_content); |
22a2d3d5 UG |
1007 | |
1008 | if (signature != NULL) { | |
1009 | field = signature_field ? signature_field : "gpgsig"; | |
c25aa7cd PP |
1010 | |
1011 | if ((error = format_header_field(&commit, field, signature)) < 0) | |
1012 | goto cleanup; | |
22a2d3d5 UG |
1013 | } |
1014 | ||
e579e0f7 | 1015 | git_str_puts(&commit, header_end); |
02d61a3b | 1016 | |
e579e0f7 | 1017 | if (git_str_oom(&commit)) |
02d61a3b CMN |
1018 | return -1; |
1019 | ||
1020 | if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) | |
1021 | goto cleanup; | |
1022 | ||
ac3d33df | 1023 | if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJECT_COMMIT)) < 0) |
02d61a3b CMN |
1024 | goto cleanup; |
1025 | ||
1026 | cleanup: | |
22a2d3d5 | 1027 | git_commit__free(parsed); |
e579e0f7 | 1028 | git_str_dispose(&commit); |
02d61a3b CMN |
1029 | return error; |
1030 | } | |
ac3d33df JK |
1031 | |
1032 | int git_commit_committer_with_mailmap( | |
1033 | git_signature **out, const git_commit *commit, const git_mailmap *mailmap) | |
1034 | { | |
1035 | return git_mailmap_resolve_signature(out, mailmap, commit->committer); | |
1036 | } | |
1037 | ||
1038 | int git_commit_author_with_mailmap( | |
1039 | git_signature **out, const git_commit *commit, const git_mailmap *mailmap) | |
1040 | { | |
1041 | return git_mailmap_resolve_signature(out, mailmap, commit->author); | |
1042 | } |