Git remotes and collaboration¶
Exercise git-remotes-and-collaboration-annex.pdf · PDF
Your repository has exactly one copy. It lives inside a virtual disk, inside one PC of this classroom. Remember the 3-2-1 backup rule from Unit 4: how many copies is that? One. If that disk dies, every commit dies with it. And nobody else can read your work or help with it.
Real teams have the opposite problem: twelve people at Tramuntana Apps writing the same documentation at the same time. How do they share one history without overwriting each other?
What you will learn
- What a remote is and what a forge adds to Git
- Publish a repository with
git remote addandgit push, and get one withgit clone - Bring other people's commits with
git pull, and read a rejected push calmly - Work on a branch and propose it with a pull request that someone else reviews
- Resolve a merge conflict by hand: read the markers, decide, commit
- Why "push uploads my files" is a trap, and why server documentation lives in a private repository
A second home for the album¶
On the previous page your repository was a photo album in one laptop. Today the album gets a copy in a house across the street — and a second person who can add photos to it.
A remote is another copy of your repository, somewhere else, that yours knows by a short name. The usual name is
origin.A forge is a website that hosts repositories and adds what Git does not have: accounts, permissions, a page to read the files, and pull requests. GitHub, GitLab and Forgejo are forges.
Four new verbs move photos between the two albums:
| Command | What it does |
|---|---|
git remote add origin ADDRESS |
Tell your repository where the other album is, and call it origin |
git push |
Send my new commits to the remote |
git clone ADDRESS |
Make a complete local copy of a remote repository — all the history, not only the last version |
git pull |
Bring the new commits from the remote and join them to mine |
flowchart LR
subgraph OWNER ["Owner's laptop"]
A1["<b>Local repository</b><br>the owner's album"]
end
subgraph FORGE ["The forge"]
R["<b>origin</b><br>the shared album"]
end
subgraph GUEST ["Guest's laptop"]
A2["<b>Local repository</b><br>a full clone"]
end
A1 -->|"git push"| R
R -->|"git pull"| A1
R -->|"git clone, git pull"| A2
A2 -->|"git push"| R
Git is distributed: every clone is a complete album, with all the history. The forge is not "the real repository" and yours "a copy" — origin is simply the one you agreed to share. If the forge disappeared tomorrow, each of you would still have everything.
Careful: push sends commits, not files
git push uploads commits. A change you saved but did not commit stays in your room. If the forge does not show your text, the first question is always: did you commit?
A story: GitHub and the forges¶
Git was three years old when GitHub opened in April 2008: the same Git, with a website around it. It became the best-known home of open source projects, and Microsoft bought it in 2018 for 7.5 billion dollars. Not everyone wanted one company to hold the world's code. Forgejo, started in December 2022, is a forge that is itself free software: anyone can install it on their own server — a school could.
Server documentation is private
Your repository describes a server: names, addresses, open ports. From Unit 6 you know that is a gift for an attacker. Create it private: only you, your partner and the teacher get in.
Publishing your repository¶
The forge recognises you by your SSH key, the key pair you created in Unit 6. It needs the public half, the file ending in .pub:
Copy the whole line and paste it in the forge (Settings → SSH keys). If the file does not exist, create a pair with ssh-keygen -t ed25519. Test the connection, writing your forge's address instead of FORGE:
The forge must greet you by your user name. Then create a new repository on the forge: name tramuntana-docs, private, and empty — no README, no licence, no ignore file. Your history already exists on the laptop, and the forge must not start a different one.
Connect and publish (USER is your user name on the forge):
cd ~/tramuntana-docs
git remote add origin git@FORGE:USER/tramuntana-docs.git
git remote -v
git push -u origin main
git status
The very first push needs -u origin main. Without it, Git complains:
$ git push
fatal: The current branch main has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin main
$ git push -u origin main
To /srv/git/tramuntana-docs.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
-u tells Git once that your local main and the remote main belong together (the remote one is the upstream). After that, plain git push and git pull are enough, and git status says up to date with 'origin/main'. The line To ... shows your forge's address.
| Error | What it means | Fix |
|---|---|---|
Permission denied (publickey) |
The forge does not know your key | Paste the .pub file (never the private one) and test with ssh -T |
! [rejected] ... (fetch first) on the first push |
The forge repository was not created empty | Delete it on the forge and create it again, empty |
remote origin already exists |
A typo in a first attempt | git remote set-url origin NEW-ADDRESS |
src refspec main does not match any |
Your branch is called master |
git branch -m main |
A guest in your repository¶
Now work in pairs: an owner and a guest. The owner adds the partner and the teacher as collaborators with write permission on the forge. The guest clones into a separate folder, so it does not collide with their own ~/tramuntana-docs:
mkdir ~/guest && cd ~/guest
git clone git@FORGE:OWNER/tramuntana-docs.git
cd tramuntana-docs
git log --oneline
The guest sees the owner's whole history. The guest adds a line to README.md, then git add, git commit and git push. On the owner's side:
$ git pull
Updating d73894c..0953672
Fast-forward
README.md | 2 ++
1 file changed, 2 insertions(+)
$ git log --oneline
0953672 List the maintainers in the README
d73894c Add overview page and ignore build output
Fast-forward means the owner had nothing new, so Git simply moved forward to the guest's commit. One history, two authors.
Branches and pull requests¶
Until now every commit went straight to main. Fine when you are alone. Now imagine half a page about the network, not finished, pushed to main at 13:55 — and your partner builds the PDF from it at 14:00.
A branch is a separate line of commits that starts from
mainand grows apart from it.
In the album: a draft page you work on at the side, and glue in only when it is good. main stays presentable.
-c creates the branch and moves you onto it. git switch main takes you back, and the files in your folder change to match the branch you are on: on main, ls docs does not show the new page. A branch copies nothing; it is just a name for a line of commits.
Push the branch the first time with -u, exactly as you did with main:
$ git push -u origin add-network-page
* [new branch] add-network-page -> add-network-page
branch 'add-network-page' set up to track 'origin/add-network-page'.
A pull request is a page on the forge that says "please pull my branch into
main", shows exactly what would change, and lets other people comment before it is accepted.
Merging is joining the branch into main. On GitLab the same thing is called a merge request. A pull request is not a Git command: it is a feature of the forge, a branch plus a conversation.
flowchart LR
M1["<b>main</b><br>presentable"] --> B["<b>add-network-page</b><br>git switch -c, commit, push"]
B --> PR["<b>Pull request</b><br>on the forge:<br>diff + comments"]
PR -->|"reviewer approves<br>and merges"| M2["<b>main</b><br>now with the page"]
M2 -->|"git switch main<br>git pull"| L["<b>Owner's laptop</b><br>up to date"]
In a company, nothing reaches main without a second pair of eyes. A useful review comment names a line and a reason: "Line 5: the address is 10.3.0.10 in your overview page and 10.3.0.1 here — which one is right?" Not "ok", and not "bad".
After the merge, the owner updates the local main and deletes the finished branch:
Git answers Deleted branch add-network-page. If it refuses, you have not pulled the merge yet.
A conflict on purpose¶
Owner and guest both change the same line of README.md, each in their own clone, and both commit. The owner pushes first. Then the guest pushes:
$ git push
! [rejected] main -> main (fetch first)
error: failed to push some refs to '/srv/git/tramuntana-docs.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. [...] use 'git pull' before pushing again.
Nothing is broken. Git refuses to throw away the owner's commit. Before pulling, tell Git once how to join histories (by merging), or it will ask:
$ git pull
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
A merge conflict happens when two commits change the same lines and Git cannot decide which version wins.
The file now contains both versions between three conflict markers:
<<<<<<< HEAD
Technical documentation of the Tramuntana Apps office server (Palma).
=======
Documentation of the office server of Tramuntana Apps, Palma (Mallorca).
>>>>>>> 421268b3d41ff7dabd5657ee7f12e557afafccbb
| Part | What it is |
|---|---|
Between <<<<<<< HEAD and ======= |
Your version (HEAD, the commit you are on) |
Between ======= and >>>>>>> 421268b... |
Their version, the commit that came from the remote |
To resolve it: talk, decide the final sentence, edit the file until it says it, delete the three marker lines, then:
grep -n "<<<<" README.md
git add README.md
git commit -m "Merge main: agree on the first line"
git push
git log --oneline --graph
The grep must print nothing — otherwise a marker is still inside. The graph shows two lines of work that split and joined again:
* 1ab4002 Merge main: keep both changes in the first line
|\
| * 421268b Say where Palma is
* | 115f717 Reword the first line
|/
* 0953672 List the maintainers in the README
Careful: a conflict is not a mistake
A conflict means two people changed the same line. It is normal work. The real mistake would be Git choosing one of you in silence. git pull never overwrites committed work: it either joins the histories or stops and asks a human. If you panic, git merge --abort puts everything back as it was before the pull.
Key takeaways¶
- A remote is another copy of the repository, usually called
origin; a forge (GitHub, GitLab, Forgejo) hosts it and adds accounts, permissions and pull requests. git clonecopies the whole history;git pushsends commits (not files);git pullbrings and joins the others' commits.- Git is distributed: every clone is complete. Server documentation goes in a private repository.
- A branch is a separate line of commits; a pull request proposes merging it into
mainand lets a reviewer comment on specific lines. - A rejected push ("fetch first") means: pull first. A merge conflict shows both versions between markers; a human decides, deletes the markers, adds and commits.
Quick check¶
1. In a Git repository, what is origin?
- The usual short name of the remote repository
- The very first commit of the history
- The main branch of the project
- The user who created the repository
A remote is another copy of the repository that yours knows by a short name, and origin is the usual one. The main branch is called main.
2. Your git push is rejected with fetch first. What do you do?
- Run git pull, then push again
- Delete the repository on the forge
- Run git init again in the folder
- Wait a minute and push again
The remote contains commits you do not have, and Git refuses to throw them away. git pull brings and joins them (possibly with a conflict to resolve), and then the push works.
3. What is a pull request?
- A proposal on the forge to merge a branch, with review
- A Git command that downloads new commits
- Another name for the git pull command
- A complete copy of a remote repository
A pull request is a feature of the forge, not a Git command: a branch plus a conversation. It shows what would change and lets others comment before the branch is merged into main.
4. In a conflict, which lines sit between the <<<<<<< HEAD marker and the ======= marker?
- Your own version
- The other person's version
- The result Git merged for you
- An error message from Git
HEAD is the commit you are standing on, so the first part is your version and the part after ======= is theirs. Git merges nothing in a conflict: a human decides and deletes the markers.
5. You edited a file, saved it and ran git push. The forge shows no change. Why?
- You did not commit the change
- The forge takes hours to update
- Your SSH key has expired
- Private repositories hide new changes
git push sends commits, not files. A saved change that was never committed stays in your working directory, so there was nothing new to push.
6. The forge where your class repository lives closes down tomorrow. What happens to your history?
- Every clone still has the complete history
- It is lost, because the forge held the real copy
- Only the last version of each file survives
- It survives only if the repository was public
Git is distributed: git clone copies the whole history, not only the last version. You can push it to a new forge and carry on.
7. You and a classmate document the Tramuntana server in one repository. Select the two practices that are right.
- Write an unfinished page on its own branch, not on main
- Create the repository as private
- Commit straight to main and fix any problems later
- Merge your own pull request before anyone reads it
A branch keeps unfinished work away from main, and a private repository keeps server names, addresses and ports away from attackers. A pull request is only useful if a second person reviews it before the merge.
Next: today somebody met an error message they had never seen before. Where do you look, and whom do you believe? Finding technical documentation.