Skip to content

Commit 6856984

Browse files
timvwclaude
andcommitted
feat: add GitLab support with mr/pr commands
Add support for GitLab Merge Requests alongside GitHub Pull Requests: - Automatically detect remote type (GitHub vs GitLab) from git remote URL - Add 'mr' command as alias to 'pr' command - Support both 'gh' CLI (GitHub) and 'glab' CLI (GitLab) - Update URL parsing to handle both GitHub PR and GitLab MR URLs - Branch naming: 'pr-<number>' for GitHub, 'mr-<number>' for GitLab - Update shell completion (bash and zsh) to include 'mr' command - Update README with GitLab examples and glab requirement Both commands work intelligently based on the detected remote: - wt pr 123 - Works for GitHub PRs or GitLab MRs - wt mr 123 - Same as above (alias) - wt pr https://github.com/org/repo/pull/123 - wt pr https://gitlab.com/org/repo/-/merge_requests/123 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 46920bd commit 6856984

2 files changed

Lines changed: 87 additions & 21 deletions

File tree

README.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ Inspired by [haacked/dotfiles/tree-me](https://github.com/haacked/dotfiles/blob/
1414

1515
- Organized worktree structure: `~/dev/worktrees/<repo>/<branch>`
1616
- Simple commands for common worktree operations
17-
- GitHub PR checkout support (via `gh` CLI)
17+
- GitHub PR and GitLab MR checkout support (via `gh` or `glab` CLI)
18+
- Automatic platform detection based on git remote
1819
- Shell integration with auto-cd functionality
1920
- Tab completion for Bash and Zsh
2021

@@ -75,9 +76,12 @@ wt co feature-branch # short alias
7576
wt create my-feature
7677
wt create my-feature develop # specify base branch
7778

78-
# Checkout GitHub PR in worktree (requires gh CLI)
79-
wt pr 123
80-
wt pr https://github.com/org/repo/pull/123
79+
# Checkout GitHub PR or GitLab MR in worktree
80+
# Automatically detects platform from git remote
81+
wt pr 123 # GitHub PR or GitLab MR
82+
wt mr 123 # Same as above (alias)
83+
wt pr https://github.com/org/repo/pull/123 # GitHub PR URL
84+
wt pr https://gitlab.com/org/repo/-/merge_requests/123 # GitLab MR URL
8185

8286
# List all worktrees
8387
wt list
@@ -110,8 +114,9 @@ wt create add-auth-feature
110114
# Checkout an existing branch
111115
wt checkout bugfix-login
112116

113-
# Work on a PR
117+
# Work on a GitHub PR or GitLab MR
114118
wt pr 456
119+
wt mr 789 # Same as 'wt pr 789'
115120

116121
# List all your worktrees
117122
wt list
@@ -150,7 +155,8 @@ just build-all # Cross-compile for multiple platforms
150155
## Requirements
151156

152157
- Git (obviously)
153-
- `gh` CLI (optional, only needed for `wt pr` command)
158+
- `gh` CLI (optional, only needed for GitHub PRs via `wt pr` command)
159+
- `glab` CLI (optional, only needed for GitLab MRs via `wt pr`/`wt mr` commands)
154160

155161
### For Building from Source
156162

main.go

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,42 @@ func getDefaultBase() string {
8686
return strings.TrimPrefix(ref, "refs/remotes/origin/")
8787
}
8888

89+
type RemoteType int
90+
91+
const (
92+
RemoteGitHub RemoteType = iota
93+
RemoteGitLab
94+
RemoteUnknown
95+
)
96+
97+
func getRemoteType() RemoteType {
98+
cmd := exec.Command("git", "remote", "get-url", "origin")
99+
output, err := cmd.Output()
100+
if err != nil {
101+
return RemoteUnknown
102+
}
103+
104+
url := strings.TrimSpace(string(output))
105+
if strings.Contains(url, "github.com") {
106+
return RemoteGitHub
107+
}
108+
if strings.Contains(url, "gitlab.com") || strings.Contains(url, "gitlab") {
109+
return RemoteGitLab
110+
}
111+
112+
return RemoteUnknown
113+
}
114+
89115
func getPRNumber(input string) (string, error) {
90116
// Check if it's a GitHub PR URL
91-
urlRegex := regexp.MustCompile(`^https://github\.com/.*/pull/([0-9]+)`)
92-
if matches := urlRegex.FindStringSubmatch(input); matches != nil {
117+
githubRegex := regexp.MustCompile(`^https://github\.com/.*/pull/([0-9]+)`)
118+
if matches := githubRegex.FindStringSubmatch(input); matches != nil {
119+
return matches[1], nil
120+
}
121+
122+
// Check if it's a GitLab MR URL
123+
gitlabRegex := regexp.MustCompile(`^https://gitlab\.com/.*/-/merge_requests/([0-9]+)`)
124+
if matches := gitlabRegex.FindStringSubmatch(input); matches != nil {
93125
return matches[1], nil
94126
}
95127

@@ -99,7 +131,7 @@ func getPRNumber(input string) (string, error) {
99131
return input, nil
100132
}
101133

102-
return "", fmt.Errorf("invalid PR number or URL: %s", input)
134+
return "", fmt.Errorf("invalid PR/MR number or URL: %s", input)
103135
}
104136

105137
func worktreeExists(branch string) (string, bool) {
@@ -221,27 +253,54 @@ var createCmd = &cobra.Command{
221253
}
222254

223255
var prCmd = &cobra.Command{
224-
Use: "pr <number|url>",
225-
Short: "Checkout GitHub PR in worktree (uses gh)",
226-
Args: cobra.ExactArgs(1),
256+
Use: "pr <number|url>",
257+
Aliases: []string{"mr"},
258+
Short: "Checkout PR/MR in worktree (uses gh for GitHub, glab for GitLab)",
259+
Long: `Checkout a Pull Request (GitHub) or Merge Request (GitLab) in a worktree.
260+
261+
Automatically detects whether you're using GitHub or GitLab based on
262+
the git remote URL and uses the appropriate CLI tool (gh or glab).
263+
264+
Examples:
265+
wt pr 123 # PR/MR number
266+
wt pr https://github.com/org/repo/pull/123 # GitHub PR URL
267+
wt pr https://gitlab.com/org/repo/-/merge_requests/123 # GitLab MR URL
268+
wt mr 123 # Same as 'wt pr 123'`,
269+
Args: cobra.ExactArgs(1),
227270
RunE: func(cmd *cobra.Command, args []string) error {
228271
input := args[0]
229272
prNumber, err := getPRNumber(input)
230273
if err != nil {
231274
return err
232275
}
233276

234-
// Check if gh is installed
235-
if _, err := exec.LookPath("gh"); err != nil {
236-
return fmt.Errorf("'gh' CLI not found. Install it from https://cli.github.com")
277+
// Detect remote type
278+
remoteType := getRemoteType()
279+
var refSpec, prefix string
280+
281+
switch remoteType {
282+
case RemoteGitHub:
283+
refSpec = fmt.Sprintf("pull/%s/head", prNumber)
284+
prefix = "pr"
285+
if _, err := exec.LookPath("gh"); err != nil {
286+
return fmt.Errorf("'gh' CLI not found. Install it from https://cli.github.com")
287+
}
288+
case RemoteGitLab:
289+
refSpec = fmt.Sprintf("merge-requests/%s/head", prNumber)
290+
prefix = "mr"
291+
if _, err := exec.LookPath("glab"); err != nil {
292+
return fmt.Errorf("'glab' CLI not found. Install it from https://gitlab.com/gitlab-org/cli")
293+
}
294+
default:
295+
return fmt.Errorf("unable to detect remote type (GitHub or GitLab)")
237296
}
238297

239298
repo, err := getRepoName()
240299
if err != nil {
241300
return err
242301
}
243302

244-
branch := fmt.Sprintf("pr-%s", prNumber)
303+
branch := fmt.Sprintf("%s-%s", prefix, prNumber)
245304
path := filepath.Join(worktreeRoot, repo, branch)
246305

247306
// Check if worktree already exists
@@ -251,8 +310,8 @@ var prCmd = &cobra.Command{
251310
return nil
252311
}
253312

254-
// Fetch the PR
255-
fetchCmd := exec.Command("git", "fetch", "origin", fmt.Sprintf("pull/%s/head:%s", prNumber, branch))
313+
// Fetch the PR/MR
314+
fetchCmd := exec.Command("git", "fetch", "origin", fmt.Sprintf("%s:%s", refSpec, branch))
256315
fetchCmd.Stderr = os.Stderr
257316
_ = fetchCmd.Run() // Ignore errors, branch might already exist
258317

@@ -264,7 +323,7 @@ var prCmd = &cobra.Command{
264323
return fmt.Errorf("failed to create worktree: %w", err)
265324
}
266325

267-
fmt.Printf("✓ PR #%s checked out at: %s\n", prNumber, path)
326+
fmt.Printf("✓ %s #%s checked out at: %s\n", strings.ToUpper(prefix), prNumber, path)
268327
printCDMarker(path)
269328
return nil
270329
},
@@ -351,7 +410,7 @@ if [ -n "$BASH_VERSION" ]; then
351410
COMPREPLY=()
352411
cur="${COMP_WORDS[COMP_CWORD]}"
353412
prev="${COMP_WORDS[COMP_CWORD-1]}"
354-
commands="checkout co create pr list ls remove rm prune help shellenv"
413+
commands="checkout co create pr mr list ls remove rm prune help shellenv"
355414
356415
# Complete commands if first argument
357416
if [ $COMP_CWORD -eq 1 ]; then
@@ -380,7 +439,8 @@ if [ -n "$ZSH_VERSION" ]; then
380439
'checkout:Checkout existing branch in new worktree'
381440
'co:Checkout existing branch in new worktree'
382441
'create:Create new branch in worktree'
383-
'pr:Checkout GitHub PR in worktree'
442+
'pr:Checkout PR/MR in worktree'
443+
'mr:Checkout PR/MR in worktree'
384444
'list:List all worktrees'
385445
'ls:List all worktrees'
386446
'remove:Remove a worktree'

0 commit comments

Comments
 (0)