package gitrepo import ( "errors" "strings" "gitweb/internal/store" ) // Locate finds whether the upspin path "" points into a bare git // repository. It scans path prefixes from the user root downward and returns // the shallowest prefix that is a bare repo (repos do not nest), along with the // remaining path after it. // // - user: validated upspin user, e.g. "u@d.com" // - clean: the cleaned remainder, always beginning with "/", e.g. "/proj.git/log/main" // // On a match it returns repoPath="u@d.com/proj.git", rest="log/main", found=true. // found=false means no repo lies on this path and the caller should fall back to // plain namespace browsing. A store error other than not-found is returned. func Locate(st store.Store, user, clean string) (repoPath, rest string, found bool, err error) { trimmed := strings.Trim(clean, "/") var segs []string if trimmed != "" { segs = strings.Split(trimmed, "/") } // Candidates shallow -> deep: user, user/seg1, user/seg1/seg2, ... prefix := user for i := 0; ; i++ { ok, err := isRepo(st, prefix) if err != nil { return "", "", false, err } if ok { return prefix, strings.Join(segs[i:], "/"), true, nil } if i >= len(segs) { break } prefix = prefix + "/" + segs[i] } return "", "", false, nil } // isRepo reports whether prefix is a bare git repository: it must contain a // HEAD file and an objects directory. Not-found on either means "not a repo". func isRepo(st store.Store, prefix string) (bool, error) { headIsDir, err := st.Stat(prefix + "/HEAD") if err != nil { if errors.Is(err, store.ErrNotFound) { return false, nil } return false, err } if headIsDir { return false, nil } objIsDir, err := st.Stat(prefix + "/objects") if err != nil { if errors.Is(err, store.ErrNotFound) { return false, nil } return false, err } return objIsDir, nil }