feat: look for repos in the usual folders, not just ~/git
Without roots in the config, repos are found under whichever of ~/git, ~/repo, ~/repos, ~/projects, ~/code, ~/source and ~/src exist. Missing ones cost a stat each. A root that's a symlink to another is scanned once, and a repo under two roots keeps the first one's name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
5 files changed
+48
-11
No files matched your search
@@ -287,7 +287,7 @@ Flags:
|
|||||||
if start == "" {
|
if start == "" {
|
||||||
start, _ = os.Getwd()
|
start, _ = os.Getwd()
|
||||||
}
|
}
|
||||||
roots := []string{"~/git"}
|
roots := repo.DefaultRoots
|
||||||
if pc.Repos.Roots != nil {
|
if pc.Repos.Roots != nil {
|
||||||
roots = *pc.Repos.Roots
|
roots = *pc.Repos.Roots
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -7,10 +7,11 @@ lazysecrets shows git repositories under **Repos**: their
|
|||||||
|
|
||||||
## Which repos
|
## Which repos
|
||||||
|
|
||||||
**Repos** lists every repo with secret files under the `roots` (`~/git`
|
**Repos** lists every repo with secret files under the `roots` (by
|
||||||
by default; one or two levels down, `repo` or `org/repo`), any listed in
|
default whichever of `~/git`, `~/repo`, `~/repos`, `~/projects`, `~/code`,
|
||||||
`paths`, and any you starred something in (the repo, a file or a key),
|
`~/source` and `~/src` exist; one or two levels down, `repo` or
|
||||||
wherever it is. They're checked in the background.
|
`org/repo`), any listed in `paths`, and any you starred something in
|
||||||
|
(the repo, a file or a key), wherever it is. They're checked in the background.
|
||||||
|
|
||||||
Started inside a repo, that repo gets a row of its own at the top,
|
Started inside a repo, that repo gets a row of its own at the top,
|
||||||
`Repo · <name>`, and lazysecrets opens on it, with the files under the
|
`Repo · <name>`, and lazysecrets opens on it, with the files under the
|
||||||
@@ -49,7 +50,7 @@ Folders of dependencies and caches are never looked in, tracked or not:
|
|||||||
```yaml
|
```yaml
|
||||||
providers:
|
providers:
|
||||||
repos:
|
repos:
|
||||||
roots: [~/git] # folders of repos (the default; [] turns it off)
|
roots: [~/git] # folders of repos (replaces the defaults; [] turns it off)
|
||||||
paths: [~/work/infra] # single repos elsewhere
|
paths: [~/work/infra] # single repos elsewhere
|
||||||
include: ["secrets/**", "**/*.sops.yaml"] # globs from the repo root; ** for any depth
|
include: ["secrets/**", "**/*.sops.yaml"] # globs from the repo root; ** for any depth
|
||||||
exclude: ["**/testdata/**"]
|
exclude: ["**/testdata/**"]
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ type Repos struct {
|
|||||||
// Enabled defaults to true.
|
// Enabled defaults to true.
|
||||||
Enabled *bool `yaml:"enabled"`
|
Enabled *bool `yaml:"enabled"`
|
||||||
Paths []string `yaml:"paths"`
|
Paths []string `yaml:"paths"`
|
||||||
// Roots are folders of repos. Defaults to ~/git; an empty list
|
// Roots are folders of repos. Defaults to repo.DefaultRoots (~/git,
|
||||||
// turns it off.
|
// ~/src, ~/code and the like); an empty list turns it off.
|
||||||
Roots *[]string `yaml:"roots"`
|
Roots *[]string `yaml:"roots"`
|
||||||
// Include and Exclude are globs relative to a repo's root (** for any
|
// Include and Exclude are globs relative to a repo's root (** for any
|
||||||
// depth), for repos without their own .lazysecrets.yaml. Without them,
|
// depth), for repos without their own .lazysecrets.yaml. Without them,
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ type repoInfo struct {
|
|||||||
here bool // the repo lazysecrets started in
|
here bool // the repo lazysecrets started in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultRoots are the usual folders of repos, used when the config
|
||||||
|
// doesn't set roots. Ones that don't exist cost a stat each.
|
||||||
|
var DefaultRoots = []string{"~/git", "~/repo", "~/repos", "~/projects", "~/code", "~/source", "~/src"}
|
||||||
|
|
||||||
func New(s Settings) *Provider {
|
func New(s Settings) *Provider {
|
||||||
if s.Sops == "" {
|
if s.Sops == "" {
|
||||||
s.Sops = "sops"
|
s.Sops = "sops"
|
||||||
@@ -116,10 +120,18 @@ func (p *Provider) load() {
|
|||||||
}
|
}
|
||||||
add(root)
|
add(root)
|
||||||
}
|
}
|
||||||
|
// Missing roots are skipped (most of the defaults are), and one
|
||||||
|
// that's a symlink to another is scanned once.
|
||||||
|
scanned := map[string]bool{}
|
||||||
for _, dir := range p.s.Roots {
|
for _, dir := range p.s.Roots {
|
||||||
base, _ := filepath.EvalSymlinks(expandHome(dir))
|
base, err := filepath.EvalSymlinks(expandHome(dir))
|
||||||
for _, root := range reposIn(expandHome(dir), 2) {
|
if err != nil || scanned[base] {
|
||||||
if name, err := filepath.Rel(base, root); err == nil && !strings.HasPrefix(name, "..") {
|
continue
|
||||||
|
}
|
||||||
|
scanned[base] = true
|
||||||
|
for _, root := range reposIn(base, 2) {
|
||||||
|
// A repo under two roots keeps the first one's name.
|
||||||
|
if name, err := filepath.Rel(base, root); err == nil && !strings.HasPrefix(name, "..") && names[root] == "" {
|
||||||
names[root] = filepath.ToSlash(name)
|
names[root] = filepath.ToSlash(name)
|
||||||
}
|
}
|
||||||
add(root)
|
add(root)
|
||||||
|
|||||||
@@ -336,6 +336,30 @@ func TestRoots(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRootsDeduped(t *testing.T) {
|
||||||
|
root, _ := filepath.EvalSymlinks(t.TempDir())
|
||||||
|
os.Rename(gitRepo(t, map[string]string{"secrets/a.yaml": sopsYAML}), filepath.Join(root, "infra"))
|
||||||
|
os.MkdirAll(filepath.Join(root, "acme"), 0o755)
|
||||||
|
os.Rename(gitRepo(t, map[string]string{"prod.env": sopsEnv}), filepath.Join(root, "acme", "billing"))
|
||||||
|
|
||||||
|
// ~/src → ~/git, and a root reaching one repo at another depth.
|
||||||
|
links := t.TempDir()
|
||||||
|
alias := filepath.Join(links, "src")
|
||||||
|
os.Symlink(root, alias)
|
||||||
|
missing := filepath.Join(links, "nope")
|
||||||
|
acme := filepath.Join(root, "acme")
|
||||||
|
|
||||||
|
srcs, err := New(Settings{Dir: links, Roots: []string{missing, root, alias, acme}}).Sources(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got := names(srcs, func(s provider.Source) string { return s.Name })
|
||||||
|
slices.Sort(got)
|
||||||
|
if !slices.Equal(got, []string{"acme/billing", "infra"}) {
|
||||||
|
t.Errorf("repos: %v, want each once, named from the first root", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStarredReposAlongside(t *testing.T) {
|
func TestStarredReposAlongside(t *testing.T) {
|
||||||
here := gitRepo(t, map[string]string{"secrets/a.yaml": sopsYAML})
|
here := gitRepo(t, map[string]string{"secrets/a.yaml": sopsYAML})
|
||||||
starred := gitRepo(t, map[string]string{"prod.env": sopsEnv})
|
starred := gitRepo(t, map[string]string{"prod.env": sopsEnv})
|
||||||
|
|||||||
Reference in new issue
Block a user