From cb01ee6273b0fe7d1afb150acd41b49b3d38454b Mon Sep 17 00:00:00 2001 From: Udo Waechter Date: Wed, 7 Jan 2026 14:02:20 +0100 Subject: [PATCH] ganz gut so --- redmine2gitea-wiki/redmine_to_gitea_wiki.py | 75 ++++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/redmine2gitea-wiki/redmine_to_gitea_wiki.py b/redmine2gitea-wiki/redmine_to_gitea_wiki.py index 6b3ed80..afe0114 100644 --- a/redmine2gitea-wiki/redmine_to_gitea_wiki.py +++ b/redmine2gitea-wiki/redmine_to_gitea_wiki.py @@ -15,7 +15,7 @@ REDMINE_URL = "https://redmine.maketank.net" # e.g., https://redmine.example.co REDMINE_API_KEY = "62120b7ed6bf84023b2a1d7d5a265cf172f8d607" PROJECT_ID = "bastelstube" # e.g., "my-project" -GITEA_WIKI_URL = "https://git.maketank.net:2222/chaos/spielplatz.wiki.git" # Gitea wiki Git URL +GITEA_WIKI_URL = "https://git.maketank.net/chaos/spielplatz.wiki.git" # Gitea wiki Git URL GITEA_USERNAME = "do" GITEA_TOKEN = "2d2be00637182c6d5411512f456218ee59fa1f81" # Use a personal access token @@ -26,52 +26,82 @@ GITEA_TOKEN = "2d2be00637182c6d5411512f456218ee59fa1f81" # Use a personal acces def redmine_get_wiki_pages(): """Get all wiki pages from Redmine project""" - url = f"{REDMINE_URL}/projects/{PROJECT_ID}/wiki.xml" + # First get the list of pages + index_url = f"{REDMINE_URL}/projects/{PROJECT_ID}/wiki/index.xml" headers = {"X-Redmine-API-Key": REDMINE_API_KEY} - response = requests.get(url, headers=headers) + response = requests.get(index_url, headers=headers) response.raise_for_status() soup = BeautifulSoup(response.content, "xml") pages = [] - for page in soup.find_all("wiki-page"): + # Get all page titles from the index + for page in soup.find_all("wiki_page"): title = page.find("title").text - content = page.find("content").text - pages.append({"title": title, "content": content}) logger.info(f"Found page: {title}") + + # Fetch individual page content + page_url = f"{REDMINE_URL}/projects/{PROJECT_ID}/wiki/{title}.xml" + try: + page_response = requests.get(page_url, headers=headers) + page_response.raise_for_status() + + page_soup = BeautifulSoup(page_response.content, "xml") + content = page_soup.find("text").text if page_soup.find("text") else "" + + pages.append({"title": title, "content": content}) + logger.info(f"Fetched content for: {title}") + except Exception as e: + logger.warning(f"Failed to fetch content for page '{title}': {e}") + # Add empty content if fetch fails + pages.append({"title": title, "content": ""}) + return pages def redmine_markup_to_markdown(content): - """Convert Redmine wiki markup to Markdown""" + """Convert Redmine wiki markup (textile) to Markdown""" # Replace Redmine-specific syntax to Markdown - # Note: This is a simplified converter. You may need to extend it. - - # Convert bold + + # Handle child_pages macro - remove it since we're importing all pages + content = re.sub(r'\{\{child_pages(?:\([^)]*\))?\}\}', '', content) + + # Convert bold (strong in textile) content = re.sub(r'\*\*(.*?)\*\*', r'**\1**', content) content = re.sub(r'\*(.*?)\*', r'*\1*', content) # Convert headings - content = re.sub(r'={2,}(.*?)={2,}', r'# \1', content) - content = re.sub(r'={1,}(.*?)={1,}', r'## \1', content) + content = re.sub(r'^h1\.(.*?)$', r'# \1', content, flags=re.MULTILINE) + content = re.sub(r'^h2\.(.*?)$', r'## \1', content, flags=re.MULTILINE) + content = re.sub(r'^h3\.(.*?)$', r'### \1', content, flags=re.MULTILINE) + content = re.sub(r'^h4\.(.*?)$', r'#### \1', content, flags=re.MULTILINE) + content = re.sub(r'^h5\.(.*?)$', r'##### \1', content, flags=re.MULTILINE) + content = re.sub(r'^h6\.(.*?)$', r'###### \1', content, flags=re.MULTILINE) # Convert lists content = re.sub(r'^\s*\* (.*?)$', r'- \1', content, flags=re.MULTILINE) content = re.sub(r'^\s*\# (.*?)$', r'1. \1', content, flags=re.MULTILINE) - # Convert links + # Convert links - handle both [[page]] and [page] syntax content = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', content) # Simple link content = re.sub(r'\[(.*?)\](.*?)\]', r'[\1](\2)', content) # [text](url) - - # Convert images + + # Convert images - handle !image! and ![alt](image) content = re.sub(r'!\[(.*?)\]\((.*?)\)', r'![](\2)', content) + content = re.sub(r'!(.*?)!', r'![](\\1)', content) # Convert code blocks content = re.sub(r'
(.*?)
', r'```python\n\1\n```', content, flags=re.DOTALL) + + # Convert inline code + content = re.sub(r'\{\{(.*?)\}\}', r'`\1`', content) # Remove HTML tags (if any) content = re.sub(r'<[^>]+>', '', content) + # Handle textile-specific elements like blockquotes + content = re.sub(r'^bq\.(.*?)$', r'> \1', content, flags=re.MULTILINE) + return content @@ -89,7 +119,15 @@ def clone_or_init_wiki_repo(repo_url, local_dir): def push_to_gitea_wiki(local_dir, pages): """Add pages as Markdown files and push to Gitea""" - os.chdir(local_dir) + # Remove all existing files in the repo (except .git folder) + for item in os.listdir(local_dir): + if item != '.git': + item_path = os.path.join(local_dir, item) + if os.path.isfile(item_path): + os.remove(item_path) + elif os.path.isdir(item_path): + import shutil + shutil.rmtree(item_path) # Create pages as .md files for page in pages: @@ -107,9 +145,10 @@ def push_to_gitea_wiki(local_dir, pages): logger.info(f"Saved page: {filename}") # Add, commit, and push + os.chdir(local_dir) subprocess.run(["git", "add", "."], check=True) - subprocess.run(["git", "commit", "-m", "Import Redmine wiki pages"], check=True) - subprocess.run(["git", "push", "origin", "master"], check=True) + subprocess.run(["git", "commit", "-m", "ImportRedminewikipages"], check=True) + subprocess.run(["git", "push", "origin", "main"], check=True) logger.info("✅ Wiki pages pushed to Gitea!")