about summary refs log tree commit diff
path: root/nixpkgs/pkgs/common-updater
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/common-updater')
-rw-r--r--nixpkgs/pkgs/common-updater/generic-updater.nix108
-rw-r--r--nixpkgs/pkgs/common-updater/git-updater.nix21
-rw-r--r--nixpkgs/pkgs/common-updater/http-two-levels-updater.nix19
-rw-r--r--nixpkgs/pkgs/common-updater/nix-update.nix5
-rw-r--r--nixpkgs/pkgs/common-updater/scripts.nix18
-rwxr-xr-xnixpkgs/pkgs/common-updater/scripts/list-archive-two-levels-versions54
-rwxr-xr-xnixpkgs/pkgs/common-updater/scripts/list-git-tags53
-rwxr-xr-xnixpkgs/pkgs/common-updater/scripts/mark-broken106
-rwxr-xr-xnixpkgs/pkgs/common-updater/scripts/update-source-version271
-rw-r--r--nixpkgs/pkgs/common-updater/unstable-updater.nix112
10 files changed, 767 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/common-updater/generic-updater.nix b/nixpkgs/pkgs/common-updater/generic-updater.nix
new file mode 100644
index 000000000000..7a919ff5845e
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/generic-updater.nix
@@ -0,0 +1,108 @@
+{ stdenv, writeScript, coreutils, gnugrep, gnused, common-updater-scripts, nix }:
+
+{ pname
+, version
+, attrPath ? pname
+, versionLister
+, ignoredVersions ? ""
+, rev-prefix ? ""
+, odd-unstable ? false
+, patchlevel-unstable ? false
+}:
+
+let
+  # where to print git commands and debugging messages
+  fileForGitCommands = "update-git-commits.txt";
+
+  # shell script to update package
+  updateScript = writeScript "update-script.sh" ''
+    #! ${stdenv.shell}
+    set -o errexit
+    set -x
+
+    pname="$1"
+    version="$2"
+    attr_path="$3"
+    version_lister="$4"
+    ignored_versions="$5"
+    rev_prefix="$6"
+    odd_unstable="$7"
+    patchlevel_unstable="$8"
+
+    # print header
+    echo "# $pname-$version" >> ${fileForGitCommands}
+
+    function version_is_ignored() {
+      local tag="$1"
+      [ -n "$ignored_versions" ] && grep -E "$ignored_versions" <<< "$tag"
+    }
+
+    function version_is_unstable() {
+      local tag="$1"
+      local enforce="$2"
+      if [ -n "$odd_unstable" -o -n "$enforce" ]; then
+        local minor=$(echo "$tag" | ${gnused}/bin/sed -rne 's,^[0-9]+\.([0-9]+).*,\1,p')
+        if [ $((minor % 2)) -eq 1 ]; then
+          return 0
+        fi
+      fi
+      if [ -n "$patchlevel_unstable" -o -n "$enforce" ]; then
+        local patchlevel=$(echo "$tag" | ${gnused}/bin/sed -rne 's,^[0-9]+\.[0-9]+\.([0-9]+).*$,\1,p')
+        if ((patchlevel >= 90)); then
+          return 0
+        fi
+      fi
+      return 1
+    }
+
+    tags=$($version_lister --pname=${pname} --file="${fileForGitCommands}") || exit 1
+
+    # print available tags
+    for tag in $tags; do
+        echo "#	found $pname version: $tag" >> ${fileForGitCommands}
+    done
+
+    # cut any revision prefix not used in the NixOS package version
+    if [ -n "$rev_prefix" ]; then
+      tags=$(echo "$tags" | ${gnugrep}/bin/grep "^$rev_prefix")
+      tags=$(echo "$tags" | ${gnused}/bin/sed -e "s,^$rev_prefix,,")
+    fi
+    tags=$(echo "$tags" | ${gnugrep}/bin/grep "^[0-9]")
+
+    # sort the tags in decreasing order
+    tags=$(echo "$tags" | ${coreutils}/bin/sort --reverse --version-sort)
+
+    # find the newest tag
+    # do not consider development versions
+    for latest_tag in $tags; do
+      if version_is_ignored "$latest_tag"; then
+        echo "#   skip ignored version: $pname-$latest_tag" >> ${fileForGitCommands}
+        latest_tag=
+      elif version_is_unstable "$latest_tag"; then
+        echo "#   skip development version: $pname-$latest_tag" >> ${fileForGitCommands}
+        latest_tag=
+      else
+        if version_is_unstable "$latest_tag" "enforce"; then
+          echo "#   use potential development version: $pname-$latest_tag" >> ${fileForGitCommands}
+        fi
+        break
+      fi
+    done
+
+    if [ -n "$latest_tag" ]; then
+      # print commands to commit the changes
+      if [ "$version" != "$latest_tag" ]; then
+        pfile=$(EDITOR=echo ${nix}/bin/nix edit --extra-experimental-features nix-command -f. "$attr_path")
+        echo "   git add $pfile " >> ${fileForGitCommands}
+        echo "   git commit -m '$attr_path: $version -> $latest_tag'" >> ${fileForGitCommands}
+      fi
+
+      # update the nix expression
+      ${common-updater-scripts}/bin/update-source-version "$attr_path" "$latest_tag"
+    fi
+
+    echo "" >> ${fileForGitCommands}
+  '';
+
+in
+[ updateScript pname version attrPath versionLister ignoredVersions rev-prefix odd-unstable patchlevel-unstable ]
diff --git a/nixpkgs/pkgs/common-updater/git-updater.nix b/nixpkgs/pkgs/common-updater/git-updater.nix
new file mode 100644
index 000000000000..b68f4a29011d
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/git-updater.nix
@@ -0,0 +1,21 @@
+{ lib
+, genericUpdater
+, common-updater-scripts
+}:
+
+{ pname
+, version
+, attrPath ? pname
+, ignoredVersions ? ""
+, rev-prefix ? ""
+, odd-unstable ? false
+, patchlevel-unstable ? false
+# an explicit url is needed when src.meta.homepage or src.url don't
+# point to a git repo (eg. when using fetchurl, fetchzip, ...)
+, url ? null
+}:
+
+genericUpdater {
+  inherit pname version attrPath ignoredVersions rev-prefix odd-unstable patchlevel-unstable;
+  versionLister = "${common-updater-scripts}/bin/list-git-tags ${lib.optionalString (url != null) "--url=${url}"}";
+}
diff --git a/nixpkgs/pkgs/common-updater/http-two-levels-updater.nix b/nixpkgs/pkgs/common-updater/http-two-levels-updater.nix
new file mode 100644
index 000000000000..f9e1e1b7204a
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/http-two-levels-updater.nix
@@ -0,0 +1,19 @@
+{ lib
+, genericUpdater
+, common-updater-scripts
+}:
+
+{ pname
+, version
+, attrPath ? pname
+, ignoredVersions ? ""
+, rev-prefix ? ""
+, odd-unstable ? false
+, patchlevel-unstable ? false
+, url ? null
+}:
+
+genericUpdater {
+  inherit pname version attrPath ignoredVersions rev-prefix odd-unstable patchlevel-unstable;
+  versionLister = "${common-updater-scripts}/bin/list-archive-two-levels-versions ${lib.optionalString (url != null) "--url=${url}"}";
+}
diff --git a/nixpkgs/pkgs/common-updater/nix-update.nix b/nixpkgs/pkgs/common-updater/nix-update.nix
new file mode 100644
index 000000000000..13b85bf52fff
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/nix-update.nix
@@ -0,0 +1,5 @@
+{ nix-update }:
+
+{ attrPath }:
+
+[ "${nix-update}/bin/nix-update" attrPath ]
diff --git a/nixpkgs/pkgs/common-updater/scripts.nix b/nixpkgs/pkgs/common-updater/scripts.nix
new file mode 100644
index 000000000000..26c77e876362
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/scripts.nix
@@ -0,0 +1,18 @@
+{ lib, stdenv, makeWrapper, coreutils, gnused, gnugrep, diffutils, nix, git, jq }:
+
+stdenv.mkDerivation {
+  name = "common-updater-scripts";
+
+  nativeBuildInputs = [ makeWrapper ];
+
+  dontUnpack = true;
+
+  installPhase = ''
+    mkdir -p $out/bin
+    cp ${./scripts}/* $out/bin
+
+    for f in $out/bin/*; do
+      wrapProgram $f --prefix PATH : ${lib.makeBinPath [ coreutils gnused gnugrep nix diffutils git jq ]}
+    done
+  '';
+}
diff --git a/nixpkgs/pkgs/common-updater/scripts/list-archive-two-levels-versions b/nixpkgs/pkgs/common-updater/scripts/list-archive-two-levels-versions
new file mode 100755
index 000000000000..4263a9de3ca3
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/scripts/list-archive-two-levels-versions
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+# lists all available versions listed for a package in a site (http)
+
+archive=""  # archive url
+pname=""  # package name
+file=""  # file for writing debugging information
+
+while (( $# > 0 )); do
+    flag="$1"
+    shift 1
+    case "$flag" in
+        --url=*)
+            archive="${flag#*=}"
+            ;;
+        --pname=*)
+            pname="${flag#*=}"
+            ;;
+        --file=*)
+            file="${flag#*=}"
+            ;;
+        *)
+            echo "$0: unknown option ‘${flag}’"
+            exit 1
+            ;;
+    esac
+done
+
+# by default set url to the base dir of the first url in src.urls
+if [[ -z "$archive" ]]; then
+    archive="$(nix-instantiate $systemArg --eval -E \
+                   "with import ./. {}; dirOf (dirOf (lib.head $UPDATE_NIX_ATTR_PATH.src.urls))" \
+            | tr -d '"')"
+fi
+
+if [[ -z "$pname" ]]; then
+    pname="$UPDATE_NIX_ATTR_PATH"
+fi
+
+# print a debugging message
+if [[ -n "$file" ]]; then
+    echo "# Listing versions for '$pname' at $archive" >> $file
+fi
+
+# list all major-minor versions from archive
+tags1=$(curl -sS "$archive/")
+tags1=$(echo "$tags1" | sed -rne 's,^<a href="([0-9]+\.[0-9]+)/">.*,\1,p')
+
+# print available versions
+for tag in $tags1; do
+    tags2=$(curl -sS "$archive/$tag/")
+    tags2=$(echo "$tags2" | sed -rne "s,^<a href=\"$pname-([0-9.]+)\\.[^0-9].*\">.*,\\1,p")
+    echo "$tags2"
+done
diff --git a/nixpkgs/pkgs/common-updater/scripts/list-git-tags b/nixpkgs/pkgs/common-updater/scripts/list-git-tags
new file mode 100755
index 000000000000..86b4949f055d
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/scripts/list-git-tags
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+
+# lists all available tags from a git repository
+
+echo "# pname=$UPDATE_NIX_ATTR_PATH" > /tmp/test.txt
+
+url="" # git repository url
+pname="" # package name
+file="" # file for writing debugging information
+
+while (( $# > 0 )); do
+    flag="$1"
+    shift 1
+    case "$flag" in
+        --url=*)
+            url="${flag#*=}"
+            ;;
+        --pname=*)
+            pname="${flag#*=}"
+            ;;
+        --file=*)
+            file="${flag#*=}"
+            ;;
+        *)
+            echo "$0: unknown option ‘${flag}’"
+            exit 1
+            ;;
+    esac
+done
+
+# By default we set url to src.url or src.meta.homepage
+if [[ -z "$url" ]]; then
+    url="$(nix-instantiate $systemArg --eval -E \
+               "with import ./. {}; $UPDATE_NIX_ATTR_PATH.src.meta.homepage or $UPDATE_NIX_ATTR_PATH.src.url" \
+        | tr -d '"')"
+fi
+
+if [[ -z "$pname" ]]; then
+    pname="$UPDATE_NIX_ATTR_PATH"
+fi
+
+# print a debugging message
+if [[ -n "$file" ]]; then
+    echo "# Listing tags for '$pname' at $url" >> $file
+fi
+
+# list all tags from the remote repository
+tags=$(git ls-remote --tags --refs "$url")
+
+# keep only the version part of the tag
+tags=$(echo "$tags" | cut --delimiter=/ --field=3)
+
+echo "$tags"
diff --git a/nixpkgs/pkgs/common-updater/scripts/mark-broken b/nixpkgs/pkgs/common-updater/scripts/mark-broken
new file mode 100755
index 000000000000..f132c86c5e8b
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/scripts/mark-broken
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+
+# This script is meant to be used to mark failing hydra builds as broken in the meta attrs
+# To use the script, you should pass the list of failing attrs as arguments to the script.
+#
+# Example: `cat failing-attrs | xargs ./pkgs/common-updater/scripts/mark-broken`
+#
+# Generating a list of failing attrs: (this should be improved at a later date)
+#   - Go to the most recent hydra evaluation with all builds completed
+#   - Select the "builds still failing" tab
+#   - Highlight and select all packages, should be prefixed with `nixpkgs.`
+#   - Use regex and editor foo to leave only the attr names
+#   - Use the above example command to then execute the script
+#
+# OTHER NOTES:
+#   - The `denyFileList` and `denyAttrList` will likely need to be updated slightly
+#     to align with the conventions used in nixpkgs at execution time
+#   - Any attrs which failed for any reason will be written to `failed-marks.txt`.
+#     Those attrs will likely need manual attention as disablement will likely be conditional.
+
+scriptName=mark-broken # do not use the .wrapped name
+
+failMark() {
+        local attr=$1
+        shift 1
+
+        echo "$attr: $@" >&2
+        echo $attr >> failed-marks.txt
+}
+
+usage() {
+    echo "Usage: $scriptName <attrs>"
+}
+
+if (( "${#@}" < 1 )); then
+    echo "$scriptName: Too few arguments"
+    usage
+    exit 1
+fi
+
+# in case we resolve to an auto-generated file, just skip these entries
+denyFileList=(
+        node-packages.nix # node, it will mark all node packages as broken
+        generic-builder.nix # haskell, it will mark all haskell packages as broken
+)
+
+# ignore older versions of parameterized packages sets, these likely need
+# to be conditionally disabled
+denyAttrList=(
+        python27Packages
+        python37Packages
+        libsForQt512
+        linuxPackages_
+        rubyPackages_
+)
+
+function attemptToMarkBroken() {
+        local attr=$1
+
+        # skip likely to be noisy attrs
+        for badAttr in ${denyAttrList[@]};do
+                if [[ $attr =~ $badAttr ]]; then
+                        failMark $attr "attr contained $badAttr, skipped."
+                        return
+                fi
+        done
+
+        nixFile=$(nix-instantiate --eval --json -E "with import ./. {}; (builtins.unsafeGetAttrPos \"description\" $attr.meta).file" 2>/dev/null | jq -r .)
+        if [[ ! -f "$nixFile" ]]; then
+            failMark $attr "Couldn't locate correct file"
+            return
+        fi
+
+        # skip files which are auto-generated
+        for filename in ${denyFileList[@]};do
+                if [[ "$filename" == $(basename $nixFile) ]]; then
+                        failMark $attr "filename matched $filename, skipped."
+                        return
+                fi
+        done
+
+        # Insert broken attribute
+        sed -i.bak "$nixFile" -r \
+          -e "/^\s*broken\s*=.*$/d" \
+          -e "s/(\s*)meta\s*=.*\{/&\n\1  broken = true;/"
+
+        if cmp -s "$nixFile" "$nixFile.bak"; then
+            mv "$nixFile.bak" "$nixFile"
+            failMark $attr "Does it have a meta attribute?"
+            return
+        fi
+
+        # broken should evaluate to true in any case now
+        markedSuccessfully=$(nix-instantiate --eval -E "with import ./. {}; $attr.meta.broken")
+        if [[ "$markedSuccessfully" != "true" ]]; then
+            mv "$nixFile.bak" "$nixFile"
+            failMark $attr "$attr.meta.broken doesn't evaluate to true."
+            return
+        fi
+
+        rm -f "$nixFile.bak"
+}
+
+for attr in $@; do
+        attemptToMarkBroken $attr
+done
diff --git a/nixpkgs/pkgs/common-updater/scripts/update-source-version b/nixpkgs/pkgs/common-updater/scripts/update-source-version
new file mode 100755
index 000000000000..88cf7d459a72
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/scripts/update-source-version
@@ -0,0 +1,271 @@
+#!/usr/bin/env bash
+set -e
+
+scriptName=update-source-version # do not use the .wrapped name
+
+die() {
+    echo "$scriptName: error: $1" >&2
+    exit 1
+}
+
+usage() {
+    echo "Usage: $scriptName <attr> <version> [<new-source-hash>] [<new-source-url>]"
+    echo "                              [--version-key=<version-key>] [--source-key=<source-key>]"
+    echo "                              [--system=<system>] [--file=<file-to-update>] [--rev=<revision>]"
+    echo "                              [--ignore-same-hash] [--print-changes]"
+}
+
+args=()
+
+for arg in "$@"; do
+    case $arg in
+        --system=*)
+            system="${arg#*=}"
+            systemArg="--system ${arg#*=}"
+        ;;
+        --version-key=*)
+            versionKey="${arg#*=}"
+        ;;
+        --source-key=*)
+            sourceKey="${arg#*=}"
+        ;;
+        --file=*)
+            nixFile="${arg#*=}"
+            if [[ ! -f "$nixFile" ]]; then
+                die "Could not find provided file $nixFile"
+            fi
+        ;;
+        --rev=*)
+            newRevision="${arg#*=}"
+        ;;
+        --ignore-same-hash)
+            ignoreSameHash="true"
+        ;;
+        --print-changes)
+            printChanges="true"
+        ;;
+        --help)
+            usage
+            exit 0
+        ;;
+        --*)
+            echo "$scriptName: Unknown argument: $arg"
+            usage
+            exit 1
+        ;;
+        *)
+            args["${#args[*]}"]=$arg
+        ;;
+    esac
+done
+
+attr=${args[0]}
+newVersion=${args[1]}
+newHash=${args[2]}
+newUrl=${args[3]}
+
+# Third-party repositories might not accept arguments in their default.nix.
+importTree="(let tree = import ./.; in if builtins.isFunction tree then tree {} else tree)"
+
+if (( "${#args[*]}" < 2 )); then
+    echo "$scriptName: Too few arguments"
+    usage
+    exit 1
+fi
+
+if (( "${#args[*]}" > 4 )); then
+    echo "$scriptName: Too many arguments"
+    usage
+    exit 1
+fi
+
+if [[ -z "$versionKey" ]]; then
+    versionKey=version
+fi
+
+if [[ -z "$sourceKey" ]]; then
+    sourceKey=src
+fi
+
+# Allow finding packages among flake outputs in repos using flake-compat.
+pname=$(nix-instantiate $systemArg --eval --strict -A "$attr.name" || echo)
+if [[ -z "$pname" ]]; then
+    if [[ -z "$system" ]]; then
+        system=$(nix-instantiate --eval -E 'builtins.currentSystem' | tr -d '"')
+    fi
+
+    pname=$(nix-instantiate $systemArg --eval --strict -A "packages.$system.$attr.name" || echo)
+    if [[ -n "$pname" ]]; then
+        attr="packages.$system.$attr"
+    else
+        pname=$(nix-instantiate $systemArg --eval --strict -A "legacyPackages.$system.$attr.name" || echo)
+        if [[ -n "$pname" ]]; then
+            attr="legacyPackages.$system.$attr"
+        else
+            die "Could not find attribute '$attr'!"
+        fi
+    fi
+fi
+
+if [[ -z "$nixFile" ]]; then
+    nixFile=$(nix-instantiate $systemArg --eval --strict -A "$attr.meta.position" | sed -re 's/^"(.*):[0-9]+"$/\1/')
+    if [[ ! -f "$nixFile" ]]; then
+        die "Couldn't evaluate '$attr.meta.position' to locate the .nix file!"
+    fi
+
+    # flake-compat will return paths in the Nix store, we need to correct for that.
+    possiblyOutPath=$(nix-instantiate $systemArg --eval -E "with $importTree; outPath" 2>/dev/null | tr -d '"')
+    if [[ -n "$possiblyOutPath" ]]; then
+        outPathEscaped=$(echo "$possiblyOutPath" | sed 's#[$^*\\.[|]#\\&#g')
+        pwdEscaped=$(echo "$PWD" | sed 's#[$^*\\.[|]#\\&#g')
+        nixFile=$(echo "$nixFile" | sed "s|^$outPathEscaped|$pwdEscaped|")
+    fi
+fi
+
+oldHashAlgo=$(nix-instantiate $systemArg --eval --strict -A "$attr.$sourceKey.drvAttrs.outputHashAlgo" | tr -d '"')
+oldHash=$(nix-instantiate $systemArg --eval --strict -A "$attr.$sourceKey.drvAttrs.outputHash" | tr -d '"')
+
+if [[ -z "$oldHashAlgo" || -z "$oldHash" ]]; then
+    die "Couldn't evaluate old source hash from '$attr.$sourceKey'!"
+fi
+
+if [[ $(grep --count "$oldHash" "$nixFile") != 1 ]]; then
+    die "Couldn't locate old source hash '$oldHash' (or it appeared more than once) in '$nixFile'!"
+fi
+
+oldUrl=$(nix-instantiate $systemArg --eval -E "with $importTree; builtins.elemAt ($attr.$sourceKey.drvAttrs.urls or [ $attr.$sourceKey.url ]) 0" | tr -d '"')
+
+if [[ -z "$oldUrl" ]]; then
+    die "Couldn't evaluate source url from '$attr.$sourceKey'!"
+fi
+
+oldVersion=$(nix-instantiate $systemArg --eval -E "with $importTree; $attr.${versionKey} or (builtins.parseDrvName $attr.name).version" | tr -d '"')
+
+if [[ -z "$oldVersion" ]]; then
+    die "Couldn't find out the old version of '$attr'!"
+fi
+
+if [[ "$oldVersion" = "$newVersion" ]]; then
+    echo "$scriptName: New version same as old version, nothing to do." >&2
+    if [ -n "$printChanges" ]; then
+        printf '[]\n'
+    fi
+    exit 0
+fi
+
+if [[ -n "$newRevision" ]]; then
+    oldRevision=$(nix-instantiate $systemArg --eval -E "with $importTree; $attr.$sourceKey.rev" | tr -d '"')
+    if [[ -z "$oldRevision" ]]; then
+        die "Couldn't evaluate source revision from '$attr.$sourceKey'!"
+    fi
+fi
+
+# Escape regex metacharacter that are allowed in store path names
+oldVersionEscaped=$(echo "$oldVersion" | sed -re 's|[.+]|\\&|g')
+oldUrlEscaped=$(echo "$oldUrl" | sed -re 's|[${}.+]|\\&|g')
+
+if [[ $(grep --count --extended-regexp "^\s*(let\b)?\s*$versionKey\s*=\s*\"$oldVersionEscaped\"" "$nixFile") = 1 ]]; then
+    pattern="/\b$versionKey\b\s*=/ s|\"$oldVersionEscaped\"|\"$newVersion\"|"
+elif [[ $(grep --count --extended-regexp "^\s*(let\b)?\s*name\s*=\s*\"[^\"]+-$oldVersionEscaped\"" "$nixFile") = 1 ]]; then
+    pattern="/\bname\b\s*=/ s|-$oldVersionEscaped\"|-$newVersion\"|"
+else
+    die "Couldn't figure out where out where to patch in new version in '$attr'!"
+fi
+
+if [[ "$oldHash" =~ ^(sha256|sha512)[:-] ]]; then
+    # Handle the possible SRI-style hash attribute (in the form ${type}${separator}${hash})
+    # True SRI uses dash as a separator and only supports base64, whereas Nix’s SRI-style format uses a colon and supports all the same encodings like regular hashes (16/32/64).
+    # To keep this program reasonably simple, we will upgrade Nix’s format to SRI.
+    oldHashAlgo="${BASH_REMATCH[1]}"
+    sri=true
+elif [[ "$oldHashAlgo" = "null" ]]; then
+    # Some fetcher functions support SRI-style `hash` attribute in addition to legacy type-specific attributes. When `hash` is used `outputHashAlgo` is null so let’s complain when SRI-style hash value was not detected.
+    die "Unable to figure out hashing scheme from '$oldHash' in '$attr'!"
+fi
+
+case "$oldHashAlgo" in
+    # Lengths of hex-encoded hashes
+    sha256) hashLength=64 ;;
+    sha512) hashLength=128 ;;
+    *) die "Unhandled hash algorithm '$oldHashAlgo' in '$attr'!" ;;
+esac
+
+# Make a temporary all-zeroes hash of $hashLength characters
+tempHash=$(printf '%0*d' "$hashLength" 0)
+
+if [[ -n "$sri" ]]; then
+    # SRI hashes only support base64
+    # SRI hashes need to declare the hash type as part of the hash
+    tempHash="$(nix hash to-sri --type "$oldHashAlgo" "$tempHash" 2>/dev/null \
+        || nix to-sri --type "$oldHashAlgo" "$tempHash" 2>/dev/null)" \
+        || die "Failed to convert hash to SRI representation!"
+fi
+
+# Escape regex metacharacter that are allowed in hashes (+)
+oldHashEscaped=$(echo "$oldHash" | sed -re 's|[+]|\\&|g')
+tempHashEscaped=$(echo "$tempHash" | sed -re 's|[+]|\\&|g')
+
+# Replace new version
+sed -i.bak "$nixFile" -re "$pattern"
+if cmp -s "$nixFile" "$nixFile.bak"; then
+    die "Failed to replace version '$oldVersion' to '$newVersion' in '$attr'!"
+fi
+
+# Replace new URL
+if [[ -n "$newUrl" ]]; then
+    sed -i "$nixFile" -re "s|\"$oldUrlEscaped\"|\"$newUrl\"|"
+
+    if cmp -s "$nixFile" "$nixFile.bak"; then
+        die "Failed to replace source URL '$oldUrl' to '$newUrl' in '$attr'!"
+    fi
+fi
+
+sed -i "$nixFile" -re "s|\"$oldHashEscaped\"|\"$tempHash\"|"
+if cmp -s "$nixFile" "$nixFile.bak"; then
+    die "Failed to replace source hash of '$attr' to a temporary hash!"
+fi
+
+# Replace new revision, if given
+if [[ -n "$newRevision" ]]; then
+    sed -i "$nixFile" -re "s|\"$oldRevision\"|\"$newRevision\"|"
+
+    if cmp -s "$nixFile" "$nixFile.bak"; then
+        die "Failed to replace source revision '$oldRevision' to '$newRevision' in '$attr'!"
+    fi
+fi
+
+# If new hash not given on the command line, recalculate it ourselves.
+if [[ -z "$newHash" ]]; then
+    nix-build $systemArg --no-out-link -A "$attr.$sourceKey" 2>"$attr.fetchlog" >/dev/null || true
+    # FIXME: use nix-build --hash here once https://github.com/NixOS/nix/issues/1172 is fixed
+    newHash=$(sed '1,/hash mismatch in fixed-output derivation/d' "$attr.fetchlog" | grep --perl-regexp --only-matching 'got: +.+[:-]\K.+')
+
+    if [[ -n "$sri" ]]; then
+        # nix-build preserves the hashing scheme so we can just convert the result to SRI using the old type
+        newHash="$(nix hash to-sri --type "$oldHashAlgo" "$newHash" 2>/dev/null \
+            || nix to-sri --type "$oldHashAlgo" "$newHash" 2>/dev/null)" \
+            || die "Failed to convert hash to SRI representation!"
+    fi
+fi
+
+if [[ -z "$newHash" ]]; then
+    cat "$attr.fetchlog" >&2
+    die "Couldn't figure out new hash of '$attr.$sourceKey'!"
+fi
+
+if [[ -z "${ignoreSameHash}" && "$oldVersion" != "$newVersion" && "$oldHash" = "$newHash" ]]; then
+    mv "$nixFile.bak" "$nixFile"
+    die "Both the old and new source hashes of '$attr.$sourceKey' were equivalent. Please fix the package's source URL to be dependent on '\${version}'!"
+fi
+
+sed -i "$nixFile" -re "s|\"$tempHashEscaped\"|\"$newHash\"|"
+if cmp -s "$nixFile" "$nixFile.bak"; then
+    die "Failed to replace temporary source hash of '$attr' to the final source hash!"
+fi
+
+rm -f "$nixFile.bak"
+rm -f "$attr.fetchlog"
+
+if [ -n "$printChanges" ]; then
+    printf '[{"attrPath":"%s","oldVersion":"%s","newVersion":"%s","files":["%s"]}]\n' "$attr" "$oldVersion" "$newVersion" "$nixFile"
+fi
diff --git a/nixpkgs/pkgs/common-updater/unstable-updater.nix b/nixpkgs/pkgs/common-updater/unstable-updater.nix
new file mode 100644
index 000000000000..2be065f410e7
--- /dev/null
+++ b/nixpkgs/pkgs/common-updater/unstable-updater.nix
@@ -0,0 +1,112 @@
+{ lib
+, writeShellScript
+, coreutils
+, git
+, nix
+, common-updater-scripts
+}:
+
+# This is an updater for unstable packages that should always use the latest
+# commit.
+{ url ? null # The git url, if empty it will be set to src.url
+, branch ? null
+, stableVersion ? false # Use version format according to RFC 107 (i.e. LAST_TAG+date=YYYY-MM-DD)
+, tagPrefix ? "" # strip this prefix from a tag name when using stable version
+}:
+
+let
+  updateScript = writeShellScript "unstable-update-script.sh" ''
+    set -ex
+
+    url=""
+    branch=""
+    use_stable_version=""
+    tag_prefix=""
+
+    while (( $# > 0 )); do
+        flag="$1"
+        shift 1
+        case "$flag" in
+          --url=*)
+            url="''${flag#*=}"
+            ;;
+          --branch=*)
+            branch="''${flag#*=}"
+            ;;
+          --use-stable-version)
+            use_stable_version=1
+            ;;
+          --tag-prefix=*)
+            tag_prefix="''${flag#*=}"
+            ;;
+          *)
+            echo "$0: unknown option ‘''${flag}’"
+            exit 1
+            ;;
+        esac
+    done
+
+    # By default we set url to src.url
+    if [[ -z "$url" ]]; then
+        url="$(${nix}/bin/nix-instantiate $systemArg --eval -E \
+                   "with import ./. {}; $UPDATE_NIX_ATTR_PATH.src.gitRepoUrl" \
+            | tr -d '"')"
+    fi
+
+    # Get info about HEAD from a shallow git clone
+    tmpdir="$(${coreutils}/bin/mktemp -d)"
+
+    cloneArgs=(
+      --bare
+      --depth=1
+    )
+
+    if [[ -n "$branch" ]]; then
+        cloneArgs+=(--branch="$branch")
+    fi
+
+    ${git}/bin/git clone "''${cloneArgs[@]}" "$url" "$tmpdir"
+
+    pushd "$tmpdir"
+    commit_date="$(${git}/bin/git show -s --pretty='format:%cs')"
+    commit_sha="$(${git}/bin/git show -s --pretty='format:%H')"
+    if [[ -z "$use_stable_version" ]]; then
+        new_version="unstable-$commit_date"
+    else
+        depth=100
+        while (( $depth < 10000 )); do
+            last_tag="$(${git}/bin/git describe --tags --abbrev=0 2> /dev/null || true)"
+            if [[ -n "$last_tag" ]]; then
+                break
+            fi
+            ${git}/bin/git fetch --depth="$depth" --tags
+            depth=$(( $depth * 2 ))
+        done
+        if [[ -z "$last_tag" ]]; then
+            echo "Cound not found a tag within last 10000 commits" > /dev/stderr
+            exit 1
+        fi
+        if [[ -n "$tag_prefix" ]]; then
+          last_tag="''${last_tag#$tag_prefix}"
+        fi
+        new_version="$last_tag+date=$commit_date"
+    fi
+    popd
+    # ${coreutils}/bin/rm -rf "$tmpdir"
+
+    # update the nix expression
+    ${common-updater-scripts}/bin/update-source-version \
+        "$UPDATE_NIX_ATTR_PATH" \
+        "$new_version" \
+        --rev="$commit_sha"
+  '';
+
+in [
+  updateScript
+  "--url=${builtins.toString url}"
+] ++ lib.optionals (branch != null) [
+  "--branch=${branch}"
+] ++ lib.optionals stableVersion [
+  "--use-stable-version"
+  "--tag-prefix=${tagPrefix}"
+]