From 9876c86db225da4e21355811a9401c5c3314a1e0 Mon Sep 17 00:00:00 2001 From: samuel-marquis Date: Fri, 11 Sep 2026 12:54:41 -0400 Subject: [PATCH 1/4] denis: fetch notes from the pull URL run_automated_checks() fetches refs/notes/* through the grading remote, which points at PUSH_URL. That URL is served by the CGI support in python's http.server, which does not pass the Content-Encoding request header through to git http-backend. Once the local clone holds enough refs, the upload-pack negotiation request grows past the size at which git gzips it, and http-backend parses the gzip stream as pkt-lines: fatal: protocol error: bad line length character: ?\x8b? The client sees "the remote end hung up unexpectedly" and denis crashes before writing any notes. The first deadline of an assignment is unaffected because no notes exist yet, so there is nothing to negotiate. Every later deadline crashes, which silently drops the automated checks and the automatic zeros for missing submissions. That is what happened at the final deadline of the first assignment this semester, and the peer review deadline before it left no notes either. Fetch the notes through origin instead. The clone already uses PULL_URL, which is served as static files and takes no request body. Pushing is unchanged. Fixes: 22c9694 ("denis: add notes tied to initial and final submissions") Signed-off-by: samuel-marquis Co-Authored-By: Claude Opus 5 --- denis/utilities.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/denis/utilities.py b/denis/utilities.py index 59ef108e..35efdea2 100644 --- a/denis/utilities.py +++ b/denis/utilities.py @@ -174,9 +174,11 @@ def check_subject_tag(repo, tag): def run_automated_checks(tags, username_to_subs, peer=False): with tempfile.TemporaryDirectory() as repo_path: repo = git.Repo.clone_from(PULL_URL, repo_path) + # fetch from the pull URL: the CGI server behind the push URL drops + # the Content-Encoding header, so a gzipped fetch request fails there + repo.remotes.origin.fetch('refs/notes/*:refs/notes/*') remote = repo.create_remote(REMOTE_NAME, PUSH_URL) - remote.fetch('refs/notes/*:refs/notes/*') configure_repo(repo) for tag in tags: From 88024103ca1aadc072a4b2fa404e9ab8bf217bfa Mon Sep 17 00:00:00 2001 From: samuel-marquis Date: Fri, 11 Sep 2026 12:54:41 -0400 Subject: [PATCH 2/4] denis: check each peer review against its own submissions peer_review.py passes run_automated_checks() the union of the review1 and review2 username-to-submission dicts. user_to_sub() returns every user as a key, with None for users who did not submit, so the review2 dict always wins the union. check_corrupt_or_missing() looks up each tag's user without regard to the tag's component, so review1 tags are judged by review2 submissions: - a student who submitted review1 but not review2 also gets an automatic zero on review1 - a student who submitted only review2 gets review1 credited Run the checks once per component, each with its own dict. The second run fetches the notes pushed by the first, so this relies on the previous patch. Fixes: b0537c7 ("denis: automatic zero for peer review when no submission is made") Signed-off-by: samuel-marquis Co-Authored-By: Claude Opus 5 --- denis/peer_review.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/denis/peer_review.py b/denis/peer_review.py index a9ca53d2..ac2f3bd3 100755 --- a/denis/peer_review.py +++ b/denis/peer_review.py @@ -18,6 +18,7 @@ tags1 = utilities.update_tags(assignment, 'review1') tags2 = utilities.update_tags(assignment, 'review2') -utilities.run_automated_checks(tags1 + tags2, usernames_to_subs_review1 | usernames_to_subs_review2, peer=True) +utilities.run_automated_checks(tags1, usernames_to_subs_review1, peer=True) +utilities.run_automated_checks(tags2, usernames_to_subs_review2, peer=True) print(f'completed {assignment} assignment processing for peer review submission deadline') From 4c80e202e4c58d3f34612f0556040f48a781000e Mon Sep 17 00:00:00 2001 From: samuel-marquis Date: Fri, 11 Sep 2026 12:55:00 -0400 Subject: [PATCH 3/4] orbit: hyperspace: add an action to correct a user's full name A user's full name can only be set when the account is created. denis uses it to build the Signed-off-by line its DCO check expects, and orbit uses it for the name in the mutt and git configuration it generates for each student. A misspelled name therefore makes every correctly signed patch from that student fail the check, with no way to fix it short of deleting and recreating the account. Add -e/--editfullname, which sets the full name of the supplied username: orbit/warpdrive.sh -u -f '' -e Configuration a student already generated keeps the old name until they fetch it again. Signed-off-by: samuel-marquis Co-Authored-By: Claude Opus 5 --- orbit/hyperspace.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/orbit/hyperspace.py b/orbit/hyperspace.py index dcdb6ec4..0bf06972 100755 --- a/orbit/hyperspace.py +++ b/orbit/hyperspace.py @@ -16,12 +16,14 @@ def errx(msg): exit(1) -def need(a, u=False, p=False): +def need(a, u=False, p=False, f=False): needed = [] if u and a.username is None: needed.append('username') if p and a.password is None: needed.append('password') + if f and a.fullname is None: + needed.append('fullname') if needed: errx(f"Need {' and '.join(needed)}. Bye.") @@ -50,6 +52,15 @@ def do_change_password(args): nou(args.username) +def do_change_fullname(args): + need(args, u=True, f=True) + query = (db.User + .update({db.User.fullname: args.fullname}) + .where(db.User.username == args.username)) + if query.execute() < 1: + nou(args.username) + + def do_reset_password(args): need(args, u=True) query = (db.User @@ -119,6 +130,9 @@ def hyperspace_main(raw_args): actions.add_argument('-m', '--mutatepassword', action='store_const', help='Change password for supplied username to supplied password', dest='do', const=do_change_password) + actions.add_argument('-e', '--editfullname', action='store_const', + help='Change full name for supplied username to supplied full name', + dest='do', const=do_change_fullname) actions.add_argument('-c', '--clearpassword', action='store_const', help='clear password for supplied username so they cannot login', dest='do', const=do_reset_password) From 21b368dbfeba339f8983d7e70ad7292302afd049 Mon Sep 17 00:00:00 2001 From: samuel-marquis Date: Fri, 11 Sep 2026 12:55:52 -0400 Subject: [PATCH 4/4] orbit: hyperspace: include the full name in the roster The roster is the only way to list users from the admin tool, but it leaves out full names, so there is no way to check a spelling before or after correcting it with -e. Append the full name to each line. Signed-off-by: samuel-marquis Co-Authored-By: Claude Opus 5 --- orbit/hyperspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orbit/hyperspace.py b/orbit/hyperspace.py index 0bf06972..67ce5a0c 100755 --- a/orbit/hyperspace.py +++ b/orbit/hyperspace.py @@ -100,7 +100,7 @@ def do_newuser(args): def do_roster(args): print('Users:') for u in db.User.select(): - print(f'{u.username}, {u.pwdhash}, {u.student_id}') + print(f'{u.username}, {u.pwdhash}, {u.student_id}, {u.fullname}') def do_list_sessions(args):