Skip to content

Commit 32b6536

Browse files
committed
Improve git completions
* Fix previous logic assuming single remote name `origin` * Fix wrong Local vs Remote branch name descriptions * Fix git branch output with multiple branches per line breaking auto-completions * Fix current branch not showing up in git checkout branch completions * Fix remote branches not showing up when local branch with upstream exists despite them being different refs * Show more contextual information in completion descriptions * Use consistent sorting between command completions; Local branches before remote branches before files before commits, with sorting from git kept intact Change implementation to a more encapsulated and type-specific approach: * Use for-each-ref instead of git branch with manual current branch query and current and HEAD ref removal * Separate local and remote branch logic for good case separation * Use separate for-each-ref calls for early case separation * Decorate local and remote branch information in their respective cases instead of content-conditional * Decorate with 'Branch, Local|Remote, Commit Sha1 Subject, upstream [head|behind x] * Use new consistent description format '{Type}' | '{Type}, Commit {Sha1} Subject | '{Type}, {Subtype}, {Commit Sha1+Subject}, {upstream name + track} * Dissolves some spread out logic, dropping commands get-all-git-branches and extract-remote-branches-nonlocal-short and extract-mergable-sources Influenced completions: `git checkout`, `git switch`, `git merge`
1 parent 4d0f184 commit 32b6536

File tree

1 file changed

+29
-47
lines changed

1 file changed

+29
-47
lines changed

custom-completions/git/git-completions.nu

Lines changed: 29 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -81,52 +81,21 @@ module git-completion-utils {
8181
| each { split row ' ' -n 9 | last }
8282
}
8383

84-
export def get-all-git-branches []: nothing -> list<string> {
85-
^git branch -a --format '%(refname:lstrip=2)%09%(upstream:lstrip=2)' | lines | str trim | filter { not ($in ends-with 'HEAD' ) }
84+
export def get-all-git-local-refs []: nothing -> list<record<ref: string, obj: string, upstream: string, subject: string>> {
85+
^git for-each-ref --format '%(refname:lstrip=2)%09%(objectname:short)%09%(upstream:remotename)%(upstream:track)%09%(contents:subject)' refs/heads | lines | parse "{ref}\t{obj}\t{upstream}\t{subject}"
8686
}
8787

88-
# Extract remote branches which do not have local counterpart
89-
export def extract-remote-branches-nonlocal-short [current: string]: list<string> -> list<string> {
90-
# Input is a list of lines, like:
91-
# ╭────┬────────────────────────────────────────────────╮
92-
# │ 0 │ feature/awesome-1 origin/feature/awesome-1 │
93-
# │ 1 │ fix/bug-1 origin/fix/bug-1 │
94-
# │ 2 │ main origin/main │
95-
# │ 3 │ origin/HEAD │
96-
# │ 4 │ origin/feature/awesome-1 │
97-
# │ 5 │ origin/fix/bug-1 │
98-
# │ 6 │ origin/feature/awesome-2 │
99-
# │ 7 │ origin/main │
100-
# │ 8 │ upstream/main │
101-
# │ 9 │ upstream/awesome-3 │
102-
# ╰────┴────────────────────────────────────────────────╯
103-
# and we pick ['feature/awesome-2', 'awesome-3']
104-
let lines = $in
105-
let long_current = if ($current | is-empty) { '' } else { $'origin/($current)' }
106-
let branches = $lines | filter { ($in != $long_current) and not ($in starts-with $"($current)\t") }
107-
let tracked_remotes = $branches | find --no-highlight "\t" | each { split row "\t" -n 2 | get 1 }
108-
let floating_remotes = $lines | filter { "\t" not-in $in and $in not-in $tracked_remotes }
109-
$floating_remotes | each {
110-
let v = $in | split row -n 2 '/' | get 1
111-
if $v == $current { null } else $v
112-
}
113-
}
114-
115-
export def extract-mergable-sources [current: string]: list<string> -> list<record<value: string, description: string>> {
116-
let lines = $in
117-
let long_current = if ($current | is-empty) { '' } else { $'origin/($current)' }
118-
let branches = $lines | filter { ($in != $long_current) and not ($in starts-with $"($current)\t") }
119-
let git_table: list<record<n: string, u: string>> = $branches | each {|v| if "\t" in $v { $v | split row "\t" -n 2 | {n: $in.0, u: $in.1 } } else {n: $v, u: null } }
120-
let siblings = $git_table | where u == null and n starts-with 'origin/' | get n | str substring 7..
121-
let remote_branches = $git_table | filter {|r| $r.u == null and not ($r.n starts-with 'origin/') } | get n
122-
[...($siblings | wrap value | insert description Local), ...($remote_branches | wrap value | insert description Remote)]
88+
export def get-all-git-remote-refs []: nothing -> list<record<ref: string, obj: string, subject: string>> {
89+
^git for-each-ref --format '%(refname:lstrip=2)%09%(objectname:short)%09%(contents:subject)' refs/remotes | lines | parse "{ref}\t{obj}\t{subject}"
12390
}
12491

12592
# Get local branches, remote branches which can be passed to `git merge`
12693
export def get-mergable-sources []: nothing -> list<record<value: string, description: string>> {
127-
let current = (^git branch --show-current) # Can be empty if in detached HEAD
128-
(get-all-git-branches | extract-mergable-sources $current)
129-
}
94+
# expensive: (^git show --no-patch --format=%s $x.obj)
95+
let local = get-all-git-local-refs | each {|x| {value: $x.ref description: $'Branch, Local, ($x.obj) ($x.subject), (if ($x.upstream | is-not-empty) { $x.upstream } else { "no upstream" } )'} } | insert style 'light_blue'
96+
let remote = get-all-git-remote-refs | each {|x| {value: $x.ref description: $'Branch, Remote, ($x.obj) ($x.subject)'} } | insert style 'blue_italic'
97+
$local | append $remote
98+
}
13099
}
131100

