commit.c 7.69 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7
 */

8 9 10
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
11
#include "git2/signature.h"
12
#include "git2/sys/commit.h"
13

14
#include "common.h"
Vicent Marti committed
15
#include "odb.h"
16
#include "commit.h"
17
#include "signature.h"
18
#include "message.h"
19

Vicent Marti committed
20 21
#include <stdarg.h>

22 23
static void clear_parents(git_commit *commit)
{
24
	size_t i;
25

Vicent Marti committed
26 27
	for (i = 0; i < commit->parent_ids.length; ++i) {
		git_oid *parent = git_vector_get(&commit->parent_ids, i);
28
		git__free(parent);
29 30
	}

Vicent Marti committed
31
	git_vector_clear(&commit->parent_ids);
32 33
}

34
void git_commit__free(void *_commit)
35
{
36 37
	git_commit *commit = _commit;

38
	clear_parents(commit);
Vicent Marti committed
39
	git_vector_free(&commit->parent_ids);
40

41 42
	git_signature_free(commit->author);
	git_signature_free(commit->committer);
43

44 45 46
	git__free(commit->message);
	git__free(commit->message_encoding);
	git__free(commit);
47 48
}

Vicent Marti committed
49
int git_commit_create_v(
50 51 52 53 54 55 56 57 58 59
	git_oid *oid,
	git_repository *repo,
	const char *update_ref,
	const git_signature *author,
	const git_signature *committer,
	const char *message_encoding,
	const char *message,
	const git_tree *tree,
	int parent_count,
	...)
Vicent Marti committed
60 61
{
	va_list ap;
62
	int i, res;
63
	const git_commit **parents;
Vicent Marti committed
64

65
	parents = git__malloc(parent_count * sizeof(git_commit *));
66
	GITERR_CHECK_ALLOC(parents);
Vicent Marti committed
67 68 69

	va_start(ap, parent_count);
	for (i = 0; i < parent_count; ++i)
70
		parents[i] = va_arg(ap, const git_commit *);
Vicent Marti committed
71 72
	va_end(ap);

73
	res = git_commit_create(
74 75
		oid, repo, update_ref, author, committer,
		message_encoding, message,
76
		tree, parent_count, parents);
Vicent Marti committed
77

78
	git__free((void *)parents);
79 80 81
	return res;
}

82 83 84 85 86 87 88 89 90 91 92
int git_commit_create_from_oids(
	git_oid *oid,
	git_repository *repo,
	const char *update_ref,
	const git_signature *author,
	const git_signature *committer,
	const char *message_encoding,
	const char *message,
	const git_oid *tree,
	int parent_count,
	const git_oid *parents[])
Vicent Marti committed
93
{
94
	git_buf commit = GIT_BUF_INIT;
95
	int i;
96
	git_odb *odb;
Vicent Marti committed
97

98 99
	assert(oid && repo && tree && parent_count >= 0);

100
	git_oid__writebuf(&commit, "tree ", tree);
101

102 103
	for (i = 0; i < parent_count; ++i)
		git_oid__writebuf(&commit, "parent ", parents[i]);
104

105 106
	git_signature__writebuf(&commit, "author ", author);
	git_signature__writebuf(&commit, "committer ", committer);
107

108 109 110
	if (message_encoding != NULL)
		git_buf_printf(&commit, "encoding %s\n", message_encoding);

111
	git_buf_putc(&commit, '\n');
112

113
	if (git_buf_puts(&commit, message) < 0)
114 115
		goto on_error;

116 117
	if (git_repository_odb__weakptr(&odb, repo) < 0)
		goto on_error;
118

119 120
	if (git_odb_write(oid, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0)
		goto on_error;
Vicent Marti committed
121

122
	git_buf_free(&commit);
123

124
	if (update_ref != NULL)
125
		return git_reference__update_terminal(repo, update_ref, oid);
Vicent Marti committed
126

127
	return 0;
128

129
on_error:
130
	git_buf_free(&commit);
131
	giterr_set(GITERR_OBJECT, "Failed to create commit.");
132
	return -1;
133 134
}

135
int git_commit_create(
136 137 138 139 140 141 142 143 144 145
	git_oid *oid,
	git_repository *repo,
	const char *update_ref,
	const git_signature *author,
	const git_signature *committer,
	const char *message_encoding,
	const char *message,
	const git_tree *tree,
	int parent_count,
	const git_commit *parents[])
146
{
147
	int retval, i;
148 149
	const git_oid **parent_oids;

150
	assert(parent_count >= 0);
151
	assert(git_object_owner((const git_object *)tree) == repo);
152 153 154 155 156 157 158 159 160

	parent_oids = git__malloc(parent_count * sizeof(git_oid *));
	GITERR_CHECK_ALLOC(parent_oids);

	for (i = 0; i < parent_count; ++i) {
		assert(git_object_owner((const git_object *)parents[i]) == repo);
		parent_oids[i] = git_object_id((const git_object *)parents[i]);
	}

161 162 163 164
	retval = git_commit_create_from_oids(
		oid, repo, update_ref, author, committer,
		message_encoding, message,
		git_object_id((const git_object *)tree), parent_count, parent_oids);
165 166

	git__free((void *)parent_oids);
167 168

	return retval;
169 170
}

171
int git_commit__parse(void *_commit, git_odb_object *odb_obj)
172
{
173
	git_commit *commit = _commit;
174 175
	const char *buffer = git_odb_object_data(odb_obj);
	const char *buffer_end = buffer + git_odb_object_size(odb_obj);
Vicent Marti committed
176
	git_oid parent_id;
177

178 179
	if (git_vector_init(&commit->parent_ids, 4, NULL) < 0)
		return -1;
180

Vicent Marti committed
181
	if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
182
		goto bad_buffer;
183

184 185 186
	/*
	 * TODO: commit grafts!
	 */
187

Vicent Marti committed
188
	while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
189
		git_oid *new_id = git__malloc(sizeof(git_oid));
Vicent Marti committed
190
		GITERR_CHECK_ALLOC(new_id);
191

Vicent Marti committed
192
		git_oid_cpy(new_id, &parent_id);
193

Vicent Marti committed
194
		if (git_vector_insert(&commit->parent_ids, new_id) < 0)
195
			return -1;
196
	}
197

198
	commit->author = git__malloc(sizeof(git_signature));
199 200 201 202
	GITERR_CHECK_ALLOC(commit->author);

	if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0)
		return -1;
