Skip to content

feat: add GitLab support for pr command#26

Closed
Cl3MM wants to merge 1 commit intojohnlindquist:mainfrom
Cl3MM:feat/gitlab-support
Closed

feat: add GitLab support for pr command#26
Cl3MM wants to merge 1 commit intojohnlindquist:mainfrom
Cl3MM:feat/gitlab-support

Conversation

@Cl3MM
Copy link

@Cl3MM Cl3MM commented Dec 5, 2025

Add support for GitLab CLI (glab) alongside GitHub CLI (gh) for the pr command.

  • Auto-detect git provider from remote URL (github.com or gitlab.com)
  • Add config option: wt config set provider gh|glab
  • Adapt pr command to work with both GitHub PRs and GitLab MRs
  • Update error messages to be provider-specific (PR vs MR, GitHub vs GitLab)
  • Add comprehensive documentation in README

The tool automatically detects the provider from the remote URL. Manual configuration is available as fallback via config command.

Backward compatible: defaults to gh (GitHub CLI) maintaining existing behavior.

Summary by CodeRabbit

  • New Features

    • Multi-provider support for both GitHub and GitLab workflows
    • Configurable Git provider setting with automatic detection from repository URL
    • New commands: setup, open, and extract for enhanced workflow management
  • Bug Fixes

    • Safety checks prevent operations on bare repositories
    • Improved error messaging for better user guidance
  • Documentation

    • Updated documentation with PR and Merge Request terminology

✏️ Tip: You can customize this high-level summary in your review settings.

Add support for GitLab CLI (glab) alongside GitHub CLI (gh) for the pr command.

- Auto-detect git provider from remote URL (github.com or gitlab.com)
- Add config option: wt config set provider gh|glab
- Adapt pr command to work with both GitHub PRs and GitLab MRs
- Update error messages to be provider-specific (PR vs MR, GitHub vs GitLab)
- Add comprehensive documentation in README

The tool automatically detects the provider from the remote URL.
Manual configuration is available as fallback via config command.

Backward compatible: defaults to gh (GitHub CLI) maintaining existing behavior.
@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

The changes introduce multi-provider Git CLI support for GitHub (gh) and GitLab (glab) with automatic provider detection from remote URLs, new configuration options for provider selection, safety checks preventing operations on bare repositories, and three new worktree management commands. User-facing terminology and documentation updated to reference PR/MR interchangeably.

Changes

Cohort / File(s) Summary
Documentation
README.md
Updated terminology from "PR" to "PR/MR" and added sections documenting multi-provider support (gh/glab), automatic provider detection from Git remotes, and new configuration options for provider selection with fallback behavior.
Configuration Schema
src/config.ts, build/config.js
Added new gitProvider configuration field with allowed values 'gh' | 'glab' and default 'gh'; introduced getGitProvider() and setGitProvider() accessor functions.
Config Command
src/commands/config.ts, build/commands/config.js
Extended config command to support provider key alongside existing editor key; added validation, get/set workflows, and error messaging for unknown keys and invalid provider values.
PR/MR Command
src/commands/pr.ts, build/commands/pr.js
Refactored PR workflow to support both GitHub and GitLab; added provider detection and configuration logic, provider-aware branch retrieval via getBranchNameFromPR(prNumber, provider), updated CLI invocation and error handling to differentiate PR vs MR messaging.
Git Utilities
src/utils/git.ts, build/utils/git.js
Added three new utility functions: detectGitProvider() to infer provider from origin URL, isMainRepoBare() to check for bare repositories, and getRepoRoot() to locate repository root.
Worktree Safety
src/commands/{new.ts, merge.ts, remove.ts}, build/commands/{new.js, merge.js, remove.js}
Integrated isMainRepoBare() checks before worktree operations; updated new.js to error on non-clean main worktree (previously warned); all three commands now prevent operations on bare main repositories.
CLI Main
src/index.ts, build/index.js
Added imports and wiring for three new commands (setup, open, extract); extended config command with provider subcommand for set/get workflows; standardized string literal formatting in config handler calls.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as CLI Handler<br/>(prWorktreeHandler)
    participant Config as Config Module<br/>(getGitProvider)
    participant GitUtil as Git Utilities<br/>(detectGitProvider)
    participant GitRemote as Git Remote
    participant GitCLI as gh/glab CLI
    participant Repo as Local Repo

    User->>CLI: pr checkout <pr-number>
    CLI->>Config: getGitProvider()
    Config-->>CLI: configured provider or 'gh'
    
    CLI->>GitUtil: detectGitProvider()
    GitUtil->>GitRemote: git remote get-url origin
    GitRemote-->>GitUtil: remote URL
    GitUtil-->>CLI: detected provider (gh/glab)
    
    alt Provider mismatch detected
        CLI-->>User: ⚠️ Warning: switching to detected provider
    end
    
    CLI->>Repo: isWorktreeClean check
    Repo-->>CLI: clean/dirty status
    
    CLI->>GitCLI: gh pr view / glab mr view <pr-number>
    GitCLI-->>CLI: branch name
    
    CLI->>Repo: git checkout <branch>
    Repo-->>CLI: ✓ Branch checked out
    
    CLI->>Repo: Create worktree for PR/MR
    Repo-->>CLI: ✓ Worktree created
    
    CLI-->>User: ✓ Ready to work on PR/MR
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Provider detection logic: Verify detectGitProvider() correctly maps origin URLs to 'gh'/'glab' and handles edge cases
  • Multi-provider branch retrieval: Ensure getBranchNameFromPR() correctly invokes gh/glab CLIs and extracts branch names from different JSON structures
  • Bare repository checks: Confirm isMainRepoBare() detection works across bare and non-bare repos; verify it halts operations with appropriate exit codes
  • Configuration validation: Check that provider key validation and config storage/retrieval work correctly; verify fallback to auto-detection on mismatch

