@@ -36,57 +36,113 @@ def from_potential_worktree(cls, wd: _t.PathT) -> HgWorkdir | None:
36
36
return cls (Path (res .stdout ))
37
37
38
38
def get_meta (self , config : Configuration ) -> ScmVersion | None :
39
- node : str
40
- tags_str : str
41
- node_date_str : str
42
- node , tags_str , node_date_str = self .hg_log (
43
- "." , "{node}\n {tag}\n {date|shortdate}"
44
- ).split ("\n " )
45
-
46
39
# TODO: support bookmarks and topics (but nowadays bookmarks are
47
40
# mainly used to emulate Git branches, which is already supported with
48
41
# the dedicated class GitWorkdirHgClient)
49
42
43
+ node_info = self ._get_node_info ()
44
+ if node_info is None :
45
+ return None
46
+
47
+ node , tags_str , node_date_str = node_info
48
+ branch_info = self ._get_branch_info ()
49
+ branch , dirty , dirty_date = branch_info
50
+
51
+ # Determine the appropriate node date
52
+ node_date = self ._get_node_date (dirty , node_date_str , dirty_date )
53
+
54
+ # Handle initial/empty repository
55
+ if self ._is_initial_node (node ):
56
+ return self ._create_initial_meta (config , dirty , branch , node_date )
57
+
58
+ node = "h" + node [:7 ]
59
+ tags = self ._parse_tags (tags_str )
60
+
61
+ # Try to get version from current tags
62
+ tag_version = self ._get_version_from_tags (tags , config )
63
+ if tag_version :
64
+ return meta (tag_version , dirty = dirty , branch = branch , config = config )
65
+
66
+ # Fall back to distance-based versioning
67
+ return self ._get_distance_based_version (config , dirty , branch , node , node_date )
68
+
69
+ def _get_node_info (self ) -> tuple [str , str , str ] | None :
70
+ """Get node, tags, and date information from mercurial log."""
71
+ try :
72
+ node , tags_str , node_date_str = self .hg_log (
73
+ "." , "{node}\n {tag}\n {date|shortdate}"
74
+ ).split ("\n " )
75
+ return node , tags_str , node_date_str
76
+ except ValueError :
77
+ log .exception ("Failed to get node info" )
78
+ return None
79
+
80
+ def _get_branch_info (self ) -> tuple [str , bool , str ]:
81
+ """Get branch name, dirty status, and dirty date."""
50
82
branch , dirty_str , dirty_date = _run (
51
83
[HG_COMMAND , "id" , "-T" , "{branch}\n {if(dirty, 1, 0)}\n {date|shortdate}" ],
52
84
cwd = self .path ,
53
85
check = True ,
54
86
).stdout .split ("\n " )
55
87
dirty = bool (int (dirty_str ))
88
+ return branch , dirty , dirty_date
56
89
57
- # For dirty working directories, try to use the latest file modification time
58
- # before falling back to the hg id date
90
+ def _get_node_date (
91
+ self , dirty : bool , node_date_str : str , dirty_date : str
92
+ ) -> datetime .date :
93
+ """Get the appropriate node date, preferring file modification times for dirty repos."""
59
94
if dirty :
60
95
file_mod_date = self .get_dirty_tag_date ()
61
96
if file_mod_date is not None :
62
- node_date = file_mod_date
63
- else :
64
- node_date = datetime .date .fromisoformat (dirty_date )
97
+ return file_mod_date
98
+ # Fall back to hg id date for dirty repos
99
+ return datetime .date .fromisoformat (dirty_date )
65
100
else :
66
- node_date = datetime .date .fromisoformat (node_date_str )
67
-
68
- if node == "0" * len (node ):
69
- log .debug ("initial node %s" , self .path )
70
- return meta (
71
- Version ("0.0" ),
72
- config = config ,
73
- dirty = dirty ,
74
- branch = branch ,
75
- node_date = node_date ,
76
- )
101
+ return datetime .date .fromisoformat (node_date_str )
77
102
78
- node = "h" + node [:7 ]
103
+ def _is_initial_node (self , node : str ) -> bool :
104
+ """Check if this is an initial/empty repository node."""
105
+ return node == "0" * len (node )
106
+
107
+ def _create_initial_meta (
108
+ self , config : Configuration , dirty : bool , branch : str , node_date : datetime .date
109
+ ) -> ScmVersion :
110
+ """Create metadata for initial/empty repository."""
111
+ log .debug ("initial node %s" , self .path )
112
+ return meta (
113
+ Version ("0.0" ),
114
+ config = config ,
115
+ dirty = dirty ,
116
+ branch = branch ,
117
+ node_date = node_date ,
118
+ )
79
119
120
+ def _parse_tags (self , tags_str : str ) -> list [str ]:
121
+ """Parse and filter tags from mercurial output."""
80
122
tags = tags_str .split ()
81
123
if "tip" in tags :
82
124
# tip is not a real tag
83
125
tags .remove ("tip" )
126
+ return tags
84
127
128
+ def _get_version_from_tags (
129
+ self , tags : list [str ], config : Configuration
130
+ ) -> Version | None :
131
+ """Try to get a version from the current tags."""
85
132
if tags :
86
133
tag = tag_to_version (tags [0 ], config )
87
- if tag :
88
- return meta ( tag , dirty = dirty , branch = branch , config = config )
134
+ return tag
135
+ return None
89
136
137
+ def _get_distance_based_version (
138
+ self ,
139
+ config : Configuration ,
140
+ dirty : bool ,
141
+ branch : str ,
142
+ node : str ,
143
+ node_date : datetime .date ,
144
+ ) -> ScmVersion | None :
145
+ """Get version based on distance from latest tag."""
90
146
try :
91
147
tag_str = self .get_latest_normalizable_tag ()
92
148
if tag_str is None :
@@ -98,8 +154,13 @@ def get_meta(self, config: Configuration) -> ScmVersion | None:
98
154
tag = Version ("0.0" )
99
155
dist += 1
100
156
else :
101
- tag = tag_to_version (tag_str , config = config )
102
- assert tag is not None
157
+ maybe_tag = tag_to_version (tag_str , config = config )
158
+ if maybe_tag is None :
159
+ # If tag conversion fails, treat as no tag found
160
+ tag = Version ("0.0" )
161
+ dist += 1
162
+ else :
163
+ tag = maybe_tag
103
164
104
165
if self .check_changes_since_tag (tag_str ) or dirty :
105
166
return meta (
@@ -117,8 +178,7 @@ def get_meta(self, config: Configuration) -> ScmVersion | None:
117
178
except ValueError :
118
179
# unpacking failed, old hg
119
180
log .exception ("error" )
120
-
121
- return None
181
+ return None
122
182
123
183
def hg_log (self , revset : str , template : str ) -> str :
124
184
cmd = [HG_COMMAND , "log" , "-r" , revset , "-T" , template ]
0 commit comments