diff options
Diffstat (limited to 'Documentation/technical')
| -rw-r--r-- | Documentation/technical/.gitignore | 1 | ||||
| -rw-r--r-- | Documentation/technical/api-error-handling.adoc (renamed from Documentation/technical/api-error-handling.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/api-index-skel.adoc (renamed from Documentation/technical/api-index-skel.txt) | 0 | ||||
| -rwxr-xr-x | Documentation/technical/api-index.sh | 8 | ||||
| -rw-r--r-- | Documentation/technical/api-merge.adoc (renamed from Documentation/technical/api-merge.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/api-parse-options.adoc (renamed from Documentation/technical/api-parse-options.txt) | 10 | ||||
| -rw-r--r-- | Documentation/technical/api-path-walk.adoc | 84 | ||||
| -rw-r--r-- | Documentation/technical/api-simple-ipc.adoc (renamed from Documentation/technical/api-simple-ipc.txt) | 2 | ||||
| -rw-r--r-- | Documentation/technical/api-trace2.adoc (renamed from Documentation/technical/api-trace2.txt) | 2 | ||||
| -rw-r--r-- | Documentation/technical/bitmap-format.adoc (renamed from Documentation/technical/bitmap-format.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/build-systems.adoc (renamed from Documentation/technical/build-systems.txt) | 5 | ||||
| -rw-r--r-- | Documentation/technical/bundle-uri.adoc (renamed from Documentation/technical/bundle-uri.txt) | 14 | ||||
| -rw-r--r-- | Documentation/technical/commit-graph.adoc (renamed from Documentation/technical/commit-graph.txt) | 29 | ||||
| -rw-r--r-- | Documentation/technical/directory-rename-detection.adoc (renamed from Documentation/technical/directory-rename-detection.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/hash-function-transition.adoc (renamed from Documentation/technical/hash-function-transition.txt) | 48 | ||||
| -rw-r--r-- | Documentation/technical/large-object-promisors.adoc | 656 | ||||
| -rw-r--r-- | Documentation/technical/long-running-process-protocol.adoc (renamed from Documentation/technical/long-running-process-protocol.txt) | 1 | ||||
| -rw-r--r-- | Documentation/technical/meson.build | 68 | ||||
| -rw-r--r-- | Documentation/technical/multi-pack-index.adoc (renamed from Documentation/technical/multi-pack-index.txt) | 82 | ||||
| -rw-r--r-- | Documentation/technical/pack-heuristics.adoc (renamed from Documentation/technical/pack-heuristics.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/packfile-uri.adoc (renamed from Documentation/technical/packfile-uri.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/parallel-checkout.adoc (renamed from Documentation/technical/parallel-checkout.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/partial-clone.adoc (renamed from Documentation/technical/partial-clone.txt) | 2 | ||||
| -rw-r--r-- | Documentation/technical/platform-support.adoc (renamed from Documentation/technical/platform-support.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/racy-git.adoc (renamed from Documentation/technical/racy-git.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/reftable.adoc (renamed from Documentation/technical/reftable.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/remembering-renames.adoc (renamed from Documentation/technical/remembering-renames.txt) | 120 | ||||
| -rw-r--r-- | Documentation/technical/repository-version.adoc (renamed from Documentation/technical/repository-version.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/rerere.adoc (renamed from Documentation/technical/rerere.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/scalar.adoc (renamed from Documentation/technical/scalar.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/send-pack-pipeline.adoc (renamed from Documentation/technical/send-pack-pipeline.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/shallow.adoc (renamed from Documentation/technical/shallow.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/sparse-checkout.adoc (renamed from Documentation/technical/sparse-checkout.txt) | 708 | ||||
| -rw-r--r-- | Documentation/technical/sparse-index.adoc (renamed from Documentation/technical/sparse-index.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/trivial-merge.adoc (renamed from Documentation/technical/trivial-merge.txt) | 0 | ||||
| -rw-r--r-- | Documentation/technical/unit-tests.adoc (renamed from Documentation/technical/unit-tests.txt) | 0 |
36 files changed, 1373 insertions, 467 deletions
diff --git a/Documentation/technical/.gitignore b/Documentation/technical/.gitignore index 8aa891daee..3caef14a93 100644 --- a/Documentation/technical/.gitignore +++ b/Documentation/technical/.gitignore @@ -1 +1,2 @@ api-index.txt +api-index.adoc diff --git a/Documentation/technical/api-error-handling.txt b/Documentation/technical/api-error-handling.adoc index 665c4960b4..665c4960b4 100644 --- a/Documentation/technical/api-error-handling.txt +++ b/Documentation/technical/api-error-handling.adoc diff --git a/Documentation/technical/api-index-skel.txt b/Documentation/technical/api-index-skel.adoc index 7780a76b08..7780a76b08 100644 --- a/Documentation/technical/api-index-skel.txt +++ b/Documentation/technical/api-index-skel.adoc diff --git a/Documentation/technical/api-index.sh b/Documentation/technical/api-index.sh index 2964885574..dd206b1ca4 100755 --- a/Documentation/technical/api-index.sh +++ b/Documentation/technical/api-index.sh @@ -13,18 +13,18 @@ OUTPUT="$2" cd "$SOURCE_DIR" c=//////////////////////////////////////////////////////////////// - skel=api-index-skel.txt + skel=api-index-skel.adoc sed -e '/^\/\/ table of contents begin/q' "$skel" echo "$c" - ls api-*.txt | + ls api-*.adoc | while read filename do case "$filename" in - api-index-skel.txt | api-index.txt) continue ;; + api-index-skel.adoc | api-index.adoc) continue ;; esac title=$(sed -e 1q "$filename") - html=${filename%.txt}.html + html=${filename%.adoc}.html echo "* link:$html[$title]" done echo "$c" diff --git a/Documentation/technical/api-merge.txt b/Documentation/technical/api-merge.adoc index c2ba01828c..c2ba01828c 100644 --- a/Documentation/technical/api-merge.txt +++ b/Documentation/technical/api-merge.adoc diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.adoc index 61fa6ee167..880eb94642 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.adoc @@ -211,11 +211,13 @@ There are some macros to easily define options: Use of `--no-option` will clear the list of preceding values. `OPT_INTEGER(short, long, &int_var, description)`:: - Introduce an option with integer argument. - The integer is put into `int_var`. + Introduce an option with integer argument. The argument must be a + integer and may include a suffix of 'k', 'm' or 'g' to + scale the provided value by 1024, 1024^2 or 1024^3 respectively. + The scaled value is put into `int_var`. -`OPT_MAGNITUDE(short, long, &unsigned_long_var, description)`:: - Introduce an option with a size argument. The argument must be a +`OPT_UNSIGNED(short, long, &unsigned_long_var, description)`:: + Introduce an option with an unsigned integer argument. The argument must be a non-negative integer and may include a suffix of 'k', 'm' or 'g' to scale the provided value by 1024, 1024^2 or 1024^3 respectively. The scaled value is put into `unsigned_long_var`. diff --git a/Documentation/technical/api-path-walk.adoc b/Documentation/technical/api-path-walk.adoc new file mode 100644 index 0000000000..a67de1b143 --- /dev/null +++ b/Documentation/technical/api-path-walk.adoc @@ -0,0 +1,84 @@ +Path-Walk API +============= + +The path-walk API is used to walk reachable objects, but to visit objects +in batches based on a common path they appear in, or by type. + +For example, all reachable commits are visited in a group. All tags are +visited in a group. Then, all root trees are visited. At some point, all +blobs reachable via a path `my/dir/to/A` are visited. When there are +multiple paths possible to reach the same object, then only one of those +paths is used to visit the object. + +Basics +------ + +To use the path-walk API, include `path-walk.h` and call +`walk_objects_by_path()` with a customized `path_walk_info` struct. The +struct is used to set all of the options for how the walk should proceed. +Let's dig into the different options and their use. + +`path_fn` and `path_fn_data`:: + The most important option is the `path_fn` option, which is a + function pointer to the callback that can execute logic on the + object IDs for objects grouped by type and path. This function + also receives a `data` value that corresponds to the + `path_fn_data` member, for providing custom data structures to + this callback function. + +`revs`:: + To configure the exact details of the reachable set of objects, + use the `revs` member and initialize it using the revision + machinery in `revision.h`. Initialize `revs` using calls such as + `setup_revisions()` or `parse_revision_opt()`. Do not call + `prepare_revision_walk()`, as that will be called within + `walk_objects_by_path()`. ++ +It is also important that you do not specify the `--objects` flag for the +`revs` struct. The revision walk should only be used to walk commits, and +the objects will be walked in a separate way based on those starting +commits. + +`commits`:: +`blobs`:: +`trees`:: +`tags`:: + By default, these members are enabled and signal that the path-walk + API should call the `path_fn` on objects of these types. Specialized + applications could disable some options to make it simpler to walk + the objects or to have fewer calls to `path_fn`. ++ +While it is possible to walk only commits in this way, consumers would be +better off using the revision walk API instead. + +`prune_all_uninteresting`:: + By default, all reachable paths are emitted by the path-walk API. + This option allows consumers to declare that they are not + interested in paths where all included objects are marked with the + `UNINTERESTING` flag. This requires using the `boundary` option in + the revision walk so that the walk emits commits marked with the + `UNINTERESTING` flag. + +`edge_aggressive`:: + For performance reasons, usually only the boundary commits are + explored to find UNINTERESTING objects. However, in the case of + shallow clones it can be helpful to mark all trees and blobs + reachable from UNINTERESTING tip commits as UNINTERESTING. This + matches the behavior of `--objects-edge-aggressive` in the + revision API. + +`pl`:: + This pattern list pointer allows focusing the path-walk search to + a set of patterns, only emitting paths that match the given + patterns. See linkgit:gitignore[5] or + linkgit:git-sparse-checkout[1] for details about pattern lists. + When the pattern list uses cone-mode patterns, then the path-walk + API can prune the set of paths it walks to improve performance. + +Examples +-------- + +See example usages in: + `t/helper/test-path-walk.c`, + `builtin/pack-objects.c`, + `builtin/backfill.c` diff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.adoc index c4fb152b23..972178b042 100644 --- a/Documentation/technical/api-simple-ipc.txt +++ b/Documentation/technical/api-simple-ipc.adoc @@ -36,7 +36,7 @@ Comparison with sub-process model --------------------------------- The Simple-IPC mechanism differs from the existing `sub-process.c` -model (Documentation/technical/long-running-process-protocol.txt) and +model (Documentation/technical/long-running-process-protocol.adoc) and used by applications like Git-LFS. In the LFS-style sub-process model, the helper is started by the foreground process, communication happens via a pair of file descriptors bound to the stdin/stdout of the diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.adoc index 5817b18310..cf493dae03 100644 --- a/Documentation/technical/api-trace2.txt +++ b/Documentation/technical/api-trace2.adoc @@ -140,7 +140,7 @@ $ cat ~/log.event To enable a target, set the corresponding environment variable or system or global config value to one of the following: -include::../trace2-target-values.txt[] +include::../trace2-target-values.adoc[] When trace files are written to a target directory, they will be named according to the last component of the SID (optionally followed by a counter to avoid diff --git a/Documentation/technical/bitmap-format.txt b/Documentation/technical/bitmap-format.adoc index bfb0ec7beb..bfb0ec7beb 100644 --- a/Documentation/technical/bitmap-format.txt +++ b/Documentation/technical/bitmap-format.adoc diff --git a/Documentation/technical/build-systems.txt b/Documentation/technical/build-systems.adoc index d9dafb407c..3c5237b9fd 100644 --- a/Documentation/technical/build-systems.txt +++ b/Documentation/technical/build-systems.adoc @@ -32,7 +32,10 @@ that generally have somebody running test pipelines against regularly: - OpenBSD The platforms which must be supported by the tool should be aligned with our -[platform support policy](platform-support.txt). +platform support policy (see platform-support.adoc). +// once we lose AsciiDoc compatibility, we can start writing the above as: +// xref:platform-support.adoc#platform-support-policy[platform support policy] +// or something like that, but until then.... === Auto-detection of supported features diff --git a/Documentation/technical/bundle-uri.txt b/Documentation/technical/bundle-uri.adoc index 91d3a13e32..12283fa9ed 100644 --- a/Documentation/technical/bundle-uri.txt +++ b/Documentation/technical/bundle-uri.adoc @@ -232,13 +232,13 @@ will interact with bundle URIs according to the following flow: are present in the client repository. If some are missing, then the client delays unbundling until other bundles have been unbundled, making those OIDs present. When all required OIDs are present, the - client unbundles that data using a refspec. The default refspec is - `+refs/heads/*:refs/bundles/*`, but this can be configured. These refs - are stored so that later `git fetch` negotiations can communicate each - bundled ref as a `have`, reducing the size of the fetch over the Git - protocol. To allow pruning refs from this ref namespace, Git may - introduce a numbered namespace (such as `refs/bundles/<i>/*`) such that - stale bundle refs can be deleted. + client unbundles that data using a refspec. The refspec used is + `+refs/*:refs/bundles/*`. These refs are stored so that later + `git fetch` negotiations can communicate each bundled ref as a `have`, + reducing the size of the fetch over the Git protocol. To allow pruning + refs from this ref namespace, Git may introduce a numbered namespace + (such as `refs/bundles/<i>/*`) such that stale bundle refs can be + deleted. 3. If the file is instead a bundle list, then the client inspects the `bundle.mode` to see if the list is of the `all` or `any` form. diff --git a/Documentation/technical/commit-graph.txt b/Documentation/technical/commit-graph.adoc index 2c26e95e51..a259d1567b 100644 --- a/Documentation/technical/commit-graph.txt +++ b/Documentation/technical/commit-graph.adoc @@ -39,6 +39,7 @@ A consumer may load the following info for a commit from the graph: Values 1-4 satisfy the requirements of parse_commit_gently(). There are two definitions of generation number: + 1. Corrected committer dates (generation number v2) 2. Topological levels (generation number v1) @@ -158,7 +159,8 @@ number of commits in the full history. By creating a "chain" of commit-graphs, we enable fast writes of new commit data without rewriting the entire commit history -- at least, most of the time. -## File Layout +File Layout +~~~~~~~~~~~ A commit-graph chain uses multiple files, and we use a fixed naming convention to organize these files. Each commit-graph file has a name @@ -170,11 +172,11 @@ hashes for the files in order from "lowest" to "highest". For example, if the `commit-graph-chain` file contains the lines -``` +---- {hash0} {hash1} {hash2} -``` +---- then the commit-graph chain looks like the following diagram: @@ -213,7 +215,8 @@ specifying the hashes of all files in the lower layers. In the above example, `graph-{hash1}.graph` contains `{hash0}` while `graph-{hash2}.graph` contains `{hash0}` and `{hash1}`. -## Merging commit-graph files +Merging commit-graph files +~~~~~~~~~~~~~~~~~~~~~~~~~~ If we only added a new commit-graph file on every write, we would run into a linear search problem through many commit-graph files. Instead, we use a merge @@ -225,6 +228,7 @@ is determined by the merge strategy that the files should collapse to the commits in `graph-{hash1}` should be combined into a new `graph-{hash3}` file. +.... +---------------------+ | | | (new commits) | @@ -250,6 +254,7 @@ file. | | | | +-----------------------+ +.... During this process, the commits to write are combined, sorted and we write the contents to a temporary file, all while holding a `commit-graph-chain.lock` @@ -257,14 +262,15 @@ lock-file. When the file is flushed, we rename it to `graph-{hash3}` according to the computed `{hash3}`. Finally, we write the new chain data to `commit-graph-chain.lock`: -``` +---- {hash3} {hash0} -``` +---- We then close the lock-file. -## Merge Strategy +Merge Strategy +~~~~~~~~~~~~~~ When writing a set of commits that do not exist in the commit-graph stack of height N, we default to creating a new file at level N + 1. We then decide to @@ -289,7 +295,8 @@ The merge strategy values (2 for the size multiple, 64,000 for the maximum number of commits) could be extracted into config settings for full flexibility. -## Handling Mixed Generation Number Chains +Handling Mixed Generation Number Chains +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ With the introduction of generation number v2 and generation data chunk, the following scenario is possible: @@ -318,7 +325,8 @@ have corrected commit dates when written by compatible versions of Git. Thus, rewriting split commit-graph as a single file (`--split=replace`) creates a single layer with corrected commit dates. -## Deleting graph-{hash} files +Deleting graph-\{hash\} files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After a new tip file is written, some `graph-{hash}` files may no longer be part of a chain. It is important to remove these files from disk, eventually. @@ -333,7 +341,8 @@ files whose modified times are older than a given expiry window. This window defaults to zero, but can be changed using command-line arguments or a config setting. -## Chains across multiple object directories +Chains across multiple object directories +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In a repo with alternates, we look for the `commit-graph-chain` file starting in the local object directory and then in each alternate. The first file that diff --git a/Documentation/technical/directory-rename-detection.txt b/Documentation/technical/directory-rename-detection.adoc index 029ee2cedc..029ee2cedc 100644 --- a/Documentation/technical/directory-rename-detection.txt +++ b/Documentation/technical/directory-rename-detection.adoc diff --git a/Documentation/technical/hash-function-transition.txt b/Documentation/technical/hash-function-transition.adoc index 7102c7c8f5..2359d7d106 100644 --- a/Documentation/technical/hash-function-transition.txt +++ b/Documentation/technical/hash-function-transition.adoc @@ -227,9 +227,9 @@ network byte order): ** 4-byte length in bytes of shortened object names. This is the shortest possible length needed to make names in the shortened object name table unambiguous. - ** 4-byte integer, recording where tables relating to this format + ** 8-byte integer, recording where tables relating to this format are stored in this index file, as an offset from the beginning. - * 4-byte offset to the trailer from the beginning of this file. + * 8-byte offset to the trailer from the beginning of this file. * Zero or more additional key/value pairs (4-byte key, 4-byte value). Only one key is supported: 'PSRC'. See the "Loose objects and unreachable objects" section for supported values and how this @@ -260,12 +260,10 @@ network byte order): compressed data to be copied directly from pack to pack during repacking without undetected data corruption. - * A table of 4-byte offset values. For an object in the table of - sorted shortened object names, the value at the corresponding - index in this table indicates where that object can be found in - the pack file. These are usually 31-bit pack file offsets, but - large offsets are encoded as an index into the next table with the - most significant bit set. + * A table of 4-byte offset values. The index of this table in pack order + indicates where that object can be found in the pack file. These are + usually 31-bit pack file offsets, but large offsets are encoded as + an index into the next table with the most significant bit set. * A table of 8-byte offset entries (empty for pack files less than 2 GiB). Pack files are organized with heavily used objects toward @@ -276,10 +274,14 @@ network byte order): up to and not including the table of CRC32 values. - Zero or more NUL bytes. - The trailer consists of the following: - * A copy of the 20-byte SHA-256 checksum at the end of the + * A copy of the full main hash checksum at the end of the corresponding packfile. - * 20-byte SHA-256 checksum of all of the above. + * Full main hash checksum of all of the above. + +The "full main hash" is a full-length hash of the main (not compatibility) +algorithm in the repository. Thus, if the main algorithm is SHA-256, this is +a 32-byte SHA-256 hash and for SHA-1, it's a 20-byte SHA-1 hash. Loose object index ~~~~~~~~~~~~~~~~~~ @@ -394,7 +396,7 @@ inflated again in step 3, for a total of two inflations. Step 4 is probably necessary for good read-time performance. "git pack-objects" on the server optimizes the pack file for good data -locality (see Documentation/technical/pack-heuristics.txt). +locality (see Documentation/technical/pack-heuristics.adoc). Details of this process are likely to change. It will take some experimenting to get this to perform well. @@ -427,17 +429,19 @@ ordinary unsigned commit. Signed Tags ~~~~~~~~~~~ -We add a new field "gpgsig-sha256" to the tag object format to allow -signing tags without relying on SHA-1. Its signed payload is the -SHA-256 content of the tag with its gpgsig-sha256 field and "-----BEGIN PGP -SIGNATURE-----" delimited in-body signature removed. - -This means tags can be signed - -1. using SHA-1 only, as in existing signed tag objects -2. using both SHA-1 and SHA-256, by using gpgsig-sha256 and an in-body - signature. -3. using only SHA-256, by only using the gpgsig-sha256 field. +We add new fields "gpgsig" and "gpgsig-sha256" to the tag object format to +allow signing tags in both formats. The in-body signature is used for the +signature in the current hash algorithm and the header is used for the +signature in the other algorithm. Thus, a dual-signature tag will contain both +an in-body signature and a gpgsig-sha256 header for the SHA-1 format of an +object or both an in-body signature and a gpgsig header for the SHA-256 format +of and object. + +The signed payload of the tag is the content of the tag in the current +algorithm with both its gpgsig and gpgsig-sha256 fields and +"-----BEGIN PGP SIGNATURE-----" delimited in-body signature removed. + +This means tags can be signed using one or both algorithms. Mergetag embedding ~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/technical/large-object-promisors.adoc b/Documentation/technical/large-object-promisors.adoc new file mode 100644 index 0000000000..2aa815e023 --- /dev/null +++ b/Documentation/technical/large-object-promisors.adoc @@ -0,0 +1,656 @@ +Large Object Promisors +====================== + +Since Git has been created, users have been complaining about issues +with storing large files in Git. Some solutions have been created to +help, but they haven't helped much with some issues. + +Git currently supports multiple promisor remotes, which could help +with some of these remaining issues, but it's very hard to use them to +help, because a number of important features are missing. + +The goal of the effort described in this document is to add these +important features. + +We will call a "Large Object Promisor", or "LOP" in short, a promisor +remote which is used to store only large blobs and which is separate +from the main remote that should store the other Git objects and the +rest of the repos. + +By extension, we will also call "Large Object Promisor", or LOP, the +effort described in this document to add a set of features to make it +easier to handle large blobs/files in Git by using LOPs. + +This effort aims to especially improve things on the server side, and +especially for large blobs that are already compressed in a binary +format. + +This effort aims to provide an alternative to Git LFS +(https://git-lfs.com/) and similar tools like git-annex +(https://git-annex.branchable.com/) for handling large files, even +though a complete alternative would very likely require other efforts +especially on the client side, where it would likely help to implement +a new object representation for large blobs as discussed in: + +https://lore.kernel.org/git/xmqqbkdometi.fsf@gitster.g/ + +Non goals +--------- + +- We will not discuss those client side improvements here, as they + would require changes in different parts of Git than this effort. ++ +So we don't pretend to fully replace Git LFS with only this effort, +but we nevertheless believe that it can significantly improve the +current situation on the server side, and that other separate +efforts could also improve the situation on the client side. + +- In the same way, we are not going to discuss all the possible ways + to implement a LOP or their underlying object storage, or to + optimize how LOP works. ++ +Our opinion is that the simplest solution for now is for LOPs to use +object storage through a remote helper (see section II.2 below for +more details) to store their objects. So we consider that this is the +default implementation. If there are improvements on top of this, +that's great, but our opinion is that such improvements are not +necessary for LOPs to already be useful. Such improvements are likely +a different technical topic, and can be taken care of separately +anyway. ++ +So in particular we are not going to discuss pluggable ODBs or other +object database backends that could chunk large blobs, dedup the +chunks and store them efficiently. Sure, that would be a nice +improvement to store large blobs on the server side, but we believe +it can just be a separate effort as it's also not technically very +related to this effort. ++ +We are also not going to discuss data transfer improvements between +LOPs and clients or servers. Sure, there might be some easy and very +effective optimizations there (as we know that objects on LOPs are +very likely incompressible and not deltifying well), but this can be +dealt with separately in a separate effort. + +In other words, the goal of this document is not to talk about all the +possible ways to optimize how Git could handle large blobs, but to +describe how a LOP based solution can already work well and alleviate +a number of current issues in the context of Git clients and servers +sharing Git objects. + +Even if LOPs are used not very efficiently, they can still be useful +and worth using in some cases, as we will see in more details +later in this document: + + - they can make it simpler for clients to use promisor remotes and + therefore avoid fetching a lot of large blobs they might not need + locally, + + - they can make it significantly cheaper or easier for servers to + host a significant part of the current repository content, and + even more to host content with larger blobs or more large blobs + than currently. + +I Issues with the current situation +----------------------------------- + +- Some statistics made on GitLab repos have shown that more than 75% + of the disk space is used by blobs that are larger than 1MB and + often in a binary format. + +- So even if users could use Git LFS or similar tools to store a lot + of large blobs out of their repos, it's a fact that in practice they + don't do it as much as they probably should. + +- On the server side ideally, the server should be able to decide for + itself how it stores things. It should not depend on users deciding + to use tools like Git LFS on some blobs or not. + +- It's much more expensive to store large blobs that don't delta + compress well on regular fast seeking drives (like SSDs) than on + object storage (like Amazon S3 or GCP Buckets). Using fast drives + for regular Git repos makes sense though, as serving regular Git + content (blobs containing text or code) needs drives where seeking + is fast, but the content is relatively small. On the other hand, + object storage for Git LFS blobs makes sense as seeking speed is not + as important when dealing with large files, while costs are more + important. So the fact that users don't use Git LFS or similar tools + for a significant number of large blobs has likely some bad + consequences on the cost of repo storage for most Git hosting + platforms. + +- Having large blobs handled in the same way as other blobs and Git + objects in Git repos instead of on object storage also has a cost in + increased memory and CPU usage, and therefore decreased performance, + when creating packfiles. (This is because Git tries to use delta + compression or zlib compression which is unlikely to work well on + already compressed binary content.) So it's not just a storage cost + increase. + +- When a large blob has been committed into a repo, it might not be + possible to remove this blob from the repo without rewriting + history, even if the user then decides to use Git LFS or a similar + tool to handle it. + +- In fact Git LFS and similar tools are not very flexible in letting + users change their minds about the blobs they should handle or not. + +- Even when users are using Git LFS or similar tools, they are often + complaining that these tools require significant effort to set up, + learn and use correctly. + +II Main features of the "Large Object Promisors" solution +--------------------------------------------------------- + +The main features below should give a rough overview of how the +solution may work. Details about needed elements can be found in +following sections. + +Even if each feature below is very useful for the full solution, it is +very likely to be also useful on its own in some cases where the full +solution is not required. However, we'll focus primarily on the big +picture here. + +Also each feature doesn't need to be implemented entirely in Git +itself. Some could be scripts, hooks or helpers that are not part of +the Git repo. It would be helpful if those could be shared and +improved on collaboratively though. So we want to encourage sharing +them. + +1) Large blobs are stored on LOPs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Large blobs should be stored on special promisor remotes that we will +call "Large Object Promisors" or LOPs. These LOPs should be additional +remotes dedicated to contain large blobs especially those in binary +format. They should be used along with main remotes that contain the +other objects. + +Note 1 +^^^^^^ + +To clarify, a LOP is a normal promisor remote, except that: + +- it should store only large blobs, + +- it should be separate from the main remote, so that the main remote + can focus on serving other objects and the rest of the repos (see + feature 4) below) and can use the LOP as a promisor remote for + itself. + +Note 2 +^^^^^^ + +Git already makes it possible for a main remote to also be a promisor +remote storing both regular objects and large blobs for a client that +clones from it with a filter on blob size. But here we explicitly want +to avoid that. + +Rationale +^^^^^^^^^ + +LOPs aim to be good at handling large blobs while main remotes are +already good at handling other objects. + +Implementation +^^^^^^^^^^^^^^ + +Git already has support for multiple promisor remotes, see +link:partial-clone.html#using-many-promisor-remotes[the partial clone documentation]. + +Also, Git already has support for partial clone using a filter on the +size of the blobs (with `git clone --filter=blob:limit=<size>`). Most +of the other main features below are based on these existing features +and are about making them easy and efficient to use for the purpose of +better handling large blobs. + +2) LOPs can use object storage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +LOPs can be implemented using object storage, like an Amazon S3 or GCP +Bucket or MinIO (which is open source under the GNU AGPLv3 license) to +actually store the large blobs, and can be accessed through a Git +remote helper (see linkgit:gitremote-helpers[7]) which makes the +underlying object storage appear like a remote to Git. + +Note +^^^^ + +A LOP can be a promisor remote accessed using a remote helper by +both some clients and the main remote. + +Rationale +^^^^^^^^^ + +This looks like the simplest way to create LOPs that can cheaply +handle many large blobs. + +Implementation +^^^^^^^^^^^^^^ + +Remote helpers are quite easy to write as shell scripts, but it might +be more efficient and maintainable to write them using other languages +like Go. + +Some already exist under open source licenses, for example: + + - https://github.com/awslabs/git-remote-s3 + - https://gitlab.com/eric.p.ju/git-remote-gs + +Other ways to implement LOPs are certainly possible, but the goal of +this document is not to discuss how to best implement a LOP or its +underlying object storage (see the "0) Non goals" section above). + +3) LOP object storage can be Git LFS storage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The underlying object storage that a LOP uses could also serve as +storage for large files handled by Git LFS. + +Rationale +^^^^^^^^^ + +This would simplify the server side if it wants to both use a LOP and +act as a Git LFS server. + +4) A main remote can offload to a LOP with a configurable threshold +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On the server side, a main remote should have a way to offload to a +LOP all its blobs with a size over a configurable threshold. + +Rationale +^^^^^^^^^ + +This makes it easy to set things up and to clean things up. For +example, an admin could use this to manually convert a repo not using +LOPs to a repo using a LOP. On a repo already using a LOP but where +some users would sometimes push large blobs, a cron job could use this +to regularly make sure the large blobs are moved to the LOP. + +Implementation +^^^^^^^^^^^^^^ + +Using something based on `git repack --filter=...` to separate the +blobs we want to offload from the other Git objects could be a good +idea. The missing part is to connect to the LOP, check if the blobs we +want to offload are already there and if not send them. + +5) A main remote should try to remain clean from large blobs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A main remote should try to avoid containing a lot of oversize +blobs. For that purpose, it should offload as needed to a LOP and it +should have ways to prevent oversize blobs to be fetched, and also +perhaps pushed, into it. + +Rationale +^^^^^^^^^ + +A main remote containing many oversize blobs would defeat the purpose +of LOPs. + +Implementation +^^^^^^^^^^^^^^ + +The way to offload to a LOP discussed in 4) above can be used to +regularly offload oversize blobs. About preventing oversize blobs from +being fetched into the repo see 6) below. About preventing oversize +blob pushes, a pre-receive hook could be used. + +Also there are different scenarios in which large blobs could get +fetched into the main remote, for example: + +- A client that doesn't implement the "promisor-remote" protocol + (described in 6) below) clones from the main remote. + +- The main remote gets a request for information about a large blob + and is not able to get that information without fetching the blob + from the LOP. + +It might not be possible to completely prevent all these scenarios +from happening. So the goal here should be to implement features that +make the fetching of large blobs less likely. For example adding a +`remote-object-info` command in the `git cat-file --batch` protocol +and its variants might make it possible for a main repo to respond to +some requests about large blobs without fetching them. + +6) A protocol negotiation should happen when a client clones +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a client clones from a main repo, there should be a protocol +negotiation so that the server can advertise one or more LOPs and so +that the client and the server can discuss if the client could +directly use a LOP the server is advertising. If the client and the +server can agree on that, then the client would be able to get the +large blobs directly from the LOP and the server would not need to +fetch those blobs from the LOP to be able to serve the client. + +Note +^^^^ + +For fetches instead of clones, a protocol negotiation might not always +happen, see the "What about fetches?" FAQ entry below for details. + +Rationale +^^^^^^^^^ + +Security, configurability and efficiency of setting things up. + +Implementation +^^^^^^^^^^^^^^ + +A "promisor-remote" protocol v2 capability looks like a good way to +implement this. The way the client and server use this capability +could be controlled by configuration variables. + +Information that the server could send to the client through that +protocol could be things like: LOP name, LOP URL, filter-spec (for +example `blob:limit=<size>`) or just size limit that should be used as +a filter when cloning, token to be used with the LOP, etc. + +7) A client can offload to a LOP +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a client is using a LOP that is also a LOP of its main remote, +the client should be able to offload some large blobs it has fetched, +but might not need anymore, to the LOP. + +Note +^^^^ + +It might depend on the context if it should be OK or not for clients +to offload large blobs they have created, instead of fetched, directly +to the LOP without the main remote checking them in some ways +(possibly using hooks or other tools). + +This should be discussed and refined when we get closer to +implementing this feature. + +Rationale +^^^^^^^^^ + +On the client, the easiest way to deal with unneeded large blobs is to +offload them. + +Implementation +^^^^^^^^^^^^^^ + +This is very similar to what 4) above is about, except on the client +side instead of the server side. So a good solution to 4) could likely +be adapted to work on the client side too. + +There might be some security issues here, as there is no negotiation, +but they might be mitigated if the client can reuse a token it got +when cloning (see 6) above). Also if the large blobs were fetched from +a LOP, it is likely, and can easily be confirmed, that the LOP still +has them, so that they can just be removed from the client. + +III Benefits of using LOPs +-------------------------- + +Many benefits are related to the issues discussed in "I) Issues with +the current situation" above: + +- No need to rewrite history when deciding which blobs are worth + handling separately than other objects, or when moving or removing + the threshold. + +- If the protocol between client and server is developed and secured + enough, then many details might be setup on the server side only and + all the clients could then easily get all the configuration + information and use it to set themselves up mostly automatically. + +- Storage costs benefits on the server side. + +- Reduced memory and CPU needs on main remotes on the server side. + +- Reduced storage needs on the client side. + +IV FAQ +------ + +What about using multiple LOPs on the server and client side? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +That could perhaps be useful in some cases, but for now it's more +likely that in most cases a single LOP will be advertised by the +server and should be used by the client. + +A case where it could be useful for a server to advertise multiple +LOPs is if a LOP is better for some users while a different LOP is +better for other users. For example some clients might have a better +connection to a LOP than others. + +In those cases it's the responsibility of the server to have some +documentation to help clients. It could say for example something like +"Users in this part of the world might want to pick only LOP A as it +is likely to be better connected to them, while users in other parts +of the world should pick only LOP B for the same reason." + +When should we trust or not trust the LOPs advertised by the server? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In some contexts, like in corporate setup where the server and all the +clients are parts of an internal network in a company where admins +have all the rights on every system, it's OK, and perhaps even a good +thing, if the clients fully trust the server, as it can help ensure +that all the clients are on the same page. + +There are also contexts in which clients trust a code hosting platform +serving them some repos, but might not fully trust other users +managing or contributing to some of these repos. For example, the code +hosting platform could have hooks in place to check that any object it +receives doesn't contain malware or otherwise bad content. In this +case it might be OK for the client to use a main remote and its LOP if +they are both hosted by the code hosting platform, but not if the LOP +is hosted elsewhere (where the content is not checked). + +In other contexts, a client should just not trust a server. + +So there should be different ways to configure how the client should +behave when a server advertises a LOP to it at clone time. + +As the basic elements that a server can advertise about a LOP are a +LOP name and a LOP URL, the client should base its decision about +accepting a LOP on these elements. + +One simple way to be very strict in the LOP it accepts is for example +for the client to check that the LOP is already configured on the +client with the same name and URL as what the server advertises. + +In general default and "safe" settings should require that the LOP are +configured on the client separately from the "promisor-remote" +protocol and that the client accepts a LOP only when information about +it from the protocol matches what has been already configured +separately. + +What about LOP names? +~~~~~~~~~~~~~~~~~~~~~ + +In some contexts, for example if the clients sometimes fetch from each +other, it can be a good idea for all the clients to use the same names +for all the remotes they use, including LOPs. + +In other contexts, each client might want to be able to give the name +it wants to each remote, including each LOP, it interacts with. + +So there should be different ways to configure how the client accepts +or not the LOP name the server advertises. + +If a default or "safe" setting is used, then as such a setting should +require that the LOP be configured separately, then the name would be +configured separately and there is no risk that the server could +dictate a name to a client. + +Could the main remote be bogged down by old or paranoid clients? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Yes, it could happen if there are too many clients that are either +unwilling to trust the main remote or that just don't implement the +"promisor-remote" protocol because they are too old or not fully +compatible with the 'git' client. + +When serving such a client, the main remote has no other choice than +to first fetch from its LOP, to then be able to provide to the client +everything it requested. So the main remote, even if it has cleanup +mechanisms (see section II.4 above), would be burdened at least +temporarily with the large blobs it had to fetch from its LOP. + +Not behaving like this would be breaking backward compatibility, and +could be seen as segregating clients. For example, it might be +possible to implement a special mode that allows the server to just +reject clients that don't implement the "promisor-remote" protocol or +aren't willing to trust the main remote. This mode might be useful in +a special context like a corporate environment. There is no plan to +implement such a mode though, and this should be discussed separately +later anyway. + +A better way to proceed is probably for the main remote to show a +message telling clients that don't implement the protocol or are +unwilling to accept the advertised LOP(s) that they would get faster +clone and fetches by upgrading client software or properly setting +them up to accept LOP(s). + +Waiting for clients to upgrade, monitoring these upgrades and limiting +the use of LOPs to repos that are not very frequently accessed might +be other good ways to make sure that some benefits are still reaped +from LOPs. Over time, as more and more clients upgrade and benefit +from LOPs, using them in more and more frequently accessed repos will +become worth it. + +Corporate environments, where it might be easier to make sure that all +the clients are up-to-date and properly configured, could hopefully +benefit more and earlier from using LOPs. + +What about fetches? +~~~~~~~~~~~~~~~~~~~ + +There are different kinds of fetches. A regular fetch happens when +some refs have been updated on the server and the client wants the ref +updates and possibly the new objects added with them. A "backfill" or +"lazy" fetch, on the contrary, happens when the client needs to use +some objects it already knows about but doesn't have because they are +on a promisor remote. + +Regular fetch +^^^^^^^^^^^^^ + +In a regular fetch, the client will contact the main remote and a +protocol negotiation will happen between them. It's a good thing that +a protocol negotiation happens every time, as the configuration on the +client or the main remote could have changed since the previous +protocol negotiation. In this case, the new protocol negotiation +should ensure that the new fetch will happen in a way that satisfies +the new configuration of both the client and the server. + +In most cases though, the configurations on the client and the main +remote will not have changed between 2 fetches or between the initial +clone and a subsequent fetch. This means that the result of a new +protocol negotiation will be the same as the previous result, so the +new fetch will happen in the same way as the previous clone or fetch, +using, or not using, the same LOP(s) as last time. + +"Backfill" or "lazy" fetch +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When there is a backfill fetch, the client doesn't necessarily contact +the main remote first. It will try to fetch from its promisor remotes +in the order they appear in the config file, except that a remote +configured using the `extensions.partialClone` config variable will be +tried last. See +link:partial-clone.html#using-many-promisor-remotes[the partial clone documentation]. + +This is not new with this effort. In fact this is how multiple remotes +have already been working for around 5 years. + +When using LOPs, having the main remote configured using +`extensions.partialClone`, so it's tried last, makes sense, as missing +objects should only be large blobs that are on LOPs. + +This means that a protocol negotiation will likely not happen as the +missing objects will be fetched from the LOPs, and then there will be +nothing left to fetch from the main remote. + +To secure that, it could be a good idea for LOPs to require a token +from the client when it fetches from them. The client could get the +token when performing a protocol negotiation with the main remote (see +section II.6 above). + +V Future improvements +--------------------- + +It is expected that at the beginning using LOPs will be mostly worth +it either in a corporate context where the Git version that clients +use can easily be controlled, or on repos that are infrequently +accessed. (See the "Could the main remote be bogged down by old or +paranoid clients?" section in the FAQ above.) + +Over time, as more and more clients upgrade to a version that +implements the "promisor-remote" protocol v2 capability described +above in section II.6), it will be worth it to use LOPs more widely. + +A lot of improvements may also help using LOPs more widely. Some of +these improvements are part of the scope of this document like the +following: + + - Implementing a "remote-object-info" command in the + `git cat-file --batch` protocol and its variants to allow main + remotes to respond to requests about large blobs without fetching + them. (Eric Ju has started working on this based on previous work + by Calvin Wan.) + + - Creating better cleanup and offload mechanisms for main remotes + and clients to prevent accumulation of large blobs. + + - Developing more sophisticated protocol negotiation capabilities + between clients and servers for handling LOPs, for example adding + a filter-spec (e.g., blob:limit=<size>) or size limit for + filtering when cloning, or adding a token for LOP authentication. + + - Improving security measures for LOP access, particularly around + token handling and authentication. + + - Developing standardized ways to configure and manage multiple LOPs + across different environments. Especially in the case where + different LOPs serve the same content to clients in different + geographical locations, there is a need for replication or + synchronization between LOPs. + +Some improvements, including some that have been mentioned in the "0) +Non Goals" section of this document, are out of the scope of this +document: + + - Implementing a new object representation for large blobs on the + client side. + + - Developing pluggable ODBs or other object database backends that + could chunk large blobs, dedup the chunks and store them + efficiently. + + - Optimizing data transfer between LOPs and clients/servers, + particularly for incompressible and non-deltifying content. + + - Creating improved client side tools for managing large objects + more effectively, for example tools for migrating from Git LFS or + git-annex, or tools to find which objects could be offloaded and + how much disk space could be reclaimed by offloading them. + +Some improvements could be seen as part of the scope of this document, +but might already have their own separate projects from the Git +project, like: + + - Improving existing remote helpers to access object storage or + developing new ones. + + - Improving existing object storage solutions or developing new + ones. + +Even though all the above improvements may help, this document and the +LOP effort should try to focus, at least first, on a relatively small +number of improvements mostly those that are in its current scope. + +For example introducing pluggable ODBs and a new object database +backend is likely a multi-year effort on its own that can happen +separately in parallel. It has different technical requirements, +touches other part of the Git code base and should have its own design +document(s). diff --git a/Documentation/technical/long-running-process-protocol.txt b/Documentation/technical/long-running-process-protocol.adoc index 6f33654b42..39bd89d467 100644 --- a/Documentation/technical/long-running-process-protocol.txt +++ b/Documentation/technical/long-running-process-protocol.adoc @@ -24,6 +24,7 @@ After the version negotiation Git sends a list of all capabilities that it supports and a flush packet. Git expects to read a list of desired capabilities, which must be a subset of the supported capabilities list, and a flush packet as response: + ------------------------ packet: git> git-filter-client packet: git> version=2 diff --git a/Documentation/technical/meson.build b/Documentation/technical/meson.build index 21dfb8b5c9..be698ef22a 100644 --- a/Documentation/technical/meson.build +++ b/Documentation/technical/meson.build @@ -1,37 +1,38 @@ api_docs = [ - 'api-error-handling.txt', - 'api-merge.txt', - 'api-parse-options.txt', - 'api-simple-ipc.txt', - 'api-trace2.txt', + 'api-error-handling.adoc', + 'api-merge.adoc', + 'api-parse-options.adoc', + 'api-simple-ipc.adoc', + 'api-trace2.adoc', ] articles = [ - 'bitmap-format.txt', - 'build-systems.txt', - 'bundle-uri.txt', - 'commit-graph.txt', - 'directory-rename-detection.txt', - 'hash-function-transition.txt', - 'long-running-process-protocol.txt', - 'multi-pack-index.txt', - 'packfile-uri.txt', - 'pack-heuristics.txt', - 'parallel-checkout.txt', - 'partial-clone.txt', - 'platform-support.txt', - 'racy-git.txt', - 'reftable.txt', - 'remembering-renames.txt', - 'repository-version.txt', - 'rerere.txt', - 'scalar.txt', - 'send-pack-pipeline.txt', - 'shallow.txt', - 'sparse-checkout.txt', - 'sparse-index.txt', - 'trivial-merge.txt', - 'unit-tests.txt', + 'bitmap-format.adoc', + 'build-systems.adoc', + 'bundle-uri.adoc', + 'commit-graph.adoc', + 'directory-rename-detection.adoc', + 'hash-function-transition.adoc', + 'large-object-promisors.adoc', + 'long-running-process-protocol.adoc', + 'multi-pack-index.adoc', + 'packfile-uri.adoc', + 'pack-heuristics.adoc', + 'parallel-checkout.adoc', + 'partial-clone.adoc', + 'platform-support.adoc', + 'racy-git.adoc', + 'reftable.adoc', + 'remembering-renames.adoc', + 'repository-version.adoc', + 'rerere.adoc', + 'scalar.adoc', + 'send-pack-pipeline.adoc', + 'shallow.adoc', + 'sparse-checkout.adoc', + 'sparse-index.adoc', + 'trivial-merge.adoc', + 'unit-tests.adoc', ] api_index = custom_target( @@ -43,10 +44,10 @@ api_index = custom_target( ], env: script_environment, input: api_docs, - output: 'api-index.txt', + output: 'api-index.adoc', ) -custom_target( +doc_targets += custom_target( command: asciidoc_html_options, input: api_index, output: 'api-index.html', @@ -56,10 +57,11 @@ custom_target( ) foreach article : api_docs + articles - custom_target( + doc_targets += custom_target( command: asciidoc_html_options, input: article, output: fs.stem(article) + '.html', + depends: documentation_deps, install: true, install_dir: get_option('datadir') / 'doc/git-doc/technical', ) diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.adoc index cc063b30be..ffda70aa13 100644 --- a/Documentation/technical/multi-pack-index.txt +++ b/Documentation/technical/multi-pack-index.adoc @@ -164,19 +164,81 @@ objects_nr($H2) + objects_nr($H1) + i (in the C implementation, this is often computed as `i + m->num_objects_in_base`). +=== Pseudo-pack order for incremental MIDXs + +The original implementation of multi-pack reachability bitmaps defined +the pseudo-pack order in linkgit:gitformat-pack[5] (see the section +titled "multi-pack-index reverse indexes") roughly as follows: + +____ +In short, a MIDX's pseudo-pack is the de-duplicated concatenation of +objects in packs stored by the MIDX, laid out in pack order, and the +packs arranged in MIDX order (with the preferred pack coming first). +____ + +In the incremental MIDX design, we extend this definition to include +objects from multiple layers of the MIDX chain. The pseudo-pack order +for incremental MIDXs is determined by concatenating the pseudo-pack +ordering for each layer of the MIDX chain in order. Formally two objects +`o1` and `o2` are compared as follows: + +1. If `o1` appears in an earlier layer of the MIDX chain than `o2`, then + `o1` sorts ahead of `o2`. + +2. Otherwise, if `o1` and `o2` appear in the same MIDX layer, and that + MIDX layer has no base, then if one of `pack(o1)` and `pack(o2)` is + preferred and the other is not, then the preferred one sorts ahead of + the non-preferred one. If there is a base layer (i.e. the MIDX layer + is not the first layer in the chain), then if `pack(o1)` appears + earlier in that MIDX layer's pack order, then `o1` sorts ahead of + `o2`. Likewise if `pack(o2)` appears earlier, then the opposite is + true. + +3. Otherwise, `o1` and `o2` appear in the same pack, and thus in the + same MIDX layer. Sort `o1` and `o2` by their offset within their + containing packfile. + +Note that the preferred pack is a property of the MIDX chain, not the +individual layers themselves. Fundamentally we could introduce a +per-layer preferred pack, but this is less relevant now that we can +perform multi-pack reuse across the set of packs in a MIDX. + +=== Reachability bitmaps and incremental MIDXs + +Each layer of an incremental MIDX chain may have its objects (and the +objects from any previous layer in the same MIDX chain) represented in +its own `*.bitmap` file. + +The structure of a `*.bitmap` file belonging to an incremental MIDX +chain is identical to that of a non-incremental MIDX bitmap, or a +classic single-pack bitmap. Since objects are added to the end of the +incremental MIDX's pseudo-pack order (see above), it is possible to +extend a bitmap when appending to the end of a MIDX chain. + +(Note: it is possible likewise to compress a contiguous sequence of MIDX +incremental layers, and their `*.bitmap` files into a single layer and +`*.bitmap`, but this is not yet implemented.) + +The object positions used are global within the pseudo-pack order, so +subsequent layers will have, for example, `m->num_objects_in_base` +number of `0` bits in each of their four type bitmaps. This follows from +the fact that we only write type bitmap entries for objects present in +the layer immediately corresponding to the bitmap). + +Note also that only the bitmap pertaining to the most recent layer in an +incremental MIDX chain is used to store reachability information about +the interesting and uninteresting objects in a reachability query. +Earlier bitmap layers are only used to look up commit and pseudo-merge +bitmaps from that layer, as well as the type-level bitmaps for objects +in that layer. + +To simplify the implementation, type-level bitmaps are iterated +simultaneously, and their results are OR'd together to avoid recursively +calling internal bitmap functions. + Future Work ----------- -- The multi-pack-index allows many packfiles, especially in a context - where repacking is expensive (such as a very large repo), or - unexpected maintenance time is unacceptable (such as a high-demand - build machine). However, the multi-pack-index needs to be rewritten - in full every time. We can extend the format to be incremental, so - writes are fast. By storing a small "tip" multi-pack-index that - points to large "base" MIDX files, we can keep writes fast while - still reducing the number of binary searches required for object - lookups. - - If the multi-pack-index is extended to store a "stable object order" (a function Order(hash) = integer that is constant for a given hash, even as the multi-pack-index is updated) then MIDX bitmaps could be diff --git a/Documentation/technical/pack-heuristics.txt b/Documentation/technical/pack-heuristics.adoc index 95a07db6e8..95a07db6e8 100644 --- a/Documentation/technical/pack-heuristics.txt +++ b/Documentation/technical/pack-heuristics.adoc diff --git a/Documentation/technical/packfile-uri.txt b/Documentation/technical/packfile-uri.adoc index 9d453d4765..9d453d4765 100644 --- a/Documentation/technical/packfile-uri.txt +++ b/Documentation/technical/packfile-uri.adoc diff --git a/Documentation/technical/parallel-checkout.txt b/Documentation/technical/parallel-checkout.adoc index b4a144e5f4..b4a144e5f4 100644 --- a/Documentation/technical/parallel-checkout.txt +++ b/Documentation/technical/parallel-checkout.adoc diff --git a/Documentation/technical/partial-clone.txt b/Documentation/technical/partial-clone.adoc index bf5ec5c82d..e513e391ea 100644 --- a/Documentation/technical/partial-clone.txt +++ b/Documentation/technical/partial-clone.adoc @@ -85,7 +85,7 @@ See "filter" in linkgit:gitprotocol-pack[5]. server to request filtering during packfile construction. + There are various filters available to accommodate different situations. -See "--filter=<filter-spec>" in Documentation/rev-list-options.txt. +See "--filter=<filter-spec>" in Documentation/rev-list-options.adoc. - On the server pack-objects applies the requested filter-spec as it creates "filtered" packfiles for the client. diff --git a/Documentation/technical/platform-support.txt b/Documentation/technical/platform-support.adoc index 0a2fb28d62..0a2fb28d62 100644 --- a/Documentation/technical/platform-support.txt +++ b/Documentation/technical/platform-support.adoc diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.adoc index 59bea66c0f..59bea66c0f 100644 --- a/Documentation/technical/racy-git.txt +++ b/Documentation/technical/racy-git.adoc diff --git a/Documentation/technical/reftable.txt b/Documentation/technical/reftable.adoc index dd0b37c4e3..dd0b37c4e3 100644 --- a/Documentation/technical/reftable.txt +++ b/Documentation/technical/reftable.adoc diff --git a/Documentation/technical/remembering-renames.txt b/Documentation/technical/remembering-renames.adoc index 73f41761e2..6155f36c72 100644 --- a/Documentation/technical/remembering-renames.txt +++ b/Documentation/technical/remembering-renames.adoc @@ -10,32 +10,32 @@ history as an optimization, assuming all merges are automatic and clean Outline: - 0. Assumptions + 1. Assumptions - 1. How rebasing and cherry-picking work + 2. How rebasing and cherry-picking work - 2. Why the renames on MERGE_SIDE1 in any given pick are *always* a + 3. Why the renames on MERGE_SIDE1 in any given pick are *always* a superset of the renames on MERGE_SIDE1 for the next pick. - 3. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also + 4. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also a rename on MERGE_SIDE1 for the next pick - 4. A detailed description of the counter-examples to #3. + 5. A detailed description of the counter-examples to #4. - 5. Why the special cases in #4 are still fully reasonable to use to pair + 6. Why the special cases in #5 are still fully reasonable to use to pair up files for three-way content merging in the merge machinery, and why they do not affect the correctness of the merge. - 6. Interaction with skipping of "irrelevant" renames + 7. Interaction with skipping of "irrelevant" renames - 7. Additional items that need to be cached + 8. Additional items that need to be cached - 8. How directory rename detection interacts with the above and why this + 9. How directory rename detection interacts with the above and why this optimization is still safe even if merge.directoryRenames is set to "true". -=== 0. Assumptions === +== 1. Assumptions == There are two assumptions that will hold throughout this document: @@ -44,8 +44,8 @@ There are two assumptions that will hold throughout this document: * All merges are fully automatic -and a third that will hold in sections 2-5 for simplicity, that I'll later -address in section 8: +and a third that will hold in sections 3-6 for simplicity, that I'll later +address in section 9: * No directory renames occur @@ -77,9 +77,9 @@ conflicts that the user needs to resolve), the cache of renames is not stored on disk, and thus is thrown away as soon as the rebase or cherry pick stops for the user to resolve the operation. -The third assumption makes sections 2-5 simpler, and allows people to +The third assumption makes sections 3-6 simpler, and allows people to understand the basics of why this optimization is safe and effective, and -then I can go back and address the specifics in section 8. It is probably +then I can go back and address the specifics in section 9. It is probably also worth noting that if directory renames do occur, then the default of merge.directoryRenames being set to "conflict" means that the operation will stop for users to resolve the conflicts and the cache will be thrown @@ -88,22 +88,26 @@ reason we need to address directory renames specifically, is that some users will have set merge.directoryRenames to "true" to allow the merges to continue to proceed automatically. The optimization is still safe with this config setting, but we have to discuss a few more cases to show why; -this discussion is deferred until section 8. +this discussion is deferred until section 9. -=== 1. How rebasing and cherry-picking work === +== 2. How rebasing and cherry-picking work == Consider the following setup (from the git-rebase manpage): +------------ A---B---C topic / D---E---F---G main +------------ After rebasing or cherry-picking topic onto main, this will appear as: +------------ A'--B'--C' topic / D---E---F---G main +------------ The way the commits A', B', and C' are created is through a series of merges, where rebase or cherry-pick sequentially uses each of the three @@ -111,6 +115,7 @@ A-B-C commits in a special merge operation. Let's label the three commits in the merge operation as MERGE_BASE, MERGE_SIDE1, and MERGE_SIDE2. For this picture, the three commits for each of the three merges would be: +.... To create A': MERGE_BASE: E MERGE_SIDE1: G @@ -125,6 +130,7 @@ To create C': MERGE_BASE: B MERGE_SIDE1: B' MERGE_SIDE2: C +.... Sometimes, folks are surprised that these three-way merges are done. It can be useful in understanding these three-way merges to view them in a @@ -138,8 +144,7 @@ Conceptually the two statements above are the same as a three-way merge of B, B', and C, at least the parts before you decide to record a commit. -=== 2. Why the renames on MERGE_SIDE1 in any given pick are always a === -=== superset of the renames on MERGE_SIDE1 for the next pick. === +== 3. Why the renames on MERGE_SIDE1 in any given pick are always a superset of the renames on MERGE_SIDE1 for the next pick. == The merge machinery uses the filenames it is fed from MERGE_BASE, MERGE_SIDE1, and MERGE_SIDE2. It will only move content to a different @@ -156,6 +161,7 @@ filename under one of three conditions: First, let's remember what commits are involved in the first and second picks of the cherry-pick or rebase sequence: +.... To create A': MERGE_BASE: E MERGE_SIDE1: G @@ -165,6 +171,7 @@ To create B': MERGE_BASE: A MERGE_SIDE1: A' MERGE_SIDE2: B +.... So, in particular, we need to show that the renames between E and G are a superset of those between A and A'. @@ -181,11 +188,11 @@ are a subset of those between E and G. Equivalently, all renames between E and G are a superset of those between A and A'. -=== 3. Why any rename on MERGE_SIDE1 in any given pick is _almost_ === -=== always also a rename on MERGE_SIDE1 for the next pick. === +== 4. Why any rename on MERGE_SIDE1 in any given pick is _almost_ always also a rename on MERGE_SIDE1 for the next pick. == Let's again look at the first two picks: +.... To create A': MERGE_BASE: E MERGE_SIDE1: G @@ -195,17 +202,25 @@ To create B': MERGE_BASE: A MERGE_SIDE1: A' MERGE_SIDE2: B +.... Now let's look at any given rename from MERGE_SIDE1 of the first pick, i.e. any given rename from E to G. Let's use the filenames 'oldfile' and 'newfile' for demonstration purposes. That first pick will function as follows; when the rename is detected, the merge machinery will do a three-way content merge of the following: + +.... E:oldfile G:newfile A:oldfile +.... + and produce a new result: + +.... A':newfile +.... Note above that I've assumed that E->A did not rename oldfile. If that side did rename, then we most likely have a rename/rename(1to2) conflict @@ -254,19 +269,21 @@ were detected as renames, A:oldfile and A':newfile should also be detectable as renames almost always. -=== 4. A detailed description of the counter-examples to #3. === +== 5. A detailed description of the counter-examples to #4. == -We already noted in section 3 that rename/rename(1to1) (i.e. both sides +We already noted in section 4 that rename/rename(1to1) (i.e. both sides renaming a file the same way) was one counter-example. The more interesting bit, though, is why did we need to use the "almost" qualifier when stating that A:oldfile and A':newfile are "almost" always detectable as renames? -Let's repeat an earlier point that section 3 made: +Let's repeat an earlier point that section 4 made: +.... A':newfile was created by applying the changes between E:oldfile and G:newfile to A:oldfile. The changes between E:oldfile and G:newfile were <50% of the size of E:oldfile. +.... If those changes that were <50% of the size of E:oldfile are also <50% of the size of A:oldfile, then A:oldfile and A':newfile will be detectable as @@ -276,18 +293,21 @@ still somehow merge cleanly), then traditional rename detection would not detect A:oldfile and A':newfile as renames. Here's an example where that can happen: + * E:oldfile had 20 lines * G:newfile added 10 new lines at the beginning of the file * A:oldfile kept the first 3 lines of the file, and deleted all the rest + then + +.... => A':newfile would have 13 lines, 3 of which matches those in A:oldfile. -E:oldfile -> G:newfile would be detected as a rename, but A:oldfile and -A':newfile would not be. + E:oldfile -> G:newfile would be detected as a rename, but A:oldfile and + A':newfile would not be. +.... -=== 5. Why the special cases in #4 are still fully reasonable to use to === -=== pair up files for three-way content merging in the merge machinery, === -=== and why they do not affect the correctness of the merge. === +== 6. Why the special cases in #5 are still fully reasonable to use to pair up files for three-way content merging in the merge machinery, and why they do not affect the correctness of the merge. == In the rename/rename(1to1) case, A:newfile and A':newfile are not renames since they use the *same* filename. However, files with the same filename @@ -295,14 +315,14 @@ are obviously fine to pair up for three-way content merging (the merge machinery has never employed break detection). The interesting counter-example case is thus not the rename/rename(1to1) case, but the case where A did not rename oldfile. That was the case that we spent most of -the time discussing in sections 3 and 4. The remainder of this section +the time discussing in sections 4 and 5. The remainder of this section will be devoted to that case as well. So, even if A:oldfile and A':newfile aren't detectable as renames, why is it still reasonable to pair them up for three-way content merging in the merge machinery? There are multiple reasons: - * As noted in sections 3 and 4, the diff between A:oldfile and A':newfile + * As noted in sections 4 and 5, the diff between A:oldfile and A':newfile is *exactly* the same as the diff between E:oldfile and G:newfile. The latter pair were detected as renames, so it seems unlikely to surprise users for us to treat A:oldfile and A':newfile as renames. @@ -394,7 +414,7 @@ cases 1 and 3 seem to provide as good or better behavior with the optimization than without. -=== 6. Interaction with skipping of "irrelevant" renames === +== 7. Interaction with skipping of "irrelevant" renames == Previous optimizations involved skipping rename detection for paths considered to be "irrelevant". See for example the following commits: @@ -421,24 +441,27 @@ detection -- though we can limit it to the paths for which we have not already detected renames. -=== 7. Additional items that need to be cached === +== 8. Additional items that need to be cached == It turns out we have to cache more than just renames; we also cache: +.... A) non-renames (i.e. unpaired deletes) B) counts of renames within directories C) sources that were marked as RELEVANT_LOCATION, but which were downgraded to RELEVANT_NO_MORE D) the toplevel trees involved in the merge +.... These are all stored in struct rename_info, and respectively appear in + * cached_pairs (along side actual renames, just with a value of NULL) * dir_rename_counts * cached_irrelevant * merge_trees -The reason for (A) comes from the irrelevant renames skipping -optimization discussed in section 6. The fact that irrelevant renames +The reason for `(A)` comes from the irrelevant renames skipping +optimization discussed in section 7. The fact that irrelevant renames are skipped means we only get a subset of the potential renames detected and subsequent commits may need to run rename detection on the upstream side on a subset of the remaining renames (to get the @@ -447,23 +470,24 @@ deletes are involved in rename detection too, we don't want to repeatedly check that those paths remain unpaired on the upstream side with every commit we are transplanting. -The reason for (B) is that diffcore_rename_extended() is what +The reason for `(B)` is that diffcore_rename_extended() is what generates the counts of renames by directory which is needed in directory rename detection, and if we don't run diffcore_rename_extended() again then we need to have the output from it, including dir_rename_counts, from the previous run. -The reason for (C) is that merge-ort's tree traversal will again think +The reason for `(C)` is that merge-ort's tree traversal will again think those paths are relevant (marking them as RELEVANT_LOCATION), but the fact that they were downgraded to RELEVANT_NO_MORE means that dir_rename_counts already has the information we need for directory rename detection. (A path which becomes RELEVANT_CONTENT in a subsequent commit will be removed from cached_irrelevant.) -The reason for (D) is that is how we determine whether the remember +The reason for `(D)` is that is how we determine whether the remember renames optimization can be used. In particular, remembering that our sequence of merges looks like: +.... Merge 1: MERGE_BASE: E MERGE_SIDE1: G @@ -475,6 +499,7 @@ sequence of merges looks like: MERGE_SIDE1: A' MERGE_SIDE2: B => Creates B' +.... It is the fact that the trees A and A' appear both in Merge 1 and in Merge 2, with A as a parent of A' that allows this optimization. So @@ -482,12 +507,11 @@ we store the trees to compare with what we are asked to merge next time. -=== 8. How directory rename detection interacts with the above and === -=== why this optimization is still safe even if === -=== merge.directoryRenames is set to "true". === +== 9. How directory rename detection interacts with the above and why this optimization is still safe even if merge.directoryRenames is set to "true". == As noted in the assumptions section: +.... """ ...if directory renames do occur, then the default of merge.directoryRenames being set to "conflict" means that the operation @@ -497,11 +521,13 @@ As noted in the assumptions section: is that some users will have set merge.directoryRenames to "true" to allow the merges to continue to proceed automatically. """ +.... Let's remember that we need to look at how any given pick affects the next one. So let's again use the first two picks from the diagram in section one: +.... First pick does this three-way merge: MERGE_BASE: E MERGE_SIDE1: G @@ -513,6 +539,7 @@ one: MERGE_SIDE1: A' MERGE_SIDE2: B => creates B' +.... Now, directory rename detection exists so that if one side of history renames a directory, and the other side adds a new file to the old @@ -545,7 +572,7 @@ while considering all of these cases: concerned; see the assumptions section). Two interesting sub-notes about these counts: - * If we need to perform rename-detection again on the given side (e.g. + ** If we need to perform rename-detection again on the given side (e.g. some paths are relevant for rename detection that weren't before), then we clear dir_rename_counts and recompute it, making use of cached_pairs. The reason it is important to do this is optimizations @@ -556,7 +583,7 @@ while considering all of these cases: easiest way to "fix up" dir_rename_counts in such cases is to just recompute it. - * If we prune rename/rename(1to1) entries from the cache, then we also + ** If we prune rename/rename(1to1) entries from the cache, then we also need to update dir_rename_counts to decrement the counts for the involved directory and any relevant parent directories (to undo what update_dir_rename_counts() in diffcore-rename.c incremented when the @@ -578,6 +605,7 @@ in order: Case 1: MERGE_SIDE1 renames old dir, MERGE_SIDE2 adds new file to old dir +.... This case looks like this: MERGE_BASE: E, Has olddir/ @@ -595,10 +623,13 @@ Case 1: MERGE_SIDE1 renames old dir, MERGE_SIDE2 adds new file to old dir * MERGE_SIDE1 has cached olddir/newfile -> newdir/newfile Given the cached rename noted above, the second merge can proceed as expected without needing to perform rename detection from A -> A'. +.... Case 2: MERGE_SIDE1 renames old dir, MERGE_SIDE2 renames file into old dir +.... This case looks like this: + MERGE_BASE: E oldfile, olddir/ MERGE_SIDE1: G oldfile, olddir/ -> newdir/ MERGE_SIDE2: A oldfile -> olddir/newfile @@ -617,9 +648,11 @@ Case 2: MERGE_SIDE1 renames old dir, MERGE_SIDE2 renames file into old dir Given the cached rename noted above, the second merge can proceed as expected without needing to perform rename detection from A -> A'. +.... Case 3: MERGE_SIDE1 adds new file to old dir, MERGE_SIDE2 renames old dir +.... This case looks like this: MERGE_BASE: E, Has olddir/ @@ -635,9 +668,11 @@ Case 3: MERGE_SIDE1 adds new file to old dir, MERGE_SIDE2 renames old dir In this case, with the optimization, note that after the first commit there were no renames on MERGE_SIDE1, and any renames on MERGE_SIDE2 are tossed. But the second merge didn't need any renames so this is fine. +.... Case 4: MERGE_SIDE1 renames file into old dir, MERGE_SIDE2 renames old dir +.... This case looks like this: MERGE_BASE: E, Has olddir/ @@ -658,6 +693,7 @@ Case 4: MERGE_SIDE1 renames file into old dir, MERGE_SIDE2 renames old dir Given the cached rename noted above, the second merge can proceed as expected without needing to perform rename detection from A -> A'. +.... Finally, I'll just note here that interactions with the skip-irrelevant-renames optimization means we sometimes don't detect diff --git a/Documentation/technical/repository-version.txt b/Documentation/technical/repository-version.adoc index b9bb81a81f..b9bb81a81f 100644 --- a/Documentation/technical/repository-version.txt +++ b/Documentation/technical/repository-version.adoc diff --git a/Documentation/technical/rerere.txt b/Documentation/technical/rerere.adoc index 580f23360a..580f23360a 100644 --- a/Documentation/technical/rerere.txt +++ b/Documentation/technical/rerere.adoc diff --git a/Documentation/technical/scalar.txt b/Documentation/technical/scalar.adoc index 921cb104c3..921cb104c3 100644 --- a/Documentation/technical/scalar.txt +++ b/Documentation/technical/scalar.adoc diff --git a/Documentation/technical/send-pack-pipeline.txt b/Documentation/technical/send-pack-pipeline.adoc index 9b5a0bc186..9b5a0bc186 100644 --- a/Documentation/technical/send-pack-pipeline.txt +++ b/Documentation/technical/send-pack-pipeline.adoc diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.adoc index f3738baa0f..f3738baa0f 100644 --- a/Documentation/technical/shallow.txt +++ b/Documentation/technical/shallow.adoc diff --git a/Documentation/technical/sparse-checkout.txt b/Documentation/technical/sparse-checkout.adoc index d968659354..3fa8e53655 100644 --- a/Documentation/technical/sparse-checkout.txt +++ b/Documentation/technical/sparse-checkout.adoc @@ -14,37 +14,41 @@ Table of contents: * Reference Emails -=== Terminology === +== Terminology == -cone mode: one of two modes for specifying the desired subset of files +*`cone mode`*:: + one of two modes for specifying the desired subset of files in a sparse-checkout. In cone-mode, the user specifies directories (getting both everything under that directory as well as everything in leading directories), while in non-cone mode, the user specifies gitignore-style patterns. Controlled by the --[no-]cone option to sparse-checkout init|set. -SKIP_WORKTREE: When tracked files do not match the sparse specification and +*`SKIP_WORKTREE`*:: + When tracked files do not match the sparse specification and are removed from the working tree, the file in the index is marked with a SKIP_WORKTREE bit. Note that if a tracked file has the SKIP_WORKTREE bit set but the file is later written by the user to the working tree anyway, the SKIP_WORKTREE bit will be cleared at the beginning of any subsequent Git operation. - - Most sparse checkout users are unaware of this implementation - detail, and the term should generally be avoided in user-facing - descriptions and command flags. Unfortunately, prior to the - `sparse-checkout` subcommand this low-level detail was exposed, - and as of time of writing, is still exposed in various places. - -sparse-checkout: a subcommand in git used to reduce the files present in ++ +Most sparse checkout users are unaware of this implementation +detail, and the term should generally be avoided in user-facing +descriptions and command flags. Unfortunately, prior to the +`sparse-checkout` subcommand this low-level detail was exposed, +and as of time of writing, is still exposed in various places. + +*`sparse-checkout`*:: + a subcommand in git used to reduce the files present in the working tree to a subset of all tracked files. Also, the name of the file in the $GIT_DIR/info directory used to track the sparsity patterns corresponding to the user's desired subset. -sparse cone: see cone mode +*`sparse cone`*:: see cone mode -sparse directory: An entry in the index corresponding to a directory, which +*`sparse directory`*:: + An entry in the index corresponding to a directory, which appears in the index instead of all the files under that directory that would normally appear. See also sparse-index. Something that can cause confusion is that the "sparse directory" does NOT match @@ -52,7 +56,8 @@ sparse directory: An entry in the index corresponding to a directory, which working tree. May be renamed in the future (e.g. to "skipped directory"). -sparse index: A special mode for sparse-checkout that also makes the +*`sparse index`*:: + A special mode for sparse-checkout that also makes the index sparse by recording a directory entry in lieu of all the files underneath that directory (thus making that a "skipped directory" which unfortunately has also been called a "sparse @@ -60,17 +65,19 @@ sparse index: A special mode for sparse-checkout that also makes the directories. Controlled by the --[no-]sparse-index option to init|set|reapply. -sparsity patterns: patterns from $GIT_DIR/info/sparse-checkout used to +*`sparsity patterns`*:: + patterns from $GIT_DIR/info/sparse-checkout used to define the set of files of interest. A warning: It is easy to over-use this term (or the shortened "patterns" term), for two reasons: (1) users in cone mode specify directories rather than patterns (their directories are transformed into patterns, but users may think you are talking about non-cone mode if you use the - word "patterns"), and (b) the sparse specification might + word "patterns"), and (2) the sparse specification might transiently differ in the working tree or index from the sparsity patterns (see "Sparse specification vs. sparsity patterns"). -sparse specification: The set of paths in the user's area of focus. This +*`sparse specification`*:: + The set of paths in the user's area of focus. This is typically just the tracked files that match the sparsity patterns, but the sparse specification can temporarily differ and include additional files. (See also "Sparse specification @@ -87,12 +94,13 @@ sparse specification: The set of paths in the user's area of focus. This * If working with the index and the working copy, the sparse specification is the union of the paths from above. -vivifying: When a command restores a tracked file to the working tree (and +*`vivifying`*:: + When a command restores a tracked file to the working tree (and hopefully also clears the SKIP_WORKTREE bit in the index for that file), this is referred to as "vivifying" the file. -=== Purpose of sparse-checkouts === +== Purpose of sparse-checkouts == sparse-checkouts exist to allow users to work with a subset of their files. @@ -120,14 +128,12 @@ those usecases, sparse-checkouts can modify different subcommands in over a half dozen different ways. Let's start by considering the high level usecases: - A) Users are _only_ interested in the sparse portion of the repo - - A*) Users are _only_ interested in the sparse portion of the repo - that they have downloaded so far - - B) Users want a sparse working tree, but are working in a larger whole - - C) sparse-checkout is a behind-the-scenes implementation detail allowing +[horizontal] +A):: Users are _only_ interested in the sparse portion of the repo +A*):: Users are _only_ interested in the sparse portion of the repo + that they have downloaded so far +B):: Users want a sparse working tree, but are working in a larger whole +C):: sparse-checkout is a behind-the-scenes implementation detail allowing Git to work with a specially crafted in-house virtual file system; users are actually working with a "full" working tree that is lazily populated, and sparse-checkout helps with the lazy population @@ -136,7 +142,7 @@ usecases: It may be worth explaining each of these in a bit more detail: - (Behavior A) Users are _only_ interested in the sparse portion of the repo +=== (Behavior A) Users are _only_ interested in the sparse portion of the repo These folks might know there are other things in the repository, but don't care. They are uninterested in other parts of the repository, and @@ -163,8 +169,7 @@ side-effects of various other commands (such as the printed diffstat after a merge or pull) can lead to worries about local repository size growing unnecessarily[10]. - (Behavior A*) Users are _only_ interested in the sparse portion of the repo - that they have downloaded so far (a variant on the first usecase) +=== (Behavior A*) Users are _only_ interested in the sparse portion of the repo that they have downloaded so far (a variant on the first usecase) This variant is driven by folks who using partial clones together with sparse checkouts and do disconnected development (so far sounding like a @@ -173,15 +178,14 @@ reason for yet another variant is that downloading even just the blobs through history within their sparse specification may be too much, so they only download some. They would still like operations to succeed without network connectivity, though, so things like `git log -S${SEARCH_TERM} -p` -or `git grep ${SEARCH_TERM} OLDREV ` would need to be prepared to provide +or `git grep ${SEARCH_TERM} OLDREV` would need to be prepared to provide partial results that depend on what happens to have been downloaded. This variant could be viewed as Behavior A with the sparse specification for history querying operations modified from "sparsity patterns" to "sparsity patterns limited to the blobs we have already downloaded". - (Behavior B) Users want a sparse working tree, but are working in a - larger whole +=== (Behavior B) Users want a sparse working tree, but are working in a larger whole Stolee described this usecase this way[11]: @@ -229,8 +233,7 @@ those expensive checks when interacting with the working copy, and may prefer getting "unrelated" results from their history queries over having slow commands. - (Behavior C) sparse-checkout is an implementational detail supporting a - special VFS. +=== (Behavior C) sparse-checkout is an implementational detail supporting a special VFS. This usecase goes slightly against the traditional definition of sparse-checkout in that it actually tries to present a full or dense @@ -255,13 +258,13 @@ will perceive the checkout as dense, and commands should thus behave as if all files are present. -=== Usecases of primary concern === +== Usecases of primary concern == Most of the rest of this document will focus on Behavior A and Behavior B. Some notes about the other two cases and why we are not focusing on them: - (Behavior A*) +=== (Behavior A*) Supporting this usecase is estimated to be difficult and a lot of work. There are no plans to implement it currently, but it may be a potential @@ -275,7 +278,7 @@ valid for this usecase, with the only exception being that it redefines the sparse specification to restrict it to already-downloaded blobs. The hard part is in making commands capable of respecting that modified definition. - (Behavior C) +=== (Behavior C) This usecase violates some of the early sparse-checkout documented assumptions (since files marked as SKIP_WORKTREE will be displayed to users @@ -300,20 +303,20 @@ Behavior C do not assume they are part of the Behavior B camp and propose patches that break things for the real Behavior B folks. -=== Oversimplified mental models === +== Oversimplified mental models == An oversimplification of the differences in the above behaviors is: - Behavior A: Restrict worktree and history operations to sparse specification - Behavior B: Restrict worktree operations to sparse specification; have any - history operations work across all files - Behavior C: Do not restrict either worktree or history operations to the - sparse specification...with the exception of branch checkouts or - switches which avoid writing files that will match the index so - they can later lazily be populated instead. +(Behavior A):: Restrict worktree and history operations to sparse specification +(Behavior B):: Restrict worktree operations to sparse specification; have any + history operations work across all files +(Behavior C):: Do not restrict either worktree or history operations to the + sparse specification...with the exception of branch checkouts or + switches which avoid writing files that will match the index so + they can later lazily be populated instead. -=== Desired behavior === +== Desired behavior == As noted previously, despite the simple idea of just working with a subset of files, there are a range of different behavioral changes that need to be @@ -326,39 +329,38 @@ understanding these differences can be beneficial. * Commands behaving the same regardless of high-level use-case - * commands that only look at files within the sparsity specification + ** commands that only look at files within the sparsity specification - * diff (without --cached or REVISION arguments) - * grep (without --cached or REVISION arguments) - * diff-files + *** diff (without --cached or REVISION arguments) + *** grep (without --cached or REVISION arguments) + *** diff-files - * commands that restore files to the working tree that match sparsity + ** commands that restore files to the working tree that match sparsity patterns, and remove unmodified files that don't match those patterns: - * switch - * checkout (the switch-like half) - * read-tree - * reset --hard + *** switch + *** checkout (the switch-like half) + *** read-tree + *** reset --hard - * commands that write conflicted files to the working tree, but otherwise + ** commands that write conflicted files to the working tree, but otherwise will omit writing files to the working tree that do not match the sparsity patterns: - * merge - * rebase - * cherry-pick - * revert + *** merge + *** rebase + *** cherry-pick + *** revert - * `am` and `apply --cached` should probably be in this section but + *** `am` and `apply --cached` should probably be in this section but are buggy (see the "Known bugs" section below) The behavior for these commands somewhat depends upon the merge strategy being used: - * `ort` behaves as described above - * `recursive` tries to not vivify files unnecessarily, but does sometimes - vivify files without conflicts. - * `octopus` and `resolve` will always vivify any file changed in the merge + + *** `ort` behaves as described above + *** `octopus` and `resolve` will always vivify any file changed in the merge relative to the first parent, which is rather suboptimal. It is also important to note that these commands WILL update the index @@ -374,21 +376,21 @@ understanding these differences can be beneficial. specification and the sparsity patterns (much like the commands in the previous section). - * commands that always ignore sparsity since commits must be full-tree + ** commands that always ignore sparsity since commits must be full-tree - * archive - * bundle - * commit - * format-patch - * fast-export - * fast-import - * commit-tree + *** archive + *** bundle + *** commit + *** format-patch + *** fast-export + *** fast-import + *** commit-tree - * commands that write any modified file to the working tree (conflicted + ** commands that write any modified file to the working tree (conflicted or not, and whether those paths match sparsity patterns or not): - * stash - * apply (without `--index` or `--cached`) + *** stash + *** apply (without `--index` or `--cached`) * Commands that may slightly differ for behavior A vs. behavior B: @@ -396,19 +398,20 @@ understanding these differences can be beneficial. behaviors, but may differ in verbosity and types of warning and error messages. - * commands that make modifications to which files are tracked: - * add - * rm - * mv - * update-index + ** commands that make modifications to which files are tracked: + + *** add + *** rm + *** mv + *** update-index The fact that files can move between the 'tracked' and 'untracked' categories means some commands will have to treat untracked files differently. But if we have to treat untracked files differently, then additional commands may also need changes: - * status - * clean + *** status + *** clean In particular, `status` may need to report any untracked files outside the sparsity specification as an erroneous condition (especially to @@ -422,9 +425,10 @@ understanding these differences can be beneficial. may need to ignore the sparse specification by its nature. Also, its current --[no-]ignore-skip-worktree-entries default is totally bogus. - * commands for manually tweaking paths in both the index and the working tree - * `restore` - * the restore-like half of `checkout` + ** commands for manually tweaking paths in both the index and the working tree + + *** `restore` + *** the restore-like half of `checkout` These commands should be similar to add/rm/mv in that they should only operate on the sparse specification by default, and require a @@ -435,18 +439,19 @@ understanding these differences can be beneficial. * Commands that significantly differ for behavior A vs. behavior B: - * commands that query history - * diff (with --cached or REVISION arguments) - * grep (with --cached or REVISION arguments) - * show (when given commit arguments) - * blame (only matters when one or more -C flags are passed) - * and annotate - * log - * whatchanged - * ls-files - * diff-index - * diff-tree - * ls-tree + ** commands that query history + + *** diff (with --cached or REVISION arguments) + *** grep (with --cached or REVISION arguments) + *** show (when given commit arguments) + *** blame (only matters when one or more -C flags are passed) + **** and annotate + *** log + *** whatchanged (may not exist anymore) + *** ls-files + *** diff-index + *** diff-tree + *** ls-tree Note: for log and whatchanged, revision walking logic is unaffected but displaying of patches is affected by scoping the command to the @@ -460,91 +465,91 @@ understanding these differences can be beneficial. * Commands I don't know how to classify - * range-diff + ** range-diff Is this like `log` or `format-patch`? - * cherry + ** cherry See range-diff * Commands unaffected by sparse-checkouts - * shortlog - * show-branch - * rev-list - * bisect - - * branch - * describe - * fetch - * gc - * init - * maintenance - * notes - * pull (merge & rebase have the necessary changes) - * push - * submodule - * tag - - * config - * filter-branch (works in separate checkout without sparse-checkout setup) - * pack-refs - * prune - * remote - * repack - * replace - - * bugreport - * count-objects - * fsck - * gitweb - * help - * instaweb - * merge-tree (doesn't touch worktree or index, and merges always compute full-tree) - * rerere - * verify-commit - * verify-tag - - * commit-graph - * hash-object - * index-pack - * mktag - * mktree - * multi-pack-index - * pack-objects - * prune-packed - * symbolic-ref - * unpack-objects - * update-ref - * write-tree (operates on index, possibly optimized to use sparse dir entries) - - * for-each-ref - * get-tar-commit-id - * ls-remote - * merge-base (merges are computed full tree, so merge base should be too) - * name-rev - * pack-redundant - * rev-parse - * show-index - * show-ref - * unpack-file - * var - * verify-pack - - * <Everything under 'Interacting with Others' in 'git help --all'> - * <Everything under 'Low-level...Syncing' in 'git help --all'> - * <Everything under 'Low-level...Internal Helpers' in 'git help --all'> - * <Everything under 'External commands' in 'git help --all'> + ** shortlog + ** show-branch + ** rev-list + ** bisect + + ** branch + ** describe + ** fetch + ** gc + ** init + ** maintenance + ** notes + ** pull (merge & rebase have the necessary changes) + ** push + ** submodule + ** tag + + ** config + ** filter-branch (works in separate checkout without sparse-checkout setup) + ** pack-refs + ** prune + ** remote + ** repack + ** replace + + ** bugreport + ** count-objects + ** fsck + ** gitweb + ** help + ** instaweb + ** merge-tree (doesn't touch worktree or index, and merges always compute full-tree) + ** rerere + ** verify-commit + ** verify-tag + + ** commit-graph + ** hash-object + ** index-pack + ** mktag + ** mktree + ** multi-pack-index + ** pack-objects + ** prune-packed + ** symbolic-ref + ** unpack-objects + ** update-ref + ** write-tree (operates on index, possibly optimized to use sparse dir entries) + + ** for-each-ref + ** get-tar-commit-id + ** ls-remote + ** merge-base (merges are computed full tree, so merge base should be too) + ** name-rev + ** pack-redundant + ** rev-parse + ** show-index + ** show-ref + ** unpack-file + ** var + ** verify-pack + + ** <Everything under 'Interacting with Others' in 'git help --all'> + ** <Everything under 'Low-level...Syncing' in 'git help --all'> + ** <Everything under 'Low-level...Internal Helpers' in 'git help --all'> + ** <Everything under 'External commands' in 'git help --all'> * Commands that might be affected, but who cares? - * merge-file - * merge-index - * gitk? + ** merge-file + ** merge-index + ** gitk? -=== Behavior classes === +== Behavior classes == From the above there are a few classes of behavior: @@ -575,18 +580,19 @@ From the above there are a few classes of behavior: Commands in this class generally behave like the "restrict" class, except that: - (1) they will ignore the sparse specification and write files with - conflicts to the working tree (thus temporarily expanding the - sparse specification to include such files.) - (2) they are grouped with commands which move to a new commit, since - they often create a commit and then move to it, even though we - know there are many exceptions to moving to the new commit. (For - example, the user may rebase a commit that becomes empty, or have - a cherry-pick which conflicts, or a user could run `merge - --no-commit`, and we also view `apply --index` kind of like `am - --no-commit`.) As such, these commands can make changes to index - files outside the sparse specification, though they'll mark such - files with SKIP_WORKTREE. + + (1) they will ignore the sparse specification and write files with + conflicts to the working tree (thus temporarily expanding the + sparse specification to include such files.) + (2) they are grouped with commands which move to a new commit, since + they often create a commit and then move to it, even though we + know there are many exceptions to moving to the new commit. (For + example, the user may rebase a commit that becomes empty, or have + a cherry-pick which conflicts, or a user could run `merge + --no-commit`, and we also view `apply --index` kind of like `am + --no-commit`.) As such, these commands can make changes to index + files outside the sparse specification, though they'll mark such + files with SKIP_WORKTREE. * "restrict also specially applied to untracked files" @@ -611,37 +617,39 @@ From the above there are a few classes of behavior: specification. -=== Subcommand-dependent defaults === +== Subcommand-dependent defaults == Note that we have different defaults depending on the command for the desired behavior : * Commands defaulting to "restrict": - * diff-files - * diff (without --cached or REVISION arguments) - * grep (without --cached or REVISION arguments) - * switch - * checkout (the switch-like half) - * reset (<commit>) - - * restore - * checkout (the restore-like half) - * checkout-index - * reset (with pathspec) + + ** diff-files + ** diff (without --cached or REVISION arguments) + ** grep (without --cached or REVISION arguments) + ** switch + ** checkout (the switch-like half) + ** reset (<commit>) + + ** restore + ** checkout (the restore-like half) + ** checkout-index + ** reset (with pathspec) This behavior makes sense; these interact with the working tree. * Commands defaulting to "restrict modulo conflicts": - * merge - * rebase - * cherry-pick - * revert - * am - * apply --index (which is kind of like an `am --no-commit`) + ** merge + ** rebase + ** cherry-pick + ** revert + + ** am + ** apply --index (which is kind of like an `am --no-commit`) - * read-tree (especially with -m or -u; is kind of like a --no-commit merge) - * reset (<tree-ish>, due to similarity to read-tree) + ** read-tree (especially with -m or -u; is kind of like a --no-commit merge) + ** reset (<tree-ish>, due to similarity to read-tree) These also interact with the working tree, but require slightly different behavior either so that (a) conflicts can be resolved or (b) @@ -650,16 +658,17 @@ desired behavior : (See also the "Known bugs" section below regarding `am` and `apply`) * Commands defaulting to "no restrict": - * archive - * bundle - * commit - * format-patch - * fast-export - * fast-import - * commit-tree - * stash - * apply (without `--index`) + ** archive + ** bundle + ** commit + ** format-patch + ** fast-export + ** fast-import + ** commit-tree + + ** stash + ** apply (without `--index`) These have completely different defaults and perhaps deserve the most detailed explanation: @@ -681,53 +690,59 @@ desired behavior : sparse specification then we'll lose changes from the user. * Commands defaulting to "restrict also specially applied to untracked files": - * add - * rm - * mv - * update-index - * status - * clean (?) - - Our original implementation for the first three of these commands was - "no restrict", but it had some severe usability issues: - * `git add <somefile>` if honored and outside the sparse - specification, can result in the file randomly disappearing later - when some subsequent command is run (since various commands - automatically clean up unmodified files outside the sparse - specification). - * `git rm '*.jpg'` could very negatively surprise users if it deletes - files outside the range of the user's interest. - * `git mv` has similar surprises when moving into or out of the cone, - so best to restrict by default - - So, we switched `add` and `rm` to default to "restrict", which made - usability problems much less severe and less frequent, but we still got - complaints because commands like: - git add <file-outside-sparse-specification> - git rm <file-outside-sparse-specification> - would silently do nothing. We should instead print an error in those - cases to get usability right. - - update-index needs to be updated to match, and status and maybe clean - also need to be updated to specially handle untracked paths. - - There may be a difference in here between behavior A and behavior B in - terms of verboseness of errors or additional warnings. + + ** add + ** rm + ** mv + ** update-index + ** status + ** clean (?) + +.... + Our original implementation for the first three of these commands was + "no restrict", but it had some severe usability issues: + + * `git add <somefile>` if honored and outside the sparse + specification, can result in the file randomly disappearing later + when some subsequent command is run (since various commands + automatically clean up unmodified files outside the sparse + specification). + * `git rm '*.jpg'` could very negatively surprise users if it deletes + files outside the range of the user's interest. + * `git mv` has similar surprises when moving into or out of the cone, + so best to restrict by default + + So, we switched `add` and `rm` to default to "restrict", which made + usability problems much less severe and less frequent, but we still got + complaints because commands like: + + git add <file-outside-sparse-specification> + git rm <file-outside-sparse-specification> + + would silently do nothing. We should instead print an error in those + cases to get usability right. + + update-index needs to be updated to match, and status and maybe clean + also need to be updated to specially handle untracked paths. + + There may be a difference in here between behavior A and behavior B in + terms of verboseness of errors or additional warnings. +.... * Commands falling under "restrict or no restrict dependent upon behavior A vs. behavior B" - * diff (with --cached or REVISION arguments) - * grep (with --cached or REVISION arguments) - * show (when given commit arguments) - * blame (only matters when one or more -C flags passed) - * and annotate - * log - * and variants: shortlog, gitk, show-branch, whatchanged, rev-list - * ls-files - * diff-index - * diff-tree - * ls-tree + ** diff (with --cached or REVISION arguments) + ** grep (with --cached or REVISION arguments) + ** show (when given commit arguments) + ** blame (only matters when one or more -C flags passed) + *** and annotate + ** log + *** and variants: shortlog, gitk, show-branch, whatchanged, rev-list + ** ls-files + ** diff-index + ** diff-tree + ** ls-tree For now, we default to behavior B for these, which want a default of "no restrict". @@ -751,7 +766,7 @@ desired behavior : implemented. -=== Sparse specification vs. sparsity patterns === +== Sparse specification vs. sparsity patterns == In a well-behaved situation, the sparse specification is given directly by the $GIT_DIR/info/sparse-checkout file. However, it can transiently @@ -823,45 +838,48 @@ under behavior B index operations are lumped with history and tend to operate full-tree. -=== Implementation Questions === - - * Do the options --scope={sparse,all} sound good to others? Are there better - options? - * Names in use, or appearing in patches, or previously suggested: - * --sparse/--dense - * --ignore-skip-worktree-bits - * --ignore-skip-worktree-entries - * --ignore-sparsity - * --[no-]restrict-to-sparse-paths - * --full-tree/--sparse-tree - * --[no-]restrict - * --scope={sparse,all} - * --focus/--unfocus - * --limit/--unlimited - * Rationale making me lean slightly towards --scope={sparse,all}: - * We want a name that works for many commands, so we need a name that +== Implementation Questions == + + * Do the options --scope={sparse,all} sound good to others? Are there better options? + + ** Names in use, or appearing in patches, or previously suggested: + + *** --sparse/--dense + *** --ignore-skip-worktree-bits + *** --ignore-skip-worktree-entries + *** --ignore-sparsity + *** --[no-]restrict-to-sparse-paths + *** --full-tree/--sparse-tree + *** --[no-]restrict + *** --scope={sparse,all} + *** --focus/--unfocus + *** --limit/--unlimited + + ** Rationale making me lean slightly towards --scope={sparse,all}: + + *** We want a name that works for many commands, so we need a name that does not conflict - * We know that we have more than two possible usecases, so it is best + *** We know that we have more than two possible usecases, so it is best to avoid a flag that appears to be binary. - * --scope={sparse,all} isn't overly long and seems relatively + *** --scope={sparse,all} isn't overly long and seems relatively explanatory - * `--sparse`, as used in add/rm/mv, is totally backwards for + *** `--sparse`, as used in add/rm/mv, is totally backwards for grep/log/etc. Changing the meaning of `--sparse` for these commands would fix the backwardness, but possibly break existing scripts. Using a new name pairing would allow us to treat `--sparse` in these commands as a deprecated alias. - * There is a different `--sparse`/`--dense` pair for commands using + *** There is a different `--sparse`/`--dense` pair for commands using revision machinery, so using that naming might cause confusion - * There is also a `--sparse` in both pack-objects and show-branch, which + *** There is also a `--sparse` in both pack-objects and show-branch, which don't conflict but do suggest that `--sparse` is overloaded - * The name --ignore-skip-worktree-bits is a double negative, is + *** The name --ignore-skip-worktree-bits is a double negative, is quite a mouthful, refers to an implementation detail that many users may not be familiar with, and we'd need a negation for it which would probably be even more ridiculously long. (But we can make --ignore-skip-worktree-bits a deprecated alias for --no-restrict.) - * If a config option is added (sparse.scope?) what should the values and + ** If a config option is added (sparse.scope?) what should the values and description be? "sparse" (behavior A), "worktree-sparse-history-dense" (behavior B), "dense" (behavior C)? There's a risk of confusion, because even for Behaviors A and B we want some commands to be @@ -870,19 +888,20 @@ operate full-tree. the primary difference we are focusing is just the history-querying commands (log/diff/grep). Previous config suggestion here: [13] - * Is `--no-expand` a good alias for ls-files's `--sparse` option? + ** Is `--no-expand` a good alias for ls-files's `--sparse` option? (`--sparse` does not map to either `--scope=sparse` or `--scope=all`, because in non-cone mode it does nothing and in cone-mode it shows the sparse directory entries which are technically outside the sparse specification) - * Under Behavior A: - * Does ls-files' `--no-expand` override the default `--scope=all`, or + ** Under Behavior A: + + *** Does ls-files' `--no-expand` override the default `--scope=all`, or does it need an extra flag? - * Does ls-files' `-t` option imply `--scope=all`? - * Does update-index's `--[no-]skip-worktree` option imply `--scope=all`? + *** Does ls-files' `-t` option imply `--scope=all`? + *** Does update-index's `--[no-]skip-worktree` option imply `--scope=all`? - * sparse-checkout: once behavior A is fully implemented, should we take + ** sparse-checkout: once behavior A is fully implemented, should we take an interim measure to ease people into switching the default? Namely, if folks are not already in a sparse checkout, then require `sparse-checkout init/set` to take a @@ -894,7 +913,7 @@ operate full-tree. is seamless for them. -=== Implementation Goals/Plans === +== Implementation Goals/Plans == * Get buy-in on this document in general. @@ -912,25 +931,26 @@ operate full-tree. request that they not trigger this bug." flag * Flags & Config - * Make `--sparse` in add/rm/mv a deprecated alias for `--scope=all` - * Make `--ignore-skip-worktree-bits` in checkout-index/checkout/restore + + ** Make `--sparse` in add/rm/mv a deprecated alias for `--scope=all` + ** Make `--ignore-skip-worktree-bits` in checkout-index/checkout/restore a deprecated aliases for `--scope=all` - * Create config option (sparse.scope?), tie it to the "Cliff notes" + ** Create config option (sparse.scope?), tie it to the "Cliff notes" overview - * Add --scope=sparse (and --scope=all) flag to each of the history querying + ** Add --scope=sparse (and --scope=all) flag to each of the history querying commands. IMPORTANT: make sure diff machinery changes don't mess with format-patch, fast-export, etc. -=== Known bugs === +== Known bugs == This list used to be a lot longer (see e.g. [1,2,3,4,5,6,7,8,9]), but we've been working on it. -0. Behavior A is not well supported in Git. (Behavior B didn't used to +1. Behavior A is not well supported in Git. (Behavior B didn't used to be either, but was the easier of the two to implement.) -1. am and apply: +2. am and apply: apply, without `--index` or `--cached`, relies on files being present in the working copy, and also writes to them unconditionally. As @@ -950,7 +970,7 @@ been working on it. files and then complain that those vivified files would be overwritten by merge. -2. reset --hard: +3. reset --hard: reset --hard provides confusing error message (works correctly, but misleads the user into believing it didn't): @@ -973,13 +993,13 @@ been working on it. `git reset --hard` DID remove addme from the index and the working tree, contrary to the error message, but in line with how reset --hard should behave. -3. read-tree +4. read-tree `read-tree` doesn't apply the 'SKIP_WORKTREE' bit to *any* of the entries it reads into the index, resulting in all your files suddenly appearing to be "deleted". -4. Checkout, restore: +5. Checkout, restore: These command do not handle path & revision arguments appropriately: @@ -1032,7 +1052,7 @@ been working on it. S tracked H tracked-but-maybe-skipped -5. checkout and restore --staged, continued: +6. checkout and restore --staged, continued: These commands do not correctly scope operations to the sparse specification, and make it worse by not setting important SKIP_WORKTREE @@ -1048,56 +1068,82 @@ been working on it. the sparse specification, but then it will be important to set the SKIP_WORKTREE bits appropriately. -6. Performance issues; see: - https://lore.kernel.org/git/CABPp-BEkJQoKZsQGCYioyga_uoDQ6iBeW+FKr8JhyuuTMK1RDw@mail.gmail.com/ +7. Performance issues; see: + + https://lore.kernel.org/git/CABPp-BEkJQoKZsQGCYioyga_uoDQ6iBeW+FKr8JhyuuTMK1RDw@mail.gmail.com/ -=== Reference Emails === +== Reference Emails == Emails that detail various bugs we've had in sparse-checkout: -[1] (Original descriptions of behavior A & behavior B) - https://lore.kernel.org/git/CABPp-BGJ_Nvi5TmgriD9Bh6eNXE2EDq2f8e8QKXAeYG3BxZafA@mail.gmail.com/ -[2] (Fix stash applications in sparse checkouts; bugs from behavioral differences) - https://lore.kernel.org/git/ccfedc7140dbf63ba26a15f93bd3885180b26517.1606861519.git.gitgitgadget@gmail.com/ -[3] (Present-despite-skipped entries) - https://lore.kernel.org/git/11d46a399d26c913787b704d2b7169cafc28d639.1642175983.git.gitgitgadget@gmail.com/ -[4] (Clone --no-checkout interaction) - https://lore.kernel.org/git/pull.801.v2.git.git.1591324899170.gitgitgadget@gmail.com/ (clone --no-checkout) -[5] (The need for update_sparsity() and avoiding `read-tree -mu HEAD`) - https://lore.kernel.org/git/3a1f084641eb47515b5a41ed4409a36128913309.1585270142.git.gitgitgadget@gmail.com/ -[6] (SKIP_WORKTREE is advisory, not mandatory) - https://lore.kernel.org/git/844306c3e86ef67591cc086decb2b760e7d710a3.1585270142.git.gitgitgadget@gmail.com/ -[7] (`worktree add` should copy sparsity settings from current worktree) - https://lore.kernel.org/git/c51cb3714e7b1d2f8c9370fe87eca9984ff4859f.1644269584.git.gitgitgadget@gmail.com/ -[8] (Avoid negative surprises in add, rm, and mv) - https://lore.kernel.org/git/cover.1617914011.git.matheus.bernardino@usp.br/ - https://lore.kernel.org/git/pull.1018.v4.git.1632497954.gitgitgadget@gmail.com/ -[9] (Move from out-of-cone to in-cone) - https://lore.kernel.org/git/20220630023737.473690-6-shaoxuan.yuan02@gmail.com/ - https://lore.kernel.org/git/20220630023737.473690-4-shaoxuan.yuan02@gmail.com/ -[10] (Unnecessarily downloading objects outside sparse specification) - https://lore.kernel.org/git/CAOLTT8QfwOi9yx_qZZgyGa8iL8kHWutEED7ok_jxwTcYT_hf9Q@mail.gmail.com/ - -[11] (Stolee's comments on high-level usecases) - https://lore.kernel.org/git/1a1e33f6-3514-9afc-0a28-5a6b85bd8014@gmail.com/ +[1] (Original descriptions of behavior A & behavior B): + +https://lore.kernel.org/git/CABPp-BGJ_Nvi5TmgriD9Bh6eNXE2EDq2f8e8QKXAeYG3BxZafA@mail.gmail.com/ + +[2] (Fix stash applications in sparse checkouts; bugs from behavioral differences): + +https://lore.kernel.org/git/ccfedc7140dbf63ba26a15f93bd3885180b26517.1606861519.git.gitgitgadget@gmail.com/ + +[3] (Present-despite-skipped entries): + +https://lore.kernel.org/git/11d46a399d26c913787b704d2b7169cafc28d639.1642175983.git.gitgitgadget@gmail.com/ + +[4] (Clone --no-checkout interaction): + +https://lore.kernel.org/git/pull.801.v2.git.git.1591324899170.gitgitgadget@gmail.com/ (clone --no-checkout) + +[5] (The need for update_sparsity() and avoiding `read-tree -mu HEAD`): + +https://lore.kernel.org/git/3a1f084641eb47515b5a41ed4409a36128913309.1585270142.git.gitgitgadget@gmail.com/ + +[6] (SKIP_WORKTREE is advisory, not mandatory): + +https://lore.kernel.org/git/844306c3e86ef67591cc086decb2b760e7d710a3.1585270142.git.gitgitgadget@gmail.com/ + +[7] (`worktree add` should copy sparsity settings from current worktree): + +https://lore.kernel.org/git/c51cb3714e7b1d2f8c9370fe87eca9984ff4859f.1644269584.git.gitgitgadget@gmail.com/ + +[8] (Avoid negative surprises in add, rm, and mv): + + * https://lore.kernel.org/git/cover.1617914011.git.matheus.bernardino@usp.br/ + * https://lore.kernel.org/git/pull.1018.v4.git.1632497954.gitgitgadget@gmail.com/ + +[9] (Move from out-of-cone to in-cone): + + * https://lore.kernel.org/git/20220630023737.473690-6-shaoxuan.yuan02@gmail.com/ + * https://lore.kernel.org/git/20220630023737.473690-4-shaoxuan.yuan02@gmail.com/ + +[10] (Unnecessarily downloading objects outside sparse specification): + +https://lore.kernel.org/git/CAOLTT8QfwOi9yx_qZZgyGa8iL8kHWutEED7ok_jxwTcYT_hf9Q@mail.gmail.com/ + +[11] (Stolee's comments on high-level usecases): + +https://lore.kernel.org/git/1a1e33f6-3514-9afc-0a28-5a6b85bd8014@gmail.com/ [12] Others commenting on eventually switching default to behavior A: + * https://lore.kernel.org/git/xmqqh719pcoo.fsf@gitster.g/ * https://lore.kernel.org/git/xmqqzgeqw0sy.fsf@gitster.g/ * https://lore.kernel.org/git/a86af661-cf58-a4e5-0214-a67d3a794d7e@github.com/ -[13] Previous config name suggestion and description - * https://lore.kernel.org/git/CABPp-BE6zW0nJSStcVU=_DoDBnPgLqOR8pkTXK3dW11=T01OhA@mail.gmail.com/ +[13] Previous config name suggestion and description: + + https://lore.kernel.org/git/CABPp-BE6zW0nJSStcVU=_DoDBnPgLqOR8pkTXK3dW11=T01OhA@mail.gmail.com/ [14] Tangential issue: switch to cone mode as default sparse specification mechanism: - https://lore.kernel.org/git/a1b68fd6126eb341ef3637bb93fedad4309b36d0.1650594746.git.gitgitgadget@gmail.com/ + +https://lore.kernel.org/git/a1b68fd6126eb341ef3637bb93fedad4309b36d0.1650594746.git.gitgitgadget@gmail.com/ [15] Lengthy email on grep behavior, covering what should be searched: - * https://lore.kernel.org/git/CABPp-BGVO3QdbfE84uF_3QDF0-y2iHHh6G5FAFzNRfeRitkuHw@mail.gmail.com/ + +https://lore.kernel.org/git/CABPp-BGVO3QdbfE84uF_3QDF0-y2iHHh6G5FAFzNRfeRitkuHw@mail.gmail.com/ [16] Email explaining sparsity patterns vs. SKIP_WORKTREE and history operations, search for the parenthetical comment starting "We do not check". - https://lore.kernel.org/git/CABPp-BFsCPPNOZ92JQRJeGyNd0e-TCW-LcLyr0i_+VSQJP+GCg@mail.gmail.com/ + +https://lore.kernel.org/git/CABPp-BFsCPPNOZ92JQRJeGyNd0e-TCW-LcLyr0i_+VSQJP+GCg@mail.gmail.com/ [17] https://lore.kernel.org/git/20220207190320.2960362-1-jonathantanmy@google.com/ diff --git a/Documentation/technical/sparse-index.txt b/Documentation/technical/sparse-index.adoc index 3b24c1a219..3b24c1a219 100644 --- a/Documentation/technical/sparse-index.txt +++ b/Documentation/technical/sparse-index.adoc diff --git a/Documentation/technical/trivial-merge.txt b/Documentation/technical/trivial-merge.adoc index 1f1c33d0da..1f1c33d0da 100644 --- a/Documentation/technical/trivial-merge.txt +++ b/Documentation/technical/trivial-merge.adoc diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.adoc index 5a432b7b29..5a432b7b29 100644 --- a/Documentation/technical/unit-tests.txt +++ b/Documentation/technical/unit-tests.adoc |
