Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/cli/commands/diff.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
//! `parsec diff` / `parsec conflicts` / `parsec sync` — worktree-aware diff and sync.
//!
//! ## Commands
//! - **`parsec diff [ticket]`** — show changes in a worktree against its merge-base.
//! Supports `--stat`, `--name-only`, and `--json` output modes.
//! - **`parsec conflicts`** — pre-flight check that scans all active worktrees for
//! files that diverge from a common ancestor (speculative conflict detection).
//! - **`parsec sync [ticket]`** — fast-forward an active worktree against the latest
//! upstream base branch via rebase (default) or merge. See issue #290 for the
//! full roadmap.

use std::path::Path;

use anyhow::Result;
Expand All @@ -8,6 +19,18 @@ use crate::git;
use crate::output::{self, Mode};
use crate::worktree::WorktreeManager;

/// Show the diff between a worktree's current state and its merge-base with the
/// upstream base branch (`origin/<base_branch>`).
///
/// If `ticket` is `None`, the function auto-detects the worktree by comparing
/// `cwd` against known worktree paths; returns an error if the cwd is outside
/// any parsec-managed worktree.
///
/// Output modes:
/// - `--name-only` → list of changed file paths (human or JSON)
/// - `--stat` → diffstat summary (human or JSON)
/// - default → full unified diff piped to the terminal (human) or
/// name-status pairs (JSON)
pub async fn diff(
repo: &Path,
ticket: Option<&str>,
Expand Down Expand Up @@ -69,6 +92,12 @@ pub async fn diff(
Ok(())
}

/// Detect files that are modified in multiple active worktrees simultaneously.
///
/// Scans every workspace returned by [`WorktreeManager::list`] and compares
/// the set of changed files. Pairs of worktrees that touch the same path are
/// reported as potential conflicts so the developer can resolve them before
/// merging. Does **not** modify any worktree state.
pub async fn conflicts(repo: &Path, mode: Mode) -> Result<()> {
let config = ParsecConfig::load()?;
let manager = WorktreeManager::new(repo, &config)?;
Expand All @@ -80,6 +109,18 @@ pub async fn conflicts(repo: &Path, mode: Mode) -> Result<()> {
Ok(())
}

/// Sync one or more worktrees with the latest state of their upstream base branch.
///
/// Fetches `origin/<base_branch>` and applies either a **rebase** (default,
/// `strategy = "rebase"`) or a **merge** (`strategy = "merge"`). A failed
/// rebase/merge is automatically aborted so the worktree is left clean.
///
/// Selection logic (in order):
/// 1. `--all` → all active worktrees
/// 2. `ticket` → the named worktree only
/// 3. auto-detect → the worktree whose path contains `cwd`
///
/// Returns a summary of synced tickets and any failures via [`output::print_sync`].
pub async fn sync(
repo: &Path,
ticket: Option<&str>,
Expand Down
30 changes: 30 additions & 0 deletions src/cli/commands/history.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
//! `parsec log` / `parsec log --export` / `parsec undo` — operation history and undo.
//!
//! Parsec maintains two complementary audit trails:
//! - **OpLog** (`~/.parsec/oplog.json`) — structured log of high-level parsec
//! operations (start, ship, clean, adopt, undo). Displayed by `parsec log` and
//! used by `parsec undo` to reconstruct the previous state.
//! - **ExecLog** (`.parsec/execlog.ndjson` in the repo) — low-level shell command
//! trace for debugging. Exported verbatim by `parsec log --export`.

use std::path::Path;

use anyhow::{Context, Result};
Expand All @@ -7,6 +16,11 @@ use crate::errors::ErrorCode;
use crate::git;
use crate::output::{self, Mode};

/// Display the parsec operation log, optionally filtered to a single ticket.
///
/// `last` controls how many entries to show (counted from the end of the log).
/// When `ticket` is `Some`, only entries matching that ticket are shown.
/// Uses [`output::print_log`] for human/JSON rendering.
pub async fn log(repo: &Path, ticket: Option<&str>, last: usize, mode: Mode) -> Result<()> {
let repo_root = git::get_main_repo_root(repo).or_else(|_| git::get_repo_root(repo))?;
let oplog = crate::oplog::OpLog::load(&repo_root)?;
Expand All @@ -18,6 +32,9 @@ pub async fn log(repo: &Path, ticket: Option<&str>, last: usize, mode: Mode) ->
Ok(())
}

/// Dump the raw execution log (ndjson) to stdout for debugging or external tooling.
///
/// Prints a warning to stderr when the log is empty; exits cleanly in either case.
pub async fn log_export(repo: &Path) -> Result<()> {
let repo_root = git::get_main_repo_root(repo).or_else(|_| git::get_repo_root(repo))?;
let raw = crate::execlog::read_raw(&repo_root)?;
Expand All @@ -29,6 +46,19 @@ pub async fn log_export(repo: &Path) -> Result<()> {
Ok(())
}

/// Undo the most recent reversible parsec operation.
///
/// Reads the last [`OpLog`] entry and reverses it:
/// - `start` / `adopt` — removes the worktree, deletes the local branch, and
/// drops the workspace from state.
/// - `ship` / `clean` — re-creates the worktree from the local branch (or
/// restores it from `origin/<branch>` if the local ref is gone).
///
/// `undo` itself is **not** re-undoable; attempting `parsec undo` after `parsec
/// undo` returns [`ErrorCode::E013`].
///
/// When `dry_run = true` the intended action is printed but no mutations are
/// performed.
pub async fn undo(repo: &Path, dry_run: bool, mode: Mode) -> Result<()> {
let config = ParsecConfig::load()?;
let repo_root = git::get_main_repo_root(repo).or_else(|_| git::get_repo_root(repo))?;
Expand Down
Loading