From adc7fe836a348eb02efa7bc7ce761aec9bb9280a Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Sun, 8 Sep 2024 02:42:17 +0930 Subject: [PATCH] feat: add auth to git operations --- backend/app/git/branches/delete.py | 11 +--- backend/app/git/branches/push.py | 62 ++++++++++++++++--- backend/app/git/operations/push.py | 41 ++++++++++-- backend/app/git/operations/stage.py | 38 +++++++++--- .../settings/SettingsBranchModal.jsx | 2 +- 5 files changed, 125 insertions(+), 29 deletions(-) diff --git a/backend/app/git/branches/delete.py b/backend/app/git/branches/delete.py index 276f538..e6190fd 100644 --- a/backend/app/git/branches/delete.py +++ b/backend/app/git/branches/delete.py @@ -34,16 +34,7 @@ def delete_branch(repo_path, branch_name): # Check if remote branch exists remote_branch = f"origin/{branch_name}" if remote_branch in repo.refs: - logger.debug(f"Attempting to delete remote branch: {branch_name}") - try: - repo.git.push('origin', '--delete', branch_name) - logger.debug( - f"Successfully deleted remote branch: {branch_name}") - except GitCommandError as e: - logger.error( - f"Failed to delete remote branch: {branch_name}. Error: {str(e)}" - ) - return False, f"Failed to delete remote branch: {branch_name}. There might be permission issues." + pass return True, { "message": f"Deleted branch: {branch_name}", diff --git a/backend/app/git/branches/push.py b/backend/app/git/branches/push.py index 8d925d0..1d8bc5d 100644 --- a/backend/app/git/branches/push.py +++ b/backend/app/git/branches/push.py @@ -1,27 +1,75 @@ import git import logging +import os +from git import RemoteProgress +from urllib.parse import urlparse, urlunparse +from ..auth.authenticate import get_github_token, check_dev_mode logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) + +class CredentialedRemoteProgress(RemoteProgress): + + def __init__(self, github_token): + super().__init__() + self.github_token = github_token + + def update(self, op_code, cur_count, max_count=None, message=''): + if op_code & RemoteProgress.AUTHENTICATING: + # This is where we would inject the GitHub token if needed + pass + + def push_branch_to_remote(repo_path, branch_name): try: logger.debug(f"Attempting to push branch {branch_name} to remote") + + # Check if we're in dev mode + if not check_dev_mode(): + logger.warning("Not in dev mode. Push operation not allowed.") + return False, "Push operation not allowed in production mode." + + # Get the GitHub token + github_token = get_github_token() + if not github_token: + logger.error("GitHub token not available") + return False, "GitHub token not available" + repo = git.Repo(repo_path) - + # Check if the branch exists locally if branch_name not in repo.heads: return False, f"Branch '{branch_name}' does not exist locally." - - # Push the branch to remote and set the upstream branch + + # Get the remote URL and inject the GitHub token origin = repo.remote(name='origin') - origin.push(refspec=f"{branch_name}:{branch_name}", set_upstream=True) - + url = list(urlparse(next(origin.urls))) + url[1] = f"{github_token}@{url[1]}" # Inject GitHub token into the URL + auth_url = urlunparse(url) + + # Set the new URL with the GitHub token + origin.set_url(auth_url) + + # Push the branch to remote and set the upstream branch + progress = CredentialedRemoteProgress(github_token) + origin.push(refspec=f"{branch_name}:{branch_name}", + set_upstream=True, + progress=progress) + logger.debug(f"Successfully pushed branch to remote: {branch_name}") return True, {"message": f"Pushed branch to remote: {branch_name}"} + except git.GitCommandError as e: - logger.error(f"Git command error pushing branch to remote: {str(e)}", exc_info=True) + logger.error(f"Git command error pushing branch to remote: {str(e)}", + exc_info=True) return False, {"error": f"Error pushing branch to remote: {str(e)}"} + except Exception as e: - logger.error(f"Error pushing branch to remote: {str(e)}", exc_info=True) + logger.error(f"Error pushing branch to remote: {str(e)}", + exc_info=True) return False, {"error": f"Error pushing branch to remote: {str(e)}"} + + finally: + # Reset the URL to remove the GitHub token + origin.set_url(next(origin.urls)) diff --git a/backend/app/git/operations/push.py b/backend/app/git/operations/push.py index d9d0455..0f603b7 100644 --- a/backend/app/git/operations/push.py +++ b/backend/app/git/operations/push.py @@ -3,16 +3,49 @@ import git import logging from .commit import commit_changes +from ..auth.authenticate import check_dev_mode, get_github_token logger = logging.getLogger(__name__) + def push_changes(repo_path, files, message): try: + # Check if we're in dev mode + if not check_dev_mode(): + logger.warning("Not in dev mode. Push operation not allowed.") + return False, "Push operation not allowed in production mode." + + # Get the GitHub token + github_token = get_github_token() + if not github_token: + logger.error("GitHub token not available") + return False, "GitHub token not available" + repo = git.Repo(repo_path) - commit_changes(repo_path, files, message) - origin = repo.remote(name='origin') - origin.push() + + # Commit changes + commit_success, commit_message = commit_changes( + repo_path, files, message) + if not commit_success: + return False, commit_message + + # Authenticate and push changes + with repo.git.custom_environment(GIT_ASKPASS='echo', + GIT_USERNAME=github_token): + origin = repo.remote(name='origin') + push_info = origin.push() + + # Check if the push was successful + if push_info[0].flags & push_info[0].ERROR: + raise git.GitCommandError("git push", push_info[0].summary) + return True, "Successfully pushed changes." + + except git.GitCommandError as e: + logger.error(f"Git command error pushing changes: {str(e)}", + exc_info=True) + return False, f"Error pushing changes: {str(e)}" + except Exception as e: logger.error(f"Error pushing changes: {str(e)}", exc_info=True) - return False, f"Error pushing changes: {str(e)}" \ No newline at end of file + return False, f"Error pushing changes: {str(e)}" diff --git a/backend/app/git/operations/stage.py b/backend/app/git/operations/stage.py index 72a59cc..6b90ebd 100644 --- a/backend/app/git/operations/stage.py +++ b/backend/app/git/operations/stage.py @@ -2,19 +2,43 @@ import git import logging +from ..auth.authenticate import check_dev_mode, get_github_token logger = logging.getLogger(__name__) + def stage_files(repo_path, files): try: + # Check if we're in dev mode + if not check_dev_mode(): + logger.warning("Not in dev mode. Staging operation not allowed.") + return False, "Staging operation not allowed in production mode." + + # Get the GitHub token + github_token = get_github_token() + if not github_token: + logger.error("GitHub token not available") + return False, "GitHub token not available" + repo = git.Repo(repo_path) - if not files: - repo.git.add(A=True) - message = "All changes have been staged." - else: - repo.index.add(files) - message = "Specified files have been staged." + + # Authenticate with GitHub token + with repo.git.custom_environment(GIT_ASKPASS='echo', + GIT_USERNAME=github_token): + if not files: + repo.git.add(A=True) + message = "All changes have been staged." + else: + repo.index.add(files) + message = "Specified files have been staged." + return True, message + + except git.GitCommandError as e: + logger.error(f"Git command error staging files: {str(e)}", + exc_info=True) + return False, f"Error staging files: {str(e)}" + except Exception as e: logger.error(f"Error staging files: {str(e)}", exc_info=True) - return False, f"Error staging files: {str(e)}" \ No newline at end of file + return False, f"Error staging files: {str(e)}" diff --git a/frontend/src/components/settings/SettingsBranchModal.jsx b/frontend/src/components/settings/SettingsBranchModal.jsx index d02a29a..4ec1eab 100644 --- a/frontend/src/components/settings/SettingsBranchModal.jsx +++ b/frontend/src/components/settings/SettingsBranchModal.jsx @@ -346,7 +346,7 @@ const SettingsBranchModal = ({ )} {(branch.isLocal || - (branch.isRemote && isDevMode)) && + (!branch.isRemote && isDevMode)) && branch.name !== currentBranch && branch.name.toLowerCase() !== 'stable' && (