archives2git: HelpM: set $ARCHIVES2GIT_ENV in the example
[archives2git.git] / archives2git
blobdea63ca50e0c7f20e16924ed7d3525b0cd128a1b
1 #!/bin/sh
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
11 # for more details.
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>"
19 help_message () {
20 cat <<HelpMessage
21 NAME
22 $PROGRAM_NAME - generate a Git history from a series of release tarballs
23 USAGE
24 $PROGRAM_USAGE
25 DESCRIPTION
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.
28 OPTIONS
29 The options are parsed by git-rev-parse(1) with the option --parseopt.
30 $( helpm_options )
31 SHELL VARIABLES
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.
35 SHELL FUNCTIONS
36 The following shell functions are defined in the script and are usable in
37 the shell code snippets: get_version(filename).
38 ENVIRONMENT
39 ARCHIVES2GIT_ENV
40 After parameter and command substitutions and arithmetic expansion, it
41 gives the path of the config file to source. If unset the default
42 configuration file is sourced instead.
43 GIT_AUTHOR_NAME
44 GIT_AUTHOR_EMAIL
45 GIT_COMMITTER_NAME
46 GIT_COMMITTER_EMAIL
47 FILES
48 ~/.archives2gitrc
49 This file is sourced by the script. An example file is distributed.
50 EXAMPLES
51 Some possible options:
52 $ ARCHIVES2GIT_ENV=/dev/null \\
53 archives2git \$(ls ../oldproject-*.tgz | sort -V) \\
54 # keep (only) the .gitignore file
55 --keep-filter 'test x.gitignore = x"\$file"'
56 --keep-filter 'echo "\$file" | grep -q -f \$HOME/filepats'
57 # suppress the version info from the root directory in the archive
58 --rename 'echo "\${file%-*}"'
59 # allow to add ignored files
60 --gitadd-arg -f
61 # use the archive modification time as the author date (with GNU date)
62 --date 'LC_ALL=C date -r "\$arch"'
63 # do not include the path components in the commit title
64 --title 'echo "\${arch##*/}"'
65 # interactively edit each commit message
66 --gitci-arg "--edit"
67 # keep only the files of the project in the root of the tree
68 --gitfilter $'--subdirectory-filter\noldproject'
69 # move the project's files out of their subdir (while keeping other files)
70 --gitfilter $'--tree-filter\nmv oldproject/.* .; mv oldproject/* .'
71 See also the example configuration file.
72 NOTES
73 The code snippets passed to eval are not guaranteed to run in a subshell,
74 so make sure they do not change the shell environment.
75 BUGS
76 Trailing whitespaces in filenames are not supported.
77 AUTHOR
78 $PROGRAM_NAME was written by G.raud Meyer.
79 SEE ALSO
80 git-commit-tree(1), git_load_dirs(1), git-rev-parse(1)
81 HelpMessage
83 OPTS_SPEC="\
84 $PROGRAM_USAGE
86 unpack= command to extract the files of the \$arch in \$TMPDIR
87 strip-ext= sh code to strip the filename extension from \$file
88 repo= work tree subdir (current dir by default)
89 rename= sh code to rename the current \$file of the root
90 keep-filter= sh code to decide whether not to remove the \$file of the root
91 gitadd-arg= argument to pass to git-add
92 date= sh code printing the commit author date for the current \$arch
93 author= sh code printing the commit author name for the current \$arch
94 title= sh code printing the commit title (and an optional body start)
95 body= string appended to the title as a new paragraph
96 gitci-arg= argument to pass to git-commit
97 gitfilter= line separated arguments to pass to git-filter
98 tag tag the commits with the default tagname
99 tagname= tag the commits with the name printed by the given sh code
100 command= sh code to eval after having commited and tagged
101 debug enable the verbose printing of what is done
102 h,help print a usage message and exit
103 helpm print the manual and exit
104 man view the manual in the pager and exit
105 version print the version information and exit
108 # helper functions
109 helpm_options () {
110 printf %s "$OPTS_SPEC" | LC_ALL=C awk '/^--$/ { parse=1; next }
111 function opt(spec) {
112 if (sub(/^[a-zA-Z0-9][=*?!]{0,3}(,|$)/, "-&", spec)) sub(/^-.[=*?!]{0,3},/, "& --", spec)
113 else sub(/^/, "--&", spec)
114 return spec
116 parse { print " \t" opt($1); sub(/^[^ \t]*[ \t]*/,""); if ($0) print "\t\t" $0 }'
118 get_version () {
119 _file=$1; shift
120 _base=$( printf "%s\n" "$_file" |
121 LC_ALL=C sed -r -e '$s;(([0-9]{1,}\.)|[-_])[0-9]{1,}([-_.]{0,1}([0-9]{1,}|[ab]|alpha|beta|gamma))*$;;' )
122 _version=${_file#"$_base"}
123 _version=${_version#"-"}; _version=${_version#"."}; _version=${_version#"_"}
124 printf "%s\n" "${_version}"
126 strip_extension () {
127 printf "%s\n" "$1" | LC_ALL=C sed -r \
128 -e '$s;^([^/]*/)*([^/]{1,})/{0,1}$;\2;' \
129 -e '$s;\.([Zz]|lzo|bz|gz|bz2|xz|lzma|7z|lz|rz|lrz)$;;' \
130 -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)$;;' \
131 -e '$s;\.(orig)$;;'
134 # default parameters
135 UNPACK=
136 STRIP_EXT='strip_extension "$arch"'
137 RENAME='' # $file $arch $archname
138 FILTER='false' # $file $arch $archname
139 DATE='' # $arch $archname
140 TITLE='echo "$arch"' # $arch $archname
141 BODY="" # message taken as is
142 ADDARGS=
143 CIARGS=
144 FILTERHEAD= # line separated arguments
145 TAG=
146 TAGDEF='printf version/; get_version "$archname"'
147 COMMAND=
148 # config (file, environment, command line)
149 if [ -n "${ARCHIVES2GIT_ENV+set}" ]
150 then
151 dquotedexpr=$( printf "%s" "$ARCHIVES2GIT_ENV" | LC_ALL=C sed 's/"/\\"/g' )
152 # double quoting still allows backslash escapes (as well as substitutions
153 # and expansions)
154 configfile=
155 (eval : '${configfile:="'"$dquotedexpr"'"}') &&
156 eval : '${configfile:="'"$dquotedexpr"'"}'
157 # use := instead of = as the substitution for more portability (which is
158 # not that sensible since the + substitution is not implemented either
159 # when = is not)
160 else
161 configfile=$HOME/.archives2gitrc
163 [ -r "$configfile" ] && . "$configfile"
164 GIT_WORK_TREE=${GIT_WORK_TREE-.}
165 # may be a subdir of the top level
166 parsing=$(echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@"); rc=$?
167 eval "$parsing"
168 if [ $rc -ne 0 ]
169 then printf %s "$parsing" | grep '^EOF' >/dev/null && exit 0 || exit $rc
171 while [ $# -gt 0 ]
173 case $1 in
174 --unpack)
175 shift; UNPACK=$1 ;;
176 --strip-ext)
177 shift; STRIP_EXT=$1 ;;
178 --repo)
179 shift; GIT_WORK_TREE=$1 ;;
180 --rename)
181 shift; RENAME=$1 ;;
182 --keep-filter)
183 shift; FILTER=$1 ;;
184 --gitadd-arg)
185 shift; ADDARGS="$ADDARGS $1" ;;
186 --date)
187 shift; DATE=$1 ;;
188 --author)
189 shift; AUTHOR=$1 ;;
190 --title)
191 shift; TITLE=$1 ;;
192 --body)
193 shift; BODY=$1 ;;
194 --gitci-arg)
195 shift; CIARGS="$CIARGS $1" ;;
196 --gitfilter)
197 shift; FILTERHEAD=$1 ;;
198 --tag)
199 TAG=$TAGDEF ;;
200 --tagname)
201 shift; TAG=$1 ;;
202 --command)
203 shift; COMMAND=$1 ;;
204 --debug)
205 set -x ;;
206 --helpm)
207 help_message
208 exit ;;
209 --man)
210 help_message | helpm2pod --man - ARCHIVES2GIT
211 exit ;;
212 --version)
213 echo "$PROGRAM_NAME version $PROGRAM_VERSION"
214 exit ;;
215 --?*)
216 echo >&2 "$PROGRAM_NAME: internal error: invalid option $1"
217 exit 255 ;;
219 shift; break ;;
221 break ;;
222 esac
223 shift
224 done
225 # setup
226 NL='
228 trap 'rm -rf "$TMPDIR"' EXIT INT QUIT TERM
229 TMPDIR=$(mktemp -d) ||
231 echo >&2 "$PROGRAM_NAME: error: cannot create a temporary directory"
232 exit 253
234 WRKDIR=$(pwd)
235 # git repository check
236 cd "$GIT_WORK_TREE" &&
237 { test -d .git || git rev-parse --git-dir >/dev/null; } ||
239 echo >&2 "$PROGRAM_NAME: error: not in a git repository ($(pwd))"
240 exit 255
242 test -z "$(git status --porcelain)" &&
243 git update-index -q --refresh ||
245 echo >&2 "$PROGRAM_NAME: error: unstaged files or dirty index"
246 exit 254
249 # main
250 set -e
251 OLDIFS=$IFS
252 for arch
254 # put the files in the archive dir
255 cd "$WRKDIR"
256 if [ -d "$arch" ]
257 then
258 cp -R "$arch" "$TMPDIR"
259 elif [ -n "$UNPACK" ]
260 then
261 eval "$UNPACK"
262 else
263 aunpack -X "$TMPDIR" "$arch"
265 archdir=$TMPDIR
266 archname=$(eval "$STRIP_EXT")
267 # remove (almost) everything from the repository dir
268 cd "$GIT_WORK_TREE"
269 for file in * .*
271 [ -e "$file" ] || [ -h "$file" ] || continue
272 [ x"$file" = x"." ] || [ x"$file" = x".." ] || [ x"$file" = x".git" ] && continue
273 eval "$FILTER" || { git rm -r "$file" || rm -R "$file"; }
274 done
275 # move the content of the temp dir to the repository and stage it
276 for file in "$archdir"/* "$archdir"/.*
278 [ -e "$file" ] || [ -h "$file" ] || continue
279 file=${file#$archdir/}
280 [ x"$file" = x"." ] || [ x"$file" = x".." ] && continue
281 [ -n "$RENAME" ] && name=$(eval "$RENAME") || name=$file
282 if [ -n "$name" ]
283 then
284 if [ -e ./"$name" ]
285 then
286 # remove conflicting file (previously kept or renamed to the same name)
287 rm -R ./"$name"
288 echo >&2 "WARNING: conflicting file removed: \`$name'"
290 mv "$archdir"/"$file" ./"$name"
291 git add $ADDARGS ./"$name"
292 else
293 rm -R "$archdir"/"$file"
295 unset name
296 done
297 unset file
298 # commit, filter and clean up
299 date=$(eval "$DATE")
300 author=$(eval "$AUTHOR")
301 title=$(eval "$TITLE")
302 git commit ${AUTHOR:+--author "$author"} -m "$title"${BODY:+"$NL$NL$BODY"} ${DATE:+--date "$date"} $CIARGS
303 if [ -n "$FILTERHEAD" ]
304 then
305 IFS=$NL
306 git filter-branch $FILTERHEAD -- HEAD^..HEAD
307 rm -R "$(git rev-parse --git-dir)"/refs/original/
308 IFS=$OLDIFS
310 if [ -n "$TAG" ]
311 then
312 tagmessage=$(eval "$TAG" | LC_ALL=C tail -n +2)
313 tagname=$(eval "$TAG" | LC_ALL=C head -1)
314 git tag ${tagmessage:+-m "$tagmessage"} "$tagname"
316 if [ -n "$COMMAND" ]
317 then
318 eval "$COMMAND"
320 unset title date author tagname tagmessage
321 done