Propose, Review and Merge Changes With Pull Requests | Heidelberg AI Curriculum
T19-L03
Propose, Review and Merge Changes With Pull Requests
Protect main so nothing reaches it unread, record a problem as an issue, propose the fix as a pull request, review it line by line, and merge it so the issue closes by itself.
The qPCR protocol from T19-L02 · Work on a Shared Gitea Repository From Your Computer With OpenCode is now the one five people in the lab follow. Last week a change went straight in with a typo in a dilution, and two plates were wasted before anyone noticed. Everyone could push to main, so nobody read a change before it counted.
In this book you close main to direct pushes, so every change comes as a pull request: a proposal that a colleague reads line by line, comments on, and approves before it goes in. You record the problem as an issue and let the merge close it. You are at Level 3 Builder: colleagues depend on this repository, and a change that reaches main unread reaches all of them.
After this you can
Protect main so that nobody, including you, can push to it or merge without an approval.
Record a problem as an issue, and link the change that fixes it.
Make a change on a branch with OpenCode and open a pull request for it.
Review a pull request: comment on a line, request changes, approve.
Merge the pull request, and let the merge close the issue.
Owner or Admin rights on the repository, to change its settings.
About 105 minutes: 15 to read, 90 to do, most of it waiting for and doing the review with your colleague.
Last verified 25 September 2026. Branch protection, pull requests, reviews and issues are built into every Gitea, free, including gitea.com. Details for experts at the end of this book.
The idea in one page
main is what everyone uses. Protect it: no direct pushes, and no merge without an approval.
A branch is a side copy for one change. You work there; main stays untouched.
A pull request asks to merge a branch into main: merging copies the branch's changes into main. Its page shows the change in red and green, the discussion, the reviews and whether it may be merged.
A review ends in one of three answers:Comment, Approve or Request changes. With the rule from step 1, a request for changes blocks the merge until that reviewer approves. Official approvals are those from people with write access.
An issue records a problem.Fixes #1 in a pull request closes issue #1 when the pull request is merged.
The reviewer reads the change, not the summary. OpenCode's answer says what it meant to do; the Files Changed tab shows what it did.
Step 1: Protect main. In the repository, click Settings, then Branches, then Add New Rule. Fill in:
Protected Branch Name Pattern:main
Push:Disable Push
Required approvals:1
Tick Dismiss stale approvals (a new commit needs a fresh approval), Block merge on rejected reviews (Request changes stops the merge) and Administrators must follow branch protection rules (you, the owner, too)
Click Save Rule.
You should see:main in the list of protected branches.
Worked example: a rule for failed controls
Structure it
Step 2: Say what you want in one sentence.
When someone changes the protocol, I want a colleague to read and approve the change before it counts, instead of finding mistakes on the next plate.
Understand it
Step 3: Record the problem as an issue.(Jonas's screen.) Jonas found something the protocol doesn't cover. In the repository he clicks Issues, then New Issue, writes a title and what happened, and clicks Create Issue.
You should see: the issue with a number, here #1, and a green Open label.
Choose tools
A branch and a pull request for every change, Jonas as reviewer, and OpenCode for the change on Mia's computer.
Build it: propose the change
Step 4: Make the change on a branch. In OpenCode, in the project's session, send:
Get the latest changes from Gitea. Then create a branch ntc-rule from main and switch to it.
In protocol.md, under "## Checks", add the bullet: "- If a no-template control shows a melt peak, repeat the run with fresh primer dilutions." Change nothing else.
Commit it with the message "Add what to do when an NTC shows a peak (fixes #1)" and push the branch ntc-rule to Gitea.
If Git reports a conflict, stop: don't resolve it yourself. Show me both versions of the lines and who wrote each.
You should see: OpenCode says it pushed a new branch ntc-rule. Name branches after what they change. If OpenCode says the issue closes when you open the pull request: it closes at the merge (step 11).
Step 5: Open the pull request. On the repository page Gitea shows a green bar for a while after a push. Click New Pull Request in it (or, later, Pull Requests → New Pull Request and choose the branch).
Check the two boxes at the top read merge into: …:main and pull from: …:ntc-rule. Give it a title and a description that starts with Fixes #1. and says what the reviewer should check. On the right, click Reviewers and choose Jonas. Click Create Pull Request.
You should see: the pull request with its own number (here #2; issues and pull requests share the numbers), and a red merge box: 0 of 1 official approvals granted. Don't click the red Create merge commit (When checks succeed) button: it would schedule the merge for later. Wait for the review. There is no key available to sign this commit is harmless.
Build it: review
Step 6: Comment on a line.(Jonas's screen.) Gitea tells Jonas about the review request with the bell at the top right (and by email, if set up). He opens the pull request and clicks Files Changed. He moves the mouse over the green line, clicks the blue + at its start, writes his comment, and clicks Start review: the comment waits until he sends the whole review, instead of going out alone.
Step 7: Request changes.(Jonas's screen.) Jonas clicks Review at the top right, writes one sentence for the whole review, and clicks Request changes.
You should see (as Mia, on the pull request):jonas requested changes, his comment on the line, and a merge box with two red lines.
Step 8: Answer the review on the same branch. Mia agrees with Jonas and sends in OpenCode:
Stay on the branch ntc-rule. In protocol.md, change the NTC line to: "- If a no-template control has a Cq below 35 or a melt peak above 70 °C, repeat the run with fresh primer dilutions." Change nothing else.
Commit it with the message "Give the NTC rule a threshold: Cq below 35 or a peak above 70 °C" and push the branch.
If Git reports a conflict, stop: don't resolve it yourself. Show me both versions of the lines and who wrote each.
You should see: the push went to ntc-rule. You don't open a new pull request: the new commit appears in pull request #2 by itself.
Step 9: Approve.(Jonas's screen.) Jonas opens Files Changed again. His comment is now marked Outdated, because the line changed.
He checks the new line, clicks Review, writes a short note and clicks Approve.
You should see:jonas approved these changes and a blue merge button with This pull request can be merged automatically: Gitea's words for no conflicts, ready to merge. Nothing merges until someone clicks.
Build it: merge
Step 10: Merge and delete the branch. Mia clicks Create merge commit. Gitea fills in a message that names the pull request and Reviewed-by: jonas. She ticks Delete Branch "ntc-rule" and clicks Create merge commit again.
You should see: a purple Merged label, and mia deleted branch ntc-rule.
Step 11: Check the issue. Open Issues, then the closed issue #1.
You should see:Closed, and the pull request that closed it. Anyone who reads the issue later finds the fix and its review.
Step 12: Update main on your computer. In OpenCode, send:
The pull request is merged on Gitea. Switch to main, get the latest changes from Gitea, and delete my local branch ntc-rule. Then show me the NTC line in protocol.md and the last two commits.
You should see: the final NTC line and the merge on top. OpenCode may add that the branch "is still on Gitea" and offer to delete it: not needed. It's looking at an old local note; Gitea deleted the branch in step 10, as the Commits page shows: 1 Branch.
Company: the onboarding checklist
Aylin (IT) and Tom (HR) protect main of onboarding the same way.
Step 3: a new colleague reports an issue: My mail account didn't exist on day 1.
Steps 4 and 5: Aylin opens a pull request Create mail accounts five days ahead (fixes #1) and asks Tom to review.
Steps 6 to 9: Tom asks for the name of the ticket queue; Aylin adds it; Tom approves.
Steps 10 and 11: the merge closes the issue.
What goes wrong
"Not allowed to push to protected branch main."Symptom: a push from OpenCode ends with this line and pre-receive hook declined. Fix: that's step 1 doing its job. Ask OpenCode: "Move my last commit from main to a new branch <name>, make my main match Gitea again, and push the branch." Then open a pull request.
The merge button stays red.Symptom:0 of 1 official approvals granted, or changes requested. Fix: the reviewer hasn't approved yet, or asked for changes. Answer the comments on the same branch (step 8), then click the round-arrow icon next to the reviewer's name under Reviewers to ask again.
The approval disappeared.Symptom: Jonas had approved, then you pushed a small fix, and the merge box is red again. Fix: expected: Dismiss stale approvals removes an approval when the change changes. Ask for a quick second look.
"This branch is out-of-date" or conflicts.Symptom: main changed while the pull request was open. Fix: if the merge box offers Update branch, click it. If it reports conflicts, in OpenCode: "On the branch <name>, merge the latest main from Gitea and push the branch. If Git reports a conflict, stop and show me both versions." Decide with the colleague who changed main (T19-L02, step 16).
The review only read the description.Symptom: an approval within a minute of a long change. Fix: a reviewer reads Files Changed, line by line. If a pull request is too long to read in ten minutes, split it into smaller ones.
Wrong direction.Symptom: the pull request wants to merge main into your branch. Fix: the top boxes must read merge into: main and pull from: your branch. Close it and open a new one the right way round.
Do it yourself (90 minutes)
Set up (10 min): step 1 on your repository. Then ask OpenCode to push a one-word change straight to main. It must be refused; undo it with the first fix in What goes wrong.
Structure it (5 min): your sentence: "When someone changes ___, I want ___ to read it before it counts, instead of ___."
Understand it (10 min): your colleague records a real open question as an issue (step 3).
Choose tools (5 min): a branch name that says what the change is; your colleague as reviewer.
Build it (60 min): steps 4 and 5 for the fix, then a real review with at least one line comment and one round of changes (steps 6 to 9), then merge (steps 10 to 12).
Exit check
A protected main, an issue, and a merged pull request that fixes it. It passes when the pull request shows at least one line comment, one Request changes, a new commit answering it and an Approve; the issue was closed by the merge; and a direct push to main from your computer is refused.
Rule to remember
Nothing reaches main until a second person has read the change itself.
Details for experts
Checked on 25 September 2026. You don't need this to follow the book; it's for setting things up or answering a colleague.
What
Details
What it means for you
Versions
Gitea 1.27.2, OpenCode Desktop 2.0.16 with the free Space Bunny model.
Settings may move slightly between Gitea versions; the names stay.
Protection rule
Also: protected file patterns, a push allowlist, signed commits, Block merge if pull request is outdated, required status checks (automatic tests), and Restrict approvals to named reviewers.
Status checks connect to automatic tests: Level 4. Restrict approvals when only some people may approve, for example a lab manager for SOPs.
Merge styles
The arrow next to Create merge commit offers Rebase then fast-forward, Rebase then create merge commit, Create squash commit and Fast-forward only.
Squash turns the branch's commits into one: a tidy history, but the separate steps are gone from main (the review stays on the pull request). A merge commit keeps them.
Closing keywords
Fixes, Closes, Resolves (and a few more) followed by #number, in the pull request description or a commit message.
The issue closes when the pull request is merged into the default branch.
Scheduled merge
While the rules aren't met, the button reads Create merge commit (When checks succeed) and schedules an automatic merge for later.
It's meant for automatic tests (Level 4). After a review, use the normal blue button.
WIP
A title starting with WIP: marks a pull request as work in progress and blocks its merge.
Open a draft early, ask for comments, remove WIP: when it's ready.
GitHub
Settings → Rules → Rulesets (or Branches → branch protection): Require a pull request before merging, Required approvals, Dismiss stale pull request approvals, Do not allow bypassing the above settings. Reviews: Files changed → Review changes (Submit review in the newer view) → Comment, Approve, Request changes.
Same ideas and keywords; GitHub offers draft pull requests in place of WIP:. For private repositories, GitHub's protection rules need a paid plan.
Gitea: Taught. Branch protection, issues, pull requests and reviews.
OpenCode: Taught. Branches, pushes and answering a review.
GitHub: Catalogued. The same workflow on the best-known hosted service.
Watch
15 minHow to Review a Pull Request Like a Senior Developer
How to Review a Pull Request Like a Senior DeveloperMatt Stauffer · 15 min
Focus on 00:38–10:58 for review habits that transfer to T19-L03. Read the diff and test the change yourself. Follow the book's approval-before-merge rule rather than the speaker's later merge-then-fix option.
Git & GitHub Crash Course for Beginners [2026]freeCodeCamp.org
T19-L02: Learn the Git concepts behind local and remote copies, commits, merging and conflicts. The course uses GitHub and command-line Git rather than Gitea/OpenCode. Retain the book's merge-not-rebase rule, secret handling and requirement that a person resolves conflicts.
T19-L03: Use the branching, merging and pull-request sections to understand a proposed change's path into shared history. The examples use GitHub, not Gitea/OpenCode; follow the book for required approvals, branch protection and issue closure.