Artifact 6e93b81975645f2bd994b3fe39cd62d68f032f3a37b16c61dc62d39eeb8d713c:
- File
cli/repo_merge.go
— part of check-in
[7ea6fba63f]
at
2026-10-06 15:57:25
on branch trunk
— fix(checkout): merge the way fossil does, and commit merges with their parent (#244) (#252)
The CLI's merge marked a conflict with vfile.chnged=5, which fossil reads as "added by integrate", and recorded no merge-in version or vmerge row, so a merge went unseen and the next commit dropped the merge parent.
Merging now lives in the library as Checkout.Merge and records what fossil records: chnged=2 and mrid for a file taken from the merged-in version, chnged=3 for an added file, a per-file vmerge row for a merged file, and vmerge id 0 for the merge. Conflicts use fossil's markers and are found by looking for them (Checkout.Conflicts), so fossil and go-libfossil read each other's merges. Where fossil relies on its undo copy, Merge keeps the file or refuses instead.
Commit reads vmerge for merge parents, and, as fossil's commit does, refuses unresolved conflict markers, empty commits, unknown names and partial commits of a merge. The CLI's merge, conflicts, mark-resolved and ci commands call the library; ci now commits the checkout. (user: [email protected], size: 2176) [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) { r, co, done, err := openWorkingCheckout(g, c.Dir) if err != nil { return err } defer closeWith(done, &err) rid, err := resolveRID(r, 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.") } }