Skip to content

Commit 2a4b31b

Browse files
committed
chore: enforce line endings via .gitattributes
0 parents  commit 2a4b31b

5 files changed

Lines changed: 384 additions & 0 deletions

File tree

.gitattributes

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Default: keep text normalized in repo
2+
* text=auto
3+
4+
# PowerShell scripts: use CRLF in working tree on Windows
5+
*.ps1 text eol=crlf
6+
*.psm1 text eol=crlf
7+
*.psd1 text eol=crlf
8+
9+
# Common text files
10+
*.md text eol=lf
11+
*.yml text eol=lf
12+
*.yaml text eol=lf
13+
*.json text eol=lf
14+
15+
# Binary files (avoid any conversions)
16+
*.png binary
17+
*.jpg binary
18+
*.jpeg binary
19+
*.gif binary
20+
*.zip binary
21+
*.exe binary
22+
*.dll binary

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
private.ps1
2+

bootstrap.ps1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function Ensure-Module($name, [Version]$min = $null) {
2+
$ok = Get-Module -ListAvailable $name | Where-Object { -not $min -or $_.Version -ge $min }
3+
if (-not $ok) { Install-Module $name -Scope CurrentUser -Force -AllowClobber -SkipPublisherCheck }
4+
}
5+
6+
# PSReadLine is required for Windows PowerShell 5.1; it's already built into PowerShell Core (pwsh)
7+
if ($PSVersionTable.PSEdition -ne 'Core') { Ensure-Module PSReadLine ([Version]'2.2.6') }
8+
9+
Ensure-Module posh-git
10+
Ensure-Module git-aliases
11+
12+
Write-Host "Bootstrap done. Restart PowerShell."
13+
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
# ===================================================================
2+
# GitAliases.Extras.psm1
3+
#
4+
# Extends posh-git and git-aliases with custom functions and
5+
# adds robust tab completion for all git aliases.
6+
#
7+
# Mainly inspired by: https://github.com/zh30/zsh-shortcut-git
8+
# ===================================================================
9+
10+
# --- Custom Helper Functions ---
11+
function Test-InGitRepo {
12+
try {
13+
git rev-parse --is-inside-work-tree *> $null
14+
$true
15+
} catch {
16+
$false
17+
}
18+
}
19+
20+
function Test-GitInProgress {
21+
if (-not (Test-InGitRepo)) { return $false }
22+
$gitDir = git rev-parse --git-dir
23+
$inProgressFiles = @("MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "BISECT_LOG")
24+
foreach ($file in $inProgressFiles) {
25+
if (Test-Path (Join-Path $gitDir $file)) { return $true }
26+
}
27+
$false
28+
}
29+
30+
function Test-WorkingTreeClean {
31+
if (-not (Test-InGitRepo)) { return $false }
32+
# Checks for both unstaged and staged changes.
33+
& git diff --quiet --exit-code
34+
$isClean = ($LASTEXITCODE -eq 0)
35+
& git diff --cached --quiet --exit-code
36+
$isClean = $isClean -and ($LASTEXITCODE -eq 0)
37+
return $isClean
38+
}
39+
40+
function Get-CurrentBranch {
41+
if (Test-InGitRepo) {
42+
$branch = git rev-parse --abbrev-ref HEAD
43+
if ($branch -ne "HEAD") {
44+
return $branch.Trim()
45+
}
46+
}
47+
return $null
48+
}
49+
50+
function Test-GitRefExists {
51+
param(
52+
[Parameter(Mandatory=$true)]
53+
[string]$RefName
54+
)
55+
& git show-ref --verify --quiet $RefName
56+
return ($LASTEXITCODE -eq 0)
57+
}
58+
59+
60+
# --- Custom Git Command Functions ---
61+
function UpMerge {
62+
[CmdletBinding()]
63+
param(
64+
[string]$Src = "origin/main",
65+
[switch]$AllowDirty, [switch]$AllowInProgress, [switch]$NoFetch, [switch]$NoFF
66+
)
67+
if (-not (Test-InGitRepo)) { throw "Not a git repository." }
68+
$tgt = Get-CurrentBranch; if (-not $tgt) { throw "Detached HEAD. Cannot merge." }
69+
if (-not $AllowInProgress -and (Test-GitInProgress)) { throw "Another git operation is in progress." }
70+
if (-not $AllowDirty -and -not (Test-WorkingTreeClean)) { throw "Working tree is not clean." }
71+
if (-not $NoFetch) { git fetch --all --prune; if ($LASTEXITCODE -ne 0) { throw "git fetch failed." } }
72+
$msg = "chore(sync): merge $Src into $tgt"
73+
if ($NoFF) { git merge --no-ff --no-edit $Src -m $msg } else { git merge --no-edit $Src -m $msg }
74+
}
75+
76+
function UpRebase {
77+
[CmdletBinding()]
78+
param(
79+
[string]$Src = "origin/main",
80+
[switch]$AllowDirty, [switch]$AllowInProgress, [switch]$NoFetch, [switch]$Autostash
81+
)
82+
if (-not (Test-InGitRepo)) { throw "Not a git repository." }
83+
$tgt = Get-CurrentBranch; if (-not $tgt) { throw "Detached HEAD. Cannot rebase." }
84+
if (-not $AllowInProgress -and (Test-GitInProgress)) { throw "Another git operation is in progress." }
85+
if (-not $AllowDirty -and -not (Test-WorkingTreeClean)) { throw "Working tree is not clean." }
86+
if (-not $NoFetch) { git fetch --all --prune; if ($LASTEXITCODE -ne 0) { throw "git fetch failed." } }
87+
if ($Autostash) { git rebase --autostash $Src } else { git rebase $Src }
88+
}
89+
90+
function gapt { git apply --3way @args }
91+
function gcor { git checkout --recurse-submodules @args }
92+
function gdct { git describe --tags (git rev-list --tags --max-count=1) }
93+
function gdt { git diff-tree --no-commit-id --name-only -r @args }
94+
function gdnolock { git diff @args ":(exclude)package-lock.json" ":(exclude)*.lock" }
95+
function gdv { git diff -w @args | Out-String | less }
96+
function gfo { git fetch origin @args }
97+
function glp { param([string]$format) if($format){ git log --pretty=$format } else { git log } }
98+
function gmtl { git mergetool --no-prompt @args }
99+
function gmtlvim{ git mergetool --no-prompt --tool=vimdiff @args }
100+
function gtv { git tag | Sort-Object { $_ -as [version] } }
101+
function gtl { param($p='') git tag --sort=-v:refname -n -l "$p*" }
102+
function gwip { git add -A; git rm (git ls-files --deleted) 2>$null; git commit --no-verify --no-gpg-sign -m "--wip-- [skip ci]" }
103+
function gunwip { if (git log -n 1 | Select-String -Quiet -- "--wip--") { git reset HEAD~1 } }
104+
105+
function grsh { git reset --soft HEAD~1 }
106+
function gccd {
107+
param([Parameter(ValueFromRemainingArguments=$true)][string[]]$rest)
108+
git clone --recurse-submodules @rest
109+
$last = if ($rest.Count) { $rest[-1] } else { '' }
110+
if ($last -match '\.git$') { $last = $last -replace '\.git$','' }
111+
if ($last) {
112+
$dirName = Split-Path $last -Leaf
113+
if (Test-Path $dirName) {
114+
Set-Location $dirName
115+
}
116+
}
117+
}
118+
function grl { git reflog @args }
119+
120+
# Get commit hash - returns the hash of HEAD or HEAD~n
121+
# Usage: ghash [steps] [-Short]
122+
# ghash - full hash of HEAD
123+
# ghash 3 - full hash of HEAD~3
124+
# ghash -Short - short hash of HEAD
125+
# ghash 3 -Short - short hash of HEAD~3
126+
function ghash {
127+
[CmdletBinding()]
128+
param(
129+
[Parameter(Position=0)]
130+
[int]$StepsBack = 0,
131+
[Alias('s')]
132+
[switch]$Short
133+
)
134+
135+
if (-not (Test-InGitRepo)) {
136+
Write-Error "Not a git repository." -ErrorAction Stop
137+
return
138+
}
139+
140+
$ref = if ($StepsBack -eq 0) { "HEAD" } else { "HEAD~$StepsBack" }
141+
142+
try {
143+
if ($Short) {
144+
$hash = git rev-parse --short $ref 2>$null
145+
} else {
146+
$hash = git rev-parse $ref 2>$null
147+
}
148+
149+
if ($LASTEXITCODE -eq 0 -and $hash) {
150+
$hash.Trim()
151+
} else {
152+
Write-Error "Invalid reference: $ref" -ErrorAction Stop
153+
}
154+
} catch {
155+
Write-Error "Failed to get commit hash: $_" -ErrorAction Stop
156+
}
157+
}
158+
159+
function gsw {
160+
[CmdletBinding()]
161+
param(
162+
[Parameter(ValueFromRemainingArguments=$true)]
163+
[string[]]$rest
164+
)
165+
166+
# Keep native behavior for advanced invocations (flags, start-points, etc.).
167+
if ($rest.Count -ne 1 -or $rest[0].StartsWith('-')) {
168+
git switch @rest
169+
return
170+
}
171+
172+
if (-not (Test-InGitRepo)) {
173+
git switch @rest
174+
return
175+
}
176+
177+
$target = $rest[0].Trim()
178+
if (-not $target) {
179+
git switch @rest
180+
return
181+
}
182+
183+
# Prefer an existing local branch first.
184+
if (Test-GitRefExists "refs/heads/$target") {
185+
git switch $target
186+
return
187+
}
188+
189+
# If only remote exists, create a tracking local branch explicitly.
190+
# This avoids ambiguous SHA/ref parsing for numeric names like "8695".
191+
$defaultRemote = git config --get checkout.defaultRemote 2>$null
192+
if (-not $defaultRemote) { $defaultRemote = 'origin' }
193+
if (Test-GitRefExists "refs/remotes/$defaultRemote/$target") {
194+
git switch --track "$defaultRemote/$target"
195+
return
196+
}
197+
198+
git switch @rest
199+
}
200+
201+
function gswc { git switch -c @args }
202+
203+
# Alias for shorter command
204+
205+
# --- Set Aliases for Custom Functions ---
206+
Set-Alias gum UpMerge
207+
Set-Alias gur UpRebase
208+
Set-Alias gh ghash
209+
210+
211+
# ===================================================================
212+
# Tab Completion Registration
213+
# ===================================================================
214+
function Register-GitAliasCompletion {
215+
# 1. Build a map of alias functions to their git subcommands
216+
$script:gitAliasMap = @{}
217+
$aliasRegex = '^\s*git\s+([\w-]+)\s+'
218+
219+
# Get all aliases from the 'git-aliases' module
220+
Get-Command -Module git-aliases | ForEach-Object {
221+
$definition = (Get-Content Function:\$($_.Name)).ToString()
222+
if ($definition -match $aliasRegex) {
223+
$script:gitAliasMap[$_.Name] = $matches[1]
224+
}
225+
}
226+
227+
# Add your custom aliases from this module
228+
Get-Command -Module GitAliases.Extras -CommandType Function | ForEach-Object {
229+
$func = $_
230+
$definition = $func.ScriptBlock.ToString()
231+
if ($definition -match $aliasRegex) {
232+
$subCommand = $matches[1]
233+
$script:gitAliasMap[$func.Name] = $subCommand
234+
}
235+
}
236+
237+
# Manually add complex functions that don't fit the regex pattern
238+
$script:gitAliasMap['gum'] = 'merge'
239+
$script:gitAliasMap['gur'] = 'rebase'
240+
$script:gitAliasMap['gccd'] = 'clone'
241+
$script:gitAliasMap['gsw'] = 'switch'
242+
243+
if ($script:gitAliasMap.Count -eq 0) {
244+
Write-Warning "No git alias functions were found to register for completion."
245+
return
246+
}
247+
248+
# 2. Create the proxy completer script block
249+
$proxyCompleter = {
250+
param($wordToComplete, $commandAst, $cursorPosition)
251+
252+
$commandName = $commandAst.CommandElements[0].Value
253+
$subCommand = $script:gitAliasMap[$commandName]
254+
255+
# Reconstruct the command line as if 'git <subcommand>' was typed
256+
$line = $commandAst.Extent.Text
257+
$gitLine = $line -replace "^$commandName", "git $subCommand"
258+
$offset = ("git $subCommand").Length - $commandName.Length
259+
$gitCursorPosition = $cursorPosition + $offset
260+
261+
# Use posh-git's official completion function
262+
if (Get-Command GitTabExpansion -ErrorAction SilentlyContinue) {
263+
try {
264+
return GitTabExpansion $gitLine $gitCursorPosition
265+
} catch {
266+
Write-Warning "posh-git's GitTabExpansion failed for alias '$commandName'."
267+
}
268+
}
269+
270+
# Fallback if posh-git isn't loaded or its function fails
271+
if ($subCommand -in @('checkout', 'switch', 'merge', 'rebase', 'branch', 'reset', 'revert')) {
272+
try {
273+
$branches = git branch -a --format='%(refname:short)' |
274+
ForEach-Object { $_ -replace '^remotes/origin/', '' } |
275+
Sort-Object -Unique |
276+
Where-Object { $_ -like "$wordToComplete*" }
277+
if ($branches) {
278+
return $branches | ForEach-Object {
279+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
280+
}
281+
}
282+
} catch {}
283+
}
284+
285+
# Return nothing if no completions are found
286+
return @()
287+
}
288+
289+
# 3. Register the completer for every alias found
290+
foreach ($aliasName in $script:gitAliasMap.Keys) {
291+
# Ensure we only register for commands that actually exist
292+
if (Get-Command $aliasName -ErrorAction SilentlyContinue) {
293+
Register-ArgumentCompleter -CommandName $aliasName -ScriptBlock $proxyCompleter
294+
}
295+
}
296+
297+
Write-Host "Git alias tab completion is now active for $($script:gitAliasMap.Count) aliases." -ForegroundColor Cyan
298+
}
299+
300+
# --- Run Registration and Export Members ---
301+
302+
# This runs when the module is imported
303+
Register-GitAliasCompletion
304+
305+
# Export all public functions and aliases for use in the shell
306+
Export-ModuleMember -Function * -Alias *

profile.ps1

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# --- PSReadLine Configuration ---
2+
# This enhances the command-line editing experience.
3+
if (Get-Module -ListAvailable -Name PSReadLine) {
4+
if (-not (Get-Module -Name PSReadLine)) {
5+
Import-Module PSReadLine -ErrorAction SilentlyContinue
6+
}
7+
Set-PSReadLineOption -BellStyle None
8+
# Use Tab for menu completion and Shift+Tab to go backward.
9+
Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete
10+
Set-PSReadLineKeyHandler -Key "Shift+Tab" -Function TabCompletePrevious
11+
}
12+
13+
# --- Add your custom modules directory to the PSModulePath ---
14+
# This ensures PowerShell can find your 'GitAliases.Extras' module.
15+
$dotFilesRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
16+
$dotModules = Join-Path $dotFilesRoot 'modules'
17+
$sep = [IO.Path]::PathSeparator
18+
if ($env:PSModulePath -notlike "*$dotModules*") {
19+
$env:PSModulePath = "$dotModules$sep$env:PSModulePath"
20+
}
21+
22+
# --- Load Git Modules in the Correct Order ---
23+
# 1. posh-git: Provides the core Git prompt and tab completion engine.
24+
# It MUST be loaded first.
25+
Import-Module posh-git -ErrorAction Stop
26+
27+
# 2. git-aliases: Provides the standard set of 'g' aliases (gco, gsw, etc.).
28+
Import-Module git-aliases -ErrorAction Stop -DisableNameChecking
29+
30+
# Set the base 'g' alias to 'git' after modules that might define it as a function.
31+
# This ensures 'g<tab>' works correctly.
32+
if (Get-Command g -CommandType Function -ErrorAction SilentlyContinue) {
33+
Remove-Item Function:\g -Force -ErrorAction SilentlyContinue
34+
}
35+
Set-Alias -Name g -Value git -Force
36+
37+
# 3. GitAliases.Extras: Your custom module that DEPENDS on posh-git.
38+
# It finds all aliases and registers the proxy completer.
39+
Import-Module GitAliases.Extras -Force -ErrorAction Stop
40+
41+
Write-Host "PowerShell profile loaded. Posh-git and custom alias completion are active." -ForegroundColor Green

0 commit comments

Comments
 (0)