Skip to content

Synchronize deliberately

  • Lesson 5
  • 40 min
  • Module 2: Work with branches and remotes
Lesson 5 of 15 · Git and Team Development EssentialsSynchronize deliberately

What you will be able to do

Share your commits with a team, and get their commits, without surprises. After this lesson, you can say exactly what each command changed: your repository, or the remote.

Before you startWhat to know and to have readyAbout 40 minutes

What you need to know

What you need on your computer

  • The repository ~/projects/ticket-notes with the branches main and add-priorities. You made add-priorities in Branch basics: Guided practice: work on a branch. If it is missing, do that guided practice again.

  • In the repository, git status shows working tree clean.

  • For the “Your turn” task, the branch add-contacts. You made it in Branch basics: Your turn: a second branch. If you do not have it, the task tells you how to make it.

  • If the repository is missing or broken, a script can make the start state of this lesson again. In ~/projects, run:

    Terminal window
    git clone https://github.com/Nextia-Intelligence/nextia-learning-labs.git
    sh nextia-learning-labs/C14/setup.sh m02-l02 --reset

    Run the git clone command only once. If you already have the nextia-learning-labs folder, run git -C nextia-learning-labs pull instead. Pull-request sequence: Set up the practice lab explains the script in full. The script makes ~/projects/c14-lab/ticket-notes with main at d8ae027 and add-priorities at 999a7c4, the same hashes as in this lesson. It has no remote and no add-contacts. The commits that you make in this lesson, as Amira or as Sam, have different hashes from the lesson, for example 1d45995. This is expected. Then use ~/projects/c14-lab in place of ~/projects in this lesson. --reset deletes an older c14-lab folder first, but your ~/projects/ticket-notes does not change.

  • You do not need an account on a website. In this lesson, the remote is a folder on your computer. You make it in Make a practice remote.

In Set up Git, you learned that a remote is a copy of the repository in another place. Your team shares work through it. Git does not connect to the remote all the time. It connects only when you run one of three commands:

Command What it changes
git fetch Gets new commits from the remote. Updates only your record of the remote. Your branches and files do not change.
git pull Does a git fetch, then updates your current branch with the new commits.
git push Sends your new commits to the remote, and moves the branch on the remote.

Your “record of the remote” is a set of remote-tracking branches, with names such as origin/main. origin/main shows where main was on the remote the last time that you connected. It does not update itself. If a teammate pushes, origin/main stays old until you run git fetch.

Two areas. On the right, the remote called origin contains main, the team's shared work. On the left, your computer contains main, your work, and origin/main, what you last saw on the remote. git fetch goes from the remote main to origin/main. git pull also goes from origin/main to your main. git push goes from your main to the remote main.
git fetch updates only origin/main. git pull also updates your main. git push updates the remote.

Your main is linked to origin/main. This link makes origin/main the upstream of main, and main is a tracking branch. Because of the link, git status, git pull and git push know which remote branch to compare and use.

A team usually keeps its remote on a server, such as GitHub. A server keeps the repository as a bare repository: a repository with the history only, and no working tree. Nobody edits files in it. People only push to it and fetch from it.

You can make a bare repository in a folder, and use it as a remote. Git treats it the same as a server.

  1. Make the bare repository next to ticket-notes:

    Terminal window
    cd ~/projects
    git init --bare team-remote.git
    Initialized empty Git repository in /Users/amira/projects/team-remote.git/

    The name ends in .git by convention, to show that the folder is a bare repository.

  2. Go into ticket-notes, and connect it to the new remote with the name origin:

    Terminal window
    cd ticket-notes
    git remote add origin ../team-remote.git
    git remote -v
    origin ../team-remote.git (fetch)
    origin ../team-remote.git (push)

    .. means the folder above, so the path points to ~/projects/team-remote.git.

  3. Set Git to update with git pull only when this is safe. You learn why in the worked example.

    Terminal window
    git config --global pull.ff only

Amira sends main to the remote. The -u option sets origin/main as the upstream of main. You need -u only the first time that you push a branch.

