diff --git a/ChangeLog.md b/ChangeLog.md index 66f575e..7b57d11 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,12 @@ # emacs-gitlab ChangeLog +## Version 0.5 (06/04/2015) + +- ``FIX`` Unit tests on issues API +- ``FIX`` List projects without page parameters +- ``#20``: List all issues and projects (pagination refactoring) (Thanks [marcinant][]) +- ``#19``: Feature/extend gitlab mode (Thanks [marcinant][]) + ## Version 0.4 (05/29/2015) - Initial support for Users API (Thanks [marcinant][]) diff --git a/README.md b/README.md index a590039..080fe69 100644 --- a/README.md +++ b/README.md @@ -69,18 +69,20 @@ management. Install it and retrieve dependencies : * Setup your Gitlab informations : - $ export GITLAB_HOST="http://gitlab.foo.com" - $ export GITLAB_USERNAME="foo" - $ export GITLAB_PASSWORD="bar" - $ export GITLAB_TOKEN_ID="xxxxxxxxxxxxxx" - $ export GITLAB_PROJECT_ID=111222 - $ export GITLAB_PROJECT_NAME="myproject" - $ export GITLAB_PROJECT_DESCRIPTION="a project description" - $ export GITLAB_ISSUE_ID=145645 - $ export GITLAB_ISSUE_TITLE="the issue title" + $ cat $HOME/.emacs-gitlab.rc + #!/bin/bash + export GITLAB_HOST="https://gitlab.com" + export GITLAB_USERNAME="yourusername" + export GITLAB_PASSWORD="yourpassword" + export GITLAB_PROJECT_ID=111222 + export GITLAB_PROJECT_NAME="myproject" + export GITLAB_PROJECT_DESCRIPTION="a project description" + export GITLAB_ISSUE_ID=145645 + export GITLAB_ISSUE_TITLE="the issue title" * Launch unit tests : + $ . $HOME/.emacs-gitlab.rc $ make clean test @@ -104,6 +106,8 @@ See [LICENSE](LICENSE). Nicolas Lamirault + + [emacs-gitlab]: https://github.com/nlamirault/emacs-gitlab [badge-license]: https://img.shields.io/badge/license-GPL_2-green.svg?style=flat [LICENSE]: https://github.com/nlamirault/emacs-gitlab/blob/master/LICENSE diff --git a/gitlab-issues.el b/gitlab-issues.el index c071ded..df434df 100644 --- a/gitlab-issues.el +++ b/gitlab-issues.el @@ -30,17 +30,32 @@ (require 'gitlab-utils) -(defun gitlab-list-issues () +(defun gitlab-list-issues (page per-page) "Get all issues created by authenticated user. -STATE Return all issues or just those that are opened or closed -LABELS - Comma-separated list of label names" +PAGE: current page number +PER-PAGE: number of items on page max 100" (let ((params '())) - ;; (when state - ;; (add-to-list params (cons "state" state))) - ;; (when labels - ;; (add-to-list params (cons "labels" labels))) - (perform-gitlab-request "GET" "issues" params 200))) - + (add-to-list 'params (cons 'per_page (number-to-string per-page))) + (add-to-list 'params (cons 'page (number-to-string page))) + (perform-gitlab-request "GET" + "issues" + params + 200))) + +(defun gitlab-list-all-issues () + "Get a list of all issues." + (interactive) + (let* ((page 1) + (per-page 100) + (issues) + (all-issues (gitlab-list-issues page per-page)) + (all-issues-count (length all-issues))) + (while (>= all-issues-count (* page per-page)) + (setq issues (gitlab-list-issues page per-page)) + (setq all-issues (vconcat all-issues issues)) + (setq all-issues-count (length all-issues)) + (setq page (1+ page))) + all-issues)) (defun gitlab--get-issue-uri (project-id issue-id) "Retrieve URI to retrieve an issue. @@ -52,17 +67,42 @@ ISSUE-ID : The ID of a project issue" "/issues/" issue-id)) -(defun gitlab-list-project-issues (project-id) +(defun gitlab-list-project-issues (project-id &optional page per-page) "Get a list of project issues. +PROJECT-ID : The ID of a project +PAGE: current page number +PER-PAGE: number of items on page max 100" + (let ((params '())) + (when page + (add-to-list 'params (cons 'per_page (number-to-string per-page)))) + (when per-page + (add-to-list 'params (cons 'page (number-to-string page)))) + (perform-gitlab-request "GET" + (s-concat "projects/" + (url-hexify-string + (format "%s" project-id)) + "/issues") + params + 200))) + +(defun gitlab-list-all-project-issues (project-id &optional page per-page) + "Get a list of all PROJECT-ID issues. +PROJECT-ID : The ID of a project +PAGE: current page number +PER-PAGE: number of items on page max 100" + (interactive) + (let* ((page 1) + (per-page 100) + (issues) + (all-issues (gitlab-list-project-issues project-id page per-page)) + (all-issues-count (length all-issues))) + (while (>= all-issues-count (* page per-page)) + (setq issues (gitlab-list-project-issues project-id page per-page)) + (setq all-issues (vconcat all-issues issues)) + (setq all-issues-count (length all-issues)) + (setq page (1+ page))) + all-issues)) -PROJECT-ID : The ID of a project" - (perform-gitlab-request "GET" - (s-concat "projects/" - (url-hexify-string - (format "%s" project-id)) - "/issues") - nil - 200)) (defun gitlab-get-issue (project-id issue-id) "Gets a single project issue. @@ -109,10 +149,11 @@ LABELS comma-separated list label names" "Create a project issue. PROJECT-ID the ID or NAMESPACE%2FPROJECT_NAME of a project +ISSUE-ID : The ID of a project issue TITLE issue title DESCRIPTION issue description -ASSIGNEE assignee ID -MILESTONE milestone ID +ASSIGNEE-ID assignee ID +MILESTONE-ID milestone ID LABELS comma-separated list label names" (lwarn '(gitlab) :debug "UPDATE ISSUE in project: %s\n" project-id) (perform-gitlab-request "PUT" diff --git a/gitlab-mode.el b/gitlab-mode.el index 73cb064..474c3ed 100644 --- a/gitlab-mode.el +++ b/gitlab-mode.el @@ -27,6 +27,7 @@ (require 'browse-url) (require 'tabulated-list) +(require 'vc-git) ;; Gitlab library @@ -41,30 +42,124 @@ (interactive) (message (concat "Current ID is: " (tabulated-list-get-id)))) - +(defun project-make-button (text &rest props) + "Make button with TEXT propertized with PROPS." + (let ((button-text (if (display-graphic-p) + text + (concat "[" text "]"))) + (button-face (if (display-graphic-p) + '(:box (:line-width 2 :color "dark grey") + :background "light grey" + :foreground "black") + 'link))) + (apply 'insert-text-button button-text + 'face button-face + 'follow-link t + props))) ;; Projects +(defun gitlab-project-clone-button-action (button) + "Action for BUTTON." + (interactive) + + (let* ((project (gitlab-get-project (button-get button 'project-id))) + (name (assoc-default 'path project)) + (repo (assoc-default 'ssh_url_to_repo project)) + (target-dir (read-directory-name "Clone to directory:" (first query-replace-defaults)))) + + (if (file-directory-p (expand-file-name name target-dir)) + (progn + (message "Target directory exists and is not empty. Trying to pull.") + (let ((default-directory (file-name-as-directory (expand-file-name name target-dir)))) + (vc-git-command nil 0 nil "pull" repo))) + (progn + (make-directory name target-dir) + (vc-git-command nil 0 nil "clone" repo (file-name-as-directory (expand-file-name name target-dir))))) + (revert-buffer nil t) + (goto-char (point-min)))) + (defun gitlab-goto-project () "Got to web page of the project." - (let ((project (tabulated-list-get-entry))) + (interactive) + (let* ((project (gitlab-get-project (tabulated-list-get-id)))) (browse-url (assoc-default 'web_url project)))) ;;;###autoload +(defun gitlab-show-project-description (project) + "Doc string PROJECT." + (interactive) + (with-help-window (help-buffer) + (with-current-buffer standard-output + (let ((desc (assoc-default 'description project)) + (homepage (assoc-default 'web_url project)) + (id (assoc-default 'id project)) + (status (number-to-string (assoc-default 'visibility_level project)))) + + (insert " Name: ") + (princ (assoc-default 'name project)) + (princ "\n") + (insert " Path: ") + (princ (assoc-default 'path_with_namespace project)) + (princ "\n\n") + + (insert " Repository: ") + (princ (assoc-default 'ssh_url_to_repo project)) + (insert "\n\n") + + (insert " " (propertize "Status" 'font-lock-face 'bold) ": ") + (cond ((string= status "0") + (insert (propertize (capitalize "Private") 'font-lock-faces 'font-lock-builtin-face))) + ((string= status "10") + (insert (propertize (capitalize "Internal") 'font-lock-faces 'font-lock-builtin-face))) + ((string= status "20") + (insert (propertize (capitalize "Public") 'font-lock-faces 'font-lock-builtin-face)))) + (insert " -- ") + (project-make-button + "Clone to / Pull" + 'action 'gitlab-project-clone-button-action + 'project-id id) + + (insert "\n\n") + + + (insert " " (propertize "Summary" 'font-lock-face 'bold) + ": " (if desc desc) "\n") + + (when homepage + (insert " " (propertize "Homepage" 'font-lock-face 'bold) ": ") + (help-insert-xref-button homepage 'help-url homepage) + (insert "\n")))))) + + +(defun gitlab-describe-project (&optional button) + "Describe the current pproject. +If optional arg BUTTON is non-nil, describe its associated project." + (interactive) + (let ((project (gitlab-get-project (tabulated-list-get-id)))) + (if project + (gitlab-show-project-description project) + (user-error "No project here")))) + + +>>>>>>> develop (defun gitlab-show-projects () "Show Gitlab projects." (interactive) (pop-to-buffer "*Gitlab projects*" nil) (gitlab-projects-mode) (setq tabulated-list-entries - (create-projects-entries (gitlab-list-projects))) + (create-projects-entries (gitlab-list-all-projects))) (tabulated-list-print t)) (defun create-projects-entries (projects) "Create entries for 'tabulated-list-entries from PROJECTS." (mapcar (lambda (p) + (let ((id (number-to-string (assoc-default 'id p))) - (owner (assoc-default 'owner p)) + (owner (if (assoc-default 'owner p) + (assoc-default 'owner p) + (assoc-default 'namespace p))) (namespace (assoc-default 'namespace p))) (list id (vector ;id @@ -78,7 +173,9 @@ (defun gitlab-goto-issue () "Got to web page of the issue." - ) + (interactive) + (let ((project (gitlab-get-project (elt (tabulated-list-get-entry) 1)))) + (browse-url (concat (assoc-default 'web_url project) "/issues/" (tabulated-list-get-id))))) (defun create-issues-entries (issues) "Create entries for 'tabulated-list-entries from ISSUES." @@ -88,6 +185,7 @@ (list id (vector ;id (assoc-default 'state i) + (format "%s" (assoc-default 'project_id i)) (assoc-default 'name author) (assoc-default 'title i))))) issues)) @@ -99,7 +197,7 @@ (pop-to-buffer "*Gitlab issues*" nil) (gitlab-issues-mode) (setq tabulated-list-entries - (create-issues-entries (gitlab-list-issues))) + (create-issues-entries (gitlab-list-all-issues))) (tabulated-list-print t)) @@ -111,6 +209,7 @@ (let ((map (make-keymap))) (define-key map (kbd "v") 'print-current-line-id) (define-key map (kbd "w") 'gitlab-goto-project) + (define-key map (kbd "d") 'gitlab-describe-project) map) "Keymap for `gitlab-projects-mode' major mode.") @@ -142,16 +241,12 @@ :group 'gitlab (setq tabulated-list-format [;("ID" 5 t) ("State" 10 t) + ("Project" 8 t) ("Author" 20 t) ("Title" 0 t)]) (setq tabulated-list-padding 2) (setq tabulated-list-sort-key (cons "Title" nil)) (tabulated-list-init-header)) - - - - - (provide 'gitlab-mode) ;;; gitlab-mode.el ends here diff --git a/gitlab-notes.el b/gitlab-notes.el index 19d2ac0..b2e70db 100644 --- a/gitlab-notes.el +++ b/gitlab-notes.el @@ -37,21 +37,43 @@ (number-to-string issue-id) "/notes")) -(defun gitlab-list-project-issue-notes (project-id issue-id) +(defun gitlab-list-project-issue-notes (project-id issue-id &optional page per-page) "Get a list of project issue notes. +PROJECT-ID : The ID of a project +ISSUE-ID : The ID of a project issue +PAGE: current page number +PER-PAGE: number of items on page max 100" + (let* ((params '())) + (add-to-list 'params (cons 'per_page (number-to-string per-page))) + (add-to-list 'params (cons 'page (number-to-string page))) + (perform-gitlab-request "GET" + (gitlab--get-notes-uri + project-id + issue-id) + params + 200))) + +(defun gitlab-list-all-project-issue-notes (project-id issue-id) + "Get a list of allproject issue notes. + PROJECT-ID : The ID of a project ISSUE-ID : The ID of a project issue" - (perform-gitlab-request "GET" - (gitlab--get-notes-uri - project-id - issue-id) - nil - 200)) + (interactive) + (let* ((page 1) + (per-page 100) + (notes) + (all-notes (gitlab-list-project-issue-notes project-id issue-id page per-page)) + (all-notes-count (length all-notes))) + (while (>= all-notes-count (* page per-page)) + (setq notes (gitlab-list-project-issue-notes project-id issue-id page per-page)) + (setq all-notes (vconcat all-notes notes)) + (setq all-notes-count (length all-notes)) + (setq page (1+ page))) + all-notes)) (defun gitlab-get-issue-note (project-id issue-id note-id) - "Doc PROJECT-ID ISSUE-ID NOTE-ID." - ) + "Doc PROJECT-ID ISSUE-ID NOTE-ID.") (defun gitlab-add-issue-note (project-id issue-id body) "Add note for project issue. diff --git a/gitlab-projects.el b/gitlab-projects.el index b6a028f..549ea67 100644 --- a/gitlab-projects.el +++ b/gitlab-projects.el @@ -30,9 +30,34 @@ (require 'gitlab-utils) -(defun gitlab-list-projects () - "Get a list of projects accessible by the authenticated user." - (perform-gitlab-request "GET" "projects" nil 200)) +(defun gitlab-list-projects (&optional page per-page) + "Get a list of projects accessible by the authenticated user. +PAGE: current page number +PER-PAGE: number of items on page max 100" + (let* ((params '())) + (when page + (add-to-list 'params (cons 'per_page (number-to-string per-page)))) + (when per-page + (add-to-list 'params (cons 'page (number-to-string page)))) + (perform-gitlab-request "GET" + "projects" + params + 200))) + +(defun gitlab-list-all-projects () + "Get a list of all projects accessible by the authenticated user." + (interactive) + (let* ((page 1) + (per-page 100) + (projects) + (all-projects (gitlab-list-projects page per-page)) + (all-projects-count (length all-projects))) + (while (>= all-projects-count (* page per-page)) + (setq projects (gitlab-list-projects page per-page)) + (setq all-projects (vconcat all-projects projects)) + (setq all-projects-count (length all-projects)) + (setq page (1+ page))) + all-projects)) (defun gitlab-list-owned-projects () diff --git a/gitlab-utils.el b/gitlab-utils.el index 9de27fe..0168a09 100644 --- a/gitlab-utils.el +++ b/gitlab-utils.el @@ -78,15 +78,18 @@ Defaults to `error'." (defun gitlab--perform-get-request (uri params) + "Doc string URI PARAMS." (let* ((response (request (gitlab--get-rest-uri uri) :type "GET" :headers (gitlab--get-headers) :sync t :params params + ;;:data params :parser 'json-read))) response)) (defun gitlab--perform-post-request (uri params) + "Doc string URI PARAMS." (let ((response (request (gitlab--get-rest-uri uri) :type "POST" :headers (gitlab--get-headers) @@ -96,6 +99,7 @@ Defaults to `error'." response)) (defun gitlab--perform-put-request (uri params) + "Doc string URI PARAMS." (let ((response (request (gitlab--get-rest-uri uri) :type "PUT" :headers (gitlab--get-headers) @@ -106,6 +110,7 @@ Defaults to `error'." (defun perform-gitlab-request (type uri params status-code) + "Doc string TYPE URI PARAMS STATUS-CODE." (let ((response (cond ((string= type "POST") (gitlab--perform-post-request uri params)) @@ -115,10 +120,11 @@ Defaults to `error'." (gitlab--perform-put-request uri params))))) (if (= status-code (request-response-status-code response)) (request-response-data response) - (error - (signal 'gitlab-http-error - (list (request-response-status-code response) - (request-response-data response))))))) + (lwarn '(gitlab) + :error "HTTP %s Error %s on URI: %s" + type + (request-response-status-code response) + uri)))) ;; (defmacro with-gitlab-request (uri params status-code response-data &rest body) diff --git a/helm-gitlab.el b/helm-gitlab.el index d929dc6..f29e53f 100644 --- a/helm-gitlab.el +++ b/helm-gitlab.el @@ -107,7 +107,7 @@ (defun helm-gitlab--projects-init () (when (s-blank? gitlab-token-id) (gitlab-login)) - (let ((projects (gitlab-list-projects))) + (let ((projects (gitlab-list-all-projects))) (mapcar (lambda (p) (cons (format "%s" (propertize (assoc-default 'name p) 'face @@ -146,7 +146,7 @@ (defun helm-gitlab--issues-init () (when (s-blank? gitlab-token-id) (gitlab-login)) - (let ((issues (gitlab-list-issues))) + (let ((issues (gitlab-list-all-issues))) (mapcar (lambda (i) (cons (format "[%s] %s [%s]" (assoc-default 'id i) diff --git a/test/gitlab-issues-test.el b/test/gitlab-issues-test.el index 326f892..7268aea 100644 --- a/test/gitlab-issues-test.el +++ b/test/gitlab-issues-test.el @@ -32,7 +32,7 @@ :tags '(issues) (with-test-sandbox (with-gitlab-session - (let ((issues (gitlab-list-issues))) + (let ((issues (gitlab-list-issues 1 20))) (should (<= 0 (length issues))) (mapcar (lambda (i) (should (not (s-blank? (assoc-default 'title i)))) @@ -41,7 +41,7 @@ issues))))) (ert-deftest test-list-project-issues () - :tags '(issues) + :tags '(issues current) (with-test-sandbox (with-gitlab-session (let ((issues (gitlab-list-project-issues gitlab-project-id))) diff --git a/test/gitlab-projects-test.el b/test/gitlab-projects-test.el index 3f1c503..7259f8e 100644 --- a/test/gitlab-projects-test.el +++ b/test/gitlab-projects-test.el @@ -27,7 +27,7 @@ (ert-deftest test-list-projects-without-session () :tags '(projects) (with-test-sandbox - (should-error (gitlab-list-projects)))) + (gitlab-list-projects))) (ert-deftest test-list-projects () :tags '(projects) @@ -55,7 +55,7 @@ projects))))) (ert-deftest test-get-project () - :tags '(projects current) + :tags '(projects) (with-test-sandbox (with-gitlab-session (let ((project (gitlab-get-project gitlab-project-id)))