]> 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:

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