blob.c 6.88 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 12 13

#include "common.h"
#include "blob.h"
14
#include "filter.h"
15

16
const void *git_blob_rawcontent(git_blob *blob)
17
{
18
	assert(blob);
Vicent Marti committed
19
	return blob->odb_object->raw.data;
20 21
}

22
size_t git_blob_rawsize(git_blob *blob)
23 24
{
	assert(blob);
Vicent Marti committed
25
	return blob->odb_object->raw.len;
26 27
}

28 29 30 31 32 33
int git_blob__getbuf(git_buf *buffer, git_blob *blob)
{
	return git_buf_set(
		buffer, blob->odb_object->raw.data, blob->odb_object->raw.len);
}

34 35
void git_blob__free(git_blob *blob)
{
36
	git_odb_object_free(blob->odb_object);
37
	git__free(blob);
38 39
}

Vicent Marti committed
40
int git_blob__parse(git_blob *blob, git_odb_object *odb_obj)
41 42
{
	assert(blob);
Vicent Marti committed
43 44
	git_cached_obj_incref((git_cached_obj *)odb_obj);
	blob->odb_object = odb_obj;
45
	return 0;
46 47
}

Vicent Marti committed
48
int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *buffer, size_t len)
49
{
Vicent Marti committed
50
	int error;
51
	git_odb *odb;
Vicent Marti committed
52
	git_odb_stream *stream;
53

54 55
	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
		(error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0)
56 57
		return error;

58 59
	if ((error = stream->write(stream, buffer, len)) == 0)
		error = stream->finalize_write(oid, stream);
60

Vicent Marti committed
61
	stream->free(stream);
62
	return error;
63 64
}

65 66
static int write_file_stream(
	git_oid *oid, git_odb *odb, const char *path, git_off_t file_size)
67 68 69 70
{
	int fd, error;
	char buffer[4096];
	git_odb_stream *stream = NULL;
71
	ssize_t read_len = -1, written = 0;
72

73 74
	if ((error = git_odb_open_wstream(
			&stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0)
75 76
		return error;

77 78 79
	if ((fd = git_futils_open_ro(path)) < 0) {
		stream->free(stream);
		return -1;
80 81
	}

Vicent Marti committed
82 83 84
	while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
		error = stream->write(stream, buffer, read_len);
		written += read_len;
85 86 87 88
	}

	p_close(fd);

Vicent Marti committed
89 90 91 92 93
	if (written != file_size || read_len < 0) {
		giterr_set(GITERR_OS, "Failed to read file into stream");
		error = -1;
	}

94 95 96
	if (!error)
		error = stream->finalize_write(oid, stream);

97 98 99 100 101 102 103
	stream->free(stream);
	return error;
}

static int write_file_filtered(
	git_oid *oid,
	git_odb *odb,
104
	const char *full_path,
105 106 107
	git_vector *filters)
{
	int error;
108 109
	git_buf source = GIT_BUF_INIT;
	git_buf dest = GIT_BUF_INIT;
110

111
	if ((error = git_futils_readbuffer(&source, full_path)) < 0)
112 113
		return error;

114
	error = git_filters_apply(&dest, &source, filters);
115

116 117 118
	/* Free the source as soon as possible. This can be big in memory,
	 * and we don't want to ODB write to choke */
	git_buf_free(&source);
119

120 121
	/* Write the file to disk if it was properly filtered */
	if (!error)
122
		error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB);
123

124
	git_buf_free(&dest);
125
	return error;
126 127
}

128 129
static int write_symlink(
	git_oid *oid, git_odb *odb, const char *path, size_t link_size)
130 131 132 133 134 135
{
	char *link_data;
	ssize_t read_len;
	int error;

	link_data = git__malloc(link_size);
136
	GITERR_CHECK_ALLOC(link_data);
137 138 139

	read_len = p_readlink(path, link_data, link_size);
	if (read_len != (ssize_t)link_size) {
140
		giterr_set(GITERR_OS, "Failed to create blob.  Can't read symlink '%s'", path);
141
		git__free(link_data);
142
		return -1;
143 144 145
	}

	error = git_odb_write(oid, odb, (void *)link_data, link_size, GIT_OBJ_BLOB);
146
	git__free(link_data);
147 148 149
	return error;
}

