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

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