New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Git migrate Blog post draft #205

Merged
merged 13 commits into from Jun 29, 2015

Conversation

Projects
None yet
2 participants
@Haacked
Owner

Haacked commented Jun 28, 2015

  • Still need to resolve the problem with migrating specific ranges of commits

The final scenario I want to make work is when you want to migrate a commit range instead of every local commit.

Here's the alias I have so far:

    migrate = "!f(){ git branch $1 && git reset ${3-'@{u}'} --hard && git rebase --onto ${2-master} ${3-'@{u}'} $1; }; f"

Now take a look at this tree.

If I run git migrate new-branch everything works great. That would expand to the following commands:

git branch new-branch
git reset origin/wrong-branch --hard
git rebase --onto master origin/wrong-branch new-branch

And that seems to result correctly in wrong-branch being reset to origin/wrong-branch AND commits E and F (but not D) migrated to a branch new-branch off of master.

However, if I run git migrate new-branch master HEAD~1 I would expect that wrong-branch is reset to E (which it is) and that the commit F is migrated to the new-branch off of master. But what happens is that E and F are rebased, which is a bit confusing to me.

So after talking to @mhagger it looks like I might need to add some complexity to the alias so that if you happen to pass in a 3rd parameter, I reset to that parameter, but I rebase onto that parameter~1.

Maybe I could set the right "savepoint" to a variable

    migrate = "!f(){ savepoint = if test -n "$3"; then $3^ else @{u} fi; ..."

Then I'd reset to savepoint but rebase --onto using savepoint^.

Show outdated Hide outdated _posts/2015/2015-06-29-git-migrate.markdown
git reset origin/master --hard
git checkout new-branch
```

This comment has been minimized.

@mhagger

mhagger Jun 28, 2015

It is true that the most straightforward approach is these three commands. But it is awkward because it involves rewriting the working tree twice (as you jump back to origin/master then return to new-branch). The most serious problem with this is that any uncommitted changes to existing files will be lost.

In general, git reset --hard is dangerous. It actively and silently discards uncommitted work. Sometimes that's what you want, but (I think) not in this case. So in this case git reset --keep would be preferable: it does the same thing as git reset --hard but (1) tries to carry any uncommitted changes over, but (2) if it can't it reports an error and aborts.

But this can be reduced to two commands that don't involve rewriting the working tree:

git checkout -b new-branch
git branch --force master origin/master

I wasn't sure about it but I just verified that the second command retains the reflog for master (including the addition of the unwanted commits and the reset back to origin/master), which is a good thing.

You can even do this in one command, kindof:

git branch -m new-branch

This renames your local master branch to new-branch. It has two strong caveats:

  • The reflog for master is also moved to new-branch. If you don't want to lose the backstory for master, this is not so good.
  • This command leaves the repo without a master branch. This is not as bad as it sounds, because whenever you want to go back to work you can just type git checkout master and the branch will be re-created automatically from origin/master. But if your local master had unpushed commits that you didn't want to be moved to new-branch, you can't use this version.
@mhagger

mhagger Jun 28, 2015

It is true that the most straightforward approach is these three commands. But it is awkward because it involves rewriting the working tree twice (as you jump back to origin/master then return to new-branch). The most serious problem with this is that any uncommitted changes to existing files will be lost.

In general, git reset --hard is dangerous. It actively and silently discards uncommitted work. Sometimes that's what you want, but (I think) not in this case. So in this case git reset --keep would be preferable: it does the same thing as git reset --hard but (1) tries to carry any uncommitted changes over, but (2) if it can't it reports an error and aborts.

But this can be reduced to two commands that don't involve rewriting the working tree:

git checkout -b new-branch
git branch --force master origin/master

I wasn't sure about it but I just verified that the second command retains the reflog for master (including the addition of the unwanted commits and the reset back to origin/master), which is a good thing.

You can even do this in one command, kindof:

git branch -m new-branch

This renames your local master branch to new-branch. It has two strong caveats:

  • The reflog for master is also moved to new-branch. If you don't want to lose the backstory for master, this is not so good.
  • This command leaves the repo without a master branch. This is not as bad as it sounds, because whenever you want to go back to work you can just type git checkout master and the branch will be re-created automatically from origin/master. But if your local master had unpushed commits that you didn't want to be moved to new-branch, you can't use this version.
Show outdated Hide outdated _posts/2015/2015-06-29-git-migrate.markdown
git branch new-branch
git reset origin/wrong-branch --hard
git rebase --onto master origin/wrong-branch new-branch
```

