#!/bin/bash
#
# sfdk-changelog creates changelog from Git commit messages and tag annotations
#
# Copyright (C) 2016 Jolla Ltd.
# Contact: Martin Kampas <martin.kampas@jolla.com>
# All rights reserved.
#
# You may use this file under the terms of BSD license as follows:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the Jolla Ltd nor the
#     names of its contributors may be used to endorse or promote products
#     derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

# not only to get equal results everywhere, but also as a significant
# performance improvement (about 30% shorter execution time)
OLD_LC_ALL=$LC_ALL
export LC_ALL=C

set -o nounset
set -o errexit
set -o pipefail
#set -v

: ${GIT_CHANGE_LOG_RESTRICTED:=}
: ${GIT_CHANGE_LOG_NO_COMPAT_WARNINGS:=}

NL=$'\n'
US=$'\x1F'
RS=$'\x1E'
SELF=$(basename $0)
SHA1_WIDTH=40
REQUIRED_SCRIPT_SHEBANG="#!$(readlink -f "$0") --script"$'\n'

# Backward compatibility
SELF_REAL=$(basename "$(readlink -f "$0")")
SELF_OLD=git-change-log
REQUIRED_SCRIPT_SHEBANG_OLD=$'#!/usr/bin/git-change-log --script\n'

SEPARATOR=';'
cut() { command cut -d $SEPARATOR "$@"; }
join() { command join -t $SEPARATOR "$@"; }
nl() { command nl -s $SEPARATOR "$@"; }
sort() { command sort -t $SEPARATOR "$@"; }
read() { IFS=$SEPARATOR builtin read "$@"; }
write() { (IFS=$SEPARATOR; echo "$*"); }

synopsis()
{
    cat <<EOF
usage: $SELF [-a|--auto-add-annotations] [-A|--only-add-annotations]
       [-T|--only-add-tagged] [-D|--descendants-of <rev>]
       [-d|--dense <regexp>] [--[no-]favor-tagger] [--no-squeeze-spaces]
       [--rev-list-args <args>] [-e|--sed-entries <script>]
       [-s|--since[-date|-rev] {<date>|<rev>|<file>}]
       [--sort-entries] [--strip-tag-prefix <prefix>] [-t|--tags <pattern>]
       [-F|--append-from <file>[,<opts>]] [--append-cmd <cmd>]
       [--tags-with-path] [-<number>] [--] [REVISION]
   or: $SELF {-u|--untagged-only} [--] [REVISION]
   or: $SELF --script <file>
EOF
}

brief_usage()
{
    cat <<EOF
$(synopsis)

Pass '--help' for full help.
EOF
}

usage()
{
    cat <<EOF
$SELF creates changelog from Git commit messages and tag annotations.

$(synopsis)

Lists changes in reverse chronological order, starting at REVISION which
defaults to current HEAD. REVISION can be any revision as understood by
git-rev-parse(1).

Unless '--auto-add-annotations', '--only-add-annotations' or '--sed-entries' is
used, just the lines matching the following pattern will be used as changelog
entries:

    [BLANKS] '[' SHORT-DESCRIPTION ']' BLANKS LONG-DESCRIPTION

The '--script' option provides restricted execution environment, enabling
untrusted sources to customize their changelog generation without the need for a
full-fledged process isolation.  It accepts a shell-like script that consists
just from plain command invocations of '$SELF', 'git-show' or 'echo'.
Any other command will be rejected.  The '--append-cmd' option is not available
inside the script.  Custom 'sed' script passed with '--sed-entries' or
'--append-from' will be executed with 'sed --sandbox'.  The script MUST start
with the magic string '${REQUIRED_SCRIPT_SHEBANG%$NL}\\n'.

Options:
    -h | --help                  : show this help
    -a | --auto-add-annotations  : use the subjects (first lines) of tag
                                   annotations when no entry would be included
                                   for a revision otherwise
    -A | --only-add-annotations  : always use the subjects (first lines) of tag
                                   annotations as changelog entries, completely
                                   ignore commit messages
    -T | --only-add-tagged       : only consider tagged commits when searching
                                   for changelog entries. This honours the
                                   '--tags' option and therefore can be used to
                                   exclude upstream commits where
                                   '--rev-list-args' cannot be used.
    -D | --descendants-of <rev>  : only consider commits that exist directly on
                                   the ancestry path from <rev>, including <rev>
                                   itself.  This can be used to exclude upstream
                                   commits where '--rev-list-args' cannot be
                                   used.  This is most likely to be used
                                   together with '--tags'.
    -d | --dense <regexp>        : print headlines just for revisions denoted by
                                   tags matching the given <regexp>. This is
                                   matched agains the full refname, including
                                   the 'refs/...' prefix.  Can be used e.g. to
                                   omit headlines for patch-level revisions.
                                   Implies '--favor-tagger'. See also '--tags'
    --[no-]favor-tagger          : use tag-author instead of commit-author
                                   name/date/email in case of revisions denoted
                                   by annotated tags
    --no-squeeze-spaces          : do not replace sequences of blank characters
                                   with single space character
    --rev-list-args <args>       : additional arguments to 'git-rev-list'
                                   invoked to list commits between tags denoting
                                   consecutive revisions.  Passing multiple
                                   times appends <args>.  With empty <args> it
                                   clears everything including the implicit
                                   argument '--topo-order'.  Can be used to
                                   exclude upstream commits
    -e | --sed-entries <script>  : use custom 'sed' <script> to extract entries
                                   from commit messages and tag annotations.
                                   The script will be executed with auto-print
                                   disabled ('sed -n')
    --script <file>              : execute the given script <file>. See above
                                   for more details about this option
    -s | --since[-date] <date>   : only print revisions after the given <date>,
       | --since[-rev] <rev>       <rev> or the latest revision found in the
       | --since <file>            given changelog <file>.  <rev> is compared
                                   literally, i.e., it does not neccessarily
                                   match the (prefixed) tag name!
    --sort-entries               : sort entries alphabetically according to
                                   current locale - use thoughtfully! Normally
                                   entries appear in order of their appearance
                                   in output of 'git-rev-list'. See also
                                   '--rev-list-args'
    --strip-tag-prefix <prefix>  : when turning a tag name into revision name,
                                   remove <prefix> from tag base name.  Passing
                                   empty <prefix> clears the implicit 'v' prefix
    -t | --tags <pattern>        : override the default 'refs/tags/*' pattern
                                   used to enumerate tags. Compared to '--dense'
                                   this (1) discards annotations from unmatched
                                   tags, (2) does not imply '--favor-tagger' and
                                   (3) accepts patterns as understood by
                                   git-for-each-ref(1)
    -F | --append-from <file>[,<opts>]
                                 : append new lines added to <file> for each
                                   revision.  Only the topmost batch of added
                                   lines will be considered, ignoring changes
                                   to recently added lines.  A comma separated
                                   list of following <opts> may follow:
                                     noindent : Do not indent lines as
                                                continuation lines
                                     notail   : Omit the very last revision
                                     notrim   : Do not trim newlines
                                     stick    : Do not separate with blank line
                                     sed:<script>
                                              : Process lines with the supplied
                                                'sed' <script>. This option must
                                                appear as the very last one
    --append-cmd <cmd>           : append lines writtend to stdout by <cmd> for
                                   each revision. <cmd> will be executed with a
                                   single argument in form of [<rev-1>..]<rev>.
                                   (For the oldest revision only <rev> is
                                   passed.)  <cmd> can be an arbitrary complex
                                   shell command. See "Using '--append-cmd'
                                   Option" below for details.  This option is
                                   not available when invoked from a script
                                   executed with the '--script' option.
    --tags-with-path             : when turning a tag name into revision name,
                                   preserve the path prefix like 'git rev-parse
                                   --symbolic --abbrev-ref' would do for the tag
    -u | --untagged-only         : print just the most recent entries not
                                   reachable from any tag denoting a revision
                                   for which a headline would be printed
    -<number>                    : limit output to <number> revisions

Examples:
    Consider a Git history where, at some point, a decision was taken to

        1) start using annotated tags for changelog generation and
        2) no more require special markup for lines to be included in changelog

    In the following scenario, commit author is always John Author, committer is
    always Alice Committer (never figures in changelog) and tagging is always
    done by Jack Tagger.  They always perform their job on the first, second and
    third day of a month respectively. The #YYYY-MM task number designates the
    year and month of the commit/tag creation.

$(sed -n '/^# example_1_begin/,/^# example_1_end/p' $0 \
    |sed -e '/# example/d' -e 's/^/    \$ /')

    Here is how the corresponding changelog with patch-level changes merged
    together would look like:

