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

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