Skip to content

Instantly share code, notes, and snippets.

@lukechilds
Created August 9, 2016 19:43
Show Gist options
  • Save lukechilds/a83e1d7127b78fef38c2914c4ececc3c to your computer and use it in GitHub Desktop.
Save lukechilds/a83e1d7127b78fef38c2914c4ececc3c to your computer and use it in GitHub Desktop.
Shell - Get latest release from GitHub
get_latest_release() {
curl --silent "https://api.github.com/repos/$1/releases/latest" | # Get latest release from GitHub api
grep '"tag_name":' | # Get tag line
sed -E 's/.*"([^"]+)".*/\1/' # Pluck JSON value
}
# Usage
# $ get_latest_release "creationix/nvm"
# v0.31.4
@agambier
Copy link

Very nice.
Good job !
Thanks

@coldfix
Copy link

coldfix commented Sep 4, 2020

if you need a more universal answer, you can use git ls-remote --tags https://github.com/rust-lang/rust.git to get the list of all the tags from the remote without need to clone.

Nice! This is the one that works best for me (I prefer not depending on jq and the curl <url>/releases/latest based answers only yield <url>/releases but not the tag - maybe because I didn't explicitly create releases?).

Anyway, just to make it even a little bit easier to use:

lastrelease() { git ls-remote --tags "$1" | cut -d/ -f3- | tail -n1; }

lastrelease https://github.com/USER/REPO

You may also want to add another | grep or | sed to the pipe to filter for desired tagname patterns (e.g. exclude prereleases).

@hacker65536
Copy link

git ls-remote --refs --sort="version:refname" --tags $repo | cut -d/ -f3-|tail -n1

@artheus
Copy link

artheus commented Sep 18, 2020

Must say I ❤️ the github community!
There are always people who takes the challenge to make something better, and shares their findings!
Keep up the great work everyone!

@edouard-lopez
Copy link

In Fish you can use string manipulation that is native, so no other dependencies than curl:

function get_latest_release_version \
    --argument-names user_repo

    curl \
        --silent \
        "https://api.github.com/repos/$user_repo/releases/latest" \
    | string match --regex  '"tag_name": "\K.*?(?=")'
end

Usage:

❯ get_latest_release_version "rafaelrinaldi/pure"
v3.0.0

@andreasevers
Copy link

Why not use jq?

curl --silent "https://api.github.com/repos/$1/releases/latest" | jq -r .tag_name

@CybotTM
Copy link

CybotTM commented Jan 20, 2021

Example to get highest version, not just latest - because an LTS bugfix 1.2.3 could be released after a new major 3.x version

curl https://api.github.com/repos/mautic/mautic/releases -s | jq -r .[].tag_name | grep '^[0-9]\.[0-9]*\.[0-9]*$' | sort -nr | head -n
1

or find latest version for a specific major version:

curl https://api.github.com/repos/mautic/mautic/releases -s | jq -r .[].tag_name | grep '^2\.[0-9]*\.[0-9]*$' -m1
curl https://api.github.com/repos/mautic/mautic/releases -s | jq -r .[].tag_name | grep '^3\.[0-9]*\.[0-9]*$' -m1

@saki-osive
Copy link

Hi,
I made a version without sed, only using grep.
Maybe it's useful...

curl --silent "https://api.github.com/repos/$1/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")'

Thanks, for sharing the great idea!

Thanks!

@juliyvchirkov
Copy link

juliyvchirkov commented Apr 1, 2021

oneliner to get link to the source archive of latest release

curl -s https://github.com/USER/REPO/releases | 
    grep -m1 -Eo "archive/refs/tags/[^/]+\.tar\.gz" | 
        xargs printf "https://github.com/USER/REPO/%s"

command to clone the source of latest release to the current folder w/o downloading archive to local disk

ghRepoCloneLatestRelease ()
{
    [[ ${1} =~ / ]] &&
        wget -qO- https://github.com/${1}/$(curl -s https://github.com/${1}/releases |
            grep -m1 -Eo "archive/refs/tags/[^/]+\.tar\.gz") |
                tar --strip-components=1 -xzv >/dev/null
}

usage: ghRepoCloneLatestRelease user/repo

@Ishidres
Copy link

@juliy V. Chirkov Thanks a lot!

@juliyvchirkov
Copy link

juliyvchirkov commented Apr 18, 2021

@Ishidres, you're welcome 💁‍♀️

@KEINOS
Copy link

KEINOS commented Apr 27, 2021

$ get_latest_release "ipfs/go-ipfs"
v0.8.0

nice. Thanks a lot.

@CHN-STUDENT
Copy link

