From 5dd8ea8aab9810ce026648f58dcda751c0e0759e Mon Sep 17 00:00:00 2001 From: "Dr.Lt.Data" Date: Wed, 19 Feb 2025 20:59:08 +0900 Subject: [PATCH] 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 --- git_helper.py | 15 ++++++- glob/git_utils.py | 7 +++ glob/manager_core.py | 87 +++++++++++++++++++++++++++++------- glob/manager_server.py | 41 +++++++++++++---- js/comfyui-manager.js | 96 ++++++++++++++++++++++++++-------------- js/components-manager.js | 2 +- pyproject.toml | 2 +- 7 files changed, 190 insertions(+), 60 deletions(-) diff --git a/git_helper.py b/git_helper.py index f9cc661d..e79b43a6 100644 --- a/git_helper.py +++ b/git_helper.py @@ -154,14 +154,27 @@ 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: - pass + 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 print("[ComfyUI Manager] Failed to switch to the default branch") return False diff --git a/glob/git_utils.py b/glob/git_utils.py index 29946862..8785bb6d 100644 --- a/glob/git_utils.py +++ b/glob/git_utils.py @@ -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 diff --git a/glob/manager_core.py b/glob/manager_core.py index 2a378fac..5d5614e1 100644 --- a/glob/manager_core.py +++ b/glob/manager_core.py @@ -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,14 +1695,27 @@ 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: - pass + 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 print("[ComfyUI Manager] Failed to switch to the default branch") return False @@ -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(): - repo = git.Repo(comfy_path) - versions = [x.name for x in repo.tags if x.name.startswith('v')] - versions.reverse() # nearest tag +def get_comfyui_versions(repo=None): + if repo is None: + repo = git.Repo(comfy_path) + 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}'") diff --git a/glob/manager_server.py b/glob/manager_server.py index 6aa0c49d..2e462c09 100644 --- a/glob/manager_server.py +++ b/glob/manager_server.py @@ -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__) - res = core.update_path(repo_path) - + 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() diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 4d070c32..6e854828 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -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 ComfyUI.
"; - if(comfyui_state == 'success') { - msg += "ComfyUI is updated.

"; + if(comfyui_state == 'success-nightly') { + msg += "ComfyUI has been updated to latest nightly version.

"; + 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}.

`; + infoToast(`ComfyUI has been updated to ${ver}`); } else if(comfyui_state == 'skip') { - msg += "ComfyUI is already up-to-date.

" + msg += "ComfyUI is already up to date.

" } else if(comfyui_state != null) { msg += "Failed to update ComfyUI.

" @@ -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(); } }) ]), ]; diff --git a/js/components-manager.js b/js/components-manager.js index e2403d78..9244d2a4 100644 --- a/js/components-manager.js +++ b/js/components-manager.js @@ -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; }); } diff --git a/pyproject.toml b/pyproject.toml index 6f1e848f..1951766f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"]