Skip to contentSkip to conversation

Multi-pack-index (MIDX)

PATCH Superseded Derrick Stolee 9 participants 192 messages Jun 7, 2018 – Jul 20, 2018 Page 1 of 2

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover Multi-pack-index (MIDX)

This patch series includes a rewrite of the previous multi-pack-index RFC [1] using the feedback from the commit-graph feature.

I based this series on 'next' as it requires the recent object-store patches.

The multi-pack-index (MIDX) is explained fully in the design document 'Documentation/technical/midx.txt'. The short description is that the MIDX stores the information from all of the IDX files in a pack directory. The crucial design decision is that the IDX files still exist, so we can fall back to the IDX files if there is any issue with the MIDX (or core.midx is set to false, or a user downgrades Git, etc.)

The MIDX feature has been part of our GVFS releases for a few months (since the RFC). It has behaved well, indexing over 31 million commits and trees across up to 250 packfiles. These MIDX files are nearly 1GB in size and take ~20 seconds to rewrite when adding new IDX information. This ~20s mark is something I'd like to improve, and I mention how to make the file incremental (similar to split-index) in the design document. I also want to make the commit-graph file incremental, so I'd like to do that at the same time after both the MIDX and commit-graph are stable.

Lookup Speedups
---------------

When looking for an object, Git uses an most-recently- used (MRU) cache of packfiles. This does pretty well to minimize the number of misses when searching through packfiles for an object, especially if there is one "big" packfile that contains most of the objets (so it will rarely miss and is usually one of the first two packfiles in the list). The MIDX does provide a way to remove these misses, improving lookup time. However, this lookup time greatly depends on the arrangement of the packfiles.

For instance, if you take the Linux repository and repack using git repack -adfF --max-pack-size=128m then all commits will be in one packfile, all trees will be in a small set of packfiles and organized well so 'git rev-list --objects HEAD^{tree}' only inspects one or two packfiles.

GVFS has the notion of a "prefetch packfile". These are packfiles that are precomputed by cache servers to contain the commits and trees introduced to the remote each day. GVFS downloads these packfiles and places them in an alternate. Since these are organized by "first time introduced" and the working directory is so large, the MRU misses are significant when performing a checkout and updating the .git/index file.

To test the performance in this situation, I created a script that organizes the Linux repository in a similar fashion. I split the commit history into 50 parts by creating branches on every 10,000 commits of the first- parent history. Then, git rev-list --objects A ^B provides the list of objects reachable from A but not B, so I could send that to git pack-objects to create these "time-based" packfiles. With these 50 packfiles (deleting the old one from my fresh clone, and deleting all tags as they were no longer on-disk) I could then test 'git rev-list --objects HEAD^{tree}' and see:

        Before: 0.17s
        After:  0.13s
        % Diff: -23.5%

By adding logic to count hits and misses to bsearch_pack, I was able to see that the command above calls that method 266,930 times with a hit rate of 33%. The MIDX has the same number of calls with a 100% hit rate.

Abbreviation Speedups
---------------------

To fully disambiguate an abbreviation, we must iterate through all packfiles to ensure no collision exists in any packfile. This requires O(P log N) time. With the MIDX, this is only O(log N) time. Our standard test [2] is 'git log --oneline --parents --raw' because it writes many abbreviations while also doing a lot of other work (walking commits and trees to compute the raw diff).

For a copy of the Linux repository with 50 packfiles split by time, we observed the following:

        Before: 100.5 s
        After:   58.2 s
        % Diff: -59.7%

Request for Review Attention
----------------------------

I tried my best to take the feedback from the commit-graph feature and apply it to this feature. I also worked to follow the object-store refactoring as I could. I also have some local commits that create a 'verify' subcommand and integrate with 'fsck' similar to the commit-graph, but I'll leave those for a later series (and review is still underway for that part of the commit-graph).

One place where I could use some guidance is related to the current state of 'the_hash_algo' patches. The file format allows a different "hash version" which then indicates the length of the hash. What's the best way to ensure this feature doesn't cause extra pain in the hash-agnostic series? This will inform how I go back and make the commit-graph feature better in this area, too.

Thanks,
-Stolee

[1] https://public-inbox.org/git/[email protected]/T/#u
Previous MIDX RFC.

[2] https://public-inbox.org/git/[email protected]/
A patch series on abbreviation speedups

Derrick Stolee (23):
midx: add design document
midx: add midx format details to pack-format.txt
midx: add midx builtin
midx: add 'write' subcommand and basic wiring
midx: write header information to lockfile
midx: struct midxed_git and 'read' subcommand
midx: expand test data
midx: read packfiles from pack directory
midx: write pack names in chunk
midx: write a lookup into the pack names chunk
midx: sort and deduplicate objects from packfiles
midx: write object ids in a chunk
midx: write object id fanout chunk
midx: write object offsets
midx: create core.midx config setting
midx: prepare midxed_git struct
midx: read objects from multi-pack-index
midx: use midx in abbreviation calculations
midx: use existing midx when writing new one
midx: use midx in approximate_object_count
midx: prevent duplicate packfile loads
midx: use midx to find ref-deltas
midx: clear midx on repack

 .gitignore                              |   1 +
 Documentation/config.txt                |   4 +
 Documentation/git-midx.txt              |  60 ++
 Documentation/technical/midx.txt        | 109 +++
 Documentation/technical/pack-format.txt |  82 +++
 Makefile                                |   2 +
 builtin.h                               |   1 +
 builtin/midx.c                          |  88 +++
 builtin/repack.c                        |   8 +
 cache.h                                 |   1 +
 command-list.txt                        |   1 +
 config.c                                |   5 +
 environment.c                           |   1 +
 git.c                                   |   1 +
 midx.c                                  | 923 ++++++++++++++++++++++++
 midx.h                                  |  23 +
 object-store.h                          |  35 +
 packfile.c                              |  47 +-
 packfile.h                              |   1 +
 sha1-name.c                             |  70 ++
 t/t5319-midx.sh                         | 192 +++++
 21 files changed, 1652 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/git-midx.txt
 create mode 100644 Documentation/technical/midx.txt
 create mode 100644 builtin/midx.c
 create mode 100644 midx.c
 create mode 100644 midx.h
 create mode 100755 t/t5319-midx.sh
Signature · 1 line
2.18.0.rc1
#1 lore raw

PATCH 1/23 midx: add design document

---
 Documentation/technical/midx.txt | 109 +++++++++++++++++++++++++++++++
 1 file changed, 109 insertions(+)
 create mode 100644 Documentation/technical/midx.txt
Documentation/technical/midx.txt new file +109 -0
new file mode 100644
index 0000000000..789f410d71
@@ -0,0 +1,109 @@
+Multi-Pack-Index (MIDX) Design Notes
+====================================
+
+The Git object directory contains a 'pack' directory containing
+packfiles (with suffix ".pack") and pack-indexes (with suffix
+".idx"). The pack-indexes provide a way to lookup objects and
+navigate to their offset within the pack, but these must come
+in pairs with the packfiles. This pairing depends on the file
+names, as the pack-index differs only in suffix with its pack-
+file. While the pack-indexes provide fast lookup per packfile,
+this performance degrades as the number of packfiles increases,
+because abbreviations need to inspect every packfile and we are
+more likely to have a miss on our most-recently-used packfile.
+For some large repositories, repacking into a single packfile
+is not feasible due to storage space or excessive repack times.
+
+The multi-pack-index (MIDX for short) stores a list of objects
+and their offsets into multiple packfiles. It contains:
+
+- A list of packfile names.
+- A sorted list of object IDs.
+- A list of metadata for the ith object ID including:
+  - A value j referring to the jth packfile.
+  - An offset within the jth packfile for the object.
+- If large offsets are required, we use another list of large
+  offsets similar to version 2 pack-indexes.
+
+Thus, we can provide O(log N) lookup time for any number
+of packfiles.
+
+Design Details
+--------------
+
+- The MIDX is stored in a file named 'multi-pack-index' in the
+  .git/objects/pack directory. This could be stored in the pack
+  directory of an alternate. It refers only to packfiles in that
+  same directory.
+
+- The core.midx config setting must be on to consume MIDX files.
+
+- The file format includes parameters for the object ID hash
+  function, so a future change of hash algorithm does not require
+  a change in format.
+
+- The MIDX keeps only one record per object ID. If an object appears
+  in multiple packfiles, then the MIDX selects the copy in the most-
+  recently modified packfile.
+
+- If there exist packfiles in the pack directory not registered in
+  the MIDX, then those packfiles are loaded into the `packed_git`
+  list and `packed_git_mru` cache.
+
+- The pack-indexes (.idx files) remain in the pack directory so we
+  can delete the MIDX file, set core.midx to false, or downgrade
+  without any loss of information.
+
+- The MIDX file format uses a chunk-based approach (similar to the
+  commit-graph file) that allows optional data to be added.
+
+Future Work
+-----------
+
+- Add a 'verify' subcommand to the 'git midx' builtin to verify the
+  contents of the multi-pack-index file match the offsets listed in
+  the corresponding pack-indexes.
+
+- 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.
+
+- The reachability bitmap is currently paired directly with a single
+  packfile, using the pack-order as the object order to hopefully
+  compress the bitmaps well using run-length encoding. This could be
+  extended to pair a reachability bitmap with a multi-pack-index. 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 a reachability bitmap
+  could point to a multi-pack-index and be updated independently.
+
+- Packfiles can be marked as "special" using empty files that share
+  the initial name but replace ".pack" with ".keep" or ".promisor".
+  We can add an optional chunk of data to the multi-pack-index that
+  records flags of information about the packfiles. This allows new
+  states, such as 'repacked' or 'redeltified', that can help with
+  pack maintenance in a multi-pack environment. It may also be
+  helpful to organize packfiles by object type (commit, tree, blob,
+  etc.) and use this metadata to help that maintenance.
+
+- The partial clone feature records special "promisor" packs that
+  may point to objects that are not stored locally, but available
+  on request to a server. The multi-pack-index does not currently
+  track these promisor packs.
+
+Related Links
+-------------
+[0] https://bugs.chromium.org/p/git/issues/detail?id=6
+    Chromium work item for: Multi-Pack Index (MIDX)
+
+[1] https://public-inbox.org/git/[email protected]/
+    An earlier RFC for the multi-pack-index feature
+
+[2] https://public-inbox.org/git/alpine.DEB.2.20.1803091557510.23109@alexmv-linux/
+    Git Merge 2018 Contributor's summit notes (includes discussion of MIDX)
Signature · 1 line
2.18.0.rc1
#2 lore raw

Re: [PATCH 01/23] midx: add design document

Quoted 18 lines · Derrick Stolee
---
 Documentation/technical/midx.txt | 109 +++++++++++++++++++++++++++++++
 1 file changed, 109 insertions(+)
 create mode 100644 Documentation/technical/midx.txt
Documentation/technical/midx.txt new file +5 -0
new file mode 100644
index 0000000000..789f410d71
@@ -0,0 +1,109 @@
+Multi-Pack-Index (MIDX) Design Notes
+====================================
+
+The Git object directory contains a 'pack' directory containing
+packfiles (with suffix ".pack") and pack-indexes (with suffix

Documentation/technical/midx.txt line 15

Quoted 10 lines
Documentation/technical/midx.txt new file +10 -0
+".idx"). The pack-indexes provide a way to lookup objects and
+navigate to their offset within the pack, but these must come
+in pairs with the packfiles. This pairing depends on the file
+names, as the pack-index differs only in suffix with its pack-
+file. While the pack-indexes provide fast lookup per packfile,
+this performance degrades as the number of packfiles increases,
+because abbreviations need to inspect every packfile and we are
+more likely to have a miss on our most-recently-used packfile.
+For some large repositories, repacking into a single packfile
+is not feasible due to storage space or excessive repack times.

This leads to the question how MIDX will cope with large repos or a large number of packs. As it is just an index and not a pack itself, I guess it is smaller by some orders of magnitude, such that it is ok for now.

Documentation/technical/midx.txt line 29

Quoted 13 lines
Documentation/technical/midx.txt new file +13 -0
+The multi-pack-index (MIDX for short) stores a list of objects
+and their offsets into multiple packfiles. It contains:
+
+- A list of packfile names.
+- A sorted list of object IDs.
+- A list of metadata for the ith object ID including:
+  - A value j referring to the jth packfile.
+  - An offset within the jth packfile for the object.
+- If large offsets are required, we use another list of large
+  offsets similar to version 2 pack-indexes.
+
+Thus, we can provide O(log N) lookup time for any number
+of packfiles.

This sounds great for the lookup case!
Though that is for the repo-read case.
Let's read on how the dynamics of a repository are dealt with, e.g. integrating new packs into the MIDX, or how we deal with objects in multiple packs.

Documentation/technical/midx.txt line 37

Quoted 8 lines
Documentation/technical/midx.txt new file +8 -0
+
+Design Details
+--------------
+
+- The MIDX is stored in a file named 'multi-pack-index' in the
+  .git/objects/pack directory. This could be stored in the pack
+  directory of an alternate. It refers only to packfiles in that
+  same directory.

So there is one and only one multi pack index?
That makes the case of preparing the next MIDX that contains more pack references more interesting, as then we have to atomically update that file.

Documentation/technical/midx.txt line 39

Quoted 1 line
Documentation/technical/midx.txt new file +1 -0
+- The core.midx config setting must be on to consume MIDX files.

Looking through current config options, I would rename this to a more suggestive name. I searched for the core.idx counterpart that enables idx files -- it turns out that is named pack.indexVersion.

So maybe pack.MultiIndex ? That could start out as a boolean as in this series and then evolve into a version number or such later.

Documentation/technical/midx.txt line 47

Quoted 7 lines
Documentation/technical/midx.txt new file +7 -0
+- The file format includes parameters for the object ID hash
+  function, so a future change of hash algorithm does not require
+  a change in format.
+
+- The MIDX keeps only one record per object ID. If an object appears
+  in multiple packfiles, then the MIDX selects the copy in the most-
+  recently modified packfile.

Okay. That answers the question from above. Though this is just the tie breaking decision and not a hard limitation? (i.e. we could change this this later to that pack that has e.g. shortest delta chain for that object or such)

Documentation/technical/midx.txt line 51

Quoted 3 lines
Documentation/technical/midx.txt new file +3 -0
+- If there exist packfiles in the pack directory not registered in
+  the MIDX, then those packfiles are loaded into the `packed_git`
+  list and `packed_git_mru` cache.

Not sure I understand the implications of this?
Does that mean we first look at the multi index and if an object is not found, we'll search linearly through all packs that are not part of the MIDX? That would require the MIDX to be kepot up to date reasonably to be useful.

Documentation/technical/midx.txt line 55

Quoted 3 lines
Documentation/technical/midx.txt new file +3 -0
+- The pack-indexes (.idx files) remain in the pack directory so we
+  can delete the MIDX file, set core.midx to false, or downgrade
+  without any loss of information.

In the future will it be possible to have no .idx files and just have the .midx? (I guess that depends on the strategy of how to integrate new packs into the MIDX?)

Documentation/technical/midx.txt line 58

Quoted 2 lines
Documentation/technical/midx.txt new file +2 -0
+- The MIDX file format uses a chunk-based approach (similar to the
+  commit-graph file) that allows optional data to be added.

... or the index files v2 (or reftable files)? Sure, you are most familiar with commit-graph files, but others may find it easier to have some older file formats to relate to.

Documentation/technical/midx.txt

Quoted 11 lines
Documentation/technical/midx.txt new file +11 -0
+Future Work
+-----------
+
+- Add a 'verify' subcommand to the 'git midx' builtin to verify the
+  contents of the multi-pack-index file match the offsets listed in
+  the corresponding pack-indexes.
+
+- 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).

Supposedly maintenance (git gc) can be run in the background without interfering with day-to-day life, how is the regeneration of commit graph or MIDX files impacting the work here?

Documentation/technical/midx.txt line 75

Quoted 6 lines
Documentation/technical/midx.txt new file +5 -0
    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.

So we can have multiple MIDX files? How would that work? Would there be a chunk that refers to other MIDX files?

Documentation/technical/midx.txt line 82

Quoted 6 lines

+- The reachability bitmap is currently paired directly with a single

Documentation/technical/midx.txt new file +5 -0
+  packfile, using the pack-order as the object order to hopefully
+  compress the bitmaps well using run-length encoding. This could be
+  extended to pair a reachability bitmap with a multi-pack-index. 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,

This stable object order doesn't fly well with integrating new packs?

Quoted 16 lines
Documentation/technical/midx.txt new file +16 -0
+  even as the multi-pack-index is updated) then a reachability bitmap
+  could point to a multi-pack-index and be updated independently.
+
+- Packfiles can be marked as "special" using empty files that share
+  the initial name but replace ".pack" with ".keep" or ".promisor".
+  We can add an optional chunk of data to the multi-pack-index that
+  records flags of information about the packfiles. This allows new
+  states, such as 'repacked' or 'redeltified', that can help with
+  pack maintenance in a multi-pack environment. It may also be
+  helpful to organize packfiles by object type (commit, tree, blob,
+  etc.) and use this metadata to help that maintenance.
+
+- The partial clone feature records special "promisor" packs that
+  may point to objects that are not stored locally, but available
+  on request to a server. The multi-pack-index does not currently
+  track these promisor packs.
#3 lore raw

Re: [PATCH 01/23] midx: add design document

Quoted 24 lines · Stefan Beller
Quoted 23 lines · Derrick Stolee
---
  Documentation/technical/midx.txt | 109 +++++++++++++++++++++++++++++++
  1 file changed, 109 insertions(+)

create mode 100644 Documentation/technical/midx.txt

Documentation/technical/midx.txt new file +11 -0
new file mode 100644
index 0000000000..789f410d71
@@ -0,0 +1,109 @@
+Multi-Pack-Index (MIDX) Design Notes
+====================================
+
+The Git object directory contains a 'pack' directory containing
+packfiles (with suffix ".pack") and pack-indexes (with suffix
+".idx"). The pack-indexes provide a way to lookup objects and
+navigate to their offset within the pack, but these must come
+in pairs with the packfiles. This pairing depends on the file
+names, as the pack-index differs only in suffix with its pack-
+file. While the pack-indexes provide fast lookup per packfile,
+this performance degrades as the number of packfiles increases,
Quoted 8 lines

Documentation/technical/midx.txt line 15

Quoted 4 lines
Documentation/technical/midx.txt new file +4 -0
+because abbreviations need to inspect every packfile and we are
+more likely to have a miss on our most-recently-used packfile.
+For some large repositories, repacking into a single packfile
+is not feasible due to storage space or excessive repack times.

This leads to the question how MIDX will cope with large repos or a large number of packs. As it is just an index and not a pack itself, I guess it is smaller by some orders of magnitude, such that it is ok for now.

The MIDX file is only slightly larger than the union of the IDX files for those packfiles.

Quoted 19 lines

Documentation/technical/midx.txt line 29

Quoted 13 lines
Documentation/technical/midx.txt new file +13 -0
+The multi-pack-index (MIDX for short) stores a list of objects
+and their offsets into multiple packfiles. It contains:
+
+- A list of packfile names.
+- A sorted list of object IDs.
+- A list of metadata for the ith object ID including:
+  - A value j referring to the jth packfile.
+  - An offset within the jth packfile for the object.
+- If large offsets are required, we use another list of large
+  offsets similar to version 2 pack-indexes.
+
+Thus, we can provide O(log N) lookup time for any number
+of packfiles.

This sounds great for the lookup case!
Though that is for the repo-read case.
Let's read on how the dynamics of a repository are dealt with, e.g. integrating new packs into the MIDX, or how we deal with objects in multiple packs.

Quoted 12 lines

Documentation/technical/midx.txt line 37

Quoted 8 lines
Documentation/technical/midx.txt new file +8 -0
+
+Design Details
+--------------
+
+- The MIDX is stored in a file named 'multi-pack-index' in the
+  .git/objects/pack directory. This could be stored in the pack
+  directory of an alternate. It refers only to packfiles in that
+  same directory.

So there is one and only one multi pack index?
That makes the case of preparing the next MIDX that contains more pack references more interesting, as then we have to atomically update that file.

There is only one, but we can make the file incremental without changing this name similar to how the split index works.

Quoted 8 lines

Documentation/technical/midx.txt line 39

Quoted 2 lines
Documentation/technical/midx.txt new file +1 -0
+- The core.midx config setting must be on to consume MIDX files.

Looking through current config options, I would rename this to a more suggestive name. I searched for the core.idx counterpart that enables idx files -- it turns out that is named pack.indexVersion.

So maybe pack.MultiIndex ? That could start out as a boolean as in this series and then evolve into a version number or such later.

I'll use that name and rename this file to Documentation/technical/multi-pack-index.txt

Quoted 8 lines

Documentation/technical/midx.txt line 47

Quoted 8 lines
Documentation/technical/midx.txt new file +7 -0
+- The file format includes parameters for the object ID hash
+  function, so a future change of hash algorithm does not require
+  a change in format.
+
+- The MIDX keeps only one record per object ID. If an object appears
+  in multiple packfiles, then the MIDX selects the copy in the most-
+  recently modified packfile.
Quoted 4 lines

Okay. That answers the question from above. Though this is just the tie breaking decision and not a hard limitation? (i.e. we could change this this later to that pack that has e.g. shortest delta chain for that object or such)

This is a soft requirement. It is an easy thing to track at the moment. We can compute the MIDX without opening a packfile, for instance.

Quoted 9 lines

Documentation/technical/midx.txt line 51

Quoted 4 lines
Documentation/technical/midx.txt new file +3 -0
+- If there exist packfiles in the pack directory not registered in
+  the MIDX, then those packfiles are loaded into the `packed_git`
+  list and `packed_git_mru` cache.

Not sure I understand the implications of this?
Does that mean we first look at the multi index and if an object is not found, we'll search linearly through all packs that are not part of the MIDX? That would require the MIDX to be kepot up to date reasonably to be useful.

If you add a packfile to the pack directory, you can immediately start consuming it. You do not need to wait for the MIDX to be updated. The more asynchronous these auxiliary data structures (MIDX, commit-graph) can be, the better. This is in direct contrast to the reachability bitmap which is useless without its corresponding packfile.

Quoted 7 lines

Documentation/technical/midx.txt line 55

Quoted 4 lines
Documentation/technical/midx.txt new file +3 -0
+- The pack-indexes (.idx files) remain in the pack directory so we
+  can delete the MIDX file, set core.midx to false, or downgrade
+  without any loss of information.

In the future will it be possible to have no .idx files and just have the .midx? (I guess that depends on the strategy of how to integrate new packs into the MIDX?)

This may be part of a future plan, but we need to know a user will never set pack.multiIndex to false if they deleted their IDX files.

Quoted 5 lines

Documentation/technical/midx.txt line 58

Quoted 2 lines
Documentation/technical/midx.txt new file +2 -0
+- The MIDX file format uses a chunk-based approach (similar to the
+  commit-graph file) that allows optional data to be added.

... or the index files v2 (or reftable files)? Sure, you are most familiar with commit-graph files, but others may find it easier to have some older file formats to relate to.

I specifically mean that we have a "table of contents" describing the list of chunks. IDX v2 relies on a fixed ordering of the tables, and the offsets are computed by consuming the last fanout value (number of objects). Also, I'm not sure how to add optional data (data that can safely be ignored by an earlier version of Git) to an IDX without incrementing the version.

Quoted 6 lines
Quoted 6 lines
Documentation/technical/midx.txt new file +6 -0
+Future Work
+-----------
+
+- Add a 'verify' subcommand to the 'git midx' builtin to verify the
+  contents of the multi-pack-index file match the offsets listed in
+  the corresponding pack-indexes.
Quoted 8 lines

Documentation/technical/midx.txt

Quoted 5 lines
Documentation/technical/midx.txt new file +5 -0
+
+- 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).

Supposedly maintenance (git gc) can be run in the background without interfering with day-to-day life, how is the regeneration of commit graph or MIDX files impacting the work here?

Assuming infinite RAM and disk, then yes we could not interfere with daily life. A big problem we see is that users frequently don't have the disk space to store a second copy of their packfiles on disk, even if we could organize a new packfile in reasonable time.

The MIDX is only invalid when a packfile it references is deleted.

The commit-graph is never invalid, except if a commit is deleted by GC. But even in that case, how did we "see" the commit ID? As long as we don't access these commits, the commit-graph feature doesn't violate expectations and can be generated asynchronously with a GC and repack.

Quoted 9 lines

Documentation/technical/midx.txt line 75

Quoted 7 lines
Documentation/technical/midx.txt new file +5 -0
     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.

So we can have multiple MIDX files? How would that work? Would there be a chunk that refers to other MIDX files?

We can have an optional chunk that refers to a list of "base" MIDX files. We then load that full list into multiple 'midxed_git' structs, and iterate through the list. VSTS keeps this list to a maximum length of 3 (LARGE, Medium, tiny) and merging files as necessary.

Quoted 8 lines

Documentation/technical/midx.txt line 82

Quoted 7 lines

+- The reachability bitmap is currently paired directly with a single

Documentation/technical/midx.txt new file +5 -0
+  packfile, using the pack-order as the object order to hopefully
+  compress the bitmaps well using run-length encoding. This could be
+  extended to pair a reachability bitmap with a multi-pack-index. 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,

This stable object order doesn't fly well with integrating new packs?

When you integrate new packs, the lexicographic order changes as the new objects are inserted into the list. However, if we track a separate integer value (order[obj]) associated with the object, and keep that constant, we can track a stable order for an object across multiple generations of MIDX files. New objects are assigned order values larger than the previous order values. We can select a "good" ordering of the new objects as we extend the list.

This requires a new chunk in the file format. It also helps to store the reverse-lookup lex[i] which provides the lexicographic position of the object 'obj' with stable-order order[obj] == i.

I'm being intentionally vague in this document to hint towards a valuable feature without giving robust details of something that may never get built. But, I do think this is one feature of the MIDX that would be of the most value for services that use Git as a server process, as it allows the reachability bitmap to be set to this stable order instead of a single pack order. This is speculation on my part, as I don't know how such servers are maintained in the background.

Quoted 17 lines
Quoted 17 lines
Documentation/technical/midx.txt new file +16 -0
+  even as the multi-pack-index is updated) then a reachability bitmap
+  could point to a multi-pack-index and be updated independently.
+
+- Packfiles can be marked as "special" using empty files that share
+  the initial name but replace ".pack" with ".keep" or ".promisor".
+  We can add an optional chunk of data to the multi-pack-index that
+  records flags of information about the packfiles. This allows new
+  states, such as 'repacked' or 'redeltified', that can help with
+  pack maintenance in a multi-pack environment. It may also be
+  helpful to organize packfiles by object type (commit, tree, blob,
+  etc.) and use this metadata to help that maintenance.
+
+- The partial clone feature records special "promisor" packs that
+  may point to objects that are not stored locally, but available
+  on request to a server. The multi-pack-index does not currently
+  track these promisor packs.
#4 lore raw

PATCH 2/23 midx: add midx format details to pack-format.txt

In reply to Derrick Stolee

The multi-pack-index (MIDX) feature generalizes the existing pack- index (IDX) feature by indexing objects across multiple pack-files.

Describe the basic file format, using a 12-byte header followed by a lookup table for a list of "chunks" which will be described later. The file ends with a footer containing a checksum using the hash algorithm.

The header allows later versions to create breaking changes by advancing the version number. We can also change the hash algorithm using a different version value.

We will add the individual chunk format information as we introduce the code that writes that information.

---
 Documentation/technical/pack-format.txt | 49 +++++++++++++++++++++++++
 1 file changed, 49 insertions(+)
Documentation/technical/pack-format.txt +49 -0
index 70a99fd142..17666b4bfc 100644
@@ -252,3 +252,52 @@ Pack file entry: <+
     corresponding packfile.
 
     20-byte SHA-1-checksum of all of the above.
+
+== midx-*.midx files have the following format:
+
+The meta-index files refer to multiple pack-files and loose objects.
+
+In order to allow extensions that add extra data to the MIDX, we organize
+the body into "chunks" and provide a lookup table at the beginning of the
+body. The header includes certain length values, such as the number of packs,
+the number of base MIDX files, hash lengths and types.
+
+All 4-byte numbers are in network order.
+
+HEADER:
+
+	4-byte signature:
+	    The signature is: {'M', 'I', 'D', 'X'}
+
+	1-byte version number:
+	    Git only writes or recognizes version 1
+
+	1-byte Object Id Version
+	    Git only writes or recognizes verion 1 (SHA-1)
+
+	1-byte number (C) of "chunks"
+
+	1-byte number (I) of base multi-pack-index files:
+	    This value is currently always zero.
+
+	4-byte number (P) of pack files
+
+CHUNK LOOKUP:
+
+	(C + 1) * 12 bytes providing the chunk offsets:
+	    First 4 bytes describe chunk id. Value 0 is a terminating label.
+	    Other 8 bytes provide offset in current file for chunk to start.
+	    (Chunks are provided in file-order, so you can infer the length
+	    using the next chunk position if necessary.)
+
+	The remaining data in the body is described one chunk at a time, and
+	these chunks may be given in any order. Chunks are required unless
+	otherwise specified.
+
+CHUNK DATA:
+
+	(This section intentionally left incomplete.)
+
+TRAILER:
+
+	H-byte HASH-checksum of all of the above.
Signature · 1 line
2.18.0.rc1
#5 lore raw

Re: [PATCH 02/23] midx: add midx format details to pack-format.txt

Hi Derrick,

Quoted 26 lines · Derrick Stolee

The multi-pack-index (MIDX) feature generalizes the existing pack- index (IDX) feature by indexing objects across multiple pack-files.

Describe the basic file format, using a 12-byte header followed by a lookup table for a list of "chunks" which will be described later. The file ends with a footer containing a checksum using the hash algorithm.

The header allows later versions to create breaking changes by advancing the version number. We can also change the hash algorithm using a different version value.

We will add the individual chunk format information as we introduce the code that writes that information.

---
 Documentation/technical/pack-format.txt | 49 +++++++++++++++++++++++++
 1 file changed, 49 insertions(+)
Documentation/technical/pack-format.txt +0 -0
index 70a99fd142..17666b4bfc 100644

Documentation/technical/pack-format.txt Pack file entry: <+ · line 258

Quoted 7 lines
Documentation/technical/pack-format.txt +4 -0
@@ -252,3 +252,52 @@ Pack file entry: <+
     corresponding packfile.
 
     20-byte SHA-1-checksum of all of the above.
+
+== midx-*.midx files have the following format:
+
+The meta-index files refer to multiple pack-files and loose objects.

So is it meta or multi?

Quoted 7 lines
Documentation/technical/pack-format.txt +7 -0
+In order to allow extensions that add extra data to the MIDX, we organize
+the body into "chunks" and provide a lookup table at the beginning of the
+body. The header includes certain length values, such as the number of packs,
+the number of base MIDX files, hash lengths and types.
+
+All 4-byte numbers are in network order.
+

Documentation/technical/pack-format.txt Pack file entry: <+ · line 276

Quoted 10 lines
Documentation/technical/pack-format.txt +10 -0
+HEADER:
+
+       4-byte signature:
+           The signature is: {'M', 'I', 'D', 'X'}
+
+       1-byte version number:
+           Git only writes or recognizes version 1
+
+       1-byte Object Id Version
+           Git only writes or recognizes verion 1 (SHA-1)

s/verion/version/

Documentation/technical/pack-format.txt Pack file entry: <+ · line 281

Quoted 4 lines
Documentation/technical/pack-format.txt +4 -0
+       1-byte number (C) of "chunks"
+
+       1-byte number (I) of base multi-pack-index files:
+           This value is currently always zero.

Oh? Are meta-index and multi-index files different things?

Documentation/technical/pack-format.txt Pack file entry: <+ · line 291

Quoted 9 lines
Documentation/technical/pack-format.txt +9 -0
+       4-byte number (P) of pack files
+
+CHUNK LOOKUP:
+
+       (C + 1) * 12 bytes providing the chunk offsets:
+           First 4 bytes describe chunk id. Value 0 is a terminating label.
+           Other 8 bytes provide offset in current file for chunk to start.
+           (Chunks are provided in file-order, so you can infer the length
+           using the next chunk position if necessary.)

It is so nice to have the header also have 12 bytes, so it fits right into the lookup table. So an alternative point of view:

If a chunk needs to store more than 8 bytes, we'll have an offset after the first 4 bytes that describe the chunk, otherwise you can store the 8 bytes of information directly after the 4 bytes.
"MIDX" is a special chunk and must come first (does it?) and only once
as it contains the version number.

Documentation/technical/pack-format.txt Pack file entry: <+ · line 303

Quoted 11 lines
Documentation/technical/pack-format.txt +11 -0
+       The remaining data in the body is described one chunk at a time, and
+       these chunks may be given in any order. Chunks are required unless
+       otherwise specified.
+
+CHUNK DATA:
+
+       (This section intentionally left incomplete.)
+
+TRAILER:
+
+       H-byte HASH-checksum of all of the above.

This means we have to rehash the whole file for updating its contents. okay.

#6 lore raw

Re: [PATCH 02/23] midx: add midx format details to pack-format.txt

Quoted 29 lines · Stefan Beller

Hi Derrick,

Quoted 27 lines · Derrick Stolee

The multi-pack-index (MIDX) feature generalizes the existing pack- index (IDX) feature by indexing objects across multiple pack-files.

Describe the basic file format, using a 12-byte header followed by a lookup table for a list of "chunks" which will be described later. The file ends with a footer containing a checksum using the hash algorithm.

The header allows later versions to create breaking changes by advancing the version number. We can also change the hash algorithm using a different version value.

We will add the individual chunk format information as we introduce the code that writes that information.

---
  Documentation/technical/pack-format.txt | 49 +++++++++++++++++++++++++
  1 file changed, 49 insertions(+)
Documentation/technical/pack-format.txt +0 -0
index 70a99fd142..17666b4bfc 100644
@@ -252,3 +252,52 @@ Pack file entry: <+
      corresponding packfile.
 
Quoted 6 lines

Documentation/technical/pack-format.txt Pack file entry: <+ · line 258

Quoted 5 lines
Documentation/technical/pack-format.txt +4 -0
      20-byte SHA-1-checksum of all of the above.
+
+== midx-*.midx files have the following format:
+
+The meta-index files refer to multiple pack-files and loose objects.

So is it meta or multi?

Good catch. We were calling this the meta-index internally before changing to "multi-pack-index" (helps to not change the acronym).

Quoted 20 lines
Quoted 8 lines
Documentation/technical/pack-format.txt +7 -0
+In order to allow extensions that add extra data to the MIDX, we organize
+the body into "chunks" and provide a lookup table at the beginning of the
+body. The header includes certain length values, such as the number of packs,
+the number of base MIDX files, hash lengths and types.
+
+All 4-byte numbers are in network order.
+

Documentation/technical/pack-format.txt Pack file entry: <+ · line 276

Quoted 10 lines
Documentation/technical/pack-format.txt +10 -0
+HEADER:
+
+       4-byte signature:
+           The signature is: {'M', 'I', 'D', 'X'}
+
+       1-byte version number:
+           Git only writes or recognizes version 1
+
+       1-byte Object Id Version
+           Git only writes or recognizes verion 1 (SHA-1)

s/verion/version/

Quoted 5 lines

Documentation/technical/pack-format.txt Pack file entry: <+ · line 281

Quoted 4 lines
Documentation/technical/pack-format.txt +4 -0
+       1-byte number (C) of "chunks"
+
+       1-byte number (I) of base multi-pack-index files:
+           This value is currently always zero.

Oh? Are meta-index and multi-index files different things?

Not intended to be different things, but this number is related to making the feature incremental.

Quoted 13 lines

Documentation/technical/pack-format.txt Pack file entry: <+ · line 291

Quoted 10 lines
Documentation/technical/pack-format.txt +9 -0
+       4-byte number (P) of pack files
+
+CHUNK LOOKUP:
+
+       (C + 1) * 12 bytes providing the chunk offsets:
+           First 4 bytes describe chunk id. Value 0 is a terminating label.
+           Other 8 bytes provide offset in current file for chunk to start.
+           (Chunks are provided in file-order, so you can infer the length
+           using the next chunk position if necessary.)

It is so nice to have the header also have 12 bytes, so it fits right into the lookup table. So an alternative point of view:

Quoted 5 lines

If a chunk needs to store more than 8 bytes, we'll have an offset after the first 4 bytes that describe the chunk, otherwise you can store the 8 bytes of information directly after the 4 bytes.
"MIDX" is a special chunk and must come first (does it?) and only once
as it contains the version number.

This sounds feasible, but unnecessarily complicated. I don't think any other chunk will be this small.

Quoted 13 lines

Documentation/technical/pack-format.txt Pack file entry: <+ · line 303

Quoted 11 lines
Documentation/technical/pack-format.txt +11 -0
+       The remaining data in the body is described one chunk at a time, and
+       these chunks may be given in any order. Chunks are required unless
+       otherwise specified.
+
+CHUNK DATA:
+
+       (This section intentionally left incomplete.)
+
+TRAILER:
+
+       H-byte HASH-checksum of all of the above.

This means we have to rehash the whole file for updating its contents. okay.

#7 lore raw

Re: [PATCH 02/23] midx: add midx format details to pack-format.txt

Quoted 8 lines
Quoted 8 lines

Documentation/technical/pack-format.txt Pack file entry: <+ · line 291

Quoted 5 lines
Documentation/technical/pack-format.txt +5 -0
+       (C + 1) * 12 bytes providing the chunk offsets:
+           First 4 bytes describe chunk id. Value 0 is a terminating label.
+           Other 8 bytes provide offset in current file for chunk to start.
+           (Chunks are provided in file-order, so you can infer the length
+           using the next chunk position if necessary.)

It is so nice to have the header also have 12 bytes, so it fits right into the lookup table. So an alternative point of view:

Quoted 8 lines
Quoted 5 lines

If a chunk needs to store more than 8 bytes, we'll have an offset after the first 4 bytes that describe the chunk, otherwise you can store the 8 bytes of information directly after the 4 bytes.
"MIDX" is a special chunk and must come first (does it?) and only once
as it contains the version number.

This sounds feasible, but unnecessarily complicated. I don't think any other chunk will be this small.

I was just writing it as a way to test if I really understood what you said in the doc, not as a suggestion to incorporate it.

Stefan

#8 lore raw

PATCH 3/23 midx: add midx builtin

In reply to Derrick Stolee

This new 'git midx' builtin will be the plumbing access for writing, reading, and checking multi-pack-index (MIDX) files. The initial implementation is a no-op.

---
 .gitignore                 |  1 +
 Documentation/git-midx.txt | 29 +++++++++++++++++++++++++++++
 Makefile                   |  1 +
 builtin.h                  |  1 +
 builtin/midx.c             | 38 ++++++++++++++++++++++++++++++++++++++
 command-list.txt           |  1 +
 git.c                      |  1 +
 7 files changed, 72 insertions(+)
 create mode 100644 Documentation/git-midx.txt
 create mode 100644 builtin/midx.c
.gitignore +1 -0
index 388cc4beee..e309644d6b 100644
@@ -97,6 +97,7 @@
 /git-merge-subtree
 /git-mergetool
 /git-mergetool--lib
+/git-midx
 /git-mktag
 /git-mktree
 /git-name-rev
Documentation/git-midx.txt new file +29 -0
new file mode 100644
index 0000000000..2bd886f1a2
@@ -0,0 +1,29 @@
+git-midx(1)
+============
+
+NAME
+----
+git-midx - Write and verify multi-pack-indexes (MIDX files).
+
+
+SYNOPSIS
+--------
+[verse]
+'git midx' [--object-dir <dir>]
+
+DESCRIPTION
+-----------
+Write or verify a MIDX file.
+
+OPTIONS
+-------
+
+--object-dir <dir>::
+	Use given directory for the location of Git objects. We check
+	<dir>/packs/multi-pack-index for the current MIDX file, and
+	<dir>/packs for the pack-files to index.
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
Makefile +1 -0
index 1d27f36365..88958c7b42 100644
@@ -1045,6 +1045,7 @@ BUILTIN_OBJS += builtin/merge-index.o
 BUILTIN_OBJS += builtin/merge-ours.o
 BUILTIN_OBJS += builtin/merge-recursive.o
 BUILTIN_OBJS += builtin/merge-tree.o
+BUILTIN_OBJS += builtin/midx.o
 BUILTIN_OBJS += builtin/mktag.o
 BUILTIN_OBJS += builtin/mktree.o
 BUILTIN_OBJS += builtin/mv.o
builtin.h +1 -0
index 4e0f64723e..7b5bd46c7d 100644
@@ -189,6 +189,7 @@ extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_midx(int argc, const char **argv, const char *prefix);
 extern int cmd_mktag(int argc, const char **argv, const char *prefix);
 extern int cmd_mktree(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
builtin/midx.c new file +38 -0
new file mode 100644
index 0000000000..59ea92178f
@@ -0,0 +1,38 @@
+#include "builtin.h"
+#include "cache.h"
+#include "config.h"
+#include "git-compat-util.h"
+#include "parse-options.h"
+
+static char const * const builtin_midx_usage[] ={
+	N_("git midx [--object-dir <dir>]"),
+	NULL
+};
+
+static struct opts_midx {
+	const char *object_dir;
+} opts;
+
+int cmd_midx(int argc, const char **argv, const char *prefix)
+{
+	static struct option builtin_midx_options[] = {
+		{ OPTION_STRING, 0, "object-dir", &opts.object_dir,
+		  N_("dir"),
+		  N_("The object directory containing set of packfile and pack-index pairs.") },
+		OPT_END(),
+	};
+
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(builtin_midx_usage, builtin_midx_options);
+
+	git_config(git_default_config, NULL);
+
+	argc = parse_options(argc, argv, prefix,
+			     builtin_midx_options,
+			     builtin_midx_usage, 0);
+
+	if (!opts.object_dir)
+		opts.object_dir = get_object_directory();
+
+	return 0;
+}
command-list.txt +1 -0
index e1c26c1bb7..a21bd7470e 100644
@@ -123,6 +123,7 @@ git-merge-index                         plumbingmanipulators
 git-merge-one-file                      purehelpers
 git-mergetool                           ancillarymanipulators           complete
 git-merge-tree                          ancillaryinterrogators
+git-midx                                plumbingmanipulators
 git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain           worktree
git.c +1 -0
index c2f48d53dd..400fadd677 100644
@@ -503,6 +503,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+	{ "midx", cmd_midx, RUN_SETUP },
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
 	{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
Signature · 1 line
2.18.0.rc1
#9 lore raw

Re: [PATCH 03/23] midx: add midx builtin

Documentation/git-midx.txt line 6

Quoted 12 lines · Derrick Stolee
Documentation/git-midx.txt new file +6 -0
new file mode 100644
index 0000000000..2bd886f1a2
@@ -0,0 +1,29 @@
+git-midx(1)
+============
+
+NAME
+----
+git-midx - Write and verify multi-pack-indexes (MIDX files).

No full stop. This head line is collected automatically with others and its having a full stop while the rest does not looks strange/

builtin/midx.c line 4

Quoted 10 lines
builtin/midx.c new file +4 -0
new file mode 100644
index 0000000000..59ea92178f
@@ -0,0 +1,38 @@
+#include "builtin.h"
+#include "cache.h"
+#include "config.h"
+#include "git-compat-util.h"

You only need either cache.h or git-compat-util.h. If cache.h is here, git-compat-util can be removed.

Quoted 5 lines
builtin/midx.c new file +5 -0
+#include "parse-options.h"
+
+static char const * const builtin_midx_usage[] ={
+       N_("git midx [--object-dir <dir>]"),
+       NULL

builtin/midx.c line 19

Quoted 10 lines
builtin/midx.c new file +10 -0
+};
+
+static struct opts_midx {
+       const char *object_dir;
+} opts;
+
+int cmd_midx(int argc, const char **argv, const char *prefix)
+{
+       static struct option builtin_midx_options[] = {
+               { OPTION_STRING, 0, "object-dir", &opts.object_dir,

For paths (including dir), OPTION_FILENAME may be a better option to handle correctly when the command is run in a subdir. See df217ed643 (parse-opts: add OPT_FILENAME and transition builtins - 2009-05-23) for more info.

builtin/midx.c line 21

Quoted 2 lines
builtin/midx.c new file +2 -0
+                 N_("dir"),
+                 N_("The object directory containing set of packfile and pack-index pairs.") },

Other help strings do not have full stop either (I only checked a couple commands though)

Also, doesn't OPT_STRING() work here too (if you avoid OPTION_FILENAME for some reason)?

Quoted 23 lines
builtin/midx.c new file +17 -0
+               OPT_END(),
+       };
+
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_midx_usage, builtin_midx_options);
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix,
+                            builtin_midx_options,
+                            builtin_midx_usage, 0);
+
+       if (!opts.object_dir)
+               opts.object_dir = get_object_directory();
+
+       return 0;
+}
git.c +0 -0
index c2f48d53dd..400fadd677 100644

git.c static struct cmd_struct commands[] = { · line 506

Quoted 4 lines
git.c +1 -0
@@ -503,6 +503,7 @@ static struct cmd_struct commands[] = {
        { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
        { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
        { "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+       { "midx", cmd_midx, RUN_SETUP },

If it's a plumbing and can take an --object-dir, then I don't think you should require it to run in a repo (with RUN_SETUP). RUN_SETUP_GENTLY may be better. You could even leave it empty here and only call setup_git_directory() only when --object-dir is not set.

Quoted 3 lines
git.c +0 -0
        { "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
        { "mktree", cmd_mktree, RUN_SETUP },
        { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
Signature · 1 line
Duy
#10 lore raw

Re: [PATCH 03/23] midx: add midx builtin

Quoted 40 lines · Duy Nguyen

Documentation/git-midx.txt line 6

Quoted 12 lines · Derrick Stolee
Documentation/git-midx.txt new file +6 -0
new file mode 100644
index 0000000000..2bd886f1a2
@@ -0,0 +1,29 @@
+git-midx(1)
+============
+
+NAME
+----
+git-midx - Write and verify multi-pack-indexes (MIDX files).

No full stop. This head line is collected automatically with others and its having a full stop while the rest does not looks strange/

builtin/midx.c line 4

Quoted 10 lines
builtin/midx.c new file +4 -0
new file mode 100644
index 0000000000..59ea92178f
@@ -0,0 +1,38 @@
+#include "builtin.h"
+#include "cache.h"
+#include "config.h"
+#include "git-compat-util.h"

You only need either cache.h or git-compat-util.h. If cache.h is here, git-compat-util can be removed.

Quoted 11 lines
builtin/midx.c new file +11 -0
+#include "parse-options.h"
+
+static char const * const builtin_midx_usage[] ={
+       N_("git midx [--object-dir <dir>]"),
+       NULL
+};
+
+static struct opts_midx {
+       const char *object_dir;
+} opts;
+
Quoted 8 lines

builtin/midx.c line 19

Quoted 4 lines
builtin/midx.c new file +4 -0
+int cmd_midx(int argc, const char **argv, const char *prefix)
+{
+       static struct option builtin_midx_options[] = {
+               { OPTION_STRING, 0, "object-dir", &opts.object_dir,

For paths (including dir), OPTION_FILENAME may be a better option to handle correctly when the command is run in a subdir. See df217ed643 (parse-opts: add OPT_FILENAME and transition builtins - 2009-05-23) for more info.

Thanks for the pointer!

Quoted 31 lines

builtin/midx.c line 21

Quoted 3 lines
builtin/midx.c new file +2 -0
+                 N_("dir"),
+                 N_("The object directory containing set of packfile and pack-index pairs.") },

Other help strings do not have full stop either (I only checked a couple commands though)

Also, doesn't OPT_STRING() work here too (if you avoid OPTION_FILENAME for some reason)?

Quoted 22 lines
builtin/midx.c new file +17 -0
+               OPT_END(),
+       };
+
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_midx_usage, builtin_midx_options);
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix,
+                            builtin_midx_options,
+                            builtin_midx_usage, 0);
+
+       if (!opts.object_dir)
+               opts.object_dir = get_object_directory();
+
+       return 0;
+}
git.c +0 -0
index c2f48d53dd..400fadd677 100644
@@ -503,6 +503,7 @@ static struct cmd_struct commands[] = {
Quoted 8 lines

git.c static struct cmd_struct commands[] = { · line 506

Quoted 4 lines
git.c +1 -0
         { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
         { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
         { "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+       { "midx", cmd_midx, RUN_SETUP },

If it's a plumbing and can take an --object-dir, then I don't think you should require it to run in a repo (with RUN_SETUP). RUN_SETUP_GENTLY may be better. You could even leave it empty here and only call setup_git_directory() only when --object-dir is not set.

I agree. Good point. This could be run to maintain an alternate without any .git folder.

Quoted 4 lines
Quoted 4 lines
git.c +0 -0
         { "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
         { "mktree", cmd_mktree, RUN_SETUP },
         { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
#11 lore raw

Re: [PATCH 03/23] midx: add midx builtin

In reply to Derrick Stolee

Hi Derrick,

Quoted 4 lines · Derrick Stolee

This new 'git midx' builtin will be the plumbing access for writing, reading, and checking multi-pack-index (MIDX) files. The initial implementation is a no-op.

Let's talk about the name for a second:

.idx files are written by git-index-pack or as part of git-pack-objects (which just calls write_idx_file as part of finish_tmp_packfile), and the name actually suggests it writes the index files. I have a hard time understanding what the git-midx command does[1].

With both commit graph as well as multi index we introduce a command that is centered around that concept (similar to git-remote or git-config that are centered around a concept, that is closely resembled by a file), but for indexes for packs it was integrated differently into Git. So I am not sure if I want to suggest to integrate it into the packfile commands as that doesn't really fit. But maybe we can have a name that is human readable instead of the file suffix? Maybe

git multi-pack-index ?

I suppose that eventually this command is not really used by users as it will be used by other porcelain commands in the background or even as part of repack/gc so I am not worried about a long name, but I'd be more worried about understandability.

[1] While these names are not perfect for the layman, it is okay? I am sure you are aware of https://git-man-page-generator.lokaltog.net/

Documentation/git-midx.txt line 6

Quoted 11 lines

new file mode 100644
index 0000000000..2bd886f1a2

Documentation/git-midx.txt new file +6 -0
@@ -0,0 +1,29 @@
+git-midx(1)
+============
+
+NAME
+----
+git-midx - Write and verify multi-pack-indexes (MIDX files).

The reading is done as part of all other commands.

Quoted 10 lines
Documentation/git-midx.txt new file +10 -0
+
+
+SYNOPSIS
+--------
+[verse]
+'git midx' [--object-dir <dir>]
+
+DESCRIPTION
+-----------
+Write or verify a MIDX file.

Documentation/git-midx.txt line 26

Quoted 10 lines
Documentation/git-midx.txt new file +10 -0
+
+OPTIONS
+-------
+
+--object-dir <dir>::
+       Use given directory for the location of Git objects. We check
+       <dir>/packs/multi-pack-index for the current MIDX file, and
+       <dir>/packs for the pack-files to index.
+
+

Maybe we could have a SEE ALSO section that points at the explanation of multi index files?
(c.f. man git-submodule that has a SEE ALSO gitsubmodules(7), gitmodules(5) explaining concepts(7) and the file(5))

But as this is plumbing and users should not need to worry about it this is optional, I would think.

#12 lore raw

Re: [PATCH 03/23] midx: add midx builtin

Quoted 22 lines · Stefan Beller

Hi Derrick,

Quoted 3 lines · Derrick Stolee

This new 'git midx' builtin will be the plumbing access for writing, reading, and checking multi-pack-index (MIDX) files. The initial implementation is a no-op.

Let's talk about the name for a second:

.idx files are written by git-index-pack or as part of git-pack-objects (which just calls write_idx_file as part of finish_tmp_packfile), and the name actually suggests it writes the index files. I have a hard time understanding what the git-midx command does[1].

With both commit graph as well as multi index we introduce a command that is centered around that concept (similar to git-remote or git-config that are centered around a concept, that is closely resembled by a file), but for indexes for packs it was integrated differently into Git. So I am not sure if I want to suggest to integrate it into the packfile commands as that doesn't really fit. But maybe we can have a name that is human readable instead of the file suffix? Maybe

Quoted 6 lines

git multi-pack-index ?

I suppose that eventually this command is not really used by users as it will be used by other porcelain commands in the background or even as part of repack/gc so I am not worried about a long name, but I'd be more worried about understandability.

I'll use "git multi-pack-index" in v2. I'll keep "midx.c" in the root, though, if that is OK.

Quoted 2 lines

[1] While these names are not perfect for the layman, it is okay? I am sure you are aware of https://git-man-page-generator.lokaltog.net/

I was not, and enjoyed that quite a bit.

Thanks,
-Stolee

Quoted 14 lines

Documentation/git-midx.txt line 6

Quoted 13 lines

new file mode 100644
index 0000000000..2bd886f1a2

Documentation/git-midx.txt new file +6 -0
@@ -0,0 +1,29 @@
+git-midx(1)
+============
+
+NAME
+----
+git-midx - Write and verify multi-pack-indexes (MIDX files).

The reading is done as part of all other commands.

I like to think the 'read' verb is a subset of "verify" because we are checking for information about the MIDX, and mostly for tests or debugging.

Quoted 27 lines
Quoted 11 lines
Documentation/git-midx.txt new file +10 -0
+
+
+SYNOPSIS
+--------
+[verse]
+'git midx' [--object-dir <dir>]
+
+DESCRIPTION
+-----------
+Write or verify a MIDX file.

Documentation/git-midx.txt line 26

Quoted 10 lines
Documentation/git-midx.txt new file +10 -0
+
+OPTIONS
+-------
+
+--object-dir <dir>::
+       Use given directory for the location of Git objects. We check
+       <dir>/packs/multi-pack-index for the current MIDX file, and
+       <dir>/packs for the pack-files to index.
+
+

Maybe we could have a SEE ALSO section that points at the explanation of multi index files?
(c.f. man git-submodule that has a SEE ALSO gitsubmodules(7), gitmodules(5) explaining concepts(7) and the file(5))

Quoted 2 lines

But as this is plumbing and users should not need to worry about it this is optional, I would think.

The design document is also in 'Documentation/technical' instead of just 'Documentation/'. Do we have a pattern of linking to the technical documents?

Thanks,
-Stolee

#13 lore raw

Re: [PATCH 03/23] midx: add midx builtin

Quoted 6 lines
Quoted 2 lines

But as this is plumbing and users should not need to worry about it this is optional, I would think.

The design document is also in 'Documentation/technical' instead of just 'Documentation/'. Do we have a pattern of linking to the technical documents?

Apparently we do (and I was not aware of it):

    $ git -C Documentation/ grep link:technical
    git-credential.txt:23:link:technical/api-credentials.html[the Git
credential API] for more
    git.txt:839:link:technical/api-index.html[Git API documentation].
    gitcredentials.txt:184:link:technical/api-credentials.html[credentials
API] for details.
    technical/http-protocol.txt:517:link:technical/pack-protocol.html
    technical/http-protocol.txt:518:link:technical/protocol-capabilities.html
    user-manual.txt:3220:found in link:technical/pack-format.html[pack format].
#14 lore raw

Re: [PATCH 03/23] midx: add midx builtin

Quoted 7 lines · Stefan Beller
Quoted 5 lines
Quoted 2 lines

But as this is plumbing and users should not need to worry about it this is optional, I would think.

The design document is also in 'Documentation/technical' instead of just 'Documentation/'. Do we have a pattern of linking to the technical documents?

Apparently we do (and I was not aware of it):

Quoted 9 lines
     $ git -C Documentation/ grep link:technical
     git-credential.txt:23:link:technical/api-credentials.html[the Git
credential API] for more
     git.txt:839:link:technical/api-index.html[Git API documentation].
     gitcredentials.txt:184:link:technical/api-credentials.html[credentials
API] for details.
     technical/http-protocol.txt:517:link:technical/pack-protocol.html
     technical/http-protocol.txt:518:link:technical/protocol-capabilities.html
     user-manual.txt:3220:found in link:technical/pack-format.html[pack format].

Thanks! I'll add some links.

#15 lore raw

PATCH 4/23 midx: add 'write' subcommand and basic wiring

In reply to Derrick Stolee

In anticipation of writing multi-pack-indexes (MIDX files), add a 'git midx write' subcommand and send the options to a write_midx_file() method. Also create a basic test file that tests the 'write' subcommand.

---
 Documentation/git-midx.txt | 22 +++++++++++++++++++++-
 Makefile                   |  1 +
 builtin/midx.c             |  9 ++++++++-
 midx.c                     |  9 +++++++++
 midx.h                     |  4 ++++
 t/t5319-midx.sh            | 10 ++++++++++
 6 files changed, 53 insertions(+), 2 deletions(-)
 create mode 100644 midx.c
 create mode 100644 midx.h
 create mode 100755 t/t5319-midx.sh
Documentation/git-midx.txt +21 -1
index 2bd886f1a2..dcaeb1a91b 100644
@@ -9,7 +9,7 @@ git-midx - Write and verify multi-pack-indexes (MIDX files).
 SYNOPSIS
 --------
 [verse]
-'git midx' [--object-dir <dir>]
+'git midx' [--object-dir <dir>] <verb>
 
 DESCRIPTION
 -----------
@@ -23,6 +23,26 @@ OPTIONS
 	<dir>/packs/multi-pack-index for the current MIDX file, and
 	<dir>/packs for the pack-files to index.
 
+write::
+	When given as the verb, write a new MIDX file to
+	<dir>/packs/multi-pack-index.
+
+
+EXAMPLES
+--------
+
+* Write a MIDX file for the packfiles in the current .git folder.
++
+-------------------------------------------
+$ git midx write
+-------------------------------------------
+
+* Write a MIDX file for the packfiles in an alternate.
++
+-------------------------------------------
+$ git midx --object-dir <alt> write
+-------------------------------------------
+
 
 GIT
 ---
Makefile +1 -0
index 88958c7b42..aa86fcd8ec 100644
@@ -890,6 +890,7 @@ LIB_OBJS += merge.o
 LIB_OBJS += merge-blobs.o
 LIB_OBJS += merge-recursive.o
 LIB_OBJS += mergesort.o
+LIB_OBJS += midx.o
 LIB_OBJS += name-hash.o
 LIB_OBJS += notes.o
 LIB_OBJS += notes-cache.o
builtin/midx.c +8 -1
index 59ea92178f..dc0a5acd3f 100644
@@ -3,9 +3,10 @@
 #include "config.h"
 #include "git-compat-util.h"
 #include "parse-options.h"
+#include "midx.h"
 
 static char const * const builtin_midx_usage[] ={
-	N_("git midx [--object-dir <dir>]"),
+	N_("git midx [--object-dir <dir>] [write]"),
 	NULL
 };
 
@@ -34,5 +35,11 @@ int cmd_midx(int argc, const char **argv, const char *prefix)
 	if (!opts.object_dir)
 		opts.object_dir = get_object_directory();
 
+	if (argc == 0)
+		return 0;
+
+	if (!strcmp(argv[0], "write"))
+		return write_midx_file(opts.object_dir);
+
 	return 0;
 }
midx.c new file +9 -0
new file mode 100644
index 0000000000..616af66b13
@@ -0,0 +1,9 @@
+#include "git-compat-util.h"
+#include "cache.h"
+#include "dir.h"
+#include "midx.h"
+
+int write_midx_file(const char *object_dir)
+{
+	return 0;
+}
midx.h new file +4 -0
new file mode 100644
index 0000000000..3a63673952
@@ -0,0 +1,4 @@
+#include "cache.h"
+#include "packfile.h"
+
+int write_midx_file(const char *object_dir);
t/t5319-midx.sh new file +10 -0
new file mode 100755
index 0000000000..a590137af7
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='multi-pack-indexes'
+. ./test-lib.sh
+
+test_expect_success 'write midx with no pakcs' '
+	git midx --object-dir=. write
+'
+
+test_done
Signature · 1 line
2.18.0.rc1
#16 lore raw

Re: [PATCH 04/23] midx: add 'write' subcommand and basic wiring

Quoted 17 lines · Derrick Stolee
builtin/midx.c +2 -1
index 59ea92178f..dc0a5acd3f 100644
@@ -3,9 +3,10 @@
 #include "config.h"
 #include "git-compat-util.h"
 #include "parse-options.h"
+#include "midx.h"
 
 static char const * const builtin_midx_usage[] ={
-       N_("git midx [--object-dir <dir>]"),
+       N_("git midx [--object-dir <dir>] [write]"),
        NULL
 };
 

builtin/midx.c int cmd_midx(int argc, const char **argv, const char *prefix) · line 39

Quoted 5 lines
builtin/midx.c +2 -0
@@ -34,5 +35,11 @@ int cmd_midx(int argc, const char **argv, const char *prefix)
        if (!opts.object_dir)
                opts.object_dir = get_object_directory();
 
+       if (argc == 0)
+               return 0;

Isn't it better to die here when no verb is given? I don't see any good use case for running a no-op "git midx" without verbs. It's more likely a mistake (e.g. "git midx $foo" where foo happens to be empty)

Quoted 12 lines
builtin/midx.c +4 -0
+
+       if (!strcmp(argv[0], "write"))
+               return write_midx_file(opts.object_dir);
+
        return 0;
 }
midx.c new file +0 -0
new file mode 100644
index 0000000000..616af66b13

midx.c line 2

Quoted 2 lines
midx.c new file +2 -0
@@ -0,0 +1,9 @@
+#include "git-compat-util.h"
+#include "cache.h"

Only one of the two is needed

midx.c line 3

Quoted 1 line
midx.c new file +1 -0
+#include "dir.h"

Not needed yet. It's better to include it in the patch that actually needs it.

Quoted 12 lines
midx.c new file +6 -0
+#include "midx.h"
+
+int write_midx_file(const char *object_dir)
+{
+       return 0;
+}
midx.h new file +0 -0
new file mode 100644
index 0000000000..3a63673952

midx.h line 2

Quoted 2 lines
midx.h new file +2 -0
@@ -0,0 +1,4 @@
+#include "cache.h"
+#include "packfile.h"

These includes are not needed, at least not now. And please protect the header file with #ifndef __MINDX_H__ .. #endif.

Quoted 8 lines
midx.h new file +2 -0
+
+int write_midx_file(const char *object_dir);
t/t5319-midx.sh new file +0 -0
new file mode 100755
index 0000000000..a590137af7

t/t5319-midx.sh line 6

Quoted 6 lines
t/t5319-midx.sh new file +6 -0
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='multi-pack-indexes'
+. ./test-lib.sh
+
+test_expect_success 'write midx with no pakcs' '

no packs

Quoted 7 lines
t/t5319-midx.sh new file +4 -1
+       git midx --object-dir=. write
+'
+
+test_done
--

2.18.0.rc1

Signature · 1 line
Duy
#17 lore raw

PATCH 5/23 midx: write header information to lockfile

In reply to Derrick Stolee

As we begin writing the multi-pack-index format to disk, start with the basics: the 12-byte header and the 20-byte checksum footer. Start with these basics so we can add the rest of the format in small increments.

As we implement the format, we will use a technique to check that our computed offsets within the multi-pack-index file match what we are actually writing. Each method that writes to the hashfile will return the number of bytes written, and we will track that those values match our expectations.

Currently, write_midx_header() returns 12, but is not checked. We will check the return value in a later commit.

---
 midx.c          | 53 +++++++++++++++++++++++++++++++++++++++++++++++++
 t/t5319-midx.sh |  5 +++--
 2 files changed, 56 insertions(+), 2 deletions(-)
midx.c +53 -0
index 616af66b13..3e55422a21 100644
@@ -1,9 +1,62 @@
 #include "git-compat-util.h"
 #include "cache.h"
 #include "dir.h"
+#include "csum-file.h"
+#include "lockfile.h"
 #include "midx.h"
 
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1 /* SHA-1 */
+#define MIDX_HEADER_SIZE 12
+
+static char *get_midx_filename(const char *object_dir)
+{
+	struct strbuf midx_name = STRBUF_INIT;
+	strbuf_addstr(&midx_name, object_dir);
+	strbuf_addstr(&midx_name, "/pack/multi-pack-index");
+	return strbuf_detach(&midx_name, NULL);
+}
+
+static size_t write_midx_header(struct hashfile *f,
+				unsigned char num_chunks,
+				uint32_t num_packs)
+{
+	char byte_values[4];
+	hashwrite_be32(f, MIDX_SIGNATURE);
+	byte_values[0] = MIDX_VERSION;
+	byte_values[1] = MIDX_HASH_VERSION;
+	byte_values[2] = num_chunks;
+	byte_values[3] = 0; /* unused */
+	hashwrite(f, byte_values, sizeof(byte_values));
+	hashwrite_be32(f, num_packs);
+
+	return MIDX_HEADER_SIZE;
+}
+
 int write_midx_file(const char *object_dir)
 {
+	unsigned char num_chunks = 0;
+	uint32_t num_packs = 0;
+	char *midx_name;
+	struct hashfile *f;
+	struct lock_file lk;
+
+	midx_name = get_midx_filename(object_dir);
+	if (safe_create_leading_directories(midx_name)) {
+		UNLEAK(midx_name);
+		die_errno(_("unable to create leading directories of %s"),
+			  midx_name);
+	}
+
+	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
+	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
+	FREE_AND_NULL(midx_name);
+
+	write_midx_header(f, num_chunks, num_packs);
+
+	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
+	commit_lock_file(&lk);
+
 	return 0;
 }
t/t5319-midx.sh +3 -2
index a590137af7..80f9389837 100755
@@ -3,8 +3,9 @@
 test_description='multi-pack-indexes'
 . ./test-lib.sh
 
-test_expect_success 'write midx with no pakcs' '
-	git midx --object-dir=. write
+test_expect_success 'write midx with no packs' '
+	git midx --object-dir=. write &&
+	test_path_is_file pack/multi-pack-index
 '
 
 test_done
Signature · 1 line
2.18.0.rc1
#18 lore raw

Re: [PATCH 05/23] midx: write header information to lockfile

midx.c line 19

Quoted 7 lines · Derrick Stolee
midx.c +7 -0
+static char *get_midx_filename(const char *object_dir)
+{
+       struct strbuf midx_name = STRBUF_INIT;
+       strbuf_addstr(&midx_name, object_dir);
+       strbuf_addstr(&midx_name, "/pack/multi-pack-index");
+       return strbuf_detach(&midx_name, NULL);
+}

I think this whole function can be written as
xstrfmt("%s/pack/multi-pack-index", object_dir);

midx.c line 25

Quoted 6 lines
midx.c +6 -0
+
+static size_t write_midx_header(struct hashfile *f,
+                               unsigned char num_chunks,
+                               uint32_t num_packs)
+{
+       char byte_values[4];

unsigned char just to be on the safe side? 'char' is signed on ARM if I remember correctly.

Signature · 1 line
Duy
#19 lore raw

Re: [PATCH 05/23] midx: write header information to lockfile

In reply to Derrick Stolee
Quoted 15 lines · Derrick Stolee
midx.c +5 -0
index 616af66b13..3e55422a21 100644
@@ -1,9 +1,62 @@
 #include "git-compat-util.h"
 #include "cache.h"
 #include "dir.h"
+#include "csum-file.h"
+#include "lockfile.h"
 #include "midx.h"
 
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1 /* SHA-1 */

...

midx.c line 28

Quoted 8 lines
midx.c +8 -0
+static size_t write_midx_header(struct hashfile *f,
+                               unsigned char num_chunks,
+                               uint32_t num_packs)
+{
+       char byte_values[4];
+       hashwrite_be32(f, MIDX_SIGNATURE);
+       byte_values[0] = MIDX_VERSION;
+       byte_values[1] = MIDX_HASH_VERSION;

Quoting from "State of NewHash work, future directions, and discussion" [1]

* If you need to serialize an algorithm identifier into your data format, use the format_id field of struct git_hash_algo. It's designed specifically for that purpose.

[1] https://public-inbox.org/git/[email protected]/T/#m5fdd09dcaf31266c45343fb6c0beaaa3e928bc60

Quoted 7 lines
midx.c +7 -0
+       byte_values[2] = num_chunks;
+       byte_values[3] = 0; /* unused */
+       hashwrite(f, byte_values, sizeof(byte_values));
+       hashwrite_be32(f, num_packs);
+
+       return MIDX_HEADER_SIZE;
+}
Signature · 1 line
Duy
#20 lore raw

Re: [PATCH 05/23] midx: write header information to lockfile

Quoted 27 lines · Duy Nguyen
Quoted 15 lines · Derrick Stolee
midx.c +5 -0
index 616af66b13..3e55422a21 100644
@@ -1,9 +1,62 @@
  #include "git-compat-util.h"
  #include "cache.h"
  #include "dir.h"
+#include "csum-file.h"
+#include "lockfile.h"
  #include "midx.h"
 
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1 /* SHA-1 */

...

midx.c line 28

Quoted 8 lines
midx.c +8 -0
+static size_t write_midx_header(struct hashfile *f,
+                               unsigned char num_chunks,
+                               uint32_t num_packs)
+{
+       char byte_values[4];
+       hashwrite_be32(f, MIDX_SIGNATURE);
+       byte_values[0] = MIDX_VERSION;
+       byte_values[1] = MIDX_HASH_VERSION;

Quoting from "State of NewHash work, future directions, and discussion" [1]

Quoted 5 lines

* If you need to serialize an algorithm identifier into your data format, use the format_id field of struct git_hash_algo. It's designed specifically for that purpose.

[1] https://public-inbox.org/git/[email protected]/T/#m5fdd09dcaf31266c45343fb6c0beaaa3e928bc60

Thanks! I'll also use the_hash_algo->rawsz to infer the length of the hash function.

#21 lore raw

Re: [PATCH 05/23] midx: write header information to lockfile

Quoted 29 lines · Derrick Stolee
Quoted 27 lines · Duy Nguyen
Quoted 15 lines · Derrick Stolee
midx.c +5 -0
index 616af66b13..3e55422a21 100644
@@ -1,9 +1,62 @@
  #include "git-compat-util.h"
  #include "cache.h"
  #include "dir.h"
+#include "csum-file.h"
+#include "lockfile.h"
  #include "midx.h"
 
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1 /* SHA-1 */

...

midx.c line 28

Quoted 8 lines
midx.c +8 -0
+static size_t write_midx_header(struct hashfile *f,
+                               unsigned char num_chunks,
+                               uint32_t num_packs)
+{
+       char byte_values[4];
+       hashwrite_be32(f, MIDX_SIGNATURE);
+       byte_values[0] = MIDX_VERSION;
+       byte_values[1] = MIDX_HASH_VERSION;

Quoting from "State of NewHash work, future directions, and discussion" [1]

Quoted 8 lines
Quoted 5 lines

* If you need to serialize an algorithm identifier into your data format, use the format_id field of struct git_hash_algo. It's designed specifically for that purpose.

[1] https://public-inbox.org/git/[email protected]/T/#m5fdd09dcaf31266c45343fb6c0beaaa3e928bc60

Thanks! I'll also use the_hash_algo->rawsz to infer the length of the hash function.

BTW, since you're the author of commit-graph.c and may notice it has the same problem. Don't touch that code. Brian already has some WIP changes [1]. We just make sure new code does not add extra work for him. I expect he'll send all those patches out soon.

[1] https://github.com/bk2204/git/commit/3f9031e06cfb21534eb7dfff7b54e7598ac1149f

Signature · 1 line
Duy
#22 lore raw

Re: [PATCH 05/23] midx: write header information to lockfile

Quoted 34 lines · Duy Nguyen
Quoted 33 lines · Derrick Stolee
Quoted 32 lines · Duy Nguyen
Quoted 15 lines · Derrick Stolee
midx.c +5 -0
index 616af66b13..3e55422a21 100644
@@ -1,9 +1,62 @@
   #include "git-compat-util.h"
   #include "cache.h"
   #include "dir.h"
+#include "csum-file.h"
+#include "lockfile.h"
   #include "midx.h"
 
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1 /* SHA-1 */

...

midx.c line 28

Quoted 8 lines
midx.c +8 -0
+static size_t write_midx_header(struct hashfile *f,
+                               unsigned char num_chunks,
+                               uint32_t num_packs)
+{
+       char byte_values[4];
+       hashwrite_be32(f, MIDX_SIGNATURE);
+       byte_values[0] = MIDX_VERSION;
+       byte_values[1] = MIDX_HASH_VERSION;

Quoting from "State of NewHash work, future directions, and discussion" [1]

* If you need to serialize an algorithm identifier into your data format, use the format_id field of struct git_hash_algo. It's designed specifically for that purpose.

[1] https://public-inbox.org/git/[email protected]/T/#m5fdd09dcaf31266c45343fb6c0beaaa3e928bc60

Quoted 8 lines
Quoted 2 lines

Thanks! I'll also use the_hash_algo->rawsz to infer the length of the hash function.

BTW, since you're the author of commit-graph.c and may notice it has the same problem. Don't touch that code. Brian already has some WIP changes [1]. We just make sure new code does not add extra work for him. I expect he'll send all those patches out soon.

[1] https://github.com/bk2204/git/commit/3f9031e06cfb21534eb7dfff7b54e7598ac1149f

Thanks for the link. It seems he is creating an oid_version() method that returns a 1-byte version for the hash version instead of the 4-byte signature of the_hash_algo->format_id. I look forward to incorporating that into the MIDX format. I'll keep my macros for now, as we work out the other details, and while Brain's patch is cooking.

Thanks,
-Stolee

#23 lore raw

PATCH 6/23 midx: struct midxed_git and 'read' subcommand

In reply to Derrick Stolee

As we build the multi-pack-index feature by adding chunks at a time, we want to test that the data is being written correctly.

Create struct midxed_git to store an in-memory representation of a multi-pack-index and a memory-map of the binary file. Initialize this struct in load_midxed_git(object_dir).

Create the 'git midx read' subcommand to output basic information about the multi-pack-index file. This will be expanded as more information is written to the file.

---
 Documentation/git-midx.txt | 11 +++++++
 builtin/midx.c             | 23 +++++++++++++-
 midx.c                     | 65 ++++++++++++++++++++++++++++++++++++++
 midx.h                     |  9 ++++++
 object-store.h             | 19 +++++++++++
 t/t5319-midx.sh            | 12 ++++++-
 6 files changed, 137 insertions(+), 2 deletions(-)
Documentation/git-midx.txt +11 -0
index dcaeb1a91b..919283fdd8 100644
@@ -23,6 +23,11 @@ OPTIONS
 	<dir>/packs/multi-pack-index for the current MIDX file, and
 	<dir>/packs for the pack-files to index.
 
+read::
+	When given as the verb, read the current MIDX file and output
+	basic information about its contents. Used for debugging
+	purposes only.
+
 write::
 	When given as the verb, write a new MIDX file to
 	<dir>/packs/multi-pack-index.
@@ -43,6 +48,12 @@ $ git midx write
 $ git midx --object-dir <alt> write
 -------------------------------------------
 
+* Read the MIDX file in the .git/objects folder.
++
+-------------------------------------------
+$ git midx read
+-------------------------------------------
+
 
 GIT
 ---
builtin/midx.c +22 -1
index dc0a5acd3f..c7002f664a 100644
@@ -6,7 +6,7 @@
 #include "midx.h"
 
 static char const * const builtin_midx_usage[] ={
-	N_("git midx [--object-dir <dir>] [write]"),
+	N_("git midx [--object-dir <dir>] [read|write]"),
 	NULL
 };
 
@@ -14,6 +14,25 @@ static struct opts_midx {
 	const char *object_dir;
 } opts;
 
+static int read_midx_file(const char *object_dir)
+{
+	struct midxed_git *m = load_midxed_git(object_dir);
+
+	if (!m)
+		return 0;
+
+	printf("header: %08x %d %d %d %d\n",
+	       m->signature,
+	       m->version,
+	       m->hash_version,
+	       m->num_chunks,
+	       m->num_packs);
+
+	printf("object_dir: %s\n", m->object_dir);
+
+	return 0;
+}
+
 int cmd_midx(int argc, const char **argv, const char *prefix)
 {
 	static struct option builtin_midx_options[] = {
@@ -38,6 +57,8 @@ int cmd_midx(int argc, const char **argv, const char *prefix)
 	if (argc == 0)
 		return 0;
 
+	if (!strcmp(argv[0], "read"))
+		return read_midx_file(opts.object_dir);
 	if (!strcmp(argv[0], "write"))
 		return write_midx_file(opts.object_dir);
 
midx.c +65 -0

83 lines not on this page · raw message

midx.h +9 -0
index 3a63673952..a1d18ed991 100644
@@ -1,4 +1,13 @@
+#ifndef MIDX_H
+#define MIDX_H
+
+#include "git-compat-util.h"
 #include "cache.h"
+#include "object-store.h"
 #include "packfile.h"
 
+struct midxed_git *load_midxed_git(const char *object_dir);
+
 int write_midx_file(const char *object_dir);
+
+#endif
object-store.h +19 -0
index d683112fd7..77cb82621a 100644
@@ -84,6 +84,25 @@ struct packed_git {
 	char pack_name[FLEX_ARRAY]; /* more */
 };
 
+struct midxed_git {
+	struct midxed_git *next;
+
+	int fd;
+
+	const unsigned char *data;
+	size_t data_len;
+
+	uint32_t signature;
+	unsigned char version;
+	unsigned char hash_version;
+	unsigned char hash_len;
+	unsigned char num_chunks;
+	uint32_t num_packs;
+	uint32_t num_objects;
+
+	char object_dir[FLEX_ARRAY];
+};
+
 struct raw_object_store {
 	/*
 	 * Path to the repository's object store.
t/t5319-midx.sh +11 -1
index 80f9389837..e78514d8e9 100755
@@ -3,9 +3,19 @@
 test_description='multi-pack-indexes'
 . ./test-lib.sh
 
+midx_read_expect() {
+	cat >expect <<- EOF
+	header: 4d494458 1 1 0 0
+	object_dir: .
+	EOF
+	git midx read --object-dir=. >actual &&
+	test_cmp expect actual
+}
+
 test_expect_success 'write midx with no packs' '
 	git midx --object-dir=. write &&
-	test_path_is_file pack/multi-pack-index
+	test_path_is_file pack/multi-pack-index &&
+	midx_read_expect
 '
 
 test_done
Signature · 1 line
2.18.0.rc1
#24 lore raw

Re: [PATCH 06/23] midx: struct midxed_git and 'read' subcommand

Quoted 4 lines · Derrick Stolee

As we build the multi-pack-index feature by adding chunks at a time, we want to test that the data is being written correctly.

Create struct midxed_git to store an in-memory representation of a

A word play on 'packed_git'? Amusing. Some more descriptive name would be better though. midxed looks almost like random letters thrown together.

builtin/midx.c static struct opts_midx { · line 22

Quoted 9 lines

multi-pack-index and a memory-map of the binary file. Initialize this struct in load_midxed_git(object_dir).

builtin/midx.c +6 -0
+static int read_midx_file(const char *object_dir)
+{
+       struct midxed_git *m = load_midxed_git(object_dir);
+
+       if (!m)
+               return 0;

This looks like an error case, please don't just return zero, typically used to say "success". I don't know if this command stays "for debugging purposes" until the end. Of course in that case it does not really matter.

midx.c static char *get_midx_filename(const char *object_dir) · line 31

Quoted 8 lines
midx.c +8 -0
+struct midxed_git *load_midxed_git(const char *object_dir)
+{
+       struct midxed_git *m;
+       int fd;
+       struct stat st;
+       size_t midx_size;
+       void *midx_map;
+       const char *midx_name = get_midx_filename(object_dir);

mem leak? This function returns allocated memory if I remember correctly.

midx.c static char *get_midx_filename(const char *object_dir) · line 35

Quoted 4 lines
midx.c +4 -0
+
+       fd = git_open(midx_name);
+       if (fd < 0)
+               return NULL;

do an error_errno() so we know what went wrong at least.

midx.c static char *get_midx_filename(const char *object_dir) · line 38

Quoted 3 lines
midx.c +3 -0
+       if (fstat(fd, &st)) {
+               close(fd);
+               return NULL;

same here, we should know why fstat() fails.

midx.c static char *get_midx_filename(const char *object_dir) · line 44

Quoted 6 lines
midx.c +6 -0
+       }
+       midx_size = xsize_t(st.st_size);
+
+       if (midx_size < MIDX_MIN_SIZE) {
+               close(fd);
+               die("multi-pack-index file %s is too small", midx_name);

_()

The use of die() should be discouraged though. Many people still try (or wish) to libify code and new die() does not help. I think error() here would be enough then you can return NULL. Or you can go fancier and store the error string in a strbuf like refs code.

midx.c static char *get_midx_filename(const char *object_dir) · line 56

Quoted 12 lines
midx.c +12 -0
+       }
+
+       midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+
+       m = xcalloc(1, sizeof(*m) + strlen(object_dir) + 1);
+       strcpy(m->object_dir, object_dir);
+       m->data = midx_map;
+
+       m->signature = get_be32(m->data);
+       if (m->signature != MIDX_SIGNATURE) {
+               error("multi-pack-index signature %X does not match signature %X",
+                     m->signature, MIDX_SIGNATURE);

_(). Maybe 0x%08x instead of %x

midx.c static char *get_midx_filename(const char *object_dir) · line 60

Quoted 4 lines
midx.c +4 -0
+               goto cleanup_fail;
+       }
+
+       m->version = *(m->data + 4);

m->data[4] instead? shorter and easier to understand.

Same comment on "*(m->data + x)" and error() without _() for the rest.

midx.c static char *get_midx_filename(const char *object_dir) · line 63

Quoted 3 lines
midx.c +3 -0
+       if (m->version != MIDX_VERSION) {
+               error("multi-pack-index version %d not recognized",
+                     m->version);

_()

midx.c static char *get_midx_filename(const char *object_dir) · line 67

Quoted 4 lines
midx.c +4 -0
+               goto cleanup_fail;
+       }
+
+       m->hash_version = *(m->data + 5);

m->data[5]

midx.c static char *get_midx_filename(const char *object_dir) · line 83

Quoted 5 lines
midx.c +5 -0
+cleanup_fail:
+       FREE_AND_NULL(m);
+       munmap(midx_map, midx_size);
+       close(fd);
+       exit(1);

It's bad enough that you die() but exit() in this code seems too much. Please just return NULL and let the caller handle the error.

midx.h line 6

Quoted 11 lines
midx.h +5 -0
index 3a63673952..a1d18ed991 100644
@@ -1,4 +1,13 @@
+#ifndef MIDX_H
+#define MIDX_H
+
+#include "git-compat-util.h"
 #include "cache.h"
+#include "object-store.h"

I don't really think you need object-store here (git-compat-util.h too). "struct mixed_git;" would be enough for load_midxed_git declaration below.

Quoted 12 lines
midx.h +4 -0
 #include "packfile.h"
 
+struct midxed_git *load_midxed_git(const char *object_dir);
+
 int write_midx_file(const char *object_dir);
+
+#endif
object-store.h +0 -0
index d683112fd7..77cb82621a 100644

object-store.h struct packed_git { · line 88

Quoted 5 lines
object-store.h +2 -0
@@ -84,6 +84,25 @@ struct packed_git {
        char pack_name[FLEX_ARRAY]; /* more */
 };
 
+struct midxed_git {
+       struct midxed_git *next;

Do we really have multiple midx files?

Quoted 5 lines
object-store.h +5 -0
+
+       int fd;
+
+       const unsigned char *data;
+       size_t data_len;

object-store.h struct packed_git { · line 103

Quoted 10 lines
object-store.h +10 -0
+
+       uint32_t signature;
+       unsigned char version;
+       unsigned char hash_version;
+       unsigned char hash_len;
+       unsigned char num_chunks;
+       uint32_t num_packs;
+       uint32_t num_objects;
+
+       char object_dir[FLEX_ARRAY];

Why do you need to keep object_dir when it could be easily retrieved when the repo is available?

Quoted 5 lines
object-store.h +2 -0
+};
+
 struct raw_object_store {
        /*
         * Path to the repository's object store.
Signature · 1 line
Duy
#25 lore raw

Re: [PATCH 06/23] midx: struct midxed_git and 'read' subcommand

Quoted 8 lines · Duy Nguyen
Quoted 4 lines · Derrick Stolee

As we build the multi-pack-index feature by adding chunks at a time, we want to test that the data is being written correctly.

Create struct midxed_git to store an in-memory representation of a

A word play on 'packed_git'? Amusing. Some more descriptive name would be better though. midxed looks almost like random letters thrown together.

I'll use 'struct multi_pack_index'.

Quoted 13 lines

builtin/midx.c static struct opts_midx { · line 22

Quoted 9 lines

multi-pack-index and a memory-map of the binary file. Initialize this struct in load_midxed_git(object_dir).

builtin/midx.c +6 -0
+static int read_midx_file(const char *object_dir)
+{
+       struct midxed_git *m = load_midxed_git(object_dir);
+
+       if (!m)
+               return 0;

This looks like an error case, please don't just return zero, typically used to say "success". I don't know if this command stays "for debugging purposes" until the end. Of course in that case it does not really matter.

It is intended for debugging and testing. Generally, it is not an error to not have a MIDX in an object directory.

Quoted 66 lines

midx.c static char *get_midx_filename(const char *object_dir) · line 31

Quoted 8 lines
midx.c +8 -0
+struct midxed_git *load_midxed_git(const char *object_dir)
+{
+       struct midxed_git *m;
+       int fd;
+       struct stat st;
+       size_t midx_size;
+       void *midx_map;
+       const char *midx_name = get_midx_filename(object_dir);

mem leak? This function returns allocated memory if I remember correctly.

midx.c static char *get_midx_filename(const char *object_dir) · line 35

Quoted 4 lines
midx.c +4 -0
+
+       fd = git_open(midx_name);
+       if (fd < 0)
+               return NULL;

do an error_errno() so we know what went wrong at least.

midx.c static char *get_midx_filename(const char *object_dir) · line 38

Quoted 3 lines
midx.c +3 -0
+       if (fstat(fd, &st)) {
+               close(fd);
+               return NULL;

same here, we should know why fstat() fails.

midx.c static char *get_midx_filename(const char *object_dir) · line 44

Quoted 6 lines
midx.c +6 -0
+       }
+       midx_size = xsize_t(st.st_size);
+
+       if (midx_size < MIDX_MIN_SIZE) {
+               close(fd);
+               die("multi-pack-index file %s is too small", midx_name);

_()

The use of die() should be discouraged though. Many people still try (or wish) to libify code and new die() does not help. I think error() here would be enough then you can return NULL. Or you can go fancier and store the error string in a strbuf like refs code.

midx.c static char *get_midx_filename(const char *object_dir) · line 56

Quoted 12 lines
midx.c +12 -0
+       }
+
+       midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+
+       m = xcalloc(1, sizeof(*m) + strlen(object_dir) + 1);
+       strcpy(m->object_dir, object_dir);
+       m->data = midx_map;
+
+       m->signature = get_be32(m->data);
+       if (m->signature != MIDX_SIGNATURE) {
+               error("multi-pack-index signature %X does not match signature %X",
+                     m->signature, MIDX_SIGNATURE);

_(). Maybe 0x%08x instead of %x

midx.c static char *get_midx_filename(const char *object_dir) · line 60

Quoted 4 lines
midx.c +4 -0
+               goto cleanup_fail;
+       }
+
+       m->version = *(m->data + 4);

m->data[4] instead? shorter and easier to understand.

Same comment on "*(m->data + x)" and error() without _() for the rest.

midx.c static char *get_midx_filename(const char *object_dir) · line 63

Quoted 3 lines
midx.c +3 -0
+       if (m->version != MIDX_VERSION) {
+               error("multi-pack-index version %d not recognized",
+                     m->version);

_()

midx.c static char *get_midx_filename(const char *object_dir) · line 67

Quoted 4 lines
midx.c +4 -0
+               goto cleanup_fail;
+       }
+
+       m->hash_version = *(m->data + 5);

m->data[5]

Quoted 7 lines

midx.c static char *get_midx_filename(const char *object_dir) · line 83

Quoted 5 lines
midx.c +5 -0
+cleanup_fail:
+       FREE_AND_NULL(m);
+       munmap(midx_map, midx_size);
+       close(fd);
+       exit(1);

It's bad enough that you die() but exit() in this code seems too much. Please just return NULL and let the caller handle the error.

Will do.

Quoted 31 lines

midx.h line 6

Quoted 12 lines
midx.h +5 -0
index 3a63673952..a1d18ed991 100644
@@ -1,4 +1,13 @@
+#ifndef MIDX_H
+#define MIDX_H
+
+#include "git-compat-util.h"
  #include "cache.h"
+#include "object-store.h"

I don't really think you need object-store here (git-compat-util.h too). "struct mixed_git;" would be enough for load_midxed_git declaration below.

Quoted 15 lines
midx.h +4 -0
  #include "packfile.h"
 
+struct midxed_git *load_midxed_git(const char *object_dir);
+
  int write_midx_file(const char *object_dir);
+
+#endif
object-store.h +0 -0
index d683112fd7..77cb82621a 100644
@@ -84,6 +84,25 @@ struct packed_git {
         char pack_name[FLEX_ARRAY]; /* more */
  };
 
Quoted 3 lines

object-store.h struct packed_git { · line 88

Quoted 2 lines
object-store.h +2 -0
+struct midxed_git {
+       struct midxed_git *next;

Do we really have multiple midx files?

There is one per object directory currently, but you may have one locally and one in each of your alternates. I do need to double-check that we populate this list later in the series. (And I'll remove it from this commit and save it for when it is needed.)

Quoted 24 lines
Quoted 6 lines
object-store.h +5 -0
+
+       int fd;
+
+       const unsigned char *data;
+       size_t data_len;

object-store.h struct packed_git { · line 103

Quoted 10 lines
object-store.h +10 -0
+
+       uint32_t signature;
+       unsigned char version;
+       unsigned char hash_version;
+       unsigned char hash_len;
+       unsigned char num_chunks;
+       uint32_t num_packs;
+       uint32_t num_objects;
+
+       char object_dir[FLEX_ARRAY];

Why do you need to keep object_dir when it could be easily retrieved when the repo is available?

Quoted 5 lines
object-store.h +2 -0
+};
+
  struct raw_object_store {
         /*
          * Path to the repository's object store.
#26 lore raw

Re: [PATCH 06/23] midx: struct midxed_git and 'read' subcommand

In reply to Derrick Stolee

Documentation/git-midx.txt OPTIONS · line 29

Quoted 12 lines · Derrick Stolee
Documentation/git-midx.txt +4 -0
index dcaeb1a91b..919283fdd8 100644
@@ -23,6 +23,11 @@ OPTIONS
        <dir>/packs/multi-pack-index for the current MIDX file, and
        <dir>/packs for the pack-files to index.
 
+read::
+       When given as the verb, read the current MIDX file and output
+       basic information about its contents. Used for debugging
+       purposes only.

On second thought. If you just need a temporary debugging interface, adding a program in t/helper may be a better option. In the end we might still need 'read' to dump a file out, but we should have some stable output format (and json might be a good choice).

That's it I'm done for today. I will continue on the rest some day, hopefully.

Signature · 1 line
Duy
#27 lore raw

Re: [PATCH 06/23] midx: struct midxed_git and 'read' subcommand

Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Derrick Stolee
Documentation/git-midx.txt +0 -0
index dcaeb1a91b..919283fdd8 100644
@@ -23,6 +23,11 @@ OPTIONS
         <dir>/packs/multi-pack-index for the current MIDX file, and
         <dir>/packs for the pack-files to index.
 
Quoted 8 lines

Documentation/git-midx.txt OPTIONS · line 29

Quoted 4 lines
Documentation/git-midx.txt +4 -0
+read::
+       When given as the verb, read the current MIDX file and output
+       basic information about its contents. Used for debugging
+       purposes only.

On second thought. If you just need a temporary debugging interface, adding a program in t/helper may be a better option. In the end we might still need 'read' to dump a file out, but we should have some stable output format (and json might be a good choice).

My intention with this 'read' pattern in the MIDX (and commit-graph) is two-fold:

1. We can test that we are writing the correct data in our test suite. A test-tool builtin would suffice for this purpose.

2. We can help trouble-shoot users who may be having trouble with their MIDX files. Having the subcommand in a plumbing command allows us to do this in the shipped versions of Git.

Maybe this second purpose isn't enough to justify the feature in Git and we should move this to the test-tool, especially with the 'verify' mode coming in a second series. Note that a 'verify' mode doesn't satisfy item (1).

Thanks,
-Stolee

#28 lore raw

Re: [PATCH 06/23] midx: struct midxed_git and 'read' subcommand

Quoted 26 lines · Derrick Stolee
Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Derrick Stolee
Documentation/git-midx.txt +0 -0
index dcaeb1a91b..919283fdd8 100644
@@ -23,6 +23,11 @@ OPTIONS
         <dir>/packs/multi-pack-index for the current MIDX file, and
         <dir>/packs for the pack-files to index.
 
Quoted 8 lines

Documentation/git-midx.txt OPTIONS · line 29

Quoted 4 lines
Documentation/git-midx.txt +4 -0
+read::
+       When given as the verb, read the current MIDX file and output
+       basic information about its contents. Used for debugging
+       purposes only.

On second thought. If you just need a temporary debugging interface, adding a program in t/helper may be a better option. In the end we might still need 'read' to dump a file out, but we should have some stable output format (and json might be a good choice).

My intention with this 'read' pattern in the MIDX (and commit-graph) is two-fold:

1. We can test that we are writing the correct data in our test suite. A test-tool builtin would suffice for this purpose.

Quoted 8 lines

2. We can help trouble-shoot users who may be having trouble with their MIDX files. Having the subcommand in a plumbing command allows us to do this in the shipped versions of Git.

Maybe this second purpose isn't enough to justify the feature in Git and we should move this to the test-tool, especially with the 'verify' mode coming in a second series. Note that a 'verify' mode doesn't satisfy item (1).

Yeah I think normally we just have some "fsck" thing to verify when things go bad. If you need more than that I think you just ask the user to send the .midx to you (with full understanding of potentially revealing confidential info and stuff). It'll be faster than instructing them to "run this command", "ok, run another command".... I thought of suggesting a command to dump the midx file in readable form (like json), but I think if fsck fails then chances of that command successfully dumping may be very low.

Either way, if the command is meant for troubleshooting, I think it should be added at the end when the whole midx file is implemented and understood and we see what we need to troubleshoot. Adding small pieces of changes from patch to patch makes it really hard to see if it helps troubleshooting at all, it just helps the first purpose.

Signature · 1 line
Duy
#29 lore raw

Re: [PATCH 06/23] midx: struct midxed_git and 'read' subcommand

Quoted 42 lines · Duy Nguyen
Quoted 24 lines · Derrick Stolee
Quoted 9 lines · Duy Nguyen
Quoted 8 lines · Derrick Stolee
Documentation/git-midx.txt +0 -0
index dcaeb1a91b..919283fdd8 100644
@@ -23,6 +23,11 @@ OPTIONS
          <dir>/packs/multi-pack-index for the current MIDX file, and
          <dir>/packs for the pack-files to index.
 
Quoted 8 lines

Documentation/git-midx.txt OPTIONS · line 29

Quoted 4 lines
Documentation/git-midx.txt +4 -0
+read::
+       When given as the verb, read the current MIDX file and output
+       basic information about its contents. Used for debugging
+       purposes only.

On second thought. If you just need a temporary debugging interface, adding a program in t/helper may be a better option. In the end we might still need 'read' to dump a file out, but we should have some stable output format (and json might be a good choice).

My intention with this 'read' pattern in the MIDX (and commit-graph) is two-fold:

1. We can test that we are writing the correct data in our test suite. A test-tool builtin would suffice for this purpose.

Quoted 8 lines

2. We can help trouble-shoot users who may be having trouble with their MIDX files. Having the subcommand in a plumbing command allows us to do this in the shipped versions of Git.

Maybe this second purpose isn't enough to justify the feature in Git and we should move this to the test-tool, especially with the 'verify' mode coming in a second series. Note that a 'verify' mode doesn't satisfy item (1).

Yeah I think normally we just have some "fsck" thing to verify when things go bad. If you need more than that I think you just ask the user to send the .midx to you (with full understanding of potentially revealing confidential info and stuff). It'll be faster than instructing them to "run this command", "ok, run another command".... I thought of suggesting a command to dump the midx file in readable form (like json), but I think if fsck fails then chances of that command successfully dumping may be very low.

Quoted 5 lines

Either way, if the command is meant for troubleshooting, I think it should be added at the end when the whole midx file is implemented and understood and we see what we need to troubleshoot. Adding small pieces of changes from patch to patch makes it really hard to see if it helps troubleshooting at all, it just helps the first purpose.

I'll abandon point (2) for the later 'verify' patch series. Adding the test helper early allows tests to demonstrate that each patch does the right thing, and that we don't miss testing something.

Thanks,
-Stolee

#30 lore raw

PATCH 7/23 midx: expand test data

In reply to Derrick Stolee

As we build the multi-pack-index file format, we want to test the format on real repoasitories. Add tests to t5319-midx.sh that create repository data including multiple packfiles with both version 1 and version 2 formats.

The current 'git midx write' command will always write the same file with no "real" data. This will be expanded in future commits, along with the test expectations.

---
 t/t5319-midx.sh | 101 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 101 insertions(+)
t/t5319-midx.sh +101 -0
index e78514d8e9..2c25a69744 100755
@@ -14,8 +14,109 @@ midx_read_expect() {
 
 test_expect_success 'write midx with no packs' '
 	git midx --object-dir=. write &&
+	test_when_finished rm pack/multi-pack-index &&
 	test_path_is_file pack/multi-pack-index &&
 	midx_read_expect
 '
 
+test_expect_success 'create objects' '
+	for i in `test_seq 1 5`
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 > wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		i=$(expr $i + 1) || return 1
+	done &&
+	{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+	git update-index --add file_101 &&
+	tree=$(git write-tree) &&
+	commit=$(git commit-tree $tree </dev/null) && {
+	echo $tree &&
+	git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+	} >obj-list &&
+	git update-ref HEAD $commit
+'
+
+test_expect_success 'write midx with one v1 pack' '
+	pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
+	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
+	git midx --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'write midx with one v2 pack' '
+	pack=$(git pack-objects --index-version=2,0x40 pack/test <obj-list) &&
+	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx &&
+	git midx --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'Add more objects' '
+	for i in `test_seq 6 5`
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 > wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		i=$(expr $i + 1) || return 1
+	done &&
+	{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+	git update-index --add file_101 &&
+	tree=$(git write-tree) &&
+	commit=$(git commit-tree $tree -p HEAD</dev/null) && {
+	echo $tree &&
+	git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+	} >obj-list2 &&
+	git update-ref HEAD $commit
+'
+
+test_expect_success 'write midx with two packs' '
+	pack1=$(git pack-objects --index-version=1 pack/test-1 <obj-list) &&
+	pack2=$(git pack-objects --index-version=1 pack/test-2 <obj-list2) &&
+	git midx --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'Add more packs' '
+	for j in `test_seq 1 10`
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 > wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+		git update-index --add file_101 &&
+		tree=$(git write-tree) &&
+		commit=$(git commit-tree $tree -p HEAD</dev/null) && {
+		echo $tree &&
+		git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+		} >obj-list &&
+		git update-ref HEAD $commit &&
+		git pack-objects --index-version=2 test-pack <obj-list &&
+		i=$(expr $i + 1) || return 1 &&
+		j=$(expr $j + 1) || return 1
+	done
+'
+
+test_expect_success 'write midx with twelve packs' '
+	git midx --object-dir=. write &&
+	midx_read_expect
+'
+
 test_done
Signature · 1 line
2.18.0.rc1
#31 lore raw

PATCH 8/23 midx: read packfiles from pack directory

In reply to Derrick Stolee

When constructing a multi-pack-index file for a given object directory, read the files within the enclosed pack directory and find matches that end with ".idx" and find the correct paired packfile using add_packed_git().

---
 midx.c          | 51 +++++++++++++++++++++++++++++++++++++++++++++++--
 t/t5319-midx.sh | 15 ++++++++-------
 2 files changed, 57 insertions(+), 9 deletions(-)
midx.c +49 -2
index fa18770f1d..9fb89c80a2 100644
@@ -102,10 +102,15 @@ static size_t write_midx_header(struct hashfile *f,
 int write_midx_file(const char *object_dir)
 {
 	unsigned char num_chunks = 0;
-	uint32_t num_packs = 0;
 	char *midx_name;
 	struct hashfile *f;
 	struct lock_file lk;
+	struct packed_git **packs = NULL;
+	uint32_t i, nr_packs = 0, alloc_packs = 0;
+	DIR *dir;
+	struct dirent *de;
+	struct strbuf pack_dir = STRBUF_INIT;
+	size_t pack_dir_len;
 
 	midx_name = get_midx_filename(object_dir);
 	if (safe_create_leading_directories(midx_name)) {
@@ -114,14 +119,56 @@ int write_midx_file(const char *object_dir)
 			  midx_name);
 	}
 
+	strbuf_addf(&pack_dir, "%s/pack", object_dir);
+	dir = opendir(pack_dir.buf);
+
+	if (!dir) {
+		error_errno("unable to open pack directory: %s",
+			    pack_dir.buf);
+		strbuf_release(&pack_dir);
+		return 1;
+	}
+
+	strbuf_addch(&pack_dir, '/');
+	pack_dir_len = pack_dir.len;
+	ALLOC_ARRAY(packs, alloc_packs);
+	while ((de = readdir(dir)) != NULL) {
+		if (is_dot_or_dotdot(de->d_name))
+			continue;
+
+		if (ends_with(de->d_name, ".idx")) {
+			ALLOC_GROW(packs, nr_packs + 1, alloc_packs);
+
+			strbuf_setlen(&pack_dir, pack_dir_len);
+			strbuf_addstr(&pack_dir, de->d_name);
+
+			packs[nr_packs] = add_packed_git(pack_dir.buf,
+							 pack_dir.len,
+							 0);
+			if (!packs[nr_packs])
+				warning("failed to add packfile '%s'",
+					pack_dir.buf);
+			else
+				nr_packs++;
+		}
+	}
+	closedir(dir);
+	strbuf_release(&pack_dir);
+
 	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
 	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
 	FREE_AND_NULL(midx_name);
 
-	write_midx_header(f, num_chunks, num_packs);
+	write_midx_header(f, num_chunks, nr_packs);
 
 	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
 	commit_lock_file(&lk);
 
+	for (i = 0; i < nr_packs; i++) {
+		close_pack(packs[i]);
+		FREE_AND_NULL(packs[i]);
+	}
+
+	FREE_AND_NULL(packs);
 	return 0;
 }
t/t5319-midx.sh +8 -7
index 2c25a69744..abe545c7c4 100755
@@ -4,8 +4,9 @@ test_description='multi-pack-indexes'
 . ./test-lib.sh
 
 midx_read_expect() {
+	NUM_PACKS=$1
 	cat >expect <<- EOF
-	header: 4d494458 1 1 0 0
+	header: 4d494458 1 1 0 $NUM_PACKS
 	object_dir: .
 	EOF
 	git midx read --object-dir=. >actual &&
@@ -16,7 +17,7 @@ test_expect_success 'write midx with no packs' '
 	git midx --object-dir=. write &&
 	test_when_finished rm pack/multi-pack-index &&
 	test_path_is_file pack/multi-pack-index &&
-	midx_read_expect
+	midx_read_expect 0
 '
 
 test_expect_success 'create objects' '
@@ -47,14 +48,14 @@ test_expect_success 'write midx with one v1 pack' '
 	pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
 	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
 	git midx --object-dir=. write &&
-	midx_read_expect
+	midx_read_expect 1
 '
 
 test_expect_success 'write midx with one v2 pack' '
 	pack=$(git pack-objects --index-version=2,0x40 pack/test <obj-list) &&
 	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx &&
 	git midx --object-dir=. write &&
-	midx_read_expect
+	midx_read_expect 1
 '
 
 test_expect_success 'Add more objects' '
@@ -85,7 +86,7 @@ test_expect_success 'write midx with two packs' '
 	pack1=$(git pack-objects --index-version=1 pack/test-1 <obj-list) &&
 	pack2=$(git pack-objects --index-version=1 pack/test-2 <obj-list2) &&
 	git midx --object-dir=. write &&
-	midx_read_expect
+	midx_read_expect 2
 '
 
 test_expect_success 'Add more packs' '
@@ -108,7 +109,7 @@ test_expect_success 'Add more packs' '
 		git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
 		} >obj-list &&
 		git update-ref HEAD $commit &&
-		git pack-objects --index-version=2 test-pack <obj-list &&
+		git pack-objects --index-version=2 pack/test-pack <obj-list &&
 		i=$(expr $i + 1) || return 1 &&
 		j=$(expr $j + 1) || return 1
 	done
@@ -116,7 +117,7 @@ test_expect_success 'Add more packs' '
 
 test_expect_success 'write midx with twelve packs' '
 	git midx --object-dir=. write &&
-	midx_read_expect
+	midx_read_expect 12
 '
 
 test_done
Signature · 1 line
2.18.0.rc1
#32 lore raw

Re: [PATCH 08/23] midx: read packfiles from pack directory

midx.c int write_midx_file(const char *object_dir) · line 127

Quoted 10 lines · Derrick Stolee
midx.c +6 -0
@@ -114,14 +119,56 @@ int write_midx_file(const char *object_dir)
                          midx_name);
        }
 
+       strbuf_addf(&pack_dir, "%s/pack", object_dir);
+       dir = opendir(pack_dir.buf);
+
+       if (!dir) {
+               error_errno("unable to open pack directory: %s",
+                           pack_dir.buf);

_()

Quoted 19 lines
midx.c +19 -0
+               strbuf_release(&pack_dir);
+               return 1;
+       }
+
+       strbuf_addch(&pack_dir, '/');
+       pack_dir_len = pack_dir.len;
+       ALLOC_ARRAY(packs, alloc_packs);
+       while ((de = readdir(dir)) != NULL) {
+               if (is_dot_or_dotdot(de->d_name))
+                       continue;
+
+               if (ends_with(de->d_name, ".idx")) {
+                       ALLOC_GROW(packs, nr_packs + 1, alloc_packs);
+
+                       strbuf_setlen(&pack_dir, pack_dir_len);
+                       strbuf_addstr(&pack_dir, de->d_name);
+
+                       packs[nr_packs] = add_packed_git(pack_dir.buf,
+                                                        pack_dir.len,

midx.c int write_midx_file(const char *object_dir) · line 156

Quoted 10 lines
midx.c +10 -0
+                                                        0);
+                       if (!packs[nr_packs])
+                               warning("failed to add packfile '%s'",
+                                       pack_dir.buf);
+                       else
+                               nr_packs++;
+               }
+       }
+       closedir(dir);
+       strbuf_release(&pack_dir);

Can we refactor and share this scanning-for-packs code with packfile.c? I'm pretty sure it does something similar in there.

midx.c int write_midx_file(const char *object_dir) · line 162

Quoted 2 lines
midx.c +1 -1
-       write_midx_header(f, num_chunks, num_packs);
+       write_midx_header(f, num_chunks, nr_packs);

Hmm.. could have stuck to one name from the beginning...

Signature · 1 line
Duy
#33 lore raw

PATCH 1/1 packfile: generalize pack directory list

In anticipation of sharing the pack directory listing with the multi-pack-index, generalize prepare_packed_git_one() into for_each_file_in_pack_dir().

---

Duy,

I think this is what you mean by sharing code between packfile.c and midx.c for reading the packfiles from a pack directory. This does make the code in midx.c much simpler. Is this change worth it?

This patch could stand on its own, or can be incorporated into the next version of the MIDX series.

Thanks,
-Stolee

 packfile.c | 103 +++++++++++++++++++++++++++++++++--------------------
 packfile.h |   6 ++++
 2 files changed, 71 insertions(+), 38 deletions(-)
packfile.c +65 -38
index 7cd45aa4b2..db61c8813b 100644
@@ -738,13 +738,14 @@ static void report_pack_garbage(struct string_list *list)
 	report_helper(list, seen_bits, first, list->nr);
 }
 
-static void prepare_packed_git_one(struct repository *r, char *objdir, int local)
+void for_each_file_in_pack_dir(const char *objdir,
+			       each_file_in_pack_dir_fn fn,
+			       void *data)
 {
 	struct strbuf path = STRBUF_INIT;
 	size_t dirnamelen;
 	DIR *dir;
 	struct dirent *de;
-	struct string_list garbage = STRING_LIST_INIT_DUP;
 
 	strbuf_addstr(&path, objdir);
 	strbuf_addstr(&path, "/pack");
@@ -759,53 +760,79 @@ static void prepare_packed_git_one(struct repository *r, char *objdir, int local
 	strbuf_addch(&path, '/');
 	dirnamelen = path.len;
 	while ((de = readdir(dir)) != NULL) {
-		struct packed_git *p;
-		size_t base_len;
-
 		if (is_dot_or_dotdot(de->d_name))
 			continue;
 
 		strbuf_setlen(&path, dirnamelen);
 		strbuf_addstr(&path, de->d_name);
 
-		base_len = path.len;
-		if (strip_suffix_mem(path.buf, &base_len, ".idx")) {
-			/* Don't reopen a pack we already have. */
-			for (p = r->objects->packed_git; p;
-			     p = p->next) {
-				size_t len;
-				if (strip_suffix(p->pack_name, ".pack", &len) &&
-				    len == base_len &&
-				    !memcmp(p->pack_name, path.buf, len))
-					break;
-			}
-			if (p == NULL &&
-			    /*
-			     * See if it really is a valid .idx file with
-			     * corresponding .pack file that we can map.
-			     */
-			    (p = add_packed_git(path.buf, path.len, local)) != NULL)
-				install_packed_git(r, p);
-		}
-
-		if (!report_garbage)
-			continue;
-
-		if (ends_with(de->d_name, ".idx") ||
-		    ends_with(de->d_name, ".pack") ||
-		    ends_with(de->d_name, ".bitmap") ||
-		    ends_with(de->d_name, ".keep") ||
-		    ends_with(de->d_name, ".promisor"))
-			string_list_append(&garbage, path.buf);
-		else
-			report_garbage(PACKDIR_FILE_GARBAGE, path.buf);
+		fn(path.buf, path.len, de->d_name, data);
 	}
+
 	closedir(dir);
-	report_pack_garbage(&garbage);
-	string_list_clear(&garbage, 0);
 	strbuf_release(&path);
 }
 
+struct prepare_pack_data
+{
+	struct repository *r;
+	struct string_list *garbage;
+	int local;
+};
+
+static void prepare_pack(const char *full_name, size_t full_name_len, const char *file_name, void *_data)
+{
+	struct prepare_pack_data *data = (struct prepare_pack_data *)_data;
+	struct packed_git *p;
+	size_t base_len = full_name_len;
+
+	if (strip_suffix_mem(full_name, &base_len, ".idx")) {
+		/* Don't reopen a pack we already have. */
+		for (p = data->r->objects->packed_git; p; p = p->next) {
+			size_t len;
+			if (strip_suffix(p->pack_name, ".pack", &len) &&
+			    len == base_len &&
+			    !memcmp(p->pack_name, full_name, len))
+				break;
+		}
+
+		if (p == NULL &&
+		    /*
+		     * See if it really is a valid .idx file with
+		     * corresponding .pack file that we can map.
+		     */
+		    (p = add_packed_git(full_name, full_name_len, data->local)) != NULL)
+			install_packed_git(data->r, p);
+	}
+
+	if (!report_garbage)
+	       return;
+
+	if (ends_with(file_name, ".idx") ||
+	    ends_with(file_name, ".pack") ||
+	    ends_with(file_name, ".bitmap") ||
+	    ends_with(file_name, ".keep") ||
+	    ends_with(file_name, ".promisor"))
+		string_list_append(data->garbage, full_name);
+	else
+		report_garbage(PACKDIR_FILE_GARBAGE, full_name);
+}
+
+static void prepare_packed_git_one(struct repository *r, char *objdir, int local)
+{
+	struct prepare_pack_data data;
+	struct string_list garbage = STRING_LIST_INIT_DUP;
+
+	data.r = r;
+	data.garbage = &garbage;
+	data.local = local;
+
+	for_each_file_in_pack_dir(objdir, prepare_pack, &data);
+
+	report_pack_garbage(data.garbage);
+	string_list_clear(data.garbage, 0);
+}
+
 static void prepare_packed_git(struct repository *r);
 /*
  * Give a fast, rough count of the number of objects in the repository. This
packfile.h +6 -0
index e0a38aba93..d2ad30300a 100644
@@ -28,6 +28,12 @@ extern char *sha1_pack_index_name(const unsigned char *sha1);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
 
+typedef void each_file_in_pack_dir_fn(const char *full_path, size_t full_path_len,
+				      const char *file_pach, void *data);
+void for_each_file_in_pack_dir(const char *objdir,
+			       each_file_in_pack_dir_fn fn,
+			       void *data);
+
 /* A hook to report invalid files in pack directory */
 #define PACKDIR_FILE_PACK 1
 #define PACKDIR_FILE_IDX 2
Signature · 1 line
2.18.0.rc1
#34 lore raw

PATCH 9/23 midx: write pack names in chunk

In reply to Derrick Stolee

The multi-pack-index (MIDX) needs to track which pack-files are covered by the MIDX file. Store these in our first required chunk. Since filenames are not well structured, add padding to keep good alignment in later chunks.

Modify the 'git midx read' subcommand to output the existence of the pack-file name chunk. Modify t5319-midx.sh to reflect this new output and the new expected number of chunks.

Defense in depth: A pattern we are using in the multi-pack-index feature is to verify the data as we write it. We want to ensure we never write invalid data to the multi-pack-index. There are many checks during the write of a MIDX file that double-check that the values we are writing fit the format definitions. If any value is incorrect, then we notice before writing invalid data. This mainly helps developers while working on the feature, but it can also identify issues that only appear when dealing with very large data sets. These large sets are hard to encode into test cases.

---
 Documentation/technical/pack-format.txt |   6 +
 builtin/midx.c                          |   7 +
 midx.c                                  | 176 +++++++++++++++++++++++-
 object-store.h                          |   2 +
 t/t5319-midx.sh                         |   3 +-
 5 files changed, 188 insertions(+), 6 deletions(-)
Documentation/technical/pack-format.txt +6 -0
index 17666b4bfc..2b37be7b33 100644
@@ -296,6 +296,12 @@ CHUNK LOOKUP:
 
 CHUNK DATA:
 
+	Packfile Names (ID: {'P', 'N', 'A', 'M'})
+	    Stores the packfile names as concatenated, null-terminated strings.
+	    Packfiles must be listed in lexicographic order for fast lookups by
+	    name. This is the only chunk not guaranteed to be a multiple of four
+	    bytes in length, so should be the last chunk for alignment reasons.
+
 	(This section intentionally left incomplete.)
 
 TRAILER:
builtin/midx.c +7 -0
index c7002f664a..fe56560853 100644
@@ -28,6 +28,13 @@ static int read_midx_file(const char *object_dir)
 	       m->num_chunks,
 	       m->num_packs);
 
+	printf("chunks:");
+
+	if (m->chunk_pack_names)
+		printf(" pack_names");
+
+	printf("\n");
+
 	printf("object_dir: %s\n", m->object_dir);
 
 	return 0;
midx.c +171 -5

245 lines not on this page · raw message

object-store.h +2 -0
index 77cb82621a..199cf4bd44 100644
@@ -100,6 +100,8 @@ struct midxed_git {
 	uint32_t num_packs;
 	uint32_t num_objects;
 
+	const unsigned char *chunk_pack_names;
+
 	char object_dir[FLEX_ARRAY];
 };
 
t/t5319-midx.sh +2 -1
index abe545c7c4..fdf4f84a90 100755
@@ -6,7 +6,8 @@ test_description='multi-pack-indexes'
 midx_read_expect() {
 	NUM_PACKS=$1
 	cat >expect <<- EOF
-	header: 4d494458 1 1 0 $NUM_PACKS
+	header: 4d494458 1 1 1 $NUM_PACKS
+	chunks: pack_names
 	object_dir: .
 	EOF
 	git midx read --object-dir=. >actual &&
Signature · 1 line
2.18.0.rc1
#35 lore raw

Re: [PATCH 09/23] midx: write pack names in chunk

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 85

Quoted 7 lines · Derrick Stolee
midx.c +3 -0
@@ -74,6 +80,31 @@ struct midxed_git *load_midxed_git(const char *object_dir)
        m->num_chunks = *(m->data + 6);
        m->num_packs = get_be32(m->data + 8);
 
+       for (i = 0; i < m->num_chunks; i++) {
+               uint32_t chunk_id = get_be32(m->data + 12 + MIDX_CHUNKLOOKUP_WIDTH * i);
+               uint64_t chunk_offset = get_be64(m->data + 16 + MIDX_CHUNKLOOKUP_WIDTH * i);

Would be good to reduce magic numbers like 12 and 16, I think you have some header length constants for those already.

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 93

Quoted 7 lines
midx.c +7 -0
+               switch (chunk_id) {
+                       case MIDX_CHUNKID_PACKNAMES:
+                               m->chunk_pack_names = m->data + chunk_offset;
+                               break;
+
+                       case 0:
+                               die("terminating MIDX chunk id appears earlier than expected");

_()

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 100

Quoted 7 lines
midx.c +7 -0
+                               break;
+
+                       default:
+                               /*
+                                * Do nothing on unrecognized chunks, allowing future
+                                * extensions to add optional chunks.
+                                */

I wrote about the chunk term reminding me of PNG format then deleted it. But it may help to do similar to PNG here. The first letter can let us know if the chunk is optional and can be safely ignored. E.g. uppercase first letter cannot be ignored, lowercase go wild.

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 106

Quoted 6 lines
midx.c +6 -0
+                               break;
+               }
+       }
+
+       if (!m->chunk_pack_names)
+               die("MIDX missing required pack-name chunk");

_()

midx.c static size_t write_midx_header(struct hashfile *f, · line 134

Quoted 10 lines
midx.c +3 -0
+
        return m;
 
 cleanup_fail:
@@ -99,18 +130,88 @@ static size_t write_midx_header(struct hashfile *f,
        return MIDX_HEADER_SIZE;
 }
 
+struct pack_pair {
+       uint32_t pack_int_id;

can this be just pack_id?

Quoted 18 lines
midx.c +18 -0
+       char *pack_name;
+};
+
+static int pack_pair_compare(const void *_a, const void *_b)
+{
+       struct pack_pair *a = (struct pack_pair *)_a;
+       struct pack_pair *b = (struct pack_pair *)_b;
+       return strcmp(a->pack_name, b->pack_name);
+}
+
+static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *perm)
+{
+       uint32_t i;
+       struct pack_pair *pairs;
+
+       ALLOC_ARRAY(pairs, nr_packs);
+
+       for (i = 0; i < nr_packs; i++) {

midx.c static size_t write_midx_header(struct hashfile *f, · line 162

Quoted 10 lines
midx.c +10 -0
+               pairs[i].pack_int_id = i;
+               pairs[i].pack_name = pack_names[i];
+       }
+
+       QSORT(pairs, nr_packs, pack_pair_compare);
+
+       for (i = 0; i < nr_packs; i++) {
+               pack_names[i] = pairs[i].pack_name;
+               perm[pairs[i].pack_int_id] = i;
+       }

pairs[] is leaked?

Quoted 10 lines
midx.c +10 -0
+}
+
+static size_t write_midx_pack_names(struct hashfile *f,
+                                   char **pack_names,
+                                   uint32_t num_packs)
+{
+       uint32_t i;
+       unsigned char padding[MIDX_CHUNK_ALIGNMENT];
+       size_t written = 0;
+

midx.c static size_t write_midx_header(struct hashfile *f, · line 182

Quoted 10 lines
midx.c +10 -0
+       for (i = 0; i < num_packs; i++) {
+               size_t writelen = strlen(pack_names[i]) + 1;
+
+               if (i && strcmp(pack_names[i], pack_names[i - 1]) <= 0)
+                       BUG("incorrect pack-file order: %s before %s",
+                           pack_names[i - 1],
+                           pack_names[i]);
+
+               hashwrite(f, pack_names[i], writelen);
+               written += writelen;

side note. This pattern happens a lot. It may be a good idea to make hashwrite() return writelen so we can just write

written += hashwrite(f, ..., writelen);

Quoted 23 lines
midx.c +16 -1
+       }
+
+       /* add padding to be aligned */
+       i = MIDX_CHUNK_ALIGNMENT - (written % MIDX_CHUNK_ALIGNMENT);
+       if (i < MIDX_CHUNK_ALIGNMENT) {
+               bzero(padding, sizeof(padding));
+               hashwrite(f, padding, i);
+               written += i;
+       }
+
+       return written;
+}
+
 int write_midx_file(const char *object_dir)
 {
-       unsigned char num_chunks = 0;
+       unsigned char cur_chunk, num_chunks = 0;
        char *midx_name;
        struct hashfile *f;
        struct lock_file lk;
        struct packed_git **packs = NULL;
+       char **pack_names = NULL;
+       uint32_t *pack_perm;

midx.c static size_t write_midx_header(struct hashfile *f, · line 214

Quoted 10 lines
midx.c +5 -0
        uint32_t i, nr_packs = 0, alloc_packs = 0;
+       uint32_t alloc_pack_names = 0;
        DIR *dir;
        struct dirent *de;
        struct strbuf pack_dir = STRBUF_INIT;
        size_t pack_dir_len;
+       uint64_t pack_name_concat_len = 0;
+       uint64_t written = 0;
+       uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
+       uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];

This long list of local vars may be a good indicator that this function needs split up into smaller ones.

Quoted 74 lines
midx.c +39 -4
        midx_name = get_midx_filename(object_dir);
        if (safe_create_leading_directories(midx_name)) {
@@ -132,12 +233,14 @@ int write_midx_file(const char *object_dir)
        strbuf_addch(&pack_dir, '/');
        pack_dir_len = pack_dir.len;
        ALLOC_ARRAY(packs, alloc_packs);
+       ALLOC_ARRAY(pack_names, alloc_pack_names);
        while ((de = readdir(dir)) != NULL) {
                if (is_dot_or_dotdot(de->d_name))
                        continue;
 
                if (ends_with(de->d_name, ".idx")) {
                        ALLOC_GROW(packs, nr_packs + 1, alloc_packs);
+                       ALLOC_GROW(pack_names, nr_packs + 1, alloc_pack_names);
 
                        strbuf_setlen(&pack_dir, pack_dir_len);
                        strbuf_addstr(&pack_dir, de->d_name);
@@ -145,21 +248,83 @@ int write_midx_file(const char *object_dir)
                        packs[nr_packs] = add_packed_git(pack_dir.buf,
                                                         pack_dir.len,
                                                         0);
-                       if (!packs[nr_packs])
+                       if (!packs[nr_packs]) {
                                warning("failed to add packfile '%s'",
                                        pack_dir.buf);
-                       else
-                               nr_packs++;
+                               continue;
+                       }
+
+                       pack_names[nr_packs] = xstrdup(de->d_name);
+                       pack_name_concat_len += strlen(de->d_name) + 1;
+                       nr_packs++;
                }
        }
+
        closedir(dir);
        strbuf_release(&pack_dir);
 
+       if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT)
+               pack_name_concat_len += MIDX_CHUNK_ALIGNMENT -
+                                       (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT);
+
+       ALLOC_ARRAY(pack_perm, nr_packs);
+       sort_packs_by_name(pack_names, nr_packs, pack_perm);
+
        hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
        f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
        FREE_AND_NULL(midx_name);
 
-       write_midx_header(f, num_chunks, nr_packs);
+       cur_chunk = 0;
+       num_chunks = 1;
+
+       written = write_midx_header(f, num_chunks, nr_packs);
+
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES;
+       chunk_offsets[cur_chunk] = written + (num_chunks + 1) * MIDX_CHUNKLOOKUP_WIDTH;
+
+       cur_chunk++;
+       chunk_ids[cur_chunk] = 0;
+       chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + pack_name_concat_len;
+
+       for (i = 0; i <= num_chunks; i++) {
+               if (i && chunk_offsets[i] < chunk_offsets[i - 1])
+                       BUG("incorrect chunk offsets: %"PRIu64" before %"PRIu64,
+                           chunk_offsets[i - 1],
+                           chunk_offsets[i]);
+
+               if (chunk_offsets[i] % MIDX_CHUNK_ALIGNMENT)
+                       BUG("chunk offset %"PRIu64" is not properly aligned",
+                           chunk_offsets[i]);
+

midx.c int write_midx_file(const char *object_dir) · line 308

Quoted 10 lines
midx.c +10 -0
+               hashwrite_be32(f, chunk_ids[i]);
+               hashwrite_be32(f, chunk_offsets[i] >> 32);
+               hashwrite_be32(f, chunk_offsets[i]);
+
+               written += MIDX_CHUNKLOOKUP_WIDTH;
+       }
+
+       for (i = 0; i < num_chunks; i++) {
+               if (written != chunk_offsets[i])
+                       BUG("inccrrect chunk offset (%"PRIu64" != %"PRIu64") for chunk id %"PRIx32,

incorrect

Quoted 23 lines
midx.c +19 -0
+                           chunk_offsets[i],
+                           written,
+                           chunk_ids[i]);
+
+               switch (chunk_ids[i]) {
+                       case MIDX_CHUNKID_PACKNAMES:
+                               written += write_midx_pack_names(f, pack_names, nr_packs);
+                               break;
+
+                       default:
+                               BUG("trying to write unknown chunk id %"PRIx32,
+                                   chunk_ids[i]);
+               }
+       }
+
+       if (written != chunk_offsets[num_chunks])
+               BUG("incorrect final offset %"PRIu64" != %"PRIu64,
+                   written,
+                   chunk_offsets[num_chunks]);
 
        finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
        commit_lock_file(&lk);

midx.c int write_midx_file(const char *object_dir) · line 338

Quoted 4 lines
midx.c +1 -0
@@ -170,5 +335,6 @@ int write_midx_file(const char *object_dir)
        }
 
        FREE_AND_NULL(packs);
+       FREE_AND_NULL(pack_names);

What about the strings in this array? I think they are xstrdup() but I didn't spot them being freed.

And maybe just use string_list...

Quoted 2 lines
midx.c +0 -0
        return 0;
 }
Signature · 1 line
Duy
#36 lore raw

Re: [PATCH 09/23] midx: write pack names in chunk

Quoted 11 lines · Duy Nguyen

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 85

Quoted 7 lines · Derrick Stolee
midx.c +3 -0
@@ -74,6 +80,31 @@ struct midxed_git *load_midxed_git(const char *object_dir)
         m->num_chunks = *(m->data + 6);
         m->num_packs = get_be32(m->data + 8);
 
+       for (i = 0; i < m->num_chunks; i++) {
+               uint32_t chunk_id = get_be32(m->data + 12 + MIDX_CHUNKLOOKUP_WIDTH * i);
+               uint64_t chunk_offset = get_be64(m->data + 16 + MIDX_CHUNKLOOKUP_WIDTH * i);

Would be good to reduce magic numbers like 12 and 16, I think you have some header length constants for those already.

Quoted 8 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 93

Quoted 7 lines
midx.c +7 -0
+               switch (chunk_id) {
+                       case MIDX_CHUNKID_PACKNAMES:
+                               m->chunk_pack_names = m->data + chunk_offset;
+                               break;
+
+                       case 0:
+                               die("terminating MIDX chunk id appears earlier than expected");

_()

This die() and others like it are not marked for translation on purpose, as they should never be seen by an end-user.

Quoted 8 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 100

Quoted 8 lines
midx.c +7 -0
+                               break;
+
+                       default:
+                               /*
+                                * Do nothing on unrecognized chunks, allowing future
+                                * extensions to add optional chunks.
+                                */
Quoted 4 lines

I wrote about the chunk term reminding me of PNG format then deleted it. But it may help to do similar to PNG here. The first letter can let us know if the chunk is optional and can be safely ignored. E.g. uppercase first letter cannot be ignored, lowercase go wild.

That's an interesting way to think about it. That way you could add a new "required" chunk and earlier versions could die() realizing they don't know how to parse that required chunk.

I think for this format, we should update the file version value when a required chunk is needed.

Quoted 12 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 106

Quoted 7 lines
midx.c +6 -0
+                               break;
+               }
+       }
+
+       if (!m->chunk_pack_names)
+               die("MIDX missing required pack-name chunk");

_()

Quoted 3 lines
midx.c +1 -0
+
         return m;
 
Quoted 8 lines

midx.c static size_t write_midx_header(struct hashfile *f, · line 134

Quoted 7 lines
midx.c +2 -0
  cleanup_fail:
@@ -99,18 +130,88 @@ static size_t write_midx_header(struct hashfile *f,
         return MIDX_HEADER_SIZE;
  }
 
+struct pack_pair {
+       uint32_t pack_int_id;

can this be just pack_id?

Since packfiles are usually named pack-{hash}.pack, I chose to be specific here.

Quoted 22 lines
Quoted 22 lines
midx.c +21 -0
+       char *pack_name;
+};
+
+static int pack_pair_compare(const void *_a, const void *_b)
+{
+       struct pack_pair *a = (struct pack_pair *)_a;
+       struct pack_pair *b = (struct pack_pair *)_b;
+       return strcmp(a->pack_name, b->pack_name);
+}
+
+static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *perm)
+{
+       uint32_t i;
+       struct pack_pair *pairs;
+
+       ALLOC_ARRAY(pairs, nr_packs);
+
+       for (i = 0; i < nr_packs; i++) {
+               pairs[i].pack_int_id = i;
+               pairs[i].pack_name = pack_names[i];
+       }
Quoted 8 lines

midx.c static size_t write_midx_header(struct hashfile *f, · line 162

Quoted 7 lines
midx.c +7 -0
+
+       QSORT(pairs, nr_packs, pack_pair_compare);
+
+       for (i = 0; i < nr_packs; i++) {
+               pack_names[i] = pairs[i].pack_name;
+               perm[pairs[i].pack_int_id] = i;
+       }

pairs[] is leaked?

Good catch!

Quoted 24 lines
Quoted 11 lines
midx.c +10 -0
+}
+
+static size_t write_midx_pack_names(struct hashfile *f,
+                                   char **pack_names,
+                                   uint32_t num_packs)
+{
+       uint32_t i;
+       unsigned char padding[MIDX_CHUNK_ALIGNMENT];
+       size_t written = 0;
+

midx.c static size_t write_midx_header(struct hashfile *f, · line 182

Quoted 10 lines
midx.c +10 -0
+       for (i = 0; i < num_packs; i++) {
+               size_t writelen = strlen(pack_names[i]) + 1;
+
+               if (i && strcmp(pack_names[i], pack_names[i - 1]) <= 0)
+                       BUG("incorrect pack-file order: %s before %s",
+                           pack_names[i - 1],
+                           pack_names[i]);
+
+               hashwrite(f, pack_names[i], writelen);
+               written += writelen;

side note. This pattern happens a lot. It may be a good idea to make hashwrite() return writelen so we can just write

Quoted 1 line

written += hashwrite(f, ..., writelen);

If I change the prototype of hashwrite(), will other callers get warnings about not inspecting the return value (for some build options on some platforms)?

Quoted 28 lines
Quoted 28 lines
midx.c +17 -1
+       }
+
+       /* add padding to be aligned */
+       i = MIDX_CHUNK_ALIGNMENT - (written % MIDX_CHUNK_ALIGNMENT);
+       if (i < MIDX_CHUNK_ALIGNMENT) {
+               bzero(padding, sizeof(padding));
+               hashwrite(f, padding, i);
+               written += i;
+       }
+
+       return written;
+}
+
  int write_midx_file(const char *object_dir)
  {
-       unsigned char num_chunks = 0;
+       unsigned char cur_chunk, num_chunks = 0;
         char *midx_name;
         struct hashfile *f;
         struct lock_file lk;
         struct packed_git **packs = NULL;
+       char **pack_names = NULL;
+       uint32_t *pack_perm;
         uint32_t i, nr_packs = 0, alloc_packs = 0;
+       uint32_t alloc_pack_names = 0;
         DIR *dir;
         struct dirent *de;
Quoted 8 lines

midx.c static size_t write_midx_header(struct hashfile *f, · line 214

Quoted 6 lines
midx.c +4 -0
         struct strbuf pack_dir = STRBUF_INIT;
         size_t pack_dir_len;
+       uint64_t pack_name_concat_len = 0;
+       uint64_t written = 0;
+       uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
+       uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];

This long list of local vars may be a good indicator that this function needs split up into smaller ones.

Or the data needs to be grouped into structs, which is happening in my local branch due to the for_each_file_in_pack_dir() method.

Quoted 110 lines
Quoted 73 lines
midx.c +39 -4
         midx_name = get_midx_filename(object_dir);
         if (safe_create_leading_directories(midx_name)) {
@@ -132,12 +233,14 @@ int write_midx_file(const char *object_dir)
         strbuf_addch(&pack_dir, '/');
         pack_dir_len = pack_dir.len;
         ALLOC_ARRAY(packs, alloc_packs);
+       ALLOC_ARRAY(pack_names, alloc_pack_names);
         while ((de = readdir(dir)) != NULL) {
                 if (is_dot_or_dotdot(de->d_name))
                         continue;
 
                 if (ends_with(de->d_name, ".idx")) {
                         ALLOC_GROW(packs, nr_packs + 1, alloc_packs);
+                       ALLOC_GROW(pack_names, nr_packs + 1, alloc_pack_names);
 
                         strbuf_setlen(&pack_dir, pack_dir_len);
                         strbuf_addstr(&pack_dir, de->d_name);
@@ -145,21 +248,83 @@ int write_midx_file(const char *object_dir)
                         packs[nr_packs] = add_packed_git(pack_dir.buf,
                                                          pack_dir.len,
                                                          0);
-                       if (!packs[nr_packs])
+                       if (!packs[nr_packs]) {
                                 warning("failed to add packfile '%s'",
                                         pack_dir.buf);
-                       else
-                               nr_packs++;
+                               continue;
+                       }
+
+                       pack_names[nr_packs] = xstrdup(de->d_name);
+                       pack_name_concat_len += strlen(de->d_name) + 1;
+                       nr_packs++;
                 }
         }
+
         closedir(dir);
         strbuf_release(&pack_dir);
 
+       if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT)
+               pack_name_concat_len += MIDX_CHUNK_ALIGNMENT -
+                                       (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT);
+
+       ALLOC_ARRAY(pack_perm, nr_packs);
+       sort_packs_by_name(pack_names, nr_packs, pack_perm);
+
         hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
         f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
         FREE_AND_NULL(midx_name);
 
-       write_midx_header(f, num_chunks, nr_packs);
+       cur_chunk = 0;
+       num_chunks = 1;
+
+       written = write_midx_header(f, num_chunks, nr_packs);
+
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES;
+       chunk_offsets[cur_chunk] = written + (num_chunks + 1) * MIDX_CHUNKLOOKUP_WIDTH;
+
+       cur_chunk++;
+       chunk_ids[cur_chunk] = 0;
+       chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + pack_name_concat_len;
+
+       for (i = 0; i <= num_chunks; i++) {
+               if (i && chunk_offsets[i] < chunk_offsets[i - 1])
+                       BUG("incorrect chunk offsets: %"PRIu64" before %"PRIu64,
+                           chunk_offsets[i - 1],
+                           chunk_offsets[i]);
+
+               if (chunk_offsets[i] % MIDX_CHUNK_ALIGNMENT)
+                       BUG("chunk offset %"PRIu64" is not properly aligned",
+                           chunk_offsets[i]);
+

midx.c int write_midx_file(const char *object_dir) · line 308

Quoted 10 lines
midx.c +10 -0
+               hashwrite_be32(f, chunk_ids[i]);
+               hashwrite_be32(f, chunk_offsets[i] >> 32);
+               hashwrite_be32(f, chunk_offsets[i]);
+
+               written += MIDX_CHUNKLOOKUP_WIDTH;
+       }
+
+       for (i = 0; i < num_chunks; i++) {
+               if (written != chunk_offsets[i])
+                       BUG("inccrrect chunk offset (%"PRIu64" != %"PRIu64") for chunk id %"PRIx32,

incorrect

Quoted 25 lines
midx.c +19 -0
+                           chunk_offsets[i],
+                           written,
+                           chunk_ids[i]);
+
+               switch (chunk_ids[i]) {
+                       case MIDX_CHUNKID_PACKNAMES:
+                               written += write_midx_pack_names(f, pack_names, nr_packs);
+                               break;
+
+                       default:
+                               BUG("trying to write unknown chunk id %"PRIx32,
+                                   chunk_ids[i]);
+               }
+       }
+
+       if (written != chunk_offsets[num_chunks])
+               BUG("incorrect final offset %"PRIu64" != %"PRIu64,
+                   written,
+                   chunk_offsets[num_chunks]);
 
         finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
         commit_lock_file(&lk);
@@ -170,5 +335,6 @@ int write_midx_file(const char *object_dir)
         }
 
Quoted 6 lines

midx.c int write_midx_file(const char *object_dir) · line 338

Quoted 2 lines
midx.c +1 -0
         FREE_AND_NULL(packs);
+       FREE_AND_NULL(pack_names);

What about the strings in this array? I think they are xstrdup() but I didn't spot them being freed.

And maybe just use string_list...

Will do! Thanks.

#37 lore raw

Re: [PATCH 09/23] midx: write pack names in chunk

Quoted 12 lines · Derrick Stolee
Quoted 11 lines · Duy Nguyen

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 85

Quoted 7 lines · Derrick Stolee
midx.c +3 -0
@@ -74,6 +80,31 @@ struct midxed_git *load_midxed_git(const char *object_dir)
         m->num_chunks = *(m->data + 6);
         m->num_packs = get_be32(m->data + 8);
 
+       for (i = 0; i < m->num_chunks; i++) {
+               uint32_t chunk_id = get_be32(m->data + 12 + MIDX_CHUNKLOOKUP_WIDTH * i);
+               uint64_t chunk_offset = get_be64(m->data + 16 + MIDX_CHUNKLOOKUP_WIDTH * i);

Would be good to reduce magic numbers like 12 and 16, I think you have some header length constants for those already.

Quoted 4 lines
Quoted 4 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 90

Quoted 4 lines
midx.c +4 -0
+               switch (chunk_id) {
+                       case MIDX_CHUNKID_PACKNAMES:
+                               m->chunk_pack_names = m->data + chunk_offset;
+                               break;

(style: aren't these case arms indented one level too deep)?

Quoted 6 lines
Quoted 3 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 93

Quoted 2 lines
midx.c +2 -0
+                       case 0:
+                               die("terminating MIDX chunk id appears earlier than expected");

_()

This die() and others like it are not marked for translation on purpose, as they should never be seen by an end-user.

Should never be seen because it indicates a software bug, in which case this should be BUG() instead of die()?

Or did we just find a file corruption on the filesystem? If so, then the error is end-user facing and should tell the user something that hints what is going on in the language the user understands, I would guess.

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
midx.c +5 -0
+                       default:
+                               /*
+                                * Do nothing on unrecognized chunks, allowing future
+                                * extensions to add optional chunks.
+                                */
Quoted 8 lines
Quoted 4 lines

I wrote about the chunk term reminding me of PNG format then deleted it. But it may help to do similar to PNG here. The first letter can let us know if the chunk is optional and can be safely ignored. E.g. uppercase first letter cannot be ignored, lowercase go wild.

That's an interesting way to think about it. That way you could add a new "required" chunk and earlier versions could die() realizing they don't know how to parse that required chunk.

That is how the index extension sections work and may be a good example to follow.

#38 lore raw

Re: [PATCH 09/23] midx: write pack names in chunk

Quoted 42 lines · Junio C Hamano
Quoted 12 lines · Derrick Stolee
Quoted 11 lines · Duy Nguyen

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 85

Quoted 7 lines · Derrick Stolee
midx.c +3 -0
@@ -74,6 +80,31 @@ struct midxed_git *load_midxed_git(const char *object_dir)
          m->num_chunks = *(m->data + 6);
          m->num_packs = get_be32(m->data + 8);
 
+       for (i = 0; i < m->num_chunks; i++) {
+               uint32_t chunk_id = get_be32(m->data + 12 + MIDX_CHUNKLOOKUP_WIDTH * i);
+               uint64_t chunk_offset = get_be64(m->data + 16 + MIDX_CHUNKLOOKUP_WIDTH * i);

Would be good to reduce magic numbers like 12 and 16, I think you have some header length constants for those already.

Quoted 4 lines
Quoted 4 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 90

Quoted 4 lines
midx.c +4 -0
+               switch (chunk_id) {
+                       case MIDX_CHUNKID_PACKNAMES:
+                               m->chunk_pack_names = m->data + chunk_offset;
+                               break;

(style: aren't these case arms indented one level too deep)?

Quoted 5 lines
Quoted 3 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 93

Quoted 2 lines
midx.c +2 -0
+                       case 0:
+                               die("terminating MIDX chunk id appears earlier than expected");

_()

This die() and others like it are not marked for translation on purpose, as they should never be seen by an end-user.

Should never be seen because it indicates a software bug, in which case this should be BUG() instead of die()?

Or did we just find a file corruption on the filesystem? If so, then the error is end-user facing and should tell the user something that hints what is going on in the language the user understands, I would guess.

Quoted 9 lines
Quoted 9 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 100

Quoted 5 lines
midx.c +5 -0
+                       default:
+                               /*
+                                * Do nothing on unrecognized chunks, allowing future
+                                * extensions to add optional chunks.
+                                */

I wrote about the chunk term reminding me of PNG format then deleted it. But it may help to do similar to PNG here. The first letter can let us know if the chunk is optional and can be safely ignored. E.g. uppercase first letter cannot be ignored, lowercase go wild.

Quoted 5 lines
Quoted 3 lines

That's an interesting way to think about it. That way you could add a new "required" chunk and earlier versions could die() realizing they don't know how to parse that required chunk.

That is how the index extension sections work and may be a good example to follow.

The index extension documentation doesn't appear to be clear about which extensions are optional or required, but it seems the split-index is the only "required" one and uses lowercase for its extension id.

Since the multi-pack-index has similar structure to the commit-graph file, and that file includes an optional chunk with no special casing of the chunk id, I think we should stick with the existing model: chunks that are added later are optional and if Git _must_ understand it, then we increment the version number. Hence, for each version number there is a fixed list of required chunks, but an extendible list of optional chunks.

Thanks,
-Stolee

#39 lore raw

Re: [PATCH 09/23] midx: write pack names in chunk

Quoted 4 lines · Derrick Stolee

The index extension documentation doesn't appear to be clear about which extensions are optional or required, but it seems the split-index is the only "required" one and uses lowercase for its extension id.

read-cache.c::

/* Index extensions.
*
* The first letter should be 'A'..'Z' for extensions that are not
* necessary for a correct operation (i.e. optimization data).
* When new extensions are added that _needs_ to be understood in
* order to correctly interpret the index file, pick character that
* is outside the range, to cause the reader to abort. */

#40 lore raw

Re: [PATCH 09/23] midx: write pack names in chunk

Quoted 8 lines · Junio C Hamano
Quoted 4 lines · Derrick Stolee

The index extension documentation doesn't appear to be clear about which extensions are optional or required, but it seems the split-index is the only "required" one and uses lowercase for its extension id.

read-cache.c::

Quoted 8 lines

/* Index extensions.
*
* The first letter should be 'A'..'Z' for extensions that are not
* necessary for a correct operation (i.e. optimization data).
* When new extensions are added that _needs_ to be understood in
* order to correctly interpret the index file, pick character that
* is outside the range, to cause the reader to abort. */

Thanks! I was reading Documentation/technical/index-format.txt and optional extensions are mentioned but not described precisely.

#41 lore raw

PATCH 10/23 midx: write a lookup into the pack names chunk

In reply to Derrick Stolee
---
 Documentation/technical/pack-format.txt |  5 +++
 builtin/midx.c                          |  7 ++++
 midx.c                                  | 56 +++++++++++++++++++++++--
 object-store.h                          |  2 +
 t/t5319-midx.sh                         | 11 +++--
 5 files changed, 75 insertions(+), 6 deletions(-)
Documentation/technical/pack-format.txt +5 -0
index 2b37be7b33..29bf87283a 100644
@@ -296,6 +296,11 @@ CHUNK LOOKUP:
 
 CHUNK DATA:
 
+	Packfile Name Lookup (ID: {'P', 'L', 'O', 'O'}) (P * 4 bytes)
+	    P * 4 bytes storing the offset in the packfile name chunk for
+	    the null-terminated string containing the filename for the
+	    ith packfile.
+
 	Packfile Names (ID: {'P', 'N', 'A', 'M'})
 	    Stores the packfile names as concatenated, null-terminated strings.
 	    Packfiles must be listed in lexicographic order for fast lookups by
builtin/midx.c +7 -0
index fe56560853..3a261e9bbf 100644
@@ -16,6 +16,7 @@ static struct opts_midx {
 
 static int read_midx_file(const char *object_dir)
 {
+	uint32_t i;
 	struct midxed_git *m = load_midxed_git(object_dir);
 
 	if (!m)
@@ -30,11 +31,17 @@ static int read_midx_file(const char *object_dir)
 
 	printf("chunks:");
 
+	if (m->chunk_pack_lookup)
+		printf(" pack_lookup");
 	if (m->chunk_pack_names)
 		printf(" pack_names");
 
 	printf("\n");
 
+	printf("packs:\n");
+	for (i = 0; i < m->num_packs; i++)
+		printf("%s\n", m->pack_names[i]);
+
 	printf("object_dir: %s\n", m->object_dir);
 
 	return 0;
midx.c +53 -3

101 lines not on this page · raw message

object-store.h +2 -0
index 199cf4bd44..1ba50459ca 100644
@@ -100,8 +100,10 @@ struct midxed_git {
 	uint32_t num_packs;
 	uint32_t num_objects;
 
+	const uint32_t *chunk_pack_lookup;
 	const unsigned char *chunk_pack_names;
 
+	const char **pack_names;
 	char object_dir[FLEX_ARRAY];
 };
 
t/t5319-midx.sh +8 -3
index fdf4f84a90..a31c387c8f 100755
@@ -6,10 +6,15 @@ test_description='multi-pack-indexes'
 midx_read_expect() {
 	NUM_PACKS=$1
 	cat >expect <<- EOF
-	header: 4d494458 1 1 1 $NUM_PACKS
-	chunks: pack_names
-	object_dir: .
+	header: 4d494458 1 1 2 $NUM_PACKS
+	chunks: pack_lookup pack_names
+	packs:
 	EOF
+	if [ $NUM_PACKS -ge 1 ]
+	then
+		ls pack/ | grep idx | sort >> expect
+	fi
+	printf "object_dir: .\n" >>expect &&
 	git midx read --object-dir=. >actual &&
 	test_cmp expect actual
 }
Signature · 1 line
2.18.0.rc1
#42 lore raw

Re: [PATCH 10/23] midx: write a lookup into the pack names chunk

Quoted 15 lines · Derrick Stolee
---
 Documentation/technical/pack-format.txt |  5 +++
 builtin/midx.c                          |  7 ++++
 midx.c                                  | 56 +++++++++++++++++++++++--
 object-store.h                          |  2 +
 t/t5319-midx.sh                         | 11 +++--
 5 files changed, 75 insertions(+), 6 deletions(-)
Documentation/technical/pack-format.txt +0 -0
index 2b37be7b33..29bf87283a 100644

Documentation/technical/pack-format.txt CHUNK LOOKUP: · line 303

Quoted 8 lines
Documentation/technical/pack-format.txt +5 -0
@@ -296,6 +296,11 @@ CHUNK LOOKUP:
 
 CHUNK DATA:
 
+       Packfile Name Lookup (ID: {'P', 'L', 'O', 'O'}) (P * 4 bytes)
+           P * 4 bytes storing the offset in the packfile name chunk for
+           the null-terminated string containing the filename for the
+           ith packfile.
+

Commit message is too light on this one. Why does this need to be stored? Isn't the cost of rebuilding this in-core cheap?

Adding this chunk on disk in my opinion only adds more burden. Now you have to verify that these offsets actually point to the right place.

Quoted 185 lines
Documentation/technical/pack-format.txt +0 -0
        Packfile Names (ID: {'P', 'N', 'A', 'M'})
            Stores the packfile names as concatenated, null-terminated strings.
            Packfiles must be listed in lexicographic order for fast lookups by
builtin/midx.c +7 -0
index fe56560853..3a261e9bbf 100644
@@ -16,6 +16,7 @@ static struct opts_midx {
 
 static int read_midx_file(const char *object_dir)
 {
+       uint32_t i;
        struct midxed_git *m = load_midxed_git(object_dir);
 
        if (!m)
@@ -30,11 +31,17 @@ static int read_midx_file(const char *object_dir)
 
        printf("chunks:");
 
+       if (m->chunk_pack_lookup)
+               printf(" pack_lookup");
        if (m->chunk_pack_names)
                printf(" pack_names");
 
        printf("\n");
 
+       printf("packs:\n");
+       for (i = 0; i < m->num_packs; i++)
+               printf("%s\n", m->pack_names[i]);
+
        printf("object_dir: %s\n", m->object_dir);
 
        return 0;
midx.c +53 -3
index d4f4a01a51..923acda72e 100644
@@ -13,8 +13,9 @@
 #define MIDX_HASH_LEN 20
 #define MIDX_MIN_SIZE (MIDX_HEADER_SIZE + MIDX_HASH_LEN)
 
-#define MIDX_MAX_CHUNKS 1
+#define MIDX_MAX_CHUNKS 2
 #define MIDX_CHUNK_ALIGNMENT 4
+#define MIDX_CHUNKID_PACKLOOKUP 0x504c4f4f /* "PLOO" */
 #define MIDX_CHUNKID_PACKNAMES 0x504e414d /* "PNAM" */
 #define MIDX_CHUNKLOOKUP_WIDTH (sizeof(uint32_t) + sizeof(uint64_t))
 
@@ -85,6 +86,10 @@ struct midxed_git *load_midxed_git(const char *object_dir)
                uint64_t chunk_offset = get_be64(m->data + 16 + MIDX_CHUNKLOOKUP_WIDTH * i);
 
                switch (chunk_id) {
+                       case MIDX_CHUNKID_PACKLOOKUP:
+                               m->chunk_pack_lookup = (uint32_t *)(m->data + chunk_offset);
+                               break;
+
                        case MIDX_CHUNKID_PACKNAMES:
                                m->chunk_pack_names = m->data + chunk_offset;
                                break;
@@ -102,9 +107,32 @@ struct midxed_git *load_midxed_git(const char *object_dir)
                }
        }
 
+       if (!m->chunk_pack_lookup)
+               die("MIDX missing required pack lookup chunk");
        if (!m->chunk_pack_names)
                die("MIDX missing required pack-name chunk");
 
+       m->pack_names = xcalloc(m->num_packs, sizeof(const char *));
+       for (i = 0; i < m->num_packs; i++) {
+               if (i) {
+                       if (ntohl(m->chunk_pack_lookup[i]) <= ntohl(m->chunk_pack_lookup[i - 1])) {
+                               error("MIDX pack lookup value %d before %d",
+                                     ntohl(m->chunk_pack_lookup[i - 1]),
+                                     ntohl(m->chunk_pack_lookup[i]));
+                               goto cleanup_fail;
+                       }
+               }
+
+               m->pack_names[i] = (const char *)(m->chunk_pack_names + ntohl(m->chunk_pack_lookup[i]));
+
+               if (i && strcmp(m->pack_names[i], m->pack_names[i - 1]) <= 0) {
+                       error("MIDX pack names out of order: '%s' before '%s'",
+                             m->pack_names[i - 1],
+                             m->pack_names[i]);
+                       goto cleanup_fail;
+               }
+       }
+
        return m;
 
 cleanup_fail:
@@ -162,6 +190,20 @@ static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p
        }
 }
 
+static size_t write_midx_pack_lookup(struct hashfile *f,
+                                    char **pack_names,
+                                    uint32_t nr_packs)
+{
+       uint32_t i, cur_len = 0;
+
+       for (i = 0; i < nr_packs; i++) {
+               hashwrite_be32(f, cur_len);
+               cur_len += strlen(pack_names[i]) + 1;
+       }
+
+       return sizeof(uint32_t) * (size_t)nr_packs;
+}
+
 static size_t write_midx_pack_names(struct hashfile *f,
                                    char **pack_names,
                                    uint32_t num_packs)
@@ -275,13 +317,17 @@ int write_midx_file(const char *object_dir)
        FREE_AND_NULL(midx_name);
 
        cur_chunk = 0;
-       num_chunks = 1;
+       num_chunks = 2;
 
        written = write_midx_header(f, num_chunks, nr_packs);
 
-       chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES;
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKLOOKUP;
        chunk_offsets[cur_chunk] = written + (num_chunks + 1) * MIDX_CHUNKLOOKUP_WIDTH;
 
+       cur_chunk++;
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES;
+       chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_packs * sizeof(uint32_t);
+
        cur_chunk++;
        chunk_ids[cur_chunk] = 0;
        chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + pack_name_concat_len;
@@ -311,6 +357,10 @@ int write_midx_file(const char *object_dir)
                            chunk_ids[i]);
 
                switch (chunk_ids[i]) {
+                       case MIDX_CHUNKID_PACKLOOKUP:
+                               written += write_midx_pack_lookup(f, pack_names, nr_packs);
+                               break;
+
                        case MIDX_CHUNKID_PACKNAMES:
                                written += write_midx_pack_names(f, pack_names, nr_packs);
                                break;
object-store.h +2 -0
index 199cf4bd44..1ba50459ca 100644
@@ -100,8 +100,10 @@ struct midxed_git {
        uint32_t num_packs;
        uint32_t num_objects;
 
+       const uint32_t *chunk_pack_lookup;
        const unsigned char *chunk_pack_names;
 
+       const char **pack_names;
        char object_dir[FLEX_ARRAY];
 };
 
t/t5319-midx.sh +8 -4
index fdf4f84a90..a31c387c8f 100755
@@ -6,10 +6,15 @@ test_description='multi-pack-indexes'
 midx_read_expect() {
        NUM_PACKS=$1
        cat >expect <<- EOF
-       header: 4d494458 1 1 1 $NUM_PACKS
-       chunks: pack_names
-       object_dir: .
+       header: 4d494458 1 1 2 $NUM_PACKS
+       chunks: pack_lookup pack_names
+       packs:
        EOF
+       if [ $NUM_PACKS -ge 1 ]
+       then
+               ls pack/ | grep idx | sort >> expect
+       fi
+       printf "object_dir: .\n" >>expect &&
        git midx read --object-dir=. >actual &&
        test_cmp expect actual
 }
--

2.18.0.rc1

Signature · 1 line
Duy
#43 lore raw

Re: [PATCH 10/23] midx: write a lookup into the pack names chunk

Quoted 26 lines · Duy Nguyen
Quoted 14 lines · Derrick Stolee
---
  Documentation/technical/pack-format.txt |  5 +++
  builtin/midx.c                          |  7 ++++
  midx.c                                  | 56 +++++++++++++++++++++++--
  object-store.h                          |  2 +
  t/t5319-midx.sh                         | 11 +++--
  5 files changed, 75 insertions(+), 6 deletions(-)
Documentation/technical/pack-format.txt +0 -0
index 2b37be7b33..29bf87283a 100644

Documentation/technical/pack-format.txt CHUNK LOOKUP: · line 303

Quoted 8 lines
Documentation/technical/pack-format.txt +5 -0
@@ -296,6 +296,11 @@ CHUNK LOOKUP:
 
  CHUNK DATA:
 
+       Packfile Name Lookup (ID: {'P', 'L', 'O', 'O'}) (P * 4 bytes)
+           P * 4 bytes storing the offset in the packfile name chunk for
+           the null-terminated string containing the filename for the
+           ith packfile.
+

Commit message is too light on this one. Why does this need to be stored? Isn't the cost of rebuilding this in-core cheap?

Quoted 2 lines

Adding this chunk on disk in my opinion only adds more burden. Now you have to verify that these offsets actually point to the right place.

This is a very good point. I'll drop the chunk and just read the names directly to construct the array of strings.

Thanks,
-Stolee

#44 lore raw

PATCH 11/23 midx: sort and deduplicate objects from packfiles

In reply to Derrick Stolee

Before writing a list of objects and their offsets to a multi-pack-index (MIDX), we need to collect the list of objects contained in the packfiles. There may be multiple copies of some objects, so this list must be deduplicated.

It is possible to artificially get into a state where there are many duplicate copies of objects. That can create high memory pressure if we are to create a list of all objects before de-duplication. To reduce this memory pressure without a significant performance drop, automatically group objects by the first byte of their object id. Use the IDX fanout tables to group the data, copy to a local array, then sort.

Copy only the de-duplicated entries. Select the duplicate based on the most-recent modified time of a packfile containing the object.

---
 midx.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 138 insertions(+)
midx.c +138 -0
index 923acda72e..b20d52713c 100644
@@ -4,6 +4,7 @@
 #include "csum-file.h"
 #include "lockfile.h"
 #include "object-store.h"
+#include "packfile.h"
 #include "midx.h"
 
 #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
@@ -190,6 +191,140 @@ static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p
 	}
 }
 
+static uint32_t get_pack_fanout(struct packed_git *p, uint32_t value)
+{
+	const uint32_t *level1_ofs = p->index_data;
+
+	if (!level1_ofs) {
+		if (open_pack_index(p))
+			return 0;
+		level1_ofs = p->index_data;
+	}
+
+	if (p->index_version > 1) {
+		level1_ofs += 2;
+	}
+
+	return ntohl(level1_ofs[value]);
+}
+
+struct pack_midx_entry {
+	struct object_id oid;
+	uint32_t pack_int_id;
+	time_t pack_mtime;
+	uint64_t offset;
+};
+
+static int midx_oid_compare(const void *_a, const void *_b)
+{
+	struct pack_midx_entry *a = (struct pack_midx_entry *)_a;
+	struct pack_midx_entry *b = (struct pack_midx_entry *)_b;
+	int cmp = oidcmp(&a->oid, &b->oid);
+
+	if (cmp)
+		return cmp;
+
+	if (a->pack_mtime > b->pack_mtime)
+		return -1;
+	else if (a->pack_mtime < b->pack_mtime)
+		return 1;
+
+	return a->pack_int_id - b->pack_int_id;
+}
+
+static void fill_pack_entry(uint32_t pack_int_id,
+			    struct packed_git *p,
+			    uint32_t cur_object,
+			    struct pack_midx_entry *entry)
+{
+	if (!nth_packed_object_oid(&entry->oid, p, cur_object))
+		die("failed to located object %d in packfile", cur_object);
+
+	entry->pack_int_id = pack_int_id;
+	entry->pack_mtime = p->mtime;
+
+	entry->offset = nth_packed_object_offset(p, cur_object);
+}
+
+/*
+ * It is possible to artificially get into a state where there are many
+ * duplicate copies of objects. That can create high memory pressure if
+ * we are to create a list of all objects before de-duplication. To reduce
+ * this memory pressure without a significant performance drop, automatically
+ * group objects by the first byte of their object id. Use the IDX fanout
+ * tables to group the data, copy to a local array, then sort.
+ *
+ * Copy only the de-duplicated entries (selected by most-recent modified time
+ * of a packfile containing the object).
+ */
+static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
+						  uint32_t *perm,
+						  uint32_t nr_packs,
+						  uint32_t *nr_objects)
+{
+	uint32_t cur_fanout, cur_pack, cur_object;
+	uint32_t nr_fanout, alloc_fanout, alloc_objects, total_objects = 0;
+	struct pack_midx_entry *entries_by_fanout = NULL;
+	struct pack_midx_entry *deduplicated_entries = NULL;
+
+	for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+		if (open_pack_index(p[cur_pack]))
+			continue;
+
+		total_objects += p[cur_pack]->num_objects;
+	}
+
+	/*
+	 * As we de-duplicate by fanout value, we expect the fanout
+	 * slices to be evenly distributed, with some noise. Hence,
+	 * allocate slightly more than one 256th.
+	 */
+	alloc_objects = alloc_fanout = total_objects > 3200 ? total_objects / 200 : 16;
+
+	ALLOC_ARRAY(entries_by_fanout, alloc_fanout);
+	ALLOC_ARRAY(deduplicated_entries, alloc_objects);
+	*nr_objects = 0;
+
+	for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) {
+		nr_fanout = 0;
+
+		for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+			uint32_t start = 0, end;
+
+			if (cur_fanout)
+				start = get_pack_fanout(p[cur_pack], cur_fanout - 1);
+			end = get_pack_fanout(p[cur_pack], cur_fanout);
+
+			for (cur_object = start; cur_object < end; cur_object++) {
+				ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
+				fill_pack_entry(perm[cur_pack], p[cur_pack], cur_object, &entries_by_fanout[nr_fanout]);
+				nr_fanout++;
+			}
+		}
+
+		QSORT(entries_by_fanout, nr_fanout, midx_oid_compare);
+
+		/*
+		 * The batch is now sorted by OID and then mtime (descending).
+		 * Take only the first duplicate.
+		 */
+		for (cur_object = 0; cur_object < nr_fanout; cur_object++) {
+			if (cur_object && !oidcmp(&entries_by_fanout[cur_object - 1].oid,
+						  &entries_by_fanout[cur_object].oid))
+				continue;
+
+			ALLOC_GROW(deduplicated_entries, *nr_objects + 1, alloc_objects);
+			memcpy(&deduplicated_entries[*nr_objects],
+			       &entries_by_fanout[cur_object],
+			       sizeof(struct pack_midx_entry));
+			(*nr_objects)++;
+		}
+	}
+
+	FREE_AND_NULL(entries_by_fanout);
+	return deduplicated_entries;
+}
+
 static size_t write_midx_pack_lookup(struct hashfile *f,
 				     char **pack_names,
 				     uint32_t nr_packs)
@@ -254,6 +389,7 @@ int write_midx_file(const char *object_dir)
 	uint64_t written = 0;
 	uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
 	uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];
+	uint32_t nr_entries;
 
 	midx_name = get_midx_filename(object_dir);
 	if (safe_create_leading_directories(midx_name)) {
@@ -312,6 +448,8 @@ int write_midx_file(const char *object_dir)
 	ALLOC_ARRAY(pack_perm, nr_packs);
 	sort_packs_by_name(pack_names, nr_packs, pack_perm);
 
+	get_sorted_entries(packs, pack_perm, nr_packs, &nr_entries);
+
 	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
 	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
 	FREE_AND_NULL(midx_name);
Signature · 1 line
2.18.0.rc1
#45 lore raw

Re: [PATCH 11/23] midx: sort and deduplicate objects from packfiles

Quoted 5 lines · Derrick Stolee

Before writing a list of objects and their offsets to a multi-pack-index (MIDX), we need to collect the list of objects contained in the packfiles. There may be multiple copies of some objects, so this list must be deduplicated.

Can you just do merge-sort with a slight modification to ignore duplicates?

Quoted 39 lines

It is possible to artificially get into a state where there are many duplicate copies of objects. That can create high memory pressure if we are to create a list of all objects before de-duplication. To reduce this memory pressure without a significant performance drop, automatically group objects by the first byte of their object id. Use the IDX fanout tables to group the data, copy to a local array, then sort.

Copy only the de-duplicated entries. Select the duplicate based on the most-recent modified time of a packfile containing the object.

---
 midx.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 138 insertions(+)
midx.c +7 -0
index 923acda72e..b20d52713c 100644
@@ -4,6 +4,7 @@
 #include "csum-file.h"
 #include "lockfile.h"
 #include "object-store.h"
+#include "packfile.h"
 #include "midx.h"
 
 #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
@@ -190,6 +191,140 @@ static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p
        }
 }
 
+static uint32_t get_pack_fanout(struct packed_git *p, uint32_t value)
+{
+       const uint32_t *level1_ofs = p->index_data;
+
+       if (!level1_ofs) {
+               if (open_pack_index(p))

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 209

Quoted 10 lines
midx.c +10 -0
+                       return 0;
+               level1_ofs = p->index_data;
+       }
+
+       if (p->index_version > 1) {
+               level1_ofs += 2;
+       }
+
+       return ntohl(level1_ofs[value]);
+}

Maybe keep this in packfile,c, refactor fanout code in there if necessary, keep .idx file format info in that file instead of spreading out more.

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 221

Quoted 12 lines
midx.c +12 -0
+
+struct pack_midx_entry {
+       struct object_id oid;
+       uint32_t pack_int_id;
+       time_t pack_mtime;
+       uint64_t offset;
+};
+
+static int midx_oid_compare(const void *_a, const void *_b)
+{
+       struct pack_midx_entry *a = (struct pack_midx_entry *)_a;
+       struct pack_midx_entry *b = (struct pack_midx_entry *)_b;

Try not to lose "const" while typecasting.

Quoted 10 lines
midx.c +10 -0
+       int cmp = oidcmp(&a->oid, &b->oid);
+
+       if (cmp)
+               return cmp;
+
+       if (a->pack_mtime > b->pack_mtime)
+               return -1;
+       else if (a->pack_mtime < b->pack_mtime)
+               return 1;
+

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 241

Quoted 10 lines
midx.c +10 -0
+       return a->pack_int_id - b->pack_int_id;
+}
+
+static void fill_pack_entry(uint32_t pack_int_id,
+                           struct packed_git *p,
+                           uint32_t cur_object,
+                           struct pack_midx_entry *entry)
+{
+       if (!nth_packed_object_oid(&entry->oid, p, cur_object))
+               die("failed to located object %d in packfile", cur_object);

_()

Quoted 21 lines
midx.c +21 -0
+
+       entry->pack_int_id = pack_int_id;
+       entry->pack_mtime = p->mtime;
+
+       entry->offset = nth_packed_object_offset(p, cur_object);
+}
+
+/*
+ * It is possible to artificially get into a state where there are many
+ * duplicate copies of objects. That can create high memory pressure if
+ * we are to create a list of all objects before de-duplication. To reduce
+ * this memory pressure without a significant performance drop, automatically
+ * group objects by the first byte of their object id. Use the IDX fanout
+ * tables to group the data, copy to a local array, then sort.
+ *
+ * Copy only the de-duplicated entries (selected by most-recent modified time
+ * of a packfile containing the object).
+ */
+static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
+                                                 uint32_t *perm,
+                                                 uint32_t nr_packs,

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 272

Quoted 10 lines
midx.c +10 -0
+                                                 uint32_t *nr_objects)
+{
+       uint32_t cur_fanout, cur_pack, cur_object;
+       uint32_t nr_fanout, alloc_fanout, alloc_objects, total_objects = 0;
+       struct pack_midx_entry *entries_by_fanout = NULL;
+       struct pack_midx_entry *deduplicated_entries = NULL;
+
+       for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+               if (open_pack_index(p[cur_pack]))
+                       continue;

Is it a big problem if you fail to open .idx for a certain pack? Should we error out and abort instead of continuing on? Later on in the second pack loop code when get_fanout return zero (failure), you don't seem to catch it and skip the pack.

Quoted 7 lines
midx.c +7 -0
+
+               total_objects += p[cur_pack]->num_objects;
+       }
+
+       /*
+        * As we de-duplicate by fanout value, we expect the fanout
+        * slices to be evenly distributed, with some noise. Hence,

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 289

Quoted 10 lines
midx.c +10 -0
+        * allocate slightly more than one 256th.
+        */
+       alloc_objects = alloc_fanout = total_objects > 3200 ? total_objects / 200 : 16;
+
+       ALLOC_ARRAY(entries_by_fanout, alloc_fanout);
+       ALLOC_ARRAY(deduplicated_entries, alloc_objects);
+       *nr_objects = 0;
+
+       for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) {
+               nr_fanout = 0;

Keep variable scope small, declare nr_fanout here instead of at the top of the function.

Quoted 50 lines
midx.c +39 -0
+
+               for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+                       uint32_t start = 0, end;
+
+                       if (cur_fanout)
+                               start = get_pack_fanout(p[cur_pack], cur_fanout - 1);
+                       end = get_pack_fanout(p[cur_pack], cur_fanout);
+
+                       for (cur_object = start; cur_object < end; cur_object++) {
+                               ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
+                               fill_pack_entry(perm[cur_pack], p[cur_pack], cur_object, &entries_by_fanout[nr_fanout]);
+                               nr_fanout++;
+                       }
+               }
+
+               QSORT(entries_by_fanout, nr_fanout, midx_oid_compare);
+
+               /*
+                * The batch is now sorted by OID and then mtime (descending).
+                * Take only the first duplicate.
+                */
+               for (cur_object = 0; cur_object < nr_fanout; cur_object++) {
+                       if (cur_object && !oidcmp(&entries_by_fanout[cur_object - 1].oid,
+                                                 &entries_by_fanout[cur_object].oid))
+                               continue;
+
+                       ALLOC_GROW(deduplicated_entries, *nr_objects + 1, alloc_objects);
+                       memcpy(&deduplicated_entries[*nr_objects],
+                              &entries_by_fanout[cur_object],
+                              sizeof(struct pack_midx_entry));
+                       (*nr_objects)++;
+               }
+       }
+
+       FREE_AND_NULL(entries_by_fanout);
+       return deduplicated_entries;
+}
+
 static size_t write_midx_pack_lookup(struct hashfile *f,
                                     char **pack_names,
                                     uint32_t nr_packs)
@@ -254,6 +389,7 @@ int write_midx_file(const char *object_dir)
        uint64_t written = 0;
        uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
        uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];
+       uint32_t nr_entries;
 
        midx_name = get_midx_filename(object_dir);
        if (safe_create_leading_directories(midx_name)) {

midx.c int write_midx_file(const char *object_dir) · line 451

Quoted 4 lines
midx.c +1 -0
@@ -312,6 +448,8 @@ int write_midx_file(const char *object_dir)
        ALLOC_ARRAY(pack_perm, nr_packs);
        sort_packs_by_name(pack_names, nr_packs, pack_perm);
 
+       get_sorted_entries(packs, pack_perm, nr_packs, &nr_entries);

Intentional ignoring return value (and temporary leaking as a result) should have a least a comment to acknowledge it and save reviewers some head scratching. Or even better, just free it now, even if you don't use it.

Quoted 7 lines
midx.c +1 -1
+
        hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
        f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
        FREE_AND_NULL(midx_name);
--

2.18.0.rc1

Signature · 1 line
Duy
#46 lore raw

Re: [PATCH 11/23] midx: sort and deduplicate objects from packfiles

Quoted 6 lines · Duy Nguyen
Quoted 4 lines · Derrick Stolee

Before writing a list of objects and their offsets to a multi-pack-index (MIDX), we need to collect the list of objects contained in the packfiles. There may be multiple copies of some objects, so this list must be deduplicated.

Can you just do merge-sort with a slight modification to ignore duplicates?

Are you proposing we consider a multi-way merge of the existing sorted lists of packfiles (skipping duplicates)? In my head, this would work this way:

1. Keep an array of positions within each of the pack-indexes for the "current lex-least OID not already in my sorted list"

2. Scan the list of P pack-indexes to find the lex-least OID among all candidates. Advance the position of that pack-index as we put that OID in the list (and advance the position of pack-indexes with duplicates).

This would have O(P * N) performance, where P is the number of packfiles and N is the total number of objects. This gets slightly better when there are duplicates; in the world where we have P identical lists of n objects, then N = n * P and we actually get N steps because we can advance the position on a duplicate value and not revisit duplicates. However, we do not expect duplicates in this density.

By adding some complexity to the algorithm, we could sort the pack-indexes in order of their lex-least OIDs, and update the order as we advance -- or rather use a min-heap to have access to the proper pack-index. This case is most likely to be valuable when updating a large MIDX by adding a list of smaller IDX files (which we expect to not be the "best" choice for most of the selections). I'm not sure the complexity is worth it (would need to measure!).

By concatenating the lists within the fanout values and sorting, we do 256 sorts of size ~N/256, giving O(N * log(N/256)) performance. This method also has an extra array of size ~N/200 to store the batches, resulting in extra copies being pushed around.

You've convinced me that your approach may be better, especially in the typical case of adding a small number of packfiles to an existing MIDX file. Some work is needed to be sure it is better in general (such as reported cases of 5000 packfiles!). I'll leave a note to revisit this between v2 and v3.

Quoted 212 lines
Quoted 39 lines

It is possible to artificially get into a state where there are many duplicate copies of objects. That can create high memory pressure if we are to create a list of all objects before de-duplication. To reduce this memory pressure without a significant performance drop, automatically group objects by the first byte of their object id. Use the IDX fanout tables to group the data, copy to a local array, then sort.

Copy only the de-duplicated entries. Select the duplicate based on the most-recent modified time of a packfile containing the object.

---
  midx.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  1 file changed, 138 insertions(+)
midx.c +7 -0
index 923acda72e..b20d52713c 100644
@@ -4,6 +4,7 @@
  #include "csum-file.h"
  #include "lockfile.h"
  #include "object-store.h"
+#include "packfile.h"
  #include "midx.h"
 
  #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
@@ -190,6 +191,140 @@ static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p
         }
  }
 
+static uint32_t get_pack_fanout(struct packed_git *p, uint32_t value)
+{
+       const uint32_t *level1_ofs = p->index_data;
+
+       if (!level1_ofs) {
+               if (open_pack_index(p))

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 209

Quoted 10 lines
midx.c +10 -0
+                       return 0;
+               level1_ofs = p->index_data;
+       }
+
+       if (p->index_version > 1) {
+               level1_ofs += 2;
+       }
+
+       return ntohl(level1_ofs[value]);
+}

Maybe keep this in packfile,c, refactor fanout code in there if necessary, keep .idx file format info in that file instead of spreading out more.

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 221

Quoted 12 lines
midx.c +12 -0
+
+struct pack_midx_entry {
+       struct object_id oid;
+       uint32_t pack_int_id;
+       time_t pack_mtime;
+       uint64_t offset;
+};
+
+static int midx_oid_compare(const void *_a, const void *_b)
+{
+       struct pack_midx_entry *a = (struct pack_midx_entry *)_a;
+       struct pack_midx_entry *b = (struct pack_midx_entry *)_b;

Try not to lose "const" while typecasting.

Quoted 10 lines
midx.c +10 -0
+       int cmp = oidcmp(&a->oid, &b->oid);
+
+       if (cmp)
+               return cmp;
+
+       if (a->pack_mtime > b->pack_mtime)
+               return -1;
+       else if (a->pack_mtime < b->pack_mtime)
+               return 1;
+

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 241

Quoted 10 lines
midx.c +10 -0
+       return a->pack_int_id - b->pack_int_id;
+}
+
+static void fill_pack_entry(uint32_t pack_int_id,
+                           struct packed_git *p,
+                           uint32_t cur_object,
+                           struct pack_midx_entry *entry)
+{
+       if (!nth_packed_object_oid(&entry->oid, p, cur_object))
+               die("failed to located object %d in packfile", cur_object);

_()

Quoted 21 lines
midx.c +21 -0
+
+       entry->pack_int_id = pack_int_id;
+       entry->pack_mtime = p->mtime;
+
+       entry->offset = nth_packed_object_offset(p, cur_object);
+}
+
+/*
+ * It is possible to artificially get into a state where there are many
+ * duplicate copies of objects. That can create high memory pressure if
+ * we are to create a list of all objects before de-duplication. To reduce
+ * this memory pressure without a significant performance drop, automatically
+ * group objects by the first byte of their object id. Use the IDX fanout
+ * tables to group the data, copy to a local array, then sort.
+ *
+ * Copy only the de-duplicated entries (selected by most-recent modified time
+ * of a packfile containing the object).
+ */
+static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
+                                                 uint32_t *perm,
+                                                 uint32_t nr_packs,

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 272

Quoted 10 lines
midx.c +10 -0
+                                                 uint32_t *nr_objects)
+{
+       uint32_t cur_fanout, cur_pack, cur_object;
+       uint32_t nr_fanout, alloc_fanout, alloc_objects, total_objects = 0;
+       struct pack_midx_entry *entries_by_fanout = NULL;
+       struct pack_midx_entry *deduplicated_entries = NULL;
+
+       for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+               if (open_pack_index(p[cur_pack]))
+                       continue;

Is it a big problem if you fail to open .idx for a certain pack? Should we error out and abort instead of continuing on? Later on in the second pack loop code when get_fanout return zero (failure), you don't seem to catch it and skip the pack.

Quoted 7 lines
midx.c +7 -0
+
+               total_objects += p[cur_pack]->num_objects;
+       }
+
+       /*
+        * As we de-duplicate by fanout value, we expect the fanout
+        * slices to be evenly distributed, with some noise. Hence,

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 289

Quoted 10 lines
midx.c +10 -0
+        * allocate slightly more than one 256th.
+        */
+       alloc_objects = alloc_fanout = total_objects > 3200 ? total_objects / 200 : 16;
+
+       ALLOC_ARRAY(entries_by_fanout, alloc_fanout);
+       ALLOC_ARRAY(deduplicated_entries, alloc_objects);
+       *nr_objects = 0;
+
+       for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) {
+               nr_fanout = 0;

Keep variable scope small, declare nr_fanout here instead of at the top of the function.

Quoted 50 lines
midx.c +39 -0
+
+               for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+                       uint32_t start = 0, end;
+
+                       if (cur_fanout)
+                               start = get_pack_fanout(p[cur_pack], cur_fanout - 1);
+                       end = get_pack_fanout(p[cur_pack], cur_fanout);
+
+                       for (cur_object = start; cur_object < end; cur_object++) {
+                               ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
+                               fill_pack_entry(perm[cur_pack], p[cur_pack], cur_object, &entries_by_fanout[nr_fanout]);
+                               nr_fanout++;
+                       }
+               }
+
+               QSORT(entries_by_fanout, nr_fanout, midx_oid_compare);
+
+               /*
+                * The batch is now sorted by OID and then mtime (descending).
+                * Take only the first duplicate.
+                */
+               for (cur_object = 0; cur_object < nr_fanout; cur_object++) {
+                       if (cur_object && !oidcmp(&entries_by_fanout[cur_object - 1].oid,
+                                                 &entries_by_fanout[cur_object].oid))
+                               continue;
+
+                       ALLOC_GROW(deduplicated_entries, *nr_objects + 1, alloc_objects);
+                       memcpy(&deduplicated_entries[*nr_objects],
+                              &entries_by_fanout[cur_object],
+                              sizeof(struct pack_midx_entry));
+                       (*nr_objects)++;
+               }
+       }
+
+       FREE_AND_NULL(entries_by_fanout);
+       return deduplicated_entries;
+}
+
  static size_t write_midx_pack_lookup(struct hashfile *f,
                                      char **pack_names,
                                      uint32_t nr_packs)
@@ -254,6 +389,7 @@ int write_midx_file(const char *object_dir)
         uint64_t written = 0;
         uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
         uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];
+       uint32_t nr_entries;
 
         midx_name = get_midx_filename(object_dir);
         if (safe_create_leading_directories(midx_name)) {

midx.c int write_midx_file(const char *object_dir) · line 451

Quoted 4 lines
midx.c +1 -0
@@ -312,6 +448,8 @@ int write_midx_file(const char *object_dir)
         ALLOC_ARRAY(pack_perm, nr_packs);
         sort_packs_by_name(pack_names, nr_packs, pack_perm);
 
+       get_sorted_entries(packs, pack_perm, nr_packs, &nr_entries);

Intentional ignoring return value (and temporary leaking as a result) should have a least a comment to acknowledge it and save reviewers some head scratching. Or even better, just free it now, even if you don't use it.

Quoted 7 lines
midx.c +1 -1
+
         hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
         f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
         FREE_AND_NULL(midx_name);
--

2.18.0.rc1

#47 lore raw

PATCH 12/23 midx: write object ids in a chunk

In reply to Derrick Stolee
---
 Documentation/technical/pack-format.txt |  4 ++
 builtin/midx.c                          |  2 +
 midx.c                                  | 50 +++++++++++++++++++++++--
 object-store.h                          |  1 +
 t/t5319-midx.sh                         |  4 +-
 5 files changed, 55 insertions(+), 6 deletions(-)
Documentation/technical/pack-format.txt +4 -0
index 29bf87283a..de9ac778b6 100644
@@ -307,6 +307,10 @@ CHUNK DATA:
 	    name. This is the only chunk not guaranteed to be a multiple of four
 	    bytes in length, so should be the last chunk for alignment reasons.
 
+	OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes)
+	    The OIDs for all objects in the MIDX are stored in lexicographic
+	    order in this chunk.
+
 	(This section intentionally left incomplete.)
 
 TRAILER:
builtin/midx.c +2 -0
index 3a261e9bbf..86edd30174 100644
@@ -35,6 +35,8 @@ static int read_midx_file(const char *object_dir)
 		printf(" pack_lookup");
 	if (m->chunk_pack_names)
 		printf(" pack_names");
+	if (m->chunk_oid_lookup)
+		printf(" oid_lookup");
 
 	printf("\n");
 
midx.c +46 -4

109 lines not on this page · raw message

object-store.h +1 -0
index 1ba50459ca..7d14d3586e 100644
@@ -102,6 +102,7 @@ struct midxed_git {
 
 	const uint32_t *chunk_pack_lookup;
 	const unsigned char *chunk_pack_names;
+	const unsigned char *chunk_oid_lookup;
 
 	const char **pack_names;
 	char object_dir[FLEX_ARRAY];
t/t5319-midx.sh +2 -2
index a31c387c8f..e71aa52b80 100755
@@ -6,8 +6,8 @@ test_description='multi-pack-indexes'
 midx_read_expect() {
 	NUM_PACKS=$1
 	cat >expect <<- EOF
-	header: 4d494458 1 1 2 $NUM_PACKS
-	chunks: pack_lookup pack_names
+	header: 4d494458 1 1 3 $NUM_PACKS
+	chunks: pack_lookup pack_names oid_lookup
 	packs:
 	EOF
 	if [ $NUM_PACKS -ge 1 ]
Signature · 1 line
2.18.0.rc1
#48 lore raw

Re: [PATCH 12/23] midx: write object ids in a chunk

Quoted 15 lines · Derrick Stolee
---
 Documentation/technical/pack-format.txt |  4 ++
 builtin/midx.c                          |  2 +
 midx.c                                  | 50 +++++++++++++++++++++++--
 object-store.h                          |  1 +
 t/t5319-midx.sh                         |  4 +-
 5 files changed, 55 insertions(+), 6 deletions(-)
Documentation/technical/pack-format.txt +0 -0
index 29bf87283a..de9ac778b6 100644

Documentation/technical/pack-format.txt CHUNK DATA: · line 310

Quoted 4 lines
Documentation/technical/pack-format.txt +1 -0
@@ -307,6 +307,10 @@ CHUNK DATA:
            name. This is the only chunk not guaranteed to be a multiple of four
            bytes in length, so should be the last chunk for alignment reasons.
 
+       OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes)

So N is the number of objects and H is hash size? Please don't let me guess.

Documentation/technical/pack-format.txt CHUNK DATA: · line 312

Quoted 2 lines
Documentation/technical/pack-format.txt +2 -0
+           The OIDs for all objects in the MIDX are stored in lexicographic
+           order in this chunk.

The reason we keep all hashes together, packed right, is to reduce cache footprint. Another observation is it takes us usually just 12 bytes or less to uniquely identify an object, which means we could pack even tighter if we split he object hash into two chunks, do bsearch in the first chunk with just <n> bytes then verify that the remaining 20-<b> bytes is matched in the second chunk. This may matter more when we move to larger hashes. The split would of course be configurable since different project may have different optimal value, but default value could be 10/10 bytes.

Quoted 35 lines
Documentation/technical/pack-format.txt +1 -0
+
        (This section intentionally left incomplete.)
 
 TRAILER:
builtin/midx.c +2 -0
index 3a261e9bbf..86edd30174 100644
@@ -35,6 +35,8 @@ static int read_midx_file(const char *object_dir)
                printf(" pack_lookup");
        if (m->chunk_pack_names)
                printf(" pack_names");
+       if (m->chunk_oid_lookup)
+               printf(" oid_lookup");
 
        printf("\n");
 
midx.c +2 -1
index b20d52713c..d06bc6876a 100644
@@ -14,10 +14,11 @@
 #define MIDX_HASH_LEN 20
 #define MIDX_MIN_SIZE (MIDX_HEADER_SIZE + MIDX_HASH_LEN)
 
-#define MIDX_MAX_CHUNKS 2
+#define MIDX_MAX_CHUNKS 3
 #define MIDX_CHUNK_ALIGNMENT 4
 #define MIDX_CHUNKID_PACKLOOKUP 0x504c4f4f /* "PLOO" */
 #define MIDX_CHUNKID_PACKNAMES 0x504e414d /* "PNAM" */
+#define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
 #define MIDX_CHUNKLOOKUP_WIDTH (sizeof(uint32_t) + sizeof(uint64_t))
 
 static char *get_midx_filename(const char *object_dir)

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 101

Quoted 6 lines
midx.c +3 -0
@@ -95,6 +96,10 @@ struct midxed_git *load_midxed_git(const char *object_dir)
                                m->chunk_pack_names = m->data + chunk_offset;
                                break;
 
+                       case MIDX_CHUNKID_OIDLOOKUP:
+                               m->chunk_oid_lookup = m->data + chunk_offset;
+                               break;

I just now realized, how do you protect from duplicate chunks? From this patch, it looks like you could accept two oidlookup chunks just fine then siliently ignore the first one.

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 121

Quoted 9 lines
midx.c +2 -0
                        case 0:
                                die("terminating MIDX chunk id appears earlier than expected");
                                break;
@@ -112,6 +117,8 @@ struct midxed_git *load_midxed_git(const char *object_dir)
                die("MIDX missing required pack lookup chunk");
        if (!m->chunk_pack_names)
                die("MIDX missing required pack-name chunk");
+       if (!m->chunk_oid_lookup)
+               die("MIDX missing required OID lookup chunk");

_()

Quoted 14 lines
midx.c +7 -0
        m->pack_names = xcalloc(m->num_packs, sizeof(const char *));
        for (i = 0; i < m->num_packs; i++) {
@@ -370,6 +377,32 @@ static size_t write_midx_pack_names(struct hashfile *f,
        return written;
 }
 
+static size_t write_midx_oid_lookup(struct hashfile *f, unsigned char hash_len,
+                                   struct pack_midx_entry *objects,
+                                   uint32_t nr_objects)
+{
+       struct pack_midx_entry *list = objects;
+       uint32_t i;
+       size_t written = 0;

midx.c static size_t write_midx_pack_names(struct hashfile *f, · line 396

Quoted 10 lines
midx.c +10 -0
+
+       for (i = 0; i < nr_objects; i++) {
+               struct pack_midx_entry *obj = list++;
+
+               if (i < nr_objects - 1) {
+                       struct pack_midx_entry *next = list;
+                       if (oidcmp(&obj->oid, &next->oid) >= 0)
+                               BUG("OIDs not in order: %s >= %s",
+                               oid_to_hex(&obj->oid),
+                               oid_to_hex(&next->oid));

Indentation. I almost thought oid_to_hex() was a separate statement.

midx.c static size_t write_midx_pack_names(struct hashfile *f, · line 399

Quoted 3 lines
midx.c +3 -0
+               }
+
+               hashwrite(f, obj->oid.hash, (int)hash_len);

Is (int) really necessary? There's no loss in automatically casting unsigned char to int. But I didn't check C spec, maybe there's some rules...

Quoted 18 lines
midx.c +7 -0
+               written += hash_len;
+       }
+
+       return written;
+}
+
 int write_midx_file(const char *object_dir)
 {
        unsigned char cur_chunk, num_chunks = 0;
@@ -389,6 +422,7 @@ int write_midx_file(const char *object_dir)
        uint64_t written = 0;
        uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
        uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];
+       struct pack_midx_entry *entries;
        uint32_t nr_entries;
 
        midx_name = get_midx_filename(object_dir);

midx.c int write_midx_file(const char *object_dir) · line 485

Quoted 5 lines
midx.c +1 -1
@@ -448,14 +482,14 @@ int write_midx_file(const char *object_dir)
        ALLOC_ARRAY(pack_perm, nr_packs);
        sort_packs_by_name(pack_names, nr_packs, pack_perm);
 
-       get_sorted_entries(packs, pack_perm, nr_packs, &nr_entries);
+       entries = get_sorted_entries(packs, pack_perm, nr_packs, &nr_entries);

This change should belong to the previous patch. This patch alone can't tell me that entries is a new allocation. If I didn't remember the last patch, I could not realize that entries should be freed (and it does not look like it is here)

Quoted 67 lines
midx.c +10 -2
        hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
        f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
        FREE_AND_NULL(midx_name);
 
        cur_chunk = 0;
-       num_chunks = 2;
+       num_chunks = 3;
 
        written = write_midx_header(f, num_chunks, nr_packs);
 
@@ -467,9 +501,13 @@ int write_midx_file(const char *object_dir)
        chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_packs * sizeof(uint32_t);
 
        cur_chunk++;
-       chunk_ids[cur_chunk] = 0;
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDLOOKUP;
        chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + pack_name_concat_len;
 
+       cur_chunk++;
+       chunk_ids[cur_chunk] = 0;
+       chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_entries * MIDX_HASH_LEN;
+
        for (i = 0; i <= num_chunks; i++) {
                if (i && chunk_offsets[i] < chunk_offsets[i - 1])
                        BUG("incorrect chunk offsets: %"PRIu64" before %"PRIu64,
@@ -503,6 +541,10 @@ int write_midx_file(const char *object_dir)
                                written += write_midx_pack_names(f, pack_names, nr_packs);
                                break;
 
+                       case MIDX_CHUNKID_OIDLOOKUP:
+                               written += write_midx_oid_lookup(f, MIDX_HASH_LEN, entries, nr_entries);
+                               break;
+
                        default:
                                BUG("trying to write unknown chunk id %"PRIx32,
                                    chunk_ids[i]);
object-store.h +1 -0
index 1ba50459ca..7d14d3586e 100644
@@ -102,6 +102,7 @@ struct midxed_git {
 
        const uint32_t *chunk_pack_lookup;
        const unsigned char *chunk_pack_names;
+       const unsigned char *chunk_oid_lookup;
 
        const char **pack_names;
        char object_dir[FLEX_ARRAY];
t/t5319-midx.sh +2 -3
index a31c387c8f..e71aa52b80 100755
@@ -6,8 +6,8 @@ test_description='multi-pack-indexes'
 midx_read_expect() {
        NUM_PACKS=$1
        cat >expect <<- EOF
-       header: 4d494458 1 1 2 $NUM_PACKS
-       chunks: pack_lookup pack_names
+       header: 4d494458 1 1 3 $NUM_PACKS
+       chunks: pack_lookup pack_names oid_lookup
        packs:
        EOF
        if [ $NUM_PACKS -ge 1 ]
--

2.18.0.rc1

Signature · 1 line
Duy
#49 lore raw

PATCH 13/23 midx: write object id fanout chunk

In reply to Derrick Stolee
---
 Documentation/technical/pack-format.txt |  5 +++
 builtin/midx.c                          |  4 +-
 midx.c                                  | 53 +++++++++++++++++++++++--
 object-store.h                          |  1 +
 t/t5319-midx.sh                         | 18 +++++----
 5 files changed, 69 insertions(+), 12 deletions(-)
Documentation/technical/pack-format.txt +5 -0
index de9ac778b6..77e88f85e4 100644
@@ -307,6 +307,11 @@ CHUNK DATA:
 	    name. This is the only chunk not guaranteed to be a multiple of four
 	    bytes in length, so should be the last chunk for alignment reasons.
 
+	OID Fanout (ID: {'O', 'I', 'D', 'F'}) (256 * 4 bytes)
+	    The ith entry, F[i], stores the number of OIDs with first
+	    byte at most i. Thus F[255] stores the total
+	    number of objects (N).
+
 	OID Lookup (ID: {'O', 'I', 'D', 'L'}) (N * H bytes)
 	    The OIDs for all objects in the MIDX are stored in lexicographic
 	    order in this chunk.
builtin/midx.c +3 -1
index 86edd30174..e1fd0e0de4 100644
@@ -35,10 +35,12 @@ static int read_midx_file(const char *object_dir)
 		printf(" pack_lookup");
 	if (m->chunk_pack_names)
 		printf(" pack_names");
+	if (m->chunk_oid_fanout)
+		printf(" oid_fanout");
 	if (m->chunk_oid_lookup)
 		printf(" oid_lookup");
 
-	printf("\n");
+	printf("\nnum_objects: %d\n", m->num_objects);
 
 	printf("packs:\n");
 	for (i = 0; i < m->num_packs; i++)
midx.c +50 -3

105 lines not on this page · raw message

object-store.h +1 -0
index 7d14d3586e..c613ff2571 100644
@@ -102,6 +102,7 @@ struct midxed_git {
 
 	const uint32_t *chunk_pack_lookup;
 	const unsigned char *chunk_pack_names;
+	const uint32_t *chunk_oid_fanout;
 	const unsigned char *chunk_oid_lookup;
 
 	const char **pack_names;
t/t5319-midx.sh +10 -8
index e71aa52b80..d4ae988479 100755
@@ -5,9 +5,11 @@ test_description='multi-pack-indexes'
 
 midx_read_expect() {
 	NUM_PACKS=$1
+	NUM_OBJECTS=$2
 	cat >expect <<- EOF
-	header: 4d494458 1 1 3 $NUM_PACKS
-	chunks: pack_lookup pack_names oid_lookup
+	header: 4d494458 1 1 4 $NUM_PACKS
+	chunks: pack_lookup pack_names oid_fanout oid_lookup
+	num_objects: $NUM_OBJECTS
 	packs:
 	EOF
 	if [ $NUM_PACKS -ge 1 ]
@@ -23,7 +25,7 @@ test_expect_success 'write midx with no packs' '
 	git midx --object-dir=. write &&
 	test_when_finished rm pack/multi-pack-index &&
 	test_path_is_file pack/multi-pack-index &&
-	midx_read_expect 0
+	midx_read_expect 0 0
 '
 
 test_expect_success 'create objects' '
@@ -54,18 +56,18 @@ test_expect_success 'write midx with one v1 pack' '
 	pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
 	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
 	git midx --object-dir=. write &&
-	midx_read_expect 1
+	midx_read_expect 1 17
 '
 
 test_expect_success 'write midx with one v2 pack' '
 	pack=$(git pack-objects --index-version=2,0x40 pack/test <obj-list) &&
 	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx &&
 	git midx --object-dir=. write &&
-	midx_read_expect 1
+	midx_read_expect 1 17
 '
 
 test_expect_success 'Add more objects' '
-	for i in `test_seq 6 5`
+	for i in `test_seq 6 10`
 	do
 		iii=$(printf '%03i' $i)
 		test-tool genrandom "bar" 200 > wide_delta_$iii &&
@@ -92,7 +94,7 @@ test_expect_success 'write midx with two packs' '
 	pack1=$(git pack-objects --index-version=1 pack/test-1 <obj-list) &&
 	pack2=$(git pack-objects --index-version=1 pack/test-2 <obj-list2) &&
 	git midx --object-dir=. write &&
-	midx_read_expect 2
+	midx_read_expect 2 33
 '
 
 test_expect_success 'Add more packs' '
@@ -123,7 +125,7 @@ test_expect_success 'Add more packs' '
 
 test_expect_success 'write midx with twelve packs' '
 	git midx --object-dir=. write &&
-	midx_read_expect 12
+	midx_read_expect 12 73
 '
 
 test_done
Signature · 1 line
2.18.0.rc1
#50 lore raw

Re: [PATCH 13/23] midx: write object id fanout chunk

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 127

Quoted 6 lines · Derrick Stolee
midx.c +2 -0
@@ -117,9 +123,13 @@ struct midxed_git *load_midxed_git(const char *object_dir)
                die("MIDX missing required pack lookup chunk");
        if (!m->chunk_pack_names)
                die("MIDX missing required pack-name chunk");
+       if (!m->chunk_oid_fanout)
+               die("MIDX missing required OID fanout chunk");

_()

midx.c int write_midx_file(const char *object_dir) · line 543

Quoted 6 lines
midx.c +1 -1
@@ -501,9 +540,13 @@ int write_midx_file(const char *object_dir)
        chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_packs * sizeof(uint32_t);
 
        cur_chunk++;
-       chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDLOOKUP;
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDFANOUT;

Err.. mistake?

midx.c int write_midx_file(const char *object_dir) · line 547

Quoted 4 lines
midx.c +2 -0
        chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + pack_name_concat_len;
 
+       cur_chunk++;
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDLOOKUP;

Same here.

Quoted 6 lines
midx.c +2 -0
+       chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + MIDX_CHUNK_FANOUT_SIZE;
+
        cur_chunk++;
        chunk_ids[cur_chunk] = 0;
        chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_entries * MIDX_HASH_LEN;
Signature · 1 line
Duy
#51 lore raw

Re: [PATCH 13/23] midx: write object id fanout chunk

Quoted 9 lines · Duy Nguyen

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 127

Quoted 6 lines · Derrick Stolee
midx.c +2 -0
@@ -117,9 +123,13 @@ struct midxed_git *load_midxed_git(const char *object_dir)
                 die("MIDX missing required pack lookup chunk");
         if (!m->chunk_pack_names)
                 die("MIDX missing required pack-name chunk");
+       if (!m->chunk_oid_fanout)
+               die("MIDX missing required OID fanout chunk");

_()

Quoted 7 lines

midx.c int write_midx_file(const char *object_dir) · line 543

Quoted 6 lines
midx.c +1 -1
@@ -501,9 +540,13 @@ int write_midx_file(const char *object_dir)
         chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_packs * sizeof(uint32_t);
 
         cur_chunk++;
-       chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDLOOKUP;
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDFANOUT;

Err.. mistake?

Not a mistake, just a side-effect of inserting the fanout before the lookup.

The commits are in this order because we need to construct the list before we build the fanout, but it makes sense to have the smaller fanout chunk before the lookup chunk.

Quoted 13 lines

midx.c int write_midx_file(const char *object_dir) · line 547

Quoted 5 lines
midx.c +2 -0
         chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + pack_name_concat_len;
 
+       cur_chunk++;
+       chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDLOOKUP;

Same here.

Quoted 6 lines
midx.c +2 -0
+       chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + MIDX_CHUNK_FANOUT_SIZE;
+
         cur_chunk++;
         chunk_ids[cur_chunk] = 0;
         chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_entries * MIDX_HASH_LEN;
#52 lore raw

PATCH 14/23 midx: write object offsets

In reply to Derrick Stolee

The final pair of chunks for the multi-pack-index (MIDX) file stores the object offsets. We default to using 32-bit offsets as in the pack-index version 1 format, but if there exists an offset larger than 32-bits, we use a trick similar to the pack-index version 2 format by storing all offsets at least 2^31 in a 64-bit table; we use the 32-bit table to point into that 64-bit table as necessary.

We only store these 64-bit offsets if necessary, so create a test that manipulates a version 2 pack-index to fake a large offset. This allows us to test that the large offset table is created, but the data does not match the actual packfile offsets. The MIDX offset does match the (corrupted) pack-index offset, so a later commit will compare these offsets during a 'verify' step.

---
 Documentation/technical/pack-format.txt |  15 +++-
 builtin/midx.c                          |   4 +
 midx.c                                  | 100 +++++++++++++++++++++++-
 object-store.h                          |   2 +
 t/t5319-midx.sh                         |  45 ++++++++---
 5 files changed, 151 insertions(+), 15 deletions(-)
Documentation/technical/pack-format.txt +14 -1
index 77e88f85e4..0256cfb5e0 100644
@@ -316,7 +316,20 @@ CHUNK DATA:
 	    The OIDs for all objects in the MIDX are stored in lexicographic
 	    order in this chunk.
 
-	(This section intentionally left incomplete.)
+	Object Offsets (ID: {'O', 'O', 'F', 'F'}) (N * 8 bytes)
+	    Stores two 4-byte values for every object.
+	    1: The pack-int-id for the pack storing this object.
+	    2: The offset within the pack.
+		If all offsets are less than 2^31, then the large offset chunk
+		will not exist and offsets are stored as in IDX v1.
+		If there is at least one offset value larger than 2^32-1, then
+		the large offset chunk must exist. If the large offset chunk
+		exists and the 31st bit is on, then removing that bit reveals
+		the row in the large offsets containing the 8-byte offset of
+		this object.
+
+	[Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'})
+	    8-byte offsets into large packfiles.
 
 TRAILER:
 
builtin/midx.c +4 -0
index e1fd0e0de4..607d2b3544 100644
@@ -39,6 +39,10 @@ static int read_midx_file(const char *object_dir)
 		printf(" oid_fanout");
 	if (m->chunk_oid_lookup)
 		printf(" oid_lookup");
+	if (m->chunk_object_offsets)
+		printf(" object_offsets");
+	if (m->chunk_large_offsets)
+		printf(" large_offsets");
 
 	printf("\nnum_objects: %d\n", m->num_objects);
 
midx.c +96 -4

163 lines not on this page · raw message

object-store.h +2 -0
index c613ff2571..9b671f1b0a 100644
@@ -104,6 +104,8 @@ struct midxed_git {
 	const unsigned char *chunk_pack_names;
 	const uint32_t *chunk_oid_fanout;
 	const unsigned char *chunk_oid_lookup;
+	const unsigned char *chunk_object_offsets;
+	const unsigned char *chunk_large_offsets;
 
 	const char **pack_names;
 	char object_dir[FLEX_ARRAY];
t/t5319-midx.sh +35 -10

88 lines not on this page · raw message

Signature · 1 line
2.18.0.rc1
#53 lore raw

Re: [PATCH 14/23] midx: write object offsets

Quoted 5 lines · Derrick Stolee
midx.c +5 -0
+static size_t write_midx_large_offsets(struct hashfile *f, uint32_t nr_large_offset,
+                                      struct pack_midx_entry *objects, uint32_t nr_objects)
+{
+       struct pack_midx_entry *list = objects;
+       size_t written = 0;

midx.c static size_t write_midx_oid_lookup(struct hashfile *f, unsigned char hash_len, · line 501

Quoted 10 lines
midx.c +10 -0
+
+       while (nr_large_offset) {
+               struct pack_midx_entry *obj = list++;
+               uint64_t offset = obj->offset;
+
+               if (!(offset >> 31))
+                       continue;
+
+               hashwrite_be32(f, offset >> 32);
+               hashwrite_be32(f, offset & 0xffffffff);

Not sure if you need UL suffix or something here on 32-bit platform.

Quoted 8 lines
midx.c +8 -0
+               written += 2 * sizeof(uint32_t);
+
+               nr_large_offset--;
+       }
+
+       return written;
+}
+
Signature · 1 line
Duy
#54 lore raw

PATCH 15/23 midx: create core.midx config setting

In reply to Derrick Stolee

The core.midx config setting controls the multi-pack-index (MIDX) feature. If false, the setting will disable all reads from the multi-pack-index file.

Add comparison commands in t5319-midx.sh to check typical Git behavior remains the same as the config setting is turned on and off. This currently includes 'git rev-list' and 'git log' commands to trigger several object database reads.

---
 Documentation/config.txt |  4 +++
 cache.h                  |  1 +
 config.c                 |  5 ++++
 environment.c            |  1 +
 t/t5319-midx.sh          | 57 ++++++++++++++++++++++++++++++++--------
 5 files changed, 57 insertions(+), 11 deletions(-)
Documentation/config.txt +4 -0
index ab641bf5a9..e78150e452 100644
@@ -908,6 +908,10 @@ core.commitGraph::
 	Enable git commit graph feature. Allows reading from the
 	commit-graph file.
 
+core.midx::
+	Enable multi-pack-index feature. Allows reading from the multi-
+	pack-index file.
+
 core.sparseCheckout::
 	Enable "sparse checkout" feature. See section "Sparse checkout" in
 	linkgit:git-read-tree[1] for more information.
cache.h +1 -0
index 89a107a7f7..c7967f7643 100644
@@ -814,6 +814,7 @@ extern char *git_replace_ref_base;
 extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_commit_graph;
+extern int core_midx;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
 extern int protect_hfs;
config.c +5 -0
index fbbf0f8e9f..0df3dbdf74 100644
@@ -1313,6 +1313,11 @@ static int git_default_core_config(const char *var, const char *value)
 		return 0;
 	}
 
+	if (!strcmp(var, "core.midx")) {
+		core_midx = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "core.sparsecheckout")) {
 		core_apply_sparse_checkout = git_config_bool(var, value);
 		return 0;
environment.c +1 -0
index 2a6de2330b..dcb4417604 100644
@@ -67,6 +67,7 @@ enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
 char *notes_ref_name;
 int grafts_replace_parents = 1;
 int core_commit_graph;
+int core_midx;
 int core_apply_sparse_checkout;
 int merge_log_config = -1;
 int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
t/t5319-midx.sh +46 -11

94 lines not on this page · raw message

Signature · 1 line
2.18.0.rc1
#55 lore raw

PATCH 16/23 midx: prepare midxed_git struct

In reply to Derrick Stolee
---
 midx.c         | 22 ++++++++++++++++++++++
 midx.h         |  2 ++
 object-store.h |  7 +++++++
 packfile.c     |  6 +++++-
 4 files changed, 36 insertions(+), 1 deletion(-)
midx.c +22 -0
index a49300bf75..5e9290ca8f 100644
@@ -175,6 +175,28 @@ struct midxed_git *load_midxed_git(const char *object_dir)
 	exit(1);
 }
 
+int prepare_midxed_git_one(struct repository *r, const char *object_dir)
+{
+	struct midxed_git *m = r->objects->midxed_git;
+	struct midxed_git *m_search;
+
+	if (!core_midx)
+		return 0;
+
+	for (m_search = m; m_search; m_search = m_search->next)
+		if (!strcmp(object_dir, m_search->object_dir))
+			return 1;
+
+	r->objects->midxed_git = load_midxed_git(object_dir);
+
+	if (r->objects->midxed_git) {
+		r->objects->midxed_git->next = m;
+		return 1;
+	}
+
+	return 0;
+}
+
 static size_t write_midx_header(struct hashfile *f,
 				unsigned char num_chunks,
 				uint32_t num_packs)
midx.h +2 -0
index a1d18ed991..793203fc4a 100644
@@ -5,8 +5,10 @@
 #include "cache.h"
 #include "object-store.h"
 #include "packfile.h"
+#include "repository.h"
 
 struct midxed_git *load_midxed_git(const char *object_dir);
+int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
 
object-store.h +7 -0
index 9b671f1b0a..7908d46e34 100644
@@ -130,6 +130,13 @@ struct raw_object_store {
 	 */
 	struct oidmap *replace_map;
 
+	/*
+	 * private data
+	 *
+	 * should only be accessed directly by packfile.c and midx.c
+	 */
+	struct midxed_git *midxed_git;
+
 	/*
 	 * private data
 	 *
packfile.c +5 -1
index 1a714fbde9..b91ca9b9f5 100644
@@ -15,6 +15,7 @@
 #include "tree-walk.h"
 #include "tree.h"
 #include "object-store.h"
+#include "midx.h"
 
 char *odb_pack_name(struct strbuf *buf,
 		    const unsigned char *sha1,
@@ -893,10 +894,13 @@ static void prepare_packed_git(struct repository *r)
 
 	if (r->objects->packed_git_initialized)
 		return;
+	prepare_midxed_git_one(r, r->objects->objectdir);
 	prepare_packed_git_one(r, r->objects->objectdir, 1);
 	prepare_alt_odb(r);
-	for (alt = r->objects->alt_odb_list; alt; alt = alt->next)
+	for (alt = r->objects->alt_odb_list; alt; alt = alt->next) {
+		prepare_midxed_git_one(r, alt->path);
 		prepare_packed_git_one(r, alt->path, 0);
+	}
 	rearrange_packed_git(r);
 	prepare_packed_git_mru(r);
 	r->objects->packed_git_initialized = 1;
Signature · 1 line
2.18.0.rc1
#56 lore raw

Re: [PATCH 16/23] midx: prepare midxed_git struct

Quoted 89 lines · Derrick Stolee
---
 midx.c         | 22 ++++++++++++++++++++++
 midx.h         |  2 ++
 object-store.h |  7 +++++++
 packfile.c     |  6 +++++-
 4 files changed, 36 insertions(+), 1 deletion(-)
midx.c +22 -0
index a49300bf75..5e9290ca8f 100644
@@ -175,6 +175,28 @@ struct midxed_git *load_midxed_git(const char *object_dir)
        exit(1);
 }
 
+int prepare_midxed_git_one(struct repository *r, const char *object_dir)
+{
+       struct midxed_git *m = r->objects->midxed_git;
+       struct midxed_git *m_search;
+
+       if (!core_midx)
+               return 0;
+
+       for (m_search = m; m_search; m_search = m_search->next)
+               if (!strcmp(object_dir, m_search->object_dir))
+                       return 1;
+
+       r->objects->midxed_git = load_midxed_git(object_dir);
+
+       if (r->objects->midxed_git) {
+               r->objects->midxed_git->next = m;
+               return 1;
+       }
+
+       return 0;
+}
+
 static size_t write_midx_header(struct hashfile *f,
                                unsigned char num_chunks,
                                uint32_t num_packs)
midx.h +2 -0
index a1d18ed991..793203fc4a 100644
@@ -5,8 +5,10 @@
 #include "cache.h"
 #include "object-store.h"
 #include "packfile.h"
+#include "repository.h"
 
 struct midxed_git *load_midxed_git(const char *object_dir);
+int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
 
object-store.h +7 -0
index 9b671f1b0a..7908d46e34 100644
@@ -130,6 +130,13 @@ struct raw_object_store {
         */
        struct oidmap *replace_map;
 
+       /*
+        * private data
+        *
+        * should only be accessed directly by packfile.c and midx.c
+        */
+       struct midxed_git *midxed_git;
+
        /*
         * private data
         *
packfile.c +1 -0
index 1a714fbde9..b91ca9b9f5 100644
@@ -15,6 +15,7 @@
 #include "tree-walk.h"
 #include "tree.h"
 #include "object-store.h"
+#include "midx.h"
 
 char *odb_pack_name(struct strbuf *buf,
                    const unsigned char *sha1,
@@ -893,10 +894,13 @@ static void prepare_packed_git(struct repository *r)
 

packfile.c static void prepare_packed_git(struct repository *r) · line 903

Quoted 10 lines
packfile.c +4 -1
        if (r->objects->packed_git_initialized)
                return;
+       prepare_midxed_git_one(r, r->objects->objectdir);
        prepare_packed_git_one(r, r->objects->objectdir, 1);
        prepare_alt_odb(r);
-       for (alt = r->objects->alt_odb_list; alt; alt = alt->next)
+       for (alt = r->objects->alt_odb_list; alt; alt = alt->next) {
+               prepare_midxed_git_one(r, alt->path);
                prepare_packed_git_one(r, alt->path, 0);
+       }

Ah, so the object path and the linked list in midxed_git is for alternates. Makes sense. Would have saved me the trouble if you only introduced those fields now, when they are actually used (and become self explanatory)

Quoted 6 lines
packfile.c +0 -1
        rearrange_packed_git(r);
        prepare_packed_git_mru(r);
        r->objects->packed_git_initialized = 1;
--

2.18.0.rc1

Signature · 1 line
Duy
#57 lore raw

PATCH 17/23 midx: read objects from multi-pack-index

In reply to Derrick Stolee
---
 midx.c         | 96 ++++++++++++++++++++++++++++++++++++++++++++++++--
 midx.h         |  2 ++
 object-store.h |  1 +
 packfile.c     |  8 ++++-
 4 files changed, 104 insertions(+), 3 deletions(-)
midx.c +94 -2
index 5e9290ca8f..6eca8f1b12 100644
@@ -3,6 +3,7 @@
 #include "dir.h"
 #include "csum-file.h"
 #include "lockfile.h"
+#include "sha1-lookup.h"
 #include "object-store.h"
 #include "packfile.h"
 #include "midx.h"
@@ -64,7 +65,7 @@ struct midxed_git *load_midxed_git(const char *object_dir)
 
 	m = xcalloc(1, sizeof(*m) + strlen(object_dir) + 1);
 	strcpy(m->object_dir, object_dir);
-	m->data = midx_map;
+	m->data = (const unsigned char*)midx_map;
 
 	m->signature = get_be32(m->data);
 	if (m->signature != MIDX_SIGNATURE) {
@@ -145,7 +146,9 @@ struct midxed_git *load_midxed_git(const char *object_dir)
 
 	m->num_objects = ntohl(m->chunk_oid_fanout[255]);
 
-	m->pack_names = xcalloc(m->num_packs, sizeof(const char *));
+	m->packs = xcalloc(m->num_packs, sizeof(*m->packs));
+
+	ALLOC_ARRAY(m->pack_names, m->num_packs);
 	for (i = 0; i < m->num_packs; i++) {
 		if (i) {
 			if (ntohl(m->chunk_pack_lookup[i]) <= ntohl(m->chunk_pack_lookup[i - 1])) {
@@ -175,6 +178,95 @@ struct midxed_git *load_midxed_git(const char *object_dir)
 	exit(1);
 }
 
+static int prepare_midx_pack(struct midxed_git *m, uint32_t pack_int_id)
+{
+	struct strbuf pack_name = STRBUF_INIT;
+
+	if (pack_int_id >= m->num_packs)
+		BUG("bad pack-int-id");
+
+	if (m->packs[pack_int_id])
+		return 0;
+
+	strbuf_addstr(&pack_name, m->object_dir);
+	strbuf_addstr(&pack_name, "/pack/");
+	strbuf_addstr(&pack_name, m->pack_names[pack_int_id]);
+
+	m->packs[pack_int_id] = add_packed_git(pack_name.buf, pack_name.len, 1);
+	strbuf_release(&pack_name);
+	return !m->packs[pack_int_id];
+}
+
+int bsearch_midx(const struct object_id *oid, struct midxed_git *m, uint32_t *result)
+{
+	return bsearch_hash(oid->hash, m->chunk_oid_fanout, m->chunk_oid_lookup,
+			    MIDX_HASH_LEN, result);
+}
+
+static off_t nth_midxed_offset(struct midxed_git *m, uint32_t pos)
+{
+	const unsigned char *offset_data;
+	uint32_t offset32;
+
+	offset_data = m->chunk_object_offsets + pos * MIDX_CHUNK_OFFSET_WIDTH;
+	offset32 = get_be32(offset_data + sizeof(uint32_t));
+
+	if (m->chunk_large_offsets && offset32 & MIDX_LARGE_OFFSET_NEEDED) {
+		if (sizeof(offset32) < sizeof(uint64_t))
+			die(_("multi-pack-index stores a 64-bit offset, but off_t is too small"));
+
+		offset32 ^= MIDX_LARGE_OFFSET_NEEDED;
+		return get_be64(m->chunk_large_offsets + sizeof(uint64_t) * offset32);
+	}
+
+	return offset32;
+}
+
+static uint32_t nth_midxed_pack_int_id(struct midxed_git *m, uint32_t pos)
+{
+	return get_be32(m->chunk_object_offsets + pos * MIDX_CHUNK_OFFSET_WIDTH);
+}
+
+static int nth_midxed_pack_entry(struct midxed_git *m, struct pack_entry *e, uint32_t pos)
+{
+	uint32_t pack_int_id;
+	struct packed_git *p;
+
+	if (pos >= m->num_objects)
+		return 0;
+
+	pack_int_id = nth_midxed_pack_int_id(m, pos);
+
+	if (prepare_midx_pack(m, pack_int_id))
+		die(_("error preparing packfile from multi-pack-index"));
+	p = m->packs[pack_int_id];
+
+	/*
+	* We are about to tell the caller where they can locate the
+	* requested object.  We better make sure the packfile is
+	* still here and can be accessed before supplying that
+	* answer, as it may have been deleted since the MIDX was
+	* loaded!
+	*/
+	if (!is_pack_valid(p))
+		return 0;
+
+	e->offset = nth_midxed_offset(m, pos);
+	e->p = p;
+
+	return 1;
+}
+
+int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct midxed_git *m)
+{
+	uint32_t pos;
+
+	if (!bsearch_midx(oid, m, &pos))
+		return 0;
+
+	return nth_midxed_pack_entry(m, e, pos);
+}
+
 int prepare_midxed_git_one(struct repository *r, const char *object_dir)
 {
 	struct midxed_git *m = r->objects->midxed_git;
midx.h +2 -0
index 793203fc4a..0c66812229 100644
@@ -8,6 +8,8 @@
 #include "repository.h"
 
 struct midxed_git *load_midxed_git(const char *object_dir);
+int bsearch_midx(const struct object_id *oid, struct midxed_git *m, uint32_t *result);
+int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct midxed_git *m);
 int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
object-store.h +1 -0
index 7908d46e34..5af2a852bc 100644
@@ -108,6 +108,7 @@ struct midxed_git {
 	const unsigned char *chunk_large_offsets;
 
 	const char **pack_names;
+	struct packed_git **packs;
 	char object_dir[FLEX_ARRAY];
 };
 
packfile.c +7 -1
index b91ca9b9f5..73f8cc28ee 100644
@@ -1857,11 +1857,17 @@ static int fill_pack_entry(const struct object_id *oid,
 int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e)
 {
 	struct list_head *pos;
+	struct midxed_git *m;
 
 	prepare_packed_git(r);
-	if (!r->objects->packed_git)
+	if (!r->objects->packed_git && !r->objects->midxed_git)
 		return 0;
 
+	for (m = r->objects->midxed_git; m; m = m->next) {
+		if (fill_midx_entry(oid, e, m))
+			return 1;
+	}
+
 	list_for_each(pos, &r->objects->packed_git_mru) {
 		struct packed_git *p = list_entry(pos, struct packed_git, mru);
 		if (fill_pack_entry(oid, e, p)) {
Signature · 1 line
2.18.0.rc1
#58 lore raw

Re: [PATCH 17/23] midx: read objects from multi-pack-index

Quoted 22 lines · Derrick Stolee
---
 midx.c         | 96 ++++++++++++++++++++++++++++++++++++++++++++++++--
 midx.h         |  2 ++
 object-store.h |  1 +
 packfile.c     |  8 ++++-
 4 files changed, 104 insertions(+), 3 deletions(-)
midx.c +1 -0
index 5e9290ca8f..6eca8f1b12 100644
@@ -3,6 +3,7 @@
 #include "dir.h"
 #include "csum-file.h"
 #include "lockfile.h"
+#include "sha1-lookup.h"
 #include "object-store.h"
 #include "packfile.h"
 #include "midx.h"

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 68

Quoted 5 lines
midx.c +1 -1
@@ -64,7 +65,7 @@ struct midxed_git *load_midxed_git(const char *object_dir)
 
        m = xcalloc(1, sizeof(*m) + strlen(object_dir) + 1);
        strcpy(m->object_dir, object_dir);
-       m->data = midx_map;
+       m->data = (const unsigned char*)midx_map;

Hmm? Why is this typecast only needed now? Or is it not really needed at all?

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 151

Quoted 11 lines
midx.c +3 -1
        m->signature = get_be32(m->data);
        if (m->signature != MIDX_SIGNATURE) {
@@ -145,7 +146,9 @@ struct midxed_git *load_midxed_git(const char *object_dir)
 
        m->num_objects = ntohl(m->chunk_oid_fanout[255]);
 
-       m->pack_names = xcalloc(m->num_packs, sizeof(const char *));
+       m->packs = xcalloc(m->num_packs, sizeof(*m->packs));
+
+       ALLOC_ARRAY(m->pack_names, m->num_packs);

Please make this ALLOC_ARRAY change in the patch that adds xcalloc(m->num_packs).

Quoted 10 lines
midx.c +3 -0
        for (i = 0; i < m->num_packs; i++) {
                if (i) {
                        if (ntohl(m->chunk_pack_lookup[i]) <= ntohl(m->chunk_pack_lookup[i - 1])) {
@@ -175,6 +178,95 @@ struct midxed_git *load_midxed_git(const char *object_dir)
        exit(1);
 }
 
+static int prepare_midx_pack(struct midxed_git *m, uint32_t pack_int_id)
+{
+       struct strbuf pack_name = STRBUF_INIT;

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 193

Quoted 10 lines
midx.c +10 -0
+
+       if (pack_int_id >= m->num_packs)
+               BUG("bad pack-int-id");
+
+       if (m->packs[pack_int_id])
+               return 0;
+
+       strbuf_addstr(&pack_name, m->object_dir);
+       strbuf_addstr(&pack_name, "/pack/");
+       strbuf_addstr(&pack_name, m->pack_names[pack_int_id]);

Just use strbuf_addf()

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 197

Quoted 4 lines
midx.c +4 -0
+
+       m->packs[pack_int_id] = add_packed_git(pack_name.buf, pack_name.len, 1);
+       strbuf_release(&pack_name);
+       return !m->packs[pack_int_id];

This is a weird return value convention. Normally we go zero/negative or non-zero/zero for success/failure.

Quoted 1 line
midx.c +1 -0
+}
Signature · 1 line
Duy
#59 lore raw

Re: [PATCH 17/23] midx: read objects from multi-pack-index

Quoted 64 lines · Duy Nguyen
Quoted 21 lines · Derrick Stolee
---
  midx.c         | 96 ++++++++++++++++++++++++++++++++++++++++++++++++--
  midx.h         |  2 ++
  object-store.h |  1 +
  packfile.c     |  8 ++++-
  4 files changed, 104 insertions(+), 3 deletions(-)
midx.c +1 -0
index 5e9290ca8f..6eca8f1b12 100644
@@ -3,6 +3,7 @@
  #include "dir.h"
  #include "csum-file.h"
  #include "lockfile.h"
+#include "sha1-lookup.h"
  #include "object-store.h"
  #include "packfile.h"
  #include "midx.h"

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 68

Quoted 5 lines
midx.c +1 -1
@@ -64,7 +65,7 @@ struct midxed_git *load_midxed_git(const char *object_dir)
 
         m = xcalloc(1, sizeof(*m) + strlen(object_dir) + 1);
         strcpy(m->object_dir, object_dir);
-       m->data = midx_map;
+       m->data = (const unsigned char*)midx_map;

Hmm? Why is this typecast only needed now? Or is it not really needed at all?

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 151

Quoted 10 lines
midx.c +3 -1
         m->signature = get_be32(m->data);
         if (m->signature != MIDX_SIGNATURE) {
@@ -145,7 +146,9 @@ struct midxed_git *load_midxed_git(const char *object_dir)
 
         m->num_objects = ntohl(m->chunk_oid_fanout[255]);
 
-       m->pack_names = xcalloc(m->num_packs, sizeof(const char *));
+       m->packs = xcalloc(m->num_packs, sizeof(*m->packs));
+
+       ALLOC_ARRAY(m->pack_names, m->num_packs);

Please make this ALLOC_ARRAY change in the patch that adds xcalloc(m->num_packs).

Quoted 10 lines
midx.c +3 -0
         for (i = 0; i < m->num_packs; i++) {
                 if (i) {
                         if (ntohl(m->chunk_pack_lookup[i]) <= ntohl(m->chunk_pack_lookup[i - 1])) {
@@ -175,6 +178,95 @@ struct midxed_git *load_midxed_git(const char *object_dir)
         exit(1);
  }
 
+static int prepare_midx_pack(struct midxed_git *m, uint32_t pack_int_id)
+{
+       struct strbuf pack_name = STRBUF_INIT;

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 193

Quoted 10 lines
midx.c +10 -0
+
+       if (pack_int_id >= m->num_packs)
+               BUG("bad pack-int-id");
+
+       if (m->packs[pack_int_id])
+               return 0;
+
+       strbuf_addstr(&pack_name, m->object_dir);
+       strbuf_addstr(&pack_name, "/pack/");
+       strbuf_addstr(&pack_name, m->pack_names[pack_int_id]);

Just use strbuf_addf()

Quoted 6 lines

midx.c struct midxed_git *load_midxed_git(const char *object_dir) · line 197

Quoted 4 lines
midx.c +4 -0
+
+       m->packs[pack_int_id] = add_packed_git(pack_name.buf, pack_name.len, 1);
+       strbuf_release(&pack_name);
+       return !m->packs[pack_int_id];

This is a weird return value convention. Normally we go zero/negative or non-zero/zero for success/failure.

We are inconsistent.

* open_pack_index() returns non-zero on error. (This was my reference point.)
* bsearch_pack() and find_pack_entry() return non-zero when an entry is found.

Since the use is "if (error) die()", similar to open_pack_index(), I'll keep the current behavior. To switch would require using "!!m->packs[pack_int_id]" here and "if (!prepare_midx_pack()) die()" in the consumer.

Thanks,
-Stolee

#60 lore raw

PATCH 18/23 midx: use midx in abbreviation calculations

In reply to Derrick Stolee
---
 midx.c          | 11 ++++++++
 midx.h          |  3 +++
 packfile.c      |  6 +++++
 packfile.h      |  1 +
 sha1-name.c     | 70 +++++++++++++++++++++++++++++++++++++++++++++++++
 t/t5319-midx.sh |  3 ++-
 6 files changed, 93 insertions(+), 1 deletion(-)
midx.c +11 -0
index 6eca8f1b12..25d8142c2a 100644
@@ -203,6 +203,17 @@ int bsearch_midx(const struct object_id *oid, struct midxed_git *m, uint32_t *re
 			    MIDX_HASH_LEN, result);
 }
 
+struct object_id *nth_midxed_object_oid(struct object_id *oid,
+					struct midxed_git *m,
+					uint32_t n)
+{
+	if (n >= m->num_objects)
+		return NULL;
+
+	hashcpy(oid->hash, m->chunk_oid_lookup + m->hash_len * n);
+	return oid;
+}
+
 static off_t nth_midxed_offset(struct midxed_git *m, uint32_t pos)
 {
 	const unsigned char *offset_data;
midx.h +3 -0
index 0c66812229..497bdcc77c 100644
@@ -9,6 +9,9 @@
 
 struct midxed_git *load_midxed_git(const char *object_dir);
 int bsearch_midx(const struct object_id *oid, struct midxed_git *m, uint32_t *result);
+struct object_id *nth_midxed_object_oid(struct object_id *oid,
+					struct midxed_git *m,
+					uint32_t n);
 int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct midxed_git *m);
 int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
packfile.c +6 -0
index 73f8cc28ee..638e113972 100644
@@ -919,6 +919,12 @@ struct packed_git *get_packed_git(struct repository *r)
 	return r->objects->packed_git;
 }
 
+struct midxed_git *get_midxed_git(struct repository *r)
+{
+	prepare_packed_git(r);
+	return r->objects->midxed_git;
+}
+
 struct list_head *get_packed_git_mru(struct repository *r)
 {
 	prepare_packed_git(r);
packfile.h +1 -0
index e0a38aba93..01e14b93fd 100644
@@ -39,6 +39,7 @@ extern void install_packed_git(struct repository *r, struct packed_git *pack);
 
 struct packed_git *get_packed_git(struct repository *r);
 struct list_head *get_packed_git_mru(struct repository *r);
+struct midxed_git *get_midxed_git(struct repository *r);
 
 /*
  * Give a rough count of objects in the repository. This sacrifices accuracy
sha1-name.c +70 -0

104 lines not on this page · raw message

t/t5319-midx.sh +2 -1
index 1a50987778..e3873da7d6 100755
@@ -94,7 +94,8 @@ compare_results_with_midx() {
 	MSG=$1
 	test_expect_success "check normal git operations: $MSG" '
 		midx_git_two_modes "rev-list --objects --all" &&
-		midx_git_two_modes "log --raw"
+		midx_git_two_modes "log --raw" &&
+		midx_git_two_modes "log --oneline"
 	'
 }
 
Signature · 1 line
2.18.0.rc1
#61 lore raw

Re: [PATCH 18/23] midx: use midx in abbreviation calculations

sha1-name.c static void find_abbrev_len_for_pack(struct packed_git *p, · line 639

Quoted 9 lines · Derrick Stolee
sha1-name.c +3 -0
@@ -565,8 +632,11 @@ static void find_abbrev_len_for_pack(struct packed_git *p,
 
 static void find_abbrev_len_packed(struct min_abbrev_data *mad)
 {
+       struct midxed_git *m;
        struct packed_git *p;
 
+       for (m = get_midxed_git(the_repository); m; m = m->next)
+               find_abbrev_len_for_midx(m, mad);

If all the packs are in midx, we don't need to run the second loop below, do we? Otherwise I don't see why we waste cycles on finding abbrev length on midx at all.

Quoted 3 lines
sha1-name.c +0 -0
        for (p = get_packed_git(the_repository); p; p = p->next)
                find_abbrev_len_for_pack(p, mad);
 }
Signature · 1 line
Duy
#62 lore raw

Re: [PATCH 18/23] midx: use midx in abbreviation calculations

Quoted 8 lines · Duy Nguyen
Quoted 7 lines · Derrick Stolee
sha1-name.c +1 -0
@@ -565,8 +632,11 @@ static void find_abbrev_len_for_pack(struct packed_git *p,
 
  static void find_abbrev_len_packed(struct min_abbrev_data *mad)
  {
+       struct midxed_git *m;
         struct packed_git *p;
 
Quoted 5 lines

sha1-name.c static void find_abbrev_len_for_pack(struct packed_git *p, · line 639

Quoted 2 lines
sha1-name.c +2 -0
+       for (m = get_midxed_git(the_repository); m; m = m->next)
+               find_abbrev_len_for_midx(m, mad);

If all the packs are in midx, we don't need to run the second loop below, do we? Otherwise I don't see why we waste cycles on finding abbrev length on midx at all.

We put all packs _at time of writing_ into the midx. More packs may be added later that are not in the midx. There are tests in t5319-multi-pack-index.sh that verify everything works in this "mixed mode".

It is important that the packfiles are not loaded into the packed_git list if they are managed by the midx.

Quoted 4 lines
Quoted 4 lines
sha1-name.c +0 -0
         for (p = get_packed_git(the_repository); p; p = p->next)
                 find_abbrev_len_for_pack(p, mad);
  }
#63 lore raw

PATCH 19/23 midx: use existing midx when writing new one

In reply to Derrick Stolee
---
 midx.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 63 insertions(+), 5 deletions(-)
midx.c +63 -5
index 25d8142c2a..388d79b7d9 100644
@@ -389,6 +389,23 @@ static int midx_oid_compare(const void *_a, const void *_b)
 	return a->pack_int_id - b->pack_int_id;
 }
 
+static int nth_midxed_pack_midx_entry(struct midxed_git *m,
+				      uint32_t *pack_perm,
+				      struct pack_midx_entry *e,
+				      uint32_t pos)
+{
+	if (pos >= m->num_objects)
+		return 1;
+
+	nth_midxed_object_oid(&e->oid, m, pos);
+	e->pack_int_id = pack_perm[nth_midxed_pack_int_id(m, pos)];
+	e->offset = nth_midxed_offset(m, pos);
+
+	/* consider objects in midx to be from "old" packs */
+	e->pack_mtime = 0;
+	return 0;
+}
+
 static void fill_pack_entry(uint32_t pack_int_id,
 			    struct packed_git *p,
 			    uint32_t cur_object,
@@ -414,7 +431,8 @@ static void fill_pack_entry(uint32_t pack_int_id,
  * Copy only the de-duplicated entries (selected by most-recent modified time
  * of a packfile containing the object).
  */
-static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
+static struct pack_midx_entry *get_sorted_entries(struct midxed_git *m,
+						  struct packed_git **p,
 						  uint32_t *perm,
 						  uint32_t nr_packs,
 						  uint32_t *nr_objects)
@@ -423,8 +441,9 @@ static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
 	uint32_t nr_fanout, alloc_fanout, alloc_objects, total_objects = 0;
 	struct pack_midx_entry *entries_by_fanout = NULL;
 	struct pack_midx_entry *deduplicated_entries = NULL;
+	uint32_t start_pack = m ? m->num_packs : 0;
 
-	for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+	for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) {
 		if (open_pack_index(p[cur_pack]))
 			continue;
 
@@ -445,7 +464,23 @@ static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
 	for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) {
 		nr_fanout = 0;
 
-		for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+		if (m) {
+			uint32_t start = 0, end;
+
+			if (cur_fanout)
+				start = ntohl(m->chunk_oid_fanout[cur_fanout - 1]);
+			end = ntohl(m->chunk_oid_fanout[cur_fanout]);
+
+			for (cur_object = start; cur_object < end; cur_object++) {
+				ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
+				nth_midxed_pack_midx_entry(m, perm,
+							   &entries_by_fanout[nr_fanout],
+							   cur_object);
+				nr_fanout++;
+			}
+		}
+
+		for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) {
 			uint32_t start = 0, end;
 
 			if (cur_fanout)
@@ -654,6 +689,7 @@ int write_midx_file(const char *object_dir)
 	struct pack_midx_entry *entries;
 	uint32_t nr_entries, num_large_offsets = 0;
 	int large_offsets_needed = 0;
+	struct midxed_git *m = NULL;
 
 	midx_name = get_midx_filename(object_dir);
 	if (safe_create_leading_directories(midx_name)) {
@@ -662,6 +698,8 @@ int write_midx_file(const char *object_dir)
 			  midx_name);
 	}
 
+	m = load_midxed_git(object_dir);
+
 	strbuf_addf(&pack_dir, "%s/pack", object_dir);
 	dir = opendir(pack_dir.buf);
 
@@ -676,11 +714,27 @@ int write_midx_file(const char *object_dir)
 	pack_dir_len = pack_dir.len;
 	ALLOC_ARRAY(packs, alloc_packs);
 	ALLOC_ARRAY(pack_names, alloc_pack_names);
+
+	if (m) {
+		for (i = 0; i < m->num_packs; i++) {
+			ALLOC_GROW(packs, nr_packs + 1, alloc_packs);
+			ALLOC_GROW(pack_names, nr_packs + 1, alloc_pack_names);
+
+			packs[nr_packs] = NULL;
+			pack_names[nr_packs] = xstrdup(m->pack_names[i]);
+			pack_name_concat_len += strlen(pack_names[nr_packs]) + 1;
+			nr_packs++;
+		}
+	}
+
 	while ((de = readdir(dir)) != NULL) {
 		if (is_dot_or_dotdot(de->d_name))
 			continue;
 
 		if (ends_with(de->d_name, ".idx")) {
+			if (m && midx_contains_pack(m, de->d_name))
+				continue;
+
 			ALLOC_GROW(packs, nr_packs + 1, alloc_packs);
 			ALLOC_GROW(pack_names, nr_packs + 1, alloc_pack_names);
 
@@ -705,6 +759,9 @@ int write_midx_file(const char *object_dir)
 	closedir(dir);
 	strbuf_release(&pack_dir);
 
+	if (m && nr_packs == m->num_packs)
+		goto cleanup;
+
 	if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT)
 		pack_name_concat_len += MIDX_CHUNK_ALIGNMENT -
 					(pack_name_concat_len % MIDX_CHUNK_ALIGNMENT);
@@ -712,7 +769,7 @@ int write_midx_file(const char *object_dir)
 	ALLOC_ARRAY(pack_perm, nr_packs);
 	sort_packs_by_name(pack_names, nr_packs, pack_perm);
 
-	entries = get_sorted_entries(packs, pack_perm, nr_packs, &nr_entries);
+	entries = get_sorted_entries(m, packs, pack_perm, nr_packs, &nr_entries);
 	for (i = 0; i < nr_entries; i++) {
 		if (entries[i].offset > 0x7fffffff)
 			num_large_offsets++;
@@ -823,7 +880,8 @@ int write_midx_file(const char *object_dir)
 	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
 	commit_lock_file(&lk);
 
-	for (i = 0; i < nr_packs; i++) {
+cleanup:
+	for (i = m ? m->num_packs : 0; i < nr_packs; i++) {
 		close_pack(packs[i]);
 		FREE_AND_NULL(packs[i]);
 	}
Signature · 1 line
2.18.0.rc1
#64 lore raw

PATCH 20/23 midx: use midx in approximate_object_count

In reply to Derrick Stolee
---
 packfile.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
packfile.c +4 -1
index 638e113972..059b2aa097 100644
@@ -819,11 +819,14 @@ unsigned long approximate_object_count(void)
 {
 	if (!the_repository->objects->approximate_object_count_valid) {
 		unsigned long count;
+		struct midxed_git *m;
 		struct packed_git *p;
 
 		prepare_packed_git(the_repository);
 		count = 0;
-		for (p = the_repository->objects->packed_git; p; p = p->next) {
+		for (m = get_midxed_git(the_repository); m; m = m->next)
+			count += m->num_objects;
+		for (p = get_packed_git(the_repository); p; p = p->next) {
 			if (open_pack_index(p))
 				continue;
 			count += p->num_objects;
Signature · 1 line
2.18.0.rc1
#65 lore raw

Re: [PATCH 20/23] midx: use midx in approximate_object_count

Quoted 13 lines · Derrick Stolee
---
 packfile.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
packfile.c +0 -0
index 638e113972..059b2aa097 100644
@@ -819,11 +819,14 @@ unsigned long approximate_object_count(void)
 {
        if (!the_repository->objects->approximate_object_count_valid) {

packfile.c unsigned long approximate_object_count(void) · line 829

Quoted 10 lines
packfile.c +4 -1
                unsigned long count;
+               struct midxed_git *m;
                struct packed_git *p;
 
                prepare_packed_git(the_repository);
                count = 0;
-               for (p = the_repository->objects->packed_git; p; p = p->next) {
+               for (m = get_midxed_git(the_repository); m; m = m->next)
+                       count += m->num_objects;
+               for (p = get_packed_git(the_repository); p; p = p->next) {

Please don't change this line, it's not related to this patch. Same concern applies, if we have already counted objects in midx we should ignore packs that belong to it or we double count.

Quoted 6 lines
packfile.c +0 -1
                        if (open_pack_index(p))
                                continue;
                        count += p->num_objects;
--

2.18.0.rc1

Signature · 1 line
Duy
#66 lore raw

Re: [PATCH 20/23] midx: use midx in approximate_object_count

Quoted 17 lines · Duy Nguyen
Quoted 16 lines · Derrick Stolee
---
  packfile.c | 5 ++++-
  1 file changed, 4 insertions(+), 1 deletion(-)
packfile.c +1 -0
index 638e113972..059b2aa097 100644
@@ -819,11 +819,14 @@ unsigned long approximate_object_count(void)
  {
         if (!the_repository->objects->approximate_object_count_valid) {
                 unsigned long count;
+               struct midxed_git *m;
                 struct packed_git *p;
 
Quoted 7 lines

packfile.c unsigned long approximate_object_count(void) · line 829

Quoted 6 lines
packfile.c +3 -1
                 prepare_packed_git(the_repository);
                 count = 0;
-               for (p = the_repository->objects->packed_git; p; p = p->next) {
+               for (m = get_midxed_git(the_repository); m; m = m->next)
+                       count += m->num_objects;
+               for (p = get_packed_git(the_repository); p; p = p->next) {

Please don't change this line, it's not related to this patch.

Sure. I'll revert that line.

Quoted 3 lines

Same
concern applies, if we have already counted objects in midx we should ignore packs that belong to it or we double count.

Since we do not put packfiles into the packed_git list if they are tracked by the midx, we will not double count.

Quoted 8 lines
Quoted 7 lines
packfile.c +0 -1
                         if (open_pack_index(p))
                                 continue;
                         count += p->num_objects;
--

2.18.0.rc1

#67 lore raw

PATCH 21/23 midx: prevent duplicate packfile loads

In reply to Derrick Stolee

If the multi-pack-index contains a packfile, then we do not need to add that packfile to the packed_git linked list or the MRU list.

---
 midx.c     | 23 +++++++++++++++++++++++
 midx.h     |  1 +
 packfile.c |  7 +++++++
 3 files changed, 31 insertions(+)
midx.c +23 -0
index 388d79b7d9..3242646fe0 100644
@@ -278,6 +278,29 @@ int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct mi
 	return nth_midxed_pack_entry(m, e, pos);
 }
 
+int midx_contains_pack(struct midxed_git *m, const char *idx_name)
+{
+	uint32_t first = 0, last = m->num_packs;
+
+	while (first < last) {
+		uint32_t mid = first + (last - first) / 2;
+		const char *current;
+		int cmp;
+
+		current = m->pack_names[mid];
+		cmp = strcmp(idx_name, current);
+		if (!cmp)
+			return 1;
+		if (cmp > 0) {
+			first = mid + 1;
+			continue;
+		}
+		last = mid;
+	}
+
+	return 0;
+}
+
 int prepare_midxed_git_one(struct repository *r, const char *object_dir)
 {
 	struct midxed_git *m = r->objects->midxed_git;
midx.h +1 -0
index 497bdcc77c..c1db58d8c4 100644
@@ -13,6 +13,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,
 					struct midxed_git *m,
 					uint32_t n);
 int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct midxed_git *m);
+int midx_contains_pack(struct midxed_git *m, const char *idx_name);
 int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
packfile.c +7 -0
index 059b2aa097..479cb69b9f 100644
@@ -746,6 +746,11 @@ static void prepare_packed_git_one(struct repository *r, char *objdir, int local
 	DIR *dir;
 	struct dirent *de;
 	struct string_list garbage = STRING_LIST_INIT_DUP;
+	struct midxed_git *m = r->objects->midxed_git;
+
+	/* look for the multi-pack-index for this object directory */
+	while (m && strcmp(m->object_dir, objdir))
+		m = m->next;
 
 	strbuf_addstr(&path, objdir);
 	strbuf_addstr(&path, "/pack");
@@ -772,6 +777,8 @@ static void prepare_packed_git_one(struct repository *r, char *objdir, int local
 		base_len = path.len;
 		if (strip_suffix_mem(path.buf, &base_len, ".idx")) {
 			/* Don't reopen a pack we already have. */
+			if (m && midx_contains_pack(m, de->d_name))
+				continue;
 			for (p = r->objects->packed_git; p;
 			     p = p->next) {
 				size_t len;
Signature · 1 line
2.18.0.rc1
#68 lore raw

Re: [PATCH 21/23] midx: prevent duplicate packfile loads

Quoted 3 lines · Derrick Stolee

If the multi-pack-index contains a packfile, then we do not need to add that packfile to the packed_git linked list or the MRU list.

Because...?

I think I see the reason, but I'd like it spelled out to avoid any misunderstanding.

Quoted 82 lines
---
 midx.c     | 23 +++++++++++++++++++++++
 midx.h     |  1 +
 packfile.c |  7 +++++++
 3 files changed, 31 insertions(+)
midx.c +23 -0
index 388d79b7d9..3242646fe0 100644
@@ -278,6 +278,29 @@ int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct mi
        return nth_midxed_pack_entry(m, e, pos);
 }
 
+int midx_contains_pack(struct midxed_git *m, const char *idx_name)
+{
+       uint32_t first = 0, last = m->num_packs;
+
+       while (first < last) {
+               uint32_t mid = first + (last - first) / 2;
+               const char *current;
+               int cmp;
+
+               current = m->pack_names[mid];
+               cmp = strcmp(idx_name, current);
+               if (!cmp)
+                       return 1;
+               if (cmp > 0) {
+                       first = mid + 1;
+                       continue;
+               }
+               last = mid;
+       }
+
+       return 0;
+}
+
 int prepare_midxed_git_one(struct repository *r, const char *object_dir)
 {
        struct midxed_git *m = r->objects->midxed_git;
midx.h +1 -0
index 497bdcc77c..c1db58d8c4 100644
@@ -13,6 +13,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,
                                        struct midxed_git *m,
                                        uint32_t n);
 int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct midxed_git *m);
+int midx_contains_pack(struct midxed_git *m, const char *idx_name);
 int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
packfile.c +7 -1
index 059b2aa097..479cb69b9f 100644
@@ -746,6 +746,11 @@ static void prepare_packed_git_one(struct repository *r, char *objdir, int local
        DIR *dir;
        struct dirent *de;
        struct string_list garbage = STRING_LIST_INIT_DUP;
+       struct midxed_git *m = r->objects->midxed_git;
+
+       /* look for the multi-pack-index for this object directory */
+       while (m && strcmp(m->object_dir, objdir))
+               m = m->next;
 
        strbuf_addstr(&path, objdir);
        strbuf_addstr(&path, "/pack");
@@ -772,6 +777,8 @@ static void prepare_packed_git_one(struct repository *r, char *objdir, int local
                base_len = path.len;
                if (strip_suffix_mem(path.buf, &base_len, ".idx")) {
                        /* Don't reopen a pack we already have. */
+                       if (m && midx_contains_pack(m, de->d_name))
+                               continue;
                        for (p = r->objects->packed_git; p;
                             p = p->next) {
                                size_t len;
--

2.18.0.rc1

Signature · 1 line
Duy
#69 lore raw

PATCH 22/23 midx: use midx to find ref-deltas

In reply to Derrick Stolee
---
 midx.c     |  2 +-
 midx.h     |  1 +
 packfile.c | 15 +++++++++++++++
 3 files changed, 17 insertions(+), 1 deletion(-)
midx.c +1 -1
index 3242646fe0..e46f392fa4 100644
@@ -214,7 +214,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,
 	return oid;
 }
 
-static off_t nth_midxed_offset(struct midxed_git *m, uint32_t pos)
+off_t nth_midxed_offset(struct midxed_git *m, uint32_t pos)
 {
 	const unsigned char *offset_data;
 	uint32_t offset32;
midx.h +1 -0
index c1db58d8c4..6996b5ff6b 100644
@@ -9,6 +9,7 @@
 
 struct midxed_git *load_midxed_git(const char *object_dir);
 int bsearch_midx(const struct object_id *oid, struct midxed_git *m, uint32_t *result);
+off_t nth_midxed_offset(struct midxed_git *m, uint32_t n);
 struct object_id *nth_midxed_object_oid(struct object_id *oid,
 					struct midxed_git *m,
 					uint32_t n);
packfile.c +15 -0
index 479cb69b9f..9b814c89c7 100644
@@ -1794,6 +1794,21 @@ off_t find_pack_entry_one(const unsigned char *sha1,
 	uint32_t result;
 
 	if (!index) {
+		/*
+		 * If we have a MIDX, then we want to
+		 * check the MIDX for the offset instead.
+		 */
+		struct midxed_git *m;
+
+		for (m = get_midxed_git(the_repository); m; m = m->next) {
+			if (midx_contains_pack(m, p->pack_name)) {
+				if (bsearch_midx(&oid, m, &result))
+					return nth_midxed_offset(m, result);
+
+				break;
+			}
+		}
+
 		if (open_pack_index(p))
 			return 0;
 	}
Signature · 1 line
2.18.0.rc1
#70 lore raw

PATCH 23/23 midx: clear midx on repack

In reply to Derrick Stolee

If a 'git repack' command replaces existing packfiles, then we must clear the existing multi-pack-index before moving the packfiles it references.

---
 builtin/repack.c | 8 ++++++++
 midx.c           | 8 ++++++++
 midx.h           | 1 +
 3 files changed, 17 insertions(+)
builtin/repack.c +8 -0
index 6c636e159e..66a7d8e8ea 100644
@@ -8,6 +8,7 @@
 #include "strbuf.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "midx.h"
 
 static int delta_base_offset = 1;
 static int pack_kept_objects = -1;
@@ -174,6 +175,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 	int no_update_server_info = 0;
 	int quiet = 0;
 	int local = 0;
+	int midx_cleared = 0;
 
 	struct option builtin_repack_options[] = {
 		OPT_BIT('a', NULL, &pack_everything,
@@ -340,6 +342,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 				continue;
 			}
 
+			if (!midx_cleared) {
+				/* if we move a packfile, it will invalidated the midx */
+				clear_midx_file(get_object_directory());
+				midx_cleared = 1;
+			}
+
 			fname_old = mkpathdup("%s/old-%s%s", packdir,
 						item->string, exts[ext].name);
 			if (file_exists(fname_old))
midx.c +8 -0
index e46f392fa4..1043c01fa7 100644
@@ -913,3 +913,11 @@ int write_midx_file(const char *object_dir)
 	FREE_AND_NULL(pack_names);
 	return 0;
 }
+
+void clear_midx_file(const char *object_dir)
+{
+	char *midx = get_midx_filename(object_dir);
+
+	if (remove_path(midx))
+		die(_("failed to clear multi-pack-index at %s"), midx);
+}
midx.h +1 -0
index 6996b5ff6b..46f9f44c94 100644
@@ -18,5 +18,6 @@ int midx_contains_pack(struct midxed_git *m, const char *idx_name);
 int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
+void clear_midx_file(const char *object_dir);
 
 #endif
Signature · 1 line
2.18.0.rc1
#71 lore raw

Re: [PATCH 23/23] midx: clear midx on repack

Quoted 4 lines · Derrick Stolee

If a 'git repack' command replaces existing packfiles, then we must clear the existing multi-pack-index before moving the packfiles it references.

I think there are other places where we add or remove pack files and need to reprepare_packed_git(). Any midx invalidation should be part of that as well.

Quoted 29 lines
---
 builtin/repack.c | 8 ++++++++
 midx.c           | 8 ++++++++
 midx.h           | 1 +
 3 files changed, 17 insertions(+)
builtin/repack.c +2 -0
index 6c636e159e..66a7d8e8ea 100644
@@ -8,6 +8,7 @@
 #include "strbuf.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "midx.h"
 
 static int delta_base_offset = 1;
 static int pack_kept_objects = -1;
@@ -174,6 +175,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        int no_update_server_info = 0;
        int quiet = 0;
        int local = 0;
+       int midx_cleared = 0;
 
        struct option builtin_repack_options[] = {
                OPT_BIT('a', NULL, &pack_everything,

builtin/repack.c int cmd_repack(int argc, const char **argv, const char *prefix) · line 346

Quoted 5 lines
builtin/repack.c +2 -0
@@ -340,6 +342,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                                continue;
                        }
 
+                       if (!midx_cleared) {
+                               /* if we move a packfile, it will invalidated the midx */

What about removing packs, which also happens in repack? If the removed pack is part of midx, then midx becomes invalid as well.

Quoted 12 lines
builtin/repack.c +4 -0
+                               clear_midx_file(get_object_directory());
+                               midx_cleared = 1;
+                       }
+
                        fname_old = mkpathdup("%s/old-%s%s", packdir,
                                                item->string, exts[ext].name);
                        if (file_exists(fname_old))
midx.c +0 -0
index e46f392fa4..1043c01fa7 100644

midx.c int write_midx_file(const char *object_dir) · line 917

Quoted 5 lines
midx.c +2 -0
@@ -913,3 +913,11 @@ int write_midx_file(const char *object_dir)
        FREE_AND_NULL(pack_names);
        return 0;
 }
+
+void clear_midx_file(const char *object_dir)

delete_ may be more obvious than clear_

midx.c int write_midx_file(const char *object_dir) · line 922

Quoted 5 lines
midx.c +5 -0
+{
+       char *midx = get_midx_filename(object_dir);
+
+       if (remove_path(midx))
+               die(_("failed to clear multi-pack-index at %s"), midx);

die_errno()

Quoted 15 lines
midx.c +1 -0
+}
midx.h +1 -1
index 6996b5ff6b..46f9f44c94 100644
@@ -18,5 +18,6 @@ int midx_contains_pack(struct midxed_git *m, const char *idx_name);
 int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
+void clear_midx_file(const char *object_dir);
 
 #endif
--

2.18.0.rc1

Signature · 1 line
Duy
#72 lore raw

Re: [PATCH 23/23] midx: clear midx on repack

Quoted 7 lines · Duy Nguyen
Quoted 3 lines · Derrick Stolee

If a 'git repack' command replaces existing packfiles, then we must clear the existing multi-pack-index before moving the packfiles it references.

I think there are other places where we add or remove pack files and need to reprepare_packed_git(). Any midx invalidation should be part of that as well.

The other places where we call reprepare_packed_git() are for when we may have added a packfile, such as in fetch-pack.c, or sha1_file.c. The other candidate to consider is 'git gc', but the packfile deletion is handled by a call to 'git repack'.

Quoted 79 lines
Quoted 29 lines
---
  builtin/repack.c | 8 ++++++++
  midx.c           | 8 ++++++++
  midx.h           | 1 +
  3 files changed, 17 insertions(+)
builtin/repack.c +2 -0
index 6c636e159e..66a7d8e8ea 100644
@@ -8,6 +8,7 @@
  #include "strbuf.h"
  #include "string-list.h"
  #include "argv-array.h"
+#include "midx.h"
 
  static int delta_base_offset = 1;
  static int pack_kept_objects = -1;
@@ -174,6 +175,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
         int no_update_server_info = 0;
         int quiet = 0;
         int local = 0;
+       int midx_cleared = 0;
 
         struct option builtin_repack_options[] = {
                 OPT_BIT('a', NULL, &pack_everything,

builtin/repack.c int cmd_repack(int argc, const char **argv, const char *prefix) · line 346

Quoted 5 lines
builtin/repack.c +2 -0
@@ -340,6 +342,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                                 continue;
                         }
 
+                       if (!midx_cleared) {
+                               /* if we move a packfile, it will invalidated the midx */

What about removing packs, which also happens in repack? If the removed pack is part of midx, then midx becomes invalid as well.

Quoted 12 lines
builtin/repack.c +4 -0
+                               clear_midx_file(get_object_directory());
+                               midx_cleared = 1;
+                       }
+
                         fname_old = mkpathdup("%s/old-%s%s", packdir,
                                                 item->string, exts[ext].name);
                         if (file_exists(fname_old))
midx.c +0 -0
index e46f392fa4..1043c01fa7 100644

midx.c int write_midx_file(const char *object_dir) · line 917

Quoted 5 lines
midx.c +2 -0
@@ -913,3 +913,11 @@ int write_midx_file(const char *object_dir)
         FREE_AND_NULL(pack_names);
         return 0;
  }
+
+void clear_midx_file(const char *object_dir)

delete_ may be more obvious than clear_

midx.c int write_midx_file(const char *object_dir) · line 922

Quoted 5 lines
midx.c +5 -0
+{
+       char *midx = get_midx_filename(object_dir);
+
+       if (remove_path(midx))
+               die(_("failed to clear multi-pack-index at %s"), midx);

die_errno()

Quoted 15 lines
midx.c +1 -0
+}
midx.h +1 -1
index 6996b5ff6b..46f9f44c94 100644
@@ -18,5 +18,6 @@ int midx_contains_pack(struct midxed_git *m, const char *idx_name);
  int prepare_midxed_git_one(struct repository *r, const char *object_dir);
 
  int write_midx_file(const char *object_dir);
+void clear_midx_file(const char *object_dir);
 
  #endif
--

2.18.0.rc1

#74 lore raw
In reply to Derrick Stolee
Quoted 12 lines · Derrick Stolee

To test the performance in this situation, I created a script that organizes the Linux repository in a similar fashion. I split the commit history into 50 parts by creating branches on every 10,000 commits of the first- parent history. Then, git rev-list --objects A ^B provides the list of objects reachable from A but not B, so I could send that to git pack-objects to create these "time-based" packfiles. With these 50 packfiles (deleting the old one from my fresh clone, and deleting all tags as they were no longer on-disk) I could then test 'git rev-list --objects HEAD^{tree}' and see:

Quoted 8 lines
        Before: 0.17s
        After:  0.13s
        % Diff: -23.5%

By adding logic to count hits and misses to bsearch_pack, I was able to see that the command above calls that method 266,930 times with a hit rate of 33%. The MIDX has the same number of calls with a 100% hit rate.

Do you have the script you used for this? It would be very interesting as something we could stick in t/perf/ to test this use-case in the future.

How does this & the numbers below compare to just a naïve --max-pack-size=<similar size> on linux.git?

Is it possible for you to tar this test repo up and share it as a one-off? I've been polishing the core.validateAbbrev series I have, and it would be interesting to compare some of the (abbrev) numbers.

Quoted 41 lines

Abbreviation Speedups
---------------------

To fully disambiguate an abbreviation, we must iterate through all packfiles to ensure no collision exists in any packfile. This requires O(P log N) time. With the MIDX, this is only O(log N) time. Our standard test [2] is 'git log --oneline --parents --raw' because it writes many abbreviations while also doing a lot of other work (walking commits and trees to compute the raw diff).

For a copy of the Linux repository with 50 packfiles split by time, we observed the following:

        Before: 100.5 s
        After:   58.2 s
        % Diff: -59.7%

Request for Review Attention
----------------------------

I tried my best to take the feedback from the commit-graph feature and apply it to this feature. I also worked to follow the object-store refactoring as I could. I also have some local commits that create a 'verify' subcommand and integrate with 'fsck' similar to the commit-graph, but I'll leave those for a later series (and review is still underway for that part of the commit-graph).

One place where I could use some guidance is related to the current state of 'the_hash_algo' patches. The file format allows a different "hash version" which then indicates the length of the hash. What's the best way to ensure this feature doesn't cause extra pain in the hash-agnostic series? This will inform how I go back and make the commit-graph feature better in this area, too.

Thanks,
-Stolee

#75 lore raw
Quoted 26 lines · Ævar Arnfjörð Bjarmason
Quoted 12 lines · Derrick Stolee

To test the performance in this situation, I created a script that organizes the Linux repository in a similar fashion. I split the commit history into 50 parts by creating branches on every 10,000 commits of the first- parent history. Then, git rev-list --objects A ^B provides the list of objects reachable from A but not B, so I could send that to git pack-objects to create these "time-based" packfiles. With these 50 packfiles (deleting the old one from my fresh clone, and deleting all tags as they were no longer on-disk) I could then test 'git rev-list --objects HEAD^{tree}' and see:

Quoted 8 lines
         Before: 0.17s
         After:  0.13s
         % Diff: -23.5%

By adding logic to count hits and misses to bsearch_pack, I was able to see that the command above calls that method 266,930 times with a hit rate of 33%. The MIDX has the same number of calls with a 100% hit rate.

Do you have the script you used for this? It would be very interesting as something we could stick in t/perf/ to test this use-case in the future.

Quoted 6 lines

How does this & the numbers below compare to just a naïve --max-pack-size=<similar size> on linux.git?

Is it possible for you to tar this test repo up and share it as a one-off? I've been polishing the core.validateAbbrev series I have, and it would be interesting to compare some of the (abbrev) numbers.

Here is what I used. You will want to adjust your constants for whatever repo you are using. This is for the Linux kernel which has a first-parent history of ~50,000 commits. It also leaves a bunch of extra files around, so it is nowhere near incorporating into the code.

#!/bin/bash

for i in seq 1 50
do
        ORDER=$((51 - $i))
        NUM_BACK=$((1000 * ($i - 1)))
        echo creating batch/$ORDER
        git branch -f batch/$ORDER HEAD~$NUM_BACK
        echo batch/$ORDER
        git rev-parse batch/$ORDER
done

lastbranch=""
for i in seq 1 50
do
        branch=batch/$i
        if [$lastbranch -eq ""]
        then
                echo "$branch"
                git rev-list --objects $branch | sed 's/ .*//' >objects-$i.txt else
                echo "$lastbranch"
                echo "$branch"
                git rev-list --objects $branch ^$lastbranch | sed 's/ .*//' >objects-$i.txt fi

        git pack-objects --no-reuse-delta .git/objects/pack/branch-split2 <objects-$i.txt lastbranch=$branch
done

for tag in git tag --list
do
        git tag -d $tag
done

rm -rf .git/objects/pack/pack-*
git midx write

#76 lore raw

v2 cover Multi-pack-index (MIDX)

In reply to Derrick Stolee

From: Derrick Stolee <[email protected]>

This v2 patch has several significant changes from v1. Thanks for all the feedback that informed these changes:

* The 'midx' builtin is renamed to 'multi-pack-index'

* The 'core.midx' config setting is renamed to 'core.multiPackIndex'

* Many die() or error() statements are marked for translation

* The packfile name lookup chunk is dropped in favor of dynamic calculation on read.

* The 'read' verb in the builtin is moved to a test tool.

And one item that I'm saving for investigation while v2 is under review:

* Consider using a merge sort when constructing/deduplicating the list of objects and offsets instead of sorting the objects in batches by first byte.

Thanks,
-Stolee

---

The multi-pack-index (MIDX) is explained fully in
the design document 'Documentation/technical/multi-pack-index.txt'. The short description is that the MIDX stores the information from all of the IDX files in a pack directory. The crucial design decision is that the
IDX files still exist, so we can fall back to the IDX files if there is any issue with the MIDX (or core.midx is set to false, or a user downgrades Git, etc.)

The MIDX feature has been part of our GVFS releases for a few months (since the RFC). It has behaved well, indexing over 31 million commits and trees across up to 250 packfiles. These MIDX files are nearly 1GB in size and take ~20 seconds to rewrite when adding new IDX information. This ~20s mark is something I'd like to improve, and I mention how to make the file incremental (similar to split-index) in the design document. I also want to make the commit-graph file incremental, so I'd like to do that at the same time after both the MIDX and commit-graph are stable.

Lookup Speedups
---------------

When looking for an object, Git uses an most-recently- used (MRU) cache of packfiles. This does pretty well to minimize the number of misses when searching through packfiles for an object, especially if there is one "big" packfile that contains most of the objets (so it will rarely miss and is usually one of the first two packfiles in the list). The MIDX does provide a way to remove these misses, improving lookup time. However, this lookup time greatly depends on the arrangement of the packfiles.

For instance, if you take the Linux repository and repack using git repack -adfF --max-pack-size=128m then all commits will be in one packfile, all trees will be in a small set of packfiles and organized well so 'git rev-list --objects HEAD^{tree}' only inspects one or two packfiles.

GVFS has the notion of a "prefetch packfile". These are packfiles that are precomputed by cache servers to contain the commits and trees introduced to the remote each day. GVFS downloads these packfiles and places them in an alternate. Since these are organized by "first time introduced" and the working directory is so large, the MRU misses are significant when performing a checkout and updating the .git/index file.

To test the performance in this situation, I created a script that organizes the Linux repository in a similar fashion. I split the commit history into 50 parts by creating branches on every 10,000 commits of the first- parent history. Then, git rev-list --objects A ^B provides the list of objects reachable from A but not B, so I could send that to git pack-objects to create these "time-based" packfiles. With these 50 packfiles (deleting the old one from my fresh clone, and deleting all tags as they were no longer on-disk) I could then test 'git rev-list --objects HEAD^{tree}' and see:

        Before: 0.17s
        After:  0.13s
        % Diff: -23.5%

By adding logic to count hits and misses to bsearch_pack, I was able to see that the command above calls that method 266,930 times with a hit rate of 33%. The MIDX has the same number of calls with a 100% hit rate.

Abbreviation Speedups
---------------------

To fully disambiguate an abbreviation, we must iterate through all packfiles to ensure no collision exists in any packfile. This requires O(P log N) time. With the MIDX, this is only O(log N) time. Our standard test [2] is 'git log --oneline --parents --raw' because it writes many abbreviations while also doing a lot of other work (walking commits and trees to compute the raw diff).

For a copy of the Linux repository with 50 packfiles split by time, we observed the following:

        Before: 100.5 s
        After:   58.2 s
        % Diff: -59.7%

Derrick Stolee (24):
multi-pack-index: add design document
multi-pack-index: add format details
multi-pack-index: add builtin
multi-pack-index: add 'write' verb
midx: write header information to lockfile
multi-pack-index: load into memory
multi-pack-index: expand test data
packfile: generalize pack directory list
multi-pack-index: read packfile list
multi-pack-index: write pack names in chunk
midx: read pack names into array
midx: sort and deduplicate objects from packfiles
midx: write object ids in a chunk
midx: write object id fanout chunk
midx: write object offsets
config: create core.multiPackIndex setting
midx: prepare midxed_git struct
midx: read objects from multi-pack-index
midx: use midx in abbreviation calculations
midx: use existing midx when writing new one
midx: use midx in approximate_object_count
midx: prevent duplicate packfile loads
packfile: skip loading index if in multi-pack-index
midx: clear midx on repack

 .gitignore                                   |   3 +-
 Documentation/config.txt                     |   4 +
 Documentation/git-multi-pack-index.txt       |  56 ++
 Documentation/technical/multi-pack-index.txt | 109 +++
 Documentation/technical/pack-format.txt      |  77 ++
 Makefile                                     |   3 +
 builtin.h                                    |   1 +
 builtin/multi-pack-index.c                   |  46 +
 builtin/repack.c                             |   8 +
 cache.h                                      |   1 +
 command-list.txt                             |   1 +
 config.c                                     |   5 +
 environment.c                                |   1 +
 git.c                                        |   1 +
 midx.c                                       | 900 +++++++++++++++++++
 midx.h                                       |  20 +
 object-store.h                               |  33 +
 packfile.c                                   | 173 +++-
 packfile.h                                   |   9 +
 sha1-name.c                                  |  70 ++
 t/helper/test-read-midx.c                    |  54 ++
 t/helper/test-tool.c                         |   1 +
 t/helper/test-tool.h                         |   1 +
 t/t5319-multi-pack-index.sh                  | 191 ++++
 24 files changed, 1724 insertions(+), 44 deletions(-)
 create mode 100644 Documentation/git-multi-pack-index.txt
 create mode 100644 Documentation/technical/multi-pack-index.txt
 create mode 100644 builtin/multi-pack-index.c
 create mode 100644 midx.c
 create mode 100644 midx.h
 create mode 100644 t/helper/test-read-midx.c
 create mode 100755 t/t5319-multi-pack-index.sh

base-commit: 53f9a3e157dbbc901a02ac2c73346d375e24978c

Signature · 1 line
2.18.0.rc1
#77 lore raw

PATCH v2 1/24 multi-pack-index: add design document

---
 Documentation/technical/multi-pack-index.txt | 109 +++++++++++++++++++
 1 file changed, 109 insertions(+)
 create mode 100644 Documentation/technical/multi-pack-index.txt
Documentation/technical/multi-pack-index.txt new file +109 -0
new file mode 100644
index 0000000000..d7e57639f7
@@ -0,0 +1,109 @@
+Multi-Pack-Index (MIDX) Design Notes
+====================================
+
+The Git object directory contains a 'pack' directory containing
+packfiles (with suffix ".pack") and pack-indexes (with suffix
+".idx"). The pack-indexes provide a way to lookup objects and
+navigate to their offset within the pack, but these must come
+in pairs with the packfiles. This pairing depends on the file
+names, as the pack-index differs only in suffix with its pack-
+file. While the pack-indexes provide fast lookup per packfile,
+this performance degrades as the number of packfiles increases,
+because abbreviations need to inspect every packfile and we are
+more likely to have a miss on our most-recently-used packfile.
+For some large repositories, repacking into a single packfile
+is not feasible due to storage space or excessive repack times.
+
+The multi-pack-index (MIDX for short) stores a list of objects
+and their offsets into multiple packfiles. It contains:
+
+- A list of packfile names.
+- A sorted list of object IDs.
+- A list of metadata for the ith object ID including:
+  - A value j referring to the jth packfile.
+  - An offset within the jth packfile for the object.
+- If large offsets are required, we use another list of large
+  offsets similar to version 2 pack-indexes.
+
+Thus, we can provide O(log N) lookup time for any number
+of packfiles.
+
+Design Details
+--------------
+
+- The MIDX is stored in a file named 'multi-pack-index' in the
+  .git/objects/pack directory. This could be stored in the pack
+  directory of an alternate. It refers only to packfiles in that
+  same directory.
+
+- The pack.multiIndex config setting must be on to consume MIDX files.
+
+- The file format includes parameters for the object ID hash
+  function, so a future change of hash algorithm does not require
+  a change in format.
+
+- The MIDX keeps only one record per object ID. If an object appears
+  in multiple packfiles, then the MIDX selects the copy in the most-
+  recently modified packfile.
+
+- If there exist packfiles in the pack directory not registered in
+  the MIDX, then those packfiles are loaded into the `packed_git`
+  list and `packed_git_mru` cache.
+
+- The pack-indexes (.idx files) remain in the pack directory so we
+  can delete the MIDX file, set core.midx to false, or downgrade
+  without any loss of information.
+
+- The MIDX file format uses a chunk-based approach (similar to the
+  commit-graph file) that allows optional data to be added.
+
+Future Work
+-----------
+
+- Add a 'verify' subcommand to the 'git midx' builtin to verify the
+  contents of the multi-pack-index file match the offsets listed in
+  the corresponding pack-indexes.
+
+- 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.
+
+- The reachability bitmap is currently paired directly with a single
+  packfile, using the pack-order as the object order to hopefully
+  compress the bitmaps well using run-length encoding. This could be
+  extended to pair a reachability bitmap with a multi-pack-index. 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 a reachability bitmap
+  could point to a multi-pack-index and be updated independently.
+
+- Packfiles can be marked as "special" using empty files that share
+  the initial name but replace ".pack" with ".keep" or ".promisor".
+  We can add an optional chunk of data to the multi-pack-index that
+  records flags of information about the packfiles. This allows new
+  states, such as 'repacked' or 'redeltified', that can help with
+  pack maintenance in a multi-pack environment. It may also be
+  helpful to organize packfiles by object type (commit, tree, blob,
+  etc.) and use this metadata to help that maintenance.
+
+- The partial clone feature records special "promisor" packs that
+  may point to objects that are not stored locally, but available
+  on request to a server. The multi-pack-index does not currently
+  track these promisor packs.
+
+Related Links
+-------------
+[0] https://bugs.chromium.org/p/git/issues/detail?id=6
+    Chromium work item for: Multi-Pack Index (MIDX)
+
+[1] https://public-inbox.org/git/[email protected]/
+    An earlier RFC for the multi-pack-index feature
+
+[2] https://public-inbox.org/git/alpine.DEB.2.20.1803091557510.23109@alexmv-linux/
+    Git Merge 2018 Contributor's summit notes (includes discussion of MIDX)

base-commit: 53f9a3e157dbbc901a02ac2c73346d375e24978c

Signature · 1 line
2.18.0.24.g1b579a2ee9
#78 lore raw

PATCH v2 2/24 multi-pack-index: add format details

In reply to Derrick Stolee

The multi-pack-index feature generalizes the existing pack-index feature by indexing objects across multiple pack-files.

Describe the basic file format, using a 12-byte header followed by a lookup table for a list of "chunks" which will be described later. The file ends with a footer containing a checksum using the hash algorithm.

The header allows later versions to create breaking changes by advancing the version number. We can also change the hash algorithm using a different version value.

We will add the individual chunk format information as we introduce the code that writes that information.

---
 Documentation/technical/pack-format.txt | 49 +++++++++++++++++++++++++
 1 file changed, 49 insertions(+)
Documentation/technical/pack-format.txt +49 -0
index 70a99fd142..e060e693f4 100644
@@ -252,3 +252,52 @@ Pack file entry: <+
     corresponding packfile.
 
     20-byte SHA-1-checksum of all of the above.
+
+== multi-pack-index (MIDX) files have the following format:
+
+The multi-pack-index files refer to multiple pack-files and loose objects.
+
+In order to allow extensions that add extra data to the MIDX, we organize
+the body into "chunks" and provide a lookup table at the beginning of the
+body. The header includes certain length values, such as the number of packs,
+the number of base MIDX files, hash lengths and types.
+
+All 4-byte numbers are in network order.
+
+HEADER:
+
+	4-byte signature:
+	    The signature is: {'M', 'I', 'D', 'X'}
+
+	1-byte version number:
+	    Git only writes or recognizes version 1.
+
+	1-byte Object Id Version
+	    Git only writes or recognizes version 1 (SHA1).
+
+	1-byte number of "chunks"
+
+	1-byte number of base multi-pack-index files:
+	    This value is currently always zero.
+
+	4-byte number of pack files
+
+CHUNK LOOKUP:
+
+	(C + 1) * 12 bytes providing the chunk offsets:
+	    First 4 bytes describe chunk id. Value 0 is a terminating label.
+	    Other 8 bytes provide offset in current file for chunk to start.
+	    (Chunks are provided in file-order, so you can infer the length
+	    using the next chunk position if necessary.)
+
+	The remaining data in the body is described one chunk at a time, and
+	these chunks may be given in any order. Chunks are required unless
+	otherwise specified.
+
+CHUNK DATA:
+
+	(This section intentionally left incomplete.)
+
+TRAILER:
+
+	20-byte SHA1-checksum of the above contents.
Signature · 1 line
2.18.0.24.g1b579a2ee9
#79 lore raw

PATCH v2 3/24 multi-pack-index: add builtin

In reply to Derrick Stolee

This new 'git multi-pack-index' builtin will be the plumbing access for writing, reading, and checking multi-pack-index files. The initial implementation is a no-op.

---
 .gitignore                             |  3 +-
 Documentation/git-multi-pack-index.txt | 36 ++++++++++++++++++++++++
 Makefile                               |  1 +
 builtin.h                              |  1 +
 builtin/multi-pack-index.c             | 38 ++++++++++++++++++++++++++
 command-list.txt                       |  1 +
 git.c                                  |  1 +
 7 files changed, 80 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/git-multi-pack-index.txt
 create mode 100644 builtin/multi-pack-index.c
.gitignore +2 -1
index 388cc4beee..25633bc515 100644
@@ -99,8 +99,9 @@
 /git-mergetool--lib
 /git-mktag
 /git-mktree
-/git-name-rev
+/git-multi-pack-index
 /git-mv
+/git-name-rev
 /git-notes
 /git-p4
 /git-pack-redundant
Documentation/git-multi-pack-index.txt new file +36 -0
new file mode 100644
index 0000000000..9877f9c441
@@ -0,0 +1,36 @@
+git-multi-pack-index(1)
+======================
+
+NAME
+----
+git-multi-pack-index - Write and verify multi-pack-indexes
+
+
+SYNOPSIS
+--------
+[verse]
+'git multi-pack-index' [--object-dir <dir>]
+
+DESCRIPTION
+-----------
+Write or verify a multi-pack-index (MIDX) file.
+
+OPTIONS
+-------
+
+--object-dir <dir>::
+	Use given directory for the location of Git objects. We check
+	<dir>/packs/multi-pack-index for the current MIDX file, and
+	<dir>/packs for the pack-files to index.
+
+
+SEE ALSO
+--------
+See link:technical/multi-pack-index.html[The Multi-Pack-Index Design
+Document] and link:technical/pack-format.html[The Multi-Pack-Index
+Format] for more information on the multi-pack-index feature.
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
Makefile +1 -0
index e4b503d259..54610875ec 100644
@@ -1047,6 +1047,7 @@ BUILTIN_OBJS += builtin/merge-recursive.o
 BUILTIN_OBJS += builtin/merge-tree.o
 BUILTIN_OBJS += builtin/mktag.o
 BUILTIN_OBJS += builtin/mktree.o
+BUILTIN_OBJS += builtin/multi-pack-index.o
 BUILTIN_OBJS += builtin/mv.o
 BUILTIN_OBJS += builtin/name-rev.o
 BUILTIN_OBJS += builtin/notes.o
builtin.h +1 -0
index 4e0f64723e..70997d7ace 100644
@@ -191,6 +191,7 @@ extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_mktag(int argc, const char **argv, const char *prefix);
 extern int cmd_mktree(int argc, const char **argv, const char *prefix);
+extern int cmd_multi_pack_index(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
 extern int cmd_notes(int argc, const char **argv, const char *prefix);
builtin/multi-pack-index.c new file +38 -0
new file mode 100644
index 0000000000..f101873525
@@ -0,0 +1,38 @@
+#include "builtin.h"
+#include "cache.h"
+#include "config.h"
+#include "parse-options.h"
+
+static char const * const builtin_multi_pack_index_usage[] ={
+	N_("git multi-pack-index [--object-dir <dir>]"),
+	NULL
+};
+
+static struct opts_multi_pack_index {
+	const char *object_dir;
+} opts;
+
+int cmd_multi_pack_index(int argc, const char **argv,
+			 const char *prefix)
+{
+	static struct option builtin_multi_pack_index_options[] = {
+		OPT_FILENAME(0, "object-dir", &opts.object_dir,
+		  N_("The object directory containing set of packfile and pack-index pairs")),
+		OPT_END(),
+	};
+
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(builtin_multi_pack_index_usage,
+				   builtin_multi_pack_index_options);
+
+	git_config(git_default_config, NULL);
+
+	argc = parse_options(argc, argv, prefix,
+			     builtin_multi_pack_index_options,
+			     builtin_multi_pack_index_usage, 0);
+
+	if (!opts.object_dir)
+		opts.object_dir = get_object_directory();
+
+	return 0;
+}
command-list.txt +1 -0
index e1c26c1bb7..61071f8fa2 100644
@@ -123,6 +123,7 @@ git-merge-index                         plumbingmanipulators
 git-merge-one-file                      purehelpers
 git-mergetool                           ancillarymanipulators           complete
 git-merge-tree                          ancillaryinterrogators
+git-multi-pack-index                    plumbingmanipulators
 git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain           worktree
git.c +1 -0
index c2f48d53dd..a7509fa5f7 100644
@@ -505,6 +505,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
+	{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP_GENTLY },
 	{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
 	{ "name-rev", cmd_name_rev, RUN_SETUP },
 	{ "notes", cmd_notes, RUN_SETUP },
Signature · 1 line
2.18.0.24.g1b579a2ee9
#80 lore raw

Re: [PATCH v2 03/24] multi-pack-index: add builtin

Quoted 22 lines · Derrick Stolee

This new 'git multi-pack-index' builtin will be the plumbing access for writing, reading, and checking multi-pack-index files. The initial implementation is a no-op.

---
 .gitignore                             |  3 +-
 Documentation/git-multi-pack-index.txt | 36 ++++++++++++++++++++++++
 Makefile                               |  1 +
 builtin.h                              |  1 +
 builtin/multi-pack-index.c             | 38 ++++++++++++++++++++++++++
 command-list.txt                       |  1 +
 git.c                                  |  1 +
 7 files changed, 80 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/git-multi-pack-index.txt
 create mode 100644 builtin/multi-pack-index.c
.gitignore +0 -0
index 388cc4beee..25633bc515 100644

.gitignore line 104

Quoted 7 lines
.gitignore +2 -1
@@ -99,8 +99,9 @@
 /git-mergetool--lib
 /git-mktag
 /git-mktree
-/git-name-rev
+/git-multi-pack-index
 /git-mv
+/git-name-rev

Nice attention to the detail (even though the patch as an incremental change gets distracting, the result is better and future changes to the file will read cleaner).

Quoted 20 lines
Documentation/git-multi-pack-index.txt new file +14 -0
new file mode 100644
index 0000000000..9877f9c441
@@ -0,0 +1,36 @@
+git-multi-pack-index(1)
+======================
+
+NAME
+----
+git-multi-pack-index - Write and verify multi-pack-indexes
+
+
+SYNOPSIS
+--------
+[verse]
+'git multi-pack-index' [--object-dir <dir>]
+
+DESCRIPTION

Documentation/git-multi-pack-index.txt line 24

Quoted 10 lines
Documentation/git-multi-pack-index.txt new file +10 -0
+-----------
+Write or verify a multi-pack-index (MIDX) file.
+
+OPTIONS
+-------
+
+--object-dir <dir>::
+	Use given directory for the location of Git objects. We check
+	<dir>/packs/multi-pack-index for the current MIDX file, and
+	<dir>/packs for the pack-files to index.

Do we want to quote these constant strings?

builtin/multi-pack-index.c line 6

Quoted 12 lines
builtin/multi-pack-index.c new file +6 -0
new file mode 100644
index 0000000000..f101873525
@@ -0,0 +1,38 @@
+#include "builtin.h"
+#include "cache.h"
+#include "config.h"
+#include "parse-options.h"
+
+static char const * const builtin_multi_pack_index_usage[] ={

ERROR: spaces required around that '=' (ctx:WxV)
#112: FILE: builtin/multi-pack-index.c:6:

+static char const * const builtin_multi_pack_index_usage[] ={
                                                            ^
#81 lore raw

PATCH v2 4/24 multi-pack-index: add 'write' verb

In reply to Derrick Stolee

In anticipation of writing multi-pack-indexes, add a 'git multi-pack-index write' subcommand and send the options to a write_midx_file() method. Also create a basic test file that tests the 'write' subcommand.

---
 Documentation/git-multi-pack-index.txt | 22 +++++++++++++++++++++-
 Makefile                               |  1 +
 builtin/multi-pack-index.c             | 10 +++++++++-
 midx.c                                 |  7 +++++++
 midx.h                                 |  6 ++++++
 t/t5319-multi-pack-index.sh            | 10 ++++++++++
 6 files changed, 54 insertions(+), 2 deletions(-)
 create mode 100644 midx.c
 create mode 100644 midx.h
 create mode 100755 t/t5319-multi-pack-index.sh
Documentation/git-multi-pack-index.txt +21 -1
index 9877f9c441..c4dc92ddd9 100644
@@ -9,7 +9,7 @@ git-multi-pack-index - Write and verify multi-pack-indexes
 SYNOPSIS
 --------
 [verse]
-'git multi-pack-index' [--object-dir <dir>]
+'git multi-pack-index' [--object-dir <dir>] <verb>
 
 DESCRIPTION
 -----------
@@ -23,6 +23,26 @@ OPTIONS
 	<dir>/packs/multi-pack-index for the current MIDX file, and
 	<dir>/packs for the pack-files to index.
 
+write::
+	When given as the verb, write a new MIDX file to
+	<dir>/packs/multi-pack-index.
+
+
+EXAMPLES
+--------
+
+* Write a MIDX file for the packfiles in the current .git folder.
++
+-----------------------------------------------
+$ git multi-pack-index write
+-----------------------------------------------
+
+* Write a MIDX file for the packfiles in an alternate.
++
+-----------------------------------------------
+$ git multi-pack-index --object-dir <alt> write
+-----------------------------------------------
+
 
 SEE ALSO
 --------
Makefile +1 -0
index 54610875ec..f5636c711d 100644
@@ -890,6 +890,7 @@ LIB_OBJS += merge.o
 LIB_OBJS += merge-blobs.o
 LIB_OBJS += merge-recursive.o
 LIB_OBJS += mergesort.o
+LIB_OBJS += midx.o
 LIB_OBJS += name-hash.o
 LIB_OBJS += notes.o
 LIB_OBJS += notes-cache.o
builtin/multi-pack-index.c +9 -1
index f101873525..c8f1f19d1f 100644
@@ -2,9 +2,10 @@
 #include "cache.h"
 #include "config.h"
 #include "parse-options.h"
+#include "midx.h"
 
 static char const * const builtin_multi_pack_index_usage[] ={
-	N_("git multi-pack-index [--object-dir <dir>]"),
+	N_("git multi-pack-index [--object-dir <dir>] [write]"),
 	NULL
 };
 
@@ -34,5 +35,12 @@ int cmd_multi_pack_index(int argc, const char **argv,
 	if (!opts.object_dir)
 		opts.object_dir = get_object_directory();
 
+	if (argc == 0)
+		usage_with_options(builtin_multi_pack_index_usage,
+				   builtin_multi_pack_index_options);
+
+	if (!strcmp(argv[0], "write"))
+		return write_midx_file(opts.object_dir);
+
 	return 0;
 }
midx.c new file +7 -0
new file mode 100644
index 0000000000..32468db1a2
@@ -0,0 +1,7 @@
+#include "cache.h"
+#include "midx.h"
+
+int write_midx_file(const char *object_dir)
+{
+	return 0;
+}
midx.h new file +6 -0
new file mode 100644
index 0000000000..dbdbe9f873
@@ -0,0 +1,6 @@
+#ifndef __MIDX_H__
+#define __MIDX_H__
+
+int write_midx_file(const char *object_dir);
+
+#endif
t/t5319-multi-pack-index.sh new file +10 -0
new file mode 100755
index 0000000000..ec3ddbe79c
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='multi-pack-indexes'
+. ./test-lib.sh
+
+test_expect_success 'write midx with no packs' '
+	git multi-pack-index --object-dir=. write
+'
+
+test_done
Signature · 1 line
2.18.0.24.g1b579a2ee9
#82 lore raw

PATCH v2 5/24 midx: write header information to lockfile

In reply to Derrick Stolee

As we begin writing the multi-pack-index format to disk, start with the basics: the 12-byte header and the 20-byte checksum footer. Start with these basics so we can add the rest of the format in small increments.

As we implement the format, we will use a technique to check that our computed offsets within the multi-pack-index file match what we are actually writing. Each method that writes to the hashfile will return the number of bytes written, and we will track that those values match our expectations.

Currently, write_midx_header() returns 12, but is not checked. We will check the return value in a later commit.

---
 midx.c                      | 49 +++++++++++++++++++++++++++++++++++++
 t/t5319-multi-pack-index.sh |  3 ++-
 2 files changed, 51 insertions(+), 1 deletion(-)
midx.c +49 -0
index 32468db1a2..393d526881 100644
@@ -1,7 +1,56 @@
 #include "cache.h"
+#include "csum-file.h"
+#include "lockfile.h"
 #include "midx.h"
 
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1
+#define MIDX_HEADER_SIZE 12
+
+static char *get_midx_filename(const char *object_dir)
+{
+	return xstrfmt("%s/pack/multi-pack-index", object_dir);
+}
+
+static size_t write_midx_header(struct hashfile *f,
+				unsigned char num_chunks,
+				uint32_t num_packs)
+{
+	unsigned char byte_values[4];
+	hashwrite_be32(f, MIDX_SIGNATURE);
+	byte_values[0] = MIDX_VERSION;
+	byte_values[1] = MIDX_HASH_VERSION;
+	byte_values[2] = num_chunks;
+	byte_values[3] = 0; /* unused */
+	hashwrite(f, byte_values, sizeof(byte_values));
+	hashwrite_be32(f, num_packs);
+
+	return MIDX_HEADER_SIZE;
+}
+
 int write_midx_file(const char *object_dir)
 {
+	unsigned char num_chunks = 0;
+	char *midx_name;
+	struct hashfile *f;
+	struct lock_file lk;
+
+	midx_name = get_midx_filename(object_dir);
+	if (safe_create_leading_directories(midx_name)) {
+		UNLEAK(midx_name);
+		die_errno(_("unable to create leading directories of %s"),
+			  midx_name);
+	}
+
+	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
+	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
+	FREE_AND_NULL(midx_name);
+
+	write_midx_header(f, num_chunks, 0);
+
+	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
+	commit_lock_file(&lk);
+
 	return 0;
 }
t/t5319-multi-pack-index.sh +2 -1
index ec3ddbe79c..8622a7cdce 100755
@@ -4,7 +4,8 @@ test_description='multi-pack-indexes'
 . ./test-lib.sh
 
 test_expect_success 'write midx with no packs' '
-	git multi-pack-index --object-dir=. write
+	git multi-pack-index --object-dir=. write &&
+	test_path_is_file pack/multi-pack-index
 '
 
 test_done
Signature · 1 line
2.18.0.24.g1b579a2ee9
#83 lore raw

Re: [PATCH v2 05/24] midx: write header information to lockfile

Quoted 6 lines · Derrick Stolee
midx.c +6 -0
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1
+#define MIDX_HEADER_SIZE 12
+
+static char *get_midx_filename(const char *object_dir)

midx.c line 21

Quoted 10 lines
midx.c +10 -0
+{
+	return xstrfmt("%s/pack/multi-pack-index", object_dir);
+}
+
+static size_t write_midx_header(struct hashfile *f,
+				unsigned char num_chunks,
+				uint32_t num_packs)
+{
+	unsigned char byte_values[4];
+	hashwrite_be32(f, MIDX_SIGNATURE);

WARNING: Missing a blank line after declarations
#48: FILE: midx.c:21:
+ unsigned char byte_values[4];
+ hashwrite_be32(f, MIDX_SIGNATURE);

Quoted 17 lines
midx.c +15 -0
+	byte_values[0] = MIDX_VERSION;
+	byte_values[1] = MIDX_HASH_VERSION;
+	byte_values[2] = num_chunks;
+	byte_values[3] = 0; /* unused */
+	hashwrite(f, byte_values, sizeof(byte_values));
+	hashwrite_be32(f, num_packs);
+
+	return MIDX_HEADER_SIZE;
+}
+
 int write_midx_file(const char *object_dir)
 {
+	unsigned char num_chunks = 0;
+	char *midx_name;
+	struct hashfile *f;
+	struct lock_file lk;
+

midx.c line 48

Quoted 10 lines
midx.c +10 -0
+	midx_name = get_midx_filename(object_dir);
+	if (safe_create_leading_directories(midx_name)) {
+		UNLEAK(midx_name);
+		die_errno(_("unable to create leading directories of %s"),
+			  midx_name);
+	}
+
+	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
+	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
+	FREE_AND_NULL(midx_name);

I am not sure why people prefer FREE_AND_NULL over free() for things like this. It is on stack; it's not like this is a static variable visible after this function returns or anything like that.

Quoted 21 lines
midx.c +5 -0
+	write_midx_header(f, num_chunks, 0);
+
+	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
+	commit_lock_file(&lk);
+
 	return 0;
 }
t/t5319-multi-pack-index.sh +2 -1
index ec3ddbe79c..8622a7cdce 100755
@@ -4,7 +4,8 @@ test_description='multi-pack-indexes'
 . ./test-lib.sh
 
 test_expect_success 'write midx with no packs' '
-	git multi-pack-index --object-dir=. write
+	git multi-pack-index --object-dir=. write &&
+	test_path_is_file pack/multi-pack-index
 '
 
 test_done
#84 lore raw

Re: [PATCH v2 05/24] midx: write header information to lockfile

Quoted 45 lines · Junio C Hamano
Quoted 6 lines · Derrick Stolee
midx.c +6 -0
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1
+#define MIDX_HEADER_SIZE 12
+
+static char *get_midx_filename(const char *object_dir)

midx.c line 21

Quoted 10 lines
midx.c +10 -0
+{
+	return xstrfmt("%s/pack/multi-pack-index", object_dir);
+}
+
+static size_t write_midx_header(struct hashfile *f,
+				unsigned char num_chunks,
+				uint32_t num_packs)
+{
+	unsigned char byte_values[4];
+	hashwrite_be32(f, MIDX_SIGNATURE);

WARNING: Missing a blank line after declarations
#48: FILE: midx.c:21:

midx.c +2 -0
+       unsigned char byte_values[4];
+       hashwrite_be32(f, MIDX_SIGNATURE);
Quoted 23 lines
midx.c +20 -0
+	byte_values[0] = MIDX_VERSION;
+	byte_values[1] = MIDX_HASH_VERSION;
+	byte_values[2] = num_chunks;
+	byte_values[3] = 0; /* unused */
+	hashwrite(f, byte_values, sizeof(byte_values));
+	hashwrite_be32(f, num_packs);
+
+	return MIDX_HEADER_SIZE;
+}
+
  int write_midx_file(const char *object_dir)
  {
+	unsigned char num_chunks = 0;
+	char *midx_name;
+	struct hashfile *f;
+	struct lock_file lk;
+
+	midx_name = get_midx_filename(object_dir);
+	if (safe_create_leading_directories(midx_name)) {
+		UNLEAK(midx_name);
+		die_errno(_("unable to create leading directories of %s"),
+			  midx_name);
Quoted 8 lines

midx.c line 48

Quoted 5 lines
midx.c +5 -0
+	}
+
+	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
+	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
+	FREE_AND_NULL(midx_name);

I am not sure why people prefer FREE_AND_NULL over free() for things like this. It is on stack; it's not like this is a static variable visible after this function returns or anything like that.

I default to FREE_AND_NULL(X) because a later change may introduce logic to use X later in the same code block. In this case, we add a 'cleanup:' at the end which would fail in a success case if we don't set midx_name to NULL here.

I think there are some other FREE_AND_NULLs that are currently at the end of a code block, so I'll work to clean those up.

Quoted 22 lines
Quoted 22 lines
midx.c +5 -0
+	write_midx_header(f, num_chunks, 0);
+
+	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
+	commit_lock_file(&lk);
+
  	return 0;
  }
t/t5319-multi-pack-index.sh +2 -1
index ec3ddbe79c..8622a7cdce 100755
@@ -4,7 +4,8 @@ test_description='multi-pack-indexes'
  . ./test-lib.sh
 
  test_expect_success 'write midx with no packs' '
-	git multi-pack-index --object-dir=. write
+	git multi-pack-index --object-dir=. write &&
+	test_path_is_file pack/multi-pack-index
  '
 
  test_done
#85 lore raw

PATCH v2 6/24 multi-pack-index: load into memory

In reply to Derrick Stolee

Create a new multi_pack_index struct for loading multi-pack-indexes into memory. Create a test-tool builtin for reading basic information about that multi-pack-index to verify the correct data is written.

---
 Makefile                    |  1 +
 midx.c                      | 78 +++++++++++++++++++++++++++++++++++++
 midx.h                      |  4 ++
 object-store.h              | 16 ++++++++
 t/helper/test-read-midx.c   | 34 ++++++++++++++++
 t/helper/test-tool.c        |  1 +
 t/helper/test-tool.h        |  1 +
 t/t5319-multi-pack-index.sh | 12 +++++-
 8 files changed, 146 insertions(+), 1 deletion(-)
 create mode 100644 t/helper/test-read-midx.c
Makefile +1 -0
index f5636c711d..0b801d1b16 100644
@@ -717,6 +717,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-path-utils.o
 TEST_BUILTINS_OBJS += test-prio-queue.o
 TEST_BUILTINS_OBJS += test-read-cache.o
+TEST_BUILTINS_OBJS += test-read-midx.o
 TEST_BUILTINS_OBJS += test-ref-store.o
 TEST_BUILTINS_OBJS += test-regex.o
 TEST_BUILTINS_OBJS += test-revision-walking.o
midx.c +78 -0

96 lines not on this page · raw message

midx.h +4 -0
index dbdbe9f873..2d83dd9ec1 100644
@@ -1,6 +1,10 @@
 #ifndef __MIDX_H__
 #define __MIDX_H__
 
+struct multi_pack_index;
+
+struct multi_pack_index *load_multi_pack_index(const char *object_dir);
+
 int write_midx_file(const char *object_dir);
 
 #endif
object-store.h +16 -0
index d683112fd7..4f410841cc 100644
@@ -84,6 +84,22 @@ struct packed_git {
 	char pack_name[FLEX_ARRAY]; /* more */
 };
 
+struct multi_pack_index {
+	int fd;
+
+	const unsigned char *data;
+	size_t data_len;
+
+	uint32_t signature;
+	unsigned char version;
+	unsigned char hash_len;
+	unsigned char num_chunks;
+	uint32_t num_packs;
+	uint32_t num_objects;
+
+	char object_dir[FLEX_ARRAY];
+};
+
 struct raw_object_store {
 	/*
 	 * Path to the repository's object store.
t/helper/test-read-midx.c new file +34 -0
new file mode 100644
index 0000000000..5abf969175
@@ -0,0 +1,34 @@
+/*
+ * test-mktemp.c: code to exercise the creation of temporary files
+ */
+#include "test-tool.h"
+#include "cache.h"
+#include "midx.h"
+#include "repository.h"
+#include "object-store.h"
+
+static int read_midx_file(const char *object_dir)
+{
+	struct multi_pack_index *m = load_multi_pack_index(object_dir);
+
+	if (!m)
+		return 0;
+
+	printf("header: %08x %d %d %d\n",
+	       m->signature,
+	       m->version,
+	       m->num_chunks,
+	       m->num_packs);
+
+	printf("object_dir: %s\n", m->object_dir);
+
+	return 0;
+}
+
+int cmd__read_midx(int argc, const char **argv)
+{
+	if (argc != 2)
+		usage("read-midx <object_dir>");
+
+	return read_midx_file(argv[1]);
+}
t/helper/test-tool.c +1 -0
index 805a45de9c..1c3ab36e6c 100644
@@ -27,6 +27,7 @@ static struct test_cmd cmds[] = {
 	{ "path-utils", cmd__path_utils },
 	{ "prio-queue", cmd__prio_queue },
 	{ "read-cache", cmd__read_cache },
+	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
 	{ "regex", cmd__regex },
 	{ "revision-walking", cmd__revision_walking },
t/helper/test-tool.h +1 -0
index 7116ddfb94..6af8c08a66 100644
@@ -21,6 +21,7 @@ int cmd__online_cpus(int argc, const char **argv);
 int cmd__path_utils(int argc, const char **argv);
 int cmd__prio_queue(int argc, const char **argv);
 int cmd__read_cache(int argc, const char **argv);
+int cmd__read_midx(int argc, const char **argv);
 int cmd__ref_store(int argc, const char **argv);
 int cmd__regex(int argc, const char **argv);
 int cmd__revision_walking(int argc, const char **argv);
t/t5319-multi-pack-index.sh +11 -1
index 8622a7cdce..0372704c96 100755
@@ -3,9 +3,19 @@
 test_description='multi-pack-indexes'
 . ./test-lib.sh
 
+midx_read_expect() {
+	cat >expect <<- EOF
+	header: 4d494458 1 0 0
+	object_dir: .
+	EOF
+	test-tool read-midx . >actual &&
+	test_cmp expect actual
+}
+
 test_expect_success 'write midx with no packs' '
 	git multi-pack-index --object-dir=. write &&
-	test_path_is_file pack/multi-pack-index
+	test_path_is_file pack/multi-pack-index &&
+	midx_read_expect
 '
 
 test_done
Signature · 1 line
2.18.0.24.g1b579a2ee9
#86 lore raw

Re: [PATCH v2 06/24] multi-pack-index: load into memory

Quoted 31 lines · Derrick Stolee
midx.c +25 -0
+#define MIDX_HASH_LEN 20
+#define MIDX_MIN_SIZE (MIDX_HEADER_SIZE + MIDX_HASH_LEN)
 
 static char *get_midx_filename(const char *object_dir)
 {
 	return xstrfmt("%s/pack/multi-pack-index", object_dir);
 }
 
+struct multi_pack_index *load_multi_pack_index(const char *object_dir)
+{
+	struct multi_pack_index *m = NULL;
+	int fd;
+	struct stat st;
+	size_t midx_size;
+	void *midx_map = NULL;
+	uint32_t hash_version;
+	char *midx_name = get_midx_filename(object_dir);
+
+	fd = git_open(midx_name);
+
+	if (fd < 0) {
+		error_errno(_("failed to read %s"), midx_name);
+		FREE_AND_NULL(midx_name);
+		return NULL;
+	}
+	if (fstat(fd, &st)) {
+		error_errno(_("failed to read %s"), midx_name);
+		FREE_AND_NULL(midx_name);
+		close(fd);
+		return NULL;
+	}

midx.c line 51

Quoted 10 lines
midx.c +10 -0
+
+	midx_size = xsize_t(st.st_size);
+
+	if (midx_size < MIDX_MIN_SIZE) {
+		close(fd);
+		error(_("multi-pack-index file %s is too small"), midx_name);
+		goto cleanup_fail;
+	}
+
+	FREE_AND_NULL(midx_name);

Error handling in the above part looks a bit inconsistent. I first thought that the earlier ones manually clean up and leave because jumping to cleanup_fail would need a successfully opened fd and successfully mmapped midx_map, but the above "goto" forces
cleanup_fail: to munmap NULL and close an already closed fd.

I wonder if it is simpler to do

	cleanup_fail:
		/* no need to check for NULL when freeing */
		free(m);
		free(midx_name);
		if (midx_map)
			munmap(midx_map, midx_size);
		if (0 <= fd)
			close(fd);
		return NULL;

and have all of the above error codepath to jump there.

Quoted 11 lines
midx.c +11 -0
+	midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+
+	m = xcalloc(1, sizeof(*m) + strlen(object_dir) + 1);
+	strcpy(m->object_dir, object_dir);
+	m->data = midx_map;
+
+	m->signature = get_be32(m->data);
+	if (m->signature != MIDX_SIGNATURE) {
+		error(_("multi-pack-index signature 0x%08x does not match signature 0x%08x"),
+		      m->signature, MIDX_SIGNATURE);
+		goto cleanup_fail;

midx.c line 73

Quoted 10 lines
midx.c +10 -0
+	}
+
+	m->version = m->data[4];
+	if (m->version != MIDX_VERSION) {
+		error(_("multi-pack-index version %d not recognized"),
+		      m->version);
+		goto cleanup_fail;
+	}
+
+	hash_version = m->data[5];

Is there a good existing example to show a better way to avoid these hard-coded constants that describe/define the file format?

midx.c line 80

Quoted 7 lines
midx.c +7 -0
+	if (hash_version != MIDX_HASH_VERSION) {
+		error(_("hash version %u does not match"), hash_version);
+		goto cleanup_fail;
+	}
+	m->hash_len = MIDX_HASH_LEN;
+
+	m->num_chunks = *(m->data + 6);

By the way, this mixture of m->data[4] and *(m->data + 6) is even worse. You could do get_be32(&8[m->data]) if you want to irritate readers even more ;-)

Quoted 19 lines
midx.c +12 -0
+	m->num_packs = get_be32(m->data + 8);
+
+	return m;
+
+cleanup_fail:
+	FREE_AND_NULL(m);
+	FREE_AND_NULL(midx_name);
+	munmap(midx_map, midx_size);
+	close(fd);
+	return NULL;
+}
+
t/t5319-multi-pack-index.sh +0 -0
index 8622a7cdce..0372704c96 100755

t/t5319-multi-pack-index.sh line 6

Quoted 4 lines
t/t5319-multi-pack-index.sh +1 -0
@@ -3,9 +3,19 @@
 test_description='multi-pack-indexes'
 . ./test-lib.sh
 
+midx_read_expect() {

"midx_read_expect () {", i.e. SP on both sides of (), please.

t/t5319-multi-pack-index.sh line 7

Quoted 1 line
t/t5319-multi-pack-index.sh +1 -0
+	cat >expect <<- EOF

"<<-\EOF", i.e. make it easy for readers to spot that there is no funny substitutions happening in the here-doc body.

Quoted 15 lines
t/t5319-multi-pack-index.sh +9 -1
+	header: 4d494458 1 0 0
+	object_dir: .
+	EOF
+	test-tool read-midx . >actual &&
+	test_cmp expect actual
+}
+
 test_expect_success 'write midx with no packs' '
 	git multi-pack-index --object-dir=. write &&
-	test_path_is_file pack/multi-pack-index
+	test_path_is_file pack/multi-pack-index &&
+	midx_read_expect
 '
 
 test_done
#87 lore raw

Re: [PATCH v2 06/24] multi-pack-index: load into memory

Quoted 4 lines · Junio C Hamano

t/t5319-multi-pack-index.sh line 7

Quoted 1 line · Derrick Stolee
t/t5319-multi-pack-index.sh +1 -0
+	cat >expect <<- EOF

"<<-\EOF", i.e. make it easy for readers to spot that there is no funny substitutions happening in the here-doc body.

While I don't use substitutions in this patch, I do use them in later patches. Here is the final version of this method:

midx_read_expect () {
        NUM_PACKS=$1
        NUM_OBJECTS=$2
        NUM_CHUNKS=$3
        OBJECT_DIR=$4
        EXTRA_CHUNKS="$5"
        cat >expect <<-\EOF
        header: 4d494458 1 $NUM_CHUNKS $NUM_PACKS
        chunks: pack_names oid_fanout oid_lookup object_offsets$EXTRA_CHUNKS
        num_objects: $NUM_OBJECTS
        packs:
        EOF
        if [ $NUM_PACKS -ge 1 ]
        then
                ls $OBJECT_DIR/pack/ | grep idx | sort >> expect fi
        printf "object_dir: $OBJECT_DIR\n" >>expect &&
        test-tool read-midx $OBJECT_DIR >actual &&
        test_cmp expect actual
}

Using <<-\EOF causes these substitutions to fail. Is there a different way I should construct this method?

Thanks,
-Stolee

#88 lore raw

Re: [PATCH v2 06/24] multi-pack-index: load into memory

Quoted 12 lines · Derrick Stolee

On 6/25/2018 3:38 PM, Junio C Hamano wrote:
While I don't use substitutions in this patch, I do use them in later patches. Here is the final version of this method:

midx_read_expect () {
         NUM_PACKS=$1
         NUM_OBJECTS=$2
         NUM_CHUNKS=$3
         EXTRA_CHUNKS="$5"
         cat >expect <<-\EOF
         header: 4d494458 1 $NUM_CHUNKS $NUM_PACKS
         chunks: pack_names oid_fanout oid_lookup
Quoted 7 lines

object_offsets$EXTRA_CHUNKS
num_objects: $NUM_OBJECTS
packs:
EOF

Using <<-\EOF causes these substitutions to fail. Is there a different way I should construct this method?

When you need to interpolate variables into the here-doc, use <<-EOF; when you don't, use <<-\EOF.

#89 lore raw

Re: [PATCH v2 06/24] multi-pack-index: load into memory

Quoted 18 lines · Eric Sunshine
Quoted 17 lines · Derrick Stolee

On 6/25/2018 3:38 PM, Junio C Hamano wrote:
While I don't use substitutions in this patch, I do use them in later patches. Here is the final version of this method:

midx_read_expect () {
         NUM_PACKS=$1
         NUM_OBJECTS=$2
         NUM_CHUNKS=$3
         EXTRA_CHUNKS="$5"
         cat >expect <<-\EOF
         header: 4d494458 1 $NUM_CHUNKS $NUM_PACKS
         chunks: pack_names oid_fanout oid_lookup

object_offsets$EXTRA_CHUNKS
num_objects: $NUM_OBJECTS
packs:
EOF

Quoted 5 lines
Quoted 2 lines

Using <<-\EOF causes these substitutions to fail. Is there a different way I should construct this method?

When you need to interpolate variables into the here-doc, use <<-EOF; when you don't, use <<-\EOF.

I think what was said is "in an early step there is no need to interpolate but the same here-doc will need substitution in a later step, and that is why I started an early step with a form without quoting", which is different from "when should I use the form with and without quoting?"

I think a reasonable response would have been "then please do use the quoted form in the early step to help reviewers to let them know there is not yet any substitutions, and then switch to quote-less form at the step that starts needing substitution. That way, reviewers can see the test started to become "interesting" by interpolating variable bits in the test vector by seeing the line with "<<EOF" appear in the patch as modified".

#90 lore raw

PATCH v2 7/24 multi-pack-index: expand test data

In reply to Derrick Stolee

As we build the multi-pack-index file format, we want to test the format on real repoasitories. Add tests to t5319-multi-pack-index.sh that create repository data including multiple packfiles with both version 1 and version 2 formats.

The current 'git multi-pack-index write' command will always write the same file with no "real" data. This will be expanded in future commits, along with the test expectations.

---
 t/t5319-multi-pack-index.sh | 99 +++++++++++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)
t/t5319-multi-pack-index.sh +99 -0
index 0372704c96..d533fd0dbc 100755
@@ -13,9 +13,108 @@ midx_read_expect() {
 }
 
 test_expect_success 'write midx with no packs' '
+	test_when_finished rm pack/multi-pack-index &&
 	git multi-pack-index --object-dir=. write &&
 	test_path_is_file pack/multi-pack-index &&
 	midx_read_expect
 '
 
+test_expect_success 'create objects' '
+	for i in `test_seq 1 5`
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 > wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		i=$(expr $i + 1) || return 1
+	done &&
+	{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+	git update-index --add file_101 &&
+	tree=$(git write-tree) &&
+	commit=$(git commit-tree $tree </dev/null) && {
+	echo $tree &&
+	git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+	} >obj-list &&
+	git update-ref HEAD $commit
+'
+
+test_expect_success 'write midx with one v1 pack' '
+	pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
+	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
+	git multi-pack-index --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'write midx with one v2 pack' '
+	git pack-objects --index-version=2,0x40 pack/test <obj-list &&
+	git multi-pack-index --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'Add more objects' '
+	for i in `test_seq 6 5`
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 > wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		i=$(expr $i + 1) || return 1
+	done &&
+	{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+	git update-index --add file_101 &&
+	tree=$(git write-tree) &&
+	commit=$(git commit-tree $tree -p HEAD</dev/null) && {
+	echo $tree &&
+	git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+	} >obj-list2 &&
+	git update-ref HEAD $commit
+'
+
+test_expect_success 'write midx with two packs' '
+	git pack-objects --index-version=1 pack/test-2 <obj-list2 &&
+	git multi-pack-index --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'Add more packs' '
+	for j in `test_seq 1 10`
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 > wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >> wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 > deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+		git update-index --add file_101 &&
+		tree=$(git write-tree) &&
+		commit=$(git commit-tree $tree -p HEAD</dev/null) && {
+		echo $tree &&
+		git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+		} >obj-list &&
+		git update-ref HEAD $commit &&
+		git pack-objects --index-version=2 test-pack <obj-list &&
+		i=$(expr $i + 1) || return 1 &&
+		j=$(expr $j + 1) || return 1
+	done
+'
+
+test_expect_success 'write midx with twelve packs' '
+	git multi-pack-index --object-dir=. write &&
+	midx_read_expect
+'
+
 test_done
Signature · 1 line
2.18.0.24.g1b579a2ee9
#91 lore raw

Re: [PATCH v2 07/24] multi-pack-index: expand test data

t/t5319-multi-pack-index.sh midx_read_expect() { · line 16

Quoted 2 lines · Derrick Stolee

test_expect_success 'write midx with no packs' '

t/t5319-multi-pack-index.sh +1 -0
+	test_when_finished rm pack/multi-pack-index &&

It is generally a good idea to give "-f" to "rm" used in test_when_finished. The main command sequence may have failed before it has a chance to create that file; even though an error will be ignored by the when_finished handler, it is a good code hygiene to mark expected condition (e.g. the file to be removed may not exist at this point) to signal to future readers that the author knew what s/he was writing.

t/t5319-multi-pack-index.sh midx_read_expect() { · line 23

Quoted 7 lines
t/t5319-multi-pack-index.sh +2 -0
 	git multi-pack-index --object-dir=. write &&
 	test_path_is_file pack/multi-pack-index &&
 	midx_read_expect
 '
 
+test_expect_success 'create objects' '
+	for i in `test_seq 1 5`

Please write it as "$(test_seq 1 5)"

t/t5319-multi-pack-index.sh midx_read_expect() { · line 26

Quoted 3 lines
t/t5319-multi-pack-index.sh +3 -0
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 > wide_delta_$iii &&
	test-tool genrandom "bar" 200 >"wide_delta_$iii" &&

i.e. no SP between the redirection operator and the target file name. Also dq around target file name that depends on variable substitution to tell bash that we know what we are doing (some vintage of bash will throw warning at us unless we do so).

#92 lore raw

PATCH v2 8/24 packfile: generalize pack directory list

In reply to Derrick Stolee

In anticipation of sharing the pack directory listing with the multi-pack-index, generalize prepare_packed_git_one() into for_each_file_in_pack_dir().

---
 packfile.c | 103 +++++++++++++++++++++++++++++++++--------------------
 packfile.h |   6 ++++
 2 files changed, 71 insertions(+), 38 deletions(-)
packfile.c +65 -38
index 7cd45aa4b2..db61c8813b 100644
@@ -738,13 +738,14 @@ static void report_pack_garbage(struct string_list *list)
 	report_helper(list, seen_bits, first, list->nr);
 }
 
-static void prepare_packed_git_one(struct repository *r, char *objdir, int local)
+void for_each_file_in_pack_dir(const char *objdir,
+			       each_file_in_pack_dir_fn fn,
+			       void *data)
 {
 	struct strbuf path = STRBUF_INIT;
 	size_t dirnamelen;
 	DIR *dir;
 	struct dirent *de;
-	struct string_list garbage = STRING_LIST_INIT_DUP;
 
 	strbuf_addstr(&path, objdir);
 	strbuf_addstr(&path, "/pack");
@@ -759,53 +760,79 @@ static void prepare_packed_git_one(struct repository *r, char *objdir, int local
 	strbuf_addch(&path, '/');
 	dirnamelen = path.len;
 	while ((de = readdir(dir)) != NULL) {
-		struct packed_git *p;
-		size_t base_len;
-
 		if (is_dot_or_dotdot(de->d_name))
 			continue;
 
 		strbuf_setlen(&path, dirnamelen);
 		strbuf_addstr(&path, de->d_name);
 
-		base_len = path.len;
-		if (strip_suffix_mem(path.buf, &base_len, ".idx")) {
-			/* Don't reopen a pack we already have. */
-			for (p = r->objects->packed_git; p;
-			     p = p->next) {
-				size_t len;
-				if (strip_suffix(p->pack_name, ".pack", &len) &&
-				    len == base_len &&
-				    !memcmp(p->pack_name, path.buf, len))
-					break;
-			}
-			if (p == NULL &&
-			    /*
-			     * See if it really is a valid .idx file with
-			     * corresponding .pack file that we can map.
-			     */
-			    (p = add_packed_git(path.buf, path.len, local)) != NULL)
-				install_packed_git(r, p);
-		}
-
-		if (!report_garbage)
-			continue;
-
-		if (ends_with(de->d_name, ".idx") ||
-		    ends_with(de->d_name, ".pack") ||
-		    ends_with(de->d_name, ".bitmap") ||
-		    ends_with(de->d_name, ".keep") ||
-		    ends_with(de->d_name, ".promisor"))
-			string_list_append(&garbage, path.buf);
-		else
-			report_garbage(PACKDIR_FILE_GARBAGE, path.buf);
+		fn(path.buf, path.len, de->d_name, data);
 	}
+
 	closedir(dir);
-	report_pack_garbage(&garbage);
-	string_list_clear(&garbage, 0);
 	strbuf_release(&path);
 }
 
+struct prepare_pack_data
+{
+	struct repository *r;
+	struct string_list *garbage;
+	int local;
+};
+
+static void prepare_pack(const char *full_name, size_t full_name_len, const char *file_name, void *_data)
+{
+	struct prepare_pack_data *data = (struct prepare_pack_data *)_data;
+	struct packed_git *p;
+	size_t base_len = full_name_len;
+
+	if (strip_suffix_mem(full_name, &base_len, ".idx")) {
+		/* Don't reopen a pack we already have. */
+		for (p = data->r->objects->packed_git; p; p = p->next) {
+			size_t len;
+			if (strip_suffix(p->pack_name, ".pack", &len) &&
+			    len == base_len &&
+			    !memcmp(p->pack_name, full_name, len))
+				break;
+		}
+
+		if (p == NULL &&
+		    /*
+		     * See if it really is a valid .idx file with
+		     * corresponding .pack file that we can map.
+		     */
+		    (p = add_packed_git(full_name, full_name_len, data->local)) != NULL)
+			install_packed_git(data->r, p);
+	}
+
+	if (!report_garbage)
+	       return;
+
+	if (ends_with(file_name, ".idx") ||
+	    ends_with(file_name, ".pack") ||
+	    ends_with(file_name, ".bitmap") ||
+	    ends_with(file_name, ".keep") ||
+	    ends_with(file_name, ".promisor"))
+		string_list_append(data->garbage, full_name);
+	else
+		report_garbage(PACKDIR_FILE_GARBAGE, full_name);
+}
+
+static void prepare_packed_git_one(struct repository *r, char *objdir, int local)
+{
+	struct prepare_pack_data data;
+	struct string_list garbage = STRING_LIST_INIT_DUP;
+
+	data.r = r;
+	data.garbage = &garbage;
+	data.local = local;
+
+	for_each_file_in_pack_dir(objdir, prepare_pack, &data);
+
+	report_pack_garbage(data.garbage);
+	string_list_clear(data.garbage, 0);
+}
+
 static void prepare_packed_git(struct repository *r);
 /*
  * Give a fast, rough count of the number of objects in the repository. This
packfile.h +6 -0
index e0a38aba93..d2ad30300a 100644
@@ -28,6 +28,12 @@ extern char *sha1_pack_index_name(const unsigned char *sha1);
 
 extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
 
+typedef void each_file_in_pack_dir_fn(const char *full_path, size_t full_path_len,
+				      const char *file_pach, void *data);
+void for_each_file_in_pack_dir(const char *objdir,
+			       each_file_in_pack_dir_fn fn,
+			       void *data);
+
 /* A hook to report invalid files in pack directory */
 #define PACKDIR_FILE_PACK 1
 #define PACKDIR_FILE_IDX 2
Signature · 1 line
2.18.0.24.g1b579a2ee9
#93 lore raw

Re: [PATCH v2 08/24] packfile: generalize pack directory list

Review

Quoted 2 lines · Derrick Stolee
(quoted diff) +2 -0
+struct prepare_pack_data
+{

ERROR: open brace '{' following struct go on the same line #88: FILE: packfile.c:777:
+struct prepare_pack_data
+{

Quoted 19 lines
packfile.c +19 -0
+	struct repository *r;
+	struct string_list *garbage;
+	int local;
+};
+
+static void prepare_pack(const char *full_name, size_t full_name_len, const char *file_name, void *_data)
+{
+	struct prepare_pack_data *data = (struct prepare_pack_data *)_data;
+	struct packed_git *p;
+	size_t base_len = full_name_len;
+
+	if (strip_suffix_mem(full_name, &base_len, ".idx")) {
+		/* Don't reopen a pack we already have. */
+		for (p = data->r->objects->packed_git; p; p = p->next) {
+			size_t len;
+			if (strip_suffix(p->pack_name, ".pack", &len) &&
+			    len == base_len &&
+			    !memcmp(p->pack_name, full_name, len))
+				break;

packfile.c static void prepare_packed_git_one(struct repository *r, char *objdir, int local · line 806

Quoted 10 lines
packfile.c +10 -0
+		}
+
+		if (p == NULL &&
+		    /*
+		     * See if it really is a valid .idx file with
+		     * corresponding .pack file that we can map.
+		     */
+		    (p = add_packed_git(full_name, full_name_len, data->local)) != NULL)
+			install_packed_git(data->r, p);
+	}

This is merely a moved code and the issue was inherited from the original, but can we make it easier to read and at the same time remove that assignment inside if() condition (which generally makes the code harder to read)? The most naïve

	if (!p) {
		p = add_packed_git(full_name, full_name_len, data->local);
		if (p)
			install_packed_git(data->r, p);
	}

isn't all that bad, but there may be even better ways.

packfile.c static void prepare_packed_git_one(struct repository *r, char *objdir, int local · line 809

Quoted 2 lines
packfile.c +2 -0
+	if (!report_garbage)
+	       return;

This "return;" is indented with a run of SP not with HT?

#94 lore raw

PATCH v2 9/24 multi-pack-index: read packfile list

In reply to Derrick Stolee

When constructing a multi-pack-index file for a given object directory, read the files within the enclosed pack directory and find matches that end with ".idx" and find the correct paired packfile using add_packed_git().

---
 midx.c                      | 45 ++++++++++++++++++++++++++++++++++++-
 t/t5319-multi-pack-index.sh | 16 ++++++-------
 2 files changed, 52 insertions(+), 9 deletions(-)
midx.c +44 -1
index 0977397d6a..e79ffb5576 100644
@@ -1,6 +1,8 @@
 #include "cache.h"
 #include "csum-file.h"
+#include "dir.h"
 #include "lockfile.h"
+#include "packfile.h"
 #include "object-store.h"
 #include "midx.h"
 
@@ -107,12 +109,40 @@ static size_t write_midx_header(struct hashfile *f,
 	return MIDX_HEADER_SIZE;
 }
 
+struct pack_list
+{
+	struct packed_git **list;
+	uint32_t nr;
+	uint32_t alloc;
+};
+
+static void add_pack_to_midx(const char *full_path, size_t full_path_len,
+			     const char *file_name, void *data)
+{
+	struct pack_list *packs = (struct pack_list *)data;
+
+	if (ends_with(file_name, ".idx")) {
+		ALLOC_GROW(packs->list, packs->nr + 1, packs->alloc);
+
+		packs->list[packs->nr] = add_packed_git(full_path,
+							 full_path_len,
+							 0);
+		if (!packs->list[packs->nr])
+			warning(_("failed to add packfile '%s'"),
+				full_path);
+		else
+			packs->nr++;
+	}
+}
+
 int write_midx_file(const char *object_dir)
 {
 	unsigned char num_chunks = 0;
 	char *midx_name;
+	uint32_t i;
 	struct hashfile *f;
 	struct lock_file lk;
+	struct pack_list packs;
 
 	midx_name = get_midx_filename(object_dir);
 	if (safe_create_leading_directories(midx_name)) {
@@ -121,14 +151,27 @@ int write_midx_file(const char *object_dir)
 			  midx_name);
 	}
 
+	packs.nr = 0;
+	packs.alloc = 16;
+	packs.list = NULL;
+	ALLOC_ARRAY(packs.list, packs.alloc);
+
+	for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &packs);
+
 	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
 	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
 	FREE_AND_NULL(midx_name);
 
-	write_midx_header(f, num_chunks, 0);
+	write_midx_header(f, num_chunks, packs.nr);
 
 	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
 	commit_lock_file(&lk);
 
+	for (i = 0; i < packs.nr; i++) {
+		close_pack(packs.list[i]);
+		FREE_AND_NULL(packs.list[i]);
+	}
+
+	FREE_AND_NULL(packs.list);
 	return 0;
 }
t/t5319-multi-pack-index.sh +8 -8
index d533fd0dbc..4d4d6ca0a6 100755
@@ -4,8 +4,9 @@ test_description='multi-pack-indexes'
 . ./test-lib.sh
 
 midx_read_expect() {
+	NUM_PACKS=$1
 	cat >expect <<- EOF
-	header: 4d494458 1 0 0
+	header: 4d494458 1 0 $NUM_PACKS
 	object_dir: .
 	EOF
 	test-tool read-midx . >actual &&
@@ -15,8 +16,7 @@ midx_read_expect() {
 test_expect_success 'write midx with no packs' '
 	test_when_finished rm pack/multi-pack-index &&
 	git multi-pack-index --object-dir=. write &&
-	test_path_is_file pack/multi-pack-index &&
-	midx_read_expect
+	midx_read_expect 0
 '
 
 test_expect_success 'create objects' '
@@ -47,13 +47,13 @@ test_expect_success 'write midx with one v1 pack' '
 	pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
 	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect
+	midx_read_expect 1
 '
 
 test_expect_success 'write midx with one v2 pack' '
 	git pack-objects --index-version=2,0x40 pack/test <obj-list &&
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect
+	midx_read_expect 1
 '
 
 test_expect_success 'Add more objects' '
@@ -83,7 +83,7 @@ test_expect_success 'Add more objects' '
 test_expect_success 'write midx with two packs' '
 	git pack-objects --index-version=1 pack/test-2 <obj-list2 &&
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect
+	midx_read_expect 2
 '
 
 test_expect_success 'Add more packs' '
@@ -106,7 +106,7 @@ test_expect_success 'Add more packs' '
 		git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
 		} >obj-list &&
 		git update-ref HEAD $commit &&
-		git pack-objects --index-version=2 test-pack <obj-list &&
+		git pack-objects --index-version=2 pack/test-pack <obj-list &&
 		i=$(expr $i + 1) || return 1 &&
 		j=$(expr $j + 1) || return 1
 	done
@@ -114,7 +114,7 @@ test_expect_success 'Add more packs' '
 
 test_expect_success 'write midx with twelve packs' '
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect
+	midx_read_expect 12
 '
 
 test_done
Signature · 1 line
2.18.0.24.g1b579a2ee9
#95 lore raw

PATCH v2 10/24 multi-pack-index: write pack names in chunk

In reply to Derrick Stolee

The multi-pack-index needs to track which packfiles it indexes. Store these in our first required chunk. Since filenames are not well structured, add padding to keep good alignment in later chunks.

Modify the 'git multi-pack-index read' subcommand to output the existence of the pack-file name chunk. Modify t5319-multi-pack-index.sh to reflect this new output and the new expected number of chunks.

Defense in depth: A pattern we are using in the multi-pack-index feature is to verify the data as we write it. We want to ensure we never write invalid data to the multi-pack-index. There are many checks that verify that the values we are writing fit the format definitions. This mainly helps developers while working on the feature, but it can also identify issues that only appear when dealing with very large data sets. These large sets are hard to encode into test cases.

---
 Documentation/technical/pack-format.txt |   6 +
 midx.c                                  | 190 ++++++++++++++++++++++--
 object-store.h                          |   2 +
 t/helper/test-read-midx.c               |   7 +
 t/t5319-multi-pack-index.sh             |   3 +-
 5 files changed, 198 insertions(+), 10 deletions(-)
Documentation/technical/pack-format.txt +6 -0
index e060e693f4..6c5a77475f 100644
@@ -296,6 +296,12 @@ CHUNK LOOKUP:
 
 CHUNK DATA:
 
+	Packfile Names (ID: {'P', 'N', 'A', 'M'})
+	    Stores the packfile names as concatenated, null-terminated strings.
+	    Packfiles must be listed in lexicographic order for fast lookups by
+	    name. This is the only chunk not guaranteed to be a multiple of four
+	    bytes in length, so should be the last chunk for alignment reasons.
+
 	(This section intentionally left incomplete.)
 
 TRAILER:
midx.c +181 -9

259 lines not on this page · raw message

object-store.h +2 -0
index 4f410841cc..c87d051849 100644
@@ -97,6 +97,8 @@ struct multi_pack_index {
 	uint32_t num_packs;
 	uint32_t num_objects;
 
+	const unsigned char *chunk_pack_names;
+
 	char object_dir[FLEX_ARRAY];
 };
 
t/helper/test-read-midx.c +7 -0
index 5abf969175..a9232d8219 100644
@@ -20,6 +20,13 @@ static int read_midx_file(const char *object_dir)
 	       m->num_chunks,
 	       m->num_packs);
 
+	printf("chunks:");
+
+	if (m->chunk_pack_names)
+		printf(" pack_names");
+
+	printf("\n");
+
 	printf("object_dir: %s\n", m->object_dir);
 
 	return 0;
t/t5319-multi-pack-index.sh +2 -1
index 4d4d6ca0a6..1b2778961c 100755
@@ -6,7 +6,8 @@ test_description='multi-pack-indexes'
 midx_read_expect() {
 	NUM_PACKS=$1
 	cat >expect <<- EOF
-	header: 4d494458 1 0 $NUM_PACKS
+	header: 4d494458 1 1 $NUM_PACKS
+	chunks: pack_names
 	object_dir: .
 	EOF
 	test-tool read-midx . >actual &&
Signature · 1 line
2.18.0.24.g1b579a2ee9
#96 lore raw

PATCH v2 11/24 midx: read pack names into array

In reply to Derrick Stolee
---
 midx.c                      | 31 +++++++++++++++++++++++++++++++
 object-store.h              |  1 +
 t/helper/test-read-midx.c   |  5 +++++
 t/t5319-multi-pack-index.sh |  7 ++++++-
 4 files changed, 43 insertions(+), 1 deletion(-)
midx.c +31 -0
index 2fad99d1b8..8bbc966f6b 100644
@@ -33,6 +33,7 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir)
 	uint32_t hash_version;
 	char *midx_name = get_midx_filename(object_dir);
 	uint32_t i;
+	const char *cur_pack_name;
 
 	fd = git_open(midx_name);
 
@@ -116,6 +117,22 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir)
 	if (!m->chunk_pack_names)
 		die(_("multi-pack-index missing required pack-name chunk"));
 
+	m->pack_names = xcalloc(m->num_packs, sizeof(const char *));
+
+	cur_pack_name = (const char *)m->chunk_pack_names;
+	for (i = 0; i < m->num_packs; i++) {
+		m->pack_names[i] = cur_pack_name;
+
+		cur_pack_name += strlen(cur_pack_name) + 1;
+
+		if (i && strcmp(m->pack_names[i], m->pack_names[i - 1]) <= 0) {
+			error("MIDX pack names out of order: '%s' before '%s'",
+			      m->pack_names[i - 1],
+			      m->pack_names[i]);
+			goto cleanup_fail;
+		}
+	}
+
 	return m;
 
 cleanup_fail:
@@ -210,6 +227,20 @@ static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p
 	FREE_AND_NULL(pairs);
 }
 
+static size_t write_midx_pack_lookup(struct hashfile *f,
+				     char **pack_names,
+				     uint32_t nr_packs)
+{
+	uint32_t i, cur_len = 0;
+
+	for (i = 0; i < nr_packs; i++) {
+		hashwrite_be32(f, cur_len);
+		cur_len += strlen(pack_names[i]) + 1;
+	}
+
+	return sizeof(uint32_t) * (size_t)nr_packs;
+}
+
 static size_t write_midx_pack_names(struct hashfile *f,
 				    char **pack_names,
 				    uint32_t num_packs)
object-store.h +1 -0
index c87d051849..88169b33e9 100644
@@ -99,6 +99,7 @@ struct multi_pack_index {
 
 	const unsigned char *chunk_pack_names;
 
+	const char **pack_names;
 	char object_dir[FLEX_ARRAY];
 };
 
t/helper/test-read-midx.c +5 -0
index a9232d8219..0b53a9e8b5 100644
@@ -9,6 +9,7 @@
 
 static int read_midx_file(const char *object_dir)
 {
+	uint32_t i;
 	struct multi_pack_index *m = load_multi_pack_index(object_dir);
 
 	if (!m)
@@ -27,6 +28,10 @@ static int read_midx_file(const char *object_dir)
 
 	printf("\n");
 
+	printf("packs:\n");
+	for (i = 0; i < m->num_packs; i++)
+		printf("%s\n", m->pack_names[i]);
+
 	printf("object_dir: %s\n", m->object_dir);
 
 	return 0;
t/t5319-multi-pack-index.sh +6 -1
index 1b2778961c..800fa7749c 100755
@@ -8,8 +8,13 @@ midx_read_expect() {
 	cat >expect <<- EOF
 	header: 4d494458 1 1 $NUM_PACKS
 	chunks: pack_names
-	object_dir: .
+	packs:
 	EOF
+	if [ $NUM_PACKS -ge 1 ]
+	then
+		ls pack/ | grep idx | sort >> expect
+	fi
+	printf "object_dir: .\n" >>expect &&
 	test-tool read-midx . >actual &&
 	test_cmp expect actual
 }
Signature · 1 line
2.18.0.24.g1b579a2ee9
#97 lore raw

Re: [PATCH v2 11/24] midx: read pack names into array

Quoted 5 lines · Derrick Stolee
midx.c +3 -0
@@ -210,6 +227,20 @@ static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p
+static size_t write_midx_pack_lookup(struct hashfile *f,
+                                    char **pack_names,
+                                    uint32_t nr_packs)

midx.c static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p · line 239

Quoted 10 lines
midx.c +10 -0
+{
+       uint32_t i, cur_len = 0;
+
+       for (i = 0; i < nr_packs; i++) {
+               hashwrite_be32(f, cur_len);
+               cur_len += strlen(pack_names[i]) + 1;
+       }
+
+       return sizeof(uint32_t) * (size_t)nr_packs;
+}

This static function is never used, thus breaks the build with DEVELOPER=1:

midx.c:567:15: error: ‘write_midx_pack_lookup’ defined but not used
[-Werror=unused-function]
cc1: all warnings being treated as errors

#98 lore raw

PATCH v2 12/24 midx: sort and deduplicate objects from packfiles

In reply to Derrick Stolee

Before writing a list of objects and their offsets to a multi-pack-index, we need to collect the list of objects contained in the packfiles. There may be multiple copies of some objects, so this list must be deduplicated.

It is possible to artificially get into a state where there are many duplicate copies of objects. That can create high memory pressure if we are to create a list of all objects before de-duplication. To reduce this memory pressure without a significant performance drop, automatically group objects by the first byte of their object id. Use the IDX fanout tables to group the data, copy to a local array, then sort.

Copy only the de-duplicated entries. Select the duplicate based on the most-recent modified time of a packfile containing the object.

---
 midx.c     | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 packfile.c |  17 +++++++
 packfile.h |   2 +
 3 files changed, 146 insertions(+)
midx.c +127 -0
index 8bbc966f6b..648a501d74 100644
@@ -4,6 +4,7 @@
 #include "lockfile.h"
 #include "packfile.h"
 #include "object-store.h"
+#include "packfile.h"
 #include "midx.h"
 
 #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
@@ -187,6 +188,13 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len,
 			return;
 		}
 
+		if (open_pack_index(packs->list[packs->nr])) {
+			warning(_("failed to open pack-index '%s'"),
+				full_path);
+			close_pack(packs->list[packs->nr]);
+			FREE_AND_NULL(packs->list[packs->nr]);
+		}
+
 		packs->names[packs->nr] = xstrdup(file_name);
 		packs->pack_name_concat_len += strlen(file_name) + 1;
 		packs->nr++;
@@ -227,6 +235,120 @@ static void sort_packs_by_name(char **pack_names, uint32_t nr_packs, uint32_t *p
 	FREE_AND_NULL(pairs);
 }
 
+struct pack_midx_entry {
+	struct object_id oid;
+	uint32_t pack_int_id;
+	time_t pack_mtime;
+	uint64_t offset;
+};
+
+static int midx_oid_compare(const void *_a, const void *_b)
+{
+	const struct pack_midx_entry *a = (const struct pack_midx_entry *)_a;
+	const struct pack_midx_entry *b = (const struct pack_midx_entry *)_b;
+	int cmp = oidcmp(&a->oid, &b->oid);
+
+	if (cmp)
+		return cmp;
+
+	if (a->pack_mtime > b->pack_mtime)
+		return -1;
+	else if (a->pack_mtime < b->pack_mtime)
+		return 1;
+
+	return a->pack_int_id - b->pack_int_id;
+}
+
+static void fill_pack_entry(uint32_t pack_int_id,
+			    struct packed_git *p,
+			    uint32_t cur_object,
+			    struct pack_midx_entry *entry)
+{
+	if (!nth_packed_object_oid(&entry->oid, p, cur_object))
+		die(_("failed to locate object %d in packfile"), cur_object);
+
+	entry->pack_int_id = pack_int_id;
+	entry->pack_mtime = p->mtime;
+
+	entry->offset = nth_packed_object_offset(p, cur_object);
+}
+
+/*
+ * It is possible to artificially get into a state where there are many
+ * duplicate copies of objects. That can create high memory pressure if
+ * we are to create a list of all objects before de-duplication. To reduce
+ * this memory pressure without a significant performance drop, automatically
+ * group objects by the first byte of their object id. Use the IDX fanout
+ * tables to group the data, copy to a local array, then sort.
+ *
+ * Copy only the de-duplicated entries (selected by most-recent modified time
+ * of a packfile containing the object).
+ */
+static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
+						  uint32_t *perm,
+						  uint32_t nr_packs,
+						  uint32_t *nr_objects)
+{
+	uint32_t cur_fanout, cur_pack, cur_object;
+	uint32_t alloc_fanout, alloc_objects, total_objects = 0;
+	struct pack_midx_entry *entries_by_fanout = NULL;
+	struct pack_midx_entry *deduplicated_entries = NULL;
+
+	for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+		total_objects += p[cur_pack]->num_objects;
+	}
+
+	/*
+	 * As we de-duplicate by fanout value, we expect the fanout
+	 * slices to be evenly distributed, with some noise. Hence,
+	 * allocate slightly more than one 256th.
+	 */
+	alloc_objects = alloc_fanout = total_objects > 3200 ? total_objects / 200 : 16;
+
+	ALLOC_ARRAY(entries_by_fanout, alloc_fanout);
+	ALLOC_ARRAY(deduplicated_entries, alloc_objects);
+	*nr_objects = 0;
+
+	for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) {
+		uint32_t nr_fanout = 0;
+
+		for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+			uint32_t start = 0, end;
+
+			if (cur_fanout)
+				start = get_pack_fanout(p[cur_pack], cur_fanout - 1);
+			end = get_pack_fanout(p[cur_pack], cur_fanout);
+
+			for (cur_object = start; cur_object < end; cur_object++) {
+				ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
+				fill_pack_entry(perm[cur_pack], p[cur_pack], cur_object, &entries_by_fanout[nr_fanout]);
+				nr_fanout++;
+			}
+		}
+
+		QSORT(entries_by_fanout, nr_fanout, midx_oid_compare);
+
+		/*
+		 * The batch is now sorted by OID and then mtime (descending).
+		 * Take only the first duplicate.
+		 */
+		for (cur_object = 0; cur_object < nr_fanout; cur_object++) {
+			if (cur_object && !oidcmp(&entries_by_fanout[cur_object - 1].oid,
+						  &entries_by_fanout[cur_object].oid))
+				continue;
+
+			ALLOC_GROW(deduplicated_entries, *nr_objects + 1, alloc_objects);
+			memcpy(&deduplicated_entries[*nr_objects],
+			       &entries_by_fanout[cur_object],
+			       sizeof(struct pack_midx_entry));
+			(*nr_objects)++;
+		}
+	}
+
+	FREE_AND_NULL(entries_by_fanout);
+	return deduplicated_entries;
+}
+
 static size_t write_midx_pack_lookup(struct hashfile *f,
 				     char **pack_names,
 				     uint32_t nr_packs)
@@ -284,6 +406,8 @@ int write_midx_file(const char *object_dir)
 	uint64_t written = 0;
 	uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
 	uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];
+	uint32_t nr_entries;
+	struct pack_midx_entry *entries;
 
 	midx_name = get_midx_filename(object_dir);
 	if (safe_create_leading_directories(midx_name)) {
@@ -309,6 +433,8 @@ int write_midx_file(const char *object_dir)
 	ALLOC_ARRAY(pack_perm, packs.nr);
 	sort_packs_by_name(packs.names, packs.nr, pack_perm);
 
+	entries = get_sorted_entries(packs.list, pack_perm, packs.nr, &nr_entries);
+
 	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
 	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
 	FREE_AND_NULL(midx_name);
@@ -376,5 +502,6 @@ int write_midx_file(const char *object_dir)
 
 	FREE_AND_NULL(packs.list);
 	FREE_AND_NULL(packs.names);
+	FREE_AND_NULL(entries);
 	return 0;
 }
packfile.c +17 -0
index db61c8813b..39d6b66337 100644
@@ -196,6 +196,23 @@ int open_pack_index(struct packed_git *p)
 	return ret;
 }
 
+uint32_t get_pack_fanout(struct packed_git *p, uint32_t value)
+{
+	const uint32_t *level1_ofs = p->index_data;
+
+	if (!level1_ofs) {
+		if (open_pack_index(p))
+			return 0;
+		level1_ofs = p->index_data;
+	}
+
+	if (p->index_version > 1) {
+		level1_ofs += 2;
+	}
+
+	return ntohl(level1_ofs[value]);
+}
+
 static struct packed_git *alloc_packed_git(int extra)
 {
 	struct packed_git *p = xmalloc(st_add(sizeof(*p), extra));
packfile.h +2 -0
index d2ad30300a..b0eed44c0b 100644
@@ -69,6 +69,8 @@ extern int open_pack_index(struct packed_git *);
  */
 extern void close_pack_index(struct packed_git *);
 
+extern uint32_t get_pack_fanout(struct packed_git *p, uint32_t value);
+
 extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *);
 extern void close_pack_windows(struct packed_git *);
 extern void close_pack(struct packed_git *);
Signature · 1 line
2.18.0.24.g1b579a2ee9
#99 lore raw

PATCH v2 13/24 midx: write object ids in a chunk

In reply to Derrick Stolee
---
 Documentation/technical/pack-format.txt |  4 ++
 midx.c                                  | 51 ++++++++++++++++++++++---
 object-store.h                          |  1 +
 t/helper/test-read-midx.c               |  2 +
 t/t5319-multi-pack-index.sh             |  4 +-
 5 files changed, 55 insertions(+), 7 deletions(-)
Documentation/technical/pack-format.txt +4 -0
index 6c5a77475f..78ee0489c6 100644
@@ -302,6 +302,10 @@ CHUNK DATA:
 	    name. This is the only chunk not guaranteed to be a multiple of four
 	    bytes in length, so should be the last chunk for alignment reasons.
 
+	OID Lookup (ID: {'O', 'I', 'D', 'L'})
+	    The OIDs for all objects in the MIDX are stored in lexicographic
+	    order in this chunk.
+
 	(This section intentionally left incomplete.)
 
 TRAILER:
midx.c +46 -5

109 lines not on this page · raw message

object-store.h +1 -0
index 88169b33e9..25f8530eb4 100644
@@ -98,6 +98,7 @@ struct multi_pack_index {
 	uint32_t num_objects;
 
 	const unsigned char *chunk_pack_names;
+	const unsigned char *chunk_oid_lookup;
 
 	const char **pack_names;
 	char object_dir[FLEX_ARRAY];
t/helper/test-read-midx.c +2 -0
index 0b53a9e8b5..60bca5b668 100644
@@ -25,6 +25,8 @@ static int read_midx_file(const char *object_dir)
 
 	if (m->chunk_pack_names)
 		printf(" pack_names");
+	if (m->chunk_oid_lookup)
+		printf(" oid_lookup");
 
 	printf("\n");
 
t/t5319-multi-pack-index.sh +2 -2
index 800fa7749c..47e1c7d99e 100755
@@ -6,8 +6,8 @@ test_description='multi-pack-indexes'
 midx_read_expect() {
 	NUM_PACKS=$1
 	cat >expect <<- EOF
-	header: 4d494458 1 1 $NUM_PACKS
-	chunks: pack_names
+	header: 4d494458 1 2 $NUM_PACKS
+	chunks: pack_names oid_lookup
 	packs:
 	EOF
 	if [ $NUM_PACKS -ge 1 ]
Signature · 1 line
2.18.0.24.g1b579a2ee9
#100 lore raw

PATCH v2 14/24 midx: write object id fanout chunk

In reply to Derrick Stolee
---
 Documentation/technical/pack-format.txt |  5 +++
 midx.c                                  | 53 +++++++++++++++++++++++--
 object-store.h                          |  1 +
 t/helper/test-read-midx.c               |  4 +-
 t/t5319-multi-pack-index.sh             | 18 +++++----
 5 files changed, 69 insertions(+), 12 deletions(-)
Documentation/technical/pack-format.txt +5 -0
index 78ee0489c6..3215f7bfcd 100644
@@ -302,6 +302,11 @@ CHUNK DATA:
 	    name. This is the only chunk not guaranteed to be a multiple of four
 	    bytes in length, so should be the last chunk for alignment reasons.
 
+	OID Fanout (ID: {'O', 'I', 'D', 'F'})
+	    The ith entry, F[i], stores the number of OIDs with first
+	    byte at most i. Thus F[255] stores the total
+	    number of objects.
+
 	OID Lookup (ID: {'O', 'I', 'D', 'L'})
 	    The OIDs for all objects in the MIDX are stored in lexicographic
 	    order in this chunk.
midx.c +50 -3

104 lines not on this page · raw message

object-store.h +1 -0
index 25f8530eb4..3357e51100 100644
@@ -98,6 +98,7 @@ struct multi_pack_index {
 	uint32_t num_objects;
 
 	const unsigned char *chunk_pack_names;
+	const uint32_t *chunk_oid_fanout;
 	const unsigned char *chunk_oid_lookup;
 
 	const char **pack_names;
t/helper/test-read-midx.c +3 -1
index 60bca5b668..d1bb7290ae 100644
@@ -25,10 +25,12 @@ static int read_midx_file(const char *object_dir)
 
 	if (m->chunk_pack_names)
 		printf(" pack_names");
+	if (m->chunk_oid_fanout)
+		printf(" oid_fanout");
 	if (m->chunk_oid_lookup)
 		printf(" oid_lookup");
 
-	printf("\n");
+	printf("\nnum_objects: %d\n", m->num_objects);
 
 	printf("packs:\n");
 	for (i = 0; i < m->num_packs; i++)
t/t5319-multi-pack-index.sh +10 -8
index 47e1c7d99e..ad0d447522 100755
@@ -5,9 +5,11 @@ test_description='multi-pack-indexes'
 
 midx_read_expect() {
 	NUM_PACKS=$1
+	NUM_OBJECTS=$2
 	cat >expect <<- EOF
-	header: 4d494458 1 2 $NUM_PACKS
-	chunks: pack_names oid_lookup
+	header: 4d494458 1 3 $NUM_PACKS
+	chunks: pack_names oid_fanout oid_lookup
+	num_objects: $NUM_OBJECTS
 	packs:
 	EOF
 	if [ $NUM_PACKS -ge 1 ]
@@ -22,7 +24,7 @@ midx_read_expect() {
 test_expect_success 'write midx with no packs' '
 	test_when_finished rm pack/multi-pack-index &&
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect 0
+	midx_read_expect 0 0
 '
 
 test_expect_success 'create objects' '
@@ -53,17 +55,17 @@ test_expect_success 'write midx with one v1 pack' '
 	pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
 	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect 1
+	midx_read_expect 1 17
 '
 
 test_expect_success 'write midx with one v2 pack' '
 	git pack-objects --index-version=2,0x40 pack/test <obj-list &&
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect 1
+	midx_read_expect 1 17
 '
 
 test_expect_success 'Add more objects' '
-	for i in `test_seq 6 5`
+	for i in `test_seq 6 10`
 	do
 		iii=$(printf '%03i' $i)
 		test-tool genrandom "bar" 200 > wide_delta_$iii &&
@@ -89,7 +91,7 @@ test_expect_success 'Add more objects' '
 test_expect_success 'write midx with two packs' '
 	git pack-objects --index-version=1 pack/test-2 <obj-list2 &&
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect 2
+	midx_read_expect 2 33
 '
 
 test_expect_success 'Add more packs' '
@@ -120,7 +122,7 @@ test_expect_success 'Add more packs' '
 
 test_expect_success 'write midx with twelve packs' '
 	git multi-pack-index --object-dir=. write &&
-	midx_read_expect 12
+	midx_read_expect 12 73
 '
 
 test_done
Signature · 1 line
2.18.0.24.g1b579a2ee9
#101 lore raw

PATCH v2 15/24 midx: write object offsets

In reply to Derrick Stolee

The final pair of chunks for the multi-pack-index file stores the object offsets. We default to using 32-bit offsets as in the pack-index version 1 format, but if there exists an offset larger than 32-bits, we use a trick similar to the pack-index version 2 format by storing all offsets at least 2^31 in a 64-bit table; we use the 32-bit table to point into that 64-bit table as necessary.

We only store these 64-bit offsets if necessary, so create a test that manipulates a version 2 pack-index to fake a large offset. This allows us to test that the large offset table is created, but the data does not match the actual packfile offsets. The multi-pack-index offset does match the (corrupted) pack-index offset, so a future feature will compare these offsets during a 'verify' step.

---
 Documentation/technical/pack-format.txt |  15 +++-
 midx.c                                  | 100 +++++++++++++++++++++++-
 object-store.h                          |   2 +
 t/helper/test-read-midx.c               |   4 +
 t/t5319-multi-pack-index.sh             |  45 ++++++++---
 5 files changed, 151 insertions(+), 15 deletions(-)
Documentation/technical/pack-format.txt +14 -1
index 3215f7bfcd..cab5bdd2ff 100644
@@ -311,7 +311,20 @@ CHUNK DATA:
 	    The OIDs for all objects in the MIDX are stored in lexicographic
 	    order in this chunk.
 
-	(This section intentionally left incomplete.)
+	Object Offsets (ID: {'O', 'O', 'F', 'F'})
+	    Stores two 4-byte values for every object.
+	    1: The pack-int-id for the pack storing this object.
+	    2: The offset within the pack.
+		If all offsets are less than 2^31, then the large offset chunk
+		will not exist and offsets are stored as in IDX v1.
+		If there is at least one offset value larger than 2^32-1, then
+		the large offset chunk must exist. If the large offset chunk
+		exists and the 31st bit is on, then removing that bit reveals
+		the row in the large offsets containing the 8-byte offset of
+		this object.
+
+	[Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'})
+	    8-byte offsets into large packfiles.
 
 TRAILER:
 
midx.c +96 -4

163 lines not on this page · raw message

object-store.h +2 -0
index 3357e51100..07bcc80e02 100644
@@ -100,6 +100,8 @@ struct multi_pack_index {
 	const unsigned char *chunk_pack_names;
 	const uint32_t *chunk_oid_fanout;
 	const unsigned char *chunk_oid_lookup;
+	const unsigned char *chunk_object_offsets;
+	const unsigned char *chunk_large_offsets;
 
 	const char **pack_names;
 	char object_dir[FLEX_ARRAY];
t/helper/test-read-midx.c +4 -0
index d1bb7290ae..20771d1c1d 100644
@@ -29,6 +29,10 @@ static int read_midx_file(const char *object_dir)
 		printf(" oid_fanout");
 	if (m->chunk_oid_lookup)
 		printf(" oid_lookup");
+	if (m->chunk_object_offsets)
+		printf(" object_offsets");
+	if (m->chunk_large_offsets)
+		printf(" large_offsets");
 
 	printf("\nnum_objects: %d\n", m->num_objects);
 
t/t5319-multi-pack-index.sh +35 -10

87 lines not on this page · raw message

Signature · 1 line
2.18.0.24.g1b579a2ee9
#102 lore raw

PATCH v2 16/24 config: create core.multiPackIndex setting

In reply to Derrick Stolee

The core.multiPackIndex config setting controls the multi-pack- index (MIDX) feature. If false, the setting will disable all reads from the multi-pack-index file.

Add comparison commands in t5319-multi-pack-index.sh to check typical Git behavior remains the same as the config setting is turned on and off. This currently includes 'git rev-list' and 'git log' commands to trigger several object database reads.

---
 Documentation/config.txt    |  4 +++
 cache.h                     |  1 +
 config.c                    |  5 ++++
 environment.c               |  1 +
 t/t5319-multi-pack-index.sh | 55 +++++++++++++++++++++++++++++++------
 5 files changed, 57 insertions(+), 9 deletions(-)
Documentation/config.txt +4 -0
index ab641bf5a9..ab895ebb32 100644
@@ -908,6 +908,10 @@ core.commitGraph::
 	Enable git commit graph feature. Allows reading from the
 	commit-graph file.
 
+core.multiPackIndex::
+	Use the multi-pack-index file to track multiple packfiles using a
+	single index. See linkgit:technical/multi-pack-index[1].
+
 core.sparseCheckout::
 	Enable "sparse checkout" feature. See section "Sparse checkout" in
 	linkgit:git-read-tree[1] for more information.
cache.h +1 -0
index 89a107a7f7..d12aa49710 100644
@@ -814,6 +814,7 @@ extern char *git_replace_ref_base;
 extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_commit_graph;
+extern int core_multi_pack_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
 extern int protect_hfs;
config.c +5 -0
index fbbf0f8e9f..95d8da4243 100644
@@ -1313,6 +1313,11 @@ static int git_default_core_config(const char *var, const char *value)
 		return 0;
 	}
 
+	if (!strcmp(var, "core.multipackindex")) {
+		core_multi_pack_index = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "core.sparsecheckout")) {
 		core_apply_sparse_checkout = git_config_bool(var, value);
 		return 0;
environment.c +1 -0
index 2a6de2330b..b9bc919cdb 100644
@@ -67,6 +67,7 @@ enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
 char *notes_ref_name;
 int grafts_replace_parents = 1;
 int core_commit_graph;
+int core_multi_pack_index;
 int core_apply_sparse_checkout;
 int merge_log_config = -1;
 int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
t/t5319-multi-pack-index.sh +46 -9

92 lines not on this page · raw message

Signature · 1 line
2.18.0.24.g1b579a2ee9
#103 lore raw

PATCH v2 17/24 midx: prepare midxed_git struct

In reply to Derrick Stolee
---
 midx.c         | 22 ++++++++++++++++++++++
 midx.h         |  3 +++
 object-store.h |  9 +++++++++
 packfile.c     |  6 +++++-
 4 files changed, 39 insertions(+), 1 deletion(-)
midx.c +22 -0
index 71ca493107..3dd5027dc6 100644
@@ -176,6 +176,28 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir)
 	return NULL;
 }
 
+int prepare_multi_pack_index_one(struct repository *r, const char *object_dir)
+{
+	struct multi_pack_index *m = r->objects->multi_pack_index;
+	struct multi_pack_index *m_search;
+
+	if (!core_multi_pack_index)
+		return 0;
+
+	for (m_search = m; m_search; m_search = m_search->next)
+		if (!strcmp(object_dir, m_search->object_dir))
+			return 1;
+
+	r->objects->multi_pack_index = load_multi_pack_index(object_dir);
+
+	if (r->objects->multi_pack_index) {
+		r->objects->multi_pack_index->next = m;
+		return 1;
+	}
+
+	return 0;
+}
+
 static size_t write_midx_header(struct hashfile *f,
 				unsigned char num_chunks,
 				uint32_t num_packs)
midx.h +3 -0
index 2d83dd9ec1..731ad6f094 100644
@@ -1,9 +1,12 @@
 #ifndef __MIDX_H__
 #define __MIDX_H__
 
+#include "repository.h"
+
 struct multi_pack_index;
 
 struct multi_pack_index *load_multi_pack_index(const char *object_dir);
+int prepare_multi_pack_index_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
 
object-store.h +9 -0
index 07bcc80e02..7d67ad7aa9 100644
@@ -85,6 +85,8 @@ struct packed_git {
 };
 
 struct multi_pack_index {
+	struct multi_pack_index *next;
+
 	int fd;
 
 	const unsigned char *data;
@@ -126,6 +128,13 @@ struct raw_object_store {
 	 */
 	struct oidmap *replace_map;
 
+	/*
+	 * private data
+	 *
+	 * should only be accessed directly by packfile.c and midx.c
+	 */
+	struct multi_pack_index *multi_pack_index;
+
 	/*
 	 * private data
 	 *
packfile.c +5 -1
index 39d6b66337..ff2df22a0b 100644
@@ -15,6 +15,7 @@
 #include "tree-walk.h"
 #include "tree.h"
 #include "object-store.h"
+#include "midx.h"
 
 char *odb_pack_name(struct strbuf *buf,
 		    const unsigned char *sha1,
@@ -937,10 +938,13 @@ static void prepare_packed_git(struct repository *r)
 
 	if (r->objects->packed_git_initialized)
 		return;
+	prepare_multi_pack_index_one(r, r->objects->objectdir);
 	prepare_packed_git_one(r, r->objects->objectdir, 1);
 	prepare_alt_odb(r);
-	for (alt = r->objects->alt_odb_list; alt; alt = alt->next)
+	for (alt = r->objects->alt_odb_list; alt; alt = alt->next) {
+		prepare_multi_pack_index_one(r, alt->path);
 		prepare_packed_git_one(r, alt->path, 0);
+	}
 	rearrange_packed_git(r);
 	prepare_packed_git_mru(r);
 	r->objects->packed_git_initialized = 1;
Signature · 1 line
2.18.0.24.g1b579a2ee9
#104 lore raw

PATCH v2 18/24 midx: read objects from multi-pack-index

In reply to Derrick Stolee
---
 midx.c         | 93 ++++++++++++++++++++++++++++++++++++++++++++++++--
 midx.h         |  2 ++
 object-store.h |  1 +
 packfile.c     |  8 ++++-
 4 files changed, 101 insertions(+), 3 deletions(-)
midx.c +91 -2
index 3dd5027dc6..14514d6828 100644
@@ -4,7 +4,7 @@
 #include "lockfile.h"
 #include "packfile.h"
 #include "object-store.h"
-#include "packfile.h"
+#include "sha1-lookup.h"
 #include "midx.h"
 
 #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
@@ -150,7 +150,8 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir)
 
 	m->num_objects = ntohl(m->chunk_oid_fanout[255]);
 
-	m->pack_names = xcalloc(m->num_packs, sizeof(const char *));
+	m->packs = xcalloc(m->num_packs, sizeof(*m->packs));
+	ALLOC_ARRAY(m->pack_names, m->num_packs);
 
 	cur_pack_name = (const char *)m->chunk_pack_names;
 	for (i = 0; i < m->num_packs; i++) {
@@ -176,6 +177,94 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir)
 	return NULL;
 }
 
+static int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id)
+{
+	struct strbuf pack_name = STRBUF_INIT;
+
+	if (pack_int_id >= m->num_packs)
+		BUG("bad pack-int-id");
+
+	if (m->packs[pack_int_id])
+		return 0;
+
+	strbuf_addf(&pack_name, "%s/pack/%s", m->object_dir,
+		    m->pack_names[pack_int_id]);
+
+	m->packs[pack_int_id] = add_packed_git(pack_name.buf, pack_name.len, 1);
+	strbuf_release(&pack_name);
+	return !m->packs[pack_int_id];
+}
+
+int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result)
+{
+	return bsearch_hash(oid->hash, m->chunk_oid_fanout, m->chunk_oid_lookup,
+			    MIDX_HASH_LEN, result);
+}
+
+static off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos)
+{
+	const unsigned char *offset_data;
+	uint32_t offset32;
+
+	offset_data = m->chunk_object_offsets + pos * MIDX_CHUNK_OFFSET_WIDTH;
+	offset32 = get_be32(offset_data + sizeof(uint32_t));
+
+	if (m->chunk_large_offsets && offset32 & MIDX_LARGE_OFFSET_NEEDED) {
+		if (sizeof(offset32) < sizeof(uint64_t))
+			die(_("multi-pack-index stores a 64-bit offset, but off_t is too small"));
+
+		offset32 ^= MIDX_LARGE_OFFSET_NEEDED;
+		return get_be64(m->chunk_large_offsets + sizeof(uint64_t) * offset32);
+	}
+
+	return offset32;
+}
+
+static uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos)
+{
+	return get_be32(m->chunk_object_offsets + pos * MIDX_CHUNK_OFFSET_WIDTH);
+}
+
+static int nth_midxed_pack_entry(struct multi_pack_index *m, struct pack_entry *e, uint32_t pos)
+{
+	uint32_t pack_int_id;
+	struct packed_git *p;
+
+	if (pos >= m->num_objects)
+		return 0;
+
+	pack_int_id = nth_midxed_pack_int_id(m, pos);
+
+	if (prepare_midx_pack(m, pack_int_id))
+		die(_("error preparing packfile from multi-pack-index"));
+	p = m->packs[pack_int_id];
+
+	/*
+	* We are about to tell the caller where they can locate the
+	* requested object.  We better make sure the packfile is
+	* still here and can be accessed before supplying that
+	* answer, as it may have been deleted since the MIDX was
+	* loaded!
+	*/
+	if (!is_pack_valid(p))
+		return 0;
+
+	e->offset = nth_midxed_offset(m, pos);
+	e->p = p;
+
+	return 1;
+}
+
+int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m)
+{
+	uint32_t pos;
+
+	if (!bsearch_midx(oid, m, &pos))
+		return 0;
+
+	return nth_midxed_pack_entry(m, e, pos);
+}
+
 int prepare_multi_pack_index_one(struct repository *r, const char *object_dir)
 {
 	struct multi_pack_index *m = r->objects->multi_pack_index;
midx.h +2 -0
index 731ad6f094..6b74a0640f 100644
@@ -6,6 +6,8 @@
 struct multi_pack_index;
 
 struct multi_pack_index *load_multi_pack_index(const char *object_dir);
+int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result);
+int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m);
 int prepare_multi_pack_index_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
object-store.h +1 -0
index 7d67ad7aa9..03cc278758 100644
@@ -106,6 +106,7 @@ struct multi_pack_index {
 	const unsigned char *chunk_large_offsets;
 
 	const char **pack_names;
+	struct packed_git **packs;
 	char object_dir[FLEX_ARRAY];
 };
 
packfile.c +7 -1
index ff2df22a0b..946d0c241f 100644
@@ -1904,11 +1904,17 @@ static int fill_pack_entry(const struct object_id *oid,
 int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e)
 {
 	struct list_head *pos;
+	struct multi_pack_index *m;
 
 	prepare_packed_git(r);
-	if (!r->objects->packed_git)
+	if (!r->objects->packed_git && !r->objects->multi_pack_index)
 		return 0;
 
+	for (m = r->objects->multi_pack_index; m; m = m->next) {
+		if (fill_midx_entry(oid, e, m))
+			return 1;
+	}
+
 	list_for_each(pos, &r->objects->packed_git_mru) {
 		struct packed_git *p = list_entry(pos, struct packed_git, mru);
 		if (fill_pack_entry(oid, e, p)) {
Signature · 1 line
2.18.0.24.g1b579a2ee9
#105 lore raw

PATCH v2 19/24 midx: use midx in abbreviation calculations

In reply to Derrick Stolee
---
 midx.c                      | 11 ++++++
 midx.h                      |  3 ++
 packfile.c                  |  6 ++++
 packfile.h                  |  1 +
 sha1-name.c                 | 70 +++++++++++++++++++++++++++++++++++++
 t/t5319-multi-pack-index.sh |  3 +-
 6 files changed, 93 insertions(+), 1 deletion(-)
midx.c +11 -0
index 14514d6828..c258e3ebdf 100644
@@ -201,6 +201,17 @@ int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32
 			    MIDX_HASH_LEN, result);
 }
 
+struct object_id *nth_midxed_object_oid(struct object_id *oid,
+					struct multi_pack_index *m,
+					uint32_t n)
+{
+	if (n >= m->num_objects)
+		return NULL;
+
+	hashcpy(oid->hash, m->chunk_oid_lookup + m->hash_len * n);
+	return oid;
+}
+
 static off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos)
 {
 	const unsigned char *offset_data;
midx.h +3 -0
index 6b74a0640f..f7c2ec7893 100644
@@ -7,6 +7,9 @@ struct multi_pack_index;
 
 struct multi_pack_index *load_multi_pack_index(const char *object_dir);
 int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result);
+struct object_id *nth_midxed_object_oid(struct object_id *oid,
+					struct multi_pack_index *m,
+					uint32_t n);
 int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m);
 int prepare_multi_pack_index_one(struct repository *r, const char *object_dir);
 
packfile.c +6 -0
index 946d0c241f..20b743da91 100644
@@ -963,6 +963,12 @@ struct packed_git *get_packed_git(struct repository *r)
 	return r->objects->packed_git;
 }
 
+struct multi_pack_index *get_multi_pack_index(struct repository *r)
+{
+	prepare_packed_git(r);
+	return r->objects->multi_pack_index;
+}
+
 struct list_head *get_packed_git_mru(struct repository *r)
 {
 	prepare_packed_git(r);
packfile.h +1 -0
index b0eed44c0b..046280caf3 100644
@@ -45,6 +45,7 @@ extern void install_packed_git(struct repository *r, struct packed_git *pack);
 
 struct packed_git *get_packed_git(struct repository *r);
 struct list_head *get_packed_git_mru(struct repository *r);
+struct multi_pack_index *get_multi_pack_index(struct repository *r);
 
 /*
  * Give a rough count of objects in the repository. This sacrifices accuracy
sha1-name.c +70 -0

104 lines not on this page · raw message

t/t5319-multi-pack-index.sh +2 -1
index f7f55ea181..d8a636c7b7 100755
@@ -93,7 +93,8 @@ compare_results_with_midx() {
 	MSG=$1
 	test_expect_success "check normal git operations: $MSG" '
 		midx_git_two_modes "rev-list --objects --all" &&
-		midx_git_two_modes "log --raw"
+		midx_git_two_modes "log --raw" &&
+		midx_git_two_modes "log --oneline"
 	'
 }
 
Signature · 1 line
2.18.0.24.g1b579a2ee9
#106 lore raw

PATCH v2 20/24 midx: use existing midx when writing new one

In reply to Derrick Stolee
---
 midx.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
 midx.h |   1 +
 2 files changed, 96 insertions(+), 12 deletions(-)
midx.c +95 -12
index c258e3ebdf..02cbfc5bd5 100644
@@ -47,7 +47,6 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir)
 	fd = git_open(midx_name);
 
 	if (fd < 0) {
-		error_errno(_("failed to read %s"), midx_name);
 		FREE_AND_NULL(midx_name);
 		return NULL;
 	}
@@ -276,6 +275,29 @@ int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct mu
 	return nth_midxed_pack_entry(m, e, pos);
 }
 
+int midx_contains_pack(struct multi_pack_index *m, const char *idx_name)
+{
+	uint32_t first = 0, last = m->num_packs;
+
+	while (first < last) {
+		uint32_t mid = first + (last - first) / 2;
+		const char *current;
+		int cmp;
+
+		current = m->pack_names[mid];
+		cmp = strcmp(idx_name, current);
+		if (!cmp)
+			return 1;
+		if (cmp > 0) {
+			first = mid + 1;
+			continue;
+		}
+		last = mid;
+	}
+
+	return 0;
+}
+
 int prepare_multi_pack_index_one(struct repository *r, const char *object_dir)
 {
 	struct multi_pack_index *m = r->objects->multi_pack_index;
@@ -322,6 +344,7 @@ struct pack_list
 	uint32_t alloc_list;
 	uint32_t alloc_names;
 	size_t pack_name_concat_len;
+	struct multi_pack_index *m;
 };
 
 static void add_pack_to_midx(const char *full_path, size_t full_path_len,
@@ -330,6 +353,9 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len,
 	struct pack_list *packs = (struct pack_list *)data;
 
 	if (ends_with(file_name, ".idx")) {
+		if (packs->m && midx_contains_pack(packs->m, file_name))
+			return;
+
 		ALLOC_GROW(packs->list, packs->nr + 1, packs->alloc_list);
 		ALLOC_GROW(packs->names, packs->nr + 1, packs->alloc_names);
 
@@ -413,6 +439,23 @@ static int midx_oid_compare(const void *_a, const void *_b)
 	return a->pack_int_id - b->pack_int_id;
 }
 
+static int nth_midxed_pack_midx_entry(struct multi_pack_index *m,
+				      uint32_t *pack_perm,
+				      struct pack_midx_entry *e,
+				      uint32_t pos)
+{
+	if (pos >= m->num_objects)
+		return 1;
+
+	nth_midxed_object_oid(&e->oid, m, pos);
+	e->pack_int_id = pack_perm[nth_midxed_pack_int_id(m, pos)];
+	e->offset = nth_midxed_offset(m, pos);
+
+	/* consider objects in midx to be from "old" packs */
+	e->pack_mtime = 0;
+	return 0;
+}
+
 static void fill_pack_entry(uint32_t pack_int_id,
 			    struct packed_git *p,
 			    uint32_t cur_object,
@@ -438,7 +481,8 @@ static void fill_pack_entry(uint32_t pack_int_id,
  * Copy only the de-duplicated entries (selected by most-recent modified time
  * of a packfile containing the object).
  */
-static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
+static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m,
+						  struct packed_git **p,
 						  uint32_t *perm,
 						  uint32_t nr_packs,
 						  uint32_t *nr_objects)
@@ -447,8 +491,9 @@ static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
 	uint32_t alloc_fanout, alloc_objects, total_objects = 0;
 	struct pack_midx_entry *entries_by_fanout = NULL;
 	struct pack_midx_entry *deduplicated_entries = NULL;
+	uint32_t start_pack = m ? m->num_packs : 0;
 
-	for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+	for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) {
 		total_objects += p[cur_pack]->num_objects;
 	}
 
@@ -466,7 +511,23 @@ static struct pack_midx_entry *get_sorted_entries(struct packed_git **p,
 	for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) {
 		uint32_t nr_fanout = 0;
 
-		for (cur_pack = 0; cur_pack < nr_packs; cur_pack++) {
+		if (m) {
+			uint32_t start = 0, end;
+
+			if (cur_fanout)
+				start = ntohl(m->chunk_oid_fanout[cur_fanout - 1]);
+			end = ntohl(m->chunk_oid_fanout[cur_fanout]);
+
+			for (cur_object = start; cur_object < end; cur_object++) {
+				ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout);
+				nth_midxed_pack_midx_entry(m, perm,
+							   &entries_by_fanout[nr_fanout],
+							   cur_object);
+				nr_fanout++;
+			}
+		}
+
+		for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) {
 			uint32_t start = 0, end;
 
 			if (cur_fanout)
@@ -661,7 +722,7 @@ int write_midx_file(const char *object_dir)
 	struct hashfile *f;
 	struct lock_file lk;
 	struct pack_list packs;
-	uint32_t *pack_perm;
+	uint32_t *pack_perm = NULL;
 	uint64_t written = 0;
 	uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1];
 	uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1];
@@ -676,24 +737,42 @@ int write_midx_file(const char *object_dir)
 			  midx_name);
 	}
 
+	packs.m = load_multi_pack_index(object_dir);
+
 	packs.nr = 0;
-	packs.alloc_list = 16;
-	packs.alloc_names = 16;
+	packs.alloc_list = packs.m ? packs.m->num_packs : 16;
+	packs.alloc_names = packs.alloc_list;
 	packs.list = NULL;
 	packs.pack_name_concat_len = 0;
 	ALLOC_ARRAY(packs.list, packs.alloc_list);
 	ALLOC_ARRAY(packs.names, packs.alloc_names);
 
+	if (packs.m) {
+		for (i = 0; i < packs.m->num_packs; i++) {
+			ALLOC_GROW(packs.list, packs.nr + 1, packs.alloc_list);
+			ALLOC_GROW(packs.names, packs.nr + 1, packs.alloc_names);
+
+			packs.list[packs.nr] = NULL;
+			packs.names[packs.nr] = xstrdup(packs.m->pack_names[i]);
+			packs.pack_name_concat_len += strlen(packs.names[packs.nr]) + 1;
+			packs.nr++;
+		}
+	}
+
 	for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &packs);
 
+	if (packs.m && packs.nr == packs.m->num_packs)
+		goto cleanup;
+
 	if (packs.pack_name_concat_len % MIDX_CHUNK_ALIGNMENT)
 		packs.pack_name_concat_len += MIDX_CHUNK_ALIGNMENT -
-					(packs.pack_name_concat_len % MIDX_CHUNK_ALIGNMENT);
+					      (packs.pack_name_concat_len % MIDX_CHUNK_ALIGNMENT);
 
-	ALLOC_ARRAY(pack_perm, packs.nr);
+	ALLOC_ARRAY(pack_perm, packs.alloc_list);
 	sort_packs_by_name(packs.names, packs.nr, pack_perm);
 
-	entries = get_sorted_entries(packs.list, pack_perm, packs.nr, &nr_entries);
+	entries = get_sorted_entries(packs.m, packs.list, pack_perm, packs.nr, &nr_entries);
+
 	for (i = 0; i < nr_entries; i++) {
 		if (entries[i].offset > 0x7fffffff)
 			num_large_offsets++;
@@ -796,14 +875,18 @@ int write_midx_file(const char *object_dir)
 	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
 	commit_lock_file(&lk);
 
+cleanup:
 	for (i = 0; i < packs.nr; i++) {
-		close_pack(packs.list[i]);
-		FREE_AND_NULL(packs.list[i]);
+		if (packs.list[i]) {
+			close_pack(packs.list[i]);
+			FREE_AND_NULL(packs.list[i]);
+		}
 		FREE_AND_NULL(packs.names[i]);
 	}
 
 	FREE_AND_NULL(packs.list);
 	FREE_AND_NULL(packs.names);
 	FREE_AND_NULL(entries);
+	FREE_AND_NULL(pack_perm);
 	return 0;
 }
midx.h +1 -0
index f7c2ec7893..5faffb7bc6 100644
@@ -11,6 +11,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,
 					struct multi_pack_index *m,
 					uint32_t n);
 int fill_midx_entry(const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m);
+int midx_contains_pack(struct multi_pack_index *m, const char *idx_name);
 int prepare_multi_pack_index_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
Signature · 1 line
2.18.0.24.g1b579a2ee9
#107 lore raw

PATCH v2 21/24 midx: use midx in approximate_object_count

In reply to Derrick Stolee
---
 packfile.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
packfile.c +4 -1
index 20b743da91..e72e8a685d 100644
@@ -863,11 +863,14 @@ unsigned long approximate_object_count(void)
 {
 	if (!the_repository->objects->approximate_object_count_valid) {
 		unsigned long count;
+		struct multi_pack_index *m;
 		struct packed_git *p;
 
 		prepare_packed_git(the_repository);
 		count = 0;
-		for (p = the_repository->objects->packed_git; p; p = p->next) {
+		for (m = get_multi_pack_index(the_repository); m; m = m->next)
+			count += m->num_objects;
+		for (p = get_packed_git(the_repository); p; p = p->next) {
 			if (open_pack_index(p))
 				continue;
 			count += p->num_objects;
Signature · 1 line
2.18.0.24.g1b579a2ee9
#108 lore raw

PATCH v2 22/24 midx: prevent duplicate packfile loads

In reply to Derrick Stolee

The multi-pack-index, when present, tracks the existence of objects and their offsets within a list of packfiles. This allows us to use the multi-pack-index for object lookups, abbreviations, and object counts.

When the multi-pack-index tracks a packfile, then we do not need to add that packfile to the packed_git linked list or the MRU list.

We still need to load the packfiles that are not tracked by the multi-pack-index.

---
 packfile.c | 9 +++++++++
 1 file changed, 9 insertions(+)
packfile.c +9 -0
index e72e8a685d..f2b8d6f8a7 100644
@@ -796,6 +796,7 @@ struct prepare_pack_data
 	struct repository *r;
 	struct string_list *garbage;
 	int local;
+	struct multi_pack_index *m;
 };
 
 static void prepare_pack(const char *full_name, size_t full_name_len, const char *file_name, void *_data)
@@ -805,6 +806,8 @@ static void prepare_pack(const char *full_name, size_t full_name_len, const char
 	size_t base_len = full_name_len;
 
 	if (strip_suffix_mem(full_name, &base_len, ".idx")) {
+		if (data->m && midx_contains_pack(data->m, file_name))
+			return;
 		/* Don't reopen a pack we already have. */
 		for (p = data->r->objects->packed_git; p; p = p->next) {
 			size_t len;
@@ -841,6 +844,12 @@ static void prepare_packed_git_one(struct repository *r, char *objdir, int local
 	struct prepare_pack_data data;
 	struct string_list garbage = STRING_LIST_INIT_DUP;
 
+	data.m = r->objects->multi_pack_index;
+
+	/* look for the multi-pack-index for this object directory */
+	while (data.m && strcmp(data.m->object_dir, objdir))
+		data.m = data.m->next;
+
 	data.r = r;
 	data.garbage = &garbage;
 	data.local = local;
Signature · 1 line
2.18.0.24.g1b579a2ee9
#109 lore raw

PATCH v2 23/24 packfile: skip loading index if in multi-pack-index

In reply to Derrick Stolee
---
 packfile.c | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)
packfile.c +17 -2
index f2b8d6f8a7..acd02430a8 100644
@@ -469,8 +469,19 @@ static int open_packed_git_1(struct packed_git *p)
 	ssize_t read_result;
 	const unsigned hashsz = the_hash_algo->rawsz;
 
-	if (!p->index_data && open_pack_index(p))
-		return error("packfile %s index unavailable", p->pack_name);
+	if (!p->index_data) {
+		struct multi_pack_index *m;
+		const char *pack_name = strrchr(p->pack_name, '/');
+
+		for (m = the_repository->objects->multi_pack_index;
+		     m; m = m->next) {
+			if (midx_contains_pack(m, pack_name))
+				break;
+		}
+
+		if (!m && open_pack_index(p))
+			return error("packfile %s index unavailable", p->pack_name);
+	}
 
 	if (!pack_max_fds) {
 		unsigned int max_fds = get_max_fd_limit();
@@ -521,6 +532,10 @@ static int open_packed_git_1(struct packed_git *p)
 			" supported (try upgrading GIT to a newer version)",
 			p->pack_name, ntohl(hdr.hdr_version));
 
+	/* Skip index checking if in multi-pack-index */
+	if (!p->index_data)
+		return 0;
+
 	/* Verify the pack matches its index. */
 	if (p->num_objects != ntohl(hdr.hdr_entries))
 		return error("packfile %s claims to have %"PRIu32" objects"
Signature · 1 line
2.18.0.24.g1b579a2ee9
#110 lore raw

PATCH v2 24/24 midx: clear midx on repack

In reply to Derrick Stolee

If a 'git repack' command replaces existing packfiles, then we must clear the existing multi-pack-index before moving the packfiles it references.

---
 builtin/repack.c | 8 ++++++++
 midx.c           | 8 ++++++++
 midx.h           | 1 +
 3 files changed, 17 insertions(+)
builtin/repack.c +8 -0
index 6c636e159e..66a7d8e8ea 100644
@@ -8,6 +8,7 @@
 #include "strbuf.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "midx.h"
 
 static int delta_base_offset = 1;
 static int pack_kept_objects = -1;
@@ -174,6 +175,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 	int no_update_server_info = 0;
 	int quiet = 0;
 	int local = 0;
+	int midx_cleared = 0;
 
 	struct option builtin_repack_options[] = {
 		OPT_BIT('a', NULL, &pack_everything,
@@ -340,6 +342,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 				continue;
 			}
 
+			if (!midx_cleared) {
+				/* if we move a packfile, it will invalidated the midx */
+				clear_midx_file(get_object_directory());
+				midx_cleared = 1;
+			}
+
 			fname_old = mkpathdup("%s/old-%s%s", packdir,
 						item->string, exts[ext].name);
 			if (file_exists(fname_old))
midx.c +8 -0
index 02cbfc5bd5..ef9fb38610 100644
@@ -890,3 +890,11 @@ int write_midx_file(const char *object_dir)
 	FREE_AND_NULL(pack_perm);
 	return 0;
 }
+
+void clear_midx_file(const char *object_dir)
+{
+	char *midx = get_midx_filename(object_dir);
+
+	if (remove_path(midx))
+		die(_("failed to clear multi-pack-index at %s"), midx);
+}
midx.h +1 -0
index 5faffb7bc6..5a42cbed1d 100644
@@ -15,5 +15,6 @@ int midx_contains_pack(struct multi_pack_index *m, const char *idx_name);
 int prepare_multi_pack_index_one(struct repository *r, const char *object_dir);
 
 int write_midx_file(const char *object_dir);
+void clear_midx_file(const char *object_dir);
 
 #endif
Signature · 1 line
2.18.0.24.g1b579a2ee9
#111 lore raw

v3 cover Multi-pack-index (MIDX)

In reply to Derrick Stolee

Thanks for the feedback on v2. I cleaned up the patch to respond to all that feedback. I'll include the version diff in a follow-up email.

You can see the CI builds for Linux, Mac, and Windows linked from the GitHub pull request [1].

Biggest changes in this version:

* Deleted the extra static method.

* Use $(...) instead of ... in test script

* Fixed spaces in tabbing (hopefully I caught them all)

* Cleaned up some inter-patch diff issues

* ... quotes in git-multi-pack-index.txt

* Replace FREE_AND_NULL() with free() when obvious that a variable is going out of scope

* Due to how Windows handles open file handles when replacing a lockfile, be sure to close the midx before writing a new one.

I do still want to revisit the "order by fanout" vs "merge sort" discussion that we had on v1, but this series is very big already. I'd like to come back to that as a follow-up.

Thanks,
-Stolee

[1] https://github.com/gitgitgadget/git/pull/5

Derrick Stolee (24):
multi-pack-index: add design document
multi-pack-index: add format details
multi-pack-index: add builtin
multi-pack-index: add 'write' verb
midx: write header information to lockfile
multi-pack-index: load into memory
multi-pack-index: expand test data
packfile: generalize pack directory list
multi-pack-index: read packfile list
multi-pack-index: write pack names in chunk
midx: read pack names into array
midx: sort and deduplicate objects from packfiles
midx: write object ids in a chunk
midx: write object id fanout chunk
midx: write object offsets
config: create core.multiPackIndex setting
midx: prepare midxed_git struct
midx: read objects from multi-pack-index
midx: use midx in abbreviation calculations
midx: use existing midx when writing new one
midx: use midx in approximate_object_count
midx: prevent duplicate packfile loads
packfile: skip loading index if in multi-pack-index
midx: clear midx on repack

 .gitignore                                   |   3 +-
 Documentation/config.txt                     |   4 +
 Documentation/git-multi-pack-index.txt       |  56 ++
 Documentation/technical/multi-pack-index.txt | 109 +++
 Documentation/technical/pack-format.txt      |  77 ++
 Makefile                                     |   3 +
 builtin.h                                    |   1 +
 builtin/multi-pack-index.c                   |  46 +
 builtin/repack.c                             |   8 +
 cache.h                                      |   1 +
 command-list.txt                             |   1 +
 config.c                                     |   5 +
 environment.c                                |   1 +
 git.c                                        |   1 +
 midx.c                                       | 896 +++++++++++++++++++
 midx.h                                       |  20 +
 object-store.h                               |  33 +
 packfile.c                                   | 169 +++-
 packfile.h                                   |   9 +
 sha1-name.c                                  |  70 ++
 t/helper/test-read-midx.c                    |  54 ++
 t/helper/test-tool.c                         |   1 +
 t/helper/test-tool.h                         |   1 +
 t/t5319-multi-pack-index.sh                  | 191 ++++
 24 files changed, 1717 insertions(+), 43 deletions(-)
 create mode 100644 Documentation/git-multi-pack-index.txt
 create mode 100644 Documentation/technical/multi-pack-index.txt
 create mode 100644 builtin/multi-pack-index.c
 create mode 100644 midx.c
 create mode 100644 midx.h
 create mode 100644 t/helper/test-read-midx.c
 create mode 100755 t/t5319-multi-pack-index.sh

base-commit: 53f9a3e157dbbc901a02ac2c73346d375e24978c

Signature · 1 line
2.18.0.118.gd4f65b8d14
#112 lore raw

PATCH v3 1/24 multi-pack-index: add design document

---
 Documentation/technical/multi-pack-index.txt | 109 +++++++++++++++++++
 1 file changed, 109 insertions(+)
 create mode 100644 Documentation/technical/multi-pack-index.txt
Documentation/technical/multi-pack-index.txt new file +109 -0
new file mode 100644
index 0000000000..d7e57639f7
@@ -0,0 +1,109 @@
+Multi-Pack-Index (MIDX) Design Notes
+====================================
+
+The Git object directory contains a 'pack' directory containing
+packfiles (with suffix ".pack") and pack-indexes (with suffix
+".idx"). The pack-indexes provide a way to lookup objects and
+navigate to their offset within the pack, but these must come
+in pairs with the packfiles. This pairing depends on the file
+names, as the pack-index differs only in suffix with its pack-
+file. While the pack-indexes provide fast lookup per packfile,
+this performance degrades as the number of packfiles increases,
+because abbreviations need to inspect every packfile and we are
+more likely to have a miss on our most-recently-used packfile.
+For some large repositories, repacking into a single packfile
+is not feasible due to storage space or excessive repack times.
+
+The multi-pack-index (MIDX for short) stores a list of objects
+and their offsets into multiple packfiles. It contains:
+
+- A list of packfile names.
+- A sorted list of object IDs.
+- A list of metadata for the ith object ID including:
+  - A value j referring to the jth packfile.
+  - An offset within the jth packfile for the object.
+- If large offsets are required, we use another list of large
+  offsets similar to version 2 pack-indexes.
+
+Thus, we can provide O(log N) lookup time for any number
+of packfiles.
+
+Design Details
+--------------
+
+- The MIDX is stored in a file named 'multi-pack-index' in the
+  .git/objects/pack directory. This could be stored in the pack
+  directory of an alternate. It refers only to packfiles in that
+  same directory.
+
+- The pack.multiIndex config setting must be on to consume MIDX files.
+
+- The file format includes parameters for the object ID hash
+  function, so a future change of hash algorithm does not require
+  a change in format.
+
+- The MIDX keeps only one record per object ID. If an object appears
+  in multiple packfiles, then the MIDX selects the copy in the most-
+  recently modified packfile.
+
+- If there exist packfiles in the pack directory not registered in
+  the MIDX, then those packfiles are loaded into the `packed_git`
+  list and `packed_git_mru` cache.
+
+- The pack-indexes (.idx files) remain in the pack directory so we
+  can delete the MIDX file, set core.midx to false, or downgrade
+  without any loss of information.
+
+- The MIDX file format uses a chunk-based approach (similar to the
+  commit-graph file) that allows optional data to be added.
+
+Future Work
+-----------
+
+- Add a 'verify' subcommand to the 'git midx' builtin to verify the
+  contents of the multi-pack-index file match the offsets listed in
+  the corresponding pack-indexes.
+
+- 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.
+
+- The reachability bitmap is currently paired directly with a single
+  packfile, using the pack-order as the object order to hopefully
+  compress the bitmaps well using run-length encoding. This could be
+  extended to pair a reachability bitmap with a multi-pack-index. 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 a reachability bitmap
+  could point to a multi-pack-index and be updated independently.
+
+- Packfiles can be marked as "special" using empty files that share
+  the initial name but replace ".pack" with ".keep" or ".promisor".
+  We can add an optional chunk of data to the multi-pack-index that
+  records flags of information about the packfiles. This allows new
+  states, such as 'repacked' or 'redeltified', that can help with
+  pack maintenance in a multi-pack environment. It may also be
+  helpful to organize packfiles by object type (commit, tree, blob,
+  etc.) and use this metadata to help that maintenance.
+
+- The partial clone feature records special "promisor" packs that
+  may point to objects that are not stored locally, but available
+  on request to a server. The multi-pack-index does not currently
+  track these promisor packs.
+
+Related Links
+-------------
+[0] https://bugs.chromium.org/p/git/issues/detail?id=6
+    Chromium work item for: Multi-Pack Index (MIDX)
+
+[1] https://public-inbox.org/git/[email protected]/
+    An earlier RFC for the multi-pack-index feature
+
+[2] https://public-inbox.org/git/alpine.DEB.2.20.1803091557510.23109@alexmv-linux/
+    Git Merge 2018 Contributor's summit notes (includes discussion of MIDX)

base-commit: 53f9a3e157dbbc901a02ac2c73346d375e24978c

Signature · 1 line
2.18.0.118.gd4f65b8d14
#113 lore raw

PATCH v3 2/24 multi-pack-index: add format details

In reply to Derrick Stolee

The multi-pack-index feature generalizes the existing pack-index feature by indexing objects across multiple pack-files.

Describe the basic file format, using a 12-byte header followed by a lookup table for a list of "chunks" which will be described later. The file ends with a footer containing a checksum using the hash algorithm.

The header allows later versions to create breaking changes by advancing the version number. We can also change the hash algorithm using a different version value.

We will add the individual chunk format information as we introduce the code that writes that information.

---
 Documentation/technical/pack-format.txt | 49 +++++++++++++++++++++++++
 1 file changed, 49 insertions(+)
Documentation/technical/pack-format.txt +49 -0
index 70a99fd142..e060e693f4 100644
@@ -252,3 +252,52 @@ Pack file entry: <+
     corresponding packfile.
 
     20-byte SHA-1-checksum of all of the above.
+
+== multi-pack-index (MIDX) files have the following format:
+
+The multi-pack-index files refer to multiple pack-files and loose objects.
+
+In order to allow extensions that add extra data to the MIDX, we organize
+the body into "chunks" and provide a lookup table at the beginning of the
+body. The header includes certain length values, such as the number of packs,
+the number of base MIDX files, hash lengths and types.
+
+All 4-byte numbers are in network order.
+
+HEADER:
+
+	4-byte signature:
+	    The signature is: {'M', 'I', 'D', 'X'}
+
+	1-byte version number:
+	    Git only writes or recognizes version 1.
+
+	1-byte Object Id Version
+	    Git only writes or recognizes version 1 (SHA1).
+
+	1-byte number of "chunks"
+
+	1-byte number of base multi-pack-index files:
+	    This value is currently always zero.
+
+	4-byte number of pack files
+
+CHUNK LOOKUP:
+
+	(C + 1) * 12 bytes providing the chunk offsets:
+	    First 4 bytes describe chunk id. Value 0 is a terminating label.
+	    Other 8 bytes provide offset in current file for chunk to start.
+	    (Chunks are provided in file-order, so you can infer the length
+	    using the next chunk position if necessary.)
+
+	The remaining data in the body is described one chunk at a time, and
+	these chunks may be given in any order. Chunks are required unless
+	otherwise specified.
+
+CHUNK DATA:
+
+	(This section intentionally left incomplete.)
+
+TRAILER:
+
+	20-byte SHA1-checksum of the above contents.
Signature · 1 line
2.18.0.118.gd4f65b8d14
#114 lore raw

PATCH v3 3/24 multi-pack-index: add builtin

In reply to Derrick Stolee

This new 'git multi-pack-index' builtin will be the plumbing access for writing, reading, and checking multi-pack-index files. The initial implementation is a no-op.

---
 .gitignore                             |  3 +-
 Documentation/git-multi-pack-index.txt | 36 ++++++++++++++++++++++++
 Makefile                               |  1 +
 builtin.h                              |  1 +
 builtin/multi-pack-index.c             | 38 ++++++++++++++++++++++++++
 command-list.txt                       |  1 +
 git.c                                  |  1 +
 7 files changed, 80 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/git-multi-pack-index.txt
 create mode 100644 builtin/multi-pack-index.c
.gitignore +2 -1
index 388cc4beee..25633bc515 100644
@@ -99,8 +99,9 @@
 /git-mergetool--lib
 /git-mktag
 /git-mktree
-/git-name-rev
+/git-multi-pack-index
 /git-mv
+/git-name-rev
 /git-notes
 /git-p4
 /git-pack-redundant
Documentation/git-multi-pack-index.txt new file +36 -0
new file mode 100644
index 0000000000..a83c0496a6
@@ -0,0 +1,36 @@
+git-multi-pack-index(1)
+======================
+
+NAME
+----
+git-multi-pack-index - Write and verify multi-pack-indexes
+
+
+SYNOPSIS
+--------
+[verse]
+'git multi-pack-index' [--object-dir <dir>]
+
+DESCRIPTION
+-----------
+Write or verify a multi-pack-index (MIDX) file.
+
+OPTIONS
+-------
+
+--object-dir <dir>::
+	Use given directory for the location of Git objects. We check
+	`<dir>/packs/multi-pack-index` for the current MIDX file, and
+	`<dir>/packs` for the pack-files to index.
+
+
+SEE ALSO
+--------
+See link:technical/multi-pack-index.html[The Multi-Pack-Index Design
+Document] and link:technical/pack-format.html[The Multi-Pack-Index
+Format] for more information on the multi-pack-index feature.
+
+
+GIT
+---
+Part of the linkgit:git[1] suite
Makefile +1 -0
index e4b503d259..54610875ec 100644
@@ -1047,6 +1047,7 @@ BUILTIN_OBJS += builtin/merge-recursive.o
 BUILTIN_OBJS += builtin/merge-tree.o
 BUILTIN_OBJS += builtin/mktag.o
 BUILTIN_OBJS += builtin/mktree.o
+BUILTIN_OBJS += builtin/multi-pack-index.o
 BUILTIN_OBJS += builtin/mv.o
 BUILTIN_OBJS += builtin/name-rev.o
 BUILTIN_OBJS += builtin/notes.o
builtin.h +1 -0
index 4e0f64723e..70997d7ace 100644
@@ -191,6 +191,7 @@ extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
 extern int cmd_merge_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_mktag(int argc, const char **argv, const char *prefix);
 extern int cmd_mktree(int argc, const char **argv, const char *prefix);
+extern int cmd_multi_pack_index(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
 extern int cmd_notes(int argc, const char **argv, const char *prefix);
builtin/multi-pack-index.c new file +38 -0
new file mode 100644
index 0000000000..4853291477
@@ -0,0 +1,38 @@
+#include "builtin.h"
+#include "cache.h"
+#include "config.h"
+#include "parse-options.h"
+
+static char const * const builtin_multi_pack_index_usage[] = {
+	N_("git multi-pack-index [--object-dir <dir>]"),
+	NULL
+};
+
+static struct opts_multi_pack_index {
+	const char *object_dir;
+} opts;
+
+int cmd_multi_pack_index(int argc, const char **argv,
+			 const char *prefix)
+{
+	static struct option builtin_multi_pack_index_options[] = {
+		OPT_FILENAME(0, "object-dir", &opts.object_dir,
+		  N_("The object directory containing set of packfile and pack-index pairs")),
+		OPT_END(),
+	};
+
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(builtin_multi_pack_index_usage,
+				   builtin_multi_pack_index_options);
+
+	git_config(git_default_config, NULL);
+
+	argc = parse_options(argc, argv, prefix,
+			     builtin_multi_pack_index_options,
+			     builtin_multi_pack_index_usage, 0);
+
+	if (!opts.object_dir)
+		opts.object_dir = get_object_directory();
+
+	return 0;
+}
command-list.txt +1 -0
index e1c26c1bb7..61071f8fa2 100644
@@ -123,6 +123,7 @@ git-merge-index                         plumbingmanipulators
 git-merge-one-file                      purehelpers
 git-mergetool                           ancillarymanipulators           complete
 git-merge-tree                          ancillaryinterrogators
+git-multi-pack-index                    plumbingmanipulators
 git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain           worktree
git.c +1 -0
index c2f48d53dd..a7509fa5f7 100644
@@ -505,6 +505,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
+	{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP_GENTLY },
 	{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
 	{ "name-rev", cmd_name_rev, RUN_SETUP },
 	{ "notes", cmd_notes, RUN_SETUP },
Signature · 1 line
2.18.0.118.gd4f65b8d14
#115 lore raw

Re: [PATCH v3 03/24] multi-pack-index: add builtin

Documentation/git-multi-pack-index.txt line 12

Quoted 11 lines · Derrick Stolee

This new 'git multi-pack-index' builtin will be the plumbing access for writing, reading, and checking multi-pack-index files. The initial implementation is a no-op.

---

Documentation/git-multi-pack-index.txt +4 -0
+++ b/Documentation/git-multi-pack-index.txt
+SYNOPSIS
+--------
+'git multi-pack-index' [--object-dir <dir>]

In Git documentation, this is more typically written: [--object-dir=<dir>]

Quoted 3 lines

+OPTIONS

+-------
+--object-dir <dir>::

Ditto: --object-dir=<dir>::

builtin/multi-pack-index.c line 2

Quoted 7 lines
Documentation/git-multi-pack-index.txt +3 -0
+       Use given directory for the location of Git objects. We check
+       `<dir>/packs/multi-pack-index` for the current MIDX file, and
+       `<dir>/packs` for the pack-files to index.
builtin/multi-pack-index.c +2 -0
@@ -0,0 +1,38 @@
+static char const * const builtin_multi_pack_index_usage[] = {
+       N_("git multi-pack-index [--object-dir <dir>]"),

Likewise.

builtin/multi-pack-index.c line 20

Quoted 6 lines
builtin/multi-pack-index.c +6 -0
+int cmd_multi_pack_index(int argc, const char **argv,
+                        const char *prefix)
+{
+       static struct option builtin_multi_pack_index_options[] = {
+               OPT_FILENAME(0, "object-dir", &opts.object_dir,
+                 N_("The object directory containing set of packfile and pack-index pairs")),

It's more typical not to capitalize these. Also, keep them short, if possible, so perhaps drop "The".

builtin/multi-pack-index.c line 26

Quoted 6 lines
builtin/multi-pack-index.c +6 -0
+               OPT_END(),
+       };
+
+       if (argc == 2 && !strcmp(argv[1], "-h"))
+               usage_with_options(builtin_multi_pack_index_usage,
+                                  builtin_multi_pack_index_options);

Unless you are planning on adding a short "-h <something>" option later in the series, then you can do away with this conditional altogether since the below parse_options() will give you "-h" as help for free.

Quoted 5 lines
builtin/multi-pack-index.c +5 -0
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix,
+                            builtin_multi_pack_index_options,
+                            builtin_multi_pack_index_usage, 0);
#116 lore raw

PATCH v3 4/24 multi-pack-index: add 'write' verb

In reply to Derrick Stolee

In anticipation of writing multi-pack-indexes, add a 'git multi-pack-index write' subcommand and send the options to a write_midx_file() method. Also create a basic test file that tests the 'write' subcommand.

---
 Documentation/git-multi-pack-index.txt | 22 +++++++++++++++++++++-
 Makefile                               |  1 +
 builtin/multi-pack-index.c             | 10 +++++++++-
 midx.c                                 |  7 +++++++
 midx.h                                 |  6 ++++++
 t/t5319-multi-pack-index.sh            | 10 ++++++++++
 6 files changed, 54 insertions(+), 2 deletions(-)
 create mode 100644 midx.c
 create mode 100644 midx.h
 create mode 100755 t/t5319-multi-pack-index.sh
Documentation/git-multi-pack-index.txt +21 -1
index a83c0496a6..be97c9372e 100644
@@ -9,7 +9,7 @@ git-multi-pack-index - Write and verify multi-pack-indexes
 SYNOPSIS
 --------
 [verse]
-'git multi-pack-index' [--object-dir <dir>]
+'git multi-pack-index' [--object-dir <dir>] <verb>
 
 DESCRIPTION
 -----------
@@ -23,6 +23,26 @@ OPTIONS
 	`<dir>/packs/multi-pack-index` for the current MIDX file, and
 	`<dir>/packs` for the pack-files to index.
 
+write::
+	When given as the verb, write a new MIDX file to
+	`<dir>/packs/multi-pack-index`.
+
+
+EXAMPLES
+--------
+
+* Write a MIDX file for the packfiles in the current .git folder.
++
+-----------------------------------------------
+$ git multi-pack-index write
+-----------------------------------------------
+
+* Write a MIDX file for the packfiles in an alternate.
++
+-----------------------------------------------
+$ git multi-pack-index --object-dir <alt> write
+-----------------------------------------------
+
 
 SEE ALSO
 --------
Makefile +1 -0
index 54610875ec..f5636c711d 100644
@@ -890,6 +890,7 @@ LIB_OBJS += merge.o
 LIB_OBJS += merge-blobs.o
 LIB_OBJS += merge-recursive.o
 LIB_OBJS += mergesort.o
+LIB_OBJS += midx.o
 LIB_OBJS += name-hash.o
 LIB_OBJS += notes.o
 LIB_OBJS += notes-cache.o
builtin/multi-pack-index.c +9 -1
index 4853291477..14b32e1373 100644
@@ -2,9 +2,10 @@
 #include "cache.h"
 #include "config.h"
 #include "parse-options.h"
+#include "midx.h"
 
 static char const * const builtin_multi_pack_index_usage[] = {
-	N_("git multi-pack-index [--object-dir <dir>]"),
+	N_("git multi-pack-index [--object-dir <dir>] [write]"),
 	NULL
 };
 
@@ -34,5 +35,12 @@ int cmd_multi_pack_index(int argc, const char **argv,
 	if (!opts.object_dir)
 		opts.object_dir = get_object_directory();
 
+	if (argc == 0)
+		usage_with_options(builtin_multi_pack_index_usage,
+				   builtin_multi_pack_index_options);
+
+	if (!strcmp(argv[0], "write"))
+		return write_midx_file(opts.object_dir);
+
 	return 0;
 }
midx.c new file +7 -0
new file mode 100644
index 0000000000..32468db1a2
@@ -0,0 +1,7 @@
+#include "cache.h"
+#include "midx.h"
+
+int write_midx_file(const char *object_dir)
+{
+	return 0;
+}
midx.h new file +6 -0
new file mode 100644
index 0000000000..dbdbe9f873
@@ -0,0 +1,6 @@
+#ifndef __MIDX_H__
+#define __MIDX_H__
+
+int write_midx_file(const char *object_dir);
+
+#endif
t/t5319-multi-pack-index.sh new file +10 -0
new file mode 100755
index 0000000000..ec3ddbe79c
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+test_description='multi-pack-indexes'
+. ./test-lib.sh
+
+test_expect_success 'write midx with no packs' '
+	git multi-pack-index --object-dir=. write
+'
+
+test_done
Signature · 1 line
2.18.0.118.gd4f65b8d14
#117 lore raw

Re: [PATCH v3 04/24] multi-pack-index: add 'write' verb

Quoted 3 lines · Derrick Stolee

In anticipation of writing multi-pack-indexes, add a 'git multi-pack-index write' subcommand and send the options to a write_midx_file() method.

Since the 'write' command is a no-op at this point, perhaps say so in the commit message. Something like:

... add a skeleton 'git multi-pack-index write' subcommand, which will be fleshed-out by a later commit.

The bit about sending options to write_midx_file() is superfluous; it's a mere implementation detail which is clearly seen by reading the patch.

Quoted 2 lines

Also create a basic test file that tests
the 'write' subcommand.

Maybe: s/file/script

And, as above, perhaps mention that this is a _skeleton_ test script so as to avoid confusing readers into thinking that something significant is happening at this stage.

Documentation/git-multi-pack-index.txt OPTIONS · line 40

Quoted 4 lines

---

Documentation/git-multi-pack-index.txt +1 -0
+* Write a MIDX file for the packfiles in an alternate.

In an alternate what?

builtin/multi-pack-index.c line 3

Quoted 8 lines

+-----------------------------------------------
+$ git multi-pack-index --object-dir <alt> write
+-----------------------------------------------

builtin/multi-pack-index.c +1 -1
@@ -2,9 +2,10 @@
 static char const * const builtin_multi_pack_index_usage[] = {
-       N_("git multi-pack-index [--object-dir <dir>]"),
+       N_("git multi-pack-index [--object-dir <dir>] [write]"),

Is there going to be some default behavior when no verb is provided? The below implementation seems to suggest that the verb is required, so this probably ought to be typeset as:

git multi-pack-index [--object-dir=<dir>] write

Later, when you add more (mutually exclusive) verbs, change the typesetting to:

    git multi-pack-index [--object-dir=<dir>] (write|...|...)

Alternately, just use:

git multi-pack-index [--object-dir=<dir>] <verb>

builtin/multi-pack-index.c int cmd_multi_pack_index(int argc, const char **argv, · line 42

Quoted 9 lines
builtin/multi-pack-index.c +7 -0
@@ -34,5 +35,12 @@ int cmd_multi_pack_index(int argc, const char **argv,
+       if (argc == 0)
+               usage_with_options(builtin_multi_pack_index_usage,
+                                  builtin_multi_pack_index_options);
+
+       if (!strcmp(argv[0], "write"))
+               return write_midx_file(opts.object_dir);
+
        return 0;

This should be throwing an error when an unrecognized verb is provided.

It also should be throwing an error when 'write' is given too many arguments (which, at this point, appears to be 0).

#118 lore raw

PATCH v3 5/24 midx: write header information to lockfile

In reply to Derrick Stolee

As we begin writing the multi-pack-index format to disk, start with the basics: the 12-byte header and the 20-byte checksum footer. Start with these basics so we can add the rest of the format in small increments.

As we implement the format, we will use a technique to check that our computed offsets within the multi-pack-index file match what we are actually writing. Each method that writes to the hashfile will return the number of bytes written, and we will track that those values match our expectations.

Currently, write_midx_header() returns 12, but is not checked. We will check the return value in a later commit.

---
 midx.c                      | 50 +++++++++++++++++++++++++++++++++++++
 t/t5319-multi-pack-index.sh |  3 ++-
 2 files changed, 52 insertions(+), 1 deletion(-)
midx.c +50 -0
index 32468db1a2..f85f2d334d 100644
@@ -1,7 +1,57 @@
 #include "cache.h"
+#include "csum-file.h"
+#include "lockfile.h"
 #include "midx.h"
 
+#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
+#define MIDX_VERSION 1
+#define MIDX_HASH_VERSION 1
+#define MIDX_HEADER_SIZE 12
+
+static char *get_midx_filename(const char *object_dir)
+{
+	return xstrfmt("%s/pack/multi-pack-index", object_dir);
+}
+
+static size_t write_midx_header(struct hashfile *f,
+				unsigned char num_chunks,
+				uint32_t num_packs)
+{
+	unsigned char byte_values[4];
+
+	hashwrite_be32(f, MIDX_SIGNATURE);
+	byte_values[0] = MIDX_VERSION;
+	byte_values[1] = MIDX_HASH_VERSION;
+	byte_values[2] = num_chunks;
+	byte_values[3] = 0; /* unused */
+	hashwrite(f, byte_values, sizeof(byte_values));
+	hashwrite_be32(f, num_packs);
+
+	return MIDX_HEADER_SIZE;
+}
+
 int write_midx_file(const char *object_dir)
 {
+	unsigned char num_chunks = 0;
+	char *midx_name;
+	struct hashfile *f = NULL;
+	struct lock_file lk;
+
+	midx_name = get_midx_filename(object_dir);
+	if (safe_create_leading_directories(midx_name)) {
+		UNLEAK(midx_name);
+		die_errno(_("unable to create leading directories of %s"),
+			  midx_name);
+	}
+
+	hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR);
+	f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
+	FREE_AND_NULL(midx_name);
+
+	write_midx_header(f, num_chunks, 0);
+
+	finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
+	commit_lock_file(&lk);
+
 	return 0;
 }
t/t5319-multi-pack-index.sh +2 -1
index ec3ddbe79c..8622a7cdce 100755
@@ -4,7 +4,8 @@ test_description='multi-pack-indexes'
 . ./test-lib.sh
 
 test_expect_success 'write midx with no packs' '
-	git multi-pack-index --object-dir=. write
+	git multi-pack-index --object-dir=. write &&
+	test_path_is_file pack/multi-pack-index
 '
 
 test_done
Signature · 1 line
2.18.0.118.gd4f65b8d14
#119 lore raw

PATCH v3 6/24 multi-pack-index: load into memory

In reply to Derrick Stolee

Create a new multi_pack_index struct for loading multi-pack-indexes into memory. Create a test-tool builtin for reading basic information about that multi-pack-index to verify the correct data is written.

---
 Makefile                    |  1 +
 midx.c                      | 82 +++++++++++++++++++++++++++++++++++++
 midx.h                      |  4 ++
 object-store.h              | 16 ++++++++
 t/helper/test-read-midx.c   | 34 +++++++++++++++
 t/helper/test-tool.c        |  1 +
 t/helper/test-tool.h        |  1 +
 t/t5319-multi-pack-index.sh | 12 +++++-
 8 files changed, 150 insertions(+), 1 deletion(-)
 create mode 100644 t/helper/test-read-midx.c
Makefile +1 -0
index f5636c711d..0b801d1b16 100644
@@ -717,6 +717,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o
 TEST_BUILTINS_OBJS += test-path-utils.o
 TEST_BUILTINS_OBJS += test-prio-queue.o
 TEST_BUILTINS_OBJS += test-read-cache.o
+TEST_BUILTINS_OBJS += test-read-midx.o
 TEST_BUILTINS_OBJS += test-ref-store.o
 TEST_BUILTINS_OBJS += test-regex.o
 TEST_BUILTINS_OBJS += test-revision-walking.o
midx.c +82 -0

100 lines not on this page · raw message

midx.h +4 -0
index dbdbe9f873..2d83dd9ec1 100644
@@ -1,6 +1,10 @@
 #ifndef __MIDX_H__
 #define __MIDX_H__
 
+struct multi_pack_index;
+
+struct multi_pack_index *load_multi_pack_index(const char *object_dir);
+
 int write_midx_file(const char *object_dir);
 
 #endif
object-store.h +16 -0
index d683112fd7..4f410841cc 100644
@@ -84,6 +84,22 @@ struct packed_git {
 	char pack_name[FLEX_ARRAY]; /* more */
 };
 
+struct multi_pack_index {
+	int fd;
+
+	const unsigned char *data;
+	size_t data_len;
+
+	uint32_t signature;
+	unsigned char version;
+	unsigned char hash_len;
+	unsigned char num_chunks;
+	uint32_t num_packs;
+	uint32_t num_objects;
+
+	char object_dir[FLEX_ARRAY];
+};
+
 struct raw_object_store {
 	/*
 	 * Path to the repository's object store.
t/helper/test-read-midx.c new file +34 -0
new file mode 100644
index 0000000000..5abf969175
@@ -0,0 +1,34 @@
+/*
+ * test-mktemp.c: code to exercise the creation of temporary files
+ */
+#include "test-tool.h"
+#include "cache.h"
+#include "midx.h"
+#include "repository.h"
+#include "object-store.h"
+
+static int read_midx_file(const char *object_dir)
+{
+	struct multi_pack_index *m = load_multi_pack_index(object_dir);
+
+	if (!m)
+		return 0;
+
+	printf("header: %08x %d %d %d\n",
+	       m->signature,
+	       m->version,
+	       m->num_chunks,
+	       m->num_packs);
+
+	printf("object_dir: %s\n", m->object_dir);
+
+	return 0;
+}
+
+int cmd__read_midx(int argc, const char **argv)
+{
+	if (argc != 2)
+		usage("read-midx <object_dir>");
+
+	return read_midx_file(argv[1]);
+}
t/helper/test-tool.c +1 -0
index 805a45de9c..1c3ab36e6c 100644
@@ -27,6 +27,7 @@ static struct test_cmd cmds[] = {
 	{ "path-utils", cmd__path_utils },
 	{ "prio-queue", cmd__prio_queue },
 	{ "read-cache", cmd__read_cache },
+	{ "read-midx", cmd__read_midx },
 	{ "ref-store", cmd__ref_store },
 	{ "regex", cmd__regex },
 	{ "revision-walking", cmd__revision_walking },
t/helper/test-tool.h +1 -0
index 7116ddfb94..6af8c08a66 100644
@@ -21,6 +21,7 @@ int cmd__online_cpus(int argc, const char **argv);
 int cmd__path_utils(int argc, const char **argv);
 int cmd__prio_queue(int argc, const char **argv);
 int cmd__read_cache(int argc, const char **argv);
+int cmd__read_midx(int argc, const char **argv);
 int cmd__ref_store(int argc, const char **argv);
 int cmd__regex(int argc, const char **argv);
 int cmd__revision_walking(int argc, const char **argv);
t/t5319-multi-pack-index.sh +11 -1
index 8622a7cdce..2ecc369529 100755
@@ -3,9 +3,19 @@
 test_description='multi-pack-indexes'
 . ./test-lib.sh
 
+midx_read_expect () {
+	cat >expect <<-EOF
+	header: 4d494458 1 0 0
+	object_dir: .
+	EOF
+	test-tool read-midx . >actual &&
+	test_cmp expect actual
+}
+
 test_expect_success 'write midx with no packs' '
 	git multi-pack-index --object-dir=. write &&
-	test_path_is_file pack/multi-pack-index
+	test_path_is_file pack/multi-pack-index &&
+	midx_read_expect
 '
 
 test_done
Signature · 1 line
2.18.0.118.gd4f65b8d14
#120 lore raw

Re: [PATCH v3 06/24] multi-pack-index: load into memory

t/helper/test-read-midx.c line 3

Quoted 11 lines · Derrick Stolee

Create a new multi_pack_index struct for loading multi-pack-indexes into memory. Create a test-tool builtin for reading basic information about that multi-pack-index to verify the correct data is written.

---

t/helper/test-read-midx.c +3 -0
@@ -0,0 +1,34 @@
+/*
+ * test-mktemp.c: code to exercise the creation of temporary files
+ */

Meh. Copy/paste botch.

t/helper/test-read-midx.c line 15

Quoted 6 lines
t/helper/test-read-midx.c +6 -0
+static int read_midx_file(const char *object_dir)
+{
+       struct multi_pack_index *m = load_multi_pack_index(object_dir);
+
+       if (!m)
+               return 0;

Should this 'return 0' be a die() or BUG() or something?

Quoted 12 lines
t/helper/test-read-midx.c +10 -0
+       printf("header: %08x %d %d %d\n",
+              m->signature,
+              m->version,
+              m->num_chunks,
+              m->num_packs);
+
+       printf("object_dir: %s\n", m->object_dir);
+
+       return 0;
+}

t/t5319-multi-pack-index.sh line 4

Quoted 2 lines
t/t5319-multi-pack-index.sh +2 -0
@@ -3,9 +3,19 @@
+midx_read_expect () {
+       cat >expect <<-EOF

I guess you're planning on interpolating some variables here in a later patch, which is why you used -EOF rather than -\EOF?

t/t5319-multi-pack-index.sh line 19

Quoted 13 lines
t/t5319-multi-pack-index.sh +9 -1
+       header: 4d494458 1 0 0
+       object_dir: .
+       EOF
+       test-tool read-midx . >actual &&
+       test_cmp expect actual
+}
+
 test_expect_success 'write midx with no packs' '
        git multi-pack-index --object-dir=. write &&
-       test_path_is_file pack/multi-pack-index
+       test_path_is_file pack/multi-pack-index &&
+       midx_read_expect
 '

Kind of a do-nothing change. I wonder if this step would better be delayed until a later patch. (Not necessarily a big deal.)

#121 lore raw

Re: [PATCH v3 06/24] multi-pack-index: load into memory

Quoted 10 lines · Eric Sunshine
Quoted 9 lines · Derrick Stolee
t/t5319-multi-pack-index.sh +9 -0
+midx_read_expect () {
+       cat >expect <<-EOF
+       header: 4d494458 1 0 0
+       object_dir: .
+       EOF
+       test-tool read-midx . >actual &&
+       test_cmp expect actual
+}
+
Quoted 8 lines

t/t5319-multi-pack-index.sh line 18

Quoted 5 lines

test_expect_success 'write midx with no packs' '

        git multi-pack-index --object-dir=. write &&
t/t5319-multi-pack-index.sh +2 -1
-       test_path_is_file pack/multi-pack-index
+       test_path_is_file pack/multi-pack-index &&
+       midx_read_expect

Kind of a do-nothing change. I wonder if this step would better be delayed until a later patch. (Not necessarily a big deal.)

Never mind. I missed that midx_read_expect() is comparing the 'expect' and 'actual' files, so this is not a do-nothing change. The function name may have helped to mislead me (or I was just unobservant or too focused on its creation of the 'expect' file). I wonder if a different name would have helped. midx_check(), midx_verify()? Meh.

#122 lore raw

Re: [PATCH v3 06/24] multi-pack-index: load into memory

In reply to Derrick Stolee
Quoted 13 lines · Derrick Stolee
midx.c +13 -0
+struct multi_pack_index *load_multi_pack_index(const char *object_dir)
+{
+	struct multi_pack_index *m = NULL;
+	int fd;
+	struct stat st;
+	size_t midx_size;
+	void *midx_map = NULL;
+	uint32_t hash_version;
+	char *midx_name = get_midx_filename(object_dir);
+
+	fd = git_open(midx_name);
+
+	if (fd < 0)

midx.c line 45

Quoted 10 lines
midx.c +10 -0
+		goto cleanup_fail;
+	if (fstat(fd, &st)) {
+		error_errno(_("failed to read %s"), midx_name);
+		goto cleanup_fail;
+	}
+
+	midx_size = xsize_t(st.st_size);
+
+	if (midx_size < MIDX_MIN_SIZE) {
+		close(fd);

With the use of "do things normally and jump to cleanup-fail label" pattern, I think you do not want the close() here (unless you also assign -1 to fd yourself, but that is a pointless workaround). Another goto we see above after fstat() failure correctly omits it.

midx.c line 50

Quoted 5 lines
midx.c +5 -0
+		error(_("multi-pack-index file %s is too small"), midx_name);
+		goto cleanup_fail;
+	}
+
+	FREE_AND_NULL(midx_name);

This correctly calls free-and-null not just free (otherwise we'd break the cleanup-fail procedure below), which is good.

midx.c line 55

Quoted 4 lines
midx.c +4 -0
+	midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+
+	m = xcalloc(1, sizeof(*m) + strlen(object_dir) + 1);
+	strcpy(m->object_dir, object_dir);

Hmph, I thought we had FLEX_ALLOC_*() convenience functions exactly for doing things like this more safely.

Quoted 23 lines
midx.c +23 -0
+	m->fd = fd;
+	m->data = midx_map;
+	m->data_len = midx_size;
+
+	m->signature = get_be32(m->data);
+	if (m->signature != MIDX_SIGNATURE) {
+		error(_("multi-pack-index signature 0x%08x does not match signature 0x%08x"),
+		      m->signature, MIDX_SIGNATURE);
+		goto cleanup_fail;
+	}
+
+	m->version = m->data[MIDX_BYTE_FILE_VERSION];
+	if (m->version != MIDX_VERSION) {
+		error(_("multi-pack-index version %d not recognized"),
+		      m->version);
+		goto cleanup_fail;
+	}
+
+	hash_version = m->data[MIDX_BYTE_HASH_VERSION];
+	if (hash_version != MIDX_HASH_VERSION) {
+		error(_("hash version %u does not match"), hash_version);
+		goto cleanup_fail;
+	}

midx.c line 88

Quoted 10 lines
midx.c +9 -0
+	m->hash_len = MIDX_HASH_LEN;
+
+	m->num_chunks = m->data[MIDX_BYTE_NUM_CHUNKS];
+
+	m->num_packs = get_be32(m->data + MIDX_BYTE_NUM_PACKS);
+
+	return m;
 
+cleanup_fail:
+	/* no need to check for NULL when freeing */

I wonder who the target reader of this comment is. We certainly are not in the business of C language newbies.

If this _were_ a commit that looked like this:

	-	if (ptr)
	-		free(ptr);
	+	/* no need to check for NULL when freeing */
	+	free(ptr);

then it might be more understandable, but it still is wrong (such a comment does not help understanding the new code, which is the only thing the people who read the comment sees, without knowing what was there previously---it belongs to the commit log message as a rationale to make that change).

midx.h line 4

Quoted 9 lines
midx.h +1 -0
index dbdbe9f873..2d83dd9ec1 100644
@@ -1,6 +1,10 @@
 #ifndef __MIDX_H__
 #define __MIDX_H__
 
+struct multi_pack_index;

I actually was quite surprised that this struct is defined in object-store.h and not here. It feels the other way around.

The raw_object_store needs to know that such an in-core structure might exist as an optional feature in an object store, but as an optional feature, I suspect that it has a pointer to an instance of multi_pack_index, instead of embedding the struct itself in it, so I would have expected to see an "I am only telling you that there is a struct with this name, but I am leaving it opaque as you do not have any business looking inside the struct yourself. You only need to be aware of the type's existence and a pointer to it so that you can call helpers that know what's inside and that should be sufficient for your needs." decl like this in object-store.h and instead an actual implementation in here.

#123 lore raw

Re: [PATCH v3 06/24] multi-pack-index: load into memory

Quoted 13 lines · Junio C Hamano

midx.h line 4

Quoted 9 lines · Derrick Stolee
midx.h +1 -0
index dbdbe9f873..2d83dd9ec1 100644
@@ -1,6 +1,10 @@
  #ifndef __MIDX_H__
  #define __MIDX_H__
 
+struct multi_pack_index;

I actually was quite surprised that this struct is defined in object-store.h and not here. It feels the other way around.

Quoted 11 lines

The raw_object_store needs to know that such an in-core structure might exist as an optional feature in an object store, but as an optional feature, I suspect that it has a pointer to an instance of multi_pack_index, instead of embedding the struct itself in it, so I would have expected to see an "I am only telling you that there is a struct with this name, but I am leaving it opaque as you do not have any business looking inside the struct yourself. You only need to be aware of the type's existence and a pointer to it so that you can call helpers that know what's inside and that should be sufficient for your needs." decl like this in object-store.h and instead an actual implementation in here.

I thought it natural to include the struct definition next to the definition for 'struct packed_git', but I like your separation of concerns. Perhaps we could move the packed_git definition to packfile.h as well (separately). Of course, that sounds like an unnecessary churn.

Thanks,

-Stolee

#124 lore raw

PATCH v3 7/24 multi-pack-index: expand test data

In reply to Derrick Stolee

As we build the multi-pack-index file format, we want to test the format on real repoasitories. Add tests to t5319-multi-pack-index.sh that create repository data including multiple packfiles with both version 1 and version 2 formats.

The current 'git multi-pack-index write' command will always write the same file with no "real" data. This will be expanded in future commits, along with the test expectations.

---
 t/t5319-multi-pack-index.sh | 99 +++++++++++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)
t/t5319-multi-pack-index.sh +99 -0
index 2ecc369529..1be7be02b8 100755
@@ -13,9 +13,108 @@ midx_read_expect () {
 }
 
 test_expect_success 'write midx with no packs' '
+	test_when_finished rm -f pack/multi-pack-index &&
 	git multi-pack-index --object-dir=. write &&
 	test_path_is_file pack/multi-pack-index &&
 	midx_read_expect
 '
 
+test_expect_success 'create objects' '
+	for i in $(test_seq 1 5)
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 >wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >>wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 >deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >>deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >>deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		i=$(expr $i + 1) || return 1
+	done &&
+	{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+	git update-index --add file_101 &&
+	tree=$(git write-tree) &&
+	commit=$(git commit-tree $tree </dev/null) && {
+	echo $tree &&
+	git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+	} >obj-list &&
+	git update-ref HEAD $commit
+'
+
+test_expect_success 'write midx with one v1 pack' '
+	pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
+	test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
+	git multi-pack-index --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'write midx with one v2 pack' '
+	git pack-objects --index-version=2,0x40 pack/test <obj-list &&
+	git multi-pack-index --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'Add more objects' '
+	for i in $(test_seq 6 10)
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 >wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >>wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 >deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >>deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >>deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		i=$(expr $i + 1) || return 1
+	done &&
+	{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+	git update-index --add file_101 &&
+	tree=$(git write-tree) &&
+	commit=$(git commit-tree $tree -p HEAD</dev/null) && {
+	echo $tree &&
+	git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+	} >obj-list2 &&
+	git update-ref HEAD $commit
+'
+
+test_expect_success 'write midx with two packs' '
+	git pack-objects --index-version=1 pack/test-2 <obj-list2 &&
+	git multi-pack-index --object-dir=. write &&
+	midx_read_expect
+'
+
+test_expect_success 'Add more packs' '
+	for j in $(test_seq 1 10)
+	do
+		iii=$(printf '%03i' $i)
+		test-tool genrandom "bar" 200 >wide_delta_$iii &&
+		test-tool genrandom "baz $iii" 50 >>wide_delta_$iii &&
+		test-tool genrandom "foo"$i 100 >deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 1) 100 >>deep_delta_$iii &&
+		test-tool genrandom "foo"$(expr $i + 2) 100 >>deep_delta_$iii &&
+		echo $iii >file_$iii &&
+		test-tool genrandom "$iii" 8192 >>file_$iii &&
+		git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+		{ echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+		git update-index --add file_101 &&
+		tree=$(git write-tree) &&
+		commit=$(git commit-tree $tree -p HEAD</dev/null) && {
+		echo $tree &&
+		git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)	.*/\\1/"
+		} >obj-list &&
+		git update-ref HEAD $commit &&
+		git pack-objects --index-version=2 test-pack <obj-list &&
+		i=$(expr $i + 1) || return 1 &&
+		j=$(expr $j + 1) || return 1
+	done
+'
+
+test_expect_success 'write midx with twelve packs' '
+	git multi-pack-index --object-dir=. write &&
+	midx_read_expect
+'
+
 test_done
Signature · 1 line
2.18.0.118.gd4f65b8d14
#125 lore raw

Re: [PATCH v3 07/24] multi-pack-index: expand test data

Quoted 1 line · Derrick Stolee

multi-pack-index: expand test data

Since this patch is touching only t5319, a more typical title would be:

t5319: expand test data

Quoted 2 lines

As we build the multi-pack-index file format, we want to test the format on real repoasitories. Add tests to t5319-multi-pack-index.sh that

s/repoasitories/repositories/

And, since the title now mentions t5319, this can become simply:

Add tests that...

t/t5319-multi-pack-index.sh midx_read_expect () { · line 14

Quoted 13 lines

create repository data including multiple packfiles with both version 1 and version 2 formats.

The current 'git multi-pack-index write' command will always write the same file with no "real" data. This will be expanded in future commits, along with the test expectations.

---

t/t5319-multi-pack-index.sh +1 -0
@@ -13,9 +13,108 @@ midx_read_expect () {
 test_expect_success 'write midx with no packs' '
+       test_when_finished rm -f pack/multi-pack-index &&

Should this test_when_finished() have been added in an earlier patch? It seems out of place here.

t/t5319-multi-pack-index.sh midx_read_expect () { · line 27

Quoted 11 lines
t/t5319-multi-pack-index.sh +6 -0
        git multi-pack-index --object-dir=. write &&
        test_path_is_file pack/multi-pack-index &&
        midx_read_expect
 '
 
+test_expect_success 'create objects' '
+       for i in $(test_seq 1 5)
+       do
+               iii=$(printf '%03i' $i)
+               test-tool genrandom "bar" 200 >wide_delta_$iii &&
+               test-tool genrandom "baz $iii" 50 >>wide_delta_$iii &&

Alternately:

    {
        test-tool genrandom "bar" 200 &&
         test-tool genrandom "baz $iii" 50
    } >wide_delta_$iii &&

which makes it easier to see at a glance that both commands are populating the same file. Same comment for the other files. (Not worth a re-roll.)

t/t5319-multi-pack-index.sh midx_read_expect () { · line 30

Quoted 3 lines
t/t5319-multi-pack-index.sh +3 -0
+               test-tool genrandom "foo"$i 100 >deep_delta_$iii &&
+               test-tool genrandom "foo"$(expr $i + 1) 100 >>deep_delta_$iii &&
+               test-tool genrandom "foo"$(expr $i + 2) 100 >>deep_delta_$iii &&

Or, just use POSIX arithmetic expansion:

    $(( $i + 1 ))

t/t5319-multi-pack-index.sh midx_read_expect () { · line 34

Quoted 4 lines
t/t5319-multi-pack-index.sh +4 -0
+               echo $iii >file_$iii &&
+               test-tool genrandom "$iii" 8192 >>file_$iii &&
+               git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+               i=$(expr $i + 1) || return 1

Ditto, POSIX arithmetic expansion:

    i=$(( $i + 1 ))

(Not worth a re-roll.)

t/t5319-multi-pack-index.sh midx_read_expect () { · line 42

Quoted 8 lines
t/t5319-multi-pack-index.sh +8 -0
+       done &&
+       { echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+       git update-index --add file_101 &&
+       tree=$(git write-tree) &&
+       commit=$(git commit-tree $tree </dev/null) && {
+       echo $tree &&
+       git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)        .*/\\1/"
+       } >obj-list &&

Perhaps indent the content of the {...} block?

t/t5319-multi-pack-index.sh midx_read_expect () { · line 51

Quoted 9 lines
t/t5319-multi-pack-index.sh +9 -0
+       git update-ref HEAD $commit
+'
+
+test_expect_success 'write midx with one v1 pack' '
+       pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
+       test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
+       git multi-pack-index --object-dir=. write &&
+       midx_read_expect
+'

It's odd to see all these tests ending by creating an 'expect' file but not actually doing anything with that file.

t/t5319-multi-pack-index.sh midx_read_expect () { · line 59

Quoted 1 line
t/t5319-multi-pack-index.sh +1 -0
+test_expect_success 'Add more objects' '

s/Add/add/

Quoted 12 lines
t/t5319-multi-pack-index.sh +12 -0
+       for i in $(test_seq 6 10)
+       do
+               iii=$(printf '%03i' $i)
+               test-tool genrandom "bar" 200 >wide_delta_$iii &&
+               test-tool genrandom "baz $iii" 50 >>wide_delta_$iii &&
+               test-tool genrandom "foo"$i 100 >deep_delta_$iii &&
+               test-tool genrandom "foo"$(expr $i + 1) 100 >>deep_delta_$iii &&
+               test-tool genrandom "foo"$(expr $i + 2) 100 >>deep_delta_$iii &&
+               echo $iii >file_$iii &&
+               test-tool genrandom "$iii" 8192 >>file_$iii &&
+               git update-index --add file_$iii deep_delta_$iii wide_delta_$iii &&
+               i=$(expr $i + 1) || return 1

t/t5319-multi-pack-index.sh midx_read_expect () { · line 81

Quoted 10 lines
t/t5319-multi-pack-index.sh +10 -0
+       done &&
+       { echo 101 && test-tool genrandom 100 8192; } >file_101 &&
+       git update-index --add file_101 &&
+       tree=$(git write-tree) &&
+       commit=$(git commit-tree $tree -p HEAD</dev/null) && {
+       echo $tree &&
+       git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)        .*/\\1/"
+       } >obj-list2 &&
+       git update-ref HEAD $commit
+'

There seems to be a fair bit of duplication in these tests which create objects. Is it possible to factor out some of this code into a shell function?

t/t5319-multi-pack-index.sh midx_read_expect () { · line 89

Quoted 7 lines
t/t5319-multi-pack-index.sh +7 -0
+test_expect_success 'write midx with two packs' '
+       git pack-objects --index-version=1 pack/test-2 <obj-list2 &&
+       git multi-pack-index --object-dir=. write &&
+       midx_read_expect
+'
+
+test_expect_success 'Add more packs' '

s/Add/add/

Quoted 5 lines
t/t5319-multi-pack-index.sh +5 -0
+       for j in $(test_seq 1 10)
+       do
+               [...]
+       done
+'
#126 lore raw

Re: [PATCH v3 07/24] multi-pack-index: expand test data

Quoted 10 lines · Eric Sunshine

t/t5319-multi-pack-index.sh midx_read_expect () { · line 51

Quoted 6 lines · Derrick Stolee
t/t5319-multi-pack-index.sh +6 -0
+test_expect_success 'write midx with one v1 pack' '
+       pack=$(git pack-objects --index-version=1 pack/test <obj-list) &&
+       test_when_finished rm pack/test-$pack.pack pack/test-$pack.idx pack/multi-pack-index &&
+       git multi-pack-index --object-dir=. write &&
+       midx_read_expect
+'

It's odd to see all these tests ending by creating an 'expect' file but not actually doing anything with that file.

Ignore this comment. As mentioned in my follow-up to 6/24, I missed the fact that midx_read_expect() is doing more than just creating the 'expect' file.