Merge pull request #2676 from git-lfs/git-config-refactor

Git config refactor
This commit is contained in:
risk danger olson 2017-10-19 12:35:35 -07:00 committed by GitHub
commit 3480fa480e
43 changed files with 797 additions and 972 deletions

@ -63,7 +63,7 @@ func checkoutCommand(cmd *cobra.Command, args []string) {
// firstly convert any pathspecs to the root of the repo, in case this is being
// executed in a sub-folder
func rootedPaths(args []string) []string {
pathConverter, err := lfs.NewCurrentToRepoPathConverter()
pathConverter, err := lfs.NewCurrentToRepoPathConverter(cfg)
if err != nil {
Panic(err, "Could not checkout")
}

@ -90,7 +90,7 @@ func clean(to io.Writer, from io.Reader, fileName string, fileSize int64) (*lfs.
func cleanCommand(cmd *cobra.Command, args []string) {
requireStdin("This command should be run by the Git 'clean' filter")
lfs.InstallHooks(false)
installHooks(false)
var fileName string
if len(args) > 0 {

@ -7,7 +7,6 @@ import (
"path/filepath"
"strings"
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/localstorage"
"github.com/git-lfs/git-lfs/subprocess"
@ -25,7 +24,7 @@ var (
func cloneCommand(cmd *cobra.Command, args []string) {
requireGitVersion()
if git.Config.IsGitVersionAtLeast("2.15.0") {
if cfg.IsGitVersionAtLeast("2.15.0") {
msg := []string{
"WARNING: 'git lfs clone' is deprecated and will not be updated",
" with new flags from 'git clone'",
@ -73,7 +72,7 @@ func cloneCommand(cmd *cobra.Command, args []string) {
defer os.Chdir(cwd)
// Also need to derive dirs now
localstorage.ResolveDirs()
localstorage.ResolveDirs(cfg)
requireInRepo()
// Now just call pull with default args
@ -105,7 +104,7 @@ func cloneCommand(cmd *cobra.Command, args []string) {
// If --skip-repo wasn't given, install repo-level hooks while
// we're still in the checkout directory.
if err := lfs.InstallHooks(false); err != nil {
if err := installHooks(false); err != nil {
ExitWithError(err)
}
}
@ -114,7 +113,7 @@ func cloneCommand(cmd *cobra.Command, args []string) {
func postCloneSubmodules(args []string) error {
// In git 2.9+ the filter option will have been passed through to submodules
// So we need to lfs pull inside each
if !git.Config.IsGitVersionAtLeast("2.9.0") {
if !cfg.IsGitVersionAtLeast("2.9.0") {
// In earlier versions submodules would have used smudge filter
return nil
}

@ -2,7 +2,6 @@ package commands
import (
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/spf13/cobra"
)
@ -11,7 +10,7 @@ func envCommand(cmd *cobra.Command, args []string) {
config.ShowConfigWarnings = true
endpoint := getAPIClient().Endpoints.Endpoint("download", cfg.CurrentRemote)
gitV, err := git.Config.Version()
gitV, err := cfg.GitVersion()
if err != nil {
gitV = "Error getting git version: " + err.Error()
}

@ -66,6 +66,7 @@ func fetchCommand(cmd *cobra.Command, args []string) {
defer gitscanner.Close()
include, exclude := getIncludeExcludeArgs(cmd)
fetchPruneCfg := lfs.NewFetchPruneConfig(cfg.Git)
if fetchAllArg {
if fetchRecentArg || len(args) > 1 {
@ -89,17 +90,16 @@ func fetchCommand(cmd *cobra.Command, args []string) {
success = success && s
}
if fetchRecentArg || cfg.FetchPruneConfig().FetchRecentAlways {
s := fetchRecent(refs, filter)
if fetchRecentArg || fetchPruneCfg.FetchRecentAlways {
s := fetchRecent(fetchPruneCfg, refs, filter)
success = success && s
}
}
if fetchPruneArg {
fetchconf := cfg.FetchPruneConfig()
verify := fetchconf.PruneVerifyRemoteAlways
verify := fetchPruneCfg.PruneVerifyRemoteAlways
// no dry-run or verbose options in fetch, assume false
prune(fetchconf, verify, false, false)
prune(fetchPruneCfg, verify, false, false)
}
if !success {
@ -169,9 +169,7 @@ func fetchPreviousVersions(ref string, since time.Time, filter *filepathfilter.F
}
// Fetch recent objects based on config
func fetchRecent(alreadyFetchedRefs []*git.Ref, filter *filepathfilter.Filter) bool {
fetchconf := cfg.FetchPruneConfig()
func fetchRecent(fetchconf lfs.FetchPruneConfig, alreadyFetchedRefs []*git.Ref, filter *filepathfilter.Filter) bool {
if fetchconf.FetchRecentRefsDays == 0 && fetchconf.FetchRecentCommitsDays == 0 {
return true
}

@ -36,7 +36,7 @@ var filterSmudgeSkip bool
func filterCommand(cmd *cobra.Command, args []string) {
requireStdin("This command should be run by the Git filter process")
lfs.InstallHooks(false)
installHooks(false)
s := git.NewFilterProcessScanner(os.Stdin, os.Stdout)

@ -7,9 +7,9 @@ import (
"os"
"path/filepath"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/localstorage"
"github.com/spf13/cobra"
)
@ -24,7 +24,7 @@ var (
// NOTE(zeroshirts): Ideally git would have hooks for fsck such that we could
// chain a lfs-fsck, but I don't think it does.
func fsckCommand(cmd *cobra.Command, args []string) {
lfs.InstallHooks(false)
installHooks(false)
requireInRepo()
ref, err := git.CurrentRef()
@ -66,7 +66,7 @@ func fsckCommand(cmd *cobra.Command, args []string) {
return
}
storageConfig := config.Config.StorageConfig()
storageConfig := localstorage.NewConfig(cfg)
badDir := filepath.Join(storageConfig.LfsStorageDir, "bad")
Print("Moving corrupt objects to %s", badDir)

@ -18,27 +18,21 @@ var (
)
func installCommand(cmd *cobra.Command, args []string) {
opt := cmdInstallOptions()
if skipSmudgeInstall {
// assume the user is changing their smudge mode, so enable force implicitly
opt.Force = true
}
if err := lfs.InstallFilters(opt, skipSmudgeInstall); err != nil {
if err := cmdInstallOptions().Install(); err != nil {
Print("WARNING: %s", err.Error())
Print("Run `git lfs install --force` to reset git config.")
return
}
if !skipRepoInstall && (localInstall || lfs.InRepo()) {
localstorage.InitStorageOrFail()
if !skipRepoInstall && (localInstall || cfg.InRepo()) {
localstorage.InitStorageOrFail(cfg)
installHooksCommand(cmd, args)
}
Print("Git LFS initialized.")
}
func cmdInstallOptions() lfs.InstallOptions {
func cmdInstallOptions() *lfs.FilterOptions {
requireGitVersion()
if localInstall {
@ -52,10 +46,12 @@ func cmdInstallOptions() lfs.InstallOptions {
if systemInstall && os.Geteuid() != 0 {
Print("WARNING: current user is not root/admin, system install is likely to fail.")
}
return lfs.InstallOptions{
Force: forceInstall,
Local: localInstall,
System: systemInstall,
return &lfs.FilterOptions{
Force: forceInstall,
Local: localInstall,
System: systemInstall,
SkipSmudge: skipSmudgeInstall,
}
}

@ -5,7 +5,6 @@ import (
"os"
"path/filepath"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/errors"
"github.com/spf13/cobra"
)
@ -33,7 +32,7 @@ func logsShowCommand(cmd *cobra.Command, args []string) {
}
name := args[0]
by, err := ioutil.ReadFile(filepath.Join(config.LocalLogDir, name))
by, err := ioutil.ReadFile(filepath.Join(cfg.LocalLogDir(), name))
if err != nil {
Exit("Error reading log: %s", name)
}
@ -43,12 +42,12 @@ func logsShowCommand(cmd *cobra.Command, args []string) {
}
func logsClearCommand(cmd *cobra.Command, args []string) {
err := os.RemoveAll(config.LocalLogDir)
err := os.RemoveAll(cfg.LocalLogDir())
if err != nil {
Panic(err, "Error clearing %s", config.LocalLogDir)
Panic(err, "Error clearing %s", cfg.LocalLogDir())
}
Print("Cleared %s", config.LocalLogDir)
Print("Cleared %s", cfg.LocalLogDir())
}
func logsBoomtownCommand(cmd *cobra.Command, args []string) {
@ -59,7 +58,7 @@ func logsBoomtownCommand(cmd *cobra.Command, args []string) {
}
func sortedLogs() []string {
fileinfos, err := ioutil.ReadDir(config.LocalLogDir)
fileinfos, err := ioutil.ReadDir(cfg.LocalLogDir())
if err != nil {
return []string{}
}

@ -262,7 +262,7 @@ func init() {
// When lfs.TempDir is initialized to "/tmp",
// hard-linking can fail when another filesystem is
// mounted at "/tmp" (such as tmpfs).
localstorage.InitStorageOrFail()
localstorage.InitStorageOrFail(cfg)
}
cmd.AddCommand(importCmd, info)

@ -7,7 +7,6 @@ import (
"sync"
"time"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/localstorage"
@ -32,7 +31,7 @@ func pruneCommand(cmd *cobra.Command, args []string) {
Exit("Cannot specify both --verify-remote and --no-verify-remote")
}
fetchPruneConfig := cfg.FetchPruneConfig()
fetchPruneConfig := lfs.NewFetchPruneConfig(cfg.Git)
verify := !pruneDoNotVerifyArg &&
(fetchPruneConfig.PruneVerifyRemoteAlways || pruneVerifyArg)
prune(fetchPruneConfig, verify, pruneDryRunArg, pruneVerboseArg)
@ -53,7 +52,7 @@ type PruneProgress struct {
}
type PruneProgressChan chan PruneProgress
func prune(fetchPruneConfig config.FetchPruneConfig, verifyRemote, dryRun, verbose bool) {
func prune(fetchPruneConfig lfs.FetchPruneConfig, verifyRemote, dryRun, verbose bool) {
localObjects := make([]localstorage.Object, 0, 100)
retainedObjects := tools.NewStringSetWithCapacity(100)
var reachableObjects tools.StringSet
@ -345,7 +344,7 @@ func pruneTaskGetPreviousVersionsOfRef(gitscanner *lfs.GitScanner, ref string, s
}
// Background task, must call waitg.Done() once at end
func pruneTaskGetRetainedCurrentAndRecentRefs(gitscanner *lfs.GitScanner, fetchconf config.FetchPruneConfig, retainChan chan string, errorChan chan error, waitg *sync.WaitGroup) {
func pruneTaskGetRetainedCurrentAndRecentRefs(gitscanner *lfs.GitScanner, fetchconf lfs.FetchPruneConfig, retainChan chan string, errorChan chan error, waitg *sync.WaitGroup) {
defer waitg.Done()
// We actually increment the waitg in this func since we kick off sub-goroutines
@ -399,7 +398,7 @@ func pruneTaskGetRetainedCurrentAndRecentRefs(gitscanner *lfs.GitScanner, fetchc
}
// Background task, must call waitg.Done() once at end
func pruneTaskGetRetainedUnpushed(gitscanner *lfs.GitScanner, fetchconf config.FetchPruneConfig, retainChan chan string, errorChan chan error, waitg *sync.WaitGroup) {
func pruneTaskGetRetainedUnpushed(gitscanner *lfs.GitScanner, fetchconf lfs.FetchPruneConfig, retainChan chan string, errorChan chan error, waitg *sync.WaitGroup) {
defer waitg.Done()
err := gitscanner.ScanUnpushed(fetchconf.PruneRemoteName, func(p *lfs.WrappedPointer, err error) {
@ -423,7 +422,7 @@ func pruneTaskGetRetainedWorktree(gitscanner *lfs.GitScanner, retainChan chan st
// Retain other worktree HEADs too
// Working copy, branch & maybe commit is different but repo is shared
allWorktreeRefs, err := git.GetAllWorkTreeHEADs(config.LocalGitStorageDir)
allWorktreeRefs, err := git.GetAllWorkTreeHEADs(cfg.LocalGitStorageDir())
if err != nil {
errorChan <- err
return

@ -151,7 +151,7 @@ func smudge(to io.Writer, from io.Reader, filename string, skip bool, filter *fi
func smudgeCommand(cmd *cobra.Command, args []string) {
requireStdin("This command should be run by the Git 'smudge' filter")
lfs.InstallHooks(false)
installHooks(false)
if !smudgeSkip && cfg.Os.Bool("GIT_LFS_SKIP_SMUDGE", false) {
smudgeSkip = true

@ -10,9 +10,7 @@ import (
"strings"
"time"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/tools"
"github.com/spf13/cobra"
)
@ -32,18 +30,18 @@ var (
func trackCommand(cmd *cobra.Command, args []string) {
requireGitVersion()
if config.LocalGitDir == "" {
if cfg.LocalGitDir() == "" {
Print("Not a git repository.")
os.Exit(128)
}
if config.LocalWorkingDir == "" {
if cfg.LocalWorkingDir() == "" {
Print("This operation must be run in a work tree.")
os.Exit(128)
}
if !cfg.Os.Bool("GIT_LFS_TRACK_NO_INSTALL_HOOKS", false) {
lfs.InstallHooks(false)
installHooks(false)
}
if len(args) == 0 {
@ -51,7 +49,7 @@ func trackCommand(cmd *cobra.Command, args []string) {
return
}
knownPatterns := git.GetAttributePaths(config.LocalWorkingDir, config.LocalGitDir)
knownPatterns := git.GetAttributePaths(cfg.LocalWorkingDir(), cfg.LocalGitDir())
lineEnd := getAttributeLineEnding(knownPatterns)
if len(lineEnd) == 0 {
lineEnd = gitLineEnding(cfg.Git)
@ -59,9 +57,9 @@ func trackCommand(cmd *cobra.Command, args []string) {
wd, _ := tools.Getwd()
wd = tools.ResolveSymlinks(wd)
relpath, err := filepath.Rel(config.LocalWorkingDir, wd)
relpath, err := filepath.Rel(cfg.LocalWorkingDir(), wd)
if err != nil {
Exit("Current directory %q outside of git working directory %q.", wd, config.LocalWorkingDir)
Exit("Current directory %q outside of git working directory %q.", wd, cfg.LocalWorkingDir())
}
changedAttribLines := make(map[string]string)
@ -215,7 +213,7 @@ ArgsLoop:
}
func listPatterns() {
knownPatterns := git.GetAttributePaths(config.LocalWorkingDir, config.LocalGitDir)
knownPatterns := git.GetAttributePaths(cfg.LocalWorkingDir(), cfg.LocalGitDir())
if len(knownPatterns) < 1 {
return
}

@ -1,20 +1,18 @@
package commands
import (
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/localstorage"
"github.com/spf13/cobra"
)
// uninstallCmd removes any configuration and hooks set by Git LFS.
func uninstallCommand(cmd *cobra.Command, args []string) {
opt := cmdInstallOptions()
if err := lfs.UninstallFilters(opt); err != nil {
if err := cmdInstallOptions().Uninstall(); err != nil {
Error(err.Error())
}
if localInstall || lfs.InRepo() {
localstorage.InitStorageOrFail()
if localInstall || cfg.InRepo() {
localstorage.InitStorageOrFail(cfg)
uninstallHooksCommand(cmd, args)
}
@ -23,7 +21,7 @@ func uninstallCommand(cmd *cobra.Command, args []string) {
// uninstallHooksCmd removes any hooks created by Git LFS.
func uninstallHooksCommand(cmd *cobra.Command, args []string) {
if err := lfs.UninstallHooks(); err != nil {
if err := uninstallHooks(); err != nil {
Error(err.Error())
}

@ -6,24 +6,22 @@ import (
"os"
"strings"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/lfs"
"github.com/spf13/cobra"
)
// untrackCommand takes a list of paths as an argument, and removes each path from the
// default attributes file (.gitattributes), if it exists.
func untrackCommand(cmd *cobra.Command, args []string) {
if config.LocalGitDir == "" {
if cfg.LocalGitDir() == "" {
Print("Not a git repository.")
os.Exit(128)
}
if config.LocalWorkingDir == "" {
if cfg.LocalWorkingDir() == "" {
Print("This operation must be run in a work tree.")
os.Exit(128)
}
lfs.InstallHooks(false)
installHooks(false)
if len(args) < 1 {
Print("git lfs untrack <path> [path]*")

@ -3,8 +3,6 @@ package commands
import (
"regexp"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/spf13/cobra"
)
@ -31,10 +29,10 @@ func updateCommand(cmd *cobra.Command, args []string) {
switch value {
case "basic":
case "private":
git.Config.SetLocal("", key, "basic")
cfg.SetGitLocalKey("", key, "basic")
Print("Updated %s access from %s to %s.", matches[1], value, "basic")
default:
git.Config.UnsetLocalKey("", key)
cfg.UnsetGitLocalKey("", key)
Print("Removed invalid %s access of %s.", matches[1], value)
}
}
@ -44,9 +42,9 @@ func updateCommand(cmd *cobra.Command, args []string) {
}
if updateManual {
Print(lfs.GetHookInstallSteps())
Print(getHookInstallSteps())
} else {
if err := lfs.InstallHooks(updateForce); err != nil {
if err := installHooks(updateForce); err != nil {
Error(err.Error())
Exit("To resolve this, either:\n 1: run `git lfs update --manual` for instructions on how to merge hooks.\n 2: run `git lfs update --force` to overwrite your hook.")
} else {

@ -16,9 +16,9 @@ import (
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/filepathfilter"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/lfsapi"
"github.com/git-lfs/git-lfs/localstorage"
"github.com/git-lfs/git-lfs/locking"
"github.com/git-lfs/git-lfs/progress"
"github.com/git-lfs/git-lfs/tools"
@ -34,11 +34,11 @@ var (
ErrorWriter = io.MultiWriter(os.Stderr, ErrorBuffer)
OutputWriter = io.MultiWriter(os.Stdout, ErrorBuffer)
ManPages = make(map[string]string, 20)
cfg = config.Config
tqManifest = make(map[string]*tq.Manifest)
tqManifest = make(map[string]*tq.Manifest)
apiClient *lfsapi.Client
global sync.Mutex
cfg *config.Configuration
apiClient *lfsapi.Client
global sync.Mutex
includeArg string
excludeArg string
@ -91,7 +91,7 @@ func closeAPIClient() error {
}
func newLockClient(remote string) *locking.Client {
storageConfig := config.Config.StorageConfig()
storageConfig := localstorage.NewConfig(cfg)
lockClient, err := locking.NewClient(remote, getAPIClient())
if err == nil {
err = lockClient.SetupFileCache(storageConfig.LfsStorageDir)
@ -102,8 +102,8 @@ func newLockClient(remote string) *locking.Client {
}
// Configure dirs
lockClient.LocalWorkingDir = config.LocalWorkingDir
lockClient.LocalGitDir = config.LocalGitDir
lockClient.LocalWorkingDir = cfg.LocalWorkingDir()
lockClient.LocalGitDir = cfg.LocalGitDir()
lockClient.SetLockableFilesReadOnly = cfg.SetLockableFilesReadOnly()
return lockClient
@ -138,6 +138,46 @@ func downloadTransfer(p *lfs.WrappedPointer) (name, path, oid string, size int64
return p.Name, path, p.Oid, p.Size
}
// Get user-readable manual install steps for hooks
func getHookInstallSteps() string {
hooks := lfs.LoadHooks(cfg.HookDir())
steps := make([]string, 0, len(hooks))
for _, h := range hooks {
steps = append(steps, fmt.Sprintf(
"Add the following to .git/hooks/%s:\n\n%s",
h.Type, tools.Indent(h.Contents)))
}
return strings.Join(steps, "\n\n")
}
func installHooks(force bool) error {
hooks := lfs.LoadHooks(cfg.HookDir())
for _, h := range hooks {
if err := h.Install(force); err != nil {
return err
}
}
return nil
}
// uninstallHooks removes all hooks in range of the `hooks` var.
func uninstallHooks() error {
if !cfg.InRepo() {
return errors.New("Not in a git repository")
}
hooks := lfs.LoadHooks(cfg.HookDir())
for _, h := range hooks {
if err := h.Uninstall(); err != nil {
return err
}
}
return nil
}
// Error prints a formatted message to Stderr. It also gets printed to the
// panic log if one is created for this command.
func Error(format string, args ...interface{}) {
@ -253,7 +293,7 @@ func requireStdin(msg string) {
}
func requireInRepo() {
if !lfs.InRepo() {
if !cfg.InRepo() {
Print("Not in a git repository.")
os.Exit(128)
}
@ -275,11 +315,11 @@ func logPanic(loggedError error) string {
now := time.Now()
name := now.Format("20060102T150405.999999999")
full := filepath.Join(config.LocalLogDir, name+".log")
full := filepath.Join(cfg.LocalLogDir(), name+".log")
if err := os.MkdirAll(config.LocalLogDir, 0755); err != nil {
if err := os.MkdirAll(cfg.LocalLogDir(), 0755); err != nil {
full = ""
fmt.Fprintf(fmtWriter, "Unable to log panic to %s: %s\n\n", config.LocalLogDir, err.Error())
fmt.Fprintf(fmtWriter, "Unable to log panic to %s: %s\n\n", cfg.LocalLogDir(), err.Error())
} else if file, err := os.Create(full); err != nil {
filename := full
full = ""
@ -340,7 +380,7 @@ func ipAddresses() []string {
func logPanicToWriter(w io.Writer, loggedError error, le string) {
// log the version
gitV, err := git.Config.Version()
gitV, err := cfg.GitVersion()
if err != nil {
gitV = "Error getting git version: " + err.Error()
}
@ -407,15 +447,11 @@ func buildProgressMeter(dryRun bool) *progress.ProgressMeter {
func requireGitVersion() {
minimumGit := "1.8.2"
if !git.Config.IsGitVersionAtLeast(minimumGit) {
gitver, err := git.Config.Version()
if !cfg.IsGitVersionAtLeast(minimumGit) {
gitver, err := cfg.GitVersion()
if err != nil {
Exit("Error getting git version: %s", err)
}
Exit("git version >= %s is required for Git LFS, your version: %s", minimumGit, gitver)
}
}
func init() {
log.SetOutput(ErrorWriter)
}

@ -27,7 +27,7 @@ func newSingleCheckout(gitEnv config.Environment, remote string) abstractCheckou
// Get a converter from repo-relative to cwd-relative
// Since writing data & calling git update-index must be relative to cwd
pathConverter, err := lfs.NewRepoToCurrentPathConverter()
pathConverter, err := lfs.NewRepoToCurrentPathConverter(cfg)
if err != nil {
Panic(err, "Could not convert file paths")
}

@ -2,6 +2,7 @@ package commands
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
@ -51,6 +52,8 @@ func RegisterCommand(name string, runFn func(cmd *cobra.Command, args []string),
// Run initializes the 'git-lfs' command and runs it with the given stdin and
// command line args.
func Run() {
log.SetOutput(ErrorWriter)
root := NewCommand("git-lfs", gitlfsCommand)
root.PreRun = nil
@ -59,6 +62,8 @@ func Run() {
root.SetHelpFunc(helpCommand)
root.SetUsageFunc(usageCommand)
cfg = config.Config
for _, f := range commandFuncs {
if cmd := f(); cmd != nil {
root.AddCommand(cmd)
@ -78,12 +83,12 @@ func gitlfsCommand(cmd *cobra.Command, args []string) {
// necessary to wire it up via `cobra.Command.PreRun`. When run, this function
// will resolve the localstorage directories.
func resolveLocalStorage(cmd *cobra.Command, args []string) {
localstorage.ResolveDirs()
localstorage.ResolveDirs(cfg)
setupHTTPLogger(getAPIClient())
}
func setupLocalStorage(cmd *cobra.Command, args []string) {
config.ResolveGitBasicDirs()
cfg.ResolveGitBasicDirs()
setupHTTPLogger(getAPIClient())
}
@ -113,7 +118,7 @@ func setupHTTPLogger(c *lfsapi.Client) {
return
}
logBase := filepath.Join(config.LocalLogDir, "http")
logBase := filepath.Join(cfg.LocalLogDir(), "http")
if err := os.MkdirAll(logBase, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error logging http stats: %s\n", err)
return

@ -11,7 +11,6 @@ import (
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
"github.com/git-lfs/git-lfs/lfsapi"
"github.com/git-lfs/git-lfs/locking"
@ -412,7 +411,7 @@ func ensureFile(smudgePath, cleanPath string, allowMissing bool) error {
return nil
}
localPath := filepath.Join(config.LocalWorkingDir, smudgePath)
localPath := filepath.Join(cfg.LocalWorkingDir(), smudgePath)
file, err := os.Open(localPath)
if err != nil {
if allowMissing {
@ -486,6 +485,6 @@ func disableFor(endpoint lfsapi.Endpoint) error {
key := strings.Join([]string{"lfs", endpoint.Url, "locksverify"}, ".")
_, err := git.Config.SetLocal("", key, "false")
_, err := cfg.SetGitLocalKey("", key, "false")
return err
}

@ -4,15 +4,11 @@ package config
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"os"
"path/filepath"
"sync"
"path/filepath"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/tools"
)
@ -23,32 +19,6 @@ var (
gitConfigWarningPrefix = "lfs."
)
// FetchPruneConfig collects together the config options that control fetching and pruning
type FetchPruneConfig struct {
// The number of days prior to current date for which (local) refs other than HEAD
// will be fetched with --recent (default 7, 0 = only fetch HEAD)
FetchRecentRefsDays int `git:"lfs.fetchrecentrefsdays"`
// Makes the FetchRecentRefsDays option apply to remote refs from fetch source as well (default true)
FetchRecentRefsIncludeRemotes bool `git:"lfs.fetchrecentremoterefs"`
// number of days prior to latest commit on a ref that we'll fetch previous
// LFS changes too (default 0 = only fetch at ref)
FetchRecentCommitsDays int `git:"lfs.fetchrecentcommitsdays"`
// Whether to always fetch recent even without --recent
FetchRecentAlways bool `git:"lfs.fetchrecentalways"`
// Number of days added to FetchRecent*; data outside combined window will be
// deleted when prune is run. (default 3)
PruneOffsetDays int `git:"lfs.pruneoffsetdays"`
// Always verify with remote before pruning
PruneVerifyRemoteAlways bool `git:"lfs.pruneverifyremotealways"`
// Name of remote to check for unpushed and verify checks
PruneRemoteName string `git:"lfs.pruneremotetocheck"`
}
// Storage configuration
type StorageConfig struct {
LfsStorageDir string `git:"lfs.storage"`
}
type Configuration struct {
// Os provides a `*Environment` used to access to the system's
// environment through os.Getenv. It is the point of entry for all
@ -60,6 +30,12 @@ type Configuration struct {
// configuration.
Git Environment
// gitConfig can fetch or modify the current Git config and track the Git
// version.
gitConfig *git.Configuration
fs *fs
CurrentRemote string
loading sync.Mutex // guards initialization of gitConfig and remotes
@ -68,12 +44,38 @@ type Configuration struct {
}
func New() *Configuration {
c := &Configuration{Os: EnvironmentOf(NewOsFetcher())}
c.Git = &gitEnvironment{config: c}
initConfig(c)
gitConf := git.Config
c := &Configuration{
CurrentRemote: defaultRemote,
Os: EnvironmentOf(NewOsFetcher()),
gitConfig: gitConf,
}
c.Git = &delayedEnvironment{
callback: func() Environment {
sources, err := gitConf.Sources(filepath.Join(c.LocalWorkingDir(), ".lfsconfig"))
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err)
}
return c.readGitConfig(sources...)
},
}
return c
}
func (c *Configuration) readGitConfig(gitconfigs ...*git.ConfigurationSource) Environment {
gf, extensions, uniqRemotes := readGitConfig(gitconfigs...)
c.extensions = extensions
c.remotes = make([]string, 0, len(uniqRemotes))
for remote, isOrigin := range uniqRemotes {
if isOrigin {
continue
}
c.remotes = append(c.remotes, remote)
}
return EnvironmentOf(gf)
}
// 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.
@ -90,137 +92,29 @@ type Values struct {
// This method should only be used during testing.
func NewFrom(v Values) *Configuration {
c := &Configuration{
Os: EnvironmentOf(mapFetcher(v.Os)),
Git: EnvironmentOf(mapFetcher(v.Git)),
CurrentRemote: defaultRemote,
Os: EnvironmentOf(mapFetcher(v.Os)),
gitConfig: git.Config,
}
c.Git = &delayedEnvironment{
callback: func() Environment {
source := &git.ConfigurationSource{
Lines: make([]string, 0, len(v.Git)),
}
for key, values := range v.Git {
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)
},
}
initConfig(c)
return c
}
func initConfig(c *Configuration) {
c.CurrentRemote = defaultRemote
}
// Unmarshal unmarshals the *Configuration in context into all of `v`'s fields,
// according to the following rules:
//
// Values are marshaled according to the given key and environment, as follows:
// type T struct {
// Field string `git:"key"`
// Other string `os:"key"`
// }
//
// If an unknown environment is given, an error will be returned. If there is no
// method supporting conversion into a field's type, an error will be returned.
// If no value is associated with the given key and environment, the field will
// // only be modified if there is a config value present matching the given
// key. If the field is already set to a non-zero value of that field's type,
// then it will be left alone.
//
// Otherwise, the field will be set to the value of calling the
// appropriately-typed method on the specified environment.
func (c *Configuration) Unmarshal(v interface{}) error {
into := reflect.ValueOf(v)
if into.Kind() != reflect.Ptr {
return fmt.Errorf("lfs/config: unable to parse non-pointer type of %T", v)
}
into = into.Elem()
for i := 0; i < into.Type().NumField(); i++ {
field := into.Field(i)
sfield := into.Type().Field(i)
lookups, err := c.parseTag(sfield.Tag)
if err != nil {
return err
}
var val interface{}
for _, lookup := range lookups {
if _, ok := lookup.Get(); !ok {
continue
}
switch sfield.Type.Kind() {
case reflect.String:
val, _ = lookup.Get()
case reflect.Int:
val = lookup.Int(int(field.Int()))
case reflect.Bool:
val = lookup.Bool(field.Bool())
default:
return fmt.Errorf("lfs/config: unsupported target type for field %q: %v",
sfield.Name, sfield.Type.String())
}
if val != nil {
break
}
}
if val != nil {
into.Field(i).Set(reflect.ValueOf(val))
}
}
return nil
}
var (
tagRe = regexp.MustCompile("((\\w+:\"[^\"]*\")\\b?)+")
emptyEnv = EnvironmentOf(MapFetcher(nil))
)
type lookup struct {
key string
env Environment
}
func (l *lookup) Get() (interface{}, bool) { return l.env.Get(l.key) }
func (l *lookup) Int(or int) int { return l.env.Int(l.key, or) }
func (l *lookup) Bool(or bool) bool { return l.env.Bool(l.key, or) }
// parseTag returns the key, environment, and optional error assosciated with a
// given tag. It will return the XOR of either the `git` or `os` tag. That is to
// say, a field tagged with EITHER `git` OR `os` is valid, but pone tagged with
// both is not.
//
// If neither field was found, then a nil environment will be returned.
func (c *Configuration) parseTag(tag reflect.StructTag) ([]*lookup, error) {
var lookups []*lookup
parts := tagRe.FindAllString(string(tag), -1)
for _, part := range parts {
sep := strings.SplitN(part, ":", 2)
if len(sep) != 2 {
return nil, errors.Errorf("config: invalid struct tag %q", tag)
}
var env Environment
switch strings.ToLower(sep[0]) {
case "git":
env = c.Git
case "os":
env = c.Os
default:
// ignore other struct tags, like `json:""`, etc.
env = emptyEnv
}
uq, err := strconv.Unquote(sep[1])
if err != nil {
return nil, err
}
lookups = append(lookups, &lookup{
key: uq,
env: env,
})
}
return lookups, nil
}
// BasicTransfersOnly returns whether to only allow "basic" HTTP transfers.
// Default is false, including if the lfs.basictransfersonly is invalid
func (c *Configuration) BasicTransfersOnly() bool {
@ -245,7 +139,6 @@ func (c *Configuration) FetchExcludePaths() []string {
func (c *Configuration) Remotes() []string {
c.loadGitConfig()
return c.remotes
}
@ -259,34 +152,6 @@ func (c *Configuration) SortedExtensions() ([]Extension, error) {
return SortExtensions(c.Extensions())
}
func (c *Configuration) FetchPruneConfig() FetchPruneConfig {
f := &FetchPruneConfig{
FetchRecentRefsDays: 7,
FetchRecentRefsIncludeRemotes: true,
PruneOffsetDays: 3,
PruneRemoteName: "origin",
}
if err := c.Unmarshal(f); err != nil {
panic(err.Error())
}
return *f
}
func (c *Configuration) StorageConfig() StorageConfig {
s := &StorageConfig{
LfsStorageDir: "lfs",
}
if err := c.Unmarshal(s); err != nil {
panic(err.Error())
}
if !filepath.IsAbs(s.LfsStorageDir) {
s.LfsStorageDir = filepath.Join(LocalGitStorageDir, s.LfsStorageDir)
}
return *s
}
func (c *Configuration) SkipDownloadErrors() bool {
return c.Os.Bool("GIT_LFS_SKIP_DOWNLOAD_ERRORS", false) || c.Git.Bool("lfs.skipdownloaderrors", false)
}
@ -295,6 +160,110 @@ func (c *Configuration) SetLockableFilesReadOnly() bool {
return c.Os.Bool("GIT_LFS_SET_LOCKABLE_READONLY", true) && c.Git.Bool("lfs.setlockablereadonly", true)
}
func (c *Configuration) HookDir() string {
if c.gitConfig.IsGitVersionAtLeast("2.9.0") {
hp, ok := c.Git.Get("core.hooksPath")
if ok {
return hp
}
}
return filepath.Join(c.LocalGitDir(), "hooks")
}
func (c *Configuration) InRepo() bool {
return c.fs.InRepo()
}
func (c *Configuration) LocalWorkingDir() string {
c.ResolveGitBasicDirs()
return c.fs.WorkingDir
}
func (c *Configuration) LocalGitDir() string {
c.ResolveGitBasicDirs()
return c.fs.GitDir
}
func (c *Configuration) LocalGitStorageDir() string {
c.ResolveGitBasicDirs()
return c.fs.GitStorageDir
}
func (c *Configuration) LocalReferenceDir() string {
return LocalReferenceDir
}
func (c *Configuration) LocalLogDir() string {
c.ResolveGitBasicDirs()
return c.fs.LogDir
}
func (c *Configuration) SetLocalLogDir(s string) {
c.ResolveGitBasicDirs()
c.fs.LogDir = s
}
func (c *Configuration) GitConfig() *git.Configuration {
return c.gitConfig
}
func (c *Configuration) GitVersion() (string, error) {
return c.gitConfig.Version()
}
func (c *Configuration) IsGitVersionAtLeast(ver string) bool {
return c.gitConfig.IsGitVersionAtLeast(ver)
}
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)
}
func (c *Configuration) SetGitLocalKey(file, key, val string) (string, error) {
return c.gitConfig.SetLocal(file, key, val)
}
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)
}
func (c *Configuration) UnsetGitLocalKey(file, key string) (string, error) {
return c.gitConfig.UnsetLocalKey(file, key)
}
func (c *Configuration) ResolveGitBasicDirs() {
c.loading.Lock()
defer c.loading.Unlock()
if c.fs == nil {
c.fs = resolveGitBasicDirs()
}
}
// loadGitConfig is a temporary measure to support legacy behavior dependent on
// accessing properties set by ReadGitConfig, namely:
// - `c.extensions`
@ -306,12 +275,10 @@ func (c *Configuration) SetLockableFilesReadOnly() bool {
//
// loadGitConfig returns a bool returning whether or not `loadGitConfig` was
// called AND the method did not return early.
func (c *Configuration) loadGitConfig() bool {
if g, ok := c.Git.(*gitEnvironment); ok {
return g.loadGitConfig()
func (c *Configuration) loadGitConfig() {
if g, ok := c.Git.(*delayedEnvironment); ok {
g.Load()
}
return false
}
// CurrentCommitter returns the name/email that would be used to author a commit

@ -2,7 +2,6 @@ package config
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
@ -67,17 +66,12 @@ func TestTusTransfersAllowedInvalidValue(t *testing.T) {
func TestLoadValidExtension(t *testing.T) {
cfg := NewFrom(Values{
Git: map[string][]string{},
})
cfg.extensions = map[string]Extension{
"foo": Extension{
"foo",
"foo-clean %f",
"foo-smudge %f",
2,
Git: map[string][]string{
"lfs.extension.foo.clean": []string{"foo-clean %f"},
"lfs.extension.foo.smudge": []string{"foo-smudge %f"},
"lfs.extension.foo.priority": []string{"2"},
},
}
})
ext := cfg.Extensions()["foo"]
@ -97,40 +91,6 @@ func TestLoadInvalidExtension(t *testing.T) {
assert.Equal(t, 0, ext.Priority)
}
func TestFetchPruneConfigDefault(t *testing.T) {
cfg := NewFrom(Values{})
fp := cfg.FetchPruneConfig()
assert.Equal(t, 7, fp.FetchRecentRefsDays)
assert.Equal(t, 0, fp.FetchRecentCommitsDays)
assert.Equal(t, 3, fp.PruneOffsetDays)
assert.True(t, fp.FetchRecentRefsIncludeRemotes)
assert.Equal(t, 3, fp.PruneOffsetDays)
assert.Equal(t, "origin", fp.PruneRemoteName)
assert.False(t, fp.PruneVerifyRemoteAlways)
}
func TestFetchPruneConfigCustom(t *testing.T) {
cfg := NewFrom(Values{
Git: map[string][]string{
"lfs.fetchrecentrefsdays": []string{"12"},
"lfs.fetchrecentremoterefs": []string{"false"},
"lfs.fetchrecentcommitsdays": []string{"9"},
"lfs.pruneoffsetdays": []string{"30"},
"lfs.pruneverifyremotealways": []string{"true"},
"lfs.pruneremotetocheck": []string{"upstream"},
},
})
fp := cfg.FetchPruneConfig()
assert.Equal(t, 12, fp.FetchRecentRefsDays)
assert.Equal(t, 9, fp.FetchRecentCommitsDays)
assert.False(t, fp.FetchRecentRefsIncludeRemotes)
assert.Equal(t, 30, fp.PruneOffsetDays)
assert.Equal(t, "upstream", fp.PruneRemoteName)
assert.True(t, fp.PruneVerifyRemoteAlways)
}
func TestFetchIncludeExcludesAreCleaned(t *testing.T) {
cfg := NewFrom(Values{
Git: map[string][]string{
@ -142,157 +102,3 @@ func TestFetchIncludeExcludesAreCleaned(t *testing.T) {
assert.Equal(t, []string{"/path/to/clean"}, cfg.FetchIncludePaths())
assert.Equal(t, []string{"/other/path/to/clean"}, cfg.FetchExcludePaths())
}
func TestUnmarshalMultipleTypes(t *testing.T) {
cfg := NewFrom(Values{
Git: map[string][]string{
"string": []string{"string"},
"int": []string{"1"},
"bool": []string{"true"},
},
Os: map[string][]string{
"string": []string{"string"},
"int": []string{"1"},
"bool": []string{"true"},
},
})
v := &struct {
GitString string `git:"string"`
GitInt int `git:"int"`
GitBool bool `git:"bool"`
OsString string `os:"string"`
OsInt int `os:"int"`
OsBool bool `os:"bool"`
}{}
assert.Nil(t, cfg.Unmarshal(v))
assert.Equal(t, "string", v.GitString)
assert.Equal(t, 1, v.GitInt)
assert.Equal(t, true, v.GitBool)
assert.Equal(t, "string", v.OsString)
assert.Equal(t, 1, v.OsInt)
assert.Equal(t, true, v.OsBool)
}
func TestUnmarshalErrsOnNonPointerType(t *testing.T) {
type T struct {
Foo string `git:"foo"`
}
cfg := NewFrom(Values{})
err := cfg.Unmarshal(T{})
assert.Equal(t, "lfs/config: unable to parse non-pointer type of config.T", err.Error())
}
func TestUnmarshalLeavesNonZeroValuesWhenKeysEmpty(t *testing.T) {
v := &struct {
String string `git:"string"`
Int int `git:"int"`
Bool bool `git:"bool"`
}{"foo", 1, true}
cfg := NewFrom(Values{})
err := cfg.Unmarshal(v)
assert.Nil(t, err)
assert.Equal(t, "foo", v.String)
assert.Equal(t, 1, v.Int)
assert.Equal(t, true, v.Bool)
}
func TestUnmarshalOverridesNonZeroValuesWhenValuesPresent(t *testing.T) {
v := &struct {
String string `git:"string"`
Int int `git:"int"`
Bool bool `git:"bool"`
}{"foo", 1, true}
cfg := NewFrom(Values{
Git: map[string][]string{
"string": []string{"bar"},
"int": []string{"2"},
"bool": []string{"false"},
},
})
err := cfg.Unmarshal(v)
assert.Nil(t, err)
assert.Equal(t, "bar", v.String)
assert.Equal(t, 2, v.Int)
assert.Equal(t, false, v.Bool)
}
func TestUnmarshalAllowsBothOsAndGitTags(t *testing.T) {
v := &struct {
String string `git:"string" os:"STRING"`
}{}
cfg := NewFrom(Values{
Git: map[string][]string{"string": []string{"foo"}},
Os: map[string][]string{"STRING": []string{"bar"}},
})
err := cfg.Unmarshal(v)
assert.Nil(t, err)
assert.Equal(t, "foo", v.String)
}
func TestUnmarshalYieldsToDefaultIfBothEnvsMissing(t *testing.T) {
v := &struct {
String string `git:"string" os:"STRING"`
}{"foo"}
cfg := NewFrom(Values{})
err := cfg.Unmarshal(v)
assert.Nil(t, err)
assert.Equal(t, "foo", v.String)
}
func TestUnmarshalOverridesDefaultIfAnyEnvPresent(t *testing.T) {
v := &struct {
String string `git:"string" os:"STRING"`
}{"foo"}
cfg := NewFrom(Values{
Git: map[string][]string{"string": []string{"bar"}},
Os: map[string][]string{"STRING": []string{"baz"}},
})
err := cfg.Unmarshal(v)
assert.Nil(t, err)
assert.Equal(t, "bar", v.String)
}
func TestUnmarshalIgnoresUnknownEnvironments(t *testing.T) {
v := &struct {
String string `unknown:"string"`
}{}
cfg := NewFrom(Values{})
assert.Nil(t, cfg.Unmarshal(v))
}
func TestUnmarshalErrsOnUnsupportedTypes(t *testing.T) {
v := &struct {
Unsupported time.Duration `git:"duration"`
}{}
cfg := NewFrom(Values{
Git: map[string][]string{"duration": []string{"foo"}},
})
err := cfg.Unmarshal(v)
assert.Equal(t, "lfs/config: unsupported target type for field \"Unsupported\": time.Duration", err.Error())
}

@ -0,0 +1,68 @@
package config
import (
"sync"
)
// delayedEnvironment is an implementation of the Environment which wraps the legacy
// behavior of `*config.Configuration.loadGitConfig()`.
//
// It is functionally equivelant to call `cfg.loadGitConfig()` before calling
// methods on the Environment type.
type delayedEnvironment struct {
env Environment
loading sync.Mutex
callback func() Environment
}
// Get is shorthand for calling the e.Load(), and then returning
// `e.env.Get(key)`.
func (e *delayedEnvironment) Get(key string) (string, bool) {
e.Load()
return e.env.Get(key)
}
// Get is shorthand for calling the e.Load(), and then returning
// `e.env.GetAll(key)`.
func (e *delayedEnvironment) GetAll(key string) []string {
e.Load()
return e.env.GetAll(key)
}
// Get is shorthand for calling the e.Load(), and then returning
// `e.env.Bool(key, def)`.
func (e *delayedEnvironment) Bool(key string, def bool) bool {
e.Load()
return e.env.Bool(key, def)
}
// Get is shorthand for calling the e.Load(), and then returning
// `e.env.Int(key, def)`.
func (e *delayedEnvironment) Int(key string, def int) int {
e.Load()
return e.env.Int(key, def)
}
// All returns a copy of all the key/value pairs for the current git config.
func (e *delayedEnvironment) All() map[string][]string {
e.Load()
return e.env.All()
}
// Load reads and parses the .gitconfig by calling ReadGitConfig. It
// also sets values on the configuration instance `g.config`.
//
// If Load has already been called, this method will bail out early,
// and return false. Otherwise it will preform the entire parse and return true.
//
// Load is safe to call across multiple goroutines.
func (e *delayedEnvironment) Load() {
e.loading.Lock()
defer e.loading.Unlock()
if e.env != nil {
return
}
e.env = e.callback()
}

@ -13,32 +13,48 @@ import (
)
var (
LocalWorkingDir string
LocalGitDir string // parent of index / config / hooks etc
LocalGitStorageDir string // parent of objects/lfs (may be same as LocalGitDir but may not)
LocalReferenceDir string // alternative local media dir (relative to clone reference repo)
LocalLogDir string
LocalReferenceDir string // alternative local media dir (relative to clone reference repo)
)
type fs struct {
WorkingDir string
GitDir string // parent of index / config / hooks etc
GitStorageDir string // parent of objects/lfs (may be same as LocalGitDir but may not)
ReferenceDir string // alternative local media dir (relative to clone reference repo)
LogDir string
}
func (f *fs) InRepo() bool {
if f == nil {
return false
}
return len(f.GitDir) > 0
}
// Determins the LocalWorkingDir, LocalGitDir etc
func ResolveGitBasicDirs() {
var err error
LocalGitDir, LocalWorkingDir, err = git.GitAndRootDirs()
if err == nil {
// Make sure we've fully evaluated symlinks, failure to do consistently
// can cause discrepancies
LocalGitDir = tools.ResolveSymlinks(LocalGitDir)
LocalWorkingDir = tools.ResolveSymlinks(LocalWorkingDir)
LocalGitStorageDir = resolveGitStorageDir(LocalGitDir)
LocalReferenceDir = resolveReferenceDir(LocalGitStorageDir)
} else {
func resolveGitBasicDirs() *fs {
localGitDir, localWorkingDir, err := git.GitAndRootDirs()
if err != nil {
errMsg := err.Error()
tracerx.Printf("Error running 'git rev-parse': %s", errMsg)
if !strings.Contains(errMsg, "Not a git repository") {
fmt.Fprintf(os.Stderr, "Error: %s\n", errMsg)
}
return &fs{}
}
// Make sure we've fully evaluated symlinks, failure to do consistently
// can cause discrepancies
localGitDir = tools.ResolveSymlinks(localGitDir)
localWorkingDir = tools.ResolveSymlinks(localWorkingDir)
localGitStorageDir := resolveGitStorageDir(localGitDir)
LocalReferenceDir = resolveReferenceDir(localGitStorageDir)
return &fs{
GitDir: localGitDir,
WorkingDir: localWorkingDir,
GitStorageDir: localGitStorageDir,
ReferenceDir: LocalReferenceDir,
}
}

@ -1,85 +0,0 @@
package config
// gitEnvironment is an implementation of the Environment which wraps the legacy
// behavior or `*config.Configuration.loadGitConfig()`.
//
// It is functionally equivelant to call `cfg.loadGitConfig()` before calling
// methods on the Environment type.
type gitEnvironment struct {
// git is the Environment which gitEnvironment wraps.
git Environment
// config is the *Configuration instance which is mutated by
// `loadGitConfig`.
config *Configuration
}
// Get is shorthand for calling the loadGitConfig, and then returning
// `g.git.Get(key)`.
func (g *gitEnvironment) Get(key string) (val string, ok bool) {
g.loadGitConfig()
return g.git.Get(key)
}
// Get is shorthand for calling the loadGitConfig, and then returning
// `g.git.GetAll(key)`.
func (g *gitEnvironment) GetAll(key string) []string {
g.loadGitConfig()
return g.git.GetAll(key)
}
// Get is shorthand for calling the loadGitConfig, and then returning
// `g.git.Bool(key, def)`.
func (g *gitEnvironment) Bool(key string, def bool) (val bool) {
g.loadGitConfig()
return g.git.Bool(key, def)
}
// Get is shorthand for calling the loadGitConfig, and then returning
// `g.git.Int(key, def)`.
func (g *gitEnvironment) Int(key string, def int) (val int) {
g.loadGitConfig()
return g.git.Int(key, def)
}
// All returns a copy of all the key/value pairs for the current git config.
func (g *gitEnvironment) All() map[string][]string {
g.loadGitConfig()
return g.git.All()
}
// loadGitConfig reads and parses the .gitconfig by calling ReadGitConfig. It
// also sets values on the configuration instance `g.config`.
//
// If loadGitConfig has already been called, this method will bail out early,
// and return false. Otherwise it will preform the entire parse and return true.
//
// loadGitConfig is safe to call across multiple goroutines.
func (g *gitEnvironment) loadGitConfig() bool {
g.config.loading.Lock()
defer g.config.loading.Unlock()
if g.git != nil {
return false
}
gf, extensions, uniqRemotes := ReadGitConfig(getGitConfigs()...)
g.git = EnvironmentOf(gf)
g.config.extensions = extensions
g.config.remotes = make([]string, 0, len(uniqRemotes))
for remote, isOrigin := range uniqRemotes {
if isOrigin {
continue
}
g.config.remotes = append(g.config.remotes, remote)
}
return true
}

@ -3,7 +3,6 @@ package config
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
@ -16,19 +15,7 @@ type GitFetcher struct {
vals map[string][]string
}
type GitConfig struct {
Lines []string
OnlySafeKeys bool
}
func NewGitConfig(gitconfiglines string, onlysafe bool) *GitConfig {
return &GitConfig{
Lines: strings.Split(gitconfiglines, "\n"),
OnlySafeKeys: onlysafe,
}
}
func ReadGitConfig(configs ...*GitConfig) (gf *GitFetcher, extensions map[string]Extension, uniqRemotes map[string]bool) {
func readGitConfig(configs ...*git.ConfigurationSource) (gf *GitFetcher, extensions map[string]Extension, uniqRemotes map[string]bool) {
vals := make(map[string][]string)
ignored := make([]string, 0)
@ -161,39 +148,6 @@ func (g *GitFetcher) All() map[string][]string {
return newmap
}
func getGitConfigs() (sources []*GitConfig) {
if lfsconfig := getFileGitConfig(".lfsconfig"); lfsconfig != nil {
sources = append(sources, lfsconfig)
}
globalList, err := git.Config.List()
if err == nil {
sources = append(sources, NewGitConfig(globalList, false))
} else {
fmt.Fprintf(os.Stderr, "Error reading git config: %s\n", err)
}
return
}
func getFileGitConfig(basename string) *GitConfig {
fullname := filepath.Join(LocalWorkingDir, basename)
if _, err := os.Stat(fullname); err != nil {
if !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", basename, err)
}
return nil
}
lines, err := git.Config.ListFromFile(fullname)
if err == nil {
return NewGitConfig(lines, true)
}
fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", basename, err)
return nil
}
func keyIsUnsafe(key string) bool {
for _, safe := range safeKeys {
if safe == key {

167
git/config.go Normal file

@ -0,0 +1,167 @@
package git
import (
"os"
"strings"
"sync"
"github.com/rubyist/tracerx"
)
var Config = &Configuration{}
// Configuration can fetch or modify the current Git config and track the Git
// version.
type Configuration struct {
version string
mu sync.Mutex
}
func ParseConfigLines(lines string, onlySafeKeys bool) *ConfigurationSource {
return &ConfigurationSource{
Lines: strings.Split(lines, "\n"),
OnlySafeKeys: onlySafeKeys,
}
}
type ConfigurationSource struct {
Lines []string
OnlySafeKeys bool
}
// Find returns the git config value for the key
func (c *Configuration) Find(val string) string {
output, _ := gitSimple("config", val)
return output
}
// FindGlobal returns the git config value global scope for the key
func (c *Configuration) FindGlobal(key string) string {
output, _ := gitSimple("config", "--global", key)
return output
}
// FindSystem returns the git config value in system scope for the key
func (c *Configuration) FindSystem(key string) string {
output, _ := gitSimple("config", "--system", key)
return output
}
// Find returns the git config value for the key
func (c *Configuration) FindLocal(key string) string {
output, _ := gitSimple("config", "--local", key)
return output
}
// SetGlobal sets the git config value for the key in the global config
func (c *Configuration) SetGlobal(key, val string) (string, error) {
return gitSimple("config", "--global", "--replace-all", key, val)
}
// SetSystem sets the git config value for the key in the system config
func (c *Configuration) SetSystem(key, val string) (string, error) {
return gitSimple("config", "--system", "--replace-all", key, val)
}
// UnsetGlobalSection removes the entire named section from the global config
func (c *Configuration) UnsetGlobalSection(key string) (string, error) {
return gitSimple("config", "--global", "--remove-section", key)
}
// UnsetSystemSection removes the entire named section from the system config
func (c *Configuration) UnsetSystemSection(key string) (string, error) {
return gitSimple("config", "--system", "--remove-section", key)
}
// UnsetLocalSection removes the entire named section from the system config
func (c *Configuration) UnsetLocalSection(key string) (string, error) {
return gitSimple("config", "--local", "--remove-section", key)
}
// SetLocal sets the git config value for the key in the specified config file
func (c *Configuration) SetLocal(file, key, val string) (string, error) {
args := make([]string, 1, 6)
args[0] = "config"
if len(file) > 0 {
args = append(args, "--file", file)
}
args = append(args, "--replace-all", key, val)
return gitSimple(args...)
}
// UnsetLocalKey removes the git config value for the key from the specified config file
func (c *Configuration) UnsetLocalKey(file, key string) (string, error) {
args := make([]string, 1, 5)
args[0] = "config"
if len(file) > 0 {
args = append(args, "--file", file)
}
args = append(args, "--unset", key)
return gitSimple(args...)
}
func (c *Configuration) Sources(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) {
return nil, err
}
configs := make([]*ConfigurationSource, 0, 2)
if fileconfig != nil {
configs = append(configs, fileconfig)
}
return append(configs, gitconfig), nil
}
func (c *Configuration) FileSource(filename string) (*ConfigurationSource, error) {
if _, err := os.Stat(filename); err != nil {
return nil, err
}
out, err := gitSimple("config", "-l", "-f", filename)
if err != nil {
return nil, err
}
return ParseConfigLines(out, true), nil
}
func (c *Configuration) Source() (*ConfigurationSource, error) {
out, err := gitSimple("config", "-l")
if err != nil {
return nil, err
}
return ParseConfigLines(out, false), nil
}
// Version returns the git version
func (c *Configuration) Version() (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
if len(c.version) == 0 {
v, err := gitSimple("version")
if err != nil {
return v, err
}
c.version = v
}
return c.version, nil
}
// IsVersionAtLeast returns whether the git version is the one specified or higher
// argument is plain version string separated by '.' e.g. "2.3.1" but can omit minor/patch
func (c *Configuration) IsGitVersionAtLeast(ver string) bool {
gitver, err := c.Version()
if err != nil {
tracerx.Printf("Error getting git version: %v", err)
return false
}
return IsVersionAtLeast(gitver, ver)
}

@ -16,7 +16,6 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"time"
lfserrors "github.com/git-lfs/git-lfs/errors"
@ -293,7 +292,6 @@ func RemoteBranchForLocalBranch(localBranch string) string {
} else {
return localBranch
}
}
func RemoteList() ([]string, error) {
@ -462,131 +460,6 @@ func UpdateIndexFromStdin() *subprocess.Cmd {
return git("update-index", "-q", "--refresh", "--stdin")
}
type gitConfig struct {
gitVersion string
mu sync.Mutex
}
var Config = &gitConfig{}
// Find returns the git config value for the key
func (c *gitConfig) Find(val string) string {
output, _ := gitSimple("config", val)
return output
}
// FindGlobal returns the git config value global scope for the key
func (c *gitConfig) FindGlobal(val string) string {
output, _ := gitSimple("config", "--global", val)
return output
}
// FindSystem returns the git config value in system scope for the key
func (c *gitConfig) FindSystem(val string) string {
output, _ := gitSimple("config", "--system", val)
return output
}
// Find returns the git config value for the key
func (c *gitConfig) FindLocal(val string) string {
output, _ := gitSimple("config", "--local", val)
return output
}
// SetGlobal sets the git config value for the key in the global config
func (c *gitConfig) SetGlobal(key, val string) (string, error) {
return gitSimple("config", "--global", "--replace-all", key, val)
}
// SetSystem sets the git config value for the key in the system config
func (c *gitConfig) SetSystem(key, val string) (string, error) {
return gitSimple("config", "--system", "--replace-all", key, val)
}
// UnsetGlobal removes the git config value for the key from the global config
func (c *gitConfig) UnsetGlobal(key string) (string, error) {
return gitSimple("config", "--global", "--unset", key)
}
// UnsetSystem removes the git config value for the key from the system config
func (c *gitConfig) UnsetSystem(key string) (string, error) {
return gitSimple("config", "--system", "--unset", key)
}
// UnsetGlobalSection removes the entire named section from the global config
func (c *gitConfig) UnsetGlobalSection(key string) (string, error) {
return gitSimple("config", "--global", "--remove-section", key)
}
// UnsetSystemSection removes the entire named section from the system config
func (c *gitConfig) UnsetSystemSection(key string) (string, error) {
return gitSimple("config", "--system", "--remove-section", key)
}
// UnsetLocalSection removes the entire named section from the system config
func (c *gitConfig) UnsetLocalSection(key string) (string, error) {
return gitSimple("config", "--local", "--remove-section", key)
}
// SetLocal sets the git config value for the key in the specified config file
func (c *gitConfig) SetLocal(file, key, val string) (string, error) {
args := make([]string, 1, 6)
args[0] = "config"
if len(file) > 0 {
args = append(args, "--file", file)
}
args = append(args, "--replace-all", key, val)
return gitSimple(args...)
}
// UnsetLocalKey removes the git config value for the key from the specified config file
func (c *gitConfig) UnsetLocalKey(file, key string) (string, error) {
args := make([]string, 1, 5)
args[0] = "config"
if len(file) > 0 {
args = append(args, "--file", file)
}
args = append(args, "--unset", key)
return gitSimple(args...)
}
// List lists all of the git config values
func (c *gitConfig) List() (string, error) {
return gitSimple("config", "-l")
}
// ListFromFile lists all of the git config values in the given config file
func (c *gitConfig) ListFromFile(f string) (string, error) {
return gitSimple("config", "-l", "-f", f)
}
// Version returns the git version
func (c *gitConfig) Version() (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
if len(c.gitVersion) == 0 {
v, err := gitSimple("version")
if err != nil {
return v, err
}
c.gitVersion = v
}
return c.gitVersion, nil
}
// IsVersionAtLeast returns whether the git version is the one specified or higher
// argument is plain version string separated by '.' e.g. "2.3.1" but can omit minor/patch
func (c *gitConfig) IsGitVersionAtLeast(ver string) bool {
gitver, err := c.Version()
if err != nil {
tracerx.Printf("Error getting git version: %v", err)
return false
}
return IsVersionAtLeast(gitver, ver)
}
// RecentBranches returns branches with commit dates on or after the given date/time
// Return full Ref type for easier detection of duplicate SHAs etc
// since: refs with commits on or after this date will be included

@ -26,11 +26,68 @@ type Attribute struct {
Upgradeables map[string][]string
}
// InstallOptions serves as an argument to Install().
type InstallOptions struct {
Force bool
Local bool
System bool
// FilterOptions serves as an argument to Install().
type FilterOptions struct {
Force bool
Local bool
System bool
SkipSmudge bool
}
func (o *FilterOptions) Install() error {
if o.SkipSmudge {
return skipSmudgeFilterAttribute().Install(o)
}
return filterAttribute().Install(o)
}
func (o *FilterOptions) Uninstall() error {
filterAttribute().Uninstall(o)
return nil
}
func filterAttribute() *Attribute {
return &Attribute{
Section: "filter.lfs",
Properties: map[string]string{
"clean": "git-lfs clean -- %f",
"smudge": "git-lfs smudge -- %f",
"process": "git-lfs filter-process",
"required": "true",
},
Upgradeables: upgradeables(),
}
}
func skipSmudgeFilterAttribute() *Attribute {
return &Attribute{
Section: "filter.lfs",
Properties: map[string]string{
"clean": "git-lfs clean -- %f",
"smudge": "git-lfs smudge --skip -- %f",
"process": "git-lfs filter-process --skip",
"required": "true",
},
Upgradeables: upgradeables(),
}
}
func upgradeables() map[string][]string {
return map[string][]string{
"clean": []string{"git-lfs clean %f"},
"smudge": []string{
"git-lfs smudge %f",
"git-lfs smudge --skip %f",
"git-lfs smudge -- %f",
"git-lfs smudge --skip -- %f",
},
"process": []string{
"git-lfs filter",
"git-lfs filter --skip",
"git-lfs filter-process",
"git-lfs filter-process --skip",
},
}
}
// Install instructs Git to set all keys and values relative to the root
@ -39,7 +96,7 @@ type InstallOptions struct {
// `force` argument is passed as true. If an attribute is already set to a
// different value than what is given, and force is false, an error will be
// returned immediately, and the rest of the attributes will not be set.
func (a *Attribute) Install(opt InstallOptions) error {
func (a *Attribute) Install(opt *FilterOptions) error {
for k, v := range a.Properties {
var upgradeables []string
if a.Upgradeables != nil {
@ -65,7 +122,7 @@ func (a *Attribute) normalizeKey(relative string) string {
// matching key already exists and the value is not equal to the desired value,
// an error will be thrown if force is set to false. If force is true, the value
// will be overridden.
func (a *Attribute) set(key, value string, upgradeables []string, opt InstallOptions) error {
func (a *Attribute) set(key, value string, upgradeables []string, opt *FilterOptions) error {
var currentValue string
if opt.Local {
currentValue = git.Config.FindLocal(key)
@ -94,7 +151,7 @@ func (a *Attribute) set(key, value string, upgradeables []string, opt InstallOpt
}
// Uninstall removes all properties in the path of this property.
func (a *Attribute) Uninstall(opt InstallOptions) {
func (a *Attribute) Uninstall(opt *FilterOptions) {
if opt.Local {
git.Config.UnsetLocalSection(a.Section)
} else if opt.System {

41
lfs/config.go Normal file

@ -0,0 +1,41 @@
package lfs
import "github.com/git-lfs/git-lfs/config"
// FetchPruneConfig collects together the config options that control fetching and pruning
type FetchPruneConfig struct {
// The number of days prior to current date for which (local) refs other than HEAD
// will be fetched with --recent (default 7, 0 = only fetch HEAD)
FetchRecentRefsDays int
// Makes the FetchRecentRefsDays option apply to remote refs from fetch source as well (default true)
FetchRecentRefsIncludeRemotes bool
// number of days prior to latest commit on a ref that we'll fetch previous
// LFS changes too (default 0 = only fetch at ref)
FetchRecentCommitsDays int
// Whether to always fetch recent even without --recent
FetchRecentAlways bool
// Number of days added to FetchRecent*; data outside combined window will be
// deleted when prune is run. (default 3)
PruneOffsetDays int
// Always verify with remote before pruning
PruneVerifyRemoteAlways bool
// Name of remote to check for unpushed and verify checks
PruneRemoteName string
}
func NewFetchPruneConfig(git config.Environment) FetchPruneConfig {
pruneRemote, _ := git.Get("lfs.pruneremotetocheck")
if len(pruneRemote) == 0 {
pruneRemote = "origin"
}
return FetchPruneConfig{
FetchRecentRefsDays: git.Int("lfs.fetchrecentrefsdays", 7),
FetchRecentRefsIncludeRemotes: git.Bool("lfs.fetchrecentremoterefs", true),
FetchRecentCommitsDays: git.Int("lfs.fetchrecentcommitsdays", 0),
FetchRecentAlways: git.Bool("lfs.fetchrecentalways", false),
PruneOffsetDays: git.Int("lfs.pruneoffsetdays", 3),
PruneVerifyRemoteAlways: git.Bool("lfs.pruneverifyremotealways", false),
PruneRemoteName: pruneRemote,
}
}

42
lfs/config_test.go Normal file

@ -0,0 +1,42 @@
package lfs
import (
"testing"
"github.com/git-lfs/git-lfs/config"
"github.com/stretchr/testify/assert"
)
func TestFetchPruneConfigDefault(t *testing.T) {
cfg := config.NewFrom(config.Values{})
fp := NewFetchPruneConfig(cfg.Git)
assert.Equal(t, 7, fp.FetchRecentRefsDays)
assert.Equal(t, 0, fp.FetchRecentCommitsDays)
assert.Equal(t, 3, fp.PruneOffsetDays)
assert.True(t, fp.FetchRecentRefsIncludeRemotes)
assert.Equal(t, 3, fp.PruneOffsetDays)
assert.Equal(t, "origin", fp.PruneRemoteName)
assert.False(t, fp.PruneVerifyRemoteAlways)
}
func TestFetchPruneConfigCustom(t *testing.T) {
cfg := config.NewFrom(config.Values{
Git: map[string][]string{
"lfs.fetchrecentrefsdays": []string{"12"},
"lfs.fetchrecentremoterefs": []string{"false"},
"lfs.fetchrecentcommitsdays": []string{"9"},
"lfs.pruneoffsetdays": []string{"30"},
"lfs.pruneverifyremotealways": []string{"true"},
"lfs.pruneremotetocheck": []string{"upstream"},
},
})
fp := NewFetchPruneConfig(cfg.Git)
assert.Equal(t, 12, fp.FetchRecentRefsDays)
assert.Equal(t, 9, fp.FetchRecentCommitsDays)
assert.False(t, fp.FetchRecentRefsIncludeRemotes)
assert.Equal(t, 30, fp.PruneOffsetDays)
assert.Equal(t, "upstream", fp.PruneRemoteName)
assert.True(t, fp.PruneVerifyRemoteAlways)
}

@ -8,9 +8,6 @@ import (
"path/filepath"
"strings"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/tools"
"github.com/rubyist/tracerx"
)
@ -26,15 +23,32 @@ var (
type Hook struct {
Type string
Contents string
Upgradeables []string
Dir string
upgradeables []string
}
func LoadHooks(hookDir string) []*Hook {
return []*Hook{
NewStandardHook("pre-push", hookDir, []string{
"#!/bin/sh\ngit lfs push --stdin $*",
"#!/bin/sh\ngit lfs push --stdin \"$@\"",
"#!/bin/sh\ngit lfs pre-push \"$@\"",
"#!/bin/sh\ncommand -v git-lfs >/dev/null 2>&1 || { echo >&2 \"\\nThis repository has been set up with Git LFS but Git LFS is not installed.\\n\"; exit 0; }\ngit lfs pre-push \"$@\"",
"#!/bin/sh\ncommand -v git-lfs >/dev/null 2>&1 || { echo >&2 \"\\nThis repository has been set up with Git LFS but Git LFS is not installed.\\n\"; exit 2; }\ngit lfs pre-push \"$@\"",
}),
NewStandardHook("post-checkout", hookDir, []string{}),
NewStandardHook("post-commit", hookDir, []string{}),
NewStandardHook("post-merge", hookDir, []string{}),
}
}
// NewStandardHook creates a new hook using the template script calling 'git lfs theType'
func NewStandardHook(theType string, upgradeables []string) *Hook {
func NewStandardHook(theType, hookDir string, upgradeables []string) *Hook {
return &Hook{
Type: theType,
Contents: strings.Replace(hookBaseContent, "{{Command}}", theType, -1),
Upgradeables: upgradeables,
Dir: hookDir,
upgradeables: upgradeables,
}
}
@ -47,20 +61,7 @@ func (h *Hook) Exists() bool {
// Path returns the desired (or actual, if installed) location where this hook
// should be installed. It returns an absolute path in all cases.
func (h *Hook) Path() string {
return filepath.Join(h.Dir(), h.Type)
}
// Dir returns the directory used by LFS for storing Git hooks. By default, it
// will return the hooks/ sub-directory of the local repository's .git
// directory. If `core.hooksPath` is configured and supported (Git verison is
// greater than "2.9.0"), it will return that instead.
func (h *Hook) Dir() string {
customHooksSupported := git.Config.IsGitVersionAtLeast("2.9.0")
if hp, ok := config.Config.Git.Get("core.hooksPath"); ok && customHooksSupported {
return hp
}
return filepath.Join(config.LocalGitDir, "hooks")
return filepath.Join(h.Dir, h.Type)
}
// Install installs this Git hook on disk, or upgrades it if it does exist, and
@ -70,7 +71,7 @@ func (h *Hook) Dir() string {
func (h *Hook) Install(force bool) error {
msg := fmt.Sprintf("Install hook: %s, force=%t, path=%s", h.Type, force, h.Path())
if err := os.MkdirAll(h.Dir(), 0755); err != nil {
if err := os.MkdirAll(h.Dir, 0755); err != nil {
return err
}
@ -110,10 +111,6 @@ func (h *Hook) Upgrade() error {
// Uninstall removes the hook on disk so long as it matches the current version,
// or any of the past versions of this hook.
func (h *Hook) Uninstall() error {
if !InRepo() {
return errors.New("Not in a git repository")
}
msg := fmt.Sprintf("Uninstall hook: %s, path=%s", h.Type, h.Path())
match, err := h.matchesCurrent()
@ -151,7 +148,7 @@ func (h *Hook) matchesCurrent() (bool, error) {
return true, nil
}
for _, u := range h.Upgradeables {
for _, u := range h.upgradeables {
if u == contents {
return true, nil
}

@ -78,15 +78,15 @@ func Environ(cfg *config.Configuration, manifest *tq.Manifest) []string {
ultransfers := manifest.GetUploadAdapterNames()
sort.Strings(ultransfers)
fetchPruneConfig := cfg.FetchPruneConfig()
storageConfig := cfg.StorageConfig()
fetchPruneConfig := NewFetchPruneConfig(cfg.Git)
storageConfig := localstorage.NewConfig(cfg)
env = append(env,
fmt.Sprintf("LocalWorkingDir=%s", config.LocalWorkingDir),
fmt.Sprintf("LocalGitDir=%s", config.LocalGitDir),
fmt.Sprintf("LocalGitStorageDir=%s", config.LocalGitStorageDir),
fmt.Sprintf("LocalWorkingDir=%s", cfg.LocalWorkingDir()),
fmt.Sprintf("LocalGitDir=%s", cfg.LocalGitDir()),
fmt.Sprintf("LocalGitStorageDir=%s", cfg.LocalGitStorageDir()),
fmt.Sprintf("LocalMediaDir=%s", LocalMediaDir()),
fmt.Sprintf("LocalReferenceDir=%s", config.LocalReferenceDir),
fmt.Sprintf("LocalReferenceDir=%s", cfg.LocalReferenceDir()),
fmt.Sprintf("TempDir=%s", TempDir()),
fmt.Sprintf("ConcurrentTransfers=%d", api.ConcurrentTransfers),
fmt.Sprintf("TusTransfers=%v", cfg.TusTransfersAllowed()),
@ -125,10 +125,6 @@ func Environ(cfg *config.Configuration, manifest *tq.Manifest) []string {
return env
}
func InRepo() bool {
return config.LocalGitDir != ""
}
func ClearTempObjects() error {
if localstorage.Objects() == nil {
return nil

@ -1,123 +0,0 @@
package lfs
import (
"fmt"
"strings"
"github.com/git-lfs/git-lfs/tools"
)
var (
// prePushHook invokes `git lfs pre-push` at the pre-push phase.
prePushHook = NewStandardHook("pre-push", []string{
"#!/bin/sh\ngit lfs push --stdin $*",
"#!/bin/sh\ngit lfs push --stdin \"$@\"",
"#!/bin/sh\ngit lfs pre-push \"$@\"",
"#!/bin/sh\ncommand -v git-lfs >/dev/null 2>&1 || { echo >&2 \"\\nThis repository has been set up with Git LFS but Git LFS is not installed.\\n\"; exit 0; }\ngit lfs pre-push \"$@\"",
"#!/bin/sh\ncommand -v git-lfs >/dev/null 2>&1 || { echo >&2 \"\\nThis repository has been set up with Git LFS but Git LFS is not installed.\\n\"; exit 2; }\ngit lfs pre-push \"$@\"",
})
// postCheckoutHook invokes `git lfs post-checkout`
postCheckoutHook = NewStandardHook("post-checkout", []string{})
postCommitHook = NewStandardHook("post-commit", []string{})
postMergeHook = NewStandardHook("post-merge", []string{})
hooks = []*Hook{
prePushHook,
postCheckoutHook,
postCommitHook,
postMergeHook,
}
upgradeables = map[string][]string{
"clean": []string{"git-lfs clean %f"},
"smudge": []string{
"git-lfs smudge %f",
"git-lfs smudge --skip %f",
"git-lfs smudge -- %f",
"git-lfs smudge --skip -- %f",
},
"process": []string{
"git-lfs filter",
"git-lfs filter --skip",
"git-lfs filter-process",
"git-lfs filter-process --skip",
},
}
filters = &Attribute{
Section: "filter.lfs",
Properties: map[string]string{
"clean": "git-lfs clean -- %f",
"smudge": "git-lfs smudge -- %f",
"process": "git-lfs filter-process",
"required": "true",
},
Upgradeables: upgradeables,
}
passFilters = &Attribute{
Section: "filter.lfs",
Properties: map[string]string{
"clean": "git-lfs clean -- %f",
"smudge": "git-lfs smudge --skip -- %f",
"process": "git-lfs filter-process --skip",
"required": "true",
},
Upgradeables: upgradeables,
}
)
// Get user-readable manual install steps for hooks
func GetHookInstallSteps() string {
steps := make([]string, 0, len(hooks))
for _, h := range hooks {
steps = append(steps, fmt.Sprintf(
"Add the following to .git/hooks/%s:\n\n%s",
h.Type, tools.Indent(h.Contents)))
}
return strings.Join(steps, "\n\n")
}
// InstallHooks installs all hooks in the `hooks` var.
func InstallHooks(force bool) error {
for _, h := range hooks {
if err := h.Install(force); err != nil {
return err
}
}
return nil
}
// UninstallHooks removes all hooks in range of the `hooks` var.
func UninstallHooks() error {
for _, h := range hooks {
if err := h.Uninstall(); err != nil {
return err
}
}
return nil
}
// InstallFilters installs filters necessary for git-lfs to process normal git
// operations. Currently, that list includes:
// - smudge filter
// - clean filter
//
// An error will be returned if a filter is unable to be set, or if the required
// filters were not present.
func InstallFilters(opt InstallOptions, passThrough bool) error {
if passThrough {
return passFilters.Install(opt)
}
return filters.Install(opt)
}
// UninstallFilters proxies into the Uninstall method on the Filters type to
// remove all installed filters.
func UninstallFilters(opt InstallOptions) error {
filters.Uninstall(opt)
return nil
}

@ -95,8 +95,8 @@ type PathConverter interface {
// current working dir. Useful when needing to calling git with results from a rooted command,
// but the user is in a subdir of their repo
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func NewRepoToCurrentPathConverter() (PathConverter, error) {
r, c, p, err := pathConverterArgs()
func NewRepoToCurrentPathConverter(cfg *config.Configuration) (PathConverter, error) {
r, c, p, err := pathConverterArgs(cfg)
if err != nil {
return nil, err
}
@ -133,8 +133,8 @@ func (p *repoToCurrentPathConverter) Convert(filename string) string {
// relative to the repo root. Useful when calling git with arguments that requires them
// to be rooted but the user is in a subdir of their repo & expects to use relative args
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func NewCurrentToRepoPathConverter() (PathConverter, error) {
r, c, p, err := pathConverterArgs()
func NewCurrentToRepoPathConverter(cfg *config.Configuration) (PathConverter, error) {
r, c, p, err := pathConverterArgs(cfg)
if err != nil {
return nil, err
}
@ -172,13 +172,13 @@ func (p *currentToRepoPathConverter) Convert(filename string) string {
}
}
func pathConverterArgs() (string, string, bool, error) {
func pathConverterArgs(cfg *config.Configuration) (string, string, bool, error) {
currDir, err := os.Getwd()
if err != nil {
return "", "", false, fmt.Errorf("Unable to get working dir: %v", err)
}
currDir = tools.ResolveSymlinks(currDir)
return config.LocalWorkingDir, currDir, config.LocalWorkingDir == currDir, nil
return cfg.LocalWorkingDir(), currDir, cfg.LocalWorkingDir() == currDir, nil
}
// Are we running on Windows? Need to handle some extra path shenanigans

@ -37,8 +37,8 @@ type credsConfig struct {
//
// It returns an error if any configuration was invalid, or otherwise
// un-useable.
func getCredentialHelper(cfg *config.Configuration) (CredentialHelper, error) {
ccfg, err := getCredentialConfig(cfg)
func getCredentialHelper(osEnv, gitEnv config.Environment) (CredentialHelper, error) {
ccfg, err := getCredentialConfig(osEnv, gitEnv)
if err != nil {
return nil, err
}
@ -71,13 +71,20 @@ func getCredentialHelper(cfg *config.Configuration) (CredentialHelper, error) {
// getCredentialConfig parses a *credsConfig given the OS and Git
// configurations.
func getCredentialConfig(cfg *config.Configuration) (*credsConfig, error) {
what := &credsConfig{
Cached: cfg.Git.Bool("lfs.cachecredentials", true),
func getCredentialConfig(o, g config.Environment) (*credsConfig, error) {
askpass, ok := o.Get("GIT_ASKPASS")
if !ok {
askpass, ok = g.Get("core.askpass")
}
if err := cfg.Unmarshal(what); err != nil {
return nil, err
if !ok {
askpass, ok = o.Get("SSH_ASKPASS")
}
helper, _ := g.Get("credential.helper")
what := &credsConfig{
AskPass: askpass,
Helper: helper,
Cached: g.Bool("lfs.cachecredentials", true),
SkipPrompt: o.Bool("GIT_TERMINAL_PROMPT", false),
}
return what, nil

@ -66,8 +66,7 @@ func NewClient(osEnv Env, gitEnv Env) (*Client, error) {
return nil, errors.Wrap(err, fmt.Sprintf("bad netrc file %s", netrcfile))
}
creds, err := getCredentialHelper(&config.Configuration{
Os: osEnv, Git: gitEnv})
creds, err := getCredentialHelper(osEnv, gitEnv)
if err != nil {
return nil, errors.Wrap(err, "cannot find credential helper(s)")
}

@ -27,16 +27,15 @@ func Objects() *LocalStorage {
return objects
}
func InitStorage() error {
if len(config.LocalGitStorageDir) == 0 || len(config.LocalGitDir) == 0 {
func InitStorage(cfg *config.Configuration) error {
if len(cfg.LocalGitStorageDir()) == 0 || len(cfg.LocalGitDir()) == 0 {
return notInRepoErr
}
cfg := config.Config.StorageConfig()
TempDir = filepath.Join(cfg.LfsStorageDir, "tmp") // temp files per worktree
storCfg := NewConfig(cfg)
TempDir = filepath.Join(storCfg.LfsStorageDir, "tmp") // temp files per worktree
objs, err := NewStorage(
filepath.Join(cfg.LfsStorageDir, "objects"),
filepath.Join(storCfg.LfsStorageDir, "objects"),
filepath.Join(TempDir, "objects"),
)
@ -45,16 +44,16 @@ func InitStorage() error {
}
objects = objs
config.LocalLogDir = filepath.Join(objs.RootDir, "logs")
if err := os.MkdirAll(config.LocalLogDir, localLogDirPerms); err != nil {
cfg.SetLocalLogDir(filepath.Join(objs.RootDir, "logs"))
if err := os.MkdirAll(cfg.LocalLogDir(), localLogDirPerms); err != nil {
return errors.Wrap(err, "create log dir")
}
return nil
}
func InitStorageOrFail() {
if err := InitStorage(); err != nil {
func InitStorageOrFail(cfg *config.Configuration) {
if err := InitStorage(cfg); err != nil {
if err == notInRepoErr {
return
}
@ -64,9 +63,9 @@ func InitStorageOrFail() {
}
}
func ResolveDirs() {
config.ResolveGitBasicDirs()
InitStorageOrFail()
func ResolveDirs(cfg *config.Configuration) {
cfg.ResolveGitBasicDirs()
InitStorageOrFail(cfg)
}
func TempFile(prefix string) (*os.File, error) {

@ -7,6 +7,8 @@ import (
"os"
"path/filepath"
"regexp"
"github.com/git-lfs/git-lfs/config"
)
const (
@ -55,6 +57,25 @@ func (s *LocalStorage) BuildObjectPath(oid string) (string, error) {
return filepath.Join(dir, oid), nil
}
// Storage configuration
type Configuration struct {
LfsStorageDir string
}
func NewConfig(cfg *config.Configuration) (c Configuration) {
dir, _ := cfg.Git.Get("lfs.storage")
if len(dir) == 0 {
dir = "lfs"
}
if filepath.IsAbs(dir) {
c.LfsStorageDir = dir
} else {
c.LfsStorageDir = filepath.Join(cfg.LocalGitStorageDir(), dir)
}
return
}
func localObjectDir(s *LocalStorage, oid string) string {
return filepath.Join(s.RootDir, oid[0:2], oid[2:4])
}

@ -20,6 +20,7 @@ import (
"sync"
"time"
"github.com/git-lfs/git-lfs/config"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfs"
@ -83,7 +84,7 @@ func (r *Repo) Pushd() {
r.callback.Fatalf("Can't chdir %v", err)
}
r.popDir = oldwd
localstorage.ResolveDirs()
localstorage.ResolveDirs(config.Config)
}
func (r *Repo) Popd() {
@ -220,7 +221,7 @@ func (infile *FileInput) writeLFSPointer(inputData io.Reader) (*lfs.Pointer, err
// this only created the temp file, move to final location
tmpfile := cleaned.Filename
storageOnce.Do(localstorage.ResolveDirs)
storageOnce.Do(func() { localstorage.ResolveDirs(config.Config) })
mediafile, err := lfs.LocalMediaPath(cleaned.Oid)
if err != nil {
return nil, errors.Wrap(err, "local media path")