mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-05 15:32:49 +00:00
143 lines
5.0 KiB
Rust
143 lines
5.0 KiB
Rust
// Copyright 2024 The Jujutsu Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use clap_complete::ArgValueCompleter;
|
|
use jj_lib::absorb::absorb_hunks;
|
|
use jj_lib::absorb::split_hunks_to_trees;
|
|
use jj_lib::absorb::AbsorbSource;
|
|
use jj_lib::matchers::EverythingMatcher;
|
|
use pollster::FutureExt as _;
|
|
use tracing::instrument;
|
|
|
|
use crate::cli_util::print_updated_commits;
|
|
use crate::cli_util::CommandHelper;
|
|
use crate::cli_util::RevisionArg;
|
|
use crate::command_error::CommandError;
|
|
use crate::complete;
|
|
use crate::diff_util::DiffFormat;
|
|
use crate::ui::Ui;
|
|
|
|
/// Move changes from a revision into the stack of mutable revisions
|
|
///
|
|
/// This command splits changes in the source revision and moves each change to
|
|
/// the closest mutable ancestor where the corresponding lines were modified
|
|
/// last. If the destination revision cannot be determined unambiguously, the
|
|
/// change will be left in the source revision.
|
|
///
|
|
/// The source revision will be abandoned if all changes are absorbed into the
|
|
/// destination revisions, and if the source revision has no description.
|
|
///
|
|
/// The modification made by `jj absorb` can be reviewed by `jj op show -p`.
|
|
#[derive(clap::Args, Clone, Debug)]
|
|
pub(crate) struct AbsorbArgs {
|
|
/// Source revision to absorb from
|
|
#[arg(
|
|
long, short,
|
|
default_value = "@",
|
|
value_name = "REVSET",
|
|
add = ArgValueCompleter::new(complete::revset_expression_mutable),
|
|
)]
|
|
from: RevisionArg,
|
|
/// Destination revisions to absorb into
|
|
///
|
|
/// Only ancestors of the source revision will be considered.
|
|
#[arg(
|
|
long, short = 't', visible_alias = "to",
|
|
default_value = "mutable()",
|
|
value_name = "REVSETS",
|
|
add = ArgValueCompleter::new(complete::revset_expression_mutable),
|
|
)]
|
|
into: Vec<RevisionArg>,
|
|
/// Move only changes to these paths (instead of all paths)
|
|
#[arg(value_name = "FILESETS", value_hint = clap::ValueHint::AnyPath)]
|
|
paths: Vec<String>,
|
|
}
|
|
|
|
#[instrument(skip_all)]
|
|
pub(crate) fn cmd_absorb(
|
|
ui: &mut Ui,
|
|
command: &CommandHelper,
|
|
args: &AbsorbArgs,
|
|
) -> Result<(), CommandError> {
|
|
let mut workspace_command = command.workspace_helper(ui)?;
|
|
|
|
let source_commit = workspace_command.resolve_single_rev(ui, &args.from)?;
|
|
let destinations = workspace_command
|
|
.parse_union_revsets(ui, &args.into)?
|
|
.resolve()?;
|
|
|
|
let matcher = workspace_command
|
|
.parse_file_patterns(ui, &args.paths)?
|
|
.to_matcher();
|
|
|
|
let repo = workspace_command.repo().as_ref();
|
|
let source = AbsorbSource::from_commit(repo, source_commit)?;
|
|
let selected_trees = split_hunks_to_trees(repo, &source, &destinations, &matcher).block_on()?;
|
|
|
|
let path_converter = workspace_command.path_converter();
|
|
for (path, reason) in selected_trees.skipped_paths {
|
|
let ui_path = path_converter.format_file_path(&path);
|
|
writeln!(ui.warning_default(), "Skipping {ui_path}: {reason}")?;
|
|
}
|
|
|
|
workspace_command.check_rewritable(selected_trees.target_commits.keys())?;
|
|
|
|
let mut tx = workspace_command.start_transaction();
|
|
let stats = absorb_hunks(tx.repo_mut(), &source, selected_trees.target_commits)?;
|
|
|
|
if let Some(mut formatter) = ui.status_formatter() {
|
|
if !stats.rewritten_destinations.is_empty() {
|
|
writeln!(
|
|
formatter,
|
|
"Absorbed changes into {} revisions:",
|
|
stats.rewritten_destinations.len()
|
|
)?;
|
|
print_updated_commits(
|
|
formatter.as_mut(),
|
|
&tx.commit_summary_template(),
|
|
stats.rewritten_destinations.iter().rev(),
|
|
)?;
|
|
}
|
|
if stats.num_rebased > 0 {
|
|
writeln!(
|
|
formatter,
|
|
"Rebased {} descendant commits.",
|
|
stats.num_rebased
|
|
)?;
|
|
}
|
|
}
|
|
|
|
tx.finish(
|
|
ui,
|
|
format!(
|
|
"absorb changes into {} commits",
|
|
stats.rewritten_destinations.len()
|
|
),
|
|
)?;
|
|
|
|
if let Some(mut formatter) = ui.status_formatter() {
|
|
if let Some(commit) = &stats.rewritten_source {
|
|
let repo = workspace_command.repo().as_ref();
|
|
if !commit.is_empty(repo)? {
|
|
writeln!(formatter, "Remaining changes:")?;
|
|
let diff_renderer = workspace_command.diff_renderer(vec![DiffFormat::Summary]);
|
|
let matcher = &EverythingMatcher; // also print excluded paths
|
|
let width = ui.term_width();
|
|
diff_renderer.show_patch(ui, formatter.as_mut(), commit, matcher, width)?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|