Artifact 8d33645473358b3fffdaa300dfc154d877b2f46bdc7bc41f05730bc4ce6be35c:
- File
internal/checkout/rename.go
— part of check-in
[f5744359d3]
at
2026-04-19 16:30:54
on branch trunk
— Initial open source release
libfossil is a pure-Go implementation of the Fossil SCM, reading and writing the same .fossil SQLite repository format as upstream Fossil.
This is the first public release of code that previously lived in a private repository. The module path is github.com/danmestas/libfossil and the binary is named libfossil to avoid $PATH collisions with upstream fossil.
Included: - Repo lifecycle (create, clone), checkout/checkin, timeline, merge, diff, annotate, shun, tag, branch, bisect, stash, undo/redo - Sync-over-HTTP protocol (client and handler) - Two SQLite drivers: modernc (pure Go, default) and ncruces (wasm-capable) - Observer interfaces (SyncObserver, CheckoutObserver) with an optional OpenTelemetry implementation in observer/otel (separate submodule) - Deterministic simulation testing harness with BUGGIFY fault injection - Kong-based embeddable CLI under cli/ and a standalone binary under cmd/libfossil/ (user: [email protected], size: 5864) [annotate] [blame] [check-ins using]
package checkout import ( "database/sql" "fmt" "os" "path/filepath" ) // moveFile reads a file from oldPath, writes it to newPath with the given // permissions, and removes the original. Ignores NotExist on remove. func (c *Checkout) moveFile(oldPath, newPath string, perm os.FileMode) error { data, err := c.env.Storage.ReadFile(oldPath) if err != nil { return fmt.Errorf("read %s: %w", oldPath, err) } newParentDir := filepath.Dir(newPath) if err := c.env.Storage.MkdirAll(newParentDir, 0o755); err != nil { return fmt.Errorf("mkdir %s: %w", newParentDir, err) } if err := c.env.Storage.WriteFile(newPath, data, perm); err != nil { return fmt.Errorf("write %s: %w", newPath, err) } if err := c.env.Storage.Remove(oldPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("remove %s: %w", oldPath, err) } return nil } // vfilePerm queries the isexe flag for a vfile row and returns the file mode. func (c *Checkout) vfilePerm(vfileID int64) (os.FileMode, error) { var isexe int64 err := c.db.QueryRow("SELECT isexe FROM vfile WHERE id=?", vfileID).Scan(&isexe) if err != nil { return 0, fmt.Errorf("query vfile permissions: %w", err) } if isexe != 0 { return 0o755, nil } return 0o644, nil } // Rename marks a file as renamed in vfile by updating pathname and origname. // Sets chnged=1 to indicate the file has been modified. // // If opts.DoFsMove is true, also moves the file in Storage from old to new path. // // Panics if c is nil, or if From or To is empty (TigerStyle precondition). func (c *Checkout) Rename(opts RenameOpts) error { if c == nil { panic("checkout.Rename: nil *Checkout") } if opts.From == "" { panic("checkout.Rename: empty From") } if opts.To == "" { panic("checkout.Rename: empty To") } // Get current version vid, _, err := c.Version() if err != nil { return fmt.Errorf("checkout.Rename: %w", err) } // Verify From exists in vfile var vfileID int64 err = c.db.QueryRow( "SELECT id FROM vfile WHERE vid=? AND pathname=?", int64(vid), opts.From, ).Scan(&vfileID) if err == sql.ErrNoRows { return fmt.Errorf("checkout.Rename: file %s not found in vfile", opts.From) } if err != nil { return fmt.Errorf("checkout.Rename: query vfile for %s: %w", opts.From, err) } // Verify To does NOT exist in vfile (uniqueness check) var existingID int64 err = c.db.QueryRow( "SELECT id FROM vfile WHERE vid=? AND pathname=?", int64(vid), opts.To, ).Scan(&existingID) if err != nil && err != sql.ErrNoRows { return fmt.Errorf("checkout.Rename: query vfile for %s: %w", opts.To, err) } if err == nil { return fmt.Errorf("checkout.Rename: target file %s already exists in vfile", opts.To) } // Update vfile: set new pathname, store old pathname in origname, mark as changed _, err = c.db.Exec( "UPDATE vfile SET pathname=?, origname=?, chnged=1 WHERE id=?", opts.To, opts.From, vfileID, ) if err != nil { return fmt.Errorf("checkout.Rename: update vfile: %w", err) } // If DoFsMove, move the file in Storage if opts.DoFsMove { perm, err := c.vfilePerm(vfileID) if err != nil { return fmt.Errorf("checkout.Rename: %w", err) } oldPath, err := c.safePath(opts.From) if err != nil { return fmt.Errorf("checkout.Rename: path traversal in %s: %w", opts.From, err) } newPath, err := c.safePath(opts.To) if err != nil { return fmt.Errorf("checkout.Rename: path traversal in %s: %w", opts.To, err) } if err := c.moveFile(oldPath, newPath, perm); err != nil { return fmt.Errorf("checkout.Rename: %w", err) } } // Call callback if provided if opts.Callback != nil { if err := opts.Callback(opts.From, opts.To); err != nil { return fmt.Errorf("checkout.Rename: callback: %w", err) } } return nil } // RevertRename restores a renamed file to its original pathname. // Returns (true, nil) if the revert succeeded, (false, nil) if there was nothing to revert. // // Panics if c is nil or name is empty (TigerStyle precondition). func (c *Checkout) RevertRename(name string, doFsMove bool) (bool, error) { if c == nil { panic("checkout.RevertRename: nil *Checkout") } if name == "" { panic("checkout.RevertRename: empty name") } // Get current version vid, _, err := c.Version() if err != nil { return false, fmt.Errorf("checkout.RevertRename: %w", err) } // Look up vfile entry var vfileID int64 var origname sql.NullString err = c.db.QueryRow( "SELECT id, origname FROM vfile WHERE vid=? AND pathname=?", int64(vid), name, ).Scan(&vfileID, &origname) if err == sql.ErrNoRows { return false, fmt.Errorf("checkout.RevertRename: file %s not found in vfile", name) } if err != nil { return false, fmt.Errorf("checkout.RevertRename: query vfile for %s: %w", name, err) } // If origname is empty/NULL, nothing to revert if !origname.Valid || origname.String == "" { return false, nil } oldOrigName := origname.String // Update vfile: restore origname as pathname, clear origname, reset chnged _, err = c.db.Exec( "UPDATE vfile SET pathname=?, origname=NULL, chnged=0 WHERE id=?", oldOrigName, vfileID, ) if err != nil { return false, fmt.Errorf("checkout.RevertRename: update vfile: %w", err) } // If doFsMove, move the file back in Storage if doFsMove { perm, err := c.vfilePerm(vfileID) if err != nil { return false, fmt.Errorf("checkout.RevertRename: %w", err) } currentPath, err := c.safePath(name) if err != nil { return false, fmt.Errorf("checkout.RevertRename: path traversal in %s: %w", name, err) } originalPath, err := c.safePath(oldOrigName) if err != nil { return false, fmt.Errorf("checkout.RevertRename: path traversal in %s: %w", oldOrigName, err) } if err := c.moveFile(currentPath, originalPath, perm); err != nil { return false, fmt.Errorf("checkout.RevertRename: %w", err) } } return true, nil }