gitbash 1.9.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/commands/pr.sh CHANGED
@@ -1,11 +1,83 @@
1
- ## pr opens the current pr, if in repo
2
- # shellcheck shell=bash
1
+ #!/usr/bin/env bash
2
+
3
+ # Source common utilities
4
+ SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ # shellcheck source=_utils.sh
6
+ source "$SOURCE_DIR/_utils.sh"
7
+
8
+ # Convert a remote URL to the repository's web URL (credentials and .git removed).
9
+ # Handles https://[user[:token]@]host/path, ssh://[user@]host[:port]/path and user@host:path.
10
+ _pr_web_base() {
11
+ local url="$1" scheme="https" rest host path
12
+ case "$url" in
13
+ http://*|https://*)
14
+ scheme="${url%%://*}"
15
+ rest="${url#*://}"
16
+ host="${rest%%/*}"
17
+ host="${host##*@}"
18
+ path="${rest#*/}"
19
+ ;;
20
+ ssh://*)
21
+ rest="${url#ssh://}"
22
+ host="${rest%%/*}"
23
+ host="${host##*@}"
24
+ host="${host%%:*}"
25
+ path="${rest#*/}"
26
+ ;;
27
+ *@*:*)
28
+ host="${url%%:*}"
29
+ host="${host##*@}"
30
+ path="${url#*:}"
31
+ ;;
32
+ *)
33
+ return 1
34
+ ;;
35
+ esac
36
+ path="${path%/}"
37
+ path="${path%.git}"
38
+ if [[ -z "$host" || -z "$path" || ( -n "$rest" && "$path" == "$rest" ) ]]; then
39
+ return 1
40
+ fi
41
+
42
+ # Azure DevOps SSH: ssh.dev.azure.com:v3/org/project/repo
43
+ if [[ "$host" == "ssh.dev.azure.com" && "$path" == v3/*/*/* ]]; then
44
+ local org project repo
45
+ path="${path#v3/}"
46
+ org="${path%%/*}"; path="${path#*/}"
47
+ project="${path%%/*}"; repo="${path#*/}"
48
+ echo "https://dev.azure.com/$org/$project/_git/$repo"
49
+ return 0
50
+ fi
51
+ echo "$scheme://$host/$path"
52
+ }
53
+
54
+ # Web URL for creating a pull/merge request for a branch
55
+ _pr_create_url() {
56
+ local base="$1" branch="$2" encoded
57
+ encoded=$(gb_urlencode "$branch")
58
+ case "$base" in
59
+ *gitlab*)
60
+ echo "$base/-/merge_requests/new?merge_request%5Bsource_branch%5D=$encoded"
61
+ ;;
62
+ *bitbucket.org/*)
63
+ echo "$base/pull-requests/new?source=$encoded"
64
+ ;;
65
+ *dev.azure.com/*|*visualstudio.com/*)
66
+ echo "$base/pullrequestcreate?sourceRef=$encoded"
67
+ ;;
68
+ *)
69
+ echo "$base/compare/$encoded?expand=1"
70
+ ;;
71
+ esac
72
+ }
73
+
74
+ # Open the current branch's pull request (or the page to create one)
3
75
  pr() {
4
76
  local should_push=false
77
+ local print_only=false
5
78
 
6
- # Parse arguments
7
79
  while [[ $# -gt 0 ]]; do
8
- case $1 in
80
+ case "$1" in
9
81
  -v|--version)
10
82
  echo "gitbash ${FUNCNAME[0]} v$VERSION"
11
83
  return 0
@@ -15,36 +87,30 @@ pr() {
15
87
  Usage: pr [OPTIONS]
16
88
 
17
89
  Open the current branch's pull request in your browser.
18
- If no PR exists, opens GitHub's compare page to create one.
90
+ If no pull request exists yet, opens the page to create one.
19
91
 
20
92
  Options:
21
- -p, --push Push current branch to origin before opening PR
93
+ -p, --push Push the current branch before opening
94
+ --print Print the URL instead of opening it
95
+ -y, --yes Don't ask for confirmations
22
96
  -h, --help Show this help message
23
97
 
24
98
  Behavior:
25
- - Must be run inside a git repository
26
- - Uses the 'origin' remote URL
27
- - Opens a compare URL for the current branch
28
- - Automatically converts SSH URLs to HTTPS
99
+ - Uncommitted changes: offers to commit them first (via 'commit')
100
+ - Branch not on the remote yet: offers to push it
101
+ - With the GitHub CLI (gh) installed and logged in, an existing PR is opened directly
102
+ - Otherwise opens the create page for the hosting service:
103
+ GitHub / GitHub Enterprise <repo>/compare/<branch>?expand=1
104
+ GitLab <repo>/-/merge_requests/new?...
105
+ Bitbucket <repo>/pull-requests/new?source=<branch>
106
+ Azure DevOps <repo>/pullrequestcreate?sourceRef=<branch>
107
+ - Works with SSH and HTTPS remotes; credentials in the remote URL are never used
108
+ - Opens the browser with 'open' (macOS) or 'xdg-open' (Linux), otherwise prints the URL
29
109
 
30
110
  Examples:
31
111
  $ pr
32
- # Opens: https://github.com/user/repo/compare/feature-branch?expand=1
33
-
34
- $ git checkout feature/helix/LOVE-123-fix-bug
35
- $ pr
36
- # Opens: https://github.com/user/repo/compare/feature/helix/LOVE-123-fix-bug?expand=1
37
-
38
- # Push before opening PR
39
112
  $ pr -p
40
- Pushing 'feature-branch' to origin...
41
- ✓ Successfully pushed 'feature-branch' to origin.
42
- # Opens: https://github.com/user/repo/compare/feature-branch?expand=1
43
-
44
- Notes:
45
- - Works with both SSH and HTTPS remote URLs
46
- - Opens in your default browser using the 'open' command
47
- - Branch name is automatically URL-encoded by the browser
113
+ $ pr --print
48
114
 
49
115
  EOF
50
116
  return 0
@@ -53,146 +119,92 @@ EOF
53
119
  should_push=true
54
120
  shift
55
121
  ;;
56
- -*)
57
- echo "Unknown option: $1"
58
- echo "Usage: pr [-p|--push]"
59
- return 1
122
+ --print)
123
+ print_only=true
124
+ shift
125
+ ;;
126
+ -y|--yes)
127
+ GITBASH_ASSUME_YES=1
128
+ shift
60
129
  ;;
61
130
  *)
62
- echo "Unknown argument: $1"
63
- echo "Usage: pr [-p|--push]"
131
+ print_error "Unknown argument: $1"
132
+ echo "Usage: pr [-p|--push] [--print] [-y|--yes]" >&2
64
133
  return 1
65
134
  ;;
66
135
  esac
67
136
  done
68
137
 
69
- # Ensure we're inside a Git repo
70
- if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
71
- echo "Not inside a git repository."
138
+ require_git_repo || return 1
139
+
140
+ local remote remote_url branch base
141
+ remote=$(gb_remote)
142
+ if ! remote_url=$(git config --get "remote.$remote.url"); then
143
+ print_error "No remote '$remote' found."
72
144
  return 1
73
145
  fi
74
-
75
- # Check for uncommitted changes or untracked files
76
- local has_changes=false
77
- if ! git diff-index --quiet HEAD -- 2>/dev/null || [[ -n "$(git ls-files --others --exclude-standard)" ]]; then
78
- has_changes=true
146
+ if ! branch=$(gb_current_branch); then
147
+ print_error "Detached HEAD: check out a branch first."
148
+ return 1
149
+ fi
150
+ if ! base=$(_pr_web_base "$remote_url"); then
151
+ print_error "Don't know how to open '$remote_url' in a browser."
152
+ return 1
79
153
  fi
80
-
81
- if [[ "$has_changes" == true ]]; then
82
- echo "You have uncommitted changes or untracked files."
83
- echo ""
84
-
85
- # Source _utils.sh for prompt_read (status may use prompts)
86
- if ! declare -f prompt_read >/dev/null 2>&1; then
87
- SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
88
- source "$SOURCE_DIR/_utils.sh"
89
- fi
90
154
 
91
- local commit_answer
92
- # If -p/--push was provided, assume the user wants to commit (don't ask)
93
- if [[ "$should_push" == true ]]; then
94
- commit_answer="y"
155
+ # -----------------------------
156
+ # 1. Uncommitted changes
157
+ # -----------------------------
158
+ if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then
159
+ echo "You have uncommitted changes or untracked files."
160
+ if [[ "$should_push" == true ]] || gb_confirm "Commit them first?" y; then
161
+ local before after
162
+ before=$(git rev-parse --verify --quiet HEAD || true)
163
+ if ! gb_run commit; then
164
+ print_error "Commit cancelled or failed. Not opening the pull request."
165
+ return 1
166
+ fi
167
+ after=$(git rev-parse --verify --quiet HEAD || true)
168
+ if [[ "$after" != "$before" && "$should_push" == false ]]; then
169
+ gb_confirm "Push the new commit to '$remote'?" y && should_push=true
170
+ fi
95
171
  else
96
- prompt_read "Would you like to commit them first? (Y/n): " commit_answer
172
+ echo "Continuing without committing..."
97
173
  fi
98
-
99
- case "$commit_answer" in
100
- [nN][oO]|[nN])
101
- echo "Continuing without committing..."
102
- ;;
103
- *)
104
- # Run status command for interactive staging and committing
105
- local local_before
106
- local local_after
107
- local_before=$(git rev-parse --verify HEAD 2>/dev/null || true)
108
- if ! gitbash status; then
109
- echo "Commit cancelled or failed. Aborting PR creation."
110
- return 1
111
- fi
112
- local_after=$(git rev-parse --verify HEAD 2>/dev/null || true)
113
-
114
- # Only offer to push if a new commit was actually created
115
- if [[ "$local_after" != "$local_before" ]]; then
116
- # If -p wasn't specified, offer to push the fresh commit
117
- if [[ "$should_push" != true ]]; then
118
- local push_answer
119
- echo ""
120
- prompt_read "Push the fresh commit to origin? (Y/n): " push_answer
121
- case "$push_answer" in
122
- [nN][oO]|[nN])
123
- echo "Continuing without pushing..."
124
- ;;
125
- *)
126
- should_push=true
127
- ;;
128
- esac
129
- fi
130
- else
131
- echo "No new commit was created. Continuing without pushing..."
132
- fi
133
- ;;
134
- esac
135
- echo ""
136
174
  fi
137
175
 
138
- # Remote URL (e.g. git@github.com:user/repo.git or https://github.com/user/repo.git)
139
- remote=$(git config --get remote.origin.url)
140
-
141
- if [[ -z "$remote" ]]; then
142
- echo "No remote 'origin' found."
143
- return 1
176
+ # -----------------------------
177
+ # 2. Make sure the branch exists on the remote
178
+ # -----------------------------
179
+ if [[ "$should_push" == false ]]; then
180
+ local on_remote
181
+ on_remote=$(git ls-remote --heads "$remote" "refs/heads/$branch" 2>/dev/null || true)
182
+ if [[ -z "$on_remote" ]]; then
183
+ print_warning "Branch '$branch' is not on '$remote' yet."
184
+ gb_confirm "Push it now?" y && should_push=true
185
+ fi
144
186
  fi
145
187
 
146
- # Convert SSH URLs to HTTPS URLs
147
- remote_https=$remote
148
- remote_https=${remote_https/git@github.com:/https://github.com/}
149
- remote_https=${remote_https%.git}
150
-
151
- # Current branch
152
- branch=$(git rev-parse --abbrev-ref HEAD)
153
-
154
- # Push if requested
155
188
  if [[ "$should_push" == true ]]; then
156
- # Check if remote has updates and pull first
157
- git fetch origin "$branch" 2>/dev/null
158
- local local_commit
159
- local remote_commit
160
- local_commit=$(git rev-parse HEAD)
161
- remote_commit=$(git rev-parse "origin/$branch" 2>/dev/null)
162
-
163
- if [[ -n "$remote_commit" && "$local_commit" != "$remote_commit" ]]; then
164
- # Check if we're behind the remote
165
- if git merge-base --is-ancestor "$local_commit" "$remote_commit" 2>/dev/null; then
166
- echo "Remote has updates. Pulling first..."
167
- if ! git pull --rebase origin "$branch"; then
168
- echo "⚠ Failed to pull remote changes. Please resolve conflicts and try again."
169
- return 1
170
- fi
171
- echo "✓ Pulled latest changes."
172
- elif ! git merge-base --is-ancestor "$remote_commit" "$local_commit" 2>/dev/null; then
173
- # Branches have diverged
174
- echo "Remote has diverged. Pulling with rebase..."
175
- git pull --rebase origin "$branch"
176
- if ! git pull --rebase origin "$branch"; then
177
- echo "⚠ Failed to pull remote changes. Please resolve conflicts and try again."
178
- return 1
179
- fi
180
- echo "✓ Rebased on latest changes."
181
- fi
182
- fi
183
-
184
- echo "Pushing '$branch' to origin..."
185
- if git push origin "$branch"; then
186
- echo "✓ Successfully pushed '$branch' to origin."
187
- else
188
- echo "⚠ Failed to push '$branch' to origin."
189
- return 1
190
- fi
189
+ gb_sync_and_push || return 1
191
190
  fi
192
191
 
193
- # Construct compare URL
194
- url="$remote_https/compare/$branch?expand=1"
192
+ # -----------------------------
193
+ # 3. Open existing PR with gh, else the create page
194
+ # -----------------------------
195
+ if [[ "$print_only" == false && "$base" != *gitlab* && "$base" != *bitbucket.org/* ]] &&
196
+ command -v gh >/dev/null 2>&1; then
197
+ if GH_PROMPT_DISABLED=1 gh pr view "$branch" --web >/dev/null 2>&1; then
198
+ print_success "Opened the pull request for '$branch'."
199
+ return 0
200
+ fi
201
+ fi
195
202
 
196
- # Open in browser
197
- open "$url"
203
+ local url
204
+ url=$(_pr_create_url "$base" "$branch")
205
+ if [[ "$print_only" == true ]]; then
206
+ echo "$url"
207
+ else
208
+ gb_open_url "$url"
209
+ fi
198
210
  }