]>
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 && | |
113 | (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) != 0; | |
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 | ||
360f42f4 | 130 | int git_diff_file_content__init_from_blob( |
114f5a6c RB |
131 | git_diff_file_content *fc, |
132 | git_repository *repo, | |
133 | const git_diff_options *opts, | |
74ded024 RB |
134 | const git_blob *blob, |
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; |
114f5a6c RB |
140 | fc->blob = blob; |
141 | ||
142 | if (!blob) { | |
74ded024 | 143 | fc->flags |= GIT_DIFF_FLAG__NO_DATA; |
114f5a6c | 144 | } else { |
74ded024 RB |
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)); | |
114f5a6c | 150 | |
74ded024 | 151 | fc->map.len = (size_t)fc->file->size; |
114f5a6c RB |
152 | fc->map.data = (char *)git_blob_rawcontent(blob); |
153 | } | |
154 | ||
5dc98298 | 155 | return diff_file_content_init_common(fc, opts); |
114f5a6c RB |
156 | } |
157 | ||
360f42f4 | 158 | int git_diff_file_content__init_from_raw( |
114f5a6c RB |
159 | git_diff_file_content *fc, |
160 | git_repository *repo, | |
161 | const git_diff_options *opts, | |
162 | const char *buf, | |
74ded024 RB |
163 | size_t buflen, |
164 | git_diff_file *as_file) | |
114f5a6c RB |
165 | { |
166 | memset(fc, 0, sizeof(*fc)); | |
167 | fc->repo = repo; | |
74ded024 | 168 | fc->file = as_file; |
114f5a6c RB |
169 | |
170 | if (!buf) { | |
74ded024 | 171 | fc->flags |= GIT_DIFF_FLAG__NO_DATA; |
114f5a6c | 172 | } else { |
74ded024 RB |
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); | |
114f5a6c RB |
178 | |
179 | fc->map.len = buflen; | |
180 | fc->map.data = (char *)buf; | |
181 | } | |
182 | ||
5dc98298 | 183 | return diff_file_content_init_common(fc, opts); |
114f5a6c RB |
184 | } |
185 | ||
186 | static int diff_file_content_commit_to_str( | |
187 | git_diff_file_content *fc, bool check_status) | |
188 | { | |
189 | char oid[GIT_OID_HEXSZ+1]; | |
190 | git_buf content = GIT_BUF_INIT; | |
191 | const char *status = ""; | |
192 | ||
193 | if (check_status) { | |
194 | int error = 0; | |
195 | git_submodule *sm = NULL; | |
196 | unsigned int sm_status = 0; | |
197 | const git_oid *sm_head; | |
198 | ||
74ded024 | 199 | if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0 || |
114f5a6c RB |
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) | |
203 | error = 0; | |
204 | return error; | |
205 | } | |
206 | ||
207 | /* update OID if we didn't have it previously */ | |
74ded024 | 208 | if ((fc->file->flags & GIT_DIFF_FLAG_VALID_OID) == 0 && |
114f5a6c RB |
209 | ((sm_head = git_submodule_wd_id(sm)) != NULL || |
210 | (sm_head = git_submodule_head_id(sm)) != NULL)) | |
211 | { | |
74ded024 RB |
212 | git_oid_cpy(&fc->file->oid, sm_head); |
213 | fc->file->flags |= GIT_DIFF_FLAG_VALID_OID; | |
114f5a6c RB |
214 | } |
215 | ||
216 | if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status)) | |
217 | status = "-dirty"; | |
218 | } | |
219 | ||
74ded024 | 220 | git_oid_tostr(oid, sizeof(oid), &fc->file->oid); |
114f5a6c RB |
221 | if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0) |
222 | return -1; | |
223 | ||
224 | fc->map.len = git_buf_len(&content); | |
225 | fc->map.data = git_buf_detach(&content); | |
74ded024 | 226 | fc->flags |= GIT_DIFF_FLAG__FREE_DATA; |
114f5a6c RB |
227 | |
228 | return 0; | |
229 | } | |
230 | ||
231 | static int diff_file_content_load_blob(git_diff_file_content *fc) | |
232 | { | |
233 | int error = 0; | |
234 | git_odb_object *odb_obj = NULL; | |
235 | ||
74ded024 | 236 | if (git_oid_iszero(&fc->file->oid)) |
114f5a6c RB |
237 | return 0; |
238 | ||
74ded024 | 239 | if (fc->file->mode == GIT_FILEMODE_COMMIT) |
114f5a6c RB |
240 | return diff_file_content_commit_to_str(fc, false); |
241 | ||
242 | /* if we don't know size, try to peek at object header first */ | |
74ded024 | 243 | if (!fc->file->size) { |
effdbeb3 RB |
244 | if ((error = git_diff_file__resolve_zero_size( |
245 | fc->file, &odb_obj, fc->repo)) < 0) | |
114f5a6c | 246 | return error; |
114f5a6c RB |
247 | } |
248 | ||
249 | if (diff_file_content_binary_by_size(fc)) | |
250 | return 0; | |
251 | ||
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); | |
256 | } else { | |
257 | error = git_blob_lookup( | |
74ded024 | 258 | (git_blob **)&fc->blob, fc->repo, &fc->file->oid); |
114f5a6c RB |
259 | } |
260 | ||
261 | if (!error) { | |
74ded024 | 262 | fc->flags |= GIT_DIFF_FLAG__FREE_BLOB; |
114f5a6c RB |
263 | fc->map.data = (void *)git_blob_rawcontent(fc->blob); |
264 | fc->map.len = (size_t)git_blob_rawsize(fc->blob); | |
265 | } | |
266 | ||
267 | return error; | |
268 | } | |
269 | ||
270 | static int diff_file_content_load_workdir_symlink( | |
271 | git_diff_file_content *fc, git_buf *path) | |
272 | { | |
273 | ssize_t alloc_len, read_len; | |
274 | ||
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 | |
277 | */ | |
74ded024 | 278 | alloc_len = (ssize_t)(fc->file->size * 2) + 1; |
114f5a6c RB |
279 | |
280 | fc->map.data = git__calloc(alloc_len, sizeof(char)); | |
281 | GITERR_CHECK_ALLOC(fc->map.data); | |
282 | ||
74ded024 | 283 | fc->flags |= GIT_DIFF_FLAG__FREE_DATA; |
114f5a6c RB |
284 | |
285 | read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len); | |
286 | if (read_len < 0) { | |
74ded024 | 287 | giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file->path); |
114f5a6c RB |
288 | return -1; |
289 | } | |
290 | ||
291 | fc->map.len = read_len; | |
292 | return 0; | |
293 | } | |
294 | ||
295 | static int diff_file_content_load_workdir_file( | |
296 | git_diff_file_content *fc, git_buf *path) | |
297 | { | |
298 | int error = 0; | |
85d54812 | 299 | git_filter_list *fl = NULL; |
114f5a6c | 300 | git_file fd = git_futils_open_ro(git_buf_cstr(path)); |
2a7d224f | 301 | git_buf raw = GIT_BUF_INIT; |
114f5a6c RB |
302 | |
303 | if (fd < 0) | |
304 | return fd; | |
305 | ||
74ded024 RB |
306 | if (!fc->file->size && |
307 | !(fc->file->size = git_futils_filesize(fd))) | |
114f5a6c RB |
308 | goto cleanup; |
309 | ||
310 | if (diff_file_content_binary_by_size(fc)) | |
311 | goto cleanup; | |
312 | ||
85d54812 | 313 | if ((error = git_filter_list_load( |
4b11f25a | 314 | &fl, fc->repo, NULL, fc->file->path, GIT_FILTER_TO_ODB)) < 0) |
114f5a6c | 315 | goto cleanup; |
114f5a6c | 316 | |
85d54812 RB |
317 | /* if there are no filters, try to mmap the file */ |
318 | if (fl == NULL) { | |
114f5a6c | 319 | if (!(error = git_futils_mmap_ro( |
85d54812 | 320 | &fc->map, fd, 0, (size_t)fc->file->size))) { |
74ded024 | 321 | fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA; |
114f5a6c | 322 | goto cleanup; |
114f5a6c RB |
323 | } |
324 | ||
85d54812 RB |
325 | /* if mmap failed, fall through to try readbuffer below */ |
326 | giterr_clear(); | |
114f5a6c RB |
327 | } |
328 | ||
2a7d224f | 329 | if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) { |
a9f51e43 | 330 | git_buf out = GIT_BUF_INIT; |
2a7d224f | 331 | |
a9f51e43 | 332 | error = git_filter_list_apply_to_data(&out, fl, &raw); |
2a7d224f | 333 | |
a9f51e43 | 334 | git_buf_free(&raw); |
85d54812 | 335 | |
2a7d224f RB |
336 | if (!error) { |
337 | fc->map.len = out.size; | |
338 | fc->map.data = out.ptr; | |
339 | fc->flags |= GIT_DIFF_FLAG__FREE_DATA; | |
340 | } | |
341 | } | |
85d54812 | 342 | |
114f5a6c | 343 | cleanup: |
85d54812 | 344 | git_filter_list_free(fl); |
114f5a6c RB |
345 | p_close(fd); |
346 | ||
347 | return error; | |
348 | } | |
349 | ||
350 | static int diff_file_content_load_workdir(git_diff_file_content *fc) | |
351 | { | |
352 | int error = 0; | |
353 | git_buf path = GIT_BUF_INIT; | |
354 | ||
74ded024 | 355 | if (fc->file->mode == GIT_FILEMODE_COMMIT) |
114f5a6c RB |
356 | return diff_file_content_commit_to_str(fc, true); |
357 | ||
74ded024 | 358 | if (fc->file->mode == GIT_FILEMODE_TREE) |
114f5a6c RB |
359 | return 0; |
360 | ||
361 | if (git_buf_joinpath( | |
74ded024 | 362 | &path, git_repository_workdir(fc->repo), fc->file->path) < 0) |
114f5a6c RB |
363 | return -1; |
364 | ||
74ded024 | 365 | if (S_ISLNK(fc->file->mode)) |
114f5a6c RB |
366 | error = diff_file_content_load_workdir_symlink(fc, &path); |
367 | else | |
368 | error = diff_file_content_load_workdir_file(fc, &path); | |
369 | ||
370 | /* once data is loaded, update OID if we didn't have it previously */ | |
74ded024 | 371 | if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) { |
114f5a6c | 372 | error = git_odb_hash( |
74ded024 RB |
373 | &fc->file->oid, fc->map.data, fc->map.len, GIT_OBJ_BLOB); |
374 | fc->file->flags |= GIT_DIFF_FLAG_VALID_OID; | |
114f5a6c RB |
375 | } |
376 | ||
377 | git_buf_free(&path); | |
378 | return error; | |
379 | } | |
380 | ||
360f42f4 | 381 | int git_diff_file_content__load(git_diff_file_content *fc) |
114f5a6c RB |
382 | { |
383 | int error = 0; | |
384 | ||
74ded024 | 385 | if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0) |
114f5a6c RB |
386 | return 0; |
387 | ||
74ded024 | 388 | if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0) |
114f5a6c RB |
389 | return 0; |
390 | ||
391 | if (fc->src == GIT_ITERATOR_TYPE_WORKDIR) | |
392 | error = diff_file_content_load_workdir(fc); | |
393 | else | |
394 | error = diff_file_content_load_blob(fc); | |
395 | if (error) | |
396 | return error; | |
397 | ||
74ded024 | 398 | fc->flags |= GIT_DIFF_FLAG__LOADED; |
114f5a6c RB |
399 | |
400 | diff_file_content_binary_by_content(fc); | |
401 | ||
402 | return 0; | |
403 | } | |
404 | ||
360f42f4 | 405 | void git_diff_file_content__unload(git_diff_file_content *fc) |
114f5a6c | 406 | { |
39a1a662 RB |
407 | if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0) |
408 | return; | |
409 | ||
74ded024 | 410 | if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) { |
114f5a6c RB |
411 | git__free(fc->map.data); |
412 | fc->map.data = ""; | |
413 | fc->map.len = 0; | |
74ded024 | 414 | fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA; |
114f5a6c | 415 | } |
74ded024 | 416 | else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) { |
114f5a6c RB |
417 | git_futils_mmap_free(&fc->map); |
418 | fc->map.data = ""; | |
419 | fc->map.len = 0; | |
74ded024 | 420 | fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA; |
114f5a6c RB |
421 | } |
422 | ||
74ded024 | 423 | if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) { |
114f5a6c RB |
424 | git_blob_free((git_blob *)fc->blob); |
425 | fc->blob = NULL; | |
74ded024 | 426 | fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB; |
114f5a6c RB |
427 | } |
428 | ||
74ded024 | 429 | fc->flags &= ~GIT_DIFF_FLAG__LOADED; |
114f5a6c RB |
430 | } |
431 | ||
360f42f4 | 432 | void git_diff_file_content__clear(git_diff_file_content *fc) |
114f5a6c | 433 | { |
360f42f4 | 434 | git_diff_file_content__unload(fc); |
114f5a6c RB |
435 | |
436 | /* for now, nothing else to do */ | |
437 | } |