Skip to content

Instantly share code, notes, and snippets.

@Altreus
Created May 20, 2014 13:51
Show Gist options
  • Save Altreus/75f3425d61708d1f9322 to your computer and use it in GitHub Desktop.
Save Altreus/75f3425d61708d1f9322 to your computer and use it in GitHub Desktop.
Rebase example!
===============
I forgot to commit some things remotely before I cloned my repo to work locally.
altreus@local:~ $ git clone altre.us:altre.us
Cloning into altre.us ...
<snip>
altreus@local:~/altre.us $ do_stuff
altreus@local:~/altre.us $ git commit -a -m"Turn off static cache for now"
altreus@local:~/altre.us $ do_stuff
altreus@local:~/altre.us $ git commit -a -m"Implement and use text-input and number-input"
altreus@local:~/altre.us $ git lg
* e817192 (HEAD, master) Implement and use text-input and number-input
* 2810396 Turn off static cache for now
* 2aa0b7d (origin/master, origin/HEAD) package.json
* dc6c29a this should be git
Note that origin is the host called "remote". The below is standard SSH hostname:path stuff.
altreus@local:~/altre.us $ git remote -v
origin altre.us:altre.us (fetch)
origin altre.us:altre.us (push)
Note also that my commits have moved master but not origin/master. origin/master is immutable because it
represents localhost's idea of what the branch is at altre.us; this can't be changed without either asking
(fetch) or telling (push) the remote about the branch.
origin/master is a "remote branch" and master is a "local branch". Local branches can be moved to your
heart's content, but note that any local branch that "tracks" a remote branch comes with social contracts
because other repositories may *also* have a local tracking branch.
A tracking branch is simply a local branch that is associated with a remote branch. This can be implicit
by its name, but git includes a mechanism by which it associates the one with the other, so to be a true
tracking branch this mechanism should be used. It's set up automatically most of the time.
Anyway I committed these and wondered what happened to my ability-score element:
altreus@altre.us:~/altre.us $ git lg
* 2aa0b7d (HEAD, master) package.json
* dc6c29a this should be git
Forgot to commit.
Note here that the commits I made on local do not exist. There is no origin/master because we *are* origin.
There is no central repository from which the two were cloned; this is the master repo.
This is a bad idea but it's easily fixable so I'll leave it for now.
altreus@altre.us:~/altre.us $ git commit -a -m"Update and use ability-score doodah"
altreus@altre.us:~/altre.us $ git lg
* 8ef0a66 (HEAD, master) Update and use ability-score doodah
* 2aa0b7d package.json
* dc6c29a this should be git
master moves because HEAD points to master. It is possible for HEAD and master to coincide but for HEAD
not to point to master; this happens if a) HEAD points to a different (usually new) branch; or b) HEAD
points to the SHA at master (8ef0a66) instead.
This creates a divergence, but local doesn't know about it yet.
altreus@local:~/altre.us $ git lg
* e817192 (HEAD, master) Implement and use text-input and number-input
* 2810396 Turn off static cache for now
* 2aa0b7d (origin/master, origin/HEAD) package.json
* dc6c29a this should be git
altreus@local:~/altre.us $ git fetch
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 6 (delta 4), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From altre.us:altre.us
2aa0b7d..8ef0a66 master -> origin/master
git fetch will retrieve information about origin/master, but will not do anything about it. Note that
last line says "The master branch from altre.us:altre.us has become origin/master here. That's an update
from 2aa0b7d to 8ef0a66". That's correct: previously to committing remotely, origin/master was on the same
commit as master-at-origin, i.e. 2aa0b7d.
After committing remotely, master-at-origin moved to 8ef0a66 (which we created), so fetch has retrieved
this commit and updated origin/master to match.
altreus@local:~/altre.us $ git fetch
* e817192 (HEAD, master) Implement and use text-input and number-input
* 2810396 Turn off static cache for now
| * 8ef0a66 (origin/master, origin/HEAD) Update and use ability-score doodah
|/
* 2aa0b7d package.json
* dc6c29a this should be git
Now we see the divergence on local. We don't see it at origin:
altreus@altre.us:~/altre.us $ git lg
* 8ef0a66 (HEAD, master) Update and use ability-score doodah
* 2aa0b7d package.json
* dc6c29a this should be git
That's because we haven't sent the data *to* origin. We can't ssh to altre.us and issue a `git fetch`
because origin doesn't have a remote; and `git fetch` fetches from a remote.
We could add localhost as a remote to origin, but then we'd have to have the local machine on a permanent
hostname and open it up to remote SSH (or readonly HTTP, at a pinch).
So we have to send from local to origin, with push:
altreus@local:~/altre.us $ git push origin master
To altre.us:altre.us
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'altre.us:altre.us'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
It was rejected. The reason for this is that you cannot trace ancestry from 8ef0a66 to e817192. If we
moved origin's master branch from 8ef0a66 to e817192, we would lose 8ef0a66 entirely!
That's a non-fast-forward, i.e. you can't fast-forward the repository from snapshot 8ef0a66 to snapshot
e817192 without losing information.
We need to make it so that local master is based on the new remote master, i.e. the change "Turn off
static cache for now" (2810396) is based on "Update and use ability-score doodah" (origin/master).
Note that master is already based on 2810396, so as long as we re-parent 2810396, master should follow.
Re-parent is of course rebase.
altreus@local:~/altre.us $ git rebase origin/master
First, rewinding head to replay your work on top of it...
Applying: Turn off static cache for now
Applying: Implement and use text-input and number-input
Line by line: HEAD is at master; we want to rebase master onto origin/master.
(master and origin/master are just labels. We could easily have used the SHA at origin/master to do
this. We don't mention master explicitly because rebase as a default action rebases your current
branch).
HEAD is rewound (I think this should be capitalised in the output, ideally) to the "merge base" of
master and origin/master, i.e. "2aa0b7d package.json". That produces a list of commits that need
to be replayed. HEAD is moved to origin/master (being the argument to rebase) and the produced list
of commits are re-applied (remember they are snapshots). Git attempts to apply the first snapshot
to the state of origin/master. If that can be done, it does so automatically and moves on. It can;
it does.
The second commit is considerably more likely to apply because it already was based on the previous
commit. However, there is still a chance it interposes itself on stuff changed in the destination.
At any point, if git cannot consolidate the new snapshot against the old, you are simply invited
to amend the snapshot and tell git to continue.
In this case, both commits applied.
altreus@local:~/altre.us $ git lg
* e330387 (HEAD, master) Implement and use text-input and number-input
* 8ceabe5 Turn off static cache for now
* 8ef0a66 (origin/master, origin/HEAD) Update and use ability-score doodah
* 2aa0b7d package.json
* dc6c29a this should be git
The thing to note here is that the commits have different SHAs, but the same messages. The old
commits still exist; they will eventually be garbage collected.
altreus@local:~/altre.us $ git log -1 e817192
commit e817192eacea0fbed561dcec61794300f8e23390
Author: Alastair McGowan-Douglas <alastair.douglas@gmail.com>
Date: Tue May 20 13:58:33 2014 +0100
Implement and use text-input and number-input
altreus@local:~/altre.us $ git log -1 master
commit e33038788457bad4f9888a08207702727ec2d446
Author: Alastair McGowan-Douglas <alastair.douglas@gmail.com>
Date: Tue May 20 13:58:33 2014 +0100
Implement and use text-input and number-input
This lets you get them back, by making a branch there temporarily, if needs be.
Now we still can't push, but that's because the remote refuses to clobber its working tree:
altreus@local:~/altre.us $ git push origin master
Counting objects: 11, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 919 bytes | 0 bytes/s, done.
Total 7 (delta 5), reused 0 (delta 0)
remote: error: refusing to update checked out branch: refs/heads/master
<snip>
As noted, there is no reason for HEAD to point to master; HEAD can point to a SHA.
altreus@altre.us:~/altre.us $ git co 8ef0a66
Note: checking out '8ef0a66'.
<snip>
This has no apparent effect:
altreus@altre.us:~/altre.us $ git lg
* 8ef0a66 (HEAD, master) Update and use ability-score doodah
* 2aa0b7d package.json
* dc6c29a this should be git
However:
altreus@local:~/altre.us $ git push origin master
Counting objects: 11, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 919 bytes | 0 bytes/s, done.
Total 7 (delta 5), reused 0 (delta 0)
To altre.us:altre.us
8ef0a66..e330387 master -> master
This lets us push to master because moving master on altre.us does not affect HEAD.
Previously, moving master would move HEAD and hence force the working tree to update
to the new master.
altreus@altre.us:~/altre.us $ git lg
* e330387 (master) Implement and use text-input and number-input
* 8ceabe5 Turn off static cache for now
* 8ef0a66 (HEAD) Update and use ability-score doodah
* 2aa0b7d package.json
* dc6c29a this should be git
Note how HEAD did not move. This push also updates our local information about the
remote branches:
altreus@local:~/altre.us $ git lg
* e330387 (HEAD, origin/master, origin/HEAD, master) Implement and use text-input and number-input
* 8ceabe5 Turn off static cache for now
* 8ef0a66 Update and use ability-score doodah
* 2aa0b7d package.json
* dc6c29a this should be git
The information about origin/HEAD is wrong. This may be a bug. I'm not sure origin/HEAD
is even useful information. We should update the remote to the new stuff anyway:
altreus@altre.us:~/altre.us $ git co master
Previous HEAD position was 8ef0a66... Update and use ability-score doodah
Switched to branch 'master'
altreus@altre.us:~/altre.us $ git lg
* e330387 (HEAD, master) Implement and use text-input and number-input
* 8ceabe5 Turn off static cache for now
* 8ef0a66 Update and use ability-score doodah
* 2aa0b7d package.json
* dc6c29a this should be git
No merge commit, no cruft, no problem.
Rather than checking HEAD out to a SHA we should have pushed-pulled via a central remote:
altre.us:altre.us.git (bare)
/ \
localhost (files) altre.us:altre.us (files)
Bare repositories contain an index of the commits, but no files. That means you can move
the branches around, even HEAD, and it will not clobber the working tree. As the first
example showed, you can always fetch into a working tree - files only change when you
move HEAD, and fetch only moves remote branches.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment