简体   繁体   中英

How can I use git to accomplish the same workflow I use with mercurial?

I'm an hg girl living in a git world. I have a common workflow that happens all the time that i cant seem to understand how it should be done in git. So let me describe how I manage with mercurial.

So I pull from my origin and I have these 4 commits

o  3:1f6b4172c41e
|
o  2:639ba9dc8afc
|
o  1:5bfb5ae3faf5
|
o  0:e0890b04d1d3

I start hacking away and make 3 more commits, but I don't push them

o  6:e48cc97b3508
|
o  5:9a4c73cfb4f9
|
o  4:e32af117ea73
|
o  3:1f6b4172c41e
|
o  2:639ba9dc8afc
|
o  1:5bfb5ae3faf5
|
o  0:e0890b04d1d3

and lo and behold, I find a problem with my new work. I want to start over but not lose any of the work i've done so far. So I issue the command to bring me back to 1f6b4

hg update 1f6b4

o  6:e48cc97b3508
|
o  5:9a4c73cfb4f9
|
o  4:e32af117ea73
|
@  3:1f6b4172c41e
|
o  2:639ba9dc8afc
|
o  1:5bfb5ae3faf5
|
o  0:e0890b04d1d3

notice the @ at 3 (1f6b4)? that means the next commit will start from this point. So lets say i make three more commits, and my log looks like:

@  9:32001847b67a
|
o  8:fb7016a799d0
|
o  7:0269a427f0d9
|
| o  6:e48cc97b3508
| |
| o  5:9a4c73cfb4f9
| |
| o  4:e32af117ea73
|/
o  3:1f6b4172c41e
|
o  2:639ba9dc8afc
|
o  1:5bfb5ae3faf5
|
o  0:e0890b04d1d3

At this point im happy, and i decide i no longer need changesets 4-6. Since they havent been pushed to my central repository, i can just get rid of them:

hg strip e32af

And the new log looks like: (remember, the revision # before the hashcode is just for convienence, like git its the hashcode which never changes)

@  6:32001847b67a
|
o  5:fb7016a799d0
|
o  4:0269a427f0d9
|
o  3:1f6b4172c41e
|
o  2:639ba9dc8afc
|
o  1:5bfb5ae3faf5
|
o  0:e0890b04d1d3

Now I can push, and 3 new commits will be sent.

How can I accomplish this in git? This can be broken down into two questions:

  1. How do i have git "take me to 1f6b4" so the working directory has those files AND the next commit will branch from here. And by branch, i dont mean create a new branch, but a new head on the existing branch. So if im working on master and the head is at e48cc, how can i "go to" f16b4, which is 3 "ancestral" commits back, and the next commit will be an immediate descendant and becoming a new head on the master branch?
  2. Once I decided i no longer want the commit and descendants of e32af, how do i get rid of them completely ? I thought i got rid of things in git before, only to see them later when i do something like git log --all . These commits only existed here, they've never been pushed, how can I be shut of them forever?

Now there might be a better way to handle this in git, and i'm all for learning about it. However i really want to know how to accomplish this "jump to a point" and branch from there (preferably not having to create a new branch name).

What you have described is common in any workflow and it is possible to do it in git with different branches but can be difficult if you want to stick with the same branch.

How you would do it it is:

  • Once you reach commit 6, run git branch new_branch_name <sha1-of-commit-3>
  • Add commits 7, 8, 9
  • Push new_branch_name git push new_branch_name
  • Delete the old branch git branch -d old_branch_name

It is possible to replicate the exact same flow as you described too, but it is very easy to screw up too. The way to do that is:

  • git rebase -i <sha1-of-commit-3>
  • Add commits 7, 8, 9
  • git rebase --skip to skip the other commits.

... i really want to know how to accomplish this "jump to a point" and branch from there (preferably not having to create a new branch name).

Git can do what Mercurial can do, but you must create a new branch name. The thing is that in Git, branch names don't mean anything. They don't have any semantics. They can be added and deleted and changed at whim. They really serve only one purpose, and that is to find some commit(s).

Commits are on many branches simultaneously, in Git. This is fundamentally weird to those used to Mercurial. But this is because branch names in Git aren't real , in a sense. (After a while, you get used to it.) The closest analogy Mercurial has to a Git branch name is an hg bookmark.

Branch names in Mercurial are real things: solid things that last forever . 1 Once made, you can't really change a Mercurial branch name (outside of tricks like the convert extension that is). Each commit is made on some particular branch, and once made, is on that branch, and only that branch, forever.

Git commits, by contrast, are just made. They exist on some number of branches: anywhere from zero of them, to all of them, at the same time. Those branch names that allow you to find the commit are said to "contain" that commit. What's special about a branch name is that it can be the current branch , and when it is, making a new commit shoves that new commit's ID into the branch name.

How do i have git "take me to 1f6b4" so the working directory has those files AND the next commit will branch from here. And by branch, i dont mean create a new branch, but a new head on the existing branch.

Git doesn't have heads—or, mostly-equivalently, a branch name is a head.

In Mercurial, commits exist whether or not you can find them. Once made, a commit exists. If the commit has no descendants at all, or all of its descendant commits are on some other branch—again, a Mercurial commit is only on one branch—then that commit is a "head". So if you list out all the heads, or all the heads in the current branch, you can use those to find all the commits, or all the commits in the current branch.

In Git, there are no "heads". If you can't find a commit, it might as well not exist. 2 The commits you can find directly are those that have names: branch names, tag names, or any other names. The other commits you can find are those that are ancestors of the commits you can find directly.

Hence, to be able to find commit 1f6b4 , you either find it directly , by a branch name, or else you find a later commit by a branch name, then work your way back to 1f6b4 .

So:

git checkout -b newbranch 1f6b4

will make a new name that finds 1f6b4 . That doesn't change which set of branches 1f6b4 is on already: it just adds one more branch that 1f6b4 is on. Commits that are ancestors of 1f6b4 are also on all those branches, including the new one.

Once I decided i no longer want the commit and descendants of e32af, how do i get rid of them completely?

You don't. Instead, you remove the branch name(s) that lead to e32af . Now you can't find them, and after some time, 3 git gc , which will run automatically now and then, will eat them.


1 More precisely, they last as long as they have commits. Like Git, Mercurial can't have a branch name exist unless it has commits.

2 There are some maintenance commands that can find such commits, but if you don't run them soon enough and attach names to them, Git's garbage collector , git gc , will throw them away.

3 The amount of time an invisible commit will survive depends on:

  • gc.reflogExpire , default 90 days, for commits reachable from the reference; or
  • gc.reflogExpireUnreachable , default 30 days, for commits not reachable from the reference

which requires getting into the way Git's reflogs work. The short way to put this though is that the default for these commits will be 30 days. The automatic gc only runs whenever Git feels like it, though: so after 30 days, when gc.reflogExpireUnreachable will stop protecting the commits, then the next automatic gc, whenever that actually runs , will strip them for real.

It's possible, but almost ridiculously difficult, to remove them sooner. Note that Git doesn't back up removed commits, but as long as you can't see them, you'll never notice that they exist—unless they have obvious side effects like holding a terabyte of file data, or something.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM