Pushing, Fetching, and Pulling with Git

Chapter 8: Pushing, Fetching, and Pulling with Git

Chapter Objectives

By the end of this chapter, you will be able to:

  • Fetch changes from a remote repository using git fetch and understand how it updates remote-tracking branches.
  • Clearly distinguish between git fetch and git pull.
  • Pull changes from a remote repository using git pull, understanding it as a combination of fetch and merge.
  • Push your local commits to a remote repository using git push.
  • Push new local branches to a remote and set them up for tracking.
  • Understand and set upstream branches for your local branches using git push -u or git branch --set-upstream-to.
  • Recognize the implications and dangers of force pushing (--force, --force-with-lease) and when it might be considered.
  • Understand the concept of tracking branches and their relationship with remote branches.

Introduction

In Chapter 7, you learned how to set up connections to remote repositories by cloning existing projects or adding remotes to your local projects. These connections are like pathways, but no data has been exchanged yet beyond the initial clone. Now, it’s time to learn how to use these pathways to synchronize your work. How do you get the latest changes made by your collaborators? How do you share your own brilliant contributions with them?

This chapter delves into the core Git commands that manage the flow of data between your local repository and its remote counterparts: git fetch, git pull, and git push. You’ll learn how to download changes from a remote without immediately merging them, how to integrate remote changes into your local work, and how to upload your local commits to share them. We’ll also cover the important concept of tracking branches and the cautious use of force pushing. Mastering these operations is fundamental to effective collaboration and maintaining an up-to-date project.

Theory

Understanding the Synchronization Process

Working with remote repositories involves a three-step conceptual process for staying synchronized:

  1. Fetch: Download new data and commit history from the remote repository. This updates your local copies of remote branches (remote-tracking branches) but doesn’t change your own local working branches.
  2. Merge (or Rebase): Integrate the fetched changes into your local working branches.
  3. Push: Upload your local commits (that are not yet on the remote) to the remote repository.

Git provides commands that handle these steps either separately or in combination.

Fetching Changes: git fetch

The git fetch <remote-name> command (e.g., git fetch origin) communicates with the specified remote repository and downloads all the data (commits, files, tags, etc.) that exist on the remote but are not currently in your local repository.

Crucially, git fetch:

  • Updates your remote-tracking branches: For example, if the remote origin has a main branch, git fetch origin will update your local origin/main pointer to match the current state of main on the remote.
  • Does NOT modify your local working branches: Your main branch (or any other local branch you’re working on) remains untouched. Your working directory also remains unchanged.

Fetching is a safe way to see what new work has been done on the remote without immediately integrating it into your own local branches. After fetching, you can inspect the changes on the remote-tracking branches (e.g., git log origin/main) and then decide how and when to merge them.

%%{
  init: {
    "theme": "base",
    "themeVariables": {
      "primaryColor": "#EDE9FE",      /* Default commit node color */
      "primaryTextColor": "#5B21B6",  /* Text on commits */
      "primaryBorderColor": "#5B21B6",
      "lineColor": "#5B21B6",         /* Default branch line color */
      "textColor": "#1F2937",         /* General text */
      "fontSize": "13px",
      "fontFamily": "Open Sans"
    }
  }
}%%
gitGraph BT:
    commit id: "C1" tag: "Initial commit"
    commit id: "C2" tag: "main (HEAD) - Before & After Fetch"
    
    branch "origin/main"
    checkout "origin/main"
    commit id: "C2-orig" tag: "Before Fetch: origin/main @ C2"
    commit id: "C3" tag: "After Fetch: origin/main @ C3" type: HIGHLIGHT
    
    %% Diagram Explanation:
    %% Before Fetch:
    %% - Local 'main' (HEAD) is at C2.
    %% - Local 'origin/main' (remote-tracking branch) is also at C2.
    %% - Remote 'origin' has its 'main' branch at C3 (C2 -> C3).
    %%
    %% After 'git fetch origin':
    %% - Commit C3 is downloaded to the local repository.
    %% - Local 'origin/main' is updated to point to C3.
    %% - Local 'main' (HEAD) remains untouched at C2.
    %% - The working directory is unchanged.

In this graph:

  1. Initial State:
    • We start with commit C1 as the initial commit
    • The local main branch is at commit C2
    • The local origin/main branch (remote-tracking branch) is also at C2
    • (Not directly shown, but implied) The remote repository’s main branch has advanced to C3
  2. After Running git fetch origin:
    • The local main branch remains at C2 (unchanged)
    • The local origin/main is updated to point to C3 (highlighted)
    • Commit C3 is downloaded into your local repository

