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