Blame


1 372ccdbb 2018-06-10 stsp /*
2 5aa81393 2020-01-06 stsp * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org>
3 372ccdbb 2018-06-10 stsp *
4 372ccdbb 2018-06-10 stsp * Permission to use, copy, modify, and distribute this software for any
5 372ccdbb 2018-06-10 stsp * purpose with or without fee is hereby granted, provided that the above
6 372ccdbb 2018-06-10 stsp * copyright notice and this permission notice appear in all copies.
7 372ccdbb 2018-06-10 stsp *
8 372ccdbb 2018-06-10 stsp * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 372ccdbb 2018-06-10 stsp * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 372ccdbb 2018-06-10 stsp * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 372ccdbb 2018-06-10 stsp * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 372ccdbb 2018-06-10 stsp * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 372ccdbb 2018-06-10 stsp * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 372ccdbb 2018-06-10 stsp * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 372ccdbb 2018-06-10 stsp */
16 372ccdbb 2018-06-10 stsp
17 372ccdbb 2018-06-10 stsp #include <sys/types.h>
18 372ccdbb 2018-06-10 stsp #include <sys/stat.h>
19 8b925c6c 2022-07-16 thomas #include <sys/queue.h>
20 372ccdbb 2018-06-10 stsp
21 56e0773d 2019-11-28 stsp #include <limits.h>
22 372ccdbb 2018-06-10 stsp #include <stdio.h>
23 372ccdbb 2018-06-10 stsp #include <stdlib.h>
24 372ccdbb 2018-06-10 stsp #include <string.h>
25 372ccdbb 2018-06-10 stsp #include <zlib.h>
26 372ccdbb 2018-06-10 stsp #include <ctype.h>
27 dd038bc6 2021-09-21 thomas.ad
28 dd038bc6 2021-09-21 thomas.ad #include "got_compat.h"
29 372ccdbb 2018-06-10 stsp
30 372ccdbb 2018-06-10 stsp #include "got_error.h"
31 372ccdbb 2018-06-10 stsp #include "got_object.h"
32 6fb7cd11 2019-08-22 stsp #include "got_cancel.h"
33 372ccdbb 2018-06-10 stsp #include "got_commit_graph.h"
34 324d37e7 2019-05-11 stsp #include "got_path.h"
35 372ccdbb 2018-06-10 stsp
36 372ccdbb 2018-06-10 stsp #include "got_lib_delta.h"
37 63581804 2018-07-09 stsp #include "got_lib_inflate.h"
38 372ccdbb 2018-06-10 stsp #include "got_lib_object.h"
39 372ccdbb 2018-06-10 stsp #include "got_lib_object_idset.h"
40 827e685d 2024-03-30 thomas #include "got_lib_object_qid.h"
41 372ccdbb 2018-06-10 stsp
42 827e685d 2024-03-30 thomas #ifndef nitems
43 827e685d 2024-03-30 thomas #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
44 827e685d 2024-03-30 thomas #endif
45 827e685d 2024-03-30 thomas
46 372ccdbb 2018-06-10 stsp struct got_commit_graph_node {
47 372ccdbb 2018-06-10 stsp struct got_object_id id;
48 b43fbaa0 2018-06-11 stsp
49 827e685d 2024-03-30 thomas /* Used for topological sorting. */
50 827e685d 2024-03-30 thomas struct got_commit_graph_node *parents[2];
51 827e685d 2024-03-30 thomas struct got_commit_graph_node **more_parents;
52 827e685d 2024-03-30 thomas int nparents;
53 827e685d 2024-03-30 thomas int indegree;
54 827e685d 2024-03-30 thomas
55 ca6e02ac 2020-01-07 stsp /* Used only during iteration. */
56 ca6e02ac 2020-01-07 stsp time_t timestamp;
57 372ccdbb 2018-06-10 stsp TAILQ_ENTRY(got_commit_graph_node) entry;
58 372ccdbb 2018-06-10 stsp };
59 372ccdbb 2018-06-10 stsp
60 9ba79e04 2018-06-11 stsp TAILQ_HEAD(got_commit_graph_iter_list, got_commit_graph_node);
61 9ba79e04 2018-06-11 stsp
62 b565f6f8 2018-07-23 stsp struct got_commit_graph_branch_tip {
63 32777563 2018-11-07 stsp struct got_object_id *commit_id;
64 32777563 2018-11-07 stsp struct got_commit_object *commit;
65 32777563 2018-11-07 stsp struct got_commit_graph_node *new_node;
66 b565f6f8 2018-07-23 stsp };
67 b565f6f8 2018-07-23 stsp
68 372ccdbb 2018-06-10 stsp struct got_commit_graph {
69 372ccdbb 2018-06-10 stsp /* The set of all commits we have traversed. */
70 372ccdbb 2018-06-10 stsp struct got_object_idset *node_ids;
71 372ccdbb 2018-06-10 stsp
72 0ed6ed4c 2018-06-13 stsp int flags;
73 0ed6ed4c 2018-06-13 stsp #define GOT_COMMIT_GRAPH_FIRST_PARENT_TRAVERSAL 0x01
74 827e685d 2024-03-30 thomas #define GOT_COMMIT_GRAPH_TOPOSORT 0x02
75 0ed6ed4c 2018-06-13 stsp
76 372ccdbb 2018-06-10 stsp /*
77 372ccdbb 2018-06-10 stsp * A set of object IDs of known parent commits which we have not yet
78 372ccdbb 2018-06-10 stsp * traversed. Each commit ID in this set represents a branch in commit
79 372ccdbb 2018-06-10 stsp * history: Either the first-parent branch of the head node, or another
80 372ccdbb 2018-06-10 stsp * branch corresponding to a traversed merge commit for which we have
81 372ccdbb 2018-06-10 stsp * not traversed a branch point commit yet.
82 372ccdbb 2018-06-10 stsp *
83 372ccdbb 2018-06-10 stsp * Whenever we add a commit with a matching ID to the graph, we remove
84 372ccdbb 2018-06-10 stsp * its corresponding element from this set, and add new elements for
85 372ccdbb 2018-06-10 stsp * each of that commit's parent commits which were not traversed yet.
86 372ccdbb 2018-06-10 stsp *
87 372ccdbb 2018-06-10 stsp * When API users ask us to fetch more commits, we fetch commits from
88 372ccdbb 2018-06-10 stsp * all currently open branches. This allows API users to process
89 372ccdbb 2018-06-10 stsp * commits in linear order even though the history contains branches.
90 372ccdbb 2018-06-10 stsp */
91 372ccdbb 2018-06-10 stsp struct got_object_idset *open_branches;
92 b565f6f8 2018-07-23 stsp
93 32777563 2018-11-07 stsp /* Array of branch tips for fetch_commits_from_open_branches(). */
94 b565f6f8 2018-07-23 stsp struct got_commit_graph_branch_tip *tips;
95 5e50c36a 2018-11-08 stsp int ntips;
96 372ccdbb 2018-06-10 stsp
97 31cedeaf 2018-09-15 stsp /* Path of tree entry of interest to the API user. */
98 31cedeaf 2018-09-15 stsp char *path;
99 31cedeaf 2018-09-15 stsp
100 94489f7d 2020-01-04 stsp /*
101 94489f7d 2020-01-04 stsp * Nodes which will be passed to the API user next, sorted by
102 827e685d 2024-03-30 thomas * commit timestamp. Sorted in topological order only if topological
103 827e685d 2024-03-30 thomas * sorting was requested.
104 94489f7d 2020-01-04 stsp */
105 9ba79e04 2018-06-11 stsp struct got_commit_graph_iter_list iter_list;
106 372ccdbb 2018-06-10 stsp };
107 372ccdbb 2018-06-10 stsp
108 31cedeaf 2018-09-15 stsp static const struct got_error *
109 41fa1437 2018-11-05 stsp detect_changed_path(int *changed, struct got_commit_object *commit,
110 31cedeaf 2018-09-15 stsp struct got_object_id *commit_id, const char *path,
111 31cedeaf 2018-09-15 stsp struct got_repository *repo)
112 31cedeaf 2018-09-15 stsp {
113 31cedeaf 2018-09-15 stsp const struct got_error *err = NULL;
114 41fa1437 2018-11-05 stsp struct got_commit_object *pcommit = NULL;
115 31cedeaf 2018-09-15 stsp struct got_tree_object *tree = NULL, *ptree = NULL;
116 31cedeaf 2018-09-15 stsp struct got_object_qid *pid;
117 31cedeaf 2018-09-15 stsp
118 31cedeaf 2018-09-15 stsp if (got_path_is_root_dir(path)) {
119 31cedeaf 2018-09-15 stsp *changed = 1;
120 31cedeaf 2018-09-15 stsp return NULL;
121 31cedeaf 2018-09-15 stsp }
122 31cedeaf 2018-09-15 stsp
123 31cedeaf 2018-09-15 stsp *changed = 0;
124 31cedeaf 2018-09-15 stsp
125 dbdddfee 2021-06-23 naddy pid = STAILQ_FIRST(&commit->parent_ids);
126 31cedeaf 2018-09-15 stsp if (pid == NULL) {
127 31cedeaf 2018-09-15 stsp struct got_object_id *obj_id;
128 945f9229 2022-04-16 thomas err = got_object_id_by_path(&obj_id, repo, commit, path);
129 fd1d2703 2018-11-04 stsp if (err) {
130 d1451975 2018-11-11 stsp if (err->code == GOT_ERR_NO_TREE_ENTRY)
131 fd1d2703 2018-11-04 stsp err = NULL;
132 fd1d2703 2018-11-04 stsp } else
133 fd1d2703 2018-11-04 stsp *changed = 1; /* The path was created in this commit. */
134 31cedeaf 2018-09-15 stsp free(obj_id);
135 0e9101d5 2018-11-18 stsp return err;
136 0e9101d5 2018-11-18 stsp }
137 7310c1c3 2018-11-18 stsp
138 0e9101d5 2018-11-18 stsp err = got_object_open_as_tree(&tree, repo, commit->tree_id);
139 0e9101d5 2018-11-18 stsp if (err)
140 0e9101d5 2018-11-18 stsp return err;
141 372ccdbb 2018-06-10 stsp
142 ec242592 2022-04-22 thomas err = got_object_open_as_commit(&pcommit, repo, &pid->id);
143 0e9101d5 2018-11-18 stsp if (err)
144 0e9101d5 2018-11-18 stsp goto done;
145 0e9101d5 2018-11-18 stsp
146 0e9101d5 2018-11-18 stsp err = got_object_open_as_tree(&ptree, repo, pcommit->tree_id);
147 0e9101d5 2018-11-18 stsp if (err)
148 0e9101d5 2018-11-18 stsp goto done;
149 0e9101d5 2018-11-18 stsp
150 81a966c0 2018-11-18 stsp err = got_object_tree_path_changed(changed, tree, ptree, path, repo);
151 31cedeaf 2018-09-15 stsp done:
152 31cedeaf 2018-09-15 stsp if (tree)
153 31cedeaf 2018-09-15 stsp got_object_tree_close(tree);
154 31cedeaf 2018-09-15 stsp if (ptree)
155 31cedeaf 2018-09-15 stsp got_object_tree_close(ptree);
156 31cedeaf 2018-09-15 stsp if (pcommit)
157 41fa1437 2018-11-05 stsp got_object_commit_close(pcommit);
158 31cedeaf 2018-09-15 stsp return err;
159 31cedeaf 2018-09-15 stsp }
160 31cedeaf 2018-09-15 stsp
161 4626e416 2018-06-10 stsp static void
162 9ba79e04 2018-06-11 stsp add_node_to_iter_list(struct got_commit_graph *graph,
163 ca6e02ac 2020-01-07 stsp struct got_commit_graph_node *node, time_t committer_time)
164 372ccdbb 2018-06-10 stsp {
165 4bd3f2bb 2018-06-10 stsp struct got_commit_graph_node *n, *next;
166 1c7a5dcb 2018-09-15 stsp
167 ca6e02ac 2020-01-07 stsp node->timestamp = committer_time;
168 ca6e02ac 2020-01-07 stsp
169 94489f7d 2020-01-04 stsp n = TAILQ_FIRST(&graph->iter_list);
170 bee6b577 2018-09-19 stsp while (n) {
171 bee6b577 2018-09-19 stsp next = TAILQ_NEXT(n, entry);
172 73026088 2018-11-18 stsp if (next && node->timestamp >= next->timestamp) {
173 bee6b577 2018-09-19 stsp TAILQ_INSERT_BEFORE(next, node, entry);
174 bee6b577 2018-09-19 stsp return;
175 fe8df4c2 2018-06-16 stsp }
176 bee6b577 2018-09-19 stsp n = next;
177 fe8df4c2 2018-06-16 stsp }
178 93e45b7c 2018-09-24 stsp TAILQ_INSERT_TAIL(&graph->iter_list, node, entry);
179 0ed6ed4c 2018-06-13 stsp }
180 0ed6ed4c 2018-06-13 stsp
181 0ed6ed4c 2018-06-13 stsp static const struct got_error *
182 ca6e02ac 2020-01-07 stsp add_node(struct got_commit_graph_node **new_node,
183 ca6e02ac 2020-01-07 stsp struct got_commit_graph *graph, struct got_object_id *commit_id,
184 ca6e02ac 2020-01-07 stsp struct got_repository *repo)
185 ca6e02ac 2020-01-07 stsp {
186 ca6e02ac 2020-01-07 stsp const struct got_error *err = NULL;
187 ca6e02ac 2020-01-07 stsp struct got_commit_graph_node *node;
188 ca6e02ac 2020-01-07 stsp
189 ca6e02ac 2020-01-07 stsp *new_node = NULL;
190 ca6e02ac 2020-01-07 stsp
191 ca6e02ac 2020-01-07 stsp node = calloc(1, sizeof(*node));
192 ca6e02ac 2020-01-07 stsp if (node == NULL)
193 ca6e02ac 2020-01-07 stsp return got_error_from_errno("calloc");
194 ca6e02ac 2020-01-07 stsp
195 ca6e02ac 2020-01-07 stsp memcpy(&node->id, commit_id, sizeof(node->id));
196 827e685d 2024-03-30 thomas node->nparents = -1;
197 827e685d 2024-03-30 thomas err = got_object_idset_add(graph->node_ids, &node->id, node);
198 ca6e02ac 2020-01-07 stsp if (err)
199 ca6e02ac 2020-01-07 stsp free(node);
200 ca6e02ac 2020-01-07 stsp else
201 ca6e02ac 2020-01-07 stsp *new_node = node;
202 ca6e02ac 2020-01-07 stsp return err;
203 ca6e02ac 2020-01-07 stsp }
204 ca6e02ac 2020-01-07 stsp
205 ca6e02ac 2020-01-07 stsp /*
206 ca6e02ac 2020-01-07 stsp * Ask got-read-pack to traverse first-parent history until a commit is
207 ca6e02ac 2020-01-07 stsp * encountered which modified graph->path, or until the pack file runs
208 ca6e02ac 2020-01-07 stsp * out of relevant commits. This is faster than sending an individual
209 ca6e02ac 2020-01-07 stsp * request for each commit stored in the pack file.
210 ca6e02ac 2020-01-07 stsp */
211 ca6e02ac 2020-01-07 stsp static const struct got_error *
212 ca6e02ac 2020-01-07 stsp packed_first_parent_traversal(int *ncommits_traversed,
213 ca6e02ac 2020-01-07 stsp struct got_commit_graph *graph, struct got_object_id *commit_id,
214 ca6e02ac 2020-01-07 stsp struct got_repository *repo)
215 ca6e02ac 2020-01-07 stsp {
216 ca6e02ac 2020-01-07 stsp const struct got_error *err = NULL;
217 ca6e02ac 2020-01-07 stsp struct got_object_id_queue traversed_commits;
218 ca6e02ac 2020-01-07 stsp struct got_object_qid *qid;
219 ca6e02ac 2020-01-07 stsp
220 dbdddfee 2021-06-23 naddy STAILQ_INIT(&traversed_commits);
221 ca6e02ac 2020-01-07 stsp *ncommits_traversed = 0;
222 ca6e02ac 2020-01-07 stsp
223 ca6e02ac 2020-01-07 stsp err = got_traverse_packed_commits(&traversed_commits,
224 ca6e02ac 2020-01-07 stsp commit_id, graph->path, repo);
225 ca6e02ac 2020-01-07 stsp if (err)
226 ca6e02ac 2020-01-07 stsp return err;
227 ca6e02ac 2020-01-07 stsp
228 ca6e02ac 2020-01-07 stsp /* Add all traversed commits to the graph... */
229 dbdddfee 2021-06-23 naddy STAILQ_FOREACH(qid, &traversed_commits, entry) {
230 ec242592 2022-04-22 thomas if (got_object_idset_contains(graph->open_branches, &qid->id))
231 ca6e02ac 2020-01-07 stsp continue;
232 ec242592 2022-04-22 thomas if (got_object_idset_contains(graph->node_ids, &qid->id))
233 ca6e02ac 2020-01-07 stsp continue;
234 ca6e02ac 2020-01-07 stsp
235 ca6e02ac 2020-01-07 stsp (*ncommits_traversed)++;
236 ca6e02ac 2020-01-07 stsp
237 ca6e02ac 2020-01-07 stsp /* ... except the last commit is the new branch tip. */
238 dbdddfee 2021-06-23 naddy if (STAILQ_NEXT(qid, entry) == NULL) {
239 ca6e02ac 2020-01-07 stsp err = got_object_idset_add(graph->open_branches,
240 ec242592 2022-04-22 thomas &qid->id, NULL);
241 ca6e02ac 2020-01-07 stsp break;
242 ca6e02ac 2020-01-07 stsp }
243 ca6e02ac 2020-01-07 stsp
244 99c6f580 2022-09-05 thomas err = got_object_idset_add(graph->node_ids, &qid->id, NULL);
245 ca6e02ac 2020-01-07 stsp if (err)
246 ca6e02ac 2020-01-07 stsp break;
247 ca6e02ac 2020-01-07 stsp }
248 ca6e02ac 2020-01-07 stsp
249 ca6e02ac 2020-01-07 stsp got_object_id_queue_free(&traversed_commits);
250 ca6e02ac 2020-01-07 stsp return err;
251 ca6e02ac 2020-01-07 stsp }
252 ca6e02ac 2020-01-07 stsp
253 ca6e02ac 2020-01-07 stsp static const struct got_error *
254 32c85d2c 2020-01-06 stsp close_branch(struct got_commit_graph *graph, struct got_object_id *commit_id)
255 32c85d2c 2020-01-06 stsp {
256 32c85d2c 2020-01-06 stsp const struct got_error *err;
257 32c85d2c 2020-01-06 stsp
258 32c85d2c 2020-01-06 stsp err = got_object_idset_remove(NULL, graph->open_branches, commit_id);
259 32c85d2c 2020-01-06 stsp if (err && err->code != GOT_ERR_NO_OBJ)
260 32c85d2c 2020-01-06 stsp return err;
261 32c85d2c 2020-01-06 stsp return NULL;
262 32c85d2c 2020-01-06 stsp }
263 32c85d2c 2020-01-06 stsp
264 32c85d2c 2020-01-06 stsp static const struct got_error *
265 14159a7b 2020-01-04 stsp advance_branch(struct got_commit_graph *graph, struct got_object_id *commit_id,
266 14159a7b 2020-01-04 stsp struct got_commit_object *commit, struct got_repository *repo)
267 0ed6ed4c 2018-06-13 stsp {
268 0ed6ed4c 2018-06-13 stsp const struct got_error *err;
269 0ed6ed4c 2018-06-13 stsp struct got_object_qid *qid;
270 bae38d30 2024-02-11 thomas struct got_object_id *merged_id = NULL;
271 0ed6ed4c 2018-06-13 stsp
272 32c85d2c 2020-01-06 stsp err = close_branch(graph, commit_id);
273 32c85d2c 2020-01-06 stsp if (err)
274 32c85d2c 2020-01-06 stsp return err;
275 32c85d2c 2020-01-06 stsp
276 0ed6ed4c 2018-06-13 stsp if (graph->flags & GOT_COMMIT_GRAPH_FIRST_PARENT_TRAVERSAL) {
277 dbdddfee 2021-06-23 naddy qid = STAILQ_FIRST(&commit->parent_ids);
278 9b88e78c 2018-11-18 stsp if (qid == NULL ||
279 ec242592 2022-04-22 thomas got_object_idset_contains(graph->open_branches, &qid->id))
280 0ed6ed4c 2018-06-13 stsp return NULL;
281 ca6e02ac 2020-01-07 stsp /*
282 ca6e02ac 2020-01-07 stsp * The root directory always changes by definition, and when
283 ca6e02ac 2020-01-07 stsp * logging the root we want to traverse consecutive commits
284 ca6e02ac 2020-01-07 stsp * even if they point at the same tree.
285 ca6e02ac 2020-01-07 stsp * But if we are looking for a specific path then we can avoid
286 ca6e02ac 2020-01-07 stsp * fetching packed commits which did not modify the path and
287 ca6e02ac 2020-01-07 stsp * only fetch their IDs. This speeds up 'got blame'.
288 ca6e02ac 2020-01-07 stsp */
289 ca6e02ac 2020-01-07 stsp if (!got_path_is_root_dir(graph->path) &&
290 ca6e02ac 2020-01-07 stsp (commit->flags & GOT_COMMIT_FLAG_PACKED)) {
291 ca6e02ac 2020-01-07 stsp int ncommits = 0;
292 ca6e02ac 2020-01-07 stsp err = packed_first_parent_traversal(&ncommits,
293 ec242592 2022-04-22 thomas graph, &qid->id, repo);
294 ca6e02ac 2020-01-07 stsp if (err || ncommits > 0)
295 ca6e02ac 2020-01-07 stsp return err;
296 ca6e02ac 2020-01-07 stsp }
297 9b88e78c 2018-11-18 stsp return got_object_idset_add(graph->open_branches,
298 ec242592 2022-04-22 thomas &qid->id, NULL);
299 bee6b577 2018-09-19 stsp }
300 bee6b577 2018-09-19 stsp
301 bee6b577 2018-09-19 stsp /*
302 bee6b577 2018-09-19 stsp * If we are graphing commits for a specific path, skip branches
303 bee6b577 2018-09-19 stsp * which do not contribute any content to this path.
304 bee6b577 2018-09-19 stsp */
305 7a62478b 2018-11-18 stsp if (commit->nparents > 1 && !got_path_is_root_dir(graph->path)) {
306 bae38d30 2024-02-11 thomas err = got_object_id_by_path(&merged_id, repo, commit, graph->path);
307 bae38d30 2024-02-11 thomas if (err && err->code != GOT_ERR_NO_TREE_ENTRY)
308 bae38d30 2024-02-11 thomas return err;
309 bae38d30 2024-02-11 thomas /* The requested path does not exist in this merge commit. */
310 bae38d30 2024-02-11 thomas }
311 bae38d30 2024-02-11 thomas if (commit->nparents > 1 && !got_path_is_root_dir(graph->path) &&
312 bae38d30 2024-02-11 thomas merged_id != NULL) {
313 bae38d30 2024-02-11 thomas struct got_object_id *prev_id = NULL;
314 bee6b577 2018-09-19 stsp int branches_differ = 0;
315 bee6b577 2018-09-19 stsp
316 bee6b577 2018-09-19 stsp
317 dbdddfee 2021-06-23 naddy STAILQ_FOREACH(qid, &commit->parent_ids, entry) {
318 945f9229 2022-04-16 thomas struct got_object_id *id = NULL;
319 945f9229 2022-04-16 thomas struct got_commit_object *pcommit = NULL;
320 6dcdad08 2018-11-18 stsp
321 8e291695 2020-01-04 stsp if (got_object_idset_contains(graph->open_branches,
322 ec242592 2022-04-22 thomas &qid->id))
323 9b88e78c 2018-11-18 stsp continue;
324 bee6b577 2018-09-19 stsp
325 945f9229 2022-04-16 thomas err = got_object_open_as_commit(&pcommit, repo,
326 ec242592 2022-04-22 thomas &qid->id);
327 945f9229 2022-04-16 thomas if (err) {
328 945f9229 2022-04-16 thomas free(merged_id);
329 945f9229 2022-04-16 thomas free(prev_id);
330 945f9229 2022-04-16 thomas return err;
331 945f9229 2022-04-16 thomas }
332 945f9229 2022-04-16 thomas err = got_object_id_by_path(&id, repo, pcommit,
333 bee6b577 2018-09-19 stsp graph->path);
334 945f9229 2022-04-16 thomas got_object_commit_close(pcommit);
335 945f9229 2022-04-16 thomas pcommit = NULL;
336 bee6b577 2018-09-19 stsp if (err) {
337 d1451975 2018-11-11 stsp if (err->code == GOT_ERR_NO_TREE_ENTRY) {
338 bee6b577 2018-09-19 stsp branches_differ = 1;
339 bee6b577 2018-09-19 stsp continue;
340 bee6b577 2018-09-19 stsp }
341 6dcdad08 2018-11-18 stsp free(merged_id);
342 6dcdad08 2018-11-18 stsp free(prev_id);
343 bee6b577 2018-09-19 stsp return err;
344 bee6b577 2018-09-19 stsp }
345 bee6b577 2018-09-19 stsp
346 bee6b577 2018-09-19 stsp if (prev_id) {
347 bee6b577 2018-09-19 stsp if (!branches_differ &&
348 d2c2d781 2018-11-18 stsp got_object_id_cmp(id, prev_id) != 0)
349 bee6b577 2018-09-19 stsp branches_differ = 1;
350 6dcdad08 2018-11-18 stsp free(prev_id);
351 6dcdad08 2018-11-18 stsp }
352 6dcdad08 2018-11-18 stsp prev_id = id;
353 bee6b577 2018-09-19 stsp
354 bee6b577 2018-09-19 stsp /*
355 bee6b577 2018-09-19 stsp * If a branch has created the merged content we can
356 bee6b577 2018-09-19 stsp * skip any other branches.
357 bee6b577 2018-09-19 stsp */
358 bee6b577 2018-09-19 stsp if (got_object_id_cmp(merged_id, id) == 0) {
359 b36429ab 2018-11-05 stsp err = got_object_idset_add(graph->open_branches,
360 ec242592 2022-04-22 thomas &qid->id, NULL);
361 6dcdad08 2018-11-18 stsp free(merged_id);
362 6dcdad08 2018-11-18 stsp free(id);
363 b36429ab 2018-11-05 stsp return err;
364 bee6b577 2018-09-19 stsp }
365 bee6b577 2018-09-19 stsp }
366 6dcdad08 2018-11-18 stsp
367 6dcdad08 2018-11-18 stsp free(prev_id);
368 6dcdad08 2018-11-18 stsp prev_id = NULL;
369 6dcdad08 2018-11-18 stsp free(merged_id);
370 6dcdad08 2018-11-18 stsp merged_id = NULL;
371 bee6b577 2018-09-19 stsp
372 bee6b577 2018-09-19 stsp /*
373 bee6b577 2018-09-19 stsp * If the path's content is the same on all branches,
374 bee6b577 2018-09-19 stsp * follow the first parent only.
375 bee6b577 2018-09-19 stsp */
376 bee6b577 2018-09-19 stsp if (!branches_differ) {
377 dbdddfee 2021-06-23 naddy qid = STAILQ_FIRST(&commit->parent_ids);
378 bee6b577 2018-09-19 stsp if (qid == NULL)
379 bee6b577 2018-09-19 stsp return NULL;
380 8e291695 2020-01-04 stsp if (got_object_idset_contains(graph->open_branches,
381 ec242592 2022-04-22 thomas &qid->id))
382 b36429ab 2018-11-05 stsp return NULL;
383 8e291695 2020-01-04 stsp if (got_object_idset_contains(graph->node_ids,
384 ec242592 2022-04-22 thomas &qid->id))
385 998ff57f 2018-11-18 stsp return NULL; /* parent already traversed */
386 b36429ab 2018-11-05 stsp return got_object_idset_add(graph->open_branches,
387 ec242592 2022-04-22 thomas &qid->id, NULL);
388 bee6b577 2018-09-19 stsp }
389 0ed6ed4c 2018-06-13 stsp }
390 0ed6ed4c 2018-06-13 stsp
391 dbdddfee 2021-06-23 naddy STAILQ_FOREACH(qid, &commit->parent_ids, entry) {
392 ec242592 2022-04-22 thomas if (got_object_idset_contains(graph->open_branches, &qid->id))
393 b36429ab 2018-11-05 stsp continue;
394 ec242592 2022-04-22 thomas if (got_object_idset_contains(graph->node_ids, &qid->id))
395 998ff57f 2018-11-18 stsp continue; /* parent already traversed */
396 ec242592 2022-04-22 thomas err = got_object_idset_add(graph->open_branches, &qid->id,
397 ec242592 2022-04-22 thomas NULL);
398 b36429ab 2018-11-05 stsp if (err)
399 0ed6ed4c 2018-06-13 stsp return err;
400 0ed6ed4c 2018-06-13 stsp }
401 0ed6ed4c 2018-06-13 stsp
402 b43fbaa0 2018-06-11 stsp return NULL;
403 de56b2d7 2020-01-04 stsp }
404 372ccdbb 2018-06-10 stsp
405 de56b2d7 2020-01-04 stsp const struct got_error *
406 c4d7a9c4 2018-06-11 stsp got_commit_graph_open(struct got_commit_graph **graph,
407 3d509237 2020-01-04 stsp const char *path, int first_parent_traversal)
408 3d509237 2020-01-04 stsp {
409 22220781 2020-01-04 stsp const struct got_error *err = NULL;
410 3ddcebf3 2020-01-04 stsp
411 3ddcebf3 2020-01-04 stsp *graph = calloc(1, sizeof(**graph));
412 3d509237 2020-01-04 stsp if (*graph == NULL)
413 3ddcebf3 2020-01-04 stsp return got_error_from_errno("calloc");
414 88cdb9c6 2020-01-04 stsp
415 88cdb9c6 2020-01-04 stsp TAILQ_INIT(&(*graph)->iter_list);
416 3ddcebf3 2020-01-04 stsp
417 3ddcebf3 2020-01-04 stsp (*graph)->path = strdup(path);
418 3ddcebf3 2020-01-04 stsp if ((*graph)->path == NULL) {
419 3ddcebf3 2020-01-04 stsp err = got_error_from_errno("strdup");
420 22220781 2020-01-04 stsp goto done;
421 3ddcebf3 2020-01-04 stsp }
422 3ddcebf3 2020-01-04 stsp
423 3ddcebf3 2020-01-04 stsp (*graph)->node_ids = got_object_idset_alloc();
424 3ddcebf3 2020-01-04 stsp if ((*graph)->node_ids == NULL) {
425 3ddcebf3 2020-01-04 stsp err = got_error_from_errno("got_object_idset_alloc");
426 22220781 2020-01-04 stsp goto done;
427 3ddcebf3 2020-01-04 stsp }
428 372ccdbb 2018-06-10 stsp
429 3ddcebf3 2020-01-04 stsp (*graph)->open_branches = got_object_idset_alloc();
430 3ddcebf3 2020-01-04 stsp if ((*graph)->open_branches == NULL) {
431 3ddcebf3 2020-01-04 stsp err = got_error_from_errno("got_object_idset_alloc");
432 22220781 2020-01-04 stsp goto done;
433 3ddcebf3 2020-01-04 stsp }
434 3ddcebf3 2020-01-04 stsp
435 0ed6ed4c 2018-06-13 stsp if (first_parent_traversal)
436 0ed6ed4c 2018-06-13 stsp (*graph)->flags |= GOT_COMMIT_GRAPH_FIRST_PARENT_TRAVERSAL;
437 22220781 2020-01-04 stsp done:
438 22220781 2020-01-04 stsp if (err) {
439 22220781 2020-01-04 stsp got_commit_graph_close(*graph);
440 22220781 2020-01-04 stsp *graph = NULL;
441 22220781 2020-01-04 stsp }
442 22220781 2020-01-04 stsp return err;
443 372ccdbb 2018-06-10 stsp }
444 372ccdbb 2018-06-10 stsp
445 32777563 2018-11-07 stsp struct add_branch_tip_arg {
446 b565f6f8 2018-07-23 stsp struct got_commit_graph_branch_tip *tips;
447 b565f6f8 2018-07-23 stsp int ntips;
448 32777563 2018-11-07 stsp struct got_repository *repo;
449 32777563 2018-11-07 stsp struct got_commit_graph *graph;
450 372ccdbb 2018-06-10 stsp };
451 372ccdbb 2018-06-10 stsp
452 cb103d04 2018-11-07 stsp static const struct got_error *
453 32777563 2018-11-07 stsp add_branch_tip(struct got_object_id *commit_id, void *data, void *arg)
454 372ccdbb 2018-06-10 stsp {
455 32777563 2018-11-07 stsp const struct got_error *err;
456 32777563 2018-11-07 stsp struct add_branch_tip_arg *a = arg;
457 32777563 2018-11-07 stsp struct got_commit_graph_node *new_node;
458 32777563 2018-11-07 stsp struct got_commit_object *commit;
459 32777563 2018-11-07 stsp
460 32777563 2018-11-07 stsp err = got_object_open_as_commit(&commit, a->repo, commit_id);
461 32777563 2018-11-07 stsp if (err)
462 32777563 2018-11-07 stsp return err;
463 32777563 2018-11-07 stsp
464 ca6e02ac 2020-01-07 stsp err = add_node(&new_node, a->graph, commit_id, a->repo);
465 07894e99 2022-09-04 thomas if (err) {
466 07894e99 2022-09-04 thomas got_object_commit_close(commit);
467 32777563 2018-11-07 stsp return err;
468 07894e99 2022-09-04 thomas }
469 32777563 2018-11-07 stsp
470 07894e99 2022-09-04 thomas a->tips[a->ntips].commit_id = &new_node->id;
471 32777563 2018-11-07 stsp a->tips[a->ntips].commit = commit;
472 32777563 2018-11-07 stsp a->tips[a->ntips].new_node = new_node;
473 b565f6f8 2018-07-23 stsp a->ntips++;
474 32777563 2018-11-07 stsp
475 cb103d04 2018-11-07 stsp return NULL;
476 372ccdbb 2018-06-10 stsp }
477 372ccdbb 2018-06-10 stsp
478 1142eae9 2018-06-11 stsp static const struct got_error *
479 57eecd46 2020-01-04 stsp fetch_commits_from_open_branches(struct got_commit_graph *graph,
480 6fb7cd11 2019-08-22 stsp struct got_repository *repo, got_cancel_cb cancel_cb, void *cancel_arg)
481 372ccdbb 2018-06-10 stsp {
482 372ccdbb 2018-06-10 stsp const struct got_error *err;
483 32777563 2018-11-07 stsp struct add_branch_tip_arg arg;
484 32777563 2018-11-07 stsp int i, ntips;
485 372ccdbb 2018-06-10 stsp
486 32777563 2018-11-07 stsp ntips = got_object_idset_num_elements(graph->open_branches);
487 32777563 2018-11-07 stsp if (ntips == 0)
488 372ccdbb 2018-06-10 stsp return NULL;
489 372ccdbb 2018-06-10 stsp
490 32777563 2018-11-07 stsp /* (Re-)allocate branch tips array if necessary. */
491 32777563 2018-11-07 stsp if (graph->ntips < ntips) {
492 b565f6f8 2018-07-23 stsp struct got_commit_graph_branch_tip *tips;
493 5e50c36a 2018-11-08 stsp tips = recallocarray(graph->tips, graph->ntips, ntips,
494 5e50c36a 2018-11-08 stsp sizeof(*tips));
495 b565f6f8 2018-07-23 stsp if (tips == NULL)
496 638f9024 2019-05-13 stsp return got_error_from_errno("recallocarray");
497 b565f6f8 2018-07-23 stsp graph->tips = tips;
498 32777563 2018-11-07 stsp graph->ntips = ntips;
499 b565f6f8 2018-07-23 stsp }
500 b565f6f8 2018-07-23 stsp arg.tips = graph->tips;
501 32777563 2018-11-07 stsp arg.ntips = 0; /* add_branch_tip() will increment */
502 32777563 2018-11-07 stsp arg.repo = repo;
503 32777563 2018-11-07 stsp arg.graph = graph;
504 32777563 2018-11-07 stsp err = got_object_idset_for_each(graph->open_branches, add_branch_tip,
505 32777563 2018-11-07 stsp &arg);
506 cb103d04 2018-11-07 stsp if (err)
507 32777563 2018-11-07 stsp goto done;
508 372ccdbb 2018-06-10 stsp
509 b565f6f8 2018-07-23 stsp for (i = 0; i < arg.ntips; i++) {
510 372ccdbb 2018-06-10 stsp struct got_object_id *commit_id;
511 41fa1437 2018-11-05 stsp struct got_commit_object *commit;
512 32777563 2018-11-07 stsp struct got_commit_graph_node *new_node;
513 13a851c1 2020-01-04 stsp int changed;
514 372ccdbb 2018-06-10 stsp
515 6fb7cd11 2019-08-22 stsp if (cancel_cb) {
516 6fb7cd11 2019-08-22 stsp err = (*cancel_cb)(cancel_arg);
517 6fb7cd11 2019-08-22 stsp if (err)
518 6fb7cd11 2019-08-22 stsp break;
519 6fb7cd11 2019-08-22 stsp }
520 6fb7cd11 2019-08-22 stsp
521 32777563 2018-11-07 stsp commit_id = arg.tips[i].commit_id;
522 32777563 2018-11-07 stsp commit = arg.tips[i].commit;
523 32777563 2018-11-07 stsp new_node = arg.tips[i].new_node;
524 de56b2d7 2020-01-04 stsp
525 13a851c1 2020-01-04 stsp err = detect_changed_path(&changed, commit, commit_id,
526 13a851c1 2020-01-04 stsp graph->path, repo);
527 13a851c1 2020-01-04 stsp if (err) {
528 13a851c1 2020-01-04 stsp if (err->code != GOT_ERR_NO_OBJ)
529 13a851c1 2020-01-04 stsp break;
530 13a851c1 2020-01-04 stsp /*
531 13a851c1 2020-01-04 stsp * History of the path stops here on the current
532 13a851c1 2020-01-04 stsp * branch. Keep going on other branches.
533 13a851c1 2020-01-04 stsp */
534 32c85d2c 2020-01-06 stsp err = close_branch(graph, commit_id);
535 32c85d2c 2020-01-06 stsp if (err)
536 32c85d2c 2020-01-06 stsp break;
537 ec1904dc 2020-01-04 stsp continue;
538 13a851c1 2020-01-04 stsp }
539 f3933be6 2022-09-04 thomas if (changed) {
540 ca6e02ac 2020-01-07 stsp add_node_to_iter_list(graph, new_node,
541 ca6e02ac 2020-01-07 stsp got_object_commit_get_committer_time(commit));
542 f3933be6 2022-09-04 thomas arg.tips[i].new_node = NULL;
543 f3933be6 2022-09-04 thomas }
544 14159a7b 2020-01-04 stsp err = advance_branch(graph, commit_id, commit, repo);
545 cb352812 2018-07-22 stsp if (err)
546 cb352812 2018-07-22 stsp break;
547 372ccdbb 2018-06-10 stsp }
548 32777563 2018-11-07 stsp done:
549 f3933be6 2022-09-04 thomas for (i = 0; i < arg.ntips; i++) {
550 32777563 2018-11-07 stsp got_object_commit_close(arg.tips[i].commit);
551 f3933be6 2022-09-04 thomas free(arg.tips[i].new_node);
552 f3933be6 2022-09-04 thomas }
553 372ccdbb 2018-06-10 stsp return err;
554 372ccdbb 2018-06-10 stsp }
555 372ccdbb 2018-06-10 stsp
556 372ccdbb 2018-06-10 stsp void
557 372ccdbb 2018-06-10 stsp got_commit_graph_close(struct got_commit_graph *graph)
558 372ccdbb 2018-06-10 stsp {
559 599785e8 2022-09-04 thomas struct got_commit_graph_node *node;
560 599785e8 2022-09-04 thomas
561 599785e8 2022-09-04 thomas while ((node = TAILQ_FIRST(&graph->iter_list))) {
562 599785e8 2022-09-04 thomas TAILQ_REMOVE(&graph->iter_list, node, entry);
563 827e685d 2024-03-30 thomas free(node->more_parents);
564 599785e8 2022-09-04 thomas free(node);
565 599785e8 2022-09-04 thomas }
566 599785e8 2022-09-04 thomas
567 22220781 2020-01-04 stsp if (graph->open_branches)
568 22220781 2020-01-04 stsp got_object_idset_free(graph->open_branches);
569 22220781 2020-01-04 stsp if (graph->node_ids)
570 22220781 2020-01-04 stsp got_object_idset_free(graph->node_ids);
571 b565f6f8 2018-07-23 stsp free(graph->tips);
572 31cedeaf 2018-09-15 stsp free(graph->path);
573 372ccdbb 2018-06-10 stsp free(graph);
574 372ccdbb 2018-06-10 stsp }
575 372ccdbb 2018-06-10 stsp
576 e06005c3 2024-03-30 thomas static const struct got_error *
577 e06005c3 2024-03-30 thomas remove_branch_tip(struct got_object_id *commit_id, void *data, void *arg)
578 e06005c3 2024-03-30 thomas {
579 e06005c3 2024-03-30 thomas struct got_object_idset *open_branches = arg;
580 e06005c3 2024-03-30 thomas
581 e06005c3 2024-03-30 thomas return got_object_idset_remove(NULL, open_branches, commit_id);
582 e06005c3 2024-03-30 thomas }
583 e06005c3 2024-03-30 thomas
584 372ccdbb 2018-06-10 stsp const struct got_error *
585 0279329d 2024-03-30 thomas got_commit_graph_bfsort(struct got_commit_graph *graph,
586 6fb7cd11 2019-08-22 stsp struct got_object_id *id, struct got_repository *repo,
587 6fb7cd11 2019-08-22 stsp got_cancel_cb cancel_cb, void *cancel_arg)
588 372ccdbb 2018-06-10 stsp {
589 31cedeaf 2018-09-15 stsp const struct got_error *err = NULL;
590 e06005c3 2024-03-30 thomas struct got_commit_graph_node *node;
591 827e685d 2024-03-30 thomas
592 827e685d 2024-03-30 thomas graph->flags &= ~GOT_COMMIT_GRAPH_TOPOSORT;
593 372ccdbb 2018-06-10 stsp
594 e06005c3 2024-03-30 thomas /* Clear left-over state from previous iteration attempts. */
595 e06005c3 2024-03-30 thomas while ((node = TAILQ_FIRST(&graph->iter_list)))
596 e06005c3 2024-03-30 thomas TAILQ_REMOVE(&graph->iter_list, node, entry);
597 e06005c3 2024-03-30 thomas err = got_object_idset_for_each(graph->open_branches,
598 e06005c3 2024-03-30 thomas remove_branch_tip, graph->open_branches);
599 e06005c3 2024-03-30 thomas if (err)
600 e06005c3 2024-03-30 thomas return err;
601 31cedeaf 2018-09-15 stsp
602 3ff3126d 2020-01-04 stsp err = got_object_idset_add(graph->open_branches, id, NULL);
603 3d509237 2020-01-04 stsp if (err)
604 7e33c8c5 2020-01-04 stsp return err;
605 3d509237 2020-01-04 stsp
606 3ff3126d 2020-01-04 stsp /* Locate first commit which changed graph->path. */
607 94489f7d 2020-01-04 stsp while (TAILQ_EMPTY(&graph->iter_list) &&
608 3ff3126d 2020-01-04 stsp got_object_idset_num_elements(graph->open_branches) > 0) {
609 3ff3126d 2020-01-04 stsp err = fetch_commits_from_open_branches(graph, repo,
610 3ff3126d 2020-01-04 stsp cancel_cb, cancel_arg);
611 3ff3126d 2020-01-04 stsp if (err)
612 7e33c8c5 2020-01-04 stsp return err;
613 5175b31a 2020-01-04 stsp }
614 5175b31a 2020-01-04 stsp
615 94489f7d 2020-01-04 stsp if (TAILQ_EMPTY(&graph->iter_list)) {
616 5175b31a 2020-01-04 stsp const char *path;
617 5175b31a 2020-01-04 stsp if (got_path_is_root_dir(graph->path))
618 5175b31a 2020-01-04 stsp return got_error_no_obj(id);
619 5175b31a 2020-01-04 stsp path = graph->path;
620 5175b31a 2020-01-04 stsp while (path[0] == '/')
621 5175b31a 2020-01-04 stsp path++;
622 5175b31a 2020-01-04 stsp return got_error_path(path, GOT_ERR_NO_TREE_ENTRY);
623 31cedeaf 2018-09-15 stsp }
624 7e33c8c5 2020-01-04 stsp
625 7e33c8c5 2020-01-04 stsp return NULL;
626 372ccdbb 2018-06-10 stsp }
627 372ccdbb 2018-06-10 stsp
628 372ccdbb 2018-06-10 stsp const struct got_error *
629 7210b715 2022-09-11 thomas got_commit_graph_iter_next(struct got_object_id *id,
630 ee780d5c 2020-01-04 stsp struct got_commit_graph *graph, struct got_repository *repo,
631 ee780d5c 2020-01-04 stsp got_cancel_cb cancel_cb, void *cancel_arg)
632 372ccdbb 2018-06-10 stsp {
633 ee780d5c 2020-01-04 stsp const struct got_error *err = NULL;
634 827e685d 2024-03-30 thomas struct got_commit_graph_node *node, *pnode;
635 827e685d 2024-03-30 thomas int i;
636 372ccdbb 2018-06-10 stsp
637 df8cd9c6 2020-01-05 stsp node = TAILQ_FIRST(&graph->iter_list);
638 df8cd9c6 2020-01-05 stsp if (node == NULL) {
639 2c7f8870 2018-09-19 stsp /* We are done iterating, or iteration was not started. */
640 9ba79e04 2018-06-11 stsp return got_error(GOT_ERR_ITER_COMPLETED);
641 9ba79e04 2018-06-11 stsp }
642 9ba79e04 2018-06-11 stsp
643 827e685d 2024-03-30 thomas if (graph->flags & GOT_COMMIT_GRAPH_TOPOSORT) {
644 827e685d 2024-03-30 thomas /* At least one node with in-degree zero must exist. */
645 827e685d 2024-03-30 thomas while (node->indegree != 0)
646 827e685d 2024-03-30 thomas node = TAILQ_NEXT(node, entry);
647 827e685d 2024-03-30 thomas } else {
648 827e685d 2024-03-30 thomas while (TAILQ_NEXT(node, entry) == NULL &&
649 827e685d 2024-03-30 thomas got_object_idset_num_elements(graph->open_branches) > 0) {
650 827e685d 2024-03-30 thomas err = fetch_commits_from_open_branches(graph, repo,
651 827e685d 2024-03-30 thomas cancel_cb, cancel_arg);
652 827e685d 2024-03-30 thomas if (err)
653 827e685d 2024-03-30 thomas return err;
654 827e685d 2024-03-30 thomas }
655 ee780d5c 2020-01-04 stsp }
656 372ccdbb 2018-06-10 stsp
657 7210b715 2022-09-11 thomas memcpy(id, &node->id, sizeof(*id));
658 d68b9737 2022-09-05 thomas
659 94489f7d 2020-01-04 stsp TAILQ_REMOVE(&graph->iter_list, node, entry);
660 827e685d 2024-03-30 thomas if (graph->flags & GOT_COMMIT_GRAPH_TOPOSORT) {
661 827e685d 2024-03-30 thomas /* When visiting a commit decrement in-degree of all parents. */
662 827e685d 2024-03-30 thomas for (i = 0; i < node->nparents; i++) {
663 827e685d 2024-03-30 thomas if (i < nitems(node->parents))
664 827e685d 2024-03-30 thomas pnode = node->parents[i];
665 827e685d 2024-03-30 thomas else
666 827e685d 2024-03-30 thomas pnode = node->more_parents[i];
667 827e685d 2024-03-30 thomas pnode->indegree--;
668 827e685d 2024-03-30 thomas }
669 827e685d 2024-03-30 thomas }
670 d68b9737 2022-09-05 thomas free(node);
671 372ccdbb 2018-06-10 stsp return NULL;
672 7210b715 2022-09-11 thomas }
673 7210b715 2022-09-11 thomas
674 7210b715 2022-09-11 thomas static const struct got_error *
675 7210b715 2022-09-11 thomas find_yca_add_id(struct got_object_id **yca_id, struct got_commit_graph *graph,
676 7210b715 2022-09-11 thomas struct got_object_idset *commit_ids, struct got_repository *repo,
677 7210b715 2022-09-11 thomas got_cancel_cb cancel_cb, void *cancel_arg)
678 7210b715 2022-09-11 thomas {
679 7210b715 2022-09-11 thomas const struct got_error *err = NULL;
680 7210b715 2022-09-11 thomas struct got_object_id id;
681 7210b715 2022-09-11 thomas
682 7210b715 2022-09-11 thomas err = got_commit_graph_iter_next(&id, graph, repo, cancel_cb,
683 7210b715 2022-09-11 thomas cancel_arg);
684 7210b715 2022-09-11 thomas if (err)
685 7210b715 2022-09-11 thomas return err;
686 7210b715 2022-09-11 thomas
687 7210b715 2022-09-11 thomas if (got_object_idset_contains(commit_ids, &id)) {
688 7210b715 2022-09-11 thomas *yca_id = got_object_id_dup(&id);
689 7210b715 2022-09-11 thomas if (*yca_id == NULL)
690 7210b715 2022-09-11 thomas err = got_error_from_errno("got_object_id_dup");
691 7210b715 2022-09-11 thomas return err;
692 7210b715 2022-09-11 thomas }
693 7210b715 2022-09-11 thomas
694 7210b715 2022-09-11 thomas return got_object_idset_add(commit_ids, &id, NULL);
695 372ccdbb 2018-06-10 stsp }
696 a9833bc9 2019-05-13 stsp
697 a4c8ed77 2023-05-25 thomas /*
698 a4c8ed77 2023-05-25 thomas * Sets *yca_id to the youngest common ancestor of commit_id and
699 a4c8ed77 2023-05-25 thomas * commit_id2. Returns got_error(GOT_ERR_ANCESTRY) if they have no
700 a4c8ed77 2023-05-25 thomas * common ancestors.
701 a4c8ed77 2023-05-25 thomas *
702 a4c8ed77 2023-05-25 thomas * If first_parent_traversal is nonzero, only linear history is considered.
703 807c60e9 2024-03-30 thomas * If toposort is set then sort commits in topological order before
704 807c60e9 2024-03-30 thomas * traversing them.
705 a4c8ed77 2023-05-25 thomas */
706 a9833bc9 2019-05-13 stsp const struct got_error *
707 a9833bc9 2019-05-13 stsp got_commit_graph_find_youngest_common_ancestor(struct got_object_id **yca_id,
708 a9833bc9 2019-05-13 stsp struct got_object_id *commit_id, struct got_object_id *commit_id2,
709 807c60e9 2024-03-30 thomas int first_parent_traversal, int toposort, struct got_repository *repo,
710 807c60e9 2024-03-30 thomas got_cancel_cb cancel_cb, void *cancel_arg)
711 a9833bc9 2019-05-13 stsp {
712 a9833bc9 2019-05-13 stsp const struct got_error *err = NULL;
713 a9833bc9 2019-05-13 stsp struct got_commit_graph *graph = NULL, *graph2 = NULL;
714 a9833bc9 2019-05-13 stsp int completed = 0, completed2 = 0;
715 a9833bc9 2019-05-13 stsp struct got_object_idset *commit_ids;
716 a9833bc9 2019-05-13 stsp
717 a9833bc9 2019-05-13 stsp *yca_id = NULL;
718 a9833bc9 2019-05-13 stsp
719 a9833bc9 2019-05-13 stsp commit_ids = got_object_idset_alloc();
720 a9833bc9 2019-05-13 stsp if (commit_ids == NULL)
721 638f9024 2019-05-13 stsp return got_error_from_errno("got_object_idset_alloc");
722 a9833bc9 2019-05-13 stsp
723 768705e3 2021-09-27 thomas err = got_commit_graph_open(&graph, "/", first_parent_traversal);
724 a9833bc9 2019-05-13 stsp if (err)
725 a9833bc9 2019-05-13 stsp goto done;
726 a9833bc9 2019-05-13 stsp
727 768705e3 2021-09-27 thomas err = got_commit_graph_open(&graph2, "/", first_parent_traversal);
728 a9833bc9 2019-05-13 stsp if (err)
729 a9833bc9 2019-05-13 stsp goto done;
730 a9833bc9 2019-05-13 stsp
731 807c60e9 2024-03-30 thomas if (toposort) {
732 807c60e9 2024-03-30 thomas err = got_commit_graph_toposort(graph, commit_id, repo,
733 807c60e9 2024-03-30 thomas cancel_cb, cancel_arg);
734 807c60e9 2024-03-30 thomas if (err)
735 807c60e9 2024-03-30 thomas goto done;
736 a9833bc9 2019-05-13 stsp
737 807c60e9 2024-03-30 thomas err = got_commit_graph_toposort(graph2, commit_id2, repo,
738 807c60e9 2024-03-30 thomas cancel_cb, cancel_arg);
739 807c60e9 2024-03-30 thomas if (err)
740 807c60e9 2024-03-30 thomas goto done;
741 807c60e9 2024-03-30 thomas } else {
742 0279329d 2024-03-30 thomas err = got_commit_graph_bfsort(graph, commit_id, repo,
743 807c60e9 2024-03-30 thomas cancel_cb, cancel_arg);
744 807c60e9 2024-03-30 thomas if (err)
745 807c60e9 2024-03-30 thomas goto done;
746 a9833bc9 2019-05-13 stsp
747 0279329d 2024-03-30 thomas err = got_commit_graph_bfsort(graph2, commit_id2, repo,
748 807c60e9 2024-03-30 thomas cancel_cb, cancel_arg);
749 807c60e9 2024-03-30 thomas if (err)
750 807c60e9 2024-03-30 thomas goto done;
751 807c60e9 2024-03-30 thomas }
752 807c60e9 2024-03-30 thomas
753 a9833bc9 2019-05-13 stsp for (;;) {
754 6fb7cd11 2019-08-22 stsp if (cancel_cb) {
755 6fb7cd11 2019-08-22 stsp err = (*cancel_cb)(cancel_arg);
756 6fb7cd11 2019-08-22 stsp if (err)
757 6fb7cd11 2019-08-22 stsp break;
758 6fb7cd11 2019-08-22 stsp }
759 6fb7cd11 2019-08-22 stsp
760 a9833bc9 2019-05-13 stsp if (!completed) {
761 7210b715 2022-09-11 thomas err = find_yca_add_id(yca_id, graph, commit_ids, repo,
762 ee780d5c 2020-01-04 stsp cancel_cb, cancel_arg);
763 a9833bc9 2019-05-13 stsp if (err) {
764 ee780d5c 2020-01-04 stsp if (err->code != GOT_ERR_ITER_COMPLETED)
765 a9833bc9 2019-05-13 stsp break;
766 ee780d5c 2020-01-04 stsp err = NULL;
767 ee780d5c 2020-01-04 stsp completed = 1;
768 a9833bc9 2019-05-13 stsp }
769 7210b715 2022-09-11 thomas if (*yca_id)
770 7210b715 2022-09-11 thomas break;
771 a9833bc9 2019-05-13 stsp }
772 a9833bc9 2019-05-13 stsp
773 a9833bc9 2019-05-13 stsp if (!completed2) {
774 7210b715 2022-09-11 thomas err = find_yca_add_id(yca_id, graph2, commit_ids, repo,
775 ee780d5c 2020-01-04 stsp cancel_cb, cancel_arg);
776 a9833bc9 2019-05-13 stsp if (err) {
777 ee780d5c 2020-01-04 stsp if (err->code != GOT_ERR_ITER_COMPLETED)
778 a9833bc9 2019-05-13 stsp break;
779 ee780d5c 2020-01-04 stsp err = NULL;
780 ee780d5c 2020-01-04 stsp completed2 = 1;
781 a9833bc9 2019-05-13 stsp }
782 7210b715 2022-09-11 thomas if (*yca_id)
783 a9833bc9 2019-05-13 stsp break;
784 a9833bc9 2019-05-13 stsp }
785 a9833bc9 2019-05-13 stsp
786 a9833bc9 2019-05-13 stsp if (completed && completed2) {
787 a9833bc9 2019-05-13 stsp err = got_error(GOT_ERR_ANCESTRY);
788 a9833bc9 2019-05-13 stsp break;
789 a9833bc9 2019-05-13 stsp }
790 a9833bc9 2019-05-13 stsp }
791 a9833bc9 2019-05-13 stsp done:
792 a9833bc9 2019-05-13 stsp got_object_idset_free(commit_ids);
793 a9833bc9 2019-05-13 stsp if (graph)
794 a9833bc9 2019-05-13 stsp got_commit_graph_close(graph);
795 a9833bc9 2019-05-13 stsp if (graph2)
796 a9833bc9 2019-05-13 stsp got_commit_graph_close(graph2);
797 827e685d 2024-03-30 thomas return err;
798 827e685d 2024-03-30 thomas }
799 827e685d 2024-03-30 thomas
800 827e685d 2024-03-30 thomas /*
801 827e685d 2024-03-30 thomas * Sort the graph for traversal in topological order.
802 827e685d 2024-03-30 thomas *
803 827e685d 2024-03-30 thomas * This implementation is based on the description of topological sorting
804 827e685d 2024-03-30 thomas * of git commits by Derrick Stolee at
805 827e685d 2024-03-30 thomas * https://github.blog/2022-08-30-gits-database-internals-ii-commit-history-queries/#topological-sorting
806 827e685d 2024-03-30 thomas * which reads as follows:
807 827e685d 2024-03-30 thomas *
808 827e685d 2024-03-30 thomas * The basic algorithm for topological sorting is Kahn’s algorithm which
809 827e685d 2024-03-30 thomas * follows two big steps:
810 827e685d 2024-03-30 thomas * 1. Walk all reachable commits, counting the number of times a commit appears
811 827e685d 2024-03-30 thomas * as a parent of another commit. Call these numbers the in-degree of the
812 827e685d 2024-03-30 thomas * commit, referencing the number of incoming edges.
813 827e685d 2024-03-30 thomas * 2. Walk the reachable commits, but only visit a commit if its in-degree
814 827e685d 2024-03-30 thomas * value is zero. When visiting a commit, decrement the in-degree value of
815 827e685d 2024-03-30 thomas * each parent.
816 827e685d 2024-03-30 thomas *
817 827e685d 2024-03-30 thomas * This algorithm works because at least one of our starting points will
818 827e685d 2024-03-30 thomas * have in-degree zero, and then decrementing the in-degree value is similar
819 827e685d 2024-03-30 thomas * to deleting the commit from the graph, always having at least one commit
820 827e685d 2024-03-30 thomas * with in-degree zero.
821 827e685d 2024-03-30 thomas */
822 827e685d 2024-03-30 thomas const struct got_error *
823 827e685d 2024-03-30 thomas got_commit_graph_toposort(struct got_commit_graph *graph,
824 827e685d 2024-03-30 thomas struct got_object_id *id, struct got_repository *repo,
825 827e685d 2024-03-30 thomas got_cancel_cb cancel_cb, void *cancel_arg)
826 827e685d 2024-03-30 thomas {
827 827e685d 2024-03-30 thomas const struct got_error *err = NULL;
828 827e685d 2024-03-30 thomas struct got_commit_graph_node *node = NULL, *pnode = NULL;
829 827e685d 2024-03-30 thomas struct got_commit_object *commit = NULL;
830 827e685d 2024-03-30 thomas struct got_object_id_queue commits;
831 827e685d 2024-03-30 thomas const struct got_object_id_queue *parent_ids;
832 827e685d 2024-03-30 thomas struct got_object_qid *qid = NULL, *pid;
833 827e685d 2024-03-30 thomas int i;
834 827e685d 2024-03-30 thomas
835 827e685d 2024-03-30 thomas STAILQ_INIT(&commits);
836 827e685d 2024-03-30 thomas
837 827e685d 2024-03-30 thomas if (graph->flags & GOT_COMMIT_GRAPH_FIRST_PARENT_TRAVERSAL)
838 0279329d 2024-03-30 thomas return got_commit_graph_bfsort(graph, id, repo,
839 827e685d 2024-03-30 thomas cancel_cb, cancel_arg);
840 827e685d 2024-03-30 thomas
841 827e685d 2024-03-30 thomas /* Clear left-over state from previous iteration attempts. */
842 827e685d 2024-03-30 thomas while ((node = TAILQ_FIRST(&graph->iter_list)))
843 827e685d 2024-03-30 thomas TAILQ_REMOVE(&graph->iter_list, node, entry);
844 827e685d 2024-03-30 thomas err = got_object_idset_for_each(graph->open_branches,
845 827e685d 2024-03-30 thomas remove_branch_tip, graph->open_branches);
846 827e685d 2024-03-30 thomas if (err)
847 827e685d 2024-03-30 thomas return err;
848 827e685d 2024-03-30 thomas
849 827e685d 2024-03-30 thomas graph->flags |= GOT_COMMIT_GRAPH_TOPOSORT;
850 827e685d 2024-03-30 thomas
851 827e685d 2024-03-30 thomas /*
852 827e685d 2024-03-30 thomas * Sorting the commit graph in topological order requires visiting
853 827e685d 2024-03-30 thomas * every reachable commit. This is very expensive but there are
854 827e685d 2024-03-30 thomas * ways to speed this up significantly in the future:
855 827e685d 2024-03-30 thomas * 1) Run this loop in got-read-pack if possible.
856 827e685d 2024-03-30 thomas * 2) Use Git's commit-graph file to compute the result incrementally.
857 827e685d 2024-03-30 thomas * See the blog post linked above for details.
858 827e685d 2024-03-30 thomas */
859 827e685d 2024-03-30 thomas err = got_object_qid_alloc_partial(&qid);
860 827e685d 2024-03-30 thomas if (err)
861 827e685d 2024-03-30 thomas return err;
862 827e685d 2024-03-30 thomas memcpy(&qid->id, id, sizeof(qid->id));
863 827e685d 2024-03-30 thomas STAILQ_INSERT_TAIL(&commits, qid, entry);
864 827e685d 2024-03-30 thomas while (!STAILQ_EMPTY(&commits)) {
865 827e685d 2024-03-30 thomas if (cancel_cb) {
866 827e685d 2024-03-30 thomas err = (*cancel_cb)(cancel_arg);
867 827e685d 2024-03-30 thomas if (err)
868 827e685d 2024-03-30 thomas break;
869 827e685d 2024-03-30 thomas }
870 827e685d 2024-03-30 thomas
871 827e685d 2024-03-30 thomas qid = STAILQ_FIRST(&commits);
872 827e685d 2024-03-30 thomas STAILQ_REMOVE_HEAD(&commits, entry);
873 827e685d 2024-03-30 thomas err = got_object_open_as_commit(&commit, repo, &qid->id);
874 827e685d 2024-03-30 thomas if (err)
875 827e685d 2024-03-30 thomas break;
876 827e685d 2024-03-30 thomas
877 827e685d 2024-03-30 thomas node = got_object_idset_get(graph->node_ids, &qid->id);
878 827e685d 2024-03-30 thomas if (node == NULL) {
879 827e685d 2024-03-30 thomas err = add_node(&node, graph, id, repo);
880 827e685d 2024-03-30 thomas if (err)
881 827e685d 2024-03-30 thomas break;
882 827e685d 2024-03-30 thomas TAILQ_INSERT_TAIL(&graph->iter_list, node, entry);
883 827e685d 2024-03-30 thomas }
884 827e685d 2024-03-30 thomas
885 827e685d 2024-03-30 thomas got_object_qid_free(qid);
886 827e685d 2024-03-30 thomas qid = NULL;
887 827e685d 2024-03-30 thomas
888 827e685d 2024-03-30 thomas if (node->timestamp != 0) /* already traversed once */
889 827e685d 2024-03-30 thomas continue;
890 827e685d 2024-03-30 thomas
891 827e685d 2024-03-30 thomas if (node->nparents == -1) {
892 827e685d 2024-03-30 thomas node->nparents = got_object_commit_get_nparents(commit);
893 827e685d 2024-03-30 thomas if (node->nparents > nitems(node->parents)) {
894 827e685d 2024-03-30 thomas node->more_parents = calloc(node->nparents,
895 827e685d 2024-03-30 thomas sizeof(*node->more_parents));
896 827e685d 2024-03-30 thomas if (node->more_parents == NULL) {
897 827e685d 2024-03-30 thomas err = got_error_from_errno("calloc");
898 827e685d 2024-03-30 thomas break;
899 827e685d 2024-03-30 thomas }
900 827e685d 2024-03-30 thomas }
901 827e685d 2024-03-30 thomas
902 827e685d 2024-03-30 thomas }
903 827e685d 2024-03-30 thomas
904 827e685d 2024-03-30 thomas node->timestamp = got_object_commit_get_committer_time(commit);
905 827e685d 2024-03-30 thomas parent_ids = got_object_commit_get_parent_ids(commit);
906 827e685d 2024-03-30 thomas i = 0;
907 827e685d 2024-03-30 thomas STAILQ_FOREACH(pid, parent_ids, entry) {
908 827e685d 2024-03-30 thomas if (cancel_cb) {
909 827e685d 2024-03-30 thomas err = (*cancel_cb)(cancel_arg);
910 827e685d 2024-03-30 thomas if (err)
911 827e685d 2024-03-30 thomas goto done;
912 827e685d 2024-03-30 thomas }
913 827e685d 2024-03-30 thomas
914 827e685d 2024-03-30 thomas /*
915 827e685d 2024-03-30 thomas * Increment the in-degree counter every time a given
916 827e685d 2024-03-30 thomas * commit appears as the parent of another commit.
917 827e685d 2024-03-30 thomas */
918 827e685d 2024-03-30 thomas pnode = got_object_idset_get(graph->node_ids, &pid->id);
919 827e685d 2024-03-30 thomas if (pnode == NULL) {
920 827e685d 2024-03-30 thomas err = add_node(&pnode, graph, &pid->id, repo);
921 827e685d 2024-03-30 thomas if (err)
922 827e685d 2024-03-30 thomas goto done;
923 827e685d 2024-03-30 thomas TAILQ_INSERT_TAIL(&graph->iter_list, pnode,
924 827e685d 2024-03-30 thomas entry);
925 827e685d 2024-03-30 thomas }
926 827e685d 2024-03-30 thomas pnode->indegree++;
927 827e685d 2024-03-30 thomas
928 827e685d 2024-03-30 thomas /*
929 827e685d 2024-03-30 thomas * Cache parent pointers on the node to make future
930 827e685d 2024-03-30 thomas * in-degree updates easier.
931 827e685d 2024-03-30 thomas */
932 827e685d 2024-03-30 thomas if (node->nparents <= nitems(node->parents)) {
933 827e685d 2024-03-30 thomas node->parents[i] = pnode;
934 827e685d 2024-03-30 thomas } else {
935 827e685d 2024-03-30 thomas node->more_parents[i] = pnode;
936 827e685d 2024-03-30 thomas if (i < nitems(node->parents))
937 827e685d 2024-03-30 thomas node->parents[i] = pnode;
938 827e685d 2024-03-30 thomas }
939 827e685d 2024-03-30 thomas i++;
940 827e685d 2024-03-30 thomas
941 827e685d 2024-03-30 thomas /* Keep traversing through all parent commits. */
942 827e685d 2024-03-30 thomas err = got_object_qid_alloc_partial(&qid);
943 827e685d 2024-03-30 thomas if (err)
944 827e685d 2024-03-30 thomas goto done;
945 827e685d 2024-03-30 thomas memcpy(&qid->id, &pid->id, sizeof(qid->id));
946 827e685d 2024-03-30 thomas STAILQ_INSERT_TAIL(&commits, qid, entry);
947 827e685d 2024-03-30 thomas qid = NULL;
948 827e685d 2024-03-30 thomas }
949 827e685d 2024-03-30 thomas
950 827e685d 2024-03-30 thomas got_object_commit_close(commit);
951 827e685d 2024-03-30 thomas commit = NULL;
952 827e685d 2024-03-30 thomas }
953 827e685d 2024-03-30 thomas done:
954 827e685d 2024-03-30 thomas if (commit)
955 827e685d 2024-03-30 thomas got_object_commit_close(commit);
956 827e685d 2024-03-30 thomas got_object_qid_free(qid);
957 827e685d 2024-03-30 thomas got_object_id_queue_free(&commits);
958 a9833bc9 2019-05-13 stsp return err;
959 a9833bc9 2019-05-13 stsp }