Compare commits

5 Commits

8 changed files with 611 additions and 100 deletions
+46 -39
View File
@@ -1,51 +1,58 @@
#lang racket #lang racket/base
(require racket-makefile (require racket-makefile
package-zipper package-zipper
net/sendurl net/sendurl
) )
(target all
(displayln "use (make clean) or (make package)")
)
(target clean
(for-each (λ (f) (displayln f) (rm-f f)) (list-files "." #px"([.]bak|~)$" #:recursive #t))
(for-each (λ (d) (displayln d) (rm-rf d)) (list-dirs "." #px"(compiled|doc|docs)$" #:recursive #t))
(when (directory-exists? "scribblings")
(for-each (λ (f) (displayln f) (rm-f f)) (list-files "scribblings" #px"[.](css|js|html)$")))
)
(target package
(deps clean)
(zip-package))
(target zip
(deps package))
(define doc-target "docs/git.html") (define doc-target "docs/git.html")
(define doc-src "scribblings/git.scrbl") (define doc-src "scribblings/git.scrbl")
(target doc-target (makefile git-cli
(deps doc-src)
(unless (directory-exists? "docs")
(make-directory "docs"))
(raco '(scribble --html +m --dest "docs" $<)))
(target doc (target all
(deps doc-target) (displayln "use (make clean) or (make package)")
(displayln "Documentation built") )
)
(target showdoc (target clean
(deps doc) (for-each (λ (f) (displayln f) (rm-f f)) (list-files "." #px"([.]bak|~)$" #:recursive #t))
(send-url/file doc-target)) (for-each (λ (d) (displayln d) (rm-rf d)) (list-dirs "." #px"(compiled|doc|docs)$" #:recursive #t))
(when (directory-exists? "scribblings")
(for-each (λ (f) (displayln f) (rm-f f)) (list-files "scribblings" #px"[.](css|js|html)$")))
)
(target refresh (target version
(displayln "Refreshing makefile") (displayln
(refresh-makefile) (git 'next-version)))
(displayln "done.")
) (target package
(target setup (deps clean)
(raco '(setup git))) (zip-package))
(target zip
(deps package))
(target doc-target
(deps doc-src)
(unless (directory-exists? "docs")
(make-directory "docs"))
(raco '(scribble --html +m --dest "docs" $<)))
(target doc
(deps doc-target)
(displayln "Documentation built")
)
(target showdoc
(deps doc)
(send-url/file doc-target))
(target refresh
(displayln "Refreshing makefile")
(refresh-makefile)
(displayln "done.")
)
(target setup
(raco '(setup git)))
)
+74 -15
View File
@@ -23,13 +23,37 @@ procedure and through direct procedures.
``` ```
`git` is an ordinary procedure. The first argument is the Git command symbol `git` is an ordinary procedure. The first argument is the Git command symbol
and the remaining arguments are passed to that command. and the remaining arguments are passed to that command. Registered commands use
their git-cli wrapper and can provide structured Racket results or additional
behavior. Any other command is passed directly to the installed Git executable
and handled with git-cli's standard command result processing.
For example, commands that do not have a dedicated wrapper can still be used:
```racket
(git 'blame "main.rkt")
(git* clean -n)
(git* worktree list)
(git* archive --format=zip HEAD)
```
A successful fallback command returns `#t` after displaying normal Git output.
A failing fallback command raises the same standard git-cli error as an ordinary
pass-through wrapper.
`git*` is the compact command-style syntax. Bare arguments are converted to `git*` is the compact command-style syntax. Bare arguments are converted to
strings, so `(git* remote get-url origin)` is equivalent to strings, so `(git* remote get-url origin)` is equivalent to
`(git 'remote "get-url" "origin")`. Use `(eval expression)` when an argument `(git 'remote "get-url" "origin")`. A bare identifier is therefore command-line
must come from a Racket expression. `gt` remains available as a compatibility text, not the value of a Racket variable or procedure with the same name. Use
alias for `git*`. `(eval expression)` when an argument must come from a Racket expression.
For example, `(git* switch branch)` passes the text `"branch"`, while
`(git* switch (eval branch))` passes the value of the Racket variable `branch`.
git-cli-specific wrappers should accept the textual arguments produced by
`git*`; `new-version` accepts both symbols and text, so both
`(git 'new-version 'min)` and `(git* new-version min)` work.
`gt` remains available as a compatibility alias for `git*`.
```racket ```racket
(git* init) (git* init)
@@ -67,14 +91,22 @@ behavior:
Git is searched on `PATH`. Git itself remains responsible for remotes, Git is searched on `PATH`. Git itself remains responsible for remotes,
credentials, SSH keys, pull strategy, and other repository configuration. credentials, SSH keys, pull strategy, and other repository configuration.
## Rash integration
Rash integration is provided by the separate `rash-git-cli` package. The `git-cli` package itself has no dependency on Rash or Linea.
## Commands ## Commands
The package currently registers commands including `init`, `status`, `add`, `commit`, The package registers wrappers for commands where git-cli adds useful behavior,
`push`, `pull`, `fetch`, `config`, `branch`, `remote`, `stash`, `restore`, `reset`, `revert`, `rebase`, `merge`, `cherry-pick`, `mergetool`, `switch`, `clone`, `tag`, `log`, including `init`, `status`, `add`, `commit`, `push`, `pull`, `fetch`, `config`,
`rev-list`, `diff`, `show`, `grep`, `help`, `version`, and `new-version`. `branch`, `remote`, `stash`, `restore`, `reset`, `revert`, `rebase`, `merge`,
`cherry-pick`, `mergetool`, `switch`, `clone`, `tag`, `log`, `rev-list`, `diff`,
`show`, `grep`, `help`, `version`, and `new-version`. Other Git commands do not
need a wrapper and are passed directly to Git.
Most are also exported as direct procedures such as `git-init`, `git-status`, `git-add`, Most registered commands are also exported as direct procedures such as
`git-fetch`, `git-config`, `git-switch`, `git-tag`, `git-log`, `git-diff`, and `git-show`. `git-init`, `git-status`, `git-add`, `git-fetch`, `git-config`, `git-switch`,
`git-tag`, `git-log`, `git-diff`, and `git-show`.
See the Scribble documentation for command-specific behavior and return values. See the Scribble documentation for command-specific behavior and return values.
@@ -111,17 +143,41 @@ The non-interactive forms are:
```racket ```racket
(git* config editor --list) (git* config editor --list)
(git* config editor --downloads)
(git* config editor vscode) (git* config editor vscode)
(git* config editor notepad++)
(git* config editor auto) (git* config editor auto)
(git 'config 'editor "C:\\Program Files\\MyEditor\\editor.exe --wait") (git 'config 'editor "C:\\Program Files\\MyEditor\\editor.exe --wait")
``` ```
`--list` returns `(name description command current?)` items. A known editor `--list` returns `(name description command current?)` items and `--downloads`
name selects the matching detected editor. `auto` clears the explicit git-cli returns official download pointers for optional editors. A known editor name
editor choice and returns to automatic detection. Any other single value is selects the matching detected editor. `auto` clears the explicit git-cli editor
stored as the editor command. Changing the editor immediately updates choice and returns to automatic detection. Any other single value is stored as
`GIT_EDITOR` and `GIT_SEQUENCE_EDITOR` for subsequent Git commands. the editor command. Changing the editor immediately updates `GIT_EDITOR` and
`GIT_SEQUENCE_EDITOR` for subsequent Git commands.
On Windows, Notepad++ is detected both on `PATH` and in the normal Program Files
locations. It is started with `-multiInst -nosession`, so Git waits for the
separate editor instance to close.
`config mergetool` uses the same git-cli configuration pattern:
```racket
(git* config mergetool)
(git* config mergetool --list)
(git* config mergetool --downloads)
(git* config mergetool winmerge)
(git* config mergetool auto)
```
`--list` returns `(name description path current?)` items. A known tool name
selects the detected tool, while another single value is stored as the Git
mergetool name. `auto` clears the explicit git-cli choice and returns to
automatic detection. The interactive form also offers download/install
suggestions.
## Low-level Git execution ## Low-level Git execution
@@ -180,7 +236,7 @@ The editor can be inspected or configured explicitly:
The editor search first checks `PATH` and then well-known platform locations. The editor search first checks `PATH` and then well-known platform locations.
On Windows this includes the normal per-user and Program Files locations for On Windows this includes the normal per-user and Program Files locations for
VS Code, with Notepad as fallback. On macOS the standard Visual Studio Code VS Code and Notepad++, with Notepad as fallback. On macOS the standard Visual Studio Code
application bundle and TextEdit are recognized. On Linux common `/usr`, application bundle and TextEdit are recognized. On Linux common `/usr`,
`/usr/local`, and Snap locations are checked for VS Code, Kate, Gedit, and Xed. `/usr/local`, and Snap locations are checked for VS Code, Kate, Gedit, and Xed.
@@ -193,8 +249,11 @@ If no tool is found, Git is left to select its own default.
```racket ```racket
(find-mergetool) (find-mergetool)
(find-mergetools)
(find-mergetool-path) (find-mergetool-path)
(mergetool-downloads)
(set-mergetool! "winmerge") (set-mergetool! "winmerge")
(set-mergetool-auto!)
(git* mergetool) (git* mergetool)
(git* mergetool --tool=meld) (git* mergetool --tool=meld)
``` ```
+4 -3
View File
@@ -2,7 +2,7 @@
(define collection "git-cli") (define collection "git-cli")
(define pkg-desc "Command-line-like Git operations for Racket, interface to the git cli command") (define pkg-desc "Command-line-like Git operations for Racket, interface to the git cli command")
(define version "0.3.40") (define version "0.4.7")
(define pkg-authors '("Hans Dijkema")) (define pkg-authors '("Hans Dijkema"))
(define license 'MIT) (define license 'MIT)
@@ -13,11 +13,12 @@
"racket-index" "racket-index"
"scribble-lib" "scribble-lib"
"net-lib" "net-lib"
)) ))
(define build-deps (define build-deps
'("rackunit-lib" '("rackunit-lib"
"racket-doc")) "racket-doc"
))
(define scribblings (define scribblings
'(("scribblings/git-cli.scrbl" () ("git-cli")))) '(("scribblings/git-cli.scrbl" () ("git-cli"))))
+216 -16
View File
@@ -10,6 +10,7 @@
"private/find-mergetool.rkt" "private/find-mergetool.rkt"
simple-log simple-log
racket/string racket/string
racket/list
net/sendurl net/sendurl
) )
@@ -48,11 +49,15 @@
git-next-version git-next-version
find-editor find-editor
find-editors find-editors
editor-downloads
set-editor! set-editor!
set-editor-auto! set-editor-auto!
find-mergetool find-mergetool
find-mergetools
find-mergetool-path find-mergetool-path
mergetool-downloads
set-mergetool! set-mergetool!
set-mergetool-auto!
default-git-authentication-handler default-git-authentication-handler
current-git-authentication-handler current-git-authentication-handler
exn:fail:git-auth? exn:fail:git-auth?
@@ -91,17 +96,26 @@
(git* cmd arg ...)))) (git* cmd arg ...))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Invoke a supported Git command through the command table. ; goal : Invoke a Git command through a registered wrapper or direct fallback.
; pre : command is a registered Git command symbol. ; pre : command identifies a Git command and args contains its arguments.
; post : The selected command has processed all supplied arguments. ; post : Registered commands use their wrapper; other commands are passed to Git.
; result : The command-specific result. ; result : The command-specific result or the standard Git command result.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (git command . args) (define (git command . args)
((hash-ref git-commands command (let ((cmd (hash-ref git-commands command #f)))
(λ () (if cmd
(error "Not a supported or recognized git command: " command))) (cmd args)
args)) (let-values (((exit-code output)
(run-git (cons command (flatten args)))))
(let-values (((result out) (git-out command output)))
(std-process-git-result
command
exit-code
result
output
out
(make-hash)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Supporting functions ;; Supporting functions
@@ -261,6 +275,8 @@
(+ (length editors) 1))) (+ (length editors) 1)))
(displayln (format " ~a. Automatic detection" (displayln (format " ~a. Automatic detection"
(+ (length editors) 2))) (+ (length editors) 2)))
(displayln (format " ~a. Download/install suggestions"
(+ (length editors) 3)))
(displayln " 0. Cancel") (displayln " 0. Cancel")
(newline)) (newline))
@@ -275,6 +291,7 @@
(display-editor-selection editors) (display-editor-selection editors)
(let* ((custom-index (+ (length editors) 1)) (let* ((custom-index (+ (length editors) 1))
(auto-index (+ (length editors) 2)) (auto-index (+ (length editors) 2))
(downloads-index (+ (length editors) 3))
(choice (choice
(input-prompt (input-prompt
"Editor: " "Editor: "
@@ -286,7 +303,7 @@
(let ((n (string->number value))) (let ((n (string->number value)))
(if (and n (if (and n
(integer? n) (integer? n)
(<= 0 n auto-index)) (<= 0 n downloads-index))
n n
#f)))))))) #f))))))))
(cond (cond
@@ -309,6 +326,10 @@
(set-editor! command)))) (set-editor! command))))
((= choice auto-index) ((= choice auto-index)
(set-editor-auto!)) (set-editor-auto!))
((= choice downloads-index)
(display-download-pointers
"Suggested editors:"
(editor-downloads)))
(else #f))))) (else #f)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -326,6 +347,10 @@
(git-argument=? (car args) '--list)) (git-argument=? (car args) '--list))
(available-editors)) (available-editors))
((and (= (length args) 1)
(git-argument=? (car args) '--downloads))
(editor-downloads))
((and (= (length args) 1) ((and (= (length args) 1)
(git-argument=? (car args) 'auto)) (git-argument=? (car args) 'auto))
(set-editor-auto!)) (set-editor-auto!))
@@ -337,7 +362,177 @@
(set-editor! (format "~a" (car args)))))) (set-editor! (format "~a" (car args))))))
(else (else
(error 'git-config "Expected config editor [--list|auto|editor-name|editor-command]")))) (error 'git-config "Expected config editor [--list|--downloads|auto|editor-name|editor-command]"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Display official download pointers.
; pre : items contains (name description url) items.
; post : The pointers have been displayed.
; result : void.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (display-download-pointers title items)
(displayln title)
(newline)
(for-each
(λ (item)
(displayln (format " ~a" (cadr item)))
(displayln (format " ~a" (caddr item)))
(newline))
items)
(void))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return the merge tool description matching a configured tool name.
; pre : name can be formatted as a merge tool name.
; post : The merge tool list has only been inspected.
; result : A (name description path) item, or #f.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (find-mergetool-by-name name)
(let ((name* (string-downcase (format "~a" name))))
(let loop ((tools (find-mergetools)))
(cond
((null? tools) #f)
((string=? (string-downcase (car (car tools))) name*)
(car tools))
(else
(loop (cdr tools)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return a Racket-oriented list of available merge tools.
; pre : The platform merge tool finder is available.
; post : No merge tool has been started.
; result : (name description path current?) items.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (available-mergetools)
(let ((current (find-mergetool)))
(map
(λ (tool)
(list (car tool)
(cadr tool)
(caddr tool)
(and current
(string=? current (car tool)))))
(find-mergetools))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Display the interactive git-cli merge tool selection.
; pre : tools contains the discovered merge tool descriptions.
; post : The choices have been displayed.
; result : void.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (display-mergetool-selection tools)
(displayln "Available merge tools:")
(newline)
(let loop ((items tools)
(index 1))
(unless (null? items)
(let* ((tool (car items))
(current (find-mergetool))
(current? (and current
(string=? current (car tool)))))
(displayln
(format " ~a. ~a~a"
index
(cadr tool)
(if current? " [current]" "")))
(displayln (format " ~a" (caddr tool)))
(newline)
(loop (cdr items) (+ index 1)))))
(displayln (format " ~a. Specify another merge tool name"
(+ (length tools) 1)))
(displayln (format " ~a. Automatic detection"
(+ (length tools) 2)))
(displayln (format " ~a. Download/install suggestions"
(+ (length tools) 3)))
(displayln " 0. Cancel")
(newline))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Ask the user to choose or enter the merge tool used by git-cli.
; pre : Standard input and output are available.
; post : A selected merge tool has been stored, automatic detection was restored,
; download pointers were shown, or the operation was cancelled.
; result : The selected merge tool name, #f after cancellation, or void after pointers.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (configure-mergetool-interactively)
(let ((tools (find-mergetools)))
(display-mergetool-selection tools)
(let* ((custom-index (+ (length tools) 1))
(auto-index (+ (length tools) 2))
(downloads-index (+ (length tools) 3))
(choice
(input-prompt
"Merge tool: "
#:loop-until
(λ (value)
(cond
((eof-object? value) 'cancel)
(else
(let ((n (string->number value)))
(if (and n
(integer? n)
(<= 0 n downloads-index))
n
#f))))))))
(cond
((eq? choice 'cancel) #f)
((= choice 0) #f)
((<= choice (length tools))
(set-mergetool! (car (list-ref tools (- choice 1)))))
((= choice custom-index)
(let ((tool
(input-prompt
"Merge tool name: "
#:loop-until
(λ (value)
(cond
((eof-object? value) 'cancel)
((string=? (string-trim value) "") #f)
(else value))))))
(if (eq? tool 'cancel)
#f
(set-mergetool! tool))))
((= choice auto-index)
(set-mergetool-auto!))
((= choice downloads-index)
(display-download-pointers
"Suggested merge tools:"
(mergetool-downloads)))
(else #f)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Process the git-cli-specific `config mergetool` command.
; pre : args contains the arguments following `mergetool`.
; post : The requested merge tool configuration action has been performed.
; result : Merge tool data, the selected tool name, download pointers, or #f.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (git-config-mergetool args)
(cond
((null? args)
(configure-mergetool-interactively))
((and (= (length args) 1)
(git-argument=? (car args) '--list))
(available-mergetools))
((and (= (length args) 1)
(git-argument=? (car args) '--downloads))
(mergetool-downloads))
((and (= (length args) 1)
(git-argument=? (car args) 'auto))
(set-mergetool-auto!))
((= (length args) 1)
(let ((tool (find-mergetool-by-name (car args))))
(if tool
(set-mergetool! (car tool))
(set-mergetool! (format "~a" (car args))))))
(else
(error 'git-config
"Expected config mergetool [--list|--downloads|auto|tool-name]"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (git-config-args args info) (define (git-config-args args info)
@@ -579,10 +774,15 @@
process-git-config-result) process-git-config-result)
(define (cmd-git-config args) (define (cmd-git-config args)
(if (and (pair? args) (cond
(git-argument=? (car args) 'editor)) ((and (pair? args)
(git-config-editor (cdr args)) (git-argument=? (car args) 'editor))
(cmd-git-config-git args))) (git-config-editor (cdr args)))
((and (pair? args)
(git-argument=? (car args) 'mergetool))
(git-config-mergetool (cdr args)))
(else
(cmd-git-config-git args))))
(define (git-config . args) (define (git-config . args)
(cmd-git-config args)) (cmd-git-config args))
@@ -1151,7 +1351,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Increment the package version in info.rkt. ; goal : Increment the package version in info.rkt.
; pre : kind is 'maj, 'major, 'min, 'minor or 'patch. ; pre : kind represents maj, major, min, minor or patch as symbol or text.
; post : The version definition in info.rkt has been updated. ; post : The version definition in info.rkt has been updated.
; result : The new version as a list containing major, minor and patch. ; result : The new version as a list containing major, minor and patch.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1160,7 +1360,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Implement the registered new-version command. ; goal : Implement the registered new-version command.
; pre : args contains a supported version kind. ; pre : args contains a supported version kind as symbol or text.
; post : The version definition in info.rkt has been updated. ; post : The version definition in info.rkt has been updated.
; result : The new version as a list containing major, minor and patch. ; result : The new version as a list containing major, minor and patch.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+32
View File
@@ -6,6 +6,7 @@
(provide find-editor (provide find-editor
find-editors find-editors
editor-downloads
configured-editor configured-editor
set-editor! set-editor!
set-editor-auto!) set-editor-auto!)
@@ -108,6 +109,20 @@
"Visual Studio Code" "Visual Studio Code"
(environment-path "ProgramFiles(x86)" "Microsoft VS Code" "bin" "code.cmd") (environment-path "ProgramFiles(x86)" "Microsoft VS Code" "bin" "code.cmd")
" --wait") " --wait")
(editor-on-path "notepad++"
"Notepad++"
"notepad++.exe"
" -multiInst -nosession")
(editor-at
"notepad++"
"Notepad++"
(environment-path "ProgramFiles" "Notepad++" "notepad++.exe")
" -multiInst -nosession")
(editor-at
"notepad++"
"Notepad++"
(environment-path "ProgramFiles(x86)" "Notepad++" "notepad++.exe")
" -multiInst -nosession")
(editor-on-path "notepad" "Notepad" "notepad.exe" "") (editor-on-path "notepad" "Notepad" "notepad.exe" "")
(editor-at (editor-at
"notepad" "notepad"
@@ -190,6 +205,23 @@
((unix) (find-unix-editors)) ((unix) (find-unix-editors))
(else '()))) (else '())))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return official download pointers for optional GUI editors.
; pre : The current platform is known.
; post : No network request has been made.
; result : A list of (name description url) items.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (editor-downloads)
(case (system-type 'os)
((windows)
'(("vscode" "Visual Studio Code" "https://code.visualstudio.com/download")
("notepad++" "Notepad++" "https://notepad-plus-plus.org/downloads/")))
((macosx)
'(("vscode" "Visual Studio Code" "https://code.visualstudio.com/download")))
((unix)
'(("vscode" "Visual Studio Code" "https://code.visualstudio.com/download")))
(else '())))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Read the editor explicitly configured for git-cli. ; goal : Read the editor explicitly configured for git-cli.
; pre : The git-cli configuration is readable. ; pre : The git-cli configuration is readable.
+114 -9
View File
@@ -4,9 +4,12 @@
"config.rkt") "config.rkt")
(provide find-mergetool (provide find-mergetool
find-mergetools
find-mergetool-path find-mergetool-path
mergetool-downloads
configured-mergetool configured-mergetool
set-mergetool!) set-mergetool!
set-mergetool-auto!)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Supporting functions ;; Supporting functions
@@ -62,6 +65,56 @@
candidate candidate
(find-candidates (cdr candidates))))))) (find-candidates (cdr candidates)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return every usable merge tool candidate without duplicate tool names.
; pre : candidates contains merge tool candidate descriptions.
; post : PATH and the filesystem have only been inspected.
; result : A list of (tool description path) items.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (find-all-candidates candidates)
(let loop ((remaining candidates)
(names '())
(result '()))
(cond
((null? remaining)
(reverse result))
(else
(let ((candidate (find-candidate (car remaining))))
(cond
((not candidate)
(loop (cdr remaining) names result))
((member (car candidate) names)
(loop (cdr remaining) names result))
(else
(loop (cdr remaining)
(cons (car candidate) names)
(cons
(list (car candidate)
(mergetool-description (car candidate))
(cadr candidate))
result)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return well-known graphical merge tool candidates for Windows.
; pre : Windows environment variables may or may not be defined.
; post : The environment has only been inspected.
; result : Merge tool candidate descriptions in preference order.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return a display description for a known Git merge tool.
; pre : tool is a Git merge tool name.
; post : tool has only been inspected.
; result : A human-readable description.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (mergetool-description tool)
(cond
((string=? tool "winmerge") "WinMerge")
((string=? tool "vscode") "Visual Studio Code")
((string=? tool "kdiff3") "KDiff3")
((string=? tool "meld") "Meld")
((string=? tool "tortoisemerge") "TortoiseMerge")
((string=? tool "opendiff") "FileMerge / opendiff")
(else tool)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return well-known graphical merge tool candidates for Windows. ; goal : Return well-known graphical merge tool candidates for Windows.
; pre : Windows environment variables may or may not be defined. ; pre : Windows environment variables may or may not be defined.
@@ -159,9 +212,44 @@
; pre : The git-cli configuration is readable. ; pre : The git-cli configuration is readable.
; post : The configuration has not been changed. ; post : The configuration has not been changed.
; result : The configured Git merge tool name, or #f. ; result : The configured Git merge tool name, or #f.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return all well-known merge tools found on the current platform.
; pre : The platform and filesystem are available.
; post : No merge tool has been started.
; result : A list of (name description path) items.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (find-mergetools)
(find-all-candidates (mergetool-candidates)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return official download pointers for optional merge tools.
; pre : The current platform is known.
; post : No network request has been made.
; result : A list of (name description url) items.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (mergetool-downloads)
(case (system-type 'os)
((windows)
'(("winmerge" "WinMerge" "https://winmerge.org/downloads/")
("kdiff3" "KDiff3" "https://apps.kde.org/kdiff3/")
("meld" "Meld" "https://meldmerge.org/")
("vscode" "Visual Studio Code" "https://code.visualstudio.com/download")))
((macosx)
'(("kdiff3" "KDiff3" "https://apps.kde.org/kdiff3/")
("vscode" "Visual Studio Code" "https://code.visualstudio.com/download")))
((unix)
'(("meld" "Meld" "https://meldmerge.org/")
("kdiff3" "KDiff3" "https://apps.kde.org/kdiff3/")
("vscode" "Visual Studio Code" "https://code.visualstudio.com/download")))
(else '())))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (configured-mergetool) (define (configured-mergetool)
(cfg-get 'git 'mergetool #f)) (let ((tool (cfg-get 'git 'mergetool #f)))
(if (and (string? tool)
(not (string=? tool "")))
tool
#f)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Store the Git merge tool name used by git-cli. ; goal : Store the Git merge tool name used by git-cli.
@@ -172,6 +260,20 @@
(define (set-mergetool! tool) (define (set-mergetool! tool)
(cfg-set! 'git 'mergetool tool)) (cfg-set! 'git 'mergetool tool))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Return git-cli to automatic merge tool detection.
; pre : A well-known merge tool can be found on the current platform.
; post : The explicit merge tool setting is cleared.
; result : The automatically detected Git merge tool name.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (set-mergetool-auto!)
(let ((detected (detected-mergetool)))
(if detected
(begin
(cfg-set! 'git 'mergetool "")
(car detected))
(error 'git-config "No well-known merge tool found"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Find the graphical merge tool that git-cli should prefer. ; goal : Find the graphical merge tool that git-cli should prefer.
; pre : The platform and git-cli configuration are available. ; pre : The platform and git-cli configuration are available.
@@ -192,10 +294,13 @@
; result : The executable path, or #f. ; result : The executable path, or #f.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (find-mergetool-path) (define (find-mergetool-path)
(let ((configured (configured-mergetool)) (let ((tool (find-mergetool)))
(detected (detected-mergetool))) (if tool
(if configured (let loop ((tools (find-mergetools)))
#f (cond
(if detected ((null? tools) #f)
(cadr detected) ((string=? tool (car (car tools)))
#f)))) (caddr (car tools)))
(else
(loop (cdr tools)))))
#f)))
+8 -7
View File
@@ -70,23 +70,24 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; goal : Increment a package version. ; goal : Increment a package version.
; pre : kind is maj, major, min, minor or patch. ; pre : kind represents maj, major, min, minor or patch as symbol or text.
; post : The version definition in info.rkt has been updated. ; post : The version definition in info.rkt has been updated.
; result : #t after writing the new version. ; result : #t after writing the new version.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (info-next-version kind . dir*) (define (info-next-version kind . dir*)
(let ((dir (if (null? dir*) (let ((dir (if (null? dir*)
"." "."
(car dir*)))) (car dir*)))
(if (memq kind '(maj major min minor patch)) (kind* (string->symbol (format "~a" kind))))
(if (memq kind* '(maj major min minor patch))
(let ((v (info-version dir))) (let ((v (info-version dir)))
(cond (cond
((or (eq? kind 'maj) ((or (eq? kind* 'maj)
(eq? kind 'major)) (eq? kind* 'major))
(apply set-info-version! (cons dir (apply set-info-version! (cons dir
(list (+ (car v) 1) 0 0)))) (list (+ (car v) 1) 0 0))))
((or (eq? kind 'min) ((or (eq? kind* 'min)
(eq? kind 'minor)) (eq? kind* 'minor))
(apply set-info-version! (cons dir (apply set-info-version! (cons dir
(list (car v) (+ (cadr v) 1) 0)))) (list (car v) (+ (cadr v) 1) 0))))
(else (else
+117 -11
View File
@@ -16,19 +16,39 @@ read credentials or other answers from the terminal.
@section{Command interface} @section{Command interface}
@defform[(git command argument ...)]{ @defform[(git command argument ...)]{
Runs a registered Git @racket[command]. The arguments are passed to the command. Runs a Git @racket[command]. When the command has a registered git-cli wrapper,
Registered command symbols are @racket['init], @racket['status], @racket['add], that wrapper is used. Registered wrappers can provide structured Racket results,
@racket['commit], @racket['push], @racket['pull], @racket['fetch], argument handling, or other command-specific behavior.
@racket['config], @racket['branch], @racket['remote], @racket['stash], @racket['restore], @racket['reset], @racket['revert], @racket['rebase], @racket['merge], @racket['cherry-pick], @racket['mergetool], @racket['switch], @racket['clone],
@racket['tag], Registered command symbols include @racket['init], @racket['status],
@racket['log], @racket['rev-list], @racket['diff], @racket['add], @racket['commit], @racket['push], @racket['pull],
@racket['show], @racket['grep], @racket['help], @racket['version], and @racket['fetch], @racket['config], @racket['branch], @racket['remote],
@racket['stash], @racket['restore], @racket['reset], @racket['revert],
@racket['rebase], @racket['merge], @racket['cherry-pick],
@racket['mergetool], @racket['switch], @racket['clone], @racket['tag],
@racket['log], @racket['rev-list], @racket['diff], @racket['show],
@racket['grep], @racket['help], @racket['version], and
@racket['new-version]. @racket['new-version].
Most registered commands invoke the Git command with the same name. Some When no wrapper is registered, the command and arguments are passed directly to
commands process the result into a Racket value, such as @racket['status], the installed Git executable through @racket[run-git]. The result is handled by
@racket['grep], @racket['log] with @tt{--list}, @racket['version], and the same standard result processing used by ordinary pass-through wrappers:
@racket['new-version]. normal Git output is displayed and a successful command returns @racket[#t];
a non-zero exit status raises a git-cli error.
This makes dedicated wrappers optional for Git commands where git-cli does not
add useful behavior.
@racketblock[
(git 'blame "main.rkt")
(git* clean -n)
(git* worktree list)
(git* archive --format=zip HEAD)
]
Some registered commands process the result into a Racket value, such as
@racket['status], @racket['grep], @racket['log] with @tt{--list},
@racket['version], and @racket['new-version].
} }
@@ -49,9 +69,40 @@ converted from its literal syntax.
(git* switch (eval branch)) (git* switch (eval branch))
] ]
@bold{Important:} bare arguments to @racket[git*] are command-line text, not
Racket values. An identifier is quoted syntactically and converted to a string,
even when that identifier is also bound to a Racket variable or procedure.
@racketblock[
(define branch "develop")
(git* switch branch)
; passes "branch"
(git* switch (eval branch))
; passes "develop"
]
This distinction matters most for git-cli commands whose arguments are not
ordinary Git command-line strings. Such wrappers should accept the textual
arguments produced by @racket[git*]. For example, @racket[git-new-version] now
accepts both symbols and text:
@racketblock[
(git 'new-version 'min)
(git* new-version min)
]
Because @racket[git] falls back to direct Git execution for commands without a
registered wrapper, @racket[git*] can also be used with those commands.
@racket[gt] is retained as a compatibility alias for @racket[git*]. @racket[gt] is retained as a compatibility alias for @racket[git*].
} }
@section{Rash integration}
Rash integration is provided by the separate @tt{rash-git-cli} package. The @racketmodname[git-cli] package itself does not depend on Rash or Linea.
@section{Provided commands} @section{Provided commands}
@defproc[(git-init [argument any/c] ...) boolean?]{ @defproc[(git-init [argument any/c] ...) boolean?]{
@@ -183,6 +234,7 @@ The available editors can also be returned without prompting.
@racketblock[ @racketblock[
(git* config editor --list) (git* config editor --list)
(git* config editor --downloads)
] ]
Each item contains the short editor name, description, command and a boolean Each item contains the short editor name, description, command and a boolean
@@ -193,6 +245,7 @@ be restored.
@racketblock[ @racketblock[
(git* config editor vscode) (git* config editor vscode)
(git* config editor notepad++)
(git* config editor auto) (git* config editor auto)
] ]
@@ -216,6 +269,49 @@ detected editor and returns its command. An exception is raised when no
well-known GUI editor can be found. well-known GUI editor can be found.
} }
@defproc[(editor-downloads) list?]{
Returns official download pointers for optional GUI editors as
@racket[(name description url)] items. No network request is performed.
}
On Windows, Notepad++ is detected both on @tt{PATH} and in the normal Program
Files locations. git-cli invokes it with @tt{-multiInst -nosession}.
@subsection{git-cli merge tool configuration}
The @racket[git-config] procedure also recognizes the git-cli-specific
@tt{mergetool} operation.
@racketblock[
(git* config mergetool)
(git* config mergetool --list)
(git* config mergetool --downloads)
(git* config mergetool winmerge)
(git* config mergetool auto)
]
With no additional argument an interactive selection is displayed.
@tt{--list} returns @racket[(name description path current?)] items and
@tt{--downloads} returns official download pointers. A detected merge tool can
be selected by its short Git tool name. @tt{auto} clears the explicit git-cli
selection and restores automatic detection.
@defproc[(find-mergetools) list?]{
Returns all well-known graphical merge tools found on the current platform as
@racket[(name description path)] items.
}
@defproc[(mergetool-downloads) list?]{
Returns official download pointers for optional merge tools as
@racket[(name description url)] items. No network request is performed.
}
@defproc[(set-mergetool-auto!) string?]{
Clears the explicit git-cli merge tool selection and returns the first
automatically detected Git merge tool name. An exception is raised when no
well-known merge tool can be found.
}
@defproc[(git-branch [argument any/c] ...) (or/c boolean? list?)]{ @defproc[(git-branch [argument any/c] ...) (or/c boolean? list?)]{
Runs @tt{git branch} with the supplied arguments. This can be used to list, Runs @tt{git branch} with the supplied arguments. This can be used to list,
create, rename, or delete branches according to the options supported by the create, rename, or delete branches according to the options supported by the
@@ -571,6 +667,16 @@ Updates the version in @filepath{info.rkt}. The kind is @racket['major],
abbreviations. The result is the new version as a list of three integers. abbreviations. The result is the new version as a list of three integers.
} }
@racketblock[
(git-new-version 'min)
(git 'new-version 'min)
(git* new-version min)
]
The version kind may be supplied as a symbol or string. This makes the command
compatible with @racket[git*], whose bare arguments are converted to text.
@section{Low-level Git execution} @section{Low-level Git execution}