]>
Commit | Line | Data |
---|---|---|
114f5a6c RB |
1 | /* |
2 | * Copyright (C) the libgit2 contributors. All rights reserved. | |
3 | * | |
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. | |
6 | */ | |
7 | #include "common.h" | |
8 | #include "git2/blob.h" | |
9 | #include "git2/submodule.h" | |
10 | #include "diff.h" | |
11 | #include "diff_file.h" | |
12 | #include "odb.h" | |
13 | #include "fileops.h" | |
14 | #include "filter.h" | |
15 | ||
16 | #define DIFF_MAX_FILESIZE 0x20000000 | |
17 | ||
18 | static bool diff_file_content_binary_by_size(git_diff_file_content *fc) | |
19 | { | |
20 | /* if we have diff opts, check max_size vs file size */ | |
74ded024 | 21 | if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 && |
5dc98298 | 22 | fc->opts_max_size > 0 && |
74ded024 RB |
23 | fc->file->size > fc->opts_max_size) |
24 | fc->file->flags |= GIT_DIFF_FLAG_BINARY; | |
114f5a6c | 25 | |
74ded024 | 26 | return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0); |
114f5a6c RB |
27 | } |
28 | ||
29 | static void diff_file_content_binary_by_content(git_diff_file_content *fc) | |
30 | { | |
74ded024 | 31 | if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) |
114f5a6c RB |
32 | return; |
33 | ||
34 | switch (git_diff_driver_content_is_binary( | |
35 | fc->driver, fc->map.data, fc->map.len)) { | |
74ded024 RB |
36 | case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break; |
37 | case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break; | |
114f5a6c RB |
38 | default: break; |
39 | } | |
40 | } | |
41 | ||
5dc98298 RB |
42 | static int diff_file_content_init_common( |
43 | git_diff_file_content *fc, const git_diff_options *opts) | |
114f5a6c | 44 | { |
5dc98298 RB |
45 | fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL; |
46 | ||
47 | if (opts && opts->max_size >= 0) | |
48 | fc->opts_max_size = opts->max_size ? | |
49 | opts->max_size : DIFF_MAX_FILESIZE; | |
114f5a6c | 50 | |
74ded024 | 51 | if (fc->src == GIT_ITERATOR_TYPE_EMPTY) |
114f5a6c | 52 | fc->src = GIT_ITERATOR_TYPE_TREE; |
74ded024 RB |
53 | |
54 | if (!fc->driver && | |
55 | git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0) | |
56 | return -1; | |
114f5a6c | 57 | |
5dc98298 RB |
58 | /* give driver a chance to modify options */ |
59 | git_diff_driver_update_options(&fc->opts_flags, fc->driver); | |
60 | ||
114f5a6c | 61 | /* make sure file is conceivable mmap-able */ |
74ded024 RB |
62 | if ((git_off_t)((size_t)fc->file->size) != fc->file->size) |
63 | fc->file->flags |= GIT_DIFF_FLAG_BINARY; | |
5dc98298 RB |
64 | /* check if user is forcing text diff the file */ |
65 | else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) { | |
74ded024 RB |
66 | fc->file->flags &= ~GIT_DIFF_FLAG_BINARY; |
67 | fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; | |
5dc98298 RB |
68 | } |
69 | /* check if user is forcing binary diff the file */ | |
70 | else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) { | |
74ded024 RB |
71 | fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY; |
72 | fc->file->flags |= GIT_DIFF_FLAG_BINARY; | |
5dc98298 | 73 | } |
114f5a6c RB |
74 | |
75 | diff_file_content_binary_by_size(fc); | |
76 | ||
74ded024 RB |
77 | if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) { |
78 | fc->flags |= GIT_DIFF_FLAG__LOADED; | |
114f5a6c RB |
79 | fc->map.len = 0; |
80 | fc->map.data = ""; | |
81 | } | |
82 | ||
74ded024 | 83 | if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) |
114f5a6c RB |
84 | diff_file_content_binary_by_content(fc); |
85 | ||
86 | return 0; | |
87 | } | |
88 | ||
360f42f4 | 89 | int git_diff_file_content__init_from_diff( |
114f5a6c | 90 | git_diff_file_content *fc, |
3ff1d123 | 91 | git_diff *diff, |
114f5a6c RB |
92 | size_t delta_index, |
93 | bool use_old) | |
94 | { | |
95 | git_diff_delta *delta = git_vector_get(&diff->deltas, delta_index); | |
114f5a6c RB |
96 | bool has_data = true; |
97 | ||
98 | memset(fc, 0, sizeof(*fc)); | |
99 | fc->repo = diff->repo; | |
74ded024 | 100 | fc->file = use_old ? &delta->old_file : &delta->new_file; |
114f5a6c | 101 | fc->src = use_old ? diff->old_src : diff->new_src; |
114f5a6c | 102 | |
74ded024 | 103 | if (git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0) |
114f5a6c RB |
104 | return -1; |
105 | ||
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 && | |
10672e3e | 113 | (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0; |
114f5a6c RB |
114 | break; |
115 | case GIT_DELTA_MODIFIED: | |
116 | case GIT_DELTA_COPIED: | |
117 | case GIT_DELTA_RENAMED: | |
118 | break; | |
119 | default: | |
120 | has_data = false; | |
121 | break; | |
122 | } | |
123 | ||
124 | if (!has_data) | |
74ded024 | 125 | fc->flags |= GIT_DIFF_FLAG__NO_DATA; |
114f5a6c | 126 | |
5dc98298 | 127 | return diff_file_content_init_common(fc, &diff->opts); |
114f5a6c RB |
128 | } |
129 | ||
6789b7a7 | 130 | int git_diff_file_content__init_from_src( |
114f5a6c RB |
131 | git_diff_file_content *fc, |
132 | git_repository *repo, | |
133 | const git_diff_options *opts, | |
6789b7a7 | 134 | const git_diff_file_content_src *src, |
74ded024 | 135 | git_diff_file *as_file) |
114f5a6c RB |
136 | { |
137 | memset(fc, 0, sizeof(*fc)); | |
138 | fc->repo = repo; | |
74ded024 | 139 | fc->file = as_file; |
6789b7a7 | 140 | fc->blob = src->blob; |
114f5a6c | 141 | |
6789b7a7 | 142 | if (!src->blob && !src->buf) { |
74ded024 | 143 | fc->flags |= GIT_DIFF_FLAG__NO_DATA; |
114f5a6c | 144 | } else { |
74ded024 | 145 | fc->flags |= GIT_DIFF_FLAG__LOADED; |
9950bb4e | 146 | fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; |
74ded024 | 147 | fc->file->mode = GIT_FILEMODE_BLOB; |
114f5a6c | 148 | |
6789b7a7 RB |
149 | if (src->blob) { |
150 | fc->file->size = git_blob_rawsize(src->blob); | |
151 | git_oid_cpy(&fc->file->id, git_blob_id(src->blob)); | |
114f5a6c | 152 | |
6789b7a7 RB |
153 | fc->map.len = (size_t)fc->file->size; |
154 | fc->map.data = (char *)git_blob_rawcontent(src->blob); | |
155 | } else { | |
156 | fc->file->size = src->buflen; | |
157 | git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB); | |
114f5a6c | 158 | |
6789b7a7 RB |
159 | fc->map.len = src->buflen; |
160 | fc->map.data = (char *)src->buf; | |
161 | } | |
114f5a6c RB |
162 | } |
163 | ||
5dc98298 | 164 | return diff_file_content_init_common(fc, opts); |
114f5a6c RB |
165 | } |
166 | ||
167 | static int diff_file_content_commit_to_str( | |
168 | git_diff_file_content *fc, bool check_status) | |
169 | { | |
170 | char oid[GIT_OID_HEXSZ+1]; | |
171 | git_buf content = GIT_BUF_INIT; | |
172 | const char *status = ""; | |
173 | ||
174 | if (check_status) { | |
175 | int error = 0; | |
176 | git_submodule *sm = NULL; | |
177 | unsigned int sm_status = 0; | |
178 | const git_oid *sm_head; | |
179 | ||
74ded024 | 180 | if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0 || |
114f5a6c RB |
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) | |
184 | error = 0; | |
185 | return error; | |
186 | } | |
187 | ||
188 | /* update OID if we didn't have it previously */ | |
9950bb4e | 189 | if ((fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0 && |
114f5a6c RB |
190 | ((sm_head = git_submodule_wd_id(sm)) != NULL || |
191 | (sm_head = git_submodule_head_id(sm)) != NULL)) | |
192 | { | |
9950bb4e CMN |
193 | git_oid_cpy(&fc->file->id, sm_head); |
194 | fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; | |
114f5a6c RB |
195 | } |
196 | ||
197 | if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) | |
198 | status = "-dirty"; | |
199 | } | |
200 | ||
9950bb4e | 201 | git_oid_tostr(oid, sizeof(oid), &fc->file->id); |
114f5a6c RB |
202 | if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0) |
203 | return -1; | |
204 | ||
205 | fc->map.len = git_buf_len(&content); | |
206 | fc->map.data = git_buf_detach(&content); | |
74ded024 | 207 | fc->flags |= GIT_DIFF_FLAG__FREE_DATA; |
114f5a6c RB |
208 | |
209 | return 0; | |
210 | } | |
211 | ||
212 | static int diff_file_content_load_blob(git_diff_file_content *fc) | |
213 | { | |
214 | int error = 0; | |
215 | git_odb_object *odb_obj = NULL; | |
216 | ||
9950bb4e | 217 | if (git_oid_iszero(&fc->file->id)) |
114f5a6c RB |
218 | return 0; |
219 | ||
74ded024 | 220 | if (fc->file->mode == GIT_FILEMODE_COMMIT) |
114f5a6c RB |
221 | return diff_file_content_commit_to_str(fc, false); |
222 | ||
223 | /* if we don't know size, try to peek at object header first */ | |
74ded024 | 224 | if (!fc->file->size) { |
effdbeb3 RB |
225 | if ((error = git_diff_file__resolve_zero_size( |
226 | fc->file, &odb_obj, fc->repo)) < 0) | |
114f5a6c | 227 | return error; |
114f5a6c RB |
228 | } |
229 | ||
230 | if (diff_file_content_binary_by_size(fc)) | |
231 | return 0; | |
232 | ||
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); | |
237 | } else { | |
238 | error = git_blob_lookup( | |
9950bb4e | 239 | (git_blob **)&fc->blob, fc->repo, &fc->file->id); |
114f5a6c RB |
240 | } |
241 | ||
242 | if (!error) { | |
74ded024 | 243 | fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; |
114f5a6c RB |
244 | fc->map.data = (void *)git_blob_rawcontent(fc->blob); |
245 | fc->map.len = (size_t)git_blob_rawsize(fc->blob); | |
246 | } | |
247 | ||
248 | return error; | |
249 | } | |
250 | ||
251 | static int diff_file_content_load_workdir_symlink( | |
252 | git_diff_file_content *fc, git_buf *path) | |
253 | { | |
254 | ssize_t alloc_len, read_len; | |
255 | ||
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 | |
258 | */ | |
74ded024 | 259 | alloc_len = (ssize_t)(fc->file->size * 2) + 1; |
114f5a6c RB |
260 | |
261 | fc->map.data = git__calloc(alloc_len, sizeof(char)); | |
262 | GITERR_CHECK_ALLOC(fc->map.data); | |
263 | ||
74ded024 | 264 | fc->flags |= GIT_DIFF_FLAG__FREE_DATA; |
114f5a6c RB |
265 | |
266 | read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len); | |
267 | if (read_len < 0) { | |
74ded024 | 268 | giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file->path); |
114f5a6c RB |
269 | return -1; |
270 | } | |
271 | ||
272 | fc->map.len = read_len; | |
273 | return 0; | |
274 | } | |
275 | ||
276 | static int diff_file_content_load_workdir_file( | |
277 | git_diff_file_content *fc, git_buf *path) | |
278 | { | |
279 | int error = 0; | |
85d54812 | 280 | git_filter_list *fl = NULL; |
114f5a6c | 281 | git_file fd = git_futils_open_ro(git_buf_cstr(path)); |
2a7d224f | 282 | git_buf raw = GIT_BUF_INIT; |
114f5a6c RB |
283 | |
284 | if (fd < 0) | |
285 | return fd; | |
286 | ||
74ded024 RB |
287 | if (!fc->file->size && |
288 | !(fc->file->size = git_futils_filesize(fd))) | |
114f5a6c RB |
289 | goto cleanup; |
290 | ||
291 | if (diff_file_content_binary_by_size(fc)) | |
292 | goto cleanup; | |
293 | ||
85d54812 | 294 | if ((error = git_filter_list_load( |
4b11f25a | 295 | &fl, fc->repo, NULL, fc->file->path, GIT_FILTER_TO_ODB)) < 0) |
114f5a6c | 296 | goto cleanup; |
114f5a6c | 297 | |
85d54812 RB |
298 | /* if there are no filters, try to mmap the file */ |
299 | if (fl == NULL) { | |
114f5a6c | 300 | if (!(error = git_futils_mmap_ro( |
85d54812 | 301 | &fc->map, fd, 0, (size_t)fc->file->size))) { |
74ded024 | 302 | fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; |
114f5a6c | 303 | goto cleanup; |
114f5a6c RB |
304 | } |
305 | ||
85d54812 RB |
306 | /* if mmap failed, fall through to try readbuffer below */ |
307 | giterr_clear(); | |
114f5a6c RB |
308 | } |
309 | ||
2a7d224f | 310 | if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) { |
a9f51e43 | 311 | git_buf out = GIT_BUF_INIT; |
2a7d224f | 312 | |
a9f51e43 | 313 | error = git_filter_list_apply_to_data(&out, fl, &raw); |
2a7d224f | 314 | |
a9f51e43 | 315 | git_buf_free(&raw); |
85d54812 | 316 | |
2a7d224f RB |
317 | if (!error) { |
318 | fc->map.len = out.size; | |
319 | fc->map.data = out.ptr; | |
320 | fc->flags |= GIT_DIFF_FLAG__FREE_DATA; | |
321 | } | |
322 | } | |
85d54812 | 323 | |
114f5a6c | 324 | cleanup: |
85d54812 | 325 | git_filter_list_free(fl); |
114f5a6c RB |
326 | p_close(fd); |
327 | ||
328 | return error; | |
329 | } | |
330 | ||
331 | static int diff_file_content_load_workdir(git_diff_file_content *fc) | |
332 | { | |
333 | int error = 0; | |
334 | git_buf path = GIT_BUF_INIT; | |
335 | ||
74ded024 | 336 | if (fc->file->mode == GIT_FILEMODE_COMMIT) |
114f5a6c RB |
337 | return diff_file_content_commit_to_str(fc, true); |
338 | ||
74ded024 | 339 | if (fc->file->mode == GIT_FILEMODE_TREE) |
114f5a6c RB |
340 | return 0; |
341 | ||
342 | if (git_buf_joinpath( | |
74ded024 | 343 | &path, git_repository_workdir(fc->repo), fc->file->path) < 0) |
114f5a6c RB |
344 | return -1; |
345 | ||
74ded024 | 346 | if (S_ISLNK(fc->file->mode)) |
114f5a6c RB |
347 | error = diff_file_content_load_workdir_symlink(fc, &path); |
348 | else | |
349 | error = diff_file_content_load_workdir_file(fc, &path); | |
350 | ||
351 | /* once data is loaded, update OID if we didn't have it previously */ | |
9950bb4e | 352 | if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0) { |
114f5a6c | 353 | error = git_odb_hash( |
9950bb4e CMN |
354 | &fc->file->id, fc->map.data, fc->map.len, GIT_OBJ_BLOB); |
355 | fc->file->flags |= GIT_DIFF_FLAG_VALID_ID; | |
114f5a6c RB |
356 | } |
357 | ||
358 | git_buf_free(&path); | |
359 | return error; | |
360 | } | |
361 | ||
360f42f4 | 362 | int git_diff_file_content__load(git_diff_file_content *fc) |
114f5a6c RB |
363 | { |
364 | int error = 0; | |
365 | ||
74ded024 | 366 | if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) |
114f5a6c RB |
367 | return 0; |
368 | ||
74ded024 | 369 | if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0) |
114f5a6c RB |
370 | return 0; |
371 | ||
372 | if (fc->src == GIT_ITERATOR_TYPE_WORKDIR) | |
373 | error = diff_file_content_load_workdir(fc); | |
374 | else | |
375 | error = diff_file_content_load_blob(fc); | |
376 | if (error) | |
377 | return error; | |
378 | ||
74ded024 | 379 | fc->flags |= GIT_DIFF_FLAG__LOADED; |
114f5a6c RB |
380 | |
381 | diff_file_content_binary_by_content(fc); | |
382 | ||
383 | return 0; | |
384 | } | |
385 | ||
360f42f4 | 386 | void git_diff_file_content__unload(git_diff_file_content *fc) |
114f5a6c | 387 | { |
39a1a662 RB |
388 | if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0) |
389 | return; | |
390 | ||
74ded024 | 391 | if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { |
114f5a6c RB |
392 | git__free(fc->map.data); |
393 | fc->map.data = ""; | |
394 | fc->map.len = 0; | |
74ded024 | 395 | fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA; |
114f5a6c | 396 | } |
74ded024 | 397 | else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) { |
114f5a6c RB |
398 | git_futils_mmap_free(&fc->map); |
399 | fc->map.data = ""; | |
400 | fc->map.len = 0; | |
74ded024 | 401 | fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; |
114f5a6c RB |
402 | } |
403 | ||
74ded024 | 404 | if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) { |
114f5a6c RB |
405 | git_blob_free((git_blob *)fc->blob); |
406 | fc->blob = NULL; | |
74ded024 | 407 | fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB; |
114f5a6c RB |
408 | } |
409 | ||
74ded024 | 410 | fc->flags &= ~GIT_DIFF_FLAG__LOADED; |
114f5a6c RB |
411 | } |
412 | ||
360f42f4 | 413 | void git_diff_file_content__clear(git_diff_file_content *fc) |
114f5a6c | 414 | { |
360f42f4 | 415 | git_diff_file_content__unload(fc); |
114f5a6c RB |
416 | |
417 | /* for now, nothing else to do */ | |
418 | } |