2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
9 #include "git2/submodule.h"
11 #include "diff_file.h"
16 #define DIFF_MAX_FILESIZE 0x20000000
18 static bool diff_file_content_binary_by_size(git_diff_file_content
*fc
)
20 /* if we have diff opts, check max_size vs file size */
21 if ((fc
->file
->flags
& DIFF_FLAGS_KNOWN_BINARY
) == 0 &&
22 fc
->opts_max_size
> 0 &&
23 fc
->file
->size
> fc
->opts_max_size
)
24 fc
->file
->flags
|= GIT_DIFF_FLAG_BINARY
;
26 return ((fc
->file
->flags
& GIT_DIFF_FLAG_BINARY
) != 0);
29 static void diff_file_content_binary_by_content(git_diff_file_content
*fc
)
31 if ((fc
->file
->flags
& DIFF_FLAGS_KNOWN_BINARY
) != 0)
34 switch (git_diff_driver_content_is_binary(
35 fc
->driver
, fc
->map
.data
, fc
->map
.len
)) {
36 case 0: fc
->file
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
; break;
37 case 1: fc
->file
->flags
|= GIT_DIFF_FLAG_BINARY
; break;
42 static int diff_file_content_init_common(
43 git_diff_file_content
*fc
, const git_diff_options
*opts
)
45 fc
->opts_flags
= opts
? opts
->flags
: GIT_DIFF_NORMAL
;
47 if (opts
&& opts
->max_size
>= 0)
48 fc
->opts_max_size
= opts
->max_size
?
49 opts
->max_size
: DIFF_MAX_FILESIZE
;
51 if (fc
->src
== GIT_ITERATOR_TYPE_EMPTY
)
52 fc
->src
= GIT_ITERATOR_TYPE_TREE
;
55 git_diff_driver_lookup(&fc
->driver
, fc
->repo
, fc
->file
->path
) < 0)
58 /* give driver a chance to modify options */
59 git_diff_driver_update_options(&fc
->opts_flags
, fc
->driver
);
61 /* make sure file is conceivable mmap-able */
62 if ((git_off_t
)((size_t)fc
->file
->size
) != fc
->file
->size
)
63 fc
->file
->flags
|= GIT_DIFF_FLAG_BINARY
;
64 /* check if user is forcing text diff the file */
65 else if (fc
->opts_flags
& GIT_DIFF_FORCE_TEXT
) {
66 fc
->file
->flags
&= ~GIT_DIFF_FLAG_BINARY
;
67 fc
->file
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
;
69 /* check if user is forcing binary diff the file */
70 else if (fc
->opts_flags
& GIT_DIFF_FORCE_BINARY
) {
71 fc
->file
->flags
&= ~GIT_DIFF_FLAG_NOT_BINARY
;
72 fc
->file
->flags
|= GIT_DIFF_FLAG_BINARY
;
75 diff_file_content_binary_by_size(fc
);
77 if ((fc
->flags
& GIT_DIFF_FLAG__NO_DATA
) != 0) {
78 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
83 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) != 0)
84 diff_file_content_binary_by_content(fc
);
89 int git_diff_file_content__init_from_diff(
90 git_diff_file_content
*fc
,
95 git_diff_delta
*delta
= git_vector_get(&diff
->deltas
, delta_index
);
98 memset(fc
, 0, sizeof(*fc
));
99 fc
->repo
= diff
->repo
;
100 fc
->file
= use_old
? &delta
->old_file
: &delta
->new_file
;
101 fc
->src
= use_old
? diff
->old_src
: diff
->new_src
;
103 if (git_diff_driver_lookup(&fc
->driver
, fc
->repo
, fc
->file
->path
) < 0)
106 switch (delta
->status
) {
107 case GIT_DELTA_ADDED
:
108 has_data
= !use_old
; break;
109 case GIT_DELTA_DELETED
:
110 has_data
= use_old
; break;
111 case GIT_DELTA_UNTRACKED
:
112 has_data
= !use_old
&&
113 (diff
->opts
.flags
& GIT_DIFF_SHOW_UNTRACKED_CONTENT
) != 0;
115 case GIT_DELTA_UNREADABLE
:
116 case GIT_DELTA_MODIFIED
:
117 case GIT_DELTA_COPIED
:
118 case GIT_DELTA_RENAMED
:
126 fc
->flags
|= GIT_DIFF_FLAG__NO_DATA
;
128 return diff_file_content_init_common(fc
, &diff
->opts
);
131 int git_diff_file_content__init_from_src(
132 git_diff_file_content
*fc
,
133 git_repository
*repo
,
134 const git_diff_options
*opts
,
135 const git_diff_file_content_src
*src
,
136 git_diff_file
*as_file
)
138 memset(fc
, 0, sizeof(*fc
));
141 fc
->blob
= src
->blob
;
143 if (!src
->blob
&& !src
->buf
) {
144 fc
->flags
|= GIT_DIFF_FLAG__NO_DATA
;
146 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
147 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
148 fc
->file
->mode
= GIT_FILEMODE_BLOB
;
151 fc
->file
->size
= git_blob_rawsize(src
->blob
);
152 git_oid_cpy(&fc
->file
->id
, git_blob_id(src
->blob
));
154 fc
->map
.len
= (size_t)fc
->file
->size
;
155 fc
->map
.data
= (char *)git_blob_rawcontent(src
->blob
);
157 fc
->file
->size
= src
->buflen
;
158 git_odb_hash(&fc
->file
->id
, src
->buf
, src
->buflen
, GIT_OBJ_BLOB
);
160 fc
->map
.len
= src
->buflen
;
161 fc
->map
.data
= (char *)src
->buf
;
165 return diff_file_content_init_common(fc
, opts
);
168 static int diff_file_content_commit_to_str(
169 git_diff_file_content
*fc
, bool check_status
)
171 char oid
[GIT_OID_HEXSZ
+1];
172 git_buf content
= GIT_BUF_INIT
;
173 const char *status
= "";
177 git_submodule
*sm
= NULL
;
178 unsigned int sm_status
= 0;
179 const git_oid
*sm_head
;
181 if ((error
= git_submodule_lookup(&sm
, fc
->repo
, fc
->file
->path
)) < 0) {
182 /* GIT_EEXISTS means a "submodule" that has not been git added */
183 if (error
== GIT_EEXISTS
) {
190 if ((error
= git_submodule_status(&sm_status
, sm
)) < 0) {
191 git_submodule_free(sm
);
195 /* update OID if we didn't have it previously */
196 if ((fc
->file
->flags
& GIT_DIFF_FLAG_VALID_ID
) == 0 &&
197 ((sm_head
= git_submodule_wd_id(sm
)) != NULL
||
198 (sm_head
= git_submodule_head_id(sm
)) != NULL
))
200 git_oid_cpy(&fc
->file
->id
, sm_head
);
201 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
204 if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status
))
207 git_submodule_free(sm
);
210 git_oid_tostr(oid
, sizeof(oid
), &fc
->file
->id
);
211 if (git_buf_printf(&content
, "Subproject commit %s%s\n", oid
, status
) < 0)
214 fc
->map
.len
= git_buf_len(&content
);
215 fc
->map
.data
= git_buf_detach(&content
);
216 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
221 static int diff_file_content_load_blob(git_diff_file_content
*fc
)
224 git_odb_object
*odb_obj
= NULL
;
226 if (git_oid_iszero(&fc
->file
->id
))
229 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
230 return diff_file_content_commit_to_str(fc
, false);
232 /* if we don't know size, try to peek at object header first */
233 if (!fc
->file
->size
) {
234 if ((error
= git_diff_file__resolve_zero_size(
235 fc
->file
, &odb_obj
, fc
->repo
)) < 0)
239 if (diff_file_content_binary_by_size(fc
))
242 if (odb_obj
!= NULL
) {
243 error
= git_object__from_odb_object(
244 (git_object
**)&fc
->blob
, fc
->repo
, odb_obj
, GIT_OBJ_BLOB
);
245 git_odb_object_free(odb_obj
);
247 error
= git_blob_lookup(
248 (git_blob
**)&fc
->blob
, fc
->repo
, &fc
->file
->id
);
252 fc
->flags
|= GIT_DIFF_FLAG__FREE_BLOB
;
253 fc
->map
.data
= (void *)git_blob_rawcontent(fc
->blob
);
254 fc
->map
.len
= (size_t)git_blob_rawsize(fc
->blob
);
260 static int diff_file_content_load_workdir_symlink(
261 git_diff_file_content
*fc
, git_buf
*path
)
263 ssize_t alloc_len
, read_len
;
265 /* link path on disk could be UTF-16, so prepare a buffer that is
266 * big enough to handle some UTF-8 data expansion
268 alloc_len
= (ssize_t
)(fc
->file
->size
* 2) + 1;
270 fc
->map
.data
= git__calloc(alloc_len
, sizeof(char));
271 GITERR_CHECK_ALLOC(fc
->map
.data
);
273 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
275 read_len
= p_readlink(git_buf_cstr(path
), fc
->map
.data
, alloc_len
);
277 giterr_set(GITERR_OS
, "Failed to read symlink '%s'", fc
->file
->path
);
281 fc
->map
.len
= read_len
;
285 static int diff_file_content_load_workdir_file(
286 git_diff_file_content
*fc
, git_buf
*path
)
289 git_filter_list
*fl
= NULL
;
290 git_file fd
= git_futils_open_ro(git_buf_cstr(path
));
291 git_buf raw
= GIT_BUF_INIT
;
296 if (!fc
->file
->size
&&
297 !(fc
->file
->size
= git_futils_filesize(fd
)))
300 if (diff_file_content_binary_by_size(fc
))
303 if ((error
= git_filter_list_load(
304 &fl
, fc
->repo
, NULL
, fc
->file
->path
,
305 GIT_FILTER_TO_ODB
, GIT_FILTER_OPT_ALLOW_UNSAFE
)) < 0)
308 /* if there are no filters, try to mmap the file */
310 if (!(error
= git_futils_mmap_ro(
311 &fc
->map
, fd
, 0, (size_t)fc
->file
->size
))) {
312 fc
->flags
|= GIT_DIFF_FLAG__UNMAP_DATA
;
316 /* if mmap failed, fall through to try readbuffer below */
320 if (!(error
= git_futils_readbuffer_fd(&raw
, fd
, (size_t)fc
->file
->size
))) {
321 git_buf out
= GIT_BUF_INIT
;
323 error
= git_filter_list_apply_to_data(&out
, fl
, &raw
);
325 if (out
.ptr
!= raw
.ptr
)
329 fc
->map
.len
= out
.size
;
330 fc
->map
.data
= out
.ptr
;
331 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
336 git_filter_list_free(fl
);
342 static int diff_file_content_load_workdir(git_diff_file_content
*fc
)
345 git_buf path
= GIT_BUF_INIT
;
347 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
348 return diff_file_content_commit_to_str(fc
, true);
350 if (fc
->file
->mode
== GIT_FILEMODE_TREE
)
353 if (git_buf_joinpath(
354 &path
, git_repository_workdir(fc
->repo
), fc
->file
->path
) < 0)
357 if (S_ISLNK(fc
->file
->mode
))
358 error
= diff_file_content_load_workdir_symlink(fc
, &path
);
360 error
= diff_file_content_load_workdir_file(fc
, &path
);
362 /* once data is loaded, update OID if we didn't have it previously */
363 if (!error
&& (fc
->file
->flags
& GIT_DIFF_FLAG_VALID_ID
) == 0) {
364 error
= git_odb_hash(
365 &fc
->file
->id
, fc
->map
.data
, fc
->map
.len
, GIT_OBJ_BLOB
);
366 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
373 int git_diff_file_content__load(git_diff_file_content
*fc
)
377 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) != 0)
380 if ((fc
->file
->flags
& GIT_DIFF_FLAG_BINARY
) != 0)
383 if (fc
->src
== GIT_ITERATOR_TYPE_WORKDIR
)
384 error
= diff_file_content_load_workdir(fc
);
386 error
= diff_file_content_load_blob(fc
);
390 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
392 diff_file_content_binary_by_content(fc
);
397 void git_diff_file_content__unload(git_diff_file_content
*fc
)
399 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) == 0)
402 if (fc
->flags
& GIT_DIFF_FLAG__FREE_DATA
) {
403 git__free(fc
->map
.data
);
406 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_DATA
;
408 else if (fc
->flags
& GIT_DIFF_FLAG__UNMAP_DATA
) {
409 git_futils_mmap_free(&fc
->map
);
412 fc
->flags
&= ~GIT_DIFF_FLAG__UNMAP_DATA
;
415 if (fc
->flags
& GIT_DIFF_FLAG__FREE_BLOB
) {
416 git_blob_free((git_blob
*)fc
->blob
);
418 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_BLOB
;
421 fc
->flags
&= ~GIT_DIFF_FLAG__LOADED
;
424 void git_diff_file_content__clear(git_diff_file_content
*fc
)
426 git_diff_file_content__unload(fc
);
428 /* for now, nothing else to do */