Expose git-dir on push table for CI jobs
Thread the bare repo path through Runtime::new and set it as git-dir on
the push Lua table. ci.fnl uses GIT_DIR env var so git tag/push work
against the bare repo without a checkout.

Assisted-by: GLM-5.1 via pi
change nkosvpksztnomuynqszopulrxykwwytm
commit f5908f2f048fdc82d602b5a5821491c917a1c27b
author Alpha Chen <alpha@kejadlen.dev>
date
parent srrrvxsy
diff --git a/.quire/ci.fnl b/.quire/ci.fnl
index e60d38d..1b87cb7 100644
--- a/.quire/ci.fnl
+++ b/.quire/ci.fnl
@@ -3,7 +3,7 @@
 
 (job :tag-and-mirror [:quire/push]
      (fn [{: sh : secret : jobs}]
-       (let [{: ref : sha} (jobs :quire/push)
+       (let [{: ref : sha : git-dir} (jobs :quire/push)
              token (secret :github_token)]
          (when (= ref "refs/heads/main")
            (let [date (-> (sh "date --utc +%Y-%m-%d")
@@ -14,12 +14,13 @@
                                  {:env {:T (.. "x-access-token:" token)}})
                              (. :stdout)
                              (: :gsub "\n$" ""))
-                 auth-header (.. "Authorization: Basic " encoded)]
-             (sh [:git :tag tag sha])
+                 auth-header (.. "Authorization: Basic " encoded)
+                 git-opts {:env {:GIT_DIR git-dir}}]
+             (sh [:git :tag tag sha] git-opts)
              (sh [:git
                   :-c
                   (.. :http.extraHeader= auth-header)
                   :push
                   :--porcelain
                   mirror-url
-                  (.. :refs/tags/ tag)]))))))
+                  (.. :refs/tags/ tag)] git-opts))))))
diff --git a/src/bin/quire/commands/ci.rs b/src/bin/quire/commands/ci.rs
index 249dfcb..b1fac2e 100644
--- a/src/bin/quire/commands/ci.rs
+++ b/src/bin/quire/commands/ci.rs
@@ -44,7 +44,7 @@ pub async fn validate(maybe_sha: Option<&str>) -> Result<()> {
 pub async fn run(quire: &Quire, maybe_sha: Option<&str>) -> Result<()> {
     let repo_path = discover_repo()?;
     let commit = resolve_commit(maybe_sha)?;
-    let ci = Ci::new(repo_path);
+    let ci = Ci::new(repo_path.clone());
 
     // Pull secrets from the global config; absence is fine for local
     // testing. A broken-but-present config is a real error. Secrets
@@ -76,7 +76,7 @@ pub async fn run(quire: &Quire, maybe_sha: Option<&str>) -> Result<()> {
     let run = runs.create(&meta)?;
     println!("Run {}: executing at {}", run.id(), commit.display);
 
-    let exec_result = run.execute(pipeline, secrets);
+    let exec_result = run.execute(pipeline, secrets, &repo_path);
 
     match exec_result {
         Ok(outputs) => {
diff --git a/src/ci/lua.rs b/src/ci/lua.rs
index c402096..5acba22 100644
--- a/src/ci/lua.rs
+++ b/src/ci/lua.rs
@@ -139,6 +139,7 @@ impl Runtime {
         pipeline: super::pipeline::Pipeline,
         secrets: HashMap<String, SecretString>,
         meta: &super::run::RunMeta,
+        git_dir: &std::path::Path,
     ) -> Self {
         let transitive = pipeline.transitive_inputs();
         let lua = pipeline.fennel().lua();
@@ -149,6 +150,8 @@ impl Runtime {
         push.set("ref", meta.r#ref.as_str()).expect("set ref");
         push.set("pushed-at", meta.pushed_at.to_string().as_str())
             .expect("set pushed-at");
+        push.set("git-dir", git_dir.to_string_lossy().as_ref())
+            .expect("set git-dir");
         let push_value = push.into_lua(lua).expect("push table to value");
 
         // Build per-job input views from transitive reachability.
diff --git a/src/ci/mod.rs b/src/ci/mod.rs
index 8c4931d..6faa8bc 100644
--- a/src/ci/mod.rs
+++ b/src/ci/mod.rs
@@ -170,7 +170,7 @@ fn trigger_ref(
         }
     };
 
-    run.execute(pipeline, secrets.clone())?;
+    run.execute(pipeline, secrets.clone(), &repo.path())?;
     Ok(())
 }
 
diff --git a/src/ci/run.rs b/src/ci/run.rs
index 3d4453e..7167ed3 100644
--- a/src/ci/run.rs
+++ b/src/ci/run.rs
@@ -269,10 +269,11 @@ impl Run {
         mut self,
         pipeline: Pipeline,
         secrets: HashMap<String, SecretString>,
+        git_dir: &std::path::Path,
     ) -> Result<HashMap<String, Vec<ShOutput>>> {
         let meta = self.read_meta()?;
 
-        let runtime = Rc::new(Runtime::new(pipeline, secrets, &meta));
+        let runtime = Rc::new(Runtime::new(pipeline, secrets, &meta, git_dir));
 
         let lua = runtime.lua();
         let rt_value = RuntimeHandle(runtime.clone())
@@ -760,7 +761,9 @@ mod tests {
         );
 
         let run_id = run.id().to_string();
-        let outputs = run.execute(pipeline, HashMap::new()).expect("execute");
+        let outputs = run
+            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .expect("execute");
 
         // Verify the run landed in complete/ on disk.
         let completed = runs.base.join(RunState::Complete.dir_name()).join(&run_id);
@@ -792,7 +795,8 @@ mod tests {
         );
         let pipeline = load(&source);
 
-        run.execute(pipeline, HashMap::new()).expect("execute");
+        run.execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .expect("execute");
 
         let contents = fs_err::read_to_string(&log).expect("read log");
         assert_eq!(contents, "a\nb\n");
@@ -812,7 +816,7 @@ mod tests {
 
         let run_id = run.id().to_string();
         let err = run
-            .execute(pipeline, HashMap::new())
+            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
             .expect_err("expected failure");
         assert!(matches!(err, Error::JobFailed { ref job, .. } if job == "a"));
 
@@ -835,7 +839,9 @@ mod tests {
       (sh ["echo" push.sha push.ref]))))"#,
         );
 
-        let outputs = run.execute(pipeline, HashMap::new()).expect("execute");
+        let outputs = run
+            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .expect("execute");
 
         let grab = &outputs["grab"];
         assert_eq!(grab.len(), 1);
@@ -859,7 +865,9 @@ mod tests {
       (sh ["echo" push.sha]))))"#,
         );
 
-        let outputs = run.execute(pipeline, HashMap::new()).expect("execute");
+        let outputs = run
+            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .expect("execute");
 
         let b = &outputs["b"];
         assert_eq!(b.len(), 1);
@@ -878,7 +886,7 @@ mod tests {
         );
 
         let err = run
-            .execute(pipeline, HashMap::new())
+            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
             .expect_err("expected failure");
         let Error::JobFailed { job, source } = err else {
             unreachable!()
@@ -905,7 +913,7 @@ mod tests {
         );
 
         let err = run
-            .execute(pipeline, HashMap::new())
+            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
             .expect_err("expected failure");
         let Error::JobFailed { source, .. } = err else {
             unreachable!()
@@ -929,7 +937,7 @@ mod tests {
         );
 
         let err = run
-            .execute(pipeline, HashMap::new())
+            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
             .expect_err("expected failure");
         let Error::JobFailed { source, .. } = err else {
             unreachable!()
@@ -957,7 +965,9 @@ mod tests {
       (sh ["echo" (tostring a-outputs)]))))"#,
         );
 
-        let outputs = run.execute(pipeline, HashMap::new()).expect("execute");
+        let outputs = run
+            .execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .expect("execute");
         let b = &outputs["b"];
         assert_eq!(b.len(), 1);
         assert_eq!(b[0].stdout, "nil\n");
@@ -975,7 +985,8 @@ mod tests {
         );
 
         let run_id = run.id().to_string();
-        run.execute(pipeline, HashMap::new()).expect("execute");
+        run.execute(pipeline, HashMap::new(), std::path::Path::new("."))
+            .expect("execute");
 
         let log_path = runs
             .base
@@ -1010,7 +1021,7 @@ mod tests {
         );
 
         let run_id = run.id().to_string();
-        let _ = run.execute(pipeline, HashMap::new());
+        let _ = run.execute(pipeline, HashMap::new(), std::path::Path::new("."));
 
         let failed_dir = runs.base.join(RunState::Failed.dir_name()).join(&run_id);
         assert!(failed_dir.exists(), "run should be in failed/");