From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:57:34
This is based on ds/cache-tree-basics.
Here are a few more cleanups that are vaguely related to the index. I
discovered these while preparing my sparse-index RFC that I intend to send
early next week.
The biggest patch is the final one, which creates a test script for
comparing sparse-checkouts to full checkouts. There are some commands that
do not behave similarly. This script will be the backbone of my testing
strategy for the sparse-index by adding a new mode to compare
sparse-checkouts with the two index types (full and sparse).
Thanks, -Stolee
Derrick Stolee (9):
cache-tree: clean up cache_tree_update()
cache-tree: extract subtree_pos()
fsmonitor: de-duplicate BUG()s around dirty bits
repository: add repo reference to index_state
name-hash: use trace2 regions for init
sparse-checkout: load sparse-checkout patterns
sparse-checkout: hold pattern list in index
test-lib: test_region looks for trace2 regions
t1092: test interesting sparse-checkout scenarios
builtin/sparse-checkout.c | 22 +-
cache-tree.c | 20 +-
cache-tree.h | 2 +
cache.h | 3 +
dir.c | 17 ++
dir.h | 2 +
fsmonitor.c | 27 +-
name-hash.c | 3 +
repository.c | 4 +
t/t0500-progress-display.sh | 3 +-
t/t1092-sparse-checkout-compatibility.sh | 323 +++++++++++++++++++++++
t/test-lib-functions.sh | 40 +++
unpack-trees.c | 6 +-
13 files changed, 431 insertions(+), 41 deletions(-)
create mode 100755 t/t1092-sparse-checkout-compatibility.sh
base-commit: a4b6d202caad83c6dc29abe9b17e53a1b3fb54a0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-839%2Fderrickstolee%2Fmore-index-cleanups-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-839/derrickstolee/more-index-cleanups-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/839
--
gitgitgadget
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:55:12
From: Derrick Stolee <redacted>
Make the method safer by allocating a cache_tree member for the given
index_state if it is not already present.
Also drop local variables that are used exactly once and can be found
directly from the 'istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:55:59
From: Derrick Stolee <redacted>
The index has an fsmonitor_dirty bitmap that records which index entries
are "dirty" based on the response from the FSMonitor. If this bitmap
ever grows larger than the index, then there was an error in how it was
constructed, and it was probably a developer's bug.
There are several BUG() statements that are very similar, so replace
these uses with a simpler assert_index_minimum(). Since there is one
caller that uses a custom 'pos' value instead of the bit_size member, we
cannot simplify it too much. However, the error string is identical in
each, so this simplifies things.
The end result is that the code is simpler to read while also preserving
these assertions for developers in the FSMonitor space.
Signed-off-by: Derrick Stolee <redacted>
---
fsmonitor.c | 27 +++++++++++++--------------
1 file changed, 13 insertions(+), 14 deletions(-)
@@ -13,14 +13,19 @@structtrace_keytrace_fsmonitor=TRACE_KEY_INIT(FSMONITOR);+staticvoidassert_index_minimum(structindex_state*istate,size_tpos)+{+if(pos>istate->cache_nr)+BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",+(uintmax_t)pos,istate->cache_nr);+}+staticvoidfsmonitor_ewah_callback(size_tpos,void*is){structindex_state*istate=(structindex_state*)is;structcache_entry*ce;-if(pos>=istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" >= %u)",-(uintmax_t)pos,istate->cache_nr);+assert_index_minimum(istate,pos);ce=istate->cache[pos];ce->ce_flags&=~CE_FSMONITOR_VALID;
@@ -82,10 +87,8 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data,}istate->fsmonitor_dirty=fsmonitor_dirty;-if(!istate->split_index&&-istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+if(!istate->split_index)+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);trace_printf_key(&trace_fsmonitor,"read fsmonitor extension successful");return0;
@@ -110,10 +113,8 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)uint32_tewah_size=0;intfixup=0;-if(!istate->split_index&&-istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+if(!istate->split_index)+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);put_be32(&hdr_version,INDEX_EXTENSION_VERSION2);strbuf_add(sb,&hdr_version,sizeof(uint32_t));
@@ -335,9 +336,7 @@ void tweak_fsmonitor(struct index_state *istate)}/* Mark all previously saved entries as dirty */-if(istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);ewah_each_bit(istate->fsmonitor_dirty,fsmonitor_ewah_callback,istate);refresh_fsmonitor(istate);
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:56:03
From: Derrick Stolee <redacted>
It will be helpful to add behavior to index opertations that might
trigger an object lookup. Since each index belongs to a specific
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
This will prevent future changes from needing to pass an additional
'struct repository *repo' parameter and instead rely only on the 'struct
index_state *istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache.h | 1 +
repository.c | 4 ++++
2 files changed, 5 insertions(+)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:56:05
From: Derrick Stolee <redacted>
The lazy_init_name_hash() populates a hashset with all filenames and
another with all directories represented in the index. This is run only
if we need to use the hashsets to check for existence or case-folding
renames.
Place trace2 regions where there is already a performance trace.
Signed-off-by: Derrick Stolee <redacted>
---
name-hash.c | 3 +++
1 file changed, 3 insertions(+)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:56:49
From: Derrick Stolee <redacted>
A future feature will want to load the sparse-checkout patterns into a
pattern_list, but the current mechanism to do so is a bit complicated.
This is made difficult due to needing to find the sparse-checkout file
in different ways throughout the codebase.
The logic implemented in the new get_sparse_checkout_patterns() was
duplicated in populate_from_existing_patterns() in unpack-trees.c. Use
the new method instead, keeping the logic around handling the struct
unpack_trees_options.
The callers to get_sparse_checkout_filename() in
builtin/sparse-checkout.c manipulate the sparse-checkout file directly,
so it is not appropriate to replace logic in that file with
get_sparse_checkout_patterns().
Signed-off-by: Derrick Stolee <redacted>
---
builtin/sparse-checkout.c | 5 -----
dir.c | 17 +++++++++++++++++
dir.h | 2 ++
unpack-trees.c | 6 +-----
4 files changed, 20 insertions(+), 10 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:57:14
From: Derrick Stolee <redacted>
As we modify the sparse-checkout definition, we perform index operations
on a pattern_list that only exists in-memory. This allows easy backing
out in case the index update fails.
However, if the index write itself cares about the sparse-checkout
pattern set, we need access to that in-memory copy. Place a pointer to
a 'struct pattern_list' in the index so we can access this on-demand.
Signed-off-by: Derrick Stolee <redacted>
---
builtin/sparse-checkout.c | 17 ++++++++++-------
cache.h | 2 ++
2 files changed, 12 insertions(+), 7 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:57:14
From: Derrick Stolee <redacted>
Most test cases can verify Git's behavior using input/output
expectations or changes to the .git directory. However, sometimes we
want to check that Git did or did not run a certain section of code.
This is particularly important for performance-only features that we
want to ensure have been enabled in certain cases.
Add a new 'test_region' function that checks if a trace2 region was
entered and left in a given trace2 event log.
There is one existing test (t0500-progress-display.sh) that performs
this check already, so use the helper function instead. More uses will
be added in a later change.
t6423-merge-rename-directories.sh also greps for region_enter lines, but
it verifies the number of such lines, which is not the same as an
existence check.
Signed-off-by: Derrick Stolee <redacted>
---
t/t0500-progress-display.sh | 3 +--
t/test-lib-functions.sh | 40 +++++++++++++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 2 deletions(-)
@@ -1655,3 +1655,43 @@ test_subcommand () {grep"\[$expr\]"fi}++# Check that the given command was invoked as part of the+# trace2-format trace on stdin.+#+# test_region [!] <category> <label> git <command> <args>...+#+# For example, to look for trace2_region_enter("index", "do_read_index", repo)+# in an invocation of "git checkout HEAD~1", run+#+# GIT_TRACE2_EVENT="$(pwd)/trace.txt" GIT_TRACE2_EVENT_NESTING=10 \+# git checkout HEAD~1 &&+# test_region index do_read_index <trace.txt+#+# If the first parameter passed is !, this instead checks that+# the given region was not entered.+#+test_region(){+localexpect_exit=0+iftest"$1"="!"+then+expect_exit=1+shift+fi++grep-e"region_enter"-e"\"category\":\"$1\",\"label\":\"$2\"""$3"+exitcode=$?++iftest$exitcode!=$expect_exit+then+return1+fi++grep-e"region_leave"-e"\"category\":\"$1\",\"label\":\"$2\"""$3"+exitcode=$?++iftest$exitcode!=$expect_exit+then+return1+fi+}
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 16:57:36
From: Derrick Stolee <redacted>
This method will be helpful to use outside of cache-tree.c in a later
feature. The implementation is subtle due to subtree_name_cmp() sorting
by length and then lexicographically.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 6 +++---
cache-tree.h | 2 ++
2 files changed, 5 insertions(+), 3 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-20 17:01:12
From: Derrick Stolee <redacted>
These also document some behaviors that differ from a full checkout, and
possibly in a way that is not intended.
Signed-off-by: Derrick Stolee <redacted>
---
t/t1092-sparse-checkout-compatibility.sh | 323 +++++++++++++++++++++++
1 file changed, 323 insertions(+)
create mode 100755 t/t1092-sparse-checkout-compatibility.sh
@@ -0,0 +1,323 @@+#!/bin/sh++test_description='compare full workdir to sparse workdir'++../test-lib.sh++test_expect_success'setup''+gitinitinitial-repo&&+(+cdinitial-repo&&+echoa>a&&+echo"after deep">e&&+echo"after folder1">g&&+mkdirfolder1folder2deepx&&+mkdirdeep/deeper1deep/deeper2&&+mkdirdeep/deeper1/deepest&&+echo"after deeper1">deep/e&&+echo"after deepest">deep/deeper1/e&&+cpafolder1&&+cpafolder2&&+cpadeep&&+cpadeep/deeper1&&+cpadeep/deeper2&&+cpadeep/deeper1/deepest&&+gitadd.&&+gitcommit-m"initial commit"&&+gitcheckout-bbase&&+fordirinfolder1folder2deep+do+gitcheckout-bupdate-$dir&&+echo"updated $dir">$dir/a&&+gitcommit-a-m"update $dir"||return1+done&&++gitcheckout-brename-basebase&&+echo>folder1/larger-content<<-\EOF&&+matching+lines+help+inexact+renames+EOF+cpfolder1/larger-contentfolder2/&&+cpfolder1/larger-contentdeep/deeper1/&&+gitadd.&&+gitcommit-m"add interesting rename content"&&++gitcheckout-brename-out-to-outrename-base&&+mvfolder1/afolder2/b&&+mvfolder1/larger-contentfolder2/edited-content&&+echo>>folder2/edited-content&&+gitadd.&&+gitcommit-m"rename folder1/... to folder2/..."&&++gitcheckout-brename-out-to-inrename-base&&+mvfolder1/adeep/deeper1/b&&+mvfolder1/larger-contentdeep/deeper1/edited-content&&+echo>>deep/deeper1/edited-content&&+gitadd.&&+gitcommit-m"rename folder1/... to deep/deeper1/..."&&++gitcheckout-brename-in-to-outrename-base&&+mvdeep/deeper1/afolder1/b&&+mvdeep/deeper1/larger-contentfolder1/edited-content&&+echo>>folder1/edited-content&&+gitadd.&&+gitcommit-m"rename deep/deeper1/... to folder1/..."&&++gitcheckout-bdeepestbase&&+echo"updated deepest">deep/deeper1/deepest/a&&+gitcommit-a-m"update deepest"&&++gitcheckout-fbase&&+gitreset--hard+)+'++init_repos(){+rm-rffull-checkoutsparse-checkoutsparse-index&&++# create repos in initial state+cp-rinitial-repofull-checkout&&+git-Cfull-checkoutreset--hard&&++cp-rinitial-reposparse-checkout&&+git-Csparse-checkoutreset--hard&&+git-Csparse-checkoutsparse-checkoutinit--cone&&++# initialize sparse-checkout definitions+git-Csparse-checkoutsparse-checkoutsetdeep+}++run_on_sparse(){+(+cdsparse-checkout&&+$*>../sparse-checkout-out2>../sparse-checkout-err+)+}++run_on_all(){+(+cdfull-checkout&&+$*>../full-checkout-out2>../full-checkout-err+)&&+run_on_sparse$*+}++test_all_match(){+run_on_all$*&&+test_cmpfull-checkout-outsparse-checkout-out&&+test_cmpfull-checkout-errsparse-checkout-err+}++test_expect_success'status with options''+init_repos&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno&&+run_on_all"touch README.md"&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno&&+test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno+'++test_expect_success'add, commit, checkout''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>README.md+EOF+run_on_all"../edit-contents"&&++test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Add README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents"&&++test_all_matchgitadd-A&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Extend README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-+'++test_expect_success'add, commit, checkout''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>$1+EOF+run_on_all"../edit-contents README.md"&&++test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Add README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents README.md"&&++test_all_matchgitadd-A&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Extend README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents deep/newfile"&&++test_all_matchgitstatus--porcelain=v2-uno&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitadd.&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"add deep/newfile"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-+'++test_expect_success'checkout and reset --hard''+init_repos&&++test_all_matchgitcheckoutupdate-folder1&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitcheckoutupdate-deep&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitcheckout-breset-test&&+test_all_matchgitreset--harddeepest&&+test_all_matchgitreset--hardupdate-folder1&&+test_all_matchgitreset--hardupdate-folder2+'++test_expect_success'diff --staged''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>README.md+EOF+run_on_all"../edit-contents"&&++test_all_matchgitdiff&&+test_all_matchgitdiff--staged&&+test_all_matchgitaddREADME.md&&+test_all_matchgitdiff&&+test_all_matchgitdiff--staged+'++test_expect_success'diff with renames''+init_repos&&++forbranchinrename-out-to-outrename-out-to-inrename-in-to-out+do+test_all_matchgitcheckoutrename-base&&+test_all_matchgitcheckout$branch--.&&+test_all_matchgitdiff--staged&&+test_all_matchgitdiff--staged--find-renames||return1+done+'++test_expect_success'log with pathspec outside sparse definition''+init_repos&&++test_all_matchgitlog--a&&+test_all_matchgitlog--folder1/a&&+test_all_matchgitlog--folder2/a&&+test_all_matchgitlog--deep/a&&+test_all_matchgitlog--deep/deeper1/a&&+test_all_matchgitlog--deep/deeper1/deepest/a&&++test_all_matchgitcheckoutupdate-folder1&&+test_all_matchgitlog--folder1/a+'++test_expect_success'blame with pathspec inside sparse definition''+init_repos&&++test_all_matchgitblamea&&+test_all_matchgitblamedeep/a&&+test_all_matchgitblamedeep/deeper1/a&&+test_all_matchgitblamedeep/deeper1/deepest/a+'++# TODO: blame currently does not support blaming files outside of the+# sparse definition. It complains that the file doesn't exist locally.+test_expect_failure'blame with pathspec outside sparse definition''+init_repos&&++test_all_matchgitblamefolder1/a&&+test_all_matchgitblamefolder2/a&&+test_all_matchgitblamedeep/deeper2/a&&+test_all_matchgitblamedeep/deeper2/deepest/a+'++# TODO: reset currently does not behave as expected when in a+# sparse-checkout.+test_expect_failure'checkout and reset (mixed)''+init_repos&&++test_all_matchgitcheckout-breset-testupdate-deep&&+test_all_matchgitresetdeepest&&+test_all_matchgitresetupdate-folder1&&+test_all_matchgitresetupdate-folder2+'++test_expect_success'merge''+init_repos&&++test_all_matchgitcheckout-bmergeupdate-deep&&+test_all_matchgitmerge-m"folder1"update-folder1&&+test_all_matchgitrev-parseHEAD^{tree}&&+test_all_matchgitmerge-m"folder2"update-folder2&&+test_all_matchgitrev-parseHEAD^{tree}+'++test_expect_success'merge with outside renames''+init_repos&&++fortypeinout-to-outout-to-inin-to-out+do+test_all_matchgitreset--hard&&+test_all_matchgitcheckout-f-bmerge-$typeupdate-deep&&+test_all_matchgitmerge-m"$type"rename-$type&&+test_all_matchgitrev-parseHEAD^{tree}||return1+done+'++test_expect_success'clean''+init_repos&&++echobogus>>.gitignore&&+run_on_allcp../.gitignore.&&+test_all_matchgitadd.gitignore&&+test_all_matchgitcommit-mignore-bogus-files&&++run_on_sparsemkdirfolder1&&+run_on_alltouchfolder1/bogus&&++test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitclean-f&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitclean-xf&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitclean-xdf&&+test_all_matchgitstatus--porcelain=v2&&++test_path_is_dirsparse-checkout/folder1+'++test_done
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted hunk
From: Derrick Stolee <redacted>
Make the method safer by allocating a cache_tree member for the given
index_state if it is not already present.
Also drop local variables that are used exactly once and can be found
directly from the 'istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
@@ -436,16 +436,20 @@ static int update_one(struct cache_tree *it,intcache_tree_update(structindex_state*istate,intflags){-structcache_tree*it=istate->cache_tree;-structcache_entry**cache=istate->cache;-intentries=istate->cache_nr;-intskip,i=verify_cache(cache,entries,flags);+intskip,i;++i=verify_cache(istate->cache,istate->cache_nr,flags);
All mechanical changes so far; these look obviously correct.
if (i)
return i;
+
+ if (!istate->cache_tree)
+ istate->cache_tree = cache_tree();
This is the only substantive change. It seems fairly innocuous, but
it makes me wonder the reasoning...I don't know/remember enough about
cache_tree handling to know when this would or wouldn't have already
been allocated. It seems that this would have had to segfault below
if istate->cache_tree were ever NULL, and I don't see you mentioning
any bug you are fixing, so I presume this means you are going to be
adding new codepaths somewhere that cause this function to be reached
under different circumstances than previously had been and you need it
to be more safe for those. Is that correct? Or is it just an
abundance of caution thing that you're adding? If the latter, any
reason you chose to allocate one rather than assume it's a violation
of design invariants and BUG() instead? (Perhaps the commit message
could add a sentence about the rationale for the extra safety?)
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted hunk
From: Derrick Stolee <redacted>
This method will be helpful to use outside of cache-tree.c in a later
feature. The implementation is subtle due to subtree_name_cmp() sorting
by length and then lexicographically.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 6 +++---
cache-tree.h | 2 ++
2 files changed, 5 insertions(+), 3 deletions(-)
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
From: Derrick Stolee <redacted>
The index has an fsmonitor_dirty bitmap that records which index entries
are "dirty" based on the response from the FSMonitor. If this bitmap
ever grows larger than the index, then there was an error in how it was
constructed, and it was probably a developer's bug.
There are several BUG() statements that are very similar, so replace
these uses with a simpler assert_index_minimum(). Since there is one
caller that uses a custom 'pos' value instead of the bit_size member, we
cannot simplify it too much. However, the error string is identical in
each, so this simplifies things.
The end result is that the code is simpler to read while also preserving
these assertions for developers in the FSMonitor space.
Indeed, looking through the patch, the end result is simpler to read.
Nice cleanup.
@@ -13,14 +13,19 @@structtrace_keytrace_fsmonitor=TRACE_KEY_INIT(FSMONITOR);+staticvoidassert_index_minimum(structindex_state*istate,size_tpos)+{+if(pos>istate->cache_nr)+BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",+(uintmax_t)pos,istate->cache_nr);+}+staticvoidfsmonitor_ewah_callback(size_tpos,void*is){structindex_state*istate=(structindex_state*)is;structcache_entry*ce;-if(pos>=istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" >= %u)",-(uintmax_t)pos,istate->cache_nr);+assert_index_minimum(istate,pos);ce=istate->cache[pos];ce->ce_flags&=~CE_FSMONITOR_VALID;
@@ -82,10 +87,8 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data,}istate->fsmonitor_dirty=fsmonitor_dirty;-if(!istate->split_index&&-istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+if(!istate->split_index)+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);trace_printf_key(&trace_fsmonitor,"read fsmonitor extension successful");return0;
@@ -110,10 +113,8 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)uint32_tewah_size=0;intfixup=0;-if(!istate->split_index&&-istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+if(!istate->split_index)+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);put_be32(&hdr_version,INDEX_EXTENSION_VERSION2);strbuf_add(sb,&hdr_version,sizeof(uint32_t));
@@ -335,9 +336,7 @@ void tweak_fsmonitor(struct index_state *istate)}/* Mark all previously saved entries as dirty */-if(istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);ewah_each_bit(istate->fsmonitor_dirty,fsmonitor_ewah_callback,istate);refresh_fsmonitor(istate);--
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
From: Derrick Stolee <redacted>
It will be helpful to add behavior to index opertations that might
s/opertations/operations/
quoted hunk
trigger an object lookup. Since each index belongs to a specific
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
This will prevent future changes from needing to pass an additional
'struct repository *repo' parameter and instead rely only on the 'struct
index_state *istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache.h | 1 +
repository.c | 4 ++++
2 files changed, 5 insertions(+)
@@ -264,6 +264,10 @@ int repo_read_index(struct repository *repo)if(!repo->index)repo->index=xcalloc(1,sizeof(*repo->index));+/* Complete the double-reference */+if(!repo->index->repo)+repo->index->repo=repo;+returnread_index_from(repo->index,repo->index_file,repo->gitdir);}--
gitgitgadget
Since we have repo->index and we have index->repo, which are intended
to be circular...what if they aren't? Do we want or need to add
assertions anywhere that repo == repo->index->repo or that index ==
index->repo->index ?
My initial implementations of --remerge-diff[1] played around with
creating a second repo, with a different primary object store but
everything else the same. The index for the two repository objects
was thus the same, and thus clearly would have violated this assumed
invariant for one of the two repos. I discarded that initial
implementation (which I didn't quite have working) because I
discovered tmp-objdir.h and was able to add some
tmp_objdir_make_primary() and tmp_objdir_remove_as_primary() functions
that merely altered the existing repo's primary object store, but I'm
curious if there might be other cases of folks doing stuff that might
have weird failures with this new invariant.
It's entirely possible that --remerge-diff was just so different, and
I was so unfamiliar with repo objects (and still kind of am) that I
was just doing weird stuff no one has done before, so perhaps no
additional checks are needed -- I'm just throwing my gut question out
there as food for thought.
[1] I have not yet submitted `--remerge-diff` to the list; you haven't
missed anything. I'm waiting for merge-ort to be submitted, reviewed,
and merged first. It's the remerge-diff branch in my fork on GitHub
if anyone is curious, though.
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted hunk
From: Derrick Stolee <redacted>
The lazy_init_name_hash() populates a hashset with all filenames and
another with all directories represented in the index. This is run only
if we need to use the hashsets to check for existence or case-folding
renames.
Place trace2 regions where there is already a performance trace.
Signed-off-by: Derrick Stolee <redacted>
---
name-hash.c | 3 +++
1 file changed, 3 insertions(+)
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted hunk
From: Derrick Stolee <redacted>
A future feature will want to load the sparse-checkout patterns into a
pattern_list, but the current mechanism to do so is a bit complicated.
This is made difficult due to needing to find the sparse-checkout file
in different ways throughout the codebase.
The logic implemented in the new get_sparse_checkout_patterns() was
duplicated in populate_from_existing_patterns() in unpack-trees.c. Use
the new method instead, keeping the logic around handling the struct
unpack_trees_options.
The callers to get_sparse_checkout_filename() in
builtin/sparse-checkout.c manipulate the sparse-checkout file directly,
so it is not appropriate to replace logic in that file with
get_sparse_checkout_patterns().
Signed-off-by: Derrick Stolee <redacted>
---
builtin/sparse-checkout.c | 5 -----
dir.c | 17 +++++++++++++++++
dir.h | 2 ++
unpack-trees.c | 6 +-----
4 files changed, 20 insertions(+), 10 deletions(-)
Looks straightforward and well motivated to me.
But the cherry on top that really sells this patch is that more lines
of dir.c will blame to someone besides me. Win-win!
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted hunk
From: Derrick Stolee <redacted>
As we modify the sparse-checkout definition, we perform index operations
on a pattern_list that only exists in-memory. This allows easy backing
out in case the index update fails.
However, if the index write itself cares about the sparse-checkout
pattern set, we need access to that in-memory copy. Place a pointer to
a 'struct pattern_list' in the index so we can access this on-demand.
Signed-off-by: Derrick Stolee <redacted>
---
builtin/sparse-checkout.c | 17 ++++++++++-------
cache.h | 2 ++
2 files changed, 12 insertions(+), 7 deletions(-)
@@ -110,6 +110,8 @@ static int update_working_directory(struct pattern_list *pl)if(is_index_unborn(r->index))returnUPDATE_SPARSITY_SUCCESS;+r->index->sparse_checkout_patterns=pl;+memset(&o,0,sizeof(o));o.verbose_update=isatty(2);o.update=1;
@@ -138,6 +140,7 @@ static int update_working_directory(struct pattern_list *pl)elserollback_lock_file(&lock_file);+r->index->sparse_checkout_patterns=NULL;returnresult;
The setting back to NULL made me curious; we don't want this
information to remain available later? Is it only going to be used
for the updating of the working directory?
I dug a bit into the callers, and didn't find the answer to my
question...but I did notice that modify_pattern_list() will correctly
free the patterns after write_patterns_and_update() via calling
clear_pattern_list(&pl), but sparse_checkout_init() appears to leak
the patterns it allocates. That's a separate issue from this patch,
but do you want to fix that up while working in this area (so I avoid
stepping on your toes with all your other patches)?
quoted hunk
}
@@ -517,19 +520,18 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m) { int result; int changed_config = 0;- struct pattern_list pl;- memset(&pl, 0, sizeof(pl));+ struct pattern_list *pl = xcalloc(1, sizeof(*pl)); switch (m) { case ADD: if (core_sparse_checkout_cone)- add_patterns_cone_mode(argc, argv, &pl);+ add_patterns_cone_mode(argc, argv, pl); else- add_patterns_literal(argc, argv, &pl);+ add_patterns_literal(argc, argv, pl); break; case REPLACE:- add_patterns_from_input(&pl, argc, argv);+ add_patterns_from_input(pl, argc, argv);
Slightly annoying that the other functions are (argc, argv, pl) and
this one is (pl, argc, argv). But again, that's outside the scope of
this patch and might not be worth the churn to fix.
quoted hunk
break;
}
@@ -539,12 +541,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m) changed_config = 1; }- result = write_patterns_and_update(&pl);+ result = write_patterns_and_update(pl); if (result && changed_config) set_config(MODE_NO_PATTERNS);- clear_pattern_list(&pl);+ clear_pattern_list(pl);+ free(pl); return result; }
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
From: Derrick Stolee <redacted>
Most test cases can verify Git's behavior using input/output
expectations or changes to the .git directory. However, sometimes we
want to check that Git did or did not run a certain section of code.
This is particularly important for performance-only features that we
want to ensure have been enabled in certain cases.
Add a new 'test_region' function that checks if a trace2 region was
entered and left in a given trace2 event log.
Ooh, others do this too? Sounds like a helpful function to add, but
just checking for entered and left means that...
There is one existing test (t0500-progress-display.sh) that performs
this check already, so use the helper function instead. More uses will
be added in a later change.
t6423-merge-rename-directories.sh also greps for region_enter lines, but
it verifies the number of such lines, which is not the same as an
existence check.
...yeah, won't cover the case that I added. That's fine, since it
appears to be a one-off for now and we don't know of any other cases,
current or planned, that want to do something like that yet.
@@ -303,8 +303,7 @@ test_expect_success 'progress generates traces' '"Working hard"<in2>stderr&&# t0212/parse_events.perl intentionally omits regions and data.-grep-e"region_enter"-e"\"category\":\"progress\""trace.event&&-grep-e"region_leave"-e"\"category\":\"progress\""trace.event&&+test_regioncategoryprogresstrace.event&&
Sidenote: Hmm...about 40% of my region labels in merge-ort.c and 90%
in diffcore-rename.c have spaces in them. This function could still
be used, but I'm curious if I should change the labels (but then
again, they are testing logical regions rather than individual
functions, and the spaces instead of underscores kind of convey
that...)
@@ -1655,3 +1655,43 @@ test_subcommand () {grep"\[$expr\]"fi}++# Check that the given command was invoked as part of the+# trace2-format trace on stdin.+#+# test_region [!] <category> <label> git <command> <args>...+#+# For example, to look for trace2_region_enter("index", "do_read_index", repo)+# in an invocation of "git checkout HEAD~1", run+#+# GIT_TRACE2_EVENT="$(pwd)/trace.txt" GIT_TRACE2_EVENT_NESTING=10 \+# git checkout HEAD~1 &&+# test_region index do_read_index <trace.txt+#+# If the first parameter passed is !, this instead checks that+# the given region was not entered.+#+test_region(){+localexpect_exit=0+iftest"$1"="!"+then+expect_exit=1+shift+fi++grep-e"region_enter"-e"\"category\":\"$1\",\"label\":\"$2\"""$3"+exitcode=$?++iftest$exitcode!=$expect_exit+then+return1+fi++grep-e"region_leave"-e"\"category\":\"$1\",\"label\":\"$2\"""$3"+exitcode=$?++iftest$exitcode!=$expect_exit+then+return1+fi+}--
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:...
quoted
+
+ if (!istate->cache_tree)
+ istate->cache_tree = cache_tree();
This is the only substantive change. It seems fairly innocuous, but
it makes me wonder the reasoning...I don't know/remember enough about
cache_tree handling to know when this would or wouldn't have already
been allocated. It seems that this would have had to segfault below
if istate->cache_tree were ever NULL, and I don't see you mentioning
any bug you are fixing, so I presume this means you are going to be
adding new codepaths somewhere that cause this function to be reached
under different circumstances than previously had been and you need it
to be more safe for those. Is that correct? Or is it just an
abundance of caution thing that you're adding? If the latter, any
reason you chose to allocate one rather than assume it's a violation
of design invariants and BUG() instead? (Perhaps the commit message
could add a sentence about the rationale for the extra safety?)
It's something I need in the future when I use the cache_tree_update()
in more places. I think I call it two times, and either I need to
initialize the cache_tree member outside of both, or just make it a
feature of the method that it will re-initialize the cache-tree.
Note: the implementation treats an initialized, but empty cache-tree
as "invalid" so update_one() correctly populates the full tree.
Thanks,
-Stolee
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted
From: Derrick Stolee <redacted>
It will be helpful to add behavior to index opertations that might
s/opertations/operations/
Thanks.
quoted
trigger an object lookup. Since each index belongs to a specific
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
This will prevent future changes from needing to pass an additional
'struct repository *repo' parameter and instead rely only on the 'struct
index_state *istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache.h | 1 +
repository.c | 4 ++++
2 files changed, 5 insertions(+)
@@ -264,6 +264,10 @@ int repo_read_index(struct repository *repo)if(!repo->index)repo->index=xcalloc(1,sizeof(*repo->index));+/* Complete the double-reference */+if(!repo->index->repo)+repo->index->repo=repo;+returnread_index_from(repo->index,repo->index_file,repo->gitdir);}--
gitgitgadget
Since we have repo->index and we have index->repo, which are intended
to be circular...what if they aren't? Do we want or need to add
assertions anywhere that repo == repo->index->repo or that index ==
index->repo->index ?
Here, we are pairing them together and the loop is complete. I don't
view that as a permanent thing. This only initializes istate->repo
when we are parsing an index from a file, but not when we create one
in memory.
I imagine it will be likely in some cases to have multiple index_state
instances for a single repository. However, having the pointer "this
index belongs to this repository" seems helpful (to me).
My initial implementations of --remerge-diff[1] played around with
creating a second repo, with a different primary object store but
everything else the same. The index for the two repository objects
was thus the same, and thus clearly would have violated this assumed
invariant for one of the two repos. I discarded that initial
implementation (which I didn't quite have working) because I
discovered tmp-objdir.h and was able to add some
tmp_objdir_make_primary() and tmp_objdir_remove_as_primary() functions
that merely altered the existing repo's primary object store, but I'm
curious if there might be other cases of folks doing stuff that might
have weird failures with this new invariant.
This is an interesting concept, and definitely violates my expectations
that an index belongs to only one repository. I'd need to know more
about why this was a good design decision before being convinced that
the relationship should not be many-to-one (index-to-repo).
It's entirely possible that --remerge-diff was just so different, and
I was so unfamiliar with repo objects (and still kind of am) that I
was just doing weird stuff no one has done before, so perhaps no
additional checks are needed -- I'm just throwing my gut question out
there as food for thought.
[1] I have not yet submitted `--remerge-diff` to the list; you haven't
missed anything. I'm waiting for merge-ort to be submitted, reviewed,
and merged first. It's the remerge-diff branch in my fork on GitHub
if anyone is curious, though.
I'm interested in what others might say about this idea. I'd be able
to do most of what I want to do without this patch, but it just gets
a lot messier. (istate->repo is used in the very next patch in a way
that would be less clean without it.)
Thanks,
-Stolee
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted
From: Derrick Stolee <redacted>
As we modify the sparse-checkout definition, we perform index operations
on a pattern_list that only exists in-memory. This allows easy backing
out in case the index update fails.
However, if the index write itself cares about the sparse-checkout
pattern set, we need access to that in-memory copy. Place a pointer to
a 'struct pattern_list' in the index so we can access this on-demand.
Signed-off-by: Derrick Stolee <redacted>
---
builtin/sparse-checkout.c | 17 ++++++++++-------
cache.h | 2 ++
2 files changed, 12 insertions(+), 7 deletions(-)
@@ -110,6 +110,8 @@ static int update_working_directory(struct pattern_list *pl)if(is_index_unborn(r->index))returnUPDATE_SPARSITY_SUCCESS;+r->index->sparse_checkout_patterns=pl;+memset(&o,0,sizeof(o));o.verbose_update=isatty(2);o.update=1;
@@ -138,6 +140,7 @@ static int update_working_directory(struct pattern_list *pl)elserollback_lock_file(&lock_file);+r->index->sparse_checkout_patterns=NULL;returnresult;
The setting back to NULL made me curious; we don't want this
information to remain available later? Is it only going to be used
for the updating of the working directory?
I dug a bit into the callers, and didn't find the answer to my
question...but I did notice that modify_pattern_list() will correctly
free the patterns after write_patterns_and_update() via calling
clear_pattern_list(&pl), but sparse_checkout_init() appears to leak
the patterns it allocates. That's a separate issue from this patch,
but do you want to fix that up while working in this area (so I avoid
stepping on your toes with all your other patches)?
The thing that caught me here is that update_working_directory() uses
an in-memory pattern_list that hasn't been committed to the
sparse-checkout file yet. This means we need to (temporarily) point
to this pattern_list.
Perhaps this patch is premature, since nothing actually _uses_
sparse_checkout_patterns yet. When we do add such a use, it will
initialize a NULL value with the patterns in the sparse-checkout
file. In that case, we definitely want to inject our in-memory
patterns instead.
Thanks,
-Stolee
Sidenote: Hmm...about 40% of my region labels in merge-ort.c and 90%
in diffcore-rename.c have spaces in them. This function could still
be used, but I'm curious if I should change the labels (but then
again, they are testing logical regions rather than individual
functions, and the spaces instead of underscores kind of convey
that...)
You should be able to use
test_region "category with spaces" "progress with spaces" trace
but if not, then the test_region helper could be improved to match.
I do think that it's better to avoid spaces in these identifiers.
Thanks,
-Stolee
Think out loud...so you are only adding files that were not previously
tracked and that would have been part of the sparse cone. You aren't
trying to add files that would be outside the sparse cone, or manually
creating files missing from the working tree due to sparseness and
then attempting to add them. (Which is fine, we have to start
somewhere with our testing. Also, I think my testcases didn't look at
the case you did, and only covered one of these other two cases.)
Aren't these last two lines the same? (diff.renames defaults to true
ever since commit 5404c116aa, "diff: activate diff.renames by
default", 2016-02-25) Are they only different because you have a
tweaked config that turns off renames by default?
Perhaps the first diff line should have a --no-renames flag.
Good check.
On a side note going back to a piece of the other thread I didn't get
a response to, I'm still curious whether
git blame -C -C $PATH_INSIDE_SPARSE_DEFINITION
should (optionally?) behave differently in a sparse checkout. In
particular, should it limit its copy detection to other paths also in
the sparse checkout, or should it always search all other files within
the repository for copied lines? Searching just within the sparse
checkout seems like it could be a really nice performance
optimization.
+
+# TODO: blame currently does not support blaming files outside of the
+# sparse definition. It complains that the file doesn't exist locally.
Nice catch. Yeah, blame tries to check the local working copy for
changes, and shows those lines with a changed in commit 0000000000.
We should add a check that says that if the file is SKIP_WORKTREE,
then we treat it the same as `git blame $PATH HEAD`.
+test_expect_failure 'blame with pathspec outside sparse definition' '
+ init_repos &&
+
+ test_all_match git blame folder1/a &&
+ test_all_match git blame folder2/a &&
+ test_all_match git blame deep/deeper2/a &&
+ test_all_match git blame deep/deeper2/deepest/a
+'
+
+# TODO: reset currently does not behave as expected when in a
+# sparse-checkout.
I'm going to go to test this out to see what it does. It's the first
testcase you listed that I didn't know how it worked and couldn't
figure it out from your comments. However it turns out, definitely a
good test to have.
I made lots of comments, but overall these tests look good to me other
than just one question about test duplication and another about using
--no-renames for diff when rename detection isn't wanted.
On Wed, Jan 20, 2021 at 11:16 AM Derrick Stolee [off-list ref] wrote:
On 1/20/2021 12:46 PM, Elijah Newren wrote:
quoted
On Wed, Jan 20, 2021 at 8:54 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted
From: Derrick Stolee <redacted>
It will be helpful to add behavior to index opertations that might
s/opertations/operations/
Thanks.
quoted
quoted
trigger an object lookup. Since each index belongs to a specific
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
This will prevent future changes from needing to pass an additional
'struct repository *repo' parameter and instead rely only on the 'struct
index_state *istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache.h | 1 +
repository.c | 4 ++++
2 files changed, 5 insertions(+)
@@ -264,6 +264,10 @@ int repo_read_index(struct repository *repo)if(!repo->index)repo->index=xcalloc(1,sizeof(*repo->index));+/* Complete the double-reference */+if(!repo->index->repo)+repo->index->repo=repo;+returnread_index_from(repo->index,repo->index_file,repo->gitdir);}--
gitgitgadget
Since we have repo->index and we have index->repo, which are intended
to be circular...what if they aren't? Do we want or need to add
assertions anywhere that repo == repo->index->repo or that index ==
index->repo->index ?
Here, we are pairing them together and the loop is complete. I don't
view that as a permanent thing. This only initializes istate->repo
when we are parsing an index from a file, but not when we create one
in memory.
I imagine it will be likely in some cases to have multiple index_state
instances for a single repository. However, having the pointer "this
index belongs to this repository" seems helpful (to me).
quoted
My initial implementations of --remerge-diff[1] played around with
creating a second repo, with a different primary object store but
everything else the same. The index for the two repository objects
was thus the same, and thus clearly would have violated this assumed
invariant for one of the two repos. I discarded that initial
implementation (which I didn't quite have working) because I
discovered tmp-objdir.h and was able to add some
tmp_objdir_make_primary() and tmp_objdir_remove_as_primary() functions
that merely altered the existing repo's primary object store, but I'm
curious if there might be other cases of folks doing stuff that might
have weird failures with this new invariant.
This is an interesting concept, and definitely violates my expectations
that an index belongs to only one repository. I'd need to know more
about why this was a good design decision before being convinced that
the relationship should not be many-to-one (index-to-repo).
I'm not sure what I did was a good design decision; I was kind of
exploring and trying to figure things out. In retrospect, I think it
was probably a bad idea. But we have various guard rails in the form
of BUG() calls and such when basic assumptions are violated, and here
it seems that you are now making a new basic assumption that an index
belongs to only one repository. (Even if all current callers happen
to satisfy that assumption, it's not clear to me that git previously
cared if this condition were satisfied or not). Hence my question
about safety checks.
quoted
It's entirely possible that --remerge-diff was just so different, and
I was so unfamiliar with repo objects (and still kind of am) that I
was just doing weird stuff no one has done before, so perhaps no
additional checks are needed -- I'm just throwing my gut question out
there as food for thought.
[1] I have not yet submitted `--remerge-diff` to the list; you haven't
missed anything. I'm waiting for merge-ort to be submitted, reviewed,
and merged first. It's the remerge-diff branch in my fork on GitHub
if anyone is curious, though.
I'm interested in what others might say about this idea. I'd be able
to do most of what I want to do without this patch, but it just gets
a lot messier. (istate->repo is used in the very next patch in a way
that would be less clean without it.)
I'm less concerned with your patch as-is (I think your assumption
seems reasonable and I'm fine with labelling my former unsubmitted
patches as erroneous), and more wondering whether others in the future
will accidentally violate assumptions your patch starts encoding...and
whether we can or should do anything about it. If there's a simple
place we can add a check for such an error, then it probably makes
sense to add one. If there isn't...then at least we considered it?
That would be helpful, and adding them here would be helpful.
For the cases where things don't work correctly, it is good
to add "test_expect_failure" cases as a TODO list for the
feature space.
Think out loud...so you are only adding files that were not previously
tracked and that would have been part of the sparse cone. You aren't
trying to add files that would be outside the sparse cone, or manually
creating files missing from the working tree due to sparseness and
then attempting to add them. (Which is fine, we have to start
somewhere with our testing. Also, I think my testcases didn't look at
the case you did, and only covered one of these other two cases.)
Yes, these tests are currently focusing on the "happy" cases
of what is happening within the sparse cone. I plan to expand
to the more complicated cases later, as I start implementing
them correctly with the sparse-index. However, it would be fine
to have the tests here earlier. Extra documentation of the
expected behavior (or how the current implementation is not
desirable) would be good.
Aren't these last two lines the same? (diff.renames defaults to true
ever since commit 5404c116aa, "diff: activate diff.renames by
default", 2016-02-25) Are they only different because you have a
tweaked config that turns off renames by default?
Perhaps the first diff line should have a --no-renames flag.
Good check.
On a side note going back to a piece of the other thread I didn't get
a response to, I'm still curious whether
git blame -C -C $PATH_INSIDE_SPARSE_DEFINITION
should (optionally?) behave differently in a sparse checkout. In
particular, should it limit its copy detection to other paths also in
the sparse checkout, or should it always search all other files within
the repository for copied lines? Searching just within the sparse
checkout seems like it could be a really nice performance
optimization.
All of the "find movements or copies" logic could benefit from a
"universal" option to restrict to the sparse-checkout definition.
quoted
+
+# TODO: blame currently does not support blaming files outside of the
+# sparse definition. It complains that the file doesn't exist locally.
Nice catch. Yeah, blame tries to check the local working copy for
changes, and shows those lines with a changed in commit 0000000000.
We should add a check that says that if the file is SKIP_WORKTREE,
then we treat it the same as `git blame $PATH HEAD`.
Right. If it's not in the working directory, then we should
interpret that as HEAD.
quoted
+test_expect_failure 'blame with pathspec outside sparse definition' '
+ init_repos &&
+
+ test_all_match git blame folder1/a &&
+ test_all_match git blame folder2/a &&
+ test_all_match git blame deep/deeper2/a &&
+ test_all_match git blame deep/deeper2/deepest/a
+'
+
+# TODO: reset currently does not behave as expected when in a
+# sparse-checkout.
I'm going to go to test this out to see what it does. It's the first
testcase you listed that I didn't know how it worked and couldn't
figure it out from your comments. However it turns out, definitely a
good test to have.
Oddly, when I merge this into our branch in microsoft/git,
these failures disappear. There is something in those commits
that resolve this particular case. I hope to figure that out
sometime.
Thanks,
-Stolee
From: Chris Torek <hidden> Date: 2021-01-21 12:54:30
On Wed, Jan 20, 2021 at 8:58 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
From: Derrick Stolee <redacted>
The index has an fsmonitor_dirty bitmap that records which index entries
are "dirty" based on the response from the FSMonitor. If this bitmap
ever grows larger than the index, then there was an error in how it was
constructed, and it was probably a developer's bug.
Curious: some of the tests were >=, some were > (not >=). Now
that they're shared in a function they are all ">".
It's pretty clear that for size-based ones, greater-than is the
right test, but for position ones, isn't it still greater-or-equal? So
perhaps the calls that pass an actual position should add 1...
Chris
On Wed, Jan 20, 2021 at 8:58 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted
From: Derrick Stolee <redacted>
The index has an fsmonitor_dirty bitmap that records which index entries
are "dirty" based on the response from the FSMonitor. If this bitmap
ever grows larger than the index, then there was an error in how it was
constructed, and it was probably a developer's bug.
Curious: some of the tests were >=, some were > (not >=). Now
that they're shared in a function they are all ">".
It's pretty clear that for size-based ones, greater-than is the
right test, but for position ones, isn't it still greater-or-equal? So
perhaps the calls that pass an actual position should add 1...
That's a good point. I should pass "pos + 1" in the appropriate
places.
Thanks,
-Stolee
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:05:14
This is based on ds/cache-tree-basics.
Here are a few more cleanups that are vaguely related to the index. I
discovered these while preparing my sparse-index RFC that I intend to send
early next week.
The biggest patch is the final one, which creates a test script for
comparing sparse-checkouts to full checkouts. There are some commands that
do not behave similarly. This script will be the backbone of my testing
strategy for the sparse-index by adding a new mode to compare
sparse-checkouts with the two index types (full and sparse).
UPDATES IN V2
=============
* Fixed duplicated test in t1092.
* Changed the implementation of 'test_region' after I discovered the
negation doesn't work correctly. (I updated the test to use what was in
t0500-progress-display.sh at the last minute before v1, but that
implementation was wrong.) The use of it in t0500-progress-display.sh was
incorrect, as well.
* Updated commit messages to be more informative and have fewer typos.
* I dropped the patch that placed the sparse-checkout patterns in struct
index_state. I'll re-introduce that in time for the actual use of the
member.
Thanks, -Stolee
Derrick Stolee (8):
cache-tree: clean up cache_tree_update()
cache-tree: extract subtree_pos()
fsmonitor: de-duplicate BUG()s around dirty bits
repository: add repo reference to index_state
name-hash: use trace2 regions for init
sparse-checkout: load sparse-checkout patterns
test-lib: test_region looks for trace2 regions
t1092: test interesting sparse-checkout scenarios
builtin/sparse-checkout.c | 5 -
cache-tree.c | 20 +-
cache-tree.h | 2 +
cache.h | 1 +
dir.c | 17 ++
dir.h | 2 +
fsmonitor.c | 27 +-
name-hash.c | 3 +
repository.c | 4 +
t/t0500-progress-display.sh | 3 +-
t/t1092-sparse-checkout-compatibility.sh | 298 +++++++++++++++++++++++
t/test-lib-functions.sh | 40 +++
unpack-trees.c | 6 +-
13 files changed, 394 insertions(+), 34 deletions(-)
create mode 100755 t/t1092-sparse-checkout-compatibility.sh
base-commit: a4b6d202caad83c6dc29abe9b17e53a1b3fb54a0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-839%2Fderrickstolee%2Fmore-index-cleanups-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-839/derrickstolee/more-index-cleanups-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/839
Range-diff vs v1:
1: 0bccfd34ae5 ! 1: f9dccaed0ac cache-tree: clean up cache_tree_update()
@@ Commit message
cache-tree: clean up cache_tree_update()
Make the method safer by allocating a cache_tree member for the given
- index_state if it is not already present.
+ index_state if it is not already present. This is preferrable to a
+ BUG() statement or returning with an error because future callers will
+ want to populate an empty cache-tree using this method.
Also drop local variables that are used exactly once and can be found
directly from the 'istate' parameter.
2: a6f2406a795 = 2: 84323e04d08 cache-tree: extract subtree_pos()
3: 838922de2e9 = 3: 31095f9aa0e fsmonitor: de-duplicate BUG()s around dirty bits
4: d4ff0468fc0 ! 4: a0d89d7a973 repository: add repo reference to index_state
@@ Metadata
## Commit message ##
repository: add repo reference to index_state
- It will be helpful to add behavior to index opertations that might
+ It will be helpful to add behavior to index operations that might
trigger an object lookup. Since each index belongs to a specific
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
5: 3ba4b35f09c = 5: bc092f5c703 name-hash: use trace2 regions for init
6: 64358ec7ea2 = 6: 04d1daf7222 sparse-checkout: load sparse-checkout patterns
7: 91344f5108c < -: ----------- sparse-checkout: hold pattern list in index
8: 8326a9b5320 ! 7: 8832ce84623 test-lib: test_region looks for trace2 regions
@@ Commit message
entered and left in a given trace2 event log.
There is one existing test (t0500-progress-display.sh) that performs
- this check already, so use the helper function instead. More uses will
- be added in a later change.
+ this check already, so use the helper function instead. Note that this
+ changes the expectations slightly. The old test (incorrectly) used two
+ patterns for the 'grep' invocation, but this performs an OR of the
+ patterns, not an AND. This means that as long as one region_enter event
+ was logged, the test would succeed, even if it was not due to the
+ progress category.
+
+ More uses will be added in a later change.
t6423-merge-rename-directories.sh also greps for region_enter lines, but
it verifies the number of such lines, which is not the same as an
@@ t/t0500-progress-display.sh: test_expect_success 'progress generates traces' '
# t0212/parse_events.perl intentionally omits regions and data.
- grep -e "region_enter" -e "\"category\":\"progress\"" trace.event &&
- grep -e "region_leave" -e "\"category\":\"progress\"" trace.event &&
-+ test_region category progress trace.event &&
++ test_region progress "Working hard" trace.event &&
grep "\"key\":\"total_objects\",\"value\":\"40\"" trace.event &&
grep "\"key\":\"total_bytes\",\"value\":\"409600\"" trace.event
'
@@ t/test-lib-functions.sh: test_subcommand () {
+ shift
+ fi
+
-+ grep -e "region_enter" -e "\"category\":\"$1\",\"label\":\"$2\"" "$3"
++ grep -e "\"region_enter\".*\"category\":\"$1\",\"label\":\"$2\"" "$3"
+ exitcode=$?
+
+ if test $exitcode != $expect_exit
@@ t/test-lib-functions.sh: test_subcommand () {
+ return 1
+ fi
+
-+ grep -e "region_leave" -e "\"category\":\"$1\",\"label\":\"$2\"" "$3"
++ grep -e "\"region_leave\".*\"category\":\"$1\",\"label\":\"$2\"" "$3"
+ exitcode=$?
+
+ if test $exitcode != $expect_exit
9: 555e210dc03 ! 8: 984458007ed t1092: test interesting sparse-checkout scenarios
@@ Commit message
These also document some behaviors that differ from a full checkout, and
possibly in a way that is not intended.
+ The test is designed to be run with "--run=1,X" where 'X' is an
+ interesting test case. Each test uses 'init_repos' to reset the full and
+ sparse copies of the initial-repo that is created by the first test
+ case. This also makes it possible to have test cases leave the working
+ directory or index in unusual states without disturbing later cases.
+
Signed-off-by: Derrick Stolee [off-list ref]
## t/t1092-sparse-checkout-compatibility.sh (new) ##
@@ t/t1092-sparse-checkout-compatibility.sh (new)
+ init_repos &&
+
+ write_script edit-contents <<-\EOF &&
-+ echo text >>README.md
-+ EOF
-+ run_on_all "../edit-contents" &&
-+
-+ test_all_match git add README.md &&
-+ test_all_match git status --porcelain=v2 &&
-+ test_all_match git commit -m "Add README.md" &&
-+
-+ test_all_match git checkout HEAD~1 &&
-+ test_all_match git checkout - &&
-+
-+ run_on_all "../edit-contents" &&
-+
-+ test_all_match git add -A &&
-+ test_all_match git status --porcelain=v2 &&
-+ test_all_match git commit -m "Extend README.md" &&
-+
-+ test_all_match git checkout HEAD~1 &&
-+ test_all_match git checkout -
-+'
-+
-+test_expect_success 'add, commit, checkout' '
-+ init_repos &&
-+
-+ write_script edit-contents <<-\EOF &&
+ echo text >>$1
+ EOF
+ run_on_all "../edit-contents README.md" &&
@@ t/t1092-sparse-checkout-compatibility.sh (new)
+ do
+ test_all_match git checkout rename-base &&
+ test_all_match git checkout $branch -- .&&
-+ test_all_match git diff --staged &&
++ test_all_match git diff --staged --no-renames &&
+ test_all_match git diff --staged --find-renames || return 1
+ done
+'
--
gitgitgadget
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:06:07
From: Derrick Stolee <redacted>
Most test cases can verify Git's behavior using input/output
expectations or changes to the .git directory. However, sometimes we
want to check that Git did or did not run a certain section of code.
This is particularly important for performance-only features that we
want to ensure have been enabled in certain cases.
Add a new 'test_region' function that checks if a trace2 region was
entered and left in a given trace2 event log.
There is one existing test (t0500-progress-display.sh) that performs
this check already, so use the helper function instead. Note that this
changes the expectations slightly. The old test (incorrectly) used two
patterns for the 'grep' invocation, but this performs an OR of the
patterns, not an AND. This means that as long as one region_enter event
was logged, the test would succeed, even if it was not due to the
progress category.
More uses will be added in a later change.
t6423-merge-rename-directories.sh also greps for region_enter lines, but
it verifies the number of such lines, which is not the same as an
existence check.
Signed-off-by: Derrick Stolee <redacted>
---
t/t0500-progress-display.sh | 3 +--
t/test-lib-functions.sh | 40 +++++++++++++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 2 deletions(-)
@@ -1655,3 +1655,43 @@ test_subcommand () {grep"\[$expr\]"fi}++# Check that the given command was invoked as part of the+# trace2-format trace on stdin.+#+# test_region [!] <category> <label> git <command> <args>...+#+# For example, to look for trace2_region_enter("index", "do_read_index", repo)+# in an invocation of "git checkout HEAD~1", run+#+# GIT_TRACE2_EVENT="$(pwd)/trace.txt" GIT_TRACE2_EVENT_NESTING=10 \+# git checkout HEAD~1 &&+# test_region index do_read_index <trace.txt+#+# If the first parameter passed is !, this instead checks that+# the given region was not entered.+#+test_region(){+localexpect_exit=0+iftest"$1"="!"+then+expect_exit=1+shift+fi++grep-e"\"region_enter\".*\"category\":\"$1\",\"label\":\"$2\"""$3"+exitcode=$?++iftest$exitcode!=$expect_exit+then+return1+fi++grep-e"\"region_leave\".*\"category\":\"$1\",\"label\":\"$2\"""$3"+exitcode=$?++iftest$exitcode!=$expect_exit+then+return1+fi+}
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:06:34
From: Derrick Stolee <redacted>
These also document some behaviors that differ from a full checkout, and
possibly in a way that is not intended.
The test is designed to be run with "--run=1,X" where 'X' is an
interesting test case. Each test uses 'init_repos' to reset the full and
sparse copies of the initial-repo that is created by the first test
case. This also makes it possible to have test cases leave the working
directory or index in unusual states without disturbing later cases.
Signed-off-by: Derrick Stolee <redacted>
---
t/t1092-sparse-checkout-compatibility.sh | 298 +++++++++++++++++++++++
1 file changed, 298 insertions(+)
create mode 100755 t/t1092-sparse-checkout-compatibility.sh
@@ -0,0 +1,298 @@+#!/bin/sh++test_description='compare full workdir to sparse workdir'++../test-lib.sh++test_expect_success'setup''+gitinitinitial-repo&&+(+cdinitial-repo&&+echoa>a&&+echo"after deep">e&&+echo"after folder1">g&&+mkdirfolder1folder2deepx&&+mkdirdeep/deeper1deep/deeper2&&+mkdirdeep/deeper1/deepest&&+echo"after deeper1">deep/e&&+echo"after deepest">deep/deeper1/e&&+cpafolder1&&+cpafolder2&&+cpadeep&&+cpadeep/deeper1&&+cpadeep/deeper2&&+cpadeep/deeper1/deepest&&+gitadd.&&+gitcommit-m"initial commit"&&+gitcheckout-bbase&&+fordirinfolder1folder2deep+do+gitcheckout-bupdate-$dir&&+echo"updated $dir">$dir/a&&+gitcommit-a-m"update $dir"||return1+done&&++gitcheckout-brename-basebase&&+echo>folder1/larger-content<<-\EOF&&+matching+lines+help+inexact+renames+EOF+cpfolder1/larger-contentfolder2/&&+cpfolder1/larger-contentdeep/deeper1/&&+gitadd.&&+gitcommit-m"add interesting rename content"&&++gitcheckout-brename-out-to-outrename-base&&+mvfolder1/afolder2/b&&+mvfolder1/larger-contentfolder2/edited-content&&+echo>>folder2/edited-content&&+gitadd.&&+gitcommit-m"rename folder1/... to folder2/..."&&++gitcheckout-brename-out-to-inrename-base&&+mvfolder1/adeep/deeper1/b&&+mvfolder1/larger-contentdeep/deeper1/edited-content&&+echo>>deep/deeper1/edited-content&&+gitadd.&&+gitcommit-m"rename folder1/... to deep/deeper1/..."&&++gitcheckout-brename-in-to-outrename-base&&+mvdeep/deeper1/afolder1/b&&+mvdeep/deeper1/larger-contentfolder1/edited-content&&+echo>>folder1/edited-content&&+gitadd.&&+gitcommit-m"rename deep/deeper1/... to folder1/..."&&++gitcheckout-bdeepestbase&&+echo"updated deepest">deep/deeper1/deepest/a&&+gitcommit-a-m"update deepest"&&++gitcheckout-fbase&&+gitreset--hard+)+'++init_repos(){+rm-rffull-checkoutsparse-checkoutsparse-index&&++# create repos in initial state+cp-rinitial-repofull-checkout&&+git-Cfull-checkoutreset--hard&&++cp-rinitial-reposparse-checkout&&+git-Csparse-checkoutreset--hard&&+git-Csparse-checkoutsparse-checkoutinit--cone&&++# initialize sparse-checkout definitions+git-Csparse-checkoutsparse-checkoutsetdeep+}++run_on_sparse(){+(+cdsparse-checkout&&+$*>../sparse-checkout-out2>../sparse-checkout-err+)+}++run_on_all(){+(+cdfull-checkout&&+$*>../full-checkout-out2>../full-checkout-err+)&&+run_on_sparse$*+}++test_all_match(){+run_on_all$*&&+test_cmpfull-checkout-outsparse-checkout-out&&+test_cmpfull-checkout-errsparse-checkout-err+}++test_expect_success'status with options''+init_repos&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno&&+run_on_all"touch README.md"&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno&&+test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno+'++test_expect_success'add, commit, checkout''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>$1+EOF+run_on_all"../edit-contents README.md"&&++test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Add README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents README.md"&&++test_all_matchgitadd-A&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Extend README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents deep/newfile"&&++test_all_matchgitstatus--porcelain=v2-uno&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitadd.&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"add deep/newfile"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-+'++test_expect_success'checkout and reset --hard''+init_repos&&++test_all_matchgitcheckoutupdate-folder1&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitcheckoutupdate-deep&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitcheckout-breset-test&&+test_all_matchgitreset--harddeepest&&+test_all_matchgitreset--hardupdate-folder1&&+test_all_matchgitreset--hardupdate-folder2+'++test_expect_success'diff --staged''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>README.md+EOF+run_on_all"../edit-contents"&&++test_all_matchgitdiff&&+test_all_matchgitdiff--staged&&+test_all_matchgitaddREADME.md&&+test_all_matchgitdiff&&+test_all_matchgitdiff--staged+'++test_expect_success'diff with renames''+init_repos&&++forbranchinrename-out-to-outrename-out-to-inrename-in-to-out+do+test_all_matchgitcheckoutrename-base&&+test_all_matchgitcheckout$branch--.&&+test_all_matchgitdiff--staged--no-renames&&+test_all_matchgitdiff--staged--find-renames||return1+done+'++test_expect_success'log with pathspec outside sparse definition''+init_repos&&++test_all_matchgitlog--a&&+test_all_matchgitlog--folder1/a&&+test_all_matchgitlog--folder2/a&&+test_all_matchgitlog--deep/a&&+test_all_matchgitlog--deep/deeper1/a&&+test_all_matchgitlog--deep/deeper1/deepest/a&&++test_all_matchgitcheckoutupdate-folder1&&+test_all_matchgitlog--folder1/a+'++test_expect_success'blame with pathspec inside sparse definition''+init_repos&&++test_all_matchgitblamea&&+test_all_matchgitblamedeep/a&&+test_all_matchgitblamedeep/deeper1/a&&+test_all_matchgitblamedeep/deeper1/deepest/a+'++# TODO: blame currently does not support blaming files outside of the+# sparse definition. It complains that the file doesn't exist locally.+test_expect_failure'blame with pathspec outside sparse definition''+init_repos&&++test_all_matchgitblamefolder1/a&&+test_all_matchgitblamefolder2/a&&+test_all_matchgitblamedeep/deeper2/a&&+test_all_matchgitblamedeep/deeper2/deepest/a+'++# TODO: reset currently does not behave as expected when in a+# sparse-checkout.+test_expect_failure'checkout and reset (mixed)''+init_repos&&++test_all_matchgitcheckout-breset-testupdate-deep&&+test_all_matchgitresetdeepest&&+test_all_matchgitresetupdate-folder1&&+test_all_matchgitresetupdate-folder2+'++test_expect_success'merge''+init_repos&&++test_all_matchgitcheckout-bmergeupdate-deep&&+test_all_matchgitmerge-m"folder1"update-folder1&&+test_all_matchgitrev-parseHEAD^{tree}&&+test_all_matchgitmerge-m"folder2"update-folder2&&+test_all_matchgitrev-parseHEAD^{tree}+'++test_expect_success'merge with outside renames''+init_repos&&++fortypeinout-to-outout-to-inin-to-out+do+test_all_matchgitreset--hard&&+test_all_matchgitcheckout-f-bmerge-$typeupdate-deep&&+test_all_matchgitmerge-m"$type"rename-$type&&+test_all_matchgitrev-parseHEAD^{tree}||return1+done+'++test_expect_success'clean''+init_repos&&++echobogus>>.gitignore&&+run_on_allcp../.gitignore.&&+test_all_matchgitadd.gitignore&&+test_all_matchgitcommit-mignore-bogus-files&&++run_on_sparsemkdirfolder1&&+run_on_alltouchfolder1/bogus&&++test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitclean-f&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitclean-xf&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitclean-xdf&&+test_all_matchgitstatus--porcelain=v2&&++test_path_is_dirsparse-checkout/folder1+'++test_done
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:06:40
From: Derrick Stolee <redacted>
A future feature will want to load the sparse-checkout patterns into a
pattern_list, but the current mechanism to do so is a bit complicated.
This is made difficult due to needing to find the sparse-checkout file
in different ways throughout the codebase.
The logic implemented in the new get_sparse_checkout_patterns() was
duplicated in populate_from_existing_patterns() in unpack-trees.c. Use
the new method instead, keeping the logic around handling the struct
unpack_trees_options.
The callers to get_sparse_checkout_filename() in
builtin/sparse-checkout.c manipulate the sparse-checkout file directly,
so it is not appropriate to replace logic in that file with
get_sparse_checkout_patterns().
Signed-off-by: Derrick Stolee <redacted>
---
builtin/sparse-checkout.c | 5 -----
dir.c | 17 +++++++++++++++++
dir.h | 2 ++
unpack-trees.c | 6 +-----
4 files changed, 20 insertions(+), 10 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:08:42
From: Derrick Stolee <redacted>
It will be helpful to add behavior to index operations that might
trigger an object lookup. Since each index belongs to a specific
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
This will prevent future changes from needing to pass an additional
'struct repository *repo' parameter and instead rely only on the 'struct
index_state *istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache.h | 1 +
repository.c | 4 ++++
2 files changed, 5 insertions(+)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:09:07
From: Derrick Stolee <redacted>
The lazy_init_name_hash() populates a hashset with all filenames and
another with all directories represented in the index. This is run only
if we need to use the hashsets to check for existence or case-folding
renames.
Place trace2 regions where there is already a performance trace.
Signed-off-by: Derrick Stolee <redacted>
---
name-hash.c | 3 +++
1 file changed, 3 insertions(+)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:10:35
From: Derrick Stolee <redacted>
The index has an fsmonitor_dirty bitmap that records which index entries
are "dirty" based on the response from the FSMonitor. If this bitmap
ever grows larger than the index, then there was an error in how it was
constructed, and it was probably a developer's bug.
There are several BUG() statements that are very similar, so replace
these uses with a simpler assert_index_minimum(). Since there is one
caller that uses a custom 'pos' value instead of the bit_size member, we
cannot simplify it too much. However, the error string is identical in
each, so this simplifies things.
The end result is that the code is simpler to read while also preserving
these assertions for developers in the FSMonitor space.
Signed-off-by: Derrick Stolee <redacted>
---
fsmonitor.c | 27 +++++++++++++--------------
1 file changed, 13 insertions(+), 14 deletions(-)
@@ -13,14 +13,19 @@structtrace_keytrace_fsmonitor=TRACE_KEY_INIT(FSMONITOR);+staticvoidassert_index_minimum(structindex_state*istate,size_tpos)+{+if(pos>istate->cache_nr)+BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",+(uintmax_t)pos,istate->cache_nr);+}+staticvoidfsmonitor_ewah_callback(size_tpos,void*is){structindex_state*istate=(structindex_state*)is;structcache_entry*ce;-if(pos>=istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" >= %u)",-(uintmax_t)pos,istate->cache_nr);+assert_index_minimum(istate,pos);ce=istate->cache[pos];ce->ce_flags&=~CE_FSMONITOR_VALID;
@@ -82,10 +87,8 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data,}istate->fsmonitor_dirty=fsmonitor_dirty;-if(!istate->split_index&&-istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+if(!istate->split_index)+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);trace_printf_key(&trace_fsmonitor,"read fsmonitor extension successful");return0;
@@ -110,10 +113,8 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)uint32_tewah_size=0;intfixup=0;-if(!istate->split_index&&-istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+if(!istate->split_index)+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);put_be32(&hdr_version,INDEX_EXTENSION_VERSION2);strbuf_add(sb,&hdr_version,sizeof(uint32_t));
@@ -335,9 +336,7 @@ void tweak_fsmonitor(struct index_state *istate)}/* Mark all previously saved entries as dirty */-if(istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);ewah_each_bit(istate->fsmonitor_dirty,fsmonitor_ewah_callback,istate);refresh_fsmonitor(istate);
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:10:35
From: Derrick Stolee <redacted>
This method will be helpful to use outside of cache-tree.c in a later
feature. The implementation is subtle due to subtree_name_cmp() sorting
by length and then lexicographically.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 6 +++---
cache-tree.h | 2 ++
2 files changed, 5 insertions(+), 3 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-22 13:10:39
From: Derrick Stolee <redacted>
Make the method safer by allocating a cache_tree member for the given
index_state if it is not already present. This is preferrable to a
BUG() statement or returning with an error because future callers will
want to populate an empty cache-tree using this method.
Also drop local variables that are used exactly once and can be found
directly from the 'istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
On Fri, Jan 22, 2021 at 5:04 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
This is based on ds/cache-tree-basics.
Here are a few more cleanups that are vaguely related to the index. I
discovered these while preparing my sparse-index RFC that I intend to send
early next week.
The biggest patch is the final one, which creates a test script for
comparing sparse-checkouts to full checkouts. There are some commands that
do not behave similarly. This script will be the backbone of my testing
strategy for the sparse-index by adding a new mode to compare
sparse-checkouts with the two index types (full and sparse).
UPDATES IN V2
=============
* Fixed duplicated test in t1092.
* Changed the implementation of 'test_region' after I discovered the
negation doesn't work correctly. (I updated the test to use what was in
t0500-progress-display.sh at the last minute before v1, but that
implementation was wrong.) The use of it in t0500-progress-display.sh was
incorrect, as well.
* Updated commit messages to be more informative and have fewer typos.
* I dropped the patch that placed the sparse-checkout patterns in struct
index_state. I'll re-introduce that in time for the actual use of the
member.
You've addressed all my feedback from v1, but it looks like you missed
the pos + 1 changes highlighted by Chris in his review of patch 3.
Oversight?
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 19:59:15
This is based on ds/cache-tree-basics.
Here are a few more cleanups that are vaguely related to the index. I
discovered these while preparing my sparse-index RFC that I intend to send
early next week.
The biggest patch is the final one, which creates a test script for
comparing sparse-checkouts to full checkouts. There are some commands that
do not behave similarly. This script will be the backbone of my testing
strategy for the sparse-index by adding a new mode to compare
sparse-checkouts with the two index types (full and sparse).
UPDATES IN V3
=============
* Callers to cache_tree_update() no longer initialize the cache_tree in
advance.
* Added a patch to update verify_cache() prototype.
* Added missing "pos + 1" in fsmonitor.c.
* Added a BUG() statement when repo->istate->repo is already populated, but
not equal to repo.
* Cleaned up test_region pattern quoting. Thanks, Junio!
Thanks, -Stolee
Derrick Stolee (9):
cache-tree: clean up cache_tree_update()
cache-tree: simplify verify_cache() prototype
cache-tree: extract subtree_pos()
fsmonitor: de-duplicate BUG()s around dirty bits
repository: add repo reference to index_state
name-hash: use trace2 regions for init
sparse-checkout: load sparse-checkout patterns
test-lib: test_region looks for trace2 regions
t1092: test interesting sparse-checkout scenarios
builtin/checkout.c | 3 -
builtin/sparse-checkout.c | 5 -
cache-tree.c | 38 +--
cache-tree.h | 2 +
cache.h | 1 +
dir.c | 17 ++
dir.h | 2 +
fsmonitor.c | 27 +-
name-hash.c | 3 +
repository.c | 6 +
sequencer.c | 3 -
t/t0500-progress-display.sh | 3 +-
t/t1092-sparse-checkout-compatibility.sh | 301 +++++++++++++++++++++++
t/test-lib-functions.sh | 42 ++++
unpack-trees.c | 8 +-
15 files changed, 408 insertions(+), 53 deletions(-)
create mode 100755 t/t1092-sparse-checkout-compatibility.sh
base-commit: a4b6d202caad83c6dc29abe9b17e53a1b3fb54a0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-839%2Fderrickstolee%2Fmore-index-cleanups-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-839/derrickstolee/more-index-cleanups-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/839
Range-diff vs v2:
1: f9dccaed0ac ! 1: bdc8ecca3d2 cache-tree: clean up cache_tree_update()
@@ Commit message
BUG() statement or returning with an error because future callers will
want to populate an empty cache-tree using this method.
- Also drop local variables that are used exactly once and can be found
- directly from the 'istate' parameter.
+ Callers can also remove their conditional allocations of cache_tree.
+
+ Also drop local variables that can be found directly from the 'istate'
+ parameter.
Signed-off-by: Derrick Stolee [off-list ref]
+ ## builtin/checkout.c ##
+@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,
+ }
+ }
+
+- if (!active_cache_tree)
+- active_cache_tree = cache_tree();
+-
+ if (!cache_tree_fully_valid(active_cache_tree))
+ cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
+
+
## cache-tree.c ##
@@ cache-tree.c: static int update_one(struct cache_tree *it,
@@ cache-tree.c: static int update_one(struct cache_tree *it,
trace2_region_leave("cache_tree", "update", the_repository);
trace_performance_leave("cache_tree_update");
if (i < 0)
+@@ cache-tree.c: static int write_index_as_tree_internal(struct object_id *oid,
+ cache_tree_valid = 0;
+ }
+
+- if (!index_state->cache_tree)
+- index_state->cache_tree = cache_tree();
+-
+ if (!cache_tree_valid && cache_tree_update(index_state, flags) < 0)
+ return WRITE_TREE_UNMERGED_INDEX;
+
+
+ ## sequencer.c ##
+@@ sequencer.c: static int do_recursive_merge(struct repository *r,
+
+ static struct object_id *get_cache_tree_oid(struct index_state *istate)
+ {
+- if (!istate->cache_tree)
+- istate->cache_tree = cache_tree();
+-
+ if (!cache_tree_fully_valid(istate->cache_tree))
+ if (cache_tree_update(istate, 0)) {
+ error(_("unable to update cache tree"));
+
+ ## unpack-trees.c ##
+@@ unpack-trees.c: int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
+ if (!ret) {
+ if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
+ cache_tree_verify(the_repository, &o->result);
+- if (!o->result.cache_tree)
+- o->result.cache_tree = cache_tree();
+ if (!cache_tree_fully_valid(o->result.cache_tree))
+ cache_tree_update(&o->result,
+ WRITE_TREE_SILENT |
-: ----------- > 2: 1b8b5680094 cache-tree: simplify verify_cache() prototype
2: 84323e04d08 = 3: 314b6b34f75 cache-tree: extract subtree_pos()
3: 31095f9aa0e ! 4: 4e688d25f8c fsmonitor: de-duplicate BUG()s around dirty bits
@@ Commit message
cannot simplify it too much. However, the error string is identical in
each, so this simplifies things.
+ Be sure to add one when checking if a position if valid, since the
+ minimum is a bound on the expected size.
+
The end result is that the code is simpler to read while also preserving
these assertions for developers in the FSMonitor space.
@@ fsmonitor.c
- if (pos >= istate->cache_nr)
- BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" >= %u)",
- (uintmax_t)pos, istate->cache_nr);
-+ assert_index_minimum(istate, pos);
++ assert_index_minimum(istate, pos + 1);
ce = istate->cache[pos];
ce->ce_flags &= ~CE_FSMONITOR_VALID;
4: a0d89d7a973 ! 5: 6373997e05c repository: add repo reference to index_state
@@ Commit message
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
+ Add a BUG() statement if the repo already has an index, and the index
+ already has a repo, but somehow the index points to a different repo.
+
This will prevent future changes from needing to pass an additional
'struct repository *repo' parameter and instead rely only on the 'struct
index_state *istate' parameter.
@@ repository.c: int repo_read_index(struct repository *repo)
+ /* Complete the double-reference */
+ if (!repo->index->repo)
+ repo->index->repo = repo;
++ else if (repo->index->repo != repo)
++ BUG("repo's index should point back at itself");
+
return read_index_from(repo->index, repo->index_file, repo->gitdir);
}
5: bc092f5c703 = 6: 9b545d7dbec name-hash: use trace2 regions for init
6: 04d1daf7222 = 7: 554cc7647e6 sparse-checkout: load sparse-checkout patterns
7: 8832ce84623 ! 8: b37181bdec4 test-lib: test_region looks for trace2 regions
@@ t/test-lib-functions.sh: test_subcommand () {
+ shift
+ fi
+
-+ grep -e "\"region_enter\".*\"category\":\"$1\",\"label\":\"$2\"" "$3"
++ grep -e '"region_enter".*"category":"'"$1"'","label":"'"$2"\" "$3"
+ exitcode=$?
+
-+ if test $exitcode != $expect_exit
++ if test $exitcode != $expect_exit = 1]
+ then
+ return 1
+ fi
+
-+ grep -e "\"region_leave\".*\"category\":\"$1\",\"label\":\"$2\"" "$3"
++ grep -e '"region_leave".*"category":"'"$1"'","label":"'"$2"\" "$3"
+ exitcode=$?
+
-+ if test $exitcode != $expect_exit
++ if test $exitcode != $expect_exit = 1]
+ then
+ return 1
+ fi
++
++ return 0
+}
8: 984458007ed ! 9: 72f925353d3 t1092: test interesting sparse-checkout scenarios
@@ t/t1092-sparse-checkout-compatibility.sh (new)
+ echo a >a &&
+ echo "after deep" >e &&
+ echo "after folder1" >g &&
++ echo "after x" >z &&
+ mkdir folder1 folder2 deep x &&
+ mkdir deep/deeper1 deep/deeper2 &&
+ mkdir deep/deeper1/deepest &&
@@ t/t1092-sparse-checkout-compatibility.sh (new)
+ echo "after deepest" >deep/deeper1/e &&
+ cp a folder1 &&
+ cp a folder2 &&
++ cp a x &&
+ cp a deep &&
+ cp a deep/deeper1 &&
+ cp a deep/deeper2 &&
+ cp a deep/deeper1/deepest &&
++ cp -r deep/deeper1/deepest deep/deeper2 &&
+ git add . &&
+ git commit -m "initial commit" &&
+ git checkout -b base &&
--
gitgitgadget
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 19:59:18
From: Derrick Stolee <redacted>
This method will be helpful to use outside of cache-tree.c in a later
feature. The implementation is subtle due to subtree_name_cmp() sorting
by length and then lexicographically.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 6 +++---
cache-tree.h | 2 ++
2 files changed, 5 insertions(+), 3 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 19:59:35
From: Derrick Stolee <redacted>
The verify_cache() method takes an array of cache entries and a count,
but these are always provided directly from a struct index_state. Use
a pointer to the full structure instead.
There is a subtle point when istate->cache_nr is zero that subtracting
one will underflow. This triggers a failure in t0000-basic.sh, among
others. Use "i + 1 < istate->cache_nr" to avoid these strange
comparisons. Convert i to be unsigned as well, which also removes the
potential signed overflow in the unlikely case that cache_nr is over 2.1
billion entries. The 'funny' variable has a maximum value of 11, so
making it unsigned does not change anything of importance.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
@@ -151,16 +151,15 @@ void cache_tree_invalidate_path(struct index_state *istate, const char *path)istate->cache_changed|=CACHE_TREE_CHANGED;}-staticintverify_cache(structcache_entry**cache,-intentries,intflags)+staticintverify_cache(structindex_state*istate,intflags){-inti,funny;+unsignedi,funny;intsilent=flags&WRITE_TREE_SILENT;/* Verify that the tree is merged */funny=0;-for(i=0;i<entries;i++){-conststructcache_entry*ce=cache[i];+for(i=0;i<istate->cache_nr;i++){+conststructcache_entry*ce=istate->cache[i];if(ce_stage(ce)){if(silent)return-1;
@@ -180,13 +179,13 @@ static int verify_cache(struct cache_entry **cache,*stage0entries.*/funny=0;-for(i=0;i<entries-1;i++){+for(i=0;i+1<istate->cache_nr;i++){/* path/file always comes after path because of the way*thecacheissorted.Alsopathcanappearonlyonce,*whichmeansconflictingonewouldimmediatelyfollow.*/-conststructcache_entry*this_ce=cache[i];-conststructcache_entry*next_ce=cache[i+1];+conststructcache_entry*this_ce=istate->cache[i];+conststructcache_entry*next_ce=istate->cache[i+1];constchar*this_name=this_ce->name;constchar*next_name=next_ce->name;intthis_len=ce_namelen(this_ce);
@@ -438,7 +437,7 @@ int cache_tree_update(struct index_state *istate, int flags){intskip,i;-i=verify_cache(istate->cache,istate->cache_nr,flags);+i=verify_cache(istate,flags);if(i)returni;
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 19:59:38
From: Derrick Stolee <redacted>
The index has an fsmonitor_dirty bitmap that records which index entries
are "dirty" based on the response from the FSMonitor. If this bitmap
ever grows larger than the index, then there was an error in how it was
constructed, and it was probably a developer's bug.
There are several BUG() statements that are very similar, so replace
these uses with a simpler assert_index_minimum(). Since there is one
caller that uses a custom 'pos' value instead of the bit_size member, we
cannot simplify it too much. However, the error string is identical in
each, so this simplifies things.
Be sure to add one when checking if a position if valid, since the
minimum is a bound on the expected size.
The end result is that the code is simpler to read while also preserving
these assertions for developers in the FSMonitor space.
Signed-off-by: Derrick Stolee <redacted>
---
fsmonitor.c | 27 +++++++++++++--------------
1 file changed, 13 insertions(+), 14 deletions(-)
@@ -13,14 +13,19 @@structtrace_keytrace_fsmonitor=TRACE_KEY_INIT(FSMONITOR);+staticvoidassert_index_minimum(structindex_state*istate,size_tpos)+{+if(pos>istate->cache_nr)+BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",+(uintmax_t)pos,istate->cache_nr);+}+staticvoidfsmonitor_ewah_callback(size_tpos,void*is){structindex_state*istate=(structindex_state*)is;structcache_entry*ce;-if(pos>=istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" >= %u)",-(uintmax_t)pos,istate->cache_nr);+assert_index_minimum(istate,pos+1);ce=istate->cache[pos];ce->ce_flags&=~CE_FSMONITOR_VALID;
@@ -82,10 +87,8 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data,}istate->fsmonitor_dirty=fsmonitor_dirty;-if(!istate->split_index&&-istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+if(!istate->split_index)+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);trace_printf_key(&trace_fsmonitor,"read fsmonitor extension successful");return0;
@@ -110,10 +113,8 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)uint32_tewah_size=0;intfixup=0;-if(!istate->split_index&&-istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+if(!istate->split_index)+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);put_be32(&hdr_version,INDEX_EXTENSION_VERSION2);strbuf_add(sb,&hdr_version,sizeof(uint32_t));
@@ -335,9 +336,7 @@ void tweak_fsmonitor(struct index_state *istate)}/* Mark all previously saved entries as dirty */-if(istate->fsmonitor_dirty->bit_size>istate->cache_nr)-BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",-(uintmax_t)istate->fsmonitor_dirty->bit_size,istate->cache_nr);+assert_index_minimum(istate,istate->fsmonitor_dirty->bit_size);ewah_each_bit(istate->fsmonitor_dirty,fsmonitor_ewah_callback,istate);refresh_fsmonitor(istate);
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 19:59:42
From: Derrick Stolee <redacted>
Make the method safer by allocating a cache_tree member for the given
index_state if it is not already present. This is preferrable to a
BUG() statement or returning with an error because future callers will
want to populate an empty cache-tree using this method.
Callers can also remove their conditional allocations of cache_tree.
Also drop local variables that can be found directly from the 'istate'
parameter.
Signed-off-by: Derrick Stolee <redacted>
---
builtin/checkout.c | 3 ---
cache-tree.c | 17 +++++++++--------
sequencer.c | 3 ---
unpack-trees.c | 2 --
4 files changed, 9 insertions(+), 16 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 20:00:00
From: Derrick Stolee <redacted>
It will be helpful to add behavior to index operations that might
trigger an object lookup. Since each index belongs to a specific
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
Add a BUG() statement if the repo already has an index, and the index
already has a repo, but somehow the index points to a different repo.
This will prevent future changes from needing to pass an additional
'struct repository *repo' parameter and instead rely only on the 'struct
index_state *istate' parameter.
Signed-off-by: Derrick Stolee <redacted>
---
cache.h | 1 +
repository.c | 6 ++++++
2 files changed, 7 insertions(+)
@@ -264,6 +264,12 @@ int repo_read_index(struct repository *repo)if(!repo->index)repo->index=xcalloc(1,sizeof(*repo->index));+/* Complete the double-reference */+if(!repo->index->repo)+repo->index->repo=repo;+elseif(repo->index->repo!=repo)+BUG("repo's index should point back at itself");+returnread_index_from(repo->index,repo->index_file,repo->gitdir);}
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 20:00:16
From: Derrick Stolee <redacted>
The lazy_init_name_hash() populates a hashset with all filenames and
another with all directories represented in the index. This is run only
if we need to use the hashsets to check for existence or case-folding
renames.
Place trace2 regions where there is already a performance trace.
Signed-off-by: Derrick Stolee <redacted>
---
name-hash.c | 3 +++
1 file changed, 3 insertions(+)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 20:00:34
From: Derrick Stolee <redacted>
Most test cases can verify Git's behavior using input/output
expectations or changes to the .git directory. However, sometimes we
want to check that Git did or did not run a certain section of code.
This is particularly important for performance-only features that we
want to ensure have been enabled in certain cases.
Add a new 'test_region' function that checks if a trace2 region was
entered and left in a given trace2 event log.
There is one existing test (t0500-progress-display.sh) that performs
this check already, so use the helper function instead. Note that this
changes the expectations slightly. The old test (incorrectly) used two
patterns for the 'grep' invocation, but this performs an OR of the
patterns, not an AND. This means that as long as one region_enter event
was logged, the test would succeed, even if it was not due to the
progress category.
More uses will be added in a later change.
t6423-merge-rename-directories.sh also greps for region_enter lines, but
it verifies the number of such lines, which is not the same as an
existence check.
Signed-off-by: Derrick Stolee <redacted>
---
t/t0500-progress-display.sh | 3 +--
t/test-lib-functions.sh | 42 +++++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 2 deletions(-)
@@ -1655,3 +1655,45 @@ test_subcommand () {grep"\[$expr\]"fi}++# Check that the given command was invoked as part of the+# trace2-format trace on stdin.+#+# test_region [!] <category> <label> git <command> <args>...+#+# For example, to look for trace2_region_enter("index", "do_read_index", repo)+# in an invocation of "git checkout HEAD~1", run+#+# GIT_TRACE2_EVENT="$(pwd)/trace.txt" GIT_TRACE2_EVENT_NESTING=10 \+# git checkout HEAD~1 &&+# test_region index do_read_index <trace.txt+#+# If the first parameter passed is !, this instead checks that+# the given region was not entered.+#+test_region(){+localexpect_exit=0+iftest"$1"="!"+then+expect_exit=1+shift+fi++grep-e'"region_enter".*"category":"'"$1"'","label":"'"$2"\""$3"+exitcode=$?++iftest$exitcode!=$expect_exit=1]+then+return1+fi++grep-e'"region_leave".*"category":"'"$1"'","label":"'"$2"\""$3"+exitcode=$?++iftest$exitcode!=$expect_exit=1]+then+return1+fi++return0+}
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 20:01:43
From: Derrick Stolee <redacted>
A future feature will want to load the sparse-checkout patterns into a
pattern_list, but the current mechanism to do so is a bit complicated.
This is made difficult due to needing to find the sparse-checkout file
in different ways throughout the codebase.
The logic implemented in the new get_sparse_checkout_patterns() was
duplicated in populate_from_existing_patterns() in unpack-trees.c. Use
the new method instead, keeping the logic around handling the struct
unpack_trees_options.
The callers to get_sparse_checkout_filename() in
builtin/sparse-checkout.c manipulate the sparse-checkout file directly,
so it is not appropriate to replace logic in that file with
get_sparse_checkout_patterns().
Signed-off-by: Derrick Stolee <redacted>
---
builtin/sparse-checkout.c | 5 -----
dir.c | 17 +++++++++++++++++
dir.h | 2 ++
unpack-trees.c | 6 +-----
4 files changed, 20 insertions(+), 10 deletions(-)
From: Derrick Stolee via GitGitGadget <hidden> Date: 2021-01-23 20:01:56
From: Derrick Stolee <redacted>
These also document some behaviors that differ from a full checkout, and
possibly in a way that is not intended.
The test is designed to be run with "--run=1,X" where 'X' is an
interesting test case. Each test uses 'init_repos' to reset the full and
sparse copies of the initial-repo that is created by the first test
case. This also makes it possible to have test cases leave the working
directory or index in unusual states without disturbing later cases.
Signed-off-by: Derrick Stolee <redacted>
---
t/t1092-sparse-checkout-compatibility.sh | 301 +++++++++++++++++++++++
1 file changed, 301 insertions(+)
create mode 100755 t/t1092-sparse-checkout-compatibility.sh
@@ -0,0 +1,301 @@+#!/bin/sh++test_description='compare full workdir to sparse workdir'++../test-lib.sh++test_expect_success'setup''+gitinitinitial-repo&&+(+cdinitial-repo&&+echoa>a&&+echo"after deep">e&&+echo"after folder1">g&&+echo"after x">z&&+mkdirfolder1folder2deepx&&+mkdirdeep/deeper1deep/deeper2&&+mkdirdeep/deeper1/deepest&&+echo"after deeper1">deep/e&&+echo"after deepest">deep/deeper1/e&&+cpafolder1&&+cpafolder2&&+cpax&&+cpadeep&&+cpadeep/deeper1&&+cpadeep/deeper2&&+cpadeep/deeper1/deepest&&+cp-rdeep/deeper1/deepestdeep/deeper2&&+gitadd.&&+gitcommit-m"initial commit"&&+gitcheckout-bbase&&+fordirinfolder1folder2deep+do+gitcheckout-bupdate-$dir&&+echo"updated $dir">$dir/a&&+gitcommit-a-m"update $dir"||return1+done&&++gitcheckout-brename-basebase&&+echo>folder1/larger-content<<-\EOF&&+matching+lines+help+inexact+renames+EOF+cpfolder1/larger-contentfolder2/&&+cpfolder1/larger-contentdeep/deeper1/&&+gitadd.&&+gitcommit-m"add interesting rename content"&&++gitcheckout-brename-out-to-outrename-base&&+mvfolder1/afolder2/b&&+mvfolder1/larger-contentfolder2/edited-content&&+echo>>folder2/edited-content&&+gitadd.&&+gitcommit-m"rename folder1/... to folder2/..."&&++gitcheckout-brename-out-to-inrename-base&&+mvfolder1/adeep/deeper1/b&&+mvfolder1/larger-contentdeep/deeper1/edited-content&&+echo>>deep/deeper1/edited-content&&+gitadd.&&+gitcommit-m"rename folder1/... to deep/deeper1/..."&&++gitcheckout-brename-in-to-outrename-base&&+mvdeep/deeper1/afolder1/b&&+mvdeep/deeper1/larger-contentfolder1/edited-content&&+echo>>folder1/edited-content&&+gitadd.&&+gitcommit-m"rename deep/deeper1/... to folder1/..."&&++gitcheckout-bdeepestbase&&+echo"updated deepest">deep/deeper1/deepest/a&&+gitcommit-a-m"update deepest"&&++gitcheckout-fbase&&+gitreset--hard+)+'++init_repos(){+rm-rffull-checkoutsparse-checkoutsparse-index&&++# create repos in initial state+cp-rinitial-repofull-checkout&&+git-Cfull-checkoutreset--hard&&++cp-rinitial-reposparse-checkout&&+git-Csparse-checkoutreset--hard&&+git-Csparse-checkoutsparse-checkoutinit--cone&&++# initialize sparse-checkout definitions+git-Csparse-checkoutsparse-checkoutsetdeep+}++run_on_sparse(){+(+cdsparse-checkout&&+$*>../sparse-checkout-out2>../sparse-checkout-err+)+}++run_on_all(){+(+cdfull-checkout&&+$*>../full-checkout-out2>../full-checkout-err+)&&+run_on_sparse$*+}++test_all_match(){+run_on_all$*&&+test_cmpfull-checkout-outsparse-checkout-out&&+test_cmpfull-checkout-errsparse-checkout-err+}++test_expect_success'status with options''+init_repos&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno&&+run_on_all"touch README.md"&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno&&+test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno+'++test_expect_success'add, commit, checkout''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>$1+EOF+run_on_all"../edit-contents README.md"&&++test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Add README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents README.md"&&++test_all_matchgitadd-A&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Extend README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents deep/newfile"&&++test_all_matchgitstatus--porcelain=v2-uno&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitadd.&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"add deep/newfile"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-+'++test_expect_success'checkout and reset --hard''+init_repos&&++test_all_matchgitcheckoutupdate-folder1&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitcheckoutupdate-deep&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitcheckout-breset-test&&+test_all_matchgitreset--harddeepest&&+test_all_matchgitreset--hardupdate-folder1&&+test_all_matchgitreset--hardupdate-folder2+'++test_expect_success'diff --staged''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>README.md+EOF+run_on_all"../edit-contents"&&++test_all_matchgitdiff&&+test_all_matchgitdiff--staged&&+test_all_matchgitaddREADME.md&&+test_all_matchgitdiff&&+test_all_matchgitdiff--staged+'++test_expect_success'diff with renames''+init_repos&&++forbranchinrename-out-to-outrename-out-to-inrename-in-to-out+do+test_all_matchgitcheckoutrename-base&&+test_all_matchgitcheckout$branch--.&&+test_all_matchgitdiff--staged--no-renames&&+test_all_matchgitdiff--staged--find-renames||return1+done+'++test_expect_success'log with pathspec outside sparse definition''+init_repos&&++test_all_matchgitlog--a&&+test_all_matchgitlog--folder1/a&&+test_all_matchgitlog--folder2/a&&+test_all_matchgitlog--deep/a&&+test_all_matchgitlog--deep/deeper1/a&&+test_all_matchgitlog--deep/deeper1/deepest/a&&++test_all_matchgitcheckoutupdate-folder1&&+test_all_matchgitlog--folder1/a+'++test_expect_success'blame with pathspec inside sparse definition''+init_repos&&++test_all_matchgitblamea&&+test_all_matchgitblamedeep/a&&+test_all_matchgitblamedeep/deeper1/a&&+test_all_matchgitblamedeep/deeper1/deepest/a+'++# TODO: blame currently does not support blaming files outside of the+# sparse definition. It complains that the file doesn't exist locally.+test_expect_failure'blame with pathspec outside sparse definition''+init_repos&&++test_all_matchgitblamefolder1/a&&+test_all_matchgitblamefolder2/a&&+test_all_matchgitblamedeep/deeper2/a&&+test_all_matchgitblamedeep/deeper2/deepest/a+'++# TODO: reset currently does not behave as expected when in a+# sparse-checkout.+test_expect_failure'checkout and reset (mixed)''+init_repos&&++test_all_matchgitcheckout-breset-testupdate-deep&&+test_all_matchgitresetdeepest&&+test_all_matchgitresetupdate-folder1&&+test_all_matchgitresetupdate-folder2+'++test_expect_success'merge''+init_repos&&++test_all_matchgitcheckout-bmergeupdate-deep&&+test_all_matchgitmerge-m"folder1"update-folder1&&+test_all_matchgitrev-parseHEAD^{tree}&&+test_all_matchgitmerge-m"folder2"update-folder2&&+test_all_matchgitrev-parseHEAD^{tree}+'++test_expect_success'merge with outside renames''+init_repos&&++fortypeinout-to-outout-to-inin-to-out+do+test_all_matchgitreset--hard&&+test_all_matchgitcheckout-f-bmerge-$typeupdate-deep&&+test_all_matchgitmerge-m"$type"rename-$type&&+test_all_matchgitrev-parseHEAD^{tree}||return1+done+'++test_expect_success'clean''+init_repos&&++echobogus>>.gitignore&&+run_on_allcp../.gitignore.&&+test_all_matchgitadd.gitignore&&+test_all_matchgitcommit-mignore-bogus-files&&++run_on_sparsemkdirfolder1&&+run_on_alltouchfolder1/bogus&&++test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitclean-f&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitclean-xf&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitclean-xdf&&+test_all_matchgitstatus--porcelain=v2&&++test_path_is_dirsparse-checkout/folder1+'++test_done
On Sat, Jan 23, 2021 at 11:58 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
From: Derrick Stolee <redacted>
The verify_cache() method takes an array of cache entries and a count,
but these are always provided directly from a struct index_state. Use
a pointer to the full structure instead.
There is a subtle point when istate->cache_nr is zero that subtracting
one will underflow. This triggers a failure in t0000-basic.sh, among
others. Use "i + 1 < istate->cache_nr" to avoid these strange
comparisons. Convert i to be unsigned as well, which also removes the
potential signed overflow in the unlikely case that cache_nr is over 2.1
billion entries. The 'funny' variable has a maximum value of 11, so
AND a minimum value of 0 (which is important for the type change to be valid).
quoted hunk
making it unsigned does not change anything of importance.
Signed-off-by: Derrick Stolee <redacted>
---
cache-tree.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
@@ -151,16 +151,15 @@ void cache_tree_invalidate_path(struct index_state *istate, const char *path)istate->cache_changed|=CACHE_TREE_CHANGED;}-staticintverify_cache(structcache_entry**cache,-intentries,intflags)+staticintverify_cache(structindex_state*istate,intflags){-inti,funny;+unsignedi,funny;intsilent=flags&WRITE_TREE_SILENT;/* Verify that the tree is merged */funny=0;-for(i=0;i<entries;i++){-conststructcache_entry*ce=cache[i];+for(i=0;i<istate->cache_nr;i++){+conststructcache_entry*ce=istate->cache[i];if(ce_stage(ce)){if(silent)return-1;
@@ -180,13 +179,13 @@ static int verify_cache(struct cache_entry **cache,*stage0entries.*/funny=0;-for(i=0;i<entries-1;i++){+for(i=0;i+1<istate->cache_nr;i++){/* path/file always comes after path because of the way*thecacheissorted.Alsopathcanappearonlyonce,*whichmeansconflictingonewouldimmediatelyfollow.*/-conststructcache_entry*this_ce=cache[i];-conststructcache_entry*next_ce=cache[i+1];+conststructcache_entry*this_ce=istate->cache[i];+conststructcache_entry*next_ce=istate->cache[i+1];constchar*this_name=this_ce->name;constchar*next_name=next_ce->name;intthis_len=ce_namelen(this_ce);
@@ -438,7 +437,7 @@ int cache_tree_update(struct index_state *istate, int flags){intskip,i;-i=verify_cache(istate->cache,istate->cache_nr,flags);+i=verify_cache(istate,flags);if(i)returni;--
gitgitgadget
Makes sense. Thanks for explaining the i + 1 < istate->cache_nr bit
in the commit message; made it easier to read through quickly. I'm
curious if it deserves a comment in the code too, since it does feel
slightly unusual.
On Sat, Jan 23, 2021 at 11:58 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
This is based on ds/cache-tree-basics.
Here are a few more cleanups that are vaguely related to the index. I
discovered these while preparing my sparse-index RFC that I intend to send
early next week.
The biggest patch is the final one, which creates a test script for
comparing sparse-checkouts to full checkouts. There are some commands that
do not behave similarly. This script will be the backbone of my testing
strategy for the sparse-index by adding a new mode to compare
sparse-checkouts with the two index types (full and sparse).
UPDATES IN V3
=============
* Callers to cache_tree_update() no longer initialize the cache_tree in
advance.
* Added a patch to update verify_cache() prototype.
* Added missing "pos + 1" in fsmonitor.c.
* Added a BUG() statement when repo->istate->repo is already populated, but
not equal to repo.
* Cleaned up test_region pattern quoting. Thanks, Junio!
Thanks, -Stolee
Derrick Stolee (9):
cache-tree: clean up cache_tree_update()
cache-tree: simplify verify_cache() prototype
cache-tree: extract subtree_pos()
fsmonitor: de-duplicate BUG()s around dirty bits
repository: add repo reference to index_state
name-hash: use trace2 regions for init
sparse-checkout: load sparse-checkout patterns
test-lib: test_region looks for trace2 regions
t1092: test interesting sparse-checkout scenarios
builtin/checkout.c | 3 -
builtin/sparse-checkout.c | 5 -
cache-tree.c | 38 +--
cache-tree.h | 2 +
cache.h | 1 +
dir.c | 17 ++
dir.h | 2 +
fsmonitor.c | 27 +-
name-hash.c | 3 +
repository.c | 6 +
sequencer.c | 3 -
t/t0500-progress-display.sh | 3 +-
t/t1092-sparse-checkout-compatibility.sh | 301 +++++++++++++++++++++++
t/test-lib-functions.sh | 42 ++++
unpack-trees.c | 8 +-
15 files changed, 408 insertions(+), 53 deletions(-)
create mode 100755 t/t1092-sparse-checkout-compatibility.sh
base-commit: a4b6d202caad83c6dc29abe9b17e53a1b3fb54a0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-839%2Fderrickstolee%2Fmore-index-cleanups-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-839/derrickstolee/more-index-cleanups-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/839
Range-diff vs v2:
1: f9dccaed0ac ! 1: bdc8ecca3d2 cache-tree: clean up cache_tree_update()
@@ Commit message
BUG() statement or returning with an error because future callers will
want to populate an empty cache-tree using this method.
- Also drop local variables that are used exactly once and can be found
- directly from the 'istate' parameter.
+ Callers can also remove their conditional allocations of cache_tree.
+
+ Also drop local variables that can be found directly from the 'istate'
+ parameter.
Signed-off-by: Derrick Stolee [off-list ref]
+ ## builtin/checkout.c ##
+@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,
+ }
+ }
+
+- if (!active_cache_tree)
+- active_cache_tree = cache_tree();
+-
+ if (!cache_tree_fully_valid(active_cache_tree))
+ cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
+
+
## cache-tree.c ##
@@ cache-tree.c: static int update_one(struct cache_tree *it,
@@ cache-tree.c: static int update_one(struct cache_tree *it,
trace2_region_leave("cache_tree", "update", the_repository);
trace_performance_leave("cache_tree_update");
if (i < 0)
+@@ cache-tree.c: static int write_index_as_tree_internal(struct object_id *oid,
+ cache_tree_valid = 0;
+ }
+
+- if (!index_state->cache_tree)
+- index_state->cache_tree = cache_tree();
+-
+ if (!cache_tree_valid && cache_tree_update(index_state, flags) < 0)
+ return WRITE_TREE_UNMERGED_INDEX;
+
+
+ ## sequencer.c ##
+@@ sequencer.c: static int do_recursive_merge(struct repository *r,
+
+ static struct object_id *get_cache_tree_oid(struct index_state *istate)
+ {
+- if (!istate->cache_tree)
+- istate->cache_tree = cache_tree();
+-
+ if (!cache_tree_fully_valid(istate->cache_tree))
+ if (cache_tree_update(istate, 0)) {
+ error(_("unable to update cache tree"));
+
+ ## unpack-trees.c ##
+@@ unpack-trees.c: int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
+ if (!ret) {
+ if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
+ cache_tree_verify(the_repository, &o->result);
+- if (!o->result.cache_tree)
+- o->result.cache_tree = cache_tree();
+ if (!cache_tree_fully_valid(o->result.cache_tree))
+ cache_tree_update(&o->result,
+ WRITE_TREE_SILENT |
-: ----------- > 2: 1b8b5680094 cache-tree: simplify verify_cache() prototype
2: 84323e04d08 = 3: 314b6b34f75 cache-tree: extract subtree_pos()
3: 31095f9aa0e ! 4: 4e688d25f8c fsmonitor: de-duplicate BUG()s around dirty bits
@@ Commit message
cannot simplify it too much. However, the error string is identical in
each, so this simplifies things.
+ Be sure to add one when checking if a position if valid, since the
+ minimum is a bound on the expected size.
+
The end result is that the code is simpler to read while also preserving
these assertions for developers in the FSMonitor space.
@@ fsmonitor.c
- if (pos >= istate->cache_nr)
- BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" >= %u)",
- (uintmax_t)pos, istate->cache_nr);
-+ assert_index_minimum(istate, pos);
++ assert_index_minimum(istate, pos + 1);
ce = istate->cache[pos];
ce->ce_flags &= ~CE_FSMONITOR_VALID;
4: a0d89d7a973 ! 5: 6373997e05c repository: add repo reference to index_state
@@ Commit message
repository, add a 'repo' pointer to struct index_state that allows
access to this repository.
+ Add a BUG() statement if the repo already has an index, and the index
+ already has a repo, but somehow the index points to a different repo.
+
This will prevent future changes from needing to pass an additional
'struct repository *repo' parameter and instead rely only on the 'struct
index_state *istate' parameter.
@@ repository.c: int repo_read_index(struct repository *repo)
+ /* Complete the double-reference */
+ if (!repo->index->repo)
+ repo->index->repo = repo;
++ else if (repo->index->repo != repo)
++ BUG("repo's index should point back at itself");
+
return read_index_from(repo->index, repo->index_file, repo->gitdir);
}
5: bc092f5c703 = 6: 9b545d7dbec name-hash: use trace2 regions for init
6: 04d1daf7222 = 7: 554cc7647e6 sparse-checkout: load sparse-checkout patterns
7: 8832ce84623 ! 8: b37181bdec4 test-lib: test_region looks for trace2 regions
@@ t/test-lib-functions.sh: test_subcommand () {
+ shift
+ fi
+
-+ grep -e "\"region_enter\".*\"category\":\"$1\",\"label\":\"$2\"" "$3"
++ grep -e '"region_enter".*"category":"'"$1"'","label":"'"$2"\" "$3"
+ exitcode=$?
+
-+ if test $exitcode != $expect_exit
++ if test $exitcode != $expect_exit = 1]
I don't understand this change. Is it even valid code? What does it mean?
+ then
+ return 1
+ fi
+
-+ grep -e "\"region_leave\".*\"category\":\"$1\",\"label\":\"$2\"" "$3"
++ grep -e '"region_leave".*"category":"'"$1"'","label":"'"$2"\" "$3"
+ exitcode=$?
+
-+ if test $exitcode != $expect_exit
++ if test $exitcode != $expect_exit = 1]
Same comment.
+ then
+ return 1
+ fi
++
++ return 0
+}
8: 984458007ed ! 9: 72f925353d3 t1092: test interesting sparse-checkout scenarios
@@ t/t1092-sparse-checkout-compatibility.sh (new)
+ echo a >a &&
+ echo "after deep" >e &&
+ echo "after folder1" >g &&
++ echo "after x" >z &&
+ mkdir folder1 folder2 deep x &&
+ mkdir deep/deeper1 deep/deeper2 &&
+ mkdir deep/deeper1/deepest &&
@@ t/t1092-sparse-checkout-compatibility.sh (new)
+ echo "after deepest" >deep/deeper1/e &&
+ cp a folder1 &&
+ cp a folder2 &&
++ cp a x &&
+ cp a deep &&
+ cp a deep/deeper1 &&
+ cp a deep/deeper2 &&
+ cp a deep/deeper1/deepest &&
++ cp -r deep/deeper1/deepest deep/deeper2 &&
+ git add . &&
+ git commit -m "initial commit" &&
+ git checkout -b base &&
Having read the previous rounds, the rest of the range-diff looks good
to me; I sent out separate comments on the new patch.
On Sat, Jan 23, 2021 at 11:58 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted
- for (i = 0; i < entries - 1; i++) {
+ for (i = 0; i + 1 < istate->cache_nr; i++) {
/* path/file always comes after path because of the way
* the cache is sorted. Also path can appear only once,
* which means conflicting one would immediately follow.
*/
- const struct cache_entry *this_ce = cache[i];
- const struct cache_entry *next_ce = cache[i + 1];
+ const struct cache_entry *this_ce = istate->cache[i];
+ const struct cache_entry *next_ce = istate->cache[i + 1];
const char *this_name = this_ce->name;
const char *next_name = next_ce->name;
int this_len = ce_namelen(this_ce);
Makes sense. Thanks for explaining the i + 1 < istate->cache_nr bit
in the commit message; made it easier to read through quickly. I'm
curious if it deserves a comment in the code too, since it does feel
slightly unusual.
I would argue that "i + 1 < N" is a more natural way to write this,
because we use "i + 1" as an index, so we want to ensure the index
we are about to use is within range. "i < N - 1" is the backwards
way to write that statement.
Thanks,
-Stolee
On 1/23/2021 2:58 PM, Derrick Stolee via GitGitGadget wrote:
...
+ if test $exitcode != $expect_exit = 1]
...
+ if test $exitcode != $expect_exit = 1]
As Elijah pointed out, these lines are bogus. I'm not sure how
they passed the tests without failure, but here is a replacement
for this patch:
--- >8 ---
From ff15d509b89edd4830d85d53cea3079a6b0c1c08 Mon Sep 17 00:00:00 2001
From: Derrick Stolee <redacted>
Date: Mon, 11 Jan 2021 08:53:09 -0500
Subject: [PATCH 8/9] test-lib: test_region looks for trace2 regions
Most test cases can verify Git's behavior using input/output
expectations or changes to the .git directory. However, sometimes we
want to check that Git did or did not run a certain section of code.
This is particularly important for performance-only features that we
want to ensure have been enabled in certain cases.
Add a new 'test_region' function that checks if a trace2 region was
entered and left in a given trace2 event log.
There is one existing test (t0500-progress-display.sh) that performs
this check already, so use the helper function instead. Note that this
changes the expectations slightly. The old test (incorrectly) used two
patterns for the 'grep' invocation, but this performs an OR of the
patterns, not an AND. This means that as long as one region_enter event
was logged, the test would succeed, even if it was not due to the
progress category.
More uses will be added in a later change.
t6423-merge-rename-directories.sh also greps for region_enter lines, but
it verifies the number of such lines, which is not the same as an
existence check.
Signed-off-by: Derrick Stolee <redacted>
---
t/t0500-progress-display.sh | 3 +--
t/test-lib-functions.sh | 42 +++++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 2 deletions(-)
@@ -1655,3 +1655,45 @@ test_subcommand () {grep"\[$expr\]"fi}++# Check that the given command was invoked as part of the+# trace2-format trace on stdin.+#+# test_region [!] <category> <label> git <command> <args>...+#+# For example, to look for trace2_region_enter("index", "do_read_index", repo)+# in an invocation of "git checkout HEAD~1", run+#+# GIT_TRACE2_EVENT="$(pwd)/trace.txt" GIT_TRACE2_EVENT_NESTING=10 \+# git checkout HEAD~1 &&+# test_region index do_read_index <trace.txt+#+# If the first parameter passed is !, this instead checks that+# the given region was not entered.+#+test_region(){+localexpect_exit=0+iftest"$1"="!"+then+expect_exit=1+shift+fi++grep-e'"region_enter".*"category":"'"$1"'","label":"'"$2"\""$3"+exitcode=$?++iftest$exitcode!=$expect_exit+then+return1+fi++grep-e'"region_leave".*"category":"'"$1"'","label":"'"$2"\""$3"+exitcode=$?++iftest$exitcode!=$expect_exit+then+return1+fi++return0+}
On Sat, Jan 23, 2021 at 1:02 PM Derrick Stolee [off-list ref] wrote:
On 1/23/2021 3:24 PM, Elijah Newren wrote:
quoted
On Sat, Jan 23, 2021 at 11:58 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
quoted
- for (i = 0; i < entries - 1; i++) {
+ for (i = 0; i + 1 < istate->cache_nr; i++) {
/* path/file always comes after path because of the way
* the cache is sorted. Also path can appear only once,
* which means conflicting one would immediately follow.
*/
- const struct cache_entry *this_ce = cache[i];
- const struct cache_entry *next_ce = cache[i + 1];
+ const struct cache_entry *this_ce = istate->cache[i];
+ const struct cache_entry *next_ce = istate->cache[i + 1];
const char *this_name = this_ce->name;
const char *next_name = next_ce->name;
int this_len = ce_namelen(this_ce);
Makes sense. Thanks for explaining the i + 1 < istate->cache_nr bit
in the commit message; made it easier to read through quickly. I'm
curious if it deserves a comment in the code too, since it does feel
slightly unusual.
I would argue that "i + 1 < N" is a more natural way to write this,
because we use "i + 1" as an index, so we want to ensure the index
we are about to use is within range. "i < N - 1" is the backwards
way to write that statement.
Oh, right, I think I was reading too quickly and assuming one thing in
my head (about what the code was going to do), and forgetting that
assumption when I got to the actual code. Sorry about that; I agree
with you, so ignore my previous comment.
On Sat, Jan 23, 2021 at 11:58 AM Derrick Stolee via GitGitGadget
[off-list ref] wrote:
From: Derrick Stolee <redacted>
These also document some behaviors that differ from a full checkout, and
possibly in a way that is not intended.
The test is designed to be run with "--run=1,X" where 'X' is an
interesting test case. Each test uses 'init_repos' to reset the full and
sparse copies of the initial-repo that is created by the first test
case. This also makes it possible to have test cases leave the working
directory or index in unusual states without disturbing later cases.
Signed-off-by: Derrick Stolee <redacted>
---
t/t1092-sparse-checkout-compatibility.sh | 301 +++++++++++++++++++++++
1 file changed, 301 insertions(+)
create mode 100755 t/t1092-sparse-checkout-compatibility.sh
@@ -0,0 +1,301 @@+#!/bin/sh++test_description='compare full workdir to sparse workdir'++../test-lib.sh++test_expect_success'setup''+gitinitinitial-repo&&+(+cdinitial-repo&&+echoa>a&&+echo"after deep">e&&+echo"after folder1">g&&+echo"after x">z&&+mkdirfolder1folder2deepx&&+mkdirdeep/deeper1deep/deeper2&&+mkdirdeep/deeper1/deepest&&+echo"after deeper1">deep/e&&+echo"after deepest">deep/deeper1/e&&+cpafolder1&&+cpafolder2&&+cpax&&+cpadeep&&+cpadeep/deeper1&&+cpadeep/deeper2&&+cpadeep/deeper1/deepest&&+cp-rdeep/deeper1/deepestdeep/deeper2&&+gitadd.&&+gitcommit-m"initial commit"&&+gitcheckout-bbase&&+fordirinfolder1folder2deep+do+gitcheckout-bupdate-$dir&&+echo"updated $dir">$dir/a&&+gitcommit-a-m"update $dir"||return1+done&&++gitcheckout-brename-basebase&&+echo>folder1/larger-content<<-\EOF&&+matching+lines+help+inexact+renames+EOF+cpfolder1/larger-contentfolder2/&&+cpfolder1/larger-contentdeep/deeper1/&&+gitadd.&&+gitcommit-m"add interesting rename content"&&++gitcheckout-brename-out-to-outrename-base&&+mvfolder1/afolder2/b&&+mvfolder1/larger-contentfolder2/edited-content&&+echo>>folder2/edited-content&&+gitadd.&&+gitcommit-m"rename folder1/... to folder2/..."&&++gitcheckout-brename-out-to-inrename-base&&+mvfolder1/adeep/deeper1/b&&+mvfolder1/larger-contentdeep/deeper1/edited-content&&+echo>>deep/deeper1/edited-content&&+gitadd.&&+gitcommit-m"rename folder1/... to deep/deeper1/..."&&++gitcheckout-brename-in-to-outrename-base&&+mvdeep/deeper1/afolder1/b&&+mvdeep/deeper1/larger-contentfolder1/edited-content&&+echo>>folder1/edited-content&&+gitadd.&&+gitcommit-m"rename deep/deeper1/... to folder1/..."&&++gitcheckout-bdeepestbase&&+echo"updated deepest">deep/deeper1/deepest/a&&+gitcommit-a-m"update deepest"&&++gitcheckout-fbase&&+gitreset--hard+)+'++init_repos(){+rm-rffull-checkoutsparse-checkoutsparse-index&&++# create repos in initial state+cp-rinitial-repofull-checkout&&+git-Cfull-checkoutreset--hard&&++cp-rinitial-reposparse-checkout&&+git-Csparse-checkoutreset--hard&&+git-Csparse-checkoutsparse-checkoutinit--cone&&++# initialize sparse-checkout definitions+git-Csparse-checkoutsparse-checkoutsetdeep+}++run_on_sparse(){+(+cdsparse-checkout&&+$*>../sparse-checkout-out2>../sparse-checkout-err+)+}++run_on_all(){+(+cdfull-checkout&&+$*>../full-checkout-out2>../full-checkout-err+)&&+run_on_sparse$*+}++test_all_match(){+run_on_all$*&&+test_cmpfull-checkout-outsparse-checkout-out&&+test_cmpfull-checkout-errsparse-checkout-err+}++test_expect_success'status with options''+init_repos&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno&&+run_on_all"touch README.md"&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno&&+test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitstatus--porcelain=v2-z-u&&+test_all_matchgitstatus--porcelain=v2-uno+'++test_expect_success'add, commit, checkout''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>$1+EOF+run_on_all"../edit-contents README.md"&&++test_all_matchgitaddREADME.md&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Add README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents README.md"&&++test_all_matchgitadd-A&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"Extend README.md"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-&&++run_on_all"../edit-contents deep/newfile"&&++test_all_matchgitstatus--porcelain=v2-uno&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitadd.&&+test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitcommit-m"add deep/newfile"&&++test_all_matchgitcheckoutHEAD~1&&+test_all_matchgitcheckout-+'++test_expect_success'checkout and reset --hard''+init_repos&&++test_all_matchgitcheckoutupdate-folder1&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitcheckoutupdate-deep&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitcheckout-breset-test&&+test_all_matchgitreset--harddeepest&&+test_all_matchgitreset--hardupdate-folder1&&+test_all_matchgitreset--hardupdate-folder2+'++test_expect_success'diff --staged''+init_repos&&++write_scriptedit-contents<<-\EOF&&+echotext>>README.md+EOF+run_on_all"../edit-contents"&&++test_all_matchgitdiff&&+test_all_matchgitdiff--staged&&+test_all_matchgitaddREADME.md&&+test_all_matchgitdiff&&+test_all_matchgitdiff--staged+'++test_expect_success'diff with renames''+init_repos&&++forbranchinrename-out-to-outrename-out-to-inrename-in-to-out+do+test_all_matchgitcheckoutrename-base&&+test_all_matchgitcheckout$branch--.&&+test_all_matchgitdiff--staged--no-renames&&+test_all_matchgitdiff--staged--find-renames||return1+done+'++test_expect_success'log with pathspec outside sparse definition''+init_repos&&++test_all_matchgitlog--a&&+test_all_matchgitlog--folder1/a&&+test_all_matchgitlog--folder2/a&&+test_all_matchgitlog--deep/a&&+test_all_matchgitlog--deep/deeper1/a&&+test_all_matchgitlog--deep/deeper1/deepest/a&&++test_all_matchgitcheckoutupdate-folder1&&+test_all_matchgitlog--folder1/a+'++test_expect_success'blame with pathspec inside sparse definition''+init_repos&&++test_all_matchgitblamea&&+test_all_matchgitblamedeep/a&&+test_all_matchgitblamedeep/deeper1/a&&+test_all_matchgitblamedeep/deeper1/deepest/a+'++# TODO: blame currently does not support blaming files outside of the+# sparse definition. It complains that the file doesn't exist locally.+test_expect_failure'blame with pathspec outside sparse definition''+init_repos&&++test_all_matchgitblamefolder1/a&&+test_all_matchgitblamefolder2/a&&+test_all_matchgitblamedeep/deeper2/a&&+test_all_matchgitblamedeep/deeper2/deepest/a+'++# TODO: reset currently does not behave as expected when in a+# sparse-checkout.+test_expect_failure'checkout and reset (mixed)''+init_repos&&++test_all_matchgitcheckout-breset-testupdate-deep&&+test_all_matchgitresetdeepest&&+test_all_matchgitresetupdate-folder1&&+test_all_matchgitresetupdate-folder2+'++test_expect_success'merge''+init_repos&&++test_all_matchgitcheckout-bmergeupdate-deep&&+test_all_matchgitmerge-m"folder1"update-folder1&&+test_all_matchgitrev-parseHEAD^{tree}&&+test_all_matchgitmerge-m"folder2"update-folder2&&+test_all_matchgitrev-parseHEAD^{tree}+'++test_expect_success'merge with outside renames''+init_repos&&++fortypeinout-to-outout-to-inin-to-out+do+test_all_matchgitreset--hard&&+test_all_matchgitcheckout-f-bmerge-$typeupdate-deep&&+test_all_matchgitmerge-m"$type"rename-$type&&+test_all_matchgitrev-parseHEAD^{tree}||return1+done+'++test_expect_success'clean''+init_repos&&++echobogus>>.gitignore&&+run_on_allcp../.gitignore.&&+test_all_matchgitadd.gitignore&&+test_all_matchgitcommit-mignore-bogus-files&&++run_on_sparsemkdirfolder1&&+run_on_alltouchfolder1/bogus&&++test_all_matchgitstatus--porcelain=v2&&+test_all_matchgitclean-f&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitclean-xf&&+test_all_matchgitstatus--porcelain=v2&&++test_all_matchgitclean-xdf&&+test_all_matchgitstatus--porcelain=v2&&++test_path_is_dirsparse-checkout/folder1+'++test_done--