Possibly related PRs

Poem

🐰 Hop! Hop! Through GitHub and GitLab we bound,
Detecting providers 'round and around,
PR, MR—oh what versatile names!
Multi-provider worktrees now play the same games,
With safety checks standing tall,
This little rabbit loves it all! 🌿✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add GitLab support for pr command' accurately describes the primary change: adding GitLab support to the pr command alongside existing GitHub support.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
build/commands/config.js (1)

2-2: Generated config handler matches TypeScript source

This built version correctly reflects the provider support and validation already reviewed in src/commands/config.ts; no additional issues beyond the source.

Also applies to: 11-14, 17-18, 26-34, 39-42, 45-46

🧹 Nitpick comments (11)
src/utils/git.ts (1)

85-89: The 'gitlab' substring check may cause false positives.

The check remoteUrl.includes('gitlab') (line 87) could match unintended URLs like mygitlabbackup.com, notgitlab.org, or paths containing "gitlab" in directory names. Consider being more precise:

-        if (remoteUrl.includes('github.com')) {
-            return 'gh';
-        } else if (remoteUrl.includes('gitlab.com') || remoteUrl.includes('gitlab')) {
-            return 'glab';
-        }
+        if (remoteUrl.includes('github.com')) {
+            return 'gh';
+        } else if (remoteUrl.includes('gitlab.com') || remoteUrl.match(/[:\/]gitlab[.\/]/)) {
+            return 'glab';
+        }

Alternatively, if the intent is to support self-hosted GitLab instances with varied domains, consider documenting this behavior or requiring explicit configuration for non-gitlab.com hosts.

build/commands/remove.js (1)

45-45: Remove development marker comment.

The comment // >>> ADD SAFETY CHECK HERE <<< appears to be a leftover development note that should be cleaned up before merging.

-        // >>> ADD SAFETY CHECK HERE <<<
src/index.ts (1)

103-124: Update PR command description for GitLab support.

The argument description (line 107) and command description (lines 121-123) still reference only GitHub. Per the PR objectives to use provider-specific terminology (PR vs MR), consider updating these descriptions:

 program
   .command("pr")
   .argument(
     "<prNumber>",
-    "GitHub Pull Request number to create a worktree from"
+    "Pull Request (GitHub) or Merge Request (GitLab) number to create a worktree from"
   )
   .option(
     "-p, --path <path>",
     "Specify a custom path for the worktree (defaults to repoName-branchName)"
   )
   .option(
     "-i, --install <packageManager>",
     "Package manager to use for installing dependencies (npm, pnpm, bun, etc.)"
   )
   .option(
     "-e, --editor <editor>",
     "Editor to use for opening the worktree (overrides default editor)"
   )
   .description(
-    "Fetch the branch for a given GitHub PR number and create a worktree."
+    "Fetch the branch for a given PR/MR number and create a worktree."
   )
   .action(prWorktreeHandler);
build/utils/git.js (1)

41-65: Git utility helpers are correct; Windows path edge case is minor

isMainRepoBare, getRepoRoot, and detectGitProvider are implemented safely with good error handling and sensible fallbacks; using .git as cwd still works even when endsWith('/.git') doesn’t match on Windows, so the small path‑separator nuance isn’t a blocker.

If you want to be extra defensive cross‑platform, consider trimming both /.git and \\\.git when computing mainRepoDir, but it’s not strictly necessary.

Also applies to: 66-75, 76-92

build/commands/pr.js (2)

60-70: Provider detection and override behavior is reasonable

Deriving provider from config, then overriding with detectGitProvider() when it disagrees (with a clear yellow warning) is a good balance between explicit configuration and auto-detection; the isPR/requestType branching built on top of that reads cleanly.

If you ever need to guard against a corrupted config file, a small sanity check like if (provider !== 'gh' && provider !== 'glab') provider = detectedProvider ?? 'gh'; would make this even more bulletproof, but it’s not strictly required given the Conf schema.


215-220: detached: true + await execa(...) blocks CLI despite fire-and-forget intent

The current code will wait for the editor process to exit because await execa(...) waits for the child to complete, even with detached: true. The detached flag only separates the process group (OS-dependent behavior) but doesn't prevent the parent from waiting unless you also call .unref() on the child.

