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