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

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