Skip to content

Commit

Permalink
cli: add op waypoint command to mark points in the op log to easily r…
Browse files Browse the repository at this point in the history
…estore to at a later point
  • Loading branch information
noahmayr committed Apr 3, 2024
1 parent 363b508 commit 87297b3
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 3 deletions.
96 changes: 93 additions & 3 deletions cli/src/commands/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ use std::slice;
use clap::Subcommand;
use itertools::Itertools as _;
use jj_lib::object_id::ObjectId;
use jj_lib::op_store::OperationId;
use jj_lib::op_store::{OpStoreResult, OperationId};
use jj_lib::op_walk;
use jj_lib::operation::Operation;
use jj_lib::repo::Repo;
use jj_lib::repo::{Repo, RepoLoader};

use crate::cli_util::{format_template, short_operation_hash, CommandHelper, LogContentFormat};
use crate::command_error::{user_error, user_error_with_hint, CommandError};
Expand All @@ -39,6 +39,7 @@ pub enum OperationCommand {
Log(OperationLogArgs),
Undo(OperationUndoArgs),
Restore(OperationRestoreArgs),
Waypoint(OperationWaypointArgs),
}

/// Show the operation log
Expand Down Expand Up @@ -70,6 +71,10 @@ pub struct OperationRestoreArgs {
/// state of the repo at that operation.
operation: String,

/// Restore using a waypoint instead of an operation id
#[arg(long, short)]
waypoint: bool,

/// What portions of the local state to restore (can be repeated)
///
/// This option is EXPERIMENTAL.
Expand Down Expand Up @@ -113,6 +118,22 @@ pub struct OperationAbandonArgs {
operation: String,
}

/// Create a new operation that restores the repo to an earlier state
///
/// This restores the repo to the state at the specified operation, effectively
/// undoing all later operations. It does so by creating a new operation.
#[derive(clap::Args, Clone, Debug)]
pub struct OperationWaypointArgs {
/// The name for the waypoint
waypoint: String,

/// The operation to add a named waypoint to
///
/// Use `jj op log` to find an operation to undo.
#[arg(default_value = "@")]
operation: String,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
enum UndoWhatToRestore {
/// The jj repo state and local branches
Expand Down Expand Up @@ -291,7 +312,25 @@ fn cmd_op_restore(
args: &OperationRestoreArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let target_op = workspace_command.resolve_single_op(&args.operation)?;
let target_op = if args.waypoint {
let workspace = command.load_workspace()?;
let repo_loader = workspace.repo_loader();
let ops = find_tagged_ops(repo_loader, "waypoint", &args.operation)?;
let [op] = ops.as_slice() else {
return Err(if ops.is_empty() {
user_error("There no op log entries with this waypoint!")
} else {
user_error("There is more than one op log entry with this waypoint!").hinted(
"Try setting the waypoint again, this should clear all other waypoints with \
this name.",
)
});
};
op.to_owned()
} else {
workspace_command.resolve_single_op(&args.operation)?
};

let mut tx = workspace_command.start_transaction();
let new_view = view_with_desired_portions_restored(
target_op.view()?.store_view(),
Expand Down Expand Up @@ -395,6 +434,56 @@ fn cmd_op_abandon(
Ok(())
}

pub fn cmd_op_waypoint(
ui: &mut Ui,
command: &CommandHelper,
args: &OperationWaypointArgs,
) -> Result<(), CommandError> {
let workspace = command.load_workspace()?;
let repo_loader = workspace.repo_loader();
let workspace_command = command.workspace_helper(ui)?;
let target_op = workspace_command.resolve_single_op(&args.operation)?;
let op_store = workspace_command.repo().op_store();

let tagged_ops = find_tagged_ops(repo_loader, "waypoint", &args.waypoint)?;
for op in tagged_ops {
op_store.update_tag(op.id(), "waypoint".to_string(), None)?;
}

op_store.update_tag(
target_op.id(),
"waypoint".to_string(),
Some(args.waypoint.clone()),
)?;

Ok(())
}

fn find_tagged_ops(
repo_loader: &RepoLoader,
tag: &str,
value: &str,
) -> OpStoreResult<Vec<Operation>> {
let current_op = op_walk::resolve_op_for_load(repo_loader, "@").ok();
let head_ops = if let Some(op) = current_op {
vec![op]
} else {
op_walk::get_current_head_ops(
repo_loader.op_store(),
repo_loader.op_heads_store().as_ref(),
)?
};
let ops = op_walk::walk_ancestors(&head_ops);
ops.filter_ok(|op| {
op.metadata()
.tags
.get(tag)
.map(|val| val == value)
.unwrap_or(false)
})
.collect()
}

pub fn cmd_operation(
ui: &mut Ui,
command: &CommandHelper,
Expand All @@ -405,5 +494,6 @@ pub fn cmd_operation(
OperationCommand::Log(args) => cmd_op_log(ui, command, args),
OperationCommand::Restore(args) => cmd_op_restore(ui, command, args),
OperationCommand::Undo(args) => cmd_op_undo(ui, command, args),
OperationCommand::Waypoint(args) => cmd_op_waypoint(ui, command, args),
}
}
7 changes: 7 additions & 0 deletions lib/src/op_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,13 @@ pub trait OpStore: Send + Sync + Debug {

fn write_operation(&self, contents: &Operation) -> OpStoreResult<OperationId>;

fn update_tag(
&self,
id: &OperationId,
name: String,
value: Option<String>,
) -> OpStoreResult<()>;

/// Resolves an unambiguous operation ID prefix.
fn resolve_operation_id_prefix(
&self,
Expand Down
38 changes: 38 additions & 0 deletions lib/src/simple_op_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,44 @@ impl OpStore for SimpleOpStore {
Ok(id)
}

fn update_tag(
&self,
id: &OperationId,
tag: String,
value: Option<String>,
) -> OpStoreResult<()> {
let operation = self.read_operation(id)?;

let temp_file =
NamedTempFile::new_in(&self.path).map_err(|err| io_to_write_error(err, "operation"))?;

let mut tags = operation.metadata.tags;

if let Some(value) = value {
tags.insert(tag, value);
} else {
tags.remove(&tag);
}

let operation = op_store::Operation {
metadata: op_store::OperationMetadata {
tags,
..operation.metadata
},
..operation
};

let proto = operation_to_proto(&operation);
temp_file
.as_file()
.write_all(&proto.encode_to_vec())
.map_err(|err| io_to_write_error(err, "operation"))?;

persist_content_addressed_temp_file(temp_file, self.operation_path(id))
.map_err(|err| io_to_write_error(err, "operation"))?;
Ok(())
}

fn resolve_operation_id_prefix(
&self,
prefix: &HexPrefix,
Expand Down

0 comments on commit 87297b3

Please sign in to comment.