]> git.madduck.net Git - code/vcsh.git/blob - vcsh

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Introduce `vcsh enter`
[code/vcsh.git] / vcsh
1 #!/bin/sh
2
3 [ -n "$VCSH_DEBUG" ]      && set -x
4 [ -z "$XDG_CONFIG_HOME" ] && XDG_CONFIG_HOME="$HOME/.config"
5 [ -z "$VCSH_BASE" ]       && VCSH_BASE="$XDG_CONFIG_HOME/vcsh/repo.d"
6
7 SELF=$(basename $0)
8 if [ "$SELF" = 'bash' ] ||
9    [ "$SELF" = 'dash' ] ||
10    [ "$SELF" = 'sh' ] ||
11    [ "$SELF" = 'zsh' ]; then
12         SELF='vcsh'
13         VCSH_SOURCED=1
14 fi
15
16
17 help() {
18         echo "usage: $SELF <args>
19
20    clone <remote> \\
21          [<repo>]       Clone from an existing repository
22    help                 Display this help text
23    delete               Delete an existing repository
24    enter                Enter repository; spawn new $SHELL
25    exit                 Exit repository; unset ENV
26    init <repo>          Initialize a new repository
27    list                 List all repositories
28    run <repo> \\
29        <command>        Use this repository
30
31    seed-gitignore \\
32    <repo>               Seed .gitignore.d/<repo> from git ls-files
33    use <repo>           Use repository; set ENV
34
35    <repo> <git command> Special command that allows you to run git commands
36                         directly without having to type so much ;)" >&2
37 }
38
39 debug() {
40         [ -n "$VCSH_DEBUG" ] && echo "$SELF: debug: $@"
41 }
42
43 verbose() {
44         if [ -n "$VCSH_DEBUG" ] || [ -n "$VCSH_VERBOSE" ]; then echo "$SELF: verbose: $@"; fi
45 }
46
47 use() {
48         verbose "use() begin"
49         if [ ! -d "$GIT_DIR" ]; then
50                 echo E: no repository found for "$VCSH_REPO_NAME" >&2
51                 return 1
52         fi
53         export GIT_DIR
54         export GIT_WORK_TREE="$(git config --get core.worktree)"
55         export VCSH_DIRECTORY="$VCSH_REPO_NAME"
56         verbose "use() end"
57 }
58
59 init() {
60         verbose "init() begin"
61         [ -e "$GIT_DIR" ] &&
62                 echo "$SELF: fatal: $GIT_DIR exists" &&
63                 return 21
64         export GIT_WORK_TREE="$HOME"
65         mkdir -p "$GIT_WORK_TREE"
66         cd "$GIT_WORK_TREE" ||
67                 (echo "$SELF: fatal: could not enter $GIT_WORK_TREE" &&
68                  return 1) || return $?
69         cd "$GIT_WORK_TREE"
70         git init
71         git config core.worktree     "$GIT_WORK_TREE"
72         git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
73         touch   "$HOME/.gitignore.d/$VCSH_REPO_NAME"
74         git add "$HOME/.gitignore.d/$VCSH_REPO_NAME"
75         verbose "init() end"
76 }
77
78 leave() {
79         unset GIT_DIR
80         unset GIT_WORK_TREE
81         unset VCSH_DIRECTORY
82 }
83
84
85 if [ "$1" = 'clone' ]; then
86         GIT_REMOTE="$2"
87         export GIT_REMOTE
88         VCSH_REPO_NAME="$3"
89         [ -z "$VCSH_REPO_NAME" ] && VCSH_REPO_NAME=$(basename "$GIT_REMOTE" .git)
90         export VCSH_REPO_NAME
91         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
92 elif [ "$1" = 'delete' ] ||
93      [ "$1" = 'enter' ] ||
94      [ "$1" = 'init' ] ||
95      [ "$1" = 'run' ] ||
96      [ "$1" = 'seed-gitignore' ] ||
97      [ "$1" = 'use' ]; then
98         [ -z $2 ] && echo "$SELF $1: error: please specify repository to work on" && return 1
99         export VCSH_COMMAND="$1"
100         export VCSH_REPO_NAME="$2"
101         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
102         shift 2
103         export VCSH_EXTERNAL_COMMAND="$*"
104         if [ "$VCSH_COMMAND" = 'run' ]; then
105                 [ -z "$VCSH_EXTERNAL_COMMAND" ] && echo "$SELF $1 $2: error: please specify a command" && return 1
106         fi
107 elif [ "$1" = 'exit' ] ||
108      [ "$1" = 'help' ] ||
109      [ "$1" = 'list' ]; then
110         export VCSH_COMMAND="$1"
111 else
112         [ -z $1 ] && help && return 0
113         export VCSH_COMMAND='run'
114         export VCSH_REPO_NAME="$1"
115         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
116         [ -d $GIT_DIR ] || (help && return 1) || return 0
117         shift 1
118         export VCSH_EXTERNAL_COMMAND="git $*"
119 fi
120
121
122 for check_directory in "$VCSH_BASE" "$HOME/.gitignore.d"
123 do
124         if [ ! -d "$check_directory" ]; then
125                 if [ -e "$check_directory" ]; then
126                         echo "$SELF: error: $check_directory exists but is not a directory" >&2
127                         return 2
128                 else
129                         echo "$SELF: info: attempting to create $check_directory"
130                         mkdir -p "$check_directory" || (echo "$SELF: error: could not create $check_directory" >&2; return 2) || return $?
131                 fi
132         fi
133 done
134
135
136 if [ "$VCSH_COMMAND" = 'clone' ]; then
137         verbose "clone begin"
138         init
139         git remote add origin "$GIT_REMOTE"
140         git config branch.master.remote origin
141         git config branch.master.merge  refs/heads/master
142         git fetch
143         for object in $(git ls-tree -r origin/master | awk '{print $4}'); do
144                 [ -e "$object" ] &&
145                         echo "$SELF: error: $object exists." &&
146                         VCSH_CONFLICT=1;
147         done
148         [ "$VCSH_CONFLICT" = '1' ] &&
149                 echo "$SELF: fatal: will stop after fetching and not try to merge!\n" &&
150                 echo "  Once this situation has been resolved, run 'vcsh run <foo> git pull' to finish cloning.\n" &&
151                 return 3
152         git merge origin/master
153 #       use || return $?
154         verbose "clone end"
155
156 #elif [ "$VCSH_COMMAND" = 'help' ] || [ "$#" -eq 0 ]; then
157 elif [ "$VCSH_COMMAND" = 'help' ]; then
158         help
159
160 elif [ "$VCSH_COMMAND" = 'delete' ]; then
161         verbose "delete begin"
162         old_dir="$PWD"
163         cd "$HOME"
164         use || return $?
165         echo "$SELF: info: This operation WILL DETROY DATA!"
166         files=$(git ls-files)
167         echo "These files will be deleted:
168
169 $files
170
171 AGAIN, THIS WILL DELETE YOUR DATA!
172 To continue, type \"Yes, do as I say\""
173         read answer
174         [ "x$answer" = "xYes, do as I say" ] || return 1
175         for file in $files; do
176                 rm -f $file || echo "$SELF: info: could not delete '$file', continuing with deletion"
177         done
178         rm -rf "$GIT_DIR" || echo "$SELF: info: could not delete '$GIT_DIR'"
179         cd "$old_dir"
180         verbose "delete end"
181
182 elif [ "$VCSH_COMMAND" = 'enter' ]; then
183         verbose "enter begin"
184         use || return $?
185         $SHELL
186         leave
187         verbose "enter end"
188
189 elif [ "$VCSH_COMMAND" = 'exit' ]; then
190         verbose "exit begin"
191 #       if [ -n "$ZSH_VERSION" ] && [ "$VCSH_NO_IGNORE_EOF" = '1' ]; then
192 #               unset VCSH_NO_IGNORE_EOF
193 #               setopt NO_IGNORE_EOF
194 #       fi
195         [ -z "$VCSH_SOURCED" ] && echo "$SELF $VCSH_COMMAND: You need to source vcsh if you want to run in this mode" && return 10
196         leave
197 #       [ -n "$ZSH_VERSION" ] && [ "$USER" = richih ] && buildPS1
198         verbose "exit end"
199         return 0
200
201 elif [ "$VCSH_COMMAND" = 'init' ]; then
202         verbose "init begin"
203         init
204 #       use || return $?
205         verbose "init end"
206
207 elif [ "$VCSH_COMMAND" = 'list' ]; then
208         verbose "list begin"
209         for i in "$VCSH_BASE"/*.git; do
210                 echo $(basename "$i" .git)
211         done
212         verbose "list end"
213
214 elif [ "$VCSH_COMMAND" = 'run' ]; then
215         verbose "run begin"
216         use || return $?
217         $VCSH_EXTERNAL_COMMAND
218         leave
219         verbose "run end"
220
221 elif [ "$VCSH_COMMAND" = 'seed-gitignore' ]; then
222         verbose "seed-gitignore begin"
223         use || return $?
224         # Switching directory as this has to be executed from $HOME to be of any use.
225         # Going back into old directory at the end in case `vcsh use` is reactivated.
226         old_dir="$PWD"
227         cd "$HOME"
228         git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
229         gitignores=$(for file in $(git ls-files); do
230                 while true; do
231                         echo $file; new="${file%/*}"
232                         [ "$file" = "$new" ] && break
233                         file="$new"
234                 done;
235         done | sort -u)
236         tempfile=$(mktemp) ||
237                 (echo "$SELF: fatal: could not create tempfile" && return 1) || return $?
238         echo '*' > "$tempfile"
239         for gitignore in $gitignores; do
240                 echo "$gitignore" | sed 's/^/!/' >> "$tempfile"
241                 [ -d "$gitignore" ] && echo "$gitignore/*" | sed 's/^/!/'>> "$tempfile"
242         done
243         diff -N "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" > /dev/null &&
244                 rm -f "$tempfile" &&
245                 return
246         if [ -e "$HOME/.gitignore.d/$VCSH_REPO_NAME" ]; then
247                 echo "$SELF: info: $HOME/.gitignore.d/$VCSH_REPO_NAME differs from new data, moving it to $HOME/.gitignore.d/$VCSH_REPO_NAME.bak"
248                 mv -f "$HOME/.gitignore.d/$VCSH_REPO_NAME" "$HOME/.gitignore.d/$VCSH_REPO_NAME.bak" ||
249                         (echo "$SELF: fatal: could not move $HOME/.gitignore.d/$VCSH_REPO_NAME to $HOME/.gitignore.d/$VCSH_REPO_NAME.bak" &&
250                          return 1) || return $?
251         fi
252         mv -f "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" ||
253                 (echo "$SELF: fatal: could not move $tempfile to $HOME/.gitignore.d/$VCSH_REPO_NAME" && return 1) || return $?
254         cd "$old_dir"
255         verbose "seed-gitignore end"
256
257 elif [ "$VCSH_COMMAND" = 'use' ]; then
258         verbose "use begin"
259 #       if [ -n "$ZSH_VERSION" ]; then
260 #               if [ -o NO_IGNORE_EOF ]; then
261 #                       export VCSH_NO_IGNORE_EOF=1
262 #                       setopt IGNORE_EOF
263 #               fi
264 #               vcsh_exit() {
265 #                       vcsh exit;
266 #                       zle reset-prompt;
267 #               }
268 #               zle -N vcsh_exit
269 #               bindkey '^d' 'vcsh_exit'
270 #       fi
271         [ -z "$VCSH_SOURCED" ] && echo "$SELF $VCSH_COMMAND: You need to source vcsh if you want to run in this mode" && return 10
272         use || return $?
273 #       [ -n "$ZSH_VERSION" ] && [ "$USER" = richih ] && buildPS1
274         verbose "use end"
275
276 else
277         verbose "defaulting to calling help()"
278         help
279         echo "$SELF: fatal: You should never reach this code. File a bug, please."
280         return 99
281
282 fi