Skip to content

Simplify object storage directory traversals - #6223

Merged
chrisd8088 merged 2 commits into
git-lfs:mainfrom
chrisd8088:remove-cleanup-path-filters
Mar 30, 2026
Merged

chrisd8088 merged 2 commits into
git-lfs:mainfrom
chrisd8088:remove-cleanup-path-filters

Conversation

@chrisd8088

@chrisd8088 chrisd8088 commented Mar 17, 2026 •

Copy link
Copy Markdown
Member

This PR simplifies the functions used by the Git LFS to traverse its internal temporary and object storage directories, as there is no need for these functions to check for Git submodules or .git directories.

This PR will be most easily reviewed on a commit-by-commit basis.

Background

In PR #1616 we added a FastWalkGitRepo() function to our tools package for the purpose of improving the performance of the git lfs track command. Prior to this, the git lfs track command used the Walk() function of the filepath package from the Go standard library to traverse the contents of the current Git working tree and locate all .gitattributes files.

Using the FastWalkGitRepo() function made searches for .gitattributes files in large repositories faster for three reasons. First, the functions called by the FastWalkGitRepo() function did not sort the entries in each directory, unlike the filepath package's Walk() function. Second, our functions ignored all .git directories and did not traverse into them. And third, our functions also ignored all directories entries which matched the patterns found in any .gitignore files.

Later, in PRs #1870 and #2689, we expanded the number of callers of the FastWalkGitRepo() function. In particular, in PR #2689 we began to make use of the function in our git lfs prune command to locate all the object files in our local storage directories, which by default are located within the .git/lfs/objects directory. As well, with this PR we began to use the FastWalkGitRepo() function during the final phase of all Git LFS commands where we find and delete any stale temporary files from the directory where we store such files, which by default is the .git/lfs/tmp directory.

PR #2689 introduced our fs package, whose Filesystem structure's EachObject() and cleanupTmp() methods both called the FastWalkGitRepo() function, passing the paths to the local object storage directory and the local temporary file storage directory, respectively.

We then added a FastWalkGitRepoAll() function to our fs package in PR #3190. This function operated in a similar fashion as the FastWalkGitRepo() function but did not read .gitignore files and so also did not skip directory entries matching any patterns found in those files.

Next, in PR #3686, we updated the internal implementation of the FastWalkGitRepo() and FastWalkGitRepoAll() functions to avoid entering submodules when traversing a Git working tree. To make this change, we added a check to the fastWalker structure's Walk() method so it would return immediately when processing a directory if the directory contained an entry named .git, unless the directory was the root of the working tree.

Then in commit 83d7f76 of PR #3823 we first implemented the NewLsFiles() function in our git package, which executes a git ls-files command and returns the list of files it outputs. As well, we updated the findAttributeFiles() function of our git package and the fixFileWriteFlags() function in our locking package to both call the NewLsFiles() function instead of calling either the FastWalkGitRepo() or FastWalkGitRepoAll() functions.

Since these were the only instances where we actually used the FastWalkGitRepo() or FastWalkGitRepoAll() functions to traverse a Git working tree, in the same commit of PR #3823 we removed the FastWalkGitRepo() function and renamed the FastWalkGitRepoAll() function to FastWalkDir(). We also simplified some of the internal functions called by the FastWalkDir() function because they no longer needed to detect or parse .gitignore files, or skip directory entries matching the patterns from those files.

Although the two remaining use cases for the FastWalkDir() function also did not need the function to detect and skip submodules or directories named .git, the logic to do so was retained in the internal functions of the tools package.

Specifically, the fastWalkWithExcludeFiles() function, which is called by the FastWalkDir() function, establishes two file path filters with the patterns .git and **/.git, which are then passed down to the Walk() method of the fastWalker structure. That method checks whether the current directory entry's name matches either of the filter patterns, and if it does returns immediately without processing the entry any further. In addition, the Walk() method still also performs the check added in PR #3686 to try to avoid traversing into Git submodules.

Current State

As noted above, neither of the two remaining callers of the FastWalkDir() function require these checks for .git directory entries and for submodules, because they only need the function to traverse directory hierarchies which the Git LFS client has created, and which are specifically not Git working trees.