To truly fire-and-forget the editor, remove the await and call .unref():

         try {
-            await execa(editorCommand, [resolvedPath], { stdio: "ignore", detached: true });
+            const child = execa(editorCommand, [resolvedPath], { stdio: "ignore", detached: true });
+            child.unref();
+            child.catch(editorError => {
+                console.error(chalk.red(`Failed to open editor "${editorCommand}". Please ensure it's installed and in your PATH.`));
+                console.warn(chalk.yellow(`Worktree is ready at ${resolvedPath}. You can open it manually.`));
+            });
         }

This allows wt pr to exit promptly while still reporting spawn failures via the error handler.

README.md (1)

53-61: Docs accurately describe PR/MR + provider behavior (with a tiny naming nit)

The README updates for wt pr and “Configure Git Provider” clearly explain dual GitHub/GitLab support, auto-detection from the remote URL, and the wt config set/get provider flow, matching the implementation in prWorktreeHandler and configHandler. The only small polish nit is wording like “(github.com or gitlab.com/gitlab.*)”—when referring to the product name (not the literal domain), consider “GitHub” with a capital “H” for consistency.

Also applies to: 66-67, 104-121, 194-201

src/commands/pr.ts (4)

5-6: Consider centralizing the GitProvider type definition.

The GitProvider type is defined locally here but may be needed across multiple files (config, utils, other commands). To prevent duplication and ensure consistency, consider moving this type to a shared location like src/types.ts or exporting it from src/config.ts.

Apply this approach:

  1. Create or use an existing shared types file:
// src/types.ts
export type GitProvider = 'gh' | 'glab';
  1. Import from the shared location:
-import { getDefaultEditor, getGitProvider } from "../config.js";
+import { getDefaultEditor, getGitProvider, GitProvider } from "../config.js";
 import { getCurrentBranch, isWorktreeClean, isMainRepoBare, detectGitProvider } from "../utils/git.js";

-type GitProvider = 'gh' | 'glab';

Also applies to: 8-8


105-123: Consider showing CLI output to users for better transparency.

Line 110 uses stdio: 'pipe' which captures and hides output from the gh/glab checkout command. For CLI tools, users typically expect to see progress messages, warnings, and other output from underlying commands. This also aids debugging when issues occur.

Apply this diff to show checkout output:

-            await execa(cliName, [subCommand, "checkout", prNumber], { stdio: 'pipe' });
+            await execa(cliName, [subCommand, "checkout", prNumber], { stdio: 'inherit' });

Note: If you need to suppress output only on success but show it on error, you can use a try-catch with conditional output handling.


128-135: Clarify the branch restoration logic.

The else-if condition at Line 132 handles cases where the current branch is neither prBranchName nor originalBranch. The message says "Assuming ${cliName} handled checkout correctly" but then proceeds to checkout originalBranch anyway. This messaging is confusing—if we're assuming it handled checkout correctly, why are we changing branches?

Consider clarifying the logic:

                 if (currentBranchAfterCheckout === prBranchName && currentBranchAfterCheckout !== originalBranch) {
                     console.log(chalk.blue(`Switching main worktree back to "${originalBranch}" before creating worktree...`));
                     await execa("git", ["checkout", originalBranch]);
-                } else if (currentBranchAfterCheckout !== originalBranch) {
-                    console.log(chalk.yellow(`Current branch is ${currentBranchAfterCheckout}, not ${prBranchName}. Assuming ${cliName} handled checkout correctly.`));
+                } else if (currentBranchAfterCheckout !== originalBranch) {
+                    console.log(chalk.yellow(`Warning: Current branch is "${currentBranchAfterCheckout}", expected "${originalBranch}". Restoring original branch...`));
                     await execa("git", ["checkout", originalBranch]);
                 }

242-257: Fragile error message string matching.

Line 251 checks for "already warned" in the error message to avoid duplicate warnings. This string-matching approach is fragile and could break if error messages change.

