SharpSVN出错信息:Can't determine the user's config path,从此证明了百度是个垃圾

本文介绍了一次使用SharpSvn库时遇到的配置路径未确定异常,并提供了具体的解决方案。作者通过设置临时目录下的SVN配置路径,成功解决了远程SVN仓库日志获取失败的问题。

 

找了半天没找到问题,从Google一搜就看到了,速度解决之!

Till the beginning of this month, I was a regular SVN user using SVN as my Version Control System for many projects. But then, somewhere in the second week of this month, I have turned a SVN developer (not writing code for SVN itself, but providing solutions off SVN).

For one of the projects code-named Zone, I began writing a module for SVN integration to provide seamless access to local or remote SVN repositories. Without second thought, CollabNet's open-source SharpSvn library was my library of choice for SVN development.

However my first attempt at fetching log from a remote svn repo failed with the following exception:

 

1SharpSvn.SvnFormatException: Can't determine the user's config path

 

SharpSvn's bundled documentation is good, but not that great. And googling also did not reveal much about this problem. In bits and pieces, I was able to conclude that this exception probably had to do with the missing SVN configuration path (which is a directory for holding run-time configuration information for Subversion clients).

So, do I needed to create this directory or SharpSvn would do it for itself, or is there any specific layout for this directory, etc. were the questions lurking in my mind at that point. One thing that was sure was because the code was executing as a part of ASP.NET application, it cannot and should not use a regular interactive user's Subversion configuration path. And neither should it try to create a new directory somewhere in an interactive user's folders (As a Windows 7 user, my Windows account's Subversion configuration directory is located at: C:\Users\Rahul Singla\AppData\Roaming\Subversion).

Some quick browsing through the SharpSvn's API in Visual Studio's Object Browser, I was able to figure out the LoadConfiguration method on the main SvnClient class. So, the following method call before trying to connect to a remote repo resolved the exception I was getting:

 

 

1using (SvnClient client = new SvnClient())
2{
3  client.LoadConfiguration(Path.Combine(Path.GetTempPath(), "Svn"),true);
4//....More code

 

Here, I am using sub-directory under Windows temporary directory for the ASP.NET application user account's svn configuration path. You might want to use a sub-directory under your application root, or another permanenet folder preferably.

Stay tuned for more SharpSvn blog posts...

# core/config.py import os import json import logging from pathlib import Path from typing import Dict, Any, Optional, Union, List, Tuple import hashlib import sys import configparser import yaml class CoreConfig: """修复递归错误的核心配置管理器""" _instance = None # 单例实例 def __new__(cls, *args, **kwargs): """实现单例模式""" if cls._instance is None: cls._instance = super().__new__(cls) # 在创建实例时初始化关键属性 cls._instance._initialized = False cls._instance._config = {} return cls._instance def __init__( self, config_path: Union[str, Path] = None, env_prefix: str = "AI_SYSTEM_", default_config: Dict = None, log_level: str = "INFO" ): """初始化配置管理器 修复方案: 1. 在__new__中初始化关键属性 2. 添加明确的初始化状态检查 3. 重构__getattr__方法避免递归 """ # 确保只初始化一次 if self._initialized: return # 标记为已初始化 self._initialized = True # 1. 存储基本属性 self.env_prefix = env_prefix self.config_path = Path(config_path) if config_path else None self.default_config = default_config or {} # 2. 确定基础目录 self.BASE_DIR = self._determine_base_dir() # 3. 初始化基本配置 self._config = { 'BASE_DIR': str(self.BASE_DIR), 'LOG_DIR': str(self.BASE_DIR / "logs"), 'CONFIG_DIR': str(self.BASE_DIR / "config"), 'CACHE_DIR': str(self.BASE_DIR / "cache"), 'DATA_DIR': str(self.BASE_DIR / "data"), 'MODEL_DIR': str(self.BASE_DIR / "models"), 'TEMP_DIR': str(self.BASE_DIR / "temp"), } # 4. 初始化日志器 self.logger = self._create_safe_logger(log_level) # 5. 完成初始化 self._initialize() self.logger.info("[OK] 配置管理器初始化完成 | 环境前缀: %s | 基础目录: %s", self.env_prefix, self.BASE_DIR) def _determine_base_dir(self) -> Path: """确定基础目录""" # 1. 尝试环境变量 base_dir_env = os.getenv(f"{self.env_prefix}BASE_DIR") if base_dir_env: return Path(base_dir_env).resolve() # 2. 尝试文件位置 try: return Path(__file__).parent.parent.resolve() except Exception: pass # 3. 默认当前目录 return Path.cwd().resolve() def _create_safe_logger(self, log_level: str) -> logging.Logger: """创建安全的跨平台日志器""" logger = logging.getLogger('CoreConfig') # 设置日志级别 level = getattr(logging, log_level.upper(), logging.INFO) logger.setLevel(level) # 移除所有现有处理器 for handler in logger.handlers[:]: logger.removeHandler(handler) # 安全控制台处理器 class SafeConsoleHandler(logging.StreamHandler): def __init__(self, stream=None): super().__init__(stream) self.encoding = self._detect_safe_encoding() def _detect_safe_encoding(self) -> str: """检测安全的控制台编码""" # 尝试UTF-8 if sys.stdout and hasattr(sys.stdout, 'encoding'): if sys.stdout.encoding and sys.stdout.encoding.lower().startswith('utf'): return 'utf-8' # Windows上尝试设置UTF-8代码页 if sys.platform == 'win32': try: os.system('chcp 65001 > nul') return 'utf-8' except: pass # 回退到ASCII return 'ascii' def emit(self, record): try: msg = self.format(record) # 替换表情符号为文本 msg = self._sanitize_message(msg) # 确保正确编码 if isinstance(msg, str): msg = msg.encode(self.encoding, errors='replace').decode(self.encoding) self.stream.write(msg + self.terminator) self.flush() except Exception: self.handleError(record) def _sanitize_message(self, msg: str) -> str: """替换或移除不支持的字符""" replacements = { '🔄': '[RELOAD]', '✅': '[OK]', '⚠️': '[WARN]', '❌': '[ERROR]', '📁': '[FOLDER]' } for char, replacement in replacements.items(): msg = msg.replace(char, replacement) return msg # 创建处理器 console_handler = SafeConsoleHandler() # 纯文本格式器 formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', '%Y-%m-%d %H:%M:%S' ) console_handler.setFormatter(formatter) # 添加处理器并禁用传播 logger.addHandler(console_handler) logger.propagate = False return logger def _initialize(self): """初始化配置流程""" try: self.logger.info("[RELOAD] 加载环境变量...") self._load_environment() if self.config_path and self.config_path.exists(): self.logger.info("加载配置文件: %s", self.config_path) self._load_config_file() elif self.config_path: self.logger.warning("[WARN] 配置文件不存在: %s", self.config_path) self.logger.info("[RELOAD] 合并默认配置...") self._merge_defaults() self.logger.info("创建必要目录...") self._create_directories() self.logger.info("[OK] 配置加载完成 | 条目数: %d", len(self._config)) except Exception as e: self.logger.error("[ERROR] 配置初始化失败: %s", str(e), exc_info=True) def _load_environment(self): """加载环境变量到配置""" for key, value in os.environ.items(): if key.startswith(self.env_prefix): config_key = key[len(self.env_prefix):] # 处理嵌套键 (如 DB_HOST → db.host) if '__' in config_key: parts = config_key.split('__') current_level = self._config for part in parts[:-1]: part = part.lower() if part not in current_level: current_level[part] = {} current_level = current_level[part] current_level[parts[-1].lower()] = value self.logger.debug("加载环境变量: %s = %s", config_key, value) else: self._config[config_key.lower()] = value self.logger.debug("加载环境变量: %s = %s", config_key, value) def _load_config_file(self): """加载配置文件""" if not self.config_path or not self.config_path.exists(): return file_type = self.config_path.suffix.lower() try: if file_type == '.json': with open(self.config_path, 'r', encoding='utf-8') as f: config_data = json.load(f) elif file_type in ('.yaml', '.yml'): with open(self.config_path, 'r', encoding='utf-8') as f: config_data = yaml.safe_load(f) elif file_type == '.ini': parser = configparser.ConfigParser() parser.read(self.config_path, encoding='utf-8') config_data = {} for section in parser.sections(): config_data[section] = dict(parser.items(section)) else: raise ValueError(f"不支持的配置文件类型: {file_type}") # 合并配置 self._deep_merge(self._config, config_data) self._last_hash = self._calculate_hash() self.logger.info("[OK] 配置文件加载成功") except Exception as e: self.logger.error("[ERROR] 加载配置文件失败: %s", str(e), exc_info=True) def _merge_defaults(self): """合并默认配置""" self._deep_merge(self._config, self.default_config) def _deep_merge(self, target: Dict, source: Dict): """深度合并字典""" for key, value in source.items(): if (key in target and isinstance(target[key], dict) and isinstance(value, dict)): self._deep_merge(target[key], value) else: target[key] = value def _calculate_hash(self) -> str: """计算配置哈希值""" return hashlib.md5( json.dumps(self._config, sort_keys=True).encode('utf-8') ).hexdigest() def _create_directories(self): """创建配置中指定的目录""" dir_keys = ['LOG_DIR', 'CACHE_DIR', 'MODEL_DIR', 'DATA_DIR', 'TEMP_DIR'] created_dirs = [] for key in dir_keys: path_str = self._config.get(key) if not path_str: continue path = Path(path_str) if not path.is_absolute(): path = self.BASE_DIR / path if not path.exists(): try: path.mkdir(parents=True, exist_ok=True) created_dirs.append(str(path)) except Exception as e: self.logger.error("[ERROR] 创建目录失败: %s | 错误: %s", path, str(e)) if created_dirs: self.logger.info("[FOLDER] 创建了 %d 个目录: %s", len(created_dirs), ", ".join(created_dirs)) def get(self, key: str, default: Any = None) -> Any: """安全获取配置值 Args: key: 配置键 (支持点号分隔的嵌套键) default: 默认值 (如果键不存在) Returns: 配置值或默认值 """ keys = key.split('.') current = self._config for k in keys: if isinstance(current, dict) and k in current: current = current[k] else: return default return current def get_path(self, key: str, default: Optional[Union[str, Path]] = None) -> Path: """获取路径配置项 Args: key: 配置键 default: 默认路径 (如果键不存在) Returns: 绝对路径对象 """ path_str = self.get(key, default) if path_str is None: return self.BASE_DIR / "default" path = Path(path_str) if not path.is_absolute(): return self.BASE_DIR / path return path.resolve() def get_int(self, key: str, default: int = 0) -> int: """获取整数配置值""" value = self.get(key, default) try: return int(value) except (TypeError, ValueError): return default def get_float(self, key: str, default: float = 0.0) -> float: """获取浮点数配置值""" value = self.get(key, default) try: return float(value) except (TypeError, ValueError): return default def get_bool(self, key: str, default: bool = False) -> bool: """获取布尔值配置值""" value = self.get(key, default) if isinstance(value, bool): return value if isinstance(value, str): return value.lower() in ('true', 'yes', '1', 'on') return bool(value) def get_list(self, key: str, default: list = None, separator: str = ',') -> list: """获取列表配置值""" if default is None: default = [] value = self.get(key, default) if isinstance(value, list): return value if isinstance(value, str): return [item.strip() for item in value.split(separator)] return default def reload(self): """重新加载配置""" self.logger.info("[RELOAD] 重新加载配置...") try: # 保留初始化状态 initialized = self._initialized self._initialized = False # 重置配置 self._config = {} self._initialize() # 恢复初始化状态 self._initialized = initialized self.logger.info("[OK] 配置重新加载成功") return True except Exception as e: self.logger.error("[ERROR] 配置重载失败: %s", str(e)) return False def dump_config(self, file_path: Union[str, Path] = None) -> str: """导出当前配置为JSON字符串或文件""" config_json = json.dumps(self._config, indent=2, ensure_ascii=False) if file_path: try: with open(file_path, 'w', encoding='utf-8') as f: f.write(config_json) self.logger.info("[OK] 配置已导出到: %s", file_path) except Exception as e: self.logger.error("[ERROR] 导出配置失败: %s", str(e)) return config_json def __getattr__(self, name: str) -> Any: """安全属性访问方法 - 修复递归问题""" # 1. 检查是否在初始化过程中 if name == '_config' and not hasattr(self, '_config'): raise AttributeError("_config 属性尚未初始化") # 2. 检查是否在初始化过程中访问其他属性 if not self._initialized and name != '_initialized': raise AttributeError(f"配置尚未初始化,无法访问属性: {name}") # 3. 检查配置字典 if name in self._config: return self._config[name] # 4. 检查嵌套属性 (key.subkey) if '.' in name: parts = name.split('.') current = self._config try: for part in parts: if isinstance(current, dict) and part in current: current = current[part] else: break else: # 所有部分都存在 return current except TypeError: pass # 当前值不是字典 # 5. 尝试直接访问实例属性 try: # 使用object.__getattribute__避免递归 return object.__getattribute__(self, name) except AttributeError: # 6. 抛出明确异常 raise AttributeError( f"配置项 '{name}' 在 {type(self).__name__} 中不存在" ) from None def __contains__(self, key: str) -> bool: """检查配置项是否存在""" return key in self._config def __str__(self) -> str: """返回配置摘要""" return f"CoreConfig(entries={len(self._config)}, base_dir={self.BASE_DIR})" def __len__(self) -> int: """返回配置项数量""" return len(self._config) def keys(self) -> list: """返回所有配置键""" return list(self._config.keys()) def items(self) -> list: """返回所有配置项 (键值对)""" return list(self._config.items())
最新发布
08-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值