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

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