From 446a3a2f3f4ef5701c759eb98d6743c7c47872ce Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 22:34:44 +0200 Subject: [PATCH 01/10] Each entry on their own line Signed-off-by: Noah Knegt --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f1e1be9..e975252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ resolver = "3" members = [ "command-with-spinner", - "create-worktree", "git", + "create-worktree", + "git", "git-ssh-bitwarden", "setup-repo", ] From c0fe91f91686d2142ca01f2b533f0c43a631a8dc Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 22:57:41 +0200 Subject: [PATCH 02/10] feat(setup-repo): Use the last part of the URI as directory if none is provided Signed-off-by: Noah Knegt --- setup-repo/src/main.rs | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/setup-repo/src/main.rs b/setup-repo/src/main.rs index 02269a6..d7def01 100644 --- a/setup-repo/src/main.rs +++ b/setup-repo/src/main.rs @@ -14,7 +14,7 @@ struct Args { /// Target directory for the repository setup #[arg(short, long)] - target_dir: String, + target_dir: Option, /// Enable verbose output #[arg(short, long)] @@ -32,28 +32,45 @@ fn main() -> Result<()> { // Enable or disable colored output colored::control::set_override(!args.no_color); + let target_dir = match args.target_dir { + Some(dir) => dir, + + // If a directory is not provided construct one from the last part of the URI. + // This behavior follows git itself + None => { + let start = args.repo_url.rfind("/").unwrap() + 1; + let end = match args.repo_url.rfind(".git") + { + Some(index) => index, + None => args.repo_url.len() + }; + + args.repo_url[start..end].to_owned() + } + }; + // Print verbose information if enabled if args.verbose { println!("{}", "Verbose mode enabled".dimmed()); println!("{}", format!("Repository URL: {}", args.repo_url).dimmed()); - println!("{}", format!("Target directory: {}", args.target_dir).dimmed()); + println!("{}", format!("Target directory: {}", target_dir).dimmed()); } println!("{}", "Setting up repository for worktree development".blue()); // Clone the repository as a bare clone - Git::clone_bare_repo(&args.repo_url, &args.target_dir)?; + Git::clone_bare_repo(&args.repo_url, &target_dir)?; // Set up the .git file to point to the .bare directory - Git::setup_git_pointer(&args.target_dir)?; + Git::setup_git_pointer(&target_dir)?; // Configure the remote.origin.fetch setting - Git::configure_remote_fetch(&args.target_dir)?; + Git::configure_remote_fetch(&target_dir)?; // Fetch all remotes - Git::fetch_remotes(&args.target_dir)?; + Git::fetch_remotes(&target_dir)?; println!("{}", "Repository setup complete.".green()); - println!("{}", format!("You can now create worktrees in '{}'.", args.target_dir).green()); + println!("{}", format!("You can now create worktrees in '{}'.", target_dir).green()); Ok(()) } From b46621f772e426323c49b960fc3724590665a753 Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 22:58:37 +0200 Subject: [PATCH 03/10] chore(setup-repo): Bump version to 0.2.0 Signed-off-by: Noah Knegt --- Cargo.lock | 2 +- setup-repo/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2da9807..baf0248 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,7 +330,7 @@ dependencies = [ [[package]] name = "setup-repo" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "clap", diff --git a/setup-repo/Cargo.toml b/setup-repo/Cargo.toml index cb0f995..b8c3167 100644 --- a/setup-repo/Cargo.toml +++ b/setup-repo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "setup-repo" -version = "0.1.0" +version = "0.2.0" description = "Tool to set up Git repositories for worktree development" edition.workspace = true From 002c85329e2838d6504b7cc9cca6fe5e9f00860f Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 23:15:19 +0200 Subject: [PATCH 04/10] feat(git): Remove the failure code from the error type Signed-off-by: Noah Knegt --- Cargo.lock | 2 +- git/Cargo.toml | 2 +- git/src/error.rs | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index baf0248..6caa71e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,7 +207,7 @@ dependencies = [ [[package]] name = "git" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "command-with-spinner", diff --git a/git/Cargo.toml b/git/Cargo.toml index 0ef8928..0b3769a 100644 --- a/git/Cargo.toml +++ b/git/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git" -version = "0.1.0" +version = "0.2.0" edition.workspace = true authors.workspace = true diff --git a/git/src/error.rs b/git/src/error.rs index 7685bc8..38f800c 100644 --- a/git/src/error.rs +++ b/git/src/error.rs @@ -1,9 +1,6 @@ /// Git operations error type #[derive(Debug, thiserror::Error)] pub enum GitError { - #[error("Command failed with exit code: {0}")] - Failed(i32), - #[error("Command failed without exit code")] FailedNoCode, From ddad92dc32ca7f4385af9e4e20c3013a9a26f79b Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 23:15:49 +0200 Subject: [PATCH 05/10] chore(setup-repo): Fix clippy warnings Signed-off-by: Noah Knegt --- setup-repo/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup-repo/src/main.rs b/setup-repo/src/main.rs index d7def01..ac8ad84 100644 --- a/setup-repo/src/main.rs +++ b/setup-repo/src/main.rs @@ -53,7 +53,7 @@ fn main() -> Result<()> { if args.verbose { println!("{}", "Verbose mode enabled".dimmed()); println!("{}", format!("Repository URL: {}", args.repo_url).dimmed()); - println!("{}", format!("Target directory: {}", target_dir).dimmed()); + println!("{}", format!("Target directory: {target_dir}").dimmed()); } println!("{}", "Setting up repository for worktree development".blue()); @@ -71,6 +71,6 @@ fn main() -> Result<()> { Git::fetch_remotes(&target_dir)?; println!("{}", "Repository setup complete.".green()); - println!("{}", format!("You can now create worktrees in '{}'.", target_dir).green()); + println!("{}", format!("You can now create worktrees in '{target_dir}'.").green()); Ok(()) } From 0793c8bbd74956854f7bc59faec732197d35cf49 Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 23:16:11 +0200 Subject: [PATCH 06/10] chore(setup-repo): Update the git lib version to 0.2.0 Signed-off-by: Noah Knegt --- setup-repo/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup-repo/Cargo.toml b/setup-repo/Cargo.toml index b8c3167..7780dc1 100644 --- a/setup-repo/Cargo.toml +++ b/setup-repo/Cargo.toml @@ -12,5 +12,5 @@ repository.workspace = true anyhow = "1.0" clap = { version = "4.5", features = ["derive"] } colored = "3.0" -git = { version = "0.1.0", path = "../git" } +git = { version = "0.2.0", path = "../git" } indicatif = "0.17" From 8c22757f716a654a61b97c39b856ba70cc9b9d70 Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 23:16:39 +0200 Subject: [PATCH 07/10] chore(create-worktree): Update git lib version to 0.2.0 Signed-off-by: Noah Knegt --- create-worktree/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/create-worktree/Cargo.toml b/create-worktree/Cargo.toml index 2baeaf0..21b99ae 100644 --- a/create-worktree/Cargo.toml +++ b/create-worktree/Cargo.toml @@ -13,6 +13,6 @@ anyhow = "1.0" clap = { version = "4.5", features = ["derive"] } colored = "3.0" dirs = "6.0" -git = { version = "0.1.0", path = "../git" } +git = { version = "0.2.0", path = "../git" } indicatif = "0.17" thiserror = { version = "2.0" } From 476f849dedff2016771fe112bf94dca1757ff63f Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 23:37:27 +0200 Subject: [PATCH 08/10] build: Update the linting workflow Signed-off-by: Noah Knegt --- .gitea/workflows/linting.yaml | 85 +++++++++++++++++------------------ 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/.gitea/workflows/linting.yaml b/.gitea/workflows/linting.yaml index 7181702..a29b0a1 100644 --- a/.gitea/workflows/linting.yaml +++ b/.gitea/workflows/linting.yaml @@ -7,18 +7,36 @@ env: CARGO_TERM_COLOR: always jobs: + cargo-fmt: + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + # Cache cargo registry + - name: Hash the lock file + uses: https://gitea.com/seepine/hash-files@v1 + id: get-hash + with: + patterns: | + **/Cargo.lock + + - name: Setup rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + components: rustfmt,clippy + cache-shared-key: cargo-${{ steps.get-hash.outputs.hash }} + + - name: Rustfmt Check + uses: actions-rust-lang/rustfmt@v1 + cargo-check: runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v4 - - name: Setup rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt,clippy - # Cache cargo registry - name: Hash the lock file uses: https://gitea.com/seepine/hash-files@v1 @@ -26,37 +44,23 @@ jobs: with: patterns: | **/Cargo.lock - - name: Cache cargo registry - uses: actions/cache@v3 + + - name: Setup rust + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: cargo-${{ steps.get-hash.outputs.hash }} - restore-keys: | - cargo- - # Cache cargo registry - end + toolchain: stable + components: rustfmt,clippy + cache-shared-key: cargo-${{ steps.get-hash.outputs.hash }} - name: Cargo check - uses: actions-rs/cargo@v1 - with: - command: check + run: cargo check --workspace clippy: runs-on: ubuntu-latest steps: - - name: Checkout source code + - name: Checkout source code uses: actions/checkout@v4 - - name: Setup rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt,clippy - # Cache cargo registry - name: Hash the lock file uses: https://gitea.com/seepine/hash-files@v1 @@ -64,22 +68,13 @@ jobs: with: patterns: | **/Cargo.lock - - name: Cache cargo registry - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: cargo-${{ steps.get-hash.outputs.hash }} - restore-keys: | - cargo- - # Cache cargo registry - end - - name: Run clippy - uses: actions-rs/clippy-check@v1 + - name: Setup rust + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-targets --all-features -- -Dwarnings + toolchain: stable + components: rustfmt,clippy + cache-shared-key: cargo-${{ steps.get-hash.outputs.hash }} + + - name: Cargo check + run: cargo clippy --all-targets --all-features -- -Dwarnings From cf5b30015adee88170300f3272b892155ed384a5 Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 23:40:41 +0200 Subject: [PATCH 09/10] build: Update release pipeline Signed-off-by: Noah Knegt --- .gitea/workflows/release-package.yaml | 41 +++++++-------------------- 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/.gitea/workflows/release-package.yaml b/.gitea/workflows/release-package.yaml index b9165c3..b89f740 100644 --- a/.gitea/workflows/release-package.yaml +++ b/.gitea/workflows/release-package.yaml @@ -15,53 +15,32 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Setup rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - - # Cache cargo registry + # Cache cargo registry - name: Hash the lock file uses: https://gitea.com/seepine/hash-files@v1 id: get-hash with: patterns: | **/Cargo.lock - - name: Cache cargo registry - uses: actions/cache@v4 + + - name: Setup rust + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: cargo-${{ steps.get-hash.outputs.hash }} - restore-keys: | - cargo- - # Cache cargo registry - end + toolchain: stable + cache-shared-key: cargo-${{ steps.get-hash.outputs.hash }} + matcher: false - name: Build application - uses: actions-rs/cargo@v1 - with: - command: build - args: --release --all-features --workspace --all-targets + run: cargo build --release --all-features --workspace --all-targets - name: Package application - uses: actions-rs/cargo@v1 - with: - command: package - args: --all-features --workspace --allow-dirty + ru8n: cargo package --all-features --workspace --allow-dirty - name: Add registry to cargo config run: echo -e '[registries.gitea]\nindex = "sparse+https://git.noahknegt.com/api/packages/${{ github.repository_owner }}/cargo/"\n' > ~/.cargo/config.toml - name: Publish to cargo registry - uses: actions-rs/cargo@v1 - with: - command: publish - args: --no-verify --registry gitea --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --no-verify --registry gitea --token ${{ secrets.CARGO_REGISTRY_TOKEN }} env: CARGO_NET_GIT_FETCH_WITH_CLI: true CARGO_NET_RETRY: 2 From 5d745678f346d60ab1fbf839f4032ad483e1c637 Mon Sep 17 00:00:00 2001 From: Noah Knegt Date: Thu, 3 Jul 2025 23:43:13 +0200 Subject: [PATCH 10/10] chore: Fix formatting Signed-off-by: Noah Knegt --- create-worktree/src/main.rs | 24 ++++++++-- git/src/lib.rs | 92 ++++++++++++++----------------------- setup-repo/src/main.rs | 15 ++++-- 3 files changed, 64 insertions(+), 67 deletions(-) diff --git a/create-worktree/src/main.rs b/create-worktree/src/main.rs index 00b9b84..489f296 100644 --- a/create-worktree/src/main.rs +++ b/create-worktree/src/main.rs @@ -51,11 +51,17 @@ fn update_remote(branch: &str, create_upstream: bool) -> Result<()> { if !has_remote { // Create remote branch - println!("{}", format!("Branch '{branch}' does not exist on remote. Creating.").dimmed()); + println!( + "{}", + format!("Branch '{branch}' does not exist on remote. Creating.").dimmed() + ); Git::set_upstream_branch(branch)?; // Git::create_remote_branch(branch)?; } else { - println!("{}", format!("Branch '{branch}' exists. Setting upstream.").dimmed()); + println!( + "{}", + format!("Branch '{branch}' exists. Setting upstream.").dimmed() + ); Git::set_upstream_branch(branch)?; } @@ -73,11 +79,16 @@ fn main() -> Result<()> { if args.verbose { println!("{}", "Verbose mode enabled".dimmed()); println!("{}", format!("Base branch: {}", args.base).dimmed()); - println!("{}", format!("Create upstream: {}", args.no_create_upstream).dimmed()); + println!( + "{}", + format!("Create upstream: {}", args.no_create_upstream).dimmed() + ); } // Determine branch name if not specified - let branch = args.branch.unwrap_or_else(|| format!("{}{}", args.prefix, args.worktree)); + let branch = args + .branch + .unwrap_or_else(|| format!("{}{}", args.prefix, args.worktree)); // Normalize paths let worktree_path = Path::new(&args.worktree).to_string_lossy(); @@ -93,7 +104,10 @@ fn main() -> Result<()> { } // Change to worktree directory - println!("{}", format!("Moving into worktree: {worktree_path}").dimmed()); + println!( + "{}", + format!("Moving into worktree: {worktree_path}").dimmed() + ); env::set_current_dir(&args.worktree).context("Failed to change directory")?; // Update remote diff --git a/git/src/lib.rs b/git/src/lib.rs index 61f0ad5..44feae4 100644 --- a/git/src/lib.rs +++ b/git/src/lib.rs @@ -21,16 +21,14 @@ impl Git { // Clone the repository as a bare clone into .bare directory let mut cmd = Command::new("git"); - cmd.args([ - "clone", - "--bare", - repo_url, - &bare_dir_str - ]); + cmd.args(["clone", "--bare", repo_url, &bare_dir_str]); - match run_command(&mut cmd, &format!("Cloning repository as bare clone into {bare_dir_str}")) { + match run_command( + &mut cmd, + &format!("Cloning repository as bare clone into {bare_dir_str}"), + ) { Ok(_) => Ok(()), - Err(_) => Err(GitError::FailedNoCode.into()) + Err(_) => Err(GitError::FailedNoCode.into()), } } @@ -48,14 +46,16 @@ impl Git { let mut cmd = Command::new("git"); cmd.args([ - "--git-dir", &bare_dir_str, + "--git-dir", + &bare_dir_str, "config", - "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*" + "remote.origin.fetch", + "+refs/heads/*:refs/remotes/origin/*", ]); match run_command(&mut cmd, "Configuring remote.origin.fetch") { Ok(_) => Ok(()), - Err(_) => Err(GitError::FailedNoCode.into()) + Err(_) => Err(GitError::FailedNoCode.into()), } } @@ -65,26 +65,18 @@ impl Git { let bare_dir_str = bare_dir.to_string_lossy(); let mut cmd = Command::new("git"); - cmd.args([ - "--git-dir", &bare_dir_str, - "fetch", - "--all" - ]); + cmd.args(["--git-dir", &bare_dir_str, "fetch", "--all"]); match run_command(&mut cmd, "Fetching all remotes") { Ok(_) => Ok(()), - Err(_) => Err(GitError::FailedNoCode.into()) + Err(_) => Err(GitError::FailedNoCode.into()), } } /// Check if a branch exists locally pub fn branch_exists_locally(branch: &str) -> Result { let output = Command::new("git") - .args([ - "branch", - "--list", - branch - ]) + .args(["branch", "--list", branch]) .output() .context("Failed to check branch existence")?; @@ -94,12 +86,7 @@ impl Git { /// Check if a branch exists on the remote pub fn branch_exists_on_remote(branch: &str) -> Result { let output = Command::new("git") - .args([ - "ls-remote", - "--heads", - "origin", - branch - ]) + .args(["ls-remote", "--heads", "origin", branch]) .output() .context("Failed to check remote branch existence")?; @@ -109,62 +96,53 @@ impl Git { /// Create a new worktree with an existing branch pub fn create_worktree_existing_branch(worktree_path: &str, branch: &str) -> Result<()> { let mut cmd = Command::new("git"); - cmd.args([ - "worktree", - "add", - worktree_path, - branch - ]); + cmd.args(["worktree", "add", worktree_path, branch]); - match run_command(&mut cmd, &format!("Generating new worktree from existing branch: {branch}")) { + match run_command( + &mut cmd, + &format!("Generating new worktree from existing branch: {branch}"), + ) { Ok(_) => Ok(()), - Err(_) => Err(GitError::FailedNoCode.into()) + Err(_) => Err(GitError::FailedNoCode.into()), } } /// Create a new worktree with a new branch pub fn create_worktree_new_branch(worktree_path: &str, branch: &str, base: &str) -> Result<()> { let mut cmd = Command::new("git"); - cmd.args([ - "worktree", - "add", - "-b", branch, - worktree_path, - base - ]); + cmd.args(["worktree", "add", "-b", branch, worktree_path, base]); - match run_command(&mut cmd, &format!("Generating new worktree: {worktree_path}")) { + match run_command( + &mut cmd, + &format!("Generating new worktree: {worktree_path}"), + ) { Ok(_) => Ok(()), - Err(_) => Err(GitError::FailedNoCode.into()) + Err(_) => Err(GitError::FailedNoCode.into()), } } /// Create and push a new remote branch pub fn create_remote_branch(branch: &str) -> Result<()> { let mut cmd = Command::new("git"); - cmd.args([ - "push", - "-u", "origin", - branch - ]); + cmd.args(["push", "-u", "origin", branch]); match run_command(&mut cmd, &format!("Creating remote branch {branch}...")) { Ok(_) => Ok(()), - Err(_) => Err(GitError::FailedNoCode.into()) + Err(_) => Err(GitError::FailedNoCode.into()), } } /// Set the upstream branch pub fn set_upstream_branch(branch: &str) -> Result<()> { let mut cmd = Command::new("git"); - cmd.args([ - "branch", - "--set-upstream-to", &format!("origin/{branch}") - ]); + cmd.args(["branch", "--set-upstream-to", &format!("origin/{branch}")]); - match run_command(&mut cmd, &format!("Setting upstream branch to 'origin/{branch}'")) { + match run_command( + &mut cmd, + &format!("Setting upstream branch to 'origin/{branch}'"), + ) { Ok(_) => Ok(()), - Err(_) => Err(GitError::FailedNoCode.into()) + Err(_) => Err(GitError::FailedNoCode.into()), } } } diff --git a/setup-repo/src/main.rs b/setup-repo/src/main.rs index ac8ad84..6e4c504 100644 --- a/setup-repo/src/main.rs +++ b/setup-repo/src/main.rs @@ -39,10 +39,9 @@ fn main() -> Result<()> { // This behavior follows git itself None => { let start = args.repo_url.rfind("/").unwrap() + 1; - let end = match args.repo_url.rfind(".git") - { + let end = match args.repo_url.rfind(".git") { Some(index) => index, - None => args.repo_url.len() + None => args.repo_url.len(), }; args.repo_url[start..end].to_owned() @@ -56,7 +55,10 @@ fn main() -> Result<()> { println!("{}", format!("Target directory: {target_dir}").dimmed()); } - println!("{}", "Setting up repository for worktree development".blue()); + println!( + "{}", + "Setting up repository for worktree development".blue() + ); // Clone the repository as a bare clone Git::clone_bare_repo(&args.repo_url, &target_dir)?; @@ -71,6 +73,9 @@ fn main() -> Result<()> { Git::fetch_remotes(&target_dir)?; println!("{}", "Repository setup complete.".green()); - println!("{}", format!("You can now create worktrees in '{target_dir}'.").green()); + println!( + "{}", + format!("You can now create worktrees in '{target_dir}'.").green() + ); Ok(()) }