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  }