This comment has been minimized.

@mhagger

mhagger Jun 28, 2015

It doesn't save any lines in this case, but if you want to make this example more similar to my suggested change for the previous example, you could do

git checkout -b new-branch
git branch --force wrong-branch origin/wrong-branch
git rebase --onto master wrong-branch

I find the UI of git rebase --onto unintuitive, and the two-argument form of it even more unintuitive. So it's nice that the latter is not necessary here.

@mhagger

mhagger Jun 28, 2015

It doesn't save any lines in this case, but if you want to make this example more similar to my suggested change for the previous example, you could do

git checkout -b new-branch
git branch --force wrong-branch origin/wrong-branch
git rebase --onto master wrong-branch

I find the UI of git rebase --onto unintuitive, and the two-argument form of it even more unintuitive. So it's nice that the latter is not necessary here.

This comment has been minimized.

@Haacked

Haacked Jun 29, 2015

Owner

and the two-argument form of it even more unintuitive. So it's nice that the latter is not necessary here.

But it looks like you used the 2-arg form. Did you mean the 3-arg form is not necessary here?

@Haacked

Haacked Jun 29, 2015

Owner

and the two-argument form of it even more unintuitive. So it's nice that the latter is not necessary here.

But it looks like you used the 2-arg form. Did you mean the 3-arg form is not necessary here?

@mhagger

This comment has been minimized.

Show comment
Hide comment
@mhagger

mhagger Jun 28, 2015

Maybe I could set the right "savepoint" to a variable

migrate = "!f(){ savepoint = if test -n "$3"; then $3^ else @{u} fi; ..."

Then I'd reset to savepoint but rebase --onto using savepoint^.

Well, that's not quite valid shell script (no spaces are allowed around = and if...else can't return a value) but you could use

migrate = "!f(){ savepoint="$(if test -n "$3"; then echo "$3^"; else echo "@{u}"; fi)"; ..."

or

migrate = "!f(){ if test -n "$3"; then savepoint="$3^"; else savepoint="@{u}"; fi; ..."

mhagger commented Jun 28, 2015

Maybe I could set the right "savepoint" to a variable

migrate = "!f(){ savepoint = if test -n "$3"; then $3^ else @{u} fi; ..."

Then I'd reset to savepoint but rebase --onto using savepoint^.

Well, that's not quite valid shell script (no spaces are allowed around = and if...else can't return a value) but you could use

migrate = "!f(){ savepoint="$(if test -n "$3"; then echo "$3^"; else echo "@{u}"; fi)"; ..."

or

migrate = "!f(){ if test -n "$3"; then savepoint="$3^"; else savepoint="@{u}"; fi; ..."

Haacked added some commits Jun 29, 2015

