2828
2929log = logging .getLogger (__name__ )
3030
31+ ConfigScope = t .Literal ["system" , "user" , "project" , "external" ]
32+
33+
34+ def _classify_config_scope (
35+ config_path : pathlib .Path ,
36+ * ,
37+ cwd : pathlib .Path ,
38+ home : pathlib .Path ,
39+ ) -> ConfigScope :
40+ """Determine whether a config lives in user, system, project, or external scope."""
41+
42+ resolved = config_path .expanduser ().resolve ()
43+ home = home .expanduser ().resolve ()
44+ cwd = cwd .expanduser ().resolve ()
45+
46+ default_user_configs = {
47+ (home / ".vcspull.yaml" ).resolve (),
48+ (home / ".vcspull.json" ).resolve (),
49+ }
50+ if resolved in default_user_configs :
51+ return "user"
52+
53+ xdg_config_home = pathlib .Path (
54+ os .environ .get ("XDG_CONFIG_HOME" , home / ".config" )
55+ ).expanduser ().resolve ()
56+ user_config_root = (xdg_config_home / "vcspull" ).resolve ()
57+ try :
58+ resolved .relative_to (user_config_root )
59+ return "user"
60+ except ValueError :
61+ pass
62+
63+ xdg_config_dirs_value = os .environ .get ("XDG_CONFIG_DIRS" )
64+ if xdg_config_dirs_value :
65+ config_dir_bases = [
66+ pathlib .Path (entry ).expanduser ().resolve ()
67+ for entry in xdg_config_dirs_value .split (os .pathsep )
68+ if entry
69+ ]
70+ else :
71+ config_dir_bases = [pathlib .Path ("/etc/xdg" ).resolve ()]
72+
73+ for base in config_dir_bases :
74+ candidate = (base / "vcspull" ).resolve ()
75+ try :
76+ resolved .relative_to (candidate )
77+ return "system"
78+ except ValueError :
79+ continue
80+
81+ try :
82+ resolved .relative_to (cwd )
83+ return "project"
84+ except ValueError :
85+ return "external"
86+
3187
3288def get_git_origin_url (repo_path : pathlib .Path ) -> str | None :
3389 """Get the origin URL from a git repository.
@@ -199,6 +255,11 @@ def discover_repos(
199255
200256 display_config_path = str (PrivatePath (config_file_path ))
201257
258+ cwd = pathlib .Path .cwd ()
259+ home = pathlib .Path .home ()
260+ config_scope = _classify_config_scope (config_file_path , cwd = cwd , home = home )
261+ allow_relative_workspace = config_scope == "project"
262+
202263 raw_config : dict [str , t .Any ]
203264 duplicate_root_occurrences : dict [str , list [t .Any ]]
204265 if config_file_path .exists () and config_file_path .is_file ():
@@ -288,8 +349,8 @@ def discover_repos(
288349 "" if occurrence_count == 1 else "s" ,
289350 )
290351
291- cwd = pathlib . Path . cwd ()
292- home = pathlib . Path . home ()
352+ explicit_relative_override = workspace_root_override in { "." , "./" }
353+ preserve_cwd_label = explicit_relative_override or allow_relative_workspace
293354
294355 if merge_duplicates :
295356 (
@@ -301,6 +362,7 @@ def discover_repos(
301362 raw_config ,
302363 cwd = cwd ,
303364 home = home ,
365+ preserve_cwd_label = preserve_cwd_label ,
304366 )
305367 else :
306368 (
@@ -312,6 +374,7 @@ def discover_repos(
312374 raw_config ,
313375 cwd = cwd ,
314376 home = home ,
377+ preserve_cwd_label = preserve_cwd_label ,
315378 )
316379
317380 for message in merge_conflicts :
@@ -378,7 +441,12 @@ def discover_repos(
378441 for name , url , workspace_path in found_repos :
379442 workspace_label = workspace_map .get (workspace_path )
380443 if workspace_label is None :
381- workspace_label = workspace_root_label (workspace_path , cwd = cwd , home = home )
444+ workspace_label = workspace_root_label (
445+ workspace_path ,
446+ cwd = cwd ,
447+ home = home ,
448+ preserve_cwd_label = preserve_cwd_label ,
449+ )
382450 workspace_map [workspace_path ] = workspace_label
383451 raw_config .setdefault (workspace_label , {})
384452
@@ -416,6 +484,7 @@ def discover_repos(
416484 workspace_path ,
417485 cwd = cwd ,
418486 home = home ,
487+ preserve_cwd_label = preserve_cwd_label ,
419488 )
420489 workspace_map [workspace_path ] = workspace_label
421490 raw_config .setdefault (workspace_label , {})
@@ -517,7 +586,12 @@ def discover_repos(
517586 for repo_name , repo_url , workspace_path in repos_to_add :
518587 workspace_label = workspace_map .get (workspace_path )
519588 if workspace_label is None :
520- workspace_label = workspace_root_label (workspace_path , cwd = cwd , home = home )
589+ workspace_label = workspace_root_label (
590+ workspace_path ,
591+ cwd = cwd ,
592+ home = home ,
593+ preserve_cwd_label = preserve_cwd_label ,
594+ )
521595 workspace_map [workspace_path ] = workspace_label
522596
523597 if workspace_label not in raw_config :
0 commit comments