Skip to content

Commit 9ad6845

Browse files
committed
automate.py version 0.1
1 parent dc5b1e6 commit 9ad6845

File tree

5 files changed

+340
-1
lines changed

5 files changed

+340
-1
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
configuration.yaml
2+
py-env/
3+
*github-stage*/
4+
logfile.log
5+
.DS_Store
6+
settings.json

README.md

+70-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,70 @@
1-
# github-testing-automation
1+
# GitHub Automator
2+
3+
GitHub Automator is a Python-based tool designed to automate various GitHub operations for easy GitHub Testing, such as creating and initializing repositories, committing and pushing changes, creating branches, creating and merging pull requests, generating conflicts and resolving it, comments on the pull requests, deleting repositories, testing important git commands and more. It is particularly useful for Testing Staging GitHub operations.
4+
5+
## Features
6+
7+
- **Create and Initialize Repository**: Allows for the creation and initialization of a new repository both remotely and locally.
8+
- **Commit and Push**: Commits changes to a file and pushes them to a specified branch.
9+
- **Create and Merge Pull Request**: Creates and merges a pull request between specified branches.
10+
- **Create Conflict**: Creates a conflict in a file between the main branch and a new branch.
11+
- **Resolve Conflict**: Resolves conflicts in a specified file.
12+
- **Comment on Pull Request**: Adds a comment to a specified pull request.
13+
- **Test General Git Commands**: Tests various git commands and prints their outputs.
14+
- **Test GitHub API**: Tests various GitHub API operations and prints their outputs.
15+
- **Create Branches and Make Changes**: Creates specified branches, makes changes, and pushes to remote.
16+
- **Delete Repositories with 'automation' in their name**: Deletes all repositories of the user with 'automation' in their name.
17+
18+
## Prerequisites
19+
20+
- Python 3.x (Used: 3.11)
21+
- GitHub Account
22+
- Access Token with the necessary permissions
23+
- `git` and `github` Python packages
24+
- YAML configuration file with necessary credentials
25+
26+
## Setup
27+
28+
1. Clone or download the project repository to your local machine.
29+
2. Install the required dependencies by running the following command:
30+
31+
```python
32+
pip install -r requirements.txt
33+
```
34+
35+
3. Create a `configuration.yaml` file in the project directory and populate it with the required configuration parameters. See the "Configuration" section below for details.
36+
37+
## Configuration
38+
39+
The `configuration.yaml` file contains important parameters for the GitHub Automator. It should have the following structure:
40+
41+
```yaml
42+
credentials:
43+
access_token: your_github_access_token
44+
base_url: https://github-example.com/api/v3
45+
username: your_github_user_name
46+
repo_name: your_github_new_repository_name
47+
repo_dir: /path/to/clone/locally/your_github_new_repository_name
48+
```
49+
50+
Replace the placeholder values with your actual GitHub credentials and configuration.
51+
52+
## Usage
53+
54+
Navigate to the project directory and run the following command:
55+
56+
```python
57+
python automate.py
58+
```
59+
60+
## Contributing
61+
62+
Contributions to the this project are welcome. If you find any issues or have suggestions for improvements, please open an issue or submit a pull request on the GitHub repository.
63+
64+
## License
65+
66+
This project is licensed under the `MIT License`.
67+
68+
## Acknowledgements
69+
- PyGithub for providing the GitHub API Python client.
70+
- GitPython for enabling Python interaction with Git.

