7 #include "parse-options.h"
11 static const char * const prune_usage
[] = {
12 N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"),
17 static unsigned long expire
;
18 static int show_progress
= -1;
20 static int prune_tmp_file(const char *fullpath
)
23 if (lstat(fullpath
, &st
))
24 return error("Could not stat '%s'", fullpath
);
25 if (st
.st_mtime
> expire
)
27 if (show_only
|| verbose
)
28 printf("Removing stale temporary file %s\n", fullpath
);
30 unlink_or_warn(fullpath
);
34 static int prune_object(const char *fullpath
, const unsigned char *sha1
)
37 if (lstat(fullpath
, &st
))
38 return error("Could not stat '%s'", fullpath
);
39 if (st
.st_mtime
> expire
)
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");
47 unlink_or_warn(fullpath
);
51 static int prune_dir(int i
, struct strbuf
*path
)
53 size_t baselen
= path
->len
;
54 DIR *dir
= opendir(path
->buf
);
60 while ((de
= readdir(dir
)) != NULL
) {
62 unsigned char sha1
[20];
64 if (is_dot_or_dotdot(de
->d_name
))
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)
73 * Do we know about this object?
74 * It must have been reachable
76 if (lookup_object(sha1
))
79 strbuf_addf(path
, "/%s", de
->d_name
);
80 prune_object(path
->buf
, sha1
);
81 strbuf_setlen(path
, baselen
);
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
);
90 fprintf(stderr
, "bad sha1 file: %s/%s\n", path
->buf
, de
->d_name
);
98 static void prune_object_dir(const char *path
)
100 struct strbuf buf
= STRBUF_INIT
;
104 strbuf_addstr(&buf
, path
);
105 strbuf_addch(&buf
, '/');
108 for (i
= 0; i
< 256; i
++) {
109 strbuf_addf(&buf
, "%02x", i
);
111 strbuf_setlen(&buf
, baselen
);
115 static int prune_repo_dir(const char *id
, struct strbuf
*reason
)
121 if (!is_directory(git_path("repos/%s", id
))) {
122 strbuf_addf(reason
, _("Removing repos/%s: not a valid directory"), id
);
125 if (file_exists(git_path("repos/%s/locked", id
)))
127 if (stat(git_path("repos/%s/gitdir", id
), &st
)) {
128 strbuf_addf(reason
, _("Removing repos/%s: gitdir file does not exist"), id
);
131 fd
= open(git_path("repos/%s/gitdir", id
), O_RDONLY
);
133 strbuf_addf(reason
, _("Removing repos/%s: unable to read gitdir file (%s)"),
134 id
, strerror(errno
));
138 path
= xmalloc(len
+ 1);
139 read_in_full(fd
, path
, len
);
141 while (len
&& (path
[len
- 1] == '\n' || path
[len
- 1] == '\r'))
144 strbuf_addf(reason
, _("Removing repos/%s: invalid gitdir file"), id
);
149 if (!file_exists(path
)) {
153 * the repo is moved manually and has not been
156 if (!stat(git_path("repos/%s/link", id
), &st_link
) &&
157 st_link
.st_nlink
> 1)
159 strbuf_addf(reason
, _("Removing repos/%s: gitdir file points to non-existent location"), id
);
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"));
175 while ((d
= readdir(dir
)) != NULL
) {
176 if (!strcmp(d
->d_name
, ".") || !strcmp(d
->d_name
, ".."))
178 strbuf_reset(&reason
);
179 if (!prune_repo_dir(d
->d_name
, &reason
))
181 if (show_only
|| verbose
)
182 printf("%s\n", reason
.buf
);
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
);
191 error(_("failed to remove: %s"), strerror(errno
));
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
)
213 fprintf(stderr
, "Unable to open directory %s\n", path
);
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
));
222 int cmd_prune(int argc
, const char **argv
, const char *prefix
)
224 struct rev_info revs
;
225 struct progress
*progress
= NULL
;
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>")),
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);
247 die(_("--repos does not take extra arguments"));
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
, "");
261 die("unrecognized argument: %s", name
);
264 if (show_progress
== -1)
265 show_progress
= isatty(2);
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
);
279 if (is_repository_shallow())
280 prune_shallow(show_only
);