Loading generateCR/changemarks.py +78 −4 Original line number Diff line number Diff line Loading @@ -6,8 +6,10 @@ # (c) 2023 by Andreas Kraft, Miguel Angel Reina Ortega # License: BSD 3-Clause License. See the LICENSE file for further details. # from typing import Tuple from typing import Tuple, Optional import argparse, os, re, sys import subprocess, tempfile, shutil from urllib.parse import urlparse, urlunparse, quote as urlquote from rich import print from rich.progress import Progress, TextColumn, TimeElapsedColumn import logging Loading Loading @@ -36,6 +38,19 @@ def fetch_json(url : str, expected_content_type : str = None, headers = None) -> r = fetch(url, expected_content_type, headers) return r.json() def add_basic_auth_to_url(url: str, username: str, password: Optional[str]) -> str: if not password: return url parsed = urlparse(url) if '@' in parsed.netloc: return url # already has creds user_enc = urlquote(username, safe='') pass_enc = urlquote(password, safe='') netloc = f"{user_enc}:{pass_enc}@{parsed.netloc}" parts = list(parsed) parts[1] = netloc return urlunparse(parts) def readMDFile(progress:Progress, document:str) -> list[str]: """ Read the markdown file and return a list of lines. """ Loading Loading @@ -144,10 +159,69 @@ class MR: self.source_branch = self.raw_mr_details['source_branch'] self.title = self.raw_mr_details['title'] self.description = self.raw_mr_details['description'] self.raw_diff = fetch_text(f'{self.web_url}/-/merge_requests/{self.mr_id}.diff', expected_content_type='text/plain', headers = headers) self.patch_set = PatchSet.from_string(self.raw_diff) self.headers = headers # Minimal fetch of just the two branch tips, then compute diff using explicit refs # Avoid triple-dot syntax to better handle branches with special characters # Fall back to web .diff on failure try: # Build optional authentication headers for Git over HTTPS extra:list[str] = [] if isinstance(self.headers, dict): if 'PRIVATE-TOKEN' in self.headers and self.headers['PRIVATE-TOKEN']: extra += ['-c', f"http.extraheader=PRIVATE-TOKEN: {self.headers['PRIVATE-TOKEN']}"] if 'Authorization' in self.headers and self.headers['Authorization']: # e.g., 'Bearer <token>' extra += ['-c', f"http.extraheader={self.headers['Authorization']}"] set_url_cmd = [ 'git', 'remote', 'set-url', 'origin',add_basic_auth_to_url(self.web_url, 'gitlab-ci-token', self.headers['PRIVATE-TOKEN']) ] logging.debug(f"Running: {' '.join(str(x) for x in set_url_cmd)}") set_url_proc = subprocess.run(set_url_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, text=True) logging.debug(f"set_url returncode={set_url_proc.returncode}") if set_url_proc.stderr: logging.debug(f"set_url stderr: {set_url_proc.stderr.strip()}") fetch_cmd = [ 'git', '-c', 'http.sslVerify=false', *extra, 'fetch', '--no-tags', '--depth', '100', 'origin', f'+refs/heads/{self.target_branch}:refs/remotes/origin/{self.target_branch}', f'+refs/heads/{self.source_branch}:refs/remotes/origin/{self.source_branch}' ] logging.debug(f"Running: {' '.join(str(x) for x in fetch_cmd)}") fetch_proc = subprocess.run(fetch_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, text=True) logging.debug(f"fetch returncode={fetch_proc.returncode}") if fetch_proc.stderr: logging.debug(f"fetch stderr: {fetch_proc.stderr.strip()}") # Diff from target branch to source branch to mimic MR diff semantics diff_cmd = [ 'git', 'diff', '--patch', '--no-color', f'refs/remotes/origin/{self.target_branch}...refs/remotes/origin/{self.source_branch}' ] logging.debug(f"Running: {' '.join(str(x) for x in diff_cmd)}") diff_proc = subprocess.run(diff_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True) if diff_proc.stderr: logging.debug(f"diff stderr: {diff_proc.stderr.strip()}") self.raw_diff = diff_proc.stdout if not self.raw_diff: raise RuntimeError('Empty diff produced by git') except Exception as e: logging.warning(f"git minimal fetch+diff failed ({e}); falling back to web .diff fetch") try: self.raw_diff = fetch_text( f'{self.web_url}/-/merge_requests/{self.mr_id}.diff', expected_content_type='text/plain', headers = headers ) if not self.raw_diff or not self.raw_diff.strip(): raise ValueError('Downloaded web .diff is empty') except Exception as web_err: raise ValueError( f"Unable to obtain diff: git failed with '{e}', and web .diff fetch failed with '{web_err}'. " f"Check network access, token permissions, and MR visibility." ) self.patch_set = PatchSet.from_string(self.raw_diff) def api_url(self, route : str = "") -> str: return f"{self.root}/api/v4/projects/{self.project_id}/{route}" def retrieve_text(self, branch: str, filename: str) -> str: Loading generateCR/dockerfile +1 −1 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ FROM python:3.9-slim-bullseye ADD /generateCR/ /generateCR/ RUN chmod +x generateCR/checking_conflicts.sh RUN apt-get update -y && \ # apt-get install -y libcairo2 && \ apt-get install -y git && \ rm -rf /var/lib/apt/lists/* &&\ pip install -e generateCR/ &&\ pip install -r generateCR/requirements.txt Loading
generateCR/changemarks.py +78 −4 Original line number Diff line number Diff line Loading @@ -6,8 +6,10 @@ # (c) 2023 by Andreas Kraft, Miguel Angel Reina Ortega # License: BSD 3-Clause License. See the LICENSE file for further details. # from typing import Tuple from typing import Tuple, Optional import argparse, os, re, sys import subprocess, tempfile, shutil from urllib.parse import urlparse, urlunparse, quote as urlquote from rich import print from rich.progress import Progress, TextColumn, TimeElapsedColumn import logging Loading Loading @@ -36,6 +38,19 @@ def fetch_json(url : str, expected_content_type : str = None, headers = None) -> r = fetch(url, expected_content_type, headers) return r.json() def add_basic_auth_to_url(url: str, username: str, password: Optional[str]) -> str: if not password: return url parsed = urlparse(url) if '@' in parsed.netloc: return url # already has creds user_enc = urlquote(username, safe='') pass_enc = urlquote(password, safe='') netloc = f"{user_enc}:{pass_enc}@{parsed.netloc}" parts = list(parsed) parts[1] = netloc return urlunparse(parts) def readMDFile(progress:Progress, document:str) -> list[str]: """ Read the markdown file and return a list of lines. """ Loading Loading @@ -144,10 +159,69 @@ class MR: self.source_branch = self.raw_mr_details['source_branch'] self.title = self.raw_mr_details['title'] self.description = self.raw_mr_details['description'] self.raw_diff = fetch_text(f'{self.web_url}/-/merge_requests/{self.mr_id}.diff', expected_content_type='text/plain', headers = headers) self.patch_set = PatchSet.from_string(self.raw_diff) self.headers = headers # Minimal fetch of just the two branch tips, then compute diff using explicit refs # Avoid triple-dot syntax to better handle branches with special characters # Fall back to web .diff on failure try: # Build optional authentication headers for Git over HTTPS extra:list[str] = [] if isinstance(self.headers, dict): if 'PRIVATE-TOKEN' in self.headers and self.headers['PRIVATE-TOKEN']: extra += ['-c', f"http.extraheader=PRIVATE-TOKEN: {self.headers['PRIVATE-TOKEN']}"] if 'Authorization' in self.headers and self.headers['Authorization']: # e.g., 'Bearer <token>' extra += ['-c', f"http.extraheader={self.headers['Authorization']}"] set_url_cmd = [ 'git', 'remote', 'set-url', 'origin',add_basic_auth_to_url(self.web_url, 'gitlab-ci-token', self.headers['PRIVATE-TOKEN']) ] logging.debug(f"Running: {' '.join(str(x) for x in set_url_cmd)}") set_url_proc = subprocess.run(set_url_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, text=True) logging.debug(f"set_url returncode={set_url_proc.returncode}") if set_url_proc.stderr: logging.debug(f"set_url stderr: {set_url_proc.stderr.strip()}") fetch_cmd = [ 'git', '-c', 'http.sslVerify=false', *extra, 'fetch', '--no-tags', '--depth', '100', 'origin', f'+refs/heads/{self.target_branch}:refs/remotes/origin/{self.target_branch}', f'+refs/heads/{self.source_branch}:refs/remotes/origin/{self.source_branch}' ] logging.debug(f"Running: {' '.join(str(x) for x in fetch_cmd)}") fetch_proc = subprocess.run(fetch_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False, text=True) logging.debug(f"fetch returncode={fetch_proc.returncode}") if fetch_proc.stderr: logging.debug(f"fetch stderr: {fetch_proc.stderr.strip()}") # Diff from target branch to source branch to mimic MR diff semantics diff_cmd = [ 'git', 'diff', '--patch', '--no-color', f'refs/remotes/origin/{self.target_branch}...refs/remotes/origin/{self.source_branch}' ] logging.debug(f"Running: {' '.join(str(x) for x in diff_cmd)}") diff_proc = subprocess.run(diff_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True) if diff_proc.stderr: logging.debug(f"diff stderr: {diff_proc.stderr.strip()}") self.raw_diff = diff_proc.stdout if not self.raw_diff: raise RuntimeError('Empty diff produced by git') except Exception as e: logging.warning(f"git minimal fetch+diff failed ({e}); falling back to web .diff fetch") try: self.raw_diff = fetch_text( f'{self.web_url}/-/merge_requests/{self.mr_id}.diff', expected_content_type='text/plain', headers = headers ) if not self.raw_diff or not self.raw_diff.strip(): raise ValueError('Downloaded web .diff is empty') except Exception as web_err: raise ValueError( f"Unable to obtain diff: git failed with '{e}', and web .diff fetch failed with '{web_err}'. " f"Check network access, token permissions, and MR visibility." ) self.patch_set = PatchSet.from_string(self.raw_diff) def api_url(self, route : str = "") -> str: return f"{self.root}/api/v4/projects/{self.project_id}/{route}" def retrieve_text(self, branch: str, filename: str) -> str: Loading
generateCR/dockerfile +1 −1 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ FROM python:3.9-slim-bullseye ADD /generateCR/ /generateCR/ RUN chmod +x generateCR/checking_conflicts.sh RUN apt-get update -y && \ # apt-get install -y libcairo2 && \ apt-get install -y git && \ rm -rf /var/lib/apt/lists/* &&\ pip install -e generateCR/ &&\ pip install -r generateCR/requirements.txt