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

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