Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm becoming less enamored with git over time. I've used it for years (by choice, and I'm a GitHub user), but have never been a power user.

I'm annoyed at how the very most basic workflows in Git seem awkward, like I'm working against the tool instead of with it. The simplest example is: I have a hacked-up tree, but I know changes have been made upstream, and I want to pull those changes:

$ git pull Updating 73f91c3..0ee9fa8 error: Entry 'README' not uptodate. Cannot merge.

It's complaining because I've modified README locally, which was also modified remotely. Every other reasonable version control system I've ever used will happily merge the upstream changes with my not-yet-committed local changes. But Git refuses. This is annoying.

What I have to do now is commit my hacked-up, non-compiling, possibly-swear-word-containing in-progress changes. I really dislike this. To me, "commit" means "take a finished bit of work and add it to the global history." I really dislike having to commit something that is extremely unfinished just because I wanted to integrate some upstream changes.

So what I usually do in this situation is "git stash", "git pull", "git stash apply." This works ok for the "pull" case. But what if I have multiple sets of locally-hacked-up changes? Like suppose I was working on one change when I realized that there's something else I should really fix first. "git stash" quickly becomes limiting, since you can't name the individual changes, so you get this list of changes that you don't know what they are or what branch/commit they were based on. In other VCS's like Perforce, you can have multiple sets of independent changes in your working tree. Not possible with Git AFAIK.

Anyway, I'll probably keep using Git, but I'm not as enamored with it as I once was. I used to figure this was all just porcelain issues that would be refined over time, but it doesn't seem to be getting any better.



It's complaining because I've modified README locally, which was also modified remotely. Every other reasonable version control system I've ever used will happily merge the upstream changes with my not-yet-committed local changes. But Git refuses. This is annoying.

Commit-before-merge is not a limitation, it is a feature of DVCS's. Merge-before-commit is broken by design, as you are modifying your unsaved work, and there is no way to get back to your pre-merge state (say you decide the merge conflicts are too much to deal with at the moment).

So you are correct, git will not let you merge if youmhave uncomitted work that would be affected by the merge, but it's really just trying to keep you from losing work, not trying to annoy you.

That said, this is one of the use cases for 'git stash' which will set aside your uncommitted work. You can then do the 'git pull' and then unstash (stash pop) your work. The advantage of this is that you can always undo the merge if you decide it's not what you wanted afterall. With merge-before-commit, you'd have no such option unless you manually set aside your work.

But what if I have multiple sets of locally-hacked-up changes? Like suppose I was working on one change when I realized that there's something else I should really fix first. "git stash" quickly becomes limiting, since you can't name the individual changes, so you get this list of changes that you don't know what they are or what branch/commit they were based on. In other VCS's like Perforce, you can have multiple sets of independent changes in your working tree. Not possible with Git AFAIK.

This is what branches are for. Do not be afraid to commit work in progress... you can always polish up that work before you share those changes. For example:

  git checkout -b feature origin/master
  edit, ut oh, interuption,
  git commit -a -m WIP
  git checkout -b bugfix-1234 origin/master
  fix bug, git commit -m "fixed bug"
  git push origin HEAD:master
  git checkout feature
  git reset HEAD^ # removes the WIP commit, but leaves its changes in your working copy.
I hope that gives you a better idea of how you can use branches. You might also want to spend some time reading up on rebase -i. Basically, start thinking of your localc branches as independent patch queues that you can freely edit, reorder, etc until they are ready to be shared. At which point you can push them out to the world.

HTH.


> it's really just trying to keep you from losing work, not trying to annoy you.

The same thing could be achieved by having Git automatically create an "undo" commit before performing the merge. Then you could revert to pre-merge state with "git pull --undo", just like you can abort a rebase with "git rebase --abort."

