Skip to content
Open
1 change: 1 addition & 0 deletions commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Initialize commands package
Binary file added commands/__pycache__/common.cpython-311.pyc
Binary file not shown.
Binary file not shown.
Binary file added commands/__pycache__/deploy.cpython-311.pyc
Binary file not shown.
Binary file added commands/__pycache__/open_mr.cpython-311.pyc
Binary file not shown.
Binary file added commands/__pycache__/review.cpython-311.pyc
Binary file not shown.
85 changes: 85 additions & 0 deletions commands/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import subprocess
import json
from config import GROUP_ID
import gitlab_api
import interactive
import git_utils

def get_authorized_user():
output = subprocess.check_output(["glab", "api", "/user"])
return json.loads(output)

def list_milestones(current=False):
cmd = f'glab api /groups/{GROUP_ID}/milestones?state=active'
result = subprocess.run(cmd.split(), stdout=subprocess.PIPE)
milestones = json.loads(result.stdout)
if current:
import datetime
today = datetime.date.today().strftime('%Y-%m-%d')
active_milestones = []
for milestone in milestones:
start_date = milestone['start_date']
due_date = milestone['due_date']
if start_date and due_date and start_date <= today and due_date >= today:
active_milestones.append(milestone)
active_milestones.sort(key=lambda x: x['due_date'])
return active_milestones[0] if active_milestones else None
return milestones

def list_iterations():
cmd = f'glab api /groups/{GROUP_ID}/iterations?state=opened'
result = subprocess.run(cmd.split(), stdout=subprocess.PIPE)
return json.loads(result.stdout)

def get_active_iteration():
iterations = list_iterations()
import datetime
today = datetime.date.today().strftime('%Y-%m-%d')
active_iterations = []
for iteration in iterations:
start_date = iteration['start_date']
due_date = iteration['due_date']
if start_date and due_date and start_date <= today and due_date >= today:
active_iterations.append(iteration)
active_iterations.sort(key=lambda x: x['due_date'])
return active_iterations[0] if active_iterations else None

def list_epics():
cmd = f'glab api /groups/{GROUP_ID}/epics?per_page=1000&state=opened'
result = subprocess.run(cmd.split(), stdout=subprocess.PIPE)
return json.loads(result.stdout)

def get_milestone(manual):
if manual:
milestones = list_milestones()
selected_title = interactive.select_milestone(milestones)
return next((t for t in milestones if t['title'] == selected_title), None)
return list_milestones(True)

def get_iteration(manual):
if manual:
iterations = list_iterations()
selected_range = interactive.select_iteration(iterations)
return next((t for t in iterations if t['start_date'] + ' - ' + t['due_date'] == selected_range), None)
return get_active_iteration()

def get_epic():
epics = list_epics()
selected_title = interactive.select_epic(epics)
return next((t for t in epics if t['title'] == selected_title), None)

def get_project_id():
link = git_utils.get_project_link_from_current_dir()
if link == -1:
# enter_project_id logic
import inquirer
while True:
project_id = inquirer.Text('pid', message='Please enter the ID of your GitLab project:').prompt()
if project_id: return project_id
exit('Invalid project ID.')

projects = gitlab_api.get_all_projects(link)
for project in projects:
if project.get("ssh_url_to_repo") == link:
return project.get("id")
return None
124 changes: 124 additions & 0 deletions commands/create_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import subprocess
import json
import re
from config import MAIN_BRANCH_DEFAULT, SQUASH_COMMITS, DELETE_BRANCH

def execute_issue_create(project_id, title, labels, milestone_id, epic, iteration, weight, estimated_time, issue_type='issue'):
import interactive
import gitlab_api

labels_str = ",".join(labels) if isinstance(labels, list) else labels

# Using glab for issue creation as in original code
issue_command = [
"glab", "api",
f"/projects/{str(project_id)}/issues",
"-f", f'title={title}',
"-f", f'assignee_ids={get_authorized_user()["id"]}',
"-f", f'issue_type={issue_type}'
]
if labels_str:
issue_command.extend(["-f", f'labels={labels_str}'])
if weight:
issue_command.extend(["-f", f'weight={str(weight)}'])
if milestone_id:
issue_command.extend(["-f", f'milestone_id={str(milestone_id)}'])
if epic:
issue_command.extend(["-f", f'epic_id={str(epic["id"])}'])

description = ""
if iteration:
description += f"/iteration *iteration:{str(iteration['id'])} "
if estimated_time:
description += f"\n/estimate {estimated_time}m "

issue_command.extend(["-f", f'description={description}'])

