git-archive-all.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. #!/bin/bash -
  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 3 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. # DEBUGGING
  37. set -e
  38. set -C # noclobber
  39. # TRAP SIGNALS
  40. trap 'cleanup' QUIT EXIT
  41. # For security reasons, explicitly set the internal field separator
  42. # to newline, space, tab
  43. OLD_IFS=$IFS
  44. IFS="$(printf '\n \t')"
  45. function cleanup () {
  46. rm -f $TMPFILE
  47. rm -f $TMPLIST
  48. rm -f $TOARCHIVE
  49. IFS="$OLD_IFS"
  50. }
  51. function usage () {
  52. echo "Usage is as follows:"
  53. echo
  54. echo "$PROGRAM <--version>"
  55. echo " Prints the program version number on a line by itself and exits."
  56. echo
  57. echo "$PROGRAM <--usage|--help|-?>"
  58. echo " Prints this usage output and exits."
  59. echo
  60. echo "$PROGRAM [--format <fmt>] [--prefix <path>] [--verbose|-v] [--separate|-s]"
  61. echo " [--worktree-attributes] [--tree-ish|-t <tree-ish>] [output_file]"
  62. echo " Creates an archive for the entire git superproject, and its submodules"
  63. echo " using the passed parameters, described below."
  64. echo
  65. echo " If '--format' is specified, the archive is created with the named"
  66. echo " git archiver backend. Obviously, this must be a backend that git archive"
  67. echo " understands. The format defaults to 'tar' if not specified."
  68. echo
  69. echo " If '--prefix' is specified, the archive's superproject and all submodules"
  70. echo " are created with the <path> prefix named. The default is to not use one."
  71. echo
  72. echo " If '--worktree-attributes' is specified, the invidual archive commands will"
  73. echo " look for attributes in .gitattributes in the working directory too."
  74. echo
  75. echo " If '--separate' or '-s' is specified, individual archives will be created"
  76. echo " for each of the superproject itself and its submodules. The default is to"
  77. echo " concatenate individual archives into one larger archive."
  78. echo
  79. echo " If '--tree-ish' is specified, the archive will be created based on whatever"
  80. echo " you define the tree-ish to be. Branch names, commit hash, etc. are acceptable."
  81. echo " Defaults to HEAD if not specified. See git archive's documentation for more"
  82. echo " information on what a tree-ish is."
  83. echo
  84. echo " If 'output_file' is specified, the resulting archive is created as the"
  85. echo " file named. This parameter is essentially a path that must be writeable."
  86. echo " When combined with '--separate' ('-s') this path must refer to a directory."
  87. echo " Without this parameter or when combined with '--separate' the resulting"
  88. echo " archive(s) are named with a dot-separated path of the archived directory and"
  89. echo " a file extension equal to their format (e.g., 'superdir.submodule1dir.tar')."
  90. echo
  91. echo " The special value '-' (single dash) is treated as STDOUT and, when used, the"
  92. echo " --separate option is ignored. Use a double-dash to separate the outfile from"
  93. echo " the value of previous options. For example, to write a .zip file to STDOUT:"
  94. echo
  95. echo " ./$PROGRAM --format zip -- -"
  96. echo
  97. echo " If '--verbose' or '-v' is specified, progress will be printed."
  98. }
  99. function version () {
  100. echo "$PROGRAM version $VERSION"
  101. }
  102. # Internal variables and initializations.
  103. readonly PROGRAM=`basename "$0"`
  104. readonly VERSION=0.3
  105. SEPARATE=0
  106. VERBOSE=0
  107. TARCMD=`command -v gtar || command -v gnutar || command -v tar`
  108. FORMAT=tar
  109. PREFIX=
  110. TREEISH=HEAD
  111. ARCHIVE_OPTS=
  112. # RETURN VALUES/EXIT STATUS CODES
  113. readonly E_BAD_OPTION=254
  114. readonly E_UNKNOWN=255
  115. # Process command-line arguments.
  116. while test $# -gt 0; do
  117. if [ x"$1" == x"--" ]; then
  118. # detect argument termination
  119. shift
  120. break
  121. fi
  122. case $1 in
  123. --format )
  124. shift
  125. FORMAT="$1"
  126. shift
  127. ;;
  128. --prefix )
  129. shift
  130. PREFIX="$1"
  131. shift
  132. ;;
  133. --worktree-attributes )
  134. ARCHIVE_OPTS+=" $1"
  135. shift
  136. ;;
  137. --separate | -s )
  138. shift
  139. SEPARATE=1
  140. ;;
  141. --tree-ish | -t )
  142. shift
  143. TREEISH="$1"
  144. shift
  145. ;;
  146. --version )
  147. version
  148. exit
  149. ;;
  150. --verbose | -v )
  151. shift
  152. VERBOSE=1
  153. ;;
  154. -? | --usage | --help )
  155. usage
  156. exit
  157. ;;
  158. -* )
  159. echo "Unrecognized option: $1" >&2
  160. usage
  161. exit $E_BAD_OPTION
  162. ;;
  163. * )
  164. break
  165. ;;
  166. esac
  167. done
  168. OLD_PWD="`pwd`"
  169. TMPDIR=${TMPDIR:-/tmp}
  170. TMPFILE=`mktemp "$TMPDIR/$PROGRAM.XXXXXX"` # Create a place to store our work's progress
  171. TMPLIST=`mktemp "$TMPDIR/$PROGRAM.submodules.XXXXXX"`
  172. TOARCHIVE=`mktemp "$TMPDIR/$PROGRAM.toarchive.XXXXXX"`
  173. OUT_FILE=$OLD_PWD # assume "this directory" without a name change by default
  174. if [ ! -z "$1" ]; then
  175. OUT_FILE="$1"
  176. if [ "-" == "$OUT_FILE" ]; then
  177. SEPARATE=0
  178. fi
  179. shift
  180. fi
  181. # Validate parameters; error early, error often.
  182. if [ "-" == "$OUT_FILE" -o $SEPARATE -ne 1 ] && [ "$FORMAT" == "tar" -a `$TARCMD --help | grep -q -- "--concatenate"; echo $?` -ne 0 ]; then
  183. echo "Your 'tar' does not support the '--concatenate' option, which we need"
  184. echo "to produce a single tarfile. Either install a compatible tar (such as"
  185. echo "gnutar), or invoke $PROGRAM with the '--separate' option."
  186. exit
  187. elif [ $SEPARATE -eq 1 -a ! -d "$OUT_FILE" ]; then
  188. echo "When creating multiple archives, your destination must be a directory."
  189. echo "If it's not, you risk being surprised when your files are overwritten."
  190. exit
  191. elif [ `git config -l | grep -q '^core\.bare=true'; echo $?` -eq 0 ]; then
  192. echo "$PROGRAM must be run from a git working copy (i.e., not a bare repository)."
  193. exit
  194. fi
  195. # Create the superproject's git-archive
  196. if [ $VERBOSE -eq 1 ]; then
  197. echo -n "creating superproject archive..."
  198. fi
  199. git archive --format=$FORMAT --prefix="$PREFIX" $ARCHIVE_OPTS $TREEISH > $TMPDIR/$(basename "$(pwd)").$FORMAT
  200. if [ $VERBOSE -eq 1 ]; then
  201. echo "done"
  202. fi
  203. echo $TMPDIR/$(basename "$(pwd)").$FORMAT >| $TMPFILE # clobber on purpose
  204. superfile=`head -n 1 $TMPFILE`
  205. if [ $VERBOSE -eq 1 ]; then
  206. echo -n "looking for subprojects..."
  207. fi
  208. # find all '.git' dirs, these show us the remaining to-be-archived dirs
  209. # we only want directories that are below the current directory
  210. find . -mindepth 2 -name '.git' -type d -print | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE
  211. # as of version 1.7.8, git places the submodule .git directories under the superprojects .git dir
  212. # the submodules get a .git file that points to their .git dir. we need to find all of these too
  213. find . -mindepth 2 -name '.git' -type f -print | xargs grep -l "gitdir" | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE
  214. if [ $VERBOSE -eq 1 ]; then
  215. echo "done"
  216. echo " found:"
  217. cat $TOARCHIVE | while read arch
  218. do
  219. echo " $arch"
  220. done
  221. fi
  222. if [ $VERBOSE -eq 1 ]; then
  223. echo -n "archiving submodules..."
  224. fi
  225. git submodule >>"$TMPLIST"
  226. while read path; do
  227. TREEISH=$(grep "^ .*${path%/} " "$TMPLIST" | cut -d ' ' -f 2) # git submodule does not list trailing slashes in $path
  228. cd "$path"
  229. git archive --format=$FORMAT --prefix="${PREFIX}$path" $ARCHIVE_OPTS ${TREEISH:-HEAD} > "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT
  230. if [ $FORMAT == 'zip' ]; then
  231. # delete the empty directory entry; zipped submodules won't unzip if we don't do this
  232. zip -d "$(tail -n 1 $TMPFILE)" "${PREFIX}${path%/}" >/dev/null # remove trailing '/'
  233. fi
  234. echo "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT >> $TMPFILE
  235. cd "$OLD_PWD"
  236. done < $TOARCHIVE
  237. if [ $VERBOSE -eq 1 ]; then
  238. echo "done"
  239. fi
  240. if [ $VERBOSE -eq 1 ]; then
  241. echo -n "concatenating archives into single archive..."
  242. fi
  243. # Concatenate archives into a super-archive.
  244. if [ $SEPARATE -eq 0 -o "-" == "$OUT_FILE" ]; then
  245. if [ $FORMAT == 'tar.gz' ]; then
  246. gunzip $superfile
  247. superfile=${superfile:0: -3} # Remove '.gz'
  248. sed -e '1d' $TMPFILE | while read file; do
  249. gunzip $file
  250. file=${file:0: -3}
  251. $TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file"
  252. done
  253. gzip $superfile
  254. superfile=$superfile.gz
  255. elif [ $FORMAT == 'tar' ]; then
  256. sed -e '1d' $TMPFILE | while read file; do
  257. $TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file"
  258. done
  259. elif [ $FORMAT == 'zip' ]; then
  260. sed -e '1d' $TMPFILE | while read file; do
  261. # zip incorrectly stores the full path, so cd and then grow
  262. cd `dirname "$file"`
  263. zip -g "$superfile" `basename "$file"` && rm -f "$file"
  264. done
  265. cd "$OLD_PWD"
  266. fi
  267. echo "$superfile" >| $TMPFILE # clobber on purpose
  268. fi
  269. if [ $VERBOSE -eq 1 ]; then
  270. echo "done"
  271. fi
  272. if [ $VERBOSE -eq 1 ]; then
  273. echo -n "moving archive to $OUT_FILE..."
  274. fi
  275. while read file; do
  276. if [ "-" == "$OUT_FILE" ]; then
  277. cat "$file" && rm -f "$file"
  278. else
  279. mv "$file" "$OUT_FILE"
  280. fi
  281. done < $TMPFILE
  282. if [ $VERBOSE -eq 1 ]; then
  283. echo "done"
  284. fi