Terminal (Amira)
git push -u origin main
Output
To ../team-remote.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.

She also pushes her feature branch, so that her team can see it:

Terminal (Amira)
git push -u origin add-priorities
git branch -vv
Output
To ../team-remote.git
* [new branch] add-priorities -> add-priorities
branch 'add-priorities' set up to track 'origin/add-priorities'.
add-priorities 999a7c4 [origin/add-priorities] Add priority levels
* main d8ae027 [origin/main] Add first ticket note

git branch -vv shows each branch, its commit, and its upstream in square brackets.

Sam is Amira’s teammate. He clones the remote, adds a ticket to tickets.txt, commits, and pushes. After you clone, the upstream is already set, so Sam does not need -u:

Terminal (Sam)
git clone team-remote.git sam-notes
cd sam-notes
git commit -am "Add ticket 102"
git push
Output (end)
To /Users/sam/projects/team-remote.git
d8ae027..1d45995 main -> main

git commit -am stages every tracked file that changed, and commits in one step. d8ae027..1d45995 means that main on the remote moved from d8ae027 to 1d45995.

Amira does not know about Sam’s commit yet. git status still says that she is up to date, because it compares with her old origin/main. She fetches:

Terminal (Amira)
git fetch
git status
Output
From ../team-remote
d8ae027..1d45995 main -> origin/main
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)

The fetch moved origin/main only. Her main and her files did not change. Before she takes the new commit, she checks what it changes. The diff compares her main with origin/main:

Terminal (Amira)
git diff main origin/main
Output
diff --git a/tickets.txt b/tickets.txt
index c1b66eb..e2b0391 100644
--- a/tickets.txt
+++ b/tickets.txt
@@ -1 +1,2 @@
Ticket 101: printer offline. Restarted the spooler.
+Ticket 102: password reset email not received.

The change is correct, so she pulls:

Terminal (Amira)
git pull
Output
Updating d8ae027..1d45995
Fast-forward
tickets.txt | 1 +
1 file changed, 1 insertion(+)

Fast-forward means that Git only moved the main label forward to Sam’s commit. This is possible because Amira had no new commits of her own on main. No new commit was made.

Now Sam pushes a second ticket. At the same time, Amira commits a new file, contacts.txt, on her main. She tries to push:

Terminal (Amira)
git push
Output
To ../team-remote.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '../team-remote.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. ...

The remote refuses the push. If it accepted it, Sam’s commit would be lost from main. Amira pulls:

Terminal (Amira)
git pull
Output
From ../team-remote
1d45995..7d54ed4 main -> origin/main
hint: Diverging branches can't be fast-forwarded, ...
fatal: Not possible to fast-forward, aborting.

The fetch part worked, but Git did not change main. The histories have diverged: each side has a commit that the other does not have. git log --oneline --graph --all shows the fork:

Output (first lines)
* 01f2b94 (HEAD -> main) Add contacts
| * 7d54ed4 (origin/main, origin/HEAD) Add ticket 103
|/
* 1d45995 Add ticket 102

This is why you set pull.ff only. Without it, git pull can join the two histories automatically, and you may not notice. With it, Git stops, and you choose what to do.

Amira decides to merge: to join the two lines of history with a new commit that has both as parents. The --no-edit option keeps the message that Git suggests:

Terminal (Amira)
git pull --no-rebase --no-edit
git log --oneline --graph -4
Output
Merge made by the 'ort' strategy.
tickets.txt | 1 +
1 file changed, 1 insertion(+)
* 7f87ca6 (HEAD -> main) Merge branch 'main' of ../team-remote
|\
| * 7d54ed4 (origin/main, origin/HEAD) Add ticket 103
* | 01f2b94 Add contacts
|/

Now her main contains both commits, so the push is safe:

Terminal (Amira)
git push
Output
To ../team-remote.git
7d54ed4..7f87ca6 main -> main

