Skip to content

Commit 56372eb

Browse files
committed
Add support for jujutsu
uses jj-lib crate This adds a config option `vcs_providers`, where both `git` and `jj` can be added. By default only git is enabled. Providers are used lazily in order, so if jujutsu is configured before git, git will not find a colocated jj repository if jj found it first. Worktrees/workspaces are handled differently depending on vcs provider. Git should behave the same as before, with the exception that on non-bare repos worktrees will be opened as new windows as well. jj doesn't have bare repos, and so far there doesn't seem to be a reliable way to refer from the "base" repo to its workspaces, so instead tms will scan all configured search paths for workspaces belonging to the base workspace that is opened, and then open a new window for each of them. (jj workspaces are identified by `.jj/repo` not being the actual repo, but instead a file that points to the main workspace where the repo is stored)
1 parent 3643a8a commit 56372eb

File tree

8 files changed

+2080
-364
lines changed

8 files changed

+2080
-364
lines changed

Cargo.lock

+1,742-240
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ exclude = ["images/*"]
1818
[dependencies]
1919

2020
gix = { version = "0.70.0", features = ["attributes"] }
21+
jj-lib = "0.28.2"
2122
clap = { version = "4.5", features = ["cargo", "derive"] }
2223
clap_complete = { version = "4.5", features = [ "unstable-dynamic" ] }
2324
serde_derive = "1.0"

src/cli.rs

+14-22
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@ use crate::{
1414
execute_command, get_single_selection,
1515
marks::{marks_command, MarksCommand},
1616
picker::Preview,
17-
repos::Prunable,
17+
repos::RepoProvider,
1818
session::{create_sessions, SessionContainer},
1919
tmux::Tmux,
2020
Result, TmsError,
2121
};
2222
use clap::{Args, Parser, Subcommand};
2323
use clap_complete::{ArgValueCandidates, CompletionCandidate};
2424
use error_stack::ResultExt;
25-
use gix::Repository;
2625
use ratatui::style::Color;
2726

2827
#[derive(Debug, Parser)]
@@ -230,7 +229,7 @@ impl Cli {
230229
Ok(SubCommandGiven::Yes)
231230
}
232231
Some(CliCommand::Refresh(args)) => {
233-
refresh_command(args, tmux)?;
232+
refresh_command(args, config, tmux)?;
234233
Ok(SubCommandGiven::Yes)
235234
}
236235

@@ -610,7 +609,7 @@ fn rename_subcommand(args: &RenameCommand, tmux: &Tmux) -> Result<()> {
610609
Ok(())
611610
}
612611

