def manage_repos(conf, backend, args, action): """Performs an action (lambda) on all student repos """ hw_name = args.name dry_run = args.dry_run namespace = conf.namespace semester = conf.semester backend_conf = conf.backend roster = get_filtered_roster(conf.roster, args.section, args.student) count = 0 for student in progress.iterate(roster): username = student["username"] student_section = student["section"] if "id" not in student: logging.warning("Student %s does not have a gitlab account.", username) continue full_name = backend.student_repo.build_name(semester, student_section, hw_name, username) repo = backend.student_repo(backend_conf, namespace, full_name) if not dry_run: if action(repo, student): count += 1 else: count += 1 print("Changed {} repositories.".format(count))
def open_all_assignments(conf, backend, args): """Adds each student in the roster to their respective homework repositories as Developers so they can pull/commit/push their work. """ hw_name = args.name namespace = conf.namespace semester = conf.semester backend_conf = conf.backend roster = get_filtered_roster(conf.roster, args.section, args.student) count = 0 for student in progress.iterate(roster): username = student["username"] student_section = student["section"] full_name = backend.student_repo.build_name(semester, student_section, hw_name, username) try: repo = backend.student_repo(backend_conf, namespace, full_name) if "id" not in student: student["id"] = backend.repo.get_user_id( username, backend_conf) open_assignment(repo, student, backend.access.developer) count += 1 except UserInAssignerGroup: logging.info( "%s already has access via group membership, skipping...", username) except RepoError: logging.warning("Could not add %s to %s.", username, full_name) print("Granted access to {} repositories.".format(count))
def score_assignments(conf: Config, backend: BackendBase, args: argparse.Namespace) -> None: """Goes through each student repository and grabs the most recent CI artifact, which contains their autograded score """ student = args.student roster = get_filtered_roster(conf.roster, args.section, student) scores = [] for student in progress.iterate(roster): score = handle_scoring(conf, backend, args, student) if score is not None: scores.append(score) print("Scored {} repositories.".format(len(scores))) print_statistics(scores)
def _push(conf, backend, args): hw_name = args.name hw_path = args.path namespace = conf.namespace semester = conf.semester backend_conf = conf.backend branch = args.branch force = args.force push_unlocked = args.push_unlocked path = os.path.join(hw_path, hw_name) roster = get_filtered_roster(conf.roster, args.section, args.student) for student in progress.iterate(roster): username = student["username"] student_section = student["section"] full_name = backend.student_repo.build_name(semester, student_section, hw_name, username) try: repo = backend.student_repo(backend_conf, namespace, full_name) repo_dir = os.path.join(path, username) repo.add_local_copy(repo_dir) if repo.is_locked() or push_unlocked: info = repo.repo.remote().push(branch, force=force, set_upstream=True) for line in info: logging.debug("%s: flags: %s, branch: %s, summary: %s", full_name, line.flags, line.local_ref, line.summary) if line.flags & line.ERROR: logging.warning("%s: push to %s failed: %s", full_name, line.local_ref, line.summary) else: logging.warning( "%s: repo is not locked (run 'assigner lock %s' first)", full_name, hw_name) except NoSuchPathError: logging.warning("Local repo for %s does not exist; skipping...", username) except RepoError as e: logging.warning(e)
def integrity_check(conf: Config, backend: BackendBase, args: argparse.Namespace) -> None: """Checks that none of the grading files were modified in the timeframe during which students could push to their repository """ student = args.student files_to_check = set(args.files) roster = get_filtered_roster(conf.roster, args.section, None) for student in progress.iterate(roster): username = student["username"] student_section = student["section"] full_name = backend.student_repo.build_name(conf.semester, student_section, args.name, username) try: repo = backend.student_repo(conf.backend, conf.namespace, full_name) check_repo_integrity(repo, files_to_check) except RepoError as e: logger.debug(e) logger.warning("Unable to find repo for %s with URL %s", username, full_name)
def _push(conf, backend, args): backend_conf = conf.backend namespace = conf.namespace semester = conf.semester hw_name = args.name hw_path = args.path message = args.message branch = args.branch add = args.add remove = args.remove update = args.update allow_empty = args.allow_empty # Default behavior: commit changes to all tracked files if (add == []) and (remove == []): logging.debug("Nothing explicitly added or removed; defaulting to git add --update") update = True path = os.path.join(hw_path, hw_name) roster = get_filtered_roster(conf.roster, args.section, args.student) for student in progress.iterate(roster): username = student["username"] student_section = student["section"] full_name = backend.student_repo.build_name(semester, student_section, hw_name, username) has_changes = False try: repo = backend.student_repo(backend_conf, namespace, full_name) repo_dir = os.path.join(path, username) repo.add_local_copy(repo_dir) logging.debug("%s: checking out branch %s", full_name, branch) repo.get_head(branch).checkout() index = repo.get_index() if update: # Stage modified and deleted files for commit # This exactly mimics the behavior of git add --update # (or the effect of doing git commit -a) for change in index.diff(None): has_changes = True if change.deleted_file: logging.debug("%s: git rm %s", full_name, change.b_path) index.remove([change.b_path]) else: logging.debug("%s: git add %s", full_name, change.b_path) index.add([change.b_path]) if add: has_changes = True logging.debug("%s: adding %s", full_name, add) index.add(add) if remove: has_changes = True logging.debug("%s: removing %s", full_name, remove) index.remove(remove) if has_changes or allow_empty: logging.debug("%s: committing changes with message %s", full_name, message) index.commit(message) else: logging.warning("%s: No changes in repo; skipping commit.", full_name) except NoSuchPathError: logging.warning("Local repo for %s does not exist; skipping...", username) except RepoError as e: logging.warning(e) except HTTPError as e: if e.response.status_code == 404: logging.warning("Repository %s does not exist.", full_name) else: raise
def _push(conf, backend, args): backend_conf = conf.backend namespace = conf.namespace semester = conf.semester hw_name = args.name hw_path = args.path message = args.message branch = args.branch add = args.add remove = args.remove update = args.update allow_empty = args.allow_empty gpg_sign = args.gpg_sign # Default behavior: commit changes to all tracked files if (add == []) and (remove == []): logging.debug( "Nothing explicitly added or removed; defaulting to git add --update" ) update = True path = os.path.join(hw_path, hw_name) roster = get_filtered_roster(conf.roster, args.section, args.student) for student in progress.iterate(roster): username = student["username"] student_section = student["section"] full_name = backend.student_repo.build_name(semester, student_section, hw_name, username) has_changes = False try: repo = backend.student_repo(backend_conf, namespace, full_name) repo_dir = os.path.join(path, username) repo.add_local_copy(repo_dir) logging.debug("%s: checking out branch %s", full_name, branch) repo.get_head(branch).checkout() index = repo.get_index() if update: # Stage modified and deleted files for commit # This exactly mimics the behavior of git add --update # (or the effect of doing git commit -a) for change in index.diff(None): has_changes = True if change.deleted_file: logging.debug("%s: git rm %s", full_name, change.b_path) index.remove([change.b_path]) else: logging.debug("%s: git add %s", full_name, change.b_path) index.add([change.b_path]) if add: has_changes = True logging.debug("%s: adding %s", full_name, add) index.add(add) if remove: has_changes = True logging.debug("%s: removing %s", full_name, remove) index.remove(remove) if has_changes or allow_empty: logging.debug("%s: committing changes with message %s", full_name, message) if gpg_sign: # The GitPython interface does not support signed commits, and # launching via repo.git.commit will launch an inaccessible # interactive prompt in the background index.write(ignore_extension_data=True) subprocess.check_call( ["git", "commit", "-S", "-m", '"{}"'.format(message)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=repo_dir) else: index.commit(message) else: logging.warning("%s: No changes in repo; skipping commit.", full_name) except NoSuchPathError: logging.warning("Local repo for %s does not exist; skipping...", username) except RepoError as e: logging.warning(e)