Remote-Tracking Branches Revisited

As introduced in Chapter 7, remote-tracking branches are local read-only pointers to the state of branches on a remote repository. They are in the form <remote-name>/<branch-name> (e.g., origin/main, upstream/develop).

  • When you git fetch, these pointers are updated.
  • You can check out a remote-tracking branch directly (e.g., git switch origin/main), which puts you in a “detached HEAD” state, useful for inspection.
  • They serve as a reference point for seeing what’s changed on the remote (git diff main origin/main) before you decide to merge.

Pulling Changes: git pull

The git pull <remote-name> <branch-name> command (e.g., git pull origin main) is essentially a shortcut for two other commands:

  1. git fetch <remote-name> (or git fetch <remote-name> <branch-name>)
  2. git merge FETCH_HEAD (or git merge <remote-name>/<branch-name> into your current local branch, assuming it’s set up to track the remote branch).

FETCH_HEAD is a special short-lived reference that points to the tip of the branch just fetched from the remote.

So, git pull downloads the changes from the remote and immediately tries to merge them into your currently checked-out local branch.

  • If your local branch has not diverged from the remote branch, the merge will typically be a fast-forward.
  • If your local branch has diverged (i.e., you have local commits that the remote doesn’t, and the remote has commits you don’t), git pull will create a merge commit (assuming no conflicts).
  • If there are merge conflicts, the pull process will pause, and you’ll need to resolve them just like with a local merge (see Chapter 6).

git pull vs. git fetch + git merge:

While git pull is convenient, some developers prefer to use git fetch followed by a manual git merge (or git rebase, covered later). This gives them a chance to inspect the fetched changes (git log origin/main) before integrating them. For beginners, git pull is often simpler to understand initially.

%%{
  init: {
    "theme": "base",
    "themeVariables": {
      "primaryColor": "#EDE9FE",
      "primaryTextColor": "#5B21B6",
      "primaryBorderColor": "#5B21B6",
      "lineColor": "#5B21B6",
      "textColor": "#1F2937",
      "fontSize": "13px"
    }
  }
}%%
gitGraph BT:
    %% Scenario 1: Fast-Forward Pull
    commit
    commit id: "S1C1" tag: "Initial commit"
    commit id: "S1C2" tag: "Local & remote at same point"
    
    branch originmain
    commit id: "S1C3" tag: "Remote moved ahead"
    
    checkout main
    merge originmain tag: "After pull: Fast-forward"
    
    %% Scenario 2: Merge Pull
    commit id: "S2C0" tag: "New scenario start"
    commit id: "S2C1" tag: "Common base"
    
    branch feature
    commit id: "S2C3" tag: "Local changes"
    
    checkout main
    commit id: "S2C2" tag: "Remote changes"
    
    checkout feature
    merge main tag: "After pull: Merge commit"

Diagram Explanation:

Scenario 1: Fast-Forward Pull

  • Local ‘main’ is at S1_C2. Remote ‘origin/main’ has a new commit S1_C3 (S1_C2 -> S1_C3_remote).
  • ‘git pull origin main’ (while on local ‘main’):
    • Fetches S1_C3_remote, updates local ‘origin/main’ to S1_C3_remote.
    • Merges ‘origin/main’ into local ‘main’. Since local ‘main’ (S1_C2) is an ancestor, it’s a fast-forward.
    • Local ‘main’ (HEAD) moves to S1_C3_remote.

Scenario 2: Merge Pull

  • Common ancestor is S2_C1.
  • Local ‘main’ has a new commit S2_C3_local (S2_C1 -> S2_C3_local). HEAD is on ‘main’.
  • Remote ‘origin/main’ has a different new commit S2_C2_remote (S2_C1 -> S2_C2_remote).
  • ‘git pull origin main’ (while on local ‘main’):
    • Fetches S2_C2_remote, updates local ‘origin/main’ to S2_C2_remote.
    • Merges ‘origin/main’ (at S2_C2_remote) into local ‘main’ (at S2_C3_local).
    • Since histories diverged, a new merge commit ‘S2_M’ is created on local ‘main’. ‘S2_M’ has parents S2_C3_local and S2_C2_remote. HEAD moves to ‘S2_M’.

