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_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 (error
= git_submodule_status(&sm_status
, sm
)) < 0) {
182 /* GIT_EEXISTS means a "submodule" that has not been git added */
183 if (error
== GIT_EEXISTS
)
188 /* update OID if we didn't have it previously */
189 if ((fc
->file
->flags
& GIT_DIFF_FLAG_VALID_ID
) == 0 &&
190 ((sm_head
= git_submodule_wd_id(sm
)) != NULL
||
191 (sm_head
= git_submodule_head_id(sm
)) != NULL
))
193 git_oid_cpy(&fc
->file
->id
, sm_head
);
194 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
197 if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status
))
201 git_oid_tostr(oid
, sizeof(oid
), &fc
->file
->id
);
202 if (git_buf_printf(&content
, "Subproject commit %s%s\n", oid
, status
) < 0)
205 fc
->map
.len
= git_buf_len(&content
);
206 fc
->map
.data
= git_buf_detach(&content
);
207 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
212 static int diff_file_content_load_blob(git_diff_file_content
*fc
)
215 git_odb_object
*odb_obj
= NULL
;
217 if (git_oid_iszero(&fc
->file
->id
))
220 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
221 return diff_file_content_commit_to_str(fc
, false);
223 /* if we don't know size, try to peek at object header first */
224 if (!fc
->file
->size
) {
225 if ((error
= git_diff_file__resolve_zero_size(
226 fc
->file
, &odb_obj
, fc
->repo
)) < 0)
230 if (diff_file_content_binary_by_size(fc
))
233 if (odb_obj
!= NULL
) {
234 error
= git_object__from_odb_object(
235 (git_object
**)&fc
->blob
, fc
->repo
, odb_obj
, GIT_OBJ_BLOB
);
236 git_odb_object_free(odb_obj
);
238 error
= git_blob_lookup(
239 (git_blob
**)&fc
->blob
, fc
->repo
, &fc
->file
->id
);
243 fc
->flags
|= GIT_DIFF_FLAG__FREE_BLOB
;
244 fc
->map
.data
= (void *)git_blob_rawcontent(fc
->blob
);
245 fc
->map
.len
= (size_t)git_blob_rawsize(fc
->blob
);
251 static int diff_file_content_load_workdir_symlink(
252 git_diff_file_content
*fc
, git_buf
*path
)
254 ssize_t alloc_len
, read_len
;
256 /* link path on disk could be UTF-16, so prepare a buffer that is
257 * big enough to handle some UTF-8 data expansion
259 alloc_len
= (ssize_t
)(fc
->file
->size
* 2) + 1;
261 fc
->map
.data
= git__calloc(alloc_len
, sizeof(char));
262 GITERR_CHECK_ALLOC(fc
->map
.data
);
264 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
266 read_len
= p_readlink(git_buf_cstr(path
), fc
->map
.data
, alloc_len
);
268 giterr_set(GITERR_OS
, "Failed to read symlink '%s'", fc
->file
->path
);
272 fc
->map
.len
= read_len
;
276 static int diff_file_content_load_workdir_file(
277 git_diff_file_content
*fc
, git_buf
*path
)
280 git_filter_list
*fl
= NULL
;
281 git_file fd
= git_futils_open_ro(git_buf_cstr(path
));
282 git_buf raw
= GIT_BUF_INIT
;
287 if (!fc
->file
->size
&&
288 !(fc
->file
->size
= git_futils_filesize(fd
)))
291 if (diff_file_content_binary_by_size(fc
))
294 if ((error
= git_filter_list_load(
295 &fl
, fc
->repo
, NULL
, fc
->file
->path
, GIT_FILTER_TO_ODB
)) < 0)
298 /* if there are no filters, try to mmap the file */
300 if (!(error
= git_futils_mmap_ro(
301 &fc
->map
, fd
, 0, (size_t)fc
->file
->size
))) {
302 fc
->flags
|= GIT_DIFF_FLAG__UNMAP_DATA
;
306 /* if mmap failed, fall through to try readbuffer below */
310 if (!(error
= git_futils_readbuffer_fd(&raw
, fd
, (size_t)fc
->file
->size
))) {
311 git_buf out
= GIT_BUF_INIT
;
313 error
= git_filter_list_apply_to_data(&out
, fl
, &raw
);
318 fc
->map
.len
= out
.size
;
319 fc
->map
.data
= out
.ptr
;
320 fc
->flags
|= GIT_DIFF_FLAG__FREE_DATA
;
325 git_filter_list_free(fl
);
331 static int diff_file_content_load_workdir(git_diff_file_content
*fc
)
334 git_buf path
= GIT_BUF_INIT
;
336 if (fc
->file
->mode
== GIT_FILEMODE_COMMIT
)
337 return diff_file_content_commit_to_str(fc
, true);
339 if (fc
->file
->mode
== GIT_FILEMODE_TREE
)
342 if (git_buf_joinpath(
343 &path
, git_repository_workdir(fc
->repo
), fc
->file
->path
) < 0)
346 if (S_ISLNK(fc
->file
->mode
))
347 error
= diff_file_content_load_workdir_symlink(fc
, &path
);
349 error
= diff_file_content_load_workdir_file(fc
, &path
);
351 /* once data is loaded, update OID if we didn't have it previously */
352 if (!error
&& (fc
->file
->flags
& GIT_DIFF_FLAG_VALID_ID
) == 0) {
353 error
= git_odb_hash(
354 &fc
->file
->id
, fc
->map
.data
, fc
->map
.len
, GIT_OBJ_BLOB
);
355 fc
->file
->flags
|= GIT_DIFF_FLAG_VALID_ID
;
362 int git_diff_file_content__load(git_diff_file_content
*fc
)
366 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) != 0)
369 if ((fc
->file
->flags
& GIT_DIFF_FLAG_BINARY
) != 0)
372 if (fc
->src
== GIT_ITERATOR_TYPE_WORKDIR
)
373 error
= diff_file_content_load_workdir(fc
);
375 error
= diff_file_content_load_blob(fc
);
379 fc
->flags
|= GIT_DIFF_FLAG__LOADED
;
381 diff_file_content_binary_by_content(fc
);
386 void git_diff_file_content__unload(git_diff_file_content
*fc
)
388 if ((fc
->flags
& GIT_DIFF_FLAG__LOADED
) == 0)
391 if (fc
->flags
& GIT_DIFF_FLAG__FREE_DATA
) {
392 git__free(fc
->map
.data
);
395 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_DATA
;
397 else if (fc
->flags
& GIT_DIFF_FLAG__UNMAP_DATA
) {
398 git_futils_mmap_free(&fc
->map
);
401 fc
->flags
&= ~GIT_DIFF_FLAG__UNMAP_DATA
;
404 if (fc
->flags
& GIT_DIFF_FLAG__FREE_BLOB
) {
405 git_blob_free((git_blob
*)fc
->blob
);
407 fc
->flags
&= ~GIT_DIFF_FLAG__FREE_BLOB
;
410 fc
->flags
&= ~GIT_DIFF_FLAG__LOADED
;
413 void git_diff_file_content__clear(git_diff_file_content
*fc
)
415 git_diff_file_content__unload(fc
);
417 /* for now, nothing else to do */