issue_output = subprocess.check_output(issue_command)
return json.loads(issue_output.decode())

def get_authorized_user():
import subprocess
import json
output = subprocess.check_output(["glab", "api", "/user"])
return json.loads(output)

def create_branch(project_id, issue):
issue_id = str(issue['iid'])
title = re.sub('\\s+', '-', issue['title']).lower()
title = issue_id + '-' + title.replace(':','').replace('(',' ').replace(')', '').replace(' ','-')
branch_output = subprocess.check_output(["glab", "api", f"/projects/{str(project_id)}/repository/branches", "-f", f'branch={title}', "-f", f'ref={MAIN_BRANCH_DEFAULT}', "-f", f'issue_iid={issue_id}'])
return json.loads(branch_output.decode())

def create_merge_request(project_id, branch, issue, labels, milestone_id):
issue_id = str(issue['iid'])
branch_name = branch['name']
title = issue['title']
assignee_id = get_authorized_user()['id']
labels_str = ",".join(labels) if isinstance(labels, list) else labels

merge_request_command = [
"glab", "api",
f"/projects/{str(project_id)}/merge_requests",
"-f", f'title={title}',
"-f", f'description="Closes #{issue_id}"',
"-f", f'source_branch={branch_name}',
"-f", f'target_branch={MAIN_BRANCH_DEFAULT}',
"-f", f'issue_iid={issue_id}',
"-f", f'assignee_ids={assignee_id}'
]
if SQUASH_COMMITS:
merge_request_command.extend(["-f", "squash=true"])
if DELETE_BRANCH:
merge_request_command.extend(["-f", "remove_source_branch=true"])
if labels_str:
merge_request_command.extend(["-f", f'labels={labels_str}'])
if milestone_id:
merge_request_command.extend(["-f", f'milestone_id={str(milestone_id)}'])

mr_output = subprocess.check_output(merge_request_command)
return json.loads(mr_output.decode())

def start_issue_creation(project_id, title, milestone_id, epic, iteration, selected_settings, only_issue):
import interactive
import git_utils

estimated_time = interactive.prompt_estimated_time()

if isinstance(project_id, list):
estimated_time_per_project = int(estimated_time) / len(project_id) if estimated_time else None
else:
estimated_time_per_project = estimated_time

if estimated_time_per_project:
selected_settings = selected_settings.copy() if selected_settings else {}
selected_settings['estimated_time'] = int(estimated_time_per_project)

# In reality, we should call execute_issue_create here
# For simplicity and matching original logic:
issue_type = selected_settings.get('type') or 'issue'
created_issue = execute_issue_create(
project_id,
title,
selected_settings.get('labels'),
milestone_id,
epic,
iteration,
selected_settings.get('weight'),
selected_settings.get('estimated_time'),
issue_type
)
print(f"Issue #{created_issue['iid']}: {created_issue['title']} created.")

if only_issue:
return created_issue

created_branch = create_branch(project_id, created_issue)
created_mr = create_merge_request(project_id, created_branch, created_issue, selected_settings.get('labels'), milestone_id)
print(f"Merge request #{created_mr['iid']}: {created_mr['title']} created.")
print("Run:")
print(" git fetch origin")
print(f" git checkout -b '{created_mr['source_branch']}' 'origin/{created_mr['source_branch']}'")
print("to switch to new branch.")

return created_issue
71 changes: 71 additions & 0 deletions commands/deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import subprocess
import datetime
from config import API_URL, GITLAB_TOKEN, MAIN_BRANCH_DEFAULT, PRODUCTION_MAPPINGS
import gitlab_api
import git_utils
from commands.common import get_project_id

def check_last_production_deploy():
try:
project_id = get_project_id()
ref = MAIN_BRANCH_DEFAULT
try:
ref = git_utils.get_main_branch()
except:
pass

pipelines = gitlab_api.get_pipelines(project_id, ref=ref)
if not pipelines:
print("No pipelines found.")
return

for pipeline in pipelines:
jobs = gitlab_api.get_pipeline_jobs(project_id, pipeline['id'])
if not jobs: continue

for job in jobs:
job_name = job.get('name', '')
stage = job.get('stage', '')
job_status = job.get('status', '').lower()

if job_status != 'success': continue

project_mapping = PRODUCTION_MAPPINGS.get(str(project_id))
if project_mapping:
expected_stage = project_mapping.get('stage', '').lower()
expected_job = project_mapping.get('job', '').lower()
if stage.lower() == expected_stage or (expected_job and job_name.lower() == expected_job):
display_deploy_info(pipeline, job)
return
else:
# If no mapping, original code printed 'Didn't find deployment pipeline' inside loop
# We'll just continue searching
pass

