With other people
Everything so far works fine alone. This page is about the part GitHub was actually built for: several people changing the same project without stepping on each other โ and what to do on the day you inevitably do.
1 ยท Issues: the conversation layer
An issue is a thread about something that ought to happen โ a bug, a task, a question, an idea. No code involved. If a pull request answers "here's my change", an issue asks "should something change, and what?"
A useful bug report has three parts, and most don't:
Title: Search returns no results when the query has an apostrophe
Steps to reproduce
1. Go to /search
2. Type: O'Brien
3. Press Enter
Expected Two matching records.
Actual "No results found." Console shows a 500.
Environment Firefox 128, macOS 15.2, staging as of 12 Mar
Things worth knowing about issues:
- Everything is cross-linkable. Type
#312anywhere on GitHub and it becomes a link to that issue or PR. Type@someoneand they get notified. - Closing is automatic. A pull request whose description says
Closes #312will close issue 312 when it merges, and the two stay linked in the history. - Labels and milestones are just tags for filtering.
bug,good first issue, andhelp wantedare near-universal conventions โ the last two are how maintainers signal work suitable for newcomers. - Search before opening. Someone has usually reported it. Adding a detail to an existing thread is more useful than starting a second one.
2 ยท Code review, from both sides
Review is where most of GitHub's value actually lives. It's also where new contributors feel most exposed, so it's worth being deliberate about.
Receiving a review
A reviewer leaves comments on specific lines and then picks one of three verdicts:
| Verdict | What it means |
|---|---|
| Approve | Good to merge. Any comments attached are suggestions, not blockers. |
| Comment | Feedback without a verdict โ questions, observations, a partial read. |
| Request changes | Something needs fixing before this merges. On most repos this actively blocks the merge button. |
To respond: make the edits locally, commit, push to the same branch. The PR updates itself. Then reply to each thread โ even a "done, good catch" โ and use Resolve conversation to collapse the ones that are settled, so the reviewer can see at a glance what's left.
Comments are about the code, not about you. This is genuinely hard to internalise at first, and worth doing early. Experienced engineers get "request changes" on their pull requests constantly; it's a normal Tuesday, not a report card.
If a comment doesn't make sense, ask. "I'm not sure I follow โ do you mean X?" is a completely standard reply, and reviewers are frequently wrong or missing context.
Giving a review
- Use the batch workflow. Click Start a review rather than Add single comment. Otherwise each comment fires its own notification and the author starts responding before you've finished reading.
- Separate blocking from optional. A convention that costs nothing and helps enormously: prefix anything non-blocking with
nit:so the author knows what actually holds up the merge. - Use suggestion blocks for small fixes. In a comment, a fenced block marked
suggestionrenders as a proposed change the author can commit with one click. - Ask instead of assert when you're unsure. "What happens here if the list is empty?" ages better than "this is broken."
- Say what's good. A review that's purely a list of faults is exhausting to receive, and reviewers who note the clever bit get better pull requests over time.
3 ยท Merge conflicts, calmly
A conflict is not an error and nothing is broken. It means two people changed the same lines of the same file, and Git โ sensibly โ refuses to guess which version is correct. It's asking you a question.
Here's the whole thing, start to finish. You're merging updated main into your branch, and you both edited the same constant.
-
Git tells you it stopped
$ git merge origin/main Auto-merging src/search.js CONFLICT (content): Merge conflict in src/search.js Automatic merge failed; fix conflicts and then commit the result.Note that it says Auto-merging first. Git already resolved everything it could on its own; what's left is the genuinely ambiguous part. A conflict in one file out of thirty is completely normal.
-
See what needs your attention
$ git status Unmerged paths: (use "git add <file>..." to mark resolution) both modified: src/search.js -
Open the file and read the markers
Git has written both versions into the file, fenced off:
const query = event.target.value; <<<<<<< HEAD pending = setTimeout(() => fetchResults(query), 300); ======= pending = setTimeout(() => fetchResults(query), 500); >>>>>>> origin/main }<<<<<<< HEADEverything below this line, up to the=======, is your version โ what's on the branch you're standing on.=======The divider. Nothing more to it.>>>>>>> origin/mainEverything above this, down from the divider, is their version โ what's coming in. -
Decide, and delete the scaffolding
You take theirs, take yours, or write a third thing that combines both. Whichever you choose, all three marker lines must be gone when you're done. The file should read as if the conflict never happened:
const query = event.target.value; pending = setTimeout(() => fetchResults(query), 300); }If you're unsure whose version is right โ and you often will be โ ask the other person. A thirty-second message beats silently discarding someone's work.
-
Mark it resolved and finish the merge
$ git add src/search.js $ git commit # Git pre-writes the message; you can just save and quit $ git pushHere
git addmeans "I've handled this one". Your pull request will refresh and the This branch has conflicts banner will clear.
The escape hatch: git merge --abort. At any point mid-conflict, this puts everything back exactly as it was before you started, with nothing lost. If you're confused or in over your head, use it, take a breath, and start again. It's a normal thing to do, not an admission of defeat.
Fewer conflicts, less pain
- Merge
maininto your branch often โ daily on anything long-lived. Ten small conflicts spread over a week are far easier than one enormous one on Friday. - Keep branches short. Nearly every horrifying conflict story starts with "this branch had been open for six weeks".
- Say what you're touching. Two people rewriting the same file at once is a coordination problem, and no tool solves it for you.
- Small conflicts can be fixed in the browser. GitHub offers a web editor on conflicted PRs; it's fine for a couple of lines and unpleasant for anything larger.
4 ยท Forks and contributing to projects you don't own
Everything so far assumed you can push to the repository. For open source โ and for tightly controlled repos at work โ you can't. You don't have write access, and you're not going to get it just to fix a typo.
A fork solves this. It's your own complete copy of the repository, under your account, that you can do whatever you like with. You push to your copy, then propose your changes back across.
# 1. Click "Fork" on the project's GitHub page, then:
$ git clone git@github.com:you/site.git
$ cd site
# 2. Point at the original too, so you can stay up to date
$ git remote add upstream https://github.com/acme/site.git
# 3. Work as normal
$ git switch -c fix/apostrophe-search
$ git commit -am "Escape apostrophes in search queries"
$ git push -u origin fix/apostrophe-search
# 4. GitHub shows a "Compare & pull request" banner. Click it.
You now have two remotes: origin (your fork, which you push to) and upstream (the original, which you pull from). Popular projects move quickly, so refresh yours before starting anything new:
$ git switch main
$ git fetch upstream
$ git merge upstream/main
$ git push
# your fork's main now matches the original's
GitHub also puts a Sync fork button on your fork's page, which does the same thing without the terminal.
5 ยท Guardrails teams put on main
On a real project you'll run into rules that stop you doing something. They're not obstacles aimed at you โ they're the reason main is trustworthy. Here's what you're likely to meet.
main; every change has to arrive through a pull request. Usually the very first rule a team turns on.CODEOWNERS/billing/ and the billing team is automatically added as a required reviewer.main a straight line.If the merge button is greyed out, GitHub will tell you exactly why โ read the box at the bottom of the pull request. It's always one of: a check failing, a review outstanding, a conflict, or a rule like the ones above.
6 ยท Being easy to work with
The social conventions matter as much as the commands, and they're rarely written down anywhere.
On any team
- Keep pull requests small. A 40-line PR gets reviewed today; a 2,000-line one gets reviewed reluctantly on Thursday.
- Don't mix a refactor into a bug fix. Two PRs, two reviews, two clean reverts if needed.
- Never force-push a branch someone else is working on or reviewing.
- Nudge politely after a day or two. Reviews genuinely do get lost in the notification pile.
Contributing to open source
- Read
CONTRIBUTING.mdfirst. It exists precisely so maintainers don't have to repeat themselves. - Open an issue before a large PR. "Would you accept a change that does X?" saves everyone a wasted weekend.
- Start with
good first issuelabels โ they're curated for exactly this. - Maintainers are usually volunteers. Slow replies aren't rudeness, and a rejected PR isn't a judgement of you.
One habit outranks all of the above: explain why. Reviewers can see what your code does โ they're reading it. What they can't see is the reasoning, the option you rejected, or the constraint you were working around. Put that in the pull request description and reviews get dramatically shorter.