1 | #!/usr/bin/env bash |
2 | BASEDIR="$PWD" |
3 | ENVDIR="$PWD/env" |
4 | |
5 | usage() { |
6 | cat <<EOF |
7 | Usage: $0 [options] <command> [arguments] |
8 | Commands: |
9 | help This help text |
10 | list List environments |
11 | clear Delete all environment and revert to flat config/files |
12 | new <name> Create a new environment |
13 | switch <name> Switch to a different environment |
14 | delete <name> Delete an environment |
15 | rename <newname> Rename the current environment |
16 | diff Show differences between current state and environment |
17 | save Save your changes to the environment |
18 | revert Revert your changes since last save |
19 | |
20 | Options: |
21 | |
22 | EOF |
23 | exit ${1:-1} |
24 | } |
25 | |
26 | error() { |
27 | echo "$0: $*" |
28 | exit 1 |
29 | } |
30 | |
31 | ask_bool() { |
32 | local DEFAULT="$1"; shift |
33 | local def defstr val |
34 | case "$DEFAULT" in |
35 | 1) def=0; defstr="Y/n";; |
36 | 0) def=1; defstr="y/N";; |
37 | *) def=; defstr="y/n";; |
38 | esac |
39 | while [ -z "$val" ]; do |
40 | local VAL |
41 | |
42 | echo -n "$* ($defstr): " |
43 | read VAL |
44 | case "$VAL" in |
45 | y*|Y*) val=0;; |
46 | n*|N*) val=1;; |
47 | *) val="$def";; |
48 | esac |
49 | done |
50 | return "$val" |
51 | } |
52 | |
53 | env_init() { |
54 | local CREATE="$1" |
55 | if [ -z "$CREATE" ]; then |
56 | [ -d "$ENVDIR" ] || exit 0 |
57 | fi |
58 | [ -x "$(which git 2>/dev/null)" ] || error "Git is not installed" |
59 | mkdir -p "$ENVDIR" || error "Failed to create the environment directory" |
60 | cd "$ENVDIR" || error "Failed to switch to the environment directory" |
61 | [ -d .git ] || { |
62 | git init && |
63 | touch .config && |
64 | mkdir files && |
65 | git add . && |
66 | git commit -q -m "Initial import" |
67 | } || { |
68 | rm -rf .git |
69 | error "Failed to initialize the environment directory" |
70 | } |
71 | } |
72 | |
73 | env_sync_data() { |
74 | [ \! -L "$BASEDIR/.config" -a -f "$BASEDIR/.config" ] && mv "$BASEDIR/.config" "$ENVDIR" |
75 | git add . |
76 | git add -u |
77 | } |
78 | |
79 | env_sync() { |
80 | local STR="$1" |
81 | env_sync_data |
82 | git commit -m "${STR:-Update} at $(date)" |
83 | } |
84 | |
85 | env_link_config() { |
86 | rm -f "$BASEDIR/.config" |
87 | ln -s env/.config "$BASEDIR/.config" |
88 | mkdir -p "$ENVDIR/files" |
89 | [ -L "$BASEDIR/files" ] || ln -s env/files "$BASEDIR/files" |
90 | } |
91 | |
92 | env_do_reset() { |
93 | git reset --hard HEAD |
94 | git clean -d -f |
95 | } |
96 | |
97 | env_list() { |
98 | env_init |
99 | git branch --color | grep -vE '^. master$' |
100 | } |
101 | |
102 | env_diff() { |
103 | env_init |
104 | env_sync_data |
105 | git diff --cached --color |
106 | env_link_config |
107 | } |
108 | |
109 | env_save() { |
110 | env_init |
111 | env_sync |
112 | env_link_config |
113 | } |
114 | |
115 | env_revert() { |
116 | env_init |
117 | env_do_reset |
118 | env_link_config |
119 | } |
120 | |
121 | env_ask_sync() { |
122 | env_sync_data |
123 | LINES="$(env_diff | wc -l)" # implies env_init |
124 | [ "$LINES" -gt 0 ] && { |
125 | if ask_bool 1 "Do you want to save your changes"; then |
126 | env_sync |
127 | else |
128 | env_do_reset |
129 | fi |
130 | } |
131 | } |
132 | |
133 | env_clear() { |
134 | env_init |
135 | [ -L "$BASEDIR/.config" ] && rm -f "$BASEDIR/.config" |
136 | [ -L "$BASEDIR/files" ] && rm -f "$BASEDIR/files" |
137 | [ -f "$ENVDIR/.config" ] || ( cd "$ENVDIR/files" && find | grep -vE '^\.$' > /dev/null ) |
138 | env_sync_data |
139 | if ask_bool 1 "Do you want to keep your current config and files"; then |
140 | mkdir -p "$BASEDIR/files" |
141 | shopt -s dotglob |
142 | cp -a "$ENVDIR/files/"* "$BASEDIR/files" 2>/dev/null >/dev/null |
143 | shopt -u dotglob |
144 | cp "$ENVDIR/.config" "$BASEDIR/" |
145 | else |
146 | rm -rf "$BASEDIR/files" "$BASEDIR/.config" |
147 | fi |
148 | cd "$BASEDIR" |
149 | rm -rf "$ENVDIR" |
150 | } |
151 | |
152 | env_delete() { |
153 | local name="${1##*/}" |
154 | env_init |
155 | [ -z "$name" ] && usage |
156 | branch="$(git branch | grep '^\* ' | awk '{print $2}')" |
157 | [ "$name" = "$branch" ] && error "cannot delete the currently selected environment" |
158 | git branch -D "$name" |
159 | } |
160 | |
161 | env_switch() { |
162 | local name="${1##*/}" |
163 | [ -z "$name" ] && usage |
164 | |
165 | env_init |
166 | env_ask_sync |
167 | git checkout "$name" || error "environment '$name' not found" |
168 | env_link_config |
169 | } |
170 | |
171 | env_rename() { |
172 | local NAME="${1##*/}" |
173 | env_init |
174 | git branch -m "$NAME" |
175 | } |
176 | |
177 | env_new() { |
178 | local NAME="$1" |
179 | local branch |
180 | local from="master" |
181 | |
182 | [ -z "$NAME" ] && usage |
183 | env_init 1 |
184 | |
185 | branch="$(git branch | grep '^\* ' | awk '{print $2}')" |
186 | if [ -n "$branch" -a "$branch" != "master" ]; then |
187 | env_ask_sync |
188 | if ask_bool 0 "Do you want to clone the current environment?"; then |
189 | from="$branch" |
190 | fi |
191 | rm -f "$BASEDIR/.config" "$BASEDIR/files" |
192 | fi |
193 | git checkout -b "$1" "$from" |
194 | if [ -f "$BASEDIR/.config" -o -d "$BASEDIR/files" ]; then |
195 | if ask_bool 1 "Do you want to start your configuration repository with the current configuration?"; then |
196 | [ -d "$BASEDIR/files" -a \! -L "$BASEDIR/files" ] && { |
197 | mkdir -p "$ENVDIR/files" |
198 | shopt -s dotglob |
199 | mv "$BASEDIR/files/"* "$ENVDIR/files/" 2>/dev/null |
200 | shopt -u dotglob |
201 | rmdir "$BASEDIR/files" |
202 | } |
203 | env_sync |
204 | else |
205 | rm -rf "$BASEDIR/.config" "$BASEDIR/files" |
206 | fi |
207 | fi |
208 | env_link_config |
209 | } |
210 | |
211 | COMMAND="$1"; shift |
212 | case "$COMMAND" in |
213 | help) usage 0;; |
214 | new) env_new "$@";; |
215 | list) env_list "$@";; |
216 | clear) env_clear "$@";; |
217 | switch) env_switch "$@";; |
218 | delete) env_delete "$@";; |
219 | rename) env_rename "$@";; |
220 | diff) env_diff "$@";; |
221 | save) env_save "$@";; |
222 | revert) env_revert "$@";; |
223 | *) usage;; |
224 | esac |
225 | |