Skip to content

Commit

Permalink
cli: make paths to auto-track configurable, add jj track
Browse files Browse the repository at this point in the history
  • Loading branch information
martinvonz committed Aug 25, 2024
1 parent b78c83e commit 322c363
Show file tree
Hide file tree
Showing 13 changed files with 174 additions and 12 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### New features

* The new config option `snapshot.auto-track` lets you automatically track only
the specified paths (all paths by default). Use the new `jj track` command to
manually tracks path that were not automatically tracked.
[#323](https://github.com/martinvonz/jj/issues/323)

* Add new boolean config knob, `ui.movement.edit` for controlling the behaviour
of `prev/next`. The flag turns `edit` mode `on` and `off` permanently when set
respectively to `true` or `false`.
Expand Down
15 changes: 15 additions & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use jj_lib::gitignore::GitIgnoreError;
use jj_lib::gitignore::GitIgnoreFile;
use jj_lib::hex_util::to_reverse_hex;
use jj_lib::id_prefix::IdPrefixContext;
use jj_lib::matchers::EverythingMatcher;
use jj_lib::matchers::Matcher;
use jj_lib::merge::MergedTreeValue;
use jj_lib::merged_tree::MergedTree;
Expand Down Expand Up @@ -856,6 +857,18 @@ impl WorkspaceCommandHelper {
Ok(FilesetExpression::union_all(expressions))
}

pub fn auto_tracking_matcher(&self) -> Result<Box<dyn Matcher>, CommandError> {
let pattern = self.settings.config().get_string("snapshot.auto-track")?;
// This special case is to support users who have disabled filesets
// TODO: once we delete allow-filesets, we can default to "all()" in misc.toml
// and drop this special case
if pattern.is_empty() {
return Ok(Box::new(EverythingMatcher));
}
let expression = self.parse_file_patterns(&[pattern])?;
Ok(expression.to_matcher())
}

pub(crate) fn path_converter(&self) -> &RepoPathUiConverter {
&self.path_converter
}
Expand Down Expand Up @@ -1289,6 +1302,7 @@ impl WorkspaceCommandHelper {
return Ok(());
};
let base_ignores = self.base_ignores()?;
let auto_tracking_matcher = self.auto_tracking_matcher()?;

// Compare working-copy tree and operation with repo's, and reload as needed.
let mut locked_ws = self.workspace.start_working_copy_mutation()?;
Expand Down Expand Up @@ -1341,6 +1355,7 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
base_ignores,
fsmonitor_settings: self.settings.fsmonitor_settings()?,
progress: progress.as_ref().map(|x| x as _),
start_tracking_matcher: &auto_tracking_matcher,
max_new_file_size: self.settings.max_new_file_size()?,
})?;
drop(progress);
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 @@ -51,6 +51,7 @@ mod split;
mod squash;
mod status;
mod tag;
mod track;
mod unsquash;
mod untrack;
mod util;
Expand Down Expand Up @@ -147,6 +148,7 @@ enum Command {
Status(status::StatusArgs),
#[command(subcommand)]
Tag(tag::TagCommand),
Track(track::TrackArgs),
#[command(subcommand)]
Util(util::UtilCommand),
/// Undo an operation (shortcut for `jj op undo`)
Expand Down Expand Up @@ -216,6 +218,7 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co
Command::Squash(args) => squash::cmd_squash(ui, command_helper, args),
Command::Status(args) => status::cmd_status(ui, command_helper, args),
Command::Tag(args) => tag::cmd_tag(ui, command_helper, args),
Command::Track(args) => track::cmd_track(ui, command_helper, args),
Command::Undo(args) => operation::undo::cmd_op_undo(ui, command_helper, args),
Command::Unsquash(args) => unsquash::cmd_unsquash(ui, command_helper, args),
Command::Untrack(args) => untrack::cmd_untrack(ui, command_helper, args),
Expand Down
65 changes: 65 additions & 0 deletions cli/src/commands/track.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2020 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::io::Write;

use jj_lib::working_copy::SnapshotOptions;
use tracing::instrument;

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

/// Start tracking specified paths in the working copy
///
/// By default, all paths are automatically tracked. This command is not useful
/// then. You can configure which paths to automatically track by setting e.g.
/// `snapshot.auto-track = 'none()'`. You will then need to run this command to
/// start tracking new files.
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct TrackArgs {
/// Paths to track.
#[arg(required = true, value_hint = clap::ValueHint::AnyPath)]
paths: Vec<String>,
}

#[instrument(skip_all)]
pub(crate) fn cmd_track(
ui: &mut Ui,
command: &CommandHelper,
args: &TrackArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let matcher = workspace_command
.parse_file_patterns(&args.paths)?
.to_matcher();

let mut tx = workspace_command.start_transaction().into_inner();
let base_ignores = workspace_command.base_ignores()?;
let (mut locked_ws, _wc_commit) = workspace_command.start_working_copy_mutation()?;
locked_ws.locked_wc().snapshot(SnapshotOptions {
base_ignores,
fsmonitor_settings: command.settings().fsmonitor_settings()?,
progress: None,
start_tracking_matcher: &matcher,
max_new_file_size: command.settings().max_new_file_size()?,
})?;
let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?;
if num_rebased > 0 {
writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
}
let repo = tx.commit("track paths");
locked_ws.finish(repo.op_id().clone())?;
Ok(())
}
2 changes: 2 additions & 0 deletions cli/src/commands/untrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub(crate) fn cmd_untrack(

let mut tx = workspace_command.start_transaction().into_inner();
let base_ignores = workspace_command.base_ignores()?;
let auto_tracking_matcher = workspace_command.auto_tracking_matcher()?;
let (mut locked_ws, wc_commit) = workspace_command.start_working_copy_mutation()?;
// Create a new tree without the unwanted files
let mut tree_builder = MergedTreeBuilder::new(wc_commit.tree_id().clone());
Expand All @@ -72,6 +73,7 @@ pub(crate) fn cmd_untrack(
base_ignores,
fsmonitor_settings: command.settings().fsmonitor_settings()?,
progress: None,
start_tracking_matcher: &auto_tracking_matcher,
max_new_file_size: command.settings().max_new_file_size()?,
})?;
if wc_tree_id != *new_commit.tree_id() {
Expand Down
2 changes: 2 additions & 0 deletions cli/src/config/misc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ edit = false

[snapshot]
max-new-file-size = "1MiB"
# TODO: once we delete allow-filesets, this can default to the clearer "all()"
auto-track = ""
2 changes: 2 additions & 0 deletions cli/src/merge_tools/diff_working_copies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use jj_lib::fsmonitor::FsmonitorSettings;
use jj_lib::gitignore::GitIgnoreFile;
use jj_lib::local_working_copy::TreeState;
use jj_lib::local_working_copy::TreeStateError;
use jj_lib::matchers::EverythingMatcher;
use jj_lib::matchers::Matcher;
use jj_lib::merged_tree::MergedTree;
use jj_lib::merged_tree::TreeDiffEntry;
Expand Down Expand Up @@ -286,6 +287,7 @@ diff editing in mind and be a little inaccurate.
base_ignores,
fsmonitor_settings: FsmonitorSettings::None,
progress: None,
start_tracking_matcher: &EverythingMatcher,
max_new_file_size: u64::MAX,
})?;
Ok(output_tree_state.current_tree_id().clone())
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ mod test_squash_command;
mod test_status_command;
mod test_tag_command;
mod test_templater;
mod test_track_untrack_commands;
mod test_undo;
mod test_unsquash_command;
mod test_untrack_command;
mod test_util_command;
mod test_working_copy;
mod test_workspaces;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::path::PathBuf;
use crate::common::TestEnvironment;

