commit-graph: read only from specific pack-indexes
[git] / builtin / commit-graph.c
1 #include "builtin.h"
2 #include "config.h"
3 #include "dir.h"
4 #include "lockfile.h"
5 #include "parse-options.h"
6 #include "commit-graph.h"
7
8 static char const * const builtin_commit_graph_usage[] = {
9         N_("git commit-graph [--object-dir <objdir>]"),
10         N_("git commit-graph read [--object-dir <objdir>] [--file=<hash>]"),
11         N_("git commit-graph write [--object-dir <objdir>] [--set-latest] [--delete-expired] [--stdin-packs]"),
12         NULL
13 };
14
15 static const char * const builtin_commit_graph_read_usage[] = {
16         N_("git commit-graph read [--object-dir <objdir>] [--file=<hash>]"),
17         NULL
18 };
19
20 static const char * const builtin_commit_graph_write_usage[] = {
21         N_("git commit-graph write [--object-dir <objdir>] [--set-latest] [--delete-expired] [--stdin-packs]"),
22         NULL
23 };
24
25 static struct opts_commit_graph {
26         const char *obj_dir;
27         const char *graph_file;
28         int set_latest;
29         int delete_expired;
30         int stdin_packs;
31 } opts;
32
33 static int graph_read(int argc, const char **argv)
34 {
35         struct commit_graph *graph = 0;
36         struct strbuf full_path = STRBUF_INIT;
37
38         static struct option builtin_commit_graph_read_options[] = {
39                 { OPTION_STRING, 'o', "object-dir", &opts.obj_dir,
40                         N_("dir"),
41                         N_("The object directory to store the graph") },
42                 { OPTION_STRING, 'H', "file", &opts.graph_file,
43                         N_("file"),
44                         N_("The filename for a specific commit graph file in the object directory."),
45                         PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
46                 OPT_END(),
47         };
48
49         argc = parse_options(argc, argv, NULL,
50                              builtin_commit_graph_read_options,
51                              builtin_commit_graph_read_usage, 0);
52
53         if (!opts.obj_dir)
54                 opts.obj_dir = get_object_directory();
55
56         if (!opts.graph_file)
57                 die("no graph hash specified");
58
59         strbuf_addf(&full_path, "%s/info/%s", opts.obj_dir, opts.graph_file);
60         graph = load_commit_graph_one(full_path.buf);
61
62         if (!graph)
63                 die("graph file %s does not exist", full_path.buf);
64
65         printf("header: %08x %d %d %d %d\n",
66                 ntohl(*(uint32_t*)graph->data),
67                 *(unsigned char*)(graph->data + 4),
68                 *(unsigned char*)(graph->data + 5),
69                 *(unsigned char*)(graph->data + 6),
70                 *(unsigned char*)(graph->data + 7));
71         printf("num_commits: %u\n", graph->num_commits);
72         printf("chunks:");
73
74         if (graph->chunk_oid_fanout)
75                 printf(" oid_fanout");
76         if (graph->chunk_oid_lookup)
77                 printf(" oid_lookup");
78         if (graph->chunk_commit_data)
79                 printf(" commit_metadata");
80         if (graph->chunk_large_edges)
81                 printf(" large_edges");
82         printf("\n");
83
84         return 0;
85 }
86
87 static void set_latest_file(const char *obj_dir, const char *graph_file)
88 {
89         int fd;
90         struct lock_file lk = LOCK_INIT;
91         char *latest_fname = get_graph_latest_filename(obj_dir);
92
93         fd = hold_lock_file_for_update(&lk, latest_fname, LOCK_DIE_ON_ERROR);
94         FREE_AND_NULL(latest_fname);
95
96         if (fd < 0)
97                 die_errno("unable to open graph-head");
98
99         write_in_full(fd, graph_file, strlen(graph_file));
100         commit_lock_file(&lk);
101 }
102
103 /*
104  * To avoid race conditions and deleting graph files that are being
105  * used by other processes, look inside a pack directory for all files
106  * of the form "graph-<hash>.graph" that do not match the old or new
107  * graph hashes and delete them.
108  */
109 static void do_delete_expired(const char *obj_dir,
110                               const char *old_graph_name,
111                               const char *new_graph_name)
112 {
113         DIR *dir;
114         struct dirent *de;
115         int dirnamelen;
116         struct strbuf path = STRBUF_INIT;
117
118         strbuf_addf(&path, "%s/info", obj_dir);
119         dir = opendir(path.buf);
120         if (!dir) {
121                 if (errno != ENOENT)
122                         error_errno("unable to open object pack directory: %s",
123                                     obj_dir);
124                 return;
125         }
126
127         strbuf_addch(&path, '/');
128         dirnamelen = path.len;
129         while ((de = readdir(dir)) != NULL) {
130                 size_t base_len;
131
132                 if (is_dot_or_dotdot(de->d_name))
133                         continue;
134
135                 strbuf_setlen(&path, dirnamelen);
136                 strbuf_addstr(&path, de->d_name);
137
138                 base_len = path.len;
139                 if (strip_suffix_mem(path.buf, &base_len, ".graph") &&
140                     strcmp(new_graph_name, de->d_name) &&
141                     (!old_graph_name || strcmp(old_graph_name, de->d_name)) &&
142                     remove_path(path.buf))
143                         die("failed to remove path %s", path.buf);
144         }
145
146         strbuf_release(&path);
147 }
148
149 static int graph_write(int argc, const char **argv)
150 {
151         char *graph_name;
152         char *old_graph_name;
153         const char **pack_indexes = NULL;
154         int nr_packs = 0;
155         const char **lines = NULL;
156         int nr_lines = 0;
157         int alloc_lines = 0;
158
159         static struct option builtin_commit_graph_write_options[] = {
160                 { OPTION_STRING, 'o', "object-dir", &opts.obj_dir,
161                         N_("dir"),
162                         N_("The object directory to store the graph") },
163                 OPT_BOOL('u', "set-latest", &opts.set_latest,
164                         N_("update graph-head to written graph file")),
165                 OPT_BOOL('d', "delete-expired", &opts.delete_expired,
166                         N_("delete expired head graph file")),
167                 OPT_BOOL('s', "stdin-packs", &opts.stdin_packs,
168                         N_("only scan packfiles listed by stdin")),
169                 OPT_END(),
170         };
171
172         argc = parse_options(argc, argv, NULL,
173                              builtin_commit_graph_write_options,
174                              builtin_commit_graph_write_usage, 0);
175
176         if (!opts.obj_dir)
177                 opts.obj_dir = get_object_directory();
178
179         old_graph_name = get_graph_latest_contents(opts.obj_dir);
180
181         if (opts.stdin_packs) {
182                 struct strbuf buf = STRBUF_INIT;
183                 nr_lines = 0;
184                 alloc_lines = 128;
185                 ALLOC_ARRAY(lines, alloc_lines);
186
187                 while (strbuf_getline(&buf, stdin) != EOF) {
188                         ALLOC_GROW(lines, nr_lines + 1, alloc_lines);
189                         lines[nr_lines++] = buf.buf;
190                         strbuf_detach(&buf, NULL);
191                 }
192
193                 pack_indexes = lines;
194                 nr_packs = nr_lines;
195         }
196
197         graph_name = write_commit_graph(opts.obj_dir,
198                                         pack_indexes,
199                                         nr_packs);
200
201         if (graph_name) {
202                 if (opts.set_latest)
203                         set_latest_file(opts.obj_dir, graph_name);
204
205                 if (opts.delete_expired)
206                         do_delete_expired(opts.obj_dir,
207                                           old_graph_name,
208                                           graph_name);
209
210                 printf("%s\n", graph_name);
211                 FREE_AND_NULL(graph_name);
212         }
213
214         return 0;
215 }
216
217 int cmd_commit_graph(int argc, const char **argv, const char *prefix)
218 {
219         static struct option builtin_commit_graph_options[] = {
220                 { OPTION_STRING, 'o', "object-dir", &opts.obj_dir,
221                         N_("dir"),
222                         N_("The object directory to store the graph") },
223                 OPT_END(),
224         };
225
226         if (argc == 2 && !strcmp(argv[1], "-h"))
227                 usage_with_options(builtin_commit_graph_usage,
228                                    builtin_commit_graph_options);
229
230         git_config(git_default_config, NULL);
231         argc = parse_options(argc, argv, prefix,
232                              builtin_commit_graph_options,
233                              builtin_commit_graph_usage,
234                              PARSE_OPT_STOP_AT_NON_OPTION);
235
236         if (argc > 0) {
237                 if (!strcmp(argv[0], "read"))
238                         return graph_read(argc, argv);
239                 if (!strcmp(argv[0], "write"))
240                         return graph_write(argc, argv);
241         }
242
243         usage_with_options(builtin_commit_graph_usage,
244                            builtin_commit_graph_options);
245 }