Simplify the master fixup workflow
Thanks to @mhagger my three commands are now only two and don't need to
rewrite the working tree!
The `git rebase` command is a great way to move (well, actually you replay commits, but that's a story for another day) commits onto other branches. The handy `--onto` flag makes it possible to specify a range of commits to move elsewhere. [Pivotal Labs has a helpful post](http://pivotallabs.com/git-rebase-onto/) that describes this option in more detail.
So in this case, I moved commits `E` and `F` because they are the ones since `wrong-branch` on the current branch, `new-branch`.

This comment has been minimized.

@Haacked

Haacked Jun 29, 2015

Owner

Are these previous two paragraphs the best way to explain this?

@Haacked

Haacked Jun 29, 2015

Owner

Are these previous two paragraphs the best way to explain this?

@Haacked

This comment has been minimized.

Show comment
Hide comment
@Haacked

Haacked Jun 29, 2015

Owner

One consequence of using git checkout -b as the first command is I no longer have a reference to the original branch. But I suppose I can save those references in my alias before I switch branches.

Owner

Haacked commented Jun 29, 2015

One consequence of using git checkout -b as the first command is I no longer have a reference to the original branch. But I suppose I can save those references in my alias before I switch branches.

@Haacked

This comment has been minimized.

Show comment
Hide comment
@Haacked

Haacked Jun 29, 2015

Owner

@mhagger I'm trying to save the current HEAD in a variable but this alias fails with a "Command not found CURRENT." I'll keep looking into it, but if you happen to be around maybe this is obvious to you. 😄

[alias]
    test = "!f() { CURRENT = $(git symbolic-ref HEAD); ECHO $CURRENT; }; f"
Owner

Haacked commented Jun 29, 2015

@mhagger I'm trying to save the current HEAD in a variable but this alias fails with a "Command not found CURRENT." I'll keep looking into it, but if you happen to be around maybe this is obvious to you. 😄

[alias]
    test = "!f() { CURRENT = $(git symbolic-ref HEAD); ECHO $CURRENT; }; f"
@Haacked

This comment has been minimized.

Show comment
Hide comment
@Haacked

Haacked Jun 29, 2015

Owner

@mhagger I figured it out. I guess shell scripts aren't fans of spaces around the = signs. 😛

Owner

Haacked commented Jun 29, 2015

@mhagger I figured it out. I guess shell scripts aren't fans of spaces around the = signs. 😛

@Haacked

This comment has been minimized.

Show comment
Hide comment
@Haacked

Haacked Jun 29, 2015

Owner

Ok, here's where I am now.

[alias]
    test = "!f() { REMOTE=@{u}; CURRENT=$(git symbolic-ref HEAD); git checkout -b new-branch && git branch --force $CURRENT $REMOTE; }; f"

When I run this, it states that there's no remote configured for new-branch. I mistakenly thought REMOTE=@{u} would save the current remote if any.

Owner

Haacked commented Jun 29, 2015

Ok, here's where I am now.

[alias]
    test = "!f() { REMOTE=@{u}; CURRENT=$(git symbolic-ref HEAD); git checkout -b new-branch && git branch --force $CURRENT $REMOTE; }; f"

When I run this, it states that there's no remote configured for new-branch. I mistakenly thought REMOTE=@{u} would save the current remote if any.

@Haacked

This comment has been minimized.

Show comment
Hide comment
@Haacked

Haacked Jun 29, 2015

Owner

Ok, I think I got this.

Owner

Haacked commented Jun 29, 2015

Ok, I think I got this.

@Haacked

This comment has been minimized.

Show comment
Hide comment
@Haacked

Haacked Jun 29, 2015

Owner

For those following along:

[alias]
    test = "!f() { CURRENT=$(git symbolic-ref --short HEAD); git checkout -b new-branch && git branch --force $CURRENT $CURRENT@{u}; }; f"

That works.

Owner

Haacked commented Jun 29, 2015

For those following along:

[alias]
    test = "!f() { CURRENT=$(git symbolic-ref --short HEAD); git checkout -b new-branch && git branch --force $CURRENT $CURRENT@{u}; }; f"

That works.

Haacked added a commit that referenced this pull request Jun 29, 2015

Merge pull request #205 from Haacked/git-migrate
Git migrate Blog post draft

@Haacked Haacked merged commit 9bcfbd2 into gh-pages Jun 29, 2015

@Haacked Haacked deleted the git-migrate branch Jun 29, 2015

![Always a new branch](https://cloud.githubusercontent.com/assets/19977/8412077/4d85a08c-1e3c-11e5-98eb-c421d2cf5159.png)
Again, just like before, I force the current branch to the state of the current branch as it exists on the server.

This comment has been minimized.

@mhagger

mhagger Jun 30, 2015

wrong-branch is not the current branch at this point in the story.

@mhagger

mhagger Jun 30, 2015

wrong-branch is not the current branch at this point in the story.

This comment has been minimized.

@Haacked

Haacked Jun 30, 2015

Owner

Good catch! Thanks!

@Haacked

Haacked Jun 30, 2015

Owner

Good catch! Thanks!

![The wrong branch](https://cloud.githubusercontent.com/assets/19977/8369613/48019364-1b71-11e5-9a28-b748a2802ed7.png)
This case is a bit more complicated. Here I have a branch named `wrong-branch` that is my current branch. But I thought I was working in the `master` branch. I make two commits in this branch by `mistake` which causes this fine mess.

This comment has been minimized.

@mhagger

mhagger Jun 30, 2015

Why is "mistake" highlighted like code? Maybe you have a program called mistake that you can blame when things go wrong. (That could come in handy...)

@mhagger

mhagger Jun 30, 2015

Why is "mistake" highlighted like code? Maybe you have a program called mistake that you can blame when things go wrong. (That could come in handy...)

This comment has been minimized.

@Haacked

Haacked Jun 30, 2015

Owner

Ha! This mistake was a mistake.

@Haacked

Haacked Jun 30, 2015

Owner

Ha! This mistake was a mistake.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment