If you have changes—i.e., a dirty work-tree—you have something to commit. At most, you have something you want to commit temporarily.
In other version control systems, you might do this by committing it on a branch. In Git, you can do that too: you don't have to use a branch, but that may be the most convenient way to deal with it in Git, too.
You mention a starting scenario, for which I have a work pattern I find helpful:
A very common scenario arising when people code together is to have to bring yourself (let's say, the feature branch you're working on) up-to-date.
Let's say that you are working on feature-X
, which will eventually be put into dev
(development branch). Other developers have been working on features Y and Z and one of them has finished and dev
is now updated, so you run:
$ git fetch
and see that your dev
is now behind origin/dev
. That is, you now have:
...--C--D--H <-- master
\
\ I--J <-- origin/dev
\ /
E--F--G <-- dev, feature-X (HEAD)
in your repository. You also have some things in your work-tree that are different from files in commit G
. (You might not have a branch named feature-X
yet, and have HEAD
attached to dev
instead. If this is the case, you can simply create it now with git checkout -b feature-X
, and now you match the picture.)
The thing to do at this point is to commit the stuff you're working on anyway. This makes one new commit K
:
...--C--D--H <-- master
\
\ I--J <-- origin/dev
\ /
E--F--G <-- dev
\
K feature-X (HEAD)
You can now fast-forward your own dev
to origin/dev
. The basic command method is:
$ git checkout dev # safe, since your work is committed
$ git merge --ff-only origin/dev # or `git pull` if you really insist
The drawing now looks like this:
...--C--D--H <-- master
\
\
\
E--F--G--I--J <-- dev (HEAD), origin/dev
\
K <-- feature-X (HEAD)
Here's where most people just run git checkout feature-X; git rebase dev
, which is fine and you should feel free to use that method. (I do that a lot of the time. Consider doing that followed by the git reset HEAD^
trick described below.) But what I sometimes do is simply rename feature-X
to feature-X.0
, and then create a new feature-X
with git checkout -b feature-X
:
...--C--D--H <-- master
\
\
\
E--F--G--I--J <-- dev, origin/dev, feature-X (HEAD)
\
K <-- feature-X.0
I am now ready to begin working on feature-X
again, and at this point I simply cherry-pick all the feature-X.0
commits:
$ git cherry-pick dev..feature-X.0
which produces commit K'
which is a copy of K
:
...--C--D--H <-- master
\
\ K' <-- feature-X (HEAD)
\ /
E--F--G--I--J <-- dev, origin/dev
\
K <-- feature-X.0
This works even if there are multiple commits on feature-X.0
:
...--C--D--H <-- master
\
\ K'-L' <-- feature-X (HEAD)
\ /
E--F--G--I--J <-- dev, origin/dev
\
K--L <-- feature-X.0
If the last commit of this new feature-X
(L'
in this version, K'
in the one that had just one commit) is really, seriously not-ready-to-be-committed yet, at this point I just use git reset HEAD^
(you can spell it HEAD~
if that's easier, as apparently is the case on Windows) to move the branch name back one step. This removes the end-most commit from the current branch, giving:
...--C--D--H <-- master
\
\ K' <-- feature-X (HEAD)
\ /
E--F--G--I--J <-- dev, origin/dev
\
K--L <-- feature-X.0
and leaves the work-tree "dirty" in exactly the way it was before I began this whole process. (In general, a partial commit is fine, as I'll use git rebase -i
later to clean everything up when feature-X
is mostly ready.)
If I have to repeat the process, for whatever reason, I rename the current in-progress feature-X
to feature-X.1
, or feature-X.2
, or whatever. I grow a small collection of feature-X
-es and occasionally go out to the branch garden and prune the weediest ones. The latest-and-greatest is still named feature-X
, but I have all of my previous work available as needed, up until I weed them out. This is better than either rebase or stash, because if the code is complicated and I miss something, I still have the older version, under a name that's recognizable, not just some incomprehensible hash ID in a reflog, and not a stash that's indistinguishable from a dozen other stashes.