Git tips and tricks
blog.gitbutler.com
blog.gitbutler.com
I have to admit that I learned a lot of these things fairly recently. The large repository stuff has been added into core piece by piece by Microsoft and GitHub over the last few years, it's hard to actually find one place that describes everything they've done. Hope it's helpful.
I've also had some fun conversations with the Mercurial guys about this. They've recently started writing some Hg internals in Rust and are getting some amazing speed improvements.
I'm also thinking of doing a third edition of Pro Git, so if there are other things like this that you have learned about Git the hard way, or just want to know, let me know so I can try to include it.
https://mergebase.com/blog/doing-git-pull-wrong/
TLDR: don’t be afraid of rewriting history but ALWAYS do “git pull -r —autosquash “
One question that I have is what is happening to large file support within Git? Has that been merged into the core since Microsoft changes have also made it into core. Obviously there is a difference in supporting very many small files or a few very large files but won't it make sense to roll LFS into core as well?
There was a discussion very recently about incorporating Rust into the Git core project that I think had a point about LFS then being viable due for some reason, but I'd have to find the thread.
For example, I couldn't understand why the deletion/dropping of a commit during a rebase caused changes to all subsequent commits. After all, I only asked for a snapshot to be dropped. I didn't ask for the subsequent snapshots to be modified.
Eventually, I figured out that it was operating on diffs, not snapshots (though storage was still exclusively based on snapshots). The correction on that mental model allowed me to finally understand rebasing. (I did learn later that they were 3-way merges, but that didn't affect the conclusions).
That assumption was eventually corroborated somewhere in Pro-Git or the man pages. But I couldn't find those lines again when I searched it a second time. I feel that these operations can be better understood if the diff/patch nature of those operations are emphasized a bit more. My experience on training people in rebasing also supports this.
PS: Thanks for the book! It's a fantastic example of what software documentation should look like.
The snapshot include all the history that led to the current snapshot. So even if you did a squash instead of dropping, you're changing everything that depends on that
Git snapshots don't contain any history, other than the commit chain (reference to the parent commit/s) in the commit object. While the storage format is a bit complex, they behave fundamentally like a copy of the working tree at the point of commit.
> So even if you did a squash instead of dropping, you're changing everything that depends on that
Squashes don't change the subsequent commits/snapshots either, other than the commit ID and chain. The tree itself remains untouched. You can verify this.
https://jvns.ca/blog/2024/01/05/do-we-think-of-git-commits-a...
I guess while it's true the storage layer is snapshot based, as you say, that only gets you so far conceptually, and it's probably best to focus on the _operation_ you're doing, as rebase, cherry-pick, apply-patch, etc are easier to think in terms of diffs.
When I used to use Phabricator, the fact that I could always fall back to handing it a raw patch file to submit changes also made it easier to reason about (regardless of what the server and client were actually doing).
most of the time trying the main URL + /rss works
also the tag is there <link rel="alternate" type="application/rss+xml" title="GitButler" href="https://blog.gitbutler.com/rss/">
I met you and we chatted for a bit at a bar after hours at a tech conference years ago, before you dropped you were a GitHub co-founder towards the end. You actually gave me some advice that has worked out well for me. Just wanted to say thanks!
[diff]
external = difft
Use the fantastic difftastic instead of git's diff. https://difftastic.wilfred.me.uk/ [alias]
fza = "!git ls-files -m -o --exclude-standard | fzf -m --print0 | xargs -0 git add"
gone = "!f() { git fetch --all --prune; git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -D; }; f"
root = rev-parse --show-toplevel
Those are the most used aliases in my gitconfig."git fza" shows a list of modified/new files in an fzf window, and you can select each file with tab plus arrow keys. When you hit enter, those files are fed into "git add". Needs fzf: https://github.com/junegunn/fzf
"git gone" removes local branches that don't exist on the remote.
"git root" prints out the root of the repo. You can alias it to "cd $(git root)", and zip back to the repo root from a deep directory structure. This one is less useful now for me since I started using zoxide to jump around. https://github.com/ajeetdsouza/zoxide
[alias]
brancherry = "!f() { git checkout -b $(git rev-parse --abbrev-ref HEAD)-$(git rev-parse --short \"$1\") $1; }; f"
It's intended to be used for creating a cherry-picking branch. You give it an branch name, let's say "node", and it creates a branch with that as its parent, and the short commit hash as a suffix. So running "git brancherry node" creates the branch "node-abc1234" and switches to it.The intended workflow being you cherry pick into that branch, create a PR, which then gets merged into the parent.
I've been wanting something like this for years...
However, I've been using this git pager/difftool: https://github.com/dandavison/delta
While it's not structural like difft, it does produce more readable output for me (at least when scrolling fast through git log -p /scanning quickly)
I do this for some of my more complicated aliases because I generally think it's poor form to embed shell scripts into configuration languages. (Looking at you, yaml.)
For git fza I override an oh-my-zsh alias "ga" to run it.
Say you have a script named `git-foo`. At the shell prompt, all of these should work:
$ which git-foo
$ git-foo
$ git foo
If the first or second commands fail, then `git-foo` is not in your PATH or is not executable. If those both work but the third command fails, I have no explanation. Here's the code which runs commands:https://github.com/git/git/blob/2996f11c1d11ab68823f0939b646...
It basically prefixes your `PATH` (or a suitable default if `PATH` isn't set) with `GIT_EXEC_PATH` (defaulting to a compiled in value if not set) and then uses the normal Unix execvp machinery to run the command.
You can try:
$ GIT_TRACE=1 git foo
But I'm not sure that will tell you anything helpful. $ which git-foo
$ type git-foo
git-foo is /Users/my.user/.local/bin/git-foo
$ git-foo --help
Help output from git-foo
$ GIT_TRACE=1 git foo
14:26:18.849078 git.c:749 trace: exec: git-foo
14:26:18.849815 run-command.c:657 trace: run_command: git-foo
git: 'foo' is not a git command. See 'git --help'.
I can't say I've ever seen `which` and `type` disagree before ...And today-I-learned that while bash expands `~` in PATH entries other programs do not. The fix was changing my PATH from:
PATH=~/.local/bin:other:paths
to: PATH=$HOME/.local/bin:other:paths
Thanks so much for the help!For many years now, macOS has included what are effectively wrappers in /usr/bin for the various development tools and that use the xcode-select mechanism to run the actual command. If neither Xcode nor the CLT package are installed, you'll get a prompt to install the CLT package.
First off, I loved your presentation. And your book. As someone who actually bothers to read most of github's "Highlights from Git" blogs, that the, I was somewhat familiar with some of them, but it was still very informative.
Also liked your side-swipe at people who prefer rebase over merge, I'm a merge-only guy myself...
I also took a look at GitButler and it looks like it could potentially solve one of my pain points.
If you're looking for things which are confusing to beginners, for a future version of your book, there are many useful / interesting / sometimes entertaining git discussions/rants here on HN. One of the recent ones is:
[0] https://git-scm.com/docs/git-blame#Documentation/git-blame.t...
https://github.blog/changelog/2022-03-24-ignore-commits-in-t...
One other thing you might want to be mention, which is obvious after thinking about it, is that updating the ignore-revs file has to occur in a commit after the one that you want to ignore, since you don't know what that first commit's ID is till after you make it. :-)
The part about `--force-with-leash` could include `--force-if-includes` as well. `--force-with-leash` doesn’t do much if you fetch often.
https://stackoverflow.com/questions/65837109/when-should-i-u...
Thanks for the info.
Kudos to all who love git, for me, it's just a tool I have to use.
Git is the power saw for software engineers. You don’t want someone who can’t keep all their fingers and toes anywhere near your code.
Not knowing git, when you’ve been interacting with it for years, is a red flag for me. I’m not expecting people to know the difference between rebase and rebase --onto, but they should at least know about the reflog and how to unfuck themselves.
> But because Git was initially a toolkit for a version control system rather than a full user-friendly VCS, it has a number of subcommands that do low-level work and were designed to be chained together UNIX-style or called from scripts.
Note that its author (schacon) is also the author of the article and is replying in this discussion thread.
I also remember reading somewhere that this design was the reason for the complexity in the porcelain. Will update if I find a reference.
[1] https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
You _can_ see, though, that in his first README, he refers to what he's building as not a "real SCM":
https://github.com/git/git/commit/e83c5163316f89bfbde7d9ab23...
> That was really what I always personally saw "git" as, just the plumbing beneath the surface. For example, something like arch, which is based on "patches and tar-balls" (I think darcs is similar in that respect), could use git as a _hell_ of a better "history of tar-balls".
[1]: https://lore.kernel.org/git/Pine.LNX.4.58.0504170916080.7211...
this describes all of unix. as soon as scripts were allowed to use commands, those commands could never be changed. lest we have a nerd riot on our hands
The user's a file! The internet's a file! Keyboard is a file! What are checkboxes? This is a volunteer project! You can't expect us to include UI in the OS! We'll just bikeshed forever so sorry, write your own, lol.
This was well-received without complaint or concern for breaking people’s [insane] workflows that may be relying on that behavior.
That's not a script thing, that's an API surface thing, and even then only applies to backwards-incompatible changes. You can change the arguments to git or chmod just as easily as printf() or fork()
None of this is inherent to APIs.
This was classic Torvalds — zero hand holding. But he gets away with it because the way git works is brilliantly appropriate for what it’s intended to do (if you just ignore the part where, you know, mere mortal humans need to use it sometimes). I ended up writing my masters thesis a decade ago about the version control wars, and I (somewhat grudgingly) came away in awe at Torvalds’ technical and conceptual clarity on this.
No. The reason git is confusing is that the high-level commands have very little thought put into them, they are indeed “a collection of hacky tools to manage a DAG of objects”.
That the underlying model shines through so much is a consequence of the porcelain being half-assed and not designed. The porcelain started as a bunch of scripts to automate common tasks. The creators and users of those scripts knew exactly what they wanted done, they just wanted it done more conveniently. Thus the porcelain was developed and grouped in terms of the low level operations it facilitated.
Hopefully the incantation is on the Cheat Sheet and I don't make it worse.
As it's one of those rare tools that's probably meant to stay for quite some time and we interact with quite frequently, it was time well spent for me, and it turns out it's really not as hard as the scary-looking commands imply.
But then there is Mercurial, used it decade and a half ago and it contained literally everything good about distributed model I could ever wish for, with maybe 50% of Git's complexity. Yet cargo-culting gonna cargo-cult, if Linus uses it so must we since we are not subpar and the rest be damned.
Yes sure its the tool to stay, and eventually can be learned well. But its design is far, very far from the most important software design principle (KISS).
My suggestion was more to look at the underlying data-model, which really isn't that much harder to grasp than what your average undergrad datastructure course teaches. Git really does solve a rather complex problem in a quite elegant way - it just so happens that the packaging around it (the cli) is indeed a bit more controversial.
I can only speak for myself but once I started to look at git less like "a tool" and rather from a perspective of datastructure/algorithms etc. (which are inherently agnostic to how they're implemented), it started to make sense to me rather quick (a matter of hours actually, which is nothing compared to the countless of hours I've already wasted trying to find the right cheat-sheet-incantation before).
So I'm happy for the 'complexity' of git.
And on another note, git is probably one of the longer-lasting constants in our industry. Technologies develop and change all the time, but for git, it looks like it's here to stay for a while, and it's probably one of the tools we interact with most in day-to-day dev-work. Might be worth having a bit of a look at :)
Kudos to all who love programming, for me, it's just a tool I have to use.
Even today I’d like to skip most of the underlying tedious bits although I understand knowledge and willingness to deal with much of those underlying tedious bits are what keep money flowing into my account regularly. That’s about the only saving grace of it. There are so many ideas I’d love to explore but the unfortunate fact is there’s a lot of work to develop or even glue together what one needs to test out, not to mention associated infrastructure costs these days. Even useful prototypes take quite an endeavor.
Simplicity is in the eye of the beholder. A single trick can save you a whole lot of work. Take for example interactive rebate which allows you to update your local branches to merge and reorder local commits. If you had to do everything by hand you would certainly have to work a lot more.
This isn't a rhetorical question.
But then it's GitHub you need to know, not git.[1] And doesn't that have a point-and-clicky Web interface, like any "Upload files here!" Web site?[2]
[1]: Seems you're being fooled, by organisations that also believe it, that git is GitHub and/or vice versa.)
[2]:Sorry, I've had an account there since about forever, but hardly ever (never?) used it.
I wish someone with deep pockets would hook the pijul team up with the money and talent they need to make pijul a full-featured alternative with first-class hosting tools. The way it models change is principled and based on solid theory, and I'm convinced that a markedly better tool than git could be built on that foundation.
On the subject of large monorepos, I wish "git clone" has a resume option.
I had this issue back in 2000s when trying to clone the kernel repo on a low bandwidth connection. I was able to get the source only after asking for help on a list and someone was kind enough to host the entire repo as a compressed tar on their personal site.
I still have this problem occassionally while trying to clone a large repo on corporate vpn that can disconnect momentarily for any reason(mainly ISP level). Imagine trying to clone the windows repo(300GB) and then losing the wifi connection for a short time after downloading 95%.
It is wild that both git and docker, the two major bandwidth intensive software of modern development stack don't have proper support (afaik) to resume their downloads.
Id like to see anyone else solve the challenge of many people contributing code towards different releases, different features, hotfixes, tagging releases, going back to find bugs, with an "easier" interface.
It's like people who want a low level language that hides all complexity of the system - they are literally exclusive to each other. Im happy with git, its not that hard to learn, and some people need to just grow some (metaphorical) balls and learn git.
Great tips, thank you!
Some great extra git command are there.
https://leahneukirchen.org/dotfiles/bin/git-attic
It lists files that were deleted and which commit deleted them.
Most of my git usage on the CLI is nothing fancy, just a few commands, but I keep a text file for some tips/tricks I don't use regularly.
Also, if I SSH into my Raspberry Pi that I'm using as server, I don't want to feel useless just because I'm forced to use a CLI.
I'm not entirely against using a GUI, it's just that at this point I'm more efficient using the CLI, and I don't want to spend effort searching for a GUI that is:
* Good-looking.
* Is native, not an outdated vendored copy of a web browser.
* Doesn't have telemetry, or at least it's disabled by default.
* Is fully open source; not open-core or proprietary.
* I can reasonably expect that it won't disappear 5 years in the future.
* That it doesn't make things more confusing. Like for example Visual Studio[1] having a button that very ominously says "Accept merge", when it really means "Mark conflict as resolved". If an IDE wants to use its own cute way of labelling things, good; I can accept more friendly terms that make it more approachable for a wider audience. But at least don't make things confusing for people that already expect certain words to mean certain things.
* That I can trust that it won't "helpfully" do fancy stuff, like having a button saying "Commit changes" that "helpfully" also pushes to remote. I don't know if any GUI does this, but my trust is low.
[1]: I had to use it at a previous company because it was the only realistic way to work with their codebase.
1. git columns gets real confusing if you have more data than fits the screen and you need to scroll. Numbers would help...
2. git maintenance sounds great but since I do a lot of rebases and stuff, I am worried: does this lose loose objects faster than gc would? I see gc is disabled but it's not clear.
3. Regarding git blame a little known but super useful script is https://github.com/gnddev/git-blameall . (I mean, it's so little known I myself needed to port it to Python 3 and I am no Python developer by any stretch.)
3. Nice, I may have to try this
“gc” is disabled for the scheduled maintenance. It’s enabled as task when running “maintenance run” explicitely.
It would not collect loose objects faster than gc would, because it just runs gc.
git synced #<- sync (rebase) current branch with upstream if defined, otherwise origin. Uses master or main, preferring main
git pub #<- publish my changes to remote origin (force push with lease)
git pr #<- open appropriate PR to github (no GH client needed, just opens the URL). PR goes to upstream if defined
git hub #<- opens github page for repo
https://softwaredoug.com/blog/2022/11/09/idiot-proof-git-ali...
tell me about your choice of separator, the weird hairy arrow
Porcelain can be just infuriatingly confusing. For example, "Yours and Theirs" can mean the opposite in different contexts. The whole user interface has no common style or theme - it needs a new "visual" layer in order to not drive one up the wall.
I know enough about the app, and git in general, to get my job done. On the rare occasion I need more, I can look it up. I think I’ve only had to do that once or twice in all the years I’ve been using it.
For complex tasks, like fixing someone else's mess (or my own), I always start with a visual tool to look at the history and the commits, also look at the reflog (again, in a visual tool, it's much faster for me), understand what the mess is and if I can find anything to salvage, look at some diffs.
Then if it's just a commit I need to return to, I do a reset --hard. If I need to combine stuff from several commits, then I usually use the commandline.
But, will I venture out to stage only a few chunks of local changes into a single commit using the command line interface? Hells to the no.
Also known as the "don't dead, open inside" bug.