Merge branch 'jk/fix-profile-feedback-build' into next
[git/jrn.git] / builtin / prune.c
blobe72c3917ef4c6779b9ef9ca89c04a0e1a0862e34
1 #include "cache.h"
2 #include "commit.h"
3 #include "diff.h"
4 #include "revision.h"
5 #include "builtin.h"
6 #include "reachable.h"
7 #include "parse-options.h"
8 #include "progress.h"
9 #include "dir.h"
11 static const char * const prune_usage[] = {
12 N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
13 NULL
15 static int show_only;
16 static int verbose;
17 static unsigned long expire;
18 static int show_progress = -1;
20 static int prune_tmp_file(const char *fullpath)
22 struct stat st;
23 if (lstat(fullpath, &st))
24 return error("Could not stat '%s'", fullpath);
25 if (st.st_mtime > expire)
26 return 0;
27 if (show_only || verbose)
28 printf("Removing stale temporary file %s\n", fullpath);
29 if (!show_only)
30 unlink_or_warn(fullpath);
31 return 0;
34 static int prune_object(const char *fullpath, const unsigned char *sha1)
36 struct stat st;
37 if (lstat(fullpath, &st))
38 return error("Could not stat '%s'", fullpath);
39 if (st.st_mtime > expire)
40 return 0;
41 if (show_only || verbose) {
42 enum object_type type = sha1_object_info(sha1, NULL);
43 printf("%s %s\n", sha1_to_hex(sha1),
44 (type > 0) ? typename(type) : "unknown");
46 if (!show_only)
47 unlink_or_warn(fullpath);
48 return 0;
51 static int prune_dir(int i, struct strbuf *path)
53 size_t baselen = path->len;
54 DIR *dir = opendir(path->buf);
55 struct dirent *de;
57 if (!dir)
58 return 0;
60 while ((de = readdir(dir)) != NULL) {
61 char name[100];
62 unsigned char sha1[20];
64 if (is_dot_or_dotdot(de->d_name))
65 continue;
66 if (strlen(de->d_name) == 38) {
67 sprintf(name, "%02x", i);
68 memcpy(name+2, de->d_name, 39);
69 if (get_sha1_hex(name, sha1) < 0)
70 break;
73 * Do we know about this object?
74 * It must have been reachable
76 if (lookup_object(sha1))
77 continue;
79 strbuf_addf(path, "/%s", de->d_name);
80 prune_object(path->buf, sha1);
81 strbuf_setlen(path, baselen);
82 continue;
84 if (starts_with(de->d_name, "tmp_obj_")) {
85 strbuf_addf(path, "/%s", de->d_name);
86 prune_tmp_file(path->buf);
87 strbuf_setlen(path, baselen);
88 continue;
90 fprintf(stderr, "bad sha1 file: %s/%s\n", path->buf, de->d_name);
92 closedir(dir);
93 if (!show_only)
94 rmdir(path->buf);
95 return 0;
98 static void prune_object_dir(const char *path)
100 struct strbuf buf = STRBUF_INIT;
101 size_t baselen;
102 int i;
104 strbuf_addstr(&buf, path);
105 strbuf_addch(&buf, '/');
106 baselen = buf.len;
108 for (i = 0; i < 256; i++) {
109 strbuf_addf(&buf, "%02x", i);
110 prune_dir(i, &buf);
111 strbuf_setlen(&buf, baselen);
115 static int prune_repo_dir(const char *id, struct strbuf *reason)
117 struct stat st;
118 char *path;
119 int fd, len;
121 if (!is_directory(git_path("repos/%s", id))) {
122 strbuf_addf(reason, _("Removing repos/%s: not a valid directory"), id);
123 return 1;
125 if (file_exists(git_path("repos/%s/locked", id)))
126 return 0;
127 if (stat(git_path("repos/%s/gitdir", id), &st)) {
128 strbuf_addf(reason, _("Removing repos/%s: gitdir file does not exist"), id);
129 return 1;
131 fd = open(git_path("repos/%s/gitdir", id), O_RDONLY);
132 if (fd < 0) {
133 strbuf_addf(reason, _("Removing repos/%s: unable to read gitdir file (%s)"),
134 id, strerror(errno));
135 return 1;
137 len = st.st_size;
138 path = xmalloc(len + 1);
139 read_in_full(fd, path, len);
140 close(fd);
141 while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
142 len--;
143 if (!len) {
144 strbuf_addf(reason, _("Removing repos/%s: invalid gitdir file"), id);
145 free(path);
146 return 1;
148 path[len] = '\0';
149 if (!file_exists(path)) {
150 struct stat st_link;
151 free(path);
153 * the repo is moved manually and has not been
154 * accessed since?
156 if (!stat(git_path("repos/%s/link", id), &st_link) &&
157 st_link.st_nlink > 1)
158 return 0;
159 strbuf_addf(reason, _("Removing repos/%s: gitdir file points to non-existent location"), id);
160 return 1;
162 free(path);
163 return st.st_mtime <= expire;
166 static void prune_repos_dir(void)
168 struct strbuf reason = STRBUF_INIT;
169 struct strbuf path = STRBUF_INIT;
170 DIR *dir = opendir(git_path("repos"));
171 struct dirent *d;
172 int ret;
173 if (!dir)
174 return;
175 while ((d = readdir(dir)) != NULL) {
176 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
177 continue;
178 strbuf_reset(&reason);
179 if (!prune_repo_dir(d->d_name, &reason))
180 continue;
181 if (show_only || verbose)
182 printf("%s\n", reason.buf);
183 if (show_only)
184 continue;
185 strbuf_reset(&path);
186 strbuf_addstr(&path, git_path("repos/%s", d->d_name));
187 ret = remove_dir_recursively(&path, 0);
188 if (ret < 0 && errno == ENOTDIR)
189 ret = unlink(path.buf);
190 if (ret)
191 error(_("failed to remove: %s"), strerror(errno));
193 closedir(dir);
194 if (!show_only)
195 rmdir(git_path("repos"));
196 strbuf_release(&reason);
197 strbuf_release(&path);
201 * Write errors (particularly out of space) can result in
202 * failed temporary packs (and more rarely indexes and other
203 * files beginning with "tmp_") accumulating in the object
204 * and the pack directories.
206 static void remove_temporary_files(const char *path)
208 DIR *dir;
209 struct dirent *de;
211 dir = opendir(path);
212 if (!dir) {
213 fprintf(stderr, "Unable to open directory %s\n", path);
214 return;
216 while ((de = readdir(dir)) != NULL)
217 if (starts_with(de->d_name, "tmp_"))
218 prune_tmp_file(mkpath("%s/%s", path, de->d_name));
219 closedir(dir);
222 int cmd_prune(int argc, const char **argv, const char *prefix)
224 struct rev_info revs;
225 struct progress *progress = NULL;
226 int prune_repos = 0;
227 const struct option options[] = {
228 OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
229 OPT__VERBOSE(&verbose, N_("report pruned objects")),
230 OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
231 OPT_BOOL(0, "repos", &prune_repos, N_("prune .git/repos/")),
232 OPT_EXPIRY_DATE(0, "expire", &expire,
233 N_("expire objects older than <time>")),
234 OPT_END()
236 char *s;
238 expire = ULONG_MAX;
239 save_commit_buffer = 0;
240 check_replace_refs = 0;
241 init_revisions(&revs, prefix);
243 argc = parse_options(argc, argv, prefix, options, prune_usage, 0);
245 if (prune_repos) {
246 if (argc)
247 die(_("--repos does not take extra arguments"));
248 prune_repos_dir();
249 return 0;
252 while (argc--) {
253 unsigned char sha1[20];
254 const char *name = *argv++;
256 if (!get_sha1(name, sha1)) {
257 struct object *object = parse_object_or_die(sha1, name);
258 add_pending_object(&revs, object, "");
260 else
261 die("unrecognized argument: %s", name);
264 if (show_progress == -1)
265 show_progress = isatty(2);
266 if (show_progress)
267 progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2);
269 mark_reachable_objects(&revs, 1, progress);
270 stop_progress(&progress);
271 prune_object_dir(get_object_directory());
273 prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0);
274 remove_temporary_files(get_object_directory());
275 s = mkpathdup("%s/pack", get_object_directory());
276 remove_temporary_files(s);
277 free(s);
279 if (is_repository_shallow())
280 prune_shallow(show_only);
282 return 0;