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

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