2 * Copyright (c) 2023 Mark Jamsek <mark@jamsek.dev>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include "got_compat.h"
19 #include <sys/queue.h>
28 #include "got_reference.h"
29 #include "got_error.h"
30 #include "got_object.h"
31 #include "got_repository.h"
32 #include "got_cancel.h"
33 #include "got_worktree.h"
34 #include "got_commit_graph.h"
35 #include "got_keyword.h"
45 #define GOT_KEYWORD_DESCENDANT '+'
46 #define GOT_KEYWORD_ANCESTOR '-'
48 static const struct got_error
*
49 parse_keyword(struct keyword_mod
*kwm
, const char *keyword
)
57 /* check if it is a (modified) keyword or modified reference */
58 if (*keyword
== ':') {
66 return got_error_from_errno("strdup");
68 p
= strchr(kwm
->kw
, ':');
73 if (*p
!= GOT_KEYWORD_DESCENDANT
&& *p
!= GOT_KEYWORD_ANCESTOR
)
74 return got_error_fmt(GOT_ERR_BAD_KEYWORD
,
85 n
= strtonum(p
, 0, LLONG_MAX
, &errstr
);
87 return got_error_fmt(GOT_ERR_BAD_KEYWORD
,
92 kwm
->n
= 1; /* :(+/-) == :(+/-)1 */
98 const struct got_error
*
99 got_keyword_to_idstr(char **ret
, const char *keyword
,
100 struct got_repository
*repo
, struct got_worktree
*wt
)
102 const struct got_error
*err
= NULL
;
103 struct got_commit_graph
*graph
= NULL
;
104 struct got_object_id
*head_id
= NULL
, *kwid
= NULL
;
105 struct got_object_id iter_id
;
106 struct got_reflist_head refs
;
107 struct got_object_id_queue commits
;
108 struct got_object_qid
*qid
;
109 struct keyword_mod kwm
;
110 const char *kw
= NULL
;
111 char *kwid_str
= NULL
;
116 STAILQ_INIT(&commits
);
117 memset(&kwm
, 0, sizeof(kwm
));
119 err
= parse_keyword(&kwm
, keyword
);
126 if (strcmp(kw
, GOT_KEYWORD_BASE
) == 0) {
128 err
= got_error_msg(GOT_ERR_NOT_WORKTREE
,
129 "'-c :base' requires work tree");
133 err
= got_object_id_str(&kwid_str
,
134 got_worktree_get_base_commit_id(wt
));
137 } else if (strcmp(kw
, GOT_KEYWORD_HEAD
) == 0) {
138 struct got_reference
*head_ref
;
140 err
= got_ref_open(&head_ref
, repo
, wt
!= NULL
?
141 got_worktree_get_head_ref_name(wt
) :
146 kwid_str
= got_ref_to_str(head_ref
);
147 got_ref_close(head_ref
);
148 if (kwid_str
== NULL
) {
149 err
= got_error_from_errno("got_ref_to_str");
153 err
= got_error_fmt(GOT_ERR_BAD_KEYWORD
, "'%s'", kw
);
156 } else if (kwm
.ismodified
) {
157 /* reference:[(+|-)[N]] */
158 kwid_str
= strdup(kw
);
159 if (kwid_str
== NULL
) {
160 err
= got_error_from_errno("strdup");
167 goto done
; /* unmodified keyword */
169 err
= got_ref_list(&refs
, repo
, NULL
, got_ref_cmp_by_name
, NULL
);
173 err
= got_repo_match_object_id(&kwid
, NULL
, kwid_str
,
174 GOT_OBJ_TYPE_COMMIT
, &refs
, repo
);
179 * If looking for a descendant, we need to iterate from
180 * HEAD so grab its id now if it's not already in kwid.
182 if (kwm
.sym
== GOT_KEYWORD_DESCENDANT
&& kw
!= NULL
&&
183 strcmp(kw
, GOT_KEYWORD_HEAD
) != 0) {
184 struct got_reference
*head_ref
;
186 err
= got_ref_open(&head_ref
, repo
, wt
!= NULL
?
187 got_worktree_get_head_ref_name(wt
) : GOT_REF_HEAD
, 0);
190 err
= got_ref_resolve(&head_id
, repo
, head_ref
);
191 got_ref_close(head_ref
);
196 err
= got_commit_graph_open(&graph
, "/", 1);
200 err
= got_commit_graph_bfsort(graph
,
201 head_id
!= NULL
? head_id
: kwid
, repo
, NULL
, NULL
);
206 err
= got_commit_graph_iter_next(&iter_id
, graph
, repo
,
209 if (err
->code
== GOT_ERR_ITER_COMPLETED
)
214 if (kwm
.sym
== GOT_KEYWORD_DESCENDANT
) {
216 * We want the Nth generation descendant of KEYWORD,
217 * so queue all commits from HEAD to KEYWORD then we
218 * can walk from KEYWORD to its Nth gen descendent.
220 err
= got_object_qid_alloc(&qid
, &iter_id
);
223 STAILQ_INSERT_HEAD(&commits
, qid
, entry
);
225 if (got_object_id_cmp(&iter_id
, kwid
) == 0)
232 if (kwm
.sym
== GOT_KEYWORD_DESCENDANT
) {
235 STAILQ_FOREACH(qid
, &commits
, entry
) {
236 if (qid
== STAILQ_LAST(&commits
, got_object_qid
, entry
)
242 memcpy(&iter_id
, &qid
->id
, sizeof(iter_id
));
246 err
= got_object_id_str(&kwid_str
, &iter_id
);
252 got_ref_list_free(&refs
);
253 got_object_id_queue_free(&commits
);
255 got_commit_graph_close(graph
);