sequencer: use struct strvec to store merge strategy options
[git/gitster.git] / diagnose.c
blob998f517c48869836c4c634ef9f0c129cbe2d274a
1 #include "cache.h"
2 #include "diagnose.h"
3 #include "compat/disk.h"
4 #include "archive.h"
5 #include "dir.h"
6 #include "help.h"
7 #include "hex.h"
8 #include "strvec.h"
9 #include "object-store.h"
10 #include "packfile.h"
11 #include "parse-options.h"
13 struct archive_dir {
14 const char *path;
15 int recursive;
18 struct diagnose_option {
19 enum diagnose_mode mode;
20 const char *option_name;
23 static struct diagnose_option diagnose_options[] = {
24 { DIAGNOSE_STATS, "stats" },
25 { DIAGNOSE_ALL, "all" },
28 int option_parse_diagnose(const struct option *opt, const char *arg, int unset)
30 int i;
31 enum diagnose_mode *diagnose = opt->value;
33 if (!arg) {
34 *diagnose = unset ? DIAGNOSE_NONE : DIAGNOSE_STATS;
35 return 0;
38 for (i = 0; i < ARRAY_SIZE(diagnose_options); i++) {
39 if (!strcmp(arg, diagnose_options[i].option_name)) {
40 *diagnose = diagnose_options[i].mode;
41 return 0;
45 return error(_("invalid --%s value '%s'"), opt->long_name, arg);
48 static void dir_file_stats_objects(const char *full_path,
49 size_t full_path_len UNUSED,
50 const char *file_name, void *data)
52 struct strbuf *buf = data;
53 struct stat st;
55 if (!stat(full_path, &st))
56 strbuf_addf(buf, "%-70s %16" PRIuMAX "\n", file_name,
57 (uintmax_t)st.st_size);
60 static int dir_file_stats(struct object_directory *object_dir, void *data)
62 struct strbuf *buf = data;
64 strbuf_addf(buf, "Contents of %s:\n", object_dir->path);
66 for_each_file_in_pack_dir(object_dir->path, dir_file_stats_objects,
67 data);
69 return 0;
73 * Get the d_type of a dirent. If the d_type is unknown, derive it from
74 * stat.st_mode.
76 * Note that 'path' is assumed to have a trailing slash. It is also modified
77 * in-place during the execution of the function, but is then reverted to its
78 * original value before returning.
80 static unsigned char get_dtype(struct dirent *e, struct strbuf *path)
82 struct stat st;
83 unsigned char dtype = DTYPE(e);
84 size_t base_path_len;
86 if (dtype != DT_UNKNOWN)
87 return dtype;
89 /* d_type unknown in dirent, try to fall back on lstat results */
90 base_path_len = path->len;
91 strbuf_addstr(path, e->d_name);
92 if (lstat(path->buf, &st))
93 goto cleanup;
95 /* determine d_type from st_mode */
96 if (S_ISREG(st.st_mode))
97 dtype = DT_REG;
98 else if (S_ISDIR(st.st_mode))
99 dtype = DT_DIR;
100 else if (S_ISLNK(st.st_mode))
101 dtype = DT_LNK;
103 cleanup:
104 strbuf_setlen(path, base_path_len);
105 return dtype;
108 static int count_files(struct strbuf *path)
110 DIR *dir = opendir(path->buf);
111 struct dirent *e;
112 int count = 0;
114 if (!dir)
115 return 0;
117 while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL)
118 if (get_dtype(e, path) == DT_REG)
119 count++;
121 closedir(dir);
122 return count;
125 static void loose_objs_stats(struct strbuf *buf, const char *path)
127 DIR *dir = opendir(path);
128 struct dirent *e;
129 int count;
130 int total = 0;
131 unsigned char c;
132 struct strbuf count_path = STRBUF_INIT;
133 size_t base_path_len;
135 if (!dir)
136 return;
138 strbuf_addstr(buf, "Object directory stats for ");
139 strbuf_add_absolute_path(buf, path);
140 strbuf_addstr(buf, ":\n");
142 strbuf_add_absolute_path(&count_path, path);
143 strbuf_addch(&count_path, '/');
144 base_path_len = count_path.len;
146 while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL)
147 if (get_dtype(e, &count_path) == DT_DIR &&
148 strlen(e->d_name) == 2 &&
149 !hex_to_bytes(&c, e->d_name, 1)) {
150 strbuf_setlen(&count_path, base_path_len);
151 strbuf_addf(&count_path, "%s/", e->d_name);
152 total += (count = count_files(&count_path));
153 strbuf_addf(buf, "%s : %7d files\n", e->d_name, count);
156 strbuf_addf(buf, "Total: %d loose objects", total);
158 strbuf_release(&count_path);
159 closedir(dir);
162 static int add_directory_to_archiver(struct strvec *archiver_args,
163 const char *path, int recurse)
165 int at_root = !*path;
166 DIR *dir;
167 struct dirent *e;
168 struct strbuf buf = STRBUF_INIT;
169 size_t len;
170 int res = 0;
172 dir = opendir(at_root ? "." : path);
173 if (!dir) {
174 if (errno == ENOENT) {
175 warning(_("could not archive missing directory '%s'"), path);
176 return 0;
178 return error_errno(_("could not open directory '%s'"), path);
181 if (!at_root)
182 strbuf_addf(&buf, "%s/", path);
183 len = buf.len;
184 strvec_pushf(archiver_args, "--prefix=%s", buf.buf);
186 while (!res && (e = readdir_skip_dot_and_dotdot(dir))) {
187 struct strbuf abspath = STRBUF_INIT;
188 unsigned char dtype;
190 strbuf_add_absolute_path(&abspath, at_root ? "." : path);
191 strbuf_addch(&abspath, '/');
192 dtype = get_dtype(e, &abspath);
194 strbuf_setlen(&buf, len);
195 strbuf_addstr(&buf, e->d_name);
197 if (dtype == DT_REG)
198 strvec_pushf(archiver_args, "--add-file=%s", buf.buf);
199 else if (dtype != DT_DIR)
200 warning(_("skipping '%s', which is neither file nor "
201 "directory"), buf.buf);
202 else if (recurse &&
203 add_directory_to_archiver(archiver_args,
204 buf.buf, recurse) < 0)
205 res = -1;
207 strbuf_release(&abspath);
210 closedir(dir);
211 strbuf_release(&buf);
212 return res;
215 int create_diagnostics_archive(struct strbuf *zip_path, enum diagnose_mode mode)
217 struct strvec archiver_args = STRVEC_INIT;
218 char **argv_copy = NULL;
219 int stdout_fd = -1, archiver_fd = -1;
220 struct strbuf buf = STRBUF_INIT;
221 int res, i;
222 struct archive_dir archive_dirs[] = {
223 { ".git", 0 },
224 { ".git/hooks", 0 },
225 { ".git/info", 0 },
226 { ".git/logs", 1 },
227 { ".git/objects/info", 0 }
230 if (mode == DIAGNOSE_NONE) {
231 res = 0;
232 goto diagnose_cleanup;
235 stdout_fd = dup(STDOUT_FILENO);
236 if (stdout_fd < 0) {
237 res = error_errno(_("could not duplicate stdout"));
238 goto diagnose_cleanup;
241 archiver_fd = xopen(zip_path->buf, O_CREAT | O_WRONLY | O_TRUNC, 0666);
242 if (dup2(archiver_fd, STDOUT_FILENO) < 0) {
243 res = error_errno(_("could not redirect output"));
244 goto diagnose_cleanup;
247 init_zip_archiver();
248 strvec_pushl(&archiver_args, "git-diagnose", "--format=zip", NULL);
250 strbuf_reset(&buf);
251 strbuf_addstr(&buf, "Collecting diagnostic info\n\n");
252 get_version_info(&buf, 1);
254 strbuf_addf(&buf, "Repository root: %s\n", the_repository->worktree);
255 get_disk_info(&buf);
256 write_or_die(stdout_fd, buf.buf, buf.len);
257 strvec_pushf(&archiver_args,
258 "--add-virtual-file=diagnostics.log:%.*s",
259 (int)buf.len, buf.buf);
261 strbuf_reset(&buf);
262 strbuf_addstr(&buf, "--add-virtual-file=packs-local.txt:");
263 dir_file_stats(the_repository->objects->odb, &buf);
264 foreach_alt_odb(dir_file_stats, &buf);
265 strvec_push(&archiver_args, buf.buf);
267 strbuf_reset(&buf);
268 strbuf_addstr(&buf, "--add-virtual-file=objects-local.txt:");
269 loose_objs_stats(&buf, ".git/objects");
270 strvec_push(&archiver_args, buf.buf);
272 /* Only include this if explicitly requested */
273 if (mode == DIAGNOSE_ALL) {
274 for (i = 0; i < ARRAY_SIZE(archive_dirs); i++) {
275 if (add_directory_to_archiver(&archiver_args,
276 archive_dirs[i].path,
277 archive_dirs[i].recursive)) {
278 res = error_errno(_("could not add directory '%s' to archiver"),
279 archive_dirs[i].path);
280 goto diagnose_cleanup;
285 strvec_pushl(&archiver_args, "--prefix=",
286 oid_to_hex(the_hash_algo->empty_tree), "--", NULL);
288 /* `write_archive()` modifies the `argv` passed to it. Let it. */
289 argv_copy = xmemdupz(archiver_args.v,
290 sizeof(char *) * archiver_args.nr);
291 res = write_archive(archiver_args.nr, (const char **)argv_copy, NULL,
292 the_repository, NULL, 0);
293 if (res) {
294 error(_("failed to write archive"));
295 goto diagnose_cleanup;
298 fprintf(stderr, "\n"
299 "Diagnostics complete.\n"
300 "All of the gathered info is captured in '%s'\n",
301 zip_path->buf);
303 diagnose_cleanup:
304 if (archiver_fd >= 0) {
305 dup2(stdout_fd, STDOUT_FILENO);
306 close(stdout_fd);
307 close(archiver_fd);
309 free(argv_copy);
310 strvec_clear(&archiver_args);
311 strbuf_release(&buf);
313 return res;