config: optionally read .lfsconfig from the repository

Currently we only read .lfsconfig from the working tree.  This is better
than nothing, but it means that if there's an LFS file that starts with
a name earlier than .lfsconfig, we won't read the proper config file,
and we won't use any LFS remote URLs that are located in that file.

If the file is missing, let's additionally read from the index, if
there's a working tree, and then from HEAD.  If the repository is bare,
let's just read from HEAD.  This is very similar to what Git does for
.gitmodules.

It does, however, differ in one significant way: Git will read from the
index first if it's doing a checkout, since that might have newer
information.  We don't do that here, since it's not totally clear that
we can articulate all the cases where that occurs in Git LFS, but we
explicitly allow for the possibility of changing the behavior in the
future and document the feature accordingly.

We no longer read the file .lfsconfig from a bare repository, but that
we did this in the first place was very questionable and pretty clearly
a bug, so it's intentional that we no longer do.
This commit is contained in:
brian m. carlson 2020-07-28 19:58:15 +00:00
parent 9a175c812a
commit 263b434e97
No known key found for this signature in database
GPG Key ID: 2D0C9BC12F82B3A1
4 changed files with 108 additions and 4 deletions

@ -76,7 +76,7 @@ func NewIn(workdir, gitdir string) *Configuration {
c.Git = &delayedEnvironment{
callback: func() Environment {
sources, err := gitConf.Sources(filepath.Join(c.LocalWorkingDir(), ".lfsconfig"))
sources, err := gitConf.Sources(c.LocalWorkingDir(), ".lfsconfig")
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err)
}

@ -12,6 +12,11 @@ details. This configuration file is useful for setting options such as the LFS
URL or access type for all users of a repository, especially when these differ
from the default. The `.lfsconfig` file uses the same format as `.gitconfig`.
If the `.lfsconfig` file is missing, the index is checked for a version of the
file, and that is used instead. If both are missing, `HEAD` is checked for the
file. If the repository is bare, only `HEAD` is checked. This order may change
for checkouts in the future to better match Git's behavior.
Settings from Git configuration files override the `.lfsconfig` file. This
allows you to override settings like `lfs.url` in your local environment without
having to modify the `.lfsconfig` file.

@ -2,6 +2,7 @@ package git
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
@ -134,17 +135,33 @@ func (c *Configuration) UnsetLocalKey(key string) (string, error) {
return c.gitConfigWrite("--unset", key)
}
func (c *Configuration) Sources(optionalFilename string) ([]*ConfigurationSource, error) {
func (c *Configuration) Sources(dir string, optionalFilename string) ([]*ConfigurationSource, error) {
gitconfig, err := c.Source()
if err != nil {
return nil, err
}
fileconfig, err := c.FileSource(optionalFilename)
if err != nil && !os.IsNotExist(err) {
bare, err := IsBare()
if err != nil {
return nil, err
}
// First try to read from the working directory and then the index if
// the file is missing from the working directory.
var fileconfig *ConfigurationSource
if !bare {
fileconfig, err = c.FileSource(filepath.Join(dir, optionalFilename))
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
fileconfig, _ = c.RevisionSource(fmt.Sprintf(":%s", optionalFilename))
}
}
if fileconfig == nil {
fileconfig, _ = c.RevisionSource(fmt.Sprintf("HEAD:%s", optionalFilename))
}
configs := make([]*ConfigurationSource, 0, 2)
if fileconfig != nil {
configs = append(configs, fileconfig)
@ -165,6 +182,14 @@ func (c *Configuration) FileSource(filename string) (*ConfigurationSource, error
return ParseConfigLines(out, true), nil
}
func (c *Configuration) RevisionSource(revision string) (*ConfigurationSource, error) {
out, err := c.gitConfig("-l", "--blob", revision)
if err != nil {
return nil, err
}
return ParseConfigLines(out, true), nil
}
func (c *Configuration) Source() (*ConfigurationSource, error) {
out, err := c.gitConfig("-l")
if err != nil {

@ -42,6 +42,80 @@ begin_test "default config"
)
end_test
begin_test "config reads from repository"
(
set -e
reponame="repository-config"
setup_remote_repo "$reponame"
mkdir $reponame
cd $reponame
git init
git remote add origin "$GITSERVER/$reponame"
git lfs env | tee env.log
grep "Endpoint=$GITSERVER/$reponame.git/info/lfs (auth=none)" env.log
git config --file=.lfsconfig lfs.url http://lfsconfig-file
git config --file=.lfsconfig lfs.http://lfsconfig-file.access lfsconfig
git add .lfsconfig
git commit -m 'Add file'
git push origin HEAD
git checkout -b side
git config --file=.lfsconfig lfs.url http://lfsconfig-file-side
git config --file=.lfsconfig lfs.http://lfsconfig-file-side.access lfsconfig
git add .lfsconfig
git commit -m 'Add file for side'
git push origin HEAD
mkdir "../$reponame-2"
cd "../$reponame-2"
git init
git remote add origin "$GITSERVER/$reponame"
git lfs env | tee env.log
grep "Endpoint=$GITSERVER/$reponame.git/info/lfs (auth=none)" env.log
git fetch origin
git symbolic-ref HEAD refs/remotes/origin/side
git show "HEAD:.lfsconfig"
git lfs env | tee env.log
grep "Endpoint=http://lfsconfig-file-side (auth=lfsconfig)" env.log
git read-tree refs/remotes/origin/main
git lfs env | tee env.log
grep "Endpoint=http://lfsconfig-file (auth=lfsconfig)" env.log
)
end_test
begin_test "can read LFS file with name before .lfsconfig"
(
set -e
reponame="early-file-config"
setup_remote_repo "$reponame"
mkdir $reponame
cd $reponame
git init
git remote add origin "$GITSERVER/$reponame"
git lfs track "*.bin"
git config --file=.lfsconfig lfs.url "$GITSERVER/$reponame.git/info/lfs"
echo "abc" > .bin
echo "def" > a.bin
git add .
git commit -m "Add files"
git push origin HEAD
rm -fr .git/lfs/objects
cd ..
git clone "$reponame" "$reponame-2"
cd "$reponame-2"
grep abc .bin
grep def a.bin
)
end_test
begin_test "extension config"
(
set -e