2 9139e004 2023-07-17 thomas * Copyright (c) 2023 Mark Jamsek <mark@jamsek.dev>
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.
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.
17 481d9ea6 2023-07-17 thomas #include "got_compat.h"
19 9139e004 2023-07-17 thomas #include <sys/queue.h>
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>
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"
37 9139e004 2023-07-17 thomas struct keyword_mod {
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;
45 9139e004 2023-07-17 thomas #define GOT_KEYWORD_DESCENDANT '+'
46 9139e004 2023-07-17 thomas #define GOT_KEYWORD_ANCESTOR '-'
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)
51 9139e004 2023-07-17 thomas const char *kw;
54 9139e004 2023-07-17 thomas if (keyword == NULL)
55 9139e004 2023-07-17 thomas return NULL;
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;
62 9139e004 2023-07-17 thomas kw = keyword;
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");
68 9139e004 2023-07-17 thomas p = strchr(kwm->kw, ':');
70 9139e004 2023-07-17 thomas if (p != NULL) {
71 9139e004 2023-07-17 thomas *p = '\0';
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);
77 9139e004 2023-07-17 thomas kwm->ismodified = 1;
78 9139e004 2023-07-17 thomas kwm->sym = *p;
82 9139e004 2023-07-17 thomas const char *errstr;
83 9139e004 2023-07-17 thomas long long n;
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);
90 9139e004 2023-07-17 thomas kwm->n = n;
92 9139e004 2023-07-17 thomas kwm->n = 1; /* :(+/-) == :(+/-)1 */
95 9139e004 2023-07-17 thomas return NULL;
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)
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;
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));
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;
123 9139e004 2023-07-17 thomas kw = kwm.kw;
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;
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;
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;
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;
153 9139e004 2023-07-17 thomas err = got_error_fmt(GOT_ERR_BAD_KEYWORD, "'%s'", kw);
154 9139e004 2023-07-17 thomas goto done;
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;
164 9139e004 2023-07-17 thomas goto done;
166 9139e004 2023-07-17 thomas if (kwm.n == 0)
167 9139e004 2023-07-17 thomas goto done; /* unmodified keyword */
169 9139e004 2023-07-17 thomas err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
171 9139e004 2023-07-17 thomas goto done;
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;
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.
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;
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;
196 9139e004 2023-07-17 thomas err = got_commit_graph_open(&graph, "/", 1);
198 9139e004 2023-07-17 thomas goto done;
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);
203 9139e004 2023-07-17 thomas goto done;
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;
214 9139e004 2023-07-17 thomas if (kwm.sym == GOT_KEYWORD_DESCENDANT) {
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.
220 9139e004 2023-07-17 thomas err = got_object_qid_alloc(&qid, &iter_id);
222 9139e004 2023-07-17 thomas goto done;
223 9139e004 2023-07-17 thomas STAILQ_INSERT_HEAD(&commits, qid, entry);
225 9139e004 2023-07-17 thomas if (got_object_id_cmp(&iter_id, kwid) == 0)
227 9139e004 2023-07-17 thomas continue;
232 9139e004 2023-07-17 thomas if (kwm.sym == GOT_KEYWORD_DESCENDANT) {
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)
242 9139e004 2023-07-17 thomas memcpy(&iter_id, &qid->id, sizeof(iter_id));
245 9139e004 2023-07-17 thomas free(kwid_str);
246 9139e004 2023-07-17 thomas err = got_object_id_str(&kwid_str, &iter_id);
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);
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;
262 9139e004 2023-07-17 thomas *ret = kwid_str;
263 9139e004 2023-07-17 thomas return NULL;