-
Notifications
You must be signed in to change notification settings - Fork 8
/
git_hooks.go
182 lines (147 loc) · 4.35 KB
/
git_hooks.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
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
package repositories
import (
"fmt"
"log"
"os"
"path/filepath"
"text/template"
"github.com/kballard/go-shellquote"
"github.com/reviewboard/rb-gateway/repositories/events"
)
const (
gitHookDispatchScriptTemplate = (`#!/bin/bash
# Run hooks in .git/hooks/{{ .HookName }}.d
# This file was installed by rb-gateway.
HOOK_DIR=$(dirname $0)/{{ .HookName }}.d
EXIT=0
if [ -d "$HOOK_DIR" ]; then
STDIN=$(cat /dev/stdin)
for HOOK in ${HOOK_DIR}/*; do
if [ -x "$HOOK" ]; then
echo -n "$STDIN" | "$HOOK" "$@"
fi
done
LAST_EXIT=$?
if [ $LAST_EXIT != 0 ]; then
EXIT=$LAST_EXIT
fi
fi
exit $EXIT
`)
gitHookScriptTemplate = (`#!/bin/bash
exec {{ .ExePath }} --config {{ .ConfigPath }} trigger-webhooks {{ .Repository }} {{ .Event }}
`)
)
var (
gitEvents = map[string]string{
events.PushEvent: "post-receive",
}
)
type gitHookData struct {
ConfigPath string
Event string
ExePath string
HookDir string
HookName string
Repository string
}
// Install all hooks for the given repository.
func (repo *GitRepository) InstallHooks(cfgPath string, force bool) (err error) {
var commonDir string
if commonDir, err = repo.commonDir(); err != nil {
return
}
hookDir := filepath.Join(commonDir, "hooks")
if _, err = ensureDir(hookDir); err != nil {
return
}
var exePath string
if exePath, err = filepath.Abs(os.Args[0]); err != nil {
return
}
hookData := gitHookData{
ConfigPath: shellquote.Join(cfgPath),
ExePath: shellquote.Join(exePath),
HookDir: shellquote.Join(hookDir),
Repository: shellquote.Join(repo.Name),
}
for event, hookName := range gitEvents {
hookData.Event = shellquote.Join(event)
hookData.HookName = shellquote.Join(hookName)
err = repo.installHook(hookDir, &hookData, force)
if err != nil {
return
}
}
return
}
// Install a single repository hook.
//
// This function installs (1) a hook dispatch script to run any number of hooks
// per event and (2) a RB Gateway-specific script to call `trigger-webhook`. If
// a hook with the specified name already exists, it will be renamed and moved
// into the `hookname.d` directory.
//
// If `force` is `true`, the hooks will be installed over existing hooks if
// they already exist.
func (repo *GitRepository) installHook(hookDir string, hookData *gitHookData, force bool) (err error) {
dispatchPath := filepath.Join(hookDir, hookData.HookName)
scriptDir := filepath.Join(hookDir, fmt.Sprintf("%s.d", hookData.HookName))
scriptPath := filepath.Join(scriptDir, fmt.Sprintf("99-rbgateway-%s-event.sh", hookData.Event))
var created bool
if created, err = ensureDir(scriptDir); err != nil {
return
}
if created {
renamedPath := filepath.Join(scriptDir, fmt.Sprintf("00-original-%s", hookData.HookName))
// If there is an existing hook in .git/hooks/, we copy it into the
// script dir so that it will still be executed after we install our
// dispatcher.
if _, err = os.Stat(dispatchPath); err != nil && !os.IsNotExist(err) {
return
} else if err == nil {
if err = os.Rename(dispatchPath, renamedPath); err != nil {
return
}
defer func() {
// Something went wrong so we are going to try to restore the
// filesystem to near its original state.
log.Printf(`Restoring filesystem to original state for hook "%s"`, hookData.HookName)
if err != nil {
if err = os.Rename(renamedPath, dispatchPath); err != nil {
log.Println("Could not restore filesystem after error: ", err.Error())
}
}
}()
}
}
// If the script to trigger `rbgateway trigger-webhooks` does not exist, create it.
if _, err = os.Stat(scriptPath); force || os.IsNotExist(err) {
t := template.Must(template.New(scriptPath).Parse(gitHookScriptTemplate))
var f *os.File
if f, err = os.OpenFile(scriptPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700); err != nil {
return
}
defer f.Close()
if err = t.Execute(f, hookData); err != nil {
return
}
} else if err != nil {
return
}
// If the dispatch script does not exist, create it.
if _, err = os.Stat(dispatchPath); force || os.IsNotExist(err) {
t := template.Must(template.New(dispatchPath).Parse(gitHookDispatchScriptTemplate))
var f *os.File
if f, err = os.OpenFile(dispatchPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700); err != nil {
return
}
defer f.Close()
if err = t.Execute(f, hookData); err != nil {
return
}
} else if err != nil {
return
}
return
}