132101
def "nu-complete git available upstream" [] {
@@ -165,15 +134,28 @@ def "nu-complete git remote branches with prefix" [] {
165134
# Yield local and remote branch names which can be passed to `git merge`
166135
def "nu-complete git mergable sources" [] {
167136
use git-completion-utils *
168-
(get-mergable-sources)
137+
let branches = get-mergable-sources
138+
{
139+
options: {
140+
case_sensitive: false,
141+
completion_algorithm: prefix,
142+
sort: false,
143+
},
144+
completions: $branches
145+
}
169146
}
170147

171148
def "nu-complete git switch" [] {
172149
use git-completion-utils *
173-
let current = (^git branch --show-current) # Can be empty if in detached HEAD
174-
let local_branches = ^git branch --format '%(refname:short)' | lines | filter { $in != $current } | wrap value | insert description 'Local branch'
175-
let remote_branches = (get-all-git-branches | extract-remote-branches-nonlocal-short $current) | wrap value | insert description 'Remote branch'
176-
[...$local_branches, ...$remote_branches]
150+
let branches = get-mergable-sources
151+
{
152+
options: {
153+
case_sensitive: false,
154+
completion_algorithm: prefix,
155+
sort: false,
156+
},
157+
completions: $branches
158+
}
177159
}
178160

179161
def "nu-complete git checkout" [context: string, position?:int] {
@@ -199,9 +181,9 @@ def "nu-complete git checkout" [context: string, position?:int] {
199181
}
200182
# The first argument can be local branches, remote branches, files and commits
201183
# Get local and remote branches
202-
let branches = (get-mergable-sources) | insert style {|row| if $row.description == 'Local' { 'blue' } else 'blue_italic' } | update description { $in + ' branch' }
184+
let branches = get-mergable-sources
203185
let files = (get-checkoutable-files) | wrap value | insert description 'File' | insert style green
204-
let commits = ^git rev-list -n 400 --remotes --oneline | lines | split column -n 2 ' ' value description | insert style light_cyan_dimmed
186+
let commits = ^git rev-list -n 400 --remotes --oneline | lines | split column -n 2 ' ' value description | upsert description {|x| $'Commit, ($x.value) ($x.description)' } | insert style 'light_cyan_dimmed'
205187
{
206188
options: {
207189
case_sensitive: false,

0 commit comments

Comments
 (0)