blob.c 7.61 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
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/odb_backend.h"
12 13 14

#include "common.h"
#include "blob.h"
15
#include "filter.h"
16
#include "buf_text.h"
17

18
const void *git_blob_rawcontent(const git_blob *blob)
19
{
20
	assert(blob);
21
	return git_odb_object_data(blob->odb_object);
22 23
}

24
git_off_t git_blob_rawsize(const git_blob *blob)
25 26
{
	assert(blob);
27
	return (git_off_t)git_odb_object_size(blob->odb_object);
28 29
}

30 31 32
int git_blob__getbuf(git_buf *buffer, git_blob *blob)
{
	return git_buf_set(
33 34 35
		buffer,
		git_odb_object_data(blob->odb_object),
		git_odb_object_size(blob->odb_object));
36 37
}

38
void git_blob__free(void *blob)
39
{
40
	git_odb_object_free(((git_blob *)blob)->odb_object);
41
	git__free(blob);
42 43
}

44
int git_blob__parse(void *blob, git_odb_object *odb_obj)
45 46
{
	assert(blob);
Vicent Marti committed
47
	git_cached_obj_incref((git_cached_obj *)odb_obj);
48
	((git_blob *)blob)->odb_object = odb_obj;
49
	return 0;
50 51
}

Vicent Marti committed
52
int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *buffer, size_t len)
53
{
Vicent Marti committed
54
	int error;
55
	git_odb *odb;
Vicent Marti committed
56
	git_odb_stream *stream;
57

58 59
	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
		(error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0)
60 61
		return error;

62 63
	if ((error = stream->write(stream, buffer, len)) == 0)
		error = stream->finalize_write(oid, stream);
64

Vicent Marti committed
65
	stream->free(stream);
66
	return error;
67 68
}

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

77 78
	if ((error = git_odb_open_wstream(
			&stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0)
79 80
		return error;

81 82 83
	if ((fd = git_futils_open_ro(path)) < 0) {
		stream->free(stream);
		return -1;
84 85
	}

Vicent Marti committed
86 87 88
	while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
		error = stream->write(stream, buffer, read_len);
		written += read_len;
89 90 91 92
	}

	p_close(fd);

Vicent Marti committed
93 94 95 96 97
	if (written != file_size || read_len < 0) {
		giterr_set(GITERR_OS, "Failed to read file into stream");
		error = -1;
	}

98 99 100
	if (!error)
		error = stream->finalize_write(oid, stream);

101 102 103 104 105 106 107
	stream->free(stream);
	return error;
}

static int write_file_filtered(
	git_oid *oid,
	git_odb *odb,
108
	const char *full_path,
109 110 111
	git_vector *filters)
{
	int error;
112 113
	git_buf source = GIT_BUF_INIT;
	git_buf dest = GIT_BUF_INIT;
114

115
	if ((error = git_futils_readbuffer(&source, full_path)) < 0)
116 117
		return error;

118
	error = git_filters_apply(&dest, &source, filters);
119

120 121 122
	/* 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);
123

124 125
	/* Write the file to disk if it was properly filtered */
	if (!error)
126
		error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB);
127

128
	git_buf_free(&dest);
129
	return error;
130 131
}

132 133
static int write_symlink(
	git_oid *oid, git_odb *odb, const char *path, size_t link_size)
134 135 136 137 138 139
{
	char *link_data;
	ssize_t read_len;
	int error;

	link_data = git__malloc(link_size);
140
	GITERR_CHECK_ALLOC(link_data);
141 142 143

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

	error = git_odb_write(oid, odb, (void *)link_data, link_size, GIT_OBJ_BLOB);
150
	git__free(link_data);
151 152 153
	return error;
}

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

161 162 163
	assert(hint_path || !try_load_filters);

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

166
	size = st.st_size;
167

168
	if (S_ISLNK(st.st_mode)) {
169
		error = write_symlink(oid, odb, content_path, (size_t)size);
170
	} else {
171
		git_vector write_filters = GIT_VECTOR_INIT;
172
		int filter_count = 0;
173

174 175 176 177 178
		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);
		}
179

180 181 182 183 184 185
		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 */
186
			error = write_file_stream(oid, odb, content_path, size);
187
		} else {
188
			/* We need to apply one or more filters */
189
			error = write_file_filtered(oid, odb, content_path, &write_filters);
190 191
		}

192
		git_filters_free(&write_filters);
193

194
		/*
195 196 197 198 199
		 * 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.
200
		 *
201 202 203
		 * 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`.
204 205 206
		 *
		 * CAREFULLY DESIGNED APIS YO
		 */
Vicent Marti committed
207 208
	}

209 210 211
	return error;
}

Vicent Marti committed
212
int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char *path)
213 214 215 216 217
{
	git_buf full_path = GIT_BUF_INIT;
	const char *workdir;
	int error;

218 219 220
	if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0)
		return error;

221 222 223 224 225 226 227
	workdir = git_repository_workdir(repo);

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

228 229 230
	error = blob_create_internal(
		oid, repo, git_buf_cstr(&full_path),
		git_buf_cstr(&full_path) + strlen(workdir), true);
231

232
	git_buf_free(&full_path);
233
	return error;
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;
240
	const char *workdir, *hintpath;
241 242 243 244 245 246

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

247 248 249 250 251 252 253 254
	hintpath = git_buf_cstr(&full_path);
	workdir  = git_repository_workdir(repo);

	if (workdir && !git__prefixcmp(hintpath, workdir))
		hintpath += strlen(workdir);

	error = blob_create_internal(
		oid, repo, git_buf_cstr(&full_path), hintpath, true);
255 256 257 258

	git_buf_free(&full_path);
	return error;
}
259 260 261 262 263 264 265 266 267 268 269 270 271

#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;
272 273 274 275 276 277 278 279
	git_buf path = GIT_BUF_INIT;

	if (git_buf_join_n(
		&path, '/', 3, 
		git_repository_path(repo),
		GIT_OBJECTS_DIR, 
		"streamed") < 0)
			goto cleanup;
280 281 282 283

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

284
	if (git_filebuf_open(&file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY) < 0)
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
		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:
308
	git_buf_free(&path);
309 310 311 312
	git_filebuf_cleanup(&file);
	git__free(content);
	return error;
}
313 314 315 316 317 318 319

int git_blob_is_binary(git_blob *blob)
{
	git_buf content;

	assert(blob);

Vicent Marti committed
320 321
	content.ptr = blob->odb_object->buffer;
	content.size = min(blob->odb_object->cached.size, 4000);
322 323 324

	return git_buf_text_is_binary(&content);
}