@juliyvchirkov hi, How can i get the all latest release (including windows or linux etc.) and download it to a folder?
i mean i want to write a script to check releases,if they does not exist or they are older, my script will download update it; and if they are not change, not download?

curl -s https://api.github.com/repos/user/reop/releases/latest |  /root/jq-linux64 --raw-output '.assets[] | .browser_download_url' | xargs wget 

@pwillis-els
Copy link

pwillis-els commented Jun 17, 2021

If you don't want to get just the last release, but all releases with pagination, try this. It's very ugly, I hope somebody can simplify it with awk or something 😄

next_url="https://api.github.com/repos/Versent/saml2aws/releases"
while [ -n "$next_url" ] ; do
    out="$(curl -ifsSL "$next_url")" ; echo "$out" | awk -F '"' '/"tag_name":/{print $4}' | sed -e 's/^v//'
    next_url="$(echo "$out" | grep '^link:' \
        | sed -e 's/link: //; s/, /\n/g; s/[<>]//g; s/; rel/ rel/g; s/\(https:\/\/[^ ]\+\) rel="\([a-z]\+\)"/\2 \1/g' \
        | awk '/next / { print $2}')"
done

If you do use pagination, you might prefer to use the /tags API endpoint. The data returned is about 100x smaller than /releases, and for the most part the tag name and release names are the same. Same code here, just a different URI, and use "name": instead of "tag_name": above.

Anyone have a GraphQL query for this?

@spoelstraethan
Copy link