Pushing Changes: git push

Once you have made local commits and are ready to share them with others, you use the git push <remote-name> <branch-name> command (e.g., git push origin main). This command uploads your local branch’s commits (and associated Git objects) to the specified remote repository, updating the corresponding branch on the remote.

Key points about git push:

  • Non-Fast-Forward Rejection: By default, Git will reject a push if it’s not a “fast-forward” update on the remote branch. This means if someone else has pushed changes to the same remote branch since you last pulled, your push will be rejected. This prevents you from accidentally overwriting their commits. The solution is to git pull (or fetch and merge/rebase) the remote changes first, integrate them with your local work, and then try pushing again.
  • Pushing a Local Branch to a Remote Branch:The full syntax is git push :.
    • If <remote-branch-name> is omitted, Git tries to push <local-branch-name> to a remote branch with the same name.
    • If <remote-branch-name> doesn’t exist on the remote, it will be created.
  • Pushing a New Local Branch:To push a local branch that doesn’t yet exist on the remote, you can use:git push origin my-new-feature (this will create my-new-feature on origin).

Tracking Branches and Upstream Configuration

A local branch can be configured to track a remote branch. This means Git knows that your local branch has a direct relationship with a specific branch on a specific remote. This relationship is often called an upstream branch.

When a local branch is tracking a remote branch:

  • git pull (with no arguments, while on that local branch) will automatically know to fetch from the tracked remote branch and merge it.
  • git push (with no arguments, while on that local branch) will automatically know to push to the tracked remote branch.
  • git status will tell you if your local branch is ahead of, behind, or has diverged from its upstream branch.

Setting an Upstream Branch:

The most common way to set an upstream branch is when you first push a new local branch:

git push -u <remote-name> <local-branch-name>

The -u flag (short for –set-upstream) does two things:

  1. Pushes the local branch to the remote.
  2. Sets up the tracking relationship between your local branch and the newly created remote branch.

You can also set or change an upstream branch for an existing local branch using:

git branch –set-upstream-to=<remote-name>/<remote-branch-name> <local-branch-name>

Example: git branch –set-upstream-to=origin/main main

Force Pushing: git push --force and git push --force-with-lease

Sometimes, you might want to overwrite the history on a remote branch. This is generally strongly discouraged for branches that other people are using or have pulled from, as it rewrites history and can cause major problems for collaborators.

  • git push –force <remote-name> <branch-name> (or git push -f …)This command forces your local version of the branch to overwrite the remote version, even if it’s not a fast-forward. It discards any commits on the remote branch that you don’t have locally.
  • git push –force-with-lease <remote-name> <branch-name>This is a safer alternative to –force. It will only force the push if the remote branch is in the state you expect it to be (i.e., if no one else has pushed to it since you last fetched). If someone else has pushed, –force-with-lease will fail, preventing you from accidentally overwriting their work. It’s generally preferred over plain –force.

Warning: Force pushing should be used with extreme caution. It’s acceptable on your own private feature branches that no one else uses, or in specific, coordinated situations within a team (e.g., after rebasing a feature branch before it’s merged into a shared branch). Never force push to shared branches like main or develop in a collaborative project unless you are absolutely certain of the consequences and have coordinated with your team.

Command Description Key Actions & Notes
git fetch <remote> Downloads commits, files, and refs from a remote repository into your local .git directory. – Updates remote-tracking branches (e.g., origin/main).
– Does NOT modify your local working branches or working directory.
– Safe way to see remote changes before integrating.
git pull <remote> <branch> Fetches changes from the specified remote branch and immediately tries to merge them into your current local branch. – Essentially git fetch followed by git merge FETCH_HEAD.
– Can result in a fast-forward or a merge commit.
– May lead to merge conflicts if histories have diverged.
git push <remote> <branch> Uploads your local branch’s commits to the specified branch on the remote repository. – By default, rejects non-fast-forward pushes (remote has changes you don’t).
– Requires you to git pull first if rejected.
git push -u <remote> <branch>
(or –set-upstream)
Pushes the local branch to the remote and sets up tracking: your local branch will track the remote branch. – Useful for pushing a new local branch for the first time.
– Enables argument-less git pull and git push on that branch later.
git branch –set-upstream-to=<remote>/<r_branch> <l_branch> Sets an existing local branch (<l_branch>) to track a specific remote-tracking branch (<remote>/<r_branch>). Alternative to git push -u for already existing branches or to change tracking.
git push –force <remote> <branch>
(or -f)
Forces the push, overwriting the remote branch’s history with your local branch’s history. Highly dangerous on shared branches.
– Can discard other collaborators’ work.
– Use with extreme caution.
git push –force-with-lease <remote> <branch> Safer force push. Only overwrites the remote if its state matches what you last fetched. Fails if someone else pushed in the meantime. – Preferred over –force if a forced update is absolutely necessary.

