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

Reply via email to