]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | #!/usr/bin/env bash |
7c673cae FG |
2 | # |
3 | # File: git-archive-all.sh | |
4 | # | |
5 | # Description: A utility script that builds an archive file(s) of all | |
6 | # git repositories and submodules in the current path. | |
7 | # Useful for creating a single tarfile of a git super- | |
8 | # project that contains other submodules. | |
9 | # | |
10 | # Examples: Use git-archive-all.sh to create archive distributions | |
11 | # from git repositories. To use, simply do: | |
12 | # | |
13 | # cd $GIT_DIR; git-archive-all.sh | |
14 | # | |
15 | # where $GIT_DIR is the root of your git superproject. | |
16 | # | |
17 | # License: GPL3 | |
18 | # | |
19 | ############################################################################### | |
20 | # | |
21 | # This program is free software; you can redistribute it and/or modify | |
22 | # it under the terms of the GNU General Public License as published by | |
23 | # the Free Software Foundation; either version 2 of the License, or | |
24 | # (at your option) any later version. | |
25 | # | |
26 | # This program is distributed in the hope that it will be useful, | |
27 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
28 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
29 | # GNU General Public License for more details. | |
30 | # | |
31 | # You should have received a copy of the GNU General Public License | |
32 | # along with this program; if not, write to the Free Software | |
33 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
34 | # | |
35 | ############################################################################### | |
36 | ||
37 | # DEBUGGING | |
38 | set -e | |
39 | set -C # noclobber | |
40 | ||
41 | # TRAP SIGNALS | |
42 | trap 'cleanup' QUIT EXIT | |
43 | ||
44 | # For security reasons, explicitly set the internal field separator | |
45 | # to newline, space, tab | |
46 | OLD_IFS=$IFS | |
47 | IFS=' | |
48 | ' | |
49 | ||
50 | function cleanup () { | |
51 | rm -rf $TMPDIR | |
52 | IFS="$OLD_IFS" | |
53 | } | |
54 | ||
55 | function usage () { | |
56 | echo "Usage is as follows:" | |
57 | echo | |
58 | echo "$PROGRAM <--version>" | |
59 | echo " Prints the program version number on a line by itself and exits." | |
60 | echo | |
61 | echo "$PROGRAM <--usage|--help|-?>" | |
62 | echo " Prints this usage output and exits." | |
63 | echo | |
64 | echo "$PROGRAM [--format <fmt>] [--prefix <path>] [--verbose|-v] [--separate|-s]" | |
65 | echo " [--tree-ish|-t <tree-ish>] [--ignore pattern] [output_file]" | |
66 | echo " Creates an archive for the entire git superproject, and its submodules" | |
67 | echo " using the passed parameters, described below." | |
68 | echo | |
69 | echo " If '--format' is specified, the archive is created with the named" | |
70 | echo " git archiver backend. Obviously, this must be a backend that git archive" | |
71 | echo " understands. The format defaults to 'tar' if not specified." | |
72 | echo | |
73 | echo " If '--prefix' is specified, the archive's superproject and all submodules" | |
74 | echo " are created with the <path> prefix named. The default is to not use one." | |
75 | echo | |
76 | echo " If '--separate' or '-s' is specified, individual archives will be created" | |
77 | echo " for each of the superproject itself and its submodules. The default is to" | |
78 | echo " concatenate individual archives into one larger archive." | |
79 | echo | |
80 | echo " If '--tree-ish' is specified, the archive will be created based on whatever" | |
81 | echo " you define the tree-ish to be. Branch names, commit hash, etc. are acceptable." | |
82 | echo " Defaults to HEAD if not specified. See git archive's documentation for more" | |
83 | echo " information on what a tree-ish is." | |
84 | echo | |
85 | echo " If '--ignore' is specified, we will filter out any submodules that" | |
86 | echo " match the specified pattern." | |
87 | echo | |
88 | echo " If 'output_file' is specified, the resulting archive is created as the" | |
89 | echo " file named. This parameter is essentially a path that must be writeable." | |
90 | echo " When combined with '--separate' ('-s') this path must refer to a directory." | |
91 | echo " Without this parameter or when combined with '--separate' the resulting" | |
92 | echo " archive(s) are named with a dot-separated path of the archived directory and" | |
93 | echo " a file extension equal to their format (e.g., 'superdir.submodule1dir.tar')." | |
94 | echo | |
95 | echo " If '--verbose' or '-v' is specified, progress will be printed." | |
96 | } | |
97 | ||
98 | function version () { | |
99 | echo "$PROGRAM version $VERSION" | |
100 | } | |
101 | ||
102 | # Internal variables and initializations. | |
103 | readonly PROGRAM=`basename "$0"` | |
104 | readonly VERSION=0.2 | |
105 | ||
106 | OLD_PWD="`pwd`" | |
107 | TMPDIR=`mktemp -d "${TMPDIR:-/tmp}/$PROGRAM.XXXXXX"` | |
108 | TMPFILE=`mktemp "$TMPDIR/$PROGRAM.XXXXXX"` # Create a place to store our work's progress | |
109 | TOARCHIVE=`mktemp "$TMPDIR/$PROGRAM.toarchive.XXXXXX"` | |
110 | OUT_FILE=$OLD_PWD # assume "this directory" without a name change by default | |
111 | SEPARATE=0 | |
112 | VERBOSE=0 | |
113 | ||
114 | TARCMD=tar | |
115 | [[ $(uname) == "Darwin" ]] && TARCMD=gnutar | |
116 | FORMAT=tar | |
117 | PREFIX= | |
118 | TREEISH=HEAD | |
119 | IGNORE= | |
120 | ||
121 | # RETURN VALUES/EXIT STATUS CODES | |
122 | readonly E_BAD_OPTION=254 | |
123 | readonly E_UNKNOWN=255 | |
124 | ||
125 | # Process command-line arguments. | |
126 | while test $# -gt 0; do | |
127 | case $1 in | |
128 | --format ) | |
129 | shift | |
130 | FORMAT="$1" | |
131 | shift | |
132 | ;; | |
133 | ||
134 | --prefix ) | |
135 | shift | |
136 | PREFIX="$1" | |
137 | shift | |
138 | ;; | |
139 | ||
140 | --separate | -s ) | |
141 | shift | |
142 | SEPARATE=1 | |
143 | ;; | |
144 | ||
145 | --tree-ish | -t ) | |
146 | shift | |
147 | TREEISH="$1" | |
148 | shift | |
149 | ;; | |
150 | ||
151 | --ignore ) | |
152 | shift | |
153 | IGNORE="$1" | |
154 | shift | |
155 | ;; | |
156 | ||
157 | --version ) | |
158 | version | |
159 | exit | |
160 | ;; | |
161 | ||
162 | --verbose | -v ) | |
163 | shift | |
164 | VERBOSE=1 | |
165 | ;; | |
166 | ||
167 | -? | --usage | --help ) | |
168 | usage | |
169 | exit | |
170 | ;; | |
171 | ||
172 | -* ) | |
173 | echo "Unrecognized option: $1" >&2 | |
174 | usage | |
175 | exit $E_BAD_OPTION | |
176 | ;; | |
177 | ||
178 | * ) | |
179 | break | |
180 | ;; | |
181 | esac | |
182 | done | |
183 | ||
184 | if [ ! -z "$1" ]; then | |
185 | OUT_FILE="$1" | |
186 | shift | |
187 | fi | |
188 | ||
189 | # Validate parameters; error early, error often. | |
190 | if [ $SEPARATE -eq 1 -a ! -d $OUT_FILE ]; then | |
191 | echo "When creating multiple archives, your destination must be a directory." | |
192 | echo "If it's not, you risk being surprised when your files are overwritten." | |
193 | exit | |
194 | elif [ `git config -l | grep -q '^core\.bare=false'; echo $?` -ne 0 ]; then | |
195 | echo "$PROGRAM must be run from a git working copy (i.e., not a bare repository)." | |
196 | exit | |
197 | fi | |
198 | ||
199 | # Create the superproject's git-archive | |
200 | if [ $VERBOSE -eq 1 ]; then | |
201 | echo -n "creating superproject archive..." | |
202 | fi | |
203 | git archive --format=$FORMAT --prefix="$PREFIX" $TREEISH > $TMPDIR/$(basename "$(pwd)").$FORMAT | |
204 | if [ $VERBOSE -eq 1 ]; then | |
205 | echo "done" | |
206 | fi | |
207 | echo $TMPDIR/$(basename "$(pwd)").$FORMAT >| $TMPFILE # clobber on purpose | |
208 | superfile=`head -n 1 $TMPFILE` | |
209 | ||
210 | if [ $VERBOSE -eq 1 ]; then | |
211 | echo -n "looking for subprojects..." | |
212 | fi | |
213 | # find all '.git' dirs, these show us the remaining to-be-archived dirs | |
214 | # we only want directories that are below the current directory | |
215 | find . -mindepth 2 -name '.git' -type d -print | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE | |
216 | # as of version 1.7.8, git places the submodule .git directories under the superprojects .git dir | |
217 | # the submodules get a .git file that points to their .git dir. we need to find all of these too | |
218 | find . -mindepth 2 -name '.git' -type f -print | xargs grep -l "gitdir" | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE | |
219 | ||
220 | if [ -n "$IGNORE" ]; then | |
221 | cat $TOARCHIVE | grep -v $IGNORE > $TOARCHIVE.new | |
222 | mv $TOARCHIVE.new $TOARCHIVE | |
223 | fi | |
224 | ||
225 | if [ $VERBOSE -eq 1 ]; then | |
226 | echo "done" | |
227 | echo " found:" | |
228 | cat $TOARCHIVE | while read arch | |
229 | do | |
230 | echo " $arch" | |
231 | done | |
232 | fi | |
233 | ||
234 | if [ $VERBOSE -eq 1 ]; then | |
235 | echo -n "archiving submodules..." | |
236 | fi | |
237 | while read path; do | |
238 | TREEISH=$(git submodule | grep "^ .*${path%/} " | cut -d ' ' -f 2) # git submodule does not list trailing slashes in $path | |
239 | cd "$path" | |
240 | git archive --format=$FORMAT --prefix="${PREFIX}$path" ${TREEISH:-HEAD} > "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT | |
241 | if [ $FORMAT == 'zip' ]; then | |
242 | # delete the empty directory entry; zipped submodules won't unzip if we don't do this | |
243 | zip -d "$(tail -n 1 $TMPFILE)" "${PREFIX}${path%/}" >/dev/null # remove trailing '/' | |
244 | fi | |
245 | echo "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT >> $TMPFILE | |
246 | cd "$OLD_PWD" | |
247 | done < $TOARCHIVE | |
248 | if [ $VERBOSE -eq 1 ]; then | |
249 | echo "done" | |
250 | fi | |
251 | ||
252 | if [ $VERBOSE -eq 1 ]; then | |
253 | echo -n "concatenating archives into single archive..." | |
254 | fi | |
255 | # Concatenate archives into a super-archive. | |
256 | if [ $SEPARATE -eq 0 ]; then | |
257 | if [ $FORMAT == 'tar' ]; then | |
258 | sed -e '1d' $TMPFILE | while read file; do | |
259 | $TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file" | |
260 | done | |
261 | elif [ $FORMAT == 'zip' ]; then | |
262 | sed -e '1d' $TMPFILE | while read file; do | |
263 | # zip incorrectly stores the full path, so cd and then grow | |
264 | cd `dirname "$file"` | |
265 | zip -g "$superfile" `basename "$file"` && rm -f "$file" | |
266 | done | |
267 | cd "$OLD_PWD" | |
268 | fi | |
269 | ||
270 | echo "$superfile" >| $TMPFILE # clobber on purpose | |
271 | fi | |
272 | if [ $VERBOSE -eq 1 ]; then | |
273 | echo "done" | |
274 | fi | |
275 | ||
276 | if [ $VERBOSE -eq 1 ]; then | |
277 | echo -n "moving archive to $OUT_FILE..." | |
278 | fi | |
279 | while read file; do | |
280 | mv "$file" "$OUT_FILE" | |
281 | done < $TMPFILE | |
282 | if [ $VERBOSE -eq 1 ]; then | |
283 | echo "done" | |
284 | fi |