Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: amtoine/nu-git-manager
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 6b65118a5ed4d9e263a04a88fb4958771fe26dae
Choose a base ref
..
head repository: amtoine/nu-git-manager
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 5f0e0d98bfb9f601daeaf4ac89606b536c8e167f
Choose a head ref
13 changes: 13 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: Feature Request
about: Ask for a new feature for `nu-git-manager`
title: ''
labels: ['needs-triage']
assignees: ''

---

<!-- related issues, e.g. "related to #123" -->

## Description
<!-- describe in a few words the feature -->
29 changes: 18 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ env:
jobs:
tests:
strategy:
fail-fast: false
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-20.04]

@@ -26,21 +26,28 @@ jobs:
steps:
- uses: actions/checkout@v3

- uses: hustcer/setup-nu@v3.7
- uses: hustcer/setup-nu@v3.6
with:
version: 'nightly'
version: '0.86'

- name: Install Nupm from Source
run: git clone https://github.com/nushell/nupm ~/nupm/
- name: Download the source of Nupm
run: git clone --depth 1 https://github.com/nushell/nupm /tmp/nupm/

- name: Install Nupm
run: |
use /tmp/nupm/nupm
with-env {NUPM_HOME: ("~/.local/share/nupm" | path expand)} {
# FIXME: use --no-confirm option
# related to https://github.com/nushell/nupm/pull/42
mkdir ("~/.local/share/nupm" | path expand)
nupm install --force --path /tmp/nupm
}
- name: Show Nushell Version
run: version

- name: Show Git Version
run: git --version

