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

Use `rmdir` instead of `rm -rf`
[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         # Switching directory as this has to be executed from $HOME to be of any use.
130         # Going back into old directory at the end in case `vcsh use` is reactivated.
131         old_dir="$PWD"
132         cd "$HOME"
133         gitignores=$(for file in $(git ls-files); do
134                 while true; do
135                         echo $file; new="${file%/*}"
136                         [ "$file" = "$new" ] && break
137                         file="$new"
138                 done;
139         done | sort -u)
140         tempfile=$(mktemp) || fatal "could not create tempfile" 51
141         echo '*' > "$tempfile"
142         for gitignore in $gitignores; do
143                 echo "$gitignore" | sed 's/^/!/' >> "$tempfile"
144                 [ -d "$gitignore" ] && echo "$gitignore/*" | sed 's/^/!/'>> "$tempfile"
145         done
146         diff -N "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" > /dev/null &&
147                 rm -f "$tempfile" &&
148                 exit
149         if [ -e "$HOME/.gitignore.d/$VCSH_REPO_NAME" ]; then
150                 info "'$HOME/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$HOME/.gitignore.d/$VCSH_REPO_NAME.bak'"
151                 mv -f "$HOME/.gitignore.d/$VCSH_REPO_NAME" "$HOME/.gitignore.d/$VCSH_REPO_NAME.bak" ||
152                         fatal "could not move '$HOME/.gitignore.d/$VCSH_REPO_NAME' to '$HOME/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
153         fi
154         mv -f "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" ||
155                 fatal "could not move '$tempfile' to '$HOME/.gitignore.d/$VCSH_REPO_NAME'" 53
156         cd "$old_dir"
157 }
158
159 setup() {
160         use
161         git config core.worktree     "$GIT_WORK_TREE"
162         git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
163         git config vcsh.vcsh         'true'
164         touch   "$HOME/.gitignore.d/$VCSH_REPO_NAME"
165         git add "$HOME/.gitignore.d/$VCSH_REPO_NAME"
166 }
167
168 use() {
169         git_dir_exists
170         export GIT_WORK_TREE="$(git config --get core.worktree)"
171         export VCSH_DIRECTORY="$VCSH_REPO_NAME"
172 }
173
174 if [ "$1" = 'clone' ]; then
175         [ -z $2 ] && fatal "$1: please specify a remote" 1
176         export VCSH_COMMAND="$1"
177         GIT_REMOTE="$2"
178         [ -n "$3" ] && VCSH_REPO_NAME="$3" || VCSH_REPO_NAME=$(basename "$GIT_REMOTE" .git)
179         export VCSH_REPO_NAME
180         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
181 elif [ "$1" = 'delete' ] ||
182      [ "$1" = 'enter' ] ||
183      [ "$1" = 'init' ] ||
184      [ "$1" = 'rename' ] ||
185      [ "$1" = 'run' ] ||
186      [ "$1" = 'seed-gitignore' ] ||
187      [ "$1" = 'setup' ]; then
188         [ -z $2 ]                      && fatal "$1: please specify repository to work on" 1
189         [ "$1" = 'rename' -a -z "$3" ] && fatal "$1: please specify a target name" 1
190         [ "$1" = 'run' -a -z "$3" ]    && fatal "$1: please specify a command" 1
191         export VCSH_COMMAND="$1"
192         export VCSH_REPO_NAME="$2"
193         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
194         [ "$VCSH_COMMAND" = 'rename' ]         && export GIT_DIR_NEW="$VCSH_BASE/$3.git"
195         [ "$VCSH_COMMAND" = 'run' ] && shift 2 && export VCSH_EXTERNAL_COMMAND="$@"
196         [ "$VCSH_COMMAND" = 'seed-gitignore' ] && export VCSH_COMMAND='seed_gitignore'
197 elif [ "$1" = 'list' ]; then
198         export VCSH_COMMAND="$1"
199 elif [ -n "$1" ]; then
200         export VCSH_COMMAND=run
201         export VCSH_REPO_NAME="$1"
202         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
203         [ -d $GIT_DIR ] || { help; exit 1; }
204         shift 1
205         export VCSH_EXTERNAL_COMMAND="git $*"
206 else
207         # $1 is empty, or 'help'
208         help && exit
209 fi
210
211 for check_directory in "$VCSH_BASE" "$HOME/.gitignore.d"
212 do
213         if [ ! -d "$check_directory" ]; then
214                 if [ -e "$check_directory" ]; then
215                         fatal "'$check_directory' exists but is not a directory" 13
216                 else
217                         info "attempting to create '$check_directory'"
218                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
219                 fi
220         fi
221 done
222
223 verbose "$VCSH_COMMAND begin"
224 $VCSH_COMMAND
225 verbose "$VCSH_COMMAND end, exiting"