1111
1212import git
1313import gitlab
14- from gitlab .v4 .objects import ProjectMergeRequest
14+ from gitlab .v4 .objects import ProjectMergeRequest , Project
1515
1616
1717# ----------------------------
@@ -86,6 +86,12 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
8686 default = None ,
8787 help = "Project access token for creating MR pipelines (default: PROJECT_ACCESS_TOKEN env var)." ,
8888 )
89+ parser .add_argument (
90+ "--project-id" ,
91+ type = int ,
92+ default = None ,
93+ help = "GitLab project ID (default: CI_PROJECT_ID env var)." ,
94+ )
8995 parser .add_argument (
9096 "--skip-ci" ,
9197 dest = "add_skip_ci" ,
@@ -145,11 +151,14 @@ def build_context(ns: argparse.Namespace) -> CiContext:
145151
146152 changed_out_dir = Path (ns .changed_out_dir ).resolve ()
147153
154+ server_host = os .getenv ("CI_SERVER_HOST" , "gitlab.com" )
155+ api_url = os .getenv ("CI_API_V4_URL" ) or f"https://{ server_host } /api/v4"
156+
148157 return CiContext (
149- api_url = _req_env ( "CI_API_V4_URL" ) ,
150- server_host = _req_env ( "CI_SERVER_HOST" ) ,
151- project_id = int (_req_env ("CI_PROJECT_ID" )),
152- project_path = _req_env ("CI_PROJECT_PATH" ),
158+ api_url = api_url ,
159+ server_host = server_host ,
160+ project_id = ns . project_id or int (_req_env ("CI_PROJECT_ID" )),
161+ project_path = os . getenv ("CI_PROJECT_PATH" , "swiss-armed-forces/cyber-command/cea/loom " ),
153162 job_token = os .getenv ("CI_JOB_TOKEN" ),
154163 pipeline_id = os .getenv ("CI_PIPELINE_ID" ),
155164 pipeline_source = os .getenv ("CI_PIPELINE_SOURCE" ),
@@ -349,12 +358,19 @@ def count_consecutive_ci_commits(
349358# ----------------------------
350359
351360
352- def wait_for_mr_commit_sync (mr : ProjectMergeRequest , expected_commit_sha : str , max_retries : int = 5 , base_delay : float = 2.0 ) -> bool :
361+ def wait_for_mr_commit_sync (
362+ project : Project ,
363+ mr_iid : str ,
364+ expected_commit_sha : str ,
365+ max_retries : int = 5 ,
366+ base_delay : float = 2.0 ,
367+ ) -> bool :
353368 """
354369 Wait for GitLab MR to sync with the latest commit using exponential backoff.
355370
356371 Args:
357- mr: GitLab merge request instance
372+ project: GitLab project instance
373+ mr_iid: Merge request IID
358374 expected_commit_sha: The commit SHA we expect the MR to have
359375 max_retries: Maximum number of retry attempts (default: 5)
360376 base_delay: Base delay in seconds for exponential backoff (default: 2.0)
@@ -363,21 +379,36 @@ def wait_for_mr_commit_sync(mr: ProjectMergeRequest, expected_commit_sha: str, m
363379 True if MR synced successfully, False if timed out
364380 """
365381 for attempt in range (max_retries + 1 ):
366- # Get current MR commit SHA
367- current_sha = mr .sha # Head commit SHA of the MR
382+ # Always fetch fresh MR data from API
383+ mr = project .mergerequests .get (mr_iid , lazy = True )
384+ diffs = mr .diffs .list ()
385+ current_sha = None
386+ if diffs :
387+ # Get the latest diff (most recent)
388+ latest_diff = diffs [0 ]
389+ current_sha : str | None = latest_diff .head_commit_sha
368390
369391 if current_sha == expected_commit_sha :
370392 if attempt > 0 :
371393 logging .info ("MR commit sync successful after %d attempt(s)" , attempt )
372394 return True
373395
374396 if attempt < max_retries :
375- delay = base_delay * (2 ** attempt ) # Exponential backoff
376- logging .info ("MR has commit %s, waiting for %s. Retrying in %.1fs (attempt %d/%d)" ,
377- current_sha [:8 ], expected_commit_sha [:8 ], delay , attempt + 1 , max_retries )
397+ delay = base_delay * (2 ** attempt ) # Exponential backoff
398+ logging .info (
399+ "MR has commit %s, waiting for %s. Retrying in %.1fs (attempt %d/%d)" ,
400+ current_sha [:8 ] if current_sha else "unknown" ,
401+ expected_commit_sha [:8 ],
402+ delay ,
403+ attempt + 1 ,
404+ max_retries ,
405+ )
378406 time .sleep (delay )
379407
380- logging .error ("MR commit sync timeout after %d attempts. MR may have stale commit." , max_retries )
408+ logging .error (
409+ "MR commit sync timeout after %d attempts. MR may have stale commit." ,
410+ max_retries ,
411+ )
381412 return False
382413
383414
@@ -396,7 +427,9 @@ def trigger_pipeline(ctx: CiContext) -> None:
396427 return
397428
398429 if not ctx .mr_iid :
399- logging .warning ("CI_MERGE_REQUEST_IID not available; skipping pipeline trigger." )
430+ logging .warning (
431+ "CI_MERGE_REQUEST_IID not available; skipping pipeline trigger."
432+ )
400433 return
401434
402435 if ctx .dry_run :
@@ -416,8 +449,10 @@ def trigger_pipeline(ctx: CiContext) -> None:
416449
417450 # Wait for GitLab MR to sync with the latest commit
418451 logging .info ("Waiting for MR !%s to sync with latest commit..." , ctx .mr_iid )
419- if not wait_for_mr_commit_sync (mr , latest_commit_sha ):
420- logging .error ("Failed to sync MR with latest commit. Not triggering pipeline to avoid running on stale commit." )
452+ if not wait_for_mr_commit_sync (project , ctx .mr_iid , latest_commit_sha ):
453+ logging .error (
454+ "Failed to sync MR with latest commit. Not triggering pipeline to avoid running on stale commit."
455+ )
421456 return
422457
423458 # Create merge request pipeline using the MR pipeline API
@@ -446,7 +481,9 @@ def commit_and_push(repo: git.Repo, ctx: CiContext) -> None:
446481 if ctx .dry_run :
447482 logging .info ("[DRY RUN] Would commit with message:\n %s" , msg .strip ())
448483 logging .info ("[DRY RUN] Would push HEAD:%s" , ctx .source_branch )
449- logging .info ("[DRY RUN] Would trigger new MR pipeline if project access token provided" )
484+ logging .info (
485+ "[DRY RUN] Would trigger new MR pipeline if project access token provided"
486+ )
450487 return
451488
452489 if not has_staged_changes (repo ):
0 commit comments