You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

766 lines
20 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

use argx.nu
def agree [
prompt
--default-not (-n)
] {
let prompt = if ($prompt | str ends-with '!') {
$'(ansi red)($prompt)(ansi reset)'
} else {
$'($prompt)'
}
(if $default_not { [no yes] } else { [yes no] } | input list $prompt) == 'yes'
}
def tips [ msg ] {
print -e $"(ansi light_gray)($msg)(ansi reset)"
}
def --wrapped with-flag [...flag] {
if ($in | is-empty) { [] } else { [...$flag $in] }
}
export def `git yeet` [] {
git add .
git commit -m (http get https://whatthecommit.com/index.txt)
git push
}
export alias gy = git yeet
export alias gtr = git log --graph --decorate --oneline
# git status
export def gs [] {
git status
}
# git stash
export def gst [
--apply (-a)
--clear (-c)
--drop (-d)
--list (-l)
--pop (-p)
--show (-s)
--all (-A)
--include-untracked (-i)
] {
if $apply {
git stash apply
} else if $clear {
git stash clear
} else if $drop {
git stash drop
} else if $list {
git stash list
} else if $pop {
git stash pop
} else if $show {
git stash show --text
} else if $all {
git stash --all ...(if $include_untracked {[--include-untracked]} else {[]})
} else {
git stash
}
}
# git log
export def gl [
commit?: string@"nu-complete git log"
--verbose(-v)
--num(-n):int=32
] {
if ($commit|is-empty) {
_git_log $verbose $num
} else {
git log --stat -p -n 1 $commit
}
}
# git branch
export def gb [
branch?: string@"nu-complete git branches"
--remote (-r)='origin': string@"nu-complete git remotes"
--delete (-d)
--no-merged (-n)
] {
let bs = git branch | lines | each {|x| $x | str substring 2..}
if $delete {
let remote_branches = (remote_branches)
if ($branch | is-empty) {
let dels = if $no_merged { gb } else {
gb
| where { $in.merged | is-not-empty }
}
| where { ($in.remote | is-empty) and ($in.current | is-empty) }
| each {|x|
let pf = if ($x.current | is-empty) { " " } else { $"(ansi cyan)* " }
let nm = if ($x.merged | is-not-empty ) { $"(ansi green)☑ " } else { " " }
$x | insert display $"($nm)($pf)(ansi reset)($x.branch)"
}
if ($dels | is-empty) {
tips "no branches to delete"
return
}
let $dels = $dels
| input list -d display --multi
| get branch
for b in $dels {
tips $"delete (ansi yellow)($b)"
git branch -D $b
}
if ($dels | is-not-empty) and (agree 'delete remote branch?!') {
for b in ($dels | filter { $"($remote)/($in)" in $remote_branches }) {
tips $"delete (ansi yellow)($remote)/($b)"
git branch -D -r $'($remote)/($b)'
git push $remote -d $b
}
}
} else {
if $branch in $bs and (agree 'branch will be delete!') {
git branch -D $branch
}
if $"($remote)/($branch)" in $remote_branches and (agree 'delete remote branch?!') {
git branch -D -r $'($remote)/($branch)'
git push $remote -d $branch
}
}
} else if ($branch | is-empty) {
let merged = git branch --merged
| lines
| each { $in | parse -r '\s*\*?\s*(?<b>[^\s]+)' | get 0.b }
{
local: (git branch)
remote: (git branch --remote)
}
| transpose k v
| each {|x|
$x.v | lines
| each {|n|
let n = $n | parse -r '\s*(?<c>\*)?\s*(?<b>[^\s]+)( -> )?(?<r>[^\s]+)?' | get 0
let c = if ($n.c | is-empty) { null } else { true }
let r = if ($n.r | is-empty) { null } else { $n.r }
let m = if $n.b in $merged { true } else { null }
let rm = if $x.k == 'remote' { true } else { null }
{ current: $c, remote: $rm, branch: $n.b, ref: $r, merged: $m }
}
}
| flatten
} else if $branch in $bs {
git checkout $branch
} else {
if (agree 'create new branch?') {
git checkout -b $branch
}
}
}
# git clone, init
export def --env gn [
repo?: string@"nu-complete git branches"
local?: path
--submodule (-s) # git submodule
--init (-i) # git init
] {
if $init {
if ($repo | is-empty) {
git init --initial-branch main
} else {
git init $repo --initial-branch main
cd $repo
}
if $submodule {
git submodule init
}
} else {
let local = if ($local | is-empty) {
$repo | path basename | split row '.' | get 0
} else {
$local
}
git clone ...(if $submodule {[--recurse-submodules]} else {[]}) $repo $local
cd $local
}
}
# edit .gitignore
export def gig [--empty-dir] {
if $empty_dir {
[
'# Ignore everything in this directory'
'*'
'# Except this file'
'!.gitignore'
] | str join (char newline) | save .gitignore
} else {
^$env.EDITOR $"(git rev-parse --show-toplevel)/.gitignore"
}
}
# git pull, push and switch
export def gp [
branch?: string@"nu-complete git branches"
--remote (-r)='origin': string@"nu-complete git remotes"
--force (-f) # git push -f
--override
--submodule (-s) # git submodule
--init (-i) # git init
--rebase (-r) # git pull (no)--rebase
--autostash (-a) # git pull --autostash
--back-to-prev (-b) # back to branch
] {
if $submodule {
git submodule update
} else if $override {
git pull --rebase
git add --all
git commit -v -a --no-edit --amend
git push --force
} else {
let m = if $rebase { [--rebase] } else { [] }
let a = if $autostash {[--autostash]} else {[]}
let branch = if ($branch | is-empty) { (_git_status).branch } else { $branch }
let branch_repr = $'(ansi yellow)($branch)(ansi light_gray)'
let lbs = git branch | lines | each { $in | str substring 2..}
let rbs = (remote_branches)
let prev = (_git_status).branch
if $"($remote)/($branch)" in $rbs {
if $branch in $lbs {
let bmsg = $'both local and remote have ($branch_repr) branch'
if $force {
tips $'($bmsg), with `--force`, push'
git branch -u $'($remote)/($branch)' $branch
git push --force
} else {
tips $'($bmsg), pull'
if $prev != $branch {
tips $'switch to ($branch_repr)'
git checkout $branch
}
git pull ...$m ...$a
}
} else {
tips $"local doesn't have ($branch_repr) branch, fetch"
git checkout -b $branch
git fetch $remote $branch
git branch -u $'($remote)/($branch)' $branch
git pull ...$m ...$a -v
}
} else {
let bmsg = $"remote doesn't have ($branch_repr) branch"
let force = if $force {[--force]} else {[]}
if $branch in $lbs {
tips $'($bmsg), set upstream and push'
git checkout $branch
} else {
tips $'($bmsg), create and push'
git checkout -b $branch
}
git push ...$force --set-upstream $remote $branch
}
if $back_to_prev {
git checkout $prev
}
let s = (_git_status)
if $s.ahead > 0 {
tips 'remote is behind, push'
git push
}
}
}
# git add, rm and restore
export def ga [
file?: path
--all (-A)
--patch (-p)
--update (-u)
--verbose (-v)
--delete (-d) # git rm
--cached (-c)
--force (-f)
--restore (-r) # git restore
--staged (-s)
--source (-o): string
] {
if $delete {
let c = if $cached {[--cached]} else {[]}
let f = if $force {[--force]} else {[]}
git rm ...$c ...$f -r $file
} else if $restore {
let o = $source | with-flag --source
let s = if $staged {[--staged]} else {[]}
let file = if ($file | is-empty) { [.] } else { [$file] }
git restore ...$o ...$s ...$file
} else {
let a = if $all {[--all]} else {[]}
let p = if $patch {[--patch]} else {[]}
let u = if $update {[--update]} else {[]}
let v = if $verbose {[--verbose]} else {[]}
let f = if $force {[--force]} else {[]}
let file = if ($file | is-empty) { [.] } else { [$file] }
git add ...([$a $p $u $v $f $file] | flatten)
}
}
# git commit
export def gc [
message?: string
--all (-A)
--amend (-a)
--keep (-k)
--no-promt (-n)
] {
let a = if $all {[--all]} else {[]}
let n = if $amend {[--amend]} else {[]}
let k = if $keep {[--no-edit]} else {[]}
mut e = []
let previous = git_previous_message
mut message = $message
if $previous != null {
print $"Previous message:\n ($previous)"
if (input -n 1 "Use previous message? [y/N]: " | str downcase) == "y" {
$message = $previous
$e = ["-e"]
}
}
if $message == null and (glob "*commitlint*" | length) > 0 and not $no_promt {
let type = ([ci chore feat fix docs test refactor] | input list -f "Change type")
let subject = input "Subject: "
let message = input "Message: "
if $message == null or ($message | str length) == 0 {
error make {msg: "The commit message can't be empty"}
}
let message = if $subject != null and ($subject | str length) > 0 {
$"# ($type)\(($subject)\): ($message)"
} else {
$"# ($type): ($message)"
}
print $"(ansi {attr: i})($message)(ansi reset)"
let m = $message | with-flag -m
git commit -v -e ...$m ...$a ...$n ...$k
} else {
let m = $message | with-flag -m
git commit -v ...$m ...$a ...$n ...$k ...$e
}
}
# git diff
export def gd [
file?: path
--cached (-c) # cached
--word-diff (-w) # word-diff
--staged (-s) # staged
] {
let w = if $word_diff {[--word-diff]} else {[]}
let c = if $cached {[--cached]} else {[]}
let s = if $staged {[--staged]} else {[]}
git diff ...$c ...$s ...$w ...($file | with-flag)
}
# git merge
export def gm [
branch?: string@"nu-complete git branches"
--abort (-a)
--continue (-c)
--quit (-q)
--no-squash (-n) # git merge (no)--squash
] {
let x = if $no_squash { [] } else { [--squash] }
if ($branch | is-empty) {
git merge ...$x $"origin/(git_main_branch)"
} else {
git merge ...$x $branch
}
if not $no_squash {
git commit -v
}
}
# git rebase
# TODO: --onto: (commit_id)
export def gr [
branch?: string@"nu-complete git branches"
--interactive (-i)
--onto (-o): string
--abort (-a)
--continue (-c)
--skip (-s)
--quit (-q)
] {
if $abort {
git rebase --abort
} else if $continue {
git rebase --continue
} else if $skip {
git rebase --skip
} else if $quit {
git rebase --quit
} else if ($onto | is-not-empty) {
git rebase --onto $branch
} else {
let i = if $interactive {[--interactive]} else {[]}
if ($branch | is-empty) {
git rebase ...$i (git_main_branch)
} else {
git rebase ...$i $branch
}
}
}
# git cherry-pick
export def gcp [
commit?: string@"nu-complete git log all"
--abort (-a)
--continue (-c)
--skip (-s)
--quit (-q)
] {
if $abort {
git cherry-pick --abort
} else if $continue {
git cherry-pick --continue
} else if $skip {
git cherry-pick --skip
} else if $quit {
git cherry-pick --quit
} else {
git cherry-pick $commit
}
}
# copy file from other branch
export def gcf [
branch: string@"nu-complete git branches"
...file: string@"nu-complete git branch files"
] {
^git checkout $branch $file
}
# git reset
export def grs [
commit?: string@"nu-complete git log"
--hard (-h)
--clean (-c)
] {
let h = if $hard {[--hard]} else {[]}
let cm = $commit | with-flag
git reset ...$h ...$cm
if $clean {
git clean -fd
}
}
# git remote
export def grm [
remote?: string@"nu-complete git remotes"
uri?: string
--add (-a)
--rename (-r)
--delete (-d)
--update (-u)
--set (-s)
] {
if ($remote | is-empty) {
git remote -v
} else if $add {
git remote add $remote $uri
} else if $set {
git remote set-url $remote $uri
} else if $rename {
let old = $remote
let new = $uri
git remote rename $old $new
} else if $delete {
git remote remove $remote
} else if $update {
git remote update $remote
} else {
git remote show $remote
}
}
# git bisect
export def gbs [
--bad (-b)
--good (-g)
--reset (-r)
--start (-s)
] {
if $good {
git bisect good
} else if $bad {
git bisect bad
} else if $reset {
git bisect reset
} else if $start {
git bisect start
} else {
git bisect
}
}
export def gha [] {
git log --pretty=%h»¦«%aN»¦«%s»¦«%aD
| lines
| split column "»¦«" sha1 committer desc merged_at
| histogram committer merger
| sort-by merger
| reverse
}
export def ggc [] {
git reflog expire --all --expire=now
git gc --prune=now --aggressive
}
export alias gcl = git config --list
export alias gsw = git switch
export alias gswc = git switch -c
export alias gts = git tag -s
export def _git_status [] {
# TODO: show-stash
let raw_status = do -i { git --no-optional-locks status --porcelain=2 --branch | lines }
let stashes = do -i { git stash list | lines | length }
mut status = {
idx_added_staged : 0
idx_modified_staged : 0
idx_deleted_staged : 0
idx_renamed : 0
idx_type_changed : 0
wt_untracked : 0
wt_modified : 0
wt_deleted : 0
wt_type_changed : 0
wt_renamed : 0
ignored : 0
conflicts : 0
ahead : 0
behind : 0
stashes : $stashes
repo_name : no_repository
tag : no_tag
branch : no_branch
remote : ''
}
if ($raw_status | is-empty) { return $status }
for s in $raw_status {
let r = $s | split row ' '
match $r.0 {
'#' => {
match ($r.1 | str substring 7..) {
'oid' => {
$status.commit_hash = ($r.2 | str substring 0..8)
}
'head' => {
$status.branch = $r.2
}
'upstream' => {
$status.remote = $r.2
}
'ab' => {
$status.ahead = ($r.2 | into int)
$status.behind = ($r.3 | into int | math abs)
}
}
}
'1'|'2' => {
match ($r.1 | str substring 0..1) {
'A' => {
$status.idx_added_staged += 1
}
'M' => {
$status.idx_modified_staged += 1
}
'R' => {
$status.idx_renamed += 1
}
'D' => {
$status.idx_deleted_staged += 1
}
'T' => {
$status.idx_type_changed += 1
}
}
match ($r.1 | str substring 1..2) {
'M' => {
$status.wt_modified += 1
}
'R' => {
$status.wt_renamed += 1
}
'D' => {
$status.wt_deleted += 1
}
'T' => {
$status.wt_type_changed += 1
}
}
}
'?' => {
$status.wt_untracked += 1
}
'u' => {
$status.conflicts += 1
}
}
}
$status
}
export def _git_log_stat [n] {
do -i {
git log --reverse -n $n --pretty=»¦«%h --stat
| lines
| reduce -f { c: '', r: [] } {|it, acc|
if ($it | str starts-with '»¦«') {
$acc | upsert c ($it | str substring 6.. )
} else if ($it | find -r '[0-9]+ file.+change' | is-empty) {
$acc
} else {
let x = $it
| split row ','
| each {|x| $x
| str trim
| parse -r "(?<num>[0-9]+) (?<col>.+)"
| get 0
}
| reduce -f {sha: $acc.c file:0 ins:0 del:0} {|i,a|
let col = if ($i.col | str starts-with 'file') {
'file'
} else {
$i.col | str substring ..3
}
let num = $i.num | into int
$a | upsert $col $num
}
$acc | upsert r ($acc.r | append $x)
}
}
| get r
}
}
export def _git_log [verbose num] {
let r = do -i {
git log --reverse -n $num --pretty=%h»¦«%s»¦«%aN»¦«%aE»¦«%aD»¦«%D
| lines
| split column "»¦«" sha message author email date refs
| each {|x|
let refs = if ($x.refs | is-empty) {
$x.refs
} else {
$x.refs | split row ", "
}
$x
| update date { $x.date | into datetime }
| update refs $refs
}
}
if $verbose {
$r | merge ( _git_log_stat $num )
} else {
$r
}
}
def "nu-complete git log" [] {
git log -n 32 --pretty=%h»¦«%s
| lines
| split column "»¦«" value description
| each {|x| $x | update value $"($x.value)"}
}
def "nu-complete git log all" [] {
git log --all -n 32 --pretty=%h»¦«%d»¦«%s
| lines
| split column "»¦«" value branch description
| each {|x| $x | update description $"($x.branch) ($x.description)" }
}
def "nu-complete git branch files" [context: string, offset:int] {
let token = $context | split row ' '
let branch = $token | get 1
let files = $token | skip 2
git ls-tree -r --name-only $branch
| lines
| filter {|x| not ($x in $files)}
}
def "nu-complete git branches" [] {
git branch
| lines
| filter {|x| not ($x | str starts-with '*')}
| each {|x| $"($x|str trim)"}
}
export def remote_branches [] {
git branch -r
| lines
| str trim
| filter {|x| not ($x | str starts-with 'origin/HEAD') }
}
def "nu-complete git remotes" [] {
^git remote | lines | each { |line| $line | str trim }
}
def git_main_branch [] {
git remote show origin
| lines
| str trim
| find --regex 'HEAD .*?[: ].+'
| first
| str replace --regex 'HEAD .*?[: ](.+)' '$1'
}
def git_previous_message [] {
let msg_file = ".git/COMMIT_EDITMSG"
if not ($msg_file | path exists) {
return
}
let lines = open $msg_file | lines
if ($lines | is-empty) {
return
}
let msg_line = $lines | first
if $msg_line == null or ($msg_line | str trim | str length) == 0 {
return
}
let logs = gl
if ($logs | is-empty) {
return $msg_line
}
let last_commit_msg = $logs | last | get message | lines | first
if $last_commit_msg == $msg_line {
return
}
return $msg_line
}