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