Optimize for the common case. Of probably hundreds of merge-before-commit operations I have performed with other VCS's, I can't think of a single time I have wanted to undo this operation (after all, if upstream has changed you're going to have to merge sooner or later -- it might as well be now). On the other hand, I am annoyed by commit-before-merge every single time I perform a pull.

> This is what branches are for. Do not be afraid to commit work in progress...

I'll have to try the "git reset" approach, I hadn't thought of that before. But even that has issues IMO:

1. "git reset" is a data-losing operation if you call it with certain parameters. For example, "git reset --hard HEAD^" would throw away the WIP! I'm wary of making such a command something I type all the time, because there's always the risk that I'll call it wrong.

2. I have to do this dance of "commit, checkout, pull, checkout, rebase" just to pull upstream changes. And when I want to actually push the change upstream, I have to "merge --squash" and then delete the old branch (otherwise lots of branches will build up and I won't know which ones have been committed and which haven't). It's a lot of annoying overhead. I'd rather just work on master where I can just "pull", and branch only if I really want to work on two big changes in parallel.

> HTH.

I really do appreciate that you were genuinely trying to be helpful (as opposed to other replies). But my frustration remains that Git doesn't let me work the way I want to, and makes me perform contortions to fit its way of working.


The same thing could be achieved by having Git automatically create an "undo" commit before performing the merge. Then you could revert to pre-merge state with "git pull --undo",

I'm a regular on the git mailing list, and I've never heard of anyone desiring this workflow. It is unusual to want to merge into your work-in-progress. I'd go so far as to say "you're not using git as it was intended". Perhaps this helps a little:

http://www.mail-archive.com/dri-devel@lists.sourceforge.net/...

That said, it would be straight-forward to script/alias what you describe:

  git config --global alias.cleanpull '!git stash && git pull && git stash pop'
But really, that's not how git is intended to be used.

just like you can abort a rebase with "git rebase --abort."

Actually, rebase is much stricter than merge -- it won't let you start unless your working tree is completely clean. At least merge only cares about whether the files it needs to touch are clean.

Optimize for the common case.

That's not the common case, you've just been brain-damaged by non-DVCS's into thinking it is. :-)

"git reset" is a data-losing operation if you call it with certain parameters. For example, "git reset --hard HEAD^" would throw away the WIP

  git config --global alias.popcommit "reset HEAD^"
2. I have to do this dance of "commit, checkout, pull, checkout, rebase" just to pull upstream changes. And when I want to actually push the change upstream, I have to "merge --squash" and then delete the old branch (otherwise lots of branches will build up and I won't know which ones have been committed and which haven't). It's a lot of annoying overhead. I'd rather just work on master where I can just "pull", and branch only if I really want to work on two big changes in parallel.

"merge --squash"? It really sounds like you're trying to use git as if it's subversion or cvs, and it just isn't.

You can check for merged branches with "git branch --merged origin/master"

If you just want to examine upstream changes w/o integrating them into your current work, you can use "git fetch" and then "git log master..origin/master".

And if you find you often need to be working on multiple branches at the same time, you can always make an additional clone and/or use the new-workdir script in the git.git contrib directory.

Perhaps git just doesn't fit your notion of how a VCS should work, and that's fine. But your annoyances with git seem to stem from trying to use it not as it was intended. :-(

<tangent>Git is a powerful VCS with a rather-awful CLI, but built on top of simple and elegant concepts. Trying to derive a mental-model of how git works from its CLI is fraught-with-peril and will lead you astray. It is worth learning how git works conceptually, and then mapping CLI commands to those concepts. For small projects, it doesn't really matter, but for large projects, git is extremely flexible and you can do things with it that I cannot imagine doing with any other VCS.</tangent>

Best of luck.


FWIW, Mercurial allows you to do merges against the working copy -- you just run hg update.


It does not let you merge if you have uncommitted work, just like git:

  $ hg pull
  pulling from ...
  requesting all changes
  adding changesets
  adding manifests
  adding file changes
  added 1 changesets with 1 changes to 1 files
  (run 'hg update' to get a working copy)
  $ hg update
  abort: crosses branches (use 'hg merge' to merge or use  'hg update -C' to discard changes)
  $ hg merge
  abort: outstanding uncommitted changes (use 'hg status' to list changes)


Sorry, you're right that it does refuse to merge with the working copy if the changeset you're updating to is not a direct descendent of the changeset you're currently at (this is what the error about "crosses branches" refers to).

However, if it is a direct descendent, it will try to merge for you:

  $ echo b >> a
  $ hg status
  M a
  $ hg pull ../a
  pulling from ../a
  searching for changes
  adding changesets
  adding manifests
  adding file changes
  added 1 changesets with 1 changes to 1 files
  (run 'hg update' to get a working copy)
  $ hg update
  merging a
  warning: conflicts during merge.
  merging a failed!
  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
  use 'hg resolve' to retry unresolved file merges
  $ hg resolve -l
  U a
In this example, the upstream repository made a change to "a" that conflicts with my local, uncommitted change to the same file. It uses its normal merge machinery to try to resolve the conflict.

As far as I know, Git doesn't allow merges in this situation.


Oh, that's evil, you don't even need to --force it? :-)

So there's no way to back out of that right? i.e., get back "a's" state before you ran update?

(Ah, I see from update's help that you have to use --check to prevent it from touching uncommitted files.)

Hmm.


What reason could there possibly be to downvote the parent? Dogmatic Git fans, you are turning me off to Git more and more.


> It's complaining because I've modified README locally, which was also modified remotely. Every other reasonable version control system I've ever used will happily merge the upstream changes with my not-yet-committed local changes. But Git refuses. This is annoying.

No it isn't. Git is trying to be predictable.

> Like suppose I was working on one change when I realized that there's something else I should really fix first.

Branches are designed to resolve this problem. When you want to do something that takes a lot of time, you branch from the stable version. When you need to do something else, you branch from the stable version again.


You are completely ignoring my objection that I don't want to "commit" a tree that is totally broken. What should my commit message be?

"Tree is totally broken and doesn't compile, but Git made me do this to pull upstream changes."

There is nothing logical/reasonable for me to write in that commit message. I definitely don't want that commit to make it upstream. Sure, I could squash later, but why is Git forcing me to do something that doesn't have any value (write a "commit" message for a tree that is totally broken)?


Stash, pull, stash pop. Arguably not the most intuitive solution, but there ya go.


Also, I say that something is annoying to me, and you say "No it isn't." How can you deny that something is annoying to me? Ignoring what a user actually wants because it's not what you think they should want is the classic hacker UI design failing.


It seems that what you're really hitting is the, erhm, ill-advised decision to name most of git's most common actions the same as actions in subversion, but make them do significantly different things. Git's "commit" should probably have been named "mark" or whatever because "push" is what's actually comparable to a subversion commit. I've mostly conditioned myself by now, but I also recall some fun with checkout.


Whether it's called "commit" or "mark", I have to write a commit message, which I don't want to do unless my tree has reached a state where I have actually accomplished something. The commit becomes part of the history, which will be visible upstream unless I squash later, and I definitely don't want upstream to see a series of commits where my tree is completely broken.


The problem here seems to be that you're working against git, not with it. Committing and branching should be things that you do casually in git, by making a big deal out of them you're limiting yourself and crippling git.

If you try to use a screwdriver like you use a hammer, you're always going to be disappointed.


Personally I write crappy commit messages for all those commits I'm going to squash before publishing. git is guilty, but only of giving you too much rope and not enough direction.


Don't pull. Pull is two operations -- fetching the upstream work, and merging it in to your current branch. It's true that CVS and SVN both let you do this with dirty trees, but it's generally not what you should really be doing. Merges are hard and every update from "upstream" merged into your local changes has a chance of breaking your local changes.

"git fetch" and "git remote update" both let you do the fetch without the merge. Once you have the updates, you can then decide what to do with them, which may have lots more options than just a simple implicit merge that most tools provide: rebase, overwrite, ignore for now and handle later when your work is in a cleaner state, etc.

Git has tools to manage this in the case of long running lines of development. Providing them for uncommitted changes is a much harder task (precisely because you can't name your work and roll back to it), and mostly unnecessary because commits are so lightweight. This is your fundamental issue with git -- commits still feel heavyweight to you.

Git's model really is fundamentally not "get everybody's local changes working with the exact same upstream code" (indeed for the upstream author, they may trust their code far more than the code they are pulling). Instead it is closer to "let everybody pick what changes and history to use in order to create their own coherent source". DVCS means each developer chooses what history to treat as authoritative. As such, temporary commits, temporary branches, and rewriting unshared history are encouraged.


Can you not do a stash --patch multiple times and give each patch a unique message? That would solve the many unrelated hacks problem I would think.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: