Rename the project
Hinted by the Melpa maintainers here: https://github.com/melpa/melpa/pull/8093 Using a single letter name was a mistake. Renaming the project to something a bit more sensible. Maybe not too sensible that being said: we had to adopt a pun. Kudos to Wiwi for the pun: it's perfect.
This commit is contained in:
parent
1030d83f26
commit
c058603766
6
Makefile
6
Makefile
|
@ -21,15 +21,15 @@ INIT_PACKAGES="(progn \
|
|||
all: compile test package-lint clean-elc
|
||||
|
||||
test:
|
||||
${EMACS} -Q --eval ${INIT_PACKAGES} --batch -l h.el -l h-tests.el -f ert-run-tests-batch-and-exit
|
||||
${EMACS} -Q --eval ${INIT_PACKAGES} --batch -l my-repo-pins.el -l my-repo-pins-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
package-lint:
|
||||
${EMACS} -Q --eval ${INIT_PACKAGES} --batch -f package-lint-batch-and-exit h.el
|
||||
${EMACS} -Q --eval ${INIT_PACKAGES} --batch -f package-lint-batch-and-exit my-repo-pins.el
|
||||
|
||||
compile: clean-elc
|
||||
${EMACS} -Q --eval ${INIT_PACKAGES} -L . --batch -f batch-byte-compile *.el
|
||||
|
||||
clean-elc:
|
||||
clean:
|
||||
rm -f f.elc
|
||||
|
||||
.PHONY: all compile clean-elc package-lint test
|
||||
|
|
68
README.md
68
README.md
|
@ -1,4 +1,9 @@
|
|||
# H.el [![CI](https://github.com/NinjaTrappeur/h.el/actions/workflows/test.yml/badge.svg)](https://github.com/NinjaTrappeur/h.el/actions/workflows/test.yml)
|
||||
# My Repo Pins [![CI](https://github.com/NinjaTrappeur/my-repo-pins/actions/workflows/test.yml/badge.svg)](https://github.com/NinjaTrappeur/my-repo-pins/actions/workflows/test.yml)
|
||||
|
||||
![Project logo](./doc/assets/logo-white.svg#gh-dark-mode-only)
|
||||
![Project logo](./doc/assets/logo-black.svg#gh-light-mode-only)
|
||||
|
||||
|
||||
|
||||
This Emacs plugin is all about helping you to keep your git repositories organized in a single unified tree.
|
||||
|
||||
|
@ -18,7 +23,7 @@ IE., having a directory structure like that:
|
|||
│ └── mpv
|
||||
└── NinjaTrappeur
|
||||
├── cinny
|
||||
└── h.el
|
||||
└── my-repo-pins.el
|
||||
```
|
||||
|
||||
This Emacs plugin aims to help you navigate this repository tree **and** clone new repositories at the right place in the tree.
|
||||
|
@ -29,89 +34,87 @@ As always, a small demo is worth a thousand words!
|
|||
|
||||
**Jump to a local repository you already cloned:**
|
||||
|
||||
![Screen capture showcasing h.el jumping to a already checked out repository](./doc/assets/jump-local.webp)
|
||||
![Screen capture showcasing my-repo-pins.el jumping to a already checked out repository](./doc/assets/jump-local.webp)
|
||||
|
||||
**Find a repository in a remote forge, clone it, and jump to it:**
|
||||
|
||||
![Screen capture showcasing h.el cloning a git repository from a remote forge before jumping to it](./doc/assets/clone-project.webp)
|
||||
![Screen capture showcasing my-repo-pins.el cloning a git repository from a remote forge before jumping to it](./doc/assets/clone-project.webp)
|
||||
|
||||
**Alternatively, you can also specify a absolute git URL you want to clone:**
|
||||
|
||||
![Screen capture showcasing h.el cloning a git repository using a absolute git url before jumping to it](./doc/assets/clone-absolute-url.webp)
|
||||
|
||||
This plugin is heavily inspired by [**Zimbatm's h**](https://github.com/zimbatm/h).
|
||||
![Screen capture showcasing my-repo-pins.el cloning a git repository using a absolute git url before jumping to it](./doc/assets/clone-absolute-url.webp)
|
||||
|
||||
## Quick Start
|
||||
|
||||
The minimal configuration consists in setting the directory in which you want to clone all your git repositories via the `h-code-root` variable.
|
||||
The minimal configuration consists in setting the directory in which you want to clone all your git repositories via the `my-repo-pins-code-root` variable.
|
||||
|
||||
Let's say you'd like to store all your git repositories in the `~/code-root` directory. You'll want to add the following snippet in your Emacs configuration file:
|
||||
|
||||
```elisp
|
||||
(require 'h)
|
||||
(setq h-code-root "~/code-root")
|
||||
(setq my-repo-pins-code-root "~/code-root")
|
||||
```
|
||||
|
||||
You can then call the `M-x h-jump-to-project` command to open a project living in your `~/code-root` directory **or** clone a new project in your code root.
|
||||
You can then call the `M-x my-repo-pins` command to open a project living in your `~/code-root` directory **or** clone a new project in your code root.
|
||||
|
||||
Binding this command to a global key binding might make things a bit more convenient. I personally like to bind it to `M-h`. You can add the following snippet to your Emacs configuration to set up this key binding:
|
||||
|
||||
```elisp
|
||||
(global-set-key (kbd "M-h") 'h-jump-to-project)
|
||||
(global-set-key (kbd "M-h") 'my-repo-pins)
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### h-code-root - REQUIRED
|
||||
### my-repo-pins-code-root - REQUIRED
|
||||
|
||||
Path to the directory containing all your projects. `h.el` organize the git repos you'll clone in a tree fashion.
|
||||
Path to the directory containing all your projects. `my-repo-pins.el` organize the git repos you'll clone in a tree fashion.
|
||||
|
||||
All the code fetched using `h.el` will end up in this root directory. A tree of subdirectories will be created mirroring the remote clone URL.
|
||||
All the code fetched using `my-repo-pins.el` will end up in this root directory. A tree of subdirectories will be created mirroring the remote clone URL.
|
||||
|
||||
For instance, after checking out https://git.savannah.gnu.org/git/emacs/org-mode.git, the source code will live in the h-code-root/git.savannah.gnu.org/git/emacs/org-mode/ local directory
|
||||
For instance, after checking out https://git.savannah.gnu.org/git/emacs/org-mode.git, the source code will live in the my-repo-pins-code-root/git.savannah.gnu.org/git/emacs/org-mode/ local directory
|
||||
|
||||
### h-git-bin
|
||||
### my-repo-pins-git-bin
|
||||
|
||||
Path pointing to the git binary. By default, it'll look for git in the current `$PATH`.
|
||||
|
||||
### h-forge-fetchers
|
||||
### my-repo-pins-forge-fetchers
|
||||
|
||||
Alist in the form of `("FORGE NAME" . FETCH-FUNCTION)` where `FETCH-FUNCTION` is a function in charge of retrieving a potential remote clone URL. More about this function in the [Fetchers](#fetchers) section.
|
||||
|
||||
## Fetchers
|
||||
|
||||
When a repository cannot be found in the code root directory, `h.el` will try to download it from different forges. By default, it'll try to find it on github.com, gitlab.com, git.sr.ht, and codeberg.org.
|
||||
When a repository cannot be found in the code root directory, `my-repo-pins.el` will try to download it from different forges. By default, it'll try to find it on github.com, gitlab.com, git.sr.ht, and codeberg.org.
|
||||
|
||||
### Re-Using the Default Fetchers for your own Forge Instance
|
||||
|
||||
H.el provides some generic fetchers for Gitlab, Sourcehut, and Gitea.
|
||||
My-repo-pins.el provides some generic fetchers for Gitlab, Sourcehut, and Gitea.
|
||||
|
||||
You can re-use these generic fetchers for your own forge instance using the following functions:
|
||||
|
||||
- GitLab: `h--query-gitlab-owner-repo`
|
||||
- SourceHut: `h--query-sourcehut-owner-repo`
|
||||
- Gitea: `h--query-gitea-owner-repo`
|
||||
- GitLab: `my-repo-pins--query-gitlab-owner-repo`
|
||||
- SourceHut: `my-repo-pins--query-sourcehut-owner-repo`
|
||||
- Gitea: `my-repo-pins--query-gitea-owner-repo`
|
||||
|
||||
These functions share the same 4 input arguments:
|
||||
|
||||
- `instance-url`: your instance [FQDN](https://fr.wikipedia.org/wiki/Fully_qualified_domain_name). For instance: `gitlab.gnome.org`, `git.alternativebit.fr`, …
|
||||
- `user-name`: the user name for which we want to clone the repository.
|
||||
- `repo-name`: name of the repository we want to clone.
|
||||
- `callback`: function `h.el` will use to clone the repository once we retrieved the various clone URLs. The callback takes an alist as parameter. The alist being of the form of : `((ssh . SSH-CHECKOUT-URL) (https . HTTPS-CHECKOUT-URL))`.
|
||||
- `callback`: function `My-repo-pins.el` will use to clone the repository once we retrieved the various clone URLs. The callback takes an alist as parameter. The alist being of the form of : `((ssh . SSH-CHECKOUT-URL) (https . HTTPS-CHECKOUT-URL))`.
|
||||
|
||||
You can re-use these functions by instantiating them for a specific forge, then by appending this instantiation to the `h-forge-fetchers` variable in your Emacs configuration.
|
||||
You can re-use these functions by instantiating them for a specific forge, then by appending this instantiation to the `my-repo-pins-forge-fetchers` variable in your Emacs configuration.
|
||||
|
||||
Let's say you want to retrieve repositories from the Gnome Gitlab instance living at `gitlab.gnome.org`. You'll have to add the following snippet to your Emacs configuration:
|
||||
|
||||
```elisp
|
||||
(setq h-forge-fetchers
|
||||
`(("gitlab.gnome.org" (lambda (owner repo cb)(h--query-gitlab-owner-repo "gitlab.gnome.org" owner repo cb)))
|
||||
,h-forge-fetchers))
|
||||
(setq my-repo-pins-forge-fetchers
|
||||
`(("gitlab.gnome.org" (lambda (owner repo cb)(my-repo-pins--query-gitlab-owner-repo "gitlab.gnome.org" owner repo cb)))
|
||||
,my-repo-pins-forge-fetchers))
|
||||
```
|
||||
|
||||
### Writing your Forge Fetcher from Scratch
|
||||
|
||||
You may also want to support a forge for which `h.el` currently does not provide any generic fetcher. In that case, you'll have to write a function in the form of:
|
||||
You may also want to support a forge for which `my-repo-pins.el` currently does not provide any generic fetcher. In that case, you'll have to write a function in the form of:
|
||||
|
||||
```elisp
|
||||
(defun your-custom-fetcher (owner repo)
|
||||
|
@ -121,8 +124,8 @@ You may also want to support a forge for which `h.el` currently does not provide
|
|||
|
||||
The function needs to accept two input parameters:
|
||||
|
||||
- `owner`: string containing the name of the owner of the query repository. IE. `ninjatrappeur` for the `ninjatrappeur/h.el` query.
|
||||
- `repository`: string containing the name of the query repository. IE. `h.el` for the `ninjatrappeur/h.el` query.
|
||||
- `owner`: string containing the name of the owner of the query repository. IE. `ninjatrappeur` for the `ninjatrappeur/my-repo-pins.el` query.
|
||||
- `repository`: string containing the name of the query repository. IE. `my-repo-pins.el` for the `ninjatrappeur/my-repo-pins.el` query.
|
||||
|
||||
This function will return either `nil` in case the query couldn't be found on the remote forge. An alist containing the SSH and HTTPS clone URLs in the form of:
|
||||
|
||||
|
@ -130,3 +133,8 @@ This function will return either `nil` in case the query couldn't be found on th
|
|||
'((ssh . SSH-CHECKOUT-URL)
|
||||
(https . HTTPS-CHECKOUT-URL))
|
||||
```
|
||||
|
||||
## Aknowledgements
|
||||
|
||||
- This plugin is heavily inspired by [**Zimbatm's h**](https://github.com/zimbatm/h). Thanks a lot for this amazing tool, it made my life easier for years!
|
||||
- Thanks a lot to Wiwi who found the **terrible** pun we used to name this project.
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="400mm"
|
||||
height="200mm"
|
||||
viewBox="0 0 400 199.99999"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
id="layer1">
|
||||
<g
|
||||
aria-label="My"
|
||||
id="text11"
|
||||
style="font-size:128.862px;line-height:1.25;font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';letter-spacing:0px;word-spacing:0px;fill:#000000;stroke-width:3.22156">
|
||||
<path
|
||||
d="M 248.93135,36.371491 V 99.292389 H 223.76299 V 17.495221 H 324.43643 V 99.292389 H 299.26807 V 36.371491 H 286.68389 V 99.292389 H 261.51553 V 36.371491 Z"
|
||||
id="path3197"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
d="M 349.60479,67.83194 H 330.72852 V 17.495221 h 25.16836 V 48.95567 h 12.58418 V 17.495221 h 25.16836 V 67.83194 h -18.87627 v 31.460449 h -25.16836 z"
|
||||
id="path3199"
|
||||
style="fill:#000000" />
|
||||
</g>
|
||||
<g
|
||||
aria-label="repo pins"
|
||||
id="text19"
|
||||
style="font-size:67.5452px;line-height:1.25;font-family:Helvetica;-inkscape-font-specification:Helvetica;letter-spacing:0px;word-spacing:0px;fill:#000000;stroke-width:1.68863">
|
||||
<path
|
||||
d="m 122.94092,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 6.59621,16.49053 h -6.59621 v 16.49052 H 109.7485 v -42.87537 h 32.98105 v 16.49053 l -4.94715,4.94716 4.94715,4.94716 v 16.49052 h -13.19242 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#000000"
|
||||
id="path3202" />
|
||||
<path
|
||||
d="m 159.22008,151.79952 h 19.78863 v 9.89431 h -32.98105 v -42.87537 h 32.98105 v 9.89432 h -19.78863 v 6.59621 h 19.78863 v 9.89432 h -19.78863 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#000000"
|
||||
id="path3204" />
|
||||
<path
|
||||
d="m 195.49924,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 0,16.49053 v 16.49052 h -13.19242 v -42.87537 h 32.98105 v 26.38485 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#000000"
|
||||
id="path3206" />
|
||||
<path
|
||||
d="m 231.7784,151.79952 h 6.59621 v -23.08674 h -6.59621 z m -13.19242,9.89431 v -42.87537 h 32.98105 v 42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#000000"
|
||||
id="path3208" />
|
||||
<path
|
||||
d="m 284.81194,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 0,16.49053 v 16.49052 h -13.19242 v -42.87537 h 32.98105 v 26.38485 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#000000"
|
||||
id="path3210" />
|
||||
<path
|
||||
d="m 321.0911,118.81846 v 42.87537 h -13.19242 v -42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#000000"
|
||||
id="path3212" />
|
||||
<path
|
||||
d="m 357.37026,118.81846 v 42.87537 h -13.19243 v -32.98105 h -6.59621 v 32.98105 H 324.3892 v -42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#000000"
|
||||
id="path3214" />
|
||||
<path
|
||||
d="m 380.45699,128.71278 h -6.59621 v 6.59621 h 19.78864 v 26.38484 h -32.98106 v -13.19242 h 13.19242 v 3.29811 h 6.59621 v -6.59621 h -19.78863 v -26.38485 h 32.98106 v 13.19242 h -13.19243 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#000000"
|
||||
id="path3216" />
|
||||
</g>
|
||||
<path
|
||||
id="path3000"
|
||||
style="fill:#000000;stroke-width:4.19701;stroke-linecap:square;stroke-linejoin:bevel"
|
||||
d="M 91.05284,5.9650105 C 41.866279,6.1312243 2.5722989,50.262466 2.0029785,83.050227 a 16.307598,8.1574354 0 0 1 16.1152955,-7.525639 16.307598,8.1574354 0 0 1 16.213481,7.172689 16.307598,8.1574354 0 0 1 16.101342,-7.172689 16.307598,8.1574354 0 0 1 16.388664,8.102348 l 5.17e-4,0.05478 a 16.307598,8.1574354 0 0 1 -0.0098,0.142627 h 0.268201 a 16.442225,8.1574354 0 0 1 -0.0062,-0.08733 l -5.17e-4,-0.05478 a 16.442225,8.1574354 0 0 1 16.061035,-8.15144 V 91.83264 a 16.442225,8.1574354 0 0 1 -0.0057,0 v 106.60795 h 17.792713 1.81642 8.46667 14.62598 v -13.2333 -10.19731 h -9.74153 v 10.19731 h -4.88445 -8.46667 -1.81642 V 91.83212 a 16.94002,8.1574354 0 0 1 -0.0413,10e-4 V 75.531306 a 16.94002,8.1574354 0 0 1 16.42226,7.454842 16.307598,8.1574354 0 0 1 16.15818,-7.46156 16.307598,8.1574354 0 0 1 16.19643,7.078638 16.307598,8.1574354 0 0 1 16.08274,-7.078638 16.307598,8.1574354 0 0 1 16.2972,7.333403 C 181.28843,49.988222 140.45916,5.8009387 91.05284,5.9650105 Z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="400mm"
|
||||
height="200mm"
|
||||
viewBox="0 0 400 199.99999"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
sodipodi:docname="logo-inkscape.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.457453"
|
||||
inkscape:cx="-243.74089"
|
||||
inkscape:cy="-86.347668"
|
||||
inkscape:window-width="2558"
|
||||
inkscape:window-height="1419"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
aria-label="My"
|
||||
id="text11"
|
||||
style="font-size:128.862px;line-height:1.25;font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';letter-spacing:0px;word-spacing:0px;stroke-width:3.22156">
|
||||
<path
|
||||
d="M 248.93135,36.371491 V 99.292389 H 223.76299 V 17.495221 H 324.43643 V 99.292389 H 299.26807 V 36.371491 H 286.68389 V 99.292389 H 261.51553 V 36.371491 Z"
|
||||
id="path3197" />
|
||||
<path
|
||||
d="M 349.60479,67.83194 H 330.72852 V 17.495221 h 25.16836 V 48.95567 h 12.58418 V 17.495221 h 25.16836 V 67.83194 h -18.87627 v 31.460449 h -25.16836 z"
|
||||
id="path3199" />
|
||||
</g>
|
||||
<g
|
||||
aria-label="repo pins"
|
||||
id="text19"
|
||||
style="font-size:67.5452px;line-height:1.25;font-family:Helvetica;-inkscape-font-specification:Helvetica;letter-spacing:0px;word-spacing:0px;stroke-width:1.68863">
|
||||
<path
|
||||
d="m 122.94092,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 6.59621,16.49053 h -6.59621 v 16.49052 H 109.7485 v -42.87537 h 32.98105 v 16.49053 l -4.94715,4.94716 4.94715,4.94716 v 16.49052 h -13.19242 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush'"
|
||||
id="path3202" />
|
||||
<path
|
||||
d="m 159.22008,151.79952 h 19.78863 v 9.89431 h -32.98105 v -42.87537 h 32.98105 v 9.89432 h -19.78863 v 6.59621 h 19.78863 v 9.89432 h -19.78863 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush'"
|
||||
id="path3204" />
|
||||
<path
|
||||
d="m 195.49924,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 0,16.49053 v 16.49052 h -13.19242 v -42.87537 h 32.98105 v 26.38485 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush'"
|
||||
id="path3206" />
|
||||
<path
|
||||
d="m 231.7784,151.79952 h 6.59621 v -23.08674 h -6.59621 z m -13.19242,9.89431 v -42.87537 h 32.98105 v 42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush'"
|
||||
id="path3208" />
|
||||
<path
|
||||
d="m 284.81194,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 0,16.49053 v 16.49052 h -13.19242 v -42.87537 h 32.98105 v 26.38485 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush'"
|
||||
id="path3210" />
|
||||
<path
|
||||
d="m 321.0911,118.81846 v 42.87537 h -13.19242 v -42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush'"
|
||||
id="path3212" />
|
||||
<path
|
||||
d="m 357.37026,118.81846 v 42.87537 h -13.19243 v -32.98105 h -6.59621 v 32.98105 H 324.3892 v -42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush'"
|
||||
id="path3214" />
|
||||
<path
|
||||
d="m 380.45699,128.71278 h -6.59621 v 6.59621 h 19.78864 v 26.38484 h -32.98106 v -13.19242 h 13.19242 v 3.29811 h 6.59621 v -6.59621 h -19.78863 v -26.38485 h 32.98106 v 13.19242 h -13.19243 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush'"
|
||||
id="path3216" />
|
||||
</g>
|
||||
<path
|
||||
id="path3000"
|
||||
style="fill:#000000;stroke-width:4.19701;stroke-linecap:square;stroke-linejoin:bevel"
|
||||
d="M 91.05284,5.9650105 C 41.866279,6.1312243 2.5722989,50.262466 2.0029785,83.050227 a 16.307598,8.1574354 0 0 1 16.1152955,-7.525639 16.307598,8.1574354 0 0 1 16.213481,7.172689 16.307598,8.1574354 0 0 1 16.101342,-7.172689 16.307598,8.1574354 0 0 1 16.388664,8.102348 l 5.17e-4,0.05478 a 16.307598,8.1574354 0 0 1 -0.0098,0.142627 h 0.268201 a 16.442225,8.1574354 0 0 1 -0.0062,-0.08733 l -5.17e-4,-0.05478 a 16.442225,8.1574354 0 0 1 16.061035,-8.15144 V 91.83264 a 16.442225,8.1574354 0 0 1 -0.0057,0 v 106.60795 h 17.792713 1.81642 8.46667 14.62598 v -13.2333 -10.19731 h -9.74153 v 10.19731 h -4.88445 -8.46667 -1.81642 V 91.83212 a 16.94002,8.1574354 0 0 1 -0.0413,10e-4 V 75.531306 a 16.94002,8.1574354 0 0 1 16.42226,7.454842 16.307598,8.1574354 0 0 1 16.15818,-7.46156 16.307598,8.1574354 0 0 1 16.19643,7.078638 16.307598,8.1574354 0 0 1 16.08274,-7.078638 16.307598,8.1574354 0 0 1 16.2972,7.333403 C 181.28843,49.988222 140.45916,5.8009387 91.05284,5.9650105 Z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="400mm"
|
||||
height="200mm"
|
||||
viewBox="0 0 400 199.99999"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
id="layer1">
|
||||
<g
|
||||
aria-label="My"
|
||||
id="text11"
|
||||
style="font-size:128.862px;line-height:1.25;font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';letter-spacing:0px;word-spacing:0px;fill:#ffffff;stroke-width:3.22156">
|
||||
<path
|
||||
d="M 248.93135,36.371491 V 99.292389 H 223.76299 V 17.495221 H 324.43643 V 99.292389 H 299.26807 V 36.371491 H 286.68389 V 99.292389 H 261.51553 V 36.371491 Z"
|
||||
id="path3197"
|
||||
style="fill:#ffffff" />
|
||||
<path
|
||||
d="M 349.60479,67.83194 H 330.72852 V 17.495221 h 25.16836 V 48.95567 h 12.58418 V 17.495221 h 25.16836 V 67.83194 h -18.87627 v 31.460449 h -25.16836 z"
|
||||
id="path3199"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
<g
|
||||
aria-label="repo pins"
|
||||
id="text19"
|
||||
style="font-size:67.5452px;line-height:1.25;font-family:Helvetica;-inkscape-font-specification:Helvetica;letter-spacing:0px;word-spacing:0px;fill:#ffffff;stroke-width:1.68863">
|
||||
<path
|
||||
d="m 122.94092,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 6.59621,16.49053 h -6.59621 v 16.49052 H 109.7485 v -42.87537 h 32.98105 v 16.49053 l -4.94715,4.94716 4.94715,4.94716 v 16.49052 h -13.19242 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#ffffff"
|
||||
id="path3202" />
|
||||
<path
|
||||
d="m 159.22008,151.79952 h 19.78863 v 9.89431 h -32.98105 v -42.87537 h 32.98105 v 9.89432 h -19.78863 v 6.59621 h 19.78863 v 9.89432 h -19.78863 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#ffffff"
|
||||
id="path3204" />
|
||||
<path
|
||||
d="m 195.49924,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 0,16.49053 v 16.49052 h -13.19242 v -42.87537 h 32.98105 v 26.38485 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#ffffff"
|
||||
id="path3206" />
|
||||
<path
|
||||
d="m 231.7784,151.79952 h 6.59621 v -23.08674 h -6.59621 z m -13.19242,9.89431 v -42.87537 h 32.98105 v 42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#ffffff"
|
||||
id="path3208" />
|
||||
<path
|
||||
d="m 284.81194,128.71278 v 6.59621 h 6.59621 v -6.59621 z m 0,16.49053 v 16.49052 h -13.19242 v -42.87537 h 32.98105 v 26.38485 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#ffffff"
|
||||
id="path3210" />
|
||||
<path
|
||||
d="m 321.0911,118.81846 v 42.87537 h -13.19242 v -42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#ffffff"
|
||||
id="path3212" />
|
||||
<path
|
||||
d="m 357.37026,118.81846 v 42.87537 h -13.19243 v -32.98105 h -6.59621 v 32.98105 H 324.3892 v -42.87537 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#ffffff"
|
||||
id="path3214" />
|
||||
<path
|
||||
d="m 380.45699,128.71278 h -6.59621 v 6.59621 h 19.78864 v 26.38484 h -32.98106 v -13.19242 h 13.19242 v 3.29811 h 6.59621 v -6.59621 h -19.78863 v -26.38485 h 32.98106 v 13.19242 h -13.19243 z"
|
||||
style="font-family:'Robot Crush';-inkscape-font-specification:'Robot Crush';fill:#ffffff"
|
||||
id="path3216" />
|
||||
</g>
|
||||
<path
|
||||
id="path3000"
|
||||
style="fill:#ffffff;stroke-width:4.19701;stroke-linecap:square;stroke-linejoin:bevel"
|
||||
d="M 91.05284,5.9650105 C 41.866279,6.1312243 2.5722989,50.262466 2.0029785,83.050227 a 16.307598,8.1574354 0 0 1 16.1152955,-7.525639 16.307598,8.1574354 0 0 1 16.213481,7.172689 16.307598,8.1574354 0 0 1 16.101342,-7.172689 16.307598,8.1574354 0 0 1 16.388664,8.102348 l 5.17e-4,0.05478 a 16.307598,8.1574354 0 0 1 -0.0098,0.142627 h 0.268201 a 16.442225,8.1574354 0 0 1 -0.0062,-0.08733 l -5.17e-4,-0.05478 a 16.442225,8.1574354 0 0 1 16.061035,-8.15144 V 91.83264 a 16.442225,8.1574354 0 0 1 -0.0057,0 v 106.60795 h 17.792713 1.81642 8.46667 14.62598 v -13.2333 -10.19731 h -9.74153 v 10.19731 h -4.88445 -8.46667 -1.81642 V 91.83212 a 16.94002,8.1574354 0 0 1 -0.0413,10e-4 V 75.531306 a 16.94002,8.1574354 0 0 1 16.42226,7.454842 16.307598,8.1574354 0 0 1 16.15818,-7.46156 16.307598,8.1574354 0 0 1 16.19643,7.078638 16.307598,8.1574354 0 0 1 16.08274,-7.078638 16.307598,8.1574354 0 0 1 16.2972,7.333403 C 181.28843,49.988222 140.45916,5.8009387 91.05284,5.9650105 Z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
356
h-tests.el
356
h-tests.el
|
@ -1,356 +0,0 @@
|
|||
;;; h-tests.el --- Project navigation and remote checkout -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Copyright (C) 2022 Félix Baylac Jacqué
|
||||
;;; Author: Félix Baylac Jacqué <felix at alternativebit.fr>
|
||||
;;; Maintainer: Félix Baylac Jacqué <felix at alternativebit.fr>
|
||||
;;; Version: 1.14.0
|
||||
|
||||
;;; License:
|
||||
|
||||
;;; This program is free software; you can redistribute it and/or modify
|
||||
;;; it under the terms of the GNU General Public License as published by
|
||||
;;; the Free Software Foundation, either version 3 of the License, or
|
||||
;;; (at your option) any later version.
|
||||
|
||||
;;; This program is distributed in the hope that it will be useful,
|
||||
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;;; GNU General Public License for more details.
|
||||
|
||||
;;; You should have received a copy of the GNU General Public License
|
||||
;;; along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; TODO before publish
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'h)
|
||||
|
||||
;; Test Helpers
|
||||
;;;;;;;;;;;;;;
|
||||
|
||||
(defun h--tests-with-temp-dir (func)
|
||||
"Run the FUNC function in a temporary directory.
|
||||
|
||||
FUNC gets called with the temp dir as parameter.
|
||||
The directory gets deleted once we exit FUNC."
|
||||
(let ((temp-dir (make-temp-file "h-test-" t)))
|
||||
(unwind-protect
|
||||
(funcall func (file-name-as-directory temp-dir))
|
||||
(delete-directory temp-dir t))))
|
||||
|
||||
(defun h--tests-init-fake-git-repo (dir)
|
||||
"Create a dummy git repo at DIR.
|
||||
|
||||
If DIR doesn't exists, we create it first."
|
||||
(let* ((d (file-name-as-directory dir))
|
||||
(git-process
|
||||
(progn
|
||||
(make-directory d t)
|
||||
(h--call-git-in-dir d
|
||||
nil
|
||||
"init"))))
|
||||
(progn
|
||||
(unless (file-directory-p d) (make-directory d t))
|
||||
;; ERT does not handle async processes gracefully for the time
|
||||
;; being. Blocking and waiting for the git process to exit
|
||||
;; before moving on.
|
||||
(while (accept-process-output git-process)))))
|
||||
|
||||
;; Test Dirs Setup
|
||||
;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun h--tests-run-on-testroot-1 (func)
|
||||
"Run the FUNC function on testroot1.
|
||||
|
||||
FUNC is called with the directory cotaining test root 1 as parameter.
|
||||
|
||||
For reference: test-root-1 looks like this:
|
||||
test-root-1
|
||||
├── example1.tld
|
||||
│ ├── user1
|
||||
│ │ ├── proj1
|
||||
│ │ └── proj2
|
||||
│ └── user2
|
||||
│ └── proj1
|
||||
└── example2.tld
|
||||
└── user1
|
||||
└── proj1"
|
||||
(h--tests-with-temp-dir
|
||||
(lambda (temp-dir)
|
||||
(progn
|
||||
(h--tests-init-fake-git-repo (concat temp-dir "example1.tld/user1/proj1"))
|
||||
(h--tests-init-fake-git-repo (concat temp-dir "example1.tld/user1/proj2"))
|
||||
(h--tests-init-fake-git-repo (concat temp-dir "example1.tld/user2/proj1"))
|
||||
(h--tests-init-fake-git-repo (concat temp-dir "example2.tld/user1/proj1"))
|
||||
(funcall func temp-dir)
|
||||
))))
|
||||
|
||||
(defun h--tests-run-on-testroot-2 (func)
|
||||
"Run the FUNC function on testroot2.
|
||||
|
||||
FUNC is called with the directory cotaining test root 2 as parameter.
|
||||
|
||||
For reference: test-root-2 looks like this:
|
||||
test-root-2
|
||||
├── example1.tld
|
||||
│ ├── user1
|
||||
│ │ ├── proj1
|
||||
│ │ └── proj2 (NOT A GIT REPO)
|
||||
│ └── user2
|
||||
│ └── proj1
|
||||
└── example2.tld
|
||||
└── user1
|
||||
└── proj1"
|
||||
(h--tests-with-temp-dir
|
||||
(lambda (temp-dir)
|
||||
(progn
|
||||
(h--tests-init-fake-git-repo (concat temp-dir "example1.tld/user1/proj1"))
|
||||
(make-directory (concat (file-name-as-directory temp-dir) "example1.tld/user1/proj2"))
|
||||
(h--tests-init-fake-git-repo (concat temp-dir "example1.tld/user2/proj1"))
|
||||
(h--tests-init-fake-git-repo (concat temp-dir "example2.tld/user1/proj1"))
|
||||
(funcall func temp-dir)))))
|
||||
|
||||
|
||||
(defun h--tests-run-on-empty-testroot (func)
|
||||
"Run the FUNC function on testroot1.
|
||||
|
||||
FUNC is called with a empty test root.
|
||||
|
||||
For reference: a empty test root looks like this:
|
||||
test-root"
|
||||
(h--tests-with-temp-dir
|
||||
(lambda (temp-dir)
|
||||
(progn
|
||||
(funcall func temp-dir)))))
|
||||
|
||||
;; Tests
|
||||
;;;;;;;
|
||||
|
||||
(ert-deftest h--tests-get-code-root-projects-coderoot-1 ()
|
||||
"Test the `h--get-code-root-projects with test-root-1 setup."
|
||||
(let
|
||||
((results
|
||||
(h--tests-run-on-testroot-1 (lambda (root) (h--get-code-root-projects root))))
|
||||
)
|
||||
(should (member "example1.tld/user1/proj1" results))
|
||||
(should (member "example1.tld/user1/proj2" results))
|
||||
(should (member "example1.tld/user2/proj1" results))
|
||||
(should (member "example2.tld/user1/proj1" results))
|
||||
(should (eq (length results) 4))))
|
||||
|
||||
|
||||
(ert-deftest h--tests-find-git-dirs-recursively-coderoot-1 ()
|
||||
"Test the `h--get-code-root-projects with test-root-1 setup."
|
||||
(let*
|
||||
((r nil)
|
||||
(results
|
||||
(h--tests-run-on-testroot-1
|
||||
(lambda (root)
|
||||
(progn (setq r root)
|
||||
(h--find-git-dirs-recursively root))))))
|
||||
(should (member (concat r "example1.tld/user1/proj1/") results))
|
||||
(should (member (concat r "example1.tld/user1/proj2/") results))
|
||||
(should (member (concat r "example1.tld/user2/proj1/") results))
|
||||
(should (member (concat r "example2.tld/user1/proj1/") results))
|
||||
(should (eq (length results) 4))))
|
||||
|
||||
(ert-deftest h--tests-get-code-root-projects-coderoot-2 ()
|
||||
"Test the `h--get-code-root-projects with test-root-2 setup."
|
||||
(let
|
||||
((results
|
||||
(h--tests-run-on-testroot-2 (lambda (root) (h--get-code-root-projects root))))
|
||||
)
|
||||
(should (member "example1.tld/user1/proj1" results))
|
||||
(should (member "example1.tld/user2/proj1" results))
|
||||
(should (member "example2.tld/user1/proj1" results))
|
||||
(should (eq (length results) 3))))
|
||||
|
||||
(ert-deftest h--tests-find-git-dirs-recursively-coderoot-2 ()
|
||||
"Test the `h--get-code-root-projects with test-root-2 setup."
|
||||
(let*
|
||||
((r nil)
|
||||
(results
|
||||
(h--tests-run-on-testroot-2
|
||||
(lambda (root)
|
||||
(progn (setq r root)
|
||||
(h--find-git-dirs-recursively root))))))
|
||||
(should (member (concat r "example1.tld/user1/proj1/") results))
|
||||
(should (member (concat r "example1.tld/user2/proj1/") results))
|
||||
(should (member (concat r "example2.tld/user1/proj1/") results))
|
||||
(should (eq (length results) 3))))
|
||||
|
||||
(ert-deftest h--tests-get-code-root-projects-empty-coderoot ()
|
||||
"Test the `h--get-code-root-projects with a empty coderoot."
|
||||
(let
|
||||
((results
|
||||
(h--tests-run-on-empty-testroot (lambda (root) (h--get-code-root-projects root))))
|
||||
)
|
||||
(should (seq-empty-p results))))
|
||||
|
||||
(ert-deftest h--tests-find-git-dirs-recursively-empty-coderoot ()
|
||||
"Test the `h--get-code-root-projects with a empty coderoot."
|
||||
(let
|
||||
((results
|
||||
(h--tests-run-on-empty-testroot (lambda (root) (h--find-git-dirs-recursively root))))
|
||||
)
|
||||
(should (seq-empty-p results))))
|
||||
|
||||
(ert-deftest h--tests-get-code-root-projects-no-coderoot ()
|
||||
"Test the `h--get-code-root-projects with a non-existing coderoot."
|
||||
(let
|
||||
((results (h--get-code-root-projects "/does/not/exist")))
|
||||
(should (seq-empty-p results))))
|
||||
|
||||
|
||||
;; Test Fetchers
|
||||
;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Github
|
||||
|
||||
(ert-deftest h--tests-fetch-github-parse-response-ok ()
|
||||
"Test h--tests-fetch-github-parse-response with a fixture."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "./tests/fixtures/github-get-request-ok.txt")
|
||||
(should (equal (h--fetch-github-parse-response (current-buffer))
|
||||
'((ssh . "git@github.com:NinjaTrappeur/h.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/h.el.git"))))))
|
||||
|
||||
(ert-deftest h--tests-fetch-github-parse-response-ko ()
|
||||
"Test h--tests-fetch-github-parse-response with a fixture."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "./tests/fixtures/github-get-request-ko.txt")
|
||||
(should (equal (h--fetch-github-parse-response (current-buffer)) nil))))
|
||||
|
||||
;; Gitea
|
||||
|
||||
(ert-deftest h--tests-fetch-gitea-parse-response-ok ()
|
||||
"Test h--tests-fetch-gitea-parse-response with a fixture."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "./tests/fixtures/gitea-get-request-ok.txt")
|
||||
(should (equal (h--fetch-gitea-parse-response (current-buffer))
|
||||
'((ssh . "gitea@git.alternativebit.fr:NinjaTrappeur/h.el.git")
|
||||
(https . "https://git.alternativebit.fr/NinjaTrappeur/h.el.git"))))))
|
||||
|
||||
(ert-deftest h--tests-fetch-gitea-parse-response-ko ()
|
||||
"Test h--tests-fetch-gitea-parse-response with a fixture."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "./tests/fixtures/gitea-get-request-ko.txt")
|
||||
(should (equal (h--fetch-gitea-parse-response (current-buffer)) nil))))
|
||||
|
||||
;; Test repo URI parser
|
||||
;;;;;;;;;;;;;;;;;
|
||||
|
||||
(ert-deftest h--test-parse-repo-identifier ()
|
||||
"Test h--parse-repo-identifier."
|
||||
(should (equal
|
||||
(h--parse-repo-identifier "https://github.com/Ninjatrappeur/h.el")
|
||||
'((tag . full-url) (full-url . "https://github.com/Ninjatrappeur/h.el"))))
|
||||
(should (equal
|
||||
(h--parse-repo-identifier "github.com/Ninjatrappeur/h.el")
|
||||
'((tag . full-url) (full-url . "github.com/Ninjatrappeur/h.el"))))
|
||||
(should (equal
|
||||
(h--parse-repo-identifier "Ninjatrappeur/h.el")
|
||||
'((tag . owner-repo) (owner . "Ninjatrappeur") (repo . "h.el"))))
|
||||
(should (equal
|
||||
(h--parse-repo-identifier "h.el")
|
||||
'((tag . repo) (repo . "h.el")))))
|
||||
|
||||
(ert-deftest h--test-filepath-from-clone-url ()
|
||||
"Test h--filepath-from-clone-url."
|
||||
;; HTTP/HTTPS
|
||||
(should (equal (h--filepath-from-clone-url "http://github.com/NinjaTrappeur/h.el.git") "github.com/NinjaTrappeur/h.el"))
|
||||
(should (equal (h--filepath-from-clone-url "http://github.com/NinjaTrappeur/h.el") "github.com/NinjaTrappeur/h.el"))
|
||||
(should (equal (h--filepath-from-clone-url "https://github.com/NinjaTrappeur/h.el.git") "github.com/NinjaTrappeur/h.el"))
|
||||
(should (equal (h--filepath-from-clone-url "https://github.com/NinjaTrappeur/h.el") "github.com/NinjaTrappeur/h.el"))
|
||||
(should (equal (h--filepath-from-clone-url "http://git.savannah.gnu.org/cgit/emacs/elpa.git") "git.savannah.gnu.org/cgit/emacs/elpa"))
|
||||
(should (equal (h--filepath-from-clone-url "https://git.savannah.gnu.org/git/emacs.git") "git.savannah.gnu.org/git/emacs"))
|
||||
;; SSH
|
||||
(should (equal (h--filepath-from-clone-url "ssh://git@github.com:NinjaTrappeur/h.el.git") "github.com/NinjaTrappeur/h.el"))
|
||||
(should (equal (h--filepath-from-clone-url "ssh://git@github.com:NinjaTrappeur/h.el") "github.com/NinjaTrappeur/h.el"))
|
||||
(should (equal (h--filepath-from-clone-url "git@github.com:NinjaTrappeur/h.el.git") "github.com/NinjaTrappeur/h.el"))
|
||||
(should (equal (h--filepath-from-clone-url "git@github.com:NinjaTrappeur/h.el") "github.com/NinjaTrappeur/h.el")))
|
||||
|
||||
(ert-deftest h--test-git-clone-in-dir ()
|
||||
"Test the h--git-clone-in-dir function."
|
||||
(h--tests-run-on-testroot-1
|
||||
(lambda (dir)
|
||||
(let*
|
||||
((tmpdir (make-temp-file "h-test-" t))
|
||||
(git-process (h--git-clone-in-dir
|
||||
(format "file://%s" (concat dir "example1.tld/user1/proj1/"))
|
||||
tmpdir)))
|
||||
(progn
|
||||
(while (accept-process-output git-process))
|
||||
(should (file-exists-p (format "%s/.git" tmpdir)))
|
||||
(delete-directory tmpdir t))))))
|
||||
|
||||
;;; State Management tests
|
||||
|
||||
(ert-deftest h--test-init-forges-state ()
|
||||
"Test the h--init-forges-state function."
|
||||
(let* ((forge-fetchers
|
||||
'(("GitHub.com" .
|
||||
((query-user-repo . h--query-github-owner-repo)))
|
||||
("GitLab.com" .
|
||||
((query-user-repo . (lambda (owner repo cb) (h--query-gitlab-owner-repo "gitlab.com" owner repo cb)))))
|
||||
("git.sr.ht" .
|
||||
((query-user-repo . (lambda (owner repo cb) (h--query-sourcehut-owner-repo "git.sr.ht" owner repo cb)))))
|
||||
("Codeberg.org" .
|
||||
((query-user-repo . (lambda (owner repo cb) (h--query-gitea-owner-repo "codeberg.org" owner repo cb)))))))
|
||||
(result (h--init-forges-state forge-fetchers)))
|
||||
(should (equal (alist-get "GitHub.com" result nil nil 'equal) 'loading))
|
||||
(should (equal (alist-get "GitLab.com" result nil nil 'equal) 'loading))
|
||||
(should (equal (alist-get "git.sr.ht" result nil nil 'equal) 'loading))
|
||||
(should (equal (alist-get "Codeberg.org" result nil nil 'equal) 'loading))))
|
||||
|
||||
;;; UI-related tests
|
||||
|
||||
(ert-deftest h--test-add-keys-to-forge-status ()
|
||||
"Test the h--add-keys-to-forge-status function."
|
||||
(let
|
||||
((dummy-forge-query-status-one-result
|
||||
'(("GitHub"
|
||||
(ssh . "git@github.com:NinjaTrappeur/h.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/h.el.git"))
|
||||
("GitLab" . not-found)))
|
||||
(expected-forge-query-status-with-keys-one-result
|
||||
`(("GitHub"
|
||||
(status
|
||||
(ssh . "git@github.com:NinjaTrappeur/h.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/h.el.git"))
|
||||
(key . ,?1))
|
||||
("GitLab" (status . not-found))))
|
||||
(dummy-forge-query-status-two-results
|
||||
'(("GitHub"
|
||||
(ssh . "git@github.com:NinjaTrappeur/h.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/h.el.git"))
|
||||
("Codeberg" . not-found)
|
||||
("GitLab"
|
||||
(ssh . "git@gitlab.com:NinjaTrappeur/h.el.git")
|
||||
(https . "https://gitlab.com/NinjaTrappeur/h.el.git"))))
|
||||
(expected-forge-query-status-with-keys-two-results
|
||||
`(("GitHub"
|
||||
(status
|
||||
(ssh . "git@github.com:NinjaTrappeur/h.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/h.el.git"))
|
||||
(key . ,'?1))
|
||||
("Codeberg" (status . not-found))
|
||||
("GitLab"
|
||||
(status
|
||||
(ssh . "git@gitlab.com:NinjaTrappeur/h.el.git")
|
||||
(https . "https://gitlab.com/NinjaTrappeur/h.el.git"))
|
||||
(key . ,'?2)))))
|
||||
|
||||
(should (equal
|
||||
expected-forge-query-status-with-keys-one-result
|
||||
(h--add-keys-to-forge-status dummy-forge-query-status-one-result)))
|
||||
(should (equal
|
||||
expected-forge-query-status-with-keys-two-results
|
||||
(h--add-keys-to-forge-status dummy-forge-query-status-two-results)))))
|
||||
|
||||
(provide 'h-tests)
|
||||
;;; h-tests.el ends here
|
|
@ -0,0 +1,356 @@
|
|||
;;; my-repo-pins-tests.el --- Project navigation and remote checkout -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Copyright (C) 2022 Félix Baylac Jacqué
|
||||
;;; Author: Félix Baylac Jacqué <felix at alternativebit.fr>
|
||||
;;; Maintainer: Félix Baylac Jacqué <felix at alternativebit.fr>
|
||||
;;; Version: 1.14.0
|
||||
|
||||
;;; License:
|
||||
|
||||
;;; This program is free software; you can redistribute it and/or modify
|
||||
;;; it under the terms of the GNU General Public License as published by
|
||||
;;; the Free Software Foundation, either version 3 of the License, or
|
||||
;;; (at your option) any later version.
|
||||
|
||||
;;; This program is distributed in the hope that it will be useful,
|
||||
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;;; GNU General Public License for more details.
|
||||
|
||||
;;; You should have received a copy of the GNU General Public License
|
||||
;;; along with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; TODO before publish
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'my-repo-pins)
|
||||
|
||||
;; Test Helpers
|
||||
;;;;;;;;;;;;;;
|
||||
|
||||
(defun my-repo-pins--tests-with-temp-dir (func)
|
||||
"Run the FUNC function in a temporary directory.
|
||||
|
||||
FUNC gets called with the temp dir as parameter.
|
||||
The directory gets deleted once we exit FUNC."
|
||||
(let ((temp-dir (make-temp-file "my-repo-pins-test-" t)))
|
||||
(unwind-protect
|
||||
(funcall func (file-name-as-directory temp-dir))
|
||||
(delete-directory temp-dir t))))
|
||||
|
||||
(defun my-repo-pins--tests-init-fake-git-repo (dir)
|
||||
"Create a dummy git repo at DIR.
|
||||
|
||||
If DIR doesn't exists, we create it first."
|
||||
(let* ((d (file-name-as-directory dir))
|
||||
(git-process
|
||||
(progn
|
||||
(make-directory d t)
|
||||
(my-repo-pins--call-git-in-dir d
|
||||
nil
|
||||
"init"))))
|
||||
(progn
|
||||
(unless (file-directory-p d) (make-directory d t))
|
||||
;; ERT does not handle async processes gracefully for the time
|
||||
;; being. Blocking and waiting for the git process to exit
|
||||
;; before moving on.
|
||||
(while (accept-process-output git-process)))))
|
||||
|
||||
;; Test Dirs Setup
|
||||
;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun my-repo-pins--tests-run-on-testroot-1 (func)
|
||||
"Run the FUNC function on testroot1.
|
||||
|
||||
FUNC is called with the directory cotaining test root 1 as parameter.
|
||||
|
||||
For reference: test-root-1 looks like this:
|
||||
test-root-1
|
||||
├── example1.tld
|
||||
│ ├── user1
|
||||
│ │ ├── proj1
|
||||
│ │ └── proj2
|
||||
│ └── user2
|
||||
│ └── proj1
|
||||
└── example2.tld
|
||||
└── user1
|
||||
└── proj1"
|
||||
(my-repo-pins--tests-with-temp-dir
|
||||
(lambda (temp-dir)
|
||||
(progn
|
||||
(my-repo-pins--tests-init-fake-git-repo (concat temp-dir "example1.tld/user1/proj1"))
|
||||
(my-repo-pins--tests-init-fake-git-repo (concat temp-dir "example1.tld/user1/proj2"))
|
||||
(my-repo-pins--tests-init-fake-git-repo (concat temp-dir "example1.tld/user2/proj1"))
|
||||
(my-repo-pins--tests-init-fake-git-repo (concat temp-dir "example2.tld/user1/proj1"))
|
||||
(funcall func temp-dir)
|
||||
))))
|
||||
|
||||
(defun my-repo-pins--tests-run-on-testroot-2 (func)
|
||||
"Run the FUNC function on testroot2.
|
||||
|
||||
FUNC is called with the directory cotaining test root 2 as parameter.
|
||||
|
||||
For reference: test-root-2 looks like this:
|
||||
test-root-2
|
||||
├── example1.tld
|
||||
│ ├── user1
|
||||
│ │ ├── proj1
|
||||
│ │ └── proj2 (NOT A GIT REPO)
|
||||
│ └── user2
|
||||
│ └── proj1
|
||||
└── example2.tld
|
||||
└── user1
|
||||
└── proj1"
|
||||
(my-repo-pins--tests-with-temp-dir
|
||||
(lambda (temp-dir)
|
||||
(progn
|
||||
(my-repo-pins--tests-init-fake-git-repo (concat temp-dir "example1.tld/user1/proj1"))
|
||||
(make-directory (concat (file-name-as-directory temp-dir) "example1.tld/user1/proj2"))
|
||||
(my-repo-pins--tests-init-fake-git-repo (concat temp-dir "example1.tld/user2/proj1"))
|
||||
(my-repo-pins--tests-init-fake-git-repo (concat temp-dir "example2.tld/user1/proj1"))
|
||||
(funcall func temp-dir)))))
|
||||
|
||||
|
||||
(defun my-repo-pins--tests-run-on-empty-testroot (func)
|
||||
"Run the FUNC function on testroot1.
|
||||
|
||||
FUNC is called with a empty test root.
|
||||
|
||||
For reference: a empty test root looks like this:
|
||||
test-root"
|
||||
(my-repo-pins--tests-with-temp-dir
|
||||
(lambda (temp-dir)
|
||||
(progn
|
||||
(funcall func temp-dir)))))
|
||||
|
||||
;; Tests
|
||||
;;;;;;;
|
||||
|
||||
(ert-deftest my-repo-pins--tests-get-code-root-projects-coderoot-1 ()
|
||||
"Test the `my-repo-pins--get-code-root-projects with test-root-1 setup."
|
||||
(let
|
||||
((results
|
||||
(my-repo-pins--tests-run-on-testroot-1 (lambda (root) (my-repo-pins--get-code-root-projects root))))
|
||||
)
|
||||
(should (member "example1.tld/user1/proj1" results))
|
||||
(should (member "example1.tld/user1/proj2" results))
|
||||
(should (member "example1.tld/user2/proj1" results))
|
||||
(should (member "example2.tld/user1/proj1" results))
|
||||
(should (eq (length results) 4))))
|
||||
|
||||
|
||||
(ert-deftest my-repo-pins--tests-find-git-dirs-recursively-coderoot-1 ()
|
||||
"Test the `my-repo-pins--get-code-root-projects with test-root-1 setup."
|
||||
(let*
|
||||
((r nil)
|
||||
(results
|
||||
(my-repo-pins--tests-run-on-testroot-1
|
||||
(lambda (root)
|
||||
(progn (setq r root)
|
||||
(my-repo-pins--find-git-dirs-recursively root))))))
|
||||
(should (member (concat r "example1.tld/user1/proj1/") results))
|
||||
(should (member (concat r "example1.tld/user1/proj2/") results))
|
||||
(should (member (concat r "example1.tld/user2/proj1/") results))
|
||||
(should (member (concat r "example2.tld/user1/proj1/") results))
|
||||
(should (eq (length results) 4))))
|
||||
|
||||
(ert-deftest my-repo-pins--tests-get-code-root-projects-coderoot-2 ()
|
||||
"Test the `my-repo-pins--get-code-root-projects with test-root-2 setup."
|
||||
(let
|
||||
((results
|
||||
(my-repo-pins--tests-run-on-testroot-2 (lambda (root) (my-repo-pins--get-code-root-projects root))))
|
||||
)
|
||||
(should (member "example1.tld/user1/proj1" results))
|
||||
(should (member "example1.tld/user2/proj1" results))
|
||||
(should (member "example2.tld/user1/proj1" results))
|
||||
(should (eq (length results) 3))))
|
||||
|
||||
(ert-deftest my-repo-pins--tests-find-git-dirs-recursively-coderoot-2 ()
|
||||
"Test the `my-repo-pins--get-code-root-projects with test-root-2 setup."
|
||||
(let*
|
||||
((r nil)
|
||||
(results
|
||||
(my-repo-pins--tests-run-on-testroot-2
|
||||
(lambda (root)
|
||||
(progn (setq r root)
|
||||
(my-repo-pins--find-git-dirs-recursively root))))))
|
||||
(should (member (concat r "example1.tld/user1/proj1/") results))
|
||||
(should (member (concat r "example1.tld/user2/proj1/") results))
|
||||
(should (member (concat r "example2.tld/user1/proj1/") results))
|
||||
(should (eq (length results) 3))))
|
||||
|
||||
(ert-deftest my-repo-pins--tests-get-code-root-projects-empty-coderoot ()
|
||||
"Test the `my-repo-pins--get-code-root-projects with a empty coderoot."
|
||||
(let
|
||||
((results
|
||||
(my-repo-pins--tests-run-on-empty-testroot (lambda (root) (my-repo-pins--get-code-root-projects root))))
|
||||
)
|
||||
(should (seq-empty-p results))))
|
||||
|
||||
(ert-deftest my-repo-pins--tests-find-git-dirs-recursively-empty-coderoot ()
|
||||
"Test the `my-repo-pins--get-code-root-projects with a empty coderoot."
|
||||
(let
|
||||
((results
|
||||
(my-repo-pins--tests-run-on-empty-testroot (lambda (root) (my-repo-pins--find-git-dirs-recursively root))))
|
||||
)
|
||||
(should (seq-empty-p results))))
|
||||
|
||||
(ert-deftest my-repo-pins--tests-get-code-root-projects-no-coderoot ()
|
||||
"Test the `my-repo-pins--get-code-root-projects with a non-existing coderoot."
|
||||
(let
|
||||
((results (my-repo-pins--get-code-root-projects "/does/not/exist")))
|
||||
(should (seq-empty-p results))))
|
||||
|
||||
|
||||
;; Test Fetchers
|
||||
;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Github
|
||||
|
||||
(ert-deftest my-repo-pins--tests-fetch-github-parse-response-ok ()
|
||||
"Test my-repo-pins--tests-fetch-github-parse-response with a fixture."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "./tests/fixtures/github-get-request-ok.txt")
|
||||
(should (equal (my-repo-pins--fetch-github-parse-response (current-buffer))
|
||||
'((ssh . "git@github.com:NinjaTrappeur/my-repo-pins.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/my-repo-pins.el.git"))))))
|
||||
|
||||
(ert-deftest my-repo-pins--tests-fetch-github-parse-response-ko ()
|
||||
"Test my-repo-pins--tests-fetch-github-parse-response with a fixture."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "./tests/fixtures/github-get-request-ko.txt")
|
||||
(should (equal (my-repo-pins--fetch-github-parse-response (current-buffer)) nil))))
|
||||
|
||||
;; Gitea
|
||||
|
||||
(ert-deftest my-repo-pins--tests-fetch-gitea-parse-response-ok ()
|
||||
"Test my-repo-pins--tests-fetch-gitea-parse-response with a fixture."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "./tests/fixtures/gitea-get-request-ok.txt")
|
||||
(should (equal (my-repo-pins--fetch-gitea-parse-response (current-buffer))
|
||||
'((ssh . "gitea@git.alternativebit.fr:NinjaTrappeur/my-repo-pins.el.git")
|
||||
(https . "https://git.alternativebit.fr/NinjaTrappeur/my-repo-pins.el.git"))))))
|
||||
|
||||
(ert-deftest my-repo-pins--tests-fetch-gitea-parse-response-ko ()
|
||||
"Test my-repo-pins--tests-fetch-gitea-parse-response with a fixture."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents "./tests/fixtures/gitea-get-request-ko.txt")
|
||||
(should (equal (my-repo-pins--fetch-gitea-parse-response (current-buffer)) nil))))
|
||||
|
||||
;; Test repo URI parser
|
||||
;;;;;;;;;;;;;;;;;
|
||||
|
||||
(ert-deftest my-repo-pins--test-parse-repo-identifier ()
|
||||
"Test my-repo-pins--parse-repo-identifier."
|
||||
(should (equal
|
||||
(my-repo-pins--parse-repo-identifier "https://github.com/Ninjatrappeur/my-repo-pins.el")
|
||||
'((tag . full-url) (full-url . "https://github.com/Ninjatrappeur/my-repo-pins.el"))))
|
||||
(should (equal
|
||||
(my-repo-pins--parse-repo-identifier "github.com/Ninjatrappeur/my-repo-pins.el")
|
||||
'((tag . full-url) (full-url . "github.com/Ninjatrappeur/my-repo-pins.el"))))
|
||||
(should (equal
|
||||
(my-repo-pins--parse-repo-identifier "Ninjatrappeur/my-repo-pins.el")
|
||||
'((tag . owner-repo) (owner . "Ninjatrappeur") (repo . "my-repo-pins.el"))))
|
||||
(should (equal
|
||||
(my-repo-pins--parse-repo-identifier "my-repo-pins.el")
|
||||
'((tag . repo) (repo . "my-repo-pins.el")))))
|
||||
|
||||
(ert-deftest my-repo-pins--test-filepath-from-clone-url ()
|
||||
"Test my-repo-pins--filepath-from-clone-url."
|
||||
;; HTTP/HTTPS
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "http://github.com/NinjaTrappeur/my-repo-pins.el.git") "github.com/NinjaTrappeur/my-repo-pins.el"))
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "http://github.com/NinjaTrappeur/my-repo-pins.el") "github.com/NinjaTrappeur/my-repo-pins.el"))
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "https://github.com/NinjaTrappeur/my-repo-pins.el.git") "github.com/NinjaTrappeur/my-repo-pins.el"))
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "https://github.com/NinjaTrappeur/my-repo-pins.el") "github.com/NinjaTrappeur/my-repo-pins.el"))
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "http://git.savannah.gnu.org/cgit/emacs/elpa.git") "git.savannah.gnu.org/cgit/emacs/elpa"))
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "https://git.savannah.gnu.org/git/emacs.git") "git.savannah.gnu.org/git/emacs"))
|
||||
;; SSH
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "ssh://git@github.com:NinjaTrappeur/my-repo-pins.el.git") "github.com/NinjaTrappeur/my-repo-pins.el"))
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "ssh://git@github.com:NinjaTrappeur/my-repo-pins.el") "github.com/NinjaTrappeur/my-repo-pins.el"))
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "git@github.com:NinjaTrappeur/my-repo-pins.el.git") "github.com/NinjaTrappeur/my-repo-pins.el"))
|
||||
(should (equal (my-repo-pins--filepath-from-clone-url "git@github.com:NinjaTrappeur/my-repo-pins.el") "github.com/NinjaTrappeur/my-repo-pins.el")))
|
||||
|
||||
(ert-deftest my-repo-pins--test-git-clone-in-dir ()
|
||||
"Test the my-repo-pins--git-clone-in-dir function."
|
||||
(my-repo-pins--tests-run-on-testroot-1
|
||||
(lambda (dir)
|
||||
(let*
|
||||
((tmpdir (make-temp-file "my-repo-pins-test-" t))
|
||||
(git-process (my-repo-pins--git-clone-in-dir
|
||||
(format "file://%s" (concat dir "example1.tld/user1/proj1/"))
|
||||
tmpdir)))
|
||||
(progn
|
||||
(while (accept-process-output git-process))
|
||||
(should (file-exists-p (format "%s/.git" tmpdir)))
|
||||
(delete-directory tmpdir t))))))
|
||||
|
||||
;;; State Management tests
|
||||
|
||||
(ert-deftest my-repo-pins--test-init-forges-state ()
|
||||
"Test the my-repo-pins--init-forges-state function."
|
||||
(let* ((forge-fetchers
|
||||
'(("GitHub.com" .
|
||||
((query-user-repo . my-repo-pins--query-github-owner-repo)))
|
||||
("GitLab.com" .
|
||||
((query-user-repo . (lambda (owner repo cb) (my-repo-pins--query-gitlab-owner-repo "gitlab.com" owner repo cb)))))
|
||||
("git.sr.ht" .
|
||||
((query-user-repo . (lambda (owner repo cb) (my-repo-pins--query-sourcehut-owner-repo "git.sr.ht" owner repo cb)))))
|
||||
("Codeberg.org" .
|
||||
((query-user-repo . (lambda (owner repo cb) (my-repo-pins--query-gitea-owner-repo "codeberg.org" owner repo cb)))))))
|
||||
(result (my-repo-pins--init-forges-state forge-fetchers)))
|
||||
(should (equal (alist-get "GitHub.com" result nil nil 'equal) 'loading))
|
||||
(should (equal (alist-get "GitLab.com" result nil nil 'equal) 'loading))
|
||||
(should (equal (alist-get "git.sr.ht" result nil nil 'equal) 'loading))
|
||||
(should (equal (alist-get "Codeberg.org" result nil nil 'equal) 'loading))))
|
||||
|
||||
;;; UI-related tests
|
||||
|
||||
(ert-deftest my-repo-pins--test-add-keys-to-forge-status ()
|
||||
"Test the my-repo-pins--add-keys-to-forge-status function."
|
||||
(let
|
||||
((dummy-forge-query-status-one-result
|
||||
'(("GitHub"
|
||||
(ssh . "git@github.com:NinjaTrappeur/my-repo-pins.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/my-repo-pins.el.git"))
|
||||
("GitLab" . not-found)))
|
||||
(expected-forge-query-status-with-keys-one-result
|
||||
`(("GitHub"
|
||||
(status
|
||||
(ssh . "git@github.com:NinjaTrappeur/my-repo-pins.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/my-repo-pins.el.git"))
|
||||
(key . ,?1))
|
||||
("GitLab" (status . not-found))))
|
||||
(dummy-forge-query-status-two-results
|
||||
'(("GitHub"
|
||||
(ssh . "git@github.com:NinjaTrappeur/my-repo-pins.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/my-repo-pins.el.git"))
|
||||
("Codeberg" . not-found)
|
||||
("GitLab"
|
||||
(ssh . "git@gitlab.com:NinjaTrappeur/my-repo-pins.el.git")
|
||||
(https . "https://gitlab.com/NinjaTrappeur/my-repo-pins.el.git"))))
|
||||
(expected-forge-query-status-with-keys-two-results
|
||||
`(("GitHub"
|
||||
(status
|
||||
(ssh . "git@github.com:NinjaTrappeur/my-repo-pins.el.git")
|
||||
(https . "https://github.com/NinjaTrappeur/my-repo-pins.el.git"))
|
||||
(key . ,'?1))
|
||||
("Codeberg" (status . not-found))
|
||||
("GitLab"
|
||||
(status
|
||||
(ssh . "git@gitlab.com:NinjaTrappeur/my-repo-pins.el.git")
|
||||
(https . "https://gitlab.com/NinjaTrappeur/my-repo-pins.el.git"))
|
||||
(key . ,'?2)))))
|
||||
|
||||
(should (equal
|
||||
expected-forge-query-status-with-keys-one-result
|
||||
(my-repo-pins--add-keys-to-forge-status dummy-forge-query-status-one-result)))
|
||||
(should (equal
|
||||
expected-forge-query-status-with-keys-two-results
|
||||
(my-repo-pins--add-keys-to-forge-status dummy-forge-query-status-two-results)))))
|
||||
|
||||
(provide 'my-repo-pins-tests)
|
||||
;;; my-repo-pins-tests.el ends here
|
|
@ -1,10 +1,10 @@
|
|||
;;; h.el --- Helps you keep your git repositories organized -*- lexical-binding: t -*-
|
||||
;;; my-repo-pins.el --- Helps you keep your git repositories organized -*- lexical-binding: t -*-
|
||||
|
||||
;;; Copyright (C) 2022 Félix Baylac Jacqué
|
||||
;;; Author: Félix Baylac Jacqué <felix at alternativebit.fr>
|
||||
;;; Maintainer: Félix Baylac Jacqué <felix at alternativebit.fr>
|
||||
;;; Version: 0.1
|
||||
;;; Homepage: https://github.com/NinjaTrappeur/h.el
|
||||
;;; Homepage: https://github.com/NinjaTrappeur/my-repo-pins.el
|
||||
;;; Package-Requires: ((emacs "26.1"))
|
||||
;;; License:
|
||||
;;;
|
||||
|
@ -41,10 +41,10 @@
|
|||
;;; │ └── mpv
|
||||
;;; └── NinjaTrappeur
|
||||
;;; ├── cinny
|
||||
;;; └── h.el
|
||||
;;; └── my-repo-pins.el
|
||||
;;;
|
||||
;;; The main entry point of this package is the h-jump-to-project
|
||||
;;; command. Using it, you can either:
|
||||
;;; The main entry point of this package is the my-repo-pins command.
|
||||
;;; Using it, you can either:
|
||||
;;;
|
||||
;;; - Open dired in a local project you already cloned.
|
||||
;;; - Query remote forges for a repository, clone it, and finally open
|
||||
|
@ -53,16 +53,16 @@
|
|||
;;;
|
||||
;;; The minimal configuration consists in setting the directory in
|
||||
;;; which you want to clone all your git repositories via the
|
||||
;;; h-code-root variable.
|
||||
;;; my-repo-pins-code-root variable.
|
||||
;;;
|
||||
;;; Let's say you'd like to store all your git repositories in the
|
||||
;;; ~/code-root directory. You'll want to add the following snippet in
|
||||
;;; your Emacs configuration file:
|
||||
;;;
|
||||
;;; (require 'h)
|
||||
;;; (setq h-code-root "~/code-root")
|
||||
;;; (require 'my-repo-pins)
|
||||
;;; (setq my-repo-pins-code-root "~/code-root")
|
||||
;;;
|
||||
;;; You can then call the M-x h-jump-to-project command to open a
|
||||
;;; You can then call the M-x my-repo-pins command to open a
|
||||
;;; project living in your ~/code-root directory or clone a new
|
||||
;;; project in your code root.
|
||||
;;;
|
||||
|
@ -71,7 +71,7 @@
|
|||
;;; add the following snippet to your Emacs configuration to set up
|
||||
;;; this key binding:
|
||||
;;;
|
||||
;;; (global-set-key (kbd "M-h") 'h-jump-to-project)
|
||||
;;; (global-set-key (kbd "M-h") 'my-repo-pins)
|
||||
|
||||
;;; Code:
|
||||
|
||||
|
@ -82,40 +82,40 @@
|
|||
;; loaded by default in interactive emacs, not in batch-mode emacs.
|
||||
(eval-when-compile (require 'subr-x))
|
||||
|
||||
(defgroup h-group nil
|
||||
"Variables used to setup the h.el project manager."
|
||||
(defgroup my-repo-pins-group nil
|
||||
"Variables used to setup the my-repo-pins.el project manager."
|
||||
:group 'Communication)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Internal: git primitives
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defcustom h-git-bin "git"
|
||||
(defcustom my-repo-pins-git-bin "git"
|
||||
"Path pointing to the git binary.
|
||||
By default, it'll look for git in the current $PATH."
|
||||
:type 'file
|
||||
:group 'h-group)
|
||||
:group 'my-repo-pins-group)
|
||||
|
||||
(defun h--git-path ()
|
||||
"Find the git binary path using ‘h-git-bin’.
|
||||
(defun my-repo-pins--git-path ()
|
||||
"Find the git binary path using ‘my-repo-pins-git-bin’.
|
||||
|
||||
Errors out if we can't find it."
|
||||
(if (file-executable-p h-git-bin)
|
||||
h-git-bin
|
||||
(let ((git-from-bin-path (locate-file h-git-bin exec-path)))
|
||||
(if (file-executable-p my-repo-pins-git-bin)
|
||||
my-repo-pins-git-bin
|
||||
(let ((git-from-bin-path (locate-file my-repo-pins-git-bin exec-path)))
|
||||
(if (file-executable-p git-from-bin-path)
|
||||
git-from-bin-path
|
||||
(error "Can't find git. Is h-git-bin correctly set?")))))
|
||||
(error "Can't find git. Is my-repo-pins-git-bin correctly set?")))))
|
||||
|
||||
(defun h--call-git-in-dir (dir &optional callback &rest args)
|
||||
"Call the git binary as pointed by ‘h-git-bin’ in DIR with ARGS.
|
||||
(defun my-repo-pins--call-git-in-dir (dir &optional callback &rest args)
|
||||
"Call the git binary as pointed by ‘my-repo-pins-git-bin’ in DIR with ARGS.
|
||||
|
||||
Once the git subprocess exists, call CALLBACK with a the process exit
|
||||
code as single argument. If CALLBACK is set to nil, don't call any
|
||||
callback.
|
||||
|
||||
Returns the git PROCESS object."
|
||||
(let* ((git-buffer (get-buffer-create "*h git log*"))
|
||||
(let* ((git-buffer (get-buffer-create "*my repo pins git log*"))
|
||||
(git-window nil)
|
||||
(current-buffer (current-buffer))
|
||||
(git-sentinel (lambda
|
||||
|
@ -133,49 +133,49 @@ Returns the git PROCESS object."
|
|||
(setq git-window (display-buffer git-buffer))
|
||||
(prog1
|
||||
(make-process
|
||||
:name "h-git-subprocess"
|
||||
:name "my-repo-pins-git-subprocess"
|
||||
:buffer git-buffer
|
||||
:command (seq-concatenate 'list `(,(h--git-path)) args)
|
||||
:command (seq-concatenate 'list `(,(my-repo-pins--git-path)) args)
|
||||
:sentinel git-sentinel)
|
||||
(set-buffer current-buffer)))))
|
||||
|
||||
(defun h--git-clone-in-dir (clone-url checkout-filepath &optional callback)
|
||||
(defun my-repo-pins--git-clone-in-dir (clone-url checkout-filepath &optional callback)
|
||||
"Clone the CLONE-URL repo at CHECKOUT-FILEPATH.
|
||||
|
||||
Call CALLBACK with no arguments once the git subprocess exists."
|
||||
(h--call-git-in-dir "~/" callback "clone" clone-url checkout-filepath))
|
||||
(my-repo-pins--call-git-in-dir "~/" callback "clone" clone-url checkout-filepath))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Internal: builtin fetchers
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;; Generic fetcher infrastructure
|
||||
(defvar h--builtins-forge-fetchers
|
||||
(defvar my-repo-pins--builtins-forge-fetchers
|
||||
'(("GitHub.com" .
|
||||
((query-user-repo . h--query-github-owner-repo)))
|
||||
((query-user-repo . my-repo-pins--query-github-owner-repo)))
|
||||
("GitLab.com" .
|
||||
((query-user-repo . (lambda (owner repo cb) (h--query-gitlab-owner-repo "gitlab.com" owner repo cb)))))
|
||||
((query-user-repo . (lambda (owner repo cb) (my-repo-pins--query-gitlab-owner-repo "gitlab.com" owner repo cb)))))
|
||||
("git.sr.ht" .
|
||||
((query-user-repo . (lambda (owner repo cb) (h--query-sourcehut-owner-repo "git.sr.ht" owner repo cb)))))
|
||||
((query-user-repo . (lambda (owner repo cb) (my-repo-pins--query-sourcehut-owner-repo "git.sr.ht" owner repo cb)))))
|
||||
("Codeberg.org" .
|
||||
((query-user-repo . (lambda (owner repo cb) (h--query-gitea-owner-repo "codeberg.org" owner repo cb))))))
|
||||
((query-user-repo . (lambda (owner repo cb) (my-repo-pins--query-gitea-owner-repo "codeberg.org" owner repo cb))))))
|
||||
|
||||
"Fetchers meant to be used in conjunction with ‘h-forge-fetchers’.
|
||||
"Fetchers meant to be used in conjunction with ‘my-repo-pins-forge-fetchers’.
|
||||
|
||||
This variable contains fetchers for:
|
||||
- github.com")
|
||||
|
||||
(defcustom h-forge-fetchers
|
||||
h--builtins-forge-fetchers
|
||||
(defcustom my-repo-pins-forge-fetchers
|
||||
my-repo-pins--builtins-forge-fetchers
|
||||
"List of forges for which we want to remote fetch projects."
|
||||
:type '(alist
|
||||
:key-type symbol
|
||||
:value-type (alist
|
||||
:key-type symbol
|
||||
:value-type (choice function string)))
|
||||
:group 'h-group)
|
||||
:group 'my-repo-pins-group)
|
||||
|
||||
(defvar h--forge-fetchers-state '()
|
||||
(defvar my-repo-pins--forge-fetchers-state '()
|
||||
|
||||
"Internal state where we keep a forge request status.
|
||||
|
||||
|
@ -192,12 +192,12 @@ A ongoing/failed lookup will also be represented by an entry in this alist:
|
|||
\(\"FORGE-NAME1\" . 'loading)
|
||||
\(\"FORGE-NAME1\" . 'not-found)")
|
||||
|
||||
(defvar h--forge-fetchers-state-mutex
|
||||
(make-mutex "h-ui-mutex")
|
||||
(defvar my-repo-pins--forge-fetchers-state-mutex
|
||||
(make-mutex "my-repo-pins-ui-mutex")
|
||||
"Mutex in charge of preventing several fetchers to update the state concurently.")
|
||||
|
||||
;;; Sourcehut Fetcher
|
||||
(defun h--query-sourcehut-owner-repo (instance-url user-name repo-name callback)
|
||||
(defun my-repo-pins--query-sourcehut-owner-repo (instance-url user-name repo-name callback)
|
||||
"Query the INSTANCE-URL Sourcehut instance and retrieve some infos about a repo.
|
||||
|
||||
This function will try to determine whether or not the
|
||||
|
@ -228,7 +228,7 @@ using a HEAD request and infer the clone links from there."
|
|||
(setq url-request-method nil)))
|
||||
|
||||
;;; Gitlab Fetcher
|
||||
(defun h--query-gitlab-owner-repo (instance-url user-name repo-name callback)
|
||||
(defun my-repo-pins--query-gitlab-owner-repo (instance-url user-name repo-name callback)
|
||||
"Queries the INSTANCE-URL Gitlab instance and retrieve some infos about a repo.
|
||||
|
||||
This function will try to determine whether or not the
|
||||
|
@ -260,7 +260,7 @@ only option we have for now."
|
|||
(setq url-request-method nil)))
|
||||
|
||||
;;; Github Fetcher
|
||||
(defun h--query-github-owner-repo (user-name repo-name callback)
|
||||
(defun my-repo-pins--query-github-owner-repo (user-name repo-name callback)
|
||||
"Queries the GitHub API to retrieve some infos about a GitHub repo.
|
||||
This function will first try to determine whether
|
||||
github.com/USER-NAME/REPO-NAME exists.
|
||||
|
@ -271,10 +271,10 @@ nil as parameter."
|
|||
(progn
|
||||
(url-retrieve
|
||||
(format "https://api.github.com/repos/%s/%s" user-name repo-name)
|
||||
(lambda (&rest _rest) (funcall callback (h--fetch-github-parse-response(current-buffer)))))))
|
||||
(lambda (&rest _rest) (funcall callback (my-repo-pins--fetch-github-parse-response(current-buffer)))))))
|
||||
|
||||
|
||||
(defun h--fetch-github-parse-response (response-buffer)
|
||||
(defun my-repo-pins--fetch-github-parse-response (response-buffer)
|
||||
"Parse the RESPONSE-BUFFER containing a GET response from the GitHub API.
|
||||
|
||||
Parsing a response from a GET https://api.github.com/repos/user/repo request.
|
||||
|
@ -302,7 +302,7 @@ Returns nil if the repo does not exists."
|
|||
nil)))
|
||||
|
||||
;;; Gitea Fetcher
|
||||
(defun h--query-gitea-owner-repo (instance-url user-name repo-name callback)
|
||||
(defun my-repo-pins--query-gitea-owner-repo (instance-url user-name repo-name callback)
|
||||
"Queries the INSTANCE-URL gitea instance to retrieve a repo informations.
|
||||
This function will first try to dertermine whether the
|
||||
USER-NAME/REPO-NAME exists.
|
||||
|
@ -312,9 +312,9 @@ https clone URLs. If the repo does not exists, calls the callback with
|
|||
nil as parameter."
|
||||
(url-retrieve
|
||||
(format "https://%s/api/v1/repos/%s/%s" instance-url user-name repo-name)
|
||||
(lambda (&rest _rest) (funcall callback (h--fetch-gitea-parse-response(current-buffer))))))
|
||||
(lambda (&rest _rest) (funcall callback (my-repo-pins--fetch-gitea-parse-response(current-buffer))))))
|
||||
|
||||
(defun h--fetch-gitea-parse-response (response-buffer)
|
||||
(defun my-repo-pins--fetch-gitea-parse-response (response-buffer)
|
||||
"Parse the RESPONSE-BUFFER containing a GET response from the Gitea API.
|
||||
|
||||
Parsing a response from a GET https://instance/api/v1/repos/user/repo request.
|
||||
|
@ -345,7 +345,7 @@ Returns nil if the repo does not exists."
|
|||
;; Internal: repo URI parser
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun h--parse-repo-identifier (query-str)
|
||||
(defun my-repo-pins--parse-repo-identifier (query-str)
|
||||
"Do its best to figure out which repo the user meant by QUERY-STR.
|
||||
|
||||
A valid QUERY-STR is in one of the 4 following formats:
|
||||
|
@ -368,9 +368,9 @@ each kind of format, it'll return something along the line of:
|
|||
\"https://full-url.org/path/to/git/repo/checkout\"))
|
||||
or
|
||||
\(('tag . 'owner-repo) ('owner . \"NinjaTrappeur\") ('repo\
|
||||
. \"h.el\"))
|
||||
. \"my-repo-pins.el\"))
|
||||
or
|
||||
\(('tag . 'repo) ('repo . \"h.el\"))"
|
||||
\(('tag . 'repo) ('repo . \"my-repo-pins.el\"))"
|
||||
(cond
|
||||
;; Full-url case
|
||||
((or (string-match "^https?://.*/.*/.*$" query-str)
|
||||
|
@ -386,7 +386,7 @@ or
|
|||
;; repo case
|
||||
(t `((tag . repo) (repo . ,query-str)))))
|
||||
|
||||
(defun h--filepath-from-clone-url (clone-url)
|
||||
(defun my-repo-pins--filepath-from-clone-url (clone-url)
|
||||
"Return the relative path relative to the coderoot for CLONE-URL.
|
||||
|
||||
CLONE-STR being the git clone URL we want to find the local path for."
|
||||
|
@ -409,29 +409,30 @@ CLONE-STR being the git clone URL we want to find the local path for."
|
|||
;; Internal: code-root management functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defcustom h-code-root nil
|
||||
(defcustom my-repo-pins-code-root nil
|
||||
"Root directory containing all your projects.
|
||||
h.el organise the git repos you'll checkout in a tree fashion.
|
||||
my-repo-pins.el organise the git repos you'll checkout in a tree
|
||||
fashion.
|
||||
|
||||
All the code fetched using h.el will end up in this root directory. A
|
||||
All the code fetched using my-repo-pins.el will end up in this root directory. A
|
||||
tree of subdirectories will be created mirroring the remote URI.
|
||||
|
||||
For instance, after checking out
|
||||
https://git.savannah.gnu.org/git/emacs/org-mode.git, the source code
|
||||
will live in the h-code-root/git.savannah.gnu.org/git/emacs/org-mode/
|
||||
will live in the my-repo-pins-code-root/git.savannah.gnu.org/git/emacs/org-mode/
|
||||
local directory"
|
||||
:type 'directory
|
||||
:group 'h-group)
|
||||
:group 'my-repo-pins-group)
|
||||
|
||||
(defun h--safe-get-code-root ()
|
||||
"Ensure ‘h-code-root’ is correctly set, then canonalize the path.
|
||||
Errors out if ‘h-code-root’ has not been set yet."
|
||||
(progn (when (not h-code-root)
|
||||
(error "h-code-root has not been set. Please point it to your code root"))
|
||||
(expand-file-name (file-name-as-directory h-code-root))))
|
||||
(defun my-repo-pins--safe-get-code-root ()
|
||||
"Ensure ‘my-repo-pins-code-root’ is correctly set, then canonalize the path.
|
||||
Errors out if ‘my-repo-pins-code-root’ has not been set yet."
|
||||
(progn (when (not my-repo-pins-code-root)
|
||||
(error "My-Repo-Pins-code-root has not been set. Please point it to your code root"))
|
||||
(expand-file-name (file-name-as-directory my-repo-pins-code-root))))
|
||||
|
||||
|
||||
(defun h--find-git-dirs-recursively (dir)
|
||||
(defun my-repo-pins--find-git-dirs-recursively (dir)
|
||||
"Vendored, slightly modified version of ‘directory-files-recursively’.
|
||||
|
||||
This library isn't available for Emacs > 25.1. Vendoring it for
|
||||
|
@ -464,21 +465,22 @@ included."
|
|||
;; It's not a git repo, let's recurse into it.
|
||||
(setq recur-result
|
||||
(nconc recur-result
|
||||
(h--find-git-dirs-recursively full-file)))))))))
|
||||
(my-repo-pins--find-git-dirs-recursively full-file)))))))))
|
||||
(nconc recur-result (nreverse projects))))
|
||||
|
||||
|
||||
(defun h--get-code-root-projects (code-root)
|
||||
(defun my-repo-pins--get-code-root-projects (code-root)
|
||||
"Retrieve the projects contained in the CODE-ROOT directory.
|
||||
We're going to make some hard assumptions about how the ‘h-code-root’
|
||||
directory should look like. First of all, if a directory seem to be a
|
||||
git repository, it'll automatically be considered as a project root.
|
||||
We're going to make some hard assumptions about how the
|
||||
‘my-repo-pins-code-root’ directory should look like. First of all, if
|
||||
a directory seem to be a git repository, it'll automatically be
|
||||
considered as a project root.
|
||||
|
||||
It means that after encountering a git repository, we won't recurse
|
||||
any further.
|
||||
|
||||
If the directory pointed by ‘h-code-root’ does not exists yet, returns
|
||||
an empty list."
|
||||
If the directory pointed by ‘my-repo-pins-code-root’ does not exists
|
||||
yet, returns an empty list."
|
||||
(if (not (file-directory-p code-root))
|
||||
'()
|
||||
(let*
|
||||
|
@ -486,7 +488,7 @@ an empty list."
|
|||
(lambda (path)
|
||||
(let ((path-without-prefix (string-remove-prefix code-root path)))
|
||||
(substring path-without-prefix 0 (1- (length path-without-prefix))))))
|
||||
(projects-absolute-path (h--find-git-dirs-recursively code-root))
|
||||
(projects-absolute-path (my-repo-pins--find-git-dirs-recursively code-root))
|
||||
(projects-relative-to-code-root
|
||||
(mapcar remove-code-root-prefix-and-trailing-slash projects-absolute-path)))
|
||||
projects-relative-to-code-root)))
|
||||
|
@ -495,7 +497,7 @@ an empty list."
|
|||
;; Internal: UI
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun h--evil-safe-binding (kbd action)
|
||||
(defun my-repo-pins--evil-safe-binding (kbd action)
|
||||
"Bind ACTION to the KBD keyboard key.
|
||||
|
||||
This key binding will be bound to the current buffer. If ‘evil-mode’
|
||||
|
@ -520,7 +522,7 @@ is used, the key binding will be bound to the normal mode as well."
|
|||
(eval `(evil-local-set-key 'normal ,kbd ',action))))
|
||||
(local-set-key kbd action))))
|
||||
|
||||
(defun h--draw-ui-buffer (forge-query-status user-query)
|
||||
(defun my-repo-pins--draw-ui-buffer (forge-query-status user-query)
|
||||
"Draws the UI depending on the app state.
|
||||
|
||||
FORGE-QUERY-STATUS being a alist in the form of (FORGE-NAME . LOOKUP-STATUS)
|
||||
|
@ -534,34 +536,34 @@ to clone for.
|
|||
We're going to draw these forge query status results in a buffer and
|
||||
associate each of them with a key binding.
|
||||
|
||||
, ‘h--draw-forge-status’ is in charge of
|
||||
drawing the forge status in the h.el buffer."
|
||||
, ‘my-repo-pins--draw-forge-status’ is in charge of
|
||||
drawing the forge status in the my-repo-pins.el buffer."
|
||||
(let* (
|
||||
(h-buffer (get-buffer-create "h.el"))
|
||||
(h-window nil)
|
||||
(my-repo-pins-buffer (get-buffer-create "my-repo-pins.el"))
|
||||
(my-repo-pins-window nil)
|
||||
(previous-buffer (current-buffer))
|
||||
(forge-status-with-keys (h--add-keys-to-forge-status forge-query-status)))
|
||||
(forge-status-with-keys (my-repo-pins--add-keys-to-forge-status forge-query-status)))
|
||||
(progn
|
||||
(set-buffer h-buffer)
|
||||
(set-buffer my-repo-pins-buffer)
|
||||
(setq buffer-read-only nil)
|
||||
(erase-buffer)
|
||||
(insert (format "Looking up for %s in different forges:\n\n\n" user-query))
|
||||
(set-text-properties 1 (point) `(face (:foreground "orange" :weight bold)))
|
||||
(seq-map
|
||||
(lambda (e) (h--draw-forge-status e)) forge-status-with-keys)
|
||||
(lambda (e) (my-repo-pins--draw-forge-status e)) forge-status-with-keys)
|
||||
(insert "\n\nPlease select the forge we should clone the project from.\n")
|
||||
(insert "Press q to close this window.")
|
||||
(setq buffer-read-only t)
|
||||
(h--evil-safe-binding (kbd "q")
|
||||
(my-repo-pins--evil-safe-binding (kbd "q")
|
||||
`(lambda () (interactive)
|
||||
(progn
|
||||
(delete-window)
|
||||
(kill-buffer ,h-buffer))))
|
||||
(kill-buffer ,my-repo-pins-buffer))))
|
||||
(set-buffer previous-buffer)
|
||||
(setq h-window (display-buffer h-buffer))
|
||||
(select-window h-window))))
|
||||
(setq my-repo-pins-window (display-buffer my-repo-pins-buffer))
|
||||
(select-window my-repo-pins-window))))
|
||||
|
||||
(defun h--add-keys-to-forge-status (forge-query-status)
|
||||
(defun my-repo-pins--add-keys-to-forge-status (forge-query-status)
|
||||
"Add key bindings to relevant FORGE-QUERY-STATUS entries.
|
||||
|
||||
FORGE-QUERY-STATUS is list of alists in the form of ((FORGE-NAME .
|
||||
|
@ -570,7 +572,7 @@ lookup results or the 'not-found atom when no results could be found.
|
|||
This function adds a key binding alist to the LOOKUP-STATUS list when
|
||||
results have been found, nothing if the repo couldn't be found.
|
||||
|
||||
‘h--find-next-available-key-binding’ is in charge of generating the
|
||||
‘my-repo-pins--find-next-available-key-binding’ is in charge of generating the
|
||||
key bindings."
|
||||
(reverse
|
||||
(cdr
|
||||
|
@ -583,7 +585,7 @@ key bindings."
|
|||
(key (car acc))
|
||||
(isFound (listp status))
|
||||
(nextKeybinding
|
||||
(if isFound (h--find-next-available-key-binding (car acc)) (car acc)))
|
||||
(if isFound (my-repo-pins--find-next-available-key-binding (car acc)) (car acc)))
|
||||
(forge-status-with-key
|
||||
(if isFound
|
||||
`((status . ,status)
|
||||
|
@ -595,7 +597,7 @@ key bindings."
|
|||
forge-query-status
|
||||
:initial-value '(?1 . ())))))
|
||||
|
||||
(defun h--draw-forge-status (forge-result)
|
||||
(defun my-repo-pins--draw-forge-status (forge-result)
|
||||
"Draws FORGE-RESULT status to the current buffer.
|
||||
|
||||
FORGE-STATUS being a alist in the form of (FORGE-NAME . LOOKUP-STATUS).
|
||||
|
@ -611,7 +613,7 @@ https-checkout-url)) ('key . \"1\"))."
|
|||
((eq status 'loading) (format "[?] %s (loading...)" forge-name))
|
||||
((eq status 'not-found) (format "[X] %s" forge-name))
|
||||
((listp status) (format "[✓] %s" forge-name))
|
||||
(t (error (format "h--draw-forge-status: Invalid forge status %s" status)))))
|
||||
(t (error (format "my-repo-pins--draw-forge-status: Invalid forge status %s" status)))))
|
||||
(text (if (null key)
|
||||
(format "%s\n" status-text)
|
||||
(format "%s [%s]\n" status-text (char-to-string key))))
|
||||
|
@ -619,18 +621,18 @@ https-checkout-url)) ('key . \"1\"))."
|
|||
((eq status 'loading) "orange")
|
||||
((eq status 'not-found) "red")
|
||||
((listp status) "green")
|
||||
(t (error (format "h--draw-forge-status: Invalid forge status %s" status)))))
|
||||
(h-buffer (current-buffer))
|
||||
(t (error (format "my-repo-pins--draw-forge-status: Invalid forge status %s" status)))))
|
||||
(my-repo-pins-buffer (current-buffer))
|
||||
(original-point (point)))
|
||||
(progn
|
||||
(if (not (null key))
|
||||
(h--evil-safe-binding (kbd (format "%s" (char-to-string key)))
|
||||
(my-repo-pins--evil-safe-binding (kbd (format "%s" (char-to-string key)))
|
||||
`(lambda ()
|
||||
(interactive)
|
||||
(progn
|
||||
(delete-window)
|
||||
(kill-buffer ,h-buffer)
|
||||
(h--clone-from-forge-result ',forge-result)))))
|
||||
(kill-buffer ,my-repo-pins-buffer)
|
||||
(my-repo-pins--clone-from-forge-result ',forge-result)))))
|
||||
(insert text)
|
||||
;; Set color for status indicator
|
||||
(set-text-properties original-point
|
||||
|
@ -641,8 +643,8 @@ https-checkout-url)) ('key . \"1\"))."
|
|||
(set-text-properties (- (point) 4) (point)
|
||||
'(face (:foreground "orange" :weight bold)))))))
|
||||
|
||||
(defun h--find-next-available-key-binding (cur-key-binding)
|
||||
"Find a key binding starting CUR-KEY-BINDING for the h buffer.
|
||||
(defun my-repo-pins--find-next-available-key-binding (cur-key-binding)
|
||||
"Find a key binding starting CUR-KEY-BINDING for the my-repo-pins buffer.
|
||||
|
||||
We're using the 1-9 numbers, then, once all the numbers are already in
|
||||
use, we start allocating the a-Z letters."
|
||||
|
@ -650,7 +652,7 @@ use, we start allocating the a-Z letters."
|
|||
((= cur-key-binding ?z) (error "Keys exhausted, can't bind any more"))
|
||||
(t (+ cur-key-binding 1))))
|
||||
|
||||
(defun h--clone-from-forge-result (forge-result)
|
||||
(defun my-repo-pins--clone-from-forge-result (forge-result)
|
||||
"Clone a repository using the FORGE-RESULT alist.
|
||||
|
||||
The FORGE-RESULT alist is in the form of (status . (https .
|
||||
|
@ -663,14 +665,14 @@ url."
|
|||
((forge-result-status (alist-get 'status (cdr forge-result)))
|
||||
(ssh-url (alist-get 'ssh forge-result-status))
|
||||
(http-url (alist-get 'https forge-result-status))
|
||||
(code-root (h--safe-get-code-root))
|
||||
(dest-dir (concat code-root (h--filepath-from-clone-url http-url))))
|
||||
(code-root (my-repo-pins--safe-get-code-root))
|
||||
(dest-dir (concat code-root (my-repo-pins--filepath-from-clone-url http-url))))
|
||||
(progn
|
||||
(message (format "Cloning %s to %s" ssh-url dest-dir))
|
||||
(cl-flet*
|
||||
((clone-http
|
||||
()
|
||||
(h--git-clone-in-dir
|
||||
(my-repo-pins--git-clone-in-dir
|
||||
http-url
|
||||
dest-dir
|
||||
(lambda (exit-code)
|
||||
|
@ -681,7 +683,7 @@ url."
|
|||
(find-file dest-dir))))))
|
||||
(clone-ssh
|
||||
()
|
||||
(h--git-clone-in-dir
|
||||
(my-repo-pins--git-clone-in-dir
|
||||
ssh-url
|
||||
dest-dir
|
||||
(lambda (exit-code)
|
||||
|
@ -700,7 +702,7 @@ url."
|
|||
;; Internal: improving builtin autocomplete
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun h--completing-read-or-custom (prompt collection)
|
||||
(defun my-repo-pins--completing-read-or-custom (prompt collection)
|
||||
"Behaves similarly to ‘complete-read’.
|
||||
|
||||
See the ‘complete-read’ documentation for more details about PROMPT
|
||||
|
@ -721,31 +723,31 @@ READ-RESULT)"
|
|||
;; Internal: Internal state management
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defun h--init-forges-state (forge-fetchers)
|
||||
"Initialize ‘h--forge-fetchers-state’.
|
||||
(defun my-repo-pins--init-forges-state (forge-fetchers)
|
||||
"Initialize ‘my-repo-pins--forge-fetchers-state’.
|
||||
|
||||
We iterate through the forges set in FORGE-FETCHERS and associate
|
||||
each of them with a pending status. We then return this new state
|
||||
alist."
|
||||
(seq-map (lambda (e) `(,(car e) . loading)) forge-fetchers))
|
||||
|
||||
(defun h--update-forges-state (forge-name new-state user-query)
|
||||
"Update ‘h--forge-fetchers-state’ for FORGE-NAME with NEW-STATE.
|
||||
(defun my-repo-pins--update-forges-state (forge-name new-state user-query)
|
||||
"Update ‘my-repo-pins--forge-fetchers-state’ for FORGE-NAME with NEW-STATE.
|
||||
|
||||
USER-QUERY was the original query for this state update."
|
||||
(progn
|
||||
(mutex-lock h--forge-fetchers-state-mutex)
|
||||
(setq h--forge-fetchers-state (assq-delete-all forge-name h--forge-fetchers-state))
|
||||
(setq h--forge-fetchers-state (cons `(,forge-name . ,new-state) h--forge-fetchers-state))
|
||||
(h--draw-ui-buffer h--forge-fetchers-state user-query)
|
||||
(mutex-unlock h--forge-fetchers-state-mutex)))
|
||||
(mutex-lock my-repo-pins--forge-fetchers-state-mutex)
|
||||
(setq my-repo-pins--forge-fetchers-state (assq-delete-all forge-name my-repo-pins--forge-fetchers-state))
|
||||
(setq my-repo-pins--forge-fetchers-state (cons `(,forge-name . ,new-state) my-repo-pins--forge-fetchers-state))
|
||||
(my-repo-pins--draw-ui-buffer my-repo-pins--forge-fetchers-state user-query)
|
||||
(mutex-unlock my-repo-pins--forge-fetchers-state-mutex)))
|
||||
|
||||
|
||||
(defun h--query-forge-fetchers (repo-query)
|
||||
(defun my-repo-pins--query-forge-fetchers (repo-query)
|
||||
"Find repo matches to the relevant forges for REPO-QUERY then query forge.
|
||||
|
||||
TODO: split that mess before release. We shouldn't query here."
|
||||
(let* ((parsed-repo-query (h--parse-repo-identifier repo-query))
|
||||
(let* ((parsed-repo-query (my-repo-pins--parse-repo-identifier repo-query))
|
||||
(repo-query-kind (alist-get 'tag parsed-repo-query)))
|
||||
(cond
|
||||
((equal repo-query-kind 'owner-repo)
|
||||
|
@ -762,15 +764,15 @@ TODO: split that mess before release. We shouldn't query here."
|
|||
(let ((new-state
|
||||
(if (null result) 'not-found result)))
|
||||
(progn
|
||||
(h--update-forges-state ,forge-str new-state ,repo-query))))))))
|
||||
h-forge-fetchers))
|
||||
(my-repo-pins--update-forges-state ,forge-str new-state ,repo-query))))))))
|
||||
my-repo-pins-forge-fetchers))
|
||||
((equal repo-query-kind 'repo) (error (format "Can't checkout %s (for now), please specify a owner" repo-query)))
|
||||
((equal repo-query-kind 'full-url)
|
||||
(let*
|
||||
((code-root (h--safe-get-code-root))
|
||||
(dest-dir (concat code-root (h--filepath-from-clone-url repo-query))))
|
||||
((code-root (my-repo-pins--safe-get-code-root))
|
||||
(dest-dir (concat code-root (my-repo-pins--filepath-from-clone-url repo-query))))
|
||||
(progn
|
||||
(h--git-clone-in-dir
|
||||
(my-repo-pins--git-clone-in-dir
|
||||
repo-query
|
||||
dest-dir
|
||||
(lambda (exit-code)
|
||||
|
@ -783,31 +785,29 @@ TODO: split that mess before release. We shouldn't query here."
|
|||
;; Interactive Commands
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;;###autoload
|
||||
(defun h-clone-project (user-query)
|
||||
"Clone USER-QUERY in its appropriate directory in ‘h-code-root’."
|
||||
(interactive "sGit repository to checkout: ")
|
||||
(defun my-repo-pins--clone-project (user-query)
|
||||
"Clone USER-QUERY in its appropriate directory in ‘my-repo-pins-code-root’."
|
||||
(progn
|
||||
(setq h--forge-fetchers-state (h--init-forges-state h-forge-fetchers))
|
||||
(h--query-forge-fetchers user-query)))
|
||||
(setq my-repo-pins--forge-fetchers-state (my-repo-pins--init-forges-state my-repo-pins-forge-fetchers))
|
||||
(my-repo-pins--query-forge-fetchers user-query)))
|
||||
|
||||
;;;###autoload
|
||||
(defun h-jump-to-project ()
|
||||
"Open a project contained in the ‘h-code-root’ directory.
|
||||
If the project is not in the ‘h-code-root’ yet, check it out from the
|
||||
(defun my-repo-pins ()
|
||||
"Open a project contained in the ‘my-repo-pins-code-root’ directory.
|
||||
If the project is not in the ‘my-repo-pins-code-root’ yet, check it out from the
|
||||
available forge sources."
|
||||
(interactive)
|
||||
(let ((user-query
|
||||
(h--completing-read-or-custom
|
||||
(my-repo-pins--completing-read-or-custom
|
||||
"Jump to project: "
|
||||
(h--get-code-root-projects (h--safe-get-code-root)))))
|
||||
(my-repo-pins--get-code-root-projects (my-repo-pins--safe-get-code-root)))))
|
||||
(cond
|
||||
((equal (car user-query) 'in-collection)
|
||||
(let ((selected-project-absolute-path (concat (h--safe-get-code-root) (cdr user-query))))
|
||||
(let ((selected-project-absolute-path (concat (my-repo-pins--safe-get-code-root) (cdr user-query))))
|
||||
(find-file selected-project-absolute-path)))
|
||||
((equal (car user-query) 'user-provided)
|
||||
(h-clone-project (cdr user-query))))))
|
||||
(my-repo-pins--clone-project (cdr user-query))))))
|
||||
|
||||
|
||||
(provide 'h)
|
||||
;;; h.el ends here
|
||||
(provide 'my-repo-pins)
|
||||
;;; my-repo-pins.el ends here
|
|
@ -8,4 +8,4 @@ Set-Cookie: _csrf=000000000000000000000000000000000000000000000000000000; Path=/
|
|||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
|
||||
{"id":28,"owner":{"id":1,"login":"NinjaTrappeur","full_name":"","email":"felix@alternativebit.fr","avatar_url":"https://git.alternativebit.fr/avatars/326105984221f71c9e555addc514dae6","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2018-04-10T16:10:10+02:00","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"NinjaTrappeur"},"name":"h.el","full_name":"NinjaTrappeur/h.el","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":107,"html_url":"https://git.alternativebit.fr/NinjaTrappeur/h.el","ssh_url":"gitea@git.alternativebit.fr:NinjaTrappeur/h.el.git","clone_url":"https://git.alternativebit.fr/NinjaTrappeur/h.el.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"master","archived":false,"created_at":"2020-10-25T15:53:38+01:00","updated_at":"2022-06-16T18:43:05+02:00","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":false,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}
|
||||
{"id":28,"owner":{"id":1,"login":"NinjaTrappeur","full_name":"","email":"felix@alternativebit.fr","avatar_url":"https://git.alternativebit.fr/avatars/326105984221f71c9e555addc514dae6","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2018-04-10T16:10:10+02:00","restricted":false,"active":false,"prohibit_login":false,"location":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"NinjaTrappeur"},"name":"my-repo-pins.el","full_name":"NinjaTrappeur/my-repo-pins.el","description":"","empty":false,"private":false,"fork":false,"template":false,"parent":null,"mirror":false,"size":107,"html_url":"https://git.alternativebit.fr/NinjaTrappeur/my-repo-pins.el","ssh_url":"gitea@git.alternativebit.fr:NinjaTrappeur/my-repo-pins.el.git","clone_url":"https://git.alternativebit.fr/NinjaTrappeur/my-repo-pins.el.git","original_url":"","website":"","stars_count":0,"forks_count":0,"watchers_count":1,"open_issues_count":0,"open_pr_counter":0,"release_counter":0,"default_branch":"master","archived":false,"created_at":"2020-10-25T15:53:38+01:00","updated_at":"2022-06-16T18:43:05+02:00","permissions":{"admin":false,"push":false,"pull":true},"has_issues":true,"internal_tracker":{"enable_time_tracker":true,"allow_only_contributors_to_track_time":true,"enable_issue_dependencies":true},"has_wiki":true,"has_pull_requests":true,"has_projects":false,"ignore_whitespace_conflicts":false,"allow_merge_commits":true,"allow_rebase":true,"allow_rebase_explicit":true,"allow_squash_merge":true,"default_merge_style":"merge","avatar_url":"","internal":false,"mirror_interval":"","mirror_updated":"0001-01-01T00:00:00Z","repo_transfer":null}
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue