Compare commits
4 Commits
d6bf0293ba
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 000414733c | |||
| 6d4e138954 | |||
| c0086e3bf8 | |||
| 43a260191c |
@@ -98,6 +98,16 @@ function git-update-main --description 'Checkout and fast-forward the main branc
|
||||
command git pull --ff-only
|
||||
end
|
||||
|
||||
function git-fetch-main --description 'Fetch the latest main branch from origin'
|
||||
set -l main_branch $argv[1]
|
||||
if test -z "$main_branch"
|
||||
set main_branch (git-main-branch); or return 1
|
||||
end
|
||||
|
||||
command git fetch origin "+refs/heads/$main_branch:refs/remotes/origin/$main_branch"; or return 1
|
||||
printf '%s\n' "$main_branch"
|
||||
end
|
||||
|
||||
function br --description 'Switch to an existing branch, or create one from updated main'
|
||||
if test (count $argv) -ne 1
|
||||
echo 'Usage: br <branch-name>'
|
||||
@@ -137,6 +147,19 @@ function bru --description 'Update the current branch by rebasing it on updated
|
||||
command git rebase "$main_branch"
|
||||
end
|
||||
|
||||
function brup --description 'Merge the latest origin main branch into the current branch'
|
||||
set -l current_branch (command git symbolic-ref --quiet --short HEAD 2>/dev/null)
|
||||
if test -z "$current_branch"
|
||||
echo 'Could not determine current branch.'
|
||||
return 1
|
||||
end
|
||||
|
||||
git-require-clean; or return 1
|
||||
|
||||
set -l main_branch (git-fetch-main); or return 1
|
||||
command git merge "refs/remotes/origin/$main_branch"
|
||||
end
|
||||
|
||||
function git-env --description 'Alias common git subcommands into the shell'
|
||||
set -l git_commands add bisect branch checkout clone commit diff fetch grep init log merge pull push rebase reset restore show stash tag
|
||||
for cmd in $git_commands
|
||||
|
||||
@@ -9,6 +9,67 @@ function prompt-reset --description 'Reset fish prompt state used by this rc'
|
||||
git-id-prompt
|
||||
end
|
||||
|
||||
function __fishrc_github_owner_from_url --description 'Print GitHub owner from a remote URL'
|
||||
set -l url $argv[1]
|
||||
test -n "$url"; or return 1
|
||||
|
||||
set -l owner (string replace -r '^https?://[^/]+/([^/]+)/.*$' '$1' -- "$url")
|
||||
if test "$owner" != "$url"
|
||||
printf '%s\n' "$owner"
|
||||
return 0
|
||||
end
|
||||
|
||||
set owner (string replace -r '^ssh://[^@]+@[^/]+/([^/]+)/.*$' '$1' -- "$url")
|
||||
if test "$owner" != "$url"
|
||||
printf '%s\n' "$owner"
|
||||
return 0
|
||||
end
|
||||
|
||||
set owner (string replace -r '^[^@]+@[^:]+:([^/]+)/.*$' '$1' -- "$url")
|
||||
if test "$owner" != "$url"
|
||||
printf '%s\n' "$owner"
|
||||
return 0
|
||||
end
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
function __fishrc_git_remote_url --description 'Print a git remote URL from a remote name or URL'
|
||||
set -l remote $argv[1]
|
||||
test -n "$remote"; or return 1
|
||||
|
||||
if string match -qr '://|^[^@]+@[^:]+:' -- "$remote"
|
||||
printf '%s\n' "$remote"
|
||||
else
|
||||
command git remote get-url "$remote" 2>/dev/null
|
||||
end
|
||||
end
|
||||
|
||||
function __fishrc_prompt_pr_by_head --description 'Print PR number and state for an exact GitHub head owner and branch'
|
||||
set -l head_owner $argv[1]
|
||||
set -l head_branch $argv[2]
|
||||
test -n "$head_owner"; and test -n "$head_branch"; or return 1
|
||||
|
||||
set -l pr_lines
|
||||
set -l jq 'map(select(.state == "OPEN" or .state == "MERGED")) | sort_by(.updatedAt) | reverse | .[] | [.number, .state, .headRepositoryOwner.login, .headRefName] | @tsv'
|
||||
if command -sq timeout
|
||||
set pr_lines (command timeout 1s gh pr list --head "$head_branch" --state all --limit 50 --json number,state,updatedAt,headRefName,headRepositoryOwner --jq "$jq" 2>/dev/null)
|
||||
else
|
||||
set pr_lines (command gh pr list --head "$head_branch" --state all --limit 50 --json number,state,updatedAt,headRefName,headRepositoryOwner --jq "$jq" 2>/dev/null)
|
||||
end
|
||||
|
||||
set -l tab (printf '\t')
|
||||
for line in $pr_lines
|
||||
set -l fields (string split "$tab" -- "$line")
|
||||
if test "$fields[3]" = "$head_owner"; and test "$fields[4]" = "$head_branch"
|
||||
printf '%s\n%s\n' "$fields[1]" "$fields[2]"
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
function __fishrc_prompt_pr_state --description 'Set GitHub PR prompt state for a branch'
|
||||
set -l branch $argv[1]
|
||||
test -n "$branch"; or return 1
|
||||
@@ -18,7 +79,7 @@ function __fishrc_prompt_pr_state --description 'Set GitHub PR prompt state for
|
||||
if test -z "$repo_key"
|
||||
set repo_key (command jj root --ignore-working-copy 2>/dev/null)
|
||||
end
|
||||
set -l cache_key "$repo_key:$branch"
|
||||
set -l cache_key "$repo_key:$branch:pr-v2"
|
||||
set -l now (date +%s)
|
||||
|
||||
if test "$__fishrc_prompt_pr_cache_key" = "$cache_key"
|
||||
@@ -39,11 +100,27 @@ function __fishrc_prompt_pr_state --description 'Set GitHub PR prompt state for
|
||||
end
|
||||
end
|
||||
|
||||
set -l pr_line
|
||||
set -l repo_owner
|
||||
if command -sq timeout
|
||||
set pr_line (command timeout 1s gh pr list --head "$branch" --state all --limit 20 --json number,state,updatedAt --jq 'map(select(.state == "OPEN" or .state == "MERGED")) | sort_by(.updatedAt) | reverse | .[0] | select(.number != null) | .number, .state' 2>/dev/null)
|
||||
set repo_owner (command timeout 1s gh repo view --json owner --jq '.owner.login' 2>/dev/null)
|
||||
else
|
||||
set pr_line (command gh pr list --head "$branch" --state all --limit 20 --json number,state,updatedAt --jq 'map(select(.state == "OPEN" or .state == "MERGED")) | sort_by(.updatedAt) | reverse | .[0] | select(.number != null) | .number, .state' 2>/dev/null)
|
||||
set repo_owner (command gh repo view --json owner --jq '.owner.login' 2>/dev/null)
|
||||
end
|
||||
set repo_owner (string trim -- "$repo_owner")
|
||||
test -n "$repo_owner"; or return 1
|
||||
|
||||
set -l pr_line
|
||||
set pr_line (__fishrc_prompt_pr_by_head "$repo_owner" "$branch")
|
||||
|
||||
set -l branch_remote (command git config --get "branch.$branch.remote" 2>/dev/null)
|
||||
set -l branch_merge (command git config --get "branch.$branch.merge" 2>/dev/null)
|
||||
set -l branch_head (string replace -r '^refs/heads/' '' -- "$branch_merge")
|
||||
if test -z "$pr_line"; and test -n "$branch_remote"; and test -n "$branch_head"; and test "$branch_head" != "$branch_merge"
|
||||
set -l remote_url (__fishrc_git_remote_url "$branch_remote")
|
||||
set -l remote_owner (__fishrc_github_owner_from_url "$remote_url")
|
||||
if test -n "$remote_owner"
|
||||
set pr_line (__fishrc_prompt_pr_by_head "$remote_owner" "$branch_head")
|
||||
end
|
||||
end
|
||||
|
||||
set -l pr_number (string trim -- "$pr_line[1]")
|
||||
|
||||
+98
-9
@@ -47,7 +47,10 @@ function Invoke-ExternalCommand {
|
||||
}
|
||||
|
||||
$Command = [string]$args[0]
|
||||
$CommandArgs = if ($args.Count -gt 1) { @($args[1..($args.Count - 1)]) } else { @() }
|
||||
$CommandArgs = @()
|
||||
for ($i = 1; $i -lt $args.Count; $i++) {
|
||||
$CommandArgs += ,$args[$i]
|
||||
}
|
||||
$cmd = Get-ExternalCommandPath $Command
|
||||
if (-not $cmd) {
|
||||
Write-Error "$Command is not installed."
|
||||
@@ -314,13 +317,19 @@ function setproxy {
|
||||
}
|
||||
|
||||
function global:ssh {
|
||||
$sshExe = Get-ExternalCommandPath ssh
|
||||
if (-not $sshExe) {
|
||||
Write-Error 'ssh is not installed.'
|
||||
return 127
|
||||
}
|
||||
|
||||
if ($env:TERM -eq 'xterm-kitty') {
|
||||
$oldTerm = $env:TERM
|
||||
$env:TERM = 'xterm-256color'
|
||||
try { Invoke-ExternalCommand ssh @args }
|
||||
try { & $sshExe @args }
|
||||
finally { $env:TERM = $oldTerm }
|
||||
} else {
|
||||
Invoke-ExternalCommand ssh @args
|
||||
& $sshExe @args
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,7 +556,7 @@ function git-main-branch {
|
||||
}
|
||||
|
||||
Write-Error 'Could not determine main branch.'
|
||||
return 1
|
||||
return
|
||||
}
|
||||
|
||||
function git-update-main {
|
||||
@@ -560,6 +569,16 @@ function git-update-main {
|
||||
Invoke-RawGit pull --ff-only
|
||||
}
|
||||
|
||||
function git-fetch-main {
|
||||
param([string]$MainBranch)
|
||||
if (-not $MainBranch) { $MainBranch = git-main-branch | Select-Object -First 1 }
|
||||
if (-not $MainBranch) { return }
|
||||
|
||||
Invoke-RawGit fetch origin "+refs/heads/${MainBranch}:refs/remotes/origin/${MainBranch}"
|
||||
if ($LASTEXITCODE -ne 0) { return }
|
||||
Write-Output $MainBranch
|
||||
}
|
||||
|
||||
function br {
|
||||
param([Parameter(Mandatory = $true)][string]$Branch)
|
||||
if (-not (Test-GitCleanWorktree)) { return 1 }
|
||||
@@ -600,6 +619,20 @@ function bru {
|
||||
Invoke-RawGit rebase $mainBranch
|
||||
}
|
||||
|
||||
function brup {
|
||||
$currentBranch = Invoke-RawGit symbolic-ref --quiet --short HEAD 2>$null
|
||||
if ($LASTEXITCODE -ne 0 -or -not $currentBranch) {
|
||||
Write-Error 'Could not determine current branch.'
|
||||
return 1
|
||||
}
|
||||
|
||||
if (-not (Test-GitCleanWorktree)) { return 1 }
|
||||
|
||||
$mainBranch = git-fetch-main | Select-Object -First 1
|
||||
if (-not $mainBranch) { return 1 }
|
||||
Invoke-RawGit merge "refs/remotes/origin/$mainBranch"
|
||||
}
|
||||
|
||||
function git-env {
|
||||
foreach ($cmd in @('add', 'bisect', 'branch', 'checkout', 'clone', 'commit', 'diff', 'fetch', 'grep', 'init', 'log', 'merge', 'pull', 'push', 'rebase', 'reset', 'restore', 'show', 'stash', 'tag')) {
|
||||
$name = $cmd
|
||||
@@ -622,6 +655,48 @@ function prompt-reset {
|
||||
git-id-prompt
|
||||
}
|
||||
|
||||
function Get-GithubOwnerFromRemoteUrl {
|
||||
param([string]$Url)
|
||||
if (-not $Url) { return $null }
|
||||
|
||||
foreach ($pattern in @(
|
||||
'^https?://[^/]+/([^/]+)/.*$',
|
||||
'^ssh://[^@]+@[^/]+/([^/]+)/.*$',
|
||||
'^[^@]+@[^:]+:([^/]+)/.*$'
|
||||
)) {
|
||||
if ($Url -match $pattern) { return $Matches[1] }
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-GitRemoteUrl {
|
||||
param([string]$Remote)
|
||||
if (-not $Remote) { return $null }
|
||||
|
||||
if ($Remote -match '://|^[^@]+@[^:]+:') { return $Remote }
|
||||
Invoke-RawGit remote get-url $Remote 2>$null | Select-Object -First 1
|
||||
}
|
||||
|
||||
function Get-PromptPrByHead {
|
||||
param(
|
||||
[string]$HeadOwner,
|
||||
[string]$HeadBranch
|
||||
)
|
||||
if (-not $HeadOwner -or -not $HeadBranch) { return $null }
|
||||
|
||||
$jq = 'map(select(.state == "OPEN" or .state == "MERGED")) | sort_by(.updatedAt) | reverse | .[] | [.number, .state, .headRepositoryOwner.login, .headRefName] | @tsv'
|
||||
$prLines = Invoke-ExternalCommand gh pr list --head $HeadBranch --state all --limit 50 --json number,state,updatedAt,headRefName,headRepositoryOwner --jq $jq 2>$null
|
||||
foreach ($line in $prLines) {
|
||||
$fields = $line -split "`t", 4
|
||||
if ($fields.Count -eq 4 -and $fields[2] -eq $HeadOwner -and $fields[3] -eq $HeadBranch) {
|
||||
return [pscustomobject]@{ Number = $fields[0]; State = $fields[1] }
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-PromptPrState {
|
||||
param([string]$Branch)
|
||||
if (-not $Branch -or -not (has gh)) { return $null }
|
||||
@@ -633,17 +708,31 @@ function Get-PromptPrState {
|
||||
if (-not $repoKey) { return $null }
|
||||
$repoKey = $repoKey | Select-Object -First 1
|
||||
|
||||
$cacheKey = "$repoKey`:$Branch"
|
||||
$cacheKey = "$repoKey`:$Branch`:pr-v2"
|
||||
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
|
||||
if ($global:__PwshRcPromptPrCacheKey -eq $cacheKey -and ($now - [int64]$global:__PwshRcPromptPrCacheTime) -lt 300) {
|
||||
if ($global:__PwshRcPromptPrCacheValue[0] -eq '__none') { return $null }
|
||||
return [pscustomobject]@{ Number = $global:__PwshRcPromptPrCacheValue[0]; Color = $global:__PwshRcPromptPrCacheValue[1] }
|
||||
}
|
||||
|
||||
$jq = 'map(select(.state == "OPEN" or .state == "MERGED")) | sort_by(.updatedAt) | reverse | .[0] | select(.number != null) | .number, .state'
|
||||
$prLine = Invoke-ExternalCommand gh pr list --head $Branch --state all --limit 20 --json number,state,updatedAt --jq $jq 2>$null
|
||||
$prNumber = $prLine | Select-Object -First 1
|
||||
$prState = $prLine | Select-Object -Skip 1 -First 1
|
||||
$repoOwner = Invoke-ExternalCommand gh repo view --json owner --jq '.owner.login' 2>$null | Select-Object -First 1
|
||||
if (-not $repoOwner) { return $null }
|
||||
|
||||
$pr = Get-PromptPrByHead $repoOwner $Branch
|
||||
|
||||
if (-not $pr) {
|
||||
$branchRemote = Invoke-RawGit config --get "branch.$Branch.remote" 2>$null | Select-Object -First 1
|
||||
$branchMerge = Invoke-RawGit config --get "branch.$Branch.merge" 2>$null | Select-Object -First 1
|
||||
if ($branchRemote -and $branchMerge -and $branchMerge.StartsWith('refs/heads/')) {
|
||||
$branchHead = $branchMerge.Substring('refs/heads/'.Length)
|
||||
$remoteUrl = Get-GitRemoteUrl $branchRemote
|
||||
$remoteOwner = Get-GithubOwnerFromRemoteUrl $remoteUrl
|
||||
if ($remoteOwner) { $pr = Get-PromptPrByHead $remoteOwner $branchHead }
|
||||
}
|
||||
}
|
||||
|
||||
$prNumber = if ($pr) { $pr.Number } else { $null }
|
||||
$prState = if ($pr) { $pr.State } else { $null }
|
||||
$prColor = if ($prState -eq 'MERGED') { 'AF87FF' } else { '00FF00' }
|
||||
|
||||
$global:__PwshRcPromptPrCacheKey = $cacheKey
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo 'Usage: zstdedit <file.zst>'
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "zstdedit: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
choose_editor() {
|
||||
if [[ -n "${VISUAL:-}" ]]; then
|
||||
echo "$VISUAL"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${EDITOR:-}" ]]; then
|
||||
echo "$EDITOR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local candidate
|
||||
for candidate in micro nano vim vi; do
|
||||
if command -v "$candidate" >/dev/null 2>&1; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
run_editor() {
|
||||
local file="$1"
|
||||
local editor
|
||||
editor=$(choose_editor) || die 'No editor found. Set VISUAL or EDITOR.'
|
||||
|
||||
local -a editor_cmd
|
||||
read -r -a editor_cmd <<<"$editor"
|
||||
"${editor_cmd[@]}" "$file"
|
||||
}
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v zstd >/dev/null 2>&1 || die 'zstd is not installed.'
|
||||
|
||||
target="$1"
|
||||
target_dir=$(dirname -- "$target")
|
||||
target_base=$(basename -- "$target")
|
||||
|
||||
[[ -d "$target_dir" ]] || die "Parent directory does not exist: $target_dir"
|
||||
|
||||
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/zstdedit.XXXXXX")
|
||||
compressed_tmp=''
|
||||
cleanup() {
|
||||
if [[ -n "$compressed_tmp" ]]; then
|
||||
rm -f -- "$compressed_tmp"
|
||||
fi
|
||||
rm -rf -- "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
before_file="$tmpdir/before"
|
||||
edit_file="$tmpdir/${target_base%.zst}"
|
||||
|
||||
if [[ -e "$target" ]]; then
|
||||
[[ -f "$target" ]] || die "Not a regular file: $target"
|
||||
zstd -q -dc -- "$target" >"$before_file" || die "Failed to decompress: $target"
|
||||
else
|
||||
: >"$before_file"
|
||||
fi
|
||||
|
||||
cp -- "$before_file" "$edit_file"
|
||||
run_editor "$edit_file"
|
||||
|
||||
if cmp -s -- "$before_file" "$edit_file"; then
|
||||
echo 'No changes.'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
compressed_tmp=$(mktemp -- "$target_dir/.${target_base}.zstdedit.XXXXXX")
|
||||
zstd -q -f -o "$compressed_tmp" -- "$edit_file" || die 'Failed to recompress edited file.'
|
||||
|
||||
if [[ -e "$target" ]]; then
|
||||
chmod --reference="$target" "$compressed_tmp" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
mv -f -- "$compressed_tmp" "$target"
|
||||
compressed_tmp=''
|
||||
echo "Updated $target"
|
||||
@@ -97,6 +97,16 @@ git-update-main() {
|
||||
command git pull --ff-only
|
||||
}
|
||||
|
||||
git-fetch-main() {
|
||||
local main_branch="$1"
|
||||
if [[ -z "$main_branch" ]]; then
|
||||
main_branch=$(git-main-branch) || return 1
|
||||
fi
|
||||
|
||||
command git fetch origin "+refs/heads/${main_branch}:refs/remotes/origin/${main_branch}" || return 1
|
||||
echo "$main_branch"
|
||||
}
|
||||
|
||||
br() {
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo 'Usage: br <branch-name>'
|
||||
@@ -139,6 +149,21 @@ bru() {
|
||||
command git rebase "$main_branch"
|
||||
}
|
||||
|
||||
brup() {
|
||||
local current_branch
|
||||
current_branch=$(command git symbolic-ref --quiet --short HEAD 2>/dev/null)
|
||||
if [[ -z "$current_branch" ]]; then
|
||||
echo 'Could not determine current branch.'
|
||||
return 1
|
||||
fi
|
||||
|
||||
git-require-clean || return 1
|
||||
|
||||
local main_branch
|
||||
main_branch=$(git-fetch-main) || return 1
|
||||
command git merge "refs/remotes/origin/$main_branch"
|
||||
}
|
||||
|
||||
# Git environment
|
||||
git-env() {
|
||||
git_commands=( add bisect branch checkout clone commit diff fetch grep init log merge pull push rebase reset restore show stash tag )
|
||||
|
||||
Reference in New Issue
Block a user