commit.c 8.14 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
void git_commit__free(void *_commit)
23
{
24 25
	git_commit *commit = _commit;

26
	git_array_clear(commit->parent_ids);
27

28 29
	git_signature_free(commit->author);
	git_signature_free(commit->committer);
30

31
	git__free(commit->raw_header);
32 33
	git__free(commit->message);
	git__free(commit->message_encoding);
34

35
	git__free(commit);
36 37
}

Vicent Marti committed
38
int git_commit_create_v(
39 40 41 42 43 44 45 46 47 48
	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
49 50
{
	va_list ap;
51
	int i, res;
52
	const git_commit **parents;
Vicent Marti committed
53

54
	parents = git__malloc(parent_count * sizeof(git_commit *));
55
	GITERR_CHECK_ALLOC(parents);
Vicent Marti committed
56 57 58

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

62
	res = git_commit_create(
63 64
		oid, repo, update_ref, author, committer,
		message_encoding, message,
65
		tree, parent_count, parents);
Vicent Marti committed
66

67
	git__free((void *)parents);
68 69 70
	return res;
}

71 72 73 74 75 76 77 78 79 80 81
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
82
{
83
	git_buf commit = GIT_BUF_INIT;
84
	int i;
85
	git_odb *odb;
Vicent Marti committed
86

87 88
	assert(oid && repo && tree && parent_count >= 0);

89
	git_oid__writebuf(&commit, "tree ", tree);
90

91 92
	for (i = 0; i < parent_count; ++i)
		git_oid__writebuf(&commit, "parent ", parents[i]);
93

94 95
	git_signature__writebuf(&commit, "author ", author);
	git_signature__writebuf(&commit, "committer ", committer);
96

97 98 99
	if (message_encoding != NULL)
		git_buf_printf(&commit, "encoding %s\n", message_encoding);

100
	git_buf_putc(&commit, '\n');
101

102
	if (git_buf_puts(&commit, message) < 0)
103 104
		goto on_error;

105 106
	if (git_repository_odb__weakptr(&odb, repo) < 0)
		goto on_error;
107

108 109
	if (git_odb_write(oid, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0)
		goto on_error;
Vicent Marti committed
110

111
	git_buf_free(&commit);
112

113
	if (update_ref != NULL)
114
		return git_reference__update_terminal(repo, update_ref, oid);
Vicent Marti committed
115

116
	return 0;
117

118
on_error:
119
	git_buf_free(&commit);
120
	giterr_set(GITERR_OBJECT, "Failed to create commit.");
121
	return -1;
122 123
}

124
int git_commit_create(
125 126 127 128 129 130 131 132 133 134
	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[])
135
{
136
	int retval, i;
137 138
	const git_oid **parent_oids;

139
	assert(parent_count >= 0);
140
	assert(git_object_owner((const git_object *)tree) == repo);
141 142 143 144 145 146 147 148 149

	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]);
	}

150 151 152 153
	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);
154 155

	git__free((void *)parent_oids);
156 157

	return retval;
158 159
}

160
int git_commit__parse(void *_commit, git_odb_object *odb_obj)
161
{
162
	git_commit *commit = _commit;
163 164
	const char *buffer_start = git_odb_object_data(odb_obj), *buffer;
	const char *buffer_end = buffer_start + git_odb_object_size(odb_obj);
Vicent Marti committed
165
	git_oid parent_id;
Russell Belfer committed
166 167
	uint32_t parent_count = 0;
	size_t header_len;
168

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
	/* find end-of-header (counting parents as we go) */
	for (buffer = buffer_start; buffer < buffer_end; ++buffer) {
		if (!strncmp("\n\n", buffer, 2)) {
			++buffer;
			break;
		}
		if (!strncmp("\nparent ", buffer, strlen("\nparent ")))
			++parent_count;
	}

	header_len = buffer - buffer_start;
	commit->raw_header = git__strndup(buffer_start, header_len);
	GITERR_CHECK_ALLOC(commit->raw_header);

	/* point "buffer" to header data */
	buffer = commit->raw_header;
	buffer_end = commit->raw_header + header_len;

	if (parent_count < 1)
		parent_count = 1;

190 191
	git_array_init_to_size(commit->parent_ids, parent_count);
	GITERR_CHECK_ARRAY(commit->parent_ids);
192

Vicent Marti committed
193
	if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
194
		goto bad_buffer;
195

196 197 198
	/*
	 * TODO: commit grafts!
	 */
199

Vicent Marti committed
200
	while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
201
		git_oid *new_id = git_array_alloc(commit->parent_ids);
Vicent Marti committed
202
		GITERR_CHECK_ALLOC(new_id);
203

Vicent Marti committed
204
		git_oid_cpy(new_id, &parent_id);
205
	}
206

207
	commit->author = git__malloc(sizeof(git_signature));
208 209 210 211
	GITERR_CHECK_ALLOC(commit->author);

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

213
	/* Always parse the committer; we need the commit time */
214
	commit->committer = git__malloc(sizeof(git_signature));
215 216 217 218
	GITERR_CHECK_ALLOC(commit->committer);

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

220 221
	/* Parse add'l header entries */
	while (buffer < buffer_end) {
222 223 224
		const char *eoln = buffer;
		while (eoln < buffer_end && *eoln != '\n')
			++eoln;
225

226 227
		if (git__prefixcmp(buffer, "encoding ") == 0) {
			buffer += strlen("encoding ");
228

229 230 231
			commit->message_encoding = git__strndup(buffer, eoln - buffer);
			GITERR_CHECK_ALLOC(commit->message_encoding);
		}
232

233 234
		if (eoln < buffer_end && *eoln == '\n')
			++eoln;
235
		buffer = eoln;
236
	}
237

238 239 240 241 242
	/* point "buffer" to data after header */
	buffer = git_odb_object_data(odb_obj);
	buffer_end = buffer + git_odb_object_size(odb_obj);

	buffer += header_len;
243
	while (buffer < buffer_end && *buffer == '\n')
244
		++buffer;
245

246
	/* extract commit message */
247
	if (buffer <= buffer_end) {
248
		commit->message = git__strndup(buffer, buffer_end - buffer);
249
		GITERR_CHECK_ALLOC(commit->message);
250
	}
251

252 253 254 255 256
	return 0;

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

259
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
Vicent Marti committed
260
	_rvalue git_commit_##_name(const git_commit *commit) \
261
	{\
262
		assert(commit); \
263
		return _return; \
264 265
	}

266 267 268
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)
269
GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
270
GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header)
271
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
272
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
273
GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids))
Vicent Marti committed
274
GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id);
275

Vicent Marti committed
276
int git_commit_tree(git_tree **tree_out, const git_commit *commit)
277 278
{
	assert(commit);
Vicent Marti committed
279
	return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
280
}
281

282 283
const git_oid *git_commit_parent_id(
	const git_commit *commit, unsigned int n)
284 285 286
{
	assert(commit);

287
	return git_array_get(commit->parent_ids, n);
288 289
}

290 291
int git_commit_parent(
	git_commit **parent, const git_commit *commit, unsigned int n)
292
{
Vicent Marti committed
293
	const git_oid *parent_id;
294 295
	assert(commit);

Vicent Marti committed
296 297
	parent_id = git_commit_parent_id(commit, n);
	if (parent_id == NULL) {
298 299 300
		giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
		return GIT_ENOTFOUND;
	}
301

Vicent Marti committed
302
	return git_commit_lookup(parent, commit->object.repo, parent_id);
303
}
304 305 306 307 308 309

int git_commit_nth_gen_ancestor(
	git_commit **ancestor,
	const git_commit *commit,
	unsigned int n)
{
310
	git_commit *current, *parent = NULL;
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
	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;
}