arche / internal/cli/resolve.go

commit 154431fd
  1package cli
  2
  3import (
  4	"database/sql"
  5	"encoding/hex"
  6	"fmt"
  7	"strings"
  8
  9	"arche/internal/object"
 10	"arche/internal/repo"
 11)
 12
 13func resolveRef(r *repo.Repo, ref string) ([32]byte, error) {
 14	if ref == "@" || ref == "" {
 15		_, id, err := r.HeadCommit()
 16		return id, err
 17	}
 18	if strings.HasPrefix(ref, "@-") {
 19		n := 1
 20		suffix := ref[2:]
 21		if suffix != "" {
 22			if _, err := fmt.Sscanf(suffix, "%d", &n); err != nil {
 23				return object.ZeroID, fmt.Errorf("invalid relative ref %q", ref)
 24			}
 25		}
 26		_, commitID, err := r.HeadCommit()
 27		if err != nil {
 28			return object.ZeroID, err
 29		}
 30		return nthAncestor(r, commitID, n)
 31	}
 32
 33	if strings.HasPrefix(ref, "ch:") {
 34		bare := ref[3:]
 35		id, err := r.Store.GetChangeCommit(bare)
 36		if err != nil {
 37			if err == sql.ErrNoRows {
 38				return object.ZeroID, fmt.Errorf("change ID %q not found", ref)
 39			}
 40			return object.ZeroID, err
 41		}
 42		return id, nil
 43	}
 44
 45	if isChangeIDChars(ref) {
 46		id, err := r.Store.GetChangeCommit(ref)
 47		if err == nil {
 48			return id, nil
 49		}
 50		if err != sql.ErrNoRows {
 51			return object.ZeroID, err
 52		}
 53	}
 54
 55	if isHexString(ref) && len(ref) >= 4 {
 56		id, err := resolveHashPrefix(r, ref)
 57		if err == nil {
 58			return id, nil
 59		}
 60	}
 61
 62	bm, err := r.Store.GetBookmark(ref)
 63	if err == nil && bm != nil {
 64		return bm.CommitID, nil
 65	}
 66
 67	return object.ZeroID, fmt.Errorf("unknown commit reference %q", ref)
 68}
 69
 70func nthAncestor(r *repo.Repo, id [32]byte, n int) ([32]byte, error) {
 71	cur := id
 72	for i := 0; i < n; i++ {
 73		c, err := r.ReadCommit(cur)
 74		if err != nil {
 75			return object.ZeroID, err
 76		}
 77		if len(c.Parents) == 0 {
 78			return object.ZeroID, fmt.Errorf("commit %s has no parent (went %d/%d)", object.Short(cur), i, n)
 79		}
 80		cur = c.Parents[0]
 81	}
 82	return cur, nil
 83}
 84
 85func resolveHashPrefix(r *repo.Repo, prefix string) ([32]byte, error) {
 86	p := prefix
 87	if len(p)%2 != 0 {
 88		p = p + "0"
 89	}
 90	lower, err := hex.DecodeString(p)
 91	if err != nil {
 92		return object.ZeroID, err
 93	}
 94
 95	var found [32]byte
 96	var count int
 97
 98	allChanges, err := listAllCommitIDs(r)
 99	if err != nil {
100		return object.ZeroID, err
101	}
102	for _, id := range allChanges {
103		h := fmt.Sprintf("%x", id)
104		if strings.HasPrefix(h, prefix) {
105			found = id
106			count++
107		}
108	}
109	_ = lower
110	if count == 0 {
111		return object.ZeroID, fmt.Errorf("hash prefix %q not found", prefix)
112	}
113	if count > 1 {
114		return object.ZeroID, fmt.Errorf("ambiguous hash prefix %q matches %d commits", prefix, count)
115	}
116	return found, nil
117}
118
119func listAllCommitIDs(r *repo.Repo) ([][32]byte, error) {
120	bms, err := r.Store.ListBookmarks()
121	if err != nil {
122		return nil, err
123	}
124
125	seen := make(map[[32]byte]bool)
126	var queue [][32]byte
127
128	_, headID, err := r.HeadCommit()
129	if err == nil {
130		queue = append(queue, headID)
131	}
132	for _, bm := range bms {
133		queue = append(queue, bm.CommitID)
134	}
135
136	var all [][32]byte
137	for len(queue) > 0 {
138		id := queue[0]
139		queue = queue[1:]
140		if seen[id] {
141			continue
142		}
143		seen[id] = true
144		all = append(all, id)
145		c, err := r.ReadCommit(id)
146		if err != nil {
147			continue
148		}
149		for _, p := range c.Parents {
150			if !seen[p] {
151				queue = append(queue, p)
152			}
153		}
154	}
155	return all, nil
156}
157
158func isHexString(s string) bool {
159	for _, c := range s {
160		if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
161			return false
162		}
163	}
164	return true
165}
166
167func isChangeIDChars(s string) bool {
168	const alpha = "abcdefghjkmnpqrstvwxyz"
169	for _, c := range s {
170		if !strings.ContainsRune(alpha, c) {
171			return false
172		}
173	}
174	return len(s) >= 4
175}