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

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