Skip to content

Commit

Permalink
Merge pull request #1460 from github/javabrett-config-system
Browse files Browse the repository at this point in the history
Javabrett config system
  • Loading branch information
technoweenie committed Aug 17, 2016
2 parents 757234a + 9dd99c8 commit 36b2757
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 26 deletions.
13 changes: 12 additions & 1 deletion commands/command_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package commands
import (
"github.com/github/git-lfs/lfs"
"github.com/spf13/cobra"
"os"
)

var (
forceInstall = false
localInstall = false
systemInstall = false
skipSmudgeInstall = false
)

Expand All @@ -16,7 +18,15 @@ func installCommand(cmd *cobra.Command, args []string) {
requireInRepo()
}

opt := lfs.InstallOptions{Force: forceInstall, Local: localInstall}
if systemInstall && os.Geteuid() != 0 {
Print("WARNING: current user is not root/admin, system install is likely to fail.")
}

if localInstall && systemInstall {
Exit("Only one of --local and --system options can be specified.")
}

opt := lfs.InstallOptions{Force: forceInstall, Local: localInstall, System: systemInstall}
if skipSmudgeInstall {
// assume the user is changing their smudge mode, so enable force implicitly
opt.Force = true
Expand Down Expand Up @@ -48,6 +58,7 @@ func init() {

cmd.Flags().BoolVarP(&forceInstall, "force", "f", false, "Set the Git LFS global config, overwriting previous values.")
cmd.Flags().BoolVarP(&localInstall, "local", "l", false, "Set the Git LFS config for the local Git repository only.")
cmd.Flags().BoolVarP(&systemInstall, "system", "", false, "Set the Git LFS config in system-wide scope.")
cmd.Flags().BoolVarP(&skipSmudgeInstall, "skip-smudge", "s", false, "Skip automatic downloading of objects on clone or pull.")
cmd.AddCommand(&cobra.Command{
Use: "hooks",
Expand Down
2 changes: 2 additions & 0 deletions debian/postinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
git lfs install --system
2 changes: 2 additions & 0 deletions debian/prerm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
git lfs uninstall
5 changes: 4 additions & 1 deletion docs/man/git-lfs-install.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ filters if they are not already set.
Sets the "lfs" smudge and clean filters, overwriting existing values.
* `--local`:
Sets the "lfs" smudge and clean filters in the local repository's git
config, instead of the global git config.
config, instead of the global git config (~/.gitconfig).
* `--system`:
Sets the "lfs" smudge and clean filters in the system git config, e.g. /etc/gitconfig
instead of the global git config (~/.gitconfig).
* `--skip-smudge`:
Skips automatic downloading of objects on clone or pull. This requires a
manual "git lfs pull" every time a new commit is checked out on your
Expand Down
46 changes: 34 additions & 12 deletions git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func LsRemote(remote, remoteRef string) (string, error) {
func ResolveRef(ref string) (*Ref, error) {
outp, err := subprocess.SimpleExec("git", "rev-parse", ref, "--symbolic-full-name", ref)
if err != nil {
return nil, err
return nil, fmt.Errorf("Git can't resolve ref: %q", ref)
}
if outp == "" {
return nil, fmt.Errorf("Git can't resolve ref: %q", ref)
Expand Down Expand Up @@ -316,52 +316,74 @@ func (c *gitConfig) Find(val string) string {
return output
}

// Find returns the git config value for the key
// FindGlobal returns the git config value global scope for the key
func (c *gitConfig) FindGlobal(val string) string {
output, _ := subprocess.SimpleExec("git", "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, _ := subprocess.SimpleExec("git", "config", "--system", val)
return output
}

// Find returns the git config value for the key
func (c *gitConfig) FindLocal(val string) string {
output, _ := subprocess.SimpleExec("git", "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) {
subprocess.SimpleExec("git", "config", "--global", key, val)
func (c *gitConfig) SetGlobal(key, val string) (string, error) {
return subprocess.SimpleExec("git", "config", "--global", 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 subprocess.SimpleExec("git", "config", "--system", key, val)
}

// UnsetGlobal removes the git config value for the key from the global config
func (c *gitConfig) UnsetGlobal(key string) {
subprocess.SimpleExec("git", "config", "--global", "--unset", key)
func (c *gitConfig) UnsetGlobal(key string) (string, error) {
return subprocess.SimpleExec("git", "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 subprocess.SimpleExec("git", "config", "--system", "--unset", key)
}

// UnsetGlobalSection removes the entire named section from the global config
func (c *gitConfig) UnsetGlobalSection(key string) (string, error) {
return subprocess.SimpleExec("git", "config", "--global", "--remove-section", key)
}

func (c *gitConfig) UnsetGlobalSection(key string) {
subprocess.SimpleExec("git", "config", "--global", "--remove-section", key)
// UnsetGlobalSection removes the entire named section from the system config
func (c *gitConfig) UnsetSystemSection(key string) (string, error) {
return subprocess.SimpleExec("git", "config", "--system", "--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) {
func (c *gitConfig) SetLocal(file, key, val string) (string, error) {
args := make([]string, 1, 5)
args[0] = "config"
if len(file) > 0 {
args = append(args, "--file", file)
}
args = append(args, key, val)
subprocess.SimpleExec("git", args...)
return subprocess.SimpleExec("git", args...)
}

// UnsetLocalKey removes the git config value for the key from the specified config file
func (c *gitConfig) UnsetLocalKey(file, key string) {
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)
subprocess.SimpleExec("git", args...)
return subprocess.SimpleExec("git", args...)
}

// List lists all of the git config values
Expand Down
23 changes: 17 additions & 6 deletions lfs/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ type Attribute struct {

// InstallOptions serves as an argument to Install().
type InstallOptions struct {
Force bool
Local bool
Force bool
Local bool
System bool
}

// Install instructs Git to set all keys and values relative to the root
Expand Down Expand Up @@ -66,20 +67,28 @@ func (a *Attribute) set(key, value string, opt InstallOptions) error {
var currentValue string
if opt.Local {
currentValue = git.Config.FindLocal(key)
} else if opt.System {
currentValue = git.Config.FindSystem(key)
} else {
currentValue = git.Config.FindGlobal(key)
}

if opt.Force || shouldReset(currentValue) {
var err error
if opt.Local {
// ignore error for unset, git returns non-zero if missing
git.Config.UnsetLocalKey("", key)
git.Config.SetLocal("", key, value)
_, err = git.Config.SetLocal("", key, value)
} else if opt.System {
// ignore error for unset, git returns non-zero if missing
git.Config.UnsetSystem(key)
_, err = git.Config.SetSystem(key, value)
} else {
// ignore error for unset, git returns non-zero if missing
git.Config.UnsetGlobal(key)
git.Config.SetGlobal(key, value)
_, err = git.Config.SetGlobal(key, value)
}

return nil
return err
} else if currentValue != value {
return fmt.Errorf("The %s attribute should be \"%s\" but is \"%s\"",
key, value, currentValue)
Expand All @@ -90,6 +99,8 @@ func (a *Attribute) set(key, value string, opt InstallOptions) error {

// Uninstall removes all properties in the path of this property.
func (a *Attribute) Uninstall() {
// uninstall from both system and global
git.Config.UnsetSystemSection(a.Section)
git.Config.UnsetGlobalSection(a.Section)
}

Expand Down
6 changes: 6 additions & 0 deletions rpm/SPECS/git-lfs.spec
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ mkdir -p -m 755 ${RPM_BUILD_ROOT}/usr/share/man/man5
install -D man/*.1 ${RPM_BUILD_ROOT}/usr/share/man/man1
install -D man/*.5 ${RPM_BUILD_ROOT}/usr/share/man/man5

%post
git lfs install --system

%preun
git lfs uninstall

%check
export GOPATH=`pwd`
export GIT_LFS_TEST_DIR=$(mktemp -d)
Expand Down
112 changes: 106 additions & 6 deletions subprocess/subprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
package subprocess

import (
"bytes"
"fmt"
"os"
"os/exec"
"strconv"
"strings"

"github.com/rubyist/tracerx"
Expand All @@ -16,15 +18,30 @@ func SimpleExec(name string, args ...string) (string, error) {
tracerx.Printf("run_command: '%s' %s", name, strings.Join(args, " "))
cmd := ExecCommand(name, args...)

output, err := cmd.Output()
if _, ok := err.(*exec.ExitError); ok {
return "", nil
//start copied from Go 1.6 exec.go
captureErr := cmd.Stderr == nil
if captureErr {
cmd.Stderr = &prefixSuffixSaver{N: 32 << 10}
}
if err != nil {
return fmt.Sprintf("Error running %s %s", name, args), err
//end copied from Go 1.6 exec.go

output, err := cmd.Output()

if exitError, ok := err.(*exec.ExitError); ok {
// TODO for min Go 1.6+, replace with ExitError.Stderr
errorOutput := strings.TrimSpace(string(cmd.Stderr.(*prefixSuffixSaver).Bytes()))
if errorOutput == "" {
// some commands might write nothing to stderr but something to stdout in error-conditions, in which case, we'll use that
// in the error string
errorOutput = strings.TrimSpace(string(output))
}
formattedErr := fmt.Errorf("Error running %s %s: '%s' '%s'", name, args, errorOutput, strings.TrimSpace(exitError.Error()))

// return "" as output in error case, for callers that don't care about errors but rely on "" returned, in-case stdout != ""
return "", formattedErr
}

return strings.Trim(string(output), " \n"), nil
return strings.Trim(string(output), " \n"), err
}

// An env for an exec.Command without GIT_TRACE
Expand All @@ -42,3 +59,86 @@ func init() {
env = append(env, kv)
}
}

// remaining code in file copied from Go 1.6 (c4fa25f4fc8f4419d0b0707bcdae9199a745face) exec.go and can be removed if moving to Go 1.6 minimum.
// go 1.6 adds ExitError.Stderr with nice prefix/suffix trimming, which could replace cmd.Stderr above

//start copied from Go 1.6 exec.go
// prefixSuffixSaver is an io.Writer which retains the first N bytes
// and the last N bytes written to it. The Bytes() methods reconstructs
// it with a pretty error message.
type prefixSuffixSaver struct {
N int // max size of prefix or suffix
prefix []byte
suffix []byte // ring buffer once len(suffix) == N
suffixOff int // offset to write into suffix
skipped int64

// TODO(bradfitz): we could keep one large []byte and use part of it for
// the prefix, reserve space for the '... Omitting N bytes ...' message,
// then the ring buffer suffix, and just rearrange the ring buffer
// suffix when Bytes() is called, but it doesn't seem worth it for
// now just for error messages. It's only ~64KB anyway.
}

func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) {
lenp := len(p)
p = w.fill(&w.prefix, p)

// Only keep the last w.N bytes of suffix data.
if overage := len(p) - w.N; overage > 0 {
p = p[overage:]
w.skipped += int64(overage)
}
p = w.fill(&w.suffix, p)

// w.suffix is full now if p is non-empty. Overwrite it in a circle.
for len(p) > 0 { // 0, 1, or 2 iterations.
n := copy(w.suffix[w.suffixOff:], p)
p = p[n:]
w.skipped += int64(n)
w.suffixOff += n
if w.suffixOff == w.N {
w.suffixOff = 0
}
}
return lenp, nil
}

// fill appends up to len(p) bytes of p to *dst, such that *dst does not
// grow larger than w.N. It returns the un-appended suffix of p.
func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) {
if remain := w.N - len(*dst); remain > 0 {
add := minInt(len(p), remain)
*dst = append(*dst, p[:add]...)
p = p[add:]
}
return p
}

func (w *prefixSuffixSaver) Bytes() []byte {
if w.suffix == nil {
return w.prefix
}
if w.skipped == 0 {
return append(w.prefix, w.suffix...)
}
var buf bytes.Buffer
buf.Grow(len(w.prefix) + len(w.suffix) + 50)
buf.Write(w.prefix)
buf.WriteString("\n... omitting ")
buf.WriteString(strconv.FormatInt(w.skipped, 10))
buf.WriteString(" bytes ...\n")
buf.Write(w.suffix[w.suffixOff:])
buf.Write(w.suffix[:w.suffixOff])
return buf.Bytes()
}

func minInt(a, b int) int {
if a < b {
return a
}
return b
}

//end copied from Go 1.6 exec.go

0 comments on commit 36b2757

Please sign in to comment.