Skip to content

Commit

Permalink
Implement hacky variant of jj purge
Browse files Browse the repository at this point in the history
  • Loading branch information
0xdeafbeef committed Jul 28, 2024
1 parent 304f6df commit 1895759
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 30 deletions.
31 changes: 18 additions & 13 deletions cli/src/command_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use jj_lib::revset::{
};
use jj_lib::signing::SignInitError;
use jj_lib::str_util::StringPatternParseError;
use jj_lib::working_copy::{ResetError, SnapshotError, WorkingCopyStateError};
use jj_lib::working_copy::{NewFileTooLarge, ResetError, SnapshotError, WorkingCopyStateError};
use jj_lib::workspace::WorkspaceInitError;
use thiserror::Error;

Expand Down Expand Up @@ -301,11 +301,12 @@ impl From<OpsetEvaluationError> for CommandError {
impl From<SnapshotError> for CommandError {
fn from(err: SnapshotError) -> Self {
match err {
SnapshotError::NewFileTooLarge {
path,
size,
max_size,
} => {
SnapshotError::NewFileTooLarge(ref e) => {
let NewFileTooLarge {
path,
size,
max_size,
} = e.first().unwrap();
// if the size difference is < 1KiB, then show exact bytes.
// otherwise, show in human-readable form; this avoids weird cases
// where a file is 400 bytes too large but the error says something
Expand All @@ -320,12 +321,16 @@ impl From<SnapshotError> for CommandError {
format!("it is {}; the maximum size allowed is ~{}.", size, max_size,)
};

user_error(format!(
"Failed to snapshot the working copy\nThe file '{}' is too large to be \
snapshotted: {}",
path.display(),
err_str,
))
let size = size.0;
user_error_with_message(
format!(
"Failed to snapshot the working copy\nThe file '{}' is too large to be \
snapshotted: {}",
path.display(),
err_str,
),
err,
)
.hinted(format!(
"This is to prevent large files from being added on accident. You can fix \
this error by:
Expand All @@ -334,7 +339,7 @@ impl From<SnapshotError> for CommandError {
This will increase the maximum file size allowed for new files, in this repository only.
- Run `jj --config-toml 'snapshot.max-new-file-size={}' st`
This will increase the maximum file size allowed for new files, for this command only.",
size.0, size.0
size, size
))
}
err => internal_error_with_message("Failed to snapshot the working copy", err),
Expand Down
3 changes: 3 additions & 0 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mod obslog;
mod operation;
mod parallelize;
mod prev;
mod purge;
mod rebase;
mod resolve;
mod restore;
Expand Down Expand Up @@ -123,6 +124,7 @@ enum Command {
Operation(operation::OperationCommand),
Parallelize(parallelize::ParallelizeArgs),
Prev(prev::PrevArgs),
Purge(purge::PurgeArgs),
Rebase(rebase::RebaseArgs),
Resolve(resolve::ResolveArgs),
Restore(restore::RestoreArgs),
Expand Down Expand Up @@ -204,6 +206,7 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co
Command::Resolve(args) => resolve::cmd_resolve(ui, command_helper, args),
Command::Restore(args) => restore::cmd_restore(ui, command_helper, args),
Command::Revert(_args) => revert(),
Command::Purge(args) => purge::cmd_purge(ui, command_helper, args),
Command::Root(args) => root::cmd_root(ui, command_helper, args),
Command::Run(args) => run::cmd_run(ui, command_helper, args),
Command::Show(args) => show::cmd_show(ui, command_helper, args),
Expand Down
71 changes: 71 additions & 0 deletions cli/src/commands/purge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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 std::error::Error;
use std::fs;
use std::io::Write;

use jj_lib::settings::HumanByteSize;
use jj_lib::working_copy::SnapshotError;

use crate::cli_util::CommandHelper;
use crate::command_error::CommandError;
use crate::ui::Ui;

/// Removes files not tracked by Jujutsu
/// Note: snapshot won't be taken before purging, so there is no way to undo
/// this operation
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct PurgeArgs {
/// Dry run, don't actually remove files
#[arg(short, long, default_value = "false")]
dry_run: bool,
}

pub(crate) fn cmd_purge(
ui: &mut Ui,
command: &CommandHelper,
args: &PurgeArgs,
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui);
if let Err(e) = workspace_command {
let Some(e) = e.error.source() else {
return Ok(());
};
let e = e.downcast_ref::<SnapshotError>();
if let Some(SnapshotError::NewFileTooLarge(files)) = e {
writeln!(
ui.status(),
"The following files are too large to be added to the working copy:"
)?;
for file in files {
writeln!(ui.status(), " {}", &file.path.display())?;
}
if !args.dry_run {
for file in files {
fs::remove_file(&file.path)?;
}
}
let total_size: u64 = files.iter().map(|file| file.size.0).sum();

writeln!(
ui.status(),
"Removed {} files totaling {}",
files.len(),
HumanByteSize(total_size)
)?;
}
}
Ok(())
}
16 changes: 16 additions & 0 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ This document contains the help content for the `jj` command-line program.
* [`jj operation undo`↴](#jj-operation-undo)
* [`jj parallelize`↴](#jj-parallelize)
* [`jj prev`↴](#jj-prev)
* [`jj purge`↴](#jj-purge)
* [`jj rebase`↴](#jj-rebase)
* [`jj resolve`↴](#jj-resolve)
* [`jj restore`↴](#jj-restore)
Expand Down Expand Up @@ -132,6 +133,7 @@ To get started, see the tutorial at https://github.com/martinvonz/jj/blob/main/d
* `operation`Commands for working with the operation log
* `parallelize`Parallelize revisions by making them siblings
* `prev`Change the working copy revision relative to the parent revision
* `purge`Removes files not tracked by Jujutsu Note: snapshot won't be taken before purging, so there is no way to undo this operation
* `rebase`Move revisions to different parent(s)
* `resolve`Resolve a conflicted file with an external merge tool
* `restore`Restore paths from another revision
Expand Down Expand Up @@ -1528,6 +1530,20 @@ implied.
## `jj purge`
Removes files not tracked by Jujutsu Note: snapshot won't be taken before purging, so there is no way to undo this operation
**Usage:** `jj purge [OPTIONS]`
###### **Options:**
* `-d`, `--dry-run` — Dry run, don't actually remove files
Default value: `false`
## `jj rebase`
Move revisions to different parent(s)
Expand Down
26 changes: 19 additions & 7 deletions lib/src/local_working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ use crate::settings::HumanByteSize;
use crate::store::Store;
use crate::tree::Tree;
use crate::working_copy::{
CheckoutError, CheckoutStats, LockedWorkingCopy, ResetError, SnapshotError, SnapshotOptions,
SnapshotProgress, WorkingCopy, WorkingCopyFactory, WorkingCopyStateError,
CheckoutError, CheckoutStats, LockedWorkingCopy, NewFileTooLarge, ResetError, SnapshotError,
SnapshotOptions, SnapshotProgress, WorkingCopy, WorkingCopyFactory, WorkingCopyStateError,
};

#[cfg(unix)]
Expand Down Expand Up @@ -790,6 +790,8 @@ impl TreeState {
let (file_states_tx, file_states_rx) = channel();
let (present_files_tx, present_files_rx) = channel();

let (files_to_big_tx, files_to_big_rx) = channel();

trace_span!("traverse filesystem").in_scope(|| -> Result<(), SnapshotError> {
let current_tree = self.current_tree()?;
let directory_to_visit = DirectoryToVisit {
Expand All @@ -807,6 +809,7 @@ impl TreeState {
directory_to_visit,
progress,
max_new_file_size,
files_to_big_tx,
)
})?;

Expand Down Expand Up @@ -865,6 +868,11 @@ impl TreeState {
let state_paths: HashSet<_> = file_states.paths().map(|path| path.to_owned()).collect();
assert_eq!(state_paths, tree_paths);
}
let failed_files: Vec<_> = files_to_big_rx.iter().collect();
if !failed_files.is_empty() {
return Err(SnapshotError::NewFileTooLarge(failed_files));
}

self.watchman_clock = watchman_clock;
Ok(is_dirty)
}
Expand All @@ -880,6 +888,7 @@ impl TreeState {
directory_to_visit: DirectoryToVisit,
progress: Option<&SnapshotProgress>,
max_new_file_size: u64,
files_to_big: Sender<NewFileTooLarge>,
) -> Result<(), SnapshotError> {
let DirectoryToVisit {
dir,
Expand Down Expand Up @@ -989,6 +998,7 @@ impl TreeState {
directory_to_visit,
progress,
max_new_file_size,
files_to_big.clone(),
)?;
}
} else if matcher.matches(&path) {
Expand All @@ -1008,11 +1018,13 @@ impl TreeState {
})?;
if maybe_current_file_state.is_none() && metadata.len() > max_new_file_size
{
return Err(SnapshotError::NewFileTooLarge {
path: entry.path().clone(),
size: HumanByteSize(metadata.len()),
max_size: HumanByteSize(max_new_file_size),
});
files_to_big
.send(NewFileTooLarge {
path: entry.path().clone(),
size: HumanByteSize(metadata.len()),
max_size: HumanByteSize(max_new_file_size),
})
.ok();
}
if let Some(new_file_state) = file_state(&metadata) {
present_files_tx.send(path.clone()).ok();
Expand Down
26 changes: 16 additions & 10 deletions lib/src/working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,17 +152,10 @@ pub enum SnapshotError {
/// Reading or writing from the commit backend failed.
#[error(transparent)]
BackendError(#[from] BackendError),
/// A file was larger than the specified maximum file size for new
#[error("New file too large")]
/// Files were larger than the specified maximum file size for new
/// (previously untracked) files.
#[error("New file {path} of size ~{size} exceeds snapshot.max-new-file-size ({max_size})")]
NewFileTooLarge {
/// The path of the large file.
path: PathBuf,
/// The size of the large file.
size: HumanByteSize,
/// The maximum allowed size.
max_size: HumanByteSize,
},
NewFileTooLarge(Vec<NewFileTooLarge>),
/// Checking path with ignore patterns failed.
#[error(transparent)]
GitIgnoreError(#[from] GitIgnoreError),
Expand All @@ -177,6 +170,19 @@ pub enum SnapshotError {
},
}

#[derive(Debug, Error)]
/// A file was larger than the specified maximum file size for new
/// (previously untracked) files.
#[error("New file {path} of size ~{size} exceeds snapshot.max-new-file-size ({max_size})")]
pub struct NewFileTooLarge {
/// The path of the large file.
pub path: PathBuf,
/// The size of the large file.
pub size: HumanByteSize,
/// The maximum allowed size.
pub max_size: HumanByteSize,
}

/// Options used when snapshotting the working copy. Some of them may be ignored
/// by some `WorkingCopy` implementations.
pub struct SnapshotOptions<'a> {
Expand Down

0 comments on commit 1895759

Please sign in to comment.