Skip to content

Commit 94eccd1

Browse files
chrisd8088hswong3i
authored andcommitted
fix bare repo pull/checkout path handling bug
Our "git lfs checkout" and "git lfs pull" commands may both, at present, be executed in a bare repository, although the former has no utility in a bare repository, and the latter often performs no actions, but can be used to fetch Git LFS objects in a bare repository. The "git lfs checkout" and "git lfs pull" commands are the only commands which make use of the methods of the singleCheckout structure in our "commands" package, and in a subsequent commit we will update these methods so they change the current working directory to the root of the current working tree, so long as one exists. Before we make these revisions, though, we first need to guarantee that the singleCheckout structure's methods correctly handle the case where no current work tree is defined, such as in a bare repository when the GIT_WORK_TREE environment variable has not been set. When no working tree is defined, the "git lfs pull" command should perform no action other than fetching objects, since there is no work tree into which the command should write any Git LFS file content. For the same reason, the "git lfs checkout" should have no effect when no work tree is defined, since the command's only purpose is to check out Git LFS file content into a working tree. Unfortunately, both the "git lfs checkout" and "git lfs pull" commands may, under unusual circumstances, try to check out Git LFS files by writing their object data into files either inside or outside a bare repository. In bare repositories, when the "git lfs checkout" and "git lfs pull" commands try to determine whether to check out a Git LFS file into the (non-existent) working tree, they incorrectly treat the path to a file from the root of the repository as if it were instead an absolute path starting from the root of the current filesystem. For instance, given the path "foo/bar.bin" to a Git LFS file in a repository, the commands will instead treat this path as if it were the path "/foo/bar.bin". Normally, no file will exist at this location, so the "git lfs checkout" and "git lfs pull" commands then check the Git index to try to determine whether the user has staged the file for deletion. Since bare repositories typically have no index, the commands will assume the user has intentionally removed the file, and skip any further processing for the file. If the user has added an index entry for the file, though, the commands will assume the file should be re-created in the (non-existent) working tree with the content of the object referenced by the Git LFS pointer stored in Git's version of the file. Taking the file's path from the root of the repository as if it was an absolute path, the commands will try to create any missing directories in that path, and then try to either create a new file or truncate an existing one before writing the Git LFS object content into the file. An alternative sequence of events which leads to the same result may occur in the extremely unlikely case that a file already exists at the location specified by the incorrectly-determined absolute path, and that the file contains a Git LFS pointer with the same object ID as that of the given file in the repository. In other words, using the same example file paths as above, this means a "/foo/bar.bin" file would have to already exist and contain the same raw Git LFS pointer data as the "foo/bar.bin" file in the Git repository. Should this happen, the "git lfs checkout" and "git lfs pull" commands would assume the file should be overwritten with the contents of the corresponding Git LFS object. Of course, even if the "git lfs checkout" and "git lfs pull" commands try to create or overwrite a file at the path they are incorrectly treating as an absolute path, the current user may not have sufficient permissions to permit the necessary filesystem operations to complete. Regardless, the Git LFS client should not try to read or write files outside of the current repository unless specifically requested to do so with an argument such as the --to option of the "git lfs checkout" command. In conjunction with our remediation of the vulnerability assigned the identifier CVE-2025-26625, we therefore revise the "git lfs checkout" and "git lfs pull" commands now to ensure they will never treat paths relative to the root of the current repository as if they were absolute filesystem paths. We also adjust the "git lfs checkout" command so that it generates the same error message as commands like "git lfs status" when no working tree is defined, and exits immediately afterwards. This change will make clear to our users why the "git lfs checkout" command has no effect in a bare repository, while also simplifying our test requirements as we do not have to verify the command's behaviour in a bare repository beyond checking that it exits with the appropriate warning message. The specific problem addressed in this commit is the result of the joining an empty path, which signals the lack of a current working tree, to a file's path from the root of the repository, and adding a file separator character between the two strings. This occurs within the Convert() method of the repoToCurrentPathConverter structure type from our "lfs" package. In a subsequent commit we will be able to remove the repoToCurrentPathConverter structure and its methods entirely, when we revise the "git lfs checkout" and "git lfs pull" commands to change the current working directory to the root of the current working tree. In this commit, however, we simply alter the commands so that they never call the structure's Convert() method if no working tree exists. First, we add a "hasWorkTree" element to the singleCheckout structure type in our "commands" package, and when we initialize a new structure in the newSingleCheckout() function, we set the "hasWorkTree" element's value to "true" only if the LocalWorkingDir() method of the Configuration structure type from our "config" package returns a non-empty path. The LocalWorkingDir() method returns the absolute path to the root of the current working tree, or an empty path if no working tree is defined, as determined by the GitAndRootDirs() function in our "git" package. The GitAndRootDirs() function runs the "git rev-parse" command with the --show-toplevel option, and then interprets that command's output and exit code so that if no working tree is defined, an empty path is returned instead of a path to the work tree's root directory. Second, we update the Run() method of the singleCheckout structure so that it returns immediately unless the "hasWorkTree" element is set to a "true" value, meaning a work tree exists and it is safe to create and write files within that directory tree. To verify these changes work as we expect, we introduce a new "pull: bare repository" test to our t/t-pull.sh test script, and in this test we specifically add a Git LFS pointer file to the test repository at a path that, if treated as an absolute path instead of a path from the root of the repository, could be created by the current test process. After the test clones the repository, it adds this file's path to the index, runs the "git lfs pull" command, and then checks that no file is created either inside the bare repository or, most importantly, outside the repository. (The test also ensures that a Git LFS filter attribute is defined in the "$GIT_DIR/info/attributes" file, which guarantees that regardless of which Git version is installed, the "git lfs pull" command will find our new Git LFS pointer file in the repository's contents and process it. We describe the issues pertaining to the need to use a local Git attributes file instead of a ".gitattributes" file further below.) Without our changes to the singleCheckout structure and its methods in this commit, the revised "pull: bare repository" test will fail, so we can be confident that it validates that our remediation is effective. As for the "git lfs checkout" command, we alter its main checkoutCommand() function so that after calling the setupRepository() function, the checkoutCommand() function checks whether a path to the current working tree has been found, and if not, outputs a warning message and stops execution of the command. This new check is modelled on that performed by the requireWorkingCopy() function, but causes the command to return a zero (i.e., successful) exit code rather than a non-zero one. Our new check relies on the functions invoked by the setupRepository() function to have already called the GitAndRootDirs() function in our "git" package. That function runs the "git rev-parse" command with the --show-toplevel option, and then interprets the command's output and exit code so that if no current work tree is present, an empty path will be returned by the LocalWorkingDir() method of our "config" package's Configuration structure instead of a path to the work tree's root directory. It would be more straightforward for us to revise the checkoutCommand() function to simply call the setupWorkingCopy() function rather than the setupRepository() function, because the setupWorkingCopy() function calls the requireWorkingCopy() function and so would enforce the presence of a working tree in the same manner as we employ in other commands such as the "git lfs status" and "git lfs track" commands. However, this implementation would result in a backwards-incompatible change to the behaviour the "git lfs checkout" command when it is run in a bare repository, which could result in the unexpected failure of automated CI jobs, for instance. Although the use of the "git lfs checkout" command in a bare repository has no purpose, we defer the simpler implementation to a future release, and for now ensure that the command still returns a zero exit code when run in a bare repository. We do, though, update our git-lfs-checkout(1) manual page to clarify that the command requires a working tree, and that in the future the command may exit with an error when run in a bare repository. We also add a new "checkout: bare repository" test to our t/t-checkout.sh test script, which just verifies that the command generates the expected error message and returns a zero exit code when it is run in a bare repository. Both the "git lfs checkout" and "git lfs pull" commands currently exhibit the erroneous behaviour addressed by this commit because the singleCheckout structure's Run() method relies on the Convert() method of the repoToCurrentPathConverter structure type to rewrite file paths relative to the root of the repository into paths relative to the current working directory, and this method returns invalid paths when no working tree is defined, as is the case in a bare repository. The Run() method is executed, either directly or indirectly, for each Git LFS pointer file path found by the ScanLFSFiles() method of the GitScanner structure in our "lfs" package. This method retrieves a list of files from Git, and for each one that corresponds to a Git LFS pointer, the method invokes an anonymous function which in turn causes the Run() method to be performed. In the case of the "git lfs pull" command, if a local copy of the object associated with a Git LFS pointer is found, the Run() method is invoked directly within the anonymous function, and otherwise it is invoked by a goroutine for each object whose data is successfully retrieved from the Git LFS remote by the transfer queue. In the case of the "git lfs checkout" command, the anonymous function called by the ScanLFSFiles() method appends each Git LFS pointer to a slice, and then the Run() method is invoked for each pointer in the slice after the scan through the list of files is complete. To retrieve a list of files from Git, the runScanLFSFiles() function, which is called by the ScanLFSFiles() method, uses one of two Git commands. If the installed version of Git is 2.42.0 or higher, the "git ls-files" command is executed, and otherwise the "git ls-tree" command is used. This difference accounts for one of the reasons why the "git lfs pull" command, in particular, may perform no action when run within a bare repository. Specifically, as noted in issue git-lfs#6004, the "git ls-files" command lists the files in the Git index, while the "git ls-tree" command lists the files in the Git tree associated with a given reference, which in the case of our "git lfs checkout" and "git lfs pull" commands is always the current "HEAD" symbolic reference. If the installed version of Git is older than v2.42.0, when our commands run the "git ls-tree" command they will receive a list of files from the Git tree associated with the "HEAD" reference, and will process any Git LFS pointers found in that list. (Note that pointer files will be processed even if they no longer match any Git LFS filter attributes; for instance, if there are no ".gitattributes" files in the index or in the Git tree associated with the "HEAD" reference, and no local Git attributes files.) If the installed version of Git is at least v2.42.0, our commands run the "git ls-files" command instead of the "git ls-tree" command. For two separate reasons, in a bare repository the "git ls-files" command will often return an empty list, so our "git lfs checkout" and "git lfs pull" commands will take no further action. The more obvious reason is that by default, Git creates bare repositories without an index, so unless the user has explicitly added entries to the index for Git LFS pointer files, no results will be returned by the "git ls-files" command. The less obvious reason is due to the "attr:filter=lfs" pathspec our commands pass to the "git ls-files" command, which causes the command to only return paths for files which match a Git LFS filter attribute definition. However, in a bare repository Git's internal read_attr() function by default ignores all ".gitattributes" files found in either the index or the tree associated with the "HEAD" reference: https://github.com/git/git/blob/v2.50.1/attr.c#L851-L867 Since there is no working tree in a bare repository, this means all ".gitattributes" files are ignored by default, and because users typically define Git LFS attributes in those files, the "git ls-files" command will not match any files even if entries for Git LFS pointer files have been added to the index. Users would have to specifically set the GIT_ATTR_SOURCE environment variable to a reference like "HEAD" or add Git LFS filter attributes to a local Git attributes file such as the "$GIT_DIR/info/attributes" file in order for the "git ls-files" command to match pointer files in the index to the "attr:filter=lfs" pathspec and return a non-empty list. Regardless of the source, though, if Git LFS pointers are identified from the list of files returned by Git, the "git lfs pull" command will fetch the objects referenced by those pointers unless the objects already exist in the local storage directories under "lfs/objects". (Note that in a bare repository, the usual leading ".git" directory is not necessary.) As objects are fetched by the transfer queue, the separate goroutine started by the "git lfs pull" command passes their pointer data to the Run() method of the singleCheckout structure, one pointer at a time. In a "git lfs checkout" command, by contrast, no objects are fetched, and the Run() method is instead invoked directly by the command's main function for each Git LFS pointer file path collected during the execution of the ScanLFSFiles() method. As described above, the Run() method begins by converting the file path of the Git LFS pointer provided in its "p" parameter into a file path relative to the current working directory using the Convert() method of the repoToCurrentPathConverter structure type. We initialize a structure of that type in the newSingleCheckout() function by calling the NewRepoToCurrentPathConverter() function. That function uses an internal function named pathConverterArgs() to set the new structure's "repoDir" element to the file path returned by the LocalWorkingDir() method of the Configuration structure type, which as mentioned above will be an empty path if no current work tree is defined. When the repoToCurrentPathConverter structure type's Convert() method is called, it first joins the structure's "repoDir" element to the file path provided in the method's "p" parameter using a local wrapper function around the Join() function from the Go standard library's "strings" package, rather than the Join() function from the "path/filepath" package. (This change was made in commit fd69029 of PR git-lfs#2875, presumably to make more efficient the handling of file paths which we expect to always be defined.) In a bare repository, however, the "repoDir" element contains an empty path, so the result of joining it with a file path relative to the root of the repository using the Join() function from the "strings" package is the same file path but with a leading "/" character prepended to it, in effect creating an invalid absolute path from the root of the filesystem. Note that if the Join() function from the "path/filepath" package was used instead, it ignores empty parameters, so the file path would be returned unchanged. The Convert() method then passes this invalid absolute path to the Rel() function of the "path/filepath" package of the Go standard library, along with the absolute path to the current working directory. We expect this call to return a relative path from the current working directory to the location within the current Git work tree where a file should be created or updated with the contents of a Git LFS object. In a bare repository, though, what is returned by the Convert() method to the Run() method is a relative path from the current working directory to a location constructed by treating a Git LFS pointer's path within the repository as if it was a path descending from the root of the current filesystem. For instance, given the path "foo/bar" of a Git LFS pointer within the repository, and current working directory of "/path/to/bare/repo", the Convert() method would return the path "../../../../foo/bar". After this path is returned to the Run() method, it is passed to the DecodePointerFromFile() function in our "lfs" package, which checks whether a file exists at the given location, and if so, reads it and checks whether it contains a valid Git LFS pointer. In the large majority of cases, of course, files will not exist in the locations identifed by the invalid paths that the Convert() method generates when the "git lfs checkout" or "git lfs pull" commands are executed in a bare repository. Hence the DecodePointerFromFile() function will return an error which the IsNotExist() function of the "os" package considers equivalent to an ErrNotExist error. The Run() method will then execute a "git diff-index" command to determine whether the user has intentionally removed the file from the Git index, and will pass the original file path (the one relative to the root of the repository) to that command. If the installed version of Git is older than v2.42.0, and the bare repository has no index, as is normally the case in such repositories, the "git diff-index" command's output will indicate that the file does not exist in the index and so the Run() method will return without taking further action. On the other hand, if the installed version of Git is 2.42.0 or higher, then the index must include an entry for the original file path (the one relative to the root of the repository), since otherwise the "git ls-files" command would not have listed the file at all and the Run() method would never have been called. Thus the "git diff-index" command will also list the file as present in the index, and so the Run() method will proceed on the assumption that the file is just missing in the (non-existent) working tree and should be created, even though there is no actual work tree. Even if a version of Git older than 2.42.0 is installed, though, the user may have created an index entry for the file, in which case the Run() method will likewise proceed because the "git diff-index" command's output will indicate that the file is present in the index. It is also possible, although extremely unlikely, that the DecodePointerFromFile() function finds a file at the incorrectly- generated path it was given, and is able to open it and parse it as a valid Git LFS pointer. The Run() method will then check to see if the pointer's ID matches that of the pointer under consideration. If it does not, the method will return without taking action, but if it does, it will proceed on the assumption that the pointer file should be overwritten with the contents of the associated object file. In summary, in a bare repository the singleCheckout structure's Run() method will only proceed under one of two conditions: either a Git index entry exists for the file path under consideration, which is unlikely to be the case in a bare repository since the index is typically empty, or a Git LFS pointer file with the expected object ID happens to exist at the absolute path derived by prepending a file separator to the file's path within the repository, which is even more unlikely. Should one of these circumstances occur, though, the Run() method will invoke the RunToPath() method of the singleCheckout structure, which will in turn call the SmudgeToFile() method of the GitFilter structure in our "lfs" package. That method will attempt to create or truncate a file at the incorrect path, and then write the contents of a Git LFS object into the file. With the changes in this commit, however, this incorrect behaviour should no longer occur under any circumstances.
1 parent 444cf54 commit 94eccd1

