Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Javabrett config system #1460

Merged
merged 9 commits into from
Aug 17, 2016
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