git-module

git clone git://git.lin.moe/fork/git-module.git

  1package git
  2
  3import (
  4	"os"
  5	"path"
  6	"strings"
  7)
  8
  9// HookName is the name of a Git hook.
 10type HookName string
 11
 12// A list of Git server hooks' name that are supported.
 13const (
 14	HookPreReceive  HookName = "pre-receive"
 15	HookUpdate      HookName = "update"
 16	HookPostReceive HookName = "post-receive"
 17)
 18
 19var (
 20	// ServerSideHooks contains a list of Git hooks that are supported on the server
 21	// side.
 22	ServerSideHooks = []HookName{HookPreReceive, HookUpdate, HookPostReceive}
 23	// ServerSideHookSamples contains samples of Git hooks that are supported on the
 24	// server side.
 25	ServerSideHookSamples = map[HookName]string{
 26		HookPreReceive: `#!/bin/sh
 27#
 28# An example hook script to make use of push options.
 29# The example simply echoes all push options that start with 'echoback='
 30# and rejects all pushes when the "reject" push option is used.
 31#
 32# To enable this hook, rename this file to "pre-receive".
 33
 34if test -n "$GIT_PUSH_OPTION_COUNT"
 35then
 36	i=0
 37	while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
 38	do
 39		eval "value=\$GIT_PUSH_OPTION_$i"
 40		case "$value" in
 41		echoback=*)
 42			echo "echo from the pre-receive-hook: ${value#*=}" >&2
 43			;;
 44		reject)
 45			exit 1
 46		esac
 47		i=$((i + 1))
 48	done
 49fi
 50`,
 51		HookUpdate: `#!/bin/sh
 52#
 53# An example hook script to block unannotated tags from entering.
 54# Called by "git receive-pack" with arguments: refname oid-old oid-new
 55#
 56# To enable this hook, rename this file to "update".
 57#
 58# Config
 59# ------
 60# hooks.allowunannotated
 61#   This boolean sets whether unannotated tags will be allowed into the
 62#   repository.  By default they won't be.
 63# hooks.allowdeletetag
 64#   This boolean sets whether deleting tags will be allowed in the
 65#   repository.  By default they won't be.
 66# hooks.allowmodifytag
 67#   This boolean sets whether a tag may be modified after creation. By default
 68#   it won't be.
 69# hooks.allowdeletebranch
 70#   This boolean sets whether deleting branches will be allowed in the
 71#   repository.  By default they won't be.
 72# hooks.denycreatebranch
 73#   This boolean sets whether remotely creating branches will be denied
 74#   in the repository.  By default this is allowed.
 75#
 76
 77# --- Command line
 78refname="$1"
 79oldrev="$2"
 80newrev="$3"
 81
 82# --- Safety check
 83if [ -z "$GIT_DIR" ]; then
 84	echo "Don't run this script from the command line." >&2
 85	echo " (if you want, you could supply GIT_DIR then run" >&2
 86	echo "  $0 <ref> <oldrev> <newrev>)" >&2
 87	exit 1
 88fi
 89
 90if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
 91	echo "usage: $0 <ref> <oldrev> <newrev>" >&2
 92	exit 1
 93fi
 94
 95# --- Config
 96allowunannotated=$(git config --bool hooks.allowunannotated)
 97allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
 98denycreatebranch=$(git config --bool hooks.denycreatebranch)
 99allowdeletetag=$(git config --bool hooks.allowdeletetag)
100allowmodifytag=$(git config --bool hooks.allowmodifytag)
101
102# check for no description
103projectdesc=$(sed -e '1q' "$GIT_DIR/description")
104case "$projectdesc" in
105"Unnamed repository"* | "")
106	echo "*** Project description file hasn't been set" >&2
107	exit 1
108	;;
109esac
110
111# --- Check types
112# if $newrev is 0000...0000, it's a commit to delete a ref.
113zero="0000000000000000000000000000000000000000"
114if [ "$newrev" = "$zero" ]; then
115	newrev_type=delete
116else
117	newrev_type=$(git cat-file -t $newrev)
118fi
119
120case "$refname","$newrev_type" in
121	refs/tags/*,commit)
122		# un-annotated tag
123		short_refname=${refname##refs/tags/}
124		if [ "$allowunannotated" != "true" ]; then
125			echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
126			echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
127			exit 1
128		fi
129		;;
130	refs/tags/*,delete)
131		# delete tag
132		if [ "$allowdeletetag" != "true" ]; then
133			echo "*** Deleting a tag is not allowed in this repository" >&2
134			exit 1
135		fi
136		;;
137	refs/tags/*,tag)
138		# annotated tag
139		if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
140		then
141			echo "*** Tag '$refname' already exists." >&2
142			echo "*** Modifying a tag is not allowed in this repository." >&2
143			exit 1
144		fi
145		;;
146	refs/heads/*,commit)
147		# branch
148		if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
149			echo "*** Creating a branch is not allowed in this repository" >&2
150			exit 1
151		fi
152		;;
153	refs/heads/*,delete)
154		# delete branch
155		if [ "$allowdeletebranch" != "true" ]; then
156			echo "*** Deleting a branch is not allowed in this repository" >&2
157			exit 1
158		fi
159		;;
160	refs/remotes/*,commit)
161		# tracking branch
162		;;
163	refs/remotes/*,delete)
164		# delete tracking branch
165		if [ "$allowdeletebranch" != "true" ]; then
166			echo "*** Deleting a tracking branch is not allowed in this repository" >&2
167			exit 1
168		fi
169		;;
170	*)
171		# Anything else (is there anything else?)
172		echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
173		exit 1
174		;;
175esac
176
177# --- Finished
178exit 0
179`,
180		HookPostReceive: `#!/bin/sh
181#
182# An example hook script for the "post-receive" event.
183#
184# The "post-receive" script is run after receive-pack has accepted a pack
185# and the repository has been updated.  It is passed arguments in through
186# stdin in the form
187#  <oldrev> <newrev> <refname>
188# For example:
189#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
190
191while read oldrev newrev refname
192do
193    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
194    if [ "master" = "$branch" ]; then
195        # Do something
196    fi
197done`,
198	}
199)
200
201// Hook contains information of a Git hook.
202type Hook struct {
203	name     HookName
204	path     string // The absolute file path of the hook.
205	isSample bool   // Indicates whether this hook is read from the sample.
206	content  string // The content of the hook.
207}
208
209// Name returns the name of the Git hook.
210func (h *Hook) Name() HookName {
211	return h.name
212}
213
214// Path returns the path of the Git hook.
215func (h *Hook) Path() string {
216	return h.path
217}
218
219// IsSample returns true if the content is read from the sample hook.
220func (h *Hook) IsSample() bool {
221	return h.isSample
222}
223
224// Content returns the content of the Git hook.
225func (h *Hook) Content() string {
226	return h.content
227}
228
229// Update writes the content of the Git hook on filesystem. It updates the
230// memory copy of the content as well.
231func (h *Hook) Update(content string) error {
232	h.content = strings.TrimSpace(content)
233	h.content = strings.ReplaceAll(h.content, "\r", "")
234
235	if err := os.MkdirAll(path.Dir(h.path), os.ModePerm); err != nil {
236		return err
237	} else if err = os.WriteFile(h.path, []byte(h.content), os.ModePerm); err != nil {
238		return err
239	}
240
241	h.isSample = false
242	return nil
243}