Home Home > GIT Browse
summaryrefslogtreecommitdiff
blob: 60281faf99399fb7929699332eb6963435a3c82d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
#! /bin/bash

#############################################################################
# Copyright (c) 2004-2006,2008-2010 Novell, Inc.
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#############################################################################

# git commit wrapper, generates unified commit messages from patch headers

_libdir=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")

log_entry() {
    local entry=$1

    echo "$entry" | fmt --width 65 | sed -e '1s/^/- /' -e '2,$s/^/  /'
}

patch_meta() {
    local patch=$1

    subject=$(formail -c -x Subject < "$patch" \
             | sed -e 's, *\[[#/ A-Za-z0-9-]*\],,')
    subject=${subject##:}
    subject=${subject## }
    subject=${subject%.}

    # allow one blank line before the References: header
    set -- $(awk '
	/^References:/ { sub(/^References:/, ""); print; exit }
	/^$/ { if (++blank > 1) exit }' "$patch")
    references="$*"
    case "$references" in
    None | none)
        references=
    esac
}

patch_log_entry() {
    local patch=$1 subject references old_subj old_ref old_patch="$tmpdir/old"

    git show "HEAD:$patch" >"$old_patch" 2>/dev/null
    patch_meta "$old_patch"
    old_subj="$subject"
    old_ref="$references"

    patch_meta "$patch"

    if test -z "$subject"; then
        # should not happen
        log_entry "$patch: ${references:+($references).}"
    elif test "$subject" != "$old_subj"; then
	log_entry "$subject${references:+ ($references)}."
    elif test "$references" != "$old_ref"; then
        # a new bugzilla reference suggest a non-trivial change
        log_entry "Update $patch${references:+ ($references)}."
    else
        log_entry "Refresh $patch."
    fi
}

# Print log entries for given added/modified files
log_entries()
{
    local file configs_updated=false symvers_updated=false

    for file in "$@"; do
        case "$file" in
        config/*)
            if ! $configs_updated; then
                log_entry "Update config files."
                configs_updated=true
            fi
            ;;
        patches.*)
            patch_log_entry "$file"
            ;;
        kabi/*/symvers-* | kabi/*/symtypes-* | kabi/*/symsets-* )
            if ! $symvers_updated; then
                log_entry "Update kabi files."
                symvers_updated=true
            fi
            ;;
        series.conf)
            # don't log changes in there
            ;;
        *)
            log_entry "$file: "
            ;;
        esac
    done
}

do_commit()
{
    local message edit=--edit

    if test -z "${added[*]}${modified[*]}${deleted[*]}"; then
        echo "No modified files" >&2
        exit 1
    fi
    if test "$1" = "--no-edit"; then
        edit=
    fi
    message=$tmpdir/message
    echo >"$message"
    log_entries "${added[@]}" "${modified[@]}" >>"$message"
    for file in "${deleted[@]}"; do
        log_entry "Delete $file." >>"$message"
    done

    # If there is only one entry, remove the "-" bullet to make the git log
    # prettier. If there are multiple entries, we give up, because
    # scripts/gitlog2changes wouldn't know where to insert the bullets again
    if test $(grep -c '^- ' "$message") = 1; then
        sed -i 's/^[- ] //' "$message"
    fi

    if test -n "$edit" -a -e "$tmpdir/notice"; then
        cat "$_" >>"$message"
    fi
    git commit "$@" -F $message $edit
}

# Check if series.conf contains only patch additions and print the
# added patches in order of appearance.
check_series_diff()
{
    awk '
        /^\+\+\+ / {
            body = 1
            next
        }
        !body || # ignore the patch header
        /^[@ ]/ || # ignore line numbers and context
        /^[-+][[:blank:]]*(#.*)?$/ { # ignore whitespace and comments
            next
        }
        # added unguarded patches are OK
        /^\+[[:blank:]]*patches\.[^[:blank:]]*\// {
            print $2
            next
        }
        # anything else is a problem
        #{ print >"/dev/stderr" }
        { exit 1 }
    '
}

# Check whether we are only adding new patches and possibly updating
# configs. At least one patch must be added.
only_patches()
{
    local file have_patches=false

    for file in "${modified[@]}"; do
        case "$file" in
        series.conf | config/* | supported.conf | blacklist.conf | kabi/severities)
            ;;
        *)
            return 1
        esac
    done
    if test -n "${deleted[*]}"; then
        return 1
    fi
    for file in "${added[@]}"; do
        case "$file" in
        patches.*/*)
            have_patches=true
            ;;
        *)
            return 1
        esac
    done
    if ! $have_patches; then
        return 1
    fi
    git diff HEAD -- series.conf | check_series_diff >/dev/null || return
    return 0
}


trim() {
    local var="$*"

    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"   

    echo -n "$var"
}


