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
It's a pretty frequent request to have support for turning off
auto-tracking of new files and to have a command to manually track
them instead. This patch adds a `snapshot.auto-track` config to decide
which paths to auto-track (defaults to `all()`). It also adds a `jj
track` command to manually track the untracked paths.

This patch does not include displaying the untracked paths in `jj
status`, so for now this is probably only useful in colocated repos
where you can run `git status` to find the untracked files.

#323
  • Loading branch information
martinvonz committed Aug 25, 2024
1 parent 6b65f8a commit c1f1ff0
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 13 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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
14 changes: 14 additions & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,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")?;
let expression = fileset::parse(
&pattern,
&RepoPathUiConverter::Fs {
cwd: "".into(),
base: "".into(),
},
)?;
Ok(expression.to_matcher())
}

pub(crate) fn path_converter(&self) -> &RepoPathUiConverter {
&self.path_converter
}
Expand Down Expand Up @@ -1289,6 +1301,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 +1354,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/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
pub mod chmod;
pub mod list;
pub mod show;
pub mod track;
pub mod untrack;

use crate::cli_util::CommandHelper;
Expand All @@ -27,6 +28,7 @@ pub enum FileCommand {
Chmod(chmod::FileChmodArgs),
List(list::FileListArgs),
Show(show::FileShowArgs),
Track(track::TrackArgs),
Untrack(untrack::FileUntrackArgs),
}

Expand All @@ -39,6 +41,7 @@ pub fn cmd_file(
FileCommand::Chmod(args) => chmod::cmd_file_chmod(ui, command, args),
FileCommand::List(args) => list::cmd_file_list(ui, command, args),
FileCommand::Show(args) => show::cmd_file_show(ui, command, args),
FileCommand::Track(args) => track::cmd_file_track(ui, command, args),
FileCommand::Untrack(args) => untrack::cmd_file_untrack(ui, command, args),
}
}
67 changes: 67 additions & 0 deletions cli/src/commands/file/track.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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::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
///
/// Without arguments, all paths that are not ignored will be tracked.
///
/// By default, Jujutsu starts tracking all new files automatically. 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(value_hint = clap::ValueHint::AnyPath)]
paths: Vec<String>,
}

#[instrument(skip_all)]
pub(crate) fn cmd_file_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/file/untrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub(crate) fn cmd_file_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_file_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
1 change: 1 addition & 0 deletions cli/src/config/misc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ edit = false

[snapshot]
max-new-file-size = "1MiB"
auto-track = "all()"
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
18 changes: 18 additions & 0 deletions cli/tests/cli-reference@.md.snap
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ This document contains the help content for the `jj` command-line program.
* [`jj file chmod`↴](#jj-file-chmod)
* [`jj file list`↴](#jj-file-list)
* [`jj file show`↴](#jj-file-show)
* [`jj file track`↴](#jj-file-track)
* [`jj file untrack`↴](#jj-file-untrack)
* [`jj fix`↴](#jj-fix)
* [`jj git`↴](#jj-git)
Expand Down Expand Up @@ -703,6 +704,7 @@ File operations
* `chmod` — Sets or removes the executable bit for paths in the repo
* `list` — List files in a revision
* `show` — Print contents of files in a revision
* `track` — Start tracking specified paths in the working copy
* `untrack` — Stop tracking specified paths in the working copy
Expand Down Expand Up @@ -773,6 +775,22 @@ If the given path is a directory, files in the directory will be visited recursi
## `jj file track`
Start tracking specified paths in the working copy
Without arguments, all paths that are not ignored will be tracked.
By default, Jujutsu starts tracking all new files automatically. 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.
**Usage:** `jj file track [PATHS]...`
###### **Arguments:**
* `<PATHS>` — Paths to track
## `jj file untrack`
Stop tracking specified paths in the working copy
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod test_duplicate_command;
mod test_edit_command;
mod test_file_chmod_command;
mod test_file_print_command;
mod test_file_untrack_command;
mod test_file_track_untrack_commands;
mod test_fix_command;
mod test_generate_md_cli_help;
mod test_git_clone;
Expand Down
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 @@ -103,7 +103,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 @@ -128,4 +128,79 @@ 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, &["file", "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, &["file", "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
"###);

// Defaults to tracking all paths
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "track"]);
insta::assert_snapshot!(stdout, @"");
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "list"]);
insta::assert_snapshot!(stdout, @r###"
file1.rs
file2.md
file3.md
"###);

// Can manually untrack paths
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "untrack", "file2.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
"###);

// CWD-relative paths in `snapshot.auto-track` are evaluated from the repo root
let subdir = repo_path.join("sub");
std::fs::create_dir(&subdir).unwrap();
std::fs::write(subdir.join("file1.rs"), "initial").unwrap();
let stdout = test_env.jj_cmd_success(&subdir, &["file", "list"]);
insta::assert_snapshot!(stdout, @r###"
../file1.rs
../file3.md
"###);

// But `jj track` wants CWD-relative paths
let stdout = test_env.jj_cmd_success(&subdir, &["file", "track", "file1.rs"]);
insta::assert_snapshot!(stdout, @"");
let stdout = test_env.jj_cmd_success(&subdir, &["file", "list"]);
insta::assert_snapshot!(stdout, @r###"
../file1.rs
../file3.md
file1.rs
"###);
}
5 changes: 5 additions & 0 deletions cli/tests/test_working_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,9 @@ fn test_snapshot_large_file() {
- Run `jj --config-toml 'snapshot.max-new-file-size=11264' st`
This will increase the maximum file size allowed for new files, for this command only.
"###);

// No error if we disable auto-tracking of the path
test_env.add_config(r#"snapshot.auto-track = 'none()'"#);
let stdout = test_env.jj_cmd_success(&repo_path, &["file", "list"]);
insta::assert_snapshot!(stdout, @"");
}
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
16 changes: 10 additions & 6 deletions docs/working-copy.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ 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 file 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.

You can use `jj untrack` to untrack a file while keeping it in the working copy.
However, first [ignore](#ignored-files) them or remove them from the
`snapshot.auto-track` patterns; otherwise they will be immediately tracked again.


## Conflicts
Expand Down
Loading

0 comments on commit c1f1ff0

Please sign in to comment.