Skip to content

Commit 417cc8e

Browse files
authored
Merge pull request #1423 from vindarel/legit-commits-log
legit: "l l" to show the commits log with pagination
2 parents 149cd26 + 99a4763 commit 417cc8e

File tree

4 files changed

+206
-21
lines changed

4 files changed

+206
-21
lines changed

extensions/legit/README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,22 @@ You can push to the current remote branch with `P p` and pull changes (fetch) wi
8585

8686
Note: after pressing "P" or "F", you will not see an intermediate window giving you choices. Just press "P p" one after the other.
8787

88+
## Show commits log
89+
90+
Press `l l` (lowercase "L", twice) to show the commits log in a dedicated buffer, with pagination.
91+
92+
It defaults to showing the first 200 commits. Press `f` or `b` to see
93+
the next or the previous page of the commits history.
94+
95+
Press `F` and `B` to go to the last or first page of commits.
96+
97+
Configuration:
98+
99+
- you can set `lem/porcelain:*commits-log-page-size*` to another length (defaults to 200).
100+
101+
Note that going to the last page with `B` might take a few seconds on large repositories.
102+
103+
88104
## Interactive rebase
89105

90106
You can start a Git interactive rebase. Place the cursor on a commit you want to rebase from, and press `r i`.
@@ -217,7 +233,6 @@ Please report any bug, and please let's discuss before you open a Github issue f
217233
and then:
218234

219235
- visual submenu to pick subcommands
220-
- view log
221236
- stage only selected region (more precise than hunks)
222237
- unstage/stage/discard multiple files
223238
- stashes

