blob.c 9.34 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
#include "blob.h"

10 11 12
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
13
#include "git2/odb_backend.h"
14

15
#include "filebuf.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
{
	int fd, error;
77
	char buffer[FILEIO_BUFSIZE];
78
	git_odb_stream *stream = NULL;
79 80
	ssize_t read_len = -1;
	git_off_t written = 0;
81

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

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

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

	p_close(fd);

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

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

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

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

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

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

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

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

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

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

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

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

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

171 172
	assert(hint_path || !try_load_filters);

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
	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;

188
	if (S_ISDIR(st.st_mode)) {
189
		giterr_set(GITERR_ODB, "cannot create blob from '%s': it is a directory", content_path);
190 191 192 193
		error = GIT_EDIRECTORY;
		goto done;
	}

194 195
	if (out_st)
		memcpy(out_st, &st, sizeof(st));
196

197
	size = st.st_size;
198
	mode = hint_mode ? hint_mode : st.st_mode;
199

Russell Belfer committed
200
	if (S_ISLNK(mode)) {
201
		error = write_symlink(id, odb, content_path, (size_t)size);
202
	} else {
203
		git_filter_list *fl = NULL;
204

205
		if (try_load_filters)
206
			/* Load the filters for writing this file to the ODB */
207
			error = git_filter_list_load(
208
				&fl, repo, NULL, hint_path,
209
				GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT);
210

211 212 213
		if (error < 0)
			/* well, that didn't work */;
		else if (fl == NULL)
214 215
			/* No filters need to be applied to the document: we can stream
			 * directly from disk */
216
			error = write_file_stream(id, odb, content_path, size);
217
		else {
218
			/* We need to apply one or more filters */
219
			error = write_file_filtered(id, &size, odb, content_path, fl);
220

221 222
			git_filter_list_free(fl);
		}
223

224
		/*
225 226 227 228 229
		 * 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.
230
		 *
231 232 233
		 * 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`.
234 235 236
		 *
		 * CAREFULLY DESIGNED APIS YO
		 */
Vicent Marti committed
237 238
	}

239 240 241 242
done:
	git_odb_free(odb);
	git_buf_free(&path);

243 244 245
	return error;
}

246
int git_blob_create_fromworkdir(
247
	git_oid *id, git_repository *repo, const char *path)
248
{
249
	return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true);
250 251
}

252
int git_blob_create_fromdisk(
253
	git_oid *id, git_repository *repo, const char *path)
254 255 256
{
	int error;
	git_buf full_path = GIT_BUF_INIT;
257
	const char *workdir, *hintpath;
258 259 260 261 262 263

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

264 265 266 267 268 269
	hintpath = git_buf_cstr(&full_path);
	workdir  = git_repository_workdir(repo);

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

270
	error = git_blob__create_from_paths(
271
		id, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true);
272 273

	git_buf_free(&full_path);
274 275
	return error;
}
276

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
typedef struct {
	git_writestream parent;
	git_filebuf fbuf;
	git_repository *repo;
	char *hintpath;
} blob_writestream;

static int blob_writestream_close(git_writestream *_stream)
{
	blob_writestream *stream = (blob_writestream *) _stream;

	git_filebuf_cleanup(&stream->fbuf);
	return 0;
}

static void blob_writestream_free(git_writestream *_stream)
{
	blob_writestream *stream = (blob_writestream *) _stream;

	git_filebuf_cleanup(&stream->fbuf);
	git__free(stream->hintpath);
	git__free(stream);
}

static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len)
{
	blob_writestream *stream = (blob_writestream *) _stream;

	return git_filebuf_write(&stream->fbuf, buffer, len);
}

int git_blob_create_fromstream(git_writestream **out, git_repository *repo, const char *hintpath)
{
	int error;
	git_buf path = GIT_BUF_INIT;
	blob_writestream *stream;

	assert(out && repo);

	stream = git__calloc(1, sizeof(blob_writestream));
	GITERR_CHECK_ALLOC(stream);

	if (hintpath) {
		stream->hintpath = git__strdup(hintpath);
		GITERR_CHECK_ALLOC(stream->hintpath);
	}

	stream->repo = repo;
	stream->parent.write = blob_writestream_write;
	stream->parent.close = blob_writestream_close;
	stream->parent.free  = blob_writestream_free;

329 330
	if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0
		|| (error = git_buf_joinpath(&path, path.ptr, "streamed")) < 0)
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
		goto cleanup;

	if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY,
					       0666, 2 * 1024 * 1024)) < 0)
		goto cleanup;

	*out = (git_writestream *) stream;

cleanup:
	if (error < 0)
		blob_writestream_free((git_writestream *) stream);

	git_buf_free(&path);
	return error;
}

int git_blob_create_fromstream_commit(git_oid *out, git_writestream *_stream)
{
	int error;
	blob_writestream *stream = (blob_writestream *) _stream;

	/*
	 * We can make this more officient by avoiding writing to
	 * disk, but for now let's re-use the helper functions we
	 * have.
	 */
	if ((error = git_filebuf_flush(&stream->fbuf)) < 0)
		goto cleanup;

	error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock,
					    stream->hintpath, 0, !!stream->hintpath);

cleanup:
	blob_writestream_free(_stream);
	return error;

}

Jacques Germishuys committed
369
int git_blob_is_binary(const git_blob *blob)
370
{
371
	git_buf content = GIT_BUF_INIT;
372 373 374

	assert(blob);

375 376 377
	git_buf_attach_notowned(&content, blob->odb_object->buffer,
		min(blob->odb_object->cached.size,
		GIT_FILTER_BYTES_TO_CHECK_NUL));
378 379
	return git_buf_text_is_binary(&content);
}
380 381

int git_blob_filtered_content(
382
	git_buf *out,
383
	git_blob *blob,
Russell Belfer committed
384
	const char *path,
385 386
	int check_for_binary_data)
{
387 388
	int error = 0;
	git_filter_list *fl = NULL;
389

Russell Belfer committed
390
	assert(blob && path && out);
391

392 393
	git_buf_sanitize(out);

394
	if (check_for_binary_data && git_blob_is_binary(blob))
395 396
		return 0;

397
	if (!(error = git_filter_list_load(
398
			&fl, git_blob_owner(blob), blob, path,
399
			GIT_FILTER_TO_WORKTREE, GIT_FILTER_DEFAULT))) {
400

401
		error = git_filter_list_apply_to_blob(out, fl, blob);
402

403
		git_filter_list_free(fl);
404 405 406 407
	}

	return error;
}