Added git show functionality

This commit is contained in:
2026-08-13 12:01:21 +02:00
parent f4f2c76ec6
commit d7b0520530
3 changed files with 246 additions and 42 deletions
+83
View File
@@ -22,6 +22,7 @@
git-clone
git-rev-list
git-diff
git-show
git-help
git-version
git-new-version
@@ -307,6 +308,88 @@
(std-process-git-result cmd exit-code result output out info)))
(std-process-git-result cmd exit-code result output out info))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Show a Git object, render a commit diff as HTML or return structured output.
; pre : The supplied arguments are valid for git show; --list/-l and
; --output=html/--output=-/--output=string are git-cli options.
; post : Git show has completed successfully or an exception has been raised.
; result : Structured Racket data with --list/-l, a string with --output=string,
; otherwise #t.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def-cmd git-show cmd-git-show 'show
(λ (args info)
(let* ((list-output (ormap (λ (e) (member (format "~a" e) '("--list" "-l"))) args))
(output-html (has-git-arg? args #px"^[-][-]output[=]html$"))
(output-stdout (has-git-arg? args #px"^[-][-]output[=][-]$"))
(output-string (has-git-arg? args #px"^[-][-]output[=]string$"))
(name-only (has-git-arg? args #px"^[-][-]name-only$"))
(name-status (has-git-arg? args #px"^[-][-]name-status$"))
(stat (has-git-arg? args #px"^[-][-]stat([=](.*))?$"))
(no-patch (or (has-git-arg? args #px"^[-][-]no-patch$")
(has-git-arg? args "-s")))
(list-formats (filter (λ (x) x)
(list (if name-only 'name-only #f)
(if name-status 'name-status #f)
(if stat 'stat #f)))))
(when (and list-output (or output-html output-stdout output-string))
(error 'git-show "--list/-l cannot be combined with --output=..."))
(when (and list-output (> (length list-formats) 1))
(error 'git-show "--list/-l accepts only one of --stat, --name-only or --name-status"))
(hash-set! info 'scheme-format list-output)
(hash-set! info 'show-list-format (if (null? list-formats) 'stat (car list-formats)))
(hash-set! info 'show-output
(cond (output-html 'html)
(output-stdout 'stdout)
(output-string 'string)
((or stat name-only name-status no-patch) 'stdout)
(else 'html)))
(let ((nargs (filter (λ (e)
(not (member (format "~a" e)
'("--list" "-l"
"--output=html" "--output=-" "--output=string"))))
args)))
(if (and list-output (null? list-formats))
(cons '--stat nargs)
nargs))))
(λ (cmd exit-code result output out info)
(if (and (zero? exit-code) result)
(cond
((hash-ref info 'scheme-format #f)
(let ((lines (filter (λ (line) (not (string=? (string-trim line) ""))) out)))
(case (hash-ref info 'show-list-format 'stat)
((name-only) lines)
((name-status) (map (λ (line) (string-split line "\t")) lines))
((stat)
(let ((file-re #px"^\\s*(.*?)\\s+[|]\\s+([0-9]+)\\s+([+\\-]+)$")
(total-re #px"^([0-9]+) files? changed(, ([0-9]+) insertions?\\(\\+\\))?(, ([0-9]+) deletions?\\(-\\))?$"))
(map (λ (line)
(let* ((trimmed (string-trim line))
(fm (regexp-match file-re line))
(tm (regexp-match total-re trimmed)))
(cond
(fm (list 'file (cadr fm) (string->number (caddr fm)) (cadddr fm)))
(tm (list 'total
(string->number (cadr tm))
(if (cadddr tm) (string->number (cadddr tm)) 0)
(if (list-ref tm 5) (string->number (list-ref tm 5)) 0)))
(else (list 'info line)))))
lines))))))
((eq? (hash-ref info 'show-output 'html) 'string)
(string-join out "\n"))
((eq? (hash-ref info 'show-output 'html) 'html)
(show->html
(string-join
(filter (λ (line) (not (string-prefix? (string-downcase line) "warning:"))) out)
"\n"))
#t)
(else (std-process-git-result cmd exit-code result output out info)))
(std-process-git-result cmd exit-code result output out info))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Search tracked files for a pattern.
; pre : The supplied arguments are valid for git grep.
+120 -41
View File
@@ -1,16 +1,62 @@
#lang racket/base
(require net/sendurl
racket/list
json
xml
racket/string
"config.rkt"
)
(provide diff->html)
(provide diff->html
show->html)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helper functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (make-js . args)
(string-join args "\n"))
(define (highlight-css)
(cfg-get 'diff 'highlight-css
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css")
)
(define (diff2html-min-css)
(cfg-get 'diff 'diff2html-min-css
"https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css")
)
(define (diff2html-ui-min-js)
(cfg-get 'diff 'diff2html-ui-min-js
"https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js")
)
(define (diff-script diff)
(format
(make-js
"window.do_diff = function() {"
"const diff = ~a;"
"const ui = new Diff2HtmlUI("
" document.getElementById('diff'),"
" diff,"
" {"
" drawFileList: true,"
" matching: 'lines',"
" outputFormat: 'side-by-side',"
" });"
"ui.draw();"
"ui.highlightCode();"
"};"
)
(jsexpr->string diff))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exported functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Render a Git diff in a temporary HTML file.
; pre : diff is a unified Git diff string.
@@ -18,44 +64,77 @@
; result : void.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (diff->html diff)
(let ((highlight-css "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css")
(diff2html-min-css "https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css")
(diff2html-ui-min-js "https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"))
(let ((html `(html
(head
(meta ((charset "utf-8")))
(link ((rel "stylesheet") (href ,highlight-css)))
(link ((rel "stylesheet") (href ,diff2html-min-css)))
(script ((src ,diff2html-ui-min-js)) "")
(script ,(format
(make-js
"window.do_diff = function() {"
"const diff = ~a;"
"const ui = new Diff2HtmlUI("
" document.getElementById('diff'),"
" diff,"
" {"
" drawFileList: true,"
" matching: 'lines',"
" outputFormat: 'side-by-side',"
" });"
"ui.draw();"
"ui.highlightCode();"
"};"
)
(jsexpr->string diff))
)
)
(body
(div ((id "diff")))
(script ((type "text/javascript"))
"window.do_diff();")
))))
(let ((tmp-file (build-path (find-system-path 'temp-dir) "racket-git-diff.html")))
(call-with-output-file tmp-file #:exists 'truncate
(λ (out)
(display (xexpr->string html) out)))
(send-url/file tmp-file)))))
(let ((html `(html
(head
(meta ((charset "utf-8")))
(link ((rel "stylesheet") (href ,(highlight-css) )))
(link ((rel "stylesheet") (href ,(diff2html-min-css) )))
(script ((src ,diff2html-ui-min-js)) "")
(script ,(diff-script diff))
)
(body
(div ((id "diff")))
(script ((type "text/javascript"))
"window.do_diff();")
))))
(let ((tmp-file (build-path (find-system-path 'temp-dir) "racket-git-diff.html")))
(call-with-output-file tmp-file #:exists 'truncate
(λ (out)
(display (xexpr->string html) out)))
(send-url/file tmp-file))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Render git show output as commit information followed by a diff.
; pre : show is the textual output produced by git show.
; post : The generated HTML file has been opened in the default browser.
; result : void.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (show->html show)
(let* ((lines (string-split show "\n" #:trim? #f))
(diff-pos (let loop ((rest lines) (n 0))
(cond ((null? rest) #f)
((string-prefix? (car rest) "diff --git ") n)
(else (loop (cdr rest) (+ n 1))))))
(header (string-join (if diff-pos (take lines diff-pos) lines) "\n"))
(diff (string-join (if diff-pos (drop lines diff-pos) '()) "\n"))
(highlight-css "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css")
(diff2html-min-css "https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css")
(diff2html-ui-min-js "https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js")
(html
`(html
(head
(meta ((charset "utf-8")))
(link ((rel "stylesheet") (href ,highlight-css)))
(link ((rel "stylesheet") (href ,diff2html-min-css)))
(style "body { font-family: sans-serif; margin: 1.5em; } pre.commit { white-space: pre-wrap; }")
,@(if diff-pos
`((script ((src ,diff2html-ui-min-js)) "")
(script
,(format
(make-js
"window.do_diff = function() {"
"const diff = ~a;"
"const ui = new Diff2HtmlUI("
" document.getElementById('diff'),"
" diff,"
" {"
" drawFileList: true,"
" matching: 'lines',"
" outputFormat: 'side-by-side',"
" });"
"ui.draw();"
"ui.highlightCode();"
"};")
(jsexpr->string diff))))
'()))
(body
(pre ((class "commit")) ,header)
,@(if diff-pos
`((div ((id "diff")))
(script ((type "text/javascript")) "window.do_diff();"))
'()))))
(tmp-file (build-path (find-system-path 'temp-dir) "racket-git-show.html")))
(call-with-output-file tmp-file #:exists 'truncate
(λ (out) (display (xexpr->string html) out)))
(send-url/file tmp-file)))
+43 -1
View File
@@ -20,7 +20,7 @@ Runs a registered Git @racket[command]. The arguments are passed to the command.
Registered command symbols are @racket['status], @racket['add],
@racket['commit], @racket['push], @racket['pull], @racket['branch],
@racket['clone], @racket['log], @racket['rev-list], @racket['diff],
@racket['grep], @racket['help], @racket['version], and
@racket['show], @racket['grep], @racket['help], @racket['version], and
@racket['new-version].
Most registered commands invoke the Git command with the same name. Some
@@ -94,6 +94,48 @@ extra output lines can also influence how useful @tt{--list} is as a structured
result.
}
@defproc[(git-show [argument any/c] ...) (or/c boolean? string? list?)]{
Shows a Git object.
For a commit that includes a patch, the default git-cli output is HTML. The
commit information is shown above the diff and the diff is rendered using the
same Diff2Html presentation as @racket[git-diff].
The git-cli-specific output options are @tt{--output=html},
@tt{--output=-}, and @tt{--output=string}. @tt{--output=html} explicitly
selects the HTML presentation, @tt{--output=-} keeps Git's normal textual
output, and @tt{--output=string} returns that textual output as a string.
Options such as @tt{--stat}, @tt{--name-only}, @tt{--name-status}, and
@tt{--no-patch} default to textual output because they do not normally contain
a patch.
The git-cli-specific option @tt{--list}, or its short form @tt{-l}, returns a
Racket value. Without another show-format option it implies @tt{--stat}.
@racketblock[
(git-show '-l "9741b1c")
]
The result of @tt{--stat --list} contains @racket['file] and
@racket['total] items:
@racketblock[
'((file "README.md" 67 "+++---")
(file "main.rkt" 532 "++++-------------------------------------------")
(total 9 124 823))
]
With @tt{--name-only --list}, the result is a list of file names. With
@tt{--name-status --list}, every result item is the tab-separated Git
name-status record converted to a list of strings.
@tt{--list}/@tt{-l} cannot be combined with @tt{--output=...}. Only one of
@tt{--stat}, @tt{--name-only}, and @tt{--name-status} can be used with
@tt{--list}.
}
@defproc[(git-grep [argument any/c] ...) list?]{
Searches tracked files. Each result contains the file, optional line number,
optional match count, and matched text. Exit status one means that no matches