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

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