commit.c 6.76 KB
Newer Older
1
/*
schu committed
2
 * Copyright (C) 2009-2012 the libgit2 contributors
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

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

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

21 22
static void clear_parents(git_commit *commit)
{
23 24
	unsigned int i;

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

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

33 34
void git_commit__free(git_commit *commit)
{
35
	clear_parents(commit);
Vicent Marti committed
36
	git_vector_free(&commit->parent_ids);
37

38 39
	git_signature_free(commit->author);
	git_signature_free(commit->committer);
40

41 42 43
	git__free(commit->message);
	git__free(commit->message_encoding);
	git__free(commit);
44 45
}

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

62
	parents = git__malloc(parent_count * sizeof(git_commit *));
63
	GITERR_CHECK_ALLOC(parents);
Vicent Marti committed
64 65 66

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

70
	res = git_commit_create(
71 72
		oid, repo, update_ref, author, committer,
		message_encoding, message,
73
		tree, parent_count, parents);
Vicent Marti committed
74

75
	git__free((void *)parents);
76 77 78
	return res;
}

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

95
	assert(git_object_owner((const git_object *)tree) == repo);
96

97
	git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree));
98 99

	for (i = 0; i < parent_count; ++i) {
100
		assert(git_object_owner((const git_object *)parents[i]) == repo);
101
		git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i]));
102
	}
103

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

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

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

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

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

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

121
	git_buf_free(&commit);
122

123
	if (update_ref != NULL)
nulltoken committed
124
		return git_reference__update(repo, oid, update_ref);
Vicent Marti committed
125

126
	return 0;
127

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

134
int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len)
135
{
136 137
	const char *buffer = data;
	const char *buffer_end = (const char *)data + len;
138

Vicent Marti committed
139
	git_oid parent_id;
140

Vicent Marti committed
141
	git_vector_init(&commit->parent_ids, 4, NULL);
142

Vicent Marti committed
143
	if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
144
		goto bad_buffer;
145

146 147 148
	/*
	 * TODO: commit grafts!
	 */
149

Vicent Marti committed
150 151
	while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
		git_oid *new_id;
152

Vicent Marti committed
153 154
		new_id = git__malloc(sizeof(git_oid));
		GITERR_CHECK_ALLOC(new_id);
155

Vicent Marti committed
156
		git_oid_cpy(new_id, &parent_id);
157

Vicent Marti committed
158
		if (git_vector_insert(&commit->parent_ids, new_id) < 0)
159
			return -1;
160
	}
161

162
	commit->author = git__malloc(sizeof(git_signature));
163 164 165 166
	GITERR_CHECK_ALLOC(commit->author);

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

168
	/* Always parse the committer; we need the commit time */
169
	commit->committer = git__malloc(sizeof(git_signature));
170 171 172 173
	GITERR_CHECK_ALLOC(commit->committer);

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

	if (git__prefixcmp(buffer, "encoding ") == 0) {
		const char *encoding_end;
177
		buffer += strlen("encoding ");
178 179 180 181 182 183

		encoding_end = buffer;
		while (encoding_end < buffer_end && *encoding_end != '\n')
			encoding_end++;

		commit->message_encoding = git__strndup(buffer, encoding_end - buffer);
184
		GITERR_CHECK_ALLOC(commit->message_encoding);
185 186 187

		buffer = encoding_end;
	}
188

189
	/* parse commit message */
190
	while (buffer < buffer_end - 1 && *buffer == '\n')
191 192
		buffer++;

193
	if (buffer <= buffer_end) {
194
		commit->message = git__strndup(buffer, buffer_end - buffer);
195
		GITERR_CHECK_ALLOC(commit->message);
196
	}
197

198 199 200 201 202
	return 0;

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

Vicent Marti committed
205
int git_commit__parse(git_commit *commit, git_odb_object *obj)
206
{
Vicent Marti committed
207
	assert(commit);
208
	return git_commit__parse_buffer(commit, obj->raw.data, obj->raw.len);
209 210
}

211
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
Vicent Marti committed
212
	_rvalue git_commit_##_name(const git_commit *commit) \
213
	{\
214
		assert(commit); \
215
		return _return; \
216 217
	}

218 219 220
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)
221
GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
222
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
223
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
Vicent Marti committed
224 225
GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length)
GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id);
226

Vicent Marti committed
227
int git_commit_tree(git_tree **tree_out, const git_commit *commit)
228 229
{
	assert(commit);
Vicent Marti committed
230
	return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
231
}
232

Vicent Marti committed
233
const git_oid *git_commit_parent_id(git_commit *commit, unsigned int n)
234 235 236
{
	assert(commit);

Vicent Marti committed
237
	return git_vector_get(&commit->parent_ids, n);
238 239
}

240
int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
241
{
Vicent Marti committed
242
	const git_oid *parent_id;
243 244
	assert(commit);

Vicent Marti committed
245 246
	parent_id = git_commit_parent_id(commit, n);
	if (parent_id == NULL) {
247 248 249
		giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
		return GIT_ENOTFOUND;
	}
250

Vicent Marti committed
251
	return git_commit_lookup(parent, commit->object.repo, parent_id);
252
}
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

int git_commit_nth_gen_ancestor(
	git_commit **ancestor,
	const git_commit *commit,
	unsigned int n)
{
	git_commit *current, *parent;
	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;
}