automate.py

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import os
2+
import time
3+
from typing import Dict, List
4+
5+
import git
6+
import github
7+
import yaml
8+
from github import Github
9+
10+
class GitHubAutomator:
11+
"""A class to automate the Testing of Staging GitHub operations."""
12+
13+
def __init__(self, access_token: str, base_url: str, username: str, repo_name: str, repo_dir: str):
14+
"""
15+
Initialize the GitHubAutomator class.
16+
17+
:param access_token: GitHub access token.
18+
:param base_url: Base URL for the GitHub instance.
19+
:param username: GitHub username.
20+
:param repo_name: Name of the repository.
21+
:param repo_dir: Local directory for the repository.
22+
"""
23+
self.github = Github(base_url=base_url, login_or_token=access_token)
24+
self.user = self.github.get_user(username)
25+
self.repo_name = repo_name
26+
self.repo_dir = repo_dir
27+
28+
def create_and_initialize_repository(self) -> None:
29+
"""Create a new repository and initialize it both remotely and locally."""
30+
try:
31+
self.repo = self.github.get_user().get_repo(self.repo_name)
32+
print(f'Repository {self.repo_name} already exists. Using the existing repository.')
33+
except github.UnknownObjectException:
34+
self.repo = self.github.get_user().create_repo(self.repo_name)
35+
if self.repo:
36+
print(f'Repository {self.repo_name} created successfully.')
37+
else:
38+
print(f'Failed to create repository {self.repo_name}.')
39+
40+
if not os.path.exists(self.repo_dir):
41+
os.makedirs(self.repo_dir)
42+
print(f'Directory {self.repo_dir} created successfully.')
43+
44+
os.chdir(self.repo_dir)
45+
os.system(f'echo "# {self.repo_name}" >> README.md')
46+
repo_local = git.Repo.init(self.repo_dir)
47+
if repo_local:
48+
print('Local repository initialized successfully.')
49+
else:
50+
print('Failed to initialize local repository.')
51+
52+
repo_local.git.add('README.md')
53+
repo_local.git.commit('-m', 'first commit')
54+
repo_local.git.branch('-M', 'main')
55+
repo_local.git.remote('add', 'origin', self.repo.clone_url)
56+
repo_local.git.push('-u', 'origin', 'main')
57+
print('Initial commit pushed to main branch.')
58+
59+
def commit_and_push(self, branch_name: str, file_name: str, commit_message: str) -> None:
60+
"""
61+
Commit changes to a file and push to a specified branch.
62+
63+
:param branch_name: Name of the branch to push to.
64+
:param file_name: Name of the file to commit.
65+
:param commit_message: Commit message.
66+
"""
67+
try:
68+
os.chdir(self.repo_dir)
69+
repo_local = git.Repo(self.repo_dir)
70+
repo_local.git.checkout(branch_name)
71+
with open(file_name, 'a') as f:
72+
f.write(commit_message + '\n')
73+
repo_local.git.add(file_name)
74+
repo_local.git.commit('-m', commit_message)
75+
repo_local.git.push('--set-upstream', 'origin', branch_name)
76+
print(f'Changes pushed to {branch_name} successfully.')
77+
except Exception as e:
78+
print(f'Failed to push changes to {branch_name}. Error: {e}')
79+
80+
def create_and_merge_pull_request(self, head: str, base: str, title: str, body: str) -> None:
81+
"""
82+
Create and merge a pull request.
83+
84+
:param head: Source branch for the pull request.
85+
:param base: Target branch for the pull request.
86+
:param title: Title of the pull request.
87+
:param body: Body/description of the pull request.
88+
"""
89+
pr = self.repo.create_pull(title=title, body=body, head=head, base=base)
90+
if pr:
91+
pr.merge()
92+
print(f'Pull request from {head} to {base} created and merged.')
93+
else:
94+
print(f'Failed to create or merge pull request from {head} to {base}.')
95+
96+
def create_conflict(self, file_name: str, base_content: str, branch_content: str) -> None:
97+
"""
98+
Create a conflict in a file between the main branch and a new branch.
99+
100+
:param file_name: Name of the file to create a conflict in.
101+
:param base_content: Content for the main branch.
102+
:param branch_content: Content for the new branch.
103+
"""
104+
self.commit_and_push('main', file_name, base_content)
105+
if 'main' in [ref.name for ref in git.Repo(self.repo_dir).refs]:
106+
try:
107+
os.chdir(self.repo_dir)
108+
repo_local = git.Repo(self.repo_dir)
109+
repo_local.git.checkout('-b', 'conflict-branch')
110+
with open(file_name, 'w') as f:
111+
f.write(branch_content)
112+
repo_local.git.add(file_name)
113+
repo_local.git.commit('-m', 'Create conflict')
114+
repo_local.git.push('--set-upstream', 'origin', 'conflict-branch')
115+
print('Conflict created in CONFLICT.md successfully.')
116+
except Exception as e:
117+
print(f'Failed to create conflict in {file_name}. Error: {e}')
118+
else:
119+
print('Failed to push changes to main. Skipping conflict creation.')
120+
121+
def resolve_conflict(self, file_name: str, resolved_content: str) -> None:
122+
"""
123+
Resolve a conflict in a file.
124+
125+
:param file_name: Name of the file with the conflict.
126+
:param resolved_content: Resolved content for the file.
127+
"""
128+
try:
129+
os.chdir(self.repo_dir)
130+
repo_local = git.Repo(self.repo_dir)
131+
if 'conflict-branch' in [ref.name for ref in repo_local.refs]:
132+
repo_local.git.checkout('conflict-branch')
133+
with open(file_name, 'w') as f:
134+
f.write(resolved_content)
135+
repo_local.git.add(file_name)
136+
repo_local.git.commit('-m', 'Resolve conflict')
137+
repo_local.git.push()
138+
print(f'Conflict in {file_name} resolved successfully.')
139+
else:
140+
print(f'Failed to resolve conflict in {file_name}. "conflict-branch" does not exist.')
141+
except Exception as e:
142+
print(f'Failed to resolve conflict in {file_name}. Error: {e}')
143+
144+
def comment_on_pull_request(self, pr_number: int, comment: str) -> None:
145+
"""
146+
Add a comment to a pull request.
147+
148+
:param pr_number: Pull request number.
149+
:param comment: Comment text.
150+
"""
151+
pr = self.repo.get_pull(pr_number)
152+
if pr:
153+
pr.create_issue_comment(comment)
154+
print(f'Comment added to PR #{pr_number}.')
155+
else:
156+
print(f'Failed to add comment to PR #{pr_number}.')
157+
158+
def test_general_git_commands(self) -> None:
159+
"""Test various git commands."""
160+
repo_local = git.Repo(self.repo_dir)
161+
if repo_local:
162+
print(repo_local.git.status())
163+
print(repo_local.git.log('-1'))
164+
print(repo_local.git.show('-1'))
165+
print(repo_local.git.branch('-a'))
166+
print('Git commands tested successfully.')
167+
else:
168+
print('Failed to test git commands.')
169+
170+
def test_github_api(self) -> None:
171+
"""Test various GitHub API operations."""
172+
if self.repo:
173+
print(self.repo)
174+
open_prs = self.repo.get_pulls(state='open')
175+
for pr in open_prs:
176+
print(pr)
177+
print('GitHub API tested successfully.')
178+
else:
179+
print('Failed to test GitHub API.')
180+
181+
def create_branches_and_make_changes(self, branches: Dict[str, str]) -> None:
182+
"""
183+
Create branches, make changes, and push to remote.
184+
185+
:param branches: Dictionary of branch names and their content.
186+
"""
187+
try:
188+
os.chdir(self.repo_dir)
189+
repo_local = git.Repo(self.repo_dir)
190+
for idx, (branch, content) in enumerate(branches.items()):
191+
if branch in [ref.name for ref in repo_local.refs]:
192+
repo_local.git.checkout(branch)
193+
else:
194+
repo_local.git.checkout('-b', branch)
195+
mock_file_name = f'{branch}_mock.txt'
196+
with open(mock_file_name, 'w') as f:
197+
f.write(f'def mock_function_{branch}():\n')
198+
f.write(f" print('This is a mock function from {branch}')\n")
199+
repo_local.git.add(mock_file_name)
200+
repo_local.git.commit('-m', f'Added mock function in {branch}')
201+
repo_local.git.push('--set-upstream', 'origin', branch)
202+
pr = self.repo.create_pull(title=f'Merge {branch} to main', body=f'Merging changes from {branch}', head=branch, base='main')
203+
self.comment_on_pull_request(pr.number, f'Reviewing changes from {branch}. Looks good!')
204+
if idx < len(branches) - 1:
205+
pr.merge()
206+
print(f'Branch {branch} created and changes pushed successfully.')
207+
except Exception as e:
208+
print(f'Failed to create branch or push changes. Error: {e}')
209+
210+
def delete_repos_with_automation(self) -> None:
211+
"""Delete repositories with 'automation' in their name."""
212+
for repo in self.user.get_repos():
213+
if 'automation' in repo.name.lower():
214+
repo.delete()
215+
print(f'Deleted repository: {repo.name}')
216+
217+
218+
def main() -> None:
219+
"""Main function to execute the GitHub automator."""
220+
221+
# Load credentials from YAML file
222+
with open('configuration.yaml', 'r') as file:
223+
config = yaml.safe_load(file)
224+
225+
credentials = config.get('credentials', {})
226+
227+
ACCESS_TOKEN = credentials.get('access_token', '')
228+
GITHUB_ENTERPRISE_URL = credentials.get('base_url', '')
229+
USER_NAME = credentials.get('username', '')
230+
REPO_NAME = credentials.get('repo_name', '')
231+
REPO_DIR = credentials.get('repo_dir', '')
232+
233+
automator = GitHubAutomator(ACCESS_TOKEN, GITHUB_ENTERPRISE_URL, USER_NAME, REPO_NAME, REPO_DIR)
234+
automator.create_and_initialize_repository()
235+
automator.commit_and_push('main', 'README.md', 'Second commit')
236+
automator.create_conflict('CONFLICT.md', 'Base content\n', 'Branch content\n')
237+
time.sleep(2)
238+
automator.resolve_conflict('CONFLICT.md', 'Resolved content\n')
239+
automator.create_and_merge_pull_request('conflict-branch', 'main', 'Merge Conflict Branch', 'Testing conflict resolution')
240+
automator.test_general_git_commands()
241+
automator.test_github_api()
242+
automator.comment_on_pull_request(1, 'This is a test comment on PR #1.')
243+
branches = {
244+
'feature-1': 'Changes from feature-1',
245+
'feature-2': 'Changes from feature-2',
246+
'feature-3': 'Changes from feature-3'
247+
}
248+
automator.create_branches_and_make_changes(branches)
249+
print('Repository setup and operations completed.')
250+
251+
252+
if __name__ == '__main__':
253+
main()

configuration-example.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
credentials:
2+
access_token: your_github_access_token
3+
base_url: https://github-example.com/api/v3
4+
username: your_github_user_name
5+
repo_name: your_github_new_repository_name
6+
repo_dir: /path/to/clone/locally/your_github_new_repository_name

requirements.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
gitdb==4.0.10
2+
GitPython==3.1.36
3+
pycparser==2.21
4+
PyGithub==1.59.1
5+
PyYAML==6.0.1

0 commit comments

Comments
 (0)