I've used a couple of the above snippets/tools for the initial download of the GitHub CLI, but with the GitHub CLI, you can use gh release download to download releases from a specific tag, or if you don't supply a tag at all it will use the latest release (and no, latest as the tag criteria doesn't work unless it actually exists in the repository).

https://cli.github.com/manual/gh_release_download

My favorite practical example might be using gh to download the latest version of itself. I also learned a new trick for Debian/Ubuntu at least. Since uname -a shows x86_64 on 64 bit installs I've often struggled with how to detect 32/64 bit and use the correct xxx_linux_amd64.deb where since it is a deb file we can pretty safely assume we are installing on Linux, but for a .tar.gz it might be for Solaris or BSD or Linux, so we really only care about the arch.

You can see I don't supply a tag before or after the repo, and I don't quote the * otherwise it triggers the shell to try and parse it.

gh release download -D /tmp -R cli/cli --pattern *$(dpkg --print-architecture).deb
sudo apt install /tmp/gh_*

@dweremeichik
Copy link

For PowerShell/Windows users, a better approach:

((Invoke-WebRequest -Uri https://api.github.com/repos/USER/REPO/releases/latest).Content | ConvertFrom-Json).tag_name

@Jarmos-san
Copy link

+1 for @spoelstraethan's suggestion! gh is easy to use & makes downloading the latest release asset an absolute breeze.

Would suggest using that instead of reinventing the wheel & using hacky Shell scripts on any day! And the best part it's available on Homebrew & on Windows so no worries about cross-platform support.

@KEINOS
Copy link

KEINOS commented Oct 5, 2021

Indeed gh is easier to use with no work-around, but I found this gist is useful for lightweight purposes such as Docker and CIs. Thanks!

@wgzhao
Copy link

wgzhao commented Nov 5, 2021

Hi, I made a version without sed, only using grep. Maybe its useful...

curl --silent "https://api.github.com/repos/$1/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")'

Thanks, for sharing the great idea! 👍

In MacOS, the built-in grep command has not -P option.

@meramsey
Copy link

Another mostly reusable one if you just want the latest deb

owner_repo='igniterealtime/Spark'; latest_version_url="$(curl -s https://api.github.com/repos/$owner_repo/releases/latest | grep "browser_download_url.*deb" | cut -d : -f 2,3 | tr -d \")"; echo $latest_version_url; basename $latest_version_url ; wget --content-disposition $latest_version_url

Example:

❯ owner_repo='igniterealtime/Spark'; latest_version_url="$(curl -s https://api.github.com/repos/$owner_repo/releases/latest | grep "browser_download_url.*deb" | cut -d : -f 2,3 | tr -d \")"; echo $latest_version_url; basename $latest_version_url ; wget -q --content-disposition $latest_version_url
https://github.com/igniterealtime/Spark/releases/download/v3.0.0-beta/spark_3_0_0-beta.deb
spark_3_0_0-beta.deb
~  took 1m46s  at 20:14:43 

❯ 

@ChrisKader
Copy link

Example Link: https://github.com/valinet/ExplorerPatcher/releases/latest/download/ep_setup.exe

Replace "ep_setup.exe" with whatever object name you want.

@meramsey
Copy link

meramsey commented Jun 3, 2022

Probably not Pythonic but Python way:

import requests
url = 'https://github.com/roundcube/roundcubemail/releases/latest'
r = requests.get(url)
version = r.url.split('/')[-1]

print(version)

Source: https://gist.github.com/zeldor/604a7817f9d142e908335e041ece0718

Made into a function and returns empty for the ones without it.

def get_github_latest_release(repo_path):
    import requests

    url = f"https://github.com/{repo_path}/releases/latest"
    r = requests.get(url, headers={"Content-Type": "application/vnd.github.v3+json"})
    release = r.url.split("/")[-1]
    if release in ["releases", "latest"]:
        release = ""

    return release

works nicely for ones with releases

github_repos = [
    "https://github.com/OpenVPN/openvpn",
    "https://github.com/atom/atom",
    "https://github.com/eneshecan/whatsapp-for-linux",
    "https://github.com/igniterealtime/Openfire",
    "https://github.com/igniterealtime/Spark",
    "https://github.com/jitsi/jitsi-meet",
    "https://github.com/jitsi/jitsi-meet-prosody",
    "https://github.com/jitsi/jitsi-meet-turnserver",
    "https://github.com/jitsi/jitsi-meet-web-config",
    "https://github.com/jitsi/jitsi-videobridge2",
    "https://github.com/mumble-voip/mumble",
    "https://github.com/pjsip/pjproject/",
    "https://github.com/pydio/pydio-core",
    "https://github.com/signalapp/Signal-Desktop",
    "https://github.com/sleuthkit/autopsy",
    "https://github.com/sleuthkit/sleuthkit",
    "https://github.com/ultravnc/ultravnc",
]


def get_repo_path(link):
    return link.replace("https://github.com/", "").strip().rstrip("/")


def get_repo_pkg(repo):
    return repo.split("/")[-1]


def get_repos_dict_from_urls(urls):
    packages = {}
    for repo in github_repos:
        pkg_name = get_repo_pkg(get_repo_path(repo))
        pkg_path = get_repo_path(repo)
        packages[pkg_name] = {
            "name": pkg_name,
            "repo_path": pkg_path,
            "url": repo,
            "latest_release": get_github_latest_release(pkg_path),
        }
    return packages


packages = get_repos_dict_from_urls(github_repos)
pprint(packages)

Result:

{'Openfire': {'latest_release': 'v4.7.1',
              'name': 'Openfire',
              'repo_path': 'igniterealtime/Openfire',
              'url': 'https://github.com/igniterealtime/Openfire'},
 'Signal-Desktop': {'latest_release': 'v5.45.0',
                    'name': 'Signal-Desktop',
                    'repo_path': 'signalapp/Signal-Desktop',
                    'url': 'https://github.com/signalapp/Signal-Desktop'},
 'Spark': {'latest_release': 'v3.0.0-beta',
           'name': 'Spark',
           'repo_path': 'igniterealtime/Spark',
           'url': 'https://github.com/igniterealtime/Spark'},
 'atom': {'latest_release': 'v1.60.0',
          'name': 'atom',
          'repo_path': 'atom/atom',
          'url': 'https://github.com/atom/atom'},
 'autopsy': {'latest_release': 'autopsy-4.19.3',
             'name': 'autopsy',
             'repo_path': 'sleuthkit/autopsy',
             'url': 'https://github.com/sleuthkit/autopsy'},
 'jitsi-meet': {'latest_release': 'jitsi-meet_7287',
                'name': 'jitsi-meet',
                'repo_path': 'jitsi/jitsi-meet',
                'url': 'https://github.com/jitsi/jitsi-meet'},
 'jitsi-meet-prosody': {'latest_release': '',
                        'name': 'jitsi-meet-prosody',
                        'repo_path': 'jitsi/jitsi-meet-prosody',
                        'url': 'https://github.com/jitsi/jitsi-meet-prosody'},
 'jitsi-meet-turnserver': {'latest_release': '',
                           'name': 'jitsi-meet-turnserver',
                           'repo_path': 'jitsi/jitsi-meet-turnserver',
                           'url': 'https://github.com/jitsi/jitsi-meet-turnserver'},
 'jitsi-meet-web-config': {'latest_release': '',
                           'name': 'jitsi-meet-web-config',
                           'repo_path': 'jitsi/jitsi-meet-web-config',
                           'url': 'https://github.com/jitsi/jitsi-meet-web-config'},
 'jitsi-videobridge2': {'latest_release': '',
                        'name': 'jitsi-videobridge2',
                        'repo_path': 'jitsi/jitsi-videobridge2',
                        'url': 'https://github.com/jitsi/jitsi-videobridge2'},
 'mumble': {'latest_release': 'v1.4.230',
            'name': 'mumble',
            'repo_path': 'mumble-voip/mumble',
            'url': 'https://github.com/mumble-voip/mumble'},
 'openvpn': {'latest_release': '',
             'name': 'openvpn',
             'repo_path': 'OpenVPN/openvpn',
             'url': 'https://github.com/OpenVPN/openvpn'},
 'pjproject': {'latest_release': '2.12.1',
               'name': 'pjproject',
               'repo_path': 'pjsip/pjproject',
               'url': 'https://github.com/pjsip/pjproject/'},
 'pydio-core': {'latest_release': '',
                'name': 'pydio-core',
                'repo_path': 'pydio/pydio-core',
                'url': 'https://github.com/pydio/pydio-core'},
 'sleuthkit': {'latest_release': 'sleuthkit-4.11.1',
               'name': 'sleuthkit',
               'repo_path': 'sleuthkit/sleuthkit',
               'url': 'https://github.com/sleuthkit/sleuthkit'},
 'ultravnc': {'latest_release': '',
              'name': 'ultravnc',
              'repo_path': 'ultravnc/ultravnc',
              'url': 'https://github.com/ultravnc/ultravnc'},
 'whatsapp-for-linux': {'latest_release': 'v1.4.3',
                        'name': 'whatsapp-for-linux',
                        'repo_path': 'eneshecan/whatsapp-for-linux',
                        'url': 'https://github.com/eneshecan/whatsapp-for-linux'}}

@begin-theadventure
Copy link

begin-theadventure commented Sep 1, 2022

owner_repo='igniterealtime/Spark'; latest_version_url="$(curl -s https://api.github.com/repos/$owner_repo/releases/latest | grep "browser_download_url.*deb" | cut -d : -f 2,3 | tr -d ")"; echo $latest_version_url; basename $latest_version_url ; wget --content-disposition $latest_version_url

Thanks a lot! Although it's possible to just use curl instead of wget:
owner_repo='owner/name'; latest_version_url="$(curl -s https://api.github.com/repos/$owner_repo/releases/latest| grep "browser_download_url.*extension" | cut -d : -f 2,3 | tr -d \")"; echo $latest_version_url; basename $latest_version_url ; curl -X GET $latest_version_url -LO or -Lo name.extension

@meramsey
Copy link

meramsey commented Sep 1, 2022

owner_repo='igniterealtime/Spark'; latest_version_url="$(curl -s https://api.github.com/repos/$owner_repo/releases/latest | grep "browser_download_url.*deb" | cut -d : -f 2,3 | tr -d ")"; echo $latest_version_url; basename $latest_version_url ; wget --content-disposition $latest_version_url

