5 files changed,
+62,
-12
+11,
-6
1@@ -302,16 +302,21 @@ func prepareChild(parent, id, action string) (*manifest.Manifest, engine.Meta, e
2 // --repo links them.
3 func prepareLocal(m *manifest.Manifest, root string, dir engine.Dir, f *runFlags) (engine.Meta, error) {
4 ctx := context.Background()
5- repo := filepath.Base(root)
6- meta := engine.Meta{
7- Run: dir.Base(), Workflow: m.Name, Repo: repo, Ref: f.ref,
8- Kind: "manual", Host: "local", Root: filepath.Join(dir.Path(), repo),
9- }
10 gitDir, err := checkout.GitDir(root)
11 if err != nil {
12- return meta, err
13+ return engine.Meta{}, err
14 }
15 origin := checkout.OriginURL(ctx, gitDir)
16+ // the repo's name on the forge, not the directory's (a jj workspace or a
17+ // clone may be named differently)
18+ repo := checkout.RepoName(origin)
19+ if repo == "" {
20+ repo = filepath.Base(root)
21+ }
22+ meta := engine.Meta{
23+ Run: dir.Base(), Workflow: m.Name, Repo: repo, Ref: f.ref,
24+ Kind: "manual", Host: "local", Root: filepath.Join(dir.Path(), repo),
25+ }
26 src := &checkout.Source{MirrorsDir: filepath.Join(cacheDir(), "mirrors"), URL: checkout.URLFunc(os.Getenv("CI_CLONE_URL"), origin)}
27 main := checkout.Main{Repo: repo}
28 if f.clean {
+15,
-6
1@@ -102,15 +102,24 @@ func Clone(ctx context.Context, from, sha, dest, origin string) error {
2 return nil
3 }
4
5-// GitDir returns the git directory of a local repository: .git for git and
6-// colocated jj repos, .jj/repo/store/git for jj repos without colocation.
7+// GitDir returns the git directory behind a local repository: `jj git root`
8+// for jj repos (which also covers secondary workspaces and non-colocated
9+// repos), else .git.
10 func GitDir(root string) (string, error) {
11- for _, p := range []string{filepath.Join(root, ".git"), filepath.Join(root, ".jj", "repo", "store", "git")} {
12- if _, err := os.Stat(p); err == nil {
13- return p, nil
14+ if _, err := os.Stat(filepath.Join(root, ".jj")); err == nil {
15+ cmd := exec.Command("jj", "git", "root")
16+ cmd.Dir = root
17+ out, err := cmd.Output()
18+ if err != nil {
19+ return "", fmt.Errorf("%s: jj git root: %w", root, err)
20 }
21+ return strings.TrimSpace(string(out)), nil
22 }
23- return "", fmt.Errorf("%s: no git directory (.git or .jj/repo/store/git)", root)
24+ p := filepath.Join(root, ".git")
25+ if _, err := os.Stat(p); err != nil {
26+ return "", fmt.Errorf("%s: no .git or .jj", root)
27+ }
28+ return p, nil
29 }
30
31 // Inside reports whether p is dir or below it (lexically).
+5,
-0
1@@ -174,6 +174,11 @@ func TestURLs(t *testing.T) {
2 t.Errorf("SiblingURL(%q) = %q, %v", of, got, err)
3 }
4 }
5+ for in, want := range map[string]string{"git@h:sovrn.git": "sovrn", "ssh://h/u/moods/": "moods", "https://h/x.git": "x", "": ""} {
6+ if got := RepoName(in); got != want {
7+ t.Errorf("RepoName(%q) = %q", in, got)
8+ }
9+ }
10 if u, _ := URLFunc("git@h:{repo}", "x")("data"); u != "git@h:data" {
11 t.Errorf("template: %s", u)
12 }
+11,
-0
1@@ -117,6 +117,17 @@ func SiblingURL(of, repo string) (string, error) {
2 return of[:i+1] + repo + suffix, nil
3 }
4
5+// RepoName returns the last path element of a clone URL without .git
6+// (git@host:sovrn.git -> sovrn), or "".
7+func RepoName(url string) string {
8+ url = strings.TrimSuffix(strings.TrimSuffix(url, "/"), ".git")
9+ i := strings.LastIndexAny(url, "/:")
10+ if i < 0 || i == len(url)-1 {
11+ return ""
12+ }
13+ return url[i+1:]
14+}
15+
16 // URLFunc returns the clone-URL resolver: a template with {repo} (from
17 // CI_CLONE_URL) if set, else a sibling of origin.
18 func URLFunc(template, origin string) func(string) (string, error) {
+20,
-0
1@@ -2,6 +2,7 @@ package engine
2
3 import (
4 "os"
5+ "path/filepath"
6 "sort"
7 "strconv"
8 "strings"
9@@ -22,6 +23,11 @@ func (e *Engine) actionEnv(p Pending, n int, resultFile, inputsFile, triggerFile
10 for k, v := range e.M.Environment {
11 env[k] = v
12 }
13+ // ci-result (and ci) of the engine running this action come first, so
14+ // recipes can report results even in repos that don't pin the engine.
15+ if dir := helperDir(); dir != "" {
16+ env["PATH"] = dir + string(os.PathListSeparator) + env["PATH"]
17+ }
18 m := e.st.Meta
19 set := func(k, v string) { env[k] = v }
20
21@@ -87,5 +93,19 @@ func (e *Engine) stateDir() string {
22 return e.Dir.State()
23 }
24
25+// helperDir is the directory of the running engine binary if ci-result is
26+// next to it (the Nix package installs it as a symlink), else "".
27+func helperDir() string {
28+ exe, err := os.Executable()
29+ if err != nil {
30+ return ""
31+ }
32+ dir := filepath.Dir(exe)
33+ if _, err := os.Stat(filepath.Join(dir, "ci-result")); err != nil {
34+ return ""
35+ }
36+ return dir
37+}
38+
39 // osEnviron is os.Environ, replaceable in tests.
40 var osEnviron = os.Environ