# VCS abstraction — detects jj or git from filesystem, exposes uniform operations. # # All ontoref modules should call these functions instead of hardcoding `git` # commands. Detection is filesystem-based (no config, no env var): checks for # `.jj/` (jj) and `.git/` (git) in ONTOREF_PROJECT_ROOT or ONTOREF_ROOT. use ./env.nu # Private: resolve project root following the same convention as other modules. def project-root []: nothing -> string { let pr = ($env.ONTOREF_PROJECT_ROOT? | default "") if ($pr | is-not-empty) and ($pr != $env.ONTOREF_ROOT) { $pr } else { $env.ONTOREF_ROOT } } # Detect which VCS is active in the project root. # Returns: "jj" | "git" | "none" export def detect []: nothing -> string { let root = (project-root) let has_jj = ($root | path join ".jj" | path exists) let has_git = ($root | path join ".git" | path exists) if $has_jj { "jj" } else if $has_git { "git" } else { "none" } } # True when any VCS is active. export def "is-repo" []: nothing -> bool { (detect) != "none" } # Get file content at last-committed state. # # jj model: the working copy IS a commit (@). Reading from @- gives the parent # commit — i.e., the state before any current working-copy edits. # `jj file show` requires jj >= 0.40. export def "show-committed" [file_path: string]: nothing -> string { let root = (project-root) match (detect) { "jj" => { let rel = ($file_path | path relative-to $root) do { ^jj --no-pager --repository $root file show $rel -r "@-" } | complete | if $in.exit_code == 0 { $in.stdout } else { "" } }, "git" => { let rel = ($file_path | path relative-to $root) do { ^git -C $root show $"HEAD:($rel)" } | complete | if $in.exit_code == 0 { $in.stdout } else { "" } }, _ => { "" } } } # Revert a file to its last-committed state. # # jj: `jj restore --from @-` undoes working-copy edits for the given file. # git: `git checkout -- ` restores from HEAD. export def "restore-file" [file_path: string]: nothing -> nothing { let root = (project-root) match (detect) { "jj" => { let rel = ($file_path | path relative-to $root) do { ^jj --no-pager --repository $root restore --from "@-" -- $rel } | complete | ignore }, "git" => { do { ^git -C $root checkout -- $file_path } | complete | ignore }, _ => { error make { msg: $"vcs restore-file: no VCS at ($root)" } } } } # Get the remote origin URL. export def "remote-url" []: nothing -> string { let root = (project-root) match (detect) { "jj" => { let r = do { ^jj --no-pager --repository $root git remote list } | complete if $r.exit_code != 0 or ($r.stdout | str trim | is-empty) { return "" } # Format: " " per line — extract URL from first remote $r.stdout | lines | where { |l| ($l | str trim | is-not-empty) } | first | split row "\t" | last | str trim }, "git" => { let r = do { ^git -C $root remote get-url origin } | complete if $r.exit_code == 0 { $r.stdout | str trim } else { "" } }, _ => { "" } } } # Get the current branch or bookmark name. # # jj: returns the bookmark pointing to @, or "detached" if none. # git: returns the branch name from rev-parse. export def "current-ref" []: nothing -> string { let root = (project-root) match (detect) { "jj" => { let r = do { ^jj --no-pager --repository $root bookmark list --pointing-to "@" -T "name ++ \"\\n\"" } | complete if $r.exit_code != 0 { return "detached" } let names = ($r.stdout | lines | where { |l| ($l | str trim | is-not-empty) }) if ($names | is-empty) { "detached" } else { $names | first } }, "git" => { let r = do { ^git -C $root rev-parse --abbrev-ref HEAD } | complete if $r.exit_code == 0 { $r.stdout | str trim } else { "unknown" } }, _ => { "unknown" } } } # True when the project root has a VCS remote configured. export def "has-remote" []: nothing -> bool { (remote-url) | is-not-empty }