First throw at release channels
We start out with master (Stable), rc/maintenance (Maintenance) and rc/devel (Devel)
This commit is contained in:
parent
0a78c92407
commit
b5bc03e711
8 changed files with 263 additions and 61 deletions
|
|
@ -7,10 +7,10 @@
|
|||
# The file is processed from top to bottom, the first matching line wins. If <tag> or <reference commit> are left out,
|
||||
# the lookup table does not apply to the matched branches
|
||||
|
||||
# master, prerelease and rc shall not use the lookup table, only tags
|
||||
# master and rc shall not use the lookup table, only tags
|
||||
master
|
||||
rc/.*
|
||||
prerelease
|
||||
rc
|
||||
|
||||
# neither should disconnected checkouts, e.g. 'git checkout <tag>'
|
||||
HEAD
|
||||
|
|
|
|||
|
|
@ -299,14 +299,9 @@ There are three main branches in OctoPrint:
|
|||
|
||||
* `master`: The master branch always contains the current stable release. It
|
||||
is *only* updated on new releases. Will have a version number following
|
||||
the scheme `x.y.z` (e.g. `1.2.9`) or - if it's absolutely necessary to
|
||||
add a commit after release to this branch - `x.y.z.post<commits since x.y.z>`
|
||||
the scheme `<x>.<y>.<z>` (e.g. `1.2.9`) or - if it's absolutely necessary to
|
||||
add a commit after release to this branch - `<x>.<y>.<z>.post<commits since x.y.z>`
|
||||
(e.g. `1.2.9.post1`).
|
||||
* `prerelease`: This branch is only used during the short period where a
|
||||
future release has "graduated" from the `maintenance` branch and is already
|
||||
tagged, but still marked on Github as a pre-release. This is mostly used for
|
||||
update testing just before new releases. Version number follows the scheme
|
||||
`x.y.z` (e.g. `1.2.9`), just like the `master` branch.
|
||||
* `maintenance`: Improvements and fixes of the current release that make up
|
||||
the next release go here. More or less continously updated. You can consider
|
||||
this a preview of the next release version. It should be very stable at all
|
||||
|
|
@ -314,7 +309,7 @@ There are three main branches in OctoPrint:
|
|||
next stable release, so if you want to help out development, running the
|
||||
`maintenance` branch and reporting back anything you find is a very good way
|
||||
to do that. Will usually have a version number following the scheme
|
||||
`x.y.z+1.dev.<commits since increase of z>` for an OctoPrint version of `x.y.z`
|
||||
`<x>.<y>.<z+1>.dev<commits since increase of z>` for an OctoPrint version of `<x>.<y>.<z>`
|
||||
(e.g. `1.2.10.dev12`).
|
||||
* `devel`: Ongoing development of new features that will go into the next bigger
|
||||
release (MINOR version number increases) will happen on this branch. Usually
|
||||
|
|
@ -322,8 +317,16 @@ There are three main branches in OctoPrint:
|
|||
temporarily. Can be considered the "bleeding edge". All PRs should target
|
||||
*this* branch. Important improvements and fixes from PRs here are backported to
|
||||
`maintenance` as needed. Will usually have a version number following the
|
||||
scheme `x.y+1.0.dev<commits since increase of y>` for an OctoPrint version
|
||||
of `x.y.z` (e.g. `1.3.0.dev123`).
|
||||
scheme `<x>.<y+1>.0.dev<commits since increase of y>` for a current OctoPrint version
|
||||
of `<x>.<y>.<z>` (e.g. `1.3.0.dev123`).
|
||||
* `rc/maintenance`: This branch is reserved for future releases that have graduated from
|
||||
the `maintenance` branch and are now being pushed on the "Maintenance"
|
||||
pre release channel for further testing. Version number follows the scheme
|
||||
`<x>.<y>.<z>rc<n>` (e.g. `1.2.9rc1`).
|
||||
* `rc/devel`: This branch is reserved for future releases that have graduated from
|
||||
the `devel` branch and are now being pushed on the "Devel" pre release channel
|
||||
for further testing. Version number follows the scheme `<x>.<y+1>.0rc<n>` (e.g. `1.3.0rc1`)
|
||||
for a current stable OctoPrint version of `<x>.<y>.<z>`.
|
||||
|
||||
Additionally, from time to time you might see other branches pop up in the repository.
|
||||
Those usually have one of the following prefixes:
|
||||
|
|
@ -334,9 +337,6 @@ Those usually have one of the following prefixes:
|
|||
`maintenance` and `devel` branches.
|
||||
* `dev/...` or `feature/...`: New functionality under development that is to be merged
|
||||
into the `devel` branch.
|
||||
* `rc`: A branch similar in nature to the `prerelease` branch, only that it will be
|
||||
used to provide current release candidates of the next stable version to be derived
|
||||
from the `devel` branch.
|
||||
|
||||
There is also the `gh-pages` branch, which holds OctoPrint's web page, and a couple of
|
||||
older development branches that are slowly being migrated or deleted.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from . import version_checks, updaters, exceptions, util
|
|||
|
||||
|
||||
from octoprint.server.util.flask import restricted_access
|
||||
from octoprint.server import admin_permission, VERSION, REVISION
|
||||
from octoprint.server import admin_permission, VERSION, REVISION, BRANCH
|
||||
from octoprint.util import dict_merge
|
||||
import octoprint.settings
|
||||
|
||||
|
|
@ -138,19 +138,26 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
#~~ SettingsPlugin API
|
||||
|
||||
def get_settings_defaults(self):
|
||||
update_script = os.path.join(self._basefolder, "scripts", "update-octoprint.py")
|
||||
return {
|
||||
"checks": {
|
||||
"octoprint": {
|
||||
"type": "github_release",
|
||||
"user": "foosel",
|
||||
"repo": "OctoPrint",
|
||||
"update_script": "{{python}} \"{update_script}\" --python=\"{{python}}\" \"{{folder}}\" {{target}}".format(update_script=os.path.join(self._basefolder, "scripts", "update-octoprint.py")),
|
||||
"update_script": "{{python}} \"{update_script}\" --branch={{branch}} --force={{force}} \"{{folder}}\" {{target}}".format(update_script=update_script),
|
||||
"restart": "octoprint"
|
||||
},
|
||||
},
|
||||
"pip_command": None,
|
||||
"check_providers": {},
|
||||
|
||||
"octoprint_stable_branch": dict(branch="master", name="Stable"),
|
||||
"octoprint_prerelease_branches": [
|
||||
dict(branch="rc/maintenance", name="Maintenance"),
|
||||
dict(branch="rc/devel", name="Devel")
|
||||
],
|
||||
|
||||
"cache_ttl": 24 * 60,
|
||||
}
|
||||
|
||||
|
|
@ -159,6 +166,19 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
if "checks" in data:
|
||||
del data["checks"]
|
||||
|
||||
branch_mappings = []
|
||||
if "octoprint_stable_branch" in data:
|
||||
branch_mappings.append(data["octoprint_stable_branch"])
|
||||
del data["octoprint_stable_branch"]
|
||||
if "octoprint_prerelease_branches" in data:
|
||||
for mapping in data["octoprint_prerelease_branches"]:
|
||||
branch_mappings.append(mapping)
|
||||
del data["octoprint_prerelease_branches"]
|
||||
data["octoprint_branch_mappings"] = branch_mappings
|
||||
|
||||
if "check_providers" in data:
|
||||
del data["check_providers"]
|
||||
|
||||
checks = self._get_configured_checks()
|
||||
if "octoprint" in checks:
|
||||
if "checkout_folder" in checks["octoprint"]:
|
||||
|
|
@ -168,6 +188,12 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
else:
|
||||
data["octoprint_checkout_folder"] = None
|
||||
data["octoprint_type"] = checks["octoprint"].get("type", None)
|
||||
|
||||
data["octoprint_release_channel"] = self._settings.get(["octoprint_stable_branch", "branch"])
|
||||
if checks["octoprint"].get("prerelease", False):
|
||||
channel = checks["octoprint"].get("prerelease_channel", BRANCH)
|
||||
if channel in [x["branch"] for x in self._settings.get(["octoprint_prerelease_branches"])]:
|
||||
data["octoprint_release_channel"] = channel
|
||||
else:
|
||||
data["octoprint_checkout_folder"] = None
|
||||
data["octoprint_type"] = None
|
||||
|
|
@ -191,6 +217,11 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
update_type = check.get("type", None)
|
||||
checkout_folder = check.get("checkout_folder", None)
|
||||
update_folder = check.get("update_folder", None)
|
||||
prerelease = check.get("prerelease", False)
|
||||
prerelease_channel = check.get("prerelease_channel", None)
|
||||
else:
|
||||
update_type = checkout_folder = update_folder = prerelease_channel = None
|
||||
prerelease = False
|
||||
|
||||
defaults = dict(
|
||||
plugins=dict(softwareupdate=dict(
|
||||
|
|
@ -198,7 +229,9 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
octoprint=dict(
|
||||
type=update_type,
|
||||
checkout_folder=checkout_folder,
|
||||
update_folder=update_folder
|
||||
update_folder=update_folder,
|
||||
prerelease=prerelease,
|
||||
prerelease_channel=prerelease_channel
|
||||
)
|
||||
)
|
||||
))
|
||||
|
|
@ -214,6 +247,16 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
self._settings.set(["checks", "octoprint", "type"], data["octoprint_type"], defaults=defaults, force=True)
|
||||
self._refresh_configured_checks = True
|
||||
|
||||
if "octoprint_release_channel" in data:
|
||||
if data["octoprint_release_channel"] in [x["branch"] for x in self._settings.get(["octoprint_prerelease_branches"])]:
|
||||
self._settings.set(["checks", "octoprint", "prerelease"], True, defaults=defaults, force=True)
|
||||
self._settings.set(["checks", "octoprint", "prerelease_channel"], data["octoprint_release_channel"], defaults=defaults, force=True)
|
||||
self._refresh_configured_checks = True
|
||||
else:
|
||||
self._settings.set(["checks", "octoprint", "prerelease"], False, defaults=defaults, force=True)
|
||||
self._settings.set(["checks", "octoprint", "prerelease_channel"], None, defaults=defaults, force=True)
|
||||
self._refresh_configured_checks = True
|
||||
|
||||
def get_settings_version(self):
|
||||
return 4
|
||||
|
||||
|
|
@ -704,10 +747,29 @@ class SoftwareUpdatePlugin(octoprint.plugin.BlueprintPlugin,
|
|||
result["current"] = REVISION if REVISION else "unknown"
|
||||
else:
|
||||
result["current"] = VERSION
|
||||
|
||||
if check["type"] == "github_release" and (check["prerelease"] or BRANCH != self._settings.get(["octoprint_stable_branch", "branch"])):
|
||||
# we force python unequality check here because that will also allow us to
|
||||
# downgrade on a prerelease channel change
|
||||
result["release_compare"] = "python_unequal"
|
||||
|
||||
# also we compare versions fully, not just the base so that we see a difference
|
||||
# between RCs for the same version release
|
||||
result["force_base"] = False
|
||||
|
||||
if check.get("update_script", None):
|
||||
channel = result["prerelease_channel"] = check.get("prerelease_channel", BRANCH)
|
||||
if channel:
|
||||
# if we have a release channel, we also set our update_branch here to our release channel
|
||||
result["update_branch"] = check.get("update_branch", channel)
|
||||
|
||||
# we also force our target version in the update
|
||||
result["force_exact_version"] = True
|
||||
|
||||
else:
|
||||
result["displayName"] = check.get("displayName", target)
|
||||
result["displayVersion"] = check.get("displayVersion", check.get("current", "unknown"))
|
||||
if check["type"] in ("github_commit"):
|
||||
if check["type"] in ("github_commit",):
|
||||
result["current"] = check.get("current", None)
|
||||
else:
|
||||
result["current"] = check.get("current", check.get("displayVersion", None))
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ def _python(args, cwd, python_executable, sudo=False):
|
|||
return p.returncode, stdout
|
||||
|
||||
|
||||
def update_source(git_executable, folder, target, force=False):
|
||||
def _rescue_changes(git_executable, folder):
|
||||
print(">>> Running: git diff --shortstat")
|
||||
returncode, stdout = _git(["diff", "--shortstat"], folder, git_executable=git_executable)
|
||||
if returncode != 0:
|
||||
|
|
@ -91,11 +91,30 @@ def update_source(git_executable, folder, target, force=False):
|
|||
with open(patch, "wb") as f:
|
||||
f.write(stdout)
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def update_source(git_executable, folder, target, force=False, branch=None):
|
||||
if _rescue_changes(git_executable, folder):
|
||||
print(">>> Running: git reset --hard")
|
||||
returncode, stdout = _git(["reset", "--hard"], folder, git_executable=git_executable)
|
||||
if returncode != 0:
|
||||
raise RuntimeError("Could not update, \"git reset --hard\" failed with returncode %d: %s" % (returncode, stdout))
|
||||
|
||||
print(">>> Running: git fetch")
|
||||
returncode, stdout = _git(["fetch"], folder, git_executable=git_executable)
|
||||
if returncode != 0:
|
||||
raise RuntimeError("Could not update, \"git fetch\" failed with returncode %d: %s" % (returncode, stdout))
|
||||
print(stdout)
|
||||
|
||||
if branch is not None and branch.strip() != "":
|
||||
print(">>> Running: git checkout {}".format(branch))
|
||||
returncode, stdout = _git(["checkout", branch], folder, git_executable=git_executable)
|
||||
if returncode != 0:
|
||||
raise RuntimeError("Could not update, \"git checkout\" failed with returncode %d: %s" % (returncode, stdout))
|
||||
|
||||
print(">>> Running: git pull")
|
||||
returncode, stdout = _git(["pull"], folder, git_executable=git_executable)
|
||||
if returncode != 0:
|
||||
|
|
@ -134,18 +153,24 @@ def install_source(python_executable, folder, user=False, sudo=False):
|
|||
def parse_arguments():
|
||||
import argparse
|
||||
|
||||
boolean_trues = ["true", "yes", "1"]
|
||||
boolean_falses = ["false", "no", "0"]
|
||||
|
||||
parser = argparse.ArgumentParser(prog="update-octoprint.py")
|
||||
|
||||
parser.add_argument("--git", action="store", type=str, dest="git_executable",
|
||||
help="Specify git executable to use")
|
||||
parser.add_argument("--python", action="store", type=str, dest="python_executable",
|
||||
help="Specify python executable to use")
|
||||
parser.add_argument("--force", action="store_true", dest="force",
|
||||
help="Set this to force the update to only the specified version (nothing newer)")
|
||||
parser.add_argument("--force", action="store", type=lambda x: x in boolean_trues,
|
||||
dest="force", default=False,
|
||||
help="Set this to true to force the update to only the specified version (nothing newer, nothing older)")
|
||||
parser.add_argument("--sudo", action="store_true", dest="sudo",
|
||||
help="Install with sudo")
|
||||
parser.add_argument("--user", action="store_true", dest="user",
|
||||
help="Install to the user site directory instead of the general site directory")
|
||||
parser.add_argument("--branch", action="store", type=str, dest="branch", default=None,
|
||||
help="Specify the branch to make sure is checked out")
|
||||
parser.add_argument("folder", type=str,
|
||||
help="Specify the base folder of the OctoPrint installation to update")
|
||||
parser.add_argument("target", type=str,
|
||||
|
|
@ -167,13 +192,12 @@ def main():
|
|||
python_executable = args.python_executable
|
||||
|
||||
folder = args.folder
|
||||
target = args.target
|
||||
|
||||
import os
|
||||
if not os.access(folder, os.W_OK):
|
||||
raise RuntimeError("Could not update, base folder is not writable")
|
||||
|
||||
update_source(git_executable, folder, target, force=args.force)
|
||||
update_source(git_executable, folder, args.target, force=args.force, branch=args.branch)
|
||||
install_source(python_executable, folder, user=args.user, sudo=args.sudo)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ $(function() {
|
|||
self.config_cacheTtl = ko.observable();
|
||||
self.config_checkoutFolder = ko.observable();
|
||||
self.config_checkType = ko.observable();
|
||||
self.config_releaseChannel = ko.observable();
|
||||
|
||||
self.configurationDialog = $("#settings_plugin_softwareupdate_configurationdialog");
|
||||
self.confirmationDialog = $("#softwareupdate_confirmation_dialog");
|
||||
|
|
@ -29,6 +30,7 @@ $(function() {
|
|||
{"key": "github_release", "name": gettext("Release")},
|
||||
{"key": "git_commit", "name": gettext("Commit")}
|
||||
];
|
||||
self.config_availableReleaseChannels = ko.observableArray([]);
|
||||
|
||||
self.reloadOverlay = $("#reloadui_overlay");
|
||||
|
||||
|
|
@ -97,7 +99,8 @@ $(function() {
|
|||
softwareupdate: {
|
||||
cache_ttl: parseInt(self.config_cacheTtl()),
|
||||
octoprint_checkout_folder: self.config_checkoutFolder(),
|
||||
octoprint_type: self.config_checkType()
|
||||
octoprint_type: self.config_checkType(),
|
||||
octoprint_release_channel: self.config_releaseChannel()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -112,6 +115,13 @@ $(function() {
|
|||
self.config_cacheTtl(self.settings.settings.plugins.softwareupdate.cache_ttl());
|
||||
self.config_checkoutFolder(self.settings.settings.plugins.softwareupdate.octoprint_checkout_folder());
|
||||
self.config_checkType(self.settings.settings.plugins.softwareupdate.octoprint_type());
|
||||
self.config_releaseChannel(self.settings.settings.plugins.softwareupdate.octoprint_release_channel());
|
||||
|
||||
var availableReleaseChannels = [];
|
||||
_.each(self.settings.settings.plugins.softwareupdate.octoprint_branch_mappings(), function(mapping) {
|
||||
availableReleaseChannels.push({"key": mapping.branch, "name": gettext(mapping.name || mapping.branch)});
|
||||
});
|
||||
self.config_availableReleaseChannels(availableReleaseChannels);
|
||||
};
|
||||
|
||||
self.fromCheckResponse = function(data, ignoreSeen, showIfNothingNew) {
|
||||
|
|
@ -145,7 +155,7 @@ $(function() {
|
|||
var octoprint = data.information["octoprint"];
|
||||
if (octoprint && octoprint.hasOwnProperty("check")) {
|
||||
var check = octoprint.check;
|
||||
if (BRANCH != "master" && check["type"] == "github_release") {
|
||||
if (check["release_branches"] && !_.contains(check["release_branches"], BRANCH) && check["type"] == "github_release") {
|
||||
self.octoprintUnreleased(true);
|
||||
} else {
|
||||
self.octoprintUnreleased(false);
|
||||
|
|
@ -153,8 +163,8 @@ $(function() {
|
|||
|
||||
var checkoutFolder = (check["checkout_folder"] || "").trim();
|
||||
var updateFolder = (check["update_folder"] || "").trim();
|
||||
var checkType = check["type"] || "";
|
||||
if ((checkType == "github_release" || checkType == "git_commit") && checkoutFolder == "" && updateFolder == "") {
|
||||
var needsFolder = check["update_script"] || false;
|
||||
if (needsFolder && checkoutFolder == "" && updateFolder == "") {
|
||||
self.octoprintUnconfigured(true);
|
||||
} else {
|
||||
self.octoprintUnconfigured(false);
|
||||
|
|
|
|||
|
|
@ -86,6 +86,12 @@
|
|||
<select data-bind="value: config_checkType, options: config_availableCheckTypes, optionsText: 'name', optionsValue: 'key'"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" data-bind="visible: config_checkType() == 'github_release'">
|
||||
<label class="control-label">{{ _('OctoPrint Release Channel') }}</label>
|
||||
<div class="controls">
|
||||
<select data-bind="value: config_releaseChannel, options: config_availableReleaseChannels, optionsText: 'name', optionsValue: 'key'"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ _('Version cache TTL') }}</label>
|
||||
<div class="controls">
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@ def perform_update(target, check, target_version):
|
|||
raise ConfigurationInvalid("checkout_folder and update_folder are missing for update target %s, one is needed" % target)
|
||||
|
||||
update_script = check["update_script"]
|
||||
folder = check["update_folder"] if "update_folder" in check else check["checkout_folder"]
|
||||
pre_update_script = check["pre_update_script"] if "pre_update_script" in check else None
|
||||
post_update_script = check["post_update_script"] if "post_update_script" in check else None
|
||||
update_branch = check.get("update_branch", "")
|
||||
force_exact_version = check.get("force_exact_version", False)
|
||||
folder = check.get("update_folder", check["checkout_folder"])
|
||||
pre_update_script = check.get("pre_update_script", None)
|
||||
post_update_script = check.get("post_update_script", None)
|
||||
|
||||
update_stdout = ""
|
||||
update_stderr = ""
|
||||
|
|
@ -56,7 +58,11 @@ def perform_update(target, check, target_version):
|
|||
### update
|
||||
|
||||
try:
|
||||
update_command = update_script.format(python=sys.executable, folder=folder, target=target_version)
|
||||
update_command = update_script.format(python=sys.executable,
|
||||
folder=folder,
|
||||
target=target_version,
|
||||
branch=update_branch,
|
||||
force="true" if force_exact_version else "false")
|
||||
|
||||
logger.debug("Target %s, running update script: %s" % (target, update_command))
|
||||
returncode, stdout, stderr = execute(update_command, cwd=folder)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,60 @@ RELEASE_URL = "https://api.github.com/repos/{user}/{repo}/releases"
|
|||
|
||||
logger = logging.getLogger("octoprint.plugins.softwareupdate.version_checks.github_release")
|
||||
|
||||
def _get_latest_release(user, repo, include_prerelease=False):
|
||||
def _filter_out_latest(releases, include_prerelease=False, prerelease_channel=None):
|
||||
"""
|
||||
Filters out the newest of all matching releases.
|
||||
|
||||
Tests:
|
||||
|
||||
>>> release_1_2_15 = dict(name="1.2.15", tag_name="1.2.15", html_url="some_url", published_at="2016-07-29T19:53:29Z", prerelease=False, draft=False, target_commitish="prerelease")
|
||||
>>> release_1_2_16rc1 = dict(name="1.2.16rc1", tag_name="1.2.16rc1", html_url="some_url", published_at="2016-08-29T12:00:00Z", prerelease=True, draft=False, target_commitish="rc/maintenance")
|
||||
>>> release_1_2_16rc2 = dict(name="1.2.16rc2", tag_name="1.2.16rc2", html_url="some_url", published_at="2016-08-30T12:00:00Z", prerelease=True, draft=False, target_commitish="rc/maintenance")
|
||||
>>> release_1_2_17rc1 = dict(name="1.2.17rc1", tag_name="1.2.17rc1", html_url="some_url", published_at="2016-08-31T12:00:00Z", prerelease=True, draft=True, target_commitish="rc/maintenance")
|
||||
>>> release_1_3_0rc1 = dict(name="1.3.0rc1", tag_name="1.3.0rc1", html_url="some_url", published_at="2016-12-12T12:00:00Z", prerelease=True, draft=False, target_commitish="rc/devel")
|
||||
>>> release_1_4_0rc1 = dict(name="1.4.0rc1", tag_name="1.4.0rc1", html_url="some_url", published_at="2017-12-12T12:00:00Z", prerelease=True, draft=False, target_commitish="rc/future")
|
||||
>>> releases = [release_1_2_15, release_1_2_16rc1, release_1_2_16rc2, release_1_2_17rc1, release_1_3_0rc1, release_1_4_0rc1]
|
||||
>>> _filter_out_latest(releases, include_prerelease=False, prerelease_channel=None)
|
||||
('1.2.15', '1.2.15', 'some_url')
|
||||
>>> _filter_out_latest(releases, include_prerelease=True, prerelease_channel="rc/maintenance")
|
||||
('1.2.16rc2', '1.2.16rc2', 'some_url')
|
||||
>>> _filter_out_latest(releases, include_prerelease=True, prerelease_channel="rc/devel")
|
||||
('1.3.0rc1', '1.3.0rc1', 'some_url')
|
||||
>>> _filter_out_latest(releases, include_prerelease=True, prerelease_channel=None)
|
||||
('1.4.0rc1', '1.4.0rc1', 'some_url')
|
||||
>>> _filter_out_latest(releases, include_prerelease=True, prerelease_channel="rc/doesntexist")
|
||||
('1.2.15', '1.2.15', 'some_url')
|
||||
>>> _filter_out_latest([release_1_2_17rc1])
|
||||
(None, None, None)
|
||||
>>> _filter_out_latest([release_1_2_16rc1, release_1_2_16rc2])
|
||||
(None, None, None)
|
||||
"""
|
||||
|
||||
nothing = None, None, None
|
||||
|
||||
# filter out prereleases and drafts
|
||||
filter_function = lambda rel: not rel["prerelease"] and not rel["draft"]
|
||||
if include_prerelease:
|
||||
if prerelease_channel:
|
||||
filter_function = lambda rel: not rel["draft"] and (
|
||||
not rel["prerelease"] or rel["target_commitish"] == prerelease_channel)
|
||||
else:
|
||||
filter_function = lambda rel: not rel["draft"]
|
||||
|
||||
releases = filter(filter_function, releases)
|
||||
if not releases:
|
||||
return nothing
|
||||
|
||||
# sort by date
|
||||
releases = sorted(releases, key=lambda release: release.get("published_at", None))
|
||||
|
||||
# latest release = last in list
|
||||
latest = releases[-1]
|
||||
|
||||
return latest["name"], latest["tag_name"], latest.get("html_url", None)
|
||||
|
||||
|
||||
def _get_latest_release(user, repo, include_prerelease=False, prerelease_channel=None):
|
||||
nothing = None, None, None
|
||||
r = requests.get(RELEASE_URL.format(user=user, repo=repo))
|
||||
|
||||
|
|
@ -27,36 +80,48 @@ def _get_latest_release(user, repo, include_prerelease=False):
|
|||
releases = r.json()
|
||||
|
||||
# sanitize
|
||||
required_fields = {"name", "tag_name", "html_url", "draft", "prerelease", "published_at"}
|
||||
required_fields = {"name", "tag_name", "html_url", "draft", "prerelease", "published_at", "target_commitish"}
|
||||
releases = filter(lambda rel: set(rel.keys()) & required_fields == required_fields,
|
||||
releases)
|
||||
|
||||
# filter out prereleases and drafts
|
||||
if include_prerelease:
|
||||
releases = filter(lambda rel: not rel["draft"], releases)
|
||||
else:
|
||||
releases = filter(lambda rel: not rel["prerelease"] and not rel["draft"],
|
||||
releases)
|
||||
|
||||
if not releases:
|
||||
return nothing
|
||||
|
||||
# sort by date
|
||||
comp = lambda a, b: cmp(a.get("published_at", None), b["published_at"])
|
||||
releases = sorted(releases, cmp=comp)
|
||||
|
||||
# latest release = last in list
|
||||
latest = releases[-1]
|
||||
|
||||
return latest["name"], latest["tag_name"], latest.get("html_url", None)
|
||||
return _filter_out_latest(releases, include_prerelease=include_prerelease, prerelease_channel=prerelease_channel)
|
||||
|
||||
|
||||
def _get_sanitized_version(version_string):
|
||||
"""
|
||||
Removes "-..." prefix from version strings.
|
||||
|
||||
Tests:
|
||||
>>> _get_sanitized_version("1.2.15")
|
||||
'1.2.15'
|
||||
>>> _get_sanitized_version("1.2.15-dev12")
|
||||
'1.2.15'
|
||||
"""
|
||||
if "-" in version_string:
|
||||
version_string = version_string[:version_string.find("-")]
|
||||
return version_string
|
||||
|
||||
|
||||
def _get_base_from_version_tuple(version_tuple):
|
||||
"""
|
||||
Reduces version tuple to base version.
|
||||
|
||||
Tests:
|
||||
|
||||
>>> _get_base_from_version_tuple(("1", "2", "15"))
|
||||
('1', '2', '15')
|
||||
>>> _get_base_from_version_tuple(("1", "2", "15", "*", "dev12"))
|
||||
('1', '2', '15')
|
||||
"""
|
||||
|
||||
base_version = []
|
||||
for part in version_tuple:
|
||||
if part.startswith("*"):
|
||||
break
|
||||
base_version.append(part)
|
||||
return tuple(base_version)
|
||||
|
||||
|
||||
def _get_comparable_version_pkg_resources(version_string, force_base=True):
|
||||
import pkg_resources
|
||||
|
||||
|
|
@ -65,12 +130,7 @@ def _get_comparable_version_pkg_resources(version_string, force_base=True):
|
|||
if force_base:
|
||||
if isinstance(version, tuple):
|
||||
# old setuptools
|
||||
base_version = []
|
||||
for part in version:
|
||||
if part.startswith("*"):
|
||||
break
|
||||
base_version.append(part)
|
||||
version = tuple(base_version)
|
||||
version = _get_base_from_version_tuple(version)
|
||||
else:
|
||||
# new setuptools
|
||||
version = pkg_resources.parse_version(version.base_version)
|
||||
|
|
@ -91,10 +151,32 @@ def _get_comparable_version_semantic(version_string, force_base=True):
|
|||
|
||||
|
||||
def _is_current(release_information, compare_type, custom=None, force_base=True):
|
||||
"""
|
||||
Checks if the provided release information indicates the version being the most current one.
|
||||
|
||||
Tests:
|
||||
|
||||
>>> _is_current(dict(remote=dict(value=None))
|
||||
True
|
||||
>>> _is_current(dict(local=dict(value="1.2.15"), remote=dict(value="1.2.16")))
|
||||
False
|
||||
>>> _is_current(dict(local=dict(value="1.2.16dev1"), remote=dict(value="1.2.16dev2")))
|
||||
True
|
||||
>>> _is_current(dict(local=dict(value="1.2.16dev1"), remote=dict(value="1.2.16dev2")), force_base=False)
|
||||
False
|
||||
>>> _is_current(dict(local=dict(value="1.2.16dev3"), remote=dict(value="1.2.16dev2")), force_base=False)
|
||||
True
|
||||
>>> _is_current(dict(local=dict(value="1.2.16dev3"), remote=dict(value="1.2.16dev2")), force_base=False, compare_type="python_unequal")
|
||||
False
|
||||
|
||||
"""
|
||||
|
||||
if release_information["remote"]["value"] is None:
|
||||
return True
|
||||
|
||||
if not compare_type in ("python", "semantic", "unequal", "custom") or compare_type == "custom" and custom is None:
|
||||
if not compare_type in ("python", "python_unequal",
|
||||
"semantic", "semantic_unequal",
|
||||
"unequal", "custom") or compare_type == "custom" and custom is None:
|
||||
compare_type = "python"
|
||||
|
||||
sanitized_local = _get_sanitized_version(release_information["local"]["value"])
|
||||
|
|
@ -106,11 +188,21 @@ def _is_current(release_information, compare_type, custom=None, force_base=True)
|
|||
remote_version = _get_comparable_version_pkg_resources(sanitized_remote, force_base=force_base)
|
||||
return local_version >= remote_version
|
||||
|
||||
elif compare_type == "python_unequal":
|
||||
local_version = _get_comparable_version_pkg_resources(sanitized_local, force_base=force_base)
|
||||
remote_version = _get_comparable_version_pkg_resources(sanitized_remote, force_base=force_base)
|
||||
return local_version == remote_version
|
||||
|
||||
elif compare_type == "semantic":
|
||||
local_version = _get_comparable_version_semantic(sanitized_local, force_base=force_base)
|
||||
remote_version = _get_comparable_version_semantic(sanitized_remote, force_base=force_base)
|
||||
return local_version >= remote_version
|
||||
|
||||
elif compare_type == "semantic_unequal":
|
||||
local_version = _get_comparable_version_semantic(sanitized_local, force_base=force_base)
|
||||
remote_version = _get_comparable_version_semantic(sanitized_remote, force_base=force_base)
|
||||
return local_version == remote_version
|
||||
|
||||
elif compare_type == "custom":
|
||||
return custom(sanitized_local, sanitized_remote)
|
||||
|
||||
|
|
@ -127,12 +219,14 @@ def get_latest(target, check, custom_compare=None):
|
|||
|
||||
current = check.get("current", None)
|
||||
include_prerelease = check.get("prerelease", False)
|
||||
prerelease_channel = check.get("prerelease_channel", None)
|
||||
force_base = check.get("force_base", True)
|
||||
|
||||
remote_name, remote_tag, release_notes = _get_latest_release(check["user"],
|
||||
check["repo"],
|
||||
include_prerelease=include_prerelease)
|
||||
compare_type = check["release_compare"] if "release_compare" in check else "python"
|
||||
include_prerelease=include_prerelease,
|
||||
prerelease_channel=prerelease_channel)
|
||||
compare_type = check.get("release_compare", "python")
|
||||
|
||||
information =dict(
|
||||
local=dict(name=current, value=current),
|
||||
|
|
|
|||
Loading…
Reference in a new issue