Line | Count | Source (jump to first uncovered line) |
1 | | #define USE_THE_REPOSITORY_VARIABLE |
2 | | |
3 | | #include "git-compat-util.h" |
4 | | #include "diagnose.h" |
5 | | #include "compat/disk.h" |
6 | | #include "archive.h" |
7 | | #include "dir.h" |
8 | | #include "help.h" |
9 | | #include "gettext.h" |
10 | | #include "hex.h" |
11 | | #include "strvec.h" |
12 | | #include "object-store-ll.h" |
13 | | #include "packfile.h" |
14 | | #include "parse-options.h" |
15 | | #include "write-or-die.h" |
16 | | |
17 | | struct archive_dir { |
18 | | const char *path; |
19 | | int recursive; |
20 | | }; |
21 | | |
22 | | struct diagnose_option { |
23 | | enum diagnose_mode mode; |
24 | | const char *option_name; |
25 | | }; |
26 | | |
27 | | static struct diagnose_option diagnose_options[] = { |
28 | | { DIAGNOSE_STATS, "stats" }, |
29 | | { DIAGNOSE_ALL, "all" }, |
30 | | }; |
31 | | |
32 | | int option_parse_diagnose(const struct option *opt, const char *arg, int unset) |
33 | 0 | { |
34 | 0 | int i; |
35 | 0 | enum diagnose_mode *diagnose = opt->value; |
36 | |
|
37 | 0 | if (!arg) { |
38 | 0 | *diagnose = unset ? DIAGNOSE_NONE : DIAGNOSE_STATS; |
39 | 0 | return 0; |
40 | 0 | } |
41 | | |
42 | 0 | for (i = 0; i < ARRAY_SIZE(diagnose_options); i++) { |
43 | 0 | if (!strcmp(arg, diagnose_options[i].option_name)) { |
44 | 0 | *diagnose = diagnose_options[i].mode; |
45 | 0 | return 0; |
46 | 0 | } |
47 | 0 | } |
48 | | |
49 | 0 | return error(_("invalid --%s value '%s'"), opt->long_name, arg); |
50 | 0 | } |
51 | | |
52 | | static void dir_file_stats_objects(const char *full_path, |
53 | | size_t full_path_len UNUSED, |
54 | | const char *file_name, void *data) |
55 | 0 | { |
56 | 0 | struct strbuf *buf = data; |
57 | 0 | struct stat st; |
58 | |
|
59 | 0 | if (!stat(full_path, &st)) |
60 | 0 | strbuf_addf(buf, "%-70s %16" PRIuMAX "\n", file_name, |
61 | 0 | (uintmax_t)st.st_size); |
62 | 0 | } |
63 | | |
64 | | static int dir_file_stats(struct object_directory *object_dir, void *data) |
65 | 0 | { |
66 | 0 | struct strbuf *buf = data; |
67 | |
|
68 | 0 | strbuf_addf(buf, "Contents of %s:\n", object_dir->path); |
69 | |
|
70 | 0 | for_each_file_in_pack_dir(object_dir->path, dir_file_stats_objects, |
71 | 0 | data); |
72 | |
|
73 | 0 | return 0; |
74 | 0 | } |
75 | | |
76 | | static int count_files(struct strbuf *path) |
77 | 0 | { |
78 | 0 | DIR *dir = opendir(path->buf); |
79 | 0 | struct dirent *e; |
80 | 0 | int count = 0; |
81 | |
|
82 | 0 | if (!dir) |
83 | 0 | return 0; |
84 | | |
85 | 0 | while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) |
86 | 0 | if (get_dtype(e, path, 0) == DT_REG) |
87 | 0 | count++; |
88 | |
|
89 | 0 | closedir(dir); |
90 | 0 | return count; |
91 | 0 | } |
92 | | |
93 | | static void loose_objs_stats(struct strbuf *buf, const char *path) |
94 | 0 | { |
95 | 0 | DIR *dir = opendir(path); |
96 | 0 | struct dirent *e; |
97 | 0 | int count; |
98 | 0 | int total = 0; |
99 | 0 | unsigned char c; |
100 | 0 | struct strbuf count_path = STRBUF_INIT; |
101 | 0 | size_t base_path_len; |
102 | |
|
103 | 0 | if (!dir) |
104 | 0 | return; |
105 | | |
106 | 0 | strbuf_addstr(buf, "Object directory stats for "); |
107 | 0 | strbuf_add_absolute_path(buf, path); |
108 | 0 | strbuf_addstr(buf, ":\n"); |
109 | |
|
110 | 0 | strbuf_add_absolute_path(&count_path, path); |
111 | 0 | strbuf_addch(&count_path, '/'); |
112 | 0 | base_path_len = count_path.len; |
113 | |
|
114 | 0 | while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) |
115 | 0 | if (get_dtype(e, &count_path, 0) == DT_DIR && |
116 | 0 | strlen(e->d_name) == 2 && |
117 | 0 | !hex_to_bytes(&c, e->d_name, 1)) { |
118 | 0 | strbuf_setlen(&count_path, base_path_len); |
119 | 0 | strbuf_addf(&count_path, "%s/", e->d_name); |
120 | 0 | total += (count = count_files(&count_path)); |
121 | 0 | strbuf_addf(buf, "%s : %7d files\n", e->d_name, count); |
122 | 0 | } |
123 | |
|
124 | 0 | strbuf_addf(buf, "Total: %d loose objects", total); |
125 | |
|
126 | 0 | strbuf_release(&count_path); |
127 | 0 | closedir(dir); |
128 | 0 | } |
129 | | |
130 | | static int add_directory_to_archiver(struct strvec *archiver_args, |
131 | | const char *path, int recurse) |
132 | 0 | { |
133 | 0 | int at_root = !*path; |
134 | 0 | DIR *dir; |
135 | 0 | struct dirent *e; |
136 | 0 | struct strbuf buf = STRBUF_INIT; |
137 | 0 | size_t len; |
138 | 0 | int res = 0; |
139 | |
|
140 | 0 | dir = opendir(at_root ? "." : path); |
141 | 0 | if (!dir) { |
142 | 0 | if (errno == ENOENT) { |
143 | 0 | warning(_("could not archive missing directory '%s'"), path); |
144 | 0 | return 0; |
145 | 0 | } |
146 | 0 | return error_errno(_("could not open directory '%s'"), path); |
147 | 0 | } |
148 | | |
149 | 0 | if (!at_root) |
150 | 0 | strbuf_addf(&buf, "%s/", path); |
151 | 0 | len = buf.len; |
152 | 0 | strvec_pushf(archiver_args, "--prefix=%s", buf.buf); |
153 | |
|
154 | 0 | while (!res && (e = readdir_skip_dot_and_dotdot(dir))) { |
155 | 0 | struct strbuf abspath = STRBUF_INIT; |
156 | 0 | unsigned char dtype; |
157 | |
|
158 | 0 | strbuf_add_absolute_path(&abspath, at_root ? "." : path); |
159 | 0 | strbuf_addch(&abspath, '/'); |
160 | 0 | dtype = get_dtype(e, &abspath, 0); |
161 | |
|
162 | 0 | strbuf_setlen(&buf, len); |
163 | 0 | strbuf_addstr(&buf, e->d_name); |
164 | |
|
165 | 0 | if (dtype == DT_REG) |
166 | 0 | strvec_pushf(archiver_args, "--add-file=%s", buf.buf); |
167 | 0 | else if (dtype != DT_DIR) |
168 | 0 | warning(_("skipping '%s', which is neither file nor " |
169 | 0 | "directory"), buf.buf); |
170 | 0 | else if (recurse && |
171 | 0 | add_directory_to_archiver(archiver_args, |
172 | 0 | buf.buf, recurse) < 0) |
173 | 0 | res = -1; |
174 | |
|
175 | 0 | strbuf_release(&abspath); |
176 | 0 | } |
177 | |
|
178 | 0 | closedir(dir); |
179 | 0 | strbuf_release(&buf); |
180 | 0 | return res; |
181 | 0 | } |
182 | | |
183 | | int create_diagnostics_archive(struct strbuf *zip_path, enum diagnose_mode mode) |
184 | 0 | { |
185 | 0 | struct strvec archiver_args = STRVEC_INIT; |
186 | 0 | char **argv_copy = NULL; |
187 | 0 | int stdout_fd = -1, archiver_fd = -1; |
188 | 0 | struct strbuf buf = STRBUF_INIT; |
189 | 0 | int res, i; |
190 | 0 | struct archive_dir archive_dirs[] = { |
191 | 0 | { ".git", 0 }, |
192 | 0 | { ".git/hooks", 0 }, |
193 | 0 | { ".git/info", 0 }, |
194 | 0 | { ".git/logs", 1 }, |
195 | 0 | { ".git/objects/info", 0 } |
196 | 0 | }; |
197 | |
|
198 | 0 | if (mode == DIAGNOSE_NONE) { |
199 | 0 | res = 0; |
200 | 0 | goto diagnose_cleanup; |
201 | 0 | } |
202 | | |
203 | 0 | stdout_fd = dup(STDOUT_FILENO); |
204 | 0 | if (stdout_fd < 0) { |
205 | 0 | res = error_errno(_("could not duplicate stdout")); |
206 | 0 | goto diagnose_cleanup; |
207 | 0 | } |
208 | | |
209 | 0 | archiver_fd = xopen(zip_path->buf, O_CREAT | O_WRONLY | O_TRUNC, 0666); |
210 | 0 | if (dup2(archiver_fd, STDOUT_FILENO) < 0) { |
211 | 0 | res = error_errno(_("could not redirect output")); |
212 | 0 | goto diagnose_cleanup; |
213 | 0 | } |
214 | | |
215 | 0 | init_zip_archiver(); |
216 | 0 | strvec_pushl(&archiver_args, "git-diagnose", "--format=zip", NULL); |
217 | |
|
218 | 0 | strbuf_reset(&buf); |
219 | 0 | strbuf_addstr(&buf, "Collecting diagnostic info\n\n"); |
220 | 0 | get_version_info(&buf, 1); |
221 | |
|
222 | 0 | strbuf_addf(&buf, "Repository root: %s\n", the_repository->worktree); |
223 | 0 | get_disk_info(&buf); |
224 | 0 | write_or_die(stdout_fd, buf.buf, buf.len); |
225 | 0 | strvec_pushf(&archiver_args, |
226 | 0 | "--add-virtual-file=diagnostics.log:%.*s", |
227 | 0 | (int)buf.len, buf.buf); |
228 | |
|
229 | 0 | strbuf_reset(&buf); |
230 | 0 | strbuf_addstr(&buf, "--add-virtual-file=packs-local.txt:"); |
231 | 0 | dir_file_stats(the_repository->objects->odb, &buf); |
232 | 0 | foreach_alt_odb(dir_file_stats, &buf); |
233 | 0 | strvec_push(&archiver_args, buf.buf); |
234 | |
|
235 | 0 | strbuf_reset(&buf); |
236 | 0 | strbuf_addstr(&buf, "--add-virtual-file=objects-local.txt:"); |
237 | 0 | loose_objs_stats(&buf, ".git/objects"); |
238 | 0 | strvec_push(&archiver_args, buf.buf); |
239 | | |
240 | | /* Only include this if explicitly requested */ |
241 | 0 | if (mode == DIAGNOSE_ALL) { |
242 | 0 | for (i = 0; i < ARRAY_SIZE(archive_dirs); i++) { |
243 | 0 | if (add_directory_to_archiver(&archiver_args, |
244 | 0 | archive_dirs[i].path, |
245 | 0 | archive_dirs[i].recursive)) { |
246 | 0 | res = error_errno(_("could not add directory '%s' to archiver"), |
247 | 0 | archive_dirs[i].path); |
248 | 0 | goto diagnose_cleanup; |
249 | 0 | } |
250 | 0 | } |
251 | 0 | } |
252 | | |
253 | 0 | strvec_pushl(&archiver_args, "--prefix=", |
254 | 0 | oid_to_hex(the_hash_algo->empty_tree), "--", NULL); |
255 | | |
256 | | /* `write_archive()` modifies the `argv` passed to it. Let it. */ |
257 | 0 | argv_copy = xmemdupz(archiver_args.v, |
258 | 0 | sizeof(char *) * archiver_args.nr); |
259 | 0 | res = write_archive(archiver_args.nr, (const char **)argv_copy, NULL, |
260 | 0 | the_repository, NULL, 0); |
261 | 0 | if (res) { |
262 | 0 | error(_("failed to write archive")); |
263 | 0 | goto diagnose_cleanup; |
264 | 0 | } |
265 | | |
266 | 0 | fprintf(stderr, "\n" |
267 | 0 | "Diagnostics complete.\n" |
268 | 0 | "All of the gathered info is captured in '%s'\n", |
269 | 0 | zip_path->buf); |
270 | |
|
271 | 0 | diagnose_cleanup: |
272 | 0 | if (archiver_fd >= 0) { |
273 | 0 | dup2(stdout_fd, STDOUT_FILENO); |
274 | 0 | close(stdout_fd); |
275 | 0 | close(archiver_fd); |
276 | 0 | } |
277 | 0 | free(argv_copy); |
278 | 0 | strvec_clear(&archiver_args); |
279 | 0 | strbuf_release(&buf); |
280 | |
|
281 | 0 | return res; |
282 | 0 | } |