Consider using a flag to track whether a warning was already issued:

     } catch (error: any) {
         console.error(chalk.red("❌ Failed to set up worktree from PR/MR:"), error.message || error);
         if (error.stack && !(error.stderr || error.stdout)) {
             console.error(error.stack);
         }
         process.exit(1);
     } finally {
         // 12. Ensure we are back on the original branch in the main worktree
+        let finalCheckoutWarned = false;
         if (originalBranch) {
             try {
                 const currentBranchNow = await getCurrentBranch();
                 if (currentBranchNow !== originalBranch) {
                     console.log(chalk.blue(`Ensuring main worktree is back on "${originalBranch}"...`));
                     await execa("git", ["checkout", originalBranch]);
                 }
             } catch (checkoutError: any) {
-                if (!checkoutError.message.includes("already warned")) {
+                if (!finalCheckoutWarned) {
+                    finalCheckoutWarned = true;
                     console.warn(chalk.yellow(`⚠️ Warning: Final check failed to switch main worktree back to original branch "${originalBranch}". Please check manually.`));
                     console.warn(checkoutError.stderr || checkoutError.message);
                 }
             }
         }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1d95fe and a887b99.

📒 Files selected for processing (14)
  • README.md (4 hunks)
  • build/commands/config.js (3 hunks)
  • build/commands/merge.js (2 hunks)
  • build/commands/new.js (2 hunks)
  • build/commands/pr.js (5 hunks)
  • build/commands/remove.js (2 hunks)
  • build/config.js (2 hunks)
  • build/index.js (4 hunks)
  • build/utils/git.js (1 hunks)
  • src/commands/config.ts (2 hunks)
  • src/commands/pr.ts (5 hunks)
  • src/config.ts (3 hunks)
  • src/index.ts (2 hunks)
  • src/utils/git.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/commands/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

src/commands/*.ts: Leverage Execa to execute Git commands and other external processes.
Provide clear, colored console feedback for success and error messages in CLI commands.

Files:

  • src/commands/config.ts
  • src/commands/pr.ts
src/index.ts

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

src/index.ts: The main entry point for the CLI is src/index.ts, which sets up CLI commands and orchestrates command handlers.
Utilize Commander for parsing CLI commands and handling options.

Files:

  • src/index.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: juristr
Repo: johnlindquist/worktree-cli PR: 20
File: src/commands/extract.ts:124-127
Timestamp: 2025-08-04T14:22:29.156Z
Learning: The worktree-cli project prioritizes consistency across commands. When implementing new commands like `extract`, developers follow existing patterns from similar commands like `new` to maintain API and implementation consistency.
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/list.ts : Provide functionality to list existing Git worktrees in src/commands/list.ts.
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/new.ts : Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/remove.ts : Handle removal of Git worktrees, including support for force deletion, in src/commands/remove.ts.
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/list.ts : Provide functionality to list existing Git worktrees in src/commands/list.ts.

Applied to files:

  • src/utils/git.ts
  • build/commands/remove.js
  • build/commands/merge.js
  • build/utils/git.js
  • README.md
  • build/commands/pr.js
  • src/commands/pr.ts
  • build/commands/new.js
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/remove.ts : Handle removal of Git worktrees, including support for force deletion, in src/commands/remove.ts.

Applied to files:

  • build/commands/remove.js
  • build/commands/merge.js
  • README.md
  • build/commands/pr.js
  • src/commands/pr.ts
  • build/commands/new.js
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/new.ts : Implement logic for creating new Git worktrees, including options for branch creation, dependency installation, and opening in an editor, in src/commands/new.ts.

Applied to files:

  • build/commands/remove.js
  • build/commands/merge.js
  • src/commands/config.ts
  • README.md
  • build/commands/pr.js
  • src/commands/pr.ts
  • build/commands/new.js
  • build/index.js
📚 Learning: 2025-08-04T14:22:29.156Z
Learnt from: juristr
Repo: johnlindquist/worktree-cli PR: 20
File: src/commands/extract.ts:124-127
Timestamp: 2025-08-04T14:22:29.156Z
Learning: The worktree-cli project prioritizes consistency across commands. When implementing new commands like `extract`, developers follow existing patterns from similar commands like `new` to maintain API and implementation consistency.

Applied to files:

  • build/commands/remove.js
  • README.md
  • build/commands/pr.js
  • src/commands/pr.ts
  • build/commands/new.js
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/commands/*.ts : Leverage Execa to execute Git commands and other external processes.

Applied to files:

  • build/commands/merge.js
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/index.ts : Utilize Commander for parsing CLI commands and handling options.

Applied to files:

  • src/index.ts
  • build/index.js
📚 Learning: 2025-08-04T13:02:29.847Z
Learnt from: CR
Repo: johnlindquist/worktree-cli PR: 0
File: .cursor/rules/project.mdc:0-0
Timestamp: 2025-08-04T13:02:29.847Z
Learning: Applies to src/index.ts : The main entry point for the CLI is src/index.ts, which sets up CLI commands and orchestrates command handlers.

Applied to files:

  • build/index.js
🧬 Code graph analysis (11)
build/config.js (3)
src/config.ts (2)
  • getGitProvider (48-50)
  • setGitProvider (53-55)
build/commands/config.js (1)
  • provider (12-12)
build/commands/pr.js (1)
  • provider (61-61)
build/commands/remove.js (2)
build/utils/git.js (1)
  • isMainRepoBare (42-65)
src/utils/git.ts (1)
  • isMainRepoBare (44-68)
build/commands/merge.js (2)
build/utils/git.js (1)
  • isMainRepoBare (42-65)
src/utils/git.ts (1)
  • isMainRepoBare (44-68)
src/commands/config.ts (2)
build/config.js (3)
  • getGitProvider (36-38)
  • setDefaultEditor (32-34)
  • setGitProvider (40-42)
src/config.ts (3)
  • getGitProvider (48-50)
  • setDefaultEditor (43-45)
  • setGitProvider (53-55)
src/index.ts (2)
build/commands/config.js (1)
  • configHandler (3-67)
src/commands/config.ts (1)
  • configHandler (4-60)
build/utils/git.js (1)
src/utils/git.ts (3)
  • isMainRepoBare (44-68)
  • getRepoRoot (70-78)
  • detectGitProvider (80-96)
src/config.ts (3)
build/config.js (3)
  • getGitProvider (36-38)
  • config (23-26)
  • setGitProvider (40-42)
build/commands/config.js (1)
  • provider (12-12)
build/commands/pr.js (1)
  • provider (61-61)
build/commands/config.js (3)
build/commands/pr.js (1)
  • provider (61-61)
build/config.js (2)
  • getGitProvider (36-38)
  • setGitProvider (40-42)
src/config.ts (2)
  • getGitProvider (48-50)
  • setGitProvider (53-55)
src/commands/pr.ts (1)
src/utils/git.ts (4)
  • detectGitProvider (80-96)
  • isWorktreeClean (15-40)
  • getCurrentBranch (4-13)
  • isMainRepoBare (44-68)
build/commands/new.js (2)
src/commands/new.ts (1)
  • newWorktreeHandler (8-129)
src/utils/git.ts (2)
  • isWorktreeClean (15-40)
  • isMainRepoBare (44-68)
build/index.js (5)
src/commands/setup.ts (1)
  • setupWorktreeHandler (9-232)
src/commands/open.ts (1)
  • openWorktreeHandler (7-107)
src/commands/extract.ts (1)
  • extractWorktreeHandler (8-153)
build/commands/config.js (1)
  • configHandler (3-67)
src/commands/config.ts (1)
  • configHandler (4-60)
🪛 LanguageTool
README.md

[uncategorized] ~120-~120: The official name of this software platform is spelled with a capital “H”.
Context: ...t provider by analyzing the remote URL (github.com or gitlab.com/gitlab.*). The config...

(GITHUB)

🔇 Additional comments (16)
src/utils/git.ts (1)

80-96: LGTM on the function structure and error handling.

The function follows the established patterns in this file: async/await with execa, appropriate error handling with chalk-colored warnings, and sensible default return values. The return type 'gh' | 'glab' | null is well-typed for downstream usage.

build/commands/merge.js (2)

4-4: LGTM on import.

The import correctly references the utility from the shared git utilities module.


58-63: LGTM on bare repository guard.

The safety check correctly prevents worktree removal when the main repository is bare. The error messages are clear and provide actionable guidance. The placement after the merge operation (line 54) is reasonable—it allows the merge to proceed while blocking only the cleanup step that would fail.

build/config.js (2)

17-21: LGTM on schema definition.

The gitProvider configuration follows the same pattern as defaultEditor. The enum constraint will be enforced by the Conf library's schema validation at runtime.


35-42: LGTM on getter/setter functions.

The functions follow the established pattern from getDefaultEditor/setDefaultEditor. Input validation for the provider value is handled at the command handler level (src/commands/config.ts lines 24-27), which is consistent with the existing approach for the editor configuration.

build/commands/remove.js (1)

46-51: LGTM on bare repository guard.

The safety check is correctly placed before the destructive git worktree remove operation and follows the same pattern used in merge.js. Error messages are consistent and actionable.

src/index.ts (2)

157-176: LGTM on config set subcommands.

The provider subcommand follows the established pattern from the editor subcommand. The structure using addCommand is clean and maintainable. Based on learnings, this utilizes Commander for parsing CLI commands as expected.


186-190: LGTM on config get provider subcommand.

The get provider subcommand correctly mirrors the structure of the get editor subcommand, maintaining consistency in the CLI interface.

build/commands/new.js (1)

6-6: New safety checks for dirty/bare repos look solid

Importing isWorktreeClean/isMainRepoBare and hard‑failing when the main worktree is dirty or the repo is bare is consistent with the other commands and avoids subtle git corruption. No issues here.

Also applies to: 14-17, 72-77

src/commands/config.ts (1)

2-2: Provider config wiring is correct and user-friendly

provider handling mirrors editor semantics, validates to 'gh' | 'glab', and surfaces clear error/help messages including the available keys; this should integrate cleanly with the new gitProvider field in src/config.ts.

Also applies to: 11-13, 16-16, 24-31, 35-37, 40-41

src/config.ts (1)

13-16: gitProvider schema and accessors are well-defined

Adding gitProvider with an enum schema and default 'gh', plus getGitProvider/setGitProvider, cleanly exposes provider choice while keeping existing configs backward compatible via Conf’s defaults. No changes needed.

Also applies to: 21-30, 47-55

build/commands/pr.js (4)

71-81: Worktree clean check and gh/glab checkout flow are robust

The clean‑worktree guard before touching branches, the provider‑aware cliName/subCommand selection, and the detailed error mapping for checkout failures (already checked out, not found, CLI missing) give a solid UX and minimize risky git states.

Also applies to: 83-91, 93-113


181-199: Bare-repo guard before worktree creation is consistent with other commands

Checking isMainRepoBare() right before git worktree add and aborting with a clear explanation and recovery hint (git config core.bare false) aligns well with the safety checks in new/setup/extract and should prevent confusing failures in misconfigured repos.


225-233: Top-level error reporting is clear and PR/MR-neutral

The final catch logs a provider‑agnostic failure message (Failed to set up worktree from PR/MR) and optionally prints a stack trace when stderr/stdout aren’t present, which is helpful for debugging without overwhelming normal users.


5-7: gh/glab branch resolution looks correct for PR/MR head branches

The helper correctly:

  • Uses gh pr view <n> --json headRefName -q .headRefName for GitHub, which matches current CLI docs for retrieving headRefName.
  • Uses glab mr view <n> -F json and reads source_branch, which is consistent with the GitLab MR JSON schema.

Error handling distinguishes between "not found" and missing CLI binaries with provider-specific messages, which should give clear feedback to users.

src/commands/pr.ts (1)

29-42: The code correctly uses source_branch as the field name. The glab CLI's JSON output for mr view follows the GitLab Merge Request API schema, which documents source_branch as the standard field containing the source branch name. The assumption in the code is accurate and aligns with official glab and GitLab documentation.

Comment on lines +115 to 132
// 6. Switch back to original branch IMMEDIATELY after checkout ensures the main worktree is clean
if (originalBranch) {
try {
const currentBranchAfterGh = await getCurrentBranch();
if (currentBranchAfterGh === prBranchName && currentBranchAfterGh !== originalBranch) {
const currentBranchAfterCheckout = await getCurrentBranch();
if (currentBranchAfterCheckout === prBranchName && currentBranchAfterCheckout !== originalBranch) {
console.log(chalk.blue(`Switching main worktree back to "${originalBranch}" before creating worktree...`));
await execa("git", ["checkout", originalBranch]);
}
else if (currentBranchAfterGh !== originalBranch) {
console.log(chalk.yellow(`Current branch is ${currentBranchAfterGh}, not ${prBranchName}. Assuming gh handled checkout correctly.`));
// If gh failed but left us on a different branch, still try to go back
else if (currentBranchAfterCheckout !== originalBranch) {
console.log(chalk.yellow(`Current branch is ${currentBranchAfterCheckout}, not ${prBranchName}. Assuming ${cliName} handled checkout correctly.`));
await execa("git", ["checkout", originalBranch]);
}
}
catch (checkoutError) {
console.warn(chalk.yellow(`⚠️ Warning: Failed to switch main worktree back to original branch "${originalBranch}" after gh checkout. Please check manually.`));
console.warn(chalk.yellow(`⚠️ Warning: Failed to switch main worktree back to original branch "${originalBranch}" after ${cliName} checkout. Please check manually.`));
console.warn(checkoutError.stderr || checkoutError.message);
// Proceed with caution, worktree add might fail
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate branch-restore warnings and unused “already warned” check

Both the mid‑flow restore block and the finally block try to switch back to originalBranch, but the "already warned" string is never set, so the final block will always log its warning if checkout fails, potentially duplicating messages.

You can simplify and de‑duplicate this by tracking a boolean flag instead of inspecting checkoutError.message, for example:

-    finally {
-        // 12. Ensure we are back on the original branch in the main worktree
-        if (originalBranch) {
-            try {
-                const currentBranchNow = await getCurrentBranch();
-                if (currentBranchNow !== originalBranch) {
-                    console.log(chalk.blue(`Ensuring main worktree is back on "${originalBranch}"...`));
-                    await execa("git", ["checkout", originalBranch]);
-                }
-            }
-            catch (checkoutError) {
-                if (!checkoutError.message.includes("already warned")) {
-                    console.warn(chalk.yellow(`⚠️ Warning: Final check failed to switch main worktree back to original branch "${originalBranch}". Please check manually.`));
-                    console.warn(checkoutError.stderr || checkoutError.message);
-                }
-            }
-        }
-    }
+    finally {
+        if (originalBranch) {
+            try {
+                const currentBranchNow = await getCurrentBranch();
+                if (currentBranchNow !== originalBranch) {
+                    console.log(chalk.blue(`Ensuring main worktree is back on "${originalBranch}"...`));
+                    await execa("git", ["checkout", originalBranch]);
+                }
+            } catch (checkoutError) {
+                console.warn(chalk.yellow(`⚠️ Warning: Final check failed to switch main worktree back to original branch "${originalBranch}". Please check manually.`));
+                console.warn(checkoutError.stderr || checkoutError.message);
+            }
+        }
+    }

Also applies to: 237-252

🤖 Prompt for AI Agents
In build/commands/pr.js around lines 115 to 132 (and similarly at 237-252), the
branch-restore logic can emit duplicate warnings because the "already warned"
string is never set; replace that pattern with a boolean flag (e.g.,
restoredMainWorktreeWarningEmitted) initialized false, set it true when you log
the warning in the mid-flow catch, and check that flag in the finally block
before logging so the warning is only emitted once; ensure both restore
locations use the same flag and avoid relying on checkoutError.message content.

Comment on lines +27 to +35
program
.command("setup")
.argument("[branchName]", "Name of the branch to base this worktree on")
.option("-p, --path <path>", "Relative path/folder name for new worktree")
.option("-c, --checkout", "Create new branch if it doesn't exist and checkout automatically", false)
.option("-i, --install <packageManager>", "Package manager to use for installing dependencies (npm, pnpm, bun, etc.)")
.option("-e, --editor <editor>", "Editor to use for opening the worktree (e.g., code, webstorm, windsurf, etc.)")
.description("Create a new worktree and run setup scripts from worktrees.json or .cursor/worktrees.json")
.action(setupWorktreeHandler);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

CLI wiring looks good; update pr help text to mention GitLab/MRs

The new setup, open, and extract commands, plus the config set/get provider subcommands, are wired correctly and follow the existing Commander patterns. The only mismatch is in the pr command help:

  • Argument description: "GitHub Pull Request number to create a worktree from"
  • Command description: "Fetch the branch for a given GitHub PR number and create a worktree."

Now that prWorktreeHandler supports both GitHub PRs and GitLab MRs based on provider detection, this help text should be updated to something like “Pull Request / Merge Request number” and remove the GitHub‑only wording.

-    .argument("<prNumber>", "GitHub Pull Request number to create a worktree from")
+    .argument("<prNumber>", "Pull Request / Merge Request number to create a worktree from")
@@
-    .description("Fetch the branch for a given GitHub PR number and create a worktree.")
+    .description("Fetch the branch for a given PR/MR number and create a worktree using the appropriate provider (GitHub or GitLab).")

Also applies to: 59-65, 67-79, 81-104

🤖 Prompt for AI Agents
In build/index.js around lines 27 to 35 (and similarly at 59-65, 67-79, 81-104),
the CLI help text for the `pr` command is GitHub-specific; update the argument
and command descriptions to be provider-agnostic (e.g., use "Pull Request /
Merge Request number" and remove "GitHub" wording) so the help reflects support
for both GitHub PRs and GitLab MRs; change any occurrences of "GitHub" in those
descriptions to neutral phrasing and ensure examples/help consistently reference
"PR/MR" or "Pull Request / Merge Request" as appropriate.

Comment on lines +68 to +78
// 2. Determine git provider (from config or auto-detect)
let provider = getGitProvider();
const detectedProvider = await detectGitProvider();
if (detectedProvider && detectedProvider !== provider) {
console.log(chalk.yellow(`Detected ${detectedProvider === 'gh' ? 'GitHub' : 'GitLab'} repository, but config is set to '${provider}'.`));
console.log(chalk.yellow(`Using detected provider: ${detectedProvider}`));
provider = detectedProvider;
}
const isPR = provider === 'gh';
const requestType = isPR ? "PR" : "MR";

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Provider precedence contradicts the PR description.

The PR description states: "automatic detection with the manual config as a fallback", which implies the config setting should take precedence. However, the current implementation does the opposite—when detectedProvider differs from provider, it overrides the config value and uses the detected one.

This could confuse users who explicitly run wt config set provider glab expecting their choice to be respected.

If the config should take precedence (as the PR description suggests), apply this diff:

-        // 2. Determine git provider (from config or auto-detect)
-        let provider = getGitProvider();
-        const detectedProvider = await detectGitProvider();
-        if (detectedProvider && detectedProvider !== provider) {
-            console.log(chalk.yellow(`Detected ${detectedProvider === 'gh' ? 'GitHub' : 'GitLab'} repository, but config is set to '${provider}'.`));
-            console.log(chalk.yellow(`Using detected provider: ${detectedProvider}`));
-            provider = detectedProvider;
-        }
+        // 2. Determine git provider (prefer config, fallback to auto-detect)
+        let provider = getGitProvider();
+        const detectedProvider = await detectGitProvider();
+        if (!provider && detectedProvider) {
+            console.log(chalk.blue(`Auto-detected ${detectedProvider === 'gh' ? 'GitHub' : 'GitLab'} repository.`));
+            provider = detectedProvider;
+        } else if (detectedProvider && detectedProvider !== provider) {
+            console.log(chalk.yellow(`Detected ${detectedProvider === 'gh' ? 'GitHub' : 'GitLab'} repository, but using configured provider '${provider}'.`));
+        }

Alternatively, if auto-detection should override (current behavior), please update the PR description to reflect this design choice.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 2. Determine git provider (from config or auto-detect)
let provider = getGitProvider();
const detectedProvider = await detectGitProvider();
if (detectedProvider && detectedProvider !== provider) {
console.log(chalk.yellow(`Detected ${detectedProvider === 'gh' ? 'GitHub' : 'GitLab'} repository, but config is set to '${provider}'.`));
console.log(chalk.yellow(`Using detected provider: ${detectedProvider}`));
provider = detectedProvider;
}
const isPR = provider === 'gh';
const requestType = isPR ? "PR" : "MR";
// 2. Determine git provider (prefer config, fallback to auto-detect)
let provider = getGitProvider();
const detectedProvider = await detectGitProvider();
if (!provider && detectedProvider) {
console.log(chalk.blue(`Auto-detected ${detectedProvider === 'gh' ? 'GitHub' : 'GitLab'} repository.`));
provider = detectedProvider;
} else if (detectedProvider && detectedProvider !== provider) {
console.log(chalk.yellow(`Detected ${detectedProvider === 'gh' ? 'GitHub' : 'GitLab'} repository, but using configured provider '${provider}'.`));
}
const isPR = provider === 'gh';
const requestType = isPR ? "PR" : "MR";
🤖 Prompt for AI Agents
In src/commands/pr.ts around lines 68 to 78, the code currently lets
auto-detection override an explicit config value, which contradicts the PR
description that config should take precedence; change the logic so the
configured provider is used when present and detection is only a fallback: only
assign detectedProvider to provider when provider is falsy/undefined and
detectedProvider exists, and adjust the console messages to only announce
detection when the config was absent and detection is used.

johnlindquist added a commit that referenced this pull request Dec 7, 2025
- Auto-detect git provider from remote URL (github.com or gitlab.com)
- Add config option: wt config set provider gh|glab
- Adapt pr command to work with both GitHub PRs and GitLab MRs
- Update error messages to be provider-specific (PR vs MR, GitHub vs GitLab)
- Add detectGitProvider function to git.ts
- Add getGitProvider/setGitProvider to config.ts

The tool automatically detects the provider from the remote URL.
Manual configuration is available as fallback via config command.

Backward compatible: defaults to gh (GitHub CLI) maintaining existing behavior.

Integrates PR #26 by @Cl3MM
@johnlindquist
Copy link
Owner

Superseded by PR #30 which integrates this feature along with other fixes. Thank you for your contribution @Cl3MM!

johnlindquist added a commit that referenced this pull request Dec 7, 2025
* fix: Address issues #27, #28, #29

- #27: Editor 'none' now skips opening editor instead of failing
  Added shouldSkipEditor() to config.ts and updated all command handlers
  to check for 'none' value before attempting to open editor

- #28: wt new without branch name now shows clear error
  Added validation at start of newWorktreeHandler to require branch name

- #29: build/index.js now has execute permissions after build
  Added postbuild script to package.json that runs chmod +x

Also updated .gitignore to exclude .beads/ and .vscode/ directories

* feat: Add GitLab support for pr command (PR #26)

- Auto-detect git provider from remote URL (github.com or gitlab.com)
- Add config option: wt config set provider gh|glab
- Adapt pr command to work with both GitHub PRs and GitLab MRs
- Update error messages to be provider-specific (PR vs MR, GitHub vs GitLab)
- Add detectGitProvider function to git.ts
- Add getGitProvider/setGitProvider to config.ts

The tool automatically detects the provider from the remote URL.
Manual configuration is available as fallback via config command.

Backward compatible: defaults to gh (GitHub CLI) maintaining existing behavior.

Integrates PR #26 by @Cl3MM

* feat: Add configurable default worktree directory (PR #24)

- Add defaultWorktreePath config option
- Add wt config set worktreepath <path> command
- Add wt config get worktreepath command
- Add wt config clear worktreepath command (reverts to sibling behavior)
- Update new, setup, pr, and extract commands to use configured path
- Support ~ expansion in path (expands to home directory)

Path resolution priority:
1. --path flag (highest priority)
2. defaultWorktreePath config setting
3. Sibling directory behavior (default fallback)

Example:
  wt config set worktreepath ~/worktrees
  wt new feature/login -> creates ~/worktrees/login

Integrates PR #24 by @trevortrinh

* docs: Add JSDoc comments to key functions (PR #23)

- Add detailed docstring to setupWorktreeHandler explaining parameters and behavior
- Add docstring to isMainRepoBare function
- Add docstring to getRepoRoot function

Integrates documentation improvements from PR #23 by @coderabbitai

* docs: Update README with new features

- Document GitLab support for wt pr command
- Document editor 'none' option to skip opening
- Document git provider configuration
- Document configurable default worktree directory
- Update requirements section

* feat: Add --setup flag to wt pr command (closes #25)

- Create shared runSetupScripts utility in src/utils/setup.ts
- Add -s, --setup option to pr command
- Execute setup scripts from worktrees.json or .cursor/worktrees.json
- Update README with --setup documentation

This allows users to run setup scripts when creating a worktree from a PR/MR:
  wt pr 123 --setup

* fix: Address CodeRabbit PR feedback

- Fix GitLab MR branch lookup: use -o json instead of -F json
- Add JSON parse error handling for clearer error messages
- Improve GitLab detection: parse hostname properly instead of includes()
- Fix magic string check: use hasWarnedAboutCheckout flag instead
- Remove redundant default: undefined from config schema
- Add HOME/USERPROFILE validation for tilde expansion
- Make postbuild script cross-platform (works on Windows)
- Add JSDoc documentation for detectGitProvider and getRemoteHostname
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants