feat: update policy for updating ComfyUI

https://github.com/ltdrdata/ComfyUI-Manager/issues/1552

fixed: comfyui versions should be based on commit date
https://github.com/ltdrdata/ComfyUI-Manager/issues/1566

fixed: invalid identifying of nightly node packs which has `git@github.com:...` url
fixed: switch comfyui should be based on `master` branch instead of `main` branch
fixed: switch_to_default_branch - more robust switching
refactor: endpoints for policies
This commit is contained in:
Dr.Lt.Data 2025-02-19 20:59:08 +09:00
parent 41172be796
commit 5dd8ea8aab
7 changed files with 190 additions and 60 deletions

View File

@ -154,12 +154,25 @@ def switch_to_default_branch(repo):
repo.git.checkout(default_branch)
return True
except:
# try checkout master
# try checkout main if failed
try:
repo.git.checkout(repo.heads.master)
return True
except:
try:
if remote_name is not None:
repo.git.checkout('-b', 'master', f'{remote_name}/master')
return True
except:
try:
repo.git.checkout(repo.heads.main)
return True
except:
try:
if remote_name is not None:
repo.git.checkout('-b', 'main', f'{remote_name}/main')
return True
except:
pass

View File

@ -53,7 +53,14 @@ def git_url(fullpath):
def normalize_url(url) -> str:
if 'github' in url or (GITHUB_ENDPOINT is not None and GITHUB_ENDPOINT in url):
author = os.path.basename(os.path.dirname(url))
if author.startswith('git@github.com:'):
author = author.split(':')[1]
repo_name = os.path.basename(url)
if repo_name.endswith('.git'):
repo_name = repo_name[:-4]
url = f"https://github.com/{author}/{repo_name}"
return url

View File

