Skip to content

Commit e7d6a5d

Browse files
authored
Feat: Integrate with magit worktree feature (#216)
* Add centralized Git worktree creation and test * Add worktree action and keybinding with tests * addressing feedbacks * add HISTORY
1 parent d788252 commit e7d6a5d

4 files changed

Lines changed: 100 additions & 0 deletions

File tree

HISTORY.org

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Release history
33

44
** Main branch change
5+
- Feat: Integrate with magit worktree feature
56
- Feat: Modernize PR review tool
67

78
** 1.55

ai-code-git.el

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ Candidate values:
2727
:type 'string
2828
:group 'ai-code)
2929

30+
(defcustom ai-code-git-worktree-root
31+
(expand-file-name "ai-code-worktrees" user-emacs-directory)
32+
"Directory used to host centralized Git worktrees for all repositories."
33+
:type 'directory
34+
:group 'ai-code)
35+
3036
(declare-function ai-code--insert-prompt "ai-code-prompt-mode" (prompt-text))
3137
(declare-function ai-code--ensure-files-directory "ai-code-prompt-mode" ())
3238
(declare-function ai-code--git-root "ai-code-file" (&optional dir))
@@ -755,6 +761,41 @@ buffer from which this command was invoked, instead of visiting the file."
755761
(insert "@" choice)))
756762
(find-file (expand-file-name choice base-dir))))))))
757763

764+
(defun ai-code--git-worktree-repo-dir (git-root)
765+
"Return centralized worktree directory for repository at GIT-ROOT."
766+
(let ((repo-name (file-name-nondirectory (directory-file-name git-root))))
767+
(expand-file-name repo-name ai-code-git-worktree-root)))
768+
769+
;;;###autoload
770+
(defun ai-code-git-worktree-branch (branch start-point)
771+
"Create BRANCH and check it out in a new centralized worktree.
772+
The worktree path is
773+
`ai-code-git-worktree-root/REPO-NAME/BRANCH'."
774+
(interactive
775+
(magit-branch-read-args "Create and checkout branch"))
776+
(let* ((git-root (ai-code--validate-git-repository))
777+
(repo-worktree-dir (ai-code--git-worktree-repo-dir git-root))
778+
(path (expand-file-name branch repo-worktree-dir))
779+
(parent-dir (file-name-directory path)))
780+
(unless (file-directory-p repo-worktree-dir)
781+
(make-directory repo-worktree-dir t))
782+
(when (and parent-dir
783+
(not (file-directory-p parent-dir)))
784+
(make-directory parent-dir t))
785+
(when (zerop (magit-call-git "worktree" "add" "-b" branch
786+
(file-truename path) start-point))
787+
(magit-diff-visit-directory path))))
788+
789+
;;;###autoload
790+
(defun ai-code-git-worktree-action (&optional prefix)
791+
"Dispatch worktree action by PREFIX.
792+
Without PREFIX, call `ai-code-git-worktree-branch'.
793+
With PREFIX (for example C-u), call `magit-worktree-status'."
794+
(interactive "P")
795+
(if prefix
796+
(call-interactively #'magit-worktree-status)
797+
(call-interactively #'ai-code-git-worktree-branch)))
798+
758799
(provide 'ai-code-git)
759800

760801
;;; ai-code-git.el ends here

ai-code.el

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ Shows the current backend label to the right."
445445
("<SPC>" "Send command (C-u: context)" ai-code-send-command)
446446
("@" "Context (add/show/clear)" ai-code-context-action)
447447
("C" "Create file or dir with AI" ai-code-create-file-or-dir)
448+
("w" "New worktree branch (C-u: status)" ai-code-git-worktree-action)
448449
]
449450

450451
["AI Agile Development"

test/test_ai-code-git.el

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,63 @@ When .gitignore is missing some entries, they should be added."
238238
"https://github.com/acme/demo/pull/999")))
239239
(should (string-match-p "Review this pull request\\." prompt))))
240240

241+
(ert-deftest ai-code-test-git-worktree-branch-creates-repo-directory-and-adds-worktree ()
242+
"Create repo worktree directory and invoke git worktree add with expected path."
243+
(let* ((temp-worktree-root (make-temp-file "ai-code-worktree-root-" t))
244+
(ai-code-git-worktree-root temp-worktree-root)
245+
(git-root "/tmp/sample-repo/")
246+
(branch "feature/new-branch")
247+
(start-point "main")
248+
(repo-dir (expand-file-name "sample-repo" temp-worktree-root))
249+
(worktree-path (expand-file-name branch repo-dir))
250+
(worktree-parent-dir (file-name-directory worktree-path))
251+
captured-git-args
252+
captured-visited-path)
253+
(unwind-protect
254+
(cl-letf (((symbol-function 'ai-code--validate-git-repository)
255+
(lambda () git-root))
256+
((symbol-function 'magit-run-git)
257+
(lambda (&rest _args)
258+
(ert-fail "`magit-run-git' should not be used for worktree add status check")))
259+
((symbol-function 'magit-call-git)
260+
(lambda (&rest args)
261+
(setq captured-git-args args)
262+
0))
263+
((symbol-function 'magit-diff-visit-directory)
264+
(lambda (path)
265+
(setq captured-visited-path path))))
266+
(should-not (file-directory-p repo-dir))
267+
(ai-code-git-worktree-branch branch start-point)
268+
(should (file-directory-p repo-dir))
269+
(should (file-directory-p worktree-parent-dir))
270+
(should (equal captured-git-args
271+
(list "worktree"
272+
"add"
273+
"-b"
274+
branch
275+
(file-truename worktree-path)
276+
start-point)))
277+
(should (equal captured-visited-path worktree-path)))
278+
(delete-directory temp-worktree-root t))))
279+
280+
(ert-deftest ai-code-test-git-worktree-action-without-prefix-calls-worktree-branch ()
281+
"Without prefix arg, dispatch to `ai-code-git-worktree-branch'."
282+
(let (captured-fn)
283+
(cl-letf (((symbol-function 'call-interactively)
284+
(lambda (fn &optional _record-flag _keys)
285+
(setq captured-fn fn))))
286+
(ai-code-git-worktree-action nil)
287+
(should (eq captured-fn #'ai-code-git-worktree-branch)))))
288+
289+
(ert-deftest ai-code-test-git-worktree-action-with-prefix-calls-magit-worktree-status ()
290+
"With prefix arg, dispatch to `magit-worktree-status'."
291+
(let (captured-fn)
292+
(cl-letf (((symbol-function 'call-interactively)
293+
(lambda (fn &optional _record-flag _keys)
294+
(setq captured-fn fn))))
295+
(ai-code-git-worktree-action '(4))
296+
(should (eq captured-fn #'magit-worktree-status)))))
297+
241298
(defun ai-code-test--run-pull-or-review-diff-file (choice pr-url &optional review-mode-choice)
242299
"Run `ai-code-pull-or-review-diff-file' with CHOICE and optional PR-URL.
243300
REVIEW-MODE-CHOICE is used for review mode selection when prompted.

0 commit comments

Comments
 (0)