Artifact 037953c1b1bb3745e5bd267347b3e95471be03b3ff90de898c7da2292d216554:
- File
internal/checkout/manage.go
— part of check-in
[847c195286]
at
2026-08-07 15:54:08
on branch trunk
— fix(blob): name new artifacts by the repo's hash-policy (#223) (#224)
Store hardcoded SHA1, so committing into a sha3 repo mixed hash families and made every tracked file read as CHANGED. Read hash-policy once per store and name new artifacts with the algorithm it selects.
Content already stored under the other algorithm's name keeps that name unless the policy forbids it (sha3-only, shun-sha1), so unchanged files don't churn their F-cards.
Also routes checkout.Manage and uv.Write through the same lookup, replacing their width-sniffing guesses — uv's read project-code, which is 40 hex chars in every repo, so it could only ever pick SHA1. (user: [email protected], size: 5093) [annotate] [blame] [check-ins using]
package checkout import ( "database/sql" "fmt" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/hash" ) // Manage adds files to tracking. For each path: // - Checks if already in vfile (skips if present) // - Reads file content from Storage // - Computes hash // - Inserts into vfile with rid=0, chnged=1 (marks as newly added) // // Panics if c is nil (TigerStyle precondition). func (c *Checkout) Manage(opts ManageOpts) (*ManageCounts, error) { if c == nil { panic("checkout.Manage: nil *Checkout") } counts := &ManageCounts{} vid, _, err := c.Version() if err != nil { return nil, fmt.Errorf("checkout.Manage: %w", err) } for _, path := range opts.Paths { // Check if already tracked var existingID int64 err := c.db.QueryRow( "SELECT id FROM vfile WHERE vid=? AND pathname=?", int64(vid), path, ).Scan(&existingID) if err != nil && err != sql.ErrNoRows { return counts, fmt.Errorf("checkout.Manage: query vfile: %w", err) } if err == nil { // Already tracked counts.Skipped++ if opts.Callback != nil { if err := opts.Callback(path, false); err != nil { return counts, fmt.Errorf("checkout.Manage: callback for %s: %w", path, err) } } continue } // Read file from Storage fullPath, err := c.safePath(path) if err != nil { return counts, fmt.Errorf("checkout.Manage: path traversal in %s: %w", path, err) } data, err := c.env.Storage.ReadFile(fullPath) if err != nil { return counts, fmt.Errorf("checkout.Manage: read %s: %w", path, err) } // Stat the on-disk file to capture its executable bit at add time. info, err := c.env.Storage.Stat(fullPath) if err != nil { return counts, fmt.Errorf("checkout.Manage: stat %s: %w", path, err) } isexe := 0 if modeIsExecutable(info.Mode()) { isexe = 1 } // Name the content the way this repo names artifacts, so the // vfile row matches the F-card a commit will write for it. mhash := hash.NamingFor(c.repo.DB()).New.Hash(data) // Insert into vfile with rid=0 (newly added), chnged=1 (modified) _, err = c.db.Exec( "INSERT INTO vfile(vid, pathname, rid, mrid, mhash, isexe, chnged) VALUES(?, ?, 0, 0, ?, ?, 1)", int64(vid), path, mhash, isexe, ) if err != nil { return counts, fmt.Errorf("checkout.Manage: insert vfile: %w", err) } counts.Added++ if opts.Callback != nil { if err := opts.Callback(path, true); err != nil { return counts, fmt.Errorf("checkout.Manage: callback for %s: %w", path, err) } } } return counts, nil } // Unmanage removes files from tracking. For each path: // - If rid=0 (newly added, never committed): DELETE from vfile // - If rid>0 (existing tracked file): UPDATE vfile SET deleted=1 // // Panics if c is nil (TigerStyle precondition). func (c *Checkout) Unmanage(opts UnmanageOpts) error { if c == nil { panic("checkout.Unmanage: nil *Checkout") } vid, _, err := c.Version() if err != nil { return fmt.Errorf("checkout.Unmanage: %w", err) } // Handle VFileIDs if provided if len(opts.VFileIDs) > 0 { for _, vfileID := range opts.VFileIDs { var rid int64 var pathname string err := c.db.QueryRow( "SELECT rid, pathname FROM vfile WHERE id=?", int64(vfileID), ).Scan(&rid, &pathname) if err == sql.ErrNoRows { continue // ID not found, skip } if err != nil { return fmt.Errorf("checkout.Unmanage: query vfile id=%d: %w", vfileID, err) } if err := c.unmanageFile(vfileID, rid, pathname, opts.Callback); err != nil { return fmt.Errorf("checkout.Unmanage: %w", err) } } return nil } // Handle Paths for _, path := range opts.Paths { var vfileID libfossil.FslID var rid int64 err := c.db.QueryRow( "SELECT id, rid FROM vfile WHERE vid=? AND pathname=?", int64(vid), path, ).Scan(&vfileID, &rid) if err == sql.ErrNoRows { // Not tracked, skip silently continue } if err != nil { return fmt.Errorf("checkout.Unmanage: query vfile: %w", err) } if err := c.unmanageFile(vfileID, rid, path, opts.Callback); err != nil { return fmt.Errorf("checkout.Unmanage: %w", err) } } return nil } // unmanageFile handles the logic for a single file: // - rid=0: DELETE (newly added, never committed) // - rid>0: UPDATE deleted=1 (existing file from repo) func (c *Checkout) unmanageFile( vfileID libfossil.FslID, rid int64, pathname string, callback func(string) error, ) error { if rid == 0 { // Newly added, never committed — remove completely _, err := c.db.Exec("DELETE FROM vfile WHERE id=?", int64(vfileID)) if err != nil { return fmt.Errorf("checkout.Unmanage: delete vfile: %w", err) } } else { // Existing tracked file — mark as deleted _, err := c.db.Exec("UPDATE vfile SET deleted=1 WHERE id=?", int64(vfileID)) if err != nil { return fmt.Errorf("checkout.Unmanage: update vfile: %w", err) } } if callback != nil { if err := callback(pathname); err != nil { return fmt.Errorf("checkout.Unmanage: callback for %s: %w", pathname, err) } } return nil }