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_generate.h"
12 #include "diff_file.h"
17 #define DIFF_MAX_FILESIZE 0x20000000
19 static bool diff_file_content_binary_by_size(git_diff_file_content
*fc
)
21 /* if we have diff opts, check max_size vs file size */
22 if ((fc
->file
->flags
& DIFF_FLAGS_KNOWN_BINARY
) == 0 &&
23 fc
->opts_max_size
> 0 &&
24 fc
->file
->size
> fc
->opts_max_size
)
25 fc
->file
->flags
|= GIT_DIFF_FLAG_BINARY
;
27 return ((fc
->file
->flags
& GIT_DIFF_FLAG_BINARY
) != 0);
30 static void diff_file_content_binary_by_content(git_diff_file_content
*fc
)
32 if ((fc
->file
->flags
& DIFF_FLAGS_KNOWN_BINARY
) != 0)
35 switch (git_diff_driver_content_is_binary(
36 fc
->driver
, fc
->map
.data
, fc
->map
.len
)) {
37 case 0: fc
->file
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
; break;
38 case 1: fc
->file
->flags
|= GIT_DIFF_FLAG_BINARY
; break;
43 static int diff_file_content_init_common(
44 git_diff_file_content
*fc
, const git_diff_options
*opts
)
46 fc
->opts_flags
= opts
? opts
->flags
: GIT_DIFF_NORMAL
;
48 if (opts
&& opts
->max_size
>= 0)
49 fc
->opts_max_size
= opts
->max_size
?
50 opts
->max_size
: DIFF_MAX_FILESIZE
;
52 if (fc
->src
== GIT_ITERATOR_TYPE_EMPTY
)
53 fc
->src
= GIT_ITERATOR_TYPE_TREE
;
56 git_diff_driver_lookup(&fc
->driver
, fc
->repo
, fc
->file
->path
) < 0)
59 /* give driver a chance to modify options */
60 git_diff_driver_update_options(&fc
->opts_flags
, fc
->driver
);
62 /* make sure file is conceivable mmap-able */
63 if ((git_off_t
)((size_t)fc
->file
->size
) != fc
->file
->size
)
64 fc
->file
->flags
|= GIT_DIFF_FLAG_BINARY
;
65 /* check if user is forcing text diff the file */
66 else if (fc
->opts_flags
& GIT_DIFF_FORCE_TEXT
) {
67 fc
->file
->flags
&= ~GIT_DIFF_FLAG_BINARY
;
68 fc
->file
->flags
|= GIT_DIFF_FLAG_NOT_BINARY
;
70 /* check if user is forcing binary diff the file */
71 else if (fc
->opts_flags
& GIT_DIFF_FORCE_BINARY
) {
72 fc
->file
->flags
&= ~GIT_DIFF_FLAG_NOT_BINARY
;
73 fc
->file
->flags
|= GIT_DIFF_FLAG_BINARY
;
76 diff_file_content_binary_by_size(fc
);
78 if ((fc
->flags
& GIT_DIFF_FLAG__NO_DATA
) != 0) {
79 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
84 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) != 0)
85 diff_file_content_binary_by_content(fc
);
90 int git_diff_file_content__init_from_diff(
91 git_diff_file_content
*fc
,
93 git_diff_delta
*delta
,
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
));
153 fc
->file
->id_abbrev
= GIT_OID_HEXSZ
;
155 fc
->map
.len
= (size_t)fc
->file
->size
;
156 fc
->map
.data
= (char *)git_blob_rawcontent(src
->blob
);
158 fc
->file
->size
= src
->buflen
;
159 git_odb_hash(&fc
->file
->id
, src
->buf
, src
->buflen
, GIT_OBJ_BLOB
);
160 fc
->file
->id_abbrev
= GIT_OID_HEXSZ
;
162 fc
->map
.len
= src
->buflen
;
163 fc
->map
.data
= (char *)src
->buf
;
167 return diff_file_content_init_common(fc
, opts
);
170 static int diff_file_content_commit_to_str(
171 git_diff_file_content
*fc
, bool check_status
)
173 char oid
[GIT_OID_HEXSZ
+1];
174 git_buf content
= GIT_BUF_INIT
;
175 const char *status
= "";
179 git_submodule
*sm
= NULL
;
180 unsigned int sm_status
= 0;
181 const git_oid
*sm_head
;
183 if ((error
= git_submodule_lookup(&sm
, fc
->repo
, fc
->file
->path
)) < 0) {
184 /* GIT_EEXISTS means a "submodule" that has not been git added */
185 if (error
== GIT_EEXISTS
) {
192 if ((error
= git_submodule_status(&sm_status
, fc
->repo
, fc
->file
->path
, GIT_SUBMODULE_IGNORE_UNSPECIFIED
)) < 0) {
193 git_submodule_free(sm
);
197 /* update OID if we didn't have it previously */
198 if ((fc
->file
->flags
& GIT_DIFF_FLAG_VALID_ID
) == 0 &&
199 ((sm_head
= git_submodule_wd_id(sm
)) != NULL
||
200 (sm_head
= git_submodule_head_id(sm
)) != NULL
))
202 git_oid_cpy(&fc
->file
->id
, sm_head
);
203 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
206 if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status
))
209 git_submodule_free(sm
);
212 git_oid_tostr(oid
, sizeof(oid
), &fc
->file
->id
);
213 if (git_buf_printf(&content
, "Subproject commit %s%s\n", oid
, status
) < 0)
216 fc
->map
.len
= git_buf_len(&content
);
217 fc
->map
.data
= git_buf_detach(&content
);
218 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
223 static int diff_file_content_load_blob(
224 git_diff_file_content
*fc
,
225 git_diff_options
*opts
)
228 git_odb_object
*odb_obj
= NULL
;
230 if (git_oid_iszero(&fc
->file
->id
))
233 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
234 return diff_file_content_commit_to_str(fc
, false);
236 /* if we don't know size, try to peek at object header first */
237 if (!fc
->file
->size
) {
238 if ((error
= git_diff_file__resolve_zero_size(
239 fc
->file
, &odb_obj
, fc
->repo
)) < 0)
243 if ((opts
->flags
& GIT_DIFF_SHOW_BINARY
) == 0 &&
244 diff_file_content_binary_by_size(fc
))
247 if (odb_obj
!= NULL
) {
248 error
= git_object__from_odb_object(
249 (git_object
**)&fc
->blob
, fc
->repo
, odb_obj
, GIT_OBJ_BLOB
);
250 git_odb_object_free(odb_obj
);
252 error
= git_blob_lookup(
253 (git_blob
**)&fc
->blob
, fc
->repo
, &fc
->file
->id
);
257 fc
->flags
|= GIT_DIFF_FLAG__FREE_BLOB
;
258 fc
->map
.data
= (void *)git_blob_rawcontent(fc
->blob
);
259 fc
->map
.len
= (size_t)git_blob_rawsize(fc
->blob
);
265 static int diff_file_content_load_workdir_symlink_fake(
266 git_diff_file_content
*fc
, git_buf
*path
)
268 git_buf target
= GIT_BUF_INIT
;
271 if ((error
= git_futils_readbuffer(&target
, path
->ptr
)) < 0)
274 fc
->map
.len
= git_buf_len(&target
);
275 fc
->map
.data
= git_buf_detach(&target
);
276 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
278 git_buf_free(&target
);
282 static int diff_file_content_load_workdir_symlink(
283 git_diff_file_content
*fc
, git_buf
*path
)
285 ssize_t alloc_len
, read_len
;
286 int symlink_supported
, error
;
288 if ((error
= git_repository__cvar(
289 &symlink_supported
, fc
->repo
, GIT_CVAR_SYMLINKS
)) < 0)
292 if (!symlink_supported
)
293 return diff_file_content_load_workdir_symlink_fake(fc
, path
);
295 /* link path on disk could be UTF-16, so prepare a buffer that is
296 * big enough to handle some UTF-8 data expansion
298 alloc_len
= (ssize_t
)(fc
->file
->size
* 2) + 1;
300 fc
->map
.data
= git__calloc(alloc_len
, sizeof(char));
301 GITERR_CHECK_ALLOC(fc
->map
.data
);
303 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
305 read_len
= p_readlink(git_buf_cstr(path
), fc
->map
.data
, alloc_len
);
307 giterr_set(GITERR_OS
, "Failed to read symlink '%s'", fc
->file
->path
);
311 fc
->map
.len
= read_len
;
315 static int diff_file_content_load_workdir_file(
316 git_diff_file_content
*fc
,
318 git_diff_options
*diff_opts
)
321 git_filter_list
*fl
= NULL
;
322 git_file fd
= git_futils_open_ro(git_buf_cstr(path
));
323 git_buf raw
= GIT_BUF_INIT
;
328 if (!fc
->file
->size
&&
329 !(fc
->file
->size
= git_futils_filesize(fd
)))
332 if ((diff_opts
->flags
& GIT_DIFF_SHOW_BINARY
) == 0 &&
333 diff_file_content_binary_by_size(fc
))
336 if ((error
= git_filter_list_load(
337 &fl
, fc
->repo
, NULL
, fc
->file
->path
,
338 GIT_FILTER_TO_ODB
, GIT_FILTER_ALLOW_UNSAFE
)) < 0)
341 /* if there are no filters, try to mmap the file */
343 if (!(error
= git_futils_mmap_ro(
344 &fc
->map
, fd
, 0, (size_t)fc
->file
->size
))) {
345 fc
->flags
|= GIT_DIFF_FLAG__UNMAP_DATA
;
349 /* if mmap failed, fall through to try readbuffer below */
353 if (!(error
= git_futils_readbuffer_fd(&raw
, fd
, (size_t)fc
->file
->size
))) {
354 git_buf out
= GIT_BUF_INIT
;
356 error
= git_filter_list_apply_to_data(&out
, fl
, &raw
);
358 if (out
.ptr
!= raw
.ptr
)
362 fc
->map
.len
= out
.size
;
363 fc
->map
.data
= out
.ptr
;
364 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
369 git_filter_list_free(fl
);
375 static int diff_file_content_load_workdir(
376 git_diff_file_content
*fc
,
377 git_diff_options
*diff_opts
)
380 git_buf path
= GIT_BUF_INIT
;
382 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
383 return diff_file_content_commit_to_str(fc
, true);
385 if (fc
->file
->mode
== GIT_FILEMODE_TREE
)
388 if (git_buf_joinpath(
389 &path
, git_repository_workdir(fc
->repo
), fc
->file
->path
) < 0)
392 if (S_ISLNK(fc
->file
->mode
))
393 error
= diff_file_content_load_workdir_symlink(fc
, &path
);
395 error
= diff_file_content_load_workdir_file(fc
, &path
, diff_opts
);
397 /* once data is loaded, update OID if we didn't have it previously */
398 if (!error
&& (fc
->file
->flags
& GIT_DIFF_FLAG_VALID_ID
) == 0) {
399 error
= git_odb_hash(
400 &fc
->file
->id
, fc
->map
.data
, fc
->map
.len
, GIT_OBJ_BLOB
);
401 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
408 int git_diff_file_content__load(
409 git_diff_file_content
*fc
,
410 git_diff_options
*diff_opts
)
414 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) != 0)
417 if ((fc
->file
->flags
& GIT_DIFF_FLAG_BINARY
) != 0 &&
418 (diff_opts
->flags
& GIT_DIFF_SHOW_BINARY
) == 0)
421 if (fc
->src
== GIT_ITERATOR_TYPE_WORKDIR
)
422 error
= diff_file_content_load_workdir(fc
, diff_opts
);
424 error
= diff_file_content_load_blob(fc
, diff_opts
);
428 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
430 diff_file_content_binary_by_content(fc
);
435 void git_diff_file_content__unload(git_diff_file_content
*fc
)
437 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) == 0)
440 if (fc
->flags
& GIT_DIFF_FLAG__FREE_DATA
) {
441 git__free(fc
->map
.data
);
444 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_DATA
;
446 else if (fc
->flags
& GIT_DIFF_FLAG__UNMAP_DATA
) {
447 git_futils_mmap_free(&fc
->map
);
450 fc
->flags
&= ~GIT_DIFF_FLAG__UNMAP_DATA
;
453 if (fc
->flags
& GIT_DIFF_FLAG__FREE_BLOB
) {
454 git_blob_free((git_blob
*)fc
->blob
);
456 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_BLOB
;
459 fc
->flags
&= ~GIT_DIFF_FLAG__LOADED
;
462 void git_diff_file_content__clear(git_diff_file_content
*fc
)
464 git_diff_file_content__unload(fc
);
466 /* for now, nothing else to do */