extensions/legit/legit.lisp

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Done:
3030
- rebase interactively (see legit-rebase)
3131
- basic Fossil support (current branch, add change, commit)
3232
- basic Mercurial support
33+
- show the commits log, with pagination
3334
3435
Ongoing:
3536
@@ -79,6 +80,14 @@ Currently Git-only. Concretely, this calls Git with the -w option.")
7980
(define-key lem/peek-legit:*peek-legit-keymap* "F p" 'legit-pull)
8081
(define-key *legit-diff-mode-keymap* "F p" 'legit-pull)
8182

83+
;; commits log
84+
(define-key lem/peek-legit:*peek-legit-keymap* "l l" 'legit-commits-log)
85+
(define-key lem/peek-legit:*peek-legit-keymap* "f" 'legit-commits-log-next-page)
86+
(define-key lem/peek-legit:*peek-legit-keymap* "b" 'legit-commits-log-previous-page)
87+
(define-key lem/peek-legit:*peek-legit-keymap* "F" 'legit-commits-log-last-page)
88+
(define-key lem/peek-legit:*peek-legit-keymap* "B" 'legit-commits-log-first-page)
89+
(define-key *legit-diff-mode-keymap* "l l" 'legit-commits-log)
90+
8291
;; rebase
8392
;;; interactive
8493
(define-key lem/peek-legit:*peek-legit-keymap* "r i" 'legit-rebase-interactive)
@@ -471,14 +480,14 @@ Currently Git-only. Concretely, this calls Git with the -w option.")
471480
:stage-function (make-stage-function file)
472481
:unstage-function (make-unstage-function file :already-unstaged t)
473482
:discard-file-function (make-discard-file-function file))
474-
(insert-string point
475-
(format nil "~10a ~a"
483+
(insert-string point
484+
(format nil "~10a ~a"
476485
(case type
477486
(:modified "modified")
478487
(:deleted "deleted")
479488
(t ""))
480489
file)
481-
:attribute 'lem/peek-legit:filename-attribute
490+
:attribute 'lem/peek-legit:filename-attribute
482491
:read-only t)))
483492
(lem/peek-legit:collector-insert "<none>"))
484493

@@ -495,15 +504,15 @@ Currently Git-only. Concretely, this calls Git with the -w option.")
495504
:stage-function (make-stage-function file)
496505
:unstage-function (make-unstage-function file)
497506
:discard-file-function (make-discard-file-function file :is-staged t))
498-
(insert-string point
499-
(format nil "~10a ~a"
507+
(insert-string point
508+
(format nil "~10a ~a"
500509
(case type
501510
(:modified "modified")
502511
(:added "created")
503512
(:deleted "deleted")
504513
(t ""))
505514
file)
506-
:attribute 'lem/peek-legit:filename-attribute
515+
:attribute 'lem/peek-legit:filename-attribute
507516
:read-only t)))
508517
(lem/peek-legit:collector-insert "<none>"))
509518

@@ -621,6 +630,81 @@ Currently Git-only. Concretely, this calls Git with the -w option.")
621630
"Move point to the previous header of this VCS window."
622631
(lem/peek-legit:peek-legit-previous-header))
623632

633+
(define-command legit-commits-log () ()
634+
"List commits on a new buffer."
635+
(with-current-project ()
636+
(display-commits-log 0)))
637+
638+
(defun display-commits-log (offset)
639+
"Display the commit lines on a dedicated legit buffer."
640+
(let* ((commits (lem/porcelain:commits-log :offset offset :limit lem/porcelain:*commits-log-page-size*)))
641+
(lem/peek-legit:with-collecting-sources (collector :buffer :commits-log :read-only nil)
642+
(lem/peek-legit:collector-insert
643+
(format nil "Commits (~A):" offset)
644+
:header t)
645+
(if commits
646+
(progn
647+
(loop for commit in commits
648+
for line = nil
649+
for hash = nil
650+
for message = nil
651+
if (consp commit)
652+
do (setf line (getf commit :line)
653+
hash (getf commit :hash)
654+
message (getf commit :message))
655+
else
656+
do (setf line commit)
657+
do (lem/peek-legit:with-appending-source
658+
(point :move-function (make-show-commit-function hash)
659+
:visit-file-function (lambda ())
660+
:stage-function (lambda () )
661+
:unstage-function (lambda () ))
662+
(with-point ((start point))
663+
(when hash
664+
(insert-string point hash :attribute 'lem/peek-legit:filename-attribute :read-only t))
665+
(if message
666+
(insert-string point message)
667+
(insert-string point line))
668+
(when hash
669+
(put-text-property start point :commit-hash hash)))))
670+
(setf (buffer-value (lem/peek-legit:collector-buffer collector) 'commits-offset) offset))
671+
(lem/peek-legit:collector-insert "<no commits>")))))
672+
673+
(define-command legit-commits-log-next-page () ()
674+
"Show the next page of the commits log."
675+
(with-current-project ()
676+
(let* ((buffer (current-buffer))
677+
(window-height (window-height (current-window)))
678+
(current-offset (or (buffer-value buffer 'commits-offset) 0))
679+
(new-offset (+ current-offset lem/porcelain:*commits-log-page-size*))
680+
(commits (lem/porcelain:commits-log :offset new-offset
681+
:limit lem/porcelain:*commits-log-page-size*)))
682+
(if commits
683+
(display-commits-log new-offset)
684+
(message "No more commits to display.")))))
685+
686+
(define-command legit-commits-log-previous-page () ()
687+
"Show the previous page of the commits log."
688+
(with-current-project ()
689+
(let* ((buffer (current-buffer))
690+
(window-height (window-height (current-window)))
691+
(current-offset (or (buffer-value buffer 'commits-offset) 0))
692+
(new-offset (max 0 (- current-offset lem/porcelain:*commits-log-page-size*))))
693+
(display-commits-log new-offset))))
694+
695+
(define-command legit-commits-log-first-page () ()
696+
"Go to the first page of the commit log."
697+
(with-current-project ()
698+
(display-commits-log 0)))
699+
700+
(define-command legit-commits-log-last-page () ()
701+
"Go to the last page of the commit log."
702+
(with-current-project ()
703+
(let* ((commits-per-page lem/porcelain:*commits-log-page-size*)
704+
(last-page-offset (* (floor (/ (1- (lem/porcelain:commit-count)) commits-per-page))
705+
commits-per-page)))
706+
(display-commits-log last-page-offset))))
707+
624708
(define-command legit-quit () ()
625709
"Quit"
626710
(lem/peek-legit:quit)

extensions/legit/peek-legit.lisp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,27 +234,41 @@ Notes:
234234
(start-move-point (buffer-point (collector-buffer collector)))
235235
(show-matched-line)))
236236

237-
(defun make-peek-legit-buffer ()
238-
(let ((buffer (make-buffer "*peek-legit*"
237+
(defun make-peek-legit-buffer (&key (name "*peek-legit*"))
238+
"Get or create a buffer of name NAME. By default, use a `*peek-legit*' buffer.
239+
This is where we will display legit information (status…)."
240+
(let ((buffer (make-buffer name
239241
:temporary t
240242
:enable-undo-p t
241243
:directory (uiop:getcwd))))
242244
(setf (variable-value 'line-wrap :buffer buffer) nil)
243245
buffer))
244246

245-
(defun call-with-collecting-sources (function &key read-only)
246-
(let* ((*collector* (make-instance 'collector :buffer (make-peek-legit-buffer)))
247+
(defun call-with-collecting-sources (function &key read-only buffer )
248+
"Initialize variables to display things on a legit buffer.
249+
250+
BUFFER: either :status or :commits-log.
251+
READ-ONLY: boolean."
252+
(let* ((*collector* (make-instance 'collector
253+
:buffer
254+
(make-peek-legit-buffer
255+
:name
256+
(case buffer
257+
(:status "*peek-legit*")
258+
(:commits-log "*legit-commits-log*")
259+
(t (error "Unknown buffer name to display legit data: ~a" buffer))))))
247260
(point (buffer-point (collector-buffer *collector*))))
248261
(declare (ignorable point))
249262
(funcall function *collector*)
250263
(when read-only
251264
(setf (buffer-read-only-p (collector-buffer *collector*)) t))
252265
(display *collector*)))
253266

254-
(defmacro with-collecting-sources ((collector &key (read-only t)) &body body)
267+
(defmacro with-collecting-sources ((collector &key (buffer :status) (read-only t)) &body body)
255268
`(call-with-collecting-sources (lambda (,collector)
256269
(declare (ignorable ,collector))
257270
,@body)
271+
:buffer ,buffer
258272
:read-only ,read-only))
259273

260274
(defun call-with-appending-source (insert-function

extensions/legit/porcelain.lisp

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
(defpackage :lem/porcelain
2+
(uiop:define-package :lem/porcelain
33
(:use :cl)
44
(:shadow :push)
55
(:import-from :trivial-types
@@ -26,7 +26,9 @@
2626
:stage
2727
:unstage
2828
:vcs-project-p
29-
)
29+
:commits-log
30+
:*commits-log-page-size*
31+
:commit-count)
3032
(:documentation "Functions to run VCS operations: get the list of changes, of untracked files, commit, push… Git support is the main goal, a simple layer is used with other VCS systems (Fossil, Mercurial).
3133
3234
On interactive commands, Legit will check what VCS is in use in the current project.
@@ -43,7 +45,7 @@ Supported version control systems:
4345
- Fossil: preliminary support
4446
- Mercurial: preliminary support
4547
46-
TODOs:
48+
TODOs:
4749
4850
Mercurial:
4951
@@ -67,6 +69,9 @@ Mercurial:
6769
(defvar *nb-latest-commits* 10
6870
"Number of commits to show in the status.")
6971

72+
(defvar *commits-log-page-size* 200
73+
"Number of commits to show in the commits log.")
74+
7075
(defvar *branch-sort-by* "-creatordate"
7176
"When listing branches, sort by this field name.
7277
Prefix with \"-\" to sort in descending order.
@@ -470,8 +475,13 @@ allows to learn about the file state: modified, deleted, ignored… "
470475
(str:lines
471476
(run-git (list "log" "--pretty=oneline" "-n" (princ-to-string n))))))
472477

473-
(defun git-latest-commits (&key (hash-length 8))
474-
(let ((lines (%git-list-latest-commits)))
478+
(defun git-latest-commits (&key (n *commits-log-page-size*) (hash-length 8) (offset 0))
479+
(let* ((n-arg (when n (list "-n" (princ-to-string n))))
480+
(lines (str:lines
481+
(run-git (append (list "log"
482+
"--pretty=oneline"
483+
"--skip" (princ-to-string offset))
484+
n-arg)))))
475485
(loop for line in lines
476486
for space-position = (position #\space line)
477487
for small-hash = (subseq line 0 hash-length)
@@ -480,8 +490,8 @@ allows to learn about the file state: modified, deleted, ignored… "
480490
:message message
481491
:hash small-hash))))
482492

483-
(defun hg-latest-commits (&key (hash-length 8))
484-
(declare (ignorable hash-length))
493+
(defun hg-latest-commits (&key (n *nb-latest-commits*) (hash-length 8))
494+
(declare (ignorable n hash-length))
485495
(let ((out (run-hg "log")))
486496
;; Split by paragraph.
487497
#| $ hg log
@@ -539,7 +549,7 @@ summary: test
539549
;; return bare result.
540550
(str:lines (run-fossil "timeline")))
541551

542-
(defun latest-commits (&key (hash-length 8))
552+
(defun latest-commits (&key (n *nb-latest-commits*) (hash-length 8))
543553
"Return a list of strings or plists.
544554
The plist has a :hash and a :message, or simply a :line."
545555
(case *vcs*
@@ -548,7 +558,69 @@ summary: test
548558
(:hg
549559
(hg-latest-commits))
550560
(t
551-
(git-latest-commits :hash-length hash-length))))
561+
(git-latest-commits :n n :hash-length hash-length))))
562+
563+
(defun commits-log (&key (offset 0) limit (hash-length 8))
564+
"Return a list of commits starting from the given offset.
565+
If a limit is not provided, it returns all commits after the offset."
566+
(case *vcs*
567+
(:fossil
568+
(fossil-commits-log :offset offset :limit limit :hash-length hash-length))
569+
(:hg
570+
(hg-commits-log :offset offset :limit limit :hash-length hash-length))
571+
(:git
572+
(git-commits-log :offset offset :limit limit :hash-length hash-length))
573+
(t
574+
(porcelain-error "Unknown VCS: ~a" *vcs*))))
575+
576+
(defun git-commits-log (&key (offset 0) limit (hash-length 8))
577+
(git-latest-commits :n limit
578+
:hash-length hash-length
579+
:offset offset))
580+
581+
(defun hg-commits-log (&key (offset 0) limit (hash-length 8))
582+
(declare (ignorable hash-length))
583+
(let* ((commits (hg-latest-commits))
584+
(total-commits (length commits))
585+
(end (when limit
586+
(min total-commits (+ offset limit)))))
587+
(if (>= offset total-commits)
588+
nil
589+
(subseq commits offset end))))
590+
591+
(defun fossil-commits-log (&key (offset 0) limit (hash-length 8))
592+
(declare (ignorable hash-length))
593+
(let* ((commits (fossil-latest-commits))
594+
(total-commits (length commits))
595+
(end (when limit (min total-commits (+ offset limit)))))
596+
(if (>= offset total-commits)
597+
nil
598+
(subseq commits offset end))))
599+
600+
(defun commit-count ()
601+
"Get the total number of commits in the current branch."
602+
(case *vcs*
603+
(:git
604+
(parse-integer
605+
(str:trim (run-git '("rev-list" "--count" "HEAD")))))
606+
(:hg
607+
(parse-integer
608+
(str:trim (run-hg '("id" "--num" "--rev" "tip")))))
609+
(:fossil
610+
(length))
611+
(t
612+
(porcelain-error "commit-count not implemented for VCS: ~a" *vcs*))))
613+
614+
(defun %not-fossil-commit-line (line)
615+
(str:starts-with-p "+++ no more data" line))
616+
617+
(defun fossil-commit-count ()
618+
;; Not really tested in Lem.
619+
(length
620+
;; Does the timeline command always end with "+++ no more data (1) +++" ?
621+
(remove-if #'%not-fossil-commit-line
622+
(str:lines
623+
(run-fossil (list "timeline" "--oneline"))))))
552624

553625
;; stage, add files.
554626
(defun git-stage (file)

0 commit comments

Comments
 (0)