github.com/psexton/git-lfs@v2.1.1-0.20170517224304-289a18b2bc53+incompatible/lfs/hook.go (about) 1 package lfs 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/git-lfs/git-lfs/config" 12 "github.com/git-lfs/git-lfs/errors" 13 "github.com/git-lfs/git-lfs/git" 14 ) 15 16 var ( 17 // The basic hook which just calls 'git lfs TYPE' 18 hookBaseContent = "#!/bin/sh\ncommand -v git-lfs >/dev/null 2>&1 || { echo >&2 \"\\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting .git/hooks/{{Command}}.\\n\"; exit 2; }\ngit lfs {{Command}} \"$@\"" 19 ) 20 21 // A Hook represents a githook as described in http://git-scm.com/docs/githooks. 22 // Hooks have a type, which is the type of hook that they are, and a body, which 23 // represents the thing they will execute when invoked by Git. 24 type Hook struct { 25 Type string 26 Contents string 27 Upgradeables []string 28 } 29 30 // NewStandardHook creates a new hook using the template script calling 'git lfs theType' 31 func NewStandardHook(theType string, upgradeables []string) *Hook { 32 return &Hook{ 33 Type: theType, 34 Contents: strings.Replace(hookBaseContent, "{{Command}}", theType, -1), 35 Upgradeables: upgradeables, 36 } 37 } 38 39 func (h *Hook) Exists() bool { 40 _, err := os.Stat(h.Path()) 41 return err == nil 42 } 43 44 // Path returns the desired (or actual, if installed) location where this hook 45 // should be installed. It returns an absolute path in all cases. 46 func (h *Hook) Path() string { 47 return filepath.Join(h.Dir(), h.Type) 48 } 49 50 // Dir returns the directory used by LFS for storing Git hooks. By default, it 51 // will return the hooks/ sub-directory of the local repository's .git 52 // directory. If `core.hooksPath` is configured and supported (Git verison is 53 // greater than "2.9.0"), it will return that instead. 54 func (h *Hook) Dir() string { 55 customHooksSupported := git.Config.IsGitVersionAtLeast("2.9.0") 56 if hp, ok := config.Config.Git.Get("core.hooksPath"); ok && customHooksSupported { 57 return hp 58 } 59 60 return filepath.Join(config.LocalGitDir, "hooks") 61 } 62 63 // Install installs this Git hook on disk, or upgrades it if it does exist, and 64 // is upgradeable. It will create a hooks directory relative to the local Git 65 // directory. It returns and halts at any errors, and returns nil if the 66 // operation was a success. 67 func (h *Hook) Install(force bool) error { 68 if err := os.MkdirAll(h.Dir(), 0755); err != nil { 69 return err 70 } 71 72 if h.Exists() && !force { 73 return h.Upgrade() 74 } 75 76 return h.write() 77 } 78 79 // write writes the contents of this Hook to disk, appending a newline at the 80 // end, and sets the mode to octal 0755. It writes to disk unconditionally, and 81 // returns at any error. 82 func (h *Hook) write() error { 83 return ioutil.WriteFile(h.Path(), []byte(h.Contents+"\n"), 0755) 84 } 85 86 // Upgrade upgrades the (assumed to be) existing git hook to the current 87 // contents. A hook is considered "upgrade-able" if its contents are matched in 88 // the member variable `Upgradeables`. It halts and returns any errors as they 89 // arise. 90 func (h *Hook) Upgrade() error { 91 match, err := h.matchesCurrent() 92 if err != nil { 93 return err 94 } 95 96 if !match { 97 return nil 98 } 99 100 return h.write() 101 } 102 103 // Uninstall removes the hook on disk so long as it matches the current version, 104 // or any of the past versions of this hook. 105 func (h *Hook) Uninstall() error { 106 if !InRepo() { 107 return errors.New("Not in a git repository") 108 } 109 110 match, err := h.matchesCurrent() 111 if err != nil { 112 return err 113 } 114 115 if !match { 116 return nil 117 } 118 119 return os.RemoveAll(h.Path()) 120 } 121 122 // matchesCurrent returns whether or not an existing git hook is able to be 123 // written to or upgraded. A git hook matches those conditions if and only if 124 // its contents match the current contents, or any past "upgrade-able" contents 125 // of this hook. 126 func (h *Hook) matchesCurrent() (bool, error) { 127 file, err := os.Open(h.Path()) 128 if err != nil { 129 return false, err 130 } 131 132 by, err := ioutil.ReadAll(io.LimitReader(file, 1024)) 133 file.Close() 134 if err != nil { 135 return false, err 136 } 137 138 contents := strings.TrimSpace(string(by)) 139 if contents == h.Contents || len(contents) == 0 { 140 return true, nil 141 } 142 143 for _, u := range h.Upgradeables { 144 if u == contents { 145 return true, nil 146 } 147 } 148 149 return false, fmt.Errorf("Hook already exists: %s\n\n%s\n", string(h.Type), contents) 150 }