#[test]
fn test_untrack() {
fn test_track_untrack() {
let test_env = TestEnvironment::default();
test_env.add_config(r#"ui.allow-init-native = true"#);
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo"]);
Expand Down Expand Up @@ -99,7 +99,7 @@ fn test_untrack() {
}

#[test]
fn test_untrack_sparse() {
fn test_track_untrack_sparse() {
let test_env = TestEnvironment::default();
test_env.add_config(r#"ui.allow-init-native = true"#);
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo"]);
Expand All @@ -124,4 +124,46 @@ fn test_untrack_sparse() {
insta::assert_snapshot!(stdout, @r###"
file1
"###);
// Trying to manually track a file that's not included in the sparse working has
// no effect. TODO: At least a warning would be useful
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["track", "file2"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "list"]);
insta::assert_snapshot!(stdout, @r###"
file1
"###);
}

#[test]
fn test_auto_track() {
let test_env = TestEnvironment::default();
test_env.add_config(r#"snapshot.auto-track = 'glob:*.rs'"#);
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");

std::fs::write(repo_path.join("file1.rs"), "initial").unwrap();
std::fs::write(repo_path.join("file2.md"), "initial").unwrap();
std::fs::write(repo_path.join("file3.md"), "initial").unwrap();

// Only configured paths get auto-tracked
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "list"]);
insta::assert_snapshot!(stdout, @r###"
file1.rs
"###);
// Can manually track paths
let stdout = test_env.jj_cmd_success(&repo_path, &["track", "file3.md"]);
insta::assert_snapshot!(stdout, @"");
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "list"]);
insta::assert_snapshot!(stdout, @r###"
file1.rs
file3.md
"###);
// Can manually untrack paths
let stdout = test_env.jj_cmd_success(&repo_path, &["untrack", "file3.md"]);
insta::assert_snapshot!(stdout, @"");
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "list"]);
insta::assert_snapshot!(stdout, @r###"
file1.rs
"###);
}
5 changes: 3 additions & 2 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ easy way to see the evolution of the commit's contents.

### Can I prevent Jujutsu from recording my unfinished work? I'm not ready to commit it.

Jujutsu automatically records new files in the current working-copy commit and
doesn't provide a way to prevent that.
You can set `snapshot.auto-track` to only start tracking new files matching the
configured pattern (e.g. `"none()"`). Changes to already tracked files will
still be snapshotted by every command.

However, you can easily record intermediate drafts of your work. If you think
you might want to go back to the current state of the working-copy commit,
Expand Down
14 changes: 8 additions & 6 deletions docs/working-copy.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ working-copy contents when they have changed. Most `jj` commands you run will
commit the working-copy changes if they have changed. The resulting revision
will replace the previous working-copy revision.

Also unlike most other VCSs, added files are implicitly tracked. That means that
if you add a new file to the working copy, it will be automatically committed
once you run e.g. `jj st`. Similarly, if you remove a file from the working
copy, it will implicitly be untracked. To untrack a file while keeping it in
the working copy, first make sure it's [ignored](#ignored-files) and then run
`jj untrack <path>`.
Also unlike most other VCSs, added files are implicitly tracked by default. That
means that if you add a new file to the working copy, it will be automatically
committed once you run e.g. `jj st`. Similarly, if you remove a file from the
working copy, it will implicitly be untracked. The `snapshot.auto-track` config
option controls which paths get automatically tracked when they're added. See
the [fileset documentation](filesets.md) for the syntax. To untrack a file while
keeping it in the working copy, first make sure it's [ignored](#ignored-files)
and then run `jj untrack <path>`.


## Conflicts
Expand Down
19 changes: 18 additions & 1 deletion lib/src/local_working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ impl TreeState {
base_ignores,
fsmonitor_settings,
progress,
start_tracking_matcher,
max_new_file_size,
} = options;

Expand Down Expand Up @@ -835,6 +836,7 @@ impl TreeState {
};
self.visit_directory(
&matcher,
start_tracking_matcher,
&current_tree,
tree_entries_tx,
file_states_tx,
Expand Down Expand Up @@ -908,6 +910,7 @@ impl TreeState {
fn visit_directory(
&self,
matcher: &dyn Matcher,
start_tracking_matcher: &dyn Matcher,
current_tree: &MergedTree,
tree_entries_tx: Sender<(RepoPathBuf, MergedTreeValue)>,
file_states_tx: Sender<(RepoPathBuf, FileState)>,
Expand Down Expand Up @@ -1008,6 +1011,14 @@ impl TreeState {
}
}
}
} else if start_tracking_matcher.visit(&path).is_nothing()
&& current_tree.path_value(&path)?.is_absent()
{
// Don't visit subdirectory if it's not already tracked
// and we should not start tracking it. The user might
// have a huge target/ directory that they have not yet
// added to their ignore patterns.
// TODO: Report this directory to the caller
} else {
let directory_to_visit = DirectoryToVisit {
dir: path,
Expand All @@ -1017,6 +1028,7 @@ impl TreeState {
};
self.visit_directory(
matcher,
start_tracking_matcher,
current_tree,
tree_entries_tx.clone(),
file_states_tx.clone(),
Expand Down Expand Up @@ -1049,7 +1061,12 @@ impl TreeState {
max_size: HumanByteSize(max_new_file_size),
});
}
if let Some(new_file_state) = file_state(&metadata) {
if maybe_current_file_state.is_none()
&& !start_tracking_matcher.matches(&path)
{
// Leave untracked
// TODO: Report this path to the caller
} else if let Some(new_file_state) = file_state(&metadata) {
present_files_tx.send(path.clone()).ok();
let update = self.get_updated_tree_value(
&path,
Expand Down
6 changes: 6 additions & 0 deletions lib/src/working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ use crate::commit::Commit;
use crate::fsmonitor::FsmonitorSettings;
use crate::gitignore::GitIgnoreError;
use crate::gitignore::GitIgnoreFile;
use crate::matchers::EverythingMatcher;
use crate::matchers::Matcher;
use crate::op_store::OperationId;
use crate::op_store::WorkspaceId;
use crate::repo_path::RepoPath;
Expand Down Expand Up @@ -193,6 +195,9 @@ pub struct SnapshotOptions<'a> {
pub fsmonitor_settings: FsmonitorSettings,
/// A callback for the UI to display progress.
pub progress: Option<&'a SnapshotProgress<'a>>,
/// For new files that are not already tracked, start tracking them if they
/// match this.
pub start_tracking_matcher: &'a dyn Matcher,
/// The size of the largest file that should be allowed to become tracked
/// (already tracked files are always snapshotted). If there are larger
/// files in the working copy, then `LockedWorkingCopy::snapshot()` may
Expand All @@ -208,6 +213,7 @@ impl SnapshotOptions<'_> {
base_ignores: GitIgnoreFile::empty(),
fsmonitor_settings: FsmonitorSettings::None,
progress: None,
start_tracking_matcher: &EverythingMatcher,
max_new_file_size: u64::MAX,
}
}
Expand Down

0 comments on commit 322c363

Please sign in to comment.