@ -42,7 +42,7 @@ import manager_downloader
from node_package import InstalledNodePackage
version_code = [3, 24, 1]
version_code = [3, 25]
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
@ -538,6 +538,8 @@ class UnifiedManager:
if node_package.is_disabled and node_package.is_unknown:
url = git_utils.git_url(node_package.fullpath)
if url is not None:
url = git_utils.normalize_url(url)
self.unknown_inactive_nodes[node_package.id] = (url, node_package.fullpath)
if node_package.is_disabled and node_package.is_nightly:
@ -548,6 +550,8 @@ class UnifiedManager:
if node_package.is_enabled and node_package.is_unknown:
url = git_utils.git_url(node_package.fullpath)
if url is not None:
url = git_utils.normalize_url(url)
self.unknown_active_nodes[node_package.id] = (url, node_package.fullpath)
if node_package.is_from_cnr and node_package.is_disabled:
@ -1059,8 +1063,8 @@ class UnifiedManager:
# update cache
if version_spec == 'unknown':
self.unknown_active_nodes[node_id] = self.unknown_inactive_nodes[node_id][0], to_path
del self.unknown_inactive_nodes[node_id]
self.unknown_active_nodes[node_id] = to_path
return result.with_target(to_path)
elif version_spec == 'nightly':
del self.nightly_inactive_nodes[node_id]
@ -1401,7 +1405,7 @@ class UnifiedManager:
res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall)
if res.result:
if version_spec == 'unknown':
self.unknown_active_nodes[node_id] = to_path
self.unknown_active_nodes[node_id] = repo_url, to_path
elif version_spec == 'nightly':
cnr_utils.generate_cnr_id(to_path, node_id)
self.active_nodes[node_id] = 'nightly', to_path
@ -1577,6 +1581,7 @@ def write_config():
'bypass_ssl': get_config()['bypass_ssl'],
"file_logging": get_config()['file_logging'],
'component_policy': get_config()['component_policy'],
'update_policy': get_config()['update_policy'],
'windows_selector_event_loop_policy': get_config()['windows_selector_event_loop_policy'],
'model_download_by_agent': get_config()['model_download_by_agent'],
'downgrade_blacklist': get_config()['downgrade_blacklist'],
@ -1615,6 +1620,7 @@ def read_config():
'bypass_ssl': get_bool('bypass_ssl', False),
'file_logging': get_bool('file_logging', True),
'component_policy': default_conf.get('component_policy', 'workflow').lower(),
'update_policy': default_conf.get('update_policy', 'stable-comfyui').lower(),
'windows_selector_event_loop_policy': get_bool('windows_selector_event_loop_policy', False),
'model_download_by_agent': get_bool('model_download_by_agent', False),
'downgrade_blacklist': default_conf.get('downgrade_blacklist', '').lower(),
@ -1637,6 +1643,7 @@ def read_config():
'bypass_ssl': False,
'file_logging': True,
'component_policy': 'workflow',
'update_policy': 'stable-comfyui',
'windows_selector_event_loop_policy': False,
'model_download_by_agent': False,
'downgrade_blacklist': '',
@ -1688,12 +1695,25 @@ def switch_to_default_branch(repo):
repo.git.checkout(default_branch)
return True
except:
# try checkout master
# try checkout main if failed
try:
repo.git.checkout(repo.heads.master)
return True
except:
try:
if remote_name is not None:
repo.git.checkout('-b', 'master', f'{remote_name}/master')
return True
except:
try:
repo.git.checkout(repo.heads.main)
return True
except:
try:
if remote_name is not None:
repo.git.checkout('-b', 'main', f'{remote_name}/main')
return True
except:
pass
@ -2347,6 +2367,32 @@ def gitclone_update(files, instant_execution=False, skip_script=False, msg_prefi
return True
def update_to_stable_comfyui(repo_path):
try:
repo = git.Repo(repo_path)
repo.git.checkout(repo.heads.master)
versions, current_tag, _ = get_comfyui_versions(repo)
if len(versions) == 0 or (len(versions) == 1 and versions[0] == 'nightly'):
logging.info("[ComfyUI-Manager] Unable to update to the stable ComfyUI version.")
return "fail", None
if versions[0] == 'nightly':
latest_tag = versions[1]
else:
latest_tag = versions[0]
if current_tag == latest_tag:
return "skip", None
else:
logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}")
repo.git.checkout(latest_tag)
return 'updated', latest_tag
except:
traceback.print_exc()
return "fail", None
def update_path(repo_path, instant_execution=False, no_deps=False):
if not os.path.exists(os.path.join(repo_path, '.git')):
return "fail"
@ -2354,9 +2400,12 @@ def update_path(repo_path, instant_execution=False, no_deps=False):
# version check
repo = git.Repo(repo_path)
is_switched = False
if repo.head.is_detached:
if not switch_to_default_branch(repo):
return "fail"
else:
is_switched = True
current_branch = repo.active_branch
branch_name = current_branch.name
@ -2395,6 +2444,8 @@ def update_path(repo_path, instant_execution=False, no_deps=False):
git_pull(repo_path)
execute_install_script("ComfyUI", repo_path, instant_execution=instant_execution, no_deps=no_deps)
return "updated"
elif is_switched:
return "updated"
else:
return "skipped"
@ -2705,9 +2756,6 @@ async def extract_nodes_from_workflow(filepath, mode='local', channel_url='defau
if ext == 'https://github.com/comfyanonymous/ComfyUI':
pass
elif ext is not None:
if 'Fooocus' in ext:
print(f">> {node_name}")
used_exts.add(ext)
else:
unknown_nodes.add(node_name)
@ -3176,17 +3224,26 @@ async def check_need_to_migrate():
need_to_migrate = True
def get_comfyui_versions():
def get_comfyui_versions(repo=None):
if repo is None:
repo = git.Repo(comfy_path)
versions = [x.name for x in repo.tags if x.name.startswith('v')]
versions.reverse() # nearest tag
try:
remote = get_remote_name(repo)
repo.remotes[remote].fetch()
except:
logging.error("[ComfyUI-Manager] Failed to fetch ComfyUI")
versions = [x.name for x in repo.tags if x.name.startswith('v')]
# nearest tag
versions = sorted(versions, key=lambda v: repo.git.log('-1', '--format=%ct', v), reverse=True)
versions = versions[:4]
current_tag = repo.git.describe('--tags')
if current_tag not in versions:
versions = sorted(versions + [current_tag], reverse=True)
versions = sorted(versions + [current_tag], key=lambda v: repo.git.log('-1', '--format=%ct', v), reverse=True)
versions = versions[:4]
main_branch = repo.heads.master
@ -3199,16 +3256,16 @@ def get_comfyui_versions():
versions[0] = 'nightly'
current_tag = 'nightly'
return versions, current_tag
return versions, current_tag, latest_tag
def switch_comfyui(tag):
repo = git.Repo(comfy_path)
if tag == 'nightly':
repo.git.checkout('main')
repo.git.checkout('master')
repo.remotes.origin.pull()
print("[ComfyUI-Manager] ComfyUI version is switched to the latest 'main' version")
print("[ComfyUI-Manager] ComfyUI version is switched to the latest 'master' version")
else:
repo.git.checkout(tag)
print(f"[ComfyUI-Manager] ComfyUI version is switched to '{tag}'")

View File

@ -187,6 +187,8 @@ set_preview_method(core.get_config()['preview_method'])
def set_component_policy(mode):
core.get_config()['component_policy'] = mode
def set_update_policy(mode):
core.get_config()['update_policy'] = mode
def print_comfyui_version():
global comfy_ui_hash
@ -452,20 +454,29 @@ async def task_worker():
return {'msg':f"An error occurred while updating '{node_name}'."}
async def do_update_comfyui() -> str:
async def do_update_comfyui(is_stable) -> str:
try:
repo_path = os.path.dirname(folder_paths.__file__)
latest_tag = None
if is_stable:
res, latest_tag = core.update_to_stable_comfyui(repo_path)
else:
res = core.update_path(repo_path)
if res == "fail":
logging.error("ComfyUI update fail: The installed ComfyUI does not have a Git repository.")
return "The installed ComfyUI does not have a Git repository."
elif res == "updated":
logging.info("ComfyUI is updated.")
return "success"
if is_stable:
logging.info("ComfyUI is updated to latest stable version.")
return "success-stable-"+latest_tag
else:
logging.info("ComfyUI is updated to latest nightly version.")
return "success-nightly"
else: # skipped
logging.info("ComfyUI is up-to-date.")
return "skip"
except Exception:
traceback.print_exc()
@ -597,7 +608,7 @@ async def task_worker():
elif kind == 'update-main':
msg = await do_update(item)
elif kind == 'update-comfyui':
msg = await do_update_comfyui()
msg = await do_update_comfyui(item[1])
elif kind == 'fix':
msg = await do_fix(item)
elif kind == 'uninstall':
@ -1337,14 +1348,15 @@ async def update_custom_node(request):
@routes.get("/manager/queue/update_comfyui")
async def update_comfyui(request):
task_queue.put(("update-comfyui", ('comfyui',)))
is_stable = core.get_config()['update_policy'] != 'nightly-comfyui'
task_queue.put(("update-comfyui", ('comfyui', is_stable)))
return web.Response(status=200)
@routes.get("/comfyui_manager/comfyui_versions")
async def comfyui_versions(request):
try:
res, current = core.get_comfyui_versions()
res, current, latest = core.get_comfyui_versions()
return web.json_response({'versions': res, 'current': current}, status=200, content_type='application/json')
except Exception as e:
logging.error(f"ComfyUI update fail: {e}", file=sys.stderr)
@ -1435,7 +1447,7 @@ async def preview_method(request):
return web.Response(status=200)
@routes.get("/manager/component/policy")
@routes.get("/manager/policy/component")
async def component_policy(request):
if "value" in request.rel_url.query:
set_component_policy(request.rel_url.query['value'])
@ -1446,6 +1458,17 @@ async def component_policy(request):
return web.Response(status=200)
@routes.get("/manager/policy/update")
async def update_policy(request):
if "value" in request.rel_url.query:
set_update_policy(request.rel_url.query['value'])
core.write_config()
else:
return web.Response(text=core.get_config()['update_policy'], status=200)
return web.Response(status=200)
@routes.get("/manager/channel_url_list")
async def channel_url_list(request):
channels = core.get_channel_dict()

View File

@ -230,7 +230,7 @@ var update_all_button = null;
var restart_stop_button = null;
let share_option = 'all';
var is_updating_all = false;
var is_updating = false;
// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts
@ -477,6 +477,8 @@ async function updateComfyUI() {
const response = await api.fetchApi('/manager/queue/update_comfyui');
showTerminal();
is_updating = true;
await api.fetchApi('/manager/queue/start');
}
@ -605,8 +607,14 @@ function showVersionSelectorDialog(versions, current, onSelect) {
}
async function switchComfyUI() {
switch_comfyui_button.disabled = true;
switch_comfyui_button.style.backgroundColor = "gray";
let res = await api.fetchApi(`/comfyui_manager/comfyui_versions`, { cache: "no-store" });
switch_comfyui_button.disabled = false;
switch_comfyui_button.style.backgroundColor = "";
if(res.status == 200) {
let obj = await res.json();
@ -694,11 +702,11 @@ async function onQueueStatus(event) {
else if(event.detail.status == 'done') {
reset_action_buttons();
if(!is_updating_all) {
if(!is_updating) {
return;
}
is_updating_all = false;
is_updating = false;
let success_list = [];
let failed_list = [];
@ -721,19 +729,25 @@ async function onQueueStatus(event) {
let msg = "";
if(success_list.length == 0 && comfyui_state != 'success') {
if(success_list.length == 0 && !comfyui_state.startsWith('success')) {
if(failed_list.length == 0) {
msg += "All custom nodes are already up to date.";
msg += "You are already up to date.";
}
}
else {
msg = "To apply the updates, you need to <button class='cm-small-button' id='cm-reboot-button5'>RESTART</button> ComfyUI.<hr>";
if(comfyui_state == 'success') {
msg += "ComfyUI is updated.<BR><BR>";
if(comfyui_state == 'success-nightly') {
msg += "ComfyUI has been updated to latest nightly version.<BR><BR>";
infoToast("ComfyUI has been updated to the latest nightly version.");
}
else if(comfyui_state.startsWith('success-stable')) {
const ver = comfyui_state.split("-").pop();
msg += `ComfyUI has been updated to ${ver}.<BR><BR>`;
infoToast(`ComfyUI has been updated to ${ver}`);
}
else if(comfyui_state == 'skip') {
msg += "ComfyUI is already up-to-date.<BR><BR>"
msg += "ComfyUI is already up to date.<BR><BR>"
}
else if(comfyui_state != null) {
msg += "Failed to update ComfyUI.<BR><BR>"
@ -811,7 +825,7 @@ async function updateAll(update_comfyui, manager_dialog) {
customAlert('Another task is already in progress. Please stop the ongoing task first.');
}
else if(response.status == 200) {
is_updating_all = true;
is_updating = true;
await api.fetchApi('/manager/queue/start');
}
}
@ -995,6 +1009,8 @@ class ManagerMenuDialog extends ComfyDialog {
}
createControlsLeft() {
const isElectron = 'electronAPI' in window;
let self = this;
this.update_check_checkbox = $el("input",{type:'checkbox', id:"skip_update_check"},[])
@ -1073,25 +1089,6 @@ class ManagerMenuDialog extends ComfyDialog {
share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, []));
}
// default ui state
let component_policy_combo = document.createElement("select");
component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use.");
component_policy_combo.className = "cm-menu-combo";
component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Component: Use workflow version' }, []));
component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Component: Use higher version' }, []));
component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Component: Use my version' }, []));
api.fetchApi('/manager/component/policy')
.then(response => response.text())
.then(data => {
component_policy_combo.value = data;
set_component_policy(data);
});
component_policy_combo.addEventListener('change', function (event) {
api.fetchApi(`/manager/component/policy?value=${event.target.value}`);
set_component_policy(event.target.value);
});
api.fetchApi('/manager/share_option')
.then(response => response.text())
.then(data => {
@ -1111,6 +1108,43 @@ class ManagerMenuDialog extends ComfyDialog {
}
});
let component_policy_combo = document.createElement("select");
component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use.");
component_policy_combo.className = "cm-menu-combo";
component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Component: Use workflow version' }, []));
component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Component: Use higher version' }, []));
component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Component: Use my version' }, []));
api.fetchApi('/manager/policy/component')
.then(response => response.text())
.then(data => {
component_policy_combo.value = data;
set_component_policy(data);
});
component_policy_combo.addEventListener('change', function (event) {
api.fetchApi(`/manager/policy/component?value=${event.target.value}`);
set_component_policy(event.target.value);
});
let update_policy_combo = document.createElement("select");
if(isElectron)
update_policy_combo.style.display = 'none';
update_policy_combo.setAttribute("title", "Sets the policy to be applied when performing an update.");
update_policy_combo.className = "cm-menu-combo";
update_policy_combo.appendChild($el('option', { value: 'stable-comfyui', text: 'Update: Stable ComfyUI' }, []));
update_policy_combo.appendChild($el('option', { value: 'nightly-comfyui', text: 'Update: Nightly ComfyUI' }, []));
api.fetchApi('/manager/policy/update')
.then(response => response.text())
.then(data => {
update_policy_combo.value = data;
});
update_policy_combo.addEventListener('change', function (event) {
api.fetchApi(`/manager/policy/update?value=${event.target.value}`);
});
return [
$el("div", {}, [this.update_check_checkbox, uc_checkbox_text]),
$el("br", {}, []),
@ -1119,6 +1153,7 @@ class ManagerMenuDialog extends ComfyDialog {
preview_combo,
share_combo,
component_policy_combo,
update_policy_combo,
$el("br", {}, []),
$el("br", {}, []),
@ -1145,11 +1180,6 @@ class ManagerMenuDialog extends ComfyDialog {
install_pip(url, self);
}
}
}),
$el("button.cm-experimental-button", {
type: "button",
textContent: "Unload models",
onclick: () => { free_models(); }
})
]),
];

View File

@ -709,7 +709,7 @@ app.handleFile = handleFile;
let current_component_policy = 'workflow';
try {
api.fetchApi('/manager/component/policy')
api.fetchApi('/manager/policy/component')
.then(response => response.text())
.then(data => { current_component_policy = data; });
}

View File

@ -1,7 +1,7 @@
[project]
name = "comfyui-manager"
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
version = "3.24.1"
version = "3.25"
license = { file = "LICENSE.txt" }
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]