Lesser known Git commands
hackernoon.com
hackernoon.com
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.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.
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.
Also works well for this use case
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.
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).
legit: https://github.com/kennethreitz/legit [1]
[Ignore the pretentious "for humans" tagline though...]
`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...
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.
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 :)
Total hack I know, but whatever.
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.
$ 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.
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.
- 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.
you must be one of those squash haters
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. 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`. rearrange = "rebase -i $(git merge-base HEAD @{u})"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
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)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.
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.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.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 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.
[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...
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.
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.
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.
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.
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.
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.
This article is a great way to find those hidden things.
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.
edit: downvote?
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 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`" 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!)While I'm aware of issues with rebasing the root commit, I've never heard this advice before and it seems unnecessary.
http://stackoverflow.com/questions/30277149/how-to-use-inter... goes into more detail.
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.
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.
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. :)
> 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.
(Personally, I think the right way to address this is for "git pull" not to implicitly create merge commits...)
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 alias such=git
alias very=git
alias wow='git status'
$ wow
$ such commit
$ very push
https://twitter.com/chris__martin/status/420992421673988096?... jschroeder@omniscience:~$ git config alias.up
pull --rebase
jschroeder@omniscience:~$ git config alias.down
push
They can be used thusly: git up && git down"Great now let me DOWNload the changes to another branch" git up
What?
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.
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.
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.
$ git help push-pull
`git push-pull' is aliased to `!git pull --rebase && git push'$ git rush
pull --rebase --stat
That gives me an idea of what has changed, Though I often follow with: git log @\{1\}..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.
[alias]
status = "status -sb"
because git just ignores those. i am then forced to setup non-standard commands just to bend git to my will.in some cases, there are distinct `git-config` options I can set to get the right behavior, but that's way less flexible since stuff like `--force-with-lease` don't have their own config options.
the rationale behind this is, according to the git-config manpage, that "To avoid confusion and troubles with script usage, aliases that hide existing git commands are ignored". but then there's --porcelain for that, so i don't understand that limitation.
sigh.
[alias]
pull = push
push = pull
:)Also grog is making me feel extremely groggy.
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)
I would have named it "git rum"
Not necessarily. This depends on what branch you're on as well as whether or not it's up to date with the remote.
alias grm="git fetch origin && git reset --hard origin/master"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"
mo = ls-files -m
lol = log --oneline --decorate --graph
`git mo tests/` gets you modified files for the tests folder.`git lol` I think as the command line version of gitk
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. git config --global alias.ca 'commit -am'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...
Should that maybe have been "recommend"?
Does anyone have any experience with version control tools that have worked out simpler to use than Git? I've heard good things about Darcs before (aside from its performance issues, which projects like Pijul are designed to address), are there any other tools that are worth investigating?
If you're by yourself and just need to keep a linear history all you need is 'git init' 'git add' and 'git commit'--if that's all you ever need, svn or any version control should work about equally well. But if you do need remote repositories, branches, rewriting your history, or commit hooks, it's there waiting if you're using git.
I feel like most of the different version control systems differ on those advanced features or how it scales for special needs. Git is great for decentralized and remote work, Perforce is better at handling histories with large binary files, etc. The basic features are fairly similar between them. Maybe there's a gui you like or a specific workflow, but I feel like that's a subjective choice.
That's not necessarily true. This can be done with:
git rebase -i --rootHere's some that I've created or collected over the years, in case anyone finds this useful:
https://github.com/randombk/randombk-dotfiles/blob/master/ma...
So then, whenever I need to get my copy up to date with upstream it's:
update = !git fetch upstream && git merge upstream/master'
It's probably easily changed to make the branch configurable, but I have only ever needed this with master. [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.Well, they might notice that the author and committer dates don't match, but you can add "--reset-author" to really make this undetectable.
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 (c)ommit --(a)mend --(n)o-(e)dit
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.stp = !git stash && git pull && git stash pop