# Insert part of the content added to a new series file back into the old
# series file
# splice_series <patch name> 3<old_series 4<new_series
splice_series()
{
    local patch=$1

    local old new old_eof new_eof
    local state="matching"

    IFS=
    while true; do
        if [ $state = "matching" ]; then
            read -r -u 3 old || old_eof=1
            read -r -u 4 new || new_eof=1
            if [ -z "$old_eof" -a "$new_eof" ]; then
                echo "Error: new series does not contain all" \
                    "lines from old series." > /dev/stderr
                return 1
            fi
            echo "$new"
            if [ "$old_eof" -a "$new_eof" ]; then
                break
            elif [ "$old_eof" -o "$new" != "$old" ]; then
                if [ "$(trim "$new")" = "$patch" ]; then
                    state="just after patch"
                else
                    state="diff"
                fi
            fi
        elif [ $state = "diff" ]; then
            read -r -u 4 new || new_eof=1
            if [ "$new_eof" ]; then
                echo "Error: new series does not contain all" \
                    "lines from old series." > /dev/stderr
                return 1
            fi
            echo "$new"
            if [ ! "$old_eof" -a "$new" = "$old" ]; then
                state="matching"
            elif [ "$(trim "$new")" = "$patch" ]; then
                state="just after patch"
            fi
        elif [ $state = "just after patch" ]; then
            read -r -u 4 new || new_eof=1
            if [ "$new_eof" ]; then
                break
            elif [ "$new" = "$old" ]; then
                echo "$old"
                state="after patch"
            elif [ -z "$(trim "$new")" ]; then
                echo "$new"
            fi
        elif [ $state = "after patch" ]; then
            read -r -u 3 old || old_eof=1
            #echo "@@ state $state old <$old> eof <$old_eof> new <$new> eof <$new_eof>"
            if [ "$old_eof" ]; then
                break
            fi
            echo "$old"
        fi
    done

    if [ $state = "matching" ]; then
        echo "Error: patch \"$patch\" not found in series." > /dev/stderr
        return 1
    fi
}

# Commit patches one by one for better bisectability
commit_single_patches()
{
    local saved_index=$(git write-tree) patch series
    local file added=() modified_aux=() deleted=() no_edit
    git cat-file blob HEAD:series.conf > "$tmpdir"/old_series
    cp series.conf "$tmpdir"/new_series

    for file in "${modified[@]}"; do
        case "$file" in
        config/* | supported.conf | blacklist.conf | kabi/severities)
            modified_aux=("${modified_aux[@]}" "$file")
        esac
    done
    local modified=()
    # reset the index
    git read-tree HEAD
    set -- $(git diff HEAD -- series.conf | check_series_diff)
    if test $# -gt 1; then
        # Fix the author date so that scripts/gitlog2changes can group
        # the commits into a single rpm changelog entry
        export GIT_AUTHOR_DATE=$(date -R)
        log_entries "$@" "${modified_aux[@]}"
        read -p 'Commit with these changelog messages? [Yn] '
        case "$REPLY" in
        "" | [Yy] | [Yy][Ee][Ss])
            no_edit=--no-edit
        esac
    fi
    # Commit patches except the last one
    while test $# -gt 1; do
        patch=$1
        shift
        # add a series.conf with a single new patch to the index
        series=$(splice_series "$patch" \
            3<"$tmpdir"/old_series 4<"$tmpdir"/new_series | \
            git hash-object -w --stdin)
        git read-tree $(git ls-tree HEAD | \
            sed -r "s/(.*)\\<[0-9a-f]{40}\\>(.*\\<series\.conf)$/\1$series\2/" \
            | git mktree)
        git add "$patch"
        added=("$patch")
        cat >"$tmpdir/notice" <<EOF

# Patches are being committed one by one for better bisectability.
# There are $# more patches to commit.
EOF
        if ! do_commit $no_edit; then
            # restore the index so that the user does not need to git add
            # the patches again
            git read-tree "$saved_index"
            return 1
        fi
    done
    rm -f "$tmpdir/notice"
    # commit the last patch and possible config update together
    patch=$1
    git add "$patch"
    added=("$patch")
    modified=("${modified_aux[@]}")
    if test -n "${modified[*]}"; then
        no_edit=
    fi
    if ! do_commit $no_edit -a; then
        git read-tree "$saved_index"
        return 1
    fi
}

# do not run "main" code if script is being sourced rather than executed
[[ $0 != "$BASH_SOURCE" ]] && return

. "$_libdir"/wd-functions.sh

if ! $using_git; then
    echo "ERROR: not in a git working directory."
    exit 1
fi

"$_libdir"/check-cvs-add || exit 1

trap 'rm -rf "$tmpdir"' EXIT
tmpdir=$(mktemp -d /tmp/${0##*/}.XXXXXX)

if test -e "$(git rev-parse --git-dir)/MERGE_HEAD"; then
    # Do not try to fabricate a commit message for merge commits, git itself
    # does it better
    git commit -a
    exit
fi

added=($(git diff --name-only --diff-filter=A HEAD))
modified=($(git diff --name-only --diff-filter=MT HEAD))
deleted=($(git diff --name-only --diff-filter=D HEAD))

if only_patches; then
    commit_single_patches || exit
else
    # FIXME: -a should not be the default
    do_commit -a || exit
fi

branch=$(get_branch_name)
case "$branch" in
master | stable | vanilla | linux-next | openSUSE-??.? | \
SLE?? | SLE*-ARM| SLE??-SP? | SLE*-RT | SLE*-TD | SLE*-LTSS | \
cve/linux-* | packaging | scripts)
    remote=$(get_git_remote "$branch")
    user=$(get_git_user "$remote")
    echo "after testing your changes, run"
    echo "    git push $remote HEAD:users/$user/$branch/for-next"
esac

# vim: et:sts=4:sw=4