-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
related to - #106 ## description this is an effort to - [x] update the `nu-git-manager-sugar git` module - [x] remove deprecated / useless commands - [x] expose the meaningful ones as subcommands of `gm` - [x] add tests when possible ### kept commands - `get commit` -> `gm repo get commit` - `root` -> `gm repo goto root` - `branches` -> `gm repo branches` - `is-ancestor` -> `gm repo is-ancestor` - `remote list` -> `gm repo remote list` ### removed commands - `operations` - `compare` - `lock clean` - `remote add` - `remote remove` - `fixup` ### tests a new `tests sugar git` module with - `get-commit` - `goto-root` - `branches` - `is-ancestor` - `remote-list`
- Loading branch information
Showing
4 changed files
with
130 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,198 +1,94 @@ | ||
use std log | ||
|
||
# get a summary of all the operations made between `main` and `HEAD` | ||
export def operations [] { | ||
git log $"(git merge-base FETCH_HEAD main)..HEAD" -M5 --summary | ||
| rg -e 'rename.*=>|delete mode' | ||
| lines | ||
| str trim | ||
| parse '{operation} {file}' | ||
| sort-by operation | ||
} | ||
|
||
# get the commit hash of any revision | ||
export def "get commit" [ | ||
revision: string = "HEAD" # the revision to get the hash of (defaults to "HEAD") | ||
] { | ||
git rev-parse $revision | str trim | ||
} | ||
|
||
# compare two revisions in a `git` repository | ||
export def compare [ | ||
with: string # the target revision to compare the base with | ||
from: string = "HEAD" # the base revision of the comparison (defaults to "HEAD") | ||
--share # output the comparision in pretty shareable format | ||
] { | ||
let start = (git rev-parse $with | str trim) | ||
let end = (git rev-parse $from | str trim) | ||
|
||
if $share { | ||
return $"[`($start)`..`($end)`]\(($start)..($end)\)" | ||
} | ||
|
||
print $"comparing ($start) (char lparen)($with)(char rparen) and ($end) (char lparen)($from)(char rparen)" | ||
git diff $start $end | ||
export def "gm repo get commit" [ | ||
revision: string = "HEAD" # the revision to get the hash of | ||
]: nothing -> string { | ||
# FIXME: this `str trim` sounds like a bug :thinking: | ||
^git rev-parse $revision | str trim | ||
} | ||
|
||
def repo-root [] { | ||
git rev-parse --show-toplevel | str trim | ||
} | ||
|
||
# removes the index lock | ||
# | ||
# sometimes `git` won't want to run a command because of the `.git/index.lock` file not being | ||
# cleared... | ||
# this command simply removes the lock for you. | ||
export def "lock clean" [] { | ||
try { | ||
rm --verbose (repo-root | path join ".git" "index.lock") | ||
} catch { | ||
print "the index is not busy for now." | ||
} | ||
^git rev-parse --show-toplevel | ||
} | ||
|
||
# go to the root of the repository from anywhere in the worktree | ||
export def --env root [] { | ||
export def --env "gm repo goto root" []: nothing -> nothing { | ||
cd (repo-root) | ||
} | ||
|
||
# inspect local branches | ||
# | ||
# without any options, `git branches` will show all dangling branches, i.e. | ||
# local branches that do not have a remote counterpart. | ||
export def branches [ | ||
--report # will give a table report of all the | ||
# > **Note** | ||
# > in the following, a "*dangling*" branch refers to a branch that does not have any remote | ||
# > counterpart, i.e. it's a purely local branch. | ||
# | ||
# # Examples | ||
# list branches and their associated remotes | ||
# > gm repo branches | ||
# | ||
# clean all dangling branches | ||
# > gm repo branches --clean | ||
export def "gm repo branches" [ | ||
--clean # clean all dangling branches | ||
] { | ||
let local_branches = (git branch --list | lines | str replace --regex '..' "") | ||
let remote_branches = (git branch -r | lines | str trim | find --invert "HEAD ->" | parse "{remote}/{branch}") | ||
|
||
let branches_report = ( | ||
$local_branches | each {|branch| | ||
{ | ||
branch: $branch | ||
remotes: ($remote_branches | where branch == $branch | get remote) | ||
} | ||
} | ||
) | ||
]: nothing -> table<branch: string, remotes: list<string>> { | ||
let local_branches = ^git branch --list | lines | str replace --regex '..' "" | ||
let remote_branches = ^git branch --remotes | ||
| lines | ||
| str trim | ||
| find --invert "HEAD ->" | ||
| parse "{remote}/{branch}" | ||
|
||
let branches = $local_branches | each {|branch| { | ||
branch: $branch | ||
remotes: ($remote_branches | where branch == $branch | get remote) | ||
} } | ||
|
||
if $report { | ||
return $branches_report | ||
} | ||
|
||
let dangling_branches = ($branches_report | where remotes == [] | get branch) | ||
if $clean { | ||
let dangling_branches = $branches | where remotes == [] | ||
|
||
if ($dangling_branches | length) == 0 { | ||
print "no dangling branch" | ||
return | ||
} | ||
if ($dangling_branches | is-empty) { | ||
log warning "no dangling branches" | ||
return | ||
} | ||
|
||
if $clean { | ||
$dangling_branches | each {|| git branch --delete --force $in} | ||
for branch in $dangling_branches.branch { | ||
log info $"deleting branch `($branch)`" | ||
^git branch --quiet --delete --force $branch | ||
} | ||
} else { | ||
$dangling_branches | ||
$branches | ||
} | ||
} | ||
|
||
# return true iif the first revision is an ancestor of the second | ||
export def is-ancestor [ | ||
# | ||
# # Examples | ||
# HEAD~20 is an ancestor of HEAD | ||
# > gm repo is-ancestor HEAD~20 HEAD | ||
# true | ||
# | ||
# HEAD is never an ancestor of HEAD~20 | ||
# > gm repo is-ancestor HEAD HEAD~20 | ||
# false | ||
export def "gm repo is-ancestor" [ | ||
a: string # the base commit-ish revision | ||
b: string # the *head* commit-ish revision | ||
] { | ||
let exit_code = (do -i { | ||
git merge-base $a $b --is-ancestor | ||
} | complete | get exit_code) | ||
|
||
$exit_code == 0 | ||
]: nothing -> bool { | ||
(do -i { ^git merge-base $a $b --is-ancestor } | complete | get exit_code) == 0 | ||
} | ||
|
||
# get the list of all the remotes in the current repository | ||
export def "remote list" [] { | ||
export def "gm repo remote list" []: nothing -> table<remote: string, fetch: string, push: string> { | ||
# FIXME: use the helper `list-remotes` command from ../nu-git-manager/git/repo.nu:29 | ||
^git remote --verbose | ||
| detect columns --no-headers | ||
| rename remote url mode | ||
| str trim | ||
| group-by remote | ||
| transpose | ||
| update column1 { reject remote | select mode url | transpose -r | into record } | ||
| flatten | ||
| rename remote fetch push | ||
} | ||
|
||
# add a new remote to the repository | ||
export def "remote add" [ | ||
name: string # the name of the remote, e.g. `amtoine` | ||
repo: string # the name of the upstream repo, e.g. `nu-git-manager` | ||
host: string # the host where the upstream repo is stored, e.g. `github.com` | ||
--ssh # use SSH as the communication protocol | ||
] { | ||
if $name in (remote list | get remote) { | ||
error make { | ||
msg: $"(ansi red_bold)remote_already_in_index(ansi reset)" | ||
label: { | ||
text: $"already a remote of ($env.PWD)" | ||
span: (metadata $name | get span) | ||
} | ||
| detect columns --no-headers | ||
| rename remote url mode | ||
| group-by remote | ||
| transpose | ||
| update column1 { | ||
reject remote | select mode url | transpose --header-row | into record | ||
} | ||
} | ||
|
||
let url = if $ssh { | ||
$"git@($host):($name)/($repo)" | ||
} else { | ||
$"https://($host)/($name)/($repo)" | ||
} | ||
|
||
^git remote add $name $url | ||
|
||
remote list | each {|it| | ||
if $it.remote == $name { | ||
$it | transpose | update column1 { $"(ansi yellow_bold)($in)(ansi reset)" } | transpose -r | into record | ||
} else { $it } | ||
} | ||
} | ||
|
||
def "nu-complete remotes" [] { | ||
remote list | get remote | ||
} | ||
|
||
# remove a remote from the local repository | ||
export def "remote remove" [ | ||
...remotes: string@"nu-complete remotes" # a *rest* list of remotes | ||
] { | ||
let report = ( | ||
remote list | each {|it| | ||
if $it.remote in $remotes { | ||
$it | transpose | update column1 { $"(ansi red_bold)($in)(ansi reset)" } | transpose -r | into record | ||
} else { $it } | ||
} | ||
) | ||
|
||
$remotes | each {|remote| | ||
if not ($remote in (remote list | get remote)) { | ||
log warning $"($remote) is not a remote of ($env.PWD)" | ||
} else { | ||
log info $"removing ($remote) from ($env.PWD)" | ||
^git remote remove $remote | ||
} | ||
} | ignore | ||
|
||
$report | ||
} | ||
|
||
# fixup a revision that's not the latest commit | ||
export def fixup [ | ||
revision: string # the revision of the Git worktree to fixup | ||
] { | ||
if (do --ignore-errors { git rev-parse $revision } | complete | get exit_code) != 0 { | ||
error make { | ||
msg: $"(ansi red_bold)revision_not_found(ansi reset)" | ||
label: { | ||
text: $"($revision) not found in the working tree of ($env.PWD)" | ||
span: (metadata $revision | get span) | ||
} | ||
} | ||
} | ||
|
||
git commit --fixup $revision | ||
git rebase --interactive --autosquash $"($revision)~1" | ||
| flatten | ||
| rename remote fetch push | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use std assert | ||
|
||
use ../../src/nu-git-manager-sugar/ git * | ||
use ../../src/nu-git-manager/fs/path.nu ["path sanitize"] | ||
use ../common/setup.nu [get-random-test-dir] | ||
|
||
def --env init-repo-and-cd-into []: nothing -> path { | ||
let repo = get-random-test-dir | ||
|
||
^git init $repo | ||
cd $repo | ||
|
||
$repo | ||
} | ||
|
||
export def get-commit [] { | ||
init-repo-and-cd-into | ||
|
||
^git checkout --orphan main | ||
^git commit --allow-empty --no-gpg-sign --message "init" | ||
|
||
assert equal (gm repo get commit) (^git rev-parse HEAD) | ||
} | ||
|
||
export def goto-root [] { | ||
let repo = init-repo-and-cd-into | ||
|
||
mkdir init-repo-and-cd-into/bar/baz | ||
cd init-repo-and-cd-into/bar/baz | ||
|
||
gm repo goto root | ||
assert equal (pwd | path sanitize) $repo | ||
} | ||
|
||
export def branches [] { | ||
init-repo-and-cd-into | ||
|
||
assert equal (gm repo branches) [] | ||
|
||
^git checkout --orphan foo | ||
^git commit --allow-empty --no-gpg-sign --message "init" | ||
|
||
assert equal (gm repo branches) [{branch: foo, remotes: []}] | ||
} | ||
|
||
export def is-ancestor [] { | ||
init-repo-and-cd-into | ||
|
||
^git commit --allow-empty --no-gpg-sign --message "init" | ||
^git commit --allow-empty --no-gpg-sign --message "c1" | ||
^git commit --allow-empty --no-gpg-sign --message "c2" | ||
|
||
assert (gm repo is-ancestor HEAD^ HEAD) | ||
assert not (gm repo is-ancestor HEAD HEAD^) | ||
} | ||
|
||
export def remote-list [] { | ||
init-repo-and-cd-into | ||
|
||
assert equal (gm repo remote list) [] | ||
|
||
^git remote add foo foo-url | ||
|
||
assert equal (gm repo remote list) [{remote: foo, fetch: foo-url, push: foo-url}] | ||
} |
Empty file.