Skip to content

Commit 7313a3a

Browse files
Review and return worktree commits without automatic commits or discarded conflicts
1 parent 83b9470 commit 7313a3a

11 files changed

Lines changed: 568 additions & 12 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## 0.20.0 — native development (unreleased)
44

5+
- Reviewed CLI worktree return prepares incoming commits in the source index
6+
without committing. Stale reviews and active work are rejected; conflicts and
7+
ignored local files are preserved for explicit resolution.
8+
59
- CI now exercises 200 native tasks, stalled-provider cancellations and output
610
floods while checking memory, descriptors, child cleanup and history integrity.
711

‎docs/NATIVE_CLI.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,12 @@ forwards ordinary shutdown signals, waits for native cleanup, and preserves the
9494
payload's exit status. Force-killing the wrapper still reports its signal status;
9595
the native child observes wrapper loss and shuts down.
9696

97-
See [native worktrees](NATIVE_WORKTREES.md) for `worktree --create` and running
98-
tasks in a separate branch while preserving source checkout edits.
97+
See [native worktrees](NATIVE_WORKTREES.md) for creation, reviewed removal,
98+
missing-checkout recovery and reviewed return of committed work.
99+
`worktree --review-return ID` inspects incoming changes;
100+
`worktree --return-changes ID --return-hash HASH` prepares them in the source
101+
index without committing. Conflicts return a nonzero exit status and preserve
102+
the Git merge state for explicit resolution or abort.
99103

100104
## Saved work and settings
101105

‎docs/NATIVE_MIGRATION.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ the existing React visual design. This document tracks engineering acceptance;
5959
branches and commits.
6060
CLI and desktop rescue restore a missing checkout’s retained commit into a
6161
separate checkout while preserving original recovery metadata. Dirty-state
62-
transfer, damaged-path repair and reviewed return of changes remain open.
62+
transfer, damaged-path repair and desktop return controls remain open.
63+
Reviewed CLI return prepares a merge without committing, preserving source
64+
branch work and leaving conflicts for explicit resolution or Git merge abort.
6365

6466
The Rust workspace is being introduced alongside the Python release. Existing
6567
features remain acceptance requirements; an unimplemented native feature must

‎docs/NATIVE_VERIFICATION.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,20 @@ these transitions. Assertions inspect fresh complete frames because incremental
304304
ANSI updates can split words. This covers resize recovery; broader picker
305305
navigation, visual review and sustained terminal stress remain open.
306306

307+
## Reviewed return of worktree commits
308+
309+
Nine worktree tests pass. Return-specific scenarios exercise diverged branches,
310+
source HEAD preservation until a separate commit, stale hashes after source
311+
commits, dirty/untracked files, active worktree reservations, source background
312+
processes, already-integrated commits, conflicts retained for manual resolution
313+
or abort, ignored-file collisions and changed source repository roots. Testing
314+
found that Git's `--no-overwrite-ignore` alone did not prevent an ignored-file
315+
overwrite in this merge path; explicit incoming-path/ignored-path intersection
316+
checks now reject it before mutation. Original worktree commits remain intact.
317+
The native CLI passes 19 scenario groups / 32 scripted model requests, including
318+
review, stale-hash rejection, uncommitted return and a separate merge commit.
319+
Clippy and the native build pass. Desktop return controls remain open.
320+
307321
## Missing-worktree committed recovery
308322

309323
Six worktree tests pass, including a real Git checkout with an unmerged commit

‎docs/NATIVE_WORKTREES.md‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ removal, just as with ordinary Git operations. Damaged checkout paths or missing
6464
inspection; the recovery record remains. For a missing path whose Git
6565
registration survives, use the reviewed rescue below.
6666

67-
Carrying uncommitted changes into an isolated checkout, reviewed return/merge
68-
operations and recovery controls for missing/damaged checkouts remain part of the
67+
Carrying uncommitted changes into an isolated checkout, desktop return controls
68+
and repair of damaged checkouts remain part of the
6969
[native migration gates](NATIVE_MIGRATION.md).
7070

7171
Inventory and individual actions share the same bounded, non-following record
@@ -99,3 +99,38 @@ commit, together with the uncommitted-file limitation. **Restore in new worktree
9999
creates the separate checkout; **Cancel recovery** closes the review without
100100
changes. Errors clear stale reviews. Use **Open worktree** on the newly listed
101101
checkout to enter the usual project trust flow.
102+
103+
## Review and return committed changes
104+
105+
```sh
106+
shadowcode --workspace /source/project worktree --review-return FULL_WORKTREE_ID
107+
shadowcode --workspace /source/project worktree --return-changes FULL_WORKTREE_ID --return-hash REVIEW_HASH
108+
```
109+
110+
The review includes the exact source/worktree branches and commits, their merge
111+
base, and the worktree's incoming diff from that base. This is the incoming
112+
branch diff, not a promise of a conflict-free merge. Reviews larger than the
113+
64 KB Git-output limit must be handled through ordinary Git review tools.
114+
Both checkouts must be attached to branches, free of tracked/untracked changes
115+
and unfinished Git operations. Locked worktrees and already-integrated commits
116+
are rejected. The source must be trusted and writable. Active tasks/manual
117+
operations or background processes in either checkout block the return.
118+
119+
The return rechecks the review hash, then prepares `git merge --no-commit --no-ff`
120+
in the source checkout. HEAD stays unchanged: review the staged result and commit
121+
separately using the source project's normal Git review tools. Git preserves
122+
nonconflicting source-branch work when histories have diverged. Conflicts remain
123+
in the source files/index for explicit resolution; the result is
124+
`needs_attention`, with a nonzero CLI exit status. Inspect `git status`, resolve
125+
and commit, or run `git merge --abort` in the source project to abandon the merge.
126+
Interrupted operations retain a `returning` recovery record explaining what was
127+
attempted. No reset, auto-stash, automatic commit, branch deletion or checkout
128+
removal is performed. Ignored source files cannot be overwritten by the merge.
129+
The worktree branch and committed contents remain available throughout.
130+
131+
Git hooks and filesystem-monitor hooks are disabled for these operations; Git's
132+
configured merge drivers and checkout filters still apply. Keep external Git or
133+
filesystem writers idle during review and return, as with ordinary Git merges.
134+
After committing or aborting, the record's last-operation status is historical;
135+
future actions inspect the actual Git state again. Dedicated desktop return
136+
controls remain in development.

‎native/core/src/cli/args.rs‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,30 @@ pub enum Command {
5353
},
5454
/// List managed worktrees, or create an isolated branch at a local commit.
5555
Worktree {
56-
#[arg(long,conflicts_with_all=["inspect","remove","recovery","restore"])]
56+
#[arg(long,conflicts_with_all=["inspect","remove","recovery","restore","review_return","return_changes"])]
5757
create: bool,
58-
#[arg(long, conflicts_with_all = ["remove","recovery","restore"])]
58+
#[arg(long, conflicts_with_all = ["remove","recovery","restore","review_return","return_changes"])]
5959
inspect: Option<String>,
60-
#[arg(long, requires = "hash", conflicts_with_all = ["recovery","restore"])]
60+
#[arg(long, requires = "hash", conflicts_with_all = ["recovery","restore","review_return","return_changes"])]
6161
remove: Option<String>,
6262
#[arg(long, requires = "remove")]
6363
hash: Option<String>,
6464
/// Review retained commits for a missing managed checkout.
65-
#[arg(long, conflicts_with = "restore")]
65+
#[arg(long, conflicts_with_all = ["restore","review_return","return_changes"])]
6666
recovery: Option<String>,
6767
/// Restore a reviewed commit to a separate checkout, retaining original metadata.
68-
#[arg(long, requires = "recovery_hash")]
68+
#[arg(long, requires = "recovery_hash", conflicts_with_all = ["review_return","return_changes"])]
6969
restore: Option<String>,
7070
#[arg(long, requires = "restore")]
7171
recovery_hash: Option<String>,
72+
/// Review committed changes before returning them to the source project.
73+
#[arg(long, conflicts_with = "return_changes")]
74+
review_return: Option<String>,
75+
/// Prepare a reviewed merge in the source checkout without committing.
76+
#[arg(long, requires = "return_hash")]
77+
return_changes: Option<String>,
78+
#[arg(long, requires = "return_changes")]
79+
return_hash: Option<String>,
7280
#[arg(long, default_value = "HEAD", requires = "create")]
7381
reference: String,
7482
},

