blob.c 8.17 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

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

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

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

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

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

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

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

60 61
	assert(id && repo);

62 63
	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
		(error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0)
64 65
		return error;

66
	if ((error = git_odb_stream_write(stream, buffer, len)) == 0)
67
		error = git_odb_stream_finalize_write(id, stream);
68

69
	git_odb_stream_free(stream);
70
	return error;
71 72
}

73
static int write_file_stream(
74
	git_oid *id, git_odb *odb, const char *path, git_off_t file_size)
75 76 77 78
{
	int fd, error;
	char buffer[4096];
	git_odb_stream *stream = NULL;
79
	ssize_t read_len = -1, written = 0;
80

81 82
	if ((error = git_odb_open_wstream(
			&stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0)
83 84
		return error;

85
	if ((fd = git_futils_open_ro(path)) < 0) {
86
		git_odb_stream_free(stream);
87
		return -1;
88 89
	}

Vicent Marti committed
90
	while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
91
		error = git_odb_stream_write(stream, buffer, read_len);
Vicent Marti committed
92
		written += read_len;
93 94 95 96
	}

	p_close(fd);

Vicent Marti committed
97 98 99 100 101
	if (written != file_size || read_len < 0) {
		giterr_set(GITERR_OS, "Failed to read file into stream");
		error = -1;
	}

102
	if (!error)
103
		error = git_odb_stream_finalize_write(id, stream);
104

105
	git_odb_stream_free(stream);
106 107 108 109
	return error;
}

static int write_file_filtered(
110
	git_oid *id,
111
	git_off_t *size,
112
	git_odb *odb,
113
	const char *full_path,
114
	git_filter_list *fl)
115 116
{
	int error;
117
	git_buf tgt = GIT_BUF_INIT;
118

119
	error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path);
120

121
	/* Write the file to disk if it was properly filtered */
122
	if (!error) {
123
		*size = tgt.size;
124

125
		error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJ_BLOB);
126
	}
127

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

132
static int write_symlink(
133
	git_oid *id, 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(id, odb, (void *)link_data, link_size, GIT_OBJ_BLOB);
150
	git__free(link_data);
151 152 153
	return error;
}

154
int git_blob__create_from_paths(
155
	git_oid *id,
156 157 158 159 160 161
	struct stat *out_st,
	git_repository *repo,
	const char *content_path,
	const char *hint_path,
	mode_t hint_mode,
	bool try_load_filters)
162
{
163
	int error;
164
	struct stat st;
165
	git_odb *odb = NULL;
166
	git_off_t size;
167 168
	mode_t mode;
	git_buf path = GIT_BUF_INIT;
169

170 171
	assert(hint_path || !try_load_filters);

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
	if (!content_path) {
		if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
			return GIT_EBAREREPO;

		if (git_buf_joinpath(
				&path, git_repository_workdir(repo), hint_path) < 0)
			return -1;

		content_path = path.ptr;
	}

	if ((error = git_path_lstat(content_path, &st)) < 0 ||
		(error = git_repository_odb(&odb, repo)) < 0)
		goto done;

	if (out_st)
		memcpy(out_st, &st, sizeof(st));
189

190
	size = st.st_size;
191
	mode = hint_mode ? hint_mode : st.st_mode;
192

Russell Belfer committed
193
	if (S_ISLNK(mode)) {
194
		error = write_symlink(id, odb, content_path, (size_t)size);
195
	} else {
196
		git_filter_list *fl = NULL;
197

198
		if (try_load_filters)
199
			/* Load the filters for writing this file to the ODB */
200
			error = git_filter_list_load(
Russell Belfer committed
201
				&fl, repo, NULL, hint_path, GIT_FILTER_TO_ODB);
202

203 204 205
		if (error < 0)
			/* well, that didn't work */;
		else if (fl == NULL)
206 207
			/* No filters need to be applied to the document: we can stream
			 * directly from disk */
208
			error = write_file_stream(id, odb, content_path, size);
209
		else {
210
			/* We need to apply one or more filters */
211
			error = write_file_filtered(id, &size, odb, content_path, fl);
212

213 214
			git_filter_list_free(fl);
		}
215

216
		/*
217 218 219 220 221
		 * 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.
222
		 *
223 224 225
		 * 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`.
226 227 228
		 *
		 * CAREFULLY DESIGNED APIS YO
		 */
Vicent Marti committed
229 230
	}

231 232 233 234
done:
	git_odb_free(odb);
	git_buf_free(&path);

235 236 237
	return error;
}

