Artifact 6a8a54a3af0b68ae52be43b4d7221f2157afbc3066f0a6d933161f66dc8eba05:
- File
cli/repo_merge.go
— part of check-in
[cba2d0c823]
at
2026-10-13 11:05:50
on branch main
— feat(resolve): resolve checkout-relative names and branch-type requests (#409)
* feat(resolve): resolve checkout-relative names and branch-type requests
Checkout.ResolveVersion resolves current, prev/previous and next relative to the open checkout, after tip and before the other forms, as fossil's symbolic_name_to_rid does; a keyword that names nothing falls through. Repo.ResolveBranchStart is the branch-type request: it resolves a name and walks back to the first check-in of its branch. Section 8.3 walks back from every result; fossil only from tag:NAME and plain names, and this follows the draft.
The repostate.8.3 checkout and branch-type cases run against the new entry points, with new cases for a keyword shadowing a tag, prev at a root, an untyped ckout, root:current and a branch-type hash. Tracked in #386.
* fix(resolve): match check-ins only for branch-type requests, merge via checkout
A branch-type request is rewritten to a check-in request (repo-state-01 Section 8.3), so a technote tagged with the branch name cannot answer it. A checkout with no recorded version resolves the ordinary forms, and merge resolves its version through the open checkout. Tracked in #386.
* fix(resolve): wrap a long test line (user: [email protected], size: 2180) [annotate] [blame] [check-ins using]
package cli import ( "fmt" libfossil "github.com/danmestas/go-libfossil" ) // RepoMergeCmd merges a divergent version into the current checkout. type RepoMergeCmd struct { Version string `arg:"" help:"Version to merge into current checkout"` Strategy string `help:"Override merge strategy for all files"` DryRun bool `help:"Show what would be merged without writing"` Dir string `short:"d" help:"Checkout directory" default:"."` } func (c *RepoMergeCmd) Run(g *Globals) (err error) { _, co, done, err := openWorkingCheckout(g, c.Dir) if err != nil { return err } defer closeWith(done, &err) rid, err := co.ResolveVersion(c.Version) if err != nil { return fmt.Errorf("resolving %s: %w", c.Version, err) } res, err := co.Merge(libfossil.CheckoutMergeOpts{ Version: rid, Strategy: c.Strategy, DryRun: c.DryRun, }) if err != nil { return err } printMergeResult(res, c.DryRun) return nil } // mergeLabels are the labels fossil's merge prints for each action. var mergeLabels = map[string]string{ "updated": "UPDATE", "merged": "MERGE", "conflict": "MERGE", "kept": "KEPT", "forked": "FORK", "added": "ADDED", "deleted": "DELETE", "mode": "MODE", } // printMergeResult reports a merge the way fossil's merge command does. func printMergeResult(res libfossil.CheckoutMergeResult, dryRun bool) { if len(res.Files) == 0 { fmt.Println("merge skipped: nothing to merge") return } conflicts := 0 for _, f := range res.Files { label, ok := mergeLabels[f.Action] if !ok { panic("cli.printMergeResult: unknown merge action " + f.Action) } switch f.Action { case "conflict": fmt.Printf("%s %s\n***** merge conflict in %s\n", label, f.Name, f.Name) conflicts++ case "kept": fmt.Printf("%s %s (%s)\n", label, f.Name, f.Reason) conflicts++ case "forked": fmt.Printf("%s %s (conflict-fork: all versions preserved)\n", label, f.Name) conflicts++ default: fmt.Printf("%s %s\n", label, f.Name) } } if conflicts > 0 { fmt.Printf("WARNING: %d merge conflicts\n", conflicts) } if dryRun { fmt.Println("REMINDER: this was a dry run - no files were actually changed.") } }