141 comments

[ 3.3 ms ] story [ 158 ms ] thread
Warning: not actually git commands, rather TFAA's aliases for possibly useful combinations of switches and/or commands.
See, switches in git are there because git maintains backward compatibility. Sometimes a switch really would logically be better as a whole new command. But it's a common refrain to hear that git hides useful things in the switches.

This article is a great way to find those hidden things.

> See, switches in git are there because git maintains backward compatibility.

Switches are there because git commands tend to greatly leak the abstraction and group behaviour in terms of the underlying plumbing, and because Git maintainers most likely would rather add new switches to existing commands than new commands intersecting with existing ones.

> Sometimes a switch really would logically be better as a whole new command.

Which could trivially be done without BC issues and thus can't be an explanation why existing commands keep growing new switches.

> This article is a great way to find those hidden things.

It would have been a useful article if it had been about showcasing interesting or useful git switches.

Alternatively, read the documentation and know how to use your tools instead of reading blog posts that lists a few things one person has found useful.
Yes, the title is misleading.

edit: downvote?

Agreed - these aren't lesser known git commands at all.

The switches given to git commands in the article might be lesser-known, but title could be clearer that this is:

    > Some suggestions for git aliases
I should really read more of the git doc but at least I learned about aliasing in the gitconfig from this even if, I too, was expecting actual git commands
Some of the aliases are funny but could be renamed for brevity.

Also grog is making me feel extremely groggy.

"commend" seems especially bad, it's both unclear and longer than "amend" would be.

My aliases list is pretty small, aside from amend I have

* get (pull --ff-only --all -p), basically a hard sync with all remotes, updating the current branch if there are no conflicts or divergences

* summary (log --oneline --no-merges), given a range from git fetch/pull, provides an overview of the commits you just fetched

* original (!git show $(cat .git/rebase-apply/original-commit)) during a rebase conflict, shows the original commit whose application failed (it can be difficult to untangle a rebase conflict, this sometimes helps a lot)

It make me think of pirates drinking their grog.

I would have named it "git rum"

I like the `git grog` command, I might actually use that.

The idea of `git please` is not bad but it made me wonder if it could be possible to go even further.. a variant on --force that only force-pushes IF NOT ONE HAS PULLED YET.

I can see why this wouldn't be built into git core however, as it's quite stateful and depends on tracking other people's fetches, which probably isn't easy.

My personal favorite two:

    jschroeder@omniscience:~$ git config alias.up
    pull --rebase
    jschroeder@omniscience:~$ git config alias.down
    push
They can be used thusly:

   git up && git down
While these aliases are useful, I'm confused why "down" means "push".
Well, if you think of yourself of the ruler of the repo and "pushing down" to the plebs, it makes sense ;)
"The enemy gate is down."
I love the implication that the upstream repo is the enemy, so you mentally put it down. Way to go Ender.
I would name them "down" and "up" instead.
"Here let me UPload the local repo to our git server" git down

"Great now let me DOWNload the changes to another branch" git up

What?

I think the 'up' is as in 'update' rather than 'upload', but I agree that the use of 'down' makes that confusing.
Years ago... I was a pretty prolific svn user. Coming from svn, I knew that it would fetch changes from the central server (remote in git parlance) and then rebase them with whatever you had in your local tree. This is where git up came from, literally an equivalent of svn up.
I like where your head's at and added a little something to help me here at work:

    void:~% git config alias.funky
    ! git up && git submodule update
And speaking of lesser known commands, I've been using 'git worktree' recently at work and I'm a big fan. Admittedly, it's probably mostly unknown because it's a new feature.

It's a little rough around the edges, but it has made things like customer support context switching much easier since I can leave my current development tree alone and just create a new worktree for the current escalation.

Could you share your workflow with worktree a little? What kind of development do you do and where does it fit in with your workflow?

I tried using it in a JS project for those times when I'm in the middle of work but someone asks "hey is master working fine on your system?" - eventually though, having to install all the node and bower modules and rebuild everything on the worktree didn't seem much smoother than just stashing my local changes.

Well, admittedly my current situation lends itself well to 'worktree' where normally a stash is probably just fine.

At the moment I'm doing a big rework (porting our software from a 10 year-old OS to CentOS 7), so I have many, many modified files (all tiny mods to fix new compiler warnings from new toolchains) and untracked and obsoleted files.

Basically, it's too much for me to keep track of if I change anything, so it's a case of "whatever you do, don't touch" that pushes me toward the worktree solution.

I've been pulled in to work on customer escalations on released code (one instance from 2 releases ago), so it's just easier for me to create a whole new worktree.

