-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub_handler.py
More file actions
188 lines (151 loc) · 6.08 KB
/
Copy pathgithub_handler.py
File metadata and controls
188 lines (151 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""
GitHub/Git operations - apply changes and commit to repo
"""
import os
import logging
from git import Repo
from git.exc import GitCommandError
logger = logging.getLogger(__name__)
repo = None
def init_repo():
"""Initialize or clone the repository"""
global repo
repo_path = os.getenv("REPO_LOCAL_PATH", "./website_repo")
github_token = os.getenv("GITHUB_TOKEN")
github_owner = os.getenv("GITHUB_REPO_OWNER")
github_name = os.getenv("GITHUB_REPO_NAME")
if not all([github_token, github_owner, github_name]):
raise ValueError("Missing GitHub configuration")
# Clone repo if it doesn't exist
if not os.path.exists(repo_path):
logger.info(f"Cloning repository to {repo_path}")
repo_url = f"https://x-access-token:{github_token}@github.com/{github_owner}/{github_name}.git"
repo = Repo.clone_from(repo_url, repo_path)
else:
repo = Repo(repo_path)
# Pull latest
try:
repo.remotes.origin.pull()
logger.info("Pulled latest changes from origin")
except GitCommandError as e:
logger.warning(f"Could not pull: {e}")
# Configure git user
config_reader = repo.config_reader()
if not config_reader.has_option("user", "email"):
repo.config_writer().set_value("user", "email", os.getenv("GITHUB_USER_EMAIL", "bot@programmingparty.plu.edu")).release()
repo.config_writer().set_value("user", "name", os.getenv("GITHUB_USER_NAME", "Programming Party Bot")).release()
def push_changes(repo_path: str, files_changed: list, commit_message: str) -> bool:
"""
Commit and push changes made by the agent
Args:
repo_path: Path to the repository
files_changed: List of file paths that were modified
commit_message: Commit message
Returns: True if successful
"""
try:
r = Repo(repo_path)
# Pull latest to avoid conflicts
try:
r.remotes.origin.pull()
except GitCommandError as e:
logger.warning(f"Could not pull before push: {e}")
# Stage all changed files
for file_path in files_changed:
r.index.add([file_path])
logger.info(f"Staged: {file_path}")
# Commit
commit = r.index.commit(commit_message)
logger.info(f"Committed: {commit.hexsha[:7]}")
# Push
github_token = os.getenv("GITHUB_TOKEN")
github_owner = os.getenv("GITHUB_REPO_OWNER")
github_name = os.getenv("GITHUB_REPO_NAME")
try:
r.remotes.origin.push()
except GitCommandError:
# Use token URL if standard push fails
if github_token and github_owner and github_name:
repo_url = f"https://x-access-token:{github_token}@github.com/{github_owner}/{github_name}.git"
r.git.push(repo_url, "HEAD:main")
else:
raise
logger.info("Pushed changes to origin/main")
return True
except Exception as e:
logger.error(f"Error pushing changes: {e}")
return False
def apply_changes_and_commit(file_changes: dict, prompt: str) -> str:
"""
Apply file changes and commit to the repository
Returns: commit hash
"""
global repo
if not repo:
raise RuntimeError("Repository not initialized")
try:
# Pull latest to avoid conflicts
repo.remotes.origin.pull()
logger.info("Pulled latest changes before applying modifications")
# Apply each file change
files_modified = []
for file_change in file_changes.get("files", []):
file_path = file_change.get("path")
content = file_change.get("content")
if not file_path or content is None:
logger.warning(f"Invalid file change: {file_change}")
continue
full_path = os.path.join(repo.working_dir, file_path)
# Ensure directory exists
os.makedirs(os.path.dirname(full_path), exist_ok=True)
# Write file
with open(full_path, "w") as f:
f.write(content)
# Stage file
repo.index.add([file_path])
files_modified.append(file_path)
logger.info(f"Modified: {file_path}")
if not files_modified:
raise ValueError("No files were modified")
# Commit changes
commit_message = f"Student request: {prompt}"
commit = repo.index.commit(commit_message)
logger.info(f"Committed changes: {commit.hexsha[:7]}")
# Push to main
origin = repo.remote("origin")
github_token = os.getenv("GITHUB_TOKEN")
github_owner = os.getenv("GITHUB_REPO_OWNER")
github_name = os.getenv("GITHUB_REPO_NAME")
try:
# Try standard push first
origin.push()
except GitCommandError:
# If standard push fails, use git command directly with token in URL
if github_token:
repo_url = f"https://x-access-token:{github_token}@github.com/{github_owner}/{github_name}.git"
repo.git.push(repo_url, "HEAD:main")
else:
raise
logger.info("Pushed changes to origin/main")
return commit.hexsha
except GitCommandError as e:
logger.error(f"Git error: {e}")
raise Exception(f"Failed to commit changes: {e}")
except Exception as e:
logger.error(f"Error applying changes: {e}")
raise
def rollback_commit(commit_hash: str) -> bool:
"""
Rollback a specific commit
"""
global repo
if not repo:
raise RuntimeError("Repository not initialized")
try:
repo.git.revert(commit_hash, no_edit=True)
repo.remotes.origin.push()
logger.info(f"Rolled back commit {commit_hash}")
return True
except Exception as e:
logger.error(f"Failed to rollback: {e}")
return False