‎native/core/src/cli/mod.rs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -870,8 +870,13 @@ async fn execute(backend: &Backend, workspace: &Path, options: &Options) -> Resu
870870
});
871871
}
872872
}
873-
Command::Worktree {create,reference,inspect,remove,hash,recovery,restore,recovery_hash}=>{
874-
if let Some(id)=restore {backend.call("POST","/api/worktrees/restore",json!({"id":id,"hash":recovery_hash})).await?}
873+
Command::Worktree {create,reference,inspect,remove,hash,recovery,restore,recovery_hash,review_return,return_changes,return_hash}=>{
874+
if let Some(id)=return_changes {
875+
let value=backend.call("POST","/api/worktrees/return",json!({"id":id,"hash":return_hash})).await?;
876+
return Ok(Outcome{code:if value["state"]=="merge_pending" {0}else{1},value,raw:None});
877+
}
878+
else if let Some(id)=review_return {backend.call("POST","/api/worktrees/review-return",json!({"id":id})).await?}
879+
else if let Some(id)=restore {backend.call("POST","/api/worktrees/restore",json!({"id":id,"hash":recovery_hash})).await?}
875880
else if let Some(id)=recovery {backend.call("POST","/api/worktrees/recovery",json!({"id":id})).await?}
876881
else if let Some(id)=remove {backend.call("POST","/api/worktrees/remove",json!({"id":id,"hash":hash})).await?}
877882
else if let Some(id)=inspect {backend.call("POST","/api/worktrees/inspect",json!({"id":id})).await?}

‎native/core/src/service.rs‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,49 @@ impl Service {
213213
.await?
214214
));
215215
}
216+
("POST", "/api/worktrees/review-return") => {
217+
let workspace = self.workspace()?;
218+
ensure!(
219+
Config::load(self.engine.paths(), Some(&workspace))?.is_trusted(&workspace),
220+
"Trust the source project before reviewing returned changes"
221+
);
222+
return Ok(json!(
223+
crate::worktrees::review_return(
224+
self.engine.paths(),
225+
&workspace,
226+
text("id"),
227+
CancellationToken::new()
228+
)
229+
.await?
230+
));
231+
}
232+
("POST", "/api/worktrees/return") => {
233+
let ws = self.mutable_workspace()?;
234+
let reviewed = crate::worktrees::review_return(
235+
self.engine.paths(),
236+
&ws.path,
237+
text("id"),
238+
ws.reservation.cancellation(),
239+
)
240+
.await?;
241+
let _target = self.engine.reserve_workspace(&reviewed.record.path)?;
242+
let _source_background =
243+
self.engine.background().reserve_idle_workspace(&ws.path)?;
244+
let _target_background = self
245+
.engine
246+
.background()
247+
.reserve_idle_workspace(&reviewed.record.path)?;
248+
let record = crate::worktrees::return_changes(
249+
self.engine.paths(),
250+
&ws.path,
251+
text("id"),
252+
text("hash"),
253+
ws.reservation.cancellation(),
254+
)
255+
.await?;
256+
store.add_event("worktree.returned", &json!(record), None, None)?;
257+
return Ok(json!(record));
258+
}
216259
("POST", "/api/worktrees/recovery") => {
217260
let workspace = self.workspace()?;
218261
ensure!(

0 commit comments

Comments
 (0)