203

204
	/* Always parse the committer; we need the commit time */
205
	commit->committer = git__malloc(sizeof(git_signature));
206 207 208 209
	GITERR_CHECK_ALLOC(commit->committer);

	if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
		return -1;
210

211 212 213 214 215
	/* Parse add'l header entries until blank line found */
	while (buffer < buffer_end && *buffer != '\n') {
		const char *eoln = buffer;
		while (eoln < buffer_end && *eoln != '\n')
			++eoln;
216

217 218
		if (git__prefixcmp(buffer, "encoding ") == 0) {
			buffer += strlen("encoding ");
219

220 221 222
			commit->message_encoding = git__strndup(buffer, eoln - buffer);
			GITERR_CHECK_ALLOC(commit->message_encoding);
		}
223

224 225 226
		if (eoln < buffer_end && *eoln == '\n')
			++eoln;

227
		buffer = eoln;
228
	}
229

230 231
	/* buffer is now at the end of the header, double-check and move forward into the message */
	if (buffer < buffer_end && *buffer == '\n')
232 233
		buffer++;

234
	/* parse commit message */
235
	if (buffer <= buffer_end) {
236
		commit->message = git__strndup(buffer, buffer_end - buffer);
237
		GITERR_CHECK_ALLOC(commit->message);
238
	}
239

240 241 242 243 244
	return 0;

bad_buffer:
	giterr_set(GITERR_OBJECT, "Failed to parse bad commit object");
	return -1;
245
}
246

247
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
Vicent Marti committed
248
	_rvalue git_commit_##_name(const git_commit *commit) \
249
	{\
250
		assert(commit); \
251
		return _return; \
252 253
	}

254 255 256
GIT_COMMIT_GETTER(const git_signature *, author, commit->author)
GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer)
GIT_COMMIT_GETTER(const char *, message, commit->message)
257
GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
258
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
259
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
Vicent Marti committed
260 261
GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length)
GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id);
262

Vicent Marti committed
263
int git_commit_tree(git_tree **tree_out, const git_commit *commit)
264 265
{
	assert(commit);
Vicent Marti committed
266
	return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
267
}
268

269 270
const git_oid *git_commit_parent_id(
	const git_commit *commit, unsigned int n)
271 272 273
{
	assert(commit);

Vicent Marti committed
274
	return git_vector_get(&commit->parent_ids, n);
275 276
}

277 278
int git_commit_parent(
	git_commit **parent, const git_commit *commit, unsigned int n)
279
{
Vicent Marti committed
280
	const git_oid *parent_id;
281 282
	assert(commit);

Vicent Marti committed
283 284
	parent_id = git_commit_parent_id(commit, n);
	if (parent_id == NULL) {
285 286 287
		giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
		return GIT_ENOTFOUND;
	}
288

Vicent Marti committed
289
	return git_commit_lookup(parent, commit->object.repo, parent_id);
290
}
291 292 293 294 295 296

int git_commit_nth_gen_ancestor(
	git_commit **ancestor,
	const git_commit *commit,
	unsigned int n)
{
297
	git_commit *current, *parent = NULL;
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
	int error;

	assert(ancestor && commit);

	current = (git_commit *)commit;

	if (n == 0)
		return git_commit_lookup(
			ancestor,
			commit->object.repo,
			git_object_id((const git_object *)commit));

	while (n--) {
		error = git_commit_parent(&parent, (git_commit *)current, 0);

		if (current != commit)
			git_commit_free(current);

		if (error < 0)
			return error;

		current = parent;
	}

	*ancestor = parent;
	return 0;
}