-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathmajutsu-remote.el
More file actions
178 lines (150 loc) · 7.49 KB
/
Copy pathmajutsu-remote.el
File metadata and controls
178 lines (150 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
;;; majutsu-remote.el --- Git remote readers for Majutsu -*- lexical-binding: t; -*-
;; Copyright (C) 2026 0WD0
;; Author: 0WD0 <me@0wd0.com>
;; Maintainer: 0WD0 <me@0wd0.com>
;; Keywords: tools, vc
;; URL: https://github.com/0WD0/majutsu
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; Shared readers and completion payloads for jj Git remote names.
;;; Code:
(require 'majutsu)
(require 'majutsu-completion)
(require 'subr-x)
(declare-function magit-section-value-if "magit-section" (type))
(defvar majutsu-remote-name-history nil
"Minibuffer history for exact remote-name input.")
(defvar majutsu-remote-pattern-history nil
"Minibuffer history for remote name-pattern input.")
(autoload 'majutsu-git-remote-remove "majutsu-git" nil t)
(autoload 'majutsu-git-remote-transient "majutsu-git" nil t)
(autoload 'majutsu-git-fetch-remote "majutsu-git" nil t)
(defvar-keymap majutsu-git-remote-section-map
:doc "Keymap for Git remote headings in the bookmark list."
"<remap> <majutsu-delete-thing>" #'majutsu-git-remote-remove
"r" #'majutsu-git-remote-transient
"f" #'majutsu-git-fetch-remote)
(defun majutsu-remote--validate-new-name (remote)
"Signal a user error if REMOTE is not a valid new jj Git remote name."
(cond
((string= remote "git")
(user-error "Git remote named 'git' is reserved for local Git repository"))
((string-match-p "/" remote)
(user-error "Git remotes with slashes are incompatible with jj: %s" remote))))
(defun majutsu-remote--parse-list-line (line)
"Parse one `jj git remote list` LINE into a plist."
(let ((raw (string-trim (substring-no-properties (or line "")))))
(when (and (not (string-empty-p raw))
(string-match "\\`\\([^[:blank:]]+\\)[[:blank:]]+\\(.+?\\)\\(?:[[:blank:]]+(push: \\(.*\\))\\)?\\'" raw))
(list :name (match-string 1 raw)
:fetch-url (string-trim (match-string 2 raw))
:push-url (when-let* ((push (match-string 3 raw)))
(string-trim push))))))
(defun majutsu-remote--completion-suffix (entry)
"Return aligned completion suffix for remote ENTRY."
(let ((fetch (plist-get entry :fetch-url))
(push (plist-get entry :push-url)))
(majutsu-completion-annotation
(majutsu-completion-column "git remote" 10 'majutsu-completion-key)
(majutsu-completion-column fetch 28 'majutsu-completion-file-name)
(majutsu-completion-field
(and push (not (equal push fetch))
(format "push:%s" push))
'majutsu-completion-documentation))))
(defun majutsu-remote--completion-suffix-function (entries)
"Return candidate suffix function backed by remote ENTRIES."
(majutsu-completion-entry-suffix-function
entries
#'majutsu-remote--completion-suffix))
(defun majutsu-remote-candidate-data (&optional directory)
"Return completion payload for Git remote names in DIRECTORY."
(let ((default-directory (or directory default-directory))
(entries (make-hash-table :test #'equal))
candidates)
(condition-case _
(dolist (line (or (majutsu-jj-lines "git" "remote" "list") '()))
(when-let* ((entry (majutsu-remote--parse-list-line line))
(name (plist-get entry :name)))
(unless (gethash name entries)
(push name candidates))
(puthash name entry entries)))
(error nil))
(list :category 'majutsu-remote
:candidates (nreverse candidates)
:entries entries
:annotation-suffix-function
(majutsu-remote--completion-suffix-function entries))))
(defun majutsu-remote-names (&optional directory)
"Return a list of Git remote names for DIRECTORY."
(plist-get (majutsu-remote-candidate-data directory) :candidates))
(defun majutsu-remote-at-point ()
"Return the Git remote name at point, if any."
(magit-section-value-if 'jj-git-remote))
(defun majutsu-read-remote-name (prompt &optional require-match default)
"Read a Git remote name with PROMPT.
If REQUIRE-MATCH is non-nil, require an existing remote name. DEFAULT
is preselected when non-nil; otherwise the remote section at point is used."
(let* ((root (ignore-errors (majutsu--toplevel-safe)))
(payload (majutsu-remote-candidate-data root))
(candidates (plist-get payload :candidates))
(default (or default (majutsu-remote-at-point))))
(cond
(candidates)
(require-match
(user-error "No Git remotes"))
(t
(setq payload (plist-put payload :candidates '("origin")))))
(majutsu-completing-read-payload prompt payload
nil (or require-match 'any) nil
'majutsu-remote-name-history
default 'majutsu-remote nil
(or root default-directory))))
(defun majutsu-transient-read-remote-name (prompt initial-input _history)
"Read a named Git remote for a transient option."
(majutsu-read-remote-name prompt t initial-input))
(defun majutsu-read-new-remote-name (prompt &optional default)
"Read a new Git remote name with PROMPT.
DEFAULT is preselected when non-nil. When there are no remotes, `origin' is
used as the default. Existing remote names are rejected."
(let* ((root (ignore-errors (majutsu--toplevel-safe)))
(payload (majutsu-remote-candidate-data root))
(remotes (plist-get payload :candidates))
(remote (majutsu-completing-read-payload
prompt payload nil 'any nil 'majutsu-remote-name-history
(or default (unless remotes "origin")) 'majutsu-remote nil
(or root default-directory))))
(majutsu-remote--validate-new-name remote)
(when (member remote remotes)
(user-error "Remote already exists: %s" remote))
remote))
(defun majutsu-read-remote-pattern (prompt &optional initial-input history default candidates)
"Read one remote name pattern with PROMPT.
INITIAL-INPUT is inserted for editing. HISTORY defaults to
`majutsu-remote-pattern-history'. DEFAULT is preselected when non-nil.
CANDIDATES defaults to known Git remote names."
(let ((payload (majutsu-remote-candidate-data default-directory)))
(unless (plist-get payload :candidates)
(setq payload (plist-put payload :candidates candidates)))
(majutsu-completing-read-payload
prompt payload
nil 'any initial-input (or history 'majutsu-remote-pattern-history)
default 'majutsu-remote nil default-directory)))
(defun majutsu-transient-read-remote-pattern (prompt initial-input _history)
"Read a Git remote name pattern for a transient option."
(majutsu-read-remote-pattern prompt initial-input nil initial-input))
(defun majutsu-read-remote-patterns (prompt &optional candidates default initial-input history)
"Read remote name patterns with PROMPT.
CANDIDATES defaults to known Git remote names. DEFAULT is preselected when
non-nil. INITIAL-INPUT and HISTORY are forwarded to the minibuffer."
(let ((payload (majutsu-remote-candidate-data default-directory)))
(unless (plist-get payload :candidates)
(setq payload (plist-put payload :candidates candidates)))
(majutsu-completing-read-multiple-payload
prompt payload
nil nil initial-input (or history 'majutsu-remote-pattern-history)
default 'majutsu-remote nil default-directory)))
(defun majutsu-transient-read-remote-patterns (prompt initial-input history)
"Read Git remote name patterns for a repeat transient option."
(majutsu-read-remote-patterns prompt nil nil initial-input history))
(provide 'majutsu-remote)
;;; majutsu-remote.el ends here