2 # Copyright © 2013,2014,2017 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 in
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 unpack= command to extract the files of the \$arch in \$TMPDIR
77 strip-ext= sh code to strip the filename extension from \$file
78 repo= work tree subdir (current dir by default)
79 rename= sh code to rename the current \$file of the root
80 keep-filter= sh code to decide whether not to remove the \$file of the root
81 gitadd-arg= argument to pass to git-add
82 date= sh code printing the commit author date for the current \$arch
83 author= sh code printing the commit author name for the current \$arch
84 title= sh code printing the commit title (and an optional body start)
85 body= string appended to the title as a new paragraph
86 gitci-arg= argument to pass to git-commit
87 gitfilter= line separated arguments to pass to git-filter
88 tag tag the commits with the default tagname
89 tagname= tag the commits with the name printed by the given sh code
90 command= sh code to eval after having commited and tagged
91 debug enable the verbose printing of what is done
92 h,help print a usage message and exit
93 helpm print the manual and exit
94 man view the manual in the pager and exit
95 version print the version information and exit
100 printf %s
"$OPTS_SPEC" | LC_ALL
=C
awk '/^--$/ { parse=1; next }
102 if (sub(/^[a-zA-Z0-9][=*?!]{0,3}(,|$)/, "-&", spec)) sub(/^-.[=*?!]{0,3},/, "& --", spec)
103 else sub(/^/, "--&", spec)
106 parse { print " \t" opt($1); sub(/^[^ \t]*[ \t]*/,""); if ($0) print "\t\t" $0 }'
110 _base
=$
( printf "%s\n" "$_file" |
111 LC_ALL
=C
sed -r -e '$s;(([0-9]{1,}\.)|[-_])[0-9]{1,}([-_.]{0,1}([0-9]{1,}|[ab]|alpha|beta|gamma))*$;;' )
112 _version
=${_file#"$_base"}
113 _version
=${_version#"-"}; _version=${_version#"."}; _version=${_version#"_"}
114 printf "%s\n" "${_version}"
117 printf "%s\n" "$1" | LC_ALL
=C
sed -r \
118 -e '$s;^([^/]*/)*([^/]{1,})/{0,1}$;\2;' \
119 -e '$s;\.([Zz]|lzo|bz|gz|bz2|xz|lzma|7z|lz|rz|lrz)$;;' \
120 -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)$;;' \
126 STRIP_EXT
='strip_extension "$arch"'
127 RENAME
='' # $file $arch $archname
128 FILTER
='false' # $file $arch $archname
129 DATE
='' # $arch $archname
130 TITLE
='echo "$arch"' # $arch $archname
131 BODY
="" # message taken as is
134 FILTERHEAD
= # line separated arguments
136 TAGDEF
='printf version/; get_version "$archname"'
138 # config (file, environment, command line)
139 [ -f "$HOME"/.archives2gitrc
] && .
"$HOME"/.archives2gitrc
140 GIT_WORK_TREE
=${GIT_WORK_TREE-.}
141 # may be a subdir of the top level
142 parsing
=$
(echo "$OPTS_SPEC" | git rev-parse
--parseopt -- "$@"); rc
=$?
145 then printf %s
"$parsing" |
grep '^EOF' >/dev
/null
&& exit 0 ||
exit $rc
153 shift; STRIP_EXT
=$1 ;;
155 shift; GIT_WORK_TREE
=$1 ;;
161 shift; ADDARGS
="$ADDARGS $1" ;;
171 shift; CIARGS
="$CIARGS $1" ;;
173 shift; FILTERHEAD
=$1 ;;
186 help_message | helpm2pod
--man - ARCHIVES2GIT
189 echo "$PROGRAM_NAME version $PROGRAM_VERSION"
192 echo >&2 "$PROGRAM_NAME: internal error: invalid option $1"
204 trap 'rmdir "$TMPDIR"' EXIT
205 TMPDIR
=$
(mktemp
-d) ||
207 echo >&2 "$PROGRAM_NAME: error: cannot create a temporary directory"
211 # git repository check
212 cd "$GIT_WORK_TREE" &&
213 { test -d .git || git rev-parse
--git-dir >/dev
/null
; } ||
215 echo >&2 "$PROGRAM_NAME: error: not in a git repository ($(pwd))"
218 test -z "$(git status --porcelain)" &&
219 git update-index
-q --refresh ||
221 echo >&2 "$PROGRAM_NAME: error: unstaged files or dirty index"
230 # put the files in the archive dir
234 cp -R "$arch" "$TMPDIR"
235 elif [ -n "$UNPACK" ]
239 aunpack
-X "$TMPDIR" "$arch"
242 archname
=$
(eval "$STRIP_EXT")
243 # remove (almost) everything from the repository dir
247 [ -e "$file" ] ||
[ -h "$file" ] ||
continue
248 [ x
"$file" = x
"." ] ||
[ x
"$file" = x
".." ] ||
[ x
"$file" = x
".git" ] && continue
249 eval "$FILTER" ||
{ git
rm -r "$file" ||
rm -R "$file"; }
251 # move the content of the temp dir to the repository and stage it
252 for file in "$archdir"/* "$archdir"/.
*
254 [ -e "$file" ] ||
[ -h "$file" ] ||
continue
255 file=${file#$archdir/}
256 [ x
"$file" = x
"." ] ||
[ x
"$file" = x
".." ] && continue
257 [ -n "$RENAME" ] && name
=$
(eval "$RENAME") || name
=$file
260 [ ! -e .
/"$name" ] ||
rm -R .
/"$name"
261 # remove conflicting file (previously kept or renamed to the same name)
262 mv "$archdir"/"$file" .
/"$name"
263 git add
$ADDARGS .
/"$name"
265 rm -R "$archdir"/"$file"
269 # commit, filter and clean up
271 author
=$
(eval "$AUTHOR")
272 title
=$
(eval "$TITLE")
273 git commit
${AUTHOR:+--author "$author"} -m "$title"${BODY:+"$NL$NL$BODY"} ${DATE:+--date "$date"} $CIARGS
274 if [ -n "$FILTERHEAD" ]
277 git filter-branch
$FILTERHEAD -- HEAD^..HEAD
278 rm -R "$(git rev-parse --git-dir)"/refs
/original
/
283 tagmessage
=$
(eval "$TAG" | LC_ALL
=C
tail -n +2)
284 tagname
=$
(eval "$TAG" | LC_ALL
=C
head -1)
285 git tag
${tagmessage:+-m "$tagmessage"} "$tagname"
291 unset title
date author tagname tagmessage