123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- #!/bin/sh
- #
- # Put into Public Domain, by Nicolas Sebrecht
- #
- # Create new releases in OfflineIMAP.
- # TODO: https://developer.github.com/v3/repos/releases/#create-a-release
- # https://developer.github.com/libraries/
- # https://github.com/turnkeylinux/octohub
- # https://github.com/michaelliao/githubpy (onefile)
- # https://github.com/sigmavirus24/github3.py
- # https://github.com/copitux/python-github3
- # https://github.com/PyGithub/PyGithub
- # https://github.com/micha/resty (curl)
- # TODO: move configuration out and source it.
- # TODO: implement rollback.
- __VERSION__='v0.3'
- SPHINXBUILD=sphinx-build
- MAILING_LIST='offlineimap-project@lists.alioth.debian.org'
- GITHUB_FILE_LINK_PREFIX='https://raw.githubusercontent.com/OfflineIMAP/offlineimap'
- DOCSDIR='docs'
- ANNOUNCE_MAGIC='#### Notes '
- CHANGELOG_MAGIC='{:toc}'
- CHANGELOG='Changelog.md'
- CACHEDIR='.git/offlineimap-release'
- WEBSITE='website'
- WEBSITE_LATEST="${WEBSITE}/_data/latest.yml"
- TMP_CHANGELOG_EXCERPT="${CACHEDIR}/changelog.excerpt.md"
- TMP_CHANGELOG_EXCERPT_OLD="${TMP_CHANGELOG_EXCERPT}.old"
- TMP_CHANGELOG="${CACHEDIR}/changelog.md"
- TMP_ANNOUNCE="${CACHEDIR}/announce.txt"
- True=0
- False=1
- Yes=$True
- No=$False
- DEBUG=$True
- #
- # $1: EXIT_CODE
- # $2..: message
- function die () {
- n=$1
- shift
- echo $*
- exit $n
- }
- function debug () {
- if test $DEBUG -eq $True
- then
- echo "DEBUG: $*" >&2
- fi
- }
- #
- # $1: question
- # $2: message on abort
- #
- function ask () {
- echo
- echo -n "--- $1 "
- read -r ans
- test "n$ans" = 'n' -o "n$ans" = 'ny' && return $Yes
- test "n$ans" = "ns" -o "n$ans" = 'nn' && return $No
- die 1 "! $2"
- }
- #
- # $1: message
- # $1: path to file
- #
- function edit_file () {
- ask "Press Enter to $1"
- test $? -eq $Yes && {
- $EDITOR "$2"
- reset
- }
- }
- function fix_pwd () {
- debug 'in fix_pwd'
- cd "$(git rev-parse --show-toplevel)" || \
- die 2 "cannot determine the root of the repository"
- }
- function prepare_env () {
- debug 'in prepare_env'
- mkdir "$CACHEDIR" 2>/dev/null
- test ! -d "$CACHEDIR" && die 5 "Could not make cache directory $CACHEDIR"
- }
- function check_dirty () {
- debug 'in check_dirty'
- git diff --quiet 2>/dev/null && git diff --quiet --cached 2>/dev/null || {
- die 4 "Commit all your changes first!"
- }
- }
- function welcome () {
- debug 'in welcome'
- cat <<EOF
- You will be prompted to answer questions.
- Answer by:
- - 'y' : yes, continue (default)
- - '<Enter>' : yes, continue
- - 'n' : no
- - 's' : skip (ONLY where applicable, otherwise continue)
- Any other key will abort the program.
- EOF
- ask 'Ready?'
- }
- function checkout_next () {
- debug 'in checkout_next'
- git checkout --quiet next || {
- die 6 "Could not checkout 'next' branch"
- }
- }
- function merge_maint () {
- debug 'in merge_maint'
- git merge --quiet -Xours maint || {
- die 7 "Could not merge 'maint' branch"
- }
- }
- function get_version () {
- debug 'in get_version'
- echo "v$(./offlineimap.py --version)"
- }
- function update_offlineimap_version () {
- debug 'in update_offlineimap_version'
- edit_file 'update the version in __init__.py' offlineimap/__init__.py
- }
- #
- # $1: previous version
- #
- function get_git_history () {
- debug 'in get_git_history'
- git log --format='- %h %s. [%aN]' --no-merges "${1}.."
- }
- #
- # $1: previous version
- #
- function get_git_who () {
- debug 'in get_git_who'
- git shortlog --no-merges -sn "${1}.." | \
- sed -r -e 's, +([0-9]+)\t(.*),- \2 (\1),'
- }
- #
- # $1: new version
- # $2: shortlog
- function changelog_template_part1 () {
- debug 'in changelog_template_part1'
- cat <<EOF
- // vim: expandtab ts=2 syntax=markdown
- // WARNING: let at least one empy line before the real content.
- //
- // Write a new Changelog entry.
- //
- // Comments MUST start at the beginning of the lile with two slashes.
- // They will by be ignored by the template engine.
- //
- ### OfflineIMAP $1 ($(date +%Y-%m-%d))
- #### Notes
- // Add some notes. Good notes are about what was done in this release from the
- // bigger perspective.
- // HINT: explain most important changes.
- #### Authors
- EOF
- }
- function changelog_template_part2 () {
- debug 'in changelog_template_part2'
- cat <<EOF
- #### Features
- // Use list syntax with '- '
- #### Fixes
- // Use list syntax with '- '
- #### Changes
- // Use list syntax with '- '
- // The preformatted log was added below. Make use of this to fill the sections
- // above.
- EOF
- }
- #
- # $1: new version
- # $2: previous version
- #
- function update_changelog () {
- debug 'in update_changelog'
- # Write Changelog excerpt.
- if test ! -f "$TMP_CHANGELOG_EXCERPT"
- then
- changelog_template_part1 "$1" > "$TMP_CHANGELOG_EXCERPT"
- get_git_who "$2" >> "$TMP_CHANGELOG_EXCERPT"
- changelog_template_part2 >> "$TMP_CHANGELOG_EXCERPT"
- get_git_history "$2" >> "$TMP_CHANGELOG_EXCERPT"
- edit_file "the Changelog excerpt" $TMP_CHANGELOG_EXCERPT
- # Remove comments.
- grep -v '//' "$TMP_CHANGELOG_EXCERPT" > "${TMP_CHANGELOG_EXCERPT}.nocomment"
- mv -f "${TMP_CHANGELOG_EXCERPT}.nocomment" "$TMP_CHANGELOG_EXCERPT"
- fi
- # Write new Changelog.
- cat "$CHANGELOG" > "$TMP_CHANGELOG"
- debug "include excerpt $TMP_CHANGELOG_EXCERPT to $TMP_CHANGELOG"
- sed -i -e "/${CHANGELOG_MAGIC}/ r ${TMP_CHANGELOG_EXCERPT}" "$TMP_CHANGELOG"
- debug 'remove trailing whitespaces'
- sed -i -r -e 's, +$,,' "$TMP_CHANGELOG" # Remove trailing whitespaces.
- debug "copy to $TMP_CHANGELOG -> $CHANGELOG"
- cp -f "$TMP_CHANGELOG" "$CHANGELOG"
- # Check and edit Changelog.
- ask "Next step: you'll be asked to review the diff of $CHANGELOG"
- while true
- do
- git diff -- "$CHANGELOG" | less
- ask 'edit Changelog?' $CHANGELOG
- test ! $? -eq $Yes && break
- # Asked to edit the Changelog; will loop again.
- $EDITOR "$CHANGELOG"
- done
- }
- #
- # $1: new version
- #
- function git_release () {
- debug 'in git_release'
- git commit -as -m"$1"
- git tag -a "$1" -m"$1"
- git checkout master
- git merge next
- git checkout next
- }
- function get_last_rc () {
- git tag | grep -E '^v([0-9][\.-]){3}rc' | sort -V | tail -n1
- }
- function get_last_stable () {
- git tag | grep -E '^v([0-9][\.])+' | grep -v '\-rc' | sort -V | tail -n1
- }
- function update_website_releases_info() {
- cat > "$WEBSITE_LATEST" <<EOF
- # DO NOT EDIT MANUALLY: it is generated by a script (release.sh)
- stable: $(get_last_stable)
- rc: $(get_last_rc)
- EOF
- }
- #
- # $1: new version
- #
- function update_website () {
- debug 'in update_website'
- ask "update API of the website? (require $SPHINXBUILD)"
- if test $? -eq $Yes
- then
- # Check sphinx is available.
- $SPHINXBUILD --version > /dev/null 2>&1
- if test ! $? -eq 0
- then
- echo "Oops! you don't have $SPHINXBUILD installed?"
- echo "Cannot update the webite documentation..."
- echo "You should install it and run:"
- echo " $ cd docs"
- echo " $ make websitedoc"
- echo "Then, commit and push changes of the website."
- ask 'continue'
- return
- fi
- # Check website sources are available.
- cd website
- if test ! $? -eq 0
- then
- echo "ERROR: cannot go to the website sources"
- ask 'continue'
- return
- fi
- # Stash any WIP in the website sources.
- git diff --quiet 2>/dev/null && git diff --quiet --cached 2>/dev/null || {
- echo "There is WIP in the website repository, stashing"
- echo "git stash create 'WIP during offlineimap API import'"
- git stash create 'WIP during offlineimap API import'
- ask 'continue'
- }
- cd .. # Back to offlineimap.git.
- update_website_releases_info
- cd "./$DOCSDIR" # Enter the docs directory in offlineimap.git.
- # Build the docs!
- make websitedoc && {
- # Commit changes in a branch.
- cd ../website # Enter the website sources.
- branch_name="import-$1"
- git checkout -b "$branch_name"
- git add '_doc/versions'
- git commit -a -s -m"update for offlineimap $1"
- echo "website: branch '$branch_name' ready for a merge in master!"
- }
- ask 'website updated locally; continue'
- fi
- }
- function git_username () {
- git config --get user.name
- }
- function git_usermail () {
- git config --get user.email
- }
- #
- # $1: new version
- #
- function announce_header () {
- cat <<EOF
- Message-Id: <$(git log HEAD~1.. --oneline --pretty='%H.%t.release.%ce')>
- Date: $(git log HEAD~1.. --oneline --pretty='%cD')
- From: $(git_username) <$(git_usermail)>
- To: $MAILING_LIST
- Subject: [ANNOUNCE] OfflineIMAP $1 released
- OfflineIMAP $1 is out.
- Downloads:
- http://github.com/OfflineIMAP/offlineimap/archive/${1}.tar.gz
- http://github.com/OfflineIMAP/offlineimap/archive/${1}.zip
- Pip:
- wget "${GITHUB_FILE_LINK_PREFIX}/${1}/requirements.txt" -O requirements.txt
- pip install -r ./requirements.txt --user git+https://github.com/OfflineIMAP/offlineimap.git@${1}
- EOF
- }
- function announce_footer () {
- cat <<EOF
- --
- $(git_username)
- EOF
- }
- #
- # $1: new version
- # $2: previous version
- #
- function build_announce () {
- announce_header "$1" > "$TMP_ANNOUNCE"
- grep -v '^### OfflineIMAP' "$TMP_CHANGELOG_EXCERPT" | \
- grep -v '^#### Notes' >> "$TMP_ANNOUNCE"
- sed -i -r -e "s,^$ANNOUNCE_MAGIC,," "$TMP_ANNOUNCE"
- sed -i -r -e "s,^#### ,# ," "$TMP_ANNOUNCE"
- announce_footer >> "$TMP_ANNOUNCE"
- }
- function edit_announce () {
- edit_file 'edit announce' "$TMP_ANNOUNCE"
- }
- #
- # run
- #
- function run () {
- debug 'in run'
- fix_pwd
- check_dirty
- prepare_env
- checkout_next
- clear
- welcome
- if test -f "$TMP_CHANGELOG_EXCERPT"
- then
- head "$TMP_CHANGELOG_EXCERPT"
- ask "A previous Changelog excerpt (head above) was found, use it?"
- if test ! $? -eq $Yes
- then
- mv -f "$TMP_CHANGELOG_EXCERPT" "$TMP_CHANGELOG_EXCERPT_OLD"
- fi
- fi
- previous_version="$(get_version)"
- message="Safety check: release after version:"
- ask "$message $previous_version ?"
- update_offlineimap_version
- new_version="$(get_version)"
- ask "Safety check: make a new release with version: '$new_version'" "Clear changes and restart"
- update_changelog "$new_version" "$previous_version"
- build_announce "$new_version" "$previous_version"
- edit_announce
- # Wait for the mainline and announce to be built to not include commits from
- # maint.
- merge_maint
- git_release $new_version
- # Wait for all the Changelogs to be up-to-date in next.
- update_website $new_version
- }
- run
- cat <<EOF
- Release is ready!
- Make your checks and push the changes for both offlineimap and the website.
- Announce template stands in '$TMP_ANNOUNCE'.
- Command samples to do manually:
- - git push <remote> master next $new_version
- - python setup.py sdist && twine upload dist/* && rm -rf dist MANIFEST
- - cd website
- - git checkout master
- - git merge $branch_name
- - git push <remote> master
- - cd ..
- - git send-email $TMP_ANNOUNCE
- Have fun! ,-)
- EOF
- # vim: expandtab ts=2 :
|