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.

771 lines
21 KiB
Plaintext

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
$message | save -f .git/COMMIT_EDITMSG
git commit -v -e ...$m ...$a ...$n ...$k
} else {
let m = $message | with-flag -m
if $message != null {
$message | save -f .git/COMMIT_EDITMSG
}
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
}