/* * Copyright (c) 2023 Mark Jamsek * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include "got_reference.h" #include "got_error.h" #include "got_object.h" #include "got_repository.h" #include "got_cancel.h" #include "got_worktree.h" #include "got_commit_graph.h" #include "got_keyword.h" struct keyword_mod { char *kw; uint64_t n; uint8_t sym; uint8_t iskeyword; uint8_t ismodified; }; #define GOT_KEYWORD_DESCENDANT '+' #define GOT_KEYWORD_ANCESTOR '-' static const struct got_error * parse_keyword(struct keyword_mod *kwm, const char *keyword) { const char *kw; char *p; if (keyword == NULL) return NULL; /* check if it is a (modified) keyword or modified reference */ if (*keyword == ':') { kwm->iskeyword = 1; kw = keyword + 1; } else kw = keyword; kwm->kw = strdup(kw); if (kwm->kw == NULL) return got_error_from_errno("strdup"); p = strchr(kwm->kw, ':'); if (p != NULL) { *p = '\0'; ++p; if (*p != GOT_KEYWORD_DESCENDANT && *p != GOT_KEYWORD_ANCESTOR) return got_error_fmt(GOT_ERR_BAD_KEYWORD, "'%s'", keyword); kwm->ismodified = 1; kwm->sym = *p; ++p; if (*p) { const char *errstr; long long n; n = strtonum(p, 0, LLONG_MAX, &errstr); if (errstr != NULL) return got_error_fmt(GOT_ERR_BAD_KEYWORD, "'%s'", keyword); kwm->n = n; } else kwm->n = 1; /* :(+/-) == :(+/-)1 */ } return NULL; } const struct got_error * got_keyword_to_idstr(char **ret, const char *keyword, struct got_repository *repo, struct got_worktree *wt) { const struct got_error *err = NULL; struct got_commit_graph *graph = NULL; struct got_object_id *head_id = NULL, *kwid = NULL; struct got_object_id iter_id; struct got_reflist_head refs; struct got_object_id_queue commits; struct got_object_qid *qid; struct keyword_mod kwm; const char *kw = NULL; char *kwid_str = NULL; uint64_t n = 0; *ret = NULL; TAILQ_INIT(&refs); STAILQ_INIT(&commits); memset(&kwm, 0, sizeof(kwm)); err = parse_keyword(&kwm, keyword); if (err != NULL) goto done; kw = kwm.kw; if (kwm.iskeyword) { if (strcmp(kw, GOT_KEYWORD_BASE) == 0) { if (wt == NULL) { err = got_error_msg(GOT_ERR_NOT_WORKTREE, "'-c :base' requires work tree"); goto done; } err = got_object_id_str(&kwid_str, got_worktree_get_base_commit_id(wt)); if (err != NULL) goto done; } else if (strcmp(kw, GOT_KEYWORD_HEAD) == 0) { struct got_reference *head_ref; err = got_ref_open(&head_ref, repo, wt != NULL ? got_worktree_get_head_ref_name(wt) : GOT_REF_HEAD, 0); if (err != NULL) goto done; kwid_str = got_ref_to_str(head_ref); got_ref_close(head_ref); if (kwid_str == NULL) { err = got_error_from_errno("got_ref_to_str"); goto done; } } else { err = got_error_fmt(GOT_ERR_BAD_KEYWORD, "'%s'", kw); goto done; } } else if (kwm.ismodified) { /* reference:[(+|-)[N]] */ kwid_str = strdup(kw); if (kwid_str == NULL) { err = got_error_from_errno("strdup"); goto done; } } else goto done; if (kwm.n == 0) goto done; /* unmodified keyword */ err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL); if (err) goto done; err = got_repo_match_object_id(&kwid, NULL, kwid_str, GOT_OBJ_TYPE_COMMIT, &refs, repo); if (err != NULL) goto done; /* * If looking for a descendant, we need to iterate from * HEAD so grab its id now if it's not already in kwid. */ if (kwm.sym == GOT_KEYWORD_DESCENDANT && kw != NULL && strcmp(kw, GOT_KEYWORD_HEAD) != 0) { struct got_reference *head_ref; err = got_ref_open(&head_ref, repo, wt != NULL ? got_worktree_get_head_ref_name(wt) : GOT_REF_HEAD, 0); if (err != NULL) goto done; err = got_ref_resolve(&head_id, repo, head_ref); got_ref_close(head_ref); if (err != NULL) goto done; } err = got_commit_graph_open(&graph, "/", 1); if (err) goto done; err = got_commit_graph_bfsort(graph, head_id != NULL ? head_id : kwid, repo, NULL, NULL); if (err) goto done; while (n <= kwm.n) { err = got_commit_graph_iter_next(&iter_id, graph, repo, NULL, NULL); if (err) { if (err->code == GOT_ERR_ITER_COMPLETED) err = NULL; break; } if (kwm.sym == GOT_KEYWORD_DESCENDANT) { /* * We want the Nth generation descendant of KEYWORD, * so queue all commits from HEAD to KEYWORD then we * can walk from KEYWORD to its Nth gen descendent. */ err = got_object_qid_alloc(&qid, &iter_id); if (err) goto done; STAILQ_INSERT_HEAD(&commits, qid, entry); if (got_object_id_cmp(&iter_id, kwid) == 0) break; continue; } ++n; } if (kwm.sym == GOT_KEYWORD_DESCENDANT) { n = 0; STAILQ_FOREACH(qid, &commits, entry) { if (qid == STAILQ_LAST(&commits, got_object_qid, entry) || n == kwm.n) break; ++n; } memcpy(&iter_id, &qid->id, sizeof(iter_id)); } free(kwid_str); err = got_object_id_str(&kwid_str, &iter_id); done: free(kwid); free(kwm.kw); free(head_id); got_ref_list_free(&refs); got_object_id_queue_free(&commits); if (graph != NULL) got_commit_graph_close(graph); if (err != NULL) { free(kwid_str); return err; } *ret = kwid_str; return NULL; }