forked from git-lfs/git-lfs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhook.go
136 lines (113 loc) · 3.5 KB
/
hook.go
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
package lfs
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/github/git-lfs/config"
"github.com/github/git-lfs/errutil"
"github.com/github/git-lfs/git"
)
// A Hook represents a githook as described in http://git-scm.com/docs/githooks.
// Hooks have a type, which is the type of hook that they are, and a body, which
// represents the thing they will execute when invoked by Git.
type Hook struct {
Type string
Contents string
Upgradeables []string
}
func (h *Hook) Exists() bool {
_, err := os.Stat(h.Path())
return err == nil
}
// Path returns the desired (or actual, if installed) location where this hook
// should be installed. It returns an absolute path in all cases.
func (h *Hook) Path() string {
return filepath.Join(h.Dir(), h.Type)
}
// Dir returns the directory used by LFS for storing Git hooks. By default, it
// will return the hooks/ sub-directory of the local repository's .git
// directory. If `core.hooksPath` is configured and supported (Git verison is
// greater than "2.9.0"), it will return that instead.
func (h *Hook) Dir() string {
customHooksSupported := git.Config.IsGitVersionAtLeast("2.9.0")
if hp, ok := config.Config.GitConfig("core.hooksPath"); ok && customHooksSupported {
return hp
}
return filepath.Join(config.LocalGitDir, "hooks")
}
// Install installs this Git hook on disk, or upgrades it if it does exist, and
// is upgradeable. It will create a hooks directory relative to the local Git
// directory. It returns and halts at any errors, and returns nil if the
// operation was a success.
func (h *Hook) Install(force bool) error {
if err := os.MkdirAll(h.Dir(), 0755); err != nil {
return err
}
if h.Exists() && !force {
return h.Upgrade()
}
return h.write()
}
// write writes the contents of this Hook to disk, appending a newline at the
// end, and sets the mode to octal 0755. It writes to disk unconditionally, and
// returns at any error.
func (h *Hook) write() error {
return ioutil.WriteFile(h.Path(), []byte(h.Contents+"\n"), 0755)
}
// Upgrade upgrades the (assumed to be) existing git hook to the current
// contents. A hook is considered "upgrade-able" if its contents are matched in
// the member variable `Upgradeables`. It halts and returns any errors as they
// arise.
func (h *Hook) Upgrade() error {
match, err := h.matchesCurrent()
if err != nil {
return err
}
if !match {
return nil
}
return h.write()
}
// Uninstall removes the hook on disk so long as it matches the current version,
// or any of the past versions of this hook.
func (h *Hook) Uninstall() error {
if !InRepo() {
return errutil.NewInvalidRepoError(nil)
}
match, err := h.matchesCurrent()
if err != nil {
return err
}
if !match {
return nil
}
return os.RemoveAll(h.Path())
}
// matchesCurrent returns whether or not an existing git hook is able to be
// written to or upgraded. A git hook matches those conditions if and only if
// its contents match the current contents, or any past "upgrade-able" contents
// of this hook.
func (h *Hook) matchesCurrent() (bool, error) {
file, err := os.Open(h.Path())
if err != nil {
return false, err
}
by, err := ioutil.ReadAll(io.LimitReader(file, 1024))
file.Close()
if err != nil {
return false, err
}
contents := strings.TrimSpace(string(by))
if contents == h.Contents || len(contents) == 0 {
return true, nil
}
for _, u := range h.Upgradeables {
if u == contents {
return true, nil
}
}
return false, fmt.Errorf("Hook already exists: %s\n\n%s\n", string(h.Type), contents)
}