env 4.6 KB

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