$(sed -n '/^# example_1_out_begin/,/^# example_1_out_end/p' $0 \
    |sed -e '/# example/d' \
         -e "s/^verify/$ $SELF/" \
         -e 's/ <<EOF$//' -e '/^EOF$/d' \
         -e 's/^/    /')

    More examples can be found at the bottom of this shell script, in form of
    test cases. List them with \`${EDITOR:-vi} $(which $SELF 2>/dev/null \
        || echo $0)\`.

Using '--append-cmd' Option:
    The <cmd> argument to this option can be an arbitrary complex shell script.
    <cmd> will be executed with a single argument in form of [<rev-1>..]<rev>.
    (For the oldest revision only <rev> is passed.)

    The following commands are available to this script as an extension to the
    host execution environment:

    changelog__head_added_lines [<rev-1>..]<rev> <file>
        Output lines prepended to the <file> between revisions <rev-1> and <rev>
        or the whole file when single <rev> is passed.

    changelog__trim_newlines
        Filter stdin removing any extra starting or trailing newlines.

    changelog__indent
        Add indentation suitable for changelog continuation lines.

    changelog__nostick
        Prepend blank line to a non-empty input.

    In the following example the use of '--append-cmd' option is equivalent to
    passing '--append-from Changelog':

$(sed -n '/^# example_2_begin/,/^# example_2_end/p' $0 \
    |sed -e '/# example/d' \
         -e "s/^my_append/$ &/" \
         -e '/^{/,/^}/s/^/> /' \
         -e "s/^verify/$ $SELF/" \
         -e 's/ master <<EOF$//' \
    |sed -e '/^\*/,$d' \
         -e 's/^/    /')

Environment:
    All locale(7) related environment variables are ignored and LC_ALL=C is used
    instead. The only exception is the use of '--sort-entries' switch.

Bugs:
    Either system 'sort' utility with support for '--version-sort' option, or
    a Python interpreter with 'distutils.version' module available is required.

    Attach the output of executing with '--self-test' as the only option when
    reporting bugs.

EOF
}

warn()
{
    echo "$SELF: WARNING: $*" >&2
}

warn_c()
{
    if [[ ! $GIT_CHANGE_LOG_NO_COMPAT_WARNINGS ]]; then
        warn "$@"
    fi
}

bad_usage()
{
    echo "$SELF: $*" >&2
    echo >&2
    brief_usage >&2
    exit 1
}

fatal()
{
    echo "$SELF: FATAL: $*" >&2
}

configure()
{
    feature()
    {
        local feature=$1
        local result=${2:-}

        eval $feature=$result

        if [[ $OPT_SELF_TEST ]]; then
            local ny=(no yes)
            echo "*** Configured $feature=${ny[${result:-0}]}" >&2
        fi
    }

    # compatibility with old 'sort'
    feature HAS_VERSION_SORT_OPT \
        $(! sort -V </dev/null &>/dev/null || echo 1)
    # TODO If Git is upgraded before coreutils, it is possible to let
    # 'git-for-each-ref' do version sort on refname instead of post-sorting with
    # 'sort'
    if [[ $HAS_VERSION_SORT_OPT ]]; then
        VERSION_SORT_OPT='V'
    else
        # Determining the most recent tag is likely to be inaccurate when
        # multiple tags point to a commit
        VERSION_SORT_OPT='n'

        # Alternatively perform version sort with python if available
        python_version_sort_reverse()
        {
            local field=$(($1-1))
            python3 -c '
from sys import exit, stdin, stdout, stderr
import errno
import csv
import rpm

reader = csv.reader(stdin, delimiter="'$SEPARATOR'", lineterminator="\n")
writer = csv.writer(stdout, delimiter="'$SEPARATOR'", lineterminator="\n")

def cmp_ver(v1, v2):
    v1h = rpm.hdr()
    v2h = rpm.hdr()
    v1h[rpm.RPMTAG_EPOCH] = 0
    v2h[rpm.RPMTAG_EPOCH] = 0
    v1h[rpm.RPMTAG_RELEASE] = "0"
    v2h[rpm.RPMTAG_RELEASE] = "0"
    v1h[rpm.RPMTAG_VERSION] = v1
    v2h[rpm.RPMTAG_VERSION] = v2
    return rpm.versionCompare(v1h, v2h)

try:
    writer.writerows(sorted(reader, cmp=cmp_ver, key=lambda row: row['$field'], reverse=True))
except IOError as e:
    if e.errno == errno.EPIPE:
        pass
stderr.close()
'
        }

        feature HAS_PYTHON_VERSION_SORT \
            $(! python_version_sort_reverse 0 </dev/null &>/dev/null || echo 1)
        if [[ ! $HAS_PYTHON_VERSION_SORT ]]; then
            warn_c "Your 'sort' utility does not support --version-sort and"
            warn_c "    you either do not have python installed or the 'rpm' module"
            warn_c "    could not be used."
        fi
    fi

    # compatibility with old Git
    feature HAS_GIT_VERSION_SORT \
        $(! git for-each-ref --sort=version:refname refs/heads/ &>/dev/null || echo 1)

    feature HAS_GIT_SHOW_NO_PATCH \
        $(! git show --no-patch &>/dev/null || echo 1)
    if [[ $HAS_GIT_SHOW_NO_PATCH ]]; then
        NO_PATCH_OPT='--no-patch'
    else
        NO_PATCH_OPT='--quiet'
    fi

    feature HAS_GIT_SHORT_LOCAL \
        $(! git for-each-ref --format="%(authordate:short-local)" refs/heads/ &>/dev/null \
            || echo 1)

    feature HAS_GIT_ALLOW_UNRELATED_HISTORIES_OPT \
        $(! { git merge -h 2>&1 || :; } |grep -q allow-unrelated-histories || echo 1)
    if [[ $HAS_GIT_ALLOW_UNRELATED_HISTORIES_OPT ]]; then
        ALLOW_UNRELATED_HISTORIES_OPT='--allow-unrelated-histories'
    else
        ALLOW_UNRELATED_HISTORIES_OPT=
    fi

    feature HAS_SED_SANDBOX_OPT \
        $(! sed --sandbox 1p </dev/null &>/dev/null || echo 1)
    if [[ $HAS_SED_SANDBOX_OPT ]]; then
        SANDBOX_OPT='--sandbox'
    else
        SANDBOX_OPT=
    fi
}

# Output unique lines, keep order
stable_uniq()
{
    nl |sort -k 2 -u |sort -k 1,1 |cut -f 2-
}

changelog__trim_newlines()
{
    sed -e '/./,$!d' |tac |sed -e '/./,$!d' |tac
}

git_chl__trim_newlines()
{
    warn "The 'git_chl__trim_newlines' command has been renamed as 'changelog__trim_newlines' - update your scripts."
    changelog__trim_newlines "$@"
}

# Read a patch produced by git-diff and output just the first sequence of '+' lines
head_added_lines()
{
    sed '0,/^@@/d' |sed '/^+/,$!d' |sed '/^[^+]/,$d' |sed -n 's/^+//p'
}

changelog__head_added_lines()
{
    local range=$1
    local file=$2

    if [[ $range == *..* ]]; then
        git diff "$range" -- "$file" |head_added_lines
    else
        git show "$range":"$file"
    fi
}

git_chl__head_added_lines()
{
    warn "The 'git_chl__head_added_lines' command has been renamed as 'changelog__head_added_lines' - update your scripts."
    changelog__head_added_lines "$@"
}

changelog__indent()
{
    sed '/./s/^/  /'
}

git_chl__indent()
{
    warn "The 'git_chl__indent' command has been renamed as 'changelog__indent' - update your scripts."
    changelog__indent "$@"
}

# Prepend blank line to a non-empty input
changelog__nostick()
{
    sed '1i \\'
}

git_chl__nostick()
{
    warn "The 'git_chl__nostick' command has been renamed as 'changelog__nostick' - update your scripts."
    changelog__nostick "$@"
}

# Each output line describes one tag with the following fields, sorted first by
# commit-sha1 ascending, then by refname descending
TI_COMMIT=1
TI_REFNAME=2
TI_TYPE=3 # 'commit' or 'tag'
TI_DATE=4
TI_NAME=5
TI_EMAIL=6
TI_ORDER=7
create_tag_info()
{
    if [[ $HAS_VERSION_SORT_OPT || ! $HAS_PYTHON_VERSION_SORT ]]; then
        sort_tag_info()
        { sort -k $TI_COMMIT,$TI_COMMIT -k $TI_REFNAME,$TI_REFNAME${VERSION_SORT_OPT}r; }
    else
        sort_tag_info()
        { python_version_sort_reverse $TI_REFNAME |sort -k $TI_COMMIT,$TI_COMMIT --stable; }
    fi

    if [[ $HAS_GIT_SHORT_LOCAL ]]; then
        local date_format='short-local'
        local date_postformat=
    else
        local date_format='iso'
        local date_postformat='date=$(date --date="$date" +%F)'
    fi

    limit_to_first_parent_and_add_ordering()
    {
        join <(cat) \
            <(git rev-list --first-parent --simplify-by-decoration "$OPT_HEAD" \
                |awk -v OFS=';' '{print $1, NR}' \
                |sort -k 1,1)
    }

    local format='
        if [[ %(objecttype) = tag ]]; then
            commit=%(*objectname)
            if [[ -z "'$OPT_FAVOR_TAGGER'" ]]; then
                date=%(*committerdate:'$date_format')
                name=%(*authorname)
                email=%(*authoremail)
            else
                date=%(taggerdate:'$date_format')
                name=%(taggername)
                email=%(taggeremail)
            fi
        else
            commit=%(objectname)
            date=%(committerdate:'$date_format')
            name=%(authorname)
            email=%(authoremail)
        fi
        '$date_postformat'
        write $commit %(refname) %(objecttype) "$date" "$name" "$email"
    '
    eval=$(TZ=UTC git for-each-ref --shell --format="$format" --merged "$OPT_HEAD" "$OPT_TAGS")
    TZ=UTC eval "$eval" |sort_tag_info |limit_to_first_parent_and_add_ordering
}

# Reads input produced by list_objects and outputs changelog entries, each line
# prefixed with '- '
extract_entries()
{
    sed_entries()
    {
        sed -n ${GIT_CHANGE_LOG_RESTRICTED:+$SANDBOX_OPT} "$OPT_SED_ENTRIES"
    }

    for_each_ref_desc()
    {
        local quoting=${1/--raw/}
        local format=$2
        shift
        local refs=$*

        if [[ $HAS_GIT_VERSION_SORT ]]; then
            git for-each-ref $quoting --format="$format" --sort=-v:refname $refs
        else
            for ref in $refs; do
                git for-each-ref $quoting --format="$format" $ref
            done
        fi
    }

    maybe_squeeze_spaces()
    {
        if [[ ! $OPT_NO_SQUEEZE_SPACES ]]; then
            tr -s '[:blank:]' ' '
        else
            cat
        fi
    }

    maybe_sort_entries()
    {
        if [[ $OPT_SORT_ENTRIES ]]; then
            LC_ALL=$OLD_LC_ALL sort
        else
            cat
        fi
    }

    local allentries=
    local allobjects=
    local allannotatedtags=
    while true; do
        local commit=
        local annotatedtags=
        read commit annotatedtags || true
        annotatedtags=(${annotatedtags//$SEPARATOR/ })

        allannotatedtags="$allannotatedtags${annotatedtags:+ ${annotatedtags[*]}}"

        if [[ $commit && $OPT_ONLY_ADD_ANNOTATIONS ]]; then
            : # noop
        elif [[ $commit && ${#annotatedtags[*]} -le 1 ]]; then
            allobjects="$allobjects${annotatedtags:+ ${annotatedtags[*]}}"
            if [[ ! $OPT_ONLY_ADD_TAGGED || ${#annotatedtags[*]} -gt 0 || $(look $commit $TAG_INFO) ]]; then
                allobjects="$allobjects $commit"
            fi
        else
            local entries=
            if [[ $allobjects ]]; then
                local object=
                for object in $allobjects; do
                    entries=$(git show $NO_PATCH_OPT --pretty=format:%s%n%n%b $object |sed_entries)
                    [[ $entries ]] && allentries=${allentries:+$allentries$NL}$entries
                done
                allobjects=
            fi
            if [[ ${#annotatedtags[*]} -gt 1 ]]; then
                entries=$(eval "$(for_each_ref_desc --shell "sed_entries <<<%(contents)" ${annotatedtags[*]})")
                [[ $entries ]] && allentries=${allentries:+$allentries$NL}$entries
            fi
            if [[ $commit ]]; then
                if [[ ! $OPT_ONLY_ADD_TAGGED || ${#annotatedtags[*]} -gt 0 || $(look $commit $TAG_INFO) ]]; then
                    entries=$(git show $NO_PATCH_OPT --pretty=format:%s%n%n%b $commit |sed_entries)
                    [[ $entries ]] && allentries=${allentries:+$allentries$NL}$entries
                fi
            fi

            if [[ ! $commit ]]; then
                break
            fi
        fi
    done

    if [[ ! $allentries && $allannotatedtags && $OPT_AUTO_ADD_ANNOTATIONS ]]; then
        allentries=$(for_each_ref_desc --raw "%(contents:subject)" $allannotatedtags)
    fi

    if [[ ! $allentries ]]; then
        return 1
    fi

    echo -n "$allentries" |maybe_squeeze_spaces |stable_uniq |maybe_sort_entries |sed 's/^/- /'
}

# Each row enumerates one commit in the given range, all accompanied with all
# annotated tags pointing to them, in topologic order, most recent first
list_objects()
{
    local range=$1

    local commit=
    while read commit; do
        if [[ $OPT_DESCENDANTS_OF ]]; then
            [[ -s $DESCENDANTS ]] && look $commit $DESCENDANTS >/dev/null || continue
        fi
        local commit_tags=$(! [[ -s $TAG_INFO ]] || look $commit $TAG_INFO \
            |awk -F "$SEPARATOR" '($'$TI_TYPE' == "tag") { print $'$TI_REFNAME' }')
        write $commit $commit_tags
    done < <(git rev-list ${OPT_REV_LIST_ARGS:-} $range)
}

# Turns tag name info revision string for use in the changelog headline
pretty_revision()
{
    local tag=$1

    local short=
    if [[ $OPT_TAGS_WITH_PATH ]]; then
        short=$(git rev-parse --symbolic --abbrev-ref $tag)
    else
        short=${tag##*/}
    fi

    if [[ $OPT_STRIP_TAG_PREFIX ]]; then
        local name=${short##*/}
        local path=${short%$name}
        echo "${path}${name#$OPT_STRIP_TAG_PREFIX}"
    else
        echo "$short"
    fi
}

# Write changelog headline for the given tag, using pretty_revision
write_headline()
{
    local tag=$1
    local pretty_revision=$2

    local tag_info=$(look $(git rev-parse $tag^{}) $TAG_INFO)
    local commit tag_ objecttype date name email order
    read commit tag_ objecttype date name email order <<<"$tag_info"

    date=$(date --date=$date '+%a %b %d %Y')

    printf "* %s %s %s - %s\n" "$date" "$name" "$email" $pretty_revision
}

# Outputs rows with tag-date pairs, most recent first
list_tags_with_date()
{
    uniq -w$SHA1_WIDTH $TAG_INFO \
        |sort --numeric-sort -k $TI_ORDER,$TI_ORDER \
        |cut -f $TI_REFNAME,$TI_DATE
}

# Outputs rows with tag-date-range tripples, most recent first
list_tags_dates_ranges()
{
    local current_tag=
    local current_date=
    local tag=
    local date=
    {
        while read tag date; do
            if [[ ! $OPT_DENSE ]] || grep -q -e "$OPT_DENSE" <<<"$tag"; then
                if [[ $current_tag ]]; then
                    write $current_tag $current_date $tag..$current_tag
                fi
                current_tag=$tag
                current_date=$date
            fi
        done

        if [[ $current_tag ]]; then
            write $current_tag $current_date $current_tag
        fi
    } < <(list_tags_with_date)
}

VALID_APPEND_FROM_OPTS=" noindent notail notrim stick "
append_from()
{
    local range=$1

    if [[ $OPT_APPEND_FROM_OPTS == *" notail "* && $range != *..* ]]; then
        return
    fi

    changelog__head_added_lines "$range" "$OPT_APPEND_FROM" \
        |if [[ $OPT_APPEND_FROM_SED ]]; then
            sed -n ${GIT_CHANGE_LOG_RESTRICTED:+$SANDBOX_OPT} "$OPT_APPEND_FROM_SED"
        else
            cat
        fi \
        |if [[ $OPT_APPEND_FROM_OPTS == *" notrim "* ]]; then
            cat
        else
            changelog__trim_newlines
        fi \
        |if [[ $OPT_APPEND_FROM_OPTS == *" noindent "* ]]; then
            cat
        else
            changelog__indent
        fi \
        |if [[ $OPT_APPEND_FROM_OPTS == *" stick "* ]]; then
            cat
        else
            changelog__nostick
        fi
}

# Continuation character is single backslash preceded by space and immediately
# followed by line break.
join_continuation_lines()
{
    sed ':a;/ \\$/{N;s/\\\n//;ba}'
}

# Comments are only recognized at the beginning of line, optionally preceded by
# spaces.
remove_comments_and_blanks()
{
    sed '/^[[:space:]]*\(#.*\)\?$/d'
}

# Parse a simple shell-like script consisting just from command invocations.
#
# Each command must start on a new line. No other construct is recognized except
# for comments. Comment leader must be the very first nonblank character on a
# line.
#
# Produces one $RS delimited record for each command, with arguments (starting
# with the command itself) delimited with $US.
parse_script()
{
    local parser='
foreach my $line (Text::ParseWords::nested_quotewords("\\s+", 0, <STDIN>)) {
    print join($ENV{"US"}, @{$line}), $ENV{"RS"};
}
'

    join_continuation_lines | remove_comments_and_blanks| \
        (export US RS; perl -mText::ParseWords -e "$parser")
}

run_script()
{
    local file=$1

    {
        local magic=
        # Special care must be taken to not have the trailing newline removed
        # as it normally happens with process substitution
        IFS= read -r -d '' -n "${#REQUIRED_SCRIPT_SHEBANG}" magic

        if [[ $magic != "$REQUIRED_SCRIPT_SHEBANG" ]]; then
            # Limited backward compatibility
            local more=$((${#REQUIRED_SCRIPT_SHEBANG_OLD} - ${#REQUIRED_SCRIPT_SHEBANG}))
            [[ $more -ge 0 ]] || more=0
            local more_magic=
            IFS= read -r -d '' -n "$more" more_magic

            if [[ $magic$more_magic != "$REQUIRED_SCRIPT_SHEBANG_OLD" ]]; then
                fatal "$file: The script MUST start with '${REQUIRED_SCRIPT_SHEBANG%$NL}'."
                return 1
            fi

            warn "The '$SELF_OLD' command has been renamed as '$SELF_REAL' - update your scripts."
        fi

        local parsed=
        parsed=$(parse_script) || return
    } <"$file"

    local invocation=() cmd= args=
    while IFS=$US builtin read -a invocation -d "$RS" -r; do
        cmd=${invocation[0]:-}
        args=("${invocation[@]:1}")
        case $cmd in
            "$SELF")
                cmd=$0
                ;;
            "$SELF_OLD")
                warn "The '$SELF_OLD' command has been renamed as '$SELF_REAL' - update your scripts."
                cmd=$0
                ;;
            git-show)
                cmd=git
                args=(show ${args[@]:+"${args[@]}"})
                ;;
            echo)
                ;;
            *)
                fatal "Command '$cmd' not allowed in script"
                return 1
                ;;
        esac
        if ! GIT_CHANGE_LOG_RESTRICTED=1 "$cmd" ${args[@]:+"${args[@]}"}; then
            fatal "Command failed: $file: $cmd${args[@]:+ $(printf '%q ' "${args[@]}")}"
            return 1
        fi
    done <<<"$parsed"
}

##############################################################################
if [[ ${1:-} != --self-test ]]; then ###  M A I N  EXECUTION BEGINS HERE #####
##############################################################################

if [[ $SELF == "$SELF_OLD" ]]; then
    warn "The '$SELF_OLD' command has been renamed as '$SELF_REAL' - update your scripts."
fi

OPT_SELF_TEST=
configure

OPT_AUTO_ADD_ANNOTATIONS=
OPT_ONLY_ADD_ANNOTATIONS=
OPT_ONLY_ADD_TAGGED=
OPT_DESCENDANTS_OF=
OPT_DENSE=
OPT_FAVOR_TAGGER=
OPT_NO_FAVOR_TAGGER=
OPT_HEAD=
OPT_NO_SQUEEZE_SPACES=
OPT_REV_LIST_ARGS='--topo-order'
OPT_SCRIPT=
OPT_SED_ENTRIES='/^[[:space:]]*\[.*\]/s/^[[:space:]]*//p'
OPT_SINCE=
OPT_SINCE_TYPE=
OPT_SORT_ENTRIES=
OPT_STRIP_TAG_PREFIX='v'
OPT_TAGS='refs/tags/*'
OPT_APPEND_FROM=
OPT_APPEND_FROM_OPTS=
OPT_APPEND_FROM_SED=
OPT_APPEND_CMD=
OPT_TAGS_WITH_PATH=
OPT_UNTAGGED_ONLY=
OPT_LIMIT=
while [[ ${1:-} ]]; do
    case "$1" in
        -h )
            brief_usage
            exit 0
            ;;
        --help )
            usage
            exit 0
            ;;
        -a | --auto-add-annotations )
            OPT_AUTO_ADD_ANNOTATIONS=1
            ;;
        -A | --only-add-annotations )
            OPT_AUTO_ADD_ANNOTATIONS=1
            OPT_ONLY_ADD_ANNOTATIONS=1
            ;;
        -T | --only-add-tagged )
            OPT_ONLY_ADD_TAGGED=1
            ;;
        -D | --descendants-of )
            [[ $2 ]] || bad_usage "Argument expected: '$1'"
            shift
            OPT_DESCENDANTS_OF=$1
            ;;
        -d | --dense )
            [[ $2 ]] || bad_usage "Argument expected: '$1'"
            shift
            OPT_DENSE=$1
            [[ $OPT_NO_FAVOR_TAGGER ]] || OPT_FAVOR_TAGGER=1
            ;;
        --favor-tagger )
            OPT_FAVOR_TAGGER=1
            OPT_NO_FAVOR_TAGGER=
            ;;
        --no-favor-tagger )
            OPT_FAVOR_TAGGER=
            OPT_NO_FAVOR_TAGGER=1
            ;;
        --no-squeeze-spaces )
            OPT_NO_SQUEEZE_SPACES=1
            ;;
        --rev-list-args= ) # backward compatibility
            OPT_REV_LIST_ARGS=
            ;;
        --rev-list-args=* ) # backward compatibility
            OPT_REV_LIST_ARGS="${OPT_REV_LIST_ARGS:+$OPT_REV_LIST_ARGS }${1#*=}"
            ;;
        --rev-list-args )
            [[ $# -ge 2 ]] || bad_usage "Argument expected: '$1'"
            shift
            if [[ $1 ]]; then
                OPT_REV_LIST_ARGS="${OPT_REV_LIST_ARGS:+$OPT_REV_LIST_ARGS }$1"
            else
                OPT_REV_LIST_ARGS=
            fi
            ;;
        --sed-entries )
            [[ $2 ]] || bad_usage "Argument expected: '$1'"
            if [[ $GIT_CHANGE_LOG_RESTRICTED && ! $HAS_SED_SANDBOX_OPT ]]; then
                bad_usage "Your 'sed' version does not support the '--sandbox' option." \
                          "Without this the '$1' option is not available in restricted mode."
            fi
            shift
            OPT_SED_ENTRIES=$1
            ;;
        --script )
            [[ $2 ]] || bad_usage "Argument expected: '$1'"
            shift
            OPT_SCRIPT=$1
            ;;
        --since-date | --since-rev )
            [[ $2 ]] || bad_usage "Argument expected: '$1'"
            OPT_SINCE_TYPE=${1#--since-}
            shift
            OPT_SINCE=$1
            ;;
        -s | --since )
            shift
            OPT_SINCE=$1
            ;;
        --sort-entries )
            OPT_SORT_ENTRIES=1
            ;;
        --strip-tag-prefix )
            [[ $# -gt 1 ]] || bad_usage "Argument expected: '$1'"
            shift
            OPT_STRIP_TAG_PREFIX=$1
            ;;
        -t | --tags )
            [[ $2 ]] || bad_usage "Argument expected: '$1'"
            shift
            OPT_TAGS=$1
            ;;
        -F | --append-from )
            [[ $2 ]] || bad_usage "Argument expected: '$1'"
            {
                IFS="," builtin read -d "," OPT_APPEND_FROM || true
                OPT_APPEND_FROM=${OPT_APPEND_FROM%$NL}
                opts=$(cat)
            } <<<"$2"
            if [[ $opts =~ ^([^:]*)sed:(.*)$ ]]; then
                if [[ $GIT_CHANGE_LOG_RESTRICTED && ! $HAS_SED_SANDBOX_OPT ]]; then
                    bad_usage "Your 'sed' version does not support the '--sandbox' option." \
                        "Without this the 'sed' option to '$1' is not available in restricted mode."
                fi
                opts=${BASH_REMATCH[1]%,}
                OPT_APPEND_FROM_SED=${BASH_REMATCH[2]}
            fi
            IFS="," builtin read -a opts <<<"$opts"
            for opt in "${opts[@]:0}"; do
                if ! [[ $VALID_APPEND_FROM_OPTS == *" $opt "* ]]; then
                    bad_usage "Unexpected option to '--append-from': '$opt'"
                fi
            done
            OPT_APPEND_FROM_OPTS=" ${opts[*]:-} "
            shift
            ;;
        --append-cmd )
            [[ $2 ]] || bad_usage "Argument expected: '$1'"
            if [[ $GIT_CHANGE_LOG_RESTRICTED ]]; then
                bad_usage "The '$1' option is not available in restricted mode."
            fi
            shift
            OPT_APPEND_CMD=$1
            ;;
        --tags-with-path )
            OPT_TAGS_WITH_PATH=1
            ;;
        -u | --untagged-only )
            OPT_UNTAGGED_ONLY=1
            ;;
        -[1-9]* )
            OPT_LIMIT=${1:1}
            [[ $OPT_LIMIT =~ ^[1-9][0-9]*$ ]] || bad_usage "Natural number expected: '$OPT_LIMIT'"
            ;;
        -- )
            break
            ;;
        -[^-]?* )
            arg=$1
            shift
            set -- "${arg}" -"${arg:1:1}" -"${arg:2}" "${@}"
            ;;
        -* )
            bad_usage "Unexpected option: '$1'"
            ;;
        * )
            if [[ $OPT_HEAD ]]; then
                bad_usage "No more than one revision can be specified"
            fi
            OPT_HEAD=$1
            ;;
    esac

    shift
done

: ${OPT_HEAD:=HEAD}

if [[ $OPT_SINCE ]]; then
    if [[ ! $OPT_SINCE_TYPE ]]; then
        # do not use '-f' test, allow reading from other than regular files
        if [[ -e $OPT_SINCE ]]; then
            OPT_SINCE_TYPE='file'
        elif date --date="$OPT_SINCE" &>/dev/null; then
            OPT_SINCE_TYPE='date'
        else
            OPT_SINCE_TYPE='rev'
        fi
    elif [[ $OPT_SINCE_TYPE == 'date' ]]; then
        if ! date --date="$OPT_SINCE" &>/dev/null; then
            bad_usage "Invalid date: '$OPT_SINCE'"
        fi
    fi

    if [[ $OPT_SINCE_TYPE == 'file' ]]; then
        last_rev=$(head "$OPT_SINCE" |sed -n '/^\* / { s/^.*- \([^[:space:]]\+\)$/\1/p; q }')
        if [[ ! $last_rev ]]; then
            bad_usage "This does not look like a changelog file: '$OPT_SINCE'"
        fi
        OPT_SINCE_TYPE='rev'
        OPT_SINCE=$last_rev
    fi
fi

if [[ $OPT_SCRIPT ]]; then
    run_script "$OPT_SCRIPT"
    exit
fi

cleanup()
{
    local rv=$?
    [[ ${TAG_INFO:-} ]] && rm -f "$TAG_INFO"
    [[ ${DESCENDANTS:-} ]] && rm -f "$DESCENDANTS"
    return $rv
}
trap cleanup EXIT

TAG_INFO=$(mktemp $SELF.tag-info.XXX)
create_tag_info > $TAG_INFO

if [[ $OPT_DESCENDANTS_OF ]]; then
    DESCENDANTS=$(mktemp $SELF.descendants.XXX)
    {
        git rev-list --ancestry-path "$OPT_DESCENDANTS_OF..$OPT_HEAD"
        git rev-parse "$OPT_DESCENDANTS_OF" # actually descendant-or-self
    } |sort > $DESCENDANTS
    OPT_REV_LIST_ARGS="$OPT_REV_LIST_ARGS ^$OPT_DESCENDANTS_OF^"
fi

if [[ $OPT_SINCE_TYPE == 'rev' ]]; then
    valid_rev=
    while read tag date; do
        if [[ $OPT_SINCE == $(pretty_revision $tag) ]]; then
            valid_rev=1
            break
        fi
    done < <(list_tags_with_date)
    if [[ ! $valid_rev ]]; then
        bad_usage "Unknown revision: '$OPT_SINCE'"
    fi
fi

untagged_entries=$(
    read last_tag date < <(list_tags_with_date) || true
    list_objects ${last_tag:+$last_tag..}"$OPT_HEAD" |extract_entries || true
)

if [[ $OPT_UNTAGGED_ONLY ]]; then
    echo "$untagged_entries"
else
    if [[ $untagged_entries ]]; then
        warn "skipping changelog entries in untagged commits"
    fi

    date_limit=$([[ $OPT_SINCE_TYPE != 'date' ]] || date --date="$OPT_SINCE" '+%F')
    rev_limit=$([[ $OPT_SINCE_TYPE != 'rev' ]] || echo "$OPT_SINCE")
    i=0
    while read tag date range; do
        [[ ! $date_limit || $date_limit < $date ]] || break

        pretty_revision=$(pretty_revision $tag)
        [[ ! $rev_limit || $rev_limit != $pretty_revision ]] || break

        [[ $i -eq 0 ]] || echo # blank line separator
        write_headline $tag $pretty_revision

        if ! list_objects "$range" |extract_entries; then
            tag_abbrev=$(git rev-parse --symbolic --abbrev-ref $tag)
            warn "No changelog entries for tag '$tag_abbrev'"
        fi

        if [[ $OPT_APPEND_FROM ]]; then
            append_from "$range"
        fi

        if [[ $OPT_APPEND_CMD ]]; then
            (eval "$OPT_APPEND_CMD" "$range")
        fi

        : $((++i))
        [[ ! $OPT_LIMIT || $i -lt $OPT_LIMIT ]] || break
    done < <(list_tags_dates_ranges)
fi

exit

##############################################################################
fi ###  S E L F - T E S T  EXECUTION BEGINS HERE #############################
##############################################################################

OPT_SELF_TEST=1
configure

some_failed=
verify()
{
    comm=${*#$(dirname $SELF)/}
    loc="at line ${BASH_LINENO[0]}"
    expected=$(cat)

    if ! out=$(GIT_CHANGE_LOG_NO_COMPAT_WARNINGS=1 $SELF "$@" 2>&1); then
        cat <<EOF
*** FAIL Command exited with non zero:
  Command: \`$comm\` $loc
  Output: {{{
$out
}}}

EOF
        some_failed=1
    fi

    if [[ "$out" != "$expected" ]]; then
        cat <<EOF
*** FAIL Command produced unexpected output:
  Command: \`$comm\` $loc
  Expected: {{{
$expected
}}}
  Actual: {{{
$out
}}}
  Git-Log: {{{
$(command git --no-pager log --graph --pretty=fuller --all -p)
}}}
  Git-Tags: {{{
$(command git --no-pager tag -l -n9)
}}}

EOF
        some_failed=1
    fi

    rm -rf .git
}

git()
(
    BASE_DATE=$(sed -n 's/^.*\(2000-[0-9][0-9]\).*$/\1/p' <<<"$*" |head -n1)
    export TZ=UTC
    [[ $BASE_DATE ]] && export GIT_AUTHOR_DATE="$BASE_DATE-01 00:00"
    export GIT_AUTHOR_NAME='John Author'
    export GIT_AUTHOR_EMAIL='john.author@example.net'
    [[ $BASE_DATE ]] && export GIT_COMMITTER_DATE="$BASE_DATE-02 00:00"
    export GIT_COMMITTER_NAME='Alice Comitter'
    export GIT_COMMITTER_EMAIL='alice.committer@example.net'

    case "$1" in
        init )
            shift
            command git init -b master "$@" >/dev/null
            ;;
        commit )
            shift
            command git commit --allow-empty "$@" >/dev/null
            ;;
        tag )
            [[ $BASE_DATE ]] && export GIT_COMMITTER_DATE="$BASE_DATE-03 00:00"
            export GIT_COMMITTER_NAME='Jack Tagger'
            export GIT_COMMITTER_EMAIL='jack.tagger@example.net'
            command git "$@" >/dev/null
            ;;
        checkout )
            shift
            command git checkout --quiet "$@" >/dev/null
            ;;
        * )
            command git "$@" >/dev/null
            ;;
    esac
)

REPO=$(readlink -f $(mktemp -d $SELF.test.XXX))
trap "rm -rf '$REPO/.git' && rm -r '$REPO'" EXIT

cd $REPO

# Do not test on empty repository - it will fail with error message possibly
# specific to particular Git version

# Case: No changelog entries, no tags
git init .
git commit -m 'Commit 2000-01'
# ------------------------------------------------------------
verify <<EOF
EOF
# ============================================================

# Case: No changelog entries, no tags
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
# ------------------------------------------------------------
verify <<EOF
EOF
# ============================================================

# Case: Has changelog entry, no tags
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
# ------------------------------------------------------------
verify <<EOF
$SELF: WARNING: skipping changelog entries in untagged commits
EOF
# ============================================================

# Case: Has tagged changelog entry
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
# ------------------------------------------------------------
verify <<EOF
* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Has tagged and untagged changelog entries
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m 'Commit 2000-04

Lorem ipsum...

[FooBar] Commit 2000-04'
# ------------------------------------------------------------
verify <<EOF
$SELF: WARNING: skipping changelog entries in untagged commits
* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Has tagged changelog entry, --untagged-only used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
# ------------------------------------------------------------
verify --untagged-only <<EOF
EOF
# ============================================================

# Case: Has tagged and untagged changelog entries, --untagged-only used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
# ------------------------------------------------------------
verify -u <<EOF
- [FooBar] Commit 2000-04
EOF
# ============================================================

# Case: Some entries in subject, some in body
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m 'Commit 2000-04

Lorem ipsum...

[FooBar] Commit 2000-04'
git tag 0.1.1
# ------------------------------------------------------------
verify <<EOF
* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Has all changelog entries tagged, some plain commit on top
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
# ------------------------------------------------------------
verify <<EOF
* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Has all changelog entries tagged, one tag with no entry
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag 0.1.2
# ------------------------------------------------------------
verify <<EOF
* Tue May 02 2000 John Author <john.author@example.net> - 0.1.2
$SELF: WARNING: No changelog entries for tag '0.1.2'

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Proper version sort
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1+git1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1+git1
git commit -m '[FooBar] Commit 2000-05'
git tag 0.1.2+git1
git commit -m '[FooBar] Commit 2000-06'
git tag 0.1.2+git2
git commit -m '[FooBar] Commit 2000-07'
git tag 0.1.2+git10
git commit -m '[FooBar] Commit 2000-08'
git tag 0.1.10+git1
git commit -m '[FooBar] Commit 2000-09'
git tag 0.10+git1
# ------------------------------------------------------------
verify <<EOF
* Sat Sep 02 2000 John Author <john.author@example.net> - 0.10+git1
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.1.10+git1
- [FooBar] Commit 2000-08

* Sun Jul 02 2000 John Author <john.author@example.net> - 0.1.2+git10
- [FooBar] Commit 2000-07

* Fri Jun 02 2000 John Author <john.author@example.net> - 0.1.2+git2
- [FooBar] Commit 2000-06

* Tue May 02 2000 John Author <john.author@example.net> - 0.1.2+git1
- [FooBar] Commit 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1+git1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1+git1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Annotated tag
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
# ------------------------------------------------------------
verify <<EOF
* Tue May 02 2000 John Author <john.author@example.net> - 0.1.2
- [FooBar] Tag 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Annotated tag - entry in body
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m 'Tag 2000-05

Lorem ipsum...

[FooBar] Tag 2000-05

...dolor sit amet...
'
# ------------------------------------------------------------
verify <<EOF
* Tue May 02 2000 John Author <john.author@example.net> - 0.1.2
- [FooBar] Tag 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Annotated tag with no entry
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m 'Tag 2000-06'
# ------------------------------------------------------------
verify <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.2
$SELF: WARNING: No changelog entries for tag '0.2'

* Tue May 02 2000 John Author <john.author@example.net> - 0.1.2
- [FooBar] Tag 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Annotated tag with no entry and --auto-add-annotations used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m 'Tag 2000-06'
# ------------------------------------------------------------
verify --auto-add-annotations <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.2
- Tag 2000-06

* Tue May 02 2000 John Author <john.author@example.net> - 0.1.2
- [FooBar] Tag 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --only-add-annotations used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m 'Tag 2000-06

[FooBar] Lorem ipsum...
'
git commit -m 'Commit 2000-07'
git tag -a 0.1.3 -m 'Tag 2000-08'
git tag -a 0.2 -m 'Tag 2000-09

[FooBar] Lorem ipsum...
'
git commit -m 'Commit 2000-10'
git tag -a 0.2.1 -m 'Tag 2000-11'
git tag -a 0.3 -m 'Tag 2000-12

[FooBar] Lorem ipsum...
'
# ------------------------------------------------------------
verify --only-add-annotations <<EOF
* Mon Oct 02 2000 John Author <john.author@example.net> - 0.3
- Tag 2000-12
- Tag 2000-11

* Sun Jul 02 2000 John Author <john.author@example.net> - 0.2
- Tag 2000-09
- Tag 2000-08

* Tue May 02 2000 John Author <john.author@example.net> - 0.1.2
- Tag 2000-06

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
$SELF: WARNING: No changelog entries for tag '0.1.1'

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
$SELF: WARNING: No changelog entries for tag '0.1'
EOF
# ============================================================

# Case: Custom entries extraction
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m 'Commit 2000-03

Lorem ipsum...

changelog: Committed 2000-03
'
git tag 0.1
git commit -m 'Commit 2000-04

Lorem ipsum...

changelog: Committed 2000-04
'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m 'Tag 2000-05

Lorem ipsum...

changelog: Tagged 2000-05
'
# ------------------------------------------------------------
verify --sed-entries 's/^changelog: //p' <<EOF
* Tue May 02 2000 John Author <john.author@example.net> - 0.1.2
- Tagged 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- Committed 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- Committed 2000-03
EOF
# ============================================================

# Case: --favor-tagger used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
# ------------------------------------------------------------
verify --favor-tagger <<EOF
* Wed May 03 2000 Jack Tagger <jack.tagger@example.net> - 0.1.2
- [FooBar] Tag 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --dense used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --dense '/[0-9]\+\.[0-9]\+$' <<EOF
* Sat Jun 03 2000 Jack Tagger <jack.tagger@example.net> - 0.2
- [FooBar] Tag 2000-06
- [FooBar] Tag 2000-05
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --dense and --no-favor-tagger used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --no-favor-tagger --dense '/[0-9]\+\.[0-9]\+$' <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Tag 2000-06
- [FooBar] Tag 2000-05
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --dense used, one annotated tag without entry
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m 'Tag 2000-06'
# ------------------------------------------------------------
verify --dense '/[0-9]\+\.[0-9]\+$' <<EOF
* Sat Jun 03 2000 Jack Tagger <jack.tagger@example.net> - 0.2
- [FooBar] Tag 2000-05
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --dense and --auto-add-annotations used, one annotated tag without entry
# Rationale: it is not desired to mix two styles in one changelog section,
# therefore 'Tag 2000-06' will not be mentioned.
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m 'Tag 2000-06'
# ------------------------------------------------------------
verify -a -d '/[0-9]\+\.[0-9]\+$' <<EOF
* Sat Jun 03 2000 Jack Tagger <jack.tagger@example.net> - 0.2
- [FooBar] Tag 2000-05
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: -1 used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify -1 <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Tag 2000-06
EOF
# ============================================================

# Case: -1 used and REVISION passed
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify 0.1.1 -1 <<EOF
* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04
EOF
# ============================================================

# Case: -1 and --dense used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --dense '/[0-9]\+\.[0-9]\+$' -1 <<EOF
* Sat Jun 03 2000 Jack Tagger <jack.tagger@example.net> - 0.2
- [FooBar] Tag 2000-06
- [FooBar] Tag 2000-05
- [FooBar] Commit 2000-04
EOF
# ============================================================

# Case: --since <date>
git init .
git commit -m '[FooBar] Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag 0.1
git commit -m '[FooBar] Commit 2000-03'
git commit -m '[FooBar] Commit 2000-04'
git tag 0.2
git commit -m '[FooBar] Commit 2000-05'
git commit -m '[FooBar] Commit 2000-06'
git tag 0.3
# ------------------------------------------------------------
verify --since 2000-04-01 <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.3
- [FooBar] Commit 2000-06
- [FooBar] Commit 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Commit 2000-04
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --since-date <date>
git init .
git commit -m '[FooBar] Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag 2000-02-02
git commit -m '[FooBar] Commit 2000-03'
git commit -m '[FooBar] Commit 2000-04'
git tag 2000-04-02
git commit -m '[FooBar] Commit 2000-05'
git commit -m '[FooBar] Commit 2000-06'
git tag 2000-06-02
touch 2000-04-02
# ------------------------------------------------------------
verify --since-date 2000-04-02 <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 2000-06-02
- [FooBar] Commit 2000-06
- [FooBar] Commit 2000-05
EOF
# ============================================================

# Case: --since <rev>
git init .
git commit -m '[FooBar] Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag 0.1
git commit -m '[FooBar] Commit 2000-03'
git commit -m '[FooBar] Commit 2000-04'
git tag 0.2
git commit -m '[FooBar] Commit 2000-05'
git commit -m '[FooBar] Commit 2000-06'
git tag 0.3
# ------------------------------------------------------------
verify --since 0.2 <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.3
- [FooBar] Commit 2000-06
- [FooBar] Commit 2000-05
EOF
# ============================================================

# Case: --since-rev <rev>
git init .
git commit -m '[FooBar] Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag 2000-02-02
git commit -m '[FooBar] Commit 2000-03'
git commit -m '[FooBar] Commit 2000-04'
git tag 2000-04-02
git commit -m '[FooBar] Commit 2000-05'
git commit -m '[FooBar] Commit 2000-06'
git tag 2000-06-02
touch 2000-04-02
# ------------------------------------------------------------
verify --since-rev 2000-04-02 <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 2000-06-02
- [FooBar] Commit 2000-06
- [FooBar] Commit 2000-05
EOF
# ============================================================

# Case: --since <file>
git init .
git commit -m '[FooBar] Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag 0.1
git commit -m '[FooBar] Commit 2000-03'
git commit -m '[FooBar] Commit 2000-04'
git tag 0.2
git commit -m '[FooBar] Commit 2000-05'
git commit -m '[FooBar] Commit 2000-06'
git tag 0.3
# ------------------------------------------------------------
verify --since <("$0" 0.2 2>&1) <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.3
- [FooBar] Commit 2000-06
- [FooBar] Commit 2000-05
EOF
# ============================================================

# Case: Has more tags on one commit, the most recent one will be used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git tag -a 0.2.2 -m '[FooBar] Tag 2000-05'
git tag -a 0.2.10 -m '[FooBar] Tag 2000-06'
git tag -a 0.10.2 -m '[FooBar] Tag 2000-07'
git tag -a 0.10.10 -m '[FooBar] Tag 2000-08'
# ------------------------------------------------------------
verify <<EOF
* Sun Apr 02 2000 John Author <john.author@example.net> - 0.10.10
- [FooBar] Tag 2000-08
- [FooBar] Tag 2000-07
- [FooBar] Tag 2000-06
- [FooBar] Tag 2000-05
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Has more tags on one commit, --favor-tagger used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git tag 0.1.1
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git tag -a 0.1.10 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --favor-tagger <<EOF
* Sat Jun 03 2000 Jack Tagger <jack.tagger@example.net> - 0.1.10
- [FooBar] Tag 2000-06
- [FooBar] Tag 2000-05
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --tags used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag our/v0.1
git commit -m '[FooBar] Commit 2000-04'
git tag our/v0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
git tag -a our/v0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --tags refs/tags/our <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Tag 2000-06

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --tags and --tags-with-path used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag our/v0.1
git commit -m '[FooBar] Commit 2000-04'
git tag our/v0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
git tag -a our/v0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --tags refs/tags/our --tags-with-path <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - our/0.2
- [FooBar] Tag 2000-06

* Sun Apr 02 2000 John Author <john.author@example.net> - our/0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - our/0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --tags and --tags-with-path and --strip-tag-prefix '' used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag our/v0.1
git commit -m '[FooBar] Commit 2000-04'
git tag our/v0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
git tag -a our/v0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --tags refs/tags/our --tags-with-path --strip-tag-prefix '' <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - our/v0.2
- [FooBar] Tag 2000-06

* Sun Apr 02 2000 John Author <john.author@example.net> - our/v0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - our/v0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --tags and --strip-tag-prefix used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag our/foo-0.1
git commit -m '[FooBar] Commit 2000-04'
git tag our/foo-0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
git tag -a our/foo-0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --tags refs/tags/our --strip-tag-prefix 'foo-' <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Tag 2000-06

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --tags and --dense used
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag our/0.1
git commit -m '[FooBar] Commit 2000-04'
git tag our/0.1.1
git commit -m 'Commit 2000-05'
git tag -a 0.1.2 -m '[FooBar] Tag 2000-05'
git commit -m 'Commit 2000-06'
git tag -a 0.2 -m '[FooBar] Tag 2000-06'
git tag -a our/0.2 -m '[FooBar] Tag 2000-06'
# ------------------------------------------------------------
verify --tags refs/tags/our --dense '/[0-9]\+\.[0-9]\+$' <<EOF
* Sat Jun 03 2000 Jack Tagger <jack.tagger@example.net> - 0.2
- [FooBar] Tag 2000-06
- [FooBar] Commit 2000-04

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --sort-entries used
git init .
git commit -m 'Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1
git commit -m '[FooBar] Commit 2000-04'
git commit -m 'Commit 2000-05'
git commit -m '[FooBar] Commit 2000-06'
git tag 0.2
# ------------------------------------------------------------
verify --sort-entries <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Commit 2000-04
- [FooBar] Commit 2000-06

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-02
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: --no-squeeze-spaces NOT used
git init .
git commit -m 'Commit 2000-01'
git commit -m '[FooBar] 2000-02'
git tag -a 0.1 -m '[FooBar]  2000-02'
# ------------------------------------------------------------
verify <<EOF
* Wed Feb 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] 2000-02
EOF
# ============================================================

# Case: --no-squeeze-spaces used
git init .
git commit -m 'Commit 2000-01'
git commit -m '[FooBar] 2000-02'
git tag -a 0.1 -m '[FooBar]  2000-02'
# ------------------------------------------------------------
verify --no-squeeze-spaces <<EOF
* Wed Feb 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar]  2000-02
- [FooBar] 2000-02
EOF
# ============================================================

# Case: Advanced merging
git init .
git commit -m 'Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag 0.1
git commit -m '[FooBar] Commit 2000-03'
git checkout -b 0.1-fix 0.1
git commit -m '[FooBar] Commit 2000-04'
git commit -m '[FooBar] Commit 2000-05'
git tag 0.1.0.1
git commit -m '[FooBar] Commit 2000-06'
git tag 0.1.0.2
git checkout master
git commit -m '[FooBar] Commit 2000-07'
git tag 0.1.1
git checkout 0.1-fix
git commit -m '[FooBar] Commit 2000-08'
git tag 0.1.0.3
git checkout master
git commit -m '[FooBar] Commit 2000-09'
git tag 0.1.2
git merge 0.1-fix -m 'Merge 2000-10'
git tag 0.1.3
# ------------------------------------------------------------
verify <<EOF
* Mon Oct 02 2000 John Author <john.author@example.net> - 0.1.3
- [FooBar] Commit 2000-08
- [FooBar] Commit 2000-06
- [FooBar] Commit 2000-05
- [FooBar] Commit 2000-04

* Sat Sep 02 2000 John Author <john.author@example.net> - 0.1.2
- [FooBar] Commit 2000-09

* Sun Jul 02 2000 John Author <john.author@example.net> - 0.1.1
- [FooBar] Commit 2000-07
- [FooBar] Commit 2000-03

* Wed Feb 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-02
EOF
# ============================================================

# Case: Advanced merging with --dense
git init .
git commit -m 'Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag 0.1
git commit -m '[FooBar] Commit 2000-03'
git checkout -b 0.1-fix 0.1
git commit -m '[FooBar] Commit 2000-04'
git commit -m '[FooBar] Commit 2000-05'
git tag 0.1.0.1
git commit -m '[FooBar] Commit 2000-06'
git tag 0.1.0.2
git checkout master
git commit -m '[FooBar] Commit 2000-07'
git tag 0.1.1
git checkout 0.1-fix
git commit -m '[FooBar] Commit 2000-08'
git tag 0.1.0.3
git checkout master
git commit -m '[FooBar] Commit 2000-09'
git tag 0.1.2
git merge 0.1-fix -m 'Merge 2000-10'
git tag 0.1.3
git tag 0.2
# ------------------------------------------------------------
verify --dense '/[0-9]\+\.[0-9]\+$' <<EOF
* Mon Oct 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Commit 2000-08
- [FooBar] Commit 2000-06
- [FooBar] Commit 2000-05
- [FooBar] Commit 2000-04
- [FooBar] Commit 2000-09
- [FooBar] Commit 2000-07
- [FooBar] Commit 2000-03

* Wed Feb 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-02
EOF
# ============================================================

# Case: Overriding the default --rev-list-args
git init .
git commit -m 'Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag 0.1
git commit -m '[FooBar] Commit 2000-03'
git checkout -b 0.1-fix 0.1
git commit -m '[FooBar] Commit 2000-04'
git commit -m '[FooBar] Commit 2000-05'
git tag 0.1.0.1
git commit -m '[FooBar] Commit 2000-06'
git tag 0.1.0.2
git checkout master
git commit -m '[FooBar] Commit 2000-07'
git tag 0.1.1
git checkout 0.1-fix
git commit -m '[FooBar] Commit 2000-08'
git tag 0.1.0.3
git checkout master
git commit -m '[FooBar] Commit 2000-09'
git tag 0.1.2
git merge 0.1-fix -m 'Merge 2000-10'
git tag 0.1.3
git tag 0.2
# ------------------------------------------------------------
verify --dense '/[0-9]\+\.[0-9]\+$' --rev-list-args '' \
    --rev-list-args '--date-order --reverse' <<EOF
* Mon Oct 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Commit 2000-03
- [FooBar] Commit 2000-04
- [FooBar] Commit 2000-05
- [FooBar] Commit 2000-06
- [FooBar] Commit 2000-07
- [FooBar] Commit 2000-08
- [FooBar] Commit 2000-09

* Wed Feb 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-02
EOF
# ============================================================

# Case: Ignoring upstream commits with --rev-list-args
git init .
git checkout -b upstream/master
git commit -m 'Upstream commit 2000-01'
git commit -m '[FooBar] Upstream commit 2000-02'
git tag 0.1
git checkout -b master
git commit -m '[FooBar] Commit 2000-03'
git tag our/0.1+git1
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-04'
git commit -m '[FooBar] Upstream commit 2000-05'
git tag 0.1.1
git checkout master
git commit -m '[FooBar] Commit 2000-06'
git tag our/0.1+git2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-07'
git tag 0.1.2
git checkout master
git merge upstream/master -m '[FooBar] Merge branch "upstream/master" 2000-08'
git tag our/0.1.2+git1
git commit -m '[FooBar] Commit 2000-09'
git tag our/0.1.2+git2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-10'
git tag 0.1.3
# ------------------------------------------------------------
verify --tags 'refs/tags/our/*' --rev-list-args '^upstream/master' master <<EOF
* Sat Sep 02 2000 John Author <john.author@example.net> - 0.1.2+git2
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.1.2+git1
- [FooBar] Merge branch "upstream/master" 2000-08

* Fri Jun 02 2000 John Author <john.author@example.net> - 0.1+git2
- [FooBar] Commit 2000-06

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1+git1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Ignoring upstream commits with --rev-list-args but adding appending upstream Changelog
git init .
touch Changelog
git add Changelog
git checkout -b upstream/master
echo "Committed 2000-01" > Changelog
git commit -m 'Upstream commit 2000-01' -a
git commit -m '[FooBar] Upstream commit 2000-02'
git tag 0.1
git checkout -b master
git commit -m '[FooBar] Commit 2000-03'
git tag our/0.1+git1
git checkout upstream/master
sed -i "1i Committed 2000-04" Changelog
git commit -m '[FooBar] Upstream commit 2000-04' -a
sed -i "1i Committed 2000-05" Changelog
git commit -m '[FooBar] Upstream commit 2000-05' -a
git tag 0.1.1
git checkout master
git commit -m '[FooBar] Commit 2000-06'
git tag our/0.1+git2
git checkout upstream/master
sed -i "1i Committed 2000-07" Changelog
git commit -m '[FooBar] Upstream commit 2000-07' -a
git tag 0.1.2
git checkout master
git merge upstream/master -m '[FooBar] Merge branch "upstream/master" 2000-08'
git tag our/0.1.2+git1
git commit -m '[FooBar] Commit 2000-09'
git tag our/0.1.2+git2
git checkout upstream/master
sed -i "1i Committed 2000-10" Changelog
git commit -m '[FooBar] Upstream commit 2000-10' -a
git tag 0.1.3
# ------------------------------------------------------------
verify --tags 'refs/tags/our/*' --rev-list-args '^upstream/master' \
    --append-from Changelog master <<EOF
* Sat Sep 02 2000 John Author <john.author@example.net> - 0.1.2+git2
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.1.2+git1
- [FooBar] Merge branch "upstream/master" 2000-08

  Committed 2000-07
  Committed 2000-05
  Committed 2000-04

* Fri Jun 02 2000 John Author <john.author@example.net> - 0.1+git2
- [FooBar] Commit 2000-06

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1+git1
- [FooBar] Commit 2000-03

  Committed 2000-01
EOF
# ============================================================

# Case: Ignoring upstream commits with --rev-list-args but adding appending upstream Changelog, with options
git init .
touch Change,log # intentionally include comma character
git add Change,log
git checkout -b upstream/master
echo "Committed 2000-01" > Change,log
git commit -m 'Upstream commit 2000-01' -a
git commit -m '[FooBar] Upstream commit 2000-02'
git tag 0.1
git checkout -b master
git commit -m '[FooBar] Commit 2000-03'
git tag our/0.1+git1
git checkout upstream/master
sed -i "1i Committed 2000-04" Change,log
git commit -m '[FooBar] Upstream commit 2000-04' -a
sed -i "1i Committed 2000-05" Change,log
git commit -m '[FooBar] Upstream commit 2000-05' -a
git tag 0.1.1
git checkout master
git commit -m '[FooBar] Commit 2000-06'
git tag our/0.1+git2
git checkout upstream/master
sed -i "1i Committed 2000-07" Change,log
git commit -m '[FooBar] Upstream commit 2000-07' -a
git tag 0.1.2
git checkout master
git merge upstream/master -m '[FooBar] Merge branch "upstream/master" 2000-08'
git tag our/0.1.2+git1
git commit -m '[FooBar] Commit 2000-09'
git tag our/0.1.2+git2
git checkout upstream/master
sed -i "1i Committed 2000-10" Change,log
git commit -m '[FooBar] Upstream commit 2000-10' -a
git tag 0.1.3
# ------------------------------------------------------------
filter='1i Upstream changes,sed:
s/^/- /p'
verify --tags 'refs/tags/our/*' --rev-list-args '^upstream/master' \
    --append-from Change\\,log,stick,sed:"$filter" master <<EOF
* Sat Sep 02 2000 John Author <john.author@example.net> - 0.1.2+git2
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.1.2+git1
- [FooBar] Merge branch "upstream/master" 2000-08
  Upstream changes,sed:
  - Committed 2000-07
  - Committed 2000-05
  - Committed 2000-04

* Fri Jun 02 2000 John Author <john.author@example.net> - 0.1+git2
- [FooBar] Commit 2000-06

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1+git1
- [FooBar] Commit 2000-03
  Upstream changes,sed:
  - Committed 2000-01
EOF
# ============================================================

# Case: Ignoring upstream commits with --rev-list-args but adding upstream
# changelog with a command
git init .
touch Changelog
git add Changelog
git checkout -b upstream/master
echo "Committed 2000-01" > Changelog
git commit -m 'Upstream commit 2000-01' -a
git commit -m '[FooBar] Upstream commit 2000-02'
git tag 0.1
git checkout -b master
git commit -m '[FooBar] Commit 2000-03'
git tag our/0.1+git1
git checkout upstream/master
sed -i "1i Committed 2000-04" Changelog
git commit -m '[FooBar] Upstream commit 2000-04' -a
sed -i "1i Committed 2000-05" Changelog
git commit -m '[FooBar] Upstream commit 2000-05' -a
git tag 0.1.1
git checkout master
git commit -m '[FooBar] Commit 2000-06'
git tag our/0.1+git2
git checkout upstream/master
sed -i "1i Committed 2000-07" Changelog
git commit -m '[FooBar] Upstream commit 2000-07' -a
git tag 0.1.2
git checkout master
git merge upstream/master -m '[FooBar] Merge branch "upstream/master" 2000-08'
git tag our/0.1.2+git1
git commit -m '[FooBar] Commit 2000-09'
git tag our/0.1.2+git2
git checkout upstream/master
sed -i "1i Committed 2000-10" Changelog
git commit -m '[FooBar] Upstream commit 2000-10' -a
git tag 0.1.3
# ------------------------------------------------------------
# example_2_begin
my_append()
{
    changelog__head_added_lines "$1" Changelog \
        |changelog__trim_newlines |changelog__indent |changelog__nostick
}
verify --append-cmd "$(declare -f my_append); my_append" \
    --tags 'refs/tags/our/*' --rev-list-args '^upstream/master' master <<EOF
* Sat Sep 02 2000 John Author <john.author@example.net> - 0.1.2+git2
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.1.2+git1
- [FooBar] Merge branch "upstream/master" 2000-08

  Committed 2000-07
  Committed 2000-05
  Committed 2000-04

* Fri Jun 02 2000 John Author <john.author@example.net> - 0.1+git2
- [FooBar] Commit 2000-06

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1+git1
- [FooBar] Commit 2000-03

  Committed 2000-01
EOF
# example_2_end
# ============================================================

# Case: NOT ignoring upstream commits with --rev-list-args
git init .
git checkout -b upstream/master
git commit -m 'Upstream commit 2000-01'
git commit -m '[FooBar] Upstream commit 2000-02'
git tag 0.1
git checkout -b master
git commit -m '[FooBar] Commit 2000-03'
git tag our/0.1+git1
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-04'
git commit -m '[FooBar] Upstream commit 2000-05'
git tag 0.1.1
git checkout master
git commit -m '[FooBar] Commit 2000-06'
git tag our/0.1+git2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-07'
git tag 0.1.2
git checkout master
git merge upstream/master -m '[FooBar] Merge branch "upstream/master" 2000-08'
git tag our/0.1.2+git1
git commit -m '[FooBar] Commit 2000-09'
git tag our/0.1.2+git2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-10'
git tag 0.1.3
# ------------------------------------------------------------
verify --tags 'refs/tags/our/*' master <<EOF
* Sat Sep 02 2000 John Author <john.author@example.net> - 0.1.2+git2
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.1.2+git1
- [FooBar] Merge branch "upstream/master" 2000-08
- [FooBar] Upstream commit 2000-07
- [FooBar] Upstream commit 2000-05
- [FooBar] Upstream commit 2000-04

* Fri Jun 02 2000 John Author <john.author@example.net> - 0.1+git2
- [FooBar] Commit 2000-06

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1+git1
- [FooBar] Commit 2000-03
- [FooBar] Upstream commit 2000-02
EOF
# ============================================================

# Case: Ignoring upstream commits with --tags and --only-add-tagged
git init .
git checkout -b upstream/master
git commit -m 'Upstream commit 2000-01'
git commit -m '[FooBar] Upstream commit 2000-02'
git tag 0.1
git checkout -b master
git commit -m '[FooBar] Commit 2000-03'
git tag our/0.1+git1
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-04'
git commit -m '[FooBar] Upstream commit 2000-05'
git tag 0.1.1
git checkout master
git commit -m '[FooBar] Commit 2000-06'
git tag our/0.1+git2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-07'
git tag 0.1.2
git checkout master
git merge upstream/master -m '[FooBar] Merge branch "upstream/master" 2000-08'
git tag our/0.1.2+git1
git commit -m '[FooBar] Commit 2000-09'
git tag our/0.1.2+git2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-10'
git tag 0.1.3
# ------------------------------------------------------------
verify --tags 'refs/tags/our/*' --only-add-tagged master <<EOF
* Sat Sep 02 2000 John Author <john.author@example.net> - 0.1.2+git2
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.1.2+git1
- [FooBar] Merge branch "upstream/master" 2000-08

* Fri Jun 02 2000 John Author <john.author@example.net> - 0.1+git2
- [FooBar] Commit 2000-06

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1+git1
- [FooBar] Commit 2000-03
EOF
# ============================================================

# Case: Ignoring upstream commits with --tags and --descendants-of
git init .
git checkout -b upstream/master
git commit -m 'Upstream commit 2000-01'
git commit -m '[FooBar] Upstream commit 2000-02'
git tag 0.1
git checkout -b master
git commit -m '[FooBar] Commit 2000-02'
start_of_our_devel=$(command git rev-parse HEAD)
git commit -m '[FooBar] Commit 2000-03'
git tag our/0.1+git1
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-04'
git commit -m '[FooBar] Upstream commit 2000-05'
git tag 0.1.1
git checkout -b feature master
git commit -m '[FooBar] Feature commit 2000-06'
git checkout master
git commit -m '[FooBar] Commit 2000-06'
git commit -m '[FooBar] Commit 2000-07'
git tag our/0.1+git2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-07'
git tag 0.1.2
git checkout feature
git commit -m '[FooBar] Feature commit 2000-08'
git checkout master
git merge upstream/master -m '[FooBar] Merge branch "upstream/master" 2000-08'
git merge feature -m 'Merge branch "feature" 2000-08'
git tag our/0.1.2+git1
git commit -m '[FooBar] Commit 2000-09'
git commit -m '[FooBar] Commit 2000-10'
git tag our/0.1.2+git2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-10'
git tag 0.1.3
# ------------------------------------------------------------
verify --tags 'refs/tags/our/*' --descendants-of "$start_of_our_devel" master <<EOF
* Mon Oct 02 2000 John Author <john.author@example.net> - 0.1.2+git2
- [FooBar] Commit 2000-10
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.1.2+git1
- [FooBar] Feature commit 2000-08
- [FooBar] Feature commit 2000-06
- [FooBar] Merge branch "upstream/master" 2000-08

* Sun Jul 02 2000 John Author <john.author@example.net> - 0.1+git2
- [FooBar] Commit 2000-07
- [FooBar] Commit 2000-06

* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1+git1
- [FooBar] Commit 2000-03
- [FooBar] Commit 2000-02
EOF
# ============================================================

# Case: Mixing tags with and w/o prefix
git init .
git commit -m 'Commit 2000-01'
git commit -m '[FooBar] Commit 2000-02'
git tag prefix/0.1
git commit -m 'Commit 2000-03'
git commit -m '[FooBar] Commit 2000-04'
git tag 0.2
git commit -m '[FooBar] Commit 2000-05'
git commit -m 'Commit 2000-06'
git tag 0.3
# ------------------------------------------------------------
verify --tags 'refs/tags/**' <<EOF
* Fri Jun 02 2000 John Author <john.author@example.net> - 0.3
- [FooBar] Commit 2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.2
- [FooBar] Commit 2000-04

* Wed Feb 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-02
EOF
# ============================================================

# Case: Executing script
git init .

# Initialize upstream repository, add at least one file so that git-subtree has
# anything to work with
git checkout -b upstream/master
echo foo > foo
git add foo
git commit -m 'Upstream commit 2000-01'
git commit -m '[FooBar] Upstream commit 2000-02'
git tag 0.1

# Initialize our own repository with upstream code in a subtree
git checkout -b master
git subtree add -q --squash --prefix src upstream/master
echo our > our
echo bar > src/bar
git add our src/bar
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1+git1

# Upstream releases 0.2
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-04'
git commit -m '[FooBar] Upstream commit 2000-05'
git tag 0.2

# Update to upstream 0.2
git checkout master
git subtree merge --squash --prefix src upstream/master
git commit -m '[FooBar] Update to upstream version 0.2 2000-06'
git tag 0.2+git1

# We do some changes
echo OUR > our
echo BAR > src/bar
git add our src/bar
git commit -m '[FooBar] Commit 2000-07'
git tag 0.2+git2

# Upstream releases 0.3
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-07'
git tag 0.3

# We decided to stop using git-subtree prior to updating to upstream 0.3
git checkout master
splitted=$(command git subtree split --prefix src)
git branch splitted $splitted
git checkout -b no-subtree 0.2
git merge --strategy ours ${ALLOW_UNRELATED_HISTORIES_OPT} master -m 'Reset master to upstream tag 0.2'
git branch -f master
git rebase no-subtree splitted
git checkout -B no-subtree splitted
git show 0.2+git2:our > our
git add our
git commit -m 'Add our files as of 0.2+git2'
git checkout master
git merge --no-ff no-subtree -m 'Convert from subtree to normal branch'

# Update to upstream 0.3
git merge upstream/master -m 'Merge upstream version 0.3 2000-08'
# Summarize upstream changes since last time we updated
echo "Upstream changelog 0.3" > UpstreamChanges
git add UpstreamChanges
git commit -m '[FooBar] Update to upstream version 0.3 2000-08'
git tag our/0.3+git1

# We do some changes
git commit -m '[FooBar] Commit 2000-09'
git tag our/0.3+git2

# Upstream releases 0.4
git checkout upstream/master
git commit -m '[FooBar] Upstream commit 2000-10'
git tag 0.4

# We do some changes
git checkout master
git commit -m '[FooBar] Commit 2000-10'
git tag our/0.3+git3

# Update to upstream 0.4
git merge upstream/master -m 'Merge upstream version 0.4 2000-11'
# Summarize upstream changes since last time we updated
echo "Upstream changelog 0.4" >> UpstreamChanges
git add UpstreamChanges
git commit -m '[FooBar] Update to upstream version 0.4 2000-11'
git tag our/0.4+git1

# ------------------------------------------------------------
verify --script <(cat <<EOF
#!$(readlink -f "$0") --script
# Originally we used git-subtree to separate our changelog from upstream.
# Since 0.3+git1 we keep our changes in a normal git branch, using
# --only-add-tagged and --tags to select our own changelog items. We now also
# include summary of upstream changes (stored in 'UpstreamChanges') and trim
# very old entries.

$SELF --tags 'refs/tags/our/*' \
      --only-add-tagged \
      --append-from UpstreamChanges \
      master
echo ""
$SELF --since 0.1+git1 \
      0.2+git2
echo ""
echo "  [Some changelog entries trimmed for brevity]"
EOF
) <<EOF
* Thu Nov 02 2000 John Author <john.author@example.net> - 0.4+git1
- [FooBar] Update to upstream version 0.4 2000-11

  Upstream changelog 0.4

* Mon Oct 02 2000 John Author <john.author@example.net> - 0.3+git3
- [FooBar] Commit 2000-10

* Sat Sep 02 2000 John Author <john.author@example.net> - 0.3+git2
- [FooBar] Commit 2000-09

* Wed Aug 02 2000 John Author <john.author@example.net> - 0.3+git1
- [FooBar] Update to upstream version 0.3 2000-08

  Upstream changelog 0.3

* Sun Jul 02 2000 John Author <john.author@example.net> - 0.2+git2
- [FooBar] Commit 2000-07

* Fri Jun 02 2000 John Author <john.author@example.net> - 0.2+git1
- [FooBar] Update to upstream version 0.2 2000-06

  [Some changelog entries trimmed for brevity]
EOF

# ============================================================

# Case: Executing script -- backward compatibility
git init .
git commit -m 'Commit 2000-01'
git commit -m 'Commit 2000-02'
git commit -m '[FooBar] Commit 2000-03'
git tag 0.1

# ------------------------------------------------------------
verify --script <(cat <<EOF
#!/usr/bin/git-change-log --script
git-change-log
EOF
) <<EOF
$SELF: WARNING: The 'git-change-log' command has been renamed as '$SELF' - update your scripts.
$SELF: WARNING: The 'git-change-log' command has been renamed as '$SELF' - update your scripts.
* Thu Mar 02 2000 John Author <john.author@example.net> - 0.1
- [FooBar] Commit 2000-03
EOF

# ============================================================

# Case: Example printed with --help
git init .
# example_1_begin
git commit -m 'Foo: Fix #2000-01'
git commit -m '[Foo] Fix #2000-02'
git commit -m '[Foo] Fix #2000-02 more'
git commit -m 'Foo: Fix #2000-03'
git tag 0.0.1

git commit -m '[Foo] Fix #2000-04'
git tag 0.1

# use annotated tags since now

git commit -m 'Foo: Fix #2000-05'
git commit -m 'Foo: Fix #2000-05 more'
git tag 0.1.1 -a -m 'Foo: Fixed #2000-05'

git commit -m 'Foo: Fix #2000-06'
git commit -m 'Foo: Fix #2000-06 more'
git tag 0.2 -a -m 'Foo: Fixed #2000-06'
# example_1_end
# ------------------------------------------------------------
# example_1_out_begin
verify --auto-add-annotations --dense '/[0-9]\+\.[0-9]\+$' <<EOF
* Sat Jun 03 2000 Jack Tagger <jack.tagger@example.net> - 0.2
- Foo: Fixed #2000-06
- Foo: Fixed #2000-05

* Sun Apr 02 2000 John Author <john.author@example.net> - 0.1
- [Foo] Fix #2000-04
- [Foo] Fix #2000-02 more
- [Foo] Fix #2000-02
EOF
# example_1_out_end
# ============================================================

if [[ $some_failed ]]; then
    echo "*** Some tests failed"
    exit 1
else
    echo "*** All tests passed"
    exit 0
fi
