From 99a419c6bbe69be6c6a5af6d5eb12f19ee998246 Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Tue, 24 Feb 2026 00:13:18 +0100 Subject: [PATCH] feat: initial implementation --- magit-clone-jj.el | 156 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 magit-clone-jj.el diff --git a/magit-clone-jj.el b/magit-clone-jj.el new file mode 100644 index 0000000..3b29f97 --- /dev/null +++ b/magit-clone-jj.el @@ -0,0 +1,156 @@ +;;; magit-clone-jj.el --- Clone jujutsu repositories with Magit's interface -*- lexical-binding: t -*- + +;; Author: Lucien Cartier-Tilet +;; Maintainer: Lucien Cartier-Tilet +;; Version: 0.1.0 +;; Package-Requires: ((emacs "28.1") (magit "4.5.0")) +;; Homepage: https://labs.phundrak.com/phundrak/magit-clone-jj +;; Keywords: git tools vcs + + +;; This file is not part of GNU Emacs + +;; 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: + +;; commentary + +;;; Code: + +(require 'magit) + +(defgroup magit-clone-jj nil + "Clone your jj repositories with Magit." + :group 'tools + :prefix "magit-clone-jj-" + :link '(url-link :tag "https://labs.phundrak.com/phundrak/magit-clone-jj")) + +(defcustom magit-clone-jj-default-directory nil + "See `magit-clone-default-directory'." + :package-version '(magit-clone-jj . "0.1.0") + :group 'magit-clone-jj + :type '(choice (const :tag "Value of default-directory") + (directory :tag "Constant directory") + (function :tag "Function's value"))) + +(defcustom magit-clone-jj-executable "jj" + "Local executable of jj." + :package-version '(magit-clone-jj . "0.1.0") + :group 'magit-clone-jj + :type 'path) + +(defcustom magit-clone-jj-remote-executable "jj" + "Remote executable of jj." + :package-version '(magit-clone-jj . "0.1.0") + :group 'magit-clone-jj + :type 'path) + +(defcustom magit-clone-jj-global-arguments + '("--color=never") + "Global jj arguments." + :package-version '(magit-clone-jj . "0.1.0") + :group 'magit-clone-jj + :type '(repeat string)) + +(defun magit-clone-jj-sentinel (process event) + "Default sentinel used by `magit-clone-jj-run-jj-async'." + (when (memq (process-status process) '(exit signal)) + (setq event (substring event 0 -1)) + (when (string-match "^finished" event) + (message (concat (process-name process) " finished"))) + (when (eq process magit-this-process) + (setq magit-this-process nil)))) + +(defun magit-clone-jj-executable () + "Return executable for jj. + +See `magit-git-executable'." + (if (file-remote-p default-directory) + magit-clone-jj-remote-executable + magit-clone-jj-executable)) + +(defun magit-clone-jj-run-jj-async (directory &rest args) + "Start jujutsu and return the process object. + +ARGS is flattened and then used as arguments to jujutsu. Once cloning is +done, open DIRECTORY with `find-file'. + +Inspired by `magit-start-git'." + (let ((default-process-coding-system (magit--process-coding-system)) + (magit-git-global-arguments magit-clone-jj-global-arguments)) + (apply #'magit-start-process + (magit-clone-jj-executable) + nil + (magit-process-git-arguments args)) + ;; Don't refresh the buffer we're calling from. + (process-put magit-this-process 'inhibit-refresh t) + (set-process-sentinel magit-this-process + (lambda (process event) + (magit-clone-jj-sentinel process event) + (find-file directory))))) + +(defun magit-clone-jj-read-args () + "Get all the necessary args for cloning the repository." + (let* ((magit-clone-default-directory magit-clone-jj-default-directory) + (magit-args (magit-clone-read-args))) + (list (car magit-args) + (cadr magit-args) + (yes-or-no-p "Colocate the repository? ")))) + +(defun magit-clone-jj--check-valid-clone-directory (repository directory) + "Check whether the target DIRECTORY is a valid target. + +Taken from `magit-clone-internal'. Refer to it for use of REPOSITORY and +DIRECTORY." + (let ((directory (file-name-as-directory (expand-file-name directory)))) + (when (file-exists-p directory) + (if (file-directory-p directory) + (when (length> (directory-files directory) 2) + (let ((name (magit-clone--url-to-name repository))) + (unless (and name + (setq directory (file-name-as-directory + (expand-file-name name directory))) + (not (file-exists-p directory))) + (user-error "%s already exists" directory)))) + (user-error "%s already exists and is not a directory" directory))))) + +(defun magit-clone-jj--internal (repository directory colocate-p) + "Clone REPOSITORY to DIRECTORY using jj. + +If COLOCATE-P is t, colocate the repository with git." + (let ((directory (file-name-as-directory (expand-file-name directory)))) + (magit-clone-jj--check-valid-clone-directory repository directory) + (magit-clone-jj-run-jj-async directory + "git" + "clone" + (if colocate-p "--colocate" nil) + "--" + repository + (magit-convert-filename-for-git directory)))) + +;;;###autoload +(defun magit-clone-jj (repository directory colocate-p) + "Clone a git REPOSITORY using jujutsu into a DIRECTORY. + +If COLOCATE-P is t, then colocate the repository with git. + +Depends on Magit. Inspired by `magit-clone-regular'." + (interactive (magit-clone-jj-read-args)) + (magit-clone-jj--internal repository directory colocate-p)) + +(provide 'magit-clone-jj) + +;;; magit-clone-jj.el ends here