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

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