232 lines
9.1 KiB
Plaintext
232 lines
9.1 KiB
Plaintext
|
#!/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)
|