150
static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters)
151
{
152
	int error;
153
	struct stat st;
154
	git_odb *odb = NULL;
155
	git_off_t size;
156

157 158 159
	assert(hint_path || !try_load_filters);

	if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0)
160
		return error;
161

162
	size = st.st_size;
163

164
	if (S_ISLNK(st.st_mode)) {
165
		error = write_symlink(oid, odb, content_path, (size_t)size);
166
	} else {
167
		git_vector write_filters = GIT_VECTOR_INIT;
168
		int filter_count = 0;
169

170 171 172 173 174
		if (try_load_filters) {
			/* Load the filters for writing this file to the ODB */
			filter_count = git_filters_load(
				&write_filters, repo, hint_path, GIT_FILTER_TO_ODB);
		}
175

176 177 178 179 180 181
		if (filter_count < 0) {
			/* Negative value means there was a critical error */
			error = filter_count;
		} else if (filter_count == 0) {
			/* No filters need to be applied to the document: we can stream
			 * directly from disk */
182
			error = write_file_stream(oid, odb, content_path, size);
183
		} else {
184
			/* We need to apply one or more filters */
185
			error = write_file_filtered(oid, odb, content_path, &write_filters);
186 187
		}

188
		git_filters_free(&write_filters);
189

190
		/*
191 192 193 194 195
		 * TODO: eventually support streaming filtered files, for files
		 * which are bigger than a given threshold. This is not a priority
		 * because applying a filter in streaming mode changes the final
		 * size of the blob, and without knowing its final size, the blob
		 * cannot be written in stream mode to the ODB.
196
		 *
197 198 199
		 * The plan is to do streaming writes to a tempfile on disk and then
		 * opening streaming that file to the ODB, using
		 * `write_file_stream`.
200 201 202
		 *
		 * CAREFULLY DESIGNED APIS YO
		 */
Vicent Marti committed
203 204
	}

205 206 207 208 209 210 211 212 213
	return error;
}

int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path)
{
	git_buf full_path = GIT_BUF_INIT;
	const char *workdir;
	int error;

214 215 216
	if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0)
		return error;

217 218 219 220 221 222 223
	workdir = git_repository_workdir(repo);

	if (git_buf_joinpath(&full_path, workdir, path) < 0) {
		git_buf_free(&full_path);
		return -1;
	}

224
	error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true);
225

226
	git_buf_free(&full_path);
227
	return error;
228 229
}

230 231 232 233 234 235 236 237 238 239
int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path)
{
	int error;
	git_buf full_path = GIT_BUF_INIT;

	if ((error = git_path_prettify(&full_path, path, NULL)) < 0) {
		git_buf_free(&full_path);
		return error;
	}

240
	error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true);
241 242 243 244

	git_buf_free(&full_path);
	return error;
}
245 246 247 248 249 250 251 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 287 288 289

#define BUFFER_SIZE 4096

int git_blob_create_fromchunks(
	git_oid *oid,
	git_repository *repo,
	const char *hintpath,
	int (*source_cb)(char *content, size_t max_length, void *payload),
	void *payload)
{
	int error = -1, read_bytes;
	char *content = NULL;
	git_filebuf file = GIT_FILEBUF_INIT;

	content = git__malloc(BUFFER_SIZE);
	GITERR_CHECK_ALLOC(content);

	if (git_filebuf_open(&file, hintpath == NULL ? "streamed" : hintpath, GIT_FILEBUF_TEMPORARY) < 0)
		goto cleanup;

	while (1) {
		read_bytes = source_cb(content, BUFFER_SIZE, payload);

		assert(read_bytes <= BUFFER_SIZE);

		if (read_bytes <= 0)
			break;

		if (git_filebuf_write(&file, content, read_bytes) < 0)
			goto cleanup;
	}

	if (read_bytes < 0)
		goto cleanup;

	if (git_filebuf_flush(&file) < 0)
		goto cleanup;

	error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL);

cleanup:
	git_filebuf_cleanup(&file);
	git__free(content);
	return error;
}