mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2026-03-30 05:13:35 +08:00
* feat: add pygit2 compatibility wrapper for standalone Desktop 2.0 installs Add git_compat.py abstraction layer that wraps both GitPython and pygit2 behind a unified GitRepo interface. When CM_USE_PYGIT2=1 is set (by the Desktop 2.0 Launcher for standalone installs), or when system git is not available, the pygit2 backend is used automatically. Key changes: - New comfyui_manager/common/git_compat.py with abstract GitRepo base class, _GitPythonRepo (1:1 pass-throughs) and _Pygit2Repo implementations - All 6 non-legacy files updated to use the wrapper: - comfyui_manager/glob/manager_core.py (14 git.Repo usages) - comfyui_manager/common/git_helper.py (7 git.Repo usages) - comfyui_manager/common/context.py (1 usage) - comfyui_manager/glob/utils/environment_utils.py (2 usages) - cm_cli/__main__.py (1 usage) - comfyui_manager/common/timestamp_utils.py (repo.heads usage) - get_script_env() propagates CM_USE_PYGIT2 to subprocesses - git_helper.py uses sys.path.insert to find git_compat as standalone script - All repo handles wrapped in context managers to prevent resource leaks - get_head_by_name returns _HeadProxy for interface consistency - Submodule fallback logs clear message when system git is absent - 47 tests comparing both backends via subprocess isolation Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d0ec5-cb9f-74df-a1a2-0c8154a330b3 * fix(pygit2): address review findings + bump version to 4.2b1 - C1: add submodule_update() after pygit2 clone for recursive parity - C2: check subprocess returncode in submodule_update fallback - C3: move GIT_OPT_SET_OWNER_VALIDATION to module-level (once at import) - H1: use context manager in git_pull() to prevent resource leaks - Bump version to 4.2b1, version_code to [4, 2] - Add pygit2 to dev dependencies and requirements.txt * style: fix ruff F841 unused variable and F541 unnecessary f-string --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Dr.Lt.Data <dr.lt.data@gmail.com>
137 lines
3.6 KiB
Python
137 lines
3.6 KiB
Python
"""
|
|
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.list_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()
|