Practical Examples

Setup:

For these examples, we’ll simulate a remote repository using a local bare repository and a separate “collaborator” clone.

1. Create a “central” bare repository (simulating a remote server like GitHub):

Bash
# Navigate to a directory where you keep projects (e.g., ~/Projects)
mkdir central-server-repo
cd central-server-repo
git init --bare project-alpha.git # This is our "remote"
cd .. # Go back up

A bare repository (--bare) is just the .git directory contents, with no working directory.

2. Developer 1: Clone the “remote” and make initial commits:

Bash
git clone central-server-repo/project-alpha.git dev1-project-alpha
cd dev1-project-alpha
git config user.name "Developer One" # Configure user for this repo
git config user.email "dev1@example.com"

echo "# Project Alpha" > README.md
git add README.md
git commit -m "Initial commit by Dev1"

echo "Version 1.0" >> README.md
git add README.md
git commit -m "Dev1 adds version to README"

# Push initial commits to the "remote"
git push origin main # 'origin' was set up by clone
cd ..

3. Developer 2: Clone the “remote”:

Bash
git clone central-server-repo/project-alpha.git dev2-project-alpha
cd dev2-project-alpha
git config user.name "Developer Two" # Configure user
git config user.email "dev2@example.com"
# Dev2 now has the two commits made by Dev1
cd ..

Now we have central-server-repo/project-alpha.git as our shared remote, and dev1-project-alpha and dev2-project-alpha as two local clones.

1. Fetching Changes (git fetch)

Let’s have Developer 1 make another change and push it. Then Developer 2 will fetch it.

1. Developer 1 makes and pushes a change:

Bash
cd dev1-project-alpha
echo "Developed by Awesome Inc." >> README.md
git add README.md
git commit -m "Dev1 adds company info"
git push origin main
cd ..

2. Developer 2 fetches the changes:

Bash
cd dev2-project-alpha
git fetch origin


Expected Output:

Bash
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 290 bytes | 290.00 KiB/s, done.
From ../central-server-repo/project-alpha
   <hash_old_main>..<hash_new_main>  main       -> origin/main


(Hashes will vary.)

3. Developer 2 inspects:

At this point, Dev2’s local main branch is still at the “Dev1 adds version to README” commit. The new commit (“Dev1 adds company info”) is downloaded but not yet merged.

Bash
git log main --oneline # Shows local main history
git log origin/main --oneline # Shows remote-tracking branch history
git log main..origin/main --oneline # Shows commits on origin/main but not on local main


Expected Output (for main..origin/main):

Bash
<hash_company_info_commit> (origin/main) Dev1 adds company info


This shows the commit that origin/main has, which main doesn’t. Dev2’s working directory is unchanged.

2. Pulling Changes (git pull)

Now, Developer 2 will integrate the fetched changes using git pull. (Alternatively, after fetching, Dev2 could git merge origin/main).

1. Developer 2 pulls (assuming they are on their main branch):#

Bash
# cd dev2-project-alpha (if not already there)
# git switch main (ensure on main branch)
git pull origin main # Or simply 'git pull' if main is tracking origin/main


Since Dev2’s main hasn’t diverged from origin/main‘s history (it’s a direct ancestor), this will be a fast-forward merge.

Expected Output:

Bash
From ../central-server-repo/project-alpha
 * branch            main       -> FETCH_HEAD
Updating <hash_old_main>..<hash_new_main>
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)

2. Developer 2 checks log and README.md:

git log –oneline will now show the “Dev1 adds company info” commit on the local main branch. README.md will contain the new line.

3. Pushing Local Changes (git push)

Developer 2 makes a local change and pushes it.

1. Developer 2 makes a local commit:

Bash
# cd dev2-project-alpha
echo "Copyright 2025" >> README.md
git add README.md
git commit -m "Dev2 adds copyright"

2. Developer 2 pushes to origin:

