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_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_blob(
131 git_diff_file_content
*fc
,
132 git_repository
*repo
,
133 const git_diff_options
*opts
,
134 const git_blob
*blob
,
135 git_diff_file
*as_file
)
137 memset(fc
, 0, sizeof(*fc
));
143 fc
->flags
|= GIT_DIFF_FLAG__NO_DATA
;
145 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
146 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_OID
;
147 fc
->file
->size
= git_blob_rawsize(blob
);
148 fc
->file
->mode
= GIT_FILEMODE_BLOB
;
149 git_oid_cpy(&fc
->file
->oid
, git_blob_id(blob
));
151 fc
->map
.len
= (size_t)fc
->file
->size
;
152 fc
->map
.data
= (char *)git_blob_rawcontent(blob
);
155 return diff_file_content_init_common(fc
, opts
);
158 int git_diff_file_content__init_from_raw(
159 git_diff_file_content
*fc
,
160 git_repository
*repo
,
161 const git_diff_options
*opts
,
164 git_diff_file
*as_file
)
166 memset(fc
, 0, sizeof(*fc
));
171 fc
->flags
|= GIT_DIFF_FLAG__NO_DATA
;
173 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
174 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_OID
;
175 fc
->file
->size
= buflen
;
176 fc
->file
->mode
= GIT_FILEMODE_BLOB
;
177 git_odb_hash(&fc
->file
->oid
, buf
, buflen
, GIT_OBJ_BLOB
);
179 fc
->map
.len
= buflen
;
180 fc
->map
.data
= (char *)buf
;
183 return diff_file_content_init_common(fc
, opts
);
186 static int diff_file_content_commit_to_str(
187 git_diff_file_content
*fc
, bool check_status
)
189 char oid
[GIT_OID_HEXSZ
+1];
190 git_buf content
= GIT_BUF_INIT
;
191 const char *status
= "";
195 git_submodule
*sm
= NULL
;
196 unsigned int sm_status
= 0;
197 const git_oid
*sm_head
;
199 if ((error
= git_submodule_lookup(&sm
, fc
->repo
, fc
->file
->path
)) < 0 ||
200 (error
= git_submodule_status(&sm_status
, sm
)) < 0) {
201 /* GIT_EEXISTS means a "submodule" that has not been git added */
202 if (error
== GIT_EEXISTS
)
207 /* update OID if we didn't have it previously */
208 if ((fc
->file
->flags
& GIT_DIFF_FLAG_VALID_OID
) == 0 &&
209 ((sm_head
= git_submodule_wd_id(sm
)) != NULL
||
210 (sm_head
= git_submodule_head_id(sm
)) != NULL
))
212 git_oid_cpy(&fc
->file
->oid
, sm_head
);
213 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_OID
;
216 if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status
))
220 git_oid_tostr(oid
, sizeof(oid
), &fc
->file
->oid
);
221 if (git_buf_printf(&content
, "Subproject commit %s%s\n", oid
, status
) < 0)
224 fc
->map
.len
= git_buf_len(&content
);
225 fc
->map
.data
= git_buf_detach(&content
);
226 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
231 static int diff_file_content_load_blob(git_diff_file_content
*fc
)
234 git_odb_object
*odb_obj
= NULL
;
236 if (git_oid_iszero(&fc
->file
->oid
))
239 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
240 return diff_file_content_commit_to_str(fc
, false);
242 /* if we don't know size, try to peek at object header first */
243 if (!fc
->file
->size
) {
244 if ((error
= git_diff_file__resolve_zero_size(
245 fc
->file
, &odb_obj
, fc
->repo
)) < 0)
249 if (diff_file_content_binary_by_size(fc
))
252 if (odb_obj
!= NULL
) {
253 error
= git_object__from_odb_object(
254 (git_object
**)&fc
->blob
, fc
->repo
, odb_obj
, GIT_OBJ_BLOB
);
255 git_odb_object_free(odb_obj
);
257 error
= git_blob_lookup(
258 (git_blob
**)&fc
->blob
, fc
->repo
, &fc
->file
->oid
);
262 fc
->flags
|= GIT_DIFF_FLAG__FREE_BLOB
;
263 fc
->map
.data
= (void *)git_blob_rawcontent(fc
->blob
);
264 fc
->map
.len
= (size_t)git_blob_rawsize(fc
->blob
);
270 static int diff_file_content_load_workdir_symlink(
271 git_diff_file_content
*fc
, git_buf
*path
)
273 ssize_t alloc_len
, read_len
;
275 /* link path on disk could be UTF-16, so prepare a buffer that is
276 * big enough to handle some UTF-8 data expansion
278 alloc_len
= (ssize_t
)(fc
->file
->size
* 2) + 1;
280 fc
->map
.data
= git__calloc(alloc_len
, sizeof(char));
281 GITERR_CHECK_ALLOC(fc
->map
.data
);
283 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
285 read_len
= p_readlink(git_buf_cstr(path
), fc
->map
.data
, alloc_len
);
287 giterr_set(GITERR_OS
, "Failed to read symlink '%s'", fc
->file
->path
);
291 fc
->map
.len
= read_len
;
295 static int diff_file_content_load_workdir_file(
296 git_diff_file_content
*fc
, git_buf
*path
)
299 git_filter_list
*fl
= NULL
;
300 git_file fd
= git_futils_open_ro(git_buf_cstr(path
));
301 git_buf raw
= GIT_BUF_INIT
;
306 if (!fc
->file
->size
&&
307 !(fc
->file
->size
= git_futils_filesize(fd
)))
310 if (diff_file_content_binary_by_size(fc
))
313 if ((error
= git_filter_list_load(
314 &fl
, fc
->repo
, NULL
, fc
->file
->path
, GIT_FILTER_TO_ODB
)) < 0)
317 /* if there are no filters, try to mmap the file */
319 if (!(error
= git_futils_mmap_ro(
320 &fc
->map
, fd
, 0, (size_t)fc
->file
->size
))) {
321 fc
->flags
|= GIT_DIFF_FLAG__UNMAP_DATA
;
325 /* if mmap failed, fall through to try readbuffer below */
329 if (!(error
= git_futils_readbuffer_fd(&raw
, fd
, (size_t)fc
->file
->size
))) {
330 git_buf out
= GIT_BUF_INIT
;
332 error
= git_filter_list_apply_to_data(&out
, fl
, &raw
);
337 fc
->map
.len
= out
.size
;
338 fc
->map
.data
= out
.ptr
;
339 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
344 git_filter_list_free(fl
);
350 static int diff_file_content_load_workdir(git_diff_file_content
*fc
)
353 git_buf path
= GIT_BUF_INIT
;
355 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
356 return diff_file_content_commit_to_str(fc
, true);
358 if (fc
->file
->mode
== GIT_FILEMODE_TREE
)
361 if (git_buf_joinpath(
362 &path
, git_repository_workdir(fc
->repo
), fc
->file
->path
) < 0)
365 if (S_ISLNK(fc
->file
->mode
))
366 error
= diff_file_content_load_workdir_symlink(fc
, &path
);
368 error
= diff_file_content_load_workdir_file(fc
, &path
);
370 /* once data is loaded, update OID if we didn't have it previously */
371 if (!error
&& (fc
->file
->flags
& GIT_DIFF_FLAG_VALID_OID
) == 0) {
372 error
= git_odb_hash(
373 &fc
->file
->oid
, fc
->map
.data
, fc
->map
.len
, GIT_OBJ_BLOB
);
374 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_OID
;
381 int git_diff_file_content__load(git_diff_file_content
*fc
)
385 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) != 0)
388 if ((fc
->file
->flags
& GIT_DIFF_FLAG_BINARY
) != 0)
391 if (fc
->src
== GIT_ITERATOR_TYPE_WORKDIR
)
392 error
= diff_file_content_load_workdir(fc
);
394 error
= diff_file_content_load_blob(fc
);
398 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
400 diff_file_content_binary_by_content(fc
);
405 void git_diff_file_content__unload(git_diff_file_content
*fc
)
407 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) == 0)
410 if (fc
->flags
& GIT_DIFF_FLAG__FREE_DATA
) {
411 git__free(fc
->map
.data
);
414 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_DATA
;
416 else if (fc
->flags
& GIT_DIFF_FLAG__UNMAP_DATA
) {
417 git_futils_mmap_free(&fc
->map
);
420 fc
->flags
&= ~GIT_DIFF_FLAG__UNMAP_DATA
;
423 if (fc
->flags
& GIT_DIFF_FLAG__FREE_BLOB
) {
424 git_blob_free((git_blob
*)fc
->blob
);
426 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_BLOB
;
429 fc
->flags
&= ~GIT_DIFF_FLAG__LOADED
;
432 void git_diff_file_content__clear(git_diff_file_content
*fc
)
434 git_diff_file_content__unload(fc
);
436 /* for now, nothing else to do */