238
int git_blob_create_fromworkdir(
239
	git_oid *id, git_repository *repo, const char *path)
240
{
241
	return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true);
242 243
}

244
int git_blob_create_fromdisk(
245
	git_oid *id, git_repository *repo, const char *path)
246 247 248
{
	int error;
	git_buf full_path = GIT_BUF_INIT;
249
	const char *workdir, *hintpath;
250 251 252 253 254 255

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

256 257 258 259 260 261
	hintpath = git_buf_cstr(&full_path);
	workdir  = git_repository_workdir(repo);

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

262
	error = git_blob__create_from_paths(
263
		id, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true);
264 265 266 267

	git_buf_free(&full_path);
	return error;
}
268 269 270 271

#define BUFFER_SIZE 4096

int git_blob_create_fromchunks(
272
	git_oid *id,
273 274 275 276 277
	git_repository *repo,
	const char *hintpath,
	int (*source_cb)(char *content, size_t max_length, void *payload),
	void *payload)
{
278
	int error;
279 280
	char *content = NULL;
	git_filebuf file = GIT_FILEBUF_INIT;
281 282
	git_buf path = GIT_BUF_INIT;

283
	assert(id && repo && source_cb);
284 285 286

	if ((error = git_buf_joinpath(
			&path, git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0)
287
		goto cleanup;
288 289 290 291

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

292 293
	if ((error = git_filebuf_open(
			&file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, 0666)) < 0)
294 295 296
		goto cleanup;

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

299
		if (!read_bytes)
300 301
			break;

302 303 304 305 306 307 308 309 310 311
		if (read_bytes > BUFFER_SIZE) {
			giterr_set(GITERR_OBJECT, "Invalid chunk size while creating blob");
			error = GIT_EBUFS;
		} else if (read_bytes < 0) {
			error = giterr_set_after_callback(read_bytes);
		} else {
			error = git_filebuf_write(&file, content, read_bytes);
		}

		if (error < 0)
312 313 314
			goto cleanup;
	}

315
	if ((error = git_filebuf_flush(&file)) < 0)
316 317
		goto cleanup;

318
	error = git_blob__create_from_paths(
319
		id, NULL, repo, file.path_lock, hintpath, 0, hintpath != NULL);
320 321

cleanup:
322
	git_buf_free(&path);
323 324
	git_filebuf_cleanup(&file);
	git__free(content);
325

326 327
	return error;
}
328 329 330 331 332 333 334

int git_blob_is_binary(git_blob *blob)
{
	git_buf content;

	assert(blob);

335 336 337
	content.ptr   = blob->odb_object->buffer;
	content.size  = min(blob->odb_object->cached.size, 4000);
	content.asize = 0;
338 339 340

	return git_buf_text_is_binary(&content);
}
341 342

int git_blob_filtered_content(
343
	git_buf *out,
344
	git_blob *blob,
Russell Belfer committed
345
	const char *path,
346 347
	int check_for_binary_data)
{
348 349
	int error = 0;
	git_filter_list *fl = NULL;
350

Russell Belfer committed
351
	assert(blob && path && out);
352

353 354
	git_buf_sanitize(out);

355
	if (check_for_binary_data && git_blob_is_binary(blob))
356 357
		return 0;

358
	if (!(error = git_filter_list_load(
Russell Belfer committed
359
			&fl, git_blob_owner(blob), blob, path, GIT_FILTER_TO_WORKTREE))) {
360

361
		error = git_filter_list_apply_to_blob(out, fl, blob);
362

363
		git_filter_list_free(fl);
364 365 366 367
	}

	return error;
}