The cleanupTmp() method of the Filesystem structure in our fs package uses the FastWalkDir() function to find stale files within the local temporary file storage directory. The EachObject() method of the same structure, meanwhile, uses the FastWalkDir() function to invoke a callback function for each object file in the local object storage directory.

By default, these local storage directories are the .git/lfs/tmp and .git/lfs/objects directories. If the "lfs.storage" configuration option is set to a relative path, then these directories will be located somewhere within the ".git" directory, while if the option is set to an absolute path, our local storage directories will be located under that arbitrary location.

In all these cases the Git LFS client creates and manages these local storage directories, so we can expect them not to contain .git directories or submodules. This is true even when a user has configured the lfs.storage option with an absolute path, since the client is still responsible for creating and managing tmp and objects directories within that arbitrary location.

Simplifications

We therefore now simplify the FastWalkDir() function and the internal functions it invokes by removing the unnecessary checks for submodules and for directory entries named .git.

First, we update the fastWalkWithExcludeFiles() function so that does not initialize any file path filters, and we rename the function to fastWalkDir().

Next, we remove the Walk() method's excludePaths parameter, and alter the method so that it no longer skips directory entries based on whether their names match the patterns in that parameter's file path filters.

We also eliminate the check in the Walk() method for directories containing .git directory entries, as this check's only purpose was to skip Git submodules within a working tree.

As well, we revise the code comments relating to all of these functions and methods to reflect their new names and their simplified behaviours.

To ensure that the variables and parameters in these functions accurately represent the functions' current purpose and implementation, we replace the term rootDir with the simple termdir because the FastWalkDir() function does not specifically start from the root of a Git working tree as its predecessor function FastWalkGitRepo() always did (at least, as that function always did when it was first introduced in PR #1616).

For the same reason, we rename the isRoot parameter of the fastWalker structure's Walk() method to the more generic isInitialDir, since any directory may be used as the initial starting point for a directory traversal by that method.

We also change the name of the method's workDir parameter to simply dir because the method is no longer used to only traverse Git working trees, and we likewise rename the parentWorkDir and childWorkDir variables to just parentDir and childDir.

Testing

The cleans only temp files and directories older than an hour test in our t/t-tempfile.sh shell test script verifies the behaviour of cleanupTmp() function, which employs the FastWalkDir() function, while the TestFastWalkBasic() test function in our Go test suite directly exercises the fastWalkDir() internal function. Both tests thus provide some assurance that our changes in this commit have not introduced any unexpected regressions.

In commit 13a8af6 of PR git-lfs#1616 we
added a FastWalkGitRepo() function to our "tools" package for the
purpose of improving the performance of the "git lfs track" command.
At the time, this command used the Walk() function of the "filepath"
package from the Go standard library to traverse the contents of the
current Git working tree and locate all ".gitattributes" files.

Then in commit f1fdc85 of the same
PR git-lfs#1616 we updated the "git lfs track" command's findAttributeFiles()
function to use our new FastWalkGitRepo() function instead of the
"filepath" package's Walk() function.  This change made searches for
".gitattributes" files in large repositories faster for several reasons.
Unlike the Walk() function from the "filepath" package, the functions
called by the FastWalkGitRepo() function to traverse a directory
hierarchy did not sort the entries in each directory, and also ignored
all ".git" directories and all entries which matched any pattern found
in a ".gitignore" file.

Later, in PRs git-lfs#1870 and git-lfs#2689, we expanded the number of callers of
the FastWalkGitRepo() function.  In particular, in PR git-lfs#2689 we began
to make use of the function in our "git lfs prune" command to locate
all the object files in our local storage directories, which by default
are located within the ".git/lfs/objects" directory.  As well, with
this PR we began to use the FastWalkGitRepo() function during the
final phase of all Git LFS commands where we find and delete any
stale temporary files from the directory where we store such files,
which by default is the ".git/lfs/tmp" directory.

PR git-lfs#2689 introduced our "fs" package, whose Filesystem structure's
EachObject() and cleanupTmp() methods both called the FastWalkGitRepo()
function, passing the paths to the local object storage directory
and the local temporary file storage directory, respectively.