I imagine that once I'm back into a normal dev cycle that I'll be back to stashing.

I would name them the other way around since I'm pulling "down" and pushing "up" ....
I always do this together, so I just use `git push-pull`

  $ git help push-pull
  `git push-pull' is aliased to `!git pull --rebase && git push'
same functionality, my alias is "rush"

$ git rush

I have up aliased to

  pull --rebase --stat
That gives me an idea of what has changed, Though I often follow with:

  git log @\{1\}..
Didn't know about those stash options. I've always used 'git stash save -u' to bring along the untracked files in the stash, that's the more common scenario.
I just usually do something silly like 'git add . && git stash', i had no idea it was built in...
My most useful bash alias (when you go back to master and want to start at the most up to date everything before creating a new branch):

  alias grm="git fetch origin && git reset --hard origin/master"
same here but I switch to master first:

resync = !git reset --hard HEAD && git checkout master && git reset --hard origin/master && git pull

or with a parameter to do the same with any branch:

resync = "!f() { git reset --hard HEAD && git checkout $1 && git reset --hard @{u} && git pull ; }; f"

I use a very similar version of this, and people are often surprised that I'm working directly on master. Well, when it's ready to go, `git checkout -b feature/name; git push origin HEAD; git resync`.
> so it’s good practice to create an empty commit as your repository root

While I'm aware of issues with rebasing the root commit, I've never heard this advice before and it seems unnecessary.

There's also the `--root` flag for rebase, which solves this problem without needing the dummy commit. I think this section of the article is out of date.
I actually like to make an empty root commit just as a place to describe what I intend to do with a repo. I'm uncomfortable doing it as a README commit because that lends it an undue amount of permanence once work begins.

Occasionally on repos that are really just for me I even do this as a 'root' commit for work branches, but the way rebase rolls over empty commits makes that kind of problematic in the long run.

It also helps if you want to do an octopus merge of branches that otherwise have no common ancestor. If it comes to it, you can use git replace to provide a false parent for the root commit.
I've seen more stash accidents than any other kind with git. Stashing is more dangerous than committing or branching, and to me it doesn't seem to provide any advantages... do people actually find stashing easier than branching? Is it just because when you branch you have to name it, and that causes friction? I stay away from stash.

Right from the man page: "If you mistakenly drop or clear stashes, they cannot be recovered through the normal safety mechanisms."

https://git-scm.com/docs/git-stash

--

I do like shorty. My version:

  alias gits='git status -sb'
--

My next fave is rebase the current branch against it's upstream branch point:

  [alias]
    rearrange = "!git rebase -i $(git merge-base HEAD @{u})"
git rearrange ftw.
I use stash when I forget to branch. So I might be working and then realize I never branched. So I stash my changes, create a new branch, and then pop my changes on the new branch.
Makes sense, but fwiw, there's a safer alternative: branch immediately, then go back to the source branch and reset.

There's nothing wrong with your workflow, btw, stash works in that case, as long as you immediately pop & don't get interrupted by lunch or a phone call.

Stashing is obviously providing some value that people like, it's very popular. But considering that it has extra danger, and that it's really easy to work without stash, I'm still confused why it's so popular.

And fwiw, most of the stash accidents I've seen are fairly simple scenarios like you described, and some teeny tiny twist or interruption causes someone to get confused and lose their stash, or think they lost their stash, and panic.

I'm not a git expert (yet), but I agree. I've read the stash man page a few times and tried to imagine when I'd want to use it over the alternatives, but I never do.
Why stash? Why not just branch then commit?
Because you might be currently not at the place where you intended to branch off. For example trying to make a bugfix on an experimental feature branch, but then you realize you should branch it off master.
So branch to a temporary branch, commit, checkout the branch you wanted to be on, cherry-pick the temporary branch to grab its head commit, then delete the temporary branch once you're sure you don't need it.

This isn't completely safe because deleting a branch also deletes its reflogs, but it does avoid use of the stash command at all, which I find good for my own safety (because it reduces cognitive load). And it scales obviously to making multiple commits onto the temporary branch, etc.

Or even just branch, commit, rebase onto the desired branch.
My #1 use of stash is as the quickest way to express "get rid of all this crap". I rarely intend to ever retrieve it.
git checkout -- .

Also works well for this use case

