@@ -22,6 +22,10 @@ import (
2222
2323// DiffLocal compares local changes in mxunit files against a git reference.
2424// This only works with MPR v2 format (Mendix 10.18+) which stores units in mprcontents/.
25+ //
26+ // The ref parameter can be:
27+ // - A single ref (e.g., "HEAD", "main") — compares working tree vs ref
28+ // - A range "base..target" — compares two revisions (no working tree)
2529func (e * Executor ) DiffLocal (ref string , opts DiffOptions ) error {
2630 if e .reader == nil {
2731 return fmt .Errorf ("not connected to a project" )
@@ -142,25 +146,46 @@ func (e *Executor) findChangedMxunitFiles(contentsDir, ref string) ([]gitChange,
142146 return changes , nil
143147}
144148
145- // diffMxunitFile generates a diff for a single mxunit file
149+ // diffMxunitFile generates a diff for a single mxunit file.
150+ // For two-revision diffs (ref contains ".."), both sides are read from git.
151+ // For single-ref diffs, the "current" side is read from the working tree.
146152func (e * Executor ) diffMxunitFile (change gitChange , contentsDir , ref string ) (* DiffResult , error ) {
147153 var currentContent , gitContent []byte
148154 var err error
149155
150- // Read current content (for modified or new files)
151- if change .Status != "D" {
152- currentContent , err = readFile (change .FilePath )
153- if err != nil {
154- return nil , fmt .Errorf ("failed to read current file %s: %w" , change .FilePath , err )
155- }
156- }
156+ // Determine if this is a two-revision diff
157+ baseRef , targetRef , isTwoRevision := parseRefRange (ref )
157158
158- // Read git content (for modified or deleted files)
159- if change .Status != "A" {
160- cmd := execCommand ("git" , "show" , ref + ":" + change .FilePath )
161- gitContent , err = cmd .Output ()
162- if err != nil {
163- return nil , fmt .Errorf ("failed to read git version of %s: %w" , change .FilePath , err )
159+ if isTwoRevision {
160+ // Two-revision mode: both sides from git
161+ if change .Status != "D" {
162+ cmd := execCommand ("git" , "show" , targetRef + ":" + change .FilePath )
163+ currentContent , err = cmd .Output ()
164+ if err != nil {
165+ return nil , fmt .Errorf ("failed to read %s version of %s: %w" , targetRef , change .FilePath , err )
166+ }
167+ }
168+ if change .Status != "A" {
169+ cmd := execCommand ("git" , "show" , baseRef + ":" + change .FilePath )
170+ gitContent , err = cmd .Output ()
171+ if err != nil {
172+ return nil , fmt .Errorf ("failed to read %s version of %s: %w" , baseRef , change .FilePath , err )
173+ }
174+ }
175+ } else {
176+ // Single-ref mode: current from working tree, old from git
177+ if change .Status != "D" {
178+ currentContent , err = readFile (change .FilePath )
179+ if err != nil {
180+ return nil , fmt .Errorf ("failed to read current file %s: %w" , change .FilePath , err )
181+ }
182+ }
183+ if change .Status != "A" {
184+ cmd := execCommand ("git" , "show" , ref + ":" + change .FilePath )
185+ gitContent , err = cmd .Output ()
186+ if err != nil {
187+ return nil , fmt .Errorf ("failed to read git version of %s: %w" , change .FilePath , err )
188+ }
164189 }
165190 }
166191
@@ -694,6 +719,15 @@ func (e *Executor) compareGeneric(current, proposed string) []StructuralChange {
694719// Helper Functions for Git and BSON Parsing
695720// ============================================================================
696721
722+ // parseRefRange splits a ref like "base..target" into its parts.
723+ // Returns (base, target, true) for ranges, or ("", ref, false) for single refs.
724+ func parseRefRange (ref string ) (base , target string , isRange bool ) {
725+ if idx := strings .Index (ref , ".." ); idx >= 0 {
726+ return ref [:idx ], ref [idx + 2 :], true
727+ }
728+ return "" , ref , false
729+ }
730+
697731// execCommand creates an exec.Cmd for running git commands
698732func execCommand (name string , args ... string ) * exec.Cmd {
699733 return exec .Command (name , args ... )
0 commit comments