Thanks a lot! Although it's possible to just use curl instead of wget: owner_repo='owner/name'; latest_version_url="$(curl -s https://api.github.com/repos/$owner_repo/releases/latest| grep "browser_download_url.*extensions" | cut -d : -f 2,3 | tr -d \")"; echo $latest_version_url; basename $latest_version_url ; curl -X GET $latest_version_url -LO or -Lo name.extension

Awesome thanks for sharing. That is cool always wondered how to do that in curl but never bothered to look. Appreciate the share and explanation.

@pitoneux
Copy link

pitoneux commented Sep 20, 2022

With the last update, the above breaks for me. I'm now using this:

latest_version_raw="$(curl -s https://api.github.com/repos/$owner_repo/releases | grep -m 1 "html_url" | rev | cut -    d/ -f1 | rev  )"
latest_version="${latest_version_raw%??}" # remove last 2 characters

@rotty3000
Copy link

rotty3000 commented Sep 26, 2022

Here's an example with wget & jq:

REPO="..."
VERSION=$(wget -q -O- https://api.github.com/repos/${REPO}/releases/latest | jq -r '.name')

@roelds
Copy link

roelds commented Jan 1, 2023

Here's an example with wget & jq:

REPO="..."
VERSION=$(wget -q -O- https://api.github.com/repos/${REPO}/releases/latest | jq -r '.name')

that works great! but if prefer tag name instead of release name, use:

.tag_name

@roelds
Copy link

roelds commented Jan 1, 2023

for how todo this on GitLab site, see my gist here:
https://gist.github.com/roelds/b2cd9cc2ba6c7887ddaf6bde2ef7ef50

@fonic
Copy link

fonic commented Aug 26, 2023

One-liner using only curl + grep:
curl --silent "https://api.github.com/repos/${REPO}/releases/latest" | grep -Po "(?<=\"tag_name\": \").*(?=\")"

@roryabraham
Copy link

roryabraham commented Aug 30, 2024

For the gh-cli inclined:

gh release list --exclude-pre-releases --json tagName,isLatest --jq '.[] | select(.isLatest) | .tagName'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment