GitHub Course Sync is a small PowerShell toolkit for teachers who want a GitHub Classroom-style workflow based on ordinary organization-owned private repositories.
Two commands manage a repository set:
Setup-Classroom.ps1creates and secures one private repository per student for the configured scope.Sync-Classroom.ps1distributes the complete current contents and reachable commit history of the private base repository'smainbranch to every corresponding student repository. Other branches and tags are not synchronized.
Students use ordinary Pull, Commit, and Push. They do not create
forks, configure an upstream remote, or open pull requests for normal class
work.
Students may use any Git client that supports the standard clone, fetch, pull, commit, and push operations. Command-line Git, IDE integrations, and graphical clients are all compatible. The scripts work with repositories on GitHub and do not depend on the student's local client.
Important
This is an independent community project. It is not affiliated with or supported by GitHub, Microsoft, or the author's school. Test it with a dummy repository and test account before using it with real students.
Clone this repository, copy classroom.example.psd1 to classroom.psd1 and
students.example.csv to students.csv, replace the example values with dummy
test resources, and preview provisioning with
.\scripts\Setup-Classroom.ps1 -WhatIf. Before making changes on GitHub, follow
the complete organization-security and configuration instructions
and run the manual acceptance test.
One configuration represents one synchronization scope: a base repository and the student repositories derived from it. The scope can be a complete course, a school year, a unit, or one assignment.
school organization
|
+-- <scope>-base private, teacher-only source
|
+-- <scope>-novak-jan private, Jan has Write
+-- <scope>-svobodova private, Petra has Write
+-- ... teacher team has Admin on every repository
The organization owns every student repository. Students cannot transfer or delete the repository, change its visibility, or revoke the teacher team's access. Organization owners retain administrative control.
The base repository must contain only student-safe material: assignments, starter code, inputs, and resources. Never put teacher solutions, grading notes, credentials, student data, or private preparation material in it.
The scripts do not prescribe how long a student repository should live. They
always copy and later synchronize the complete contents and reachable history
of the base repository's main branch, so that branch defines the scope.
| Model | Example | Advantages | Tradeoffs |
|---|---|---|---|
| Course or school year | programming-2026-base -> programming-2026-novak-jan |
One clone and invitation per student; continuous history; simple student routine | Repositories grow and later releases can conflict with earlier student changes |
| One assignment or unit | arrays-base -> arrays-novak-jan |
Isolated history and grading; fewer cross-assignment conflicts | More repositories, invitations, prefixes, and configurations |
| Hybrid | one scope per term or topic | Balances repository count and isolation | The teacher must define and communicate the boundaries |
For the course model, keep adding student-safe material to the same base and run synchronization whenever material is released. For the assignment model, create a new private base, unique repository prefix, and configuration for each assignment. Run setup to create that assignment's student repositories; run synchronization later only when correcting or extending that assignment.
Do not reuse a repository prefix for a different scope in the same organization. Repository names are the mechanism that keeps independently managed sets separate.
The base repository and all student repositories share the same initial Git history. For each release, the synchronization script:
- reads the current
maincommit from the base repository; - pushes that exact commit to a
github-course-sync/<commit>branch in each student repository; - creates an internal pull request titled
GitHub Course Sync: Course materials update (<commit>)from that branch to the student'smain; - merges the pull request when GitHub reports no conflict.
This is a Git merge, not a file mirror or destructive overwrite. Student commits remain in the repository. Every committed base-repository change, including additions, edits, renames, and deletions, is distributed. The base commit SHA identifies a release, so running the same synchronization again is safe and does not need a separate state file.
In a long-lived repository, reduce conflicts by placing each new assignment in a new directory and avoiding files students may already have changed. In an assignment-scoped repository, avoid changing starter files after students have begun work unless the correction is necessary. If GitHub cannot merge a release automatically, the pull request remains open for the student or teacher to resolve. The script will not start a newer synchronization for that student while an older synchronization pull request remains unresolved.
- Windows PowerShell 5.1 or PowerShell 7
- Git
- GitHub CLI
- for students, any Git client permitted and supported by the school
- a GitHub organization
- an account allowed to create private organization repositories, manage repository access, and configure repository settings
- one existing teacher team with a stable slug, for example
programming-teachers
Some branch-protection capabilities for private repositories depend on the organization's GitHub plan. The scripts report a warning when GitHub refuses a protection setting.
classroom.example.psd1- synchronization-scope configuration templatestudents.example.csv- student-name and repository-name templatescripts/Setup-Classroom.ps1- initial provisioning and later additionsscripts/Sync-Classroom.ps1- basemain-branch distributionscripts/Common.ps1- shared implementationdocs/STUDENT_WORKFLOW.en.md- English student instructionsdocs/STUDENT_WORKFLOW.cs.md- Czech student instructionsdocs/ACCEPTANCE_TEST.md- safe manual acceptance scenariotests/Test-Common.ps1- offline automated checks
Review the organization settings before managing a class. The intended policy is:
- default member repository permission:
None; - members cannot create, delete, or transfer repositories unless needed;
- private repository forking disabled;
- outside-collaborator invitations restricted to organization owners;
- two-factor authentication required when school policy permits it;
- at least two trusted organization owners for recovery;
- one teacher team, for example
programming-teachers.
The scripts additionally require managed repositories to be private, disable
forking, grant the teacher team Admin, grant each student Write only on
their repository, and protect main against force-pushes and deletion.
Do not store organization tokens, personal access tokens, or deployment secrets in this repository or in student repositories. Authentication remains in GitHub CLI's credential store on the teacher computer.
On Windows, Git and GitHub CLI can be installed with:
winget install --id Git.Git
winget install --id GitHub.cliClose and reopen PowerShell after installation, then authenticate:
git --version
gh --version
gh auth login
gh auth setup-git
gh auth statusIf Windows PowerShell blocks local scripts, use PowerShell 7 or follow the execution policy approved by your organization. Do not bypass a centrally managed security policy.
Clone the repository:
git clone https://github.com/slovak-edhouse/github-course-sync.git
Set-Location github-course-syncAlternatively, download a tagged release archive from the repository's Releases page. Prefer a tagged release for real student repositories rather than an unreviewed development commit.
First decide whether this configuration represents a course, school year, unit, or assignment. Then create one private base repository in the school organization. For example:
school-org/programming-2026-base
school-org/arrays-assignment-base
Use main as its default branch and make at least one initial commit. Give only
the teacher team access. Commit the complete student-visible repository as it
should appear when that course, unit, or assignment begins.
For one active scope, the simplest arrangement is to copy the examples into the repository root:
Copy-Item classroom.example.psd1 classroom.psd1
Copy-Item students.example.csv students.csvThe two real files are ignored by Git. Keep them private and back them up using your school's approved storage.
To manage several scopes, keep each configuration and its adjacent student CSV in a separate ignored directory:
_local/
+-- programming-2026/
| +-- classroom.psd1
| +-- students.csv
+-- arrays-assignment/
+-- classroom.psd1
+-- students.csv
Pass the chosen configuration explicitly when running a script:
.\scripts\Setup-Classroom.ps1 `
-ConfigPath .\_local\arrays-assignment\classroom.psd1 `
-WhatIfstudents.csv is always read from the same directory as its
classroom.psd1. This allows the same tool clone to manage multiple independent
repository sets. Keep every real configuration directory private.
Edit classroom.psd1:
@{
Organization = 'school-org'
BaseRepository = 'school-org/programming-2026-base'
RepositoryPrefix = 'programming-2026-'
TeacherTeamSlug = 'programming-teachers'
}For an assignment-scoped set, use a unique base and prefix instead:
@{
Organization = 'school-org'
BaseRepository = 'school-org/arrays-assignment-base'
RepositoryPrefix = 'arrays-assignment-'
TeacherTeamSlug = 'programming-teachers'
}Edit students.csv. Keep the columns in exactly this order and save the file
as UTF-8:
StudentName,GitHubUsername,RepositorySuffix
"Jan Novak",xXdragon42Xx,novak-jan
"Petra Svobodova",coder-girl-987,svobodova-petraThe fields have separate purposes:
StudentNameis the readable name in teacher reports. It may contain spaces and non-ASCII characters.GitHubUsernameis the student's personal GitHub username. It is used to verify the account and grantWriteaccess.RepositorySuffixis the stable, teacher-selected part of the repository name.
The final repository name is RepositoryPrefix plus RepositorySuffix. For
example, the assignment prefix above produces
school-org/arrays-assignment-novak-jan. The suffix is used exactly as entered;
it is not generated from the student name. Use only lowercase letters, digits,
., _, and -. GitHub usernames and repository suffixes must each be
unique within a configuration.
First preview the operation:
.\scripts\Setup-Classroom.ps1 -WhatIfThe dry run creates and removes a temporary local bare clone to validate the
base history. -WhatIf prevents GitHub repository, permission, and settings
changes; it does not skip this temporary read-only workspace.
Then provision the repositories:
.\scripts\Setup-Classroom.ps1For every CSV row, the script:
- secures the base repository and grants the teacher team
Admin; - verifies that the GitHub account exists;
- creates a private organization-owned repository when it is missing;
- identifies it as a GitHub Course Sync repository for the assigned student and links its Website field to the English student workflow;
- copies the base repository's
mainhistory into the new repository; - grants the teacher team
Adminand the individual studentWrite; - verifies that forking is disabled by either the organization or repository
policy, and protects
mainfrom force-pushes and deletion.
Re-running setup is safe: existing repositories are validated instead of recreated, and their managed description and student-workflow link are updated. A student who is not already an organization member may need to accept an invitation before cloning the repository.
To add selected students later, add their CSV rows and run:
.\scripts\Setup-Classroom.ps1 -GitHubUsername new-studentCommit and push the complete student-safe state to the base repository's
main, then preview and run synchronization:
.\scripts\Sync-Classroom.ps1 -WhatIf
.\scripts\Sync-Classroom.ps1There is no assignment path, assignment ID, release title, or state file. The
unit of distribution is always the configured base repository's complete
main branch, and the base commit SHA is the release identity. A long-lived
base may therefore contain many assignments, while an assignment-scoped base
may contain only one.
The final summary distinguishes synchronized, already current, needs
attention, and failed repositories. A merge conflict is displayed in yellow
as CONFLICT with the pull-request URL. It is a manual action, not a
PowerShell failure. Repository, permission, and history problems are displayed
in red. Use -Verbose for detailed GitHub CLI diagnostics.
A conflict affects only one student's repository. The student or teacher can resolve a simple conflict on GitHub and merge the pull request. Complex conflicts should be resolved locally with teacher guidance. A resolved synchronization branch is accepted as long as it still contains the expected base commit.
Give students the appropriate guide:
Setup places the public English guide in each student repository's Website field. Teachers can additionally share the Czech guide directly when needed.
The guides intentionally describe Git operations rather than product-specific buttons. A teacher may demonstrate those operations in the Git client used at their school.
Their normal cycle is:
Fetch/Pull -> work -> Commit -> Push
Students do not create a fork or configure repository permissions. Each organization-owned repository is their submission history for the course, unit, or assignment represented by that repository.
- Wrong username: correct
students.csvand rerun setup. Remove an accidentally invited account only after verifying the exact repository. - Wrong suffix: correct it before repository creation. After creation,
deliberately rename the repository in GitHub and update the student's
originURL. - Pending invitation: ask the student to accept it, then rerun setup.
- Unrelated Git history: inspect the repository manually. The scripts stop instead of force-pushing over it.
- Synchronization conflict: resolve and merge the printed pull request before releasing a newer base commit.
- Student leaves: remove the student's access but retain the repository according to school policy.
- Teacher unavailable: another organization owner can restore teacher-team access.
Before creating a real course, unit, or assignment repository set, choose a unique base and prefix, review the organization policy, and run the acceptance scenario. Retain or archive completed student repositories according to school policy.
This tool manages private repositories with elevated organization permissions.
Read SECURITY.md before using it. Never publish a real
classroom.psd1, students.csv, diagnostic log containing student data, or
GitHub authentication output.
The offline checks require no external PowerShell modules and never call GitHub:
.\tests\Test-Common.ps1Run them in both Windows PowerShell 5.1 and PowerShell 7 before releasing. Live GitHub behavior is covered by the separate manual acceptance scenario.
Contributions are welcome; see CONTRIBUTING.md. Changes are recorded in CHANGELOG.md.
GitHub Course Sync is available under the MIT License.