1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
package lfs
import (
"errors"
"fmt"
"strings"
"github.com/git-lfs/git-lfs/v3/git"
"github.com/git-lfs/git-lfs/v3/tr"
)
// Attribute wraps the structure and some operations of Git's conception of an
// "attribute", as defined here: http://git-scm.com/docs/gitattributes.
type Attribute struct {
// The Section of an Attribute refers to the location at which all
// properties are relative to. For example, for a Section with the value
// "core", Git will produce something like:
//
// [core]
// autocrlf = true
// ...
Section string
// The Properties of an Attribute refer to all of the keys and values
// that define that Attribute.
Properties map[string]string
// Previous values of these attributes that can be automatically upgraded
Upgradeables map[string][]string
}
// FilterOptions serves as an argument to Install().
type FilterOptions struct {
GitConfig *git.Configuration
Force bool
File string
Local bool
Worktree 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 {
attrs := filterAttribute()
if err := attrs.Uninstall(o); err != nil {
return err
}
for k := range attrs.Properties {
name := fmt.Sprintf("%s.%s", attrs.Section, k)
if len(o.GitConfig.Find(name)) > 0 {
return errors.New(tr.Tr.Get("some filter configuration was not removed (found %s)", name))
}
}
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: map[string][]string{
"clean": []string{
"git-lfs clean %f",
},
"smudge": []string{
"git-lfs smudge %f",
"git-lfs smudge --skip %f",
"git-lfs smudge --skip -- %f",
},
"process": []string{
"git-lfs filter",
"git-lfs filter --skip",
"git-lfs filter-process --skip",
},
},
}
}
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: map[string][]string{
"clean": []string{
"git-lfs clean -- %f",
},
"smudge": []string{
"git-lfs smudge %f",
"git-lfs smudge --skip %f",
"git-lfs smudge -- %f",
},
"process": []string{
"git-lfs filter",
"git-lfs filter --skip",
"git-lfs filter-process",
},
},
}
}
// Install instructs Git to set all keys and values relative to the root
// location of this Attribute. For any particular key/value pair, if a matching
// key is already set, it will be overridden if it is either a) empty, or b) the
// `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 *FilterOptions) error {
for k, v := range a.Properties {
var upgradeables []string
if a.Upgradeables != nil {
// use pre-normalised key since caller will have set up the same
upgradeables = a.Upgradeables[k]
}
key := a.normalizeKey(k)
if err := a.set(opt.GitConfig, key, v, upgradeables, opt); err != nil {
return err
}
}
return nil
}
// normalizeKey makes an absolute path out of a partial relative one. For a
// relative path of "foo", and a root Section of "bar", "bar.foo" will be returned.
func (a *Attribute) normalizeKey(relative string) string {
return strings.Join([]string{a.Section, relative}, ".")
}
// set attempts to set a single key/value pair portion of this Attribute. If a
// 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(gitConfig *git.Configuration, key, value string, upgradeables []string, opt *FilterOptions) error {
var currentValue string
if opt.Local {
currentValue = gitConfig.FindLocal(key)
} else if opt.Worktree {
currentValue = gitConfig.FindWorktree(key)
} else if opt.System {
currentValue = gitConfig.FindSystem(key)
} else if opt.File != "" {
currentValue = gitConfig.FindFile(opt.File, key)
} else {
currentValue = gitConfig.FindGlobal(key)
}
if opt.Force || shouldReset(currentValue, upgradeables) {
var err error
if opt.Local {
_, err = gitConfig.SetLocal(key, value)
} else if opt.Worktree {
_, err = gitConfig.SetWorktree(key, value)
} else if opt.System {
_, err = gitConfig.SetSystem(key, value)
} else if opt.File != "" {
_, err = gitConfig.SetFile(opt.File, key, value)
} else {
_, err = gitConfig.SetGlobal(key, value)
}
return err
} else if currentValue != value {
return errors.New(tr.Tr.Get("the %q attribute should be %q but is %q",
key, value, currentValue))
}
return nil
}
// Uninstall removes all properties in the path of this property.
func (a *Attribute) Uninstall(opt *FilterOptions) error {
var err error
if opt.Local {
_, err = opt.GitConfig.UnsetLocalSection(a.Section)
} else if opt.Worktree {
_, err = opt.GitConfig.UnsetWorktreeSection(a.Section)
} else if opt.System {
_, err = opt.GitConfig.UnsetSystemSection(a.Section)
} else if opt.File != "" {
_, err = opt.GitConfig.UnsetFileSection(opt.File, a.Section)
} else {
_, err = opt.GitConfig.UnsetGlobalSection(a.Section)
}
return err
}
// shouldReset determines whether or not a value is resettable given its current
// value on the system. If the value is empty (length = 0), then it will pass.
// It will also pass if it matches any upgradeable value
func shouldReset(value string, upgradeables []string) bool {
if len(value) == 0 {
return true
}
for _, u := range upgradeables {
if value == u {
return true
}
}
return false
}
|