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