commit.c 9.07 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 88 89
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_tree *tree,
		int parent_count,
		...)
{
	va_list ap;
	int i, error;
90
	const git_commit **parents;
Vicent Marti committed
91

92
	parents = git__malloc(parent_count * sizeof(git_commit *));
Vicent Marti committed
93 94 95

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

	error = git_commit_create(
		oid, repo, update_ref, author, committer, message,
101
		tree, parent_count, parents);
Vicent Marti committed
102

103
	free((void *)parents);
104

Vicent Marti committed
105 106 107
	return error;
}

108
int git_commit_create(
Vicent Marti committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
		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[])
{
	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
132
		return git__throw(GIT_EINVALIDARGS, "Cannot create commit. Failed to parse signature");
Vicent Marti committed
133 134 135 136 137 138 139 140

	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)
141
		return git__rethrow(error, "Failed to create commit");
Vicent Marti committed
142

143 144
	if (git_object_owner((const git_object *)tree) != repo)
		return git__throw(GIT_EINVALIDARGS, "The given tree does not belong to this repository");
145

146 147 148 149 150 151 152 153
	git__write_oid(stream, "tree", git_object_id((const git_object *)tree));

	for (i = 0; i < parent_count; ++i) {
		if (git_object_owner((const git_object *)parents[i]) != repo)
			return git__throw(GIT_EINVALIDARGS, "The given parent does not belong to this repository");

		git__write_oid(stream, "parent", git_object_id((const git_object *)parents[i]));
	}
154

Vicent Marti committed
155 156
	stream->write(stream, author_str, author_length);
	free(author_str);
157

Vicent Marti committed
158 159
	stream->write(stream, committer_str, committer_length);
	free(committer_str);
160

Vicent Marti committed
161 162 163 164 165 166 167 168 169 170 171
	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)
172
			return git__rethrow(error, "Failed to create commit");
Vicent Marti committed
173

174 175 176
		error = git_reference_resolve(&head, head);
		if (error < GIT_SUCCESS) {
			if (error != GIT_ENOTFOUND)
177
				return git__rethrow(error, "Failed to create commit");
178 179 180 181 182 183 184
		/*
		 * 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.
		 */
185
			return git_reference_create_oid(&head, repo, git_reference_target(head), oid, 1);
Vicent Marti committed
186 187 188
		}

		error = git_reference_set_oid(head, oid);
189
	}
190

191 192
	if (error < GIT_SUCCESS)
		return git__rethrow(error, "Failed to create commit");
Vicent Marti committed
193 194

	return GIT_SUCCESS;
195 196
}

197
int commit_parse_buffer(git_commit *commit, const void *data, size_t len)
198
{
199
	const char *buffer = (char *)data;
200
	const char *buffer_end = (char *)data + len;
201

202
	git_oid parent_oid;
Vicent Marti committed
203
	int error;
204

Vicent Marti committed
205
	git_vector_init(&commit->parent_oids, 4, NULL);
206

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

210 211 212
	/*
	 * TODO: commit grafts!
	 */
213

214 215
	while (git__parse_oid(&parent_oid, &buffer, buffer_end, "parent ") == GIT_SUCCESS) {
		git_oid *new_oid;
216

217 218
		new_oid = git__malloc(sizeof(git_oid));
		git_oid_cpy(new_oid, &parent_oid);
219

220
		if (git_vector_insert(&commit->parent_oids, new_oid) < GIT_SUCCESS)
221
			return GIT_ENOMEM;
222
	}
223

224 225
	commit->author = git__malloc(sizeof(git_signature));
	if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ")) < GIT_SUCCESS)
226
		return git__rethrow(error, "Failed to parse buffer");
227

228
	/* Always parse the committer; we need the commit time */
229 230
	commit->committer = git__malloc(sizeof(git_signature));
	if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ")) < GIT_SUCCESS)
231
		return git__rethrow(error, "Failed to parse buffer");
232

233 234 235 236
	/* parse commit message */
	while (buffer <= buffer_end && *buffer == '\n')
		buffer++;

237
	if (buffer < buffer_end) {
238
		const char *line_end;
239
		unsigned int i;
240
		size_t message_len;
241

242
		/* Long message */
243 244 245 246
		message_len = buffer_end - buffer;
		commit->message = git__malloc(message_len + 1);
		memcpy(commit->message, buffer, message_len);
		commit->message[message_len] = 0;
247

248
		/* Short message */
249 250 251 252 253 254 255
		if((line_end = strstr(buffer, "\n\n")) == NULL) {
			/* Cut the last '\n' if there is one */
			if (message_len && buffer[message_len - 1] == '\n')
				line_end = buffer_end - 1;
			else
				line_end = buffer_end;
		}
256 257
		message_len = line_end - buffer;
		commit->message_short = git__malloc(message_len + 1);
258 259 260
		for (i = 0; i < message_len; ++i) {
			commit->message_short[i] = (buffer[i] == '\n') ? ' ' : buffer[i];
		}
261
		commit->message_short[message_len] = 0;
262
	}
263

Vicent Marti committed
264
	return GIT_SUCCESS;
265
}
266

Vicent Marti committed
267
int git_commit__parse(git_commit *commit, git_odb_object *obj)
268
{
Vicent Marti committed
269 270
	assert(commit);
	return commit_parse_buffer(commit, obj->raw.data, obj->raw.len);
271 272
}

273 274
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
	_rvalue git_commit_##_name(git_commit *commit) \
275
	{\
276
		assert(commit); \
277
		return _return; \
278 279
	}

280 281 282 283
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)
284
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
285 286
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length)
287
GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid);
288 289 290 291 292 293 294


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

296
int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
297
{
298
	git_oid *parent_oid;
299 300
	assert(commit);

301 302
	parent_oid = git_vector_get(&commit->parent_oids, n);
	if (parent_oid == NULL)
Vicent Marti committed
303
		return git__throw(GIT_ENOTFOUND, "Parent %u does not exist", n);
304

305
	return git_commit_lookup(parent, commit->object.repo, parent_oid);
306 307
}

308 309 310
const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n)
{
	assert(commit);
311

312 313
	return git_vector_get(&commit->parent_oids, n);
}