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

d05f26956bc7f55bafb5c0e76db0751303c1b2d8
[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         cd "$HOME" || fatal "could not enter '$HOME'" 11
72         use
73         info "This operation WILL DETROY DATA!"
74         files=$(git ls-files)
75         echo "These files will be deleted:
76
77 $files
78
79 AGAIN, THIS WILL DELETE YOUR DATA!
80 To continue, type \"Yes, do as I say\""
81         read answer
82         [ "x$answer" = "xYes, do as I say" ] || exit 16
83         for file in $files; do
84                 rm -f $file || info "could not delete '$file', continuing with deletion"
85         done
86         rmdir "$GIT_DIR" || error "could not delete '$GIT_DIR'"
87 }
88
89 enter() {
90         use
91         $SHELL
92 }
93
94 git_dir_exists() {
95         [ -d "$GIT_DIR" ] || fatal "no repository found for '$VCSH_REPO_NAME'" 12
96 }
97
98 init() {
99         [ ! -e "$GIT_DIR" ] || fatal "'$GIT_DIR' exists" 10
100         export GIT_WORK_TREE="$HOME"
101         mkdir -p "$GIT_WORK_TREE" || fatal "could not create '$GIT_WORK_TREE'" 50
102         cd "$GIT_WORK_TREE" || fatal "could not enter '$GIT_WORK_TREE'" 11
103         git init
104         setup
105 }
106
107 list() {
108         for i in "$VCSH_BASE"/*.git; do
109                 echo $(basename "$i" .git)
110         done
111 }
112
113 #get_files() {
114 #       return $(git ls-files)
115 #}
116 #
117 #list_tracked_all() {
118 #       use
119 #       for repo in $(list); do
120 #               files="$files $(get_files $repo)"
121 #       done
122 #       echo $files | sort -u
123 #}
124
125 rename() {
126         git_dir_exists
127         [ -d "$GIT_DIR_NEW" ] && fatal "'$GIT_DIR_NEW' exists" 54
128         mv -f "$GIT_DIR" "$GIT_DIR_NEW" || fatal "Could not mv '$GIT_DIR' '$GIT_DIR_NEW'" 52
129
130 }
131
132 run() {
133         use
134         $VCSH_EXTERNAL_COMMAND
135 }
136
137 seed_gitignore() {
138         use
139         cd "$HOME" || fatal "could not enter '$HOME'" 11
140         gitignores=$(for file in $(git ls-files); do
141                 while true; do
142                         echo $file; new="${file%/*}"
143                         [ "$file" = "$new" ] && break
144                         file="$new"
145                 done;
146         done | sort -u)
147         tempfile=$(mktemp) || fatal "could not create tempfile" 51
148         echo '*' > "$tempfile" || fatal "could not write to '$tempfile'" 57
149         for gitignore in $gitignores; do
150                 echo "$gitignore" | sed 's/^/!/' >> "$tempfile" || fatal "could not write to '$tempfile'" 57
151                 [ -d "$gitignore" ] && { echo "$gitignore/*" | sed 's/^/!/' >> "$tempfile" || fatal "could not write to '$tempfile'" 57; }
152         done
153         if diff -N "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" > /dev/null; then
154                 rm -f "$tempfile" || error "could not delete '$tempfile'"
155                 exit
156         fi
157         if [ -e "$HOME/.gitignore.d/$VCSH_REPO_NAME" ]; then
158                 info "'$HOME/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$HOME/.gitignore.d/$VCSH_REPO_NAME.bak'"
159                 mv -f "$HOME/.gitignore.d/$VCSH_REPO_NAME" "$HOME/.gitignore.d/$VCSH_REPO_NAME.bak" ||
160                         fatal "could not move '$HOME/.gitignore.d/$VCSH_REPO_NAME' to '$HOME/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
161         fi
162         mv -f "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" ||
163                 fatal "could not move '$tempfile' to '$HOME/.gitignore.d/$VCSH_REPO_NAME'" 53
164 }
165
166 setup() {
167         use
168         git config core.worktree     "$GIT_WORK_TREE"
169         git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
170         git config vcsh.vcsh         'true'
171         [ -e "$HOME/.gitignore.d/$VCSH_REPO_NAME" ] && git add -f "$HOME/.gitignore.d/$VCSH_REPO_NAME"
172 }
173
174 use() {
175         git_dir_exists
176         export GIT_WORK_TREE="$(git config --get core.worktree)"
177         export VCSH_DIRECTORY="$VCSH_REPO_NAME"
178 }
179
180 if [ "$1" = 'clone' ]; then
181         [ -z $2 ] && fatal "$1: please specify a remote" 1
182         export VCSH_COMMAND="$1"
183         GIT_REMOTE="$2"
184         [ -n "$3" ] && VCSH_REPO_NAME="$3" || VCSH_REPO_NAME=$(basename "$GIT_REMOTE" .git)
185         export VCSH_REPO_NAME
186         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
187 elif [ "$1" = 'delete' ] ||
188      [ "$1" = 'enter' ] ||
189      [ "$1" = 'init' ] ||
190      [ "$1" = 'list-tracked-all' ] ||
191      [ "$1" = 'rename' ] ||
192      [ "$1" = 'run' ] ||
193      [ "$1" = 'seed-gitignore' ] ||
194      [ "$1" = 'setup' ]; then
195         [ -z $2 ]                      && fatal "$1: please specify repository to work on" 1
196         [ "$1" = 'rename' -a -z "$3" ] && fatal "$1: please specify a target name" 1
197         [ "$1" = 'run' -a -z "$3" ]    && fatal "$1: please specify a command" 1
198         export VCSH_COMMAND="$1"
199         export VCSH_REPO_NAME="$2"
200         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
201         [ "$VCSH_COMMAND" = 'rename' ]         && export GIT_DIR_NEW="$VCSH_BASE/$3.git"
202         if [ "$VCSH_COMMAND" = 'run' ]; then
203                 shift 2
204                 export VCSH_EXTERNAL_COMMAND="$*"
205                 # Did we receive a directory instead of a name? Mangle the input to fit normal operation!
206                 if echo $VCSH_REPO_NAME | grep -q '/'; then
207                         export GIT_DIR=$VCSH_REPO_NAME
208                         export VCSH_REPO_NAME=$(basename $VCSH_REPO_NAME .git)
209                 fi
210         fi
211         [ "$VCSH_COMMAND" = 'seed-gitignore' ]
212 #       [ "$VCSH_COMMAND" = 'seed-gitignore' ] && export VCSH_COMMAND='seed_gitignore'
213 elif [ "$1" = 'list' ]; then
214         export VCSH_COMMAND="$1"
215 elif [ -n "$1" ]; then
216         export VCSH_COMMAND=run
217         export VCSH_REPO_NAME="$1"
218         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
219         [ -d $GIT_DIR ] || { help; exit 1; }
220         shift 1
221         export VCSH_EXTERNAL_COMMAND="git $*"
222 else
223         # $1 is empty, or 'help'
224         help && exit
225 fi
226
227 for check_directory in "$VCSH_BASE" "$HOME/.gitignore.d"
228 do
229         if [ ! -d "$check_directory" ]; then
230                 if [ -e "$check_directory" ]; then
231                         fatal "'$check_directory' exists but is not a directory" 13
232                 else
233                         info "attempting to create '$check_directory'"
234                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
235                 fi
236         fi
237 done
238
239 verbose "$VCSH_COMMAND begin"
240 export VCSH_COMMAND=$(echo $VCSH_COMMAND | sed 's/-/_/g')
241 $VCSH_COMMAND
242 verbose "$VCSH_COMMAND end, exiting"