It would be useful to be able to easily see / review CHANGELOG entries when using brew outdated, or after a brew update / before a brew upgrade, etc.
As an alternative approach to needing to statically identify the CHANGELOGs for tools up front; I was thinking for a lot of things (particularly those hosted on GitHub), there are common patterns of a CHANGELOG.md file in the repo root, or using GitHub Releases, etc; that could allow us to more dynamically fetch the details (which might also mean a quicker path to adoption in core with less potential Homebrew maintainer burden/etc: Homebrew/brew#3399)
As a starting point, I stumbled on this other tool by @ltaupiac that seems to have a similar sort of goal, focussed purely on GitHub, and written in fish shell scripts:
It basically seems to do the following:
- Tries
brew ruby -e "Formula['name'].head.url" to get the repo URL.
- Falls back to
brew info --json=v2 to extract the homepage if the head URL isn’t available
brew info --json=v2 "$formula_name" | jq -r '.formulae[]?.homepage // empty, .casks[]?.homepage // empty'
- Cleans up
.git suffix if present
set repo (echo $repo | sed 's#\.git$##')
- Only continues if the repo host is
github.com; otherwise, exits with a message that only GitHub is supported.
set -l current_host (trurl --get {host} "$repo")
-
if test "$current_host" != "github.com"
# ..snip..
end
- Appends
/releases/latest to the repo URL.
set repo (echo $repo | sed 's#$#/releases/latest#')
- Rewrites host to
api.github.com.
set repo (trurl --set host='api.github.com' $repo)
- Adjusts the path to prefix with
/repos/..., turning it into the correct GitHub API endpoint for the latest release.
set -l extracted_path (trurl --get {path} "$repo")
set -l path "/repos$extracted_path"
set -l repo (trurl --set path="$path" "$repo")
- Performs a
curl request to the GitHub API endpoint, extracts .tag_name and .body from the JSON using jq, pipes that through glow -p to render the changelog nicely in the terminal.
curl -s "$repo" | jq -r '.tag_name, .body' | glow -p
If I was going to approach trying to solve this from scratch today myself, I would probably largely rely on the GitHub CLI gh for the 'heavy lifting', as well as relevant API endpoints:
- https://cli.github.com/
- https://github.com/cli/cli
-
GitHub CLI
-
GitHub’s official command line tool
-
gh is GitHub on the command line. It brings pull requests, issues, and other GitHub concepts to the terminal next to where you are already working with git and your code.
- https://docs.github.com/en/rest/repos/contents#get-repository-content
-
Get repository content
-
Gets the contents of a file or directory in a repository. Specify the file path or directory with the path parameter. If you omit the path parameter, you will receive the contents of the repository's root directory.
gh api repos/OWNER/REPO/contents/CHANGELOG.md
gh api repos/OWNER/REPO/contents/CHANGELOG.md --jq '{ name, content, encoding, html_url, download_url }'
-
gh api repos/OWNER/REPO/contents/CHANGELOG.md \
--jq '{
name,
html_url,
download_url,
encoding,
content: (if .encoding == "base64" then (.content | @base64d) else .content end)
}
| if .encoding == "base64" then del(.encoding) else . end'
If we wanted to render markdown nicely in the terminal ourself, we could use:
And then for releases...
We can list them:
List releases in a repository
For more information about output formatting flags, see `gh help formatting`.
USAGE
gh release list [flags]
ALIASES
gh release ls
FLAGS
--exclude-drafts Exclude draft releases
--exclude-pre-releases Exclude pre-releases
-q, --jq expression Filter JSON output using a jq expression
--json fields Output JSON with the specified fields
-L, --limit int Maximum number of items to fetch (default 30)
-O, --order string Order of releases returned: {asc|desc} (default "desc")
-t, --template string Format JSON output using a Go template; see "gh help formatting"
INHERITED FLAGS
--help Show help for command
-R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
JSON FIELDS
createdAt, isDraft, isLatest, isPrerelease, name, publishedAt, tagName
..snip..
We can see a scrollable list like this:
gh release list --repo OWNER/REPO
Or we could list releases (up to the limit) as JSON like this:
gh release list --exclude-drafts --exclude-pre-releases --repo OWNER/REPO --json 'createdAt,publishedAt,isLatest,name,tagName'
And filter those to only ones after a specified version like this:
AFTER_TAG="4.6.9"
gh release list \
--exclude-drafts --exclude-pre-releases \
--repo Homebrew/brew \
--json 'createdAt,publishedAt,isLatest,name,tagName' \
--jq '
def norm: ltrimstr("v");
def ver: split(".") | map(tonumber);
map(select((.tagName // .name | norm | ver) > ("'"$AFTER_TAG"'" | norm | ver)))
'
And then we can view information about the releases too:
View information about a GitHub Release.
Without an explicit tag name argument, the latest release in the project
is shown.
For more information about output formatting flags, see `gh help formatting`.
USAGE
gh release view [<tag>] [flags]
FLAGS
-q, --jq expression Filter JSON output using a jq expression
--json fields Output JSON with the specified fields
-t, --template string Format JSON output using a Go template; see "gh help formatting"
-w, --web Open the release in the browser
INHERITED FLAGS
--help Show help for command
-R, --repo [HOST/]OWNER/REPO Select another repository using the [HOST/]OWNER/REPO format
JSON FIELDS
apiUrl, assets, author, body, createdAt, databaseId, id, isDraft, isPrerelease,
name, publishedAt, tagName, tarballUrl, targetCommitish, uploadUrl, url,
zipballUrl
..snip..
Such as:
- Latest release in terminal:
gh release view --repo OWNER/REPO
- Latest release on web:
gh release view --repo OWNER/REPO --web
- Specific release in terminal:
gh release view --repo OWNER/REPO 1.2.3
- Specific release on web:
gh release view --repo OWNER/REPO 1.2.3 --web
- We could also get various parts of that as JSON/etc if we wanted, but that seems less relevant here
So then we could also combine that together by using fzf or similar; so that we can browse through the various releases, and preview/open their CHANGELOG in the browser:
OWNER="Homebrew"; \
REPO="brew"; \
AFTER_TAG="4.6.9"; \
\
gh release list \
--exclude-drafts --exclude-pre-releases \
--repo "$OWNER/$REPO" \
--json 'tagName,name,createdAt,publishedAt' \
--jq 'def norm: ltrimstr("v");
def ver: split(".") | map(tonumber);
(["TAG","NAME","CREATED","PUBLISHED"]),
(map(select((.tagName // .name | norm | ver) > ("'"$AFTER_TAG"'" | norm | ver)))
| map([.tagName, .name, .createdAt, .publishedAt])[])
| @tsv' \
| column -ts $'\t' \
| FZF_DEFAULT_COMMAND= \
fzf \
--prompt "Releases> " \
--info=inline-right \
--input-border \
--header-lines=1 \
--footer $'Enter: View in Terminal Ctrl-O: Open in Browser Ctrl-P: Toggle Preview' \
--footer-border=line \
--reverse \
--delimiter='[[:space:]]{2,}' \
--nth '1..' \
--with-nth '1..' \
--accept-nth 1 \
--preview 'GH_FORCE_TTY=1 gh release view --repo '"$OWNER/$REPO"' {1}' \
--preview-window 'right,60%,border-left,wrap,hidden' \
--bind 'enter:execute(gh release view --repo '"$OWNER/$REPO"' {1})' \
--bind 'ctrl-o:execute-silent(gh release view --repo '"$OWNER/$REPO"' {1} --web)+abort' \
--bind 'ctrl-p:toggle-preview'
And with a bit more effort, we could also make a way that can neatly choose the releases or the CHANGELOG.md depending on which is better.
Originally posted by @0xdevalias in Homebrewery/homebrew-changelog#20
It would be useful to be able to easily see / review CHANGELOG entries when using
brew outdated, or after abrew update/ before abrew upgrade, etc.Prior Art
brew changelog $packageshould open the changelog Homebrew/brew#3399My Approach
Here is the exploration / approach that I wrote up in the following issue:
Other Things We Could Improve
This could be a useful addition to my
gh outdatedalias:dotfiles/config.symlink/gh/config.yml
Lines 76 to 145 in 8d8551a
Which currently has some crude URL manipulation for showing release notes:
dotfiles/config.symlink/gh/config.yml
Lines 97 to 98 in 8d8551a
See Also
brew-upgrade-safe: Explore saferbrew upgradehelper to prevent cascading dependent upgrades #33