- name: Run the tests
run: |
use ~/nupm/nupm/
# nupm test "internals"
nupm test "gm"
# FIXME: is there a way to not rely on hardcoded paths here?
use ~/.local/share/nupm/modules/nupm
nupm test
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -4,12 +4,12 @@ A collection of Nushell tools to manage `git` repositories.
# Table of content
- [nu-git-manager](#nu-git-manager)
- [Table of content](#table-of-content)
- [:bulb: what is `nu-git-manager` \[toc\]](#bulb-what-is-nu-git-manager-toc)
- [:link: requirements \[toc\]](#link-requirements-toc)
- [:recycle: installation \[toc\]](#recycle-installation-toc)
- [:gear: usage \[toc\]](#gear-usage-toc)
- [:pray: getting help \[toc\]](#pray-getting-help-toc)
- [:exclamation: some ideas of advanced (?) usage \[toc\]](#exclamation-some-ideas-of-advanced--usage-toc)
- [:bulb: what is `nu-git-manager`](#bulb-what-is-nu-git-manager-toc)
- [:link: requirements](#link-requirements-toc)
- [:recycle: installation](#recycle-installation-toc)
- [:gear: usage](#gear-usage-toc)
- [:pray: getting help](#pray-getting-help-toc)
- [:exclamation: some ideas of advanced (?) usage](#exclamation-some-ideas-of-advanced--usage-toc)

## :bulb: what is `nu-git-manager` [[toc](#table-of-content)]
like [`ghq`](https://github.com/x-motemen/ghq), `nu-git-manager` aims at being a fully-featured
@@ -48,9 +48,9 @@ in your `config.nu` you can add the following to load `nu-git-manager` modules:
use nu-git-manager *
# the following are non-essential modules
use nu-git-manager sugar git # augmnet Git with custom commands
use nu-git-manager sugar gh # load commands to interact with *GitHub*
use nu-git-manager sugar gist # load commands to interact with *GitHub* gists
use nu-git-manager-sugar git # augment Git with custom commands
use nu-git-manager-sugar gh # load commands to interact with *GitHub*
use nu-git-manager-sugar gist # load commands to interact with *GitHub* gists
```

then you have access to the whole `nu-git-manager` suite :partying_face:
10 changes: 0 additions & 10 deletions nu-git-manager/error/error.nu

This file was deleted.

4 changes: 4 additions & 0 deletions package.nuon
Original file line number Diff line number Diff line change
@@ -18,4 +18,8 @@
}
}
type: "module"
modules: [
"./src/nu-git-manager/",
"./src/nu-git-manager-sugar/",
]
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ../fs/dir.nu [open-item]
use fs/dir.nu [open-item]
def check-gh-logged-in [] {
let out = (do -i { gh auth status } | complete)
if $out.exit_code != 0 {
File renamed without changes.
File renamed without changes.
File renamed without changes.
13 changes: 13 additions & 0 deletions src/nu-git-manager/error/error.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# throw a nice error with a standard format
export def throw-error [
error: record<msg: string, label: record<text: string, span: record<start: int, end: int>>>
] {
error make {
msg: $"(ansi red_bold)($error.msg)(ansi reset)"
label: {
text: $error.label.text
start: $error.label.span.start
end: $error.label.span.end
}
}
}
34 changes: 30 additions & 4 deletions nu-git-manager/fs/cache.nu → src/nu-git-manager/fs/cache.nu
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use path.nu "path sanitize"

# get the path to the cache of the local store of repos
#
# /!\ this command will sanitize the output path. /!\
export def get-repo-store-cache-path []: nothing -> path {
$env.GIT_REPOS_CACHE?
| default (
@@ -11,6 +14,7 @@ export def get-repo-store-cache-path []: nothing -> path {
| path sanitize
}

# make sure the cache file exists and give a nice error otherwise
export def check-cache-file [cache_file: path]: nothing -> nothing {
if not ($cache_file | path exists) {
error make --unspanned {
@@ -22,27 +26,49 @@ export def check-cache-file [cache_file: path]: nothing -> nothing {
}
}

# open the cache file
#
# /!\ this command will return sanitized paths if `add-to-cache` or `gm update-cache` have been used. /!\
export def open-cache [cache_file: path]: nothing -> list<path> {
open --raw $cache_file | from nuon
}

# save a list of paths to the cache file
#
# /!\ this command will sanitize the paths for the caller. /!\
export def save-cache [cache_file: path]: list<path> -> nothing {
to nuon | save --force $cache_file
each { path sanitize } | to nuon | save --force $cache_file
}

# add a new path to the cache file
#
# /!\ this command will sanitize the paths for the caller. /!\
export def add-to-cache [cache_file: path, new_path: path]: nothing -> nothing {
print --no-newline "updating cache... "
open-cache $cache_file | append $new_path | uniq | sort | save-cache $cache_file
open-cache $cache_file
| append ($new_path | path sanitize)
| uniq
| sort
| save-cache $cache_file
print "done"
}

# remove an old path from the cache file
#
# /!\ this command will sanitize the paths for the caller. /!\
export def remove-from-cache [cache_file: path, old_path: path]: nothing -> nothing {
print --no-newline "updating cache... "
open-cache $cache_file | where $it != $old_path | save-cache $cache_file
open-cache $cache_file | where $it != ($old_path | path sanitize) | save-cache $cache_file
print "done"
}

export def make-cache [cache_file: path]: nothing -> nothing {
# clean and prepare the cache directory
#
# this command will
# - remove any previous cache file: this avoids having an invalid file, e.g. a directory, in the
# same place as the expected cache file
# - create the parent directory of the cache file
export def clean-cache-dir [cache_file: path]: nothing -> nothing {
rm --recursive --force $cache_file
mkdir ($cache_file | path dirname)
}
File renamed without changes.
12 changes: 12 additions & 0 deletions nu-git-manager/fs/store.nu → src/nu-git-manager/fs/store.nu
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
use std log
use path.nu "path sanitize"

# get the root of the local repo store
#
# /!\ this command will sanitize the output path. /!\
export def get-repo-store-path []: nothing -> path {
$env.GIT_REPOS_HOME? | default (
$env.XDG_DATA_HOME? | default ($nu.home-path | path join ".local/share") | path join "repos"
) | path expand | path sanitize
}

# list all the repos stored locally
#
# this command will return the empty list if the store does not exist.
#
# this command will
# - catch *bare* repositories
# - remove duplicates coming from nested repositories such as Git submodules
#
# /!\ this command will sanitize the output list of paths. /!\
export def list-repos-in-store []: nothing -> list<path> {
if not (get-repo-store-path | path exists) {
log debug $"the store does not exist: `(get-repo-store-path)`"
17 changes: 13 additions & 4 deletions nu-git-manager/git/url.nu → src/nu-git-manager/git/url.nu
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
use ../fs/path.nu "path sanitize"

# parse the URL of a Git repo
#
# this command will isolate
# - the host, e.g. `github.com`
# - the owner, e.g. `amtoine`
# - the group, e.g. GitLab repos can be stored in subgroups, which can either be seen as subfields
# to the owner or superfields of the repo
# - the repo, e.g. `nu-git-manager`
export def parse-git-url []: string -> record<host: string, owner: string, group: path, repo: string> {
str replace --regex '^git@(.*):' 'ssh://$1/'
| str replace --regex '\.git$' ''
@@ -29,11 +37,12 @@ export def parse-git-url []: string -> record<host: string, owner: string, group
| into record
}

# compute the FETCH and PUSH remote URLs for a parsed repository, based on user input
export def get-fetch-push-urls [
repository: record<host: string, owner: string, group: path, repo: string>, # typically from `parse-git-url`
fetch: string, # one of 'https', 'ssh', 'git', or empty
push: string, # one of 'https', 'ssh', 'git', or empty
ssh: bool,
repository: record<host: string, owner: string, group: path, repo: string>, # the parsed repository (typically from `parse-git-url`)
fetch: string, # user input: one of 'https', 'ssh', 'git', or empty (typically from `gm clone --fetch`)
push: string, # user input: one of 'https', 'ssh', 'git', or empty (typically from `gm clone --push`)
ssh: bool, # user input (typically from `gm clone --ssh`)
]: nothing -> record<fetch: string, push: string> {
let base_url = {
scheme: null,
55 changes: 33 additions & 22 deletions nu-git-manager/mod.nu → src/nu-git-manager/mod.nu
Original file line number Diff line number Diff line change
@@ -3,11 +3,9 @@ use std log
use fs/store.nu [get-repo-store-path, list-repos-in-store]
use fs/cache.nu [
get-repo-store-cache-path, check-cache-file, add-to-cache, remove-from-cache, open-cache,
save-cache, make-cache
save-cache, clean-cache-dir
]
use fs/path.nu "path sanitize"
use git/url.nu [parse-git-url, get-fetch-push-urls]

use error/error.nu [throw-error]

def "nu-complete git-protocols" []: nothing -> table<value: string, description: string> {
@@ -78,26 +76,30 @@ export def "gm clone" [
| append [$repository.host $repository.owner $repository.group $repository.repo]
| compact
| path join
| path sanitize

if ($local_path | path exists) {(
throw-error "repository_already_in_store"
$"this repository has already been cloned by (ansi {fg: "default_dimmed", attr: "it"})gm(ansi reset)"
(metadata $url).span
)}
if ($local_path | path exists) {
throw-error {
msg: "repository_already_in_store"
label: {
text: (
"this repository has already been cloned by "
+ $"(ansi {fg: "default_dimmed", attr: "it"})gm(ansi reset)"
)
span: (metadata $url | get span)
}
}
}

let urls = get-fetch-push-urls $repository $fetch $push $ssh

mut args = [$urls.fetch $local_path --origin $remote]
if $depth != null {
if ($depth < 1) {
let span = metadata $depth | get span
error make {
msg: $"(ansi red_bold)invalid_clone_depth(ansi reset)"
throw-error {
msg: "invalid_clone_depth"
label: {
text: $"clone depth should be strictly positive, found ($depth)"
start: $span.start
end: $span.end
span: (metadata $depth | get span)
}
}
}
@@ -113,10 +115,8 @@ export def "gm clone" [
}
}

log debug "cloning the repo"
^git clone $args

log debug "setting up the remote"
^git -C $local_path remote set-url $remote $urls.fetch
^git -C $local_path remote set-url $remote --push $urls.push

@@ -129,6 +129,8 @@ export def "gm clone" [

# list all the local repositories in your local store
#
# /!\ this command will return sanitized paths. /!\
#
# # Examples
# list all the repositories in the store
# > gm list
@@ -155,6 +157,8 @@ export def "gm list" [

# get current status about the repositories managed by `nu-git-manager`
#
# /!\ `$.root.path` and `$.cache.path` will be sanitized /!\
#
# Examples
# getting status when everything is fine
# > gm status | reject missing | flatten | into record
@@ -221,7 +225,7 @@ export def "gm status" []: nothing -> record<root: record<path: path, exists: bo
# > gm update-cache
export def "gm update-cache" []: nothing -> nothing {
let cache_file = get-repo-store-cache-path
make-cache $cache_file
clean-cache-dir $cache_file

print --no-newline "updating cache... "
list-repos-in-store | save-cache $cache_file
@@ -253,11 +257,18 @@ export def "gm remove" [
| find $pattern

let repo_to_remove = match ($choices | length) {
0 => {(
throw-error "no_matching_repository"
$"no repository matching this in (ansi {fg: "default_dimmed", attr: "it"})($root)(ansi reset)"
(metadata $pattern).span
)},
0 => {
throw-error {
msg: "no_matching_repository"
label: {
text: (
"no repository matching this in "
+ $"(ansi {fg: "default_dimmed", attr: "it"})($root)(ansi reset)"
)
span: (metadata $pattern | get span)
}
}
},
1 => { $choices | first },
_ => {
let prompt = $"please choose a repository to (ansi red)remove(ansi reset)"
Loading