450 lines
15 KiB
Racket
450 lines
15 KiB
Racket
#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['remote], @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] ...) (or/c boolean? 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
|
|
installed Git executable.
|
|
|
|
With Git's @tt{-l} or @tt{--list} option, git-cli returns structured branch
|
|
information. Each item starts with one of @racket['current], @racket['local],
|
|
or @racket['remote], followed by the branch name.
|
|
|
|
@racketblock[
|
|
(git-branch '-l)
|
|
|
|
'((current "main")
|
|
(local "develop"))
|
|
]
|
|
|
|
Git's normal branch selection and sorting options are passed through. For
|
|
example, remote branches can be requested with @tt{-r}, all branches with
|
|
@tt{-a}, and Git's @tt{--sort=<key>} option controls the returned order.
|
|
|
|
@racketblock[
|
|
(git 'branch '-l '-a "--sort=refname")
|
|
|
|
'((current "main")
|
|
(local "develop")
|
|
(remote "origin/main"))
|
|
]
|
|
|
|
Without @tt{-l} or @tt{--list}, normal Git output is displayed and the
|
|
procedure returns @racket[#t] when Git exits successfully.
|
|
}
|
|
@defproc[(git-remote [argument any/c] ...) any/c]{
|
|
Runs @tt{git remote} with the supplied arguments and keeps the command's own
|
|
subcommand structure.
|
|
|
|
With no arguments, the remote names are returned as a Racket list.
|
|
|
|
@racketblock[
|
|
(git-remote)
|
|
|
|
'("origin" "upstream")
|
|
]
|
|
|
|
With top-level @tt{-v} or @tt{--verbose}, each line reported by Git is returned
|
|
as a separate structured item containing the remote name, URL, and the
|
|
@racket['fetch] or @racket['push] role.
|
|
|
|
@racketblock[
|
|
(git-remote '-v)
|
|
|
|
'(("origin" "https://example.invalid/project.git" fetch)
|
|
("origin" "https://example.invalid/project.git" push))
|
|
]
|
|
|
|
The two Git lines are deliberately not merged. This keeps the result close to
|
|
the output and semantics of @tt{git remote -v}.
|
|
|
|
For @tt{get-url}, one URL is returned as a string. With @tt{--all}, a list of
|
|
URLs is returned.
|
|
|
|
@racketblock[
|
|
(git 'remote 'get-url "origin")
|
|
(git 'remote 'get-url '--all "origin")
|
|
(git 'remote 'get-url '--push '--all "origin")
|
|
]
|
|
|
|
Other forms, including @tt{add}, @tt{rename}, @tt{remove}, @tt{set-head},
|
|
@tt{show}, @tt{prune}, @tt{update}, @tt{set-branches}, and @tt{set-url}, are
|
|
passed to Git unchanged and use the normal git-cli command result processing.
|
|
}
|
|
@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<number>} and a number greater than one, git-cli asks Git for that
|
|
many content lines using @tt{%(contents:lines=<number>)}. 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=<number>)}. 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 is @racket[default-git-authentication-handler].
|
|
|
|
@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.
|
|
}
|
|
|
|
|
|
|
|
@section{Authentication}
|
|
|
|
@defproc[(default-git-authentication-handler
|
|
[cmd symbol?]
|
|
[args list?]
|
|
[e exn:fail:git-auth?])
|
|
boolean?]{
|
|
Handles one authentication failure. A credential that already failed is first
|
|
rejected. An existing Git credential helper is then asked for a replacement
|
|
credential. If that does not succeed, git-cli requests a username and
|
|
password/token using @racket[input-prompt]. Its @racket[#:loop-until] callbacks
|
|
both validate the input and return the value that is used. If no helper is
|
|
configured, Git's non-persistent @tt{cache} helper is configured locally before
|
|
the credential is approved. The original command is retried once; a credential
|
|
that fails on the retry is rejected before the Git error is raised.
|
|
}
|
|
|
|
@defparam[current-git-authentication-handler handler procedure?]{
|
|
Contains the authentication callback used after a recognized authentication
|
|
failure. Its default value is @racket[default-git-authentication-handler].
|
|
}
|