archives2git: HelpM: improve the description of the config file
[archives2git.git] / archives2git
blob09bd94136767b578d7e9c57ccb670a1ce04d2003
1 #!/bin/sh
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
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.1+"
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 the
37 the shell code snippets: get_version(filename).
38 ENVIRONMENT
39 GIT_AUTHOR_NAME
40 GIT_AUTHOR_EMAIL
41 GIT_COMMITTER_NAME
42 GIT_COMMITTER_EMAIL
43 FILES
44 ~/.archives2gitrc
45 This file is sourced by the script. An example file is distributed.
46 EXAMPLES
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
55 --gitadd-arg -f
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
61 --gitci-arg "--edit"
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.
67 AUTHOR
68 $PROGRAM_NAME was written by G.raud Meyer.
69 SEE ALSO
70 git-commit-tree(1), git_load_dirs(1), git-rev-parse(1)
71 HelpMessage
73 OPTS_SPEC="\
74 $PROGRAM_USAGE
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
96 # helper functions
97 helpm_options () {
98 printf %s "$OPTS_SPEC" | LC_ALL=C awk '/^--$/ { parse=1; next }
99 function opt(spec) {
100 if (sub(/^[a-zA-Z0-9][=*?!]{0,3}(,|$)/, "-&", spec)) sub(/^-.[=*?!]{0,3},/, "& --", spec)
101 else sub(/^/, "--&", spec)
102 return spec
104 parse { print " \t" opt($1); sub(/^[^ \t]*[ \t]*/,""); if ($0) print "\t\t" $0 }'
106 get_version () {
107 _file=$1; shift
108 _base=$( printf "%s\n" "$_file" |
109 LC_ALL=C sed -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}"
115 # default parameters
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
121 ADDARGS=
122 CIARGS=
123 FILTERHEAD= # line separated arguments
124 TAG=
125 TAGDEF='printf version/; get_version "$archname"'
126 COMMAND=
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=$?
132 eval "$parsing"
133 if [ $rc -ne 0 ]
134 then printf %s "$parsing" | grep '^EOF' >/dev/null && exit 0 || exit $rc
136 while [ $# -gt 0 ]
138 case $1 in
139 --repo)
140 shift; GIT_WORK_TREE=$1 ;;
141 --rename)
142 shift; RENAME=$1 ;;
143 --keep-filter)
144 shift; FILTER=$1 ;;
145 --gitadd-arg)
146 shift; ADDARGS="$ADDARGS $1" ;;
147 --date)
148 shift; DATE=$1 ;;
149 --author)
150 shift; AUTHOR=$1 ;;
151 --title)
152 shift; TITLE=$1 ;;
153 --body)
154 shift; BODY=$1 ;;
155 --gitci-arg)
156 shift; CIARGS="$CIARGS $1" ;;
157 --gitfilter)
158 shift; FILTERHEAD=$1 ;;
159 --tag)
160 TAG=$TAGDEF ;;
161 --tagname)
162 shift; TAG=$1 ;;
163 --command)
164 shift; COMMAND=$1 ;;
165 --debug)
166 set -x ;;
167 --helpm)
168 help_message
169 exit ;;
170 --man)
171 help_message | helpm2pod --man - ARCHIVES2GIT
172 exit ;;
173 --version)
174 echo "$PROGRAM_NAME version $PROGRAM_VERSION"
175 exit ;;
176 --?*)
177 echo >&2 "$PROGRAM_NAME: internal error: invalid option $1"
178 exit 255 ;;
180 shift; break ;;
182 break ;;
183 esac
184 shift
185 done
186 # setup
187 NL='
189 trap 'rmdir "$TMPDIR"' EXIT
190 TMPDIR=$(mktemp -d) ||
192 echo >&2 "$PROGRAM_NAME: error: cannot create a temporary directory"
193 exit 253
195 WRKDIR=$(pwd)
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))"
201 exit 255
203 test -z "$(git status --porcelain)" &&
204 git update-index -q --refresh ||
206 echo >&2 "$PROGRAM_NAME: error: unstaged files or dirty index"
207 exit 254
210 # main
211 set -e
212 OLDIFS=$IFS
213 for arch
215 # put the files in the archive dir
216 cd "$WRKDIR"
217 if [ -d "$arch" ]
218 then
219 cp -R "$arch" "$TMPDIR"
220 else
221 aunpack -X "$TMPDIR" "$arch"
223 archdir=$TMPDIR
224 archname=$( printf "%s\n" "$arch" | LC_ALL=C sed \
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
230 cd "$GIT_WORK_TREE"
231 for file in * .*
233 [ -e "$file" ] || continue
234 [ x"$file" = x"." ] || [ x"$file" = x".." ] || [ x"$file" = x".git" ] && continue
235 eval "$FILTER" || { git rm -r "$file" || rm -R "$file"; }
236 done
237 # move the content of the temp dir to the repository and stage it
238 for file in "$archdir"/* "$archdir"/.*
240 [ -e "$file" ] || continue
241 file=${file#$archdir/}
242 [ x"$file" = x"." ] || [ x"$file" = x".." ] && continue
243 [ -n "$RENAME" ] && name=$(eval "$RENAME") || name=$file
244 if [ -n "$name" ]
245 then
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"
250 else
251 rm -R "$archdir"/"$file"
253 done
254 unset file
255 # commit, filter and clean up
256 date=$(eval "$DATE")
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" ]
261 then
262 IFS=$NL
263 git filter-branch $FILTERHEAD -- HEAD^..HEAD
264 rm -R "$(git rev-parse --git-dir)"/refs/original/
265 IFS=$OLDIFS
267 if [ -n "$TAG" ]
268 then
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"
273 if [ -n "$COMMAND" ]
274 then
275 eval "$COMMAND"
277 unset title date author tagname tagmessage
278 done