Skip to content

Instantly share code, notes, and snippets.

@lukechilds
Created August 9, 2016 19:43
Show Gist options
  • Select an option

  • Save lukechilds/a83e1d7127b78fef38c2914c4ececc3c to your computer and use it in GitHub Desktop.

Select an option

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
@coldfix

coldfix commented Sep 4, 2020

Copy link
Copy Markdown

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
Copy Markdown
git ls-remote --refs --sort="version:refname" --tags $repo | cut -d/ -f3-|tail -n1

@artheus

artheus commented Sep 18, 2020

Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

Why not use jq?

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

@CybotTM

CybotTM commented Jan 20, 2021

Copy link
Copy Markdown

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
Copy Markdown

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

juliyvchirkov commented Apr 1, 2021

Copy link
Copy Markdown

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

@FabianK-Dev

Copy link
Copy Markdown

@juliy V. Chirkov Thanks a lot!

@juliyvchirkov

juliyvchirkov commented Apr 18, 2021

Copy link
Copy Markdown

@Ishidres, you're welcome 💁‍♀️

@KEINOS

KEINOS commented Apr 27, 2021

Copy link
Copy Markdown
$ get_latest_release "ipfs/go-ipfs"
v0.8.0

nice. Thanks a lot.

@CHN-STUDENT

Copy link
Copy Markdown

@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

pwillis-els commented Jun 17, 2021

Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

+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

KEINOS commented Oct 5, 2021

Copy link
Copy Markdown

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

wgzhao commented Nov 5, 2021

Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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

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

@meramsey

meramsey commented Jun 3, 2022

Copy link
Copy Markdown

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

begin-theadventure commented Sep 1, 2022

Copy link
Copy Markdown

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

meramsey commented Sep 1, 2022

Copy link
Copy Markdown

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

pitoneux commented Sep 20, 2022

Copy link
Copy Markdown

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

rotty3000 commented Sep 26, 2022

Copy link
Copy Markdown

Here's an example with wget & jq:

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

@roelds

roelds commented Jan 1, 2023

Copy link
Copy Markdown

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

roelds commented Jan 1, 2023

Copy link
Copy Markdown

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

@fonic

fonic commented Aug 26, 2023

Copy link
Copy Markdown

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

@roryabraham

roryabraham commented Aug 30, 2024

Copy link
Copy Markdown

For the gh-cli inclined:

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

@rvdsteege

Copy link
Copy Markdown

@roryabraham also gh CLI, but even shorter:

gh repo view --json latestRelease --jq '.[] | .tagName'

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

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