commit.c 8.52 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
		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[])
{
119
	git_buf commit = GIT_BUF_INIT;
Vicent Marti committed
120 121
	int error, i;

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

125
	git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree));
126 127

	for (i = 0; i < parent_count; ++i) {
128 129 130 131
		if (git_object_owner((const git_object *)parents[i]) != repo) {
			error = git__throw(GIT_EINVALIDARGS, "The given parent does not belong to this repository");
			goto cleanup;
		}
132

133
		git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i]));
134
	}
135

136 137
	git_signature__writebuf(&commit, "author ", author);
	git_signature__writebuf(&commit, "committer ", committer);
138

139 140
	git_buf_putc(&commit, '\n');
	git_buf_puts(&commit, message);
141

142 143 144 145
	if (git_buf_oom(&commit)) {
		error = git__throw(GIT_ENOMEM, "Not enough memory to build the commit data");
		goto cleanup;
	}
Vicent Marti committed
146

147 148
	error = git_odb_write(oid, git_repository_database(repo), commit.ptr, commit.size, GIT_OBJ_COMMIT);
	git_buf_free(&commit);
Vicent Marti committed
149 150 151 152 153 154

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

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

157 158 159
		error = git_reference_resolve(&head, head);
		if (error < GIT_SUCCESS) {
			if (error != GIT_ENOTFOUND)
160
				return git__rethrow(error, "Failed to create commit");
161 162 163 164 165 166 167
		/*
		 * 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.
		 */
168
			return git_reference_create_oid(&head, repo, git_reference_target(head), oid, 1);
Vicent Marti committed
169 170 171
		}

		error = git_reference_set_oid(head, oid);
172
	}
173

174 175
	if (error < GIT_SUCCESS)
		return git__rethrow(error, "Failed to create commit");
Vicent Marti committed
176 177

	return GIT_SUCCESS;
178 179 180 181

cleanup:
	git_buf_free(&commit);
	return error;
182 183
}

184
int commit_parse_buffer(git_commit *commit, const void *data, size_t len)
185
{
186 187
	const char *buffer = data;
	const char *buffer_end = (const char *)data + len;
188

189
	git_oid parent_oid;
Vicent Marti committed
190
	int error;
191

Vicent Marti committed
192
	git_vector_init(&commit->parent_oids, 4, NULL);
193

Vicent Marti committed
194
	if ((error = git_oid__parse(&commit->tree_oid, &buffer, buffer_end, "tree ")) < GIT_SUCCESS)
195
		return git__rethrow(error, "Failed to parse buffer");
196

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

Vicent Marti committed
201
	while (git_oid__parse(&parent_oid, &buffer, buffer_end, "parent ") == GIT_SUCCESS) {
202
		git_oid *new_oid;
203

204 205
		new_oid = git__malloc(sizeof(git_oid));
		git_oid_cpy(new_oid, &parent_oid);
206

207
		if (git_vector_insert(&commit->parent_oids, new_oid) < GIT_SUCCESS)
208
			return GIT_ENOMEM;
209
	}
210

211
	commit->author = git__malloc(sizeof(git_signature));
212
	if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n')) < GIT_SUCCESS)
213
		return git__rethrow(error, "Failed to parse buffer");
214

215
	/* Always parse the committer; we need the commit time */
216
	commit->committer = git__malloc(sizeof(git_signature));
217
	if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n')) < GIT_SUCCESS)
218
		return git__rethrow(error, "Failed to parse buffer");
219

220 221 222 223
	/* parse commit message */
	while (buffer <= buffer_end && *buffer == '\n')
		buffer++;

224
	if (buffer < buffer_end) {
225
		const char *line_end;
226
		unsigned int i;
227
		size_t message_len;
228

229
		/* Long message */
230 231 232 233
		message_len = buffer_end - buffer;
		commit->message = git__malloc(message_len + 1);
		memcpy(commit->message, buffer, message_len);
		commit->message[message_len] = 0;
234

235
		/* Short message */
236 237 238 239 240 241 242
		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;
		}
243 244
		message_len = line_end - buffer;
		commit->message_short = git__malloc(message_len + 1);
245 246 247
		for (i = 0; i < message_len; ++i) {
			commit->message_short[i] = (buffer[i] == '\n') ? ' ' : buffer[i];
		}
248
		commit->message_short[message_len] = 0;
249
	}
250

Vicent Marti committed
251
	return GIT_SUCCESS;
252
}
253

Vicent Marti committed
254
int git_commit__parse(git_commit *commit, git_odb_object *obj)
255
{
Vicent Marti committed
256 257
	assert(commit);
	return commit_parse_buffer(commit, obj->raw.data, obj->raw.len);
258 259
}

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

267 268 269 270
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)
271
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
272 273
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length)
274
GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid);
275 276 277 278 279 280 281


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

283
int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
284
{
285
	git_oid *parent_oid;
286 287
	assert(commit);

288 289
	parent_oid = git_vector_get(&commit->parent_oids, n);
	if (parent_oid == NULL)
Vicent Marti committed
290
		return git__throw(GIT_ENOTFOUND, "Parent %u does not exist", n);
291

292
	return git_commit_lookup(parent, commit->object.repo, parent_oid);
293 294
}

295 296 297
const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n)
{
	assert(commit);
298

299 300
	return git_vector_get(&commit->parent_oids, n);
}