We then added a FastWalkGitRepoAll() function to our "fs" package in
PR git-lfs#3190.  This function operated in a similar fashion as the
FastWalkGitRepo() function but did not read ".gitignore" files and so
also did not skip directory entries matching any patterns found in
those files.

Next, in PR git-lfs#3686, we updated the internal implementation of the
FastWalkGitRepo() and FastWalkGitRepoAll() functions to avoid entering
submodules when traversing a Git working tree.  To make this change,
we added a check to the "fastWalker" structure's Walk() method so it
would return immediately when processing a directory if the directory
contained an entry named ".git", unless the directory was the root of
the working tree.  Note that the Walk() method already ignored any
directory entries with the name ".git", but this only meant it would
traverse through the contents of a submodule checkout in a working
tree while skipping the submodule's ".git" directory.

Then in commit 83d7f76 of PR git-lfs#3823
we first implemented the NewLsFiles() function in our "git" package,
which executes a "git ls-files" command and returns the list of files
it outputs.  As well, we updated the findAttributeFiles() function of
our "git" package and the fixFileWriteFlags() function in our "locking"
package to both call the NewLsFiles() function instead of calling
either the FastWalkGitRepo() or FastWalkGitRepoAll() functions.

Since these were the only instances where we actually used the
FastWalkGitRepo() or FastWalkGitRepoAll() functions to traverse a Git
working tree, in the same commit of PR git-lfs#3823 we removed the
FastWalkGitRepo() function and renamed the FastWalkGitRepoAll() function
to FastWalkDir().  We also simplified some of the internal functions
called by the FastWalkDir() function because they no longer needed to
detect or parse ".gitignore" files, or skip directory entries matching
the patterns from those files.

Although the two remaining use cases for the FastWalkDir() function
also did not need the function to detect and skip submodules or
directories named ".git", the logic to do so was retained in the
internal functions of the "tools" package.

Specifically, the fastWalkWithExcludeFiles() function, which is called
by the FastWalkDir() function, establishes two file path filters with
the patterns ".git" and "**/.git", which are then passed down to the
Walk() method of the "fastWalker" structure.  That method checks whether
the current directory entry's name matches either of the filter patterns,
and if it does returns immediately without processing the entry any
further.  In addition, the Walk() method still also performs the check
added in PR git-lfs#3686 to try to avoid traversing into Git submodules.

As noted above, however, neither of the two remaining callers of the
FastWalkDir() function require these checks, because they only need
the function to traverse directory hierarchies which the Git LFS client
has created, and which are specifically not Git working trees.

The cleanupTmp() method of the Filesystem structure in our "fs" package
uses the FastWalkDir() function to find stale files within the local
temporary file storage directory.  The EachObject() method of the same
structure, meanwhile, uses the FastWalkDir() function to invoke a
callback function for each object file in the local object storage
directory.

By default, these local storage directories are the ".git/lfs/tmp" and
".git/lfs/objects" directories.  If the "lfs.storage" configuration
option is set to a relative path, then these directories will be
located somewhere within the ".git" directory, while if the option is
set to an absolute path, our local storage directories will be located
under that arbitrary location.

In all these cases the Git LFS client creates and manages these local
storage directories, so we can expect them not to contain ".git"
directories or submodules.  This is true even when a user has
configured the "lfs.storage" option with an absolute path, since
the client is still responsible for creating and managing "tmp" and
"objects" directories within that arbitrary location.

We therefore now simplify the FastWalkDir() function and the internal
functions it invokes by removing the unnecessary checks for submodules
and for directory entries named ".git".

First, we update the fastWalkWithExcludeFiles() function so that does
not initialize any file path filters, and we rename the function to
fastWalkDir().

Next, we remove the Walk() method's "excludePaths" parameter, and alter
the method so that it no longer skips directory entries based on whether
their names match the patterns in that parameter's file path filters.

We also eliminate the check in the Walk() method for directories
containing ".git" directory entries, as this check's only purpose was
to skip Git submodules within a working tree.

