Search
Git Workflow for Capstone Teams: Branches, Pull Requests and Fixing Merge Conflicts
Almost every capstone team has lived this story. Three members work on the same system, files get passed around in group chats as login_final_v3_REAL.zip, someone copies an old version over a week of work, and two days before the defense nobody is sure which code actually runs. Git and GitHub fix this, but only if the whole team follows the same simple workflow.
This guide gives you a workflow that is easy enough for first-time Git users and solid enough for a real project: one protected main branch, one branch per feature, pull requests for review, and a calm way to fix merge conflicts. To show exactly what you will see on screen, we simulated a two-person team (Ana and Ben) on one computer and copied the real Git output below. In the demo, the shared "remote" is a local folder instead of GitHub, but the commands and messages are the same.
The Rules in One Minute
- Nobody commits directly to
main.mainalways contains code that runs and can be demoed to your adviser. - One branch per feature or fix, named clearly:
feature/login,feature/reports,fix/receipt-total. - Small commits with clear messages, several times a day: "Add login validation", not "update" or "asdf".
- Every branch goes into
mainthrough a pull request that at least one teammate reviews. - Pull
maininto your branch often, so conflicts stay small. - Never commit secrets or generated files. Use a
.gitignorefrom day one.
Step 1: One Person Sets Up the Repository
The team leader creates the repository on GitHub (private is fine; add your teammates as collaborators under Settings > Collaborators), clones it, adds the starting files and a .gitignore, and pushes. Our .gitignore keeps out local config, dependencies and the .env file:
config.local.php vendor/ node_modules/ .env
$ git add . $ git commit -m "Initial project structure" [main (root-commit) 6893849] Initial project structure 2 files changed, 8 insertions(+) create mode 100644 .gitignore create mode 100644 config.php $ git push -u origin main To /tmp/capstone-demo/remote.git * [new branch] main -> main branch 'main' set up to track 'origin/main'.
Everyone else clones the repository once with git clone <url>. Ben already had a clone, so he just pulled:
$ git pull From /tmp/capstone-demo/remote * [new branch] main -> origin/main
Step 2: Protect the main Branch
On GitHub, open Settings > Branches (or Rules > Rulesets on newer layouts) and add a rule for main that requires a pull request before merging, with at least one approval. This turns rule number 1 from a promise into something GitHub enforces. If an option is not available for your private repository on a free plan, keep the rule as a team agreement, and the leader should be the one who clicks Merge.
Step 3: Work on Your Own Branch
Before starting a feature, create a branch from an up-to-date main. Ana works on the login page. Notice that she also changed the database name in the shared config.php:
$ git switch -c feature/login Switched to a new branch 'feature/login' $ git add . $ git commit -m "Add login page and rename database" [feature/login 10e16af] Add login page and rename database 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 login.php $ git push -u origin feature/login To /tmp/capstone-demo/remote.git * [new branch] feature/login -> feature/login branch 'feature/login' set up to track 'origin/feature/login'.
At the same time, Ben starts the reports module on his own branch and, without knowing it, edits the same line in config.php:
$ git switch -c feature/reports Switched to a new branch 'feature/reports' $ git add . $ git commit -m "Add reports page and use school_db" [feature/reports 06b6514] Add reports page and use school_db 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 reports.php
Branches let both of them work without blocking each other. git switch -c creates a branch and moves to it. Older tutorials use git checkout -b, which does the same thing.
Step 4: Open a Pull Request and Review It
After pushing, Git prints a link to create a pull request (PR) on GitHub. A good PR for a student team has:
- A short title: "Add login page".
- A description of what changed and how to test it: "Log in with admin/admin123, wrong password shows an error".
- A screenshot if the change is visual.
The reviewer pulls the branch, runs it, reads the changes in the Files changed tab, and either approves or leaves comments. Reviewing is also the best way for every member to understand the whole system, which helps a lot when the panel asks "Who made this part?" during your defense.
Here Ana's PR was approved and merged. On GitHub you click Merge pull request. In our local demo, the same merge looks like this:
$ git switch main Switched to branch 'main' Your branch is up to date with 'origin/main'. $ git merge --no-ff feature/login -m "Merge feature/login" Merge made by the 'ort' strategy. config.php | 2 +- login.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 login.php $ git push To /tmp/capstone-demo/remote.git 6893849..3f0aa8f main -> main
Step 5: Update Your Branch and Fix the Merge Conflict
Now main has Ana's work, and Ben's branch is behind. Before opening his own PR, Ben brings main into his branch. Because both changed the same line, Git cannot decide alone and reports a conflict:
$ git fetch origin From /tmp/capstone-demo/remote * [new branch] feature/login -> origin/feature/login 6893849..3f0aa8f main -> origin/main $ git merge origin/main Auto-merging config.php CONFLICT (content): Merge conflict in config.php Automatic merge failed; fix conflicts and then commit the result. $ git status --short UU config.php A login.php $ cat config.php <?php // Database connection $host = "localhost"; <<<<<<< HEAD $db = "school_db"; ======= $db = "enrollment_db"; >>>>>>> origin/main
Don't panic: nothing is lost. UU means the file has unresolved changes from both sides. Git marks the conflict inside the file:
- Between
<<<<<<< HEADand=======is your version (Ben's branch). - Between
=======and>>>>>>> origin/mainis the version that is already inmain(Ana's).
To resolve it, decide what the line should be, talk to your teammate if you are not sure, then edit the file so that only the correct code remains and all three marker lines are gone. Ben confirmed with Ana that the database is now enrollment_db, kept that line, and finished the merge:
$ git add config.php $ git commit -m "Merge main into feature/reports, keep enrollment_db" [feature/reports be87212] Merge main into feature/reports, keep enrollment_db $ git push -u origin feature/reports To /tmp/capstone-demo/remote.git * [new branch] feature/reports -> feature/reports branch 'feature/reports' set up to track 'origin/feature/reports'. $ git log --oneline --graph --all * be87212 Merge main into feature/reports, keep enrollment_db |\ | * 3f0aa8f Merge feature/login | |\ | | * 10e16af Add login page and rename database | |/ * / 06b6514 Add reports page and use school_db |/ * 6893849 Initial project structure
The graph shows both features meeting cleanly. Ben can now open his pull request, and it will merge into main without conflicts. VS Code makes this even easier: it highlights conflicts and offers buttons such as Accept Current Change, Accept Incoming Change and Accept Both Changes. Always run the project after resolving a conflict, because code that merges without errors can still be wrong.
Daily Routine for Each Member
git switch main git pull # get the latest main git switch -c feature/my-task # or: git switch feature/my-task # ...work, test... git add . git commit -m "Describe what you did" git push -u origin feature/my-task # then open a pull request git merge origin/main # after git fetch, when main has moved
Common Mistakes and Quick Fixes
- "I committed to main by accident." If you have not pushed yet, create a branch from where you are (
git switch -c feature/x), push that, then reset your localmainto match the remote withgit switch mainandgit reset --hard origin/main. Double-check that your work is on the new branch before resetting. - "Push rejected: fetch first." Someone pushed before you. Run
git pull, fix any conflicts, then push again. Never usegit push --forceon a shared branch. - Committed a password or
.envfile. Removing the file in a new commit is not enough because it stays in history. Change the password or API key immediately, then clean up the history. - Huge "final" commits. One commit with 80 changed files is impossible to review. Commit each small working step.
- Database changes that are not in Git. Export your schema to a
database/schema.sqlfile and commit it, so teammates can rebuild the same tables.
Why Advisers and Panels Like This
A clean Git history is proof of teamwork. The commit log shows who built what and when, pull requests show that code was reviewed, and main always has a working version you can demo, even if a feature branch is broken the night before. Many advisers now ask for the repository link, and it is also a ready-made portfolio piece for your first job application.
Conclusion
You do not need advanced Git to work well as a team. Protect main, give every task its own branch, merge through pull requests, and update your branch with main often. When a conflict appears, read the markers, talk to your teammate, keep the right code and test again. Set this up on the first week of your capstone, and the last week will be about polishing your system instead of searching for the latest zip file.