Browse Source
Goal G-022: scriptable safe fossil move/rename in dev repo (all checkouts repointed, no phantom central config-db entries, no dangling old repo db, project-name renamable with project-code preserved), then rename this project fossil repo shellspy -> punkshell through that mechanism via a G-015 piped script call. New tests src/tests/modules/punk/mix/testsuites/repo/fossilmove.test pin current behaviour in a FOSSIL_HOME-isolated sandbox with an isolation guard: the file-copy + fossil test-move-repository sequence repoints the checkout but leaves the old db file on disk, a phantom repo: entry in the central config-db, and a stale ckout: back-reference; file moves never touch project-name. GAP-marked tests flip when G-022 lands. shellrun 0.1.4: add missing package require punk::args - the 0.1.3 migration left run/runout/runerr/runx calling punk::args::parse without requiring it, which broke shellrun in bare interps and made every src/tests/runtests.tcl run fail at test-file invocation. Full suite now back to baseline (523 tests: 508 pass, 14 skip, exec-14.3 known failure). Project version 0.2.4 (changelog entry for the shellrun fix). Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
8 changed files with 341 additions and 3 deletions
@ -0,0 +1,75 @@
|
||||
# G-022 Scriptable safe fossil move/rename in `dev repo`; rename this project's fossil repo to punkshell |
||||
|
||||
Status: proposed |
||||
Scope: src/modules/punk/mix/commandset/repo-999999.0a1.0.tm, src/modules/punk/repo-999999.0a1.0.tm, src/tests/modules/punk/mix/testsuites/repo/ |
||||
Acceptance: as in root GOALS.md index (canonical). |
||||
|
||||
## Context |
||||
|
||||
This project's fossil repository is recorded as project-name 'shellspy' |
||||
(C:/Users/sleek/.fossils/shellspy.fossil at the time of writing) but the |
||||
desired project name is punkshell ('shellspy' names only the experimental |
||||
launch subcommand). Rather than having an agent perform ad-hoc repo surgery, |
||||
the rename should go through the `dev repo` system - which makes the |
||||
prerequisite the reliability of agent-driven `script` calls (G-015). |
||||
|
||||
Current capability (examined 2026-07-06, characterized by |
||||
src/tests/modules/punk/mix/testsuites/repo/fossilmove.test): |
||||
|
||||
- `punk::mix::commandset::repo::fossil-move-repository` exists but is fully |
||||
interactive (punk::repo::askuser prompts throughout) - unusable from a |
||||
piped script call. |
||||
- Its move primitive is `file copy` + `fossil test-move-repository` per |
||||
checkout. Verified in a FOSSIL_HOME-isolated sandbox, this sequence: |
||||
- correctly repoints the checkout's repository link (the part that works); |
||||
- leaves the old .fossil file on disk (copy, never delete); |
||||
- leaves a phantom `repo:` entry for the old path in the central fossil |
||||
config-db (`fossil all ls` lists both old and new paths); |
||||
- leaves a stale `ckout:` back-reference in the old repo db. |
||||
- Nothing renames the fossil project-name: moving/renaming the db file leaves |
||||
`project-name` in the repo db config table untouched, and fossil has no |
||||
plain CLI setter for it (web UI Admin/Configuration, or a config-table |
||||
update e.g. via `fossil sql`). |
||||
|
||||
Phantom central-db entries matter beyond hygiene: `dev projects.work` |
||||
(G-016/G-017's agent-facing project discovery) reads the central config-db, so |
||||
phantoms surface directly in agent search results. |
||||
|
||||
## Approach |
||||
|
||||
- Extend the commandset with a non-interactive mode (explicit flags for |
||||
target path/name, project-name change, old-db disposition) alongside the |
||||
existing interactive flow; punk::args-documented per module conventions. |
||||
- Safe-move sequence: copy to target, repoint every registered checkout |
||||
(`fossil test-move-repository` per checkout, or direct checkout-db vvar |
||||
update), remove the old central-db `repo:` entry, clear/transfer stale |
||||
`ckout:` records, then remove or archive the old db file - ordered so a |
||||
failure mid-way never leaves checkouts pointing at a missing db. |
||||
- Project-name rename: update `project-name` in the repo db config table |
||||
(project-code must never change - it is the repo's identity); surface both |
||||
names in the operation's report. |
||||
- The fossilmove.test GAP tests (3.2, 3.3, 3.4, 4.1) pin today's behaviour; |
||||
flipping them to assert clean behaviour is part of this goal's verification. |
||||
- The final application to this repo runs through a G-015 piped `script` |
||||
call, as the proving use of agent-driven `dev repo` operations. |
||||
|
||||
## Alternatives considered |
||||
|
||||
- Ad-hoc agent-performed rename (fossil sql + file rename + manual central-db |
||||
cleanup by hand) - rejected: the point is that repo surgery goes through |
||||
audited `dev repo` tooling agents can reuse, not one-off incantations. |
||||
- Renaming only the .fossil file and leaving project-name as shellspy - |
||||
rejected: the project-name is the visible identity in `fossil info`, |
||||
timelines and project listings; it is the thing actually wrong. |
||||
|
||||
## Notes |
||||
|
||||
- Dependent on G-015 (reliable piped script calls) for the application step; |
||||
the capability work itself has no dependency and can proceed earlier. |
||||
- The working-tree directory name (C:/repo/jn/shellspy) is out of scope - |
||||
fossil does not require checkout dirs to match the project name. |
||||
- Other checkouts of shellspy.fossil (e.g. a shellspy_copy working tree, if |
||||
open) must be enumerated and repointed by the same run. |
||||
- Test isolation pattern for anything touching fossil state: set FOSSIL_HOME |
||||
to a temp dir and verify `fossil info` reports config-db under it before |
||||
mutating (see fossilmove.test fixture guard). |
||||
@ -1,3 +1,3 @@
|
||||
[project] |
||||
name = "punkshell" |
||||
version = "0.2.3" |
||||
version = "0.2.4" |
||||
|
||||
@ -0,0 +1,251 @@
|
||||
# -*- tcl -*- |
||||
# Tests for the fossil repository move/rename area used by |
||||
# punk::mix::commandset::repo::fossil-move-repository: |
||||
# - punk::repo fossil discovery primitives against a scratch repo+checkout |
||||
# (fossil_get_configdb, find_fossil, fossil_get_repository_file) |
||||
# - the move primitive sequence the commandset uses (file copy + fossil |
||||
# test-move-repository) and what it leaves behind |
||||
# The 3.x/4.x tests are CHARACTERIZATION tests: they pin the current behaviour, |
||||
# including the gaps a future scriptable safe-move/rename must close (see root |
||||
# GOALS.md G-022): the old repo db file is left on disk, the central config-db |
||||
# keeps a phantom repo: entry for the old path, the old repo db keeps a stale |
||||
# ckout: back-reference, and nothing renames the fossil project-name. |
||||
# When that capability lands, the gap tests here are expected to be updated to |
||||
# assert the new (clean) behaviour. |
||||
# |
||||
# Isolation: all fossil activity runs with FOSSIL_HOME pointing into a temp |
||||
# folder, so the user's real central fossil config-db is never touched. Every |
||||
# mutating step is gated on verifying fossil actually reports its config-db |
||||
# under that temp folder. |
||||
# |
||||
# Run: tclsh src/tests/runtests.tcl -report compact -show-passes 0 -include-paths modules/punk/mix/** fossilmove.test |
||||
|
||||
package require tcltest |
||||
package require punk::lib |
||||
package require punk::repo |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
|
||||
# ------------------------------------------------------------------------- |
||||
# Constraints and isolation guard |
||||
# ------------------------------------------------------------------------- |
||||
set fossilcmd [auto_execok fossil] |
||||
tcltest::testConstraint fossil_available [expr {[llength $fossilcmd] > 0}] |
||||
|
||||
variable env_saved_exists [info exists ::env(FOSSIL_HOME)] |
||||
variable env_saved_value [expr {$env_saved_exists ? $::env(FOSSIL_HOME) : ""}] |
||||
|
||||
variable tempbase "" |
||||
variable fosshome "" |
||||
variable repodir "" |
||||
variable codir "" |
||||
|
||||
proc path_eq {a b} { |
||||
string equal -nocase [file normalize $a] [file normalize $b] |
||||
} |
||||
|
||||
proc run_fossil {workdir args} { |
||||
variable fossilcmd |
||||
set original_cwd [pwd] |
||||
try { |
||||
if {$workdir ne ""} { |
||||
cd $workdir |
||||
} |
||||
exec -ignorestderr {*}$fossilcmd {*}$args |
||||
} finally { |
||||
cd $original_cwd |
||||
} |
||||
} |
||||
|
||||
# central config-db repo entries as reported by fossil itself |
||||
proc central_repo_list {} { |
||||
variable codir |
||||
set lines [run_fossil $codir all ls] |
||||
set paths [list] |
||||
foreach ln [split [string map {\r\n \n} $lines] \n] { |
||||
set ln [string trim $ln] |
||||
if {$ln ne ""} { lappend paths $ln } |
||||
} |
||||
return $paths |
||||
} |
||||
|
||||
proc central_has_repo {repopath} { |
||||
foreach p [central_repo_list] { |
||||
if {[path_eq $p $repopath]} { return 1 } |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
# ckout: checkout-record paths registered in a repository db |
||||
proc repo_ckout_dirs {repodb} { |
||||
set lines [run_fossil "" sql -R $repodb \ |
||||
"select name from config where name like 'ckout:%'"] |
||||
set dirs [list] |
||||
foreach ln [split [string map {\r\n \n} $lines] \n] { |
||||
set ln [string trim $ln] |
||||
if {[string match ckout:* $ln]} { |
||||
lappend dirs [string trimright [string range $ln 6 end] /] |
||||
} |
||||
} |
||||
return $dirs |
||||
} |
||||
|
||||
proc repo_project_name {repodb} { |
||||
string trim [run_fossil "" sql -R $repodb \ |
||||
"select value from config where name='project-name'"] |
||||
} |
||||
|
||||
# ------------------------------------------------------------------------- |
||||
# Fixture: isolated FOSSIL_HOME, scratch repo db + one open checkout. |
||||
# Any failure (including an isolation check failure) disables the |
||||
# fossil_fixture constraint so no test proceeds against the wrong config-db. |
||||
# ------------------------------------------------------------------------- |
||||
variable fixture_error "" |
||||
if {[tcltest::testConstraint fossil_available]} { |
||||
try { |
||||
set tempbase [punk::lib::tempdir_newfolder -prefix pkfossilmovetest] |
||||
set fosshome [file join $tempbase fosshome] |
||||
set repodir [file join $tempbase repos] |
||||
set codir [file join $tempbase co] |
||||
file mkdir $fosshome $repodir $codir |
||||
set ::env(FOSSIL_HOME) $fosshome |
||||
|
||||
run_fossil "" init [file join $repodir repoA.fossil] -project-name testmoveproj |
||||
run_fossil $codir open [file join $repodir repoA.fossil] |
||||
|
||||
# isolation guard: fossil must report its config-db under our |
||||
# temp FOSSIL_HOME or nothing else may run |
||||
set infolines [run_fossil $codir info] |
||||
set configdb "" |
||||
foreach ln [split [string map {\r\n \n} $infolines] \n] { |
||||
if {[string match "config-db:*" [string trim $ln]]} { |
||||
set configdb [string trim [string range [string trim $ln] 10 end]] |
||||
} |
||||
} |
||||
if {$configdb eq "" || ![string match -nocase "[file normalize $fosshome]*" [file normalize $configdb]]} { |
||||
error "isolation check failed: fossil config-db '$configdb' is not under temp FOSSIL_HOME '$fosshome'" |
||||
} |
||||
} on error {result} { |
||||
set fixture_error $result |
||||
} |
||||
} |
||||
tcltest::testConstraint fossil_fixture [expr { |
||||
[tcltest::testConstraint fossil_available] && $fixture_error eq "" |
||||
}] |
||||
if {$fixture_error ne ""} { |
||||
puts stderr "fossilmove.test fixture setup failed (tests will be skipped): $fixture_error" |
||||
} |
||||
|
||||
# ------------------------------------------------------------------------- |
||||
# 1.x punk::repo discovery primitives against the scratch checkout |
||||
# ------------------------------------------------------------------------- |
||||
test fossilmove-1.1 {fossil_get_configdb resolves to the FOSSIL_HOME-isolated config-db} -constraints fossil_fixture -body { |
||||
variable fosshome |
||||
set configdb [punk::repo::fossil_get_configdb $::testspace::codir] |
||||
string match -nocase "[file normalize $fosshome]*" [file normalize $configdb] |
||||
} -result 1 |
||||
|
||||
test fossilmove-1.2 {find_fossil locates the scratch checkout root} -constraints fossil_fixture -body { |
||||
variable codir |
||||
path_eq [punk::repo::find_fossil $codir] $codir |
||||
} -result 1 |
||||
|
||||
test fossilmove-1.3 {fossil_get_repository_file returns the opened repo db path} -constraints fossil_fixture -body { |
||||
variable codir |
||||
variable repodir |
||||
path_eq [punk::repo::fossil_get_repository_file $codir] [file join $repodir repoA.fossil] |
||||
} -result 1 |
||||
|
||||
# ------------------------------------------------------------------------- |
||||
# 2.x central config-db registration baseline |
||||
# ------------------------------------------------------------------------- |
||||
test fossilmove-2.1 {open registers the repo db in the central config-db} -constraints fossil_fixture -body { |
||||
variable repodir |
||||
central_has_repo [file join $repodir repoA.fossil] |
||||
} -result 1 |
||||
|
||||
test fossilmove-2.2 {repo db records its checkout as a ckout: entry} -constraints fossil_fixture -body { |
||||
variable repodir |
||||
variable codir |
||||
set dirs [repo_ckout_dirs [file join $repodir repoA.fossil]] |
||||
expr {[llength $dirs] == 1 && [path_eq [lindex $dirs 0] $codir]} |
||||
} -result 1 |
||||
|
||||
# ------------------------------------------------------------------------- |
||||
# 3.x the move primitive sequence used by fossil-move-repository: |
||||
# file copy old->new, then fossil test-move-repository in the checkout. |
||||
# 3.1 pins the part that works; 3.2-3.4 pin the current gaps. |
||||
# ------------------------------------------------------------------------- |
||||
variable move_error "" |
||||
if {[tcltest::testConstraint fossil_fixture]} { |
||||
try { |
||||
file copy [file join $repodir repoA.fossil] [file join $repodir repoB.fossil] |
||||
run_fossil $codir test-move-repository [file join $repodir repoB.fossil] |
||||
} on error {result} { |
||||
set move_error $result |
||||
} |
||||
} |
||||
tcltest::testConstraint fossil_moved [expr { |
||||
[tcltest::testConstraint fossil_fixture] && $move_error eq "" |
||||
}] |
||||
if {$move_error ne ""} { |
||||
puts stderr "fossilmove.test move sequence failed (3.x/4.x will be skipped): $move_error" |
||||
} |
||||
|
||||
test fossilmove-3.1 {test-move-repository repoints the checkout to the new repo db} -constraints fossil_moved -body { |
||||
variable codir |
||||
variable repodir |
||||
path_eq [punk::repo::fossil_get_repository_file $codir] [file join $repodir repoB.fossil] |
||||
} -result 1 |
||||
|
||||
test fossilmove-3.2 {GAP: move-by-copy leaves the old repo db file on disk} -constraints fossil_moved -body { |
||||
# current commandset behaviour is file copy + repoint - the old db file |
||||
# remains; a safe scriptable move should remove/archive it once no |
||||
# checkout references it (G-022) |
||||
variable repodir |
||||
file exists [file join $repodir repoA.fossil] |
||||
} -result 1 |
||||
|
||||
test fossilmove-3.3 {GAP: central config-db keeps a phantom repo: entry for the old path} -constraints fossil_moved -body { |
||||
# both old and new paths are listed after the move - the old entry is |
||||
# the phantom a safe move must remove (G-022) |
||||
variable repodir |
||||
list [central_has_repo [file join $repodir repoA.fossil]] \ |
||||
[central_has_repo [file join $repodir repoB.fossil]] |
||||
} -result {1 1} |
||||
|
||||
test fossilmove-3.4 {GAP: old repo db retains a stale ckout: back-reference} -constraints fossil_moved -body { |
||||
# the checkout no longer uses repoA but repoA still claims it (G-022) |
||||
variable repodir |
||||
variable codir |
||||
set dirs [repo_ckout_dirs [file join $repodir repoA.fossil]] |
||||
expr {[llength $dirs] == 1 && [path_eq [lindex $dirs 0] $codir]} |
||||
} -result 1 |
||||
|
||||
# ------------------------------------------------------------------------- |
||||
# 4.x project-name: moving/renaming the db file does not rename the project |
||||
# ------------------------------------------------------------------------- |
||||
test fossilmove-4.1 {GAP: repo db file move/rename leaves fossil project-name unchanged} -constraints fossil_moved -body { |
||||
# no current commandset capability renames the project-name recorded in |
||||
# the repo db config table (G-022 requires it for shellspy->punkshell) |
||||
variable repodir |
||||
repo_project_name [file join $repodir repoB.fossil] |
||||
} -result testmoveproj |
||||
|
||||
# ------------------------------------------------------------------------- |
||||
# Cleanup: restore FOSSIL_HOME and remove the temp tree |
||||
# ------------------------------------------------------------------------- |
||||
variable env_saved_exists |
||||
if {$env_saved_exists} { |
||||
set ::env(FOSSIL_HOME) $env_saved_value |
||||
} else { |
||||
unset -nocomplain ::env(FOSSIL_HOME) |
||||
} |
||||
if {$tempbase ne "" && [file isdirectory $tempbase]} { |
||||
catch {file delete -force $tempbase} |
||||
} |
||||
} |
||||
|
||||
tcltest::cleanupTests |
||||
namespace delete ::testspace |
||||
Loading…
Reference in new issue