DEVELOPMENT.md
gix-glob for examples.unwrap(). If needed, expect("why").thiserror generally.We use a style I'd call 'purposeful conventional commits', and instead of classifying every commit using conventional commit messaging, we do so only if the message should show up in the changelog.
The subject usually informs about the what and the body provides details and explains the why.
Commit messages must show up in the changelog in case of breaking changes. Examples for that are:
change!: rename Foo to Bar. (#123)
And this is why we do it in the body.
remove!: Repository::obsolete().
Nobody used this method.
Features or other changes that are visible and people should know about look like this:
feat: add Repository::foo() to do great things. (#234)
And here is how it's used and some more details.
fix: don't panic when calling foo() in a bare repository. (#456)
Everything else, particularly refactors or chores, don't use conventional commits as these don't affect users of the API. Examples could be:
make fmtPlease refrain from using chore: or refactor: prefixes as for the most part, users of the API don't care about those. When a refactor
changes the API in some way, prefer to use feat, change, rename or remove instead, and most of the time the ones that are not feat
are breaking so would be seen with their exclamation mark suffix, like change!.
Commit messages are used for guiding cargo smart-release to do most of the release work for us. This includes changelog generation
as well as picking the right version bump for each crate.
Knowing that cargo smart-release is driven by commit messages and affects their versions with per-crate granularity, it becomes important
to split edits into multiple commits to clearly indicate which crate is actually broken.
Typical patterns include making a breaking change in one crate and then fix all others to work with it. For changelogs to look proper
and version bumps to be correct, the first commit would contain only the breaking changes themselves,
like "rename: foo() to bar()", and the second commit would contain all changes to adapt to that and look like "adapt to changes in <crate name>".
We generally follow a 'track everything' approach and there is a lot of freedom leading to more commits rather than less. There is no obligation to squash commits or to otherwise tune the history.
We use feature branches and PRs most of the time to be able to take advantage of CI and GitHub review tools, and merge with merge commits
to make individual efforts stand out. There is no need for linearizing history or tuning it in any other way. However, each commit
must follow the guidelines laid out in the Commit Messages paragraph.
There is value in organizing commits by topic and Stacked Git is hereby endorsed to do that.
As a general rule, respect and implement all applicable git-config by default, but allow the caller to set overrides. How overrides work depends on the goals of the particular API so it can be done on the main call path, forcing a choice, or more typically, as a side-lane where overrides can be done on demand.
Note that it should be possible to obtain the current configuration for modification by the user for selective overrides, either
by calling methods or by obtaining a data structure that can be set as a whole using a get -> modify -> set cycle.
Note that without any of that, one should document that with config_snapshot_mut() any of the relevant configuration can be
changed in memory before invoking a method in order to affect it.
Parameters which are not available in git or specific to gitoxide or the needs of the caller can be passed as parameters or via
Options or Context structures as needed.
async
async clients as opt-in using feature toggles to help integrating into an existing async codebase.blocking can be used to make Read and Iterator async, or move any operation onto a thread which blends it into the
async world.
gix_features::interrupt::trigger() function, and after a moment
of waiting the flag can be unset with the …::uninterrupt() function to allow new long-running operations to work.
Every long running operation supports this.maybe_async
maybe_async and its dependencies to always be present, increasing compile times. For now we chose a little more code to handle
over increasing compile times for everyone. This stance may change later once compile times don't matter that much anymore to allow the removal of code.Default trait implementations
Using the Progress trait
Progress implementation
add_child(…) then use it directly to communicate progress, leaving
control of the name to the caller. However, call .init(…) to configure the iteration.add_child(…) don't use the parent progress instance for anything else.interruption of long-running operations
gix-features::interrupt::* for building support for interruptions of long-running operations only.
prepare for SHA256 support by using gix_hash::ObjectId and gix_hash::oid
Id type instead
of slices or arrays of 20 bytes. This way, eventually we can support multiple hash digest sizes.symbolic links do not exist as far as we are concerned
when to use interior mutability
gix_features::threading::* exclusively to allow switching between
thread-safe and none-threadsafe versions at compile time.
&mut self if locally stored caches are involved.Sync, but only if the gix-features/parallel is enabled due to the usage of gix_features::threading::… primitives which won't
be thread-safe without the feature.when to use shared ownership
gix_features::threading::OwnShared particularly when shared resources supposed to be used by thread-local handles. Going through a wrapper for shared ownership is fast
and won't be the bottleneck, as it's only about 16% slower than going through a shared reference on a single core.Path encoding
git, paths are just bytes no matter on which platform. We assume that on windows its path handling goes through some abstraction layer like MSYS2
which avoids it to seeing UTF-16 encoded paths (and writing them). Thus it should be safe to assume gits path encoding is byte oriented.git even on windows due to MSYS2, we use os_str_bytes to convert these back into OsStr and derivatives like Path
as needed even though it might not always be the case depending on the actual encoding used by MSYS2 or other abstraction layers, or avoiding to use std types altogether
using our own instead.A bunch of notes collected to keep track of what's needed to eventually support it
hash-function-transition.txtgpgsig-sha256 field - we won't break, but also don't do anything with it (e.g. extra_headers).unwrap() vs .expect(…)quick_error!() or Box<dyn std::error::Error>.expect(…) as assertion on Options, providing context on why the expectations should hold. Or in other words,
answer "This should work because…<expect(…)>"Options vs ContextOptions whenever there is something to configure in terms of branching behaviour. It can be defaulted, and if it can't these fields should be parameters of the method
that takes these Options.Context when data is required to perform an operation at all. See gix_config::path::Context as reference. It can't be defaulted and the fields could also be parameters.In plumbing crates, prefer to default to keeping references if this is feasible to avoid typically expensive clones.
In porcelain crates, like gix, we have Platforms which are typically cheap enough to create on demand as they configure one or more method calls. These
should keep a reference to the Repository instance that created them as the user is expected to clone the Repository if there is the need.
However, if these structures are more expensive, call them Cache or <NotPlatform> and prefer to clone the Repository into them or otherwise keep them free of lifetimes
to allow the user to keep this structure around for repeated calls. References for this paragraph are this PR and
this discussion.
Both terms are coming from the git implementation itself, even though it won't necessarily point out which commands are plumbing and which
are porcelain.
The term plumbing refers to lower-level, more rarely used commands that complement porcelain by being invoked by it or by hand for certain use
cases.
The term porcelain refers to those with a decent user experience, they are primarily intended for use by humans.
In any case, both types of programs must self-document their capabilities using through the --help flag.
From there, we can derive a few rules to adhere to unless there are good reasons not to:
Here is the hierarchy of programs - each level requires more polish and generally work to be done. Experiments are the quickest ways to obtain some insights. Examples are materialized ideas that others can learn from but that don't quite have the polish (or the potential) to move up to plumbing or porcelain. Plumbing is programs for use in scripts, whereas porcelain is for use by humans.
git2 builds sometimes failing CI)
libgit2.gitoxide.gitoxide usage so people can learn from it.--verbose flag, quiet by default.argh command-line parsing, as well as progress.--quiet and --progress.Utilities to aid in keeping the project fresh and in sync can be found in the Maintenance section of the makefile. Run make to
get an overview.
GIX_TEST_IGNORE_ARCHIVES=1 to assure new fixture scripts (if there are any) are validated
on MacOS and Windows. Linux doesn't need to be tested locally that way, as CI on Linux includes it.Run make publish-all to publish all crates in leaf-first order using cargo release based on the currently set version.
For this to work, you have to run cargo release minor|major each time you break the API of a crate but abort it during package verification.
That way, cargo release updates all the dependents for you with the new version, without actually publishing to crates.io.
Generally, we take the git version installed on ubuntu-latest as the one we stay compatible with (while maintaining backwards compatibility). Certain tests only run on CI, designed to validate certain assumptions still hold against possibly changed git program versions.
This also means that CI may fail despite everything being alright locally, and the fix depends on the problem at hand.
Fixtures are created by using a line like this which produces a line we ignore via tail +1 followed by the un-prettified object payload
trailed by a newline.
echo c56a8e7aa92c86c41a923bc760d2dc39e8a31cf7 | git cat-file --batch | tail +2 > fixture
Thus one has to post-process the file by reducing its size by one using truncate -s -1 fixture, removing the newline byte.
GIT_TRACE=true \
GIT_TRACE_PACK_ACCESS=true \
GIT_TRACE_PACKET=true \
GIT_TRACE_PACKFILE=true \
GIT_TRACE_PERFORMANCE=true \
GIT_TRACE_SHALLOW=true \
GIT_TRACE_SETUP=true \
GIT_CURL_VERBOSE=true \
GIT_SSH_COMMAND="ssh -VVV" \
git <command>
Consider adding GIT_TRACE2_PERF=1 (possibly add GIT_TRACE2_PERF_BRIEF=1 for brevity) as well for statistics and variables
(see their source for more.