7c4d7ba4bc557d2eb81d2f23e3c7d6803289a13b
[racktables] / gateways / git-commit
1 #!/bin/sh
2
3 # This file is a part of RackTables, a datacenter and server room management
4 # framework. See accompanying file "COPYING" for the full copyright and
5 # licensing information.
6
7 # This script implements a simple (one file at a time) one-way feed into a git
8 # repository. To make a commit it takes the following PHP code:
9 #
10 # $params = array
11 # (
12 # 'u' => 'racktables_pseudo_user',
13 # 'r' => '/path/to/repository',
14 # 'o' => 'pull', # or 'commit' or 'push' or unset
15 # 'f' => 'path/to/file/within/the/repository/file.txt',
16 # 'm' => 'commit message text',
17 # 'a' => 'Some Author <user@example.org>',
18 # );
19 # $rc = callScript ('git-commit', $params, $file_contents, $stdout, $stderr);
20 #
21 # The meaning of $stdout and $stderr is the same as in queryTerminal().
22 #
23 # This script uses sudo to switch between the pseudo-users and requires an
24 # entry in sudoers along the following lines:
25 # httpduser ALL=(racktablesuser) NOPASSWD:/path/to/racktables/gateways/git-commit
26
27 THISFILE=`basename "$0"`
28
29 usage_and_exit()
30 {
31 cat >&2 <<ENDOFMESSAGE
32 Usage: $THISFILE -u <u> -r <r> -o pull
33 or: $THISFILE -u <u> -r <r> -o commit -f <f> [-m <m> -a <a>]
34 or: $THISFILE -u <u> -r <r> -o push
35 or: $THISFILE -u <u> -r <r> [-o full] -f <f> [-m <m> -a <a>]
36
37 -u <username> A pseudo-user to work as (this script will try to sudo
38 itself if the current user is not the same). The user
39 must be able to write to the repository filesystem and to
40 run "git pull", "git commit" and "git push" without any
41 user interaction (i.e. the git remote must be on a local
42 filesystem or be configured to use SSH keys).
43 -r <repodir> An absolute path to an existing git repository.
44 -o pull Only run git-pull(1).
45 -o commit Only replace the file contents with stdin and run
46 git-commit(1) if the contents has changed.
47 -o push Only run git-push(1).
48 -o full This is the default and is the same as running the three
49 actions above one after another, except the push will be
50 skipped if the commit was skipped.
51 -f <filepath> A relative path to a file within the repository (if the
52 file or the path do not exist, the missing component(s)
53 will be created automatically).
54 -m <msg> An optional custom commit message instead of the default
55 one. The message may be a multi-line string, in which
56 case it should follow the format recommended in the
57 "discussion" section of the git-commit(1) man page.
58 -a <author> An optional git commit author instead of the default or the
59 one previously configured with git-config(1).
60 ENDOFMESSAGE
61 exit 1
62 }
63
64 assert_nonempty_option()
65 {
66 if [ -z "$2" ]; then
67 echo "$THISFILE: missing option $1" >&2
68 usage_and_exit
69 fi
70 }
71
72 git_pull_or_exit()
73 {
74 git pull --quiet || {
75 echo "$THISFILE: failed to run 'git pull' (rc=$?)" >&2
76 exit 2
77 }
78 }
79
80 git_push_or_exit()
81 {
82 git push --quiet || {
83 echo "$THISFILE: failed to run 'git push' (rc=$?)" >&2
84 exit 5
85 }
86 }
87
88 git_commit_or_exit()
89 {
90 # git processes the path to the file automatically, but the shell
91 # redirection obviously does not.
92 DIRNAME=`dirname "$FILEPATH"`
93 [ -d "$DIRNAME" ] || mkdir -p "$DIRNAME"
94
95 # New file contents is on stdin.
96 cat > "$FILEPATH" || {
97 echo "$THISFILE: failed to write new file contents, trying to roll back." >&2
98 git checkout --quiet -- "$FILEPATH" || {
99 echo "$THISFILE: failed to run 'git checkout' after a write error." >&2
100 exit 4
101 }
102 exit 3
103 }
104
105 # git-diff exits with 0 if the file is not in the repository.
106 if ! git cat-file -e HEAD:"$FILEPATH" 2>/dev/null || ! git diff --quiet -- "$FILEPATH"; then
107 git add -- "$FILEPATH"
108 git commit --quiet --message="$COMMITMSG" ${AUTHOR:+--author="$AUTHOR"} -- "$FILEPATH"
109 [ "$1" = "and_push" ] && git_push_or_exit
110 fi
111 }
112
113 # Both callScript() and GNU getopt support both short and long option formats.
114 # However, use of any getopt normally implies shift, which unsets the $@
115 # special parameter and makes it impossible or difficult to pass properly
116 # quoted option values to self via sudo. The getopts shell builtin (available
117 # in bash, dash and other shells) depends on its own state variables rather
118 # than shifting, but supports short options only.
119 #
120 # The only easy way to use any long options in this script would be to make
121 # the username a fixed argument, which could be tested before the getopt
122 # processing, but that would not look consistent. Hence this script uses
123 # getopts and short options for all arguments.
124
125 ONLYRUN=full
126 while getopts u:r:o:f:m:a: opt; do
127 case "$opt" in
128 u)
129 SUDOUSER="$OPTARG"
130 ;;
131 r)
132 REPODIR="$OPTARG"
133 ;;
134 o)
135 ONLYRUN="$OPTARG"
136 ;;
137 f)
138 FILEPATH="$OPTARG"
139 ;;
140 m)
141 COMMITMSG="$OPTARG"
142 ;;
143 a)
144 AUTHOR="$OPTARG"
145 ;;
146 *)
147 echo "$THISFILE: internal error parsing options!" >&2
148 exit 3
149 esac
150 done
151 : ${COMMITMSG:=update $FILEPATH}
152
153 assert_nonempty_option -u "$SUDOUSER"
154 [ `whoami` = "$SUDOUSER" ] || {
155 sudo --non-interactive --set-home --user="$SUDOUSER" -- "$0" "$@"
156 exit $?
157 }
158
159 assert_nonempty_option -r "$REPODIR"
160 cd "$REPODIR"
161
162 case "$ONLYRUN" in
163 pull)
164 git_pull_or_exit
165 ;;
166 commit)
167 assert_nonempty_option -f "$FILEPATH"
168 git_commit_or_exit
169 ;;
170 push)
171 git_push_or_exit
172 ;;
173 full)
174 assert_nonempty_option -f "$FILEPATH"
175 git_pull_or_exit
176 git_commit_or_exit and_push
177 ;;
178 *)
179 usage_and_exit
180 ;;
181 esac
182
183 exit 0