613-
fn refresh_command(args: &RefreshCommand, tmux: &Tmux) -> Result<()> {
612+
fn refresh_command(args: &RefreshCommand, config: Config, tmux: &Tmux) -> Result<()> {
614613
let session_name = args
615614
.name
616615
.clone()
@@ -629,11 +628,11 @@ fn refresh_command(args: &RefreshCommand, tmux: &Tmux) -> Result<()> {
629628
.map(|line| line.replace('\'', ""))
630629
.collect();
631630

632-
if let Ok(repository) = gix::open(&session_path) {
631+
if let Ok(repository) = RepoProvider::open(Path::new(&session_path), &config) {
633632
let mut num_worktree_windows = 0;
634-
if let Ok(worktrees) = repository.worktrees() {
633+
if let Ok(worktrees) = repository.worktrees(&config) {
635634
for worktree in worktrees.iter() {
636-
let worktree_name = worktree.id().to_string();
635+
let worktree_name = worktree.name();
637636
if existing_window_names.contains(&worktree_name) {
638637
num_worktree_windows += 1;
639638
continue;
@@ -645,12 +644,7 @@ fn refresh_command(args: &RefreshCommand, tmux: &Tmux) -> Result<()> {
645644
num_worktree_windows += 1;
646645
tmux.new_window(
647646
Some(&worktree_name),
648-
Some(
649-
&worktree
650-
.base()
651-
.change_context(TmsError::GitError)?
652-
.to_string()?,
653-
),
647+
Some(&worktree.path()?.to_string()?),
654648
Some(&session_name),
655649
);
656650
}
@@ -713,13 +707,11 @@ fn clone_repo_command(args: &CloneRepoCommand, config: Config, tmux: &Tmux) -> R
713707

714708
let previous_session = tmux.current_session("#{session_name}");
715709

716-
let repo = git_clone(&args.repository, &path)?;
710+
let repo = RepoProvider::open(git_clone(&args.repository, &path)?, &config)?;
717711

718712
let mut session_name = repo_name.to_string();
719713

720-
let switch_config = config.clone_repo_switch.unwrap_or_default();
721-
722-
let switch = match switch_config {
714+
let switch = match config.clone_repo_switch.unwrap_or_default() {
723715
CloneRepoSwitchConfig::Always => true,
724716
CloneRepoSwitchConfig::Never => false,
725717
CloneRepoSwitchConfig::Foreground => {
@@ -741,15 +733,15 @@ fn clone_repo_command(args: &CloneRepoCommand, config: Config, tmux: &Tmux) -> R
741733
}
742734

743735
tmux.new_session(Some(&session_name), Some(&path.display().to_string()));
744-
tmux.set_up_tmux_env(&repo, &session_name)?;
736+
tmux.set_up_tmux_env(&repo, &session_name, &config)?;
745737
if switch {
746738
tmux.switch_to_session(&session_name);
747739
}
748740

749741
Ok(())
750742
}
751743

752-
fn git_clone(repo: &str, target: &Path) -> Result<Repository> {
744+
fn git_clone<'a>(repo: &str, target: &'a Path) -> Result<&'a Path> {
753745
std::fs::create_dir_all(target).change_context(TmsError::IoError)?;
754746
let mut cmd = Command::new("git")
755747
.current_dir(target.parent().ok_or(TmsError::IoError)?)
@@ -760,8 +752,7 @@ fn git_clone(repo: &str, target: &Path) -> Result<Repository> {
760752
.change_context(TmsError::GitError)?;
761753

762754
cmd.wait().change_context(TmsError::GitError)?;
763-
let repo = gix::open(target).change_context(TmsError::GitError)?;
764-
Ok(repo)
755+
Ok(target)
765756
}
766757

767758
fn init_repo_command(args: &InitRepoCommand, config: Config, tmux: &Tmux) -> Result<()> {
@@ -771,6 +762,7 @@ fn init_repo_command(args: &InitRepoCommand, config: Config, tmux: &Tmux) -> Res
771762
path.push(&args.repository);
772763

773764
let repo = gix::init(&path).change_context(TmsError::GitError)?;
765+
let repo = RepoProvider::Git(repo);
774766

775767
let mut session_name = args.repository.to_string();
776768

@@ -787,7 +779,7 @@ fn init_repo_command(args: &InitRepoCommand, config: Config, tmux: &Tmux) -> Res
787779
}
788780

789781
tmux.new_session(Some(&session_name), Some(&path.display().to_string()));
790-
tmux.set_up_tmux_env(&repo, &session_name)?;
782+
tmux.set_up_tmux_env(&repo, &session_name, &config)?;
791783
tmux.switch_to_session(&session_name);
792784

793785
Ok(())

src/configs.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ pub struct Config {
5252
pub session_configs: Option<HashMap<String, SessionConfig>>,
5353
pub marks: Option<HashMap<String, String>>,
5454
pub clone_repo_switch: Option<CloneRepoSwitchConfig>,
55+
pub vcs_providers: Option<Vec<VcsProviders>>,
56+
}
57+
58+
pub const DEFAULT_VCS_PROVIDERS: &[VcsProviders] = &[VcsProviders::Git];
59+
60+
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
61+
#[serde(rename_all = "lowercase")]
62+
pub enum VcsProviders {
63+
Git,
64+
#[serde(alias = "jj")]
65+
Jujutsu,
5566
}
5667

5768
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -71,6 +82,7 @@ pub struct ConfigExport {
7182
pub session_configs: HashMap<String, SessionConfig>,
7283
pub marks: HashMap<String, String>,
7384
pub clone_repo_switch: CloneRepoSwitchConfig,
85+
pub vcs_providers: Vec<VcsProviders>,
7486
}
7587

7688
impl From<Config> for ConfigExport {
@@ -97,6 +109,7 @@ impl From<Config> for ConfigExport {
97109
session_configs: value.session_configs.unwrap_or_default(),
98110
marks: value.marks.unwrap_or_default(),
99111
clone_repo_switch: value.clone_repo_switch.unwrap_or_default(),
112+
vcs_providers: value.vcs_providers.unwrap_or(DEFAULT_VCS_PROVIDERS.into()),
100113
}
101114
}
102115
}
@@ -433,7 +446,7 @@ impl ValueEnum for SessionSortOrderConfig {
433446
}
434447
}
435448

436-
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
449+
#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
437450
pub enum CloneRepoSwitchConfig {
438451
#[default]
439452
Always,

0 commit comments

Comments
 (0)