webcraft-ai-backend / github_utils.py
3ssem0
fix(github): improve upload stability with retries and branch detection
b7fc3e1
Raw
History Blame Contribute Delete
7.63 kB
import os
import time
from github import Github
import requests
import random
import string
def create_github_repo(token, repo_name, private=False):
"""إنشاء ريبوزيتوري جديد على GitHub مع التحقق من الاسم الفريد"""
g = Github(token)
user = g.get_user()
# Try to create repo with original name
original_name = repo_name
counter = 1
max_attempts = 100 # Allow up to 100 attempts (my-website-1 through my-website-100)
for attempt in range(max_attempts):
try:
repo = user.create_repo(repo_name, private=private)
return repo
except Exception as e:
error_str = str(e)
# Check if error is due to name already existing
if "name already exists" in error_str or "422" in error_str:
# Add number suffix: (1), (2), (3), etc.
repo_name = f"{original_name}-{counter}"
counter += 1
else:
# Check for 404 which usually means scope issues
if "404" in error_str:
raise Exception("GitHub API returned 404 (Not Found). This usually means your Token lacks the 'repo' scope or 'Administration' write permission. Please ensure your token has full repo access.")
# Different error, re-raise
raise
# If all attempts failed, raise the last exception
raise Exception(f"Failed to create repository after {max_attempts} attempts")
def upload_files_to_repo(repo, local_dir, branch=None):
"""
Upload all files from a local directory to the repository.
Automatically detects the default branch if not specified.
"""
# 1. Determine the target branch
if not branch:
try:
branch = repo.default_branch
except Exception:
branch = "main"
# 2. Upload files
for root, _, files in os.walk(local_dir):
for file_name in files:
full_path = os.path.join(root, file_name)
with open(full_path, "rb") as f:
content = f.read()
relative_path = os.path.relpath(full_path, local_dir).replace("\\", "/")
# Retry mechanism for "Fresh" repositories (handles API propagation lag)
max_retries = 3
for attempt in range(max_retries):
try:
# Check if file exists to update it
try:
contents = repo.get_contents(relative_path, ref=branch)
repo.update_file(
path=relative_path,
message=f"Update {relative_path}",
content=content,
sha=contents.sha,
branch=branch
)
print(f"Updated {relative_path}")
except Exception as e:
# If not found (404), create it
if "404" in str(e) or "Not Found" in str(e):
repo.create_file(
path=relative_path,
message=f"Add {relative_path}",
content=content,
branch=branch
)
print(f"Created {relative_path}")
else:
raise e
break # Success, break retry loop
except Exception as e:
if attempt < max_retries - 1:
print(f"Retrying upload for {relative_path} (Attempt {attempt+2}/{max_retries})...")
time.sleep(2)
else:
print(f"Error with {relative_path} after {max_retries} attempts: {e}")
raise e
def enable_github_pages(username, repo_name, token):
"""تفعيل GitHub Pages"""
pages_url = f"https://api.github.com/repos/{username}/{repo_name}/pages"
headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json"
}
payload = {"source": {"branch": "main", "path": "/"}}
response = requests.post(pages_url, headers=headers, json=payload)
return response.status_code in [201, 204]
def get_repository(username, repo_name, token):
"""
Retrieve an existing GitHub repository.
Args:
username: GitHub username
repo_name: Repository name
token: GitHub personal access token
Returns:
Repository object or None if not found
"""
try:
g = Github(token)
repo = g.get_repo(f"{username}/{repo_name}")
return repo
except Exception as e:
if "404" in str(e):
print(f"Info: Repository '{repo_name}' not found on GitHub. A new one will be created.")
else:
print(f"GitHub Repository lookup error: {e}")
return None
def update_file_in_repo(repo, file_path, content, commit_message="Update file"):
"""
Update an existing file in the repository.
Args:
repo: Repository object
file_path: Path to file in repository (e.g., "index.html")
content: New file content (string or bytes)
commit_message: Commit message
Returns:
True if successful, False otherwise
"""
try:
# Get the file from the repository
file = repo.get_contents(file_path)
# Update the file
repo.update_file(
path=file_path,
message=commit_message,
content=content,
sha=file.sha,
branch="main"
)
print(f"Updated {file_path} in repository")
return True
except Exception as e:
print(f"Error updating file: {e}")
return False
def resync_webcraft_file(username, repo_name, token, html_file_path, commit_message="Resync from WebCraft"):
"""
Resync WebCraft HTML file with existing GitHub repository.
Args:
username: GitHub username
repo_name: Repository name
token: GitHub personal access token
html_file_path: Path to local HTML file
commit_message: Commit message for the update
Returns:
dict with status and URL, or error message
"""
try:
# Get the repository
repo = get_repository(username, repo_name, token)
if not repo:
return {
"status": "error",
"message": f"Repository '{repo_name}' not found"
}
# Read the HTML file
with open(html_file_path, 'rb') as f:
content = f.read()
# Find the HTML file in the repository (usually index.html)
filename = os.path.basename(html_file_path)
# Update the file
if update_file_in_repo(repo, filename, content, commit_message):
site_url = f"https://{username}.github.io/{repo_name}/"
return {
"status": "success",
"message": "Repository synced successfully",
"website_url": site_url,
"repo_url": repo.html_url
}
else:
return {
"status": "error",
"message": "Failed to update file in repository"
}
except Exception as e:
return {
"status": "error",
"message": str(e)
}