new version with git help

This commit is contained in:
2026-08-11 22:35:01 +02:00
parent fea7fb7a59
commit 7c6a338326
7 changed files with 175 additions and 19 deletions
+4 -1
View File
@@ -1,5 +1,8 @@
/compiled
/doc
/*.bak
/scribblings/compiled
/tests/compiled
/docs
*~
*.bak
+30 -3
View File
@@ -2,6 +2,7 @@
(require racket-makefile
package-zipper
net/sendurl
)
@@ -11,11 +12,37 @@
(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)$" #:recursive #t))
(for-each (λ (f) (displayln f) (rm-f f)) (list-files "scribblings" #px"[.](css|js|html)$"))
(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))
(define doc-target "docs/git.html")
(define doc-src "scribblings/git.scrbl")
(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)))
+23 -1
View File
@@ -5,6 +5,8 @@ A small command-line-like Git module for Racket, implemented directly on top of
```racket
(require git)
(git 'help)
(git 'help 'grep)
(git 'status)
(git 'diff)
(git 'diff '--cached)
@@ -24,12 +26,32 @@ A small command-line-like Git module for Racket, implemented directly on top of
`git` is an ordinary procedure; command names are symbols. `dgit` performs the
same operation, displays a compact human-readable result, and returns that result.
`(git 'help)` opens the locally installed Scribble documentation. A command can
be supplied to jump directly to its section, for example `(git 'help 'restore)`
or `(git 'help 'grep)`.
For `grep`, the natural `(git 'grep '-i "pattern")` spelling is supported even
though Racket's reader represents `-i` as the complex number `0-1i`; in grep
option position that value is interpreted as Git's `-i` flag.
The same operations are available as normal procedures such as `git-status`, `git-add`, `git-commit`, `git-tag`, `git-current-branch`, `git-switch`, and `git-checkout`.
## Help
Open the locally installed Scribble documentation through Racket's
documentation cross-reference index:
```racket
(git 'help)
(git 'help 'grep)
(git 'help 'restore)
```
`raco setup git` builds and indexes the package documentation. The help command
uses the indexed path and anchor instead of assuming a particular documentation
directory.
## HTTPS credentials
Version 0.2 adds persistent HTTPS credentials. They are stored in the
@@ -97,7 +119,7 @@ operations use them automatically:
## Supported Git operations
Version 0.2 supports repository discovery, init, clone, status, diff, add, restore, reset, grep, config,
Version 0.2 supports help, repository discovery, init, clone, status, diff, add, restore, reset, grep, config,
commit, branch, branch-current, switch, checkout, merge, lightweight tags, log, remotes, fetch,
fast-forward-only pull, push, tag push, network transfer progress, and HTTPS username/token credentials.
+3 -2
View File
@@ -2,7 +2,7 @@
(define collection "git")
(define pkg-desc "Command-line-like Git operations for Racket, implemented with libgit2")
(define version "0.2.13")
(define version "0.2.15")
(define pkg-authors '("Hans Dijkema"))
(define license 'MIT)
@@ -12,12 +12,13 @@
("simple-ini" #:version "0.3.3")
"crypto-lib"
"net-lib"
"racket-index"
"scribble-lib"
"racket-makefile"
"package-zipper"))
(define build-deps
'("rackunit-lib"
"scribble-lib"
"racket-doc"))
(define scribblings
+66
View File
@@ -9,12 +9,16 @@
racket/match
racket/path
racket/string
net/sendurl
setup/xref
scribble/xref
"credentials.rkt"
libgit2)
(provide git
dgit
git-version
git-help
git-repository?
git-root
git-init
@@ -80,6 +84,63 @@
(error 'git-version "cannot read info.rkt"))
(info 'version (lambda () (error 'git-version "info.rkt has no version"))))
(define git-help-bindings
(hash 'version 'git-version
'init 'git-init
'clone 'git-clone
'status 'git-status
'diff 'git-diff
'add 'git-add
'restore 'git-restore
'reset 'git-reset
'grep 'git-grep
'config 'git-config
'commit 'git-commit
'branch-current 'git-current-branch
'branch 'git-branch
'branch-create 'git-branch-create
'switch 'git-switch
'checkout 'git-checkout
'merge 'git-merge
'tag 'git-tag
'log 'git-log
'remote 'git-remotes
'fetch 'git-fetch
'pull 'git-pull
'push 'git-push
'push-tag 'git-push-tag
'credentials 'git-credentials-init!
'prompt 'git-prompt
'git-prompt 'git-prompt
'help 'git-help))
(define (git-help [topic #f])
(unless (or (not topic) (symbol? topic))
(raise-argument-error 'git-help "(or/c #f symbol?)" topic))
(define binding
(if topic
(hash-ref git-help-bindings topic
(lambda ()
(error 'git-help "unknown help topic: ~a" topic)))
'git))
;; Use Racket's installed-documentation cross-reference database instead of
;; guessing where raco setup placed this package's generated HTML.
(define xref (load-collections-xref))
(define tag
(xref-binding->definition-tag xref (list 'git binding) #f))
(unless tag
(error 'git-help
"documentation for ~a is not indexed; run raco setup git"
binding))
(define-values (path anchor)
(xref-tag->path+anchor xref tag))
(unless path
(error 'git-help
"documentation for ~a is indexed but has no local path"
binding))
(send-url/file path #:fragment anchor)
(void))
(define zero-oid-string (make-string GIT_OID_HEXSZ #\0))
(define branch-prefix "refs/heads/")
(define tag-prefix "refs/tags/")
@@ -1170,6 +1231,11 @@
(unless (null? args)
(error 'git "version takes no arguments"))
(git-version)]
[(help)
(match args
['() (git-help)]
[(list topic) (git-help topic)]
[_ (error 'git "help takes zero or one topic")])]
[(init) (apply git-init args)]
[(clone) (keyword-apply git-clone '(#:quiet) (list quiet) args)]
[(status) (apply git-status args)]
+44 -12
View File
@@ -2,7 +2,7 @@
@(require (for-label racket/base git))
@title{git}
@title[#:tag "top"]{git}
@author{Hans Dijkema}
@defmodule[git]
@@ -38,15 +38,27 @@ Calls @racket[git], displays its result in a compact human-readable form, and re
(dgit 'status)
]
@section{Version}
@section[#:tag "help"]{Help}
@defproc[(git-help [topic (or/c symbol? #f) #f]) void?]{Opens the installed Scribble documentation for this package in the default web browser. With a command topic, opens the section for that command. The command forms are @racket[(git 'help)] and @racket[(git 'help 'grep)].}
@racketblock[
(git 'help)
(git 'help 'grep)
(git 'help 'restore)
]
Help uses Racket's installed-documentation cross-reference database. It resolves the documented binding for the requested command through @racketmodname[setup/xref] and @racketmodname[scribble/xref], then opens the path and anchor recorded by @exec{raco setup}. No documentation directory is guessed or constructed by @racketmodname[git]. If the binding is not indexed, @racket[git-help] reports that @exec{raco setup git} should be run.
@section[#:tag "version"]{Version}
@defproc[(git-version) string?]{Returns the package version from @tt{info.rkt}. The command form is @racket[(git 'version)]. The version is not duplicated in @tt{main.rkt}; @tt{info.rkt} is the single source of truth.}
@racketblock[
(git 'version) ; => "0.2.13"
(git 'version) ; => "0.2.15"
]
@section{Repository}
@section[#:tag "repository"]{Repository}
@defproc[(git-repository? [path path-string? (current-directory)]) boolean?]{Returns whether @racket[path] is inside a Git repository.}
@@ -57,7 +69,7 @@ Calls @racket[git], displays its result in a compact human-readable form, and re
@defproc*[([(git-clone [url string?] [#:quiet quiet any/c #f]) path?]
[(git-clone [url string?] [path path-string?] [#:quiet quiet any/c #f]) path?])]{Clones @racket[url]. If @racket[path] is omitted, a directory name is derived from the URL. Progress is written to the current output port unless @racket[quiet] is true.}
@section{Status and index}
@section[#:tag "status"]{Status}
@defstruct*[git-status-entry ([path string?] [code string?] [flags list?])]{Describes one status entry. The @racket[code] field uses the familiar two-character Git status notation.}
@@ -67,10 +79,14 @@ Calls @racket[git], displays its result in a compact human-readable form, and re
@defproc[(git-clean?) boolean?]{Returns @racket[#t] when @racket[git-status] is empty.}
@section[#:tag "diff"]{Diff}
@defproc*[([(git-diff) string?]
[(git-diff [option (or/c '--cached)]) string?])]{Returns a unified patch as a string. With no arguments it compares the index with the worktree, like @tt{git diff}. With @racket['--cached] it compares HEAD with the index, like @tt{git diff --cached}.}
@section[#:tag "add"]{Add}
@defproc[(git-add [path path-string?] ...) void?]{Stages the given paths. With no paths, stages the whole repository, including tracked removals. The command form @racket[(git 'add '-A)] stages all current status entries, including new, modified, and removed paths.}
@racketblock[
@@ -78,6 +94,8 @@ Calls @racket[git], displays its result in a compact human-readable form, and re
(git 'commit "Update all changed files")
]
@section[#:tag "restore"]{Restore}
@defproc[(git-restore [argument any/c] ...) void?]{Restores paths using Git-like command arguments. With only paths, the worktree is restored from the index, as in @tt{git restore path}. With @racket['--staged], matching index entries are restored from HEAD while the worktree is left untouched. @racket['--worktree] can be combined with @racket['--staged], and @racket['--source] selects another revision.}
@racketblock[
@@ -86,6 +104,8 @@ Calls @racket[git], displays its result in a compact human-readable form, and re
(git 'restore '--source "HEAD~1" "main.rkt")
]
@section[#:tag "reset"]{Reset}
@defproc[(git-reset [argument any/c] ...) void?]{Resets HEAD, the index, or selected paths using Git-like command arguments. With @racket['--soft], @racket['--mixed], or @racket['--hard], the corresponding whole-repository reset is performed. Path resets use the familiar @tt{--} separator.}
@racketblock[
@@ -94,7 +114,7 @@ Calls @racket[git], displays its result in a compact human-readable form, and re
(git 'reset '--hard 'HEAD)
]
@section{Grep}
@section[#:tag "grep"]{Grep}
@defstruct*[git-grep-entry ([path string?] [line-number exact-positive-integer?] [line string?])]{Describes one line selected by @racket[git-grep]. Results are always structured this way, regardless of display-oriented flags such as @racket['-n], @racket['-l], or @racket['-c].}
@@ -111,19 +131,23 @@ Calls @racket[git], displays its result in a compact human-readable form, and re
(dgit 'grep '-c "TODO") ; number of matching lines per file
]
@section{Configuration and commits}
@section[#:tag "config"]{Configuration}
@defproc*[([(git-config [key string?]) string?]
[(git-config [key string?] [value string?]) string?])]{Reads or writes a repository configuration value. The two-argument form returns @racket[value].}
@defproc[(git-head) (or/c string? #f)]{Returns the full OID of HEAD, or @racket[#f] for a repository without commits.}
@section[#:tag "commit"]{Commit}
@defproc[(git-commit [message string?]) string?]{Creates a commit from the index and returns its full OID. The author and committer are read from the repository configuration.}
@subsection[#:tag "git-prompt"]{Commit prompt}
@defproc[(git-prompt [message string? #f]) string?]{Returns @racket[message] when supplied. Without an argument, displays @tt{Give (commit) message: }, reads one line from the current input port, and returns it. This is convenient in interactive make targets before staging and committing changes.}
@section{Branches, checkout, and tags}
@section[#:tag "branches"]{Branches}
@defproc[(git-current-branch) (or/c string? #f)]{Returns the current local branch name, or @racket[#f] for detached HEAD. The command form is @racket[(git 'branch-current)].}
@@ -134,14 +158,20 @@ Calls @racket[git], displays its result in a compact human-readable form, and re
@defproc[(git-branch-delete [name string?]) void?]{Deletes a local branch. The command form is @racket[(git 'branch '-d name)].}
@section[#:tag "switch-checkout"]{Switch and checkout}
@defproc[(git-switch [name string?]) (or/c string? #f)]{Switches to an existing local branch and attaches HEAD to that branch. The command form is @racket[(git 'switch name)]. An unknown local branch raises an exception.}
@defproc[(git-checkout [name string?]) (or/c string? #f)]{Checks out a local branch, tag, or commit. A tag or commit produces detached HEAD.}
@defproc[(git-checkout-new [name string?]) string?]{Creates and checks out a new branch.}
@section[#:tag "merge"]{Merge}
@defproc[(git-merge [name string?] [message (or/c string? #f) #f]) (or/c string? #f)]{Merges @racket[name] into the currently attached local branch. An up-to-date merge returns @racket[#f]. A fast-forward returns the new HEAD OID. A clean non-fast-forward merge creates a two-parent merge commit and returns its OID. If @racket[message] is @racket[#f], the merge commit message is @tt{Merge branch 'name'}. Conflicting merges raise an exception before changing HEAD or the worktree. The command forms are @racket[(git 'merge name)] and @racket[(git 'merge name message)].}
@section[#:tag "tags"]{Tags}
@defproc*[([(git-tag) (listof string?)]
[(git-tag [name string?]) string?])]{Lists tags, or creates a lightweight tag at HEAD and returns its OID.}
@@ -164,7 +194,7 @@ A commit made while HEAD is detached is not attached to a local branch. The foll
The @racket[(dgit 'log 5)] form is the compact Racket equivalent of using a short command-line log for verification: it displays the abbreviated commit OID and summary for the five newest commits.
@section{Log}
@section[#:tag "log"]{Log}
@defstruct*[git-log-entry ([id string?] [summary string?] [time integer?])]{Describes one commit returned by @racket[git-log].}
@@ -172,7 +202,7 @@ The @racket[(dgit 'log 5)] form is the compact Racket equivalent of using a shor
@defproc[(git-log-lines [entries (listof git-log-entry?) (git-log)]) (listof string?)]{Formats log entries as short OID plus summary.}
@section{Remotes}
@section[#:tag "remotes"]{Remotes}
@defproc[(git-remotes) (listof string?)]{Lists remotes.}
@@ -190,11 +220,13 @@ The @racket[(dgit 'log 5)] form is the compact Racket equivalent of using a shor
Remote HTTPS operations automatically use credentials from the @tt{racket-git} credential store when an entry exists for the remote host.
@section{Command form}
@section[#:tag "command-form"]{Command form}
The following command-like forms are supported directly:
@racketblock[
(git 'help)
(git 'help 'grep)
(git 'version)
(git 'init)
(git 'clone "https://example/repo.git")
@@ -228,7 +260,7 @@ The following command-like forms are supported directly:
(git 'push-tag "v0.1")
]
@section{HTTPS credentials}
@section[#:tag "credentials"]{HTTPS credentials}
Git credentials are stored in @tt{racket-git.ini} in the normal Racket
preferences directory. Tokens are encrypted with AES-GCM. The encryption key is
+5
View File
@@ -182,3 +182,8 @@
(check-equal? (length (git 'log 10)) 2)))
(lambda ()
(delete-directory/files tmp)))
;; Help validates its topic before trying to open the installed documentation.
(check-exn exn:fail? (lambda () (git 'help 'not-a-git-command)))
(check-exn exn:fail:contract? (lambda () (git-help "grep")))