From c058603766c3e62c2af9dc2c184dc732268f97ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Baylac-Jacqu=C3=A9?= Date: Mon, 27 Jun 2022 14:44:51 +0200 Subject: [PATCH] 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. --- Makefile | 6 +- README.md | 68 +++-- doc/assets/logo-black.svg | 71 +++++ doc/assets/logo-inkscape.svg | 95 ++++++ doc/assets/logo-white.svg | 71 +++++ h-tests.el | 356 ----------------------- my-repo-pins-tests.el | 356 +++++++++++++++++++++++ h.el => my-repo-pins.el | 266 ++++++++--------- tests/fixtures/gitea-get-request-ok.txt | 2 +- tests/fixtures/github-get-request-ok.txt | 2 +- 10 files changed, 769 insertions(+), 524 deletions(-) create mode 100644 doc/assets/logo-black.svg create mode 100644 doc/assets/logo-inkscape.svg create mode 100644 doc/assets/logo-white.svg delete mode 100644 h-tests.el create mode 100644 my-repo-pins-tests.el rename h.el => my-repo-pins.el (74%) diff --git a/Makefile b/Makefile index 888384e..80c0089 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index 6d160b9..7bd4b46 100644 --- a/README.md +++ b/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. diff --git a/doc/assets/logo-black.svg b/doc/assets/logo-black.svg new file mode 100644 index 0000000..66a17a3 --- /dev/null +++ b/doc/assets/logo-black.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/assets/logo-inkscape.svg b/doc/assets/logo-inkscape.svg new file mode 100644 index 0000000..1771440 --- /dev/null +++ b/doc/assets/logo-inkscape.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/assets/logo-white.svg b/doc/assets/logo-white.svg new file mode 100644 index 0000000..b299337 --- /dev/null +++ b/doc/assets/logo-white.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/h-tests.el b/h-tests.el deleted file mode 100644 index 5134143..0000000 --- a/h-tests.el +++ /dev/null @@ -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é -;;; Maintainer: Félix Baylac Jacqué -;;; 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 - -;;; 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 diff --git a/my-repo-pins-tests.el b/my-repo-pins-tests.el new file mode 100644 index 0000000..aee417d --- /dev/null +++ b/my-repo-pins-tests.el @@ -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é +;;; Maintainer: Félix Baylac Jacqué +;;; 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 + +;;; 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 diff --git a/h.el b/my-repo-pins.el similarity index 74% rename from h.el rename to my-repo-pins.el index 3b39e3a..58458b9 100644 --- a/h.el +++ b/my-repo-pins.el @@ -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é ;;; Maintainer: Félix Baylac Jacqué ;;; 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 diff --git a/tests/fixtures/gitea-get-request-ok.txt b/tests/fixtures/gitea-get-request-ok.txt index df52ff2..49c31c6 100644 --- a/tests/fixtures/gitea-get-request-ok.txt +++ b/tests/fixtures/gitea-get-request-ok.txt @@ -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} diff --git a/tests/fixtures/github-get-request-ok.txt b/tests/fixtures/github-get-request-ok.txt index 8299f18..43578e8 100644 --- a/tests/fixtures/github-get-request-ok.txt +++ b/tests/fixtures/github-get-request-ok.txt @@ -24,4 +24,4 @@ Accept-Ranges: bytes Content-Length: 1288 X-GitHub-Request-Id: EADE:942B:156216:17E519:62503D61 -{"id":476823023,"node_id":"R_kgDOHGu97w","name":"h.el","full_name":"NinjaTrappeur/h.el","private":false,"owner":{"login":"NinjaTrappeur","id":1219785,"node_id":"MDQ6VXNlcjEyMTk3ODU=","avatar_url":"https://avatars.githubusercontent.com/u/1219785?v=4","gravatar_id":"","url":"https://api.github.com/users/NinjaTrappeur","html_url":"https://github.com/NinjaTrappeur","followers_url":"https://api.github.com/users/NinjaTrappeur/followers","following_url":"https://api.github.com/users/NinjaTrappeur/following{/other_user}","gists_url":"https://api.github.com/users/NinjaTrappeur/gists{/gist_id}","starred_url":"https://api.github.com/users/NinjaTrappeur/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NinjaTrappeur/subscriptions","organizations_url":"https://api.github.com/users/NinjaTrappeur/orgs","repos_url":"https://api.github.com/users/NinjaTrappeur/repos","events_url":"https://api.github.com/users/NinjaTrappeur/events{/privacy}","received_events_url":"https://api.github.com/users/NinjaTrappeur/received_events","type":"User","site_admin":false},"html_url":"https://github.com/NinjaTrappeur/h.el","description":"Emacs project manager and remote git checkout","fork":false,"url":"https://api.github.com/repos/NinjaTrappeur/h.el","forks_url":"https://api.github.com/repos/NinjaTrappeur/h.el/forks","keys_url":"https://api.github.com/repos/NinjaTrappeur/h.el/keys{/key_id}","collaborators_url":"https://api.github.com/repos/NinjaTrappeur/h.el/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/NinjaTrappeur/h.el/teams","hooks_url":"https://api.github.com/repos/NinjaTrappeur/h.el/hooks","issue_events_url":"https://api.github.com/repos/NinjaTrappeur/h.el/issues/events{/number}","events_url":"https://api.github.com/repos/NinjaTrappeur/h.el/events","assignees_url":"https://api.github.com/repos/NinjaTrappeur/h.el/assignees{/user}","branches_url":"https://api.github.com/repos/NinjaTrappeur/h.el/branches{/branch}","tags_url":"https://api.github.com/repos/NinjaTrappeur/h.el/tags","blobs_url":"https://api.github.com/repos/NinjaTrappeur/h.el/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/NinjaTrappeur/h.el/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/NinjaTrappeur/h.el/git/refs{/sha}","trees_url":"https://api.github.com/repos/NinjaTrappeur/h.el/git/trees{/sha}","statuses_url":"https://api.github.com/repos/NinjaTrappeur/h.el/statuses/{sha}","languages_url":"https://api.github.com/repos/NinjaTrappeur/h.el/languages","stargazers_url":"https://api.github.com/repos/NinjaTrappeur/h.el/stargazers","contributors_url":"https://api.github.com/repos/NinjaTrappeur/h.el/contributors","subscribers_url":"https://api.github.com/repos/NinjaTrappeur/h.el/subscribers","subscription_url":"https://api.github.com/repos/NinjaTrappeur/h.el/subscription","commits_url":"https://api.github.com/repos/NinjaTrappeur/h.el/commits{/sha}","git_commits_url":"https://api.github.com/repos/NinjaTrappeur/h.el/git/commits{/sha}","comments_url":"https://api.github.com/repos/NinjaTrappeur/h.el/comments{/number}","issue_comment_url":"https://api.github.com/repos/NinjaTrappeur/h.el/issues/comments{/number}","contents_url":"https://api.github.com/repos/NinjaTrappeur/h.el/contents/{+path}","compare_url":"https://api.github.com/repos/NinjaTrappeur/h.el/compare/{base}...{head}","merges_url":"https://api.github.com/repos/NinjaTrappeur/h.el/merges","archive_url":"https://api.github.com/repos/NinjaTrappeur/h.el/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/NinjaTrappeur/h.el/downloads","issues_url":"https://api.github.com/repos/NinjaTrappeur/h.el/issues{/number}","pulls_url":"https://api.github.com/repos/NinjaTrappeur/h.el/pulls{/number}","milestones_url":"https://api.github.com/repos/NinjaTrappeur/h.el/milestones{/number}","notifications_url":"https://api.github.com/repos/NinjaTrappeur/h.el/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/NinjaTrappeur/h.el/labels{/name}","releases_url":"https://api.github.com/repos/NinjaTrappeur/h.el/releases{/id}","deployments_url":"https://api.github.com/repos/NinjaTrappeur/h.el/deployments","created_at":"2022-04-01T18:06:46Z","updated_at":"2022-04-01T18:08:16Z","pushed_at":"2022-04-07T10:20:28Z","git_url":"git://github.com/NinjaTrappeur/h.el.git","ssh_url":"git@github.com:NinjaTrappeur/h.el.git","clone_url":"https://github.com/NinjaTrappeur/h.el.git","svn_url":"https://github.com/NinjaTrappeur/h.el","homepage":null,"size":54,"stargazers_count":0,"watchers_count":0,"language":"Emacs Lisp","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"allow_forking":true,"is_template":false,"topics":[],"visibility":"public","forks":0,"open_issues":0,"watchers":0,"default_branch":"master","temp_clone_token":null,"network_count":0,"subscribers_count":1} +{"id":476823023,"node_id":"R_kgDOHGu97w","name":"my-repo-pins.el","full_name":"NinjaTrappeur/my-repo-pins.el","private":false,"owner":{"login":"NinjaTrappeur","id":1219785,"node_id":"MDQ6VXNlcjEyMTk3ODU=","avatar_url":"https://avatars.githubusercontent.com/u/1219785?v=4","gravatar_id":"","url":"https://api.github.com/users/NinjaTrappeur","html_url":"https://github.com/NinjaTrappeur","followers_url":"https://api.github.com/users/NinjaTrappeur/followers","following_url":"https://api.github.com/users/NinjaTrappeur/following{/other_user}","gists_url":"https://api.github.com/users/NinjaTrappeur/gists{/gist_id}","starred_url":"https://api.github.com/users/NinjaTrappeur/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/NinjaTrappeur/subscriptions","organizations_url":"https://api.github.com/users/NinjaTrappeur/orgs","repos_url":"https://api.github.com/users/NinjaTrappeur/repos","events_url":"https://api.github.com/users/NinjaTrappeur/events{/privacy}","received_events_url":"https://api.github.com/users/NinjaTrappeur/received_events","type":"User","site_admin":false},"html_url":"https://github.com/NinjaTrappeur/my-repo-pins.el","description":"Emacs project manager and remote git checkout","fork":false,"url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el","forks_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/forks","keys_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/keys{/key_id}","collaborators_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/teams","hooks_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/hooks","issue_events_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/issues/events{/number}","events_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/events","assignees_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/assignees{/user}","branches_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/branches{/branch}","tags_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/tags","blobs_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/git/refs{/sha}","trees_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/git/trees{/sha}","statuses_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/statuses/{sha}","languages_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/languages","stargazers_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/stargazers","contributors_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/contributors","subscribers_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/subscribers","subscription_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/subscription","commits_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/commits{/sha}","git_commits_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/git/commits{/sha}","comments_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/comments{/number}","issue_comment_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/issues/comments{/number}","contents_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/contents/{+path}","compare_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/compare/{base}...{head}","merges_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/merges","archive_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/downloads","issues_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/issues{/number}","pulls_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/pulls{/number}","milestones_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/milestones{/number}","notifications_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/labels{/name}","releases_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/releases{/id}","deployments_url":"https://api.github.com/repos/NinjaTrappeur/my-repo-pins.el/deployments","created_at":"2022-04-01T18:06:46Z","updated_at":"2022-04-01T18:08:16Z","pushed_at":"2022-04-07T10:20:28Z","git_url":"git://github.com/NinjaTrappeur/my-repo-pins.el.git","ssh_url":"git@github.com:NinjaTrappeur/my-repo-pins.el.git","clone_url":"https://github.com/NinjaTrappeur/my-repo-pins.el.git","svn_url":"https://github.com/NinjaTrappeur/my-repo-pins.el","homepage":null,"size":54,"stargazers_count":0,"watchers_count":0,"language":"Emacs Lisp","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"allow_forking":true,"is_template":false,"topics":[],"visibility":"public","forks":0,"open_issues":0,"watchers":0,"default_branch":"master","temp_clone_token":null,"network_count":0,"subscribers_count":1}