File tree

6 files changed

+176
-0
lines changed

6 files changed

+176
-0
lines changed

commands/command_checkout.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ var (
2424
func checkoutCommand(cmd *cobra.Command, args []string) {
2525
setupRepository()
2626

27+
// TODO: After suitable advance public notice, replace this block
28+
// and the preceding call to setupRepository() with a single call to
29+
// setupWorkingCopy(), which will perform the same check for a bare
30+
// repository but will exit non-zero, as other commands already do.
31+
if cfg.LocalWorkingDir() == "" {
32+
Print(tr.Tr.Get("This operation must be run in a work tree."))
33+
os.Exit(0)
34+
}
35+
2736
stage, err := whichCheckout()
2837
if err != nil {
2938
Exit(tr.Tr.Get("Error parsing args: %v", err))

commands/pull.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func newSingleCheckout(gitEnv config.Environment, remote string) abstractCheckou
3333

3434
return &singleCheckout{
3535
gitIndexer: &gitIndexer{},
36+
hasWorkTree: cfg.LocalWorkingDir() != "",
3637
pathConverter: pathConverter,
3738
manifest: nil,
3839
remote: remote,
@@ -49,6 +50,7 @@ type abstractCheckout interface {
4950

5051
type singleCheckout struct {
5152
gitIndexer *gitIndexer
53+
hasWorkTree bool
5254
pathConverter lfs.PathConverter
5355
manifest tq.Manifest
5456
remote string
@@ -66,6 +68,10 @@ func (c *singleCheckout) Skip() bool {
6668
}
6769

6870
func (c *singleCheckout) Run(p *lfs.WrappedPointer) {
71+
if !c.hasWorkTree {
72+
return
73+
}
74+
6975
cwdfilepath := c.pathConverter.Convert(p.Name)
7076

7177
// Check the content - either missing or still this pointer (not exist is ok)

docs/man/git-lfs-checkout.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ the `GIT_ATTR_SOURCE` environment variable may be set to `HEAD`, which
5050
will cause Git to only read attributes from `.gitattributes` files in
5151
`HEAD` and ignore those in the index or working tree.
5252

53+
In a bare repository, this command has no effect. In a future version,
54+
this command may exit with an error if it is run in a bare repository.
55+
5356
== OPTIONS
5457

5558
`--base`::

docs/man/git-lfs-pull.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ the `GIT_ATTR_SOURCE` environment variable may be set to `HEAD`, which
3636
will cause Git to only read attributes from `.gitattributes` files in
3737
`HEAD` and ignore those in the index or working tree.
3838

39+
In a bare repository, if the installed Git version is at least 2.42.0,
40+
this command will by default fetch Git LFS objects for files only if
41+
they are present in the Git index and if they match a Git LFS filter
42+
attribute from a local `gitattributes` file such as
43+
`$GIT_DIR/info/attributes`. Any `.gitattributes` files in `HEAD` will
44+
be ignored, unless the `GIT_ATTR_SOURCE` environment variable is set
45+
to `HEAD`, and any `.gitattributes` files in the index or current
46+
working tree will always be ignored. These constraints do not apply
47+
with prior versions of Git.
48+
3949
== OPTIONS
4050

4151
`-I <paths>`::

t/t-checkout.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,23 @@ begin_test "checkout: GIT_WORK_TREE"
961961
)
962962
end_test
963963

964+
begin_test "checkout: bare repository"
965+
(
966+
set -e
967+
968+
reponame="checkout-bare"
969+
git init --bare "$reponame"
970+
cd "$reponame"
971+
972+
git lfs checkout 2>&1 | tee checkout.log
973+
if [ "0" -ne "${PIPESTATUS[0]}" ]; then
974+
echo >&2 "fatal: expected checkout to succeed ..."
975+
exit 1
976+
fi
977+
[ "This operation must be run in a work tree." = "$(cat checkout.log)" ]
978+
)
979+
end_test
980+
964981
begin_test "checkout: sparse with partial clone and sparse index"
965982
(
966983
set -e

t/t-pull.sh

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,137 @@ begin_test "pull with empty file doesn't modify mtime"
11171117
)
11181118
end_test
11191119

1120+
begin_test "pull: bare repository"
1121+
(
1122+
set -e
1123+
1124+
reponame="pull-bare"
1125+
setup_remote_repo "$reponame"
1126+
clone_repo "$reponame" "$reponame"
1127+
1128+
git lfs track "*.dat"
1129+
1130+
contents="a"
1131+
contents_oid="$(calc_oid "$contents")"
1132+
printf "%s" "$contents" >a.dat
1133+
1134+
# The "git lfs pull" command should never check out files in a bare
1135+
# repository, either into a directory within the repository or one
1136+
# outside it. To verify this, we add a Git LFS pointer file whose path
1137+
# inside the repository is one which, if it were instead treated as an
1138+
# absolute filesystem path, corresponds to a writable directory.
1139+
# The "git lfs pull" command should not check out files into either
1140+
# this external directory or the bare repository.
1141+
external_dir="$TRASHDIR/${reponame}-external"
1142+
internal_dir="$(printf "%s" "$external_dir" | sed 's/^\/*//')"
1143+
mkdir -p "$internal_dir"
1144+
printf "%s" "$contents" >"$internal_dir/a.dat"
1145+
1146+
git add .gitattributes a.dat "$internal_dir/a.dat"
1147+
git commit -m "initial commit"
1148+
1149+
git push origin main
1150+
assert_server_object "$reponame" "$contents_oid"
1151+
1152+
cd ..
1153+
git clone --bare "$GITSERVER/$reponame" "${reponame}-assert"
1154+
1155+
cd "${reponame}-assert"
1156+
[ ! -e lfs ]
1157+
refute_local_object "$contents_oid"
1158+
1159+
git lfs pull 2>&1 | tee pull.log
1160+
if [ "0" -ne "${PIPESTATUS[0]}" ]; then
1161+
echo >&2 "fatal: expected pull to succeed ..."
1162+
exit 1
1163+
fi
1164+
1165+
# When Git version 2.42.0 or higher is available, the "git lfs pull"
1166+
# command will use the "git ls-files" command rather than the
1167+
# "git ls-tree" command to list files. By default a bare repository
1168+
# lacks an index, so we expect no Git LFS objects to be fetched when
1169+
# "git ls-files" is used because Git v2.42.0 or higher is available.
1170+
gitversion="$(git version | cut -d" " -f3)"
1171+
set +e
1172+
compare_version "$gitversion" '2.42.0'
1173+
result=$?
1174+
set -e
1175+
if [ "$result" -eq "$VERSION_LOWER" ]; then
1176+
grep "Downloading LFS objects" pull.log
1177+
1178+
assert_local_object "$contents_oid" 1
1179+
else
1180+
grep -q "Downloading LFS objects" pull.log && exit 1
1181+
1182+
refute_local_object "$contents_oid"
1183+
fi
1184+
1185+
[ ! -e "a.dat" ]
1186+
[ ! -e "$internal_dir/a.dat" ]
1187+
[ ! -e "$external_dir/a.dat" ]
1188+
1189+
rm -rf lfs/objects
1190+
refute_local_object "$contents_oid"
1191+
1192+
# When Git version 2.42.0 or higher is available, the "git lfs pull"
1193+
# command will use the "git ls-files" command rather than the
1194+
# "git ls-tree" command to list files. By default a bare repository
1195+
# lacks an index, so we expect no Git LFS objects to be fetched when
1196+
# "git ls-files" is used because Git v2.42.0 or higher is available.
1197+
#
1198+
# Therefore to verify that the "git lfs pull" command never checks out
1199+
# files in a bare repository, we first populate the index with Git LFS
1200+
# pointer files and then retry the command.
1201+
contents_git_oid="$(git ls-tree HEAD a.dat | awk '{ print $3 }')"
1202+
git update-index --add --cacheinfo 100644 "$contents_git_oid" a.dat
1203+
git update-index --add --cacheinfo 100644 "$contents_git_oid" "$internal_dir/a.dat"
1204+
1205+
# When Git version 2.42.0 or higher is available, the "git lfs pull"
1206+
# command will use the "git ls-files" command rather than the
1207+
# "git ls-tree" command to list files, and does so by passing an
1208+
# "attr:filter=lfs" pathspec to the "git ls-files" command so it only
1209+
# lists files which match that filter attribute.
1210+
#
1211+
# In a bare repository, however, the "git ls-files" command will not read
1212+
# attributes from ".gitattributes" files in the index, so by default it
1213+
# will not list any Git LFS pointer files even if those files and the
1214+
# corresponding ".gitattributes" files have been added to the index and
1215+
# the pointer files would otherwise match the "attr:filter=lfs" pathspec.
1216+
#
1217+
# Therefore, instead of adding the ".gitattributes" file to the index, we
1218+
# copy it to "info/attributes" so that the pathspec filter will match our
1219+
# pointer file index entries and they will be listed by the "git ls-files"
1220+
# command. This allows us to verify that with Git v2.42.0 or higher, the
1221+
# "git lfs pull" command will fetch the objects for these pointer files
1222+
# in the index when the command is run in a bare repository.
1223+
#
1224+
# Note that with older versions of Git, the "git lfs pull" command will
1225+
# use the "git ls-tree" command to list the files in the tree referenced
1226+
# by HEAD. The Git LFS objects for any well-formed pointer files found in
1227+
# that list will then be fetched (unless local copies already exist),
1228+
# regardless of whether the pointer files actually match a "filter=lfs"
1229+
# attribute in any ".gitattributes" file in the index, the tree
1230+
# referenced by HEAD, or the current work tree.
1231+
if [ "$result" -ne "$VERSION_LOWER" ]; then
1232+
mkdir -p info
1233+
git show HEAD:.gitattributes >info/attributes
1234+
fi
1235+
1236+
git lfs pull 2>&1 | tee pull.log
1237+
if [ "0" -ne "${PIPESTATUS[0]}" ]; then
1238+
echo >&2 "fatal: expected pull to succeed ..."
1239+
exit 1
1240+
fi
1241+
grep "Downloading LFS objects" pull.log
1242+
1243+
assert_local_object "$contents_oid" 1
1244+
1245+
[ ! -e "a.dat" ]
1246+
[ ! -e "$internal_dir/a.dat" ]
1247+
[ ! -e "$external_dir/a.dat" ]
1248+
)
1249+
end_test
1250+
11201251
begin_test "pull with partial clone and sparse checkout and index"
11211252
(
11221253
set -e

0 commit comments

Comments
 (0)