2016-05-18 10:43:42 +00:00
|
|
|
// Package config collects together all configuration settings
|
|
|
|
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
|
2016-05-13 16:38:06 +00:00
|
|
|
package config
|
2013-11-05 17:07:03 +00:00
|
|
|
|
2013-11-05 17:45:01 +00:00
|
|
|
import (
|
2017-10-18 20:45:53 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2018-10-12 18:53:07 +00:00
|
|
|
"regexp"
|
2018-10-04 21:53:06 +00:00
|
|
|
"strconv"
|
2017-10-24 21:22:13 +00:00
|
|
|
"strings"
|
2015-04-23 01:07:52 +00:00
|
|
|
"sync"
|
config: add a function for committer timestamp
The author and committer headers in a commit object each contain a name,
email address, time in seconds since the Epoch, and timezone offset.
Since we already have a function to produce the committer name and
email, add one to produce the committer timestamp.
While it may seem bizarre to place timestamp lookup in the configuration
object, it makes sense for two reasons. First, the configuration object
already stores committer name and email. Second, in a future commit,
we'll learn about the GIT_COMMITTER_DATE environment variable, which is
best read from the configuration object if present.
Finally, we compute the timestamp at the instantiation of the
configuration object because we plan to also expose author information
and timestamps. Looking the value up when the function is called could
lead to a race condition where the default author and committer
timestamps differ despite being created by the same process.
2018-10-11 14:12:01 +00:00
|
|
|
"time"
|
Properly handle config options for URLs with upper case letters
Git configuration options have a complicated situation with regard to
case. For the most part, they are case-insensitive: you may write any
case into the file, but Git interprets it as lower case. However, there
are exceptions.
Because the middle component of a three-component option can be a URL,
branch name, or remote name, this component (and only this component) is
treated as case sensitive. Since this component can be a URL, which may
(and, due to the ".git" portion, usually does) contain a dot, the first
component of the config option is read up until the first dot, and the
last component is read from the end to the last dot.
When git config writes a value into the file, it preserves the case the
user has provided, and when it prints the config values, it
canonicalizes the keys by folding the case-insensitive portions to
lowercase. Git LFS then reads this canonicalized form in as our source
of config options, relieving us from having to parse the files ourselves.
However, when we read this data in, we forced the key to lowercase, and
when we looked up a key, we also forced the key we were looking up to
lowercase. While this behavior was wrong (since URLs, at least, are
case-sensitive), it did happen to mostly work if the configuration
didn't contain settings for two URLs differing in case.
In the 2.7.0 cycle, we changed the way we did URL config lookups to
match the way Git does them. Previously, we performed configuration
lookups on several possible keys (forcing them to lower case, URL
portion included) and took the first that matched. Now, we directly
compare the URL we're connecting to (which may be in mixed case) to the
values we got in the configuration (which we've forced to lowercase).
Consequently, we will fail to find configuration options for a
mixed-case URL, resulting in things like "http.extraHeader" not being
used.
Fix this issue by letting Git do the canonicalization of configuration
keys for us instead of lowercasing them ourselves and then
canonicalizing the key when looking it up in the table. Add tests for
this behavior with "http.extraHeader" in the integration suite and
several canonicalization assertions in the unit tests. Update several
tests to use the canonical version of the data in their test data
stores, and add a check to avoid noncanonical test data.
Co-authored-by: Taylor Blau <me@ttaylorr.com>
2019-03-28 22:06:50 +00:00
|
|
|
"unicode"
|
2015-05-13 19:43:41 +00:00
|
|
|
|
2017-10-24 21:22:13 +00:00
|
|
|
"github.com/git-lfs/git-lfs/fs"
|
2017-10-18 18:34:36 +00:00
|
|
|
"github.com/git-lfs/git-lfs/git"
|
2016-11-15 17:01:18 +00:00
|
|
|
"github.com/git-lfs/git-lfs/tools"
|
2017-10-24 21:22:13 +00:00
|
|
|
"github.com/rubyist/tracerx"
|
2013-11-05 17:45:01 +00:00
|
|
|
)
|
|
|
|
|
2015-06-17 20:38:09 +00:00
|
|
|
var (
|
2015-11-24 18:58:52 +00:00
|
|
|
ShowConfigWarnings = false
|
|
|
|
defaultRemote = "origin"
|
|
|
|
gitConfigWarningPrefix = "lfs."
|
2015-06-17 20:38:09 +00:00
|
|
|
)
|
|
|
|
|
2013-11-05 17:07:03 +00:00
|
|
|
type Configuration struct {
|
2016-08-04 20:41:04 +00:00
|
|
|
// Os provides a `*Environment` used to access to the system's
|
2016-08-03 22:07:05 +00:00
|
|
|
// environment through os.Getenv. It is the point of entry for all
|
|
|
|
// system environment configuration.
|
2016-08-15 19:17:11 +00:00
|
|
|
Os Environment
|
2016-08-03 22:07:05 +00:00
|
|
|
|
2016-08-05 20:16:29 +00:00
|
|
|
// Git provides a `*Environment` used to access to the various levels of
|
|
|
|
// `.gitconfig`'s. It is the point of entry for all Git environment
|
|
|
|
// configuration.
|
2016-08-15 19:17:11 +00:00
|
|
|
Git Environment
|
2016-08-05 23:23:56 +00:00
|
|
|
|
2017-10-27 21:11:54 +00:00
|
|
|
currentRemote *string
|
2017-11-03 16:36:12 +00:00
|
|
|
pushRemote *string
|
2017-10-24 21:58:42 +00:00
|
|
|
|
2017-10-18 18:52:24 +00:00
|
|
|
// gitConfig can fetch or modify the current Git config and track the Git
|
2017-10-18 18:34:36 +00:00
|
|
|
// version.
|
2017-10-18 18:52:24 +00:00
|
|
|
gitConfig *git.Configuration
|
2017-10-18 18:34:36 +00:00
|
|
|
|
2017-10-27 22:38:49 +00:00
|
|
|
ref *git.Ref
|
2017-10-31 17:23:37 +00:00
|
|
|
remoteRef *git.Ref
|
2017-10-24 21:58:42 +00:00
|
|
|
fs *fs.Filesystem
|
|
|
|
gitDir *string
|
|
|
|
workDir string
|
2017-04-14 16:46:28 +00:00
|
|
|
loading sync.Mutex // guards initialization of gitConfig and remotes
|
2017-10-24 21:58:42 +00:00
|
|
|
loadingGit sync.Mutex // guards initialization of local git and working dirs
|
2017-04-14 16:46:28 +00:00
|
|
|
remotes []string
|
|
|
|
extensions map[string]Extension
|
2018-10-04 19:41:35 +00:00
|
|
|
mask int
|
|
|
|
maskOnce sync.Once
|
config: add a function for committer timestamp
The author and committer headers in a commit object each contain a name,
email address, time in seconds since the Epoch, and timezone offset.
Since we already have a function to produce the committer name and
email, add one to produce the committer timestamp.
While it may seem bizarre to place timestamp lookup in the configuration
object, it makes sense for two reasons. First, the configuration object
already stores committer name and email. Second, in a future commit,
we'll learn about the GIT_COMMITTER_DATE environment variable, which is
best read from the configuration object if present.
Finally, we compute the timestamp at the instantiation of the
configuration object because we plan to also expose author information
and timestamps. Looking the value up when the function is called could
lead to a race condition where the default author and committer
timestamps differ despite being created by the same process.
2018-10-11 14:12:01 +00:00
|
|
|
timestamp time.Time
|
2013-11-05 17:07:03 +00:00
|
|
|
}
|
|
|
|
|
2016-07-21 23:38:44 +00:00
|
|
|
func New() *Configuration {
|
2017-10-25 18:20:25 +00:00
|
|
|
return NewIn("", "")
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIn(workdir, gitdir string) *Configuration {
|
2017-10-25 19:47:13 +00:00
|
|
|
gitConf := git.NewConfig(workdir, gitdir)
|
2017-10-18 18:34:36 +00:00
|
|
|
c := &Configuration{
|
2017-10-27 21:11:54 +00:00
|
|
|
Os: EnvironmentOf(NewOsFetcher()),
|
|
|
|
gitConfig: gitConf,
|
config: add a function for committer timestamp
The author and committer headers in a commit object each contain a name,
email address, time in seconds since the Epoch, and timezone offset.
Since we already have a function to produce the committer name and
email, add one to produce the committer timestamp.
While it may seem bizarre to place timestamp lookup in the configuration
object, it makes sense for two reasons. First, the configuration object
already stores committer name and email. Second, in a future commit,
we'll learn about the GIT_COMMITTER_DATE environment variable, which is
best read from the configuration object if present.
Finally, we compute the timestamp at the instantiation of the
configuration object because we plan to also expose author information
and timestamps. Looking the value up when the function is called could
lead to a race condition where the default author and committer
timestamps differ despite being created by the same process.
2018-10-11 14:12:01 +00:00
|
|
|
timestamp: time.Now(),
|
2017-10-18 18:34:36 +00:00
|
|
|
}
|
2017-10-25 18:20:25 +00:00
|
|
|
|
2017-10-25 19:47:13 +00:00
|
|
|
if len(gitConf.WorkDir) > 0 {
|
|
|
|
c.gitDir = &gitConf.GitDir
|
|
|
|
c.workDir = gitConf.WorkDir
|
2017-10-25 18:20:25 +00:00
|
|
|
}
|
|
|
|
|
2017-10-18 20:45:53 +00:00
|
|
|
c.Git = &delayedEnvironment{
|
|
|
|
callback: func() Environment {
|
2017-10-19 00:35:03 +00:00
|
|
|
sources, err := gitConf.Sources(filepath.Join(c.LocalWorkingDir(), ".lfsconfig"))
|
2017-10-18 20:45:53 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err)
|
|
|
|
}
|
|
|
|
return c.readGitConfig(sources...)
|
|
|
|
},
|
2017-10-18 18:34:36 +00:00
|
|
|
}
|
2015-03-05 19:59:52 +00:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2018-10-04 19:41:35 +00:00
|
|
|
func (c *Configuration) getMask() int {
|
2018-10-04 21:53:06 +00:00
|
|
|
// This logic is necessarily complex because Git's logic is complex.
|
2018-10-04 19:41:35 +00:00
|
|
|
c.maskOnce.Do(func() {
|
2018-10-04 21:53:06 +00:00
|
|
|
val, ok := c.Git.Get("core.sharedrepository")
|
|
|
|
if !ok {
|
|
|
|
val = "umask"
|
|
|
|
} else if Bool(val, false) {
|
|
|
|
val = "group"
|
|
|
|
}
|
|
|
|
|
|
|
|
switch strings.ToLower(val) {
|
|
|
|
case "group", "true", "1":
|
|
|
|
c.mask = 007
|
|
|
|
case "all", "world", "everybody", "2":
|
|
|
|
c.mask = 002
|
|
|
|
case "umask", "false", "0":
|
|
|
|
c.mask = umask()
|
|
|
|
default:
|
|
|
|
if mode, err := strconv.ParseInt(val, 8, 16); err != nil {
|
|
|
|
// If this doesn't look like an octal number, then it
|
|
|
|
// could be a falsy value, in which case we should use
|
|
|
|
// the umask, or it's just invalid, in which case the
|
|
|
|
// umask is a safe bet.
|
|
|
|
c.mask = umask()
|
|
|
|
} else {
|
|
|
|
c.mask = 0666 & ^int(mode)
|
|
|
|
}
|
|
|
|
}
|
2018-10-04 19:41:35 +00:00
|
|
|
})
|
|
|
|
return c.mask
|
|
|
|
}
|
|
|
|
|
2017-10-18 20:45:53 +00:00
|
|
|
func (c *Configuration) readGitConfig(gitconfigs ...*git.ConfigurationSource) Environment {
|
|
|
|
gf, extensions, uniqRemotes := readGitConfig(gitconfigs...)
|
|
|
|
c.extensions = extensions
|
|
|
|
c.remotes = make([]string, 0, len(uniqRemotes))
|
2018-07-26 18:40:47 +00:00
|
|
|
for remote := range uniqRemotes {
|
2017-10-18 20:45:53 +00:00
|
|
|
c.remotes = append(c.remotes, remote)
|
|
|
|
}
|
|
|
|
|
|
|
|
return EnvironmentOf(gf)
|
|
|
|
}
|
|
|
|
|
2016-08-03 23:42:21 +00:00
|
|
|
// Values is a convenience type used to call the NewFromValues function. It
|
|
|
|
// specifies `Git` and `Env` maps to use as mock values, instead of calling out
|
|
|
|
// to real `.gitconfig`s and the `os.Getenv` function.
|
|
|
|
type Values struct {
|
2016-08-04 20:41:04 +00:00
|
|
|
// Git and Os are the stand-in maps used to provide values for their
|
2016-08-03 23:42:21 +00:00
|
|
|
// respective environments.
|
2017-04-12 21:29:11 +00:00
|
|
|
Git, Os map[string][]string
|
2016-08-03 23:42:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewFrom returns a new `*config.Configuration` that reads both its Git
|
2019-07-24 07:17:40 +00:00
|
|
|
// and Environment-level values from the ones provided instead of the actual
|
2016-08-03 23:42:21 +00:00
|
|
|
// `.gitconfig` file or `os.Getenv`, respectively.
|
2016-05-31 16:38:02 +00:00
|
|
|
//
|
2016-08-03 23:42:21 +00:00
|
|
|
// This method should only be used during testing.
|
|
|
|
func NewFrom(v Values) *Configuration {
|
2017-10-18 20:45:53 +00:00
|
|
|
c := &Configuration{
|
2017-10-27 21:11:54 +00:00
|
|
|
Os: EnvironmentOf(mapFetcher(v.Os)),
|
|
|
|
gitConfig: git.NewConfig("", ""),
|
config: add a function for committer timestamp
The author and committer headers in a commit object each contain a name,
email address, time in seconds since the Epoch, and timezone offset.
Since we already have a function to produce the committer name and
email, add one to produce the committer timestamp.
While it may seem bizarre to place timestamp lookup in the configuration
object, it makes sense for two reasons. First, the configuration object
already stores committer name and email. Second, in a future commit,
we'll learn about the GIT_COMMITTER_DATE environment variable, which is
best read from the configuration object if present.
Finally, we compute the timestamp at the instantiation of the
configuration object because we plan to also expose author information
and timestamps. Looking the value up when the function is called could
lead to a race condition where the default author and committer
timestamps differ despite being created by the same process.
2018-10-11 14:12:01 +00:00
|
|
|
timestamp: time.Now(),
|
2016-05-31 16:38:02 +00:00
|
|
|
}
|
2017-10-18 20:45:53 +00:00
|
|
|
c.Git = &delayedEnvironment{
|
|
|
|
callback: func() Environment {
|
|
|
|
source := &git.ConfigurationSource{
|
|
|
|
Lines: make([]string, 0, len(v.Git)),
|
|
|
|
}
|
|
|
|
|
|
|
|
for key, values := range v.Git {
|
Properly handle config options for URLs with upper case letters
Git configuration options have a complicated situation with regard to
case. For the most part, they are case-insensitive: you may write any
case into the file, but Git interprets it as lower case. However, there
are exceptions.
Because the middle component of a three-component option can be a URL,
branch name, or remote name, this component (and only this component) is
treated as case sensitive. Since this component can be a URL, which may
(and, due to the ".git" portion, usually does) contain a dot, the first
component of the config option is read up until the first dot, and the
last component is read from the end to the last dot.
When git config writes a value into the file, it preserves the case the
user has provided, and when it prints the config values, it
canonicalizes the keys by folding the case-insensitive portions to
lowercase. Git LFS then reads this canonicalized form in as our source
of config options, relieving us from having to parse the files ourselves.
However, when we read this data in, we forced the key to lowercase, and
when we looked up a key, we also forced the key we were looking up to
lowercase. While this behavior was wrong (since URLs, at least, are
case-sensitive), it did happen to mostly work if the configuration
didn't contain settings for two URLs differing in case.
In the 2.7.0 cycle, we changed the way we did URL config lookups to
match the way Git does them. Previously, we performed configuration
lookups on several possible keys (forcing them to lower case, URL
portion included) and took the first that matched. Now, we directly
compare the URL we're connecting to (which may be in mixed case) to the
values we got in the configuration (which we've forced to lowercase).
Consequently, we will fail to find configuration options for a
mixed-case URL, resulting in things like "http.extraHeader" not being
used.
Fix this issue by letting Git do the canonicalization of configuration
keys for us instead of lowercasing them ourselves and then
canonicalizing the key when looking it up in the table. Add tests for
this behavior with "http.extraHeader" in the integration suite and
several canonicalization assertions in the unit tests. Update several
tests to use the canonical version of the data in their test data
stores, and add a check to avoid noncanonical test data.
Co-authored-by: Taylor Blau <me@ttaylorr.com>
2019-03-28 22:06:50 +00:00
|
|
|
parts := strings.Split(key, ".")
|
|
|
|
isCaseSensitive := len(parts) >= 3
|
|
|
|
hasUpper := strings.IndexFunc(key, unicode.IsUpper) > -1
|
|
|
|
|
|
|
|
// This branch should only ever trigger in
|
|
|
|
// tests, and only if they'd be broken.
|
|
|
|
if !isCaseSensitive && hasUpper {
|
|
|
|
panic(fmt.Sprintf("key %q has uppercase, shouldn't", key))
|
|
|
|
}
|
2017-10-18 20:45:53 +00:00
|
|
|
for _, value := range values {
|
|
|
|
fmt.Printf("Config: %s=%s\n", key, value)
|
|
|
|
source.Lines = append(source.Lines, fmt.Sprintf("%s=%s", key, value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.readGitConfig(source)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return c
|
2016-05-31 16:38:02 +00:00
|
|
|
}
|
|
|
|
|
2016-07-25 18:50:58 +00:00
|
|
|
// BasicTransfersOnly returns whether to only allow "basic" HTTP transfers.
|
2016-06-10 07:43:32 +00:00
|
|
|
// Default is false, including if the lfs.basictransfersonly is invalid
|
2016-06-09 10:45:24 +00:00
|
|
|
func (c *Configuration) BasicTransfersOnly() bool {
|
2016-08-15 21:43:38 +00:00
|
|
|
return c.Git.Bool("lfs.basictransfersonly", false)
|
2016-06-09 10:45:24 +00:00
|
|
|
}
|
|
|
|
|
2016-07-25 18:50:58 +00:00
|
|
|
// TusTransfersAllowed returns whether to only use "tus.io" HTTP transfers.
|
|
|
|
// Default is false, including if the lfs.tustransfers is invalid
|
|
|
|
func (c *Configuration) TusTransfersAllowed() bool {
|
2016-08-15 21:43:38 +00:00
|
|
|
return c.Git.Bool("lfs.tustransfers", false)
|
2016-06-09 10:45:24 +00:00
|
|
|
}
|
|
|
|
|
2016-05-31 15:48:09 +00:00
|
|
|
func (c *Configuration) FetchIncludePaths() []string {
|
2016-08-05 21:59:57 +00:00
|
|
|
patterns, _ := c.Git.Get("lfs.fetchinclude")
|
|
|
|
return tools.CleanPaths(patterns, ",")
|
2015-08-06 13:56:57 +00:00
|
|
|
}
|
2016-05-27 19:11:24 +00:00
|
|
|
|
2016-05-31 15:48:09 +00:00
|
|
|
func (c *Configuration) FetchExcludePaths() []string {
|
2016-08-05 22:30:25 +00:00
|
|
|
patterns, _ := c.Git.Get("lfs.fetchexclude")
|
2016-08-05 21:59:57 +00:00
|
|
|
return tools.CleanPaths(patterns, ",")
|
2015-08-06 13:56:57 +00:00
|
|
|
}
|
|
|
|
|
2017-10-27 22:38:49 +00:00
|
|
|
func (c *Configuration) CurrentRef() *git.Ref {
|
|
|
|
c.loading.Lock()
|
|
|
|
defer c.loading.Unlock()
|
|
|
|
if c.ref == nil {
|
|
|
|
r, err := git.CurrentRef()
|
|
|
|
if err != nil {
|
|
|
|
tracerx.Printf("Error loading current ref: %s", err)
|
|
|
|
c.ref = &git.Ref{}
|
|
|
|
} else {
|
|
|
|
c.ref = r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return c.ref
|
|
|
|
}
|
|
|
|
|
2017-10-27 21:31:46 +00:00
|
|
|
func (c *Configuration) IsDefaultRemote() bool {
|
|
|
|
return c.Remote() == defaultRemote
|
|
|
|
}
|
|
|
|
|
2017-10-27 22:42:49 +00:00
|
|
|
// Remote returns the default remote based on:
|
|
|
|
// 1. The currently tracked remote branch, if present
|
|
|
|
// 2. Any other SINGLE remote defined in .git/config
|
|
|
|
// 3. Use "origin" as a fallback.
|
|
|
|
// Results are cached after the first hit.
|
2017-10-27 21:00:07 +00:00
|
|
|
func (c *Configuration) Remote() string {
|
2017-10-27 22:38:49 +00:00
|
|
|
ref := c.CurrentRef()
|
|
|
|
|
|
|
|
c.loading.Lock()
|
|
|
|
defer c.loading.Unlock()
|
|
|
|
|
2017-10-27 21:11:54 +00:00
|
|
|
if c.currentRemote == nil {
|
Fix remote autoselection when not on a branch
We currently have code that determines which remote to us automatically.
For example, if there is only one remote, we want to use it, no matter
what it's named, because there's no other logical choice.
However, if we were not on any branch, we'd always end up picking
"origin", even if that remote didn't exist. This appears to have been
because we wanted to avoid a lookup with an empty branch name in the
configuration. Doing so is harmless, though, so let's simply check that
the branch name isn't empty before we execute that condition, and fall
back to the rest of the checks if it is. This ensures that we always
pick the only remote when there's just one.
2019-08-06 21:53:13 +00:00
|
|
|
if remote, ok := c.Git.Get(fmt.Sprintf("branch.%s.remote", ref.Name)); len(ref.Name) != 0 && ok {
|
2017-10-27 22:38:49 +00:00
|
|
|
// try tracking remote
|
|
|
|
c.currentRemote = &remote
|
|
|
|
} else if remotes := c.Remotes(); len(remotes) == 1 {
|
|
|
|
// use only remote if there is only 1
|
|
|
|
c.currentRemote = &remotes[0]
|
2017-10-27 21:32:01 +00:00
|
|
|
} else {
|
2017-10-27 22:38:49 +00:00
|
|
|
// fall back to default :(
|
2017-10-27 21:32:01 +00:00
|
|
|
c.currentRemote = &defaultRemote
|
|
|
|
}
|
2017-10-27 21:11:54 +00:00
|
|
|
}
|
|
|
|
return *c.currentRemote
|
2017-10-27 21:00:07 +00:00
|
|
|
}
|
|
|
|
|
2017-11-03 16:36:12 +00:00
|
|
|
func (c *Configuration) PushRemote() string {
|
|
|
|
ref := c.CurrentRef()
|
|
|
|
c.loading.Lock()
|
|
|
|
defer c.loading.Unlock()
|
|
|
|
|
|
|
|
if c.pushRemote == nil {
|
|
|
|
if remote, ok := c.Git.Get(fmt.Sprintf("branch.%s.pushRemote", ref.Name)); ok {
|
|
|
|
c.pushRemote = &remote
|
|
|
|
} else if remote, ok := c.Git.Get("remote.pushDefault"); ok {
|
|
|
|
c.pushRemote = &remote
|
|
|
|
} else {
|
|
|
|
c.loading.Unlock()
|
|
|
|
remote := c.Remote()
|
|
|
|
c.loading.Lock()
|
|
|
|
|
|
|
|
c.pushRemote = &remote
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return *c.pushRemote
|
|
|
|
}
|
|
|
|
|
2017-10-27 22:20:38 +00:00
|
|
|
func (c *Configuration) SetValidRemote(name string) error {
|
|
|
|
if err := git.ValidateRemote(name); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.SetRemote(name)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-20 23:38:39 +00:00
|
|
|
func (c *Configuration) SetValidPushRemote(name string) error {
|
|
|
|
if err := git.ValidateRemote(name); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.SetPushRemote(name)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-10-27 21:00:07 +00:00
|
|
|
func (c *Configuration) SetRemote(name string) {
|
2017-10-27 21:11:54 +00:00
|
|
|
c.currentRemote = &name
|
2017-10-27 21:00:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-20 23:38:39 +00:00
|
|
|
func (c *Configuration) SetPushRemote(name string) {
|
|
|
|
c.pushRemote = &name
|
|
|
|
}
|
|
|
|
|
2014-02-01 20:38:29 +00:00
|
|
|
func (c *Configuration) Remotes() []string {
|
2015-03-05 19:49:15 +00:00
|
|
|
c.loadGitConfig()
|
2014-02-01 20:38:29 +00:00
|
|
|
return c.remotes
|
|
|
|
}
|
|
|
|
|
2015-07-10 20:54:06 +00:00
|
|
|
func (c *Configuration) Extensions() map[string]Extension {
|
|
|
|
c.loadGitConfig()
|
|
|
|
return c.extensions
|
|
|
|
}
|
|
|
|
|
2016-05-13 16:38:06 +00:00
|
|
|
// SortedExtensions gets the list of extensions ordered by Priority
|
|
|
|
func (c *Configuration) SortedExtensions() ([]Extension, error) {
|
|
|
|
return SortExtensions(c.Extensions())
|
|
|
|
}
|
|
|
|
|
2016-05-10 10:38:17 +00:00
|
|
|
func (c *Configuration) SkipDownloadErrors() bool {
|
2016-08-16 17:20:15 +00:00
|
|
|
return c.Os.Bool("GIT_LFS_SKIP_DOWNLOAD_ERRORS", false) || c.Git.Bool("lfs.skipdownloaderrors", false)
|
2016-05-10 10:38:17 +00:00
|
|
|
}
|
|
|
|
|
2017-01-06 09:53:14 +00:00
|
|
|
func (c *Configuration) SetLockableFilesReadOnly() bool {
|
|
|
|
return c.Os.Bool("GIT_LFS_SET_LOCKABLE_READONLY", true) && c.Git.Bool("lfs.setlockablereadonly", true)
|
|
|
|
}
|
|
|
|
|
2018-10-30 08:06:55 +00:00
|
|
|
func (c *Configuration) ForceProgress() bool {
|
|
|
|
return c.Os.Bool("GIT_LFS_FORCE_PROGRESS", false) || c.Git.Bool("lfs.forceprogress", false)
|
|
|
|
}
|
|
|
|
|
2018-08-30 19:49:38 +00:00
|
|
|
// HookDir returns the location of the hooks owned by this repository. If the
|
|
|
|
// core.hooksPath configuration variable is supported, we prefer that and expand
|
|
|
|
// paths appropriately.
|
commands,config: permit (*configuration).HookDir() to error
In preparation for (*configuration).HookDir() to perform home-directory
path expansion (e.g., expanding "~" to "/home/ttaylorr"), let's permit
this function to return an error, should the path expansion fail.
Path expansion can fail in any number of ways: either the current user
does not have a home directory, the named user (e.g., "~user") does not
have a home directory, or the named user does not exist.
Instead of either (1) throwing the error from such a case away, or (2)
rolling that error up into a panic(), let's allow ourselves a space to
propagate it outwards.
Since we do not yet support path expansion, let's always return "nil"
for now, and update the call-sites to support non-nil return values,
too.
2018-08-30 19:27:05 +00:00
|
|
|
func (c *Configuration) HookDir() (string, error) {
|
2017-10-26 02:23:43 +00:00
|
|
|
if git.IsGitVersionAtLeast("2.9.0") {
|
2017-10-18 21:51:48 +00:00
|
|
|
hp, ok := c.Git.Get("core.hooksPath")
|
|
|
|
if ok {
|
2018-08-30 19:49:38 +00:00
|
|
|
return tools.ExpandPath(hp, false)
|
2017-10-18 21:51:48 +00:00
|
|
|
}
|
|
|
|
}
|
2019-02-27 17:35:08 +00:00
|
|
|
return filepath.Join(c.LocalGitStorageDir(), "hooks"), nil
|
2017-10-19 00:25:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) InRepo() bool {
|
2017-10-24 21:58:42 +00:00
|
|
|
return len(c.LocalGitDir()) > 0
|
2017-10-18 21:51:48 +00:00
|
|
|
}
|
|
|
|
|
2017-10-19 00:09:33 +00:00
|
|
|
func (c *Configuration) LocalWorkingDir() string {
|
2017-10-24 21:58:42 +00:00
|
|
|
c.loadGitDirs()
|
|
|
|
return c.workDir
|
2017-10-19 00:09:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) LocalGitDir() string {
|
2017-10-24 21:58:42 +00:00
|
|
|
c.loadGitDirs()
|
|
|
|
return *c.gitDir
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) loadGitDirs() {
|
|
|
|
c.loadingGit.Lock()
|
|
|
|
defer c.loadingGit.Unlock()
|
|
|
|
|
|
|
|
if c.gitDir != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
gitdir, workdir, err := git.GitAndRootDirs()
|
|
|
|
if err != nil {
|
|
|
|
errMsg := err.Error()
|
|
|
|
tracerx.Printf("Error running 'git rev-parse': %s", errMsg)
|
config/config.go: case-insensitive error search
In the internal function loadGitDirs, Git LFS will attempt to resolve
the working and dotgit directory by calling 'git rev-parse --git-dir
--show-top-level'.
This information is used in the output of 'git lfs env', which displays
information about the system- and repository-level configuration as it
pertains to Git LFS.
However, when 'git lfs env' is called outside of the repository, any
information that is repository-specific is left blank. For example,
$ git lfs env
git-lfs/2.4.0 (GitHub; darwin amd64; go 1.10.3)
git version: 2.18.0
LocalWorkingDir=
We ``silence'' the error coming from 'git-rev-parse(1)' in loadGitDirs
by looking for the string "Not a git repository". If we found that
string, we don't show the error message on STDERR (because it is OK not
to), but if we fail to find that string, we assume that the error is
legitimate and thusly forward it on to STDERR.
This string changed casing in [1], so we make a corresponding change
here in order to catch the casing on all versions of Git (by making the
comparison case-insensitive).
[1]: git/git@fc045fe7d4 (Mark messages for translations, 2018-02-13)
2018-07-02 17:04:27 +00:00
|
|
|
if !strings.Contains(strings.ToLower(errMsg),
|
|
|
|
"not a git repository") {
|
2017-10-24 21:58:42 +00:00
|
|
|
fmt.Fprintf(os.Stderr, "Error: %s\n", errMsg)
|
|
|
|
}
|
|
|
|
c.gitDir = &gitdir
|
|
|
|
}
|
|
|
|
|
|
|
|
gitdir = tools.ResolveSymlinks(gitdir)
|
|
|
|
c.gitDir = &gitdir
|
|
|
|
c.workDir = tools.ResolveSymlinks(workdir)
|
2017-10-19 00:09:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) LocalGitStorageDir() string {
|
2017-10-24 21:22:13 +00:00
|
|
|
return c.Filesystem().GitStorageDir
|
2017-10-19 00:09:33 +00:00
|
|
|
}
|
|
|
|
|
2018-07-09 15:28:23 +00:00
|
|
|
func (c *Configuration) LocalReferenceDirs() []string {
|
|
|
|
return c.Filesystem().ReferenceDirs
|
2017-10-19 00:09:33 +00:00
|
|
|
}
|
|
|
|
|
2017-10-24 21:58:42 +00:00
|
|
|
func (c *Configuration) LFSStorageDir() string {
|
|
|
|
return c.Filesystem().LFSStorageDir
|
|
|
|
}
|
|
|
|
|
2017-10-25 01:16:14 +00:00
|
|
|
func (c *Configuration) LFSObjectDir() string {
|
|
|
|
return c.Filesystem().LFSObjectDir()
|
|
|
|
}
|
|
|
|
|
2017-10-25 01:20:09 +00:00
|
|
|
func (c *Configuration) LFSObjectExists(oid string, size int64) bool {
|
|
|
|
return c.Filesystem().ObjectExists(oid, size)
|
|
|
|
}
|
|
|
|
|
2017-10-25 15:49:46 +00:00
|
|
|
func (c *Configuration) EachLFSObject(fn func(fs.Object) error) error {
|
|
|
|
return c.Filesystem().EachObject(fn)
|
|
|
|
}
|
|
|
|
|
2017-10-19 00:09:33 +00:00
|
|
|
func (c *Configuration) LocalLogDir() string {
|
2017-10-24 22:21:15 +00:00
|
|
|
return c.Filesystem().LogDir()
|
2017-10-19 00:09:33 +00:00
|
|
|
}
|
|
|
|
|
2017-10-25 00:59:36 +00:00
|
|
|
func (c *Configuration) TempDir() string {
|
|
|
|
return c.Filesystem().TempDir()
|
|
|
|
}
|
|
|
|
|
2017-10-25 15:49:46 +00:00
|
|
|
func (c *Configuration) Filesystem() *fs.Filesystem {
|
|
|
|
c.loadGitDirs()
|
|
|
|
c.loading.Lock()
|
|
|
|
defer c.loading.Unlock()
|
|
|
|
|
|
|
|
if c.fs == nil {
|
|
|
|
lfsdir, _ := c.Git.Get("lfs.storage")
|
2018-07-09 16:23:27 +00:00
|
|
|
c.fs = fs.New(
|
|
|
|
c.Os,
|
|
|
|
c.LocalGitDir(),
|
|
|
|
c.LocalWorkingDir(),
|
|
|
|
lfsdir,
|
2018-12-04 22:25:02 +00:00
|
|
|
c.RepositoryPermissions(false),
|
2018-07-09 16:23:27 +00:00
|
|
|
)
|
2017-10-25 15:49:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.fs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) Cleanup() error {
|
|
|
|
c.loading.Lock()
|
|
|
|
defer c.loading.Unlock()
|
|
|
|
return c.fs.Cleanup()
|
|
|
|
}
|
|
|
|
|
2017-10-25 21:33:20 +00:00
|
|
|
func (c *Configuration) OSEnv() Environment {
|
|
|
|
return c.Os
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) GitEnv() Environment {
|
|
|
|
return c.Git
|
|
|
|
}
|
|
|
|
|
2017-10-18 18:52:24 +00:00
|
|
|
func (c *Configuration) GitConfig() *git.Configuration {
|
|
|
|
return c.gitConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) FindGitGlobalKey(key string) string {
|
|
|
|
return c.gitConfig.FindGlobal(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) FindGitSystemKey(key string) string {
|
|
|
|
return c.gitConfig.FindSystem(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) FindGitLocalKey(key string) string {
|
|
|
|
return c.gitConfig.FindLocal(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) SetGitGlobalKey(key, val string) (string, error) {
|
|
|
|
return c.gitConfig.SetGlobal(key, val)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) SetGitSystemKey(key, val string) (string, error) {
|
|
|
|
return c.gitConfig.SetSystem(key, val)
|
|
|
|
}
|
|
|
|
|
2017-10-26 01:20:35 +00:00
|
|
|
func (c *Configuration) SetGitLocalKey(key, val string) (string, error) {
|
|
|
|
return c.gitConfig.SetLocal(key, val)
|
2017-10-18 18:52:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) UnsetGitGlobalSection(key string) (string, error) {
|
|
|
|
return c.gitConfig.UnsetGlobalSection(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) UnsetGitSystemSection(key string) (string, error) {
|
|
|
|
return c.gitConfig.UnsetSystemSection(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Configuration) UnsetGitLocalSection(key string) (string, error) {
|
|
|
|
return c.gitConfig.UnsetLocalSection(key)
|
|
|
|
}
|
|
|
|
|
2017-10-26 01:20:35 +00:00
|
|
|
func (c *Configuration) UnsetGitLocalKey(key string) (string, error) {
|
|
|
|
return c.gitConfig.UnsetLocalKey(key)
|
2017-10-18 18:52:24 +00:00
|
|
|
}
|
|
|
|
|
2016-08-15 20:48:43 +00:00
|
|
|
// loadGitConfig is a temporary measure to support legacy behavior dependent on
|
|
|
|
// accessing properties set by ReadGitConfig, namely:
|
|
|
|
// - `c.extensions`
|
|
|
|
// - `c.uniqRemotes`
|
|
|
|
// - `c.gitConfig`
|
|
|
|
//
|
|
|
|
// Since the *gitEnvironment is responsible for setting these values on the
|
|
|
|
// (*config.Configuration) instance, we must call that method, if it exists.
|
|
|
|
//
|
|
|
|
// loadGitConfig returns a bool returning whether or not `loadGitConfig` was
|
|
|
|
// called AND the method did not return early.
|
2017-10-18 20:45:53 +00:00
|
|
|
func (c *Configuration) loadGitConfig() {
|
|
|
|
if g, ok := c.Git.(*delayedEnvironment); ok {
|
|
|
|
g.Load()
|
2015-10-20 16:31:56 +00:00
|
|
|
}
|
|
|
|
}
|
2016-12-05 11:45:24 +00:00
|
|
|
|
2018-10-12 18:53:07 +00:00
|
|
|
var (
|
|
|
|
// dateFormats is a list of all the date formats that Git accepts,
|
|
|
|
// except for the built-in one, which is handled below.
|
|
|
|
dateFormats = []string{
|
|
|
|
"Mon, 02 Jan 2006 15:04:05 -0700",
|
|
|
|
"2006-01-02T15:04:05-0700",
|
|
|
|
"2006-01-02 15:04:05-0700",
|
|
|
|
"2006.01.02T15:04:05-0700",
|
|
|
|
"2006.01.02 15:04:05-0700",
|
|
|
|
"01/02/2006T15:04:05-0700",
|
|
|
|
"01/02/2006 15:04:05-0700",
|
|
|
|
"02.01.2006T15:04:05-0700",
|
|
|
|
"02.01.2006 15:04:05-0700",
|
|
|
|
"2006-01-02T15:04:05Z",
|
|
|
|
"2006-01-02 15:04:05Z",
|
|
|
|
"2006.01.02T15:04:05Z",
|
|
|
|
"2006.01.02 15:04:05Z",
|
|
|
|
"01/02/2006T15:04:05Z",
|
|
|
|
"01/02/2006 15:04:05Z",
|
|
|
|
"02.01.2006T15:04:05Z",
|
|
|
|
"02.01.2006 15:04:05Z",
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultDatePattern is the regexp for Git's native date format.
|
|
|
|
defaultDatePattern = regexp.MustCompile(`\A(\d+) ([+-])(\d{2})(\d{2})\z`)
|
|
|
|
)
|
|
|
|
|
2018-10-11 15:01:05 +00:00
|
|
|
// findUserData returns the name/email that should be used in the commit header.
|
|
|
|
// We use the same technique as Git for finding this information, except that we
|
|
|
|
// don't fall back to querying the system for defaults if no values are found in
|
|
|
|
// the Git configuration or environment.
|
|
|
|
//
|
|
|
|
// envType should be "author" or "committer".
|
|
|
|
func (c *Configuration) findUserData(envType string) (name, email string) {
|
|
|
|
var filter = func(r rune) rune {
|
|
|
|
switch r {
|
|
|
|
case '<', '>', '\n':
|
|
|
|
return -1
|
|
|
|
default:
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
envType = strings.ToUpper(envType)
|
|
|
|
|
|
|
|
name, ok := c.Os.Get("GIT_" + envType + "_NAME")
|
|
|
|
if !ok {
|
|
|
|
name, _ = c.Git.Get("user.name")
|
|
|
|
}
|
|
|
|
|
|
|
|
email, ok = c.Os.Get("GIT_" + envType + "_EMAIL")
|
|
|
|
if !ok {
|
|
|
|
email, ok = c.Git.Get("user.email")
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
email, _ = c.Os.Get("EMAIL")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Git filters certain characters out of the name and email fields.
|
|
|
|
name = strings.Map(filter, name)
|
|
|
|
email = strings.Map(filter, email)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-10-12 18:53:07 +00:00
|
|
|
func (c *Configuration) findUserTimestamp(envType string) time.Time {
|
|
|
|
date, ok := c.Os.Get(fmt.Sprintf("GIT_%s_DATE", strings.ToUpper(envType)))
|
|
|
|
if !ok {
|
|
|
|
return c.timestamp
|
|
|
|
}
|
|
|
|
|
|
|
|
// time.Parse doesn't parse seconds from the Epoch, like we use in the
|
|
|
|
// Git native format, so we have to do it ourselves.
|
|
|
|
strs := defaultDatePattern.FindStringSubmatch(date)
|
|
|
|
if strs != nil {
|
|
|
|
unixSecs, _ := strconv.ParseInt(strs[1], 10, 64)
|
|
|
|
hours, _ := strconv.Atoi(strs[3])
|
|
|
|
offset, _ := strconv.Atoi(strs[4])
|
|
|
|
offset = (offset + hours*60) * 60
|
|
|
|
if strs[2] == "-" {
|
|
|
|
offset = -offset
|
|
|
|
}
|
|
|
|
|
|
|
|
return time.Unix(unixSecs, 0).In(time.FixedZone("", offset))
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, format := range dateFormats {
|
|
|
|
if t, err := time.Parse(format, date); err == nil {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The user provided an invalid value, so default to the current time.
|
|
|
|
return c.timestamp
|
|
|
|
}
|
|
|
|
|
2018-10-11 14:07:39 +00:00
|
|
|
// CurrentCommitter returns the name/email that would be used to commit a change
|
2016-12-05 11:45:24 +00:00
|
|
|
// with this configuration. In particular, the "user.name" and "user.email"
|
|
|
|
// configuration values are used
|
|
|
|
func (c *Configuration) CurrentCommitter() (name, email string) {
|
2018-10-11 15:01:05 +00:00
|
|
|
return c.findUserData("committer")
|
2016-12-05 11:45:24 +00:00
|
|
|
}
|
2018-10-04 19:41:35 +00:00
|
|
|
|
config: add a function for committer timestamp
The author and committer headers in a commit object each contain a name,
email address, time in seconds since the Epoch, and timezone offset.
Since we already have a function to produce the committer name and
email, add one to produce the committer timestamp.
While it may seem bizarre to place timestamp lookup in the configuration
object, it makes sense for two reasons. First, the configuration object
already stores committer name and email. Second, in a future commit,
we'll learn about the GIT_COMMITTER_DATE environment variable, which is
best read from the configuration object if present.
Finally, we compute the timestamp at the instantiation of the
configuration object because we plan to also expose author information
and timestamps. Looking the value up when the function is called could
lead to a race condition where the default author and committer
timestamps differ despite being created by the same process.
2018-10-11 14:12:01 +00:00
|
|
|
// CurrentCommitterTimestamp returns the timestamp that would be used to commit
|
|
|
|
// a change with this configuration.
|
|
|
|
func (c *Configuration) CurrentCommitterTimestamp() time.Time {
|
2018-10-12 18:53:07 +00:00
|
|
|
return c.findUserTimestamp("committer")
|
config: add a function for committer timestamp
The author and committer headers in a commit object each contain a name,
email address, time in seconds since the Epoch, and timezone offset.
Since we already have a function to produce the committer name and
email, add one to produce the committer timestamp.
While it may seem bizarre to place timestamp lookup in the configuration
object, it makes sense for two reasons. First, the configuration object
already stores committer name and email. Second, in a future commit,
we'll learn about the GIT_COMMITTER_DATE environment variable, which is
best read from the configuration object if present.
Finally, we compute the timestamp at the instantiation of the
configuration object because we plan to also expose author information
and timestamps. Looking the value up when the function is called could
lead to a race condition where the default author and committer
timestamps differ despite being created by the same process.
2018-10-11 14:12:01 +00:00
|
|
|
}
|
|
|
|
|
2018-10-11 18:39:18 +00:00
|
|
|
// CurrentAuthor returns the name/email that would be used to author a change
|
|
|
|
// with this configuration. In particular, the "user.name" and "user.email"
|
|
|
|
// configuration values are used
|
|
|
|
func (c *Configuration) CurrentAuthor() (name, email string) {
|
|
|
|
return c.findUserData("author")
|
|
|
|
}
|
|
|
|
|
|
|
|
// CurrentCommitterTimestamp returns the timestamp that would be used to commit
|
|
|
|
// a change with this configuration.
|
|
|
|
func (c *Configuration) CurrentAuthorTimestamp() time.Time {
|
2018-10-12 18:53:07 +00:00
|
|
|
return c.findUserTimestamp("author")
|
2018-10-11 18:39:18 +00:00
|
|
|
}
|
|
|
|
|
2018-10-04 19:41:35 +00:00
|
|
|
// RepositoryPermissions returns the permissions that should be used to write
|
|
|
|
// files in the repository.
|
2018-12-04 17:27:25 +00:00
|
|
|
func (c *Configuration) RepositoryPermissions(executable bool) os.FileMode {
|
|
|
|
perms := os.FileMode(0666 & ^c.getMask())
|
|
|
|
if executable {
|
|
|
|
return tools.ExecutablePermissions(perms)
|
|
|
|
}
|
|
|
|
return perms
|
2018-10-04 19:41:35 +00:00
|
|
|
}
|