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