Bash
git push origin main


Expected Output:

Bash
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to X threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 300 bytes | 300.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To ../central-server-repo/project-alpha.git
   <hash_prev_remote_main>..<hash_new_remote_main>  main -> main

4. Handling a Non-Fast-Forward Push Rejection

Now, let’s simulate a scenario where Dev1 tries to push, but Dev2 has already pushed changes.

1. Developer 1 (unaware of Dev2’s last push) makes a local commit:

Bash
cd ../dev1-project-alpha # Switch to Dev1's repo
echo "Project Alpha is super cool." >> notes.txt
git add notes.txt
git commit -m "Dev1 adds some notes"

2. Developer 1 tries to push:

Bash
git push origin main


Expected Output (Error!):

Bash
To ../central-server-repo/project-alpha.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '../central-server-repo/project-alpha.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.


The push is rejected because origin/main has Dev2’s “adds copyright” commit, which Dev1 doesn’t have locally. Dev1’s local main and origin/main have diverged.

3. Developer 1 pulls the changes (this will cause a merge):

Bash
git pull origin main


Git will fetch Dev2’s commit and then try to merge it with Dev1’s “adds some notes” commit. Since they changed different files (README.md vs notes.txt), it should merge cleanly, creating a merge commit. An editor will open for the merge commit message.

Expected Output (before editor for merge commit):

Bash
From ../central-server-repo/project-alpha
 * branch            main       -> FETCH_HEAD
Merge made by the 'recursive' strategy.
 README.md | 1 +
 1 file changed, 1 insertion(+)


(Save and close the editor with the default merge message)

4. Developer 1 can now push their merged history:

Bash
git push origin main


This time it should succeed.

5. Pushing a New Local Branch and Setting Upstream (git push -u)

1. Developer 1 creates a new local feature branch and makes a commit:

Bash
# cd ../dev1-project-alpha
git switch -c feature/new-styling
echo "body { font-family: sans-serif; }" > styles.css
git add styles.css
git commit -m "Add basic styles for feature"

2. Developer 1 pushes the new branch and sets it to track:

Bash
git push -u origin feature/new-styling


Expected Output:

Bash
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to X threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 320 bytes | 320.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To ../central-server-repo/project-alpha.git
 * [new branch]      feature/new-styling -> feature/new-styling
Branch 'feature/new-styling' set up to track remote branch 'feature/new-styling' from 'origin'.


Now, feature/new-styling exists on the origin remote, and Dev1’s local feature/new-styling branch is tracking origin/feature/new-styling. Dev1 can now use git pull and git push on this branch without specifying origin feature/new-styling each time.

OS-Specific Notes

  • Authentication for Push/Fetch/Pull:
    • As covered in Chapter 7, how you authenticate with the remote server (SSH keys, PATs via HTTPS, credential managers) is the main OS-dependent aspect. Ensure this is set up correctly for smooth operations. If git push or git pull hangs or fails asking for credentials repeatedly, it’s likely an authentication setup issue.
  • Network Connectivity:
    • git fetch, pull, and push all require network access to the remote server. Firewalls, proxies, or VPN configurations can affect connectivity. If you experience timeouts or “host not found” errors:
      • Verify your internet connection.
      • Check if you can access the remote server’s domain (e.g., ping github.com).
      • Ensure Git is configured for your proxy if you use one (see Chapter 7 OS-Specific Notes).
  • Case Sensitivity of Branch Names:
    • While Git itself can handle case-sensitive branch names, file systems on different OSs behave differently (macOS/Windows often case-insensitive but preserving, Linux case-sensitive). It’s best practice to use all lowercase for branch names or be strictly consistent with casing to avoid confusion or issues if collaborators are on different OSs, especially when pushing and fetching branch names.

Common Mistakes & Troubleshooting Tips

