2 # Copyright © 2013,2014 Géraud Meyer <graud@gmx.com>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 2 as
6 # published by the Free Software Foundation.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # You should have received a copy of the GNU General Public License along
14 # with this program. If not, see <http://www.gnu.org/licenses/>.
16 PROGRAM_NAME
="archives2git"
17 PROGRAM_VERSION
="0.2+"
18 PROGRAM_USAGE
="$PROGRAM_NAME [<options>] [--] <archives_and_directories>"
22 $PROGRAM_NAME - generate a Git history from a series of release tarballs
26 $PROGRAM_NAME replaces the contents of a Git repository subdir with that of
27 an <archive> file (or of another <directory>) and commits the changes.
29 The options are parsed by git-rev-parse(1) with the option --parseopt.
32 The following shell variables are usable if appropriate in the shell code
33 snippets passed as command line options: \$arch, \$archdir, \$archname,
34 \$file, \$date, \$author, \$title, \$tagname.
36 The following shell functions are defined in the script and are usable the
37 the shell code snippets: get_version(filename).
45 This file is sourced by the script. An example file is distributed.
47 Some possible options:
48 $ archives2git \$(ls ../oldproject-*.tgz | sort -V) \\
49 # keep (only) the .gitignore file
50 --keep-filter 'test x.gitignore = x"\$file"'
51 --keep-filter 'echo "\$file" | grep -q -f \$HOME/filepats'
52 # suppress the version info from the root directory in the archive
53 --rename 'echo "\${file%-*}"'
54 # allow to add ignored files
56 # use the archive modification time as the author date (with GNU date)
57 --date 'LC_ALL=C date -r "\$arch"'
58 # do not include the path components in the commit title
59 --title 'echo "\${arch##*/}"'
60 # interactively edit each commit message
62 # keep only the files of the project in the root of the tree
63 --gitfilter $'--subdirectory-filter\noldproject'
64 # move the project's files out of their subdir (while keeping other files)
65 --gitfilter $'--tree-filter\nmv oldproject/.* .; mv oldproject/* .'
66 See also the example configuration file.
68 $PROGRAM_NAME was written by G.raud Meyer.
70 git-commit-tree(1), git_load_dirs(1), git-rev-parse(1)
76 repo= work tree subdir
77 rename= sh code to rename the current \$file of the root
78 keep-filter= sh code to decide whether to ignore the current \$file of the root
79 gitadd-arg= argument to pass to git-add
80 date= sh code printing the commit author date for the current \$arch
81 author= sh code printing the commit author name for the current \$arch
82 title= sh code printing the commit title (and an optional body start)
83 body= string appended to the title as a new paragraph
84 gitci-arg= argument to pass to git-commit
85 gitfilter= line separated arguments to pass to git-filter
86 tag tag the commits with the default tagname
87 tagname= tag the commits with the name printed by the given sh code
88 command= sh code to eval after having commited and tagged
89 debug enable the verbose printing of what is done
90 h,help print a usage message and exit
91 helpm print the manual and exit
92 man view the manual in the pager and exit
93 version print the version information and exit
98 printf %s
"$OPTS_SPEC" | LC_ALL
=C
awk '/^--$/ { parse=1; next }
100 if (sub(/^[a-zA-Z0-9][=*?!]{0,3}(,|$)/, "-&", spec)) sub(/^-.[=*?!]{0,3},/, "& --", spec)
101 else sub(/^/, "--&", spec)
104 parse { print " \t" opt($1); sub(/^[^ \t]*[ \t]*/,""); if ($0) print "\t\t" $0 }'
108 _base
=$
( printf "%s\n" "$_file" |
109 LC_ALL
=C
sed -r -e '$s;(([0-9]{1,}\.)|[-_])[0-9]{1,}([-_.]{0,1}([0-9]{1,}|[ab]|alpha|beta|gamma))*$;;' )
110 _version
=${_file#"$_base"}
111 _version
=${_version#"-"}; _version=${_version#"."}; _version=${_version#"_"}
112 printf "%s\n" "${_version}"
116 RENAME
='' # $file $arch $archname
117 FILTER
='false' # $file $arch $archname
118 DATE
='' # $arch $archname
119 TITLE
='echo "$arch"' # $arch $archname
120 BODY
="" # message taken as is
123 FILTERHEAD
= # line separated arguments
125 TAGDEF
='printf version/; get_version "$archname"'
127 # config (file, environment, command line)
128 [ -f "$HOME"/.archives2gitrc
] && .
"$HOME"/.archives2gitrc
129 GIT_WORK_TREE
=${GIT_WORK_TREE-.}
130 # may be a subdir of the top level
131 parsing
=$
(echo "$OPTS_SPEC" | git rev-parse
--parseopt -- "$@"); rc
=$?
134 then printf %s
"$parsing" |
grep '^EOF' >/dev
/null
&& exit 0 ||
exit $rc
140 shift; GIT_WORK_TREE
=$1 ;;
146 shift; ADDARGS
="$ADDARGS $1" ;;
156 shift; CIARGS
="$CIARGS $1" ;;
158 shift; FILTERHEAD
=$1 ;;
171 help_message | helpm2pod
--man - ARCHIVES2GIT
174 echo "$PROGRAM_NAME version $PROGRAM_VERSION"
177 echo >&2 "$PROGRAM_NAME: internal error: invalid option $1"
189 trap 'rmdir "$TMPDIR"' EXIT
190 TMPDIR
=$
(mktemp
-d) ||
192 echo >&2 "$PROGRAM_NAME: error: cannot create a temporary directory"
196 # git repository check
197 cd "$GIT_WORK_TREE" &&
198 { test -d .git || git rev-parse
--git-dir >/dev
/null
; } ||
200 echo >&2 "$PROGRAM_NAME: error: not in a git repository ($(pwd))"
203 test -z "$(git status --porcelain)" &&
204 git update-index
-q --refresh ||
206 echo >&2 "$PROGRAM_NAME: error: unstaged files or dirty index"
215 # put the files in the archive dir
219 cp -R "$arch" "$TMPDIR"
221 aunpack
-X "$TMPDIR" "$arch"
224 archname
=$
( printf "%s\n" "$arch" | LC_ALL
=C
sed -r \
225 -e '$s;^([^/]*/)*([^/]{1,})/{0,1}$;\2;' \
226 -e '$s;\.([Zz]|lzo|bz|gz|bz2|xz|lzma|7z|lz|rz|lrz)$;;' \
227 -e '$s;\.(tar|t[Zz]|tzo|tbz|tgz|tbz2|txz|t7z|tlz|lha|lzh|7z|alz|arj|zip|rar|jar|war|ace|cab|a|cpio|shar|rpm|deb)$;;' \
228 -e '$s;\.(orig)$;;' )
229 # remove (almost) everything from the repository dir
233 [ -e "$file" ] ||
[ -h "$file" ] ||
continue
234 [ x
"$file" = x
"." ] ||
[ x
"$file" = x
".." ] ||
[ x
"$file" = x
".git" ] && continue
235 eval "$FILTER" ||
{ git
rm -r "$file" ||
rm -R "$file"; }
237 # move the content of the temp dir to the repository and stage it
238 for file in "$archdir"/* "$archdir"/.
*
240 [ -e "$file" ] ||
[ -h "$file" ] ||
continue
241 file=${file#$archdir/}
242 [ x
"$file" = x
"." ] ||
[ x
"$file" = x
".." ] && continue
243 [ -n "$RENAME" ] && name
=$
(eval "$RENAME") || name
=$file
246 [ ! -e .
/"$name" ] ||
rm -R .
/"$name"
247 # remove conflicting file (previously kept or renamed to the same name)
248 mv "$archdir"/"$file" .
/"$name"
249 git add
$ADDARGS .
/"$name"
251 rm -R "$archdir"/"$file"
255 # commit, filter and clean up
257 author
=$
(eval "$AUTHOR")
258 title
=$
(eval "$TITLE")
259 git commit
${AUTHOR:+--author "$author"} -m "$title"${BODY:+"$NL$NL$BODY"} ${DATE:+--date "$date"} $CIARGS
260 if [ -n "$FILTERHEAD" ]
263 git filter-branch
$FILTERHEAD -- HEAD^..HEAD
264 rm -R "$(git rev-parse --git-dir)"/refs
/original
/
269 tagmessage
=$
(eval "$TAG" | LC_ALL
=C
tail -n +2)
270 tagname
=$
(eval "$TAG" | LC_ALL
=C
head -1)
271 git tag
${tagmessage:+-m "$tagmessage"} "$tagname"
277 unset title
date author tagname tagmessage