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

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