You play Amira in ticket-notes and Sam in a second clone.

  1. Make the practice remote and connect it, as in Make a practice remote.

  2. In ticket-notes, push both branches:

    Terminal window
    git push -u origin main
    git push -u origin add-priorities
  3. Run git branch -vv. Each branch shows its upstream in square brackets.

  4. Make Sam’s clone, and move into it:

    Terminal window
    cd ~/projects
    git clone team-remote.git sam-notes
    cd sam-notes
  5. Add a line to tickets.txt in sam-notes, then commit and push:

    Terminal window
    git commit -am "Add ticket 102"
    git push
  6. Go back to ticket-notes, and run git status. It still says up to date, because you did not fetch.

  7. Fetch, and check again:

    Terminal window
    cd ~/projects/ticket-notes
    git fetch
    git status

    The output says behind 'origin/main' by 1 commit.

  8. Run git diff main origin/main. The output shows the new line.

  9. Run git pull. The output includes Fast-forward.

  10. Run git log --oneline -1. The newest commit is the one from step 5.

The team wants to review your add-contacts branch from Branch basics. If you do not have it, make it from main now.

  1. Push add-contacts to the remote, and set its upstream.
  2. In sam-notes, get the branch with git fetch, then git switch add-contacts. Git makes a local branch that tracks origin/add-contacts.
  3. In sam-notes, add a name to contacts.txt, commit, and push.
  4. In ticket-notes, bring the new commit into your add-contacts.

Then write one sentence for each command that you used: did it change your local history, the remote, or only your record of the remote?

Show a model answer
  • git push -u origin add-contacts (in ticket-notes): changed the remote. It made add-contacts there.
  • git fetch (in sam-notes): changed only the record of the remote: it added origin/add-contacts.
  • git switch add-contacts (in sam-notes): changed local history in Sam’s clone. It made a new local branch.
  • git commit (in sam-notes): changed local history in Sam’s clone.
  • git push (in sam-notes): changed the remote.
  • git switch add-contacts, then git pull (in ticket-notes): the pull fetched, then changed local history. It moved your add-contacts forward with a fast-forward.

Common mistake

Push rejected: fetch first

Symptom: git push shows ! [rejected] main -> main (fetch first), or (non-fast-forward).

Cause: the remote branch has commits that you do not have. Git refuses the push so that those commits are not lost.

Fix:

  1. Run git pull.
  2. If the pull shows Fast-forward, run git push again.
  3. If the pull shows Not possible to fast-forward, run git log --oneline --graph --all to see both lines of work. Then run git pull --no-rebase --no-edit to merge them, and push again.

Never force the push to “fix” a rejection. A force push replaces the remote branch with yours, and your teammate’s commits disappear from it. Module 4 explains when rewriting shared history is acceptable.

Common mistake

No upstream branch

Symptom: git push shows fatal: The current branch add-contacts has no upstream branch. Or git pull shows There is no tracking information for the current branch.

Cause: this branch is not linked to a remote branch yet. Usually you made it locally and never pushed it.

Fix: push it once with -u, for example git push -u origin add-contacts. After that, git push and git pull work without more options.

Check your understanding

Predict: Sam pushes a commit. Amira runs git fetch. Do the files in her working tree change? Does her main change?

Show the answer

No to both. git fetch changes only origin/main, her record of the remote. Her files and her main stay the same until she runs git pull (or merges origin/main herself).

Check your understanding

Completion check: in the worked example, which commands changed Amira's local history, and which updated the remote?

Show the answer
  • Local history: git commit (Add contacts), the fast-forward git pull, and git pull --no-rebase --no-edit, which made a merge commit.
  • The remote: each git push. The first two made new branches on the remote. The last one moved the remote main to the merge commit.
  • Neither: git fetch updated only origin/main. The failed git pull did the same: its fetch part worked, then it stopped before it changed main. The rejected git push changed nothing.

Recap

  • origin/main is your record of the remote. Only git fetch (or git pull) updates it.
  • git fetch changes no branch of yours. git pull fetches, then updates your current branch. git push updates the remote.
  • -u links a branch to its upstream the first time that you push it.
  • A fast-forward only moves a label. When histories have diverged, you must join them, for example with a merge.
  • With pull.ff only, Git never joins diverged histories without your decision.

Next: Keep the repository clean