# Releasing

## Requirements
* [github client](https://github.com/cli/cli#installation)
* [gitchub_changelog_generator](https://github.com/github-changelog-generator)
* [github access token](https://github.com/github-changelog-generator/github-changelog-generator#github-token)

## Export changelog token

```bash
export CHANGELOG_GITHUB_TOKEN=token
```

## Set release information

0.3.5 should always be the previous release as it's the last pyhs100 release in HISTORY.md which is the changelog prior to github release notes.

```bash
export NEW_RELEASE=x.x.x.devx
```

## Normal releases from master

### Create a branch for the release

```bash
git checkout master
git fetch upstream master
git rebase upstream/master
git checkout -b release/$NEW_RELEASE
```

### Update the version number

```bash
sed -i "0,/version = /{s/version = .*/version = \"${NEW_RELEASE}\"/}" pyproject.toml
```

### Update dependencies

```bash
uv sync --all-extras
uv lock --upgrade
uv sync --all-extras
```

### Update and run pre-commit and tests

```bash
pre-commit autoupdate
uv run pre-commit run --all-files
uv run pytest -n auto
```

### Create release summary (skip for dev releases)

Write a short and understandable summary for the release.  Can include images.

#### Create $NEW_RELEASE milestone in github

If not already created

#### Create new issue linked to the milestone

```bash
gh issue create --label "release-summary" --milestone $NEW_RELEASE --title "$NEW_RELEASE Release Summary" --body "**Release summary:**"
```

You can exclude the --body option to get an interactive editor or go into the issue on github and edit there.

#### Close the issue

Either via github or:

```bash
gh issue close ISSUE_NUMBER
```

### Generate changelog

Configuration settings are in `.github_changelog_generator`

#### For pre-release

EXCLUDE_TAGS will exclude all dev tags except for the current release dev tags.

Regex should be something like this `^((?!0\.7\.0)(.*dev\d))+`. The first match group negative matches on the current release and the second matches on releases ending with dev.

```bash
EXCLUDE_TAGS=${NEW_RELEASE%.dev*}; EXCLUDE_TAGS=${EXCLUDE_TAGS//"."/"\."}; EXCLUDE_TAGS="^((?!"$EXCLUDE_TAGS")(.*dev\d))+"
echo "$EXCLUDE_TAGS"
github_changelog_generator --future-release $NEW_RELEASE --exclude-tags-regex "$EXCLUDE_TAGS"
```

#### For production

```bash
github_changelog_generator --future-release $NEW_RELEASE --exclude-tags-regex 'dev\d$'
```

You can ignore warnings about missing PR commits like below as these relate to PRs to branches other than master:
```
Warning: PR 908 merge commit was not found in the release branch or tagged git history and no rebased SHA comment was found
```


### Export new release notes to variable

```bash
export RELEASE_NOTES=$(grep -Poz '(?<=\# Changelog\n\n)(.|\n)+?(?=\#\#)' CHANGELOG.md | tr '\0' '\n' )
echo "$RELEASE_NOTES"  # Check the output and copy paste if neccessary
```

### Commit and push the changed files

```bash
git commit --all --verbose -m "Prepare $NEW_RELEASE"
git push upstream release/$NEW_RELEASE -u
```

### Create a PR for the release, merge it, and re-fetch the master

#### Create the PR
```
gh pr create --title "Prepare $NEW_RELEASE" --body "$RELEASE_NOTES" --label release-prep --base master
```

To update the PR after refreshing the changelog:

```
gh pr edit --body "$RELEASE_NOTES"
```

#### Merge the PR once the CI passes

Create a squash commit and add the markdown from the PR description to the commit description.

```bash
gh pr merge --squash --body "$RELEASE_NOTES"
```

### Rebase local master

```bash
git checkout master
git fetch upstream master
git rebase upstream/master
```

### Create a release tag

Note, add changelog release notes as the tag commit message so `gh release create --notes-from-tag` can be used to create a release draft.

```bash
git tag --annotate $NEW_RELEASE -m "$RELEASE_NOTES"
git push upstream $NEW_RELEASE
```

### Create release

#### Pre-releases

```bash
gh release create "$NEW_RELEASE" --verify-tag --notes-from-tag --title "$NEW_RELEASE" --draft --latest=false --prerelease

```

#### Production release

```bash
gh release create "$NEW_RELEASE" --verify-tag --notes-from-tag --title "$NEW_RELEASE" --draft --latest=true
```

### Manually publish the release

Go to the linked URL, verify the contents, and click "release" button to trigger the release CI.

## Patch releases

This requires git commit signing to be enabled.

https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification

### Create release branch

#### For the first patch release since a new release only

```bash
export NEW_RELEASE=x.x.x.x
export CURRENT_RELEASE=x.x.x
```

```bash
git fetch upstream $CURRENT_RELEASE
git checkout patch
git fetch upstream patch
git rebase upstream/patch
git fetch upstream $CURRENT_RELEASE
git merge $CURRENT_RELEASE --ff-only
git push upstream patch -u
git checkout -b release/$NEW_RELEASE
```

#### For subsequent patch releases

```bash
export NEW_RELEASE=x.x.x.x
```

```bash
git checkout patch
git fetch upstream patch
git rebase upstream/patch
git checkout -b release/$NEW_RELEASE
```
### Cherry pick required commits

```bash
git cherry-pick commitSHA1 -S
git cherry-pick commitSHA2 -S
```

### Update the version number

```bash
sed -i "0,/version = /{s/version = .*/version = \"${NEW_RELEASE}\"/}" pyproject.toml
```

### Manually edit the changelog

github_changlog generator_does not work with patch releases so manually add the section for the new release to CHANGELOG.md.

### Export new release notes to variable

```bash
export RELEASE_NOTES=$(grep -Poz '(?<=\# Changelog\n\n)(.|\n)+?(?=\#\#)' CHANGELOG.md | tr '\0' '\n' )
echo "$RELEASE_NOTES"  # Check the output and copy paste if neccessary
```

### Commit and push the changed files

```bash
git commit --all --verbose -m "Prepare $NEW_RELEASE" -S
git push upstream release/$NEW_RELEASE -u
```

### Create a PR for the release, merge it, and re-fetch patch

#### Create the PR
```
gh pr create --title "$NEW_RELEASE" --body "$RELEASE_NOTES" --label release-prep --base patch
```

#### Merge the PR once the CI passes

Create a **merge** commit and add the markdown from the PR description to the commit description.

```bash
gh pr merge --merge --body "$RELEASE_NOTES"
```

### Rebase local patch

```bash
git checkout patch
git fetch upstream patch
git rebase upstream/patch
```

### Create a release tag

```bash
git tag -s --annotate $NEW_RELEASE -m "$RELEASE_NOTES"
git push upstream $NEW_RELEASE
```

### Create release

```bash
gh release create "$NEW_RELEASE" --verify-tag --notes-from-tag --title "$NEW_RELEASE" --draft --latest=true
```
Then go into github, review and release

### Merge patch back to master

```bash
git checkout master
git fetch upstream master
git rebase upstream/master
git checkout -b janitor/merge_patch
git fetch upstream patch
git merge upstream/patch --no-commit
# If there are any merge conflicts run the following command which will simply make master win
# Do not run it if there are no conflicts as it will end up checking out upstream/master
git diff --name-only --diff-filter=U | xargs git checkout upstream/master
# Check the diff is as expected
git diff --staged
# The only diff should be the version in pyproject.toml and uv.lock, and CHANGELOG.md
# unless a change made on patch that was not part of a cherry-pick commit
# If there are any other unexpected diffs `git checkout upstream/master [thefilename]`
git commit -m "Merge patch into local master" -S
git push upstream janitor/merge_patch -u
gh pr create --title "Merge patch into master" --body '' --label release-prep --base master
```

#### Temporarily allow merge commits to master

1. Open [repository settings](https://github.com/python-kasa/python-kasa/settings)
2. From the left select `Rules` > `Rulesets`
3. Open `master` ruleset, under `Bypass list` select `+ Add bypass`
4. Check `Repository admin` > `Add selected`, select `Save changes`

#### Merge commit the PR
```bash
gh pr merge --merge --body ""
```
#### Revert allow merge commits

1. Under `Bypass list` select `...` next to `Repository admins`
2. `Delete bypass`, select `Save changes`
