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
,
92 git_diff_delta
*delta
,
97 memset(fc
, 0, sizeof(*fc
));
98 fc
->repo
= diff
->repo
;
99 fc
->file
= use_old
? &delta
->old_file
: &delta
->new_file
;
100 fc
->src
= use_old
? diff
->old_src
: diff
->new_src
;
102 if (git_diff_driver_lookup(&fc
->driver
, fc
->repo
, fc
->file
->path
) < 0)
105 switch (delta
->status
) {
106 case GIT_DELTA_ADDED
:
107 has_data
= !use_old
; break;
108 case GIT_DELTA_DELETED
:
109 has_data
= use_old
; break;
110 case GIT_DELTA_UNTRACKED
:
111 has_data
= !use_old
&&
112 (diff
->opts
.flags
& GIT_DIFF_SHOW_UNTRACKED_CONTENT
) != 0;
114 case GIT_DELTA_UNREADABLE
:
115 case GIT_DELTA_MODIFIED
:
116 case GIT_DELTA_COPIED
:
117 case GIT_DELTA_RENAMED
:
125 fc
->flags
|= GIT_DIFF_FLAG__NO_DATA
;
127 return diff_file_content_init_common(fc
, &diff
->opts
);
130 int git_diff_file_content__init_from_src(
131 git_diff_file_content
*fc
,
132 git_repository
*repo
,
133 const git_diff_options
*opts
,
134 const git_diff_file_content_src
*src
,
135 git_diff_file
*as_file
)
137 memset(fc
, 0, sizeof(*fc
));
140 fc
->blob
= src
->blob
;
142 if (!src
->blob
&& !src
->buf
) {
143 fc
->flags
|= GIT_DIFF_FLAG__NO_DATA
;
145 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
146 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
147 fc
->file
->mode
= GIT_FILEMODE_BLOB
;
150 fc
->file
->size
= git_blob_rawsize(src
->blob
);
151 git_oid_cpy(&fc
->file
->id
, git_blob_id(src
->blob
));
153 fc
->map
.len
= (size_t)fc
->file
->size
;
154 fc
->map
.data
= (char *)git_blob_rawcontent(src
->blob
);
156 fc
->file
->size
= src
->buflen
;
157 git_odb_hash(&fc
->file
->id
, src
->buf
, src
->buflen
, GIT_OBJ_BLOB
);
159 fc
->map
.len
= src
->buflen
;
160 fc
->map
.data
= (char *)src
->buf
;
164 return diff_file_content_init_common(fc
, opts
);
167 static int diff_file_content_commit_to_str(
168 git_diff_file_content
*fc
, bool check_status
)
170 char oid
[GIT_OID_HEXSZ
+1];
171 git_buf content
= GIT_BUF_INIT
;
172 const char *status
= "";
176 git_submodule
*sm
= NULL
;
177 unsigned int sm_status
= 0;
178 const git_oid
*sm_head
;
180 if ((error
= git_submodule_lookup(&sm
, fc
->repo
, fc
->file
->path
)) < 0) {
181 /* GIT_EEXISTS means a "submodule" that has not been git added */
182 if (error
== GIT_EEXISTS
) {
189 if ((error
= git_submodule_status(&sm_status
, sm
)) < 0) {
190 git_submodule_free(sm
);
194 /* update OID if we didn't have it previously */
195 if ((fc
->file
->flags
& GIT_DIFF_FLAG_VALID_ID
) == 0 &&
196 ((sm_head
= git_submodule_wd_id(sm
)) != NULL
||
197 (sm_head
= git_submodule_head_id(sm
)) != NULL
))
199 git_oid_cpy(&fc
->file
->id
, sm_head
);
200 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
203 if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status
))
206 git_submodule_free(sm
);
209 git_oid_tostr(oid
, sizeof(oid
), &fc
->file
->id
);
210 if (git_buf_printf(&content
, "Subproject commit %s%s\n", oid
, status
) < 0)
213 fc
->map
.len
= git_buf_len(&content
);
214 fc
->map
.data
= git_buf_detach(&content
);
215 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
220 static int diff_file_content_load_blob(
221 git_diff_file_content
*fc
,
222 git_diff_options
*opts
)
225 git_odb_object
*odb_obj
= NULL
;
227 if (git_oid_iszero(&fc
->file
->id
))
230 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
231 return diff_file_content_commit_to_str(fc
, false);
233 /* if we don't know size, try to peek at object header first */
234 if (!fc
->file
->size
) {
235 if ((error
= git_diff_file__resolve_zero_size(
236 fc
->file
, &odb_obj
, fc
->repo
)) < 0)
240 if ((opts
->flags
& GIT_DIFF_SHOW_BINARY
) == 0 &&
241 diff_file_content_binary_by_size(fc
))
244 if (odb_obj
!= NULL
) {
245 error
= git_object__from_odb_object(
246 (git_object
**)&fc
->blob
, fc
->repo
, odb_obj
, GIT_OBJ_BLOB
);
247 git_odb_object_free(odb_obj
);
249 error
= git_blob_lookup(
250 (git_blob
**)&fc
->blob
, fc
->repo
, &fc
->file
->id
);
254 fc
->flags
|= GIT_DIFF_FLAG__FREE_BLOB
;
255 fc
->map
.data
= (void *)git_blob_rawcontent(fc
->blob
);
256 fc
->map
.len
= (size_t)git_blob_rawsize(fc
->blob
);
262 static int diff_file_content_load_workdir_symlink(
263 git_diff_file_content
*fc
, git_buf
*path
)
265 ssize_t alloc_len
, read_len
;
267 /* link path on disk could be UTF-16, so prepare a buffer that is
268 * big enough to handle some UTF-8 data expansion
270 alloc_len
= (ssize_t
)(fc
->file
->size
* 2) + 1;
272 fc
->map
.data
= git__calloc(alloc_len
, sizeof(char));
273 GITERR_CHECK_ALLOC(fc
->map
.data
);
275 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
277 read_len
= p_readlink(git_buf_cstr(path
), fc
->map
.data
, alloc_len
);
279 giterr_set(GITERR_OS
, "Failed to read symlink '%s'", fc
->file
->path
);
283 fc
->map
.len
= read_len
;
287 static int diff_file_content_load_workdir_file(
288 git_diff_file_content
*fc
,
290 git_diff_options
*diff_opts
)
293 git_filter_list
*fl
= NULL
;
294 git_file fd
= git_futils_open_ro(git_buf_cstr(path
));
295 git_buf raw
= GIT_BUF_INIT
;
300 if (!fc
->file
->size
&&
301 !(fc
->file
->size
= git_futils_filesize(fd
)))
304 if ((diff_opts
->flags
& GIT_DIFF_SHOW_BINARY
) == 0 &&
305 diff_file_content_binary_by_size(fc
))
308 if ((error
= git_filter_list_load(
309 &fl
, fc
->repo
, NULL
, fc
->file
->path
,
310 GIT_FILTER_TO_ODB
, GIT_FILTER_ALLOW_UNSAFE
)) < 0)
313 /* if there are no filters, try to mmap the file */
315 if (!(error
= git_futils_mmap_ro(
316 &fc
->map
, fd
, 0, (size_t)fc
->file
->size
))) {
317 fc
->flags
|= GIT_DIFF_FLAG__UNMAP_DATA
;
321 /* if mmap failed, fall through to try readbuffer below */
325 if (!(error
= git_futils_readbuffer_fd(&raw
, fd
, (size_t)fc
->file
->size
))) {
326 git_buf out
= GIT_BUF_INIT
;
328 error
= git_filter_list_apply_to_data(&out
, fl
, &raw
);
330 if (out
.ptr
!= raw
.ptr
)
334 fc
->map
.len
= out
.size
;
335 fc
->map
.data
= out
.ptr
;
336 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
341 git_filter_list_free(fl
);
347 static int diff_file_content_load_workdir(
348 git_diff_file_content
*fc
,
349 git_diff_options
*diff_opts
)
352 git_buf path
= GIT_BUF_INIT
;
354 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
355 return diff_file_content_commit_to_str(fc
, true);
357 if (fc
->file
->mode
== GIT_FILEMODE_TREE
)
360 if (git_buf_joinpath(
361 &path
, git_repository_workdir(fc
->repo
), fc
->file
->path
) < 0)
364 if (S_ISLNK(fc
->file
->mode
))
365 error
= diff_file_content_load_workdir_symlink(fc
, &path
);
367 error
= diff_file_content_load_workdir_file(fc
, &path
, diff_opts
);
369 /* once data is loaded, update OID if we didn't have it previously */
370 if (!error
&& (fc
->file
->flags
& GIT_DIFF_FLAG_VALID_ID
) == 0) {
371 error
= git_odb_hash(
372 &fc
->file
->id
, fc
->map
.data
, fc
->map
.len
, GIT_OBJ_BLOB
);
373 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
380 int git_diff_file_content__load(
381 git_diff_file_content
*fc
,
382 git_diff_options
*diff_opts
)
386 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) != 0)
389 if ((fc
->file
->flags
& GIT_DIFF_FLAG_BINARY
) != 0 &&
390 (diff_opts
->flags
& GIT_DIFF_SHOW_BINARY
) == 0)
393 if (fc
->src
== GIT_ITERATOR_TYPE_WORKDIR
)
394 error
= diff_file_content_load_workdir(fc
, diff_opts
);
396 error
= diff_file_content_load_blob(fc
, diff_opts
);
400 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
402 diff_file_content_binary_by_content(fc
);
407 void git_diff_file_content__unload(git_diff_file_content
*fc
)
409 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) == 0)
412 if (fc
->flags
& GIT_DIFF_FLAG__FREE_DATA
) {
413 git__free(fc
->map
.data
);
416 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_DATA
;
418 else if (fc
->flags
& GIT_DIFF_FLAG__UNMAP_DATA
) {
419 git_futils_mmap_free(&fc
->map
);
422 fc
->flags
&= ~GIT_DIFF_FLAG__UNMAP_DATA
;
425 if (fc
->flags
& GIT_DIFF_FLAG__FREE_BLOB
) {
426 git_blob_free((git_blob
*)fc
->blob
);
428 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_BLOB
;
431 fc
->flags
&= ~GIT_DIFF_FLAG__LOADED
;
434 void git_diff_file_content__clear(git_diff_file_content
*fc
)
436 git_diff_file_content__unload(fc
);
438 /* for now, nothing else to do */