branch: externals/vc-jj commit 31ab84e6bf2551a506c45d2d0dda7e3116bf84ee Author: Rudi Schlatte <r...@constantly.at> Commit: Rudi Schlatte <r...@constantly.at>
Implement vc-annotate No support yet for moving between revisions Also add some test machinery to make timestamps, change ids stable (currently unused) --- vc-jj-tests.el | 94 +++++++++++++++++++++++++++++++++++++++++++--------------- vc-jj.el | 39 ++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 24 deletions(-) diff --git a/vc-jj-tests.el b/vc-jj-tests.el index 3a72ecf254..09feb83465 100644 --- a/vc-jj-tests.el +++ b/vc-jj-tests.el @@ -19,7 +19,7 @@ ;;; Commentary: -;; +;; ;;; Code: @@ -27,34 +27,56 @@ (require 'vc) (require 'vc-dir) (require 'vc-jj) - -(defmacro vc-jj-test--with-repo (name &rest body) +(require 'iso8601) +(require 'cl-lib) +(require 'thingatpt) + +(defun vc-jj-test-environment (seq) + "Create a list suitable for prepending to `process-environment'. +The purpose is to make tests reproducible by fixing timestamps, +change ids, author information etc. SEQ is an integer that +modifies the JJ_RANDOMNESS_SEED, JJ_TIMESTAMP and JJ_OP_TIMESTAMP +environment variables. Increasing values for SEQ will result in +increasing timestamps. + +Note that it not necessary to use this function, except when +stably increasing timestamps and stable change ids across test +runs are necessary." + ;; For other potentially relevant variables, see + ;; https://github.com/jj-vcs/jj/blob/d79c7a0dd5b8f9d3d6f9436452dcf0e1600b0b14/cli/tests/common/test_environment.rs#L115 + (let* ((startdate (iso8601-parse "2001-02-03T04:05:06+07:00")) + (timezone (cl-ninth startdate)) + (offset (time-add (encode-time startdate) seq)) + (timestring (format-time-string "%FT%T%:z" offset timezone))) + (list "JJ_EMAIL=j...@example.com" + "JJ_USER=john" + (format "JJ_RANDOMNESS_SEED=%i" (+ 12345 seq)) + (format "JJ_TIMESTAMP=%s" timestring) + (format "JJ_OP_TIMESTAMP=%s" timestring)))) + +(defmacro vc-jj-test-with-repo (name &rest body) "Initialize a repository in a temporary directory and evaluate BODY. - -The current directory will be set to the top of that repository; NAME -will be bound to that directory's file name. Once BODY exits, the -directory will be deleted. - -Some environment variables that control jj's behavior will be set -for the duration of BODY." +The current directory will be set to the top of that repository; +NAME will be bound to that directory's file name. Once BODY +exits, the directory will be deleted. + +jj commands are executed with a fixed username and email; augment +`process-environment' with `vc-jj-test-environment' if control +over timestamps and random number seed (and thereby change ids) +is needed." (declare (indent 1)) `(ert-with-temp-directory ,name (let ((default-directory ,name) - ;; Note: if we need reproducible repository state, use - ;; JJ_RANDOMNESS_SEED=12345 when calling `jj git init', and - ;; increase its value by 1 or call `jj config set --repo - ;; debug.randomness-seed 12346' etc. between each jj call - ;; afterwards -- see - ;; https://github.com/jj-vcs/jj/blob/d79c7a0dd5b8f9d3d6f9436452dcf0e1600b0b14/cli/tests/common/test_environment.rs#L115 - ;; for other relevant environment variables. - (process-environment (append '("JJ_EMAIL=j...@example.com" - "JJ_USER=john") - process-environment))) - (vc-create-repo 'jj) + (process-environment + (append (list "JJ_EMAIL=j...@example.com" "JJ_USER=john") + process-environment))) + (let ((process-environment + (append (vc-jj-test-environment 0) process-environment))) + (vc-create-repo 'jj)) ,@body))) (ert-deftest vc-jj-test-add-file () - (vc-jj-test--with-repo repo + (vc-jj-test-with-repo repo (write-region "New file" nil "README") (should (vc-jj--file-tracked "README")) (should (vc-jj--file-added "README")) @@ -63,7 +85,7 @@ for the duration of BODY." (should (eq (vc-state "README" 'jj) 'added)))) (ert-deftest vc-jj-test-added-tracked () - (vc-jj-test--with-repo repo + (vc-jj-test-with-repo repo (write-region "In first commit" nil "first-file") (vc-jj-checkin '("first-file") "First commit") (write-region "In second commit" nil "second-file") @@ -71,7 +93,7 @@ for the duration of BODY." (should (eq (vc-jj-state "first-file") 'up-to-date)))) (ert-deftest vc-jj-test-conflict () - (vc-jj-test--with-repo repo + (vc-jj-test-with-repo repo (let (branch-1 branch-2 branch-merged) ;; the root change id is always zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz (shell-command "jj new zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz") @@ -91,5 +113,29 @@ for the duration of BODY." (should (eq (vc-jj-state "conflicted.txt") 'conflict)) (should (eq (vc-jj-state "subdir/conflicted.txt") 'conflict))))) +(ert-deftest vc-jj-test-annotate () + (vc-jj-test-with-repo repo + (let ( change-1 change-2 + readme-buffer annotation-buffer) + ;; Create two changes, make sure that the change ids in the + ;; annotation buffer match. This test is supposed to detect + ;; changes in the output format of `jj annotate'. + (write-region "Line 1\n" nil "README") + (setq change-1 (vc-jj-working-revision "README")) + (shell-command "jj commit -m 'First change'") + (write-region "Line 2\n" nil "README" t) + (shell-command "jj describe -m 'Second change'") + (setq change-2 (vc-jj-working-revision "README")) + (find-file "README") + (setq readme-buffer (current-buffer)) + (vc-annotate "README" change-2) + (setq annotation-buffer (current-buffer)) + (goto-char (point-min)) + (should (string-prefix-p (thing-at-point 'word) change-1)) + (forward-line) + (should (string-prefix-p (thing-at-point 'word) change-2)) + (kill-buffer readme-buffer) + (kill-buffer annotation-buffer)))) + (provide 'vc-jj-tests) ;;; vc-jj-tests.el ends here diff --git a/vc-jj.el b/vc-jj.el index 05ecf9a824..c0c92e94cf 100644 --- a/vc-jj.el +++ b/vc-jj.el @@ -32,6 +32,8 @@ (autoload 'vc-switches "vc") (autoload 'ansi-color-apply-on-region "ansi-color") +(autoload 'iso8601-parse "iso8601") +(autoload 'decoded-time-set-defaults "time-date") (add-to-list 'vc-handled-backends 'JJ) @@ -346,6 +348,43 @@ For jj, modify `.gitignore' and call `jj untrack' or `jj track'." 1 0))) +(defun vc-jj-annotate-command (file buf &optional rev) + (with-current-buffer buf + (let ((rev (or rev "@"))) + (call-process "jj" nil t nil "file" "annotate" "-r" rev file)))) + +(defconst vc-jj--annotation-line-prefix-re + (rx (: bol + (group (+ (any "a-z"))) ; change id + " " + (group (+ (any alnum))) ; author + (+ " ") + (group ; iso 8601-ish datetime + (= 4 digit) "-" (= 2 digit) "-" (= 2 digit) " " + (= 2 digit) ":" (= 2 digit) ":" (= 2 digit)) + (+ " ") + (group (+ (any "0-9"))) ; line number + ": ")) + ;; TODO: find out if the output changes when the file got renamed + ;; somewhere in its history + "Regexp for the per-line prefix of the output of 'jj file annotate'. +The regex captures four groups: change id, author, datetime, line number.") + +(defun vc-jj-annotate-time () + (and (re-search-forward vc-jj--annotation-line-prefix-re nil t) + (let* ((dt (match-string 3)) + (dt (and dt (string-replace " " "T" dt))) + (decoded (ignore-errors (iso8601-parse dt)))) + (and decoded + (vc-annotate-convert-time + (encode-time (decoded-time-set-defaults decoded))))))) + +(defun vc-jj-annotate-extract-revision-at-line () + (save-excursion + (beginning-of-line) + (when (looking-at vc-jj--annotation-line-prefix-re) + (match-string-no-properties 1)))) + (defun vc-jj-revision-completion-table (files) (let ((revisions (apply #'process-lines