@@ -20,60 +20,87 @@ import re
2020# The directory containing this script is automatically in sys.path
2121from shared .git_ops import run_command
2222
23- def increment_patch (version_str ):
24- """Calculates the next release candidate tag.
25- - If already an RC (e.g., v0.1.0rc1), increments the RC counter (v0.1.0rc2).
26- - Otherwise, increments the patch version and starts at rc1 (e.g., v0.1.0 -> v0.1.1rc1).
27-
28- TODO: Remove the rc suffix for official releases before switching to final patch versions.
23+ def parse_version (tag_str ):
24+ """Parses version tag into a sortable tuple: (major, minor, patch, rc_num, raw_tag).
25+ For official releases (non-RC), rc_num is set to 999999 to ensure it sorts higher than RC releases.
2926 """
30- rc_match = re .match (r'^v?(\d+\.\d+\.\d+)rc(\d+)$' , version_str )
31- if rc_match :
32- version = rc_match .group (1 )
33- rc_num = int (rc_match .group (2 ))
34- return f"v{ version } rc{ rc_num + 1 } "
35-
36- clean_version = re .match (r'^v?(\d+\.\d+\.\d+)' , version_str )
37- if clean_version :
38- version_to_parse = f"v{ clean_version .group (1 )} "
39- else :
40- parts = re .findall (r'\d+' , version_str )
41- while len (parts ) < 3 :
42- parts .append ("0" )
43- version_to_parse = f"v{ '.' .join (parts [:3 ])} "
44-
45- match = re .match (r'^v(\d+)\.(\d+)\.(\d+)$' , version_to_parse )
46- major , minor , patch = map (int , match .groups ())
47- patch += 1
48-
49- return f"v{ major } .{ minor } .{ patch } rc0"
50-
27+ match = re .match (r'^v?(\d+)\.(\d+)\.(\d+)(?:[-.]?rc\.?(\d+))?(?:\.post\d+)?$' , tag_str )
28+ if not match :
29+ return None
30+ major , minor , patch = map (int , match .groups ()[:3 ])
31+ rc_str = match .group (4 )
32+ rc_num = int (rc_str ) if rc_str is not None else 999999
33+ return (major , minor , patch , rc_num , tag_str )
5134
5235def main ():
36+ # 1. Check for Restart (HEAD already tagged with a valid version tag)
37+ head_tag = run_command (["git" , "describe" , "--tags" , "--exact-match" , "HEAD" ], capture_output = True , allow_error = True )
38+ if head_tag :
39+ parsed_head = parse_version (head_tag )
40+ if parsed_head :
41+ print (f"⚠️ HEAD is already tagged as { head_tag } . Resuming/restarting release process..." )
42+ if "GITHUB_OUTPUT" in os .environ :
43+ with open (os .environ ["GITHUB_OUTPUT" ], "a" ) as f :
44+ f .write ("run_release=true\n " )
45+ f .write (f"new_tag={ head_tag } \n " )
46+ print (f"SUCCESS: Auto-patched to tag { head_tag } " )
47+ sys .exit (0 )
48+
5349 print ("🔍 Checking for new commits since last release..." )
5450
55- # Get latest tag
56- # We look for tags matching 'v*'
57- latest_tag = run_command (["git" , "describe" , "--tags" , "--abbrev=0" , "--match=v*" ], capture_output = True , allow_error = True )
58- if not latest_tag :
59- print ("⚠️ Unable to determine latest tag from git describe. Defaulting to v0.1.0 base." )
51+ # 2. Get only the single latest tag reachable from HEAD
52+ latest_tag = run_command (
53+ ["git" , "describe" , "--tags" , "--abbrev=0" , "--match=v*" ],
54+ capture_output = True ,
55+ allow_error = True
56+ )
57+
58+ has_latest_tag = bool (latest_tag )
59+ is_rc_to_stable_transition = False
60+
61+ if not has_latest_tag :
62+ print ("⚠️ Unable to find any version tags in repository. Defaulting to v0.1.0 base." )
6063 latest_tag = "v0.1.0"
64+ new_tag = "v0.1.0"
65+ else :
66+ print (f"Found latest tag: { latest_tag } " )
67+ parsed = parse_version (latest_tag )
68+ if not parsed :
69+ print (f"❌ Error: Failed to parse latest tag: { latest_tag } " )
70+ sys .exit (1 )
71+
72+ major , minor , patch , rc_num , raw = parsed
73+
74+ if rc_num < 999999 : # Highest tag is an RC version (e.g. v0.4.5rc2)
75+ # Targeted check: Does the corresponding stable tag (e.g. v0.4.5) exist on HEAD?
76+ stable_tag_name = f"v{ major } .{ minor } .{ patch } "
77+ stable_exists = run_command (
78+ ["git" , "tag" , "--list" , stable_tag_name , "--merged" , "HEAD" ],
79+ capture_output = True ,
80+ allow_error = True
81+ )
82+ if not stable_exists :
83+ new_tag = stable_tag_name
84+ is_rc_to_stable_transition = True
85+ else :
86+ new_tag = f"v{ major } .{ minor } .{ patch + 1 } "
87+ else :
88+ new_tag = f"v{ major } .{ minor } .{ patch + 1 } "
89+
90+ # 3. Check for commits since latest tag
91+ if has_latest_tag :
92+ commits = run_command (["git" , "log" , f"{ latest_tag } ..HEAD" , "--oneline" ], capture_output = True )
93+ else :
94+ commits = run_command (["git" , "log" , "HEAD" , "--oneline" ], capture_output = True )
6195
62- print (f"Found latest tag: { latest_tag } " )
63-
64- # Check for commits since latest tag
65- commits = run_command (["git" , "log" , f"{ latest_tag } ..HEAD" , "--oneline" ], capture_output = True )
66-
67- if not commits :
68- print ("ℹ️ No new commits since last release. Skipping release." )
96+ if not commits and not is_rc_to_stable_transition :
97+ print (f"ℹ️ No new commits since last release ({ latest_tag } ). Skipping release." )
6998 if "GITHUB_OUTPUT" in os .environ :
7099 with open (os .environ ["GITHUB_OUTPUT" ], "a" ) as f :
71100 f .write ("run_release=false\n " )
72101 sys .exit (0 )
73102
74103 print (f"Found new commits since { latest_tag } :\n { commits } " )
75-
76- new_tag = increment_patch (latest_tag )
77104 print (f"🚀 Proposing new tag: { new_tag } " )
78105
79106 # Set output for GitHub Actions
0 commit comments