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

0a9e52dc64fe8bf0fc98fc039858473fd6b8f640
[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    setup                Set up repository with recommended settings
27    write-gitignore \\
28    <repo>               Write .gitignore.d/<repo> via git ls-files
29
30    <repo> <git command> Special command that allows you to run git commands
31                         directly without having to type so much ;)" >&2
32    <repo>               Special command that enters a repository
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 setup() {
145         use
146         git config core.worktree     "$GIT_WORK_TREE"
147         git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
148         git config vcsh.vcsh         'true'
149         [ -e "$HOME/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$HOME/.gitignore.d/$VCSH_REPO_NAME"
150 }
151
152 use() {
153         git_dir_exists
154         export GIT_WORK_TREE="$(git config --get core.worktree)"
155         export VCSH_DIRECTORY="$VCSH_REPO_NAME"
156 }
157
158 write_gitignore() {
159         use
160         cd "$HOME" || fatal "could not enter '$HOME'" 11
161         gitignores=$(for file in $(git ls-files); do
162                 while true; do
163                         echo $file; new="${file%/*}"
164                         [ "$file" = "$new" ] && break
165                         file="$new"
166                 done;
167         done | sort -u)
168         tempfile=$(mktemp) || fatal "could not create tempfile" 51
169         echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
170         for gitignore in $gitignores; do
171                 echo "$gitignore" | sed 's/^/!/' >> "$tempfile" || fatal "could not write to '$tempfile'" 57
172                 [ -d "$gitignore" ] && { echo "$gitignore/*" | sed 's/^/!/' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; }
173         done
174         if diff -N "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then
175                 rm -f "$tempfile" || error "could not delete '$tempfile'"
176                 exit
177         fi
178         if [ -e "$HOME/.gitignore.d/$VCSH_REPO_NAME" ]; then
179                 info "'$HOME/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$HOME/.gitignore.d/$VCSH_REPO_NAME.bak'"
180                 mv -f "$HOME/.gitignore.d/$VCSH_REPO_NAME" "$HOME/.gitignore.d/$VCSH_REPO_NAME.bak" ||
181                         fatal "could not move '$HOME/.gitignore.d/$VCSH_REPO_NAME' to '$HOME/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
182         fi
183         mv -f "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" ||
184                 fatal "could not move '$tempfile' to '$HOME/.gitignore.d/$VCSH_REPO_NAME'" 53
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" = 'setup' ] ||
201      [ "$1" = 'write-gitignore' ]; 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" = 'write-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"