Git Issue / Error Symptom(s) Troubleshooting / Solution
Push Rejected (Non-Fast-Forward) Error: ! [rejected] … (fetch first) or (non-fast-forward). Solution: Remote has new changes. Run git pull (or git fetch then git merge/rebase), resolve conflicts if any, then git push.
Accidental Force Push Collaborators’ history diverges, commits lost on remote. Solution: Avoid git push –force on shared branches. Use –force-with-lease if necessary. If accidental, coordinate with team for recovery (may involve git reflog, advanced).
Merge Conflicts during git pull git pull stops with CONFLICT messages. git status shows unmerged paths. Solution: Resolve conflicts (edit files, remove markers, git add), then git commit to finalize merge. Or, git merge –abort.
Committing on origin/main (Detached HEAD) Commits made in “detached HEAD” state after git switch origin/main. Commits may be lost. Solution: After git fetch, switch to local branch (git switch main) then git merge origin/main. If commits made in detached HEAD, create a branch from it: git branch temp-work HEAD.
Pushing to wrong remote/branch Code ends up in an unintended location. Solution: Be explicit: git push <remote> <branch>. Check with git remote -v and git branch -vv. If pushed to wrong remote branch, may need to delete/revert on remote.
Authentication Failure Repeated password/token prompts for HTTPS; Permission denied (publickey) for SSH. Solution: Verify credentials/tokens for HTTPS. For SSH, ensure public key is on server and SSH agent is configured. (See Ch 7).
“Everything up-to-date” on git pull/push Command runs but reports no changes, even if you expect some. Solution: For pull: local branch may already have all remote changes. For push: local branch may have no new commits compared to its upstream. Check git status and git log origin/<branch>..<local-branch>.

Exercises

Use the simulated remote setup (central-server-repo/project-alpha.git, dev1-project-alpha, dev2-project-alpha) from the Practical Examples section.

  1. Fetch, Inspect, and Merge:
    1. In dev1-project-alpha, create a new file ideas.txt with some content and commit it to main. Push this change to origin.
    2. In dev2-project-alpha, run git fetch origin.
    3. Use git log to compare dev2-project-alpha‘s local main with origin/main. What’s different?
    4. Merge the changes from origin/main into your local main branch in dev2-project-alpha. Verify ideas.txt is now present.
  2. Pushing a New Branch and Tracking:
    1. In dev1-project-alpha, create a new local branch called feature/user-profiles.
    2. Add a file profiles.txt with some initial content and commit it to this new branch.
    3. Push feature/user-profiles to origin and set it up to track the remote branch in a single command.
    4. In dev2-project-alpha, fetch from origin.
    5. Use git branch -r to see the remote-tracking branches. Do you see origin/feature/user-profiles?
    6. (Optional) In dev2-project-alpha, create a local feature/user-profiles branch that tracks origin/feature/user-profiles (e.g., git switch -c feature/user-profiles origin/feature/user-profiles).
  3. Simulate and Resolve Push Rejection:
    1. In dev1-project-alpha, on the main branch, modify README.md and commit the change. Do not push yet.
    2. In dev2-project-alpha, on the main branch, modify README.md (a different change than Dev1) and commit. Push this change from dev2-project-alpha to origin main.
    3. Now, back in dev1-project-alpha, try to git push origin main. You should get a rejection.
    4. In dev1-project-alpha, perform a git pull origin main. This should result in a merge conflict in README.md because both developers changed it.
    5. Resolve the conflict in README.md in dev1-project-alpha.
    6. Complete the merge by adding the resolved file and committing.
    7. Now, push from dev1-project-alpha again. It should succeed.

Summary

Synchronizing your local work with remote repositories is key to collaboration and backup in Git:

  • git fetch <remote>: Downloads new data and updates remote-tracking branches (e.g., origin/main) from the specified remote. It does not change your local working branches.
  • Remote-tracking branches (like origin/main) are local read-only references to the state of branches on remotes.
  • git pull <remote> <branch>: Combines git fetch (to get changes) and git merge (to integrate them into your current local branch). Can lead to fast-forwards or merge commits (and potential conflicts).
  • git push <remote> <branch>: Uploads your local branch’s commits to the specified branch on the remote repository.
    • Pushes are rejected if they are not fast-forwards on the remote, requiring you to pull first.
  • Upstream/Tracking Branches: Local branches can “track” remote branches.
    • Set up with git push -u <remote> <branch> (pushes and sets upstream) or git branch --set-upstream-to=<remote>/<branch> <local-branch>.
    • Allows for argument-less git pull and git push on that branch.
    • git status shows how your local branch relates to its upstream.
  • Force Pushing:
    • git push --force: Overwrites remote history. Highly dangerous on shared branches.
    • git push --force-with-lease: Safer alternative, checks if remote was updated by someone else.
    • Use with extreme caution, primarily on private branches if history rewriting is necessary.

These commands form the backbone of collaborative Git workflows, allowing teams to share and integrate their work effectively.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top