print(f"No production deployment found matching pattern")
except Exception as e:
print(f"Error fetching last production deploy: {str(e)}")

def display_deploy_info(pipeline, job):
print(f"🚀 Last Production Deployment:")
print(f" Pipeline: #{pipeline['id']} - {pipeline['status']}")
print(f" Job: {job['name']} ({job['status']})")
print(f" Branch/Tag: {pipeline['ref']}")
print(f" Started: {job.get('started_at', 'N/A')}")
print(f" Finished: {job.get('finished_at', 'N/A')}")
print(f" Duration: {job.get('duration', 'N/A')} seconds" if job.get('duration') else " Duration: N/A")
print(f" Commit: {pipeline['sha'][:8]}")
print(f" URL: {pipeline['web_url']}")

if job.get('finished_at'):
try:
finished_time = datetime.datetime.fromisoformat(job['finished_at'].replace('Z', '+00:00'))
time_diff = datetime.datetime.now(datetime.timezone.utc) - finished_time
if time_diff.days > 0:
print(f" ⏰ {time_diff.days} days ago")
elif time_diff.seconds > 3600:
print(f" ⏰ {time_diff.seconds // 3600} hours ago")
else:
print(f" ⏰ {time_diff.seconds // 60} minutes ago")
except:
pass
22 changes: 22 additions & 0 deletions commands/open_mr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import subprocess
import json
import webbrowser
from config import BASE_URL, API_URL, GITLAB_TOKEN, MAIN_BRANCH_DEFAULT
import gitlab_api
import git_utils
from commands.common import get_project_id

def open_mr_in_browser():
try:
branch = git_utils.get_current_branch()
project_id = get_project_id()
mr = gitlab_api.get_merge_request_for_branch(project_id, branch)
if not mr:
print("No active merge request found for current branch.")
return

remote_url = subprocess.check_output(["git", "config", "--get", "remote.origin.url"], text=True).strip()
url = BASE_URL + '/' + remote_url.split(':')[1][:-4]
webbrowser.open(f"{url}/-/merge_requests/{mr['iid']}")
except Exception as e:
print(f"Error opening merge request: {e}")
73 changes: 73 additions & 0 deletions commands/review.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import subprocess
import json
from config import API_URL, GITLAB_TOKEN, REVIEWERS, PRODUCTION_MAPPINGS
import gitlab_api
import interactive
import git_utils
from commands.common import get_project_id

def get_active_mr_id():
branch = git_utils.get_current_branch()
project_id = get_project_id()
mr = gitlab_api.get_merge_request_for_branch(project_id, branch)
if mr:
return mr['iid']
return None

def review_workflow(select_reviewers=False, auto_merge=False):
# Track issue time
track_issue_time()

reviewers = None
if select_reviewers:
reviewer_choices = []
for rid in REVIEWERS:
user = gitlab_api.get_user_details(rid)
if user:
reviewer_choices.append((f"{user.get('name')} ({user.get('username')})", rid))
else:
reviewer_choices.append((str(rid), rid))
reviewers = interactive.prompt_reviewers(reviewer_choices)

project_id = get_project_id()
mr_id = get_active_mr_id()
if not mr_id:
print("No active merge request found for current branch.")
return

gitlab_api.update_merge_request_reviewers(project_id, mr_id, reviewers if reviewers is not None else REVIEWERS)

# AI Code Review
try:
from ai_code_review import run_review_for_mr
run_review_for_mr(project_id, mr_id, GITLAB_TOKEN, API_URL)
except Exception as e:
print(f"AI review skipped: {e}")

if auto_merge:
gitlab_api.set_mr_auto_merge(project_id, mr_id)

def track_issue_time():
project_id = get_project_id()
branch = git_utils.get_current_branch()
mr = gitlab_api.get_merge_request_for_branch(project_id, branch)
if not mr:
print("Error getting merge request details")
return

# Original code extracted issue id from description
issue_id = mr['description'].replace('"','').replace('#','').split()[1]

spent_time = interactive.prompt_spent_time()

# Using glab for time tracking as in original
time_tracking_command = [
"glab", "api",
f"/projects/{project_id}/issues/{issue_id}/notes",
"-f", f"body=/spend {spent_time}m"
]
try:
subprocess.run(time_tracking_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
print(f"Added {spent_time} minutes to issue {issue_id} time tracking.")
except subprocess.CalledProcessError as e:
print(f"Error adding time tracking: {str(e)}")
Loading