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