CSE320/course_tools/git-submit
2022-04-16 11:33:13 -04:00

232 lines
9.1 KiB
Python
Executable File

#!/usr/bin/env python3
import requests
from requests import get
from requests.exceptions import ConnectTimeout, ConnectionError, HTTPError
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from datetime import datetime
from signal import signal, SIGINT, SIGTSTP, SIG_IGN
from subprocess import run, PIPE
from shlex import split
from argparse import ArgumentParser
from sys import exit
RED = '\033[1;31m'
GRE = '\033[1;32m'
YEL = '\033[0;33m'
BLU = '\033[1;34m'
PUR = '\033[1;35m'
CYA = '\033[1;36m'
BOLD = '\033[1m'
DEF = '\033[0m'
BASE_URL = 'https://cse320.starkeffect.com:3000/submit'
server_timeout = None
def yn_prompt():
prompt = input('{}Continue? Please enter Y/n{}: '.format(PUR, DEF))
while prompt != 'Y' and prompt.lower() != 'n':
prompt = input('{}Continue? Please enter Y/n{}: '.format(PUR, DEF))
if prompt != 'Y':
print('{}Aborting...{}'.format(YEL, DEF))
exit(1)
def first_api_call():
global server_timeout
try:
res = get(BASE_URL + '/timeout', params=None, timeout=15, verify=False)
res_json = res.json()
server_timeout = res_json['timeout']
except (ConnectionError, ConnectTimeout, HTTPError):
print('Error connecting to the server. Try again. (FIRST_SERVER_CALL)')
exit(1)
def initial_message():
print('Only commits made from master or merged into master will be ',
'considered. If you would like to submit a commit from a different ',
'branch, please merge the branch into master first.')
yn_prompt()
def api_call(endpoint, query):
global server_timeout
try:
res = get(BASE_URL + endpoint, params=query, timeout=server_timeout,
verify=False)
except (ConnectionError, ConnectTimeout, HTTPError):
print('Error connecting to the server. Try again. ',
'(SERVER_CONNECTION_ERROR)')
exit(1)
res_json = res.json()
if 'errmsg' in res_json:
print(res_json['errmsg'])
exit(1)
return res_json
def validate_git_directory():
cmd = split('git remote show origin')
proc = run(cmd, stdout=PIPE)
if proc.returncode:
exit(proc.returncode)
# You are not expected to understand this
return [line for line in proc.stdout.decode().split('\n') if
line.strip().startswith('Push')][0].split('/')[-1].split('.')[0]
def validate_commit(commit):
# check current user branch
branch_cmd = split('git rev-parse --abbrev-ref HEAD')
branch_proc = run(branch_cmd, stdout=PIPE, stderr=PIPE)
if branch_proc.returncode:
print('{}Error{}: Uninitialized repository. Please initialize your repo.'.format(RED, DEF))
exit(branch_proc.returncode)
current_branch = [line for line in branch_proc.stdout.decode().split('\n')][0]
if not current_branch == 'master':
print('{}Error{}: Run git submit from the master branch'.format(RED, DEF))
exit(1)
# check branch commit was made from
if not commit == 'master':
if len(commit) < 8:
print('{}Error{}: Use at least the first 8 characters of the commit hash.'.format(RED, DEF))
exit(1)
commit_branch_cmd = split('git branch --contains {}'.format(commit))
commit_branch_proc = run(commit_branch_cmd, stdout=PIPE, stderr=PIPE)
if commit_branch_proc.returncode:
print('{}Error{}: The specified commit hash is invalid.'.format(RED, DEF))
exit(commit_branch_proc.returncode)
branches = [branch for branch in commit_branch_proc.stdout.decode().split('\n')]
on_master = False
for branch in branches:
branch_name = [word.strip() for word in branch.split('*')][-1]
if branch_name == 'master':
on_master = True
if not on_master:
print('{}Error{}: The submitted commit should be from master. Merge the branch with the commit into master.'.format(RED, DEF))
exit(1)
cmd = "bash -c 'git log --branches=master* --not --remotes --pretty=oneline'"
proc = run(cmd, stdout=PIPE, shell=True)
if proc.returncode:
print(proc.stdout)
exit(proc.returncode)
elif commit == 'master' and proc.stdout:
print('{}Error{}: Push your commits to the remote using: git push'.format(RED, DEF))
exit(1)
elif commit in proc.stdout.decode():
print('{}Error{}: Push your commits to the remote using: git push'.format(RED, DEF))
exit(1)
def validate_tag(tag):
response = api_call('/verify/{}'.format(tag), None)
if 'err' in response:
print('{}Error{}: {}'.format(RED, DEF, response['err']))
exit(1)
if not response['valid']:
print('{}Error{}: {}'.format(RED, DEF, response['msg']))
exit(1)
def check_for_resubmission(args):
status = api_call('/status', args)
print('{}Submission Time{}: {}'.format(BLU, DEF, status['time']))
if status['submitted']:
print('{}Resubmitting Homework{}: {} -- Are you {}sure{} you wish to resubmit '
'(this may result in lateday penalties)?'.format(YEL, DEF, CYA + args['tag'] + DEF, BOLD, DEF))
else:
print('{}Submitting Homework{}: {} -- Are you {}sure{}? '
.format(YEL, DEF, CYA + args['tag'] + DEF, BOLD, DEF))
yn_prompt()
return status['submitted'], status['time']
def confirm_repo_state():
run(split('git -c color.status=always status'))
print('{}Current Repo State{}: Are you {}sure{} you have committed and pushed all the files needed '
'(such as .h files)?'.format(YEL, DEF, BOLD, DEF))
yn_prompt()
def confirm_commit(commit):
cmd = split('git --no-pager -c color.ui=always show {} --pretty=fuller --quiet'.format(commit))
proc = run(cmd)
if proc.returncode:
exit(proc.returncode)
print('{}Submission Commit{}: Are you {}sure{} this is the commit you wish to submit?'.format(YEL, DEF, BOLD, DEF))
yn_prompt()
def confirm_submission(tag):
print('{}Confirm Submission{}: Are you {}sure{} you want to submit {}? Your previous submission (if any) will be '
'{}overwritten{}!'.format(YEL, DEF, BOLD, DEF, CYA + tag + DEF, BOLD, DEF))
yn_prompt()
def trigger_submission(args):
info = api_call('', args)
return info['late'], info['lateDays']
def submission_info(**kwargs):
run(split('git pull --quiet'))
late = kwargs['late']
tag = kwargs['tag']
resubmit = kwargs['submit']
late_days = kwargs['days']
attempt_time = kwargs['time']
if late:
print('{}Urgent{}: {} was overdue. You did not have enough late days remaining.'.format(RED, DEF, tag))
print('{}Info{}: You have {} lateday(s) remaining.'.format(BLU, DEF, late_days))
if resubmit:
print('{}Alert{}: Your last submission will be taken into consideration.'.format(YEL, DEF))
else:
print('{}Urgent{}: You will be given an automatic zero for this assignment. Please meet your Professor\n'
'after lecture or during office hours or please email us at cse320@cs.stonybrook.edu with\n'
'{}[CSE320] - {} Overdue{} as the subject.'.format(RED, DEF, BLU, tag, DEF))
else:
print('{}Success{}: {} submission successful. Your assignment was submitted on {}.'
.format(GRE, DEF, tag, attempt_time))
print('{}Info{}: You have {} lateday(s) remaining.'.format(BLU, DEF, late_days))
print('Thank you for submitting your homework! We are working hard to get your grades out as soon as possible.')
print('If you have any concerns please email us at cse320@cs.stonybrook.edu with {}[{}]{} in the subject.'
.format(BLU, tag, DEF))
print('{}The CSE 320 Team{}'.format(PUR, DEF))
def main(arg_parser):
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
first_api_call()
initial_message()
net_id = validate_git_directory()
args = arg_parser.parse_args()
attempt_time = datetime.now()
validate_tag(args.TAG)
validate_commit(args.commit)
resubmit, time = check_for_resubmission({'tag': args.TAG, 'repo': net_id, 'attemptTime': attempt_time.isoformat()})
confirm_repo_state()
confirm_commit(args.commit)
confirm_submission(args.TAG)
signal(SIGINT, SIG_IGN)
signal(SIGTSTP, SIG_IGN)
late, late_days = trigger_submission({'tag': args.TAG, 'repo': net_id, 'commit': args.commit})
submission_info(tag=args.TAG, time=time, submit=resubmit, late=late, days=late_days)
exit(0)
class GitArgParser(ArgumentParser):
def error(self, message):
print('{}Error{}: {}'.format(RED, DEF, message))
self.print_help()
exit(2)
if __name__ == '__main__':
parser = GitArgParser(prog='git submit', description='Submit your homework assignment using git.')
parser.add_argument('TAG', type=str, help='The homework you wish to submit. Can have one of the following values: '
'hw0, hw1, hw2, hw3, hw4, hw5')
parser.add_argument('-c', dest='commit', type=str, required=False, default='master',
help='Used if you wish to submit a commit that is not the latest. COMMIT is the SHA value of '
'your commit.')
main(parser)