'git reset'.
Doesn't do the same thing, without some more options. Stash is shorter.
Now here's one I actually like. As an alternative to git reset, a git stash is safer, and gives you an easier way than reflog to recover if you dropped a commit you want back.
For one of my projects, the 'build process' requires that I use the git archive command (basically archive to .tar.gz without any git-related files/directories). git archive on its own is fine for final builds because it's based off of HEAD for the current branch, but if I want to test a build with working changes, I would either need to commit all the changes _or_ use git stash and then archive based on the state I just stashed.

So in this case git stash works a lot better for a 'temporary' commit, kind of deal.

It might not be optimal but I've found the process to work pretty well for this particular project's situation.

If you're stashing your changes, that means they're just in the working copy and haven't been committed to the incorrect branch.

In this case, simply create the branch and switch to it (git checkout -b branchname) and you're good. It's like it never happened ;) (remember that branches are just labels - until you commit it doesn't really matter).

Typically once I've noticed I haven't branched is after I've already added and committed.
Then just create the branch where you are, and afterwards go back and reset the original branch
and make sure to patch up the upstream reference
I use `legit` aliases for this. It allows me to do `git sprout <new branch name>`, where it stashes my untracked changes, create a new branch, pop the stash and applies it. Pretty handy!

legit: https://github.com/kennethreitz/legit [1]

[Ignore the pretentious "for humans" tagline though...]

`git sprout <branch-name>` looks identical to `git checkout -b <branch-name>`.
Why not branch some moment after realizing you forgot to and then go back to the (now gone a bit far) original branch and reset it to the original commit?

Simple and does not require any arcane tricks.

While it's true that they can't be recovered through the "normal" safety mechanisms, they can certainly be recovered - that too up to 30 days in most cases.

`git fsck` can help here [0]. Also when a stash is dropped, it's SHA is printed to the console. If the console buffer hasn't been cleared and you realise it soon enough, recovery can be quite straightforward.

[0]: http://stackoverflow.com/questions/89332/how-to-recover-a-dr...

Yep true, and to be fair to the man page, that's the next sentence after the one I quoted.

But, fsck is a big hammer, and most people I've had to help recover their stashes completely glaze over when they see commands named fsck and reflog. Some people when faced with fsck opt to give up and recreate the lost changes, it's that intimidating.

And why not just avoid accidents? git's role in life is to be a safety net. Stash is hanging halfway over the edge of the net. Yes it's still possible to recover from accidents, but it's harder. That alone makes it more dangerous. It should be hard to screw up, not hard to recover.

You're right of course, but whereas for a lot of purposes I understand the impulse of "I don't have time to learn this". For so many things that's the right call, but for a developer using git I would like to insert the brain worm that maybe it isn't.

I consider git to be on the short list of career-long tools. The rest of the list is linux, bash and vim. Knowledge of these things has a long half life and will serve me across jobs. Even a language I've used for 10+ years in my daily work like Ruby is more likely to become obsolete within my career than git. And the thought of using git without understanding reflog is anxiety producing. The beauty of git is not in memorizing its arcane syntax, it's in understanding the elegant data model.

That's my case for why every developer should at least know git-reflog. git-fsck I have to admit never having used :)

personally I rarely stash and often it's just to get rid of a bunch of crap. rather than googling reset --hardhatheadhenry, I stash and drop.

Total hack I know, but whatever.

> do people actually find stashing easier than branching?

Branching wouldn't be enough, you'd need to commit your entire working copy to approximate a stash.

I'd say it's generally a bug-prone mistake any time you commit your entire working copy (rather than staging lines/hunks on a one-commit-per-feature/bug basis).

So branching and committing and pushing into a `backupForTomorrow` branch is fine, but you have to be sure to get rid of that commit via something like a mixed reset when you start working on it again.

If that temporary backup commit somehow stays around in the final feature branch, a stash would be much preferred.

Committing your entire working copy to a separate branch can work just like a safe stash:

    $ git branch
      master
    * cool-feature-1
    $ git checkout -b stash-2016.09.30T01.10
    $ git add -A; git commit -m "stashing WIP for later; maybe add some commentary to remind myself what I'm doing"
    $ git branch
      master
      cool-feature-1
    * stash-2016.09.30T01.10
    $ git checkout master
    $ git branch
    * master
      cool-feature-1
      stash-2016.09.30T01.10
    # do other things
    # later...
    $ git checkout cool-feature-1
    $ git merge --no-commit --no-ff stash-2016.09.30T01.10
    Automatic merge went well; stopped before committing as requested
    $ git reset
    $ git status
    On branch cool-feature-1
    Untracked files:
     ...
And now your old WIP is back, ready to keep working, and in the meantime you could have pushed up your "stash" to a remote and grabbed it back.

There might be something terribly wrong with this, but if there's not, then I don't understand why git stash doesn't do something similar to this by default.

> Branching wouldn't be enough, you'd need to commit ...

Yes, you're right. I mentioned both committing I branching. I imagine commits only as the alternative to a single stash/pop, and commits & branching as the alternative to using multiple stashes.

> I'd say it's generally a bug-prone mistake any time you commit your entire working copy

The workflow I'd use instead of stash is to commit, do things, then reset --soft. Exact same effect as stash, except that it's less bug-prone, there's an easier & safer recovery mechanism in case of problems.

> If that temporary backup commit somehow stays around in the final feature branch, a stash would be much preferred.

You're comparing making a small mess to losing work. Your opinion is your own, I'm not here to argue, but in my book the downside of losing work while using a revision control system is much larger than the downside of accidentally preserving imperfect commit refactoring. You're preferring something superficially nice over the actual safety mechanisms that is git's sole purpose.

I use stash in one of the two workflows below, neither of which have ever caused problems. Stash's advantage is in not needing to create branches, commit, or un-commit the code back into your working copy. Very quick, and I've never had problems.

- Paired operations `git stash` / `git stash pop` to quickly get rid of everything then reapply it a few minutes later.

- Stash as a "I probably don't want this code, but may as well keep a stashed copy" alternative to a hard reset. These stashes all then show up in my SourceTree sidebar to easily peruse or apply if needed (or copy/paste individual lines). Not a big deal if lost.

If I'm actually backing up code that I'll likely use, or need a remote backup of my current working copy to protect against laptop theft or HDD failure, I'll branch/commit/push the backup. Then use a mixed reset later to get rid of the commit itself and return everything to the working copy.

there's no sense arguing. to many git users I have met, the software isn't the product, the git repository is. I have met someone who will happily spend an entire day refactoring all the commit boundaries by teasing apart and combining diffs so that they match some retroactive narrative that looks prettier.

you must be one of those squash haters

I mostly only stash when I want to rebase -i because rebase -i seems to complain when I have uncommitted changes.
That's a good example of what I'd consider to be a mildly unsafe use of stash, since if anything goes wrong you can lose unsaved changes. It's easier to forget what you're doing if you have merge conflicts during your rebase, or end up stashing something else while you rebase.

The safer alternative workflow is:

  git commit -am "WIP"           # Just throw your uncommitted changes into a commit
  git rebase -i origin/master    # you might want to checkout my git rearrange command above
  git reset --soft HEAD^         # now un-stage your uncommitted changes and keep working
The advantage of this is getting your uncommitted changes into a commit that can be easily recovered if anything goes wrong. Even though you get rid of the commit, this process is all stored in the reflog, and you can undo any of it easily and without resorting to fsck.
You might check out "git rebase --autostash" if you haven't already.
In regards to stash, yes, loss is problematic, but one can simulate it by saving the diff to a file:

  git diff > my-stash.diff
Then you can even commit these if you like, or copy them to a safer directory. Applying them is just an invocation of `patch`.
Or even better than patch, git apply.
Why did you write your rearrange with the ! form? The whole point of the leading ! is to say "don't prefix my alias with `git`", but then you go ahead and prefix your alias with `git`. You should be able to write that as

  rearrange = "rebase -i $(git merge-base HEAD @{u})"
Ha! You're right, that's silly, thanks! I didn't even notice. I don't remember why, it's been a long time, but I think I had a tricky pipe command to figure out the upstream branch-point a long time ago before I learned about merge-base and @{u}.
I was under the impression that you needed to use !-form if you wanted git to use the shell itself, and that not using it basically just does an exec on the subcommand? Is that inaccurate?
Hmm, maybe you're right. I don't know anymore. I thought it just prefixed aliases with "git " and otherwise ran them as normal, but I admit I haven't researched this topic in a long time.
Stashing is great

Because some things are only possible while there are no changes. (I changed this one-off file for testing or debugging something, need to pull latest changes, so I stash)

Stashing comes from a tool called quilt https://savannah.nongnu.org/projects/quilt

My IDE stashes things behind the scenes quite often, allowing me to do many operations on "dirty" workspaces.
Do people really use stash for things they value longer than a few minutes?

I've only ever used stash as a way to store changes that I made on the wrong branch.

  git checkout my_old_branch (wrong branch not realized yet)
   - work work work...
  git status
   - "oh, I'm on the wrong branch"
  git stash
  git checkout the_correct_branch
  git pull
  git stash apply (or git pop - but you lose what was on the top of the stash)
> Do people really use stash for things they value longer than a few minutes?

You'd be surprised. ;) Even a few minutes can get you into trouble though. The very last person I helped out of a stash crisis two days ago did exactly what you outlined, fully intending to stash pop immediately, and couldn't find his change and panicked.

What actually happened was he'd unknowingly stashed again during his branch switch, and his first stash was there, but not on the top of the stack. GitHub desktop was auto-stashing or something. (GitHub desktop has made several very strange choices...) Anyway when my friend applied an empty stash, he thought he'd lost his changes. He called for help on a Saturday, and I tried to point him in the right direction over email, but by the time I got to a terminal to see what happened, he'd already rewritten most of his change, having decided it was a loss.

OK, FWIW here are some safer alternatives to switching branches without stash:

- First thing to know is that you can switch branches with uncommitted changes in your working tree. You don't have to do anything else, git will protect against conflicts, so why not always try this first? It's less work most of the time.

If git complains, you can:

  git commit -am "WIP for the_correct_branch"
  git checkout the_correct_branch
  git cherry-pick my_old_branch
  git reset --soft HEAD^  # Optional, if you want to unstage
  ... Later ...
  git checkout my_old_branch
  git log
  ... Notice the leftover commit ...
  git reset --hard HEAD^
Or here's an alternative that doesn't leave the dangling commit for later:

  git commit -am "WIP"
  git branch work         # you can always do this with uncommitted changes
  git reset --hard HEAD^  # still on my_old_branch here
  git checkout the_correct_branch
  git cherry-pick work
  git branch -d work
If anything goes wrong with these workflows, my changes have been dropped in two branches, they'll be easy to find in the reflog, and the gc timer doesn't even start yet because they're still referenced. It's really hard to lose the changes this way.

The only option with the stash flow if the stash gets lost while switching branches is to use fsck, and the gc timer starts immediately because the stash is not referenced.

I mention the gc timer because some of the accidents I've seen are people popping stashes and only realizing later that they popped a different change than they thought they did.

They way this happens is people who are beginners to git. Once you know git, it's hard to imagine popping the wrong stash and not knowing it, but I've seen it happen multiple times. This is why I advise everyone to avoid stash - for beginners, it looks deceptively and attractively simple, but is easy to get in hot water. It's also another git subsystem to learn, as if git wasn't hard enough to learn. For git experts, it's so easy to avoid using stash, there are always safer alternatives.

I tend to stash debug code, so for example I'm working on a feature, and while developing I've added a bunch of console.log()s (or pprint.pprint()s, print_r()s, whatever). Then I can do:

    git add --patch  # include only the code I want comitted
    git stash --keep-index
    # now I can do some tests without my debug cruft
    git commit
And then ship the feature to staging/production, if there are any remaining issues with it I still have my debug cruft in the stash. It's rarely needed, so I don't want the overhead of a branch for it. If I lose it it's also easy enough to just add the debug code I need at that moment in case the stash was cleared.
> do people actually find stashing easier than branching?

Absolutely. I stash things all the time. I find feature branches only useful for pull requests. My own code never uses feature branches unless I'm unsure of where I'm headed with the feature—I'll need to continually come back to it and build it a little at a time. But that's very rare.

> Right from the man page: "If you mistakenly drop or clear stashes, they cannot be recovered through the normal safety mechanisms."

Then don't ever use those commands. I don't. Checking one of my repos:

    $ git stash list | wc -l
          46
I just let the stash grow unbounded. Usually it's for cleaning up unfinished features or debugging when manipulating the repo (in which case it gets popped almost immediately). But often I'll just hit a point where I don't care about what I have too much, but I think it might have promise in the future and so I'll just stash and start over. One of the wonderful git commands is "git stash list -p", letting me search the diffs for code I know I've written at some point.
One of the hardest thing about git is the multitude of options and workflows. It makes it powerful but also intimidating for newbies - so many commands and flags!

Having several years of git experience, I've set up on the following workflow, which avoids stashing and mixed resets. I think it's quite safe and simple.

1. using feature branches for everything (except when I'm the only developer in a given repo); I have `goto` and `forkto` and aliases for git checkout / git checkout -b

2. frequent, small commits - for backups, and to have "recovery point" for this scenario when you change one line and everything's on fire and you can't find out what's going on for next one hour

3. try to never do partial commits, always commit everything ( git add -A . && git commit ). This teaches you a discipline to do one thing at a time. If you have a sudden need to do something else, git branch, git commit, git checkout previous-branch. Or git commit, continue working, git rebase -i to reorder/squash commits later if needed.

4. for "work in progress, will come back later" commit I have an alias "wip" = "git commit -A . -m 'wip'"

5. for permanently throwing away stuff, "git reset --hard" (used rarely)

6. since I do lots of small commits, at the end of development I clean up with `git rebase -i`, squash stuff, improve commit messages if needed, maybe reorder etc.

Due to 2 and 3 I basically don't use stash at all. I think however not everyone is so keen on interactive rebase as much as I do :)

> I find feature branches only useful for pull requests

I wasn't super clear in my top post, sorry. Mentioned a couple times in the thread, but as an alternative to a single stash, I would normally commit, not branch. Consider whether a WIP commit is that much easier than a stash, given that a commit is harder to lose.

I mentioned branches because that's the alternative to a multi-stash workflow, or to having a large time gap with a lot of work in-between stash & pop. Otherwise, my question is how much harder, really, is git commit & git reset --soft, compared to git stash & git stash pop?

> Then don't ever use those commands. I don't.

That seems like a great way to use stash safely. A couple others here mentioned using stash as an alternative to git reset. I'm in favor of that kind of stash usage, it's even safer & easer than losing something to a premature reset.

I have more than 400 stashes… it turns out it's quite often that I find my current approach completely wrong and want to do a hard reset. But I don't do a hard reset but I stash it so during my next attempt I can still see it.
yeah, I find myself more doing a local commit with message WIP. I switch branches to do something else, come back, look at the log and if WIP, roll back the last commit
In Chromium (well, actually depot_tools[1], the closest thing Chromium has to a developer SDK) we provide `git freeze`[2] and `git thaw`[3]. These behave similarly to 'stash', but instead of taking your content and putting it who-knows-where, they commit it on top of your current branch in a special FREEZE commit (or multiple commits, if you have both staged and unstaged changes) which they then know how to thaw out again. Our other tools (such as `git rebase-update`[4]) also know how to deal with FREEZE commits, and can automatically thaw them for you when you go back to working on that branch.

[1] https://chromium.googlesource.com/chromium/tools/depot_tools... [2] https://commondatastorage.googleapis.com/chrome-infra-docs/f... [3] https://commondatastorage.googleapis.com/chrome-infra-docs/f... [4] https://commondatastorage.googleapis.com/chrome-infra-docs/f...

Brilliant! I wonder why git stash wasn't implemented similarly in the first place.
My hypothesis: git was implemented assuming that—at least for large projects—you would have a local on-disk bare clone of the repo, and then multiple workdir checkouts of that bare clone sitting on different branches. The setup with a single clone containing a workdir with a .git dir inside it is a hack that's nice for "on-the-go" use-cases, but wasn't intended for serious developers contributing many changes to the same codebase.

git-stash, then, is only for the use-cases that are not better solved by just leaving your WIP workdir as it is and checking out another, concurrent workdir for your new feature. Such as, for example, taking code you were accidentally writing on one branch, and "shifting it over" onto some other branch; or "moving your changes out of the way" to run some tool that is broken by them, then putting them back in place, because you really still are working on them.

Er... but then you have (N+1) checkouts of the repository where N is the number of workdirs, no? How does this even work with submodules? Will I have a copy of the submodules tree in each workdir? Our submodules tree is like 4Gb at this point, I can't very well have 5 of them. Disks are large, but not yet infinite.
Directory hard-links?
This is how you have to deal with branching in almost every scm. Changing branch might require you to rebuild something slow and then it's good to have multiple working directories. If you have monorepos you might also want to test component a on branch x vs component b on branch y etc.

If you are only working serially then switching branch in one working dir is obviously easier and to be preferred but some times you really need multiple checkouts of the same repo.

Honestly I stash only cause I don't fully understand how to branch and bring my changes with me when I realize I should have branched earlier
This is a great answer, honestly, and you're not alone.

FWIW, here's how. Since stash only captures uncommitted changes, I assume you're working on master and want to create a feature branch, and you've already made some changes for the branch.

  git checkout -b myFeature
That's it. Uncommitted changes aren't in a branch yet, so they don't have to be stashed before you make a new one. You can always start a branch from wherever you are, and switch to it, and your changes will be right there. Now you can commit, and your commit will be on the branch myFeature.

BTW, you can switch branches without stashing too. Git will prevent you from messing up your uncommitted changes. If that happens, then you can commit or stash first. The safest course of action is always to commit any changes first before you do anything.

It's also easy to build your feature branch if you have both committed and uncommitted changes already, or if you want to base your feature branch off a different branch than the one you're working in. I'm already spamming you, so just say so if you'd like me to outline those.

Thank you for explaining this! I didn't know that and will start incorporating that into my workflow :) thanks for teaching me something new!
I frequently swap between branches, for code review, or to move temporarily to something else for a short time.

I decide whether to commit what I have done or whether to stash it for now, based on what state it is in.

If I am still in the exploratory stage of the work, I usually wont commit, I will just stash and come back to it later.

Its a useful thing to be able to do at time when i am not yet ready to COMMIT to the changes I have made.

Of course I can't tell what your comfort level with git it. The more expert you are, the more it doesn't matter, and you can use stash safely.

I am not judging anyone or their workflows. It's the stash command that has the problem, because it circumvents git.

That said, my reaction to your comment is that you're taking "commit" too seriously. ;) Nothing in git is a commitment until you push. Nothing. If you haven't heard the phrase "commit early, commit often", consider that and take it to heart. If you have work you care about and even might want to save, then you should commit it. Git was literally made for the purpose of managing multiple versions of exploratory work -- to not use commits and branches for that purpose is to not use git to its full potential.

Stashing things you care about for some later time strikes me as the kind of use of stash that is the most prone to data loss. But again, if you're comfortable using fsck, none of this applies to you.

If code is important enough to me that I care whether or not I lose it, I commit and push.

I prefer to commit discrete change sets (not features, or stories, just a discrete set of changes that achieve a piece of what I am wanting, dont break the code base and dont break the build).

I have heard that phrase, I live by it and encourage others to do the same. it is especially important when working as a part of a bigger team on the same area of the code.

Teams that commit early and often are happy teams :)

OTOH I use stash constantly, multiple times every day, when switching between branches, because sometimes its just handy to be able to put stuff aside to look at something else.

I dont recall ever losing code because of it, but it is certainly possible and its not hard to see how it could happen.

Heres one I like to use sdiff = diff --ignore-space-change
> If in doubt, the long one (git staaash) will always restore your worktree to what looks like a fresh clone of your repository.

Not necessarily. This depends on what branch you're on as well as whether or not it's up to date with the remote.

I'm no Git wizard, but switching from "merge" to "rebase" and from "add <file>" to "add -p" cleaned up my repos quite a bit.
One reason I like git so much is that right there. The author is interested in reasoning about what branch certain changes came from, but I have to wonder why. Once changes are squashed and ff-merged, it's pretty clear where they came from.
One of my personal favorite:

stp = !git stash && git pull && git stash pop

If you `git pull --rebase` by default, just set the rebase.autoStash config to true and git will automatically do this for you.
Can't believe I just learned about rebase.autoStash! That is super handy.
> git it and empty root commit

Why doesn't git init does this by default? It would be nice if every repo had an empty root commit (with no author, date,... so it has the same commit hash), then every two repos would have a common commit. Semantically it would mean that repos would be just specific branches of a hypotetical large repo.

That would prevent merging a second repository into the first in its entirity.
You can already merge two divergent histories with the --allow-unrelated-histories option. (At the data level, merge commits don't care that their parents have a common ancestor, but the git merge command does this as a safety check.)

In other words, all repos already are subsets of the ur-repo. The semantically-interesting data in git is not the commits themselves, but the branch names given to specific commits in specific (groups of) repos. :)

Interesting, now I learned that a git repo can have any number of commits that have no parents. It can be achieved by fetching an unrelated repo and merging with --allow-unrelated-histories or by using git checkout --orphan.

> safety check

Why is this considered unsafe? Only to avoid pulling from unrelated repos by accident? The man says for --allow-unrelated histories:

> As that is a very rare occasion, no configuration variable to enable this by default exists and will not be added.

Well this is quite harsh, it looks like it's considered to be rather unsafe.

Yeah, I imagine it's unsafe because "git pull" implicitly creates a merge commit when it can't otherwise update, and your remote URL might now be pointing at a different history. Maybe github.com/someproject/somecode got replaced by a clean rewrite and you're actually looking for someproject/somecode-old. Maybe you were pointing at an unofficial git-svn import and now there's an official git migration. And so forth.

(Personally, I think the right way to address this is for "git pull" not to implicitly create merge commits...)

The first commit of a repository can not be rebased like regular commits

That's not necessarily true. This can be done with:

  git rebase -i --root
This doesn't even look like I wrote it, but I thought I did and use it all the time: "What was that (private) branch I worked on a few weeks ago?"

Just put this shell script in /usr/local/bin or wherever as "git-branch-dates"

  git branch-dates
  #!/bin/bash

  for k in `git branch|perl -pe s/^..//`;do echo -e `git show --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k|head -n 1`\\t$k;done|sort -r
A couple of aliases I use all the time:

    [alias]
        graph = log --oneline --decorate --graph --all
        copy-logs-since = !git log --reverse --format=%B "$1".. | pbcopy
`graph` is similar to `grog` in the article. `git copy-logs-since <tag>` copies log messages since the tag, which I use for release notes. Very handy.
Nothing esoteric, but I advise every person I teach git about to put this in their .bashrc:

    alias s='git status'
    alias l='git log --graph --oneline --decorate --all --show-signature'
    alias d='git diff'
    alias dc='git diff --cached'
    alias c='git commit -m'
    alias ca='git commit -am'
    alias a='git add -p'
    alias u='git checkout -p'
These probably saved me hours of typing by now. Especially `l` I think should be canon.
I don't know if you're aware, so this is just a friendly suggestion: if you define these as git aliases instead, you won't use your shell's namespace and have it available for other aliases. As git aliases you would run it like `git s` or `git ca`. If you also alias git to g it will be `g ca`. If you do that, don't forget to call `__git_complete g __git_main` in .bashrc for it to auto-complete aliases for `g` and not just `git`. For example, to define `ca`, run this

    git config --global alias.ca 'commit -am'
I am of course aware of git aliases (e.g. because OP uses them a lot) but it never occured to me specifically to shorten git to g and find a way to keep autocomplete working.

However, since these are by far my most typed commands (specifically, probably 40% of my executed commands are `s`), I strongly care about the difference even between 1 and 3 keystrokes.

Thanks for your feedback. I wonder if the downvote was because of using bash aliases or for something more substantial...

I never downvote anything, so I'm not sure, but maybe the use of "advise" and "canon" triggered negative votes?
Thanks (if you're still reading this thread), I didn't know "advise" carried negative connotations. I guess it's one of those words that I learned from a dictionary and never stopped to notice that English speakers use it only in much more formal situations than the word in my native language that I'm trying to evoke.

Should that maybe have been "recommend"?

English isn't my 1st language either, but the power of downvotes is misused a lot on the internet, especially when there's no public trail who downvoted. Advise and canon may just carry too strong of a meaning for those who grew up in English-speaking school systems.
I like "git ready":

    ready = !git checkout master && git fetch -p && git merge --ff-only
The use case is for when you're done with a particular feature branch and ready to start something new. It puts you back on master and updates it in a safe way.
git config --global alias.stache stash
You baitin... how is a lesser known git command a git alias?
Author's "commend" function I just call "cane"

git (c)ommit --(a)mend --(n)o-(e)dit

A few that weren't mentioned below:

  git alias start 'checkout @{u} -B'
All my branches track origin/master (rather than local master), which makes it easy to git push / git pull without extra arguments. This will checkout a new branch from whatever the current branch is tracking. Usage: `git start my-new-feature`

  git alias track 'track = branch --set-upstream-to'
Sets up your current branch to track some other branch. Usage: `git track origin/master`.

  git alias gcbr 'gcbr = !git branch --no-track --no-color --merged | sed 's/[ *]*//' | grep -v master | xargs -n1 git branch -d &> /dev/null || exit 0'
"Garbage Collect BRanches" will delete any branches which are already merged into your current branch (excluding master). Basically, any branch which is "safe" to delete. (This one has been handed down through the ages; I initially found it on the skeleton dotfiles when I started working at Facebook. Thanks, whoever!)
nitpick: I don't think you need --no-track in `branch --merged` as no branch is being created with that command.

    git diff origin/master -- test.file
It will show you a diff of a file between the master branch of remote origin and your local branch. I use it way more than I can think of.
Some of my aliases, first some basic shortcuts:

  freshen = commit --amend --no-edit --date=now
  cont = rebase --continue
  br = branch --column
  recent = branch --sort=committerdate
  idiff = diff --cached
  ff = merge --ff-only
  co = checkout
Show what's left to do in an interactive rebase:

  todo = !cat `git rev-parse --git-dir`/rebase-merge/git-rebase-todo
Set tracking to to origin/<the same branch name>

  upstream = !zsh -c 'git branch --set-upstream-to=origin/$(git symbolic-ref --short HEAD) $(git symbolic-ref --short HEAD)'
I use this to swap my work to home e-mail or vice-versa if I've got it wrong. Need to specify an initial commit.

  allmine = filter-branch --env-filter 'GIT_COMMITTER_EMAIL=my@email.com GIT_AUTHOR_EMAIL=my@email.com'

  vim = "!gvim `git ls-files -m`"