Line | Count | Source (jump to first uncovered line) |
1 | | #define USE_THE_REPOSITORY_VARIABLE |
2 | | |
3 | | #include "git-compat-util.h" |
4 | | #include "gettext.h" |
5 | | #include "hex.h" |
6 | | #include "refs.h" |
7 | | #include "commit.h" |
8 | | #include "blob.h" |
9 | | #include "diff.h" |
10 | | #include "revision.h" |
11 | | #include "reachable.h" |
12 | | #include "cache-tree.h" |
13 | | #include "progress.h" |
14 | | #include "list-objects.h" |
15 | | #include "packfile.h" |
16 | | #include "worktree.h" |
17 | | #include "object-store-ll.h" |
18 | | #include "pack-bitmap.h" |
19 | | #include "pack-mtimes.h" |
20 | | #include "config.h" |
21 | | #include "run-command.h" |
22 | | #include "sequencer.h" |
23 | | |
24 | | struct connectivity_progress { |
25 | | struct progress *progress; |
26 | | unsigned long count; |
27 | | }; |
28 | | |
29 | | static void update_progress(struct connectivity_progress *cp) |
30 | 0 | { |
31 | 0 | cp->count++; |
32 | 0 | if ((cp->count & 1023) == 0) |
33 | 0 | display_progress(cp->progress, cp->count); |
34 | 0 | } |
35 | | |
36 | | static void add_one_file(const char *path, struct rev_info *revs) |
37 | 0 | { |
38 | 0 | struct strbuf buf = STRBUF_INIT; |
39 | 0 | struct object_id oid; |
40 | 0 | struct object *object; |
41 | |
|
42 | 0 | if (!read_oneliner(&buf, path, READ_ONELINER_SKIP_IF_EMPTY)) { |
43 | 0 | strbuf_release(&buf); |
44 | 0 | return; |
45 | 0 | } |
46 | 0 | strbuf_trim(&buf); |
47 | 0 | if (!get_oid_hex(buf.buf, &oid)) { |
48 | 0 | object = parse_object_or_die(&oid, buf.buf); |
49 | 0 | add_pending_object(revs, object, ""); |
50 | 0 | } |
51 | 0 | strbuf_release(&buf); |
52 | 0 | } |
53 | | |
54 | | /* Mark objects recorded in rebase state files as reachable. */ |
55 | | static void add_rebase_files(struct rev_info *revs) |
56 | 0 | { |
57 | 0 | struct strbuf buf = STRBUF_INIT; |
58 | 0 | size_t len; |
59 | 0 | const char *path[] = { |
60 | 0 | "rebase-apply/autostash", |
61 | 0 | "rebase-apply/orig-head", |
62 | 0 | "rebase-merge/autostash", |
63 | 0 | "rebase-merge/orig-head", |
64 | 0 | }; |
65 | 0 | struct worktree **worktrees = get_worktrees(); |
66 | |
|
67 | 0 | for (struct worktree **wt = worktrees; *wt; wt++) { |
68 | 0 | strbuf_reset(&buf); |
69 | 0 | strbuf_addstr(&buf, get_worktree_git_dir(*wt)); |
70 | 0 | strbuf_complete(&buf, '/'); |
71 | 0 | len = buf.len; |
72 | 0 | for (size_t i = 0; i < ARRAY_SIZE(path); i++) { |
73 | 0 | strbuf_setlen(&buf, len); |
74 | 0 | strbuf_addstr(&buf, path[i]); |
75 | 0 | add_one_file(buf.buf, revs); |
76 | 0 | } |
77 | 0 | } |
78 | 0 | strbuf_release(&buf); |
79 | 0 | free_worktrees(worktrees); |
80 | 0 | } |
81 | | |
82 | | static int add_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid, |
83 | | int flag, void *cb_data) |
84 | 0 | { |
85 | 0 | struct rev_info *revs = (struct rev_info *)cb_data; |
86 | 0 | struct object *object; |
87 | |
|
88 | 0 | if ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) { |
89 | 0 | warning("symbolic ref is dangling: %s", path); |
90 | 0 | return 0; |
91 | 0 | } |
92 | | |
93 | 0 | object = parse_object_or_die(oid, path); |
94 | 0 | add_pending_object(revs, object, ""); |
95 | |
|
96 | 0 | return 0; |
97 | 0 | } |
98 | | |
99 | | /* |
100 | | * The traversal will have already marked us as SEEN, so we |
101 | | * only need to handle any progress reporting here. |
102 | | */ |
103 | | static void mark_object(struct object *obj UNUSED, |
104 | | const char *name UNUSED, |
105 | | void *data) |
106 | 0 | { |
107 | 0 | update_progress(data); |
108 | 0 | } |
109 | | |
110 | | static void mark_commit(struct commit *c, void *data) |
111 | 0 | { |
112 | 0 | mark_object(&c->object, NULL, data); |
113 | 0 | } |
114 | | |
115 | | struct recent_data { |
116 | | struct rev_info *revs; |
117 | | timestamp_t timestamp; |
118 | | report_recent_object_fn *cb; |
119 | | int ignore_in_core_kept_packs; |
120 | | |
121 | | struct oidset extra_recent_oids; |
122 | | int extra_recent_oids_loaded; |
123 | | }; |
124 | | |
125 | | static int run_one_gc_recent_objects_hook(struct oidset *set, |
126 | | const char *args) |
127 | 0 | { |
128 | 0 | struct child_process cmd = CHILD_PROCESS_INIT; |
129 | 0 | struct strbuf buf = STRBUF_INIT; |
130 | 0 | FILE *out; |
131 | 0 | int ret = 0; |
132 | |
|
133 | 0 | cmd.use_shell = 1; |
134 | 0 | cmd.out = -1; |
135 | |
|
136 | 0 | strvec_push(&cmd.args, args); |
137 | |
|
138 | 0 | if (start_command(&cmd)) |
139 | 0 | return -1; |
140 | | |
141 | 0 | out = xfdopen(cmd.out, "r"); |
142 | 0 | while (strbuf_getline(&buf, out) != EOF) { |
143 | 0 | struct object_id oid; |
144 | 0 | const char *rest; |
145 | |
|
146 | 0 | if (parse_oid_hex(buf.buf, &oid, &rest) || *rest) { |
147 | 0 | ret = error(_("invalid extra cruft tip: '%s'"), buf.buf); |
148 | 0 | break; |
149 | 0 | } |
150 | | |
151 | 0 | oidset_insert(set, &oid); |
152 | 0 | } |
153 | |
|
154 | 0 | fclose(out); |
155 | 0 | ret |= finish_command(&cmd); |
156 | |
|
157 | 0 | strbuf_release(&buf); |
158 | 0 | return ret; |
159 | 0 | } |
160 | | |
161 | | static void load_gc_recent_objects(struct recent_data *data) |
162 | 0 | { |
163 | 0 | const struct string_list *programs; |
164 | 0 | int ret = 0; |
165 | 0 | size_t i; |
166 | |
|
167 | 0 | data->extra_recent_oids_loaded = 1; |
168 | |
|
169 | 0 | if (git_config_get_string_multi("gc.recentobjectshook", &programs)) |
170 | 0 | return; |
171 | | |
172 | 0 | for (i = 0; i < programs->nr; i++) { |
173 | 0 | ret = run_one_gc_recent_objects_hook(&data->extra_recent_oids, |
174 | 0 | programs->items[i].string); |
175 | 0 | if (ret) |
176 | 0 | die(_("unable to enumerate additional recent objects")); |
177 | 0 | } |
178 | 0 | } |
179 | | |
180 | | static int obj_is_recent(const struct object_id *oid, timestamp_t mtime, |
181 | | struct recent_data *data) |
182 | 0 | { |
183 | 0 | if (mtime > data->timestamp) |
184 | 0 | return 1; |
185 | | |
186 | 0 | if (!data->extra_recent_oids_loaded) |
187 | 0 | load_gc_recent_objects(data); |
188 | 0 | return oidset_contains(&data->extra_recent_oids, oid); |
189 | 0 | } |
190 | | |
191 | | static void add_recent_object(const struct object_id *oid, |
192 | | struct packed_git *pack, |
193 | | off_t offset, |
194 | | timestamp_t mtime, |
195 | | struct recent_data *data) |
196 | 0 | { |
197 | 0 | struct object *obj; |
198 | 0 | enum object_type type; |
199 | |
|
200 | 0 | if (!obj_is_recent(oid, mtime, data)) |
201 | 0 | return; |
202 | | |
203 | | /* |
204 | | * We do not want to call parse_object here, because |
205 | | * inflating blobs and trees could be very expensive. |
206 | | * However, we do need to know the correct type for |
207 | | * later processing, and the revision machinery expects |
208 | | * commits and tags to have been parsed. |
209 | | */ |
210 | 0 | type = oid_object_info(the_repository, oid, NULL); |
211 | 0 | if (type < 0) |
212 | 0 | die("unable to get object info for %s", oid_to_hex(oid)); |
213 | | |
214 | 0 | switch (type) { |
215 | 0 | case OBJ_TAG: |
216 | 0 | case OBJ_COMMIT: |
217 | 0 | obj = parse_object_or_die(oid, NULL); |
218 | 0 | break; |
219 | 0 | case OBJ_TREE: |
220 | 0 | obj = (struct object *)lookup_tree(the_repository, oid); |
221 | 0 | break; |
222 | 0 | case OBJ_BLOB: |
223 | 0 | obj = (struct object *)lookup_blob(the_repository, oid); |
224 | 0 | break; |
225 | 0 | default: |
226 | 0 | die("unknown object type for %s: %s", |
227 | 0 | oid_to_hex(oid), type_name(type)); |
228 | 0 | } |
229 | | |
230 | 0 | if (!obj) |
231 | 0 | die("unable to lookup %s", oid_to_hex(oid)); |
232 | | |
233 | 0 | add_pending_object(data->revs, obj, ""); |
234 | 0 | if (data->cb) |
235 | 0 | data->cb(obj, pack, offset, mtime); |
236 | 0 | } |
237 | | |
238 | | static int want_recent_object(struct recent_data *data, |
239 | | const struct object_id *oid) |
240 | 0 | { |
241 | 0 | if (data->ignore_in_core_kept_packs && |
242 | 0 | has_object_kept_pack(oid, IN_CORE_KEEP_PACKS)) |
243 | 0 | return 0; |
244 | 0 | return 1; |
245 | 0 | } |
246 | | |
247 | | static int add_recent_loose(const struct object_id *oid, |
248 | | const char *path, void *data) |
249 | 0 | { |
250 | 0 | struct stat st; |
251 | 0 | struct object *obj; |
252 | |
|
253 | 0 | if (!want_recent_object(data, oid)) |
254 | 0 | return 0; |
255 | | |
256 | 0 | obj = lookup_object(the_repository, oid); |
257 | |
|
258 | 0 | if (obj && obj->flags & SEEN) |
259 | 0 | return 0; |
260 | | |
261 | 0 | if (stat(path, &st) < 0) { |
262 | | /* |
263 | | * It's OK if an object went away during our iteration; this |
264 | | * could be due to a simultaneous repack. But anything else |
265 | | * we should abort, since we might then fail to mark objects |
266 | | * which should not be pruned. |
267 | | */ |
268 | 0 | if (errno == ENOENT) |
269 | 0 | return 0; |
270 | 0 | return error_errno("unable to stat %s", oid_to_hex(oid)); |
271 | 0 | } |
272 | | |
273 | 0 | add_recent_object(oid, NULL, 0, st.st_mtime, data); |
274 | 0 | return 0; |
275 | 0 | } |
276 | | |
277 | | static int add_recent_packed(const struct object_id *oid, |
278 | | struct packed_git *p, |
279 | | uint32_t pos, |
280 | | void *data) |
281 | 0 | { |
282 | 0 | struct object *obj; |
283 | 0 | timestamp_t mtime = p->mtime; |
284 | |
|
285 | 0 | if (!want_recent_object(data, oid)) |
286 | 0 | return 0; |
287 | | |
288 | 0 | obj = lookup_object(the_repository, oid); |
289 | |
|
290 | 0 | if (obj && obj->flags & SEEN) |
291 | 0 | return 0; |
292 | 0 | if (p->is_cruft) { |
293 | 0 | if (load_pack_mtimes(p) < 0) |
294 | 0 | die(_("could not load cruft pack .mtimes")); |
295 | 0 | mtime = nth_packed_mtime(p, pos); |
296 | 0 | } |
297 | 0 | add_recent_object(oid, p, nth_packed_object_offset(p, pos), mtime, data); |
298 | 0 | return 0; |
299 | 0 | } |
300 | | |
301 | | int add_unseen_recent_objects_to_traversal(struct rev_info *revs, |
302 | | timestamp_t timestamp, |
303 | | report_recent_object_fn *cb, |
304 | | int ignore_in_core_kept_packs) |
305 | 0 | { |
306 | 0 | struct recent_data data; |
307 | 0 | enum for_each_object_flags flags; |
308 | 0 | int r; |
309 | |
|
310 | 0 | data.revs = revs; |
311 | 0 | data.timestamp = timestamp; |
312 | 0 | data.cb = cb; |
313 | 0 | data.ignore_in_core_kept_packs = ignore_in_core_kept_packs; |
314 | |
|
315 | 0 | oidset_init(&data.extra_recent_oids, 0); |
316 | 0 | data.extra_recent_oids_loaded = 0; |
317 | |
|
318 | 0 | r = for_each_loose_object(add_recent_loose, &data, |
319 | 0 | FOR_EACH_OBJECT_LOCAL_ONLY); |
320 | 0 | if (r) |
321 | 0 | goto done; |
322 | | |
323 | 0 | flags = FOR_EACH_OBJECT_LOCAL_ONLY | FOR_EACH_OBJECT_PACK_ORDER; |
324 | 0 | if (ignore_in_core_kept_packs) |
325 | 0 | flags |= FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS; |
326 | |
|
327 | 0 | r = for_each_packed_object(add_recent_packed, &data, flags); |
328 | |
|
329 | 0 | done: |
330 | 0 | oidset_clear(&data.extra_recent_oids); |
331 | |
|
332 | 0 | return r; |
333 | 0 | } |
334 | | |
335 | | static int mark_object_seen(const struct object_id *oid, |
336 | | enum object_type type, |
337 | | int exclude UNUSED, |
338 | | uint32_t name_hash UNUSED, |
339 | | struct packed_git *found_pack UNUSED, |
340 | | off_t found_offset UNUSED) |
341 | 0 | { |
342 | 0 | struct object *obj = lookup_object_by_type(the_repository, oid, type); |
343 | 0 | if (!obj) |
344 | 0 | die("unable to create object '%s'", oid_to_hex(oid)); |
345 | | |
346 | 0 | obj->flags |= SEEN; |
347 | 0 | return 0; |
348 | 0 | } |
349 | | |
350 | | void mark_reachable_objects(struct rev_info *revs, int mark_reflog, |
351 | | timestamp_t mark_recent, struct progress *progress) |
352 | 0 | { |
353 | 0 | struct connectivity_progress cp; |
354 | 0 | struct bitmap_index *bitmap_git; |
355 | | |
356 | | /* |
357 | | * Set up revision parsing, and mark us as being interested |
358 | | * in all object types, not just commits. |
359 | | */ |
360 | 0 | revs->tag_objects = 1; |
361 | 0 | revs->blob_objects = 1; |
362 | 0 | revs->tree_objects = 1; |
363 | | |
364 | | /* Add all refs from the index file */ |
365 | 0 | add_index_objects_to_pending(revs, 0); |
366 | | |
367 | | /* Add all external refs */ |
368 | 0 | refs_for_each_ref(get_main_ref_store(the_repository), add_one_ref, |
369 | 0 | revs); |
370 | | |
371 | | /* detached HEAD is not included in the list above */ |
372 | 0 | refs_head_ref(get_main_ref_store(the_repository), add_one_ref, revs); |
373 | 0 | other_head_refs(add_one_ref, revs); |
374 | | |
375 | | /* rebase autostash and orig-head */ |
376 | 0 | add_rebase_files(revs); |
377 | | |
378 | | /* Add all reflog info */ |
379 | 0 | if (mark_reflog) |
380 | 0 | add_reflogs_to_pending(revs, 0); |
381 | |
|
382 | 0 | cp.progress = progress; |
383 | 0 | cp.count = 0; |
384 | |
|
385 | 0 | bitmap_git = prepare_bitmap_walk(revs, 0); |
386 | 0 | if (bitmap_git) { |
387 | 0 | traverse_bitmap_commit_list(bitmap_git, revs, mark_object_seen); |
388 | 0 | free_bitmap_index(bitmap_git); |
389 | 0 | } else { |
390 | 0 | if (prepare_revision_walk(revs)) |
391 | 0 | die("revision walk setup failed"); |
392 | 0 | traverse_commit_list(revs, mark_commit, mark_object, &cp); |
393 | 0 | } |
394 | | |
395 | 0 | if (mark_recent) { |
396 | 0 | revs->ignore_missing_links = 1; |
397 | 0 | if (add_unseen_recent_objects_to_traversal(revs, mark_recent, |
398 | 0 | NULL, 0)) |
399 | 0 | die("unable to mark recent objects"); |
400 | 0 | if (prepare_revision_walk(revs)) |
401 | 0 | die("revision walk setup failed"); |
402 | 0 | traverse_commit_list(revs, mark_commit, mark_object, &cp); |
403 | 0 | } |
404 | | |
405 | 0 | display_progress(cp.progress, cp.count); |
406 | 0 | } |