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

cfcb42abe107222d9a0f3135f824b78d854f4749
[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
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
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         git init
100         setup
101 }
102
103 list() {
104         for i in "$VCSH_BASE"/*.git; do
105                 echo $(basename "$i" .git)
106         done
107 }
108
109 run() {
110         use
111         $VCSH_EXTERNAL_COMMAND
112 }
113
114 seed_gitignore() {
115         use
116         # Switching directory as this has to be executed from $HOME to be of any use.
117         # Going back into old directory at the end in case `vcsh use` is reactivated.
118         old_dir="$PWD"
119         cd "$HOME"
120         gitignores=$(for file in $(git ls-files); do
121                 while true; do
122                         echo $file; new="${file%/*}"
123                         [ "$file" = "$new" ] && break
124                         file="$new"
125                 done;
126         done | sort -u)
127         tempfile=$(mktemp) || fatal "could not create tempfile" 51
128         echo '*' > "$tempfile"
129         for gitignore in $gitignores; do
130                 echo "$gitignore" | sed 's/^/!/' >> "$tempfile"
131                 [ -d "$gitignore" ] && echo "$gitignore/*" | sed 's/^/!/'>> "$tempfile"
132         done
133         diff -N "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" > /dev/null &&
134                 rm -f "$tempfile" &&
135                 exit
136         if [ -e "$HOME/.gitignore.d/$VCSH_REPO_NAME" ]; then
137                 info "'$HOME/.gitignore.d/$VCSH_REPO_NAME' differs from new data, moving it to '$HOME/.gitignore.d/$VCSH_REPO_NAME.bak'"
138                 mv -f "$HOME/.gitignore.d/$VCSH_REPO_NAME" "$HOME/.gitignore.d/$VCSH_REPO_NAME.bak" ||
139                         fatal "could not move '$HOME/.gitignore.d/$VCSH_REPO_NAME' to '$HOME/.gitignore.d/$VCSH_REPO_NAME.bak'" 53
140         fi
141         mv -f "$tempfile" "$HOME/.gitignore.d/$VCSH_REPO_NAME" ||
142                 fatal "could not move '$tempfile' to '$HOME/.gitignore.d/$VCSH_REPO_NAME'" 53
143         cd "$old_dir"
144 }
145
146 setup() {
147         use
148         git config core.worktree     "$GIT_WORK_TREE"
149         git config core.excludesfile ".gitignore.d/$VCSH_REPO_NAME"
150         git config vcsh.vcsh         'true'
151         touch   "$HOME/.gitignore.d/$VCSH_REPO_NAME"
152         git add "$HOME/.gitignore.d/$VCSH_REPO_NAME"
153 }
154
155 use() {
156         if [ ! -d "$GIT_DIR" ]; then
157                 fatal "no repository found for '$VCSH_REPO_NAME'" 12
158         fi
159         export GIT_WORK_TREE="$(git config --get core.worktree)"
160         export VCSH_DIRECTORY="$VCSH_REPO_NAME"
161 }
162
163 if [ "$1" = 'clone' ]; then
164         [ -z $2 ] && fatal "$1: please specify a remote" 1
165         export VCSH_COMMAND="$1"
166         GIT_REMOTE="$2"
167         [ -n "$3" ] && VCSH_REPO_NAME="$3" || VCSH_REPO_NAME=$(basename "$GIT_REMOTE" .git)
168         export VCSH_REPO_NAME
169         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
170 elif [ "$1" = 'delete' ] ||
171      [ "$1" = 'enter' ] ||
172      [ "$1" = 'init' ] ||
173      [ "$1" = 'run' ] ||
174      [ "$1" = 'seed-gitignore' ] ||
175      [ "$1" = 'setup' ]; then
176         [ -z $2 ] && fatal "$1: please specify repository to work on" 1
177         [ "$1" = 'run' -a -z "$3" ] && fatal "$1: please specify a command" 1
178         export VCSH_COMMAND="$1"
179         export VCSH_REPO_NAME="$2"
180         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
181         [ "$VCSH_COMMAND" = 'run' ] && shift 2 && export VCSH_EXTERNAL_COMMAND="$@"
182         [ "$VCSH_COMMAND" = 'seed-gitignore' ] && export VCSH_COMMAND='seed_gitignore'
183 elif [ "$1" = 'list' ]; then
184         export VCSH_COMMAND="$1"
185 elif [ -n "$1" ]; then
186         export VCSH_COMMAND=run
187         export VCSH_REPO_NAME="$1"
188         export GIT_DIR="$VCSH_BASE/$VCSH_REPO_NAME.git"
189         [ -d $GIT_DIR ] || { help; exit 1; }
190         shift 1
191         export VCSH_EXTERNAL_COMMAND="git $*"
192 else
193         # $1 is empty, or 'help'
194         help && exit
195 fi
196
197 for check_directory in "$VCSH_BASE" "$HOME/.gitignore.d"
198 do
199         if [ ! -d "$check_directory" ]; then
200                 if [ -e "$check_directory" ]; then
201                         fatal "'$check_directory' exists but is not a directory" 13
202                 else
203                         info "attempting to create '$check_directory'"
204                         mkdir -p "$check_directory" || fatal "could not create '$check_directory'" 50
205                 fi
206         fi
207 done
208
209 verbose "$VCSH_COMMAND begin"
210 $VCSH_COMMAND
211 verbose "$VCSH_COMMAND end, exiting"