Git fork
1#define DISABLE_SIGN_COMPARE_WARNINGS
2
3#include "builtin.h"
4#include "commit.h"
5#include "diff.h"
6#include "dir.h"
7#include "environment.h"
8#include "gettext.h"
9#include "hex.h"
10#include "revision.h"
11#include "reachable.h"
12#include "parse-options.h"
13#include "path.h"
14#include "progress.h"
15#include "prune-packed.h"
16#include "replace-object.h"
17#include "object-file.h"
18#include "object-name.h"
19#include "odb.h"
20#include "shallow.h"
21
22static const char * const prune_usage[] = {
23 N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"),
24 NULL
25};
26static int show_only;
27static int verbose;
28static timestamp_t expire;
29static int show_progress = -1;
30
31static int prune_tmp_file(const char *fullpath)
32{
33 struct stat st;
34 if (lstat(fullpath, &st))
35 return error("Could not stat '%s'", fullpath);
36 if (st.st_mtime > expire)
37 return 0;
38 if (S_ISDIR(st.st_mode)) {
39 if (show_only || verbose)
40 printf("Removing stale temporary directory %s\n", fullpath);
41 if (!show_only) {
42 struct strbuf remove_dir_buf = STRBUF_INIT;
43
44 strbuf_addstr(&remove_dir_buf, fullpath);
45 remove_dir_recursively(&remove_dir_buf, 0);
46 strbuf_release(&remove_dir_buf);
47 }
48 } else {
49 if (show_only || verbose)
50 printf("Removing stale temporary file %s\n", fullpath);
51 if (!show_only)
52 unlink_or_warn(fullpath);
53 }
54 return 0;
55}
56
57static void perform_reachability_traversal(struct rev_info *revs)
58{
59 static int initialized;
60 struct progress *progress = NULL;
61
62 if (initialized)
63 return;
64
65 if (show_progress)
66 progress = start_delayed_progress(revs->repo,
67 _("Checking connectivity"), 0);
68 mark_reachable_objects(revs, 1, expire, progress);
69 stop_progress(&progress);
70 initialized = 1;
71}
72
73static int is_object_reachable(const struct object_id *oid,
74 struct rev_info *revs)
75{
76 struct object *obj;
77
78 perform_reachability_traversal(revs);
79
80 obj = lookup_object(revs->repo, oid);
81 return obj && (obj->flags & SEEN);
82}
83
84static int prune_object(const struct object_id *oid, const char *fullpath,
85 void *data)
86{
87 struct rev_info *revs = data;
88 struct stat st;
89
90 if (is_object_reachable(oid, revs))
91 return 0;
92
93 if (lstat(fullpath, &st)) {
94 /* report errors, but do not stop pruning */
95 error("Could not stat '%s'", fullpath);
96 return 0;
97 }
98 if (st.st_mtime > expire)
99 return 0;
100 if (show_only || verbose) {
101 enum object_type type =
102 odb_read_object_info(revs->repo->objects, oid, NULL);
103 printf("%s %s\n", oid_to_hex(oid),
104 (type > 0) ? type_name(type) : "unknown");
105 }
106 if (!show_only)
107 unlink_or_warn(fullpath);
108 return 0;
109}
110
111static int prune_cruft(const char *basename, const char *path,
112 void *data UNUSED)
113{
114 if (starts_with(basename, "tmp_obj_"))
115 prune_tmp_file(path);
116 else
117 fprintf(stderr, "bad sha1 file: %s\n", path);
118 return 0;
119}
120
121static int prune_subdir(unsigned int nr UNUSED, const char *path,
122 void *data UNUSED)
123{
124 if (!show_only)
125 rmdir(path);
126 return 0;
127}
128
129/*
130 * Write errors (particularly out of space) can result in
131 * failed temporary packs (and more rarely indexes and other
132 * files beginning with "tmp_") accumulating in the object
133 * and the pack directories.
134 */
135static void remove_temporary_files(const char *path)
136{
137 DIR *dir;
138 struct dirent *de;
139
140 dir = opendir(path);
141 if (!dir) {
142 if (errno != ENOENT)
143 fprintf(stderr, "Unable to open directory %s: %s\n",
144 path, strerror(errno));
145 return;
146 }
147 while ((de = readdir(dir)) != NULL)
148 if (starts_with(de->d_name, "tmp_"))
149 prune_tmp_file(mkpath("%s/%s", path, de->d_name));
150 closedir(dir);
151}
152
153int cmd_prune(int argc,
154 const char **argv,
155 const char *prefix,
156 struct repository *repo)
157{
158 struct rev_info revs;
159 int exclude_promisor_objects = 0;
160 const struct option options[] = {
161 OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
162 OPT__VERBOSE(&verbose, N_("report pruned objects")),
163 OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
164 OPT_EXPIRY_DATE(0, "expire", &expire,
165 N_("expire objects older than <time>")),
166 OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects,
167 N_("limit traversal to objects outside promisor packfiles")),
168 OPT_END()
169 };
170 char *s;
171
172 expire = TIME_MAX;
173 save_commit_buffer = 0;
174 disable_replace_refs();
175
176 argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
177
178 repo_init_revisions(repo, &revs, prefix);
179 if (repo->repository_format_precious_objects)
180 die(_("cannot prune in a precious-objects repo"));
181
182 while (argc--) {
183 struct object_id oid;
184 const char *name = *argv++;
185
186 if (!repo_get_oid(repo, name, &oid)) {
187 struct object *object = parse_object_or_die(repo, &oid, name);
188 add_pending_object(&revs, object, "");
189 }
190 else
191 die("unrecognized argument: %s", name);
192 }
193
194 if (show_progress == -1)
195 show_progress = isatty(2);
196 if (exclude_promisor_objects) {
197 fetch_if_missing = 0;
198 revs.exclude_promisor_objects = 1;
199 }
200
201 for_each_loose_file_in_source(repo->objects->sources,
202 prune_object, prune_cruft, prune_subdir, &revs);
203
204 prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
205 remove_temporary_files(repo_get_object_directory(repo));
206 s = mkpathdup("%s/pack", repo_get_object_directory(repo));
207 remove_temporary_files(s);
208 free(s);
209
210 if (is_repository_shallow(repo)) {
211 perform_reachability_traversal(&revs);
212 prune_shallow(show_only ? PRUNE_SHOW_ONLY : 0);
213 }
214
215 release_revisions(&revs);
216 return 0;
217}