14

I push code from two main locations: my PC at home and my laptop at work. I use Github to store my repos.

This is the scenario: I did some work in my PC on a repository I've been working on for some time (both in my PC and in my laptop), and ended with the following branches:

$ git branch
* master
* v123
* test-b

which I pushed to Github. So far so good.

Now I'm on my laptop and this is what I see before attempting to pull anything:

$ git branch
* master
* v_123

This is an old version of my repo in my laptop (since I've been working in my PC) where the differences are: a branch is missing (test-b), another one has been re-named, or equivalently deleted and re-created with a new name (ie: v_123 is now v123), and lots of things have changed possibly in all branches.

I want to sync all my branches into my laptop and have them correctly tracked. I've looked at two of the most up-voted questions regarding branch cloning/fetching (How to clone all remote branches in Git?; How to fetch all git branches) and right now I'm a bit lost.

Is there some easy to use git sync-branch --all command that can be used to sync my laptop with the latest state of the repo in Github?

Community
  • 1
  • 1
Gabriel
  • 32,750
  • 58
  • 187
  • 337
  • You don't want to do this. It's a common misconception. When you "fetch" you will get all the updates, then you can manually update any local branch you have whenever you need to use it. – Andrew C Nov 26 '14 at 19:56
  • @AndrewC but that's what I _don't_ want to do, ie: update manually each local branch one at a time. I want my repo to be completely synced with a single command. Is this not allowed? – Gabriel Nov 26 '14 at 20:03
  • When you sync your repo *is* completely synced. If you use `git branch -r` you can see all the references exactly as they are on the remote. `git branch` is showing you the stuff that is local to that repo only. – Andrew C Nov 26 '14 at 20:12
  • _When I sync_? When am I syncing? That's precisely the question. – Gabriel Nov 26 '14 at 20:13
  • `git fetch`. This will update your remote references, and additionally bring in all the needed objects (commits, trees, blobs, etc). Always `git fetch` to bring a repository up to date. What you do with your local work at that point is best dealt with on a branch by branch basis. – Andrew C Nov 26 '14 at 20:25
  • Fetching doesn't really resolve much other than allowing me to see what's changed. I don't want to deal with syncing branches one by one, that's the whole point of the question. – Gabriel Nov 26 '14 at 20:28
  • I get that. Git only updates local branches if they are checked out to your working directory (if we ignore forced updates and whatnot). Whether or not a local branch is up to date if it isn' currently checked out doesn't really fit in the normal git model. – Andrew C Nov 26 '14 at 20:52

5 Answers5

16

Not sure, this is what you expect.

git fetch origin
git reset --hard origin/master
git clean -f -d

The above commands will synchronize the remote repo with the local repo. After the above command execution, your local repo will be like the mirror image of your remote repo.

If you want to retain the changes as unstaged files, use --soft instead of --hard.

WARNING: All your untracked files will be gone when you do git clean -f -d.

Lemme know, if any questions.

not2qubit
  • 10,014
  • 4
  • 72
  • 101
Breen ho
  • 1,312
  • 10
  • 21
  • 1
    Thanks @Breen, couple of questions: What is the difference between `git fetch origin` and simply `git fetch`? Will `git reset --hard origin/master` sync only the `master` branch or all the other ones too? – Gabriel Nov 27 '14 at 21:28
5

I had a similar issue:

  • I want to sync all my local tracking branches at once
  • I want this operation to be safe. In other words, in case of local modifications or diverging branches, warn and don't touch the history (and do not tamper with uncommitted modifications)

After searching a lot for a simple solution, I ended up with my own (not so simple) solution, based on a series of git aliases (code to be added to the .gitconfig file): https://gist.github.com/arnauldvm/dcec7ee043c25dce30dbae1b576f2102

⚠ This is fresh code, not yet so heavily tested.

Usage: git sync


Some explanations:

tracking = "!f() { git for-each-ref --format '%(refname:short):%(upstream:short)' 'refs/heads' | egrep -v ':$'; }; f"

→ This will retrieve all local tracking branches and the corresponding remote branch, colon separated.

is-clean-workdir = "!f() { git diff --stat --exit-code || { echo \"Workdir dirty\"; exit 1; }; }; f"
is-clean-index = "!f() { git diff --stat --cached --exit-code || { echo \"Index dirty\"; exit 2; }; }; f"
is-clean = "!f() { git is-clean-workdir && git is-clean-index; }; f"

→ These will verify that there are no pending changes in the workdir.

co-merge = "!f() { local=\"$1\"; remote=\"$2\"; git checkout \"$local\"; git merge --ff-only \"$remote\"; }; f"

→ This takes 2 arguments (a local branch and a remote branch), it will checkout the local branch and merge it with the remote branch. It uses --ff-only to avoid creating commit merges ⇒ In case of diverging local branch, it will issue a "fatal: Not possible to fast-forward, aborting." message on STDERR.

current-branch = rev-parse --abbrev-ref HEAD

→ Retrieve name of the branch currently checked out. Will be used to put the workdir back in its initial state, at the end.

sync = "!f() { git is-clean || { echo Aborting sync.; exit 1; }; current=$(git current-branch); git fetch --all; git tracking | while IFS=: read local remote; do echo \"Merging $local with $remote\"; git co-merge \"$local\" \"$remote\"; done 3>&1 1>&2 2>&3 | egrep -i --color 'fatal|$' 3>&1 1>&2 2>&3; git checkout \"$current\"; }; f"

→ That's the main part. Let's split this into parts:

git is-clean || { echo Aborting sync.; exit 1; }

→→ Aborts the command if there is pending work in the workdir.

current=$(git current-branch)

→→ Stores the name of the branch currently checked-out.

git fetch --all

→→ Sync remote branches from all remotes (--all).

git tracking | while IFS=: read local remote; do ...

→→ Iterates over each local tracking branch.

3>&1 1>&2 2>&3 | egrep -i --color 'fatal|$' 3>&1 1>&2 2>&3

→→ Highlights errors (containing the 'fatal' keyword). The "3>&1 1>&2 2>&3" magical formula exchanges STDOUT and STDERR, it is necessary to be able to pipe STDERR to the egrep command.

git checkout "$current"

→→ Restore the workdir to its initial state.

Arnauld VM
  • 111
  • 1
  • 5
  • It has been an hour `git` was so stupidly refusing to update my local branch with the remote one, and that I have been crawling desperately through all answers of SO to resolve this. I just copy pasted your answer without even trying to understand, and it totally solve my problem with your `sync` alias. Great answer! – calandoa Sep 30 '19 at 13:30
3

You cannot see any changes made on remote before attempting to pull anything. You must fetch from remote first.

There is a reason for this. Git is decentralized. Every repository contains it's full copy. It means that you may create a full clone from repository on your disk. And it also means that you can be working entirely offline, unless you command it to sync. Actually, the way git is made forces you to work almost entirely offline, not counting moments you push or fetch. On the other hand, you have full control and nothing happens without you explicitly saying so.

How to do it? That's why git pull exists. It performs two operations. First, it fetches all branches from remote (equivalent to git fetch). They are saved as <remote>/<branch> and not listed on branch list by default (override with -a). Then, it merges currently active branch with it's tracked branch.

Now, if you want to have a remote branch tracked, then simply checkout it after pull. It will create it from last synced version and should set everything up. If you want to set nonpublished local branch tracked on remote, use git push with -u flag. It will set upstream for the branch as push target.

EDIT:

So your goal is to update all local tracked branches at once, with one command? Not those <remote>/<branch>, but your local branches, without calling pull on every single one of them, now I get it right?

Unfortunately, it's impossible to do with git alone. But there is an extension that does exactly that. From project description

git-up - fetch and rebase all locally-tracked remote branches

You can find it here http://aanandprasad.com/git-up/

When you use it, with command git up, it will stash your current changes, fetch all remotes, then rebase all tracked branches to state from tracked remote and unstash changes. I believe this is the way of synchronization you want.

Wikiii122
  • 446
  • 3
  • 9
  • I'm sorry but I think you completely missed the point of the question. I never said I could see _changes_ before pulling. I said that what I saw _before_ pulling was the old or previous version of my repo. I'm after a way to simply sync all my branches at once, that's it. – Gabriel Nov 26 '14 at 19:55
  • @Gabriel I've edited answer, is this what you are looking for? – Wikiii122 Nov 28 '14 at 07:27
1

I think you want to:

git fetch --all -Pp

where: git fetch Download objects and refs from another (remote) repository

--all fetch all remotes.

-P remove any remote-tracking references that no longer exist on the remote.

-p remove any local tags that no longer exist on the remote.

for more use git fetch --help

ton
  • 2,505
  • 1
  • 29
  • 31
0

Here is a script I wrote that will fetch and fast forward all your branches without doing any checkouts (so it's faster and doesn't affect your current working tree.

https://stackoverflow.com/a/24451300/965176

qwertzguy
  • 10,735
  • 5
  • 53
  • 59