As well, we revise the code comments relating to all of these functions
and methods to reflect their new names and their simplified behaviours.
To minimize the changes in this commit, however, we leave the names of
the functions' parameters and internal variables intact, even though
some of them still reflect the original design and the expectation that
the functions would be used with Git working trees.  In a subsequent
commit in this PR we will then rename these variables and parameters,
along with the "rootDir" field of the "fastWalker" structure, so that
they more accurately represent the functions' current purpose and
implementation.

Finally, note that the "cleans only temp files and directories older
than an hour" test in our "t/t-tempfile.sh" shell test script verifies
the behaviour of cleanupTmp() function, which employs the FastWalkDir()
function, while the TestFastWalkBasic() test function in our Go test
suite directly exercises the fastWalkDir() internal function.  Both
tests thus provide some assurance that our changes in this commit have
not introduced any unexpected regressions.
In a prior commit in this PR we simplified the FastWalkDir() function
and several related internal functions in our "tools" package.  As we
described in that commit, these functions were originally designed
to traverse Git working trees, but they are no longer used for that
purpose and so we eliminated their unnecessary checks for Git
submodules and for directory entries named ".git".

However, to minimize the changes in that commit we left the names of
the functions' parameters and internal variables intact, even though
some of them still reflect the original design and the expectation that
the functions would be used with Git working trees.

We now rename these variables and parameters, as well as the "rootDir"
field of the "fastWalker" structure, so that they more accurately
represent the functions' current purpose and implementation.

First, we replace the term "rootDir" with the simple term "dir" because
the FastWalkDir() function does not specifically start from the root of
a Git working tree as its predecessor function FastWalkGitRepo()
always did (at least, as that function always did when it was first
introduced in PR git-lfs#1616).

For the same reason, we rename the "isRoot" parameter of the "fastWalker"
structure's Walk() method to the more generic "isInitialDir", since
any directory may be used as the initial starting point for a directory
traversal by that method.

We also change the name of the method's "workDir" parameter to simply
"dir" because the method is no longer used to only traverse Git working
trees, and we likewise rename the "parentWorkDir" and "childWorkDir"
variables to just "parentDir" and "childDir".
@chrisd8088
chrisd8088 requested a review from a team as a code owner March 17, 2026 21:30
chrisd8088 added a commit to chrisd8088/git-lfs that referenced this pull request Mar 29, 2026
In a prior commit in this PR we introduced a new Bool() method to
the Environment interface in our "fs" package, with the expectation
that we would make use of it in a subsequent commit.

However, due to the changes in PR git-lfs#6223, we no longer need to modify
the "fs" package in this PR, so we reverse our change to the Environment
interface in the "fs" package now.
@chrisd8088
chrisd8088 merged commit f513d65 into git-lfs:main Mar 30, 2026
10 checks passed
@chrisd8088
chrisd8088 deleted the remove-cleanup-path-filters branch March 30, 2026 21:17
chrisd8088 added a commit to chrisd8088/git-lfs that referenced this pull request Apr 1, 2026
Note that due to the changes in PR git-lfs#6223, we no longer need to modify
the "tools" package in this PR, nor do we need to modify our calls to
that package's FastWalkDir() function in the "fs" package.

Therefore in this merge commit we resolve the conflicts which stem from
the changes in PR git-lfs#6223 by reversing some of our modifications to the
"fs" package, and by dropping all of our previous alterations to the
"tools" package.
chrisd8088 added a commit to chrisd8088/git-lfs that referenced this pull request Apr 1, 2026
In a prior commit in this PR we introduced a new Bool() method to
the Environment interface in our "fs" package, with the expectation
that we would make use of it in a subsequent commit.

However, due to the changes in PR git-lfs#6223, we no longer need to modify
the "fs" package in this PR, so we reverse our change to the Environment
interface in the "fs" package now.
chrisd8088 added a commit to chrisd8088/git-lfs that referenced this pull request Apr 1, 2026
In a prior commit in this PR we introduced a new GitConfig field to
the Filesystem structure in our "fs" package and added a corresponding
"cfg" parameter to the New() function in the same package in order to
pass an Environment type through to the FastWalkDir() function in
our "tools" package.

However, due to the changes in PR git-lfs#6223, we no longer need to modify
the "fs" package in this PR, so we reverse our changes to the
Filesystem structure and the New() function now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Sponsor
SponsoredKunjungi sekarang
Promo