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

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