]>
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" |
06160502 | 20 | |
78606263 | 21 | void git_commit__free(void *_commit) |
40721f6b | 22 | { |
78606263 RB |
23 | git_commit *commit = _commit; |
24 | ||
9abc78ae | 25 | git_array_clear(commit->parent_ids); |
52f2390b | 26 | |
638c2ca4 VM |
27 | git_signature_free(commit->author); |
28 | git_signature_free(commit->committer); | |
58519018 | 29 | |
f094f905 | 30 | git__free(commit->raw_header); |
598f069b | 31 | git__free(commit->raw_message); |
3286c408 | 32 | git__free(commit->message_encoding); |
300d192f | 33 | git__free(commit->summary); |
9abc78ae | 34 | |
3286c408 | 35 | git__free(commit); |
40721f6b VM |
36 | } |
37 | ||
80c29fe9 RB |
38 | int git_commit_create_from_callback( |
39 | git_oid *id, | |
9233b3de RB |
40 | git_repository *repo, |
41 | const char *update_ref, | |
42 | const git_signature *author, | |
43 | const git_signature *committer, | |
44 | const char *message_encoding, | |
45 | const char *message, | |
46 | const git_oid *tree, | |
80c29fe9 RB |
47 | git_commit_parent_callback parent_cb, |
48 | void *parent_payload) | |
72a3fe42 | 49 | { |
217c029b CMN |
50 | git_reference *ref = NULL; |
51 | int error = 0, matched_parent = 0; | |
52 | const git_oid *current_id = NULL; | |
e00b56eb | 53 | git_buf commit = GIT_BUF_INIT; |
80c29fe9 | 54 | size_t i = 0; |
9462c471 | 55 | git_odb *odb; |
80c29fe9 | 56 | const git_oid *parent; |
72a3fe42 | 57 | |
80c29fe9 | 58 | assert(id && repo && tree && parent_cb); |
9233b3de | 59 | |
217c029b CMN |
60 | if (update_ref) { |
61 | error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); | |
62 | if (error < 0 && error != GIT_ENOTFOUND) | |
63 | return error; | |
64 | } | |
65 | giterr_clear(); | |
66 | ||
67 | if (ref) | |
68 | current_id = git_reference_target(ref); | |
69 | ||
92550398 | 70 | git_oid__writebuf(&commit, "tree ", tree); |
d5afc039 | 71 | |
217c029b | 72 | while ((parent = parent_cb(i, parent_payload)) != NULL) { |
80c29fe9 | 73 | git_oid__writebuf(&commit, "parent ", parent); |
217c029b CMN |
74 | if (i == 0 && current_id && git_oid_equal(current_id, parent)) |
75 | matched_parent = 1; | |
76 | i++; | |
77 | } | |
78 | ||
79 | if (ref && !matched_parent) { | |
80 | git_reference_free(ref); | |
81 | git_buf_free(&commit); | |
82 | giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent"); | |
83 | return GIT_EMODIFIED; | |
84 | } | |
0c3596f1 | 85 | |
afeecf4f VM |
86 | git_signature__writebuf(&commit, "author ", author); |
87 | git_signature__writebuf(&commit, "committer ", committer); | |
0c3596f1 | 88 | |
5ae2f0c0 VM |
89 | if (message_encoding != NULL) |
90 | git_buf_printf(&commit, "encoding %s\n", message_encoding); | |
91 | ||
afeecf4f | 92 | git_buf_putc(&commit, '\n'); |
0c3596f1 | 93 | |
e00b56eb | 94 | if (git_buf_puts(&commit, message) < 0) |
458b9450 | 95 | goto on_error; |
96 | ||
73fe6a8e VM |
97 | if (git_repository_odb__weakptr(&odb, repo) < 0) |
98 | goto on_error; | |
75abd2b9 | 99 | |
80c29fe9 | 100 | if (git_odb_write(id, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0) |
73fe6a8e | 101 | goto on_error; |
72a3fe42 | 102 | |
73fe6a8e | 103 | git_buf_free(&commit); |
57450775 | 104 | |
0adb0606 | 105 | if (update_ref != NULL) { |
a612a25f | 106 | error = git_reference__update_for_commit( |
659cf202 | 107 | repo, ref, update_ref, id, "commit"); |
217c029b | 108 | git_reference_free(ref); |
0adb0606 BS |
109 | return error; |
110 | } | |
4c7a5e9e | 111 | |
73fe6a8e | 112 | return 0; |
afeecf4f | 113 | |
73fe6a8e | 114 | on_error: |
afeecf4f | 115 | git_buf_free(&commit); |
458b9450 | 116 | giterr_set(GITERR_OBJECT, "Failed to create commit."); |
73fe6a8e | 117 | return -1; |
0c3596f1 VM |
118 | } |
119 | ||
80c29fe9 RB |
120 | typedef struct { |
121 | size_t total; | |
122 | va_list args; | |
123 | } commit_parent_varargs; | |
124 | ||
125 | static const git_oid *commit_parent_from_varargs(size_t curr, void *payload) | |
126 | { | |
127 | commit_parent_varargs *data = payload; | |
128 | const git_commit *commit; | |
129 | if (curr >= data->total) | |
130 | return NULL; | |
131 | commit = va_arg(data->args, const git_commit *); | |
132 | return commit ? git_commit_id(commit) : NULL; | |
133 | } | |
134 | ||
135 | int git_commit_create_v( | |
136 | git_oid *id, | |
137 | git_repository *repo, | |
138 | const char *update_ref, | |
139 | const git_signature *author, | |
140 | const git_signature *committer, | |
141 | const char *message_encoding, | |
142 | const char *message, | |
143 | const git_tree *tree, | |
144 | size_t parent_count, | |
145 | ...) | |
146 | { | |
147 | int error = 0; | |
148 | commit_parent_varargs data; | |
149 | ||
150 | assert(tree && git_tree_owner(tree) == repo); | |
151 | ||
152 | data.total = parent_count; | |
153 | va_start(data.args, parent_count); | |
154 | ||
155 | error = git_commit_create_from_callback( | |
156 | id, repo, update_ref, author, committer, | |
157 | message_encoding, message, git_tree_id(tree), | |
158 | commit_parent_from_varargs, &data); | |
159 | ||
160 | va_end(data.args); | |
161 | return error; | |
162 | } | |
163 | ||
164 | typedef struct { | |
165 | size_t total; | |
166 | const git_oid **parents; | |
167 | } commit_parent_oids; | |
168 | ||
169 | static const git_oid *commit_parent_from_ids(size_t curr, void *payload) | |
170 | { | |
171 | commit_parent_oids *data = payload; | |
172 | return (curr < data->total) ? data->parents[curr] : NULL; | |
173 | } | |
174 | ||
175 | int git_commit_create_from_ids( | |
176 | git_oid *id, | |
177 | git_repository *repo, | |
178 | const char *update_ref, | |
179 | const git_signature *author, | |
180 | const git_signature *committer, | |
181 | const char *message_encoding, | |
182 | const char *message, | |
183 | const git_oid *tree, | |
184 | size_t parent_count, | |
185 | const git_oid *parents[]) | |
186 | { | |
187 | commit_parent_oids data = { parent_count, parents }; | |
188 | ||
189 | return git_commit_create_from_callback( | |
190 | id, repo, update_ref, author, committer, | |
191 | message_encoding, message, tree, | |
192 | commit_parent_from_ids, &data); | |
193 | } | |
194 | ||
195 | typedef struct { | |
196 | size_t total; | |
197 | const git_commit **parents; | |
198 | git_repository *repo; | |
199 | } commit_parent_data; | |
200 | ||
201 | static const git_oid *commit_parent_from_array(size_t curr, void *payload) | |
202 | { | |
203 | commit_parent_data *data = payload; | |
204 | const git_commit *commit; | |
205 | if (curr >= data->total) | |
206 | return NULL; | |
207 | commit = data->parents[curr]; | |
208 | if (git_commit_owner(commit) != data->repo) | |
209 | return NULL; | |
210 | return git_commit_id(commit); | |
211 | } | |
212 | ||
92550398 | 213 | int git_commit_create( |
80c29fe9 | 214 | git_oid *id, |
9233b3de RB |
215 | git_repository *repo, |
216 | const char *update_ref, | |
217 | const git_signature *author, | |
218 | const git_signature *committer, | |
219 | const char *message_encoding, | |
220 | const char *message, | |
221 | const git_tree *tree, | |
80c29fe9 | 222 | size_t parent_count, |
9233b3de | 223 | const git_commit *parents[]) |
92550398 | 224 | { |
80c29fe9 | 225 | commit_parent_data data = { parent_count, parents, repo }; |
92550398 | 226 | |
80c29fe9 | 227 | assert(tree && git_tree_owner(tree) == repo); |
92550398 | 228 | |
80c29fe9 RB |
229 | return git_commit_create_from_callback( |
230 | id, repo, update_ref, author, committer, | |
231 | message_encoding, message, git_tree_id(tree), | |
232 | commit_parent_from_array, &data); | |
233 | } | |
92550398 | 234 | |
80c29fe9 RB |
235 | static const git_oid *commit_parent_for_amend(size_t curr, void *payload) |
236 | { | |
237 | const git_commit *commit_to_amend = payload; | |
238 | if (curr >= git_array_size(commit_to_amend->parent_ids)) | |
239 | return NULL; | |
240 | return git_array_get(commit_to_amend->parent_ids, curr); | |
241 | } | |
92550398 | 242 | |
80c29fe9 RB |
243 | int git_commit_amend( |
244 | git_oid *id, | |
245 | const git_commit *commit_to_amend, | |
246 | const char *update_ref, | |
247 | const git_signature *author, | |
248 | const git_signature *committer, | |
249 | const char *message_encoding, | |
250 | const char *message, | |
251 | const git_tree *tree) | |
252 | { | |
253 | git_repository *repo; | |
254 | git_oid tree_id; | |
217c029b CMN |
255 | git_reference *ref; |
256 | int error; | |
80c29fe9 RB |
257 | |
258 | assert(id && commit_to_amend); | |
259 | ||
260 | repo = git_commit_owner(commit_to_amend); | |
261 | ||
262 | if (!author) | |
263 | author = git_commit_author(commit_to_amend); | |
264 | if (!committer) | |
265 | committer = git_commit_committer(commit_to_amend); | |
266 | if (!message_encoding) | |
267 | message_encoding = git_commit_message_encoding(commit_to_amend); | |
268 | if (!message) | |
269 | message = git_commit_message(commit_to_amend); | |
270 | ||
271 | if (!tree) { | |
272 | git_tree *old_tree; | |
273 | GITERR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) ); | |
274 | git_oid_cpy(&tree_id, git_tree_id(old_tree)); | |
275 | git_tree_free(old_tree); | |
276 | } else { | |
277 | assert(git_tree_owner(tree) == repo); | |
278 | git_oid_cpy(&tree_id, git_tree_id(tree)); | |
279 | } | |
9233b3de | 280 | |
217c029b CMN |
281 | if (update_ref) { |
282 | if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0) | |
283 | return error; | |
284 | ||
285 | if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) { | |
286 | git_reference_free(ref); | |
287 | giterr_set(GITERR_REFERENCE, "commit to amend is not the tip of the given branch"); | |
288 | return -1; | |
289 | } | |
290 | } | |
291 | ||
292 | error = git_commit_create_from_callback( | |
293 | id, repo, NULL, author, committer, message_encoding, message, | |
80c29fe9 | 294 | &tree_id, commit_parent_for_amend, (void *)commit_to_amend); |
217c029b CMN |
295 | |
296 | if (!error && update_ref) { | |
a612a25f | 297 | error = git_reference__update_for_commit( |
659cf202 | 298 | repo, ref, NULL, id, "commit"); |
217c029b CMN |
299 | git_reference_free(ref); |
300 | } | |
301 | ||
302 | return error; | |
92550398 JW |
303 | } |
304 | ||
3f27127d | 305 | int git_commit__parse(void *_commit, git_odb_object *odb_obj) |
417f0abc | 306 | { |
78606263 | 307 | git_commit *commit = _commit; |
f094f905 RB |
308 | const char *buffer_start = git_odb_object_data(odb_obj), *buffer; |
309 | const char *buffer_end = buffer_start + git_odb_object_size(odb_obj); | |
cfbe4be3 | 310 | git_oid parent_id; |
584f2d30 | 311 | size_t header_len; |
417f0abc | 312 | |
a6563619 | 313 | buffer = buffer_start; |
f094f905 | 314 | |
a6563619 CMN |
315 | /* Allocate for one, which will allow not to realloc 90% of the time */ |
316 | git_array_init_to_size(commit->parent_ids, 1); | |
9abc78ae | 317 | GITERR_CHECK_ARRAY(commit->parent_ids); |
eec95235 | 318 | |
a6563619 | 319 | /* The tree is always the first field */ |
cfbe4be3 | 320 | if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) |
73fe6a8e | 321 | goto bad_buffer; |
225fe215 | 322 | |
9b3577ed | 323 | /* |
9b3577ed VM |
324 | * TODO: commit grafts! |
325 | */ | |
417f0abc | 326 | |
cfbe4be3 | 327 | while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { |
9abc78ae | 328 | git_oid *new_id = git_array_alloc(commit->parent_ids); |
cfbe4be3 | 329 | GITERR_CHECK_ALLOC(new_id); |
73fe6a8e | 330 | |
cfbe4be3 | 331 | git_oid_cpy(new_id, &parent_id); |
9b3577ed | 332 | } |
417f0abc | 333 | |
6b2a1941 | 334 | commit->author = git__malloc(sizeof(git_signature)); |
73fe6a8e VM |
335 | GITERR_CHECK_ALLOC(commit->author); |
336 | ||
337 | if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0) | |
338 | return -1; | |
52f2390b | 339 | |
58519018 | 340 | /* Always parse the committer; we need the commit time */ |
638c2ca4 | 341 | commit->committer = git__malloc(sizeof(git_signature)); |
73fe6a8e VM |
342 | GITERR_CHECK_ALLOC(commit->committer); |
343 | ||
344 | if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) | |
345 | return -1; | |
5ae2f0c0 | 346 | |
f094f905 RB |
347 | /* Parse add'l header entries */ |
348 | while (buffer < buffer_end) { | |
291090a0 | 349 | const char *eoln = buffer; |
a6563619 CMN |
350 | if (buffer[-1] == '\n' && buffer[0] == '\n') |
351 | break; | |
352 | ||
291090a0 RB |
353 | while (eoln < buffer_end && *eoln != '\n') |
354 | ++eoln; | |
5ae2f0c0 | 355 | |
291090a0 RB |
356 | if (git__prefixcmp(buffer, "encoding ") == 0) { |
357 | buffer += strlen("encoding "); | |
5ae2f0c0 | 358 | |
291090a0 RB |
359 | commit->message_encoding = git__strndup(buffer, eoln - buffer); |
360 | GITERR_CHECK_ALLOC(commit->message_encoding); | |
361 | } | |
5ae2f0c0 | 362 | |
d47c6aab CMN |
363 | if (eoln < buffer_end && *eoln == '\n') |
364 | ++eoln; | |
291090a0 | 365 | buffer = eoln; |
5ae2f0c0 | 366 | } |
58519018 | 367 | |
a6563619 CMN |
368 | header_len = buffer - buffer_start; |
369 | commit->raw_header = git__strndup(buffer_start, header_len); | |
370 | GITERR_CHECK_ALLOC(commit->raw_header); | |
f094f905 | 371 | |
a6563619 CMN |
372 | /* point "buffer" to data after header, +1 for the final LF */ |
373 | buffer = buffer_start + header_len + 1; | |
52f2390b | 374 | |
f094f905 | 375 | /* extract commit message */ |
04f78802 | 376 | if (buffer <= buffer_end) { |
598f069b | 377 | commit->raw_message = git__strndup(buffer, buffer_end - buffer); |
378 | GITERR_CHECK_ALLOC(commit->raw_message); | |
52f2390b | 379 | } |
417f0abc | 380 | |
73fe6a8e VM |
381 | return 0; |
382 | ||
383 | bad_buffer: | |
384 | giterr_set(GITERR_OBJECT, "Failed to parse bad commit object"); | |
385 | return -1; | |
417f0abc | 386 | } |
4caa8962 | 387 | |
6b2a1941 | 388 | #define GIT_COMMIT_GETTER(_rvalue, _name, _return) \ |
cfbe4be3 | 389 | _rvalue git_commit_##_name(const git_commit *commit) \ |
0c3596f1 | 390 | {\ |
58519018 | 391 | assert(commit); \ |
6b2a1941 | 392 | return _return; \ |
0c3596f1 VM |
393 | } |
394 | ||
6b2a1941 VM |
395 | GIT_COMMIT_GETTER(const git_signature *, author, commit->author) |
396 | GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer) | |
598f069b | 397 | GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message) |
5ae2f0c0 | 398 | GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) |
f094f905 | 399 | GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header) |
56d8ca26 | 400 | GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) |
6b2a1941 | 401 | GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) |
9abc78ae | 402 | GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids)) |
c8e02b87 | 403 | GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id) |
6b2a1941 | 404 | |
598f069b | 405 | const char *git_commit_message(const git_commit *commit) |
406 | { | |
be0a1a79 | 407 | const char *message; |
598f069b | 408 | |
409 | assert(commit); | |
410 | ||
be0a1a79 PH |
411 | message = commit->raw_message; |
412 | ||
598f069b | 413 | /* trim leading newlines from raw message */ |
414 | while (*message && *message == '\n') | |
415 | ++message; | |
416 | ||
417 | return message; | |
418 | } | |
419 | ||
300d192f ET |
420 | const char *git_commit_summary(git_commit *commit) |
421 | { | |
422 | git_buf summary = GIT_BUF_INIT; | |
423 | const char *msg, *space; | |
424 | ||
425 | assert(commit); | |
426 | ||
427 | if (!commit->summary) { | |
428 | for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) { | |
429 | if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n')) | |
430 | break; | |
431 | else if (msg[0] == '\n') | |
432 | git_buf_putc(&summary, ' '); | |
433 | else if (git__isspace(msg[0])) | |
434 | space = space ? space : msg; | |
435 | else if (space) { | |
436 | git_buf_put(&summary, space, (msg - space) + 1); | |
437 | space = NULL; | |
438 | } else | |
439 | git_buf_putc(&summary, *msg); | |
440 | } | |
441 | ||
80c29fe9 RB |
442 | commit->summary = git_buf_detach(&summary); |
443 | if (!commit->summary) | |
238e8149 | 444 | commit->summary = git__strdup(""); |
300d192f ET |
445 | } |
446 | ||
447 | return commit->summary; | |
448 | } | |
449 | ||
cfbe4be3 | 450 | int git_commit_tree(git_tree **tree_out, const git_commit *commit) |
6b2a1941 VM |
451 | { |
452 | assert(commit); | |
cfbe4be3 | 453 | return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id); |
6b2a1941 | 454 | } |
57450775 | 455 | |
58206c9a RB |
456 | const git_oid *git_commit_parent_id( |
457 | const git_commit *commit, unsigned int n) | |
2b92a154 | 458 | { |
459 | assert(commit); | |
460 | ||
9abc78ae | 461 | return git_array_get(commit->parent_ids, n); |
2b92a154 | 462 | } |
463 | ||
58206c9a RB |
464 | int git_commit_parent( |
465 | git_commit **parent, const git_commit *commit, unsigned int n) | |
48c27f86 | 466 | { |
cfbe4be3 | 467 | const git_oid *parent_id; |
48c27f86 VM |
468 | assert(commit); |
469 | ||
cfbe4be3 VM |
470 | parent_id = git_commit_parent_id(commit, n); |
471 | if (parent_id == NULL) { | |
3aa351ea CMN |
472 | giterr_set(GITERR_INVALID, "Parent %u does not exist", n); |
473 | return GIT_ENOTFOUND; | |
474 | } | |
48c27f86 | 475 | |
cfbe4be3 | 476 | return git_commit_lookup(parent, commit->object.repo, parent_id); |
48c27f86 | 477 | } |
b1aca6ea | 478 | |
479 | int git_commit_nth_gen_ancestor( | |
480 | git_commit **ancestor, | |
481 | const git_commit *commit, | |
482 | unsigned int n) | |
483 | { | |
e583334c | 484 | git_commit *current, *parent = NULL; |
b1aca6ea | 485 | int error; |
486 | ||
487 | assert(ancestor && commit); | |
488 | ||
7c1ee212 CMN |
489 | if (git_object_dup((git_object **) ¤t, (git_object *) commit) < 0) |
490 | return -1; | |
b1aca6ea | 491 | |
7c1ee212 CMN |
492 | if (n == 0) { |
493 | *ancestor = current; | |
494 | return 0; | |
495 | } | |
b1aca6ea | 496 | |
497 | while (n--) { | |
7c1ee212 | 498 | error = git_commit_parent(&parent, current, 0); |
b1aca6ea | 499 | |
7c1ee212 | 500 | git_commit_free(current); |
b1aca6ea | 501 | |
502 | if (error < 0) | |
503 | return error; | |
504 | ||
505 | current = parent; | |
506 | } | |
507 | ||
508 | *ancestor = parent; | |
509 | return 0; | |
510 | } |