@@ -105,7 +105,41 @@ impl ObjectDiffOperation {
105105 }
106106
107107 /// Get baseline object definition (before the change)
108- fn get_baseline_object ( & self , object_name : & str , change : & Change ) -> Result < Option < moor_compiler:: ObjectDefinition > , ObjectsTreeError > {
108+ /// If baseline_change_id is provided, use that specific change as the baseline
109+ /// Otherwise, use the default logic (preceding change for merged, or index_change_id for non-merged)
110+ fn get_baseline_object ( & self , object_name : & str , change : & Change , baseline_change_id : Option < & str > ) -> Result < Option < moor_compiler:: ObjectDefinition > , ObjectsTreeError > {
111+ // If a specific baseline change was requested, use that
112+ if let Some ( baseline_id) = baseline_change_id {
113+ let resolved_baseline_id = self . database . resolve_change_id ( baseline_id) ?;
114+
115+ // Try to find the baseline change in index first
116+ let baseline_change = self
117+ . database
118+ . index ( )
119+ . get_change ( & resolved_baseline_id)
120+ . map_err ( |e| ObjectsTreeError :: SerializationError ( e. to_string ( ) ) ) ?;
121+
122+ if let Some ( base_change) = baseline_change {
123+ return self . get_object_at_change ( object_name, & base_change) ;
124+ } else {
125+ // Try workspace if not in index
126+ let baseline_change = self
127+ . database
128+ . workspace ( )
129+ . get_workspace_change ( & resolved_baseline_id)
130+ . map_err ( |e| ObjectsTreeError :: SerializationError ( e. to_string ( ) ) ) ?;
131+
132+ if let Some ( base_change) = baseline_change {
133+ return self . get_object_at_change ( object_name, & base_change) ;
134+ } else {
135+ return Err ( ObjectsTreeError :: SerializationError ( format ! (
136+ "Baseline change '{}' not found in index or workspace" ,
137+ resolved_baseline_id
138+ ) ) ) ;
139+ }
140+ }
141+ }
142+
109143 // If change is merged, we need to get the state before this change
110144 if change. status == ChangeStatus :: Merged {
111145 // Get the change order and find the position of this change
@@ -159,12 +193,12 @@ impl ObjectDiffOperation {
159193 }
160194
161195 /// Process the object diff request
162- fn process_object_diff ( & self , change_id : & str , object_name : & str ) -> Result < Vec < VerbDiff > , ObjectsTreeError > {
196+ fn process_object_diff ( & self , object_name : & str , change_id : & str , baseline_change_id : Option < & str > ) -> Result < Vec < VerbDiff > , ObjectsTreeError > {
163197 // Resolve short or full hash to full hash
164198 let resolved_change_id = self . database . resolve_change_id ( change_id) ?;
165199 info ! (
166- "Computing object diff for '{}' at change ID '{}'" ,
167- object_name, resolved_change_id
200+ "Computing object diff for '{}' at change ID '{}' (baseline: {:?}) " ,
201+ object_name, resolved_change_id, baseline_change_id
168202 ) ;
169203
170204 // Get the change
@@ -191,7 +225,7 @@ impl ObjectDiffOperation {
191225
192226 // Get current and baseline object definitions
193227 let current_obj = self . get_object_at_change ( object_name, & change) ?;
194- let baseline_obj = self . get_baseline_object ( object_name, & change) ?;
228+ let baseline_obj = self . get_baseline_object ( object_name, & change, baseline_change_id ) ?;
195229
196230
197231 let mut verb_diffs = Vec :: new ( ) ;
@@ -319,17 +353,23 @@ impl Operation for ObjectDiffOperation {
319353
320354 fn parameters ( & self ) -> Vec < OperationParameter > {
321355 vec ! [
356+ OperationParameter {
357+ name: "object_name" . to_string( ) ,
358+ description: "The name of the object to diff (e.g., '$player', '#123')"
359+ . to_string( ) ,
360+ required: true ,
361+ } ,
322362 OperationParameter {
323363 name: "change_id" . to_string( ) ,
324364 description: "The change ID (short or long Blake3 hash) to compare against"
325365 . to_string( ) ,
326366 required: true ,
327367 } ,
328368 OperationParameter {
329- name: "object_name " . to_string( ) ,
330- description: "The name of the object to diff (e.g., '$player', '#123') "
369+ name: "baseline_change_id " . to_string( ) ,
370+ description: "Optional baseline change ID to compare from. If not provided, uses the preceding change for merged changes or index_change_id for non-merged changes. "
331371 . to_string( ) ,
332- required: true ,
372+ required: false ,
333373 } ,
334374 ]
335375 }
@@ -338,18 +378,26 @@ impl Operation for ObjectDiffOperation {
338378 vec ! [
339379 OperationExample {
340380 description: "Get diff for an object at a specific change" . to_string( ) ,
341- moocode: r#"diff = worker_request("vcs", {"object/diff", "abc123def456 ", "$player "});
381+ moocode: r#"diff = worker_request("vcs", {"object/diff", "$player ", "abc123def456 "});
342382// Returns a list of verb diffs showing what changed"# . to_string( ) ,
343383 http_curl: Some ( r#"curl -X POST http://localhost:8081/api/object/diff \
344384 -H "Content-Type: application/json" \
345- -d '{"operation": "object/diff", "args": ["abc123def456 ", "$player "]}'"# . to_string( ) ) ,
385+ -d '{"operation": "object/diff", "args": ["$player ", "abc123def456 "]}'"# . to_string( ) ) ,
346386 } ,
347387 OperationExample {
348388 description: "Use short hash for convenience" . to_string( ) ,
349- moocode: r#"diff = worker_request("vcs", {"object/diff", "abc123 ", "obj123 "});
389+ moocode: r#"diff = worker_request("vcs", {"object/diff", "obj123 ", "abc123 "});
350390// Short hashes are automatically resolved to full hashes"# . to_string( ) ,
351391 http_curl: None ,
352392 } ,
393+ OperationExample {
394+ description: "Compare against a specific baseline change" . to_string( ) ,
395+ moocode: r#"diff = worker_request("vcs", {"object/diff", "$player", "abc123def456", "baseline123"});
396+ // Compares $player at change abc123def456 against the state at baseline123"# . to_string( ) ,
397+ http_curl: Some ( r#"curl -X POST http://localhost:8081/api/object/diff \
398+ -H "Content-Type: application/json" \
399+ -d '{"operation": "object/diff", "args": ["$player", "abc123def456", "baseline123"]}'"# . to_string( ) ) ,
400+ } ,
353401 ]
354402 }
355403
@@ -363,18 +411,20 @@ impl Operation for ObjectDiffOperation {
363411
364412 fn execute ( & self , args : Vec < String > , _user : & User ) -> Var {
365413 // For RPC calls, we expect the args to contain:
366- // args[0] = change_id
367- // args[1] = object_name
414+ // args[0] = object_name
415+ // args[1] = change_id
416+ // args[2] = baseline_change_id (optional)
368417
369418 if args. len ( ) < 2 {
370- error ! ( "Object diff operation requires change_id and object_name " ) ;
371- return v_error ( E_INVARG . msg ( "Change ID and object name are required" ) ) ;
419+ error ! ( "Object diff operation requires object_name and change_id " ) ;
420+ return v_error ( E_INVARG . msg ( "Object name and change ID are required" ) ) ;
372421 }
373422
374- let change_id = args[ 0 ] . clone ( ) ;
375- let object_name = args[ 1 ] . clone ( ) ;
423+ let object_name = args[ 0 ] . clone ( ) ;
424+ let change_id = args[ 1 ] . clone ( ) ;
425+ let baseline_change_id = args. get ( 2 ) . map ( |s| s. as_str ( ) ) ;
376426
377- match self . process_object_diff ( & change_id , & object_name ) {
427+ match self . process_object_diff ( & object_name , & change_id , baseline_change_id ) {
378428 Ok ( verb_diffs) => {
379429 info ! ( "Object diff operation completed successfully" ) ;
380430 // Convert each VerbDiff to MOO Var and return as list
0 commit comments