commit.c 9.75 KB
Newer Older
1
/*
2 3 4
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
5
 *
6 7 8 9 10 11 12 13
 * In addition to the permissions in the GNU General Public License,
 * the authors give you unlimited permission to link the compiled
 * version of this file into combinations with other programs,
 * and to distribute those combinations without any restriction
 * coming from the use of this file.  (The General Public License
 * restrictions do apply in other respects; for example, they cover
 * modification of the file, and distribution when not linked into
 * a combined executable.)
14
 *
15 16 17 18
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
19
 *
20 21 22 23
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
24 25
 */

26 27 28
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
29
#include "git2/signature.h"
30

31
#include "common.h"
Vicent Marti committed
32
#include "odb.h"
33
#include "commit.h"
34
#include "signature.h"
35

Vicent Marti committed
36 37
#include <stdarg.h>

38 39 40
#define COMMIT_BASIC_PARSE 0x0
#define COMMIT_FULL_PARSE 0x1

41
#define COMMIT_PRINT(commit) {\
42 43 44
	char oid[41]; oid[40] = 0;\
	git_oid_fmt(oid, &commit->object.id);\
	printf("Oid: %s | In degree: %d | Time: %u\n", oid, commit->in_degree, commit->commit_time);\
45 46
}

47 48
static void clear_parents(git_commit *commit)
{
49 50
	unsigned int i;

51 52 53
	for (i = 0; i < commit->parent_oids.length; ++i) {
		git_oid *parent = git_vector_get(&commit->parent_oids, i);
		free(parent);
54 55
	}

56
	git_vector_clear(&commit->parent_oids);
57 58
}

59 60
void git_commit__free(git_commit *commit)
{
61
	clear_parents(commit);
62
	git_vector_free(&commit->parent_oids);
63

64 65
	git_signature_free(commit->author);
	git_signature_free(commit->committer);
66

67 68
	free(commit->message);
	free(commit->message_short);
69 70 71
	free(commit);
}

72
const git_oid *git_commit_id(git_commit *c)
73
{
74
	return git_object_id((git_object *)c);
75
}
76

Vicent Marti committed
77 78 79 80 81 82 83 84 85 86 87

int git_commit_create_v(
		git_oid *oid,
		git_repository *repo,
		const char *update_ref,
		const git_signature *author,
		const git_signature *committer,
		const char *message,
		const git_oid *tree_oid,
		int parent_count,
		...)
88
{
Vicent Marti committed
89 90 91
	va_list ap;
	int i, error;
	const git_oid **oids;
92

Vicent Marti committed
93
	oids = git__malloc(parent_count * sizeof(git_oid *));
94

Vicent Marti committed
95 96 97 98
	va_start(ap, parent_count);
	for (i = 0; i < parent_count; ++i)
		oids[i] = va_arg(ap, const git_oid *);
	va_end(ap);
99

Vicent Marti committed
100 101 102 103
	error = git_commit_create(
		oid, repo, update_ref, author, committer, message,
		tree_oid, parent_count, oids);

Vicent Marti committed
104
	free((void *)oids);
105

Vicent Marti committed
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
	return error;
}

int git_commit_create_ov(
		git_oid *oid,
		git_repository *repo,
		const char *update_ref,
		const git_signature *author,
		const git_signature *committer,
		const char *message,
		const git_tree *tree,
		int parent_count,
		...)
{
	va_list ap;
	int i, error;
	const git_oid **oids;

	oids = git__malloc(parent_count * sizeof(git_oid *));

	va_start(ap, parent_count);
	for (i = 0; i < parent_count; ++i)
		oids[i] = git_object_id(va_arg(ap, const git_object *));
	va_end(ap);

	error = git_commit_create(
		oid, repo, update_ref, author, committer, message,
		git_object_id((git_object *)tree),
		parent_count, oids);

Vicent Marti committed
136
	free((void *)oids);
137

Vicent Marti committed
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
	return error;
}

int git_commit_create_o(
		git_oid *oid,
		git_repository *repo,
		const char *update_ref,
		const git_signature *author,
		const git_signature *committer,
		const char *message,
		const git_tree *tree,
		int parent_count,
		const git_commit *parents[])
{
	int i, error;
	const git_oid **oids;

	oids = git__malloc(parent_count * sizeof(git_oid *));

	for (i = 0; i < parent_count; ++i)
		oids[i] = git_object_id((git_object *)parents[i]);

	error = git_commit_create(
		oid, repo, update_ref, author, committer, message,
		git_object_id((git_object *)tree),
		parent_count, oids);
	
Vicent Marti committed
165
	free((void *)oids);
166

Vicent Marti committed
167 168
	return error;
}
169

Vicent Marti committed
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
int git_commit_create(
		git_oid *oid,
		git_repository *repo,
		const char *update_ref,
		const git_signature *author,
		const git_signature *committer,
		const char *message,
		const git_oid *tree_oid,
		int parent_count,
		const git_oid *parents[])
{
	size_t final_size = 0;
	int message_length, author_length, committer_length;

	char *author_str, *committer_str;

	int error, i;
	git_odb_stream *stream;

	message_length = strlen(message);
	author_length = git_signature__write(&author_str, "author", author);
	committer_length = git_signature__write(&committer_str, "committer", committer);

	if (author_length < 0 || committer_length < 0)
Vicent Marti committed
194
		return git__throw(GIT_EINVALIDARGS, "Cannot create commit. Failed to parse signature");
Vicent Marti committed
195 196 197 198 199 200 201 202

	final_size += GIT_OID_LINE_LENGTH("tree");
	final_size += GIT_OID_LINE_LENGTH("parent") * parent_count;
	final_size += author_length;
	final_size += committer_length;
	final_size += 1 + message_length;

	if ((error = git_odb_open_wstream(&stream, repo->db, final_size, GIT_OBJ_COMMIT)) < GIT_SUCCESS)
203
		return git__rethrow(error, "Failed to create commit");
Vicent Marti committed
204 205

	git__write_oid(stream, "tree", tree_oid);
206

Vicent Marti committed
207 208
	for (i = 0; i < parent_count; ++i)
		git__write_oid(stream, "parent", parents[i]);
209

Vicent Marti committed
210 211
	stream->write(stream, author_str, author_length);
	free(author_str);
212

Vicent Marti committed
213 214
	stream->write(stream, committer_str, committer_length);
	free(committer_str);
215

Vicent Marti committed
216 217 218 219 220 221 222 223 224 225 226 227

	stream->write(stream, "\n", 1);
	stream->write(stream, message, message_length);

	error = stream->finalize_write(oid, stream);
	stream->free(stream);

	if (error == GIT_SUCCESS && update_ref != NULL) {
		git_reference *head;

		error = git_reference_lookup(&head, repo, update_ref);
		if (error < GIT_SUCCESS)
228
			return git__rethrow(error, "Failed to create commit");
Vicent Marti committed
229

230 231 232
		error = git_reference_resolve(&head, head);
		if (error < GIT_SUCCESS) {
			if (error != GIT_ENOTFOUND)
233
				return git__rethrow(error, "Failed to create commit");
234 235 236 237 238 239 240 241
		/*
		 * The target of the reference was not found. This can happen
		 * just after a repository has been initialized (the master
		 * branch doesn't exist yet, as it doesn't have anything to
		 * point to) or after an orphan checkout, so if the target
		 * branch doesn't exist yet, create it and return.
		 */
			return git_reference_create_oid_f(&head, repo, git_reference_target(head), oid);
Vicent Marti committed
242 243 244
		}

		error = git_reference_set_oid(head, oid);
245
	}
246

247 248
	if (error < GIT_SUCCESS)
		return git__rethrow(error, "Failed to create commit");
Vicent Marti committed
249 250

	return GIT_SUCCESS;
251 252
}

253
int commit_parse_buffer(git_commit *commit, const void *data, size_t len)
254
{
255
	const char *buffer = (char *)data;
256
	const char *buffer_end = (char *)data + len;
257

258
	git_oid parent_oid;
Vicent Marti committed
259
	int error;
260

Vicent Marti committed
261
	git_vector_init(&commit->parent_oids, 4, NULL);
262

263
	if ((error = git__parse_oid(&commit->tree_oid, &buffer, buffer_end, "tree ")) < GIT_SUCCESS)
264
		return git__rethrow(error, "Failed to parse buffer");
265

266 267 268
	/*
	 * TODO: commit grafts!
	 */
269

270 271
	while (git__parse_oid(&parent_oid, &buffer, buffer_end, "parent ") == GIT_SUCCESS) {
		git_oid *new_oid;
272

273 274
		new_oid = git__malloc(sizeof(git_oid));
		git_oid_cpy(new_oid, &parent_oid);
275

276
		if (git_vector_insert(&commit->parent_oids, new_oid) < GIT_SUCCESS)
277
			return GIT_ENOMEM;
278
	}
279

280 281
	commit->author = git__malloc(sizeof(git_signature));
	if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ")) < GIT_SUCCESS)
282
		return git__rethrow(error, "Failed to parse buffer");
283

284
	/* Always parse the committer; we need the commit time */
285 286
	commit->committer = git__malloc(sizeof(git_signature));
	if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ")) < GIT_SUCCESS)
287
		return git__rethrow(error, "Failed to parse buffer");
288

289 290 291 292
	/* parse commit message */
	while (buffer <= buffer_end && *buffer == '\n')
		buffer++;

293
	if (buffer < buffer_end) {
294
		const char *line_end;
295
		size_t message_len;
296

297
		/* Long message */
298 299 300 301
		message_len = buffer_end - buffer;
		commit->message = git__malloc(message_len + 1);
		memcpy(commit->message, buffer, message_len);
		commit->message[message_len] = 0;
302

303 304 305
		/* Short message */
		if((line_end = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
			line_end = buffer_end;
306
		message_len = line_end - buffer;
307

308 309 310
		commit->message_short = git__malloc(message_len + 1);
		memcpy(commit->message_short, buffer, message_len);
		commit->message_short[message_len] = 0;
311
	}
312

Vicent Marti committed
313
	return GIT_SUCCESS;
314
}
315

Vicent Marti committed
316
int git_commit__parse(git_commit *commit, git_odb_object *obj)
317
{
Vicent Marti committed
318 319
	assert(commit);
	return commit_parse_buffer(commit, obj->raw.data, obj->raw.len);
320 321
}

322 323
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
	_rvalue git_commit_##_name(git_commit *commit) \
324
	{\
325
		assert(commit); \
326
		return _return; \
327 328
	}

329 330 331 332
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)
GIT_COMMIT_GETTER(const char *, message_short, commit->message_short)
333
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
334 335
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length)
336
GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid);
337 338 339 340 341 342 343


int git_commit_tree(git_tree **tree_out, git_commit *commit)
{
	assert(commit);
	return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_oid);
}
344

345
int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
346
{
347
	git_oid *parent_oid;
348 349
	assert(commit);

350 351
	parent_oid = git_vector_get(&commit->parent_oids, n);
	if (parent_oid == NULL)
Vicent Marti committed
352
		return git__throw(GIT_ENOTFOUND, "Parent %u does not exist", n);
353

354
	return git_commit_lookup(parent, commit->object.repo, parent_oid);
355 356
}

357 358 359
const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n)
{
	assert(commit);
360

361 362
	return git_vector_get(&commit->parent_oids, n);
}