DevOps.Academy
CoursesCurriculumPricing

    Loading…

    ↑ ↓ move · ↵ open · Ctrl K anywhere

    ◔My learningSign in
    DevOps.Academy

    Free, hands-on DevOps lessons. Made with care in India.

    Learn

    All coursesLinux roadmapCurriculumMy learning

    Academy

    PricingCertificates

    Legal

    PrivacyTermsContact

    devops.rajeev.pro

    Git & GitHubLesson 5 of 13
    Your progress · 0%
    On this page
    1. Three copies of every branch
    2. Managing remotes
    3. Push and pull
    4. Fetch: look before you merge
    ↖ Course roadmap
    Lesson 5 · Remotes

    Remotes: push, fetch and pull without surprises

    How your repository stays in sync with GitHub: remotes and remote-tracking branches, pushing a new branch, why a push gets rejected, fetching to look before you merge, and pulling with rebase.

    30 min read · beginner · hands-on

    By the end of this lesson you'll be able to

    0 of 6 complete

    Three copies of every branch

    A remote is another copy of the repository, usually on GitHub, with a short name. The one you cloned from is called origin. Once you have a remote, each branch exists in three places, and knowing which one you're looking at removes most of the confusion around push and pull:

    fetch updates your bookmark. pull also changes your branch. push changes GitHub.© Diagram: DevOps Academy

    fetch updates your bookmark. pull also changes your branch. push changes GitHub.© Diagram: DevOps Academy
    NameLivesUpdated by
    main on GitHubthe serveranyone's git push
    origin/mainyour .git, read-onlygit fetch, git pull, git push
    mainyour .git, where you commityou

    origin/main is a snapshot from the last time you talked to GitHub. It doesn't update on its own, so always fetch before deciding you're up to date.

    Managing remotes

    $ git remote -v                                     # names and URLs
    origin	https://github.com/rajeev/shop.git (fetch)
    origin	https://github.com/rajeev/shop.git (push)
    $ git remote add upstream https://github.com/owner/shop.git   # a second remote, e.g. for a fork
    $ git remote set-url origin git@github.com:rajeev/shop.git    # switch from HTTPS to SSH
    $ git remote rename upstream source
    $ git remote remove source
    $ git remote show origin                            # branches it has, and what tracks what
    
    git remote -v
    • gitversion control

    By convention origin is where you push (your repository or your fork) and upstream is the original project you forked from.

    Push and pull

    Pushing a new branch

    $ git switch -c feature/x
    $ git push -u origin feature/x
     * [new branch]      feature/x -> feature/x
    branch 'feature/x' set up to track 'origin/feature/x'.
    
    git switch -c feature/x
    • gitversion control
    git push -u origin feature/x
    • pushupload commits

    -u sets the branch's upstream. From then on, plain git push and git pull know where to go, and git status can compare:

    $ git status -sb
    ## main...origin/main [ahead 1]
    $ git branch -vv
    * main 99fec5d [origin/main: ahead 1, behind 1] Add Ravi's file
    
    git status -sb
    • gitversion control
    • statuswhat changed
    StatusMeansDo
    ahead 2you have 2 commits GitHub doesn'tgit push
    behind 3GitHub has 3 commits you don'tgit pull
    ahead 1, behind 1both moved: the branches have divergedpull (with rebase), then push

    When a push is rejected

    Asha pushes first. Ravi's push bounces, he pulls with rebase, and his second push lands on top of hers.© Animation: DevOps Academy

    Asha pushes first. Ravi's push bounces, he pulls with rebase, and his second push lands on top of hers.© Animation: DevOps Academy

    Asha and Ravi both cloned the repository. Asha pushed first. Now Ravi's push fails:

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

    Git refuses because accepting Ravi's push would throw away Asha's commit. The fix is to bring her work in first, then push:

    $ git pull --rebase                  # fetch, then replay my commits on top of theirs
    Successfully rebased and updated refs/heads/main.
    $ git log --oneline -2
    e0c625c Add Ravi's file
    ff897a0 Add Asha's note
    $ git push
       ff897a0..e0c625c  main -> main
    
    git pull --rebase
    • gitversion control
    • pulldownload and merge
    git log --oneline -2
    • loghistory
    git push
    • pushupload commits
    Never answer a rejected push with --force

    git push --force makes GitHub's branch match yours exactly, deleting the commits you didn't have. On a shared branch that silently erases a teammate's work. The Rewriting history lesson covers the rare cases where a forced push is right, and the safer --force-with-lease.

    How should pull combine work?

    When both sides have new commits, git pull must merge or rebase, and recent Git asks you to choose instead of guessing:

    fatal: Need to specify how to reconcile divergent branches.
    

    Set your default once:

    $ git config --global pull.rebase true   # replay your local commits on top: straight history
    
    git config --global pull.rebase true
    • gitversion control
    • configread or set settings
    • --globalfor your user, in every repo
    SettingDiverged pull doesHistory
    pull.rebase truereplays your commits after theirsa straight line
    pull.rebase falsemakes a merge commita "Merge branch 'main' of github…" bubble every time
    pull.ff onlyrefuses unless it's a simple fast-forwardyou decide each time

    Rebasing on pull is what most teams want for their own unpushed commits. The Merge strategies lesson explains rebase properly.

    Fetch: look before you merge

    git fetch downloads everything new and updates origin/*, and changes nothing else: not your branches, not your files. That makes it the safe way to see what your team did:

    $ git fetch
       9c53c90..ff897a0  main       -> origin/main
    $ git log --oneline main..origin/main      # commits on GitHub that I don't have
    ff897a0 Add Asha's note
    $ git log --oneline origin/main..main      # my commits GitHub doesn't have
    99fec5d Add Ravi's file
    $ git diff main origin/main --stat         # what their commits change
    $ git merge origin/main                    # or rebase, when you're ready
    
    git fetch
    • gitversion control
    git log --oneline main..origin/main
    • loghistory

    A..B means "commits in B that aren't in A". Read it as from A to B.

    $ git fetch --prune                        # also forget branches deleted on GitHub
    $ git config --global fetch.prune true     # always do that
    
    git fetch --prune
    • gitversion control
    git config --global fetch.prune true
    • configread or set settings
    • --globalfor your user, in every repo
    Before you push to a shared branch

    git fetch, read what came in, rebase or merge, run the tests, then push. Thirty seconds of this prevents most "who broke main?" mornings. Better still, protect main on GitHub so nobody pushes to it directly (the Collaboration lesson).

    ⌘
    Mini mission

    Two developers, one branch

    Simulate two teammates on your own machine: clone one repository into two folders, make a commit in each, push from the first, and get the second one's push accepted without a force push and without losing either commit.

    Input: Git, and a local bare repository standing in for GitHubOutput: Both commits on the shared main, in a straight line
    Reveal one safe solution
    $ cd ~ && git init --bare team.git              # plays the role of GitHub
    $ git clone team.git asha && git clone team.git ravi   # "cloned an empty repository" is expected
    $ cd asha && echo "# Team" > README.md && git add . && git commit -m "Add README" && git push -u origin main
    $ cd ../ravi && git pull                        # Ravi gets the README
    $ cd ../asha && echo "asha" >> README.md && git commit -am "Add Asha's note" && git push
    $ cd ../ravi && echo "ravi" > ravi.txt && git add . && git commit -m "Add Ravi's file"
    $ git push                                      # rejected: fetch first
    $ git fetch && git log --oneline main..origin/main
    $ git pull --rebase && git push
    $ git log --oneline
    
    cd ~ && git init --bare team.git
    • cdchange directory
    • ~your home directory
    • &&run the next command only if this one worked
    • gitversion control
    • initcreate a repository here
    git clone team.git asha && git clone team.git ravi
    • clonecopy a repository
    cd asha && echo "# Team" > README.md && git add . && git commit -m "Add README" && git push -u origin main
    • echoprint text
    • >write output to a file, replacing it
    • addstage changes
    • .this folder
    • commitsave staged changes
    • pushupload commits
    cd ../ravi && git pull
    • pulldownload and merge
    cd ../asha && echo "asha" >> README.md && git commit -am "Add Asha's note" && git push
    • >>append output to a file
    git fetch && git log --oneline main..origin/main
    • loghistory

    The last log shows Ravi's commit on top of Asha's, then the README commit, with no merge commit. Both folders end with the same history after git pull in asha.

    Try it yourself

    0 of 6 steps done

    Run each task in your own account or terminal, then tick it off.

    Knowledge check

    Quick check

    Question 1 of 5

    What is origin/main?

    Go deeper

    Official reading for this lesson

    • GuidePro Git: working with remotesPro Git ↗
    • GuidePro Git: remote branchesPro Git ↗
    • Docsgit-pull documentationGit ↗
    • DocsGitHub Docs: managing remote repositoriesGitHub Docs ↗

    Or get every quiz answer right and it completes itself.

    ← PreviousGitHub: your account, repositories, forks and clonesNext lesson →Merge, rebase, squash: combining work, and fixing conflicts