#lang scribble/manual @(require (for-label racket/base racket/contract "../main.rkt")) @title[#:tag "top"]{git-cli} @author{Hans Dijkema} @defmodule[git-cli] The @racketmodname[git-cli] module provides a command-line-like Git interface implemented by invoking the @tt{git} executable. Commands do not allow Git to read credentials or other answers from the terminal. @section{Command interface} @defform[(git command argument ...)]{ 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['fetch], @racket['config], @racket['branch], @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]. Most registered commands invoke the Git command with the same name. Some 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]. } @section{Provided commands} @defproc[(git-status [argument any/c] ...) list?]{ Runs @tt{git status --porcelain} with the supplied arguments. Each result item has the form @racket[(index-status worktree-status file)]. The index status describes the change staged for the next commit. The worktree status describes the change in the working tree relative to the index. Both statuses are one of @racket['unchanged], @racket['modified], @racket['type-changed], @racket['added], @racket['deleted], @racket['renamed], @racket['copied], @racket['unmerged], @racket['untracked], or @racket['ignored]. For an untracked file, Git reports @tt{??}, so both statuses are @racket['untracked]. @racketblock[ '((modified unchanged "staged.rkt") (unchanged modified "working-tree.rkt") (modified modified "both.rkt") (renamed unchanged "old.rkt -> new.rkt") (untracked untracked "new.rkt")) ]} @defproc[(git-add [argument any/c] ...) boolean?]{ Adds file contents to the index. Returns @racket[#t] when Git exits with status zero; otherwise an exception is raised. } @defproc[(git-commit [argument any/c] ...) boolean?]{ Creates a commit. When @tt{-m} is omitted, a commit message is requested before Git is started. A repository with nothing to commit returns @racket[#t]. Other non-zero exit statuses, including a rejected commit hook, raise an exception. } @defproc[(git-push [argument any/c] ...) boolean?]{ Pushes changes using @tt{--porcelain}. Returns @racket[#t] when Git exits with status zero; otherwise an exception is raised. } @defproc[(git-pull [argument any/c] ...) boolean?]{ Fetches and integrates changes. Normal progress written by Git to standard error is accepted when Git exits successfully. } @defproc[(git-fetch [argument any/c] ...) boolean?]{ Downloads refs and objects from a remote repository without integrating them into the current branch. Arguments are passed directly to @tt{git fetch}. For example: @racketblock[ (git-fetch) (git-fetch '--prune) (git 'fetch '--prune) ] } @defproc[(git-config [argument any/c] ...) any/c]{ Provides a Racket-oriented interface to @tt{git config}. The same interface is available through @racket[git] with command @racket['config]. @racketblock[ (git 'config '--all) (git 'config 'get '--all) (git 'config '--global 'get '--all) (git 'config 'get '--global '--all) ] returns all visible configuration entries as key/value items: @racketblock[ '(("user.name" "Hans Dijkema") ("user.email" "hans@example.invalid") ("credential.helper" "manager")) ] @racketblock[ (git 'config 'get "credential.helper") ] returns one value as a string, or @racket[#f] when the key is absent. @racketblock[ (git 'config 'get '--all "credential.helper") ] returns all values for one key as a list. An absent key produces the empty list. Configuration values can be written with @racket['set!]: @racketblock[ (git 'config 'set! "user.email" "hans@example.invalid") (git 'config '--global 'set! "user.email" "hans@example.invalid") (git 'config 'set! '--global "user.email" "hans@example.invalid") ] The optional scope can be @tt{--global}, @tt{--local}, or @tt{--system}. It may appear directly after @racket['config] or directly after @racket['get] / @racket['set!]. A successful write returns @racket[#t]. } @defproc[(git-branch [argument any/c] ...) boolean?]{ 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 installed Git executable. } @defproc[(git-switch [argument any/c] ...) boolean?]{ Runs @tt{git switch} with the supplied arguments. @racketblock[ (git-switch "main") (git-switch '-c "feature") (git 'switch "main") ] } @defproc[(git-clone [argument any/c] ...) boolean?]{ Runs @tt{git clone} with the supplied arguments. } @defproc[(git-tag [argument any/c] ...) (or/c boolean? list?)]{ Runs @tt{git tag} with the supplied arguments. It can list, create, delete, or verify tags according to the options supported by Git. When @tt{-l} or @tt{--list} is supplied, the matching tag names are returned as a Racket list. Git's sorting options are passed through unchanged, so the returned list keeps Git's order. @racketblock[ (git-tag '-l) (git-tag '--list "--sort=version:refname") (git-tag '--list "--sort=-creatordate") ] When @tt{-n} or @tt{-n1} is combined with @tt{-l} or @tt{--list}, each result item contains the tag name and the subject reported by Git. @racketblock[ (git-tag '-l '-n) '(("v0.3.16" "Release 0.3.16") ("v0.3.17" "Release 0.3.17")) ] With @tt{-n} and a number greater than one, git-cli asks Git for that many content lines using @tt{%(contents:lines=)}. The returned message is kept as one string, including embedded newlines. For structured tag output git-cli asks Git for an explicit format using @tt{%(refname:strip=2)} and either @tt{%(contents:subject)} or @tt{%(contents:lines=)}. Generated field and record delimiters are used to split the result safely. Other forms keep the normal command behavior and return @racket[#t] when Git exits successfully. Git errors are handled by the standard git-cli result processor. } @defproc[(git-rev-list [argument any/c] ...) boolean?]{ Runs @tt{git rev-list} with the supplied arguments and displays Git's normal output. It returns @racket[#t] when Git exits successfully. } @defproc[(git-diff [argument any/c] ...) (or/c boolean? string?)]{ Shows differences between Git objects or the working tree and index. By default a successful diff is rendered as HTML in the default browser. The git-cli-specific option @tt{--output=-} keeps Git's textual output on standard output. @tt{--output=string} returns the textual diff as a string. @racketblock[ (git-diff) (git-diff '--cached) (git-diff '--output=-) (git-diff '--output=string) ] } @defproc[(git-log [argument any/c] ...) (or/c boolean? list?)]{ Displays Git log output and returns @racket[#t] when Git exits successfully. The git-cli-specific option @tt{--list}, or its short form @tt{-l}, changes the result to a Racket list. Internally this option is replaced by Git's @tt{--oneline} option. Each returned item contains the abbreviated commit id and the commit subject. @racketblock[ (git-log '--list '-5) '(("003f371" "Diverse commando's toegevoegd. Ik weet nog niet of ik ze allemaal ga houden") ("2cb7e93" "Small changes. git main function is now a real function, not syntax")) ] Other Git log options are still passed to Git. Consequently, options that add 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 were found and returns an empty list. } @defproc[(git-help [argument any/c] ...) boolean?]{ Runs @tt{git help} with the supplied arguments and returns @racket[#t] when Git exits successfully. } @section{Package version} @defproc[(git-version) list?]{ Reads the package version from @filepath{info.rkt} and returns it as a list containing major, minor, and patch. } @defproc[(git-new-version [kind symbol?]) list?]{ Updates the version in @filepath{info.rkt}. The kind is @racket['major], @racket['minor], or @racket['patch], with @racket['maj] and @racket['min] as abbreviations. The result is the new version as a list of three integers. } @section{Low-level Git execution} @defproc[(run-git [args list?] [#:input input (or/c #f string?) #f]) (values exact-integer? list?)]{ Runs Git without interactive terminal prompts. When @racket[input] is a string, it is written to Git's standard input before that input port is closed. The procedure returns two values: Git's exit code and the ordered output items, where each item identifies either @racket['stdout] or @racket['stderr]. @racketblock[ (run-git '(credential fill) #:input "protocol=https\nhost=git.dijkewijk.nl\n\n") ] } @section{Authentication retry} Git commands recognize common authentication failures immediately after the Git process finishes and before command-specific result processing takes place. Such a failure is represented by @racket[exn:fail:git-auth?]. @defparam[current-git-authentication-handler handler procedure?]{ Controls the callback used when an authentication failure is detected. The callback receives the Git command symbol, the processed Git argument list and the @racket[exn:fail:git-auth] exception. The callback returns a true value when it has handled authentication and the original Git command should be tried again. A command is retried at most once. The default callback returns @racket[#f], preserving the normal Git error behavior. @racketblock[ (current-git-authentication-handler (lambda (cmd args e) ;; Perform credential handling here. #t)) ] } @defproc[(exn:fail:git-auth? [v any/c]) boolean?]{ Recognizes the authentication exception used internally by git-cli. } @defproc[(exn:fail:git-auth-command [e exn:fail:git-auth?]) symbol?]{ Returns the Git command of the failed invocation. } @defproc[(exn:fail:git-auth-args [e exn:fail:git-auth?]) list?]{ Returns the processed Git arguments of the failed invocation. } @defproc[(exn:fail:git-auth-exit-code [e exn:fail:git-auth?]) exact-integer?]{ Returns Git's exit code. } @defproc[(exn:fail:git-auth-output [e exn:fail:git-auth?]) list?]{ Returns the ordered @racket['stdout]/@racket['stderr] output items from the failed Git process. }