Blame


1 9139e004 2023-07-17 thomas /*
2 9139e004 2023-07-17 thomas * Copyright (c) 2023 Mark Jamsek <mark@jamsek.dev>
3 9139e004 2023-07-17 thomas *
4 9139e004 2023-07-17 thomas * Permission to use, copy, modify, and distribute this software for any
5 9139e004 2023-07-17 thomas * purpose with or without fee is hereby granted, provided that the above
6 9139e004 2023-07-17 thomas * copyright notice and this permission notice appear in all copies.
7 9139e004 2023-07-17 thomas *
8 9139e004 2023-07-17 thomas * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 9139e004 2023-07-17 thomas * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 9139e004 2023-07-17 thomas * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 9139e004 2023-07-17 thomas * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 9139e004 2023-07-17 thomas * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 9139e004 2023-07-17 thomas * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 9139e004 2023-07-17 thomas * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 9139e004 2023-07-17 thomas */
16 481d9ea6 2023-07-17 thomas
17 481d9ea6 2023-07-17 thomas #include "got_compat.h"
18 9139e004 2023-07-17 thomas
19 9139e004 2023-07-17 thomas #include <sys/queue.h>
20 9139e004 2023-07-17 thomas
21 9139e004 2023-07-17 thomas #include <ctype.h>
22 9139e004 2023-07-17 thomas #include <limits.h>
23 9139e004 2023-07-17 thomas #include <stddef.h>
24 9139e004 2023-07-17 thomas #include <stdio.h>
25 9139e004 2023-07-17 thomas #include <stdlib.h>
26 9139e004 2023-07-17 thomas #include <string.h>
27 9139e004 2023-07-17 thomas
28 9139e004 2023-07-17 thomas #include "got_reference.h"
29 9139e004 2023-07-17 thomas #include "got_error.h"
30 9139e004 2023-07-17 thomas #include "got_object.h"
31 9139e004 2023-07-17 thomas #include "got_repository.h"
32 9139e004 2023-07-17 thomas #include "got_cancel.h"
33 9139e004 2023-07-17 thomas #include "got_worktree.h"
34 9139e004 2023-07-17 thomas #include "got_commit_graph.h"
35 9139e004 2023-07-17 thomas #include "got_keyword.h"
36 9139e004 2023-07-17 thomas
37 9139e004 2023-07-17 thomas struct keyword_mod {
38 9139e004 2023-07-17 thomas char *kw;
39 9139e004 2023-07-17 thomas uint64_t n;
40 9139e004 2023-07-17 thomas uint8_t sym;
41 9139e004 2023-07-17 thomas uint8_t iskeyword;
42 9139e004 2023-07-17 thomas uint8_t ismodified;
43 9139e004 2023-07-17 thomas };
44 9139e004 2023-07-17 thomas
45 9139e004 2023-07-17 thomas #define GOT_KEYWORD_DESCENDANT '+'
46 9139e004 2023-07-17 thomas #define GOT_KEYWORD_ANCESTOR '-'
47 9139e004 2023-07-17 thomas
48 9139e004 2023-07-17 thomas static const struct got_error *
49 9139e004 2023-07-17 thomas parse_keyword(struct keyword_mod *kwm, const char *keyword)
50 9139e004 2023-07-17 thomas {
51 9139e004 2023-07-17 thomas const char *kw;
52 9139e004 2023-07-17 thomas char *p;
53 9139e004 2023-07-17 thomas
54 9139e004 2023-07-17 thomas if (keyword == NULL)
55 9139e004 2023-07-17 thomas return NULL;
56 9139e004 2023-07-17 thomas
57 9139e004 2023-07-17 thomas /* check if it is a (modified) keyword or modified reference */
58 9139e004 2023-07-17 thomas if (*keyword == ':') {
59 9139e004 2023-07-17 thomas kwm->iskeyword = 1;
60 9139e004 2023-07-17 thomas kw = keyword + 1;
61 9139e004 2023-07-17 thomas } else
62 9139e004 2023-07-17 thomas kw = keyword;
63 9139e004 2023-07-17 thomas
64 9139e004 2023-07-17 thomas kwm->kw = strdup(kw);
65 9139e004 2023-07-17 thomas if (kwm->kw == NULL)
66 9139e004 2023-07-17 thomas return got_error_from_errno("strdup");
67 9139e004 2023-07-17 thomas
68 9139e004 2023-07-17 thomas p = strchr(kwm->kw, ':');
69 9139e004 2023-07-17 thomas
70 9139e004 2023-07-17 thomas if (p != NULL) {
71 9139e004 2023-07-17 thomas *p = '\0';
72 9139e004 2023-07-17 thomas ++p;
73 9139e004 2023-07-17 thomas if (*p != GOT_KEYWORD_DESCENDANT && *p != GOT_KEYWORD_ANCESTOR)
74 9139e004 2023-07-17 thomas return got_error_fmt(GOT_ERR_BAD_KEYWORD,
75 9139e004 2023-07-17 thomas "'%s'", keyword);
76 9139e004 2023-07-17 thomas
77 9139e004 2023-07-17 thomas kwm->ismodified = 1;
78 9139e004 2023-07-17 thomas kwm->sym = *p;
79 9139e004 2023-07-17 thomas ++p;
80 9139e004 2023-07-17 thomas
81 9139e004 2023-07-17 thomas if (*p) {
82 9139e004 2023-07-17 thomas const char *errstr;
83 9139e004 2023-07-17 thomas long long n;
84 9139e004 2023-07-17 thomas
85 9139e004 2023-07-17 thomas n = strtonum(p, 0, LLONG_MAX, &errstr);
86 9139e004 2023-07-17 thomas if (errstr != NULL)
87 9139e004 2023-07-17 thomas return got_error_fmt(GOT_ERR_BAD_KEYWORD,
88 9139e004 2023-07-17 thomas "'%s'", keyword);
89 9139e004 2023-07-17 thomas
90 9139e004 2023-07-17 thomas kwm->n = n;
91 9139e004 2023-07-17 thomas } else
92 9139e004 2023-07-17 thomas kwm->n = 1; /* :(+/-) == :(+/-)1 */
93 9139e004 2023-07-17 thomas }
94 9139e004 2023-07-17 thomas
95 9139e004 2023-07-17 thomas return NULL;
96 9139e004 2023-07-17 thomas }
97 9139e004 2023-07-17 thomas
98 9139e004 2023-07-17 thomas const struct got_error *
99 9139e004 2023-07-17 thomas got_keyword_to_idstr(char **ret, const char *keyword,
100 9139e004 2023-07-17 thomas struct got_repository *repo, struct got_worktree *wt)
101 9139e004 2023-07-17 thomas {
102 9139e004 2023-07-17 thomas const struct got_error *err = NULL;
103 9139e004 2023-07-17 thomas struct got_commit_graph *graph = NULL;
104 9139e004 2023-07-17 thomas struct got_object_id *head_id = NULL, *kwid = NULL;
105 9139e004 2023-07-17 thomas struct got_object_id iter_id;
106 9139e004 2023-07-17 thomas struct got_reflist_head refs;
107 9139e004 2023-07-17 thomas struct got_object_id_queue commits;
108 9139e004 2023-07-17 thomas struct got_object_qid *qid;
109 9139e004 2023-07-17 thomas struct keyword_mod kwm;
110 9139e004 2023-07-17 thomas const char *kw = NULL;
111 9139e004 2023-07-17 thomas char *kwid_str = NULL;
112 9139e004 2023-07-17 thomas uint64_t n = 0;
113 9139e004 2023-07-17 thomas
114 9139e004 2023-07-17 thomas *ret = NULL;
115 9139e004 2023-07-17 thomas TAILQ_INIT(&refs);
116 9139e004 2023-07-17 thomas STAILQ_INIT(&commits);
117 9139e004 2023-07-17 thomas memset(&kwm, 0, sizeof(kwm));
118 9139e004 2023-07-17 thomas
119 9139e004 2023-07-17 thomas err = parse_keyword(&kwm, keyword);
120 9139e004 2023-07-17 thomas if (err != NULL)
121 9139e004 2023-07-17 thomas goto done;
122 9139e004 2023-07-17 thomas
123 9139e004 2023-07-17 thomas kw = kwm.kw;
124 9139e004 2023-07-17 thomas
125 9139e004 2023-07-17 thomas if (kwm.iskeyword) {
126 9139e004 2023-07-17 thomas if (strcmp(kw, GOT_KEYWORD_BASE) == 0) {
127 9139e004 2023-07-17 thomas if (wt == NULL) {
128 9139e004 2023-07-17 thomas err = got_error_msg(GOT_ERR_NOT_WORKTREE,
129 9139e004 2023-07-17 thomas "'-c :base' requires work tree");
130 9139e004 2023-07-17 thomas goto done;
131 9139e004 2023-07-17 thomas }
132 9139e004 2023-07-17 thomas
133 9139e004 2023-07-17 thomas err = got_object_id_str(&kwid_str,
134 9139e004 2023-07-17 thomas got_worktree_get_base_commit_id(wt));
135 9139e004 2023-07-17 thomas if (err != NULL)
136 9139e004 2023-07-17 thomas goto done;
137 9139e004 2023-07-17 thomas } else if (strcmp(kw, GOT_KEYWORD_HEAD) == 0) {
138 9139e004 2023-07-17 thomas struct got_reference *head_ref;
139 9139e004 2023-07-17 thomas
140 9139e004 2023-07-17 thomas err = got_ref_open(&head_ref, repo, wt != NULL ?
141 9139e004 2023-07-17 thomas got_worktree_get_head_ref_name(wt) :
142 9139e004 2023-07-17 thomas GOT_REF_HEAD, 0);
143 9139e004 2023-07-17 thomas if (err != NULL)
144 9139e004 2023-07-17 thomas goto done;
145 9139e004 2023-07-17 thomas
146 9139e004 2023-07-17 thomas kwid_str = got_ref_to_str(head_ref);
147 9139e004 2023-07-17 thomas got_ref_close(head_ref);
148 9139e004 2023-07-17 thomas if (kwid_str == NULL) {
149 9139e004 2023-07-17 thomas err = got_error_from_errno("got_ref_to_str");
150 9139e004 2023-07-17 thomas goto done;
151 9139e004 2023-07-17 thomas }
152 9139e004 2023-07-17 thomas } else {
153 9139e004 2023-07-17 thomas err = got_error_fmt(GOT_ERR_BAD_KEYWORD, "'%s'", kw);
154 9139e004 2023-07-17 thomas goto done;
155 9139e004 2023-07-17 thomas }
156 9139e004 2023-07-17 thomas } else if (kwm.ismodified) {
157 9139e004 2023-07-17 thomas /* reference:[(+|-)[N]] */
158 9139e004 2023-07-17 thomas kwid_str = strdup(kw);
159 9139e004 2023-07-17 thomas if (kwid_str == NULL) {
160 9139e004 2023-07-17 thomas err = got_error_from_errno("strdup");
161 9139e004 2023-07-17 thomas goto done;
162 9139e004 2023-07-17 thomas }
163 9139e004 2023-07-17 thomas } else
164 9139e004 2023-07-17 thomas goto done;
165 9139e004 2023-07-17 thomas
166 9139e004 2023-07-17 thomas if (kwm.n == 0)
167 9139e004 2023-07-17 thomas goto done; /* unmodified keyword */
168 9139e004 2023-07-17 thomas
169 9139e004 2023-07-17 thomas err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
170 9139e004 2023-07-17 thomas if (err)
171 9139e004 2023-07-17 thomas goto done;
172 9139e004 2023-07-17 thomas
173 9139e004 2023-07-17 thomas err = got_repo_match_object_id(&kwid, NULL, kwid_str,
174 9139e004 2023-07-17 thomas GOT_OBJ_TYPE_COMMIT, &refs, repo);
175 9139e004 2023-07-17 thomas if (err != NULL)
176 9139e004 2023-07-17 thomas goto done;
177 9139e004 2023-07-17 thomas
178 9139e004 2023-07-17 thomas /*
179 9139e004 2023-07-17 thomas * If looking for a descendant, we need to iterate from
180 9139e004 2023-07-17 thomas * HEAD so grab its id now if it's not already in kwid.
181 9139e004 2023-07-17 thomas */
182 9139e004 2023-07-17 thomas if (kwm.sym == GOT_KEYWORD_DESCENDANT && kw != NULL &&
183 9139e004 2023-07-17 thomas strcmp(kw, GOT_KEYWORD_HEAD) != 0) {
184 9139e004 2023-07-17 thomas struct got_reference *head_ref;
185 9139e004 2023-07-17 thomas
186 9139e004 2023-07-17 thomas err = got_ref_open(&head_ref, repo, wt != NULL ?
187 9139e004 2023-07-17 thomas got_worktree_get_head_ref_name(wt) : GOT_REF_HEAD, 0);
188 9139e004 2023-07-17 thomas if (err != NULL)
189 9139e004 2023-07-17 thomas goto done;
190 9139e004 2023-07-17 thomas err = got_ref_resolve(&head_id, repo, head_ref);
191 9139e004 2023-07-17 thomas got_ref_close(head_ref);
192 9139e004 2023-07-17 thomas if (err != NULL)
193 9139e004 2023-07-17 thomas goto done;
194 9139e004 2023-07-17 thomas }
195 9139e004 2023-07-17 thomas
196 9139e004 2023-07-17 thomas err = got_commit_graph_open(&graph, "/", 1);
197 9139e004 2023-07-17 thomas if (err)
198 9139e004 2023-07-17 thomas goto done;
199 9139e004 2023-07-17 thomas
200 0279329d 2024-03-30 thomas err = got_commit_graph_bfsort(graph,
201 9139e004 2023-07-17 thomas head_id != NULL ? head_id : kwid, repo, NULL, NULL);
202 9139e004 2023-07-17 thomas if (err)
203 9139e004 2023-07-17 thomas goto done;
204 9139e004 2023-07-17 thomas
205 9139e004 2023-07-17 thomas while (n <= kwm.n) {
206 9139e004 2023-07-17 thomas err = got_commit_graph_iter_next(&iter_id, graph, repo,
207 9139e004 2023-07-17 thomas NULL, NULL);
208 9139e004 2023-07-17 thomas if (err) {
209 9139e004 2023-07-17 thomas if (err->code == GOT_ERR_ITER_COMPLETED)
210 9139e004 2023-07-17 thomas err = NULL;
211 9139e004 2023-07-17 thomas break;
212 9139e004 2023-07-17 thomas }
213 9139e004 2023-07-17 thomas
214 9139e004 2023-07-17 thomas if (kwm.sym == GOT_KEYWORD_DESCENDANT) {
215 9139e004 2023-07-17 thomas /*
216 9139e004 2023-07-17 thomas * We want the Nth generation descendant of KEYWORD,
217 9139e004 2023-07-17 thomas * so queue all commits from HEAD to KEYWORD then we
218 9139e004 2023-07-17 thomas * can walk from KEYWORD to its Nth gen descendent.
219 9139e004 2023-07-17 thomas */
220 9139e004 2023-07-17 thomas err = got_object_qid_alloc(&qid, &iter_id);
221 9139e004 2023-07-17 thomas if (err)
222 9139e004 2023-07-17 thomas goto done;
223 9139e004 2023-07-17 thomas STAILQ_INSERT_HEAD(&commits, qid, entry);
224 9139e004 2023-07-17 thomas
225 9139e004 2023-07-17 thomas if (got_object_id_cmp(&iter_id, kwid) == 0)
226 9139e004 2023-07-17 thomas break;
227 9139e004 2023-07-17 thomas continue;
228 9139e004 2023-07-17 thomas }
229 9139e004 2023-07-17 thomas ++n;
230 9139e004 2023-07-17 thomas }
231 9139e004 2023-07-17 thomas
232 9139e004 2023-07-17 thomas if (kwm.sym == GOT_KEYWORD_DESCENDANT) {
233 9139e004 2023-07-17 thomas n = 0;
234 9139e004 2023-07-17 thomas
235 9139e004 2023-07-17 thomas STAILQ_FOREACH(qid, &commits, entry) {
236 9139e004 2023-07-17 thomas if (qid == STAILQ_LAST(&commits, got_object_qid, entry)
237 9139e004 2023-07-17 thomas || n == kwm.n)
238 9139e004 2023-07-17 thomas break;
239 9139e004 2023-07-17 thomas ++n;
240 9139e004 2023-07-17 thomas }
241 9139e004 2023-07-17 thomas
242 9139e004 2023-07-17 thomas memcpy(&iter_id, &qid->id, sizeof(iter_id));
243 9139e004 2023-07-17 thomas }
244 9139e004 2023-07-17 thomas
245 9139e004 2023-07-17 thomas free(kwid_str);
246 9139e004 2023-07-17 thomas err = got_object_id_str(&kwid_str, &iter_id);
247 9139e004 2023-07-17 thomas
248 9139e004 2023-07-17 thomas done:
249 9139e004 2023-07-17 thomas free(kwid);
250 9139e004 2023-07-17 thomas free(kwm.kw);
251 9139e004 2023-07-17 thomas free(head_id);
252 9139e004 2023-07-17 thomas got_ref_list_free(&refs);
253 9139e004 2023-07-17 thomas got_object_id_queue_free(&commits);
254 9139e004 2023-07-17 thomas if (graph != NULL)
255 9139e004 2023-07-17 thomas got_commit_graph_close(graph);
256 9139e004 2023-07-17 thomas
257 9139e004 2023-07-17 thomas if (err != NULL) {
258 9139e004 2023-07-17 thomas free(kwid_str);
259 9139e004 2023-07-17 thomas return err;
260 9139e004 2023-07-17 thomas }
261 9139e004 2023-07-17 thomas
262 9139e004 2023-07-17 thomas *ret = kwid_str;
263 9139e004 2023-07-17 thomas return NULL;
264 9139e004 2023-07-17 thomas }