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

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