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