Config - vcspull.config¶
Configuration functionality for vcspull.
- vcspull.config.expand_dir(dir_, cwd=pathlib.Path.cwd)[source]¶
Return path with environmental variables and tilde ~ expanded.
- Return type:
- Parameters:
dir (pathlib.Path) – Directory path to expand
cwd (pathlib.Path, optional) – Current working dir (used to resolve relative paths). Defaults to
pathlib.Path.cwd().dir_ (Path)
- Returns:
Absolute directory path
- Return type:
- vcspull.config._validate_worktrees_config(worktrees_raw, repo_name)[source]¶
Validate and normalize worktrees configuration.
- Return type:
- Parameters:
worktrees_raw (Any) – Raw worktrees configuration from YAML/JSON.
repo_name (str) – Name of the parent repository (for error messages).
- Returns:
Validated list of worktree configurations.
- Return type:
- Raises:
VCSPullException – If the worktrees configuration is invalid.
Examples
Valid configuration with a tag:
>>> from vcspull.config import _validate_worktrees_config >>> config = [{"dir": "../v1", "tag": "v1.0.0"}] >>> result = _validate_worktrees_config(config, "myrepo") >>> len(result) 1 >>> result[0]["dir"] '../v1' >>> result[0]["tag"] 'v1.0.0'
Valid configuration with a branch:
>>> config = [{"dir": "../dev", "branch": "develop"}] >>> result = _validate_worktrees_config(config, "myrepo") >>> result[0]["branch"] 'develop'
Valid configuration with a commit:
>>> config = [{"dir": "../fix", "commit": "abc123"}] >>> result = _validate_worktrees_config(config, "myrepo") >>> result[0]["commit"] 'abc123'
Error: worktrees must be a list:
>>> _validate_worktrees_config("not-a-list", "myrepo") Traceback (most recent call last): ... vcspull.exc.VCSPullException: ...worktrees must be a list, got str
Error: worktree entry must be a dict:
>>> _validate_worktrees_config(["not-a-dict"], "myrepo") Traceback (most recent call last): ... vcspull.exc.VCSPullException: ...must be a dict, got str
Error: missing required ‘dir’ field:
>>> _validate_worktrees_config([{"tag": "v1.0.0"}], "myrepo") Traceback (most recent call last): ... vcspull.exc.VCSPullException: ...missing required 'dir' field
Error: no ref type specified:
>>> _validate_worktrees_config([{"dir": "../wt"}], "myrepo") Traceback (most recent call last): ... vcspull.exc.VCSPullException: ...must specify one of: tag, branch, or commit
Error: empty ref value:
>>> _validate_worktrees_config([{"dir": "../wt", "tag": ""}], "myrepo") Traceback (most recent call last): ... vcspull.exc.VCSPullException: ...empty ref value...
Error: multiple refs specified:
>>> _validate_worktrees_config( ... [{"dir": "../wt", "tag": "v1", "branch": "main"}], "myrepo" ... ) Traceback (most recent call last): ... vcspull.exc.VCSPullException: ...cannot specify multiple refs...
- vcspull.config.extract_repos(config, cwd=pathlib.Path.cwd)[source]¶
Return expanded configuration.
end-user configuration permit inline configuration shortcuts, expand to identical format for parsing.
- Return type:
- Parameters:
cwd (pathlib.Path) – current working dir (for deciphering relative paths)
- Returns:
list
- Return type:
List of normalized repository information
- vcspull.config.find_home_config_files(filetype=None)[source]¶
Return configs of
.vcspull.{yaml,json}in user’s home directory.
- vcspull.config.find_config_files(path=None, match=None, filetype=None, include_home=False)[source]¶
Return repos from a directory and match. Not recursive.
- Return type:
- Parameters:
- Raises:
LoadConfigRepoConflict : – There are two configs that have same path and name with different repo urls.
- Returns:
list of absolute paths to config files.
- Return type:
- vcspull.config.load_configs(files, cwd=pathlib.Path.cwd, *, merge_duplicates=True)[source]¶
Return repos from a list of files.
- Return type:
- Parameters:
files (list) – paths to config file
cwd (pathlib.Path) – current path (pass down for
extract_repos()merge_duplicates (bool)
- Returns:
expanded config dict item
- Return type:
- vcspull.config.detect_duplicate_repos(config1, config2)[source]¶
Return duplicate repos dict if repo_dir same and vcs different.
- Return type:
- Parameters:
config1 (list[ConfigDict])
config2 (list[ConfigDict])
- Returns:
List of duplicate tuples
- Return type:
- vcspull.config.in_dir(config_dir=None, extensions=None)[source]¶
Return a list of configs in
config_dir.
- vcspull.config.filter_repos(config, path=None, vcs_url=None, name=None)[source]¶
Return a
listlist of repos from (expanded) config file.path, vcs_url and name all support fnmatch.
- Return type:
- Parameters:
- Returns:
Repos
- Return type:
- vcspull.config.is_config_file(filename, extensions=None)[source]¶
Return True if file has a valid config file type.
- vcspull.config._atomic_write(target, content)[source]¶
Write content to a file atomically via temp-file-then-rename.
- Return type:
- Parameters:
target (pathlib.Path) – Destination file path
content (str) – Content to write
- vcspull.config.save_config_yaml(config_file_path, data)[source]¶
Save configuration data to a YAML file.
- Return type:
- Parameters:
config_file_path (pathlib.Path) – Path to the configuration file to write
data (dict) – Configuration data to save
- vcspull.config.save_config_json(config_file_path, data)[source]¶
Save configuration data to a JSON file.
- Return type:
- Parameters:
config_file_path (pathlib.Path) – Path to the configuration file to write
data (dict) – Configuration data to save
- vcspull.config.save_config(config_file_path, data)[source]¶
Save configuration data, dispatching by file extension.
- Return type:
- Parameters:
config_file_path (pathlib.Path) – Path to the configuration file to write
data (dict) – Configuration data to save
Examples
>>> import pathlib, tempfile, json >>> with tempfile.TemporaryDirectory() as tmp: ... p = pathlib.Path(tmp) / "test.json" ... save_config(p, {"~/code/": {"repo": {"repo": "git+https://x"}}}) ... loaded = json.loads(p.read_text()) ... loaded["~/code/"]["repo"]["repo"] 'git+https://x'
>>> with tempfile.TemporaryDirectory() as tmp: ... p = pathlib.Path(tmp) / "test.yaml" ... save_config(p, {"~/code/": {"repo": {"repo": "git+https://x"}}}) ... "repo" in p.read_text() True
- vcspull.config.save_config_yaml_with_items(config_file_path, items)[source]¶
Persist configuration data while preserving duplicate top-level sections.
- vcspull.config._VALID_OPS: frozenset[str] = frozenset({'add', 'discover', 'fmt', 'import', 'merge'})¶
Valid operation names for pin checking.
- vcspull.config.is_pinned_for_op(entry, op)[source]¶
Return
Trueif the repo config entry is pinned for op.- Return type:
- Parameters:
entry (Any) – Raw repo config value (string, dict, or
None).op (str) – Operation name:
"import","add","discover","fmt", or"merge".
Examples
Global pin applies to all ops:
>>> is_pinned_for_op({"repo": "git+x", "options": {"pin": True}}, "import") True >>> is_pinned_for_op({"repo": "git+x", "options": {"pin": True}}, "fmt") True
Per-op pin is scoped:
>>> entry = {"repo": "git+x", "options": {"pin": {"import": True}}} >>> is_pinned_for_op(entry, "import") True >>> is_pinned_for_op(entry, "fmt") False
allow_overwrite: falseis shorthand forpin: {import: true}(guards against--sync):>>> entry2 = {"repo": "git+x", "options": {"allow_overwrite": False}} >>> is_pinned_for_op(entry2, "import") True >>> is_pinned_for_op(entry2, "add") False
Plain string entries and entries without options are never pinned:
>>> is_pinned_for_op("git+x", "import") False >>> is_pinned_for_op({"repo": "git+x"}, "import") False
Explicit false is not pinned:
>>> is_pinned_for_op({"repo": "git+x", "options": {"pin": False}}, "import") False
String values for pin (not bool) are ignored — not pinned:
>>> is_pinned_for_op({"repo": "git+x", "options": {"pin": "true"}}, "import") False
Invalid op raises ValueError:
>>> is_pinned_for_op( ... {"repo": "git+x"}, "bogus" ... ) Traceback (most recent call last): ... ValueError: Unknown op: 'bogus'
- vcspull.config.get_pin_reason(entry)[source]¶
Return the human-readable pin reason from a repo config entry.
Non-string values are coerced to
str()so callers can safely interpolate the result into log messages.Examples
>>> entry = {"repo": "git+x", "options": {"pin": True, "pin_reason": "pinned"}} >>> get_pin_reason(entry) 'pinned' >>> get_pin_reason({"repo": "git+x"}) is None True >>> get_pin_reason("git+x") is None True
Non-string pin_reason is coerced:
>>> get_pin_reason({"repo": "git+x", "options": {"pin_reason": 42}}) '42'
- class vcspull.config.MergeAction[source]¶
Bases:
EnumAction for resolving a duplicate workspace-root repo conflict.
- KEEP_EXISTING = 'keep_existing'¶
First occurrence wins (standard behavior).
- KEEP_INCOMING = 'keep_incoming'¶
Incoming pinned entry displaces unpinned existing entry.
- vcspull.config._classify_merge_action(existing_entry, incoming_entry)[source]¶
Classify the merge conflict resolution action.
- Return type:
- Parameters:
existing_entry (Any) – The entry already stored (first occurrence).
incoming_entry (Any) – The duplicate entry being merged in.
Examples
Neither pinned — keep existing (first-occurrence-wins):
>>> _classify_merge_action({"repo": "git+a"}, {"repo": "git+b"}) <MergeAction.KEEP_EXISTING: 'keep_existing'>
Incoming is pinned — incoming wins:
>>> _classify_merge_action( ... {"repo": "git+a"}, ... {"repo": "git+b", "options": {"pin": True}}, ... ) <MergeAction.KEEP_INCOMING: 'keep_incoming'>
Existing is pinned — existing wins regardless:
>>> _classify_merge_action( ... {"repo": "git+a", "options": {"pin": True}}, ... {"repo": "git+b"}, ... ) <MergeAction.KEEP_EXISTING: 'keep_existing'>
Both pinned — first-occurrence-wins:
>>> _classify_merge_action( ... {"repo": "git+a", "options": {"pin": True}}, ... {"repo": "git+b", "options": {"pin": True}}, ... ) <MergeAction.KEEP_EXISTING: 'keep_existing'>
- vcspull.config.merge_duplicate_workspace_root_entries(label, occurrences)[source]¶
Merge duplicate entries for a single workspace root.
- vcspull.config.merge_duplicate_workspace_roots(config_data, duplicate_roots)[source]¶
Merge duplicate workspace root sections captured during load.
- vcspull.config.canonicalize_workspace_path(label, *, cwd=None)[source]¶
Convert a workspace root label to an absolute canonical path.
- vcspull.config.workspace_root_label(workspace_path, *, cwd=None, home=None, preserve_cwd_label=True)[source]¶
Create a normalized label for a workspace root path.