// 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::AbsorbSource;use jj_lib::absorb::absorb_hunks;use jj_lib::absorb::split_hunks_to_trees;use jj_lib::matchers::EverythingMatcher;use pollster::FutureExt as _;use tracing::instrument; use crate::cli_util::CommandHelper;use crate::cli_util::RevisionArg;use crate::cli_util::print_updated_commits;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, add = ArgValueCompleter::new(complete::modified_from_files), )] 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() && 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) .block_on()?; } } Ok(())}
// 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::AbsorbSource;
use jj_lib::absorb::absorb_hunks;
use jj_lib::absorb::split_hunks_to_trees;
use jj_lib::matchers::EverythingMatcher;
use pollster::FutureExt as _;
use tracing::instrument;
use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg;
use crate::cli_util::print_updated_commits;
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,
add = ArgValueCompleter::new(complete::modified_from_files),
)]
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()
&& 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)
.block_on()?;
}
}
Ok(())
}