File Coverage

deps/libgit2/src/blob.c
Criterion Covered Total %
statement 87 214 40.6
branch 36 120 30.0
condition n/a
subroutine n/a
pod n/a
total 123 334 36.8


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
4             * This file is part of libgit2, distributed under the GNU GPL v2 with
5             * a Linking Exception. For full terms see the included COPYING file.
6             */
7              
8             #include "blob.h"
9              
10             #include "git2/common.h"
11             #include "git2/object.h"
12             #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 157           const void *git_blob_rawcontent(const git_blob *blob)
20             {
21 157 50         assert(blob);
22 157 50         if (blob->raw)
23 0           return blob->data.raw.data;
24             else
25 157           return git_odb_object_data(blob->data.odb);
26             }
27              
28 164           git_object_size_t git_blob_rawsize(const git_blob *blob)
29             {
30 164 50         assert(blob);
31 164 50         if (blob->raw)
32 0           return blob->data.raw.size;
33             else
34 164           return (git_object_size_t)git_odb_object_size(blob->data.odb);
35             }
36              
37 0           int git_blob__getbuf(git_buf *buffer, git_blob *blob)
38             {
39 0           git_object_size_t size = git_blob_rawsize(blob);
40              
41 0 0         GIT_ERROR_CHECK_BLOBSIZE(size);
42 0           return git_buf_set(buffer, git_blob_rawcontent(blob), (size_t)size);
43             }
44              
45 178           void git_blob__free(void *_blob)
46             {
47 178           git_blob *blob = (git_blob *) _blob;
48 178 50         if (!blob->raw)
49 178           git_odb_object_free(blob->data.odb);
50 178           git__free(blob);
51 178           }
52              
53 0           int git_blob__parse_raw(void *_blob, const char *data, size_t size)
54             {
55 0           git_blob *blob = (git_blob *) _blob;
56 0 0         assert(blob);
57 0           blob->raw = 1;
58 0           blob->data.raw.data = data;
59 0           blob->data.raw.size = size;
60 0           return 0;
61             }
62              
63 178           int git_blob__parse(void *_blob, git_odb_object *odb_obj)
64             {
65 178           git_blob *blob = (git_blob *) _blob;
66 178 50         assert(blob);
67 178           git_cached_obj_incref((git_cached_obj *)odb_obj);
68 178           blob->raw = 0;
69 178           blob->data.odb = odb_obj;
70 178           return 0;
71             }
72              
73 16           int git_blob_create_from_buffer(
74             git_oid *id, git_repository *repo, const void *buffer, size_t len)
75             {
76             int error;
77             git_odb *odb;
78             git_odb_stream *stream;
79              
80 16 50         assert(id && repo);
    50          
81              
82 16 50         if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
    50          
83 16           (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJECT_BLOB)) < 0)
84 0           return error;
85              
86 16 50         if ((error = git_odb_stream_write(stream, buffer, len)) == 0)
87 16           error = git_odb_stream_finalize_write(id, stream);
88              
89 16           git_odb_stream_free(stream);
90 16           return error;
91             }
92              
93 47           static int write_file_stream(
94             git_oid *id, git_odb *odb, const char *path, git_object_size_t file_size)
95             {
96             int fd, error;
97             char buffer[FILEIO_BUFSIZE];
98 47           git_odb_stream *stream = NULL;
99 47           ssize_t read_len = -1;
100 47           git_object_size_t written = 0;
101              
102 47 50         if ((error = git_odb_open_wstream(
103             &stream, odb, file_size, GIT_OBJECT_BLOB)) < 0)
104 0           return error;
105              
106 47 50         if ((fd = git_futils_open_ro(path)) < 0) {
107 0           git_odb_stream_free(stream);
108 0           return -1;
109             }
110              
111 93 50         while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
    100          
112 46           error = git_odb_stream_write(stream, buffer, read_len);
113 46           written += read_len;
114             }
115              
116 47           p_close(fd);
117              
118 47 50         if (written != file_size || read_len < 0) {
    50          
119 0           git_error_set(GIT_ERROR_OS, "failed to read file into stream");
120 0           error = -1;
121             }
122              
123 47 50         if (!error)
124 47           error = git_odb_stream_finalize_write(id, stream);
125              
126 47           git_odb_stream_free(stream);
127 47           return error;
128             }
129              
130 17           static int write_file_filtered(
131             git_oid *id,
132             git_object_size_t *size,
133             git_odb *odb,
134             const char *full_path,
135             git_filter_list *fl)
136             {
137             int error;
138 17           git_buf tgt = GIT_BUF_INIT;
139              
140 17           error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path);
141              
142             /* Write the file to disk if it was properly filtered */
143 17 100         if (!error) {
144 15           *size = tgt.size;
145              
146 15           error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJECT_BLOB);
147             }
148              
149 17           git_buf_dispose(&tgt);
150 17           return error;
151             }
152              
153 0           static int write_symlink(
154             git_oid *id, git_odb *odb, const char *path, size_t link_size)
155             {
156             char *link_data;
157             ssize_t read_len;
158             int error;
159              
160 0           link_data = git__malloc(link_size);
161 0 0         GIT_ERROR_CHECK_ALLOC(link_data);
162              
163 0           read_len = p_readlink(path, link_data, link_size);
164 0 0         if (read_len != (ssize_t)link_size) {
165 0           git_error_set(GIT_ERROR_OS, "failed to create blob: cannot read symlink '%s'", path);
166 0           git__free(link_data);
167 0           return -1;
168             }
169              
170 0           error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJECT_BLOB);
171 0           git__free(link_data);
172 0           return error;
173             }
174              
175 64           int git_blob__create_from_paths(
176             git_oid *id,
177             struct stat *out_st,
178             git_repository *repo,
179             const char *content_path,
180             const char *hint_path,
181             mode_t hint_mode,
182             bool try_load_filters)
183             {
184             int error;
185             struct stat st;
186 64           git_odb *odb = NULL;
187             git_object_size_t size;
188             mode_t mode;
189 64           git_buf path = GIT_BUF_INIT;
190              
191 64 50         assert(hint_path || !try_load_filters);
    0          
192              
193 64 50         if (!content_path) {
194 64 50         if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
195 0           return GIT_EBAREREPO;
196              
197 64 50         if (git_buf_joinpath(
198             &path, git_repository_workdir(repo), hint_path) < 0)
199 0           return -1;
200              
201 64           content_path = path.ptr;
202             }
203              
204 64 50         if ((error = git_path_lstat(content_path, &st)) < 0 ||
    50          
205             (error = git_repository_odb(&odb, repo)) < 0)
206             goto done;
207              
208 64 50         if (S_ISDIR(st.st_mode)) {
209 0           git_error_set(GIT_ERROR_ODB, "cannot create blob from '%s': it is a directory", content_path);
210 0           error = GIT_EDIRECTORY;
211 0           goto done;
212             }
213              
214 64 50         if (out_st)
215 64           memcpy(out_st, &st, sizeof(st));
216              
217 64           size = st.st_size;
218 64 50         mode = hint_mode ? hint_mode : st.st_mode;
219              
220 64 50         if (S_ISLNK(mode)) {
221 0           error = write_symlink(id, odb, content_path, (size_t)size);
222             } else {
223 64           git_filter_list *fl = NULL;
224              
225 64 50         if (try_load_filters)
226             /* Load the filters for writing this file to the ODB */
227 64           error = git_filter_list_load(
228             &fl, repo, NULL, hint_path,
229             GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT);
230              
231 64 50         if (error < 0)
232             /* well, that didn't work */;
233 64 100         else if (fl == NULL)
234             /* No filters need to be applied to the document: we can stream
235             * directly from disk */
236 47           error = write_file_stream(id, odb, content_path, size);
237             else {
238             /* We need to apply one or more filters */
239 17           error = write_file_filtered(id, &size, odb, content_path, fl);
240              
241 64           git_filter_list_free(fl);
242             }
243              
244             /*
245             * TODO: eventually support streaming filtered files, for files
246             * which are bigger than a given threshold. This is not a priority
247             * because applying a filter in streaming mode changes the final
248             * size of the blob, and without knowing its final size, the blob
249             * cannot be written in stream mode to the ODB.
250             *
251             * The plan is to do streaming writes to a tempfile on disk and then
252             * opening streaming that file to the ODB, using
253             * `write_file_stream`.
254             *
255             * CAREFULLY DESIGNED APIS YO
256             */
257             }
258              
259             done:
260 64           git_odb_free(odb);
261 64           git_buf_dispose(&path);
262              
263 64           return error;
264             }
265              
266 0           int git_blob_create_from_workdir(
267             git_oid *id, git_repository *repo, const char *path)
268             {
269 0           return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true);
270             }
271              
272 0           int git_blob_create_from_disk(
273             git_oid *id, git_repository *repo, const char *path)
274             {
275             int error;
276 0           git_buf full_path = GIT_BUF_INIT;
277             const char *workdir, *hintpath;
278              
279 0 0         if ((error = git_path_prettify(&full_path, path, NULL)) < 0) {
280 0           git_buf_dispose(&full_path);
281 0           return error;
282             }
283              
284 0           hintpath = git_buf_cstr(&full_path);
285 0           workdir = git_repository_workdir(repo);
286              
287 0 0         if (workdir && !git__prefixcmp(hintpath, workdir))
    0          
288 0           hintpath += strlen(workdir);
289              
290 0           error = git_blob__create_from_paths(
291             id, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true);
292              
293 0           git_buf_dispose(&full_path);
294 0           return error;
295             }
296              
297             typedef struct {
298             git_writestream parent;
299             git_filebuf fbuf;
300             git_repository *repo;
301             char *hintpath;
302             } blob_writestream;
303              
304 0           static int blob_writestream_close(git_writestream *_stream)
305             {
306 0           blob_writestream *stream = (blob_writestream *) _stream;
307              
308 0           git_filebuf_cleanup(&stream->fbuf);
309 0           return 0;
310             }
311              
312 0           static void blob_writestream_free(git_writestream *_stream)
313             {
314 0           blob_writestream *stream = (blob_writestream *) _stream;
315              
316 0           git_filebuf_cleanup(&stream->fbuf);
317 0           git__free(stream->hintpath);
318 0           git__free(stream);
319 0           }
320              
321 0           static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len)
322             {
323 0           blob_writestream *stream = (blob_writestream *) _stream;
324              
325 0           return git_filebuf_write(&stream->fbuf, buffer, len);
326             }
327              
328 0           int git_blob_create_from_stream(git_writestream **out, git_repository *repo, const char *hintpath)
329             {
330             int error;
331 0           git_buf path = GIT_BUF_INIT;
332             blob_writestream *stream;
333              
334 0 0         assert(out && repo);
    0          
335              
336 0           stream = git__calloc(1, sizeof(blob_writestream));
337 0 0         GIT_ERROR_CHECK_ALLOC(stream);
338              
339 0 0         if (hintpath) {
340 0           stream->hintpath = git__strdup(hintpath);
341 0 0         GIT_ERROR_CHECK_ALLOC(stream->hintpath);
342             }
343              
344 0           stream->repo = repo;
345 0           stream->parent.write = blob_writestream_write;
346 0           stream->parent.close = blob_writestream_close;
347 0           stream->parent.free = blob_writestream_free;
348              
349 0 0         if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0
350 0 0         || (error = git_buf_joinpath(&path, path.ptr, "streamed")) < 0)
351             goto cleanup;
352              
353 0 0         if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY,
354             0666, 2 * 1024 * 1024)) < 0)
355 0           goto cleanup;
356              
357 0           *out = (git_writestream *) stream;
358              
359             cleanup:
360 0 0         if (error < 0)
361 0           blob_writestream_free((git_writestream *) stream);
362              
363 0           git_buf_dispose(&path);
364 0           return error;
365             }
366              
367 0           int git_blob_create_from_stream_commit(git_oid *out, git_writestream *_stream)
368             {
369             int error;
370 0           blob_writestream *stream = (blob_writestream *) _stream;
371              
372             /*
373             * We can make this more officient by avoiding writing to
374             * disk, but for now let's re-use the helper functions we
375             * have.
376             */
377 0 0         if ((error = git_filebuf_flush(&stream->fbuf)) < 0)
378 0           goto cleanup;
379              
380 0           error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock,
381 0           stream->hintpath, 0, !!stream->hintpath);
382              
383             cleanup:
384 0           blob_writestream_free(_stream);
385 0           return error;
386              
387             }
388              
389 10           int git_blob_is_binary(const git_blob *blob)
390             {
391 10           git_buf content = GIT_BUF_INIT;
392             git_object_size_t size;
393              
394 10 50         assert(blob);
395              
396 10           size = git_blob_rawsize(blob);
397              
398 10           git_buf_attach_notowned(&content, git_blob_rawcontent(blob),
399 10           (size_t)min(size, GIT_FILTER_BYTES_TO_CHECK_NUL));
400 10           return git_buf_text_is_binary(&content);
401             }
402              
403 0           int git_blob_filter(
404             git_buf *out,
405             git_blob *blob,
406             const char *path,
407             git_blob_filter_options *given_opts)
408             {
409 0           int error = 0;
410 0           git_filter_list *fl = NULL;
411 0           git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
412 0           git_filter_flag_t flags = GIT_FILTER_DEFAULT;
413              
414 0 0         assert(blob && path && out);
    0          
    0          
415              
416 0           git_buf_sanitize(out);
417              
418 0 0         GIT_ERROR_CHECK_VERSION(
419             given_opts, GIT_BLOB_FILTER_OPTIONS_VERSION, "git_blob_filter_options");
420              
421 0 0         if (given_opts != NULL)
422 0           memcpy(&opts, given_opts, sizeof(git_blob_filter_options));
423              
424 0           if ((opts.flags & GIT_BLOB_FILTER_CHECK_FOR_BINARY) != 0 &&
425 0           git_blob_is_binary(blob))
426 0           return 0;
427              
428 0 0         if ((opts.flags & GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES) != 0)
429 0           flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES;
430              
431 0 0         if ((opts.flags & GIT_BLOB_FILTER_ATTTRIBUTES_FROM_HEAD) != 0)
432 0           flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD;
433              
434 0 0         if (!(error = git_filter_list_load(
435             &fl, git_blob_owner(blob), blob, path,
436             GIT_FILTER_TO_WORKTREE, flags))) {
437              
438 0           error = git_filter_list_apply_to_blob(out, fl, blob);
439              
440 0           git_filter_list_free(fl);
441             }
442              
443 0           return error;
444             }
445              
446             /* Deprecated functions */
447              
448 5           int git_blob_create_frombuffer(
449             git_oid *id, git_repository *repo, const void *buffer, size_t len)
450             {
451 5           return git_blob_create_from_buffer(id, repo, buffer, len);
452             }
453              
454 0           int git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path)
455             {
456 0           return git_blob_create_from_workdir(id, repo, relative_path);
457             }
458              
459 0           int git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path)
460             {
461 0           return git_blob_create_from_disk(id, repo, path);
462             }
463              
464 0           int git_blob_create_fromstream(
465             git_writestream **out,
466             git_repository *repo,
467             const char *hintpath)
468             {
469 0           return git_blob_create_from_stream(out, repo, hintpath);
470             }
471              
472 0           int git_blob_create_fromstream_commit(
473             git_oid *out,
474             git_writestream *stream)
475             {
476 0           return git_blob_create_from_stream_commit(out, stream);
477             }
478              
479 0           int git_blob_filtered_content(
480             git_buf *out,
481             git_blob *blob,
482             const char *path,
483             int check_for_binary_data)
484             {
485 0           git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
486              
487 0 0         if (check_for_binary_data)
488 0           opts.flags |= GIT_BLOB_FILTER_CHECK_FOR_BINARY;
489             else
490 0           opts.flags &= ~GIT_BLOB_FILTER_CHECK_FOR_BINARY;
491              
492 0           return git_blob_filter(out, blob, path, &opts);
493             }