Document ls, sort help by name
[zit] / zit
1 #!/bin/bash
2
3 # Zit: the git-based single-file content tracker
4
5 # Copyright (C) 2008-2014 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
6 # Licensed under the GNU GPL v2
7 # A full copy of the license text is not included with zit, because the
8 # program is self-contained.
9 # Refer to http://www.gnu.org/licenses/gpl-2.0.html for the full license terms
10
11 abort() {
12         echo $1 >&2
13         exit 1
14 }
15
16 me=$(basename "$0")
17 if test "$me" = zit ; then
18         cmd="$1"
19         shift
20 else
21         cmd="$me"
22 fi
23
24 USAGE="usage: zit COMMAND FILE [ARGS...]"
25 VERSION=0.2014.10.23
26
27 zit_help() {
28         cmd="$1"
29         shift
30         case "$cmd" in
31         clone)
32                 echo "usage: zit clone REPO [FILE]"
33                 echo "Create and track FILE, retrieving its history from repository REPO."
34                 echo "FILE is guessed by REPO by stripping the '.git' suffix from the last path component"
35                 ;;
36         git)
37                 git help "$@"
38                 ;;
39         import)
40                 echo "usage: zit import FILE"
41                 echo "Import history from an RCS-tracked file. Requires rcs-fast-export."
42                 ;;
43         ls|list|tracked)
44                 echo "usage: zit $cmd"
45                 echo "Show tracked files, with a one-letter prefix indicating their status:"
46                 echo "   H   cached"
47                 echo "   M   unmerged"
48                 echo "   R   removed/deleted"
49                 echo "   C   modified/changed"
50                 echo "   K   to be killed"
51                 echo "   ?   other"
52                 ;;
53         view)
54                 echo "usage: zit view FILE"
55                 echo "Browse FILE's history with gitk (if possible) or tig."
56                 ;;
57         with)
58                 echo "usage: zit with FILE COMMAND..."
59                 echo "Run COMMAND after setting up the git environment for FILE."
60                 ;;
61         zig)
62                 echo "usage: zig FILE"
63                 echo "Browse FILE's history with tig."
64                 ;;
65         zik)
66                 echo "usage: zik FILE"
67                 echo "Browse FILE's history with gitk."
68                 ;;
69         *)
70                 echo $USAGE
71                 echo ""
72                 echo "Set up a git repository under .FILE.git to track changes for FILE."
73                 echo "File must be a regular file and in the current directory."
74                 echo ""
75                 echo "Zit commands:"
76                 echo "   add            Stage changes or start tracking changes to FILE"
77                 echo "   clone  Clone and track a remote file"
78                 echo "   import Import RCS history for FILE"
79                 echo "   init           Synonym for track"
80                 echo "   list           Synonym for tracked"
81                 echo "   ls             Synonym for tracked"
82                 echo "   track  Start tracking changes to FILE"
83                 echo "   tracked        List tracked files in current directory"
84                 echo "   view           Browse FILE's history with gitk or tig"
85                 echo "   with           Run a command in FILE's context"
86                 echo ""
87                 echo "See 'zit help git' or 'git help' for git commands."
88                 ;;
89         esac
90 }
91
92 ZIT_DIR=.zit
93
94 zit_setup() {
95         ZIT_FILE="$1"
96         test "$ZIT_FILE" || abort "Please specify a file"
97         test -f "$ZIT_FILE" || abort "No such file $ZIT_FILE"
98         test "$ZIT_FILE" = $(basename "$ZIT_FILE") || abort "Sorry, Zit only works on files in the current directory"
99
100         export GIT_WORK_TREE=$(pwd)
101
102         # first, check if a repo exists already, looking for
103         # .zit/file.git or .file.git, in that order
104         # if neither is found, and .zit exists, set the repo dir
105         # to .zit/file.git, otherwise set it to .file.git
106         GIT_DIR="$ZIT_DIR/$ZIT_FILE.git"
107         if ! test -d "$GIT_DIR"; then
108                 GIT_DIR=".$ZIT_FILE.git"
109                 if ! test -d "$GIT_DIR"; then
110                         test -d "$ZIT_DIR" && GIT_DIR="$ZIT_DIR/$ZIT_FILE.git"
111                 fi
112         fi
113         export GIT_DIR
114 }
115
116 # initialize the zitdir, without actually making the first commit
117 zitdir_init() {
118         zit_setup $1
119         test -e "$GIT_DIR" && abort "$GIT_DIR exists, is $ZIT_FILE tracked already?"
120         mkdir "$GIT_DIR" && echo "Initializing Zit repository in $GIT_DIR"
121         test -d "$GIT_DIR" || abort "Failed to create $GIT_DIR"
122         git init || abort "Failed to initialize Git repository in $GIT_DIR"
123         rm -rf "$GIT_DIR"/{hooks,info,branches,refs/tags,objects/pack,description}
124         if test -d "$ZIT_DIR"; then
125                 ZIT_EXCLUDE="$ZIT_DIR/exclude"
126         else
127                 ZIT_EXCLUDE="$GIT_DIR/exclude"
128         fi
129         if ! test -f "$ZIT_EXCLUDE"; then
130                 touch "$ZIT_EXCLUDE" || abort "Cannot create $ZIT_EXCLUDE file"
131                 echo "# Ignore patterns used by Zit repositories in the parent worktree." > "$ZIT_EXCLUDE"
132                 echo "# By default it's the single '*' glob, since we want to ignore all" >> "$ZIT_EXCLUDE"
133                 echo "# non-tracked files in the work-tree." >> "$ZIT_EXCLUDE"
134                 echo "# This file is autogenerated and there's usually no need to edit it." >> "$ZIT_EXCLUDE"
135                 echo "*" >> "$ZIT_EXCLUDE"
136         fi
137         git config core.excludesfile "$ZIT_EXCLUDE"
138 }
139
140 zitdir_check() {
141         if test -f "$ZIT_DIR"; then
142                 abort "$ZIT_DIR exists but it's not a directory, cannot continue"
143         fi
144 }
145
146 zit_init() {
147         if test -n "$1"; then
148                 zitdir_init "$1"
149                 git add -f "$ZIT_FILE" || abort "Failed to add $ZIT_FILE"
150                 git commit "$@" || abort "Failed to make first commit for $ZIT_FILE"
151         else
152                 if test -d "$ZIT_DIR"; then
153                         echo "$ZIT_DIR exists already"
154                         exit
155                 fi
156                 zitdir_check
157                 mkdir "$ZIT_DIR"
158         fi
159 }
160
161 zit_add() {
162         # add the file if it is tracked, otherwise zit init
163         zitdir_check
164         zit_setup "$1"
165
166         if test -d "$GIT_DIR"; then
167                 git add "$1"
168         else
169                 zit_init "$1"
170         fi
171 }
172
173 zit_list() {
174         export GIT_WORK_TREE="$(pwd)"
175         GIT_DIR=""
176         for file in "$ZIT_DIR"/*.git .*.git; do
177                 if ! test -e $file; then
178                         continue
179                 fi
180                 export GIT_DIR="$file"
181                 file="${file#.}"
182                 file="${file#zit/}"
183                 file="${file%.git}"
184                 (git ls-files -m -d -t; git ls-files -t) | uniq -f 1
185         done;
186         # if $GIT_DIR is empty, no files were found
187         test "$GIT_DIR" || echo "(no files tracked by zit)"
188 }
189
190 # import an RCS-tracked file using rcs-fast-export, if found
191 zit_import() {
192         which rcs-fast-export || abort "rcs-fast-export not found, I can't import RCS-tracked files, sorry"
193         zitdir_init "$1"
194         # git-fast-import creates a pack file, so (re)build the objects/pack dir
195         mkdir -p "$GIT_DIR/objects/pack"
196         rcs-fast-export "$1" | git-fast-import
197         # for some reason, rcs-fast-export | git-fast-import leaves the original
198         # file in 'deleted' state, a situation which is easily fixed by adding
199         # it back
200         git add -f "$1"
201 }
202
203 zit_clone() {
204         SRC="$1"
205         test -n "$SRC" || abort "Where do you want to clone from?"
206         if [ -n "$2" ]; then
207                 ZIT_FILE="$2"
208         else
209                 ZIT_FILE=$(basename "$SRC" .git)
210         fi
211         test -e "$ZIT_FILE" && abort "File $ZIT_FILE exists already"
212         test "$ZIT_FILE" = $(basename $ZIT_FILE) || abort "Sorry, Zit only works on files in the current directory"
213         touch "$ZIT_FILE" # to make zit_setup happy
214         zitdir_init "$ZIT_FILE"
215
216         git remote add origin "$SRC"
217         git remote update
218         rm "$ZIT_FILE"
219         git checkout -b master origin/master
220 }
221
222 case "$cmd" in
223 "")
224         echo $USAGE
225         ;;
226 version|--version)
227         echo "zit version $VERSION"
228         git --version
229         ;;
230 help|--help|-h|-?)
231         zit_help "$@"
232         ;;
233 add)
234         zit_add "$1"
235         ;;
236 init|track)
237         zit_init "$1"
238         ;;
239 clone)
240         zit_clone "$@"
241         ;;
242 list|ls|tracked)
243         zit_list
244         ;;
245 import)
246         zit_import "$1"
247         ;;
248 view)
249         zit_setup "$1"
250         shift
251         if test -n "$DISPLAY" -a -n "$(which gitk)" ; then
252                 gitk "$@"
253         elif test -n "$(which tig)" ; then
254                 tig "$@"
255         else
256                 abort "Neither gitk or tig could be launched"
257         fi
258         ;;
259 with)
260         zit_setup "$1"
261         shift
262         "$@"
263         ;;
264 zig)
265         zit_setup "$1"
266         shift
267         tig "$@"
268         ;;
269 zik)
270         zit_setup "$1"
271         shift
272         gitk "$@"
273         ;;
274 # Most commands will work with the generic catch-all mechanism used
275 # below, but some of them require a more thorough analysis of the
276 # parameters to decide whether $ZIT_FILE should be put back into the
277 # parameter list or not. For example,
278 # $ zit commit somefile
279 # wouldn't do what one expects it to do, unless 'add' is run first, however
280 # $ zit commit somefile -a
281 # would work correctly. So we handle some commands separately (for the
282 # moment just commit)
283 commit)
284         zit_setup "$1"
285         shift
286         git commit "$@" "$ZIT_FILE"
287         ;;
288         # the raw<command> method can be used to not replicate $ZIT_FILE in the
289         # parameter list
290 raw*)
291         zit_setup "$1"
292         shift
293         git ${cmd#raw} "$@"
294         ;;
295 *)
296         zit_setup "$1"
297         shift
298         git $cmd "$@"
299         ;;
300 esac