diff --git a/comfyui_manager/common/__init__.py b/comfyui_manager/common/__init__.py index e69de29b..92badafc 100644 --- a/comfyui_manager/common/__init__.py +++ b/comfyui_manager/common/__init__.py @@ -0,0 +1,17 @@ +from .timestamp_utils import ( + current_timestamp, + get_timestamp_for_filename, + get_timestamp_for_path, + get_backup_branch_name, + get_now, + get_unix_timestamp, +) + +__all__ = [ + 'current_timestamp', + 'get_timestamp_for_filename', + 'get_timestamp_for_path', + 'get_backup_branch_name', + 'get_now', + 'get_unix_timestamp', +] diff --git a/comfyui_manager/common/git_helper.py b/comfyui_manager/common/git_helper.py index 5fc51478..183eece6 100644 --- a/comfyui_manager/common/git_helper.py +++ b/comfyui_manager/common/git_helper.py @@ -9,6 +9,7 @@ import yaml import requests from tqdm.auto import tqdm from git.remote import RemoteProgress +from comfyui_manager.common.timestamp_utils import get_backup_branch_name comfy_path = os.environ.get('COMFYUI_PATH') @@ -222,7 +223,14 @@ def gitpull(path): repo.close() return - remote.pull() + try: + repo.git.pull('--ff-only') + except git.GitCommandError: + backup_name = get_backup_branch_name(repo) + repo.create_head(backup_name) + print(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}") + repo.git.reset('--hard', f'{remote_name}/{branch_name}') + print(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}") repo.git.submodule('update', '--init', '--recursive') new_commit_hash = repo.head.commit.hexsha diff --git a/comfyui_manager/common/manager_util.py b/comfyui_manager/common/manager_util.py index b5bfc6b2..5a798043 100644 --- a/comfyui_manager/common/manager_util.py +++ b/comfyui_manager/common/manager_util.py @@ -8,13 +8,13 @@ import aiohttp import json import threading import os -from datetime import datetime import subprocess import sys import re import logging import platform import shlex +import time from functools import lru_cache @@ -176,7 +176,7 @@ def is_file_created_within_one_day(file_path): return False file_creation_time = os.path.getctime(file_path) - current_time = datetime.now().timestamp() + current_time = time.time() time_difference = current_time - file_creation_time return time_difference <= 86400 diff --git a/comfyui_manager/common/timestamp_utils.py b/comfyui_manager/common/timestamp_utils.py new file mode 100644 index 00000000..772817c4 --- /dev/null +++ b/comfyui_manager/common/timestamp_utils.py @@ -0,0 +1,136 @@ +""" +Robust timestamp utilities with datetime fallback. + +Some environments (especially Mac) have issues with the datetime module +due to local file name conflicts or Homebrew Python module path issues. +""" + +import logging +import time as time_module +import uuid + +_datetime_available = None +_dt_datetime = None + + +def _init_datetime(): + """Initialize datetime availability check (lazy, once).""" + global _datetime_available, _dt_datetime + if _datetime_available is not None: + return + + try: + import datetime as dt + if hasattr(dt, 'datetime'): + from datetime import datetime as dt_datetime + _dt_datetime = dt_datetime + _datetime_available = True + return + except Exception as e: + logging.debug(f"[ComfyUI-Manager] datetime import failed: {e}") + + _datetime_available = False + logging.warning("[ComfyUI-Manager] datetime unavailable, using time module fallback") + + +def current_timestamp() -> str: + """ + Get current timestamp for logging. + Format: YYYY-MM-DD HH:MM:SS.mmm (or Unix timestamp if fallback) + """ + _init_datetime() + if _datetime_available: + return _dt_datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + return str(time_module.time()).split('.')[0] + + +def get_timestamp_for_filename() -> str: + """ + Get timestamp suitable for filenames. + Format: YYYYMMDD_HHMMSS + """ + _init_datetime() + if _datetime_available: + return _dt_datetime.now().strftime('%Y%m%d_%H%M%S') + return time_module.strftime('%Y%m%d_%H%M%S') + + +def get_timestamp_for_path() -> str: + """ + Get timestamp for path/directory names. + Format: YYYY-MM-DD_HH-MM-SS + """ + _init_datetime() + if _datetime_available: + return _dt_datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + return time_module.strftime('%Y-%m-%d_%H-%M-%S') + + +def get_backup_branch_name(repo=None) -> str: + """ + Get backup branch name with current timestamp. + Format: backup_YYYYMMDD_HHMMSS (or backup_YYYYMMDD_HHMMSS_N if exists) + + Args: + repo: Optional git.Repo object. If provided, checks for name collisions + and adds sequential suffix if needed. + + Returns: + Unique backup branch name. + """ + base_name = f'backup_{get_timestamp_for_filename()}' + + if repo is None: + return base_name + + # Check if branch exists + try: + existing_branches = {b.name for b in repo.heads} + except Exception: + return base_name + + if base_name not in existing_branches: + return base_name + + # Add sequential suffix + for i in range(1, 100): + new_name = f'{base_name}_{i}' + if new_name not in existing_branches: + return new_name + + # Ultimate fallback: use UUID (very unlikely to reach here) + return f'{base_name}_{uuid.uuid4().hex[:6]}' + + +def get_now(): + """ + Get current datetime object. + Returns datetime.now() if available, otherwise a FakeDatetime object + that supports basic operations (timestamp(), strftime()). + """ + _init_datetime() + if _datetime_available: + return _dt_datetime.now() + + # Fallback: return object with basic datetime-like interface + t = time_module.localtime() + + class FakeDatetime: + def timestamp(self): + return time_module.time() + + def strftime(self, fmt): + return time_module.strftime(fmt, t) + + def isoformat(self): + return time_module.strftime('%Y-%m-%dT%H:%M:%S', t) + + return FakeDatetime() + + +def get_unix_timestamp() -> float: + """Get current Unix timestamp.""" + _init_datetime() + if _datetime_available: + return _dt_datetime.now().timestamp() + return time_module.time() diff --git a/comfyui_manager/glob/manager_core.py b/comfyui_manager/glob/manager_core.py index 1951526b..72b76857 100644 --- a/comfyui_manager/glob/manager_core.py +++ b/comfyui_manager/glob/manager_core.py @@ -12,9 +12,9 @@ import re import shutil import configparser import platform -from datetime import datetime import git +from comfyui_manager.common.timestamp_utils import get_timestamp_for_path, get_backup_branch_name from git.remote import RemoteProgress from urllib.parse import urlparse from tqdm.auto import tqdm @@ -2000,7 +2000,15 @@ def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=Fa return False, True try: - remote.pull() + try: + repo.git.pull('--ff-only') + except git.GitCommandError: + backup_name = get_backup_branch_name(repo) + repo.create_head(backup_name) + logging.info(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}") + repo.git.reset('--hard', f'{remote_name}/{branch_name}') + logging.info(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}") + repo.git.submodule('update', '--init', '--recursive') new_commit_hash = repo.head.commit.hexsha @@ -2169,9 +2177,17 @@ def git_pull(path): current_branch = repo.active_branch remote_name = current_branch.tracking_branch().remote_name - remote = repo.remote(name=remote_name) + branch_name = current_branch.name + + try: + repo.git.pull('--ff-only') + except git.GitCommandError: + backup_name = get_backup_branch_name(repo) + repo.create_head(backup_name) + logging.info(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}") + repo.git.reset('--hard', f'{remote_name}/{branch_name}') + logging.info(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}") - remote.pull() repo.git.submodule('update', '--init', '--recursive') repo.close() @@ -2681,9 +2697,7 @@ async def get_current_snapshot(custom_nodes_only = False): async def save_snapshot_with_postfix(postfix, path=None, custom_nodes_only = False): if path is None: - now = datetime.now() - - date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S") + date_time_format = get_timestamp_for_path() file_name = f"{date_time_format}_{postfix}" path = os.path.join(context.manager_snapshot_path, f"{file_name}.json") diff --git a/comfyui_manager/glob/manager_server.py b/comfyui_manager/glob/manager_server.py index fd06010e..c81b482e 100644 --- a/comfyui_manager/glob/manager_server.py +++ b/comfyui_manager/glob/manager_server.py @@ -20,10 +20,12 @@ import threading import traceback import urllib.request import uuid +import time import zipfile -from datetime import datetime, timedelta from typing import Any, Optional +from comfyui_manager.common.timestamp_utils import get_timestamp_for_filename, get_now + import folder_paths import latent_preview import nodes @@ -267,9 +269,9 @@ class TaskQueue: def _start_new_batch(self) -> None: """Start a new batch session for tracking operations.""" self.batch_id = ( - f"batch_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}" + f"batch_{get_timestamp_for_filename()}_{uuid.uuid4().hex[:8]}" ) - self.batch_start_time = datetime.now().isoformat() + self.batch_start_time = get_now().isoformat() self.batch_state_before = self._capture_system_state() logging.debug("[ComfyUI-Manager] Started new batch: %s", self.batch_id) @@ -300,7 +302,7 @@ class TaskQueue: MessageTaskStarted( ui_id=item.ui_id, kind=item.kind, - timestamp=datetime.now(), + timestamp=get_now(), state=self.get_current_state(), ), client_id=item.client_id, # Send task started only to the client that requested it @@ -317,8 +319,7 @@ class TaskQueue: """Mark task as completed and add to history""" with self.mutex: - now = datetime.now() - timestamp = now.isoformat() + now = get_now() # Remove task from running_tasks using the task_index self.running_tasks.pop(task_index, None) @@ -383,7 +384,7 @@ class TaskQueue: result=result_msg, kind=item.kind, status=status, - timestamp=datetime.fromisoformat(timestamp), + timestamp=now, state=self.get_current_state(), ), client_id=item.client_id, # Send completion only to the client that requested it @@ -494,7 +495,7 @@ class TaskQueue: ) try: - end_time = datetime.now().isoformat() + end_time = get_now().isoformat() state_after = self._capture_system_state() operations = self._extract_batch_operations() @@ -562,7 +563,7 @@ class TaskQueue: """Capture current ComfyUI system state for batch record.""" logging.debug("[ComfyUI-Manager] Capturing system state for batch record") return ComfyUISystemState( - snapshot_time=datetime.now().isoformat(), + snapshot_time=get_now().isoformat(), comfyui_version=self._get_comfyui_version_info(), frontend_version=self._get_frontend_version(), python_version=platform.python_version(), @@ -789,8 +790,8 @@ class TaskQueue: to avoid disrupting normal operations. """ try: - cutoff = datetime.now() - timedelta(days=16) - cutoff_timestamp = cutoff.timestamp() + # 16 days in seconds + cutoff_timestamp = time.time() - (16 * 24 * 60 * 60) pattern = os.path.join(context.manager_batch_history_path, "batch_*.json") removed_count = 0 diff --git a/comfyui_manager/legacy/manager_core.py b/comfyui_manager/legacy/manager_core.py index 98e2cc3d..dd187ba2 100644 --- a/comfyui_manager/legacy/manager_core.py +++ b/comfyui_manager/legacy/manager_core.py @@ -15,6 +15,7 @@ import platform from datetime import datetime import git +from comfyui_manager.common.timestamp_utils import get_timestamp_for_path, get_backup_branch_name from git.remote import RemoteProgress from urllib.parse import urlparse from tqdm.auto import tqdm @@ -2012,7 +2013,15 @@ def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=Fa return False, True try: - remote.pull() + try: + repo.git.pull('--ff-only') + except git.GitCommandError: + backup_name = get_backup_branch_name(repo) + repo.create_head(backup_name) + logging.info(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}") + repo.git.reset('--hard', f'{remote_name}/{branch_name}') + logging.info(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}") + repo.git.submodule('update', '--init', '--recursive') new_commit_hash = repo.head.commit.hexsha @@ -2167,9 +2176,17 @@ def git_pull(path): current_branch = repo.active_branch remote_name = current_branch.tracking_branch().remote_name - remote = repo.remote(name=remote_name) + branch_name = current_branch.name + + try: + repo.git.pull('--ff-only') + except git.GitCommandError: + backup_name = get_backup_branch_name(repo) + repo.create_head(backup_name) + logging.info(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}") + repo.git.reset('--hard', f'{remote_name}/{branch_name}') + logging.info(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}") - remote.pull() repo.git.submodule('update', '--init', '--recursive') repo.close() @@ -2683,9 +2700,7 @@ async def get_current_snapshot(custom_nodes_only = False): async def save_snapshot_with_postfix(postfix, path=None, custom_nodes_only = False): if path is None: - now = datetime.now() - - date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S") + date_time_format = get_timestamp_for_path() file_name = f"{date_time_format}_{postfix}" path = os.path.join(context.manager_snapshot_path, f"{file_name}.json") diff --git a/comfyui_manager/prestartup_script.py b/comfyui_manager/prestartup_script.py index b7a808b9..29ce42bb 100644 --- a/comfyui_manager/prestartup_script.py +++ b/comfyui_manager/prestartup_script.py @@ -16,25 +16,11 @@ from .common import security_check from .common import manager_util from .common import cm_global from .common import manager_downloader +from .common.timestamp_utils import current_timestamp import folder_paths manager_util.add_python_path_to_env() -import datetime as dt - -if hasattr(dt, 'datetime'): - from datetime import datetime as dt_datetime - - def current_timestamp(): - return dt_datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] -else: - # NOTE: Occurs in some Mac environments. - import time - logging.error(f"[ComfyUI-Manager] fallback timestamp mode\n datetime module is invalid: '{dt.__file__}'") - - def current_timestamp(): - return str(time.time()).split('.')[0] - cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'} cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia'] diff --git a/pyproject.toml b/pyproject.toml index 083e0acd..226e90e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "comfyui-manager" license = { text = "GPL-3.0-only" } -version = "4.0.3b4" +version = "4.0.3b5" requires-python = ">= 3.9" description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI." readme = "README.md"