1. Always use `git switch` instead of `git checkout`
2. Avoid `reset --hard` at all costs. So for the "accidentally committed something to master that should have been on a brand new branch" issue, I would do this instead:
# create a new branch from the current state of master
git branch some-new-branch-name
# switch to the previous commit
git switch -d HEAD~
# overwrite master branch to that commit instead
git switch -C master
# switch to the work branch you created
git switch some-new-branch-name
# your commit lives in this branch now :)
3. I'd apply the same to the `cherry-pick` version of "accidentally committed to the wrong branch":
git switch name-of-the-correct-branch
# grab the last commit to master
git cherry-pick master
# delete it from master
git switch -d master~
git switch -C master
4. And also to the "git-approved" way for "Fuck this noise, I give up.":
# get the lastest state of origin
git fetch origin
# reset tracked files
git restore -WS .
# delete untracked files and directories
git clean -d --force
# reset master to remote version
git switch -d origin/master
git switch -C master
# repeat for each borked branch
The disconnect between git's beautiful internal model of blobs, a tree of commits, and pointers to commits, and the command line interface is so wild. All of these recipes are unintuitive even if you have a firm grasp of git's model; you also need to know the quirks of the commands! To just look at the first one... wouldn't it be more intuitive for the command line interface to be:
# this command exists already;
$ git switch -c some-new-branch-name
# is there a command that simply moves a branch from one commit to another without changing anything else? It feels like it should be possible given how git works.
$ git move-branch master HEAD~
The real "internal model" of git contains much more data/moving parts.
There isn't one tree of commits, there are typically at least two: local and remote
Branches are not just pointers to commits, but also possibly related to pointers in the other tree via tracking.
Stash and index and the actual contents of the working directory are additional data that live outside the tree of commits. When op says "avoid git reset hard" it's because of how all these interact.
Files can be tracked, untracked and ignored not ignored. All four combinations are possible.
...and for a good reason that should be apparent to anyone who understands git's model (HEAD points to a ref in this case, so if you suddenly change what that ref points to without updating the working tree you create an inconsistency).
You can do that manually of course (with `git update-ref` or even a text editor), but then you get to clean up the mess yourself.
Actually, "git reset --soft" moves the current branch to another commit, without moving the index (aka staging area) along with it, whereas "git reset" (aka "git reset --mixed") moves the current branch AND the index to another commit. I really couldn't wrap my head around it before I had gone through "Reset demystified" [1] a couple times - it's not a quick read but I can strongly recommend it.
git reset only works if you're on the branch you want to move, which is why every one of these example flows has you create your new branch, then do the reset, then switch to the new branch, instead of just allowing you to move a branch you're not on.
> The disconnect between git's beautiful internal model of blobs, a tree of commits, and pointers to commits, and the command line interface is so wild
Something I heard somewhere that stuck with me: git is less less of a Version Control System, and more of a toolkit for assembling your own flavor of one.
> Something I heard somewhere that stuck with me: git is less less of a Version Control System, and more of a toolkit for assembling your own flavor of one.
That's how it is in principle, but it seems to me that there aren't that many different CLI "porcelains" in practice. Kind of like how Knuth figured people would essentially write their DSLs on top of plain TeX, not spend most of their time in giant macro packages like LaTeX.
> That's how it is in principle, but it seems to me that there aren't that many different CLI "porcelains" in practice.
I think that's because most of the people that make custom tooling to support particular workflows build it into graphical (including IDE extensions, web-based. etc.) frontends, not CLIs.
Another vote for jujutsu. No one else needs to know you're using it. You can think of it as just a different CLI for git (although you shouldn't mix them). I used to use third-party interfaces like lazygit, but I don't need them anymore because jujutsu _just makes sense_.
Seconded jujutsu. It's 100% git-compatible and one of those rare birds that is both more powerful and simpler to use in practice due to rethinking some of the core ideas.
The "move a branch" command is `git push .`. Yes, you can push to the current repo. I have a script called git-update-branch which just does some preflight checks and then runs `git push --no-verify . +$branch@{upstream}:$branch` to reset a branch back to its upstream version.
Indeed, as with all of these examples exceptions will apply and, it's a good idea to check the log before taking any such action. I believe your example also depends on exactly how many commits you've made that need to be moved. In any case, it depends on me remembering exactly what `~` signifies.
Not trying to defend the choice of `git checkout` over `git switch` (and `git restore`) but they were introduced in v2.23 of Git [0], which was released about 5 years ago [1]. If you take a look at their help pages, they still include a warning that says
> THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
Granted, it has been in there for basically as long as the command(s) existed [2] and after 5 years perhaps it might be time to no longer call it experimental.
Still, it does seem like `git checkout` might be a bit more backwards compatible (and also reflective of the time when this website was originally created).
5. Teaching `git add .` as default to add changes to the staging area is not ideal. Show adding specific files instead has less room for subsequent "oh shit" and better.
Learning about the `-p` option for `git add` was one of two things that revolutionized my Git usage. (The other was figuring out how to write effective commit messages.)
True enough, but it does make for good practice with the index and splitting workflows later on when you need to clean it up.
I think there's space for "git add ." as a didactic step. It maps cleanly to the most obvious way to understand a commit, as "here's what I've done". Bootstrapping from that to an understanding of "commits as communication with other developers" will naturally happen over time.
Is not very compatible with printlog-debugging. I'd rather encourage devs to prod around as they go if it benefits them, which causes grief for either them or reviewers in the end if they've internalized what you just said.
Explicitly adding internalizes a personal review process as inherent part of the push process, instead of something you attempt to force on top later.
It's better with a collaboration workflow that limits the span of time with expected discipline, imo.
Not comment OP, but checkout has two very different uses merged into one: restoring files and switching branches. To not break compatibility, git has now switch and restore commands that make commands more readable and understandable.
You should avoid reset --hard because it will delete all your uncommited, and you could end up in situations where that's really bad. Using reset --keep will keep uncommited changes, and failing if any uncommited change cannot be kept.
Rewriting these for jj users. I'm prefering long option names and full command names for clarity here, but all the commands have shortened aliases and all the option names have single-letter alternatives. `@` means "the current revision", `x+` means "the revision just after `x`", `x-` means "the revision just before `x`".
2. "Accidentally committed something to master that should have been on a brand new branch".
This doesn't really have an analogue. Branches ("bookmarks") only move when you tell them to. If you make a new commit on top of master, it doesn't point master to it, it just lives one past the tip of master. But let's say you accidentally moved master to include the new commit you shouldn't have:
# set master to the previous commit (and reaffirm that
# you're okay moving a bookmark backward)
$ jj bookmark set master --allow-backwards --revision @-
# there is no step two, you're still editing the change you already were
3. Move a commit from one branch to another.
# move the revision one-past-master on to our desired bookmark
$ jj rebase --revision master+ --destination name-of-the-correct-bookmark
# there is also no step two; technically we're not updating the bookmark
# to point to the new commit yet, but this isn't something you'd do as rote
# habit in jujutsu anyway
4. Fuck this noise, I give up:
# list all the operations I've performed against the repo
$ jj op log
# restore to some previous known-good state
$ jj op restore {id}
Bonus content, translated from the article:
> Oh shit, I committed and immediately realized I need to make one small change!
# move the current edits into the previous revision
$ jj squash
> Oh shit, I need to change the message on my last commit!
# re-describe the previous revision
$ jj describe --revision @-
> Oh shit, I tried to run a diff but nothing happened?!
# there is no staging area, all your changes are part of the repo and there is no
# staging area pseudo-commit; please understand that this still works elegantly
# with "patch-add" workflows and does not imply that large change sets can't be
# easily broken up into small commits
> Oh shit, I need to undo a commit from like 5 commits ago!
# find the commit
$ jj log
# back it out
$ jj backout {id}
> Oh shit, I need to undo my changes to a file!
# find the commit
$ jj log
# restore the paths provided to their contents in the given revision
$ jj restore --from {id} [paths...]
And finally there are a few things that are super easy/obvious in jujutsu that are far more annoying in git.
> Oh shit, I committed and many commits later realized I need to make one small change!
# moves the changes in the current working copy into the revision provided
$ jj squash --into {id}
> Oh shit, I committed and many commits later realized I need to make extensive changes!
# sets your working copy to the commit provided; later commits will be
# auto-rebased on top live as you make modifications
$ jj edit {id}
> Oh shit, I need to reorder two commits!
# does what it says on the tin
$ jj rebase --revision {a} --insert-before {b}
> Oh shit, I haven't committed anything in hours but I need something from an interim change from like thirty minutes ago
# look in the "obsolete log" for earlier iterations of the current revision
$ jj obslog
# restore the contents
$ jj restore --from {id} [paths...]
> Oh shit, I made a bunch of changes but want them to be in multiple commits (e.g., patch-add workflow)
# choose the parts to move out; you'll end up with two revisions, one with each half
$ jj split
> Oh shit, I need to break out a change from my current work into a new branch off master
# choose the parts to move out; you'll end up with two revisions, one with each half
$ jj split
# move the stuff I pulled out onto master
$ jj rebase --revision @- --destination master
# optional: name it; most of the time you wouldn't bother
$ jj bookmark create new-name --revision master+
> Oh shit, I need to make three sequential changes but roll them out one-by-one. I also might need to make fixes to previous ones before later ones are rolled out.
# author a new change on top of master and name it a
$ jj new master
…
$ jj bookmark create a
# author a new change on top of a and name it b
$ jj new
…
$ jj bookmark create b
# author a new change on top of b and name it c
$ jj new
…
$ jj bookmark create c
# edit a; nothing else is necessary to ensure b and c remain as descendants of
# revision a
jj edit a
…
# author a new change as part of b; nothing else is necessary to ensure c remains
# up to date on top of b
$ jj new --insert-before c
…
# point c at the new change
$ jj bookmark set b
One thing I really appreciate is that you can run `jj new master` at _any_ time to drop what you're doing and start a new change. The way jj handles the working copy, conflicts, and visible heads means there's just no need to think about uncommitted changes, unfinished conflict resolution, detached head, etc.. So many things that would get in your way just can't happen.
> 1. Always use `git switch` instead of `git checkout`
Even harder: always use "git reset --hard".
Basically don't use local branches. The correct workflow for almost every task these days is "all branches are remote". Fetch from remotes. Reset to whatever remote branch you want to work above. Do your work. Push back to a remote branch (usually a pull request branch in common usage) when you're done.
If you need to manage local state, do it manually with tags (or stash, but IMHO I never remember what I stashed and will always make a dummy commit and tag it).
Don't ever try to manually manage a branch locally unless you (1) absolutely have to and (2) absolutely know what you're doing. And even then, don't, just use a hosted upstream like github or whatever.
This is a workflow I’ve never seen on any team or project I’ve worked on. Another commenter already mentioned the remote branch for everything preference, but usage of tags is especially interesting to me. I think that’s how most people use branches, and tags tend to be more permanent. What do you do when you come back to the commit with the tag, cherry pick it over and delete the tag? It sounds like an overly complicated process compared to having a branch and rebasing onto the current branch when you finally go to make the change for real.
Local branches aren't names for anything other humans beings care about. All "branches" discussed in a team are remote. But because branches have "history" and "state", keeping your local names around is just inviting them to get out of sync with identically or similarly-named branches out there in the rest of the world.
> It sounds like an overly complicated process compared to having a branch and rebasing onto the current branch when you finally go to make the change for real.
Not sure I understand the problem here? The rebase is the hard part. It doesn't help you to have a name for the code you're coming "from". If it collides it collides and you have to resolve it.
What I said about tags was just as short term memory "This commit right here worked on this date", stored in a way that (unless I delete or force-update the tag) I can't forget or pollute. Branches don't have that property. And again local branches don't have any advantages.
And that sounds like you failed to understand me. I didn't say "don't use branches". I said "all branches are remote". Pushing to a branch is communication with other human beings. Mixing your own private state into that is confusing and needless in 99% of situations (and the remaining 1% is isomorphic to "you're a maintainer curating branches for pushing to other people at a well-known location").
What actual problem does this solve? For me, WIP branches only ever get pushed up if at least one of two things are true about them:
1) They're actually worth preserving, and not some experimental garbage that ended up being totally pointless.
2) I need to get them off of my local machine for disaster-recovery purposes.
> If you need to manage local state, do it manually with tags (or stash, but IMHO I never remember what I stashed and will always make a dummy commit and tag it).
I don't see the benefit one gets from putting work that's not fit for publication in a dummy commit on a public branch. That's just asking for garbage that noone should concern themselves with to accidentally get pushed up at the end of a long-ass day.
> 1) They're actually worth preserving, and not some experimental garbage that ended up being totally pointless.
That seems naive. You don't know what's pointless for years, usually. Can I tell you how many times I've gone back to stale pull requests and topic branches to recover "How did I do this?" code?
> 2) I need to get them off of my local machine for disaster-recovery purposes.
That's called a "backup", and yes, data robustness is a big advantage of this workflow. You're acting like this is some kind of rare event. I push my local work to a branch (or three) on github every hour!
A corrolary is hardware independence, btw. Working off remote branches means I can also stand up a replacement development environment with a simple clone. (And the corrolary to that means that I can trivially document this such that other people can stand up development environments for my stuff, too!)
At first I was put aback by this, but it actually kinda makes sense. I mean, if people are giving off unwarranted advises about "the right way" here, yeah, you should start with a remote branch, and push all your work ASAP. Especially when you are closing the lid of your laptop to change location.
...Not that I am gonna follow that advice, of course. Same as I'm not gonna use git switch for a task git checkout does perfectly well.
I've started recommending jj as a (git compatible) alternative to git, and one of the things I like about it is that the default action if you run `jj` with no arguments shows the relevant parts of the commit tree and where you are in it. This is a great reorientation tool, because you can see at a glance which commit you're working on right now, the branches and history associated with it, any other active branches in your repository, and for each you can see whether each commit had messages, changes associated with it, etc.
It's really powerful because it gives you precisely that visual layout that shows you what's going on in the repository, and what you're doing right now.
Agreed that UIs generally provide a better UX for Git.
I use Magit and doing things like "abort cherry-pick" is discoverable in the interface itself and uses the exact same shortcut as the other "abort X" operations. If I had to use the Git CLI, I'd have no idea where to start.
Similarly, I've made mistakes in interactive rebases where I deleted a commit that shouldn't have been deleted. If I recall correctly, the start of every rebase creates a snapshot that is accessible from the reflog, so this is a safe way to revert changes from a rebase gone wrong. Magit's UI for the reflog is exactly the same as the UI for the log, so I was not lost when I saw it for the first time. With the Git CLI, I'd likely have no clue what's going on.
I advocate this with every newbie to git - find a UI that works for you and use it over the cli. Barely anyone listens though, and then they struggle with the cli and make a mess of their branches and fall out of date with main and are scared of rebasing and have many other issues that are solvable with a drag and drop or similar in a UI. I figure it's a sort of masochism.
The cli is faster if you know by heart but a real disadvantage is that it is hard to "see" what you did or what happened in the past. Good look finding where/whether an old branch got merged and find out if it is part of a release, using cli.
Is this really true? When I want to reorder commits, I just drag & drop in a GUI and I’m done. Or if I want to switch branch or manage a complex mix of rebases or cherry picks, it’s just 2 or 3 clicks. In CLI, by the time I’ve typed out the commit hash or locator, it’s already taken longer. And I type 130 words per minute.
I have not used such a tool in a long time, and never with git: but my past experience with GUI frontends for version control was that they work fine when everything is working fine, but once you have a mess to clean up, nobody can help you.
It has generally worked better for me to use the same interface everyone else is using, even when that interface is awful, because that eases communication with the rest of the team. It also lets me take advantage of online troubleshooting resources, which all assume you are doing things the normal way.
lazygit ! Just needed to throw out a mention because it’s an amazing tui for visual orientation, is super fast, and can be used with a mouse and keyboard (or just keyboard) from the terminal. I saw it mentioned on a thread here last year and have preferred it as a fast “oh crap I think I made a mess lets back up” tool over the more complex gui apps I’ve tried (almost all of them).
I agree, I have used git for more than 10 years and it's the only tool that I refuse to learn. The command-line interface is cryptic and infuriating. I'd rather write assembly language again than learn what is essentially a CLI to the internals of git. It's not high-level, it's not intuitive, and it can be destructive if you don't use the right option. I stick to GUIs and simple actions, and I never had any problems compared to all the horror stories of my CLI-loving coworkers.
As a magit user I agree, apart from the fact most GUIs I've seen are horrendously broken and can lead to an even worse mess. For example, I was really confused about how a colleague messing up and got them to show me. Turns out in VS Code if you set the upstream branch correctly (ie. to master), it tries to get you to "sync" the branch. So it assumes the upstream branch is the branch you push to, which makes no sense at all.
to summarise: use the desktop apps now, but thou shalt need to learn the CLI.
—
when i’ve taught absolute development beginners how to use git and how to do PRs i show them both the CLI and GitHub desktop. not every single thing. but i at least show them add/commit/push and creating/checking out branches in the CLI.
why?
1) this CLI thing is what power users / experienced folks use. this is your long term goal.
2) oh look, the terminal is typing things out instead of clicking on buttons which have slightly different names (target audience has never seen a terminal)
3a) some things cannot be unfucked in a desktop app. i don’t have to explain what the CLI is to show them how to unfuck it. i might have to remind them. but it’s not totally alien to them. they’re only seeing the fix for the first time, not the fix and the CLI.
3b) they might feel more comfortable trying to use the CLI when they’ve already been shown it before. ideally in tandem with 3a — “hey i need to do this thing to unfuck it, could you sit with me while i go through to avoid fucking it even more”
4) maybe they go “screw it, i want the pain because i really want to be a magician at this”. it’s nice for them to have the option of the easier, simpler path when they’re having a bad day with it.
ill always suggest that absolute beginners use the desktop apps because it does reduce early fuck ups. and part of that involves showing them the desktop apps.
but i feel they need to be made aware the desktop apps are not the be end and end all. they can try the CLI a few times if they want. at some point, they will have to use it.
they also need to learn to fuck it up. making mistakes is how human beings really learn.
both making mistakes and demo-ing the CLI early, often and safely; rather than later and rarely; gets people to “magician” level faster in my experience.
essentially, you have more to learn buddy. keep working on it if you want to be one of the magicians in the team. if you don’t, that’s fine. but at some point you’ll need to deal with this CLI thing.
—
> people need to see visually how they can interact with the tree.
i’ve got a whole diagram thing i draw live showing them how everything is based on commits or a collection of commits, moving the commits around in front of them.
seems to be a better way to cover remotes, branches and eventually PRs than leaving them to work it out with some history visualisation which is usually a bit hard to read.
I believe I have a good mental model of what git does, but I never remember commands' arguments to use when they are moderately complex. I mean that the commands are not discoverable or easy to memorize.
I don't know if that's because the text UI is bad, or because it's simply difficult to explain with text what to do to manipulate a tree.
I’m pumped a search for hg+mercurial had hits in this thread. I am and will continue to be completely blown that hg lost the dvcs wars. It’s a better tool.
It is the unquestioningly better tool right now. It's available, stable, battle-tested and it's actively supported. It's a case of "being the change you want to see". Just use it. Claims that it lost are counterproductive. The implicit deterrence from those statements is what is actively keeping Mercurial's adoption low.
Hosting is available at least from Sourcehut and heptapod.host.
I'm running a private Heptapod instance (Gitlab fork with direct Mercurial support). It just works.
The GitHub effect is real, like all network effects, but that doesn't mean improvement is impossible. I've switched entirely to jj, and it having git compatibility means that I don't need to rely on anyone else to make that happen.
I am growing increasingly frustrated with various aspects of GitHub, and so I hope someday we all move past it too, but I don't think there's as simple of a path forward there.
SVN has always worked for me. You don’t have to “teach” people SVN because it’s intuitive and works just fine for the 99% case. I wish we would all stop larping as 1337 hackerz and just admit that git is overkill for the vast majority of people.
Starting a new repo in SVN is find a server somewhere, if doesn't have SVN install SVN, if it does have SVN deal with whatever politics runs its trunk to find space/define a branch structure for you, and so forth.
It is its own footgun as well, but with git you can git init any directory you want and have a repo. Other than the learning curve of the not particularly well tuned CLI (and there are all sorts of graphical clients today and alternative CLI clients), it's very low barrier to entry to get something/anything in source control.
It's not just "larping as leet hackerz", there are real benefits to git over SVN. There are fewer barriers to entry for small teams and hobby devs, especially in a world with GitHub/GitLab/Sourcehut/and more, but also in the world where maybe a git repo never syncs with any other single machine or is hosted on the barest feature set SAMBA folder or is only ever using email-based PR flows.
git could be easier to teach. git could have a better out of the box CLI. That doesn't mean "git is overkill" especially from the point of view of "needing a whole central server setup just to store multiple versions of a file is overkill". Git is perhaps "underkill", it does the bare minimum as best it can in whatever environment you give it. It's intentionally dumb and stupid (hence its name, to be fair) and it's learning curve is as much because it is "too dumb" than because it "isn't intuitive". I've seen some "intuitive" UIs built on top of it. Another comment here remarked "git isn't a source control system, it's a low level toolkit for building your own" and that's not far from the truth and definitely not far from git's origins (and its name). That's a sort of "underkill". ("One day we'll finally build the right, intuitive high level API." That's basically how git was designed. You don't have to just take my word on that, you can search the old mailing lists for all sorts of interesting discussions and debates on "porcelain" options.)
>Starting a new repo in SVN is find a server somewhere, if doesn't have SVN install SVN, if it does have SVN deal with whatever politics runs its trunk to find space/define a branch structure for you, and so forth.
Many years ago, SVN worked fine for me for single-user projects. I just made my own desktop be the server. But I wasn't publishing my code back then (it's not as if I would have have any effective way to tell people about it anyway). Now I have no idea how the equivalent of "pushing to origin" would have worked.
I agree with your sentiment. I was kind of "forced" to use (and eventually fully migrate to) git because the IDE integration to SVN became more quirky every year. Instead, git is already integrated in IDEs these days.
Every time I see `git reflog`, I see it as re-flog. It's possible that's even the reason I never remember the command, I subconsciously can't bring myself to think of it as "reference log". Instead, it's asking git to sell something cheaply, again.
I'm not proud of it, but my #1 "Oh shit" git operation is to just delete my local repo, reclone, and reapply the changes. Works really well for me 95% of the time. The rest I ask dev ops guy to help.
I've been using Git for almost 15 years, and have twice built programs/products that use Git internally to achieve certain results (that is, the program/product itself uses Git for things, not just using Git to manage the source code for the program/product) and... sometimes before doing something a little gnarly in Git I'll still just do "cp -R .git ../git-backup" or something like that, so I can replace my entire .git dir with an older copy if I screw it up too bad. It's a ton faster than figuring out the right way to un-fuck any particular operation or set of operations.
git break-my-shit
git reflog
... output saying where you were before things broke
... grab the good commit sha
git reset --hard good_commit_sha_from_reflog
Jujutsu has `jj undo`, but which undoes whatever was your last jj command, regardless of what it was. It makes much more confident to do an operation I'm uncertain of. And if I regret something many actions down the line, you have `jj op log` (a better reflog).
I'm not a git user, but stuff like this really drives home the idea that "git commit" is meaningless, the only thing that matters is when your commits are pushed or merged.
It's like saving a textfile. Do you write a little message every time you save a file? No that's silly. Just move on.
If I'm saving changes that were done because of a arduous debugging journey and other people are likely to have to refer back to it, yes. In fact, forget little; the smaller the change is, the bigger the text. Some of my changes have 2-3 paragraphs for a 2-3 line change because of the impact.
It seems painfully obvious to me that local and remote commits serve different purposes. Local commits are a way to create checkpoints when everything compiles and works and you can move on to the next step. That way you can stash your changes and go back to a working state when you screw things up. Then, before you push those changes, you reset them and split them into proper commits. That way the history is all nice and clean on the remote and it's not just a bunch of "wip"s.
Or you can just squash-merge your PRs and reap both benefits.
I think people would generally have an easier time with git if they didn't try to fix their commit histories and instead just let their past mistakes be known. If you make a bad commit in git the best solution is to follow it up with a good one that fixes the problem. Squash commits on PRs help too.
>It's like saving a textfile. Do you write a little message every time you save a file? No that's silly. Just move on.
Do you write a little message about your day every time you go to sleep?
I actually don't, and maybe you don't, but plenty of people do.
I think of the Git revision log as much like that sort of diary, offering the same sorts of advantages. It's more organized than having a separate TODO list document; and it encourages you to describe what you've already done in a clear readable way, too. When you're trying to figure out how far to roll back, it may be useful to understand what you were thinking at the time of the commit. And if something turned out to be the wrong approach, it helps you do the post-mortem.
And, of course: if your unmerged, unpushed work is organized into separate changes that have separate (and labelled goals), and part of that work turns out to be good and part bad, it becomes vastly easier to preserve the good parts.
Well, it is all local until you push so you can do whatever you want.
With that said, it obviously is not meaningless at a technical level because without the commit there is nothing to push or merge. On top of that, at a non-technical level it can be extremely helpful to record some plain-english prose to describe why you are changing something. If you find yourself doing that too often you need to narrow your definition of what constitutes a "change" and/or be more discerning about what you work on simultaneously.
Out of curiosity, if you do not use git, what do you use for version control and change-management?
I actually use git for my todolist/diary and I have an alias for when I want to save which does git commit -m "whatever." Basically I do that so I can view the updated version in the mobile app.
Commit is what causes git to make a copy of the file(s) internally. It's vitally important. But there is no point typing in silly messages like "more fixes" etc. What I do is make an initial commit with something like "(WIP) too feature", then keep doing amend commits until I'm happy, at which point I remove the "(WIP)" from the message.
For a developer, one of the most useful things that LLMs can do is help out with Git issues.
It really says something about the design and documentation of a tool when you need a (far more complex and energy-intensive) Artificial Intelligence to be able to use it without making too many mistakes.
Git manages pretty much everything by using the `.git` folder created by `git init` and there is (as far as I am aware) nothing stopping you from going into that .git folder and running init again there to start using git to manage the internal state of your repository. At least... that is what I assumed the joke was.
Reading these tips confirms that I'm too stoopid to use git.
Fortunately, I don't have to use git. So I just store work-in-progress as time-stamped files. Storage is cheap and plentiful. Can always diff, cherry pick, etc without confusing myself.
Lately I've been asked to avoid merge-commits. They pollute the logs?
If my push is blocked I am too far behind I create a new temp branch of master and do a "merge --squash" to it and then a "reset --hard" from temp branch back to my original branch.
Heck sometimes I rather keep my changes in patches to void does darn merge CONFLICTS...specially when rebasing.
Hard to understand exactly what your issue is here. Typically when people say "avoid merge commits" they mean they want you to almost always rebase instead of merge. Can you give an example or something?
The thing is if you merge immediately into master and have conflicts, you solve the conflict and only then you can merge. But then the conflict resolution sits at the merge point with a weird default commit message and is hard to decipher.
A nicer way is merge master into your branch, with the rebase option (you can set that option as the default). This will put your changes on top of the master changes that happened during populating your own branch. There you solve any conflicts and those usually immediately show you what happened in the meantime, making it easier to do so. The latest greatest now sits in your branch.
Then as a plus, you can retest that merge and if necessary, fix the merge.
Optionally you can do a pull request for reviewing. The diff the reviewers see is conflict-less and makes sense, has only your changes.
Then simply merge to master, which should be trivial if you don't wait for long.
I think that people that think merge commits "pollute the logs" are missing key git features such as `--first-parent`. Git is natively a graph. It gets a lot of powers from that. If you don't want to see all the details of the "subway diagram", add `--first-parent` as a default and focus on the higher level. Merge commits help avoid later conflicts by saving how earlier conflicts were solved. Three-way merging in general and many of git's more complex merging strategies all work better if you save the merge commits. Rebasing throws that baby of information out with the bath water. (It's also useful information to have as a developer needing to archeology dive for how a regression happened. With a merge commit you can see the fingerprints of a bad merge or a mistake that wasn't caught in an ugly merge. With a rebase or squash that information is gone, you have no idea where to find the bad or ugly merges, that data is swept under the rug.)
On top of this, integration is an "interesting" step on its own! When trying to diagnose an issue it can be super valuable to be able to understand whether it was broken from the start or broken by the merge. Rebasing throws all of that valuable information away!
I swear there used to be a choose your own adventure version of this where you could answer questions about what you did wrong and get a step-by-step "here's what to do" after.
I'm happy I didn't have to scroll too far to see this.
Git's CLI isn't elegant, but it really isn't that big of a deal if you understand the basics of what a commit is, what a branch is etc.
I struggle to understand why so many devs decide to treat it like mysterious arcane sorcery instead of just spending the needed time on learning how it works.
The same can be said about regexes.
Regexes and git are probably the two tools which I have benefitted the most from learning compared to how little time I've spend on learning them - and I wouldn't even consider myself an expert on either.
> it really isn't that big of a deal if you understand the basics of what a commit is, what a branch is etc.
Yes, that's what people mean when they say that git is hard. Instead of presenting you with an interface expressed in terms of the domain you intend to work in, whose commands represent the tasks you intend to perform, git dumps its guts all over the place and requires each user to re-implement the interface between "what you want to do" and "how git is built" inside their own brains instead. Once you have written git's missing user interface in your brain, you are fine; but that's a lot of work which is not necessary with other version-control systems.
>I struggle to understand why so many devs decide to treat it like mysterious arcane sorcery instead of just spending the needed time on learning how it works.
For example: you have bazilion ways to achieve the same thing, all of them having its own quirks/advantages?
It is just poorly designed, that's it, lol.
I like to joke that if somebody else invented Git, then it'd be 10% less powerful, but 10 times more user-friendly
But any software evolves over time so if it had fewer ways of doing things in the past, it would very likely eventually pick them up because people have use cases for the advanced features.
It's like complaining about languages ("English is hard to spell and doesn't have consistent pronunciation" etc.), they're constantly changing and that kind of thing is going to happen eventually...
Git is a technology that was invented to simplify things that ended up getting so complex over time that an entire industry started up around it to try to make it simple again.
Actually, no. Git was a technology invented specifically to enable Linux kernel development. It hasn't become any more complex since its inception. The problem is most people aren't doing kernel development and have absolutely no idea what a distributed version control system is, yet they use git, a distributed version control system. I have no idea why we use it, to be honest, but I'm very glad that we do because the previous options were crap. Perhaps Mercurial is better, but git is good enough.
We were using svn before Git and easy cloning (and then PRs) has solved a LOT of problems. There are definitely still some things left to improve, though.
Git is one of those technologies that I never got to wrap my head around of, because in so many ways it doesn't follow intuition and unless you have been using it for a long time, for literally every action you would probably have to Google or use the man page of the command.
Git commands, while they may be cryptic, actually mean something. Whereas that was just gibberish made by putting random mathematical words together.
(There is a similar sounding joke about category theory, "A monad is just a monoid in the category of endofunctors" but this sentence has a mathematical meaning.)
Did you start with Git or have you used other VCS systems before? I started with SVN and then coming to Git, there were obviously things to learn about how it was different but honestly it felt to me like it made things easier in many ways (since I'd experienced the horror of a very large codebase in SVN with lots of branches, and trying to track those and merge back together - git is so much better at that, it's crazy)...
I can see how it would be a much bigger learning curve if people come straight to git, but it's still hard for me to understand where the blocker is that so many people complain about using it...
I was trying to delete a file from history yesterday.
The built in way (filter-branch) pops up a warning, with a long delay, saying you should hit Control+C and go download a third party python script called filter-repo...
Possibly consider that "deleting a file from history" is rather far outside the norm or recommended practice for git (even though it is, of course, entirely possible)
I take the more realistic perspective: until git makes it impossible to commit something that shouldn't have been, like a secret, then deleting a file from history is a fundamental requirement of git.
The designers of git clearly disagreed, as you can guess from its design, so it's not surprising it might feel like a bit of an uphill struggle (and will probably remain so). There are other tools available.
Since you brought it up, I personally switched to jujutsu and prefer it greatly. I regularly help coworkers deal with issues in git and keep dropping hints like `in jujutsu this would've been done way easier like this!`. Nobody bites yet since I think most of them don't want to use the CLI but maybe someday if enough people keep talking about it the inertia will get to the point that we can get some really slick GUIs for jj.
Oh shit, I accidentally `git reset HEAD~1` and moved the last commit to file diffs, which was a merge to master, and my file diff now is both the last branch merge and everything I've done in the last 8hrs. I did this once and it was a gigantic PITA to undo, if anyone has any hot tips for that particular idiocy...
The one thing I wish people would internalise about git is it's an append only data store. To get work into the store, you commit it. Once in the store there is not a command in git that can remove it.
This is how the reflog works. Whatever you do, you can get back to a previous branch state and restore the work from the data store.
But git can't help you if you don't commit. So just commit. Commit all the time. If you think it's too much overhead and you always forget to commit, fix your tools. If you're writing code all day you should be committing at a minimum every hour, in general.
Yeah, please don't create sites like this. Just... don't.
Any, and I mean any "in case of a Git problem, just do this" recipe is wrong, often in very subtle ways. So, my advice: in case of a Git problem, contact the help channel provided by the organization hosting your Git repository. They'll help you out! And if it's your personal-I-am-truly-the-only-human-using-this repository? Just recreate it, and save yourself the pain.
Source: I'm part of the team behind the #githelp channel in many $DAYJOBs, and we know how hard things are. You committed an unencrypted password file, or worse, your entire 'secret' MP4 collection to our monorepo? Sure, just let us know! Pushed your experimental branch to master/main/head/whatever? We'll fix it!
Just don't ever, for whatever reason, run that-chain-of-commands you found on the Internet, without understanding what they do! In most cases, your initial mistake can be undone pretty quickly (despite requiring nonstandard tooling), but once you're three levels deep and four days later, not so much...
I've been using git for at least 6 years now, maybe 10.
Sites like this are a great aid to remembering how to deal with certain situations. And yes, I understand what the commands do, but that doesn't mean I always could, or always want to, put together the series of steps from scratch.
And also, we self-host our own gitea hosting because we're not getting sucked down by yet another hosting debacle (old enough to have suffered under sourceforge, and don't plan on getting in the same situation again). For git hosting just as much as everything else on line, if you're not paying for it, you're not the customer.
A 'team', by definition, consists of more people than 'you.'
And, by the time a '#githelp team' is formed, it's to address patterns to which there are known solutions.
One of the many problems with Git, is that these solutions depend very, very much on the structure of the repo and the common practices of its users.
So, instead of executing random commands from the Internet, just ask. Please! Or, if there's truly nobody going to be around, give in and recreate the repo. You'll save yourself so much pain in the long run...
> A 'team', by definition, consists of more people than 'you.'
I'm the resident git expert, but not by choice. There's more that I don't know than that I do. It's not uncommon that I need to use internet recipes to un-wedge someone's clone.
> Or, if there's truly nobody going to be around, give in and recreate the repo. You'll save yourself so much pain in the long run...
This is insane. There are a dozen other people using the remote, not to mention a whole CI/CD build configuration.
OK, so you've truly screwed up your your personal/small-team repos to the point of requiring poorly-understood command sequences from the Notoriously Reliable Internet more than once?
I don't understand your surprise or disbelief. I would imagine most devs have been there. As evidence: just look at Stack Overflow, and compare it to what it's apparently intended to look like and how it's supposed to work (as a denizen of meta.stackoverflow.com I am quite familiar with this struggle).
Yes. I think the ratio of small-team repos this describes is close to 100%. You seem to have a certain idea of how repos are managed. I don't think it's very representative of reality.
Bro, really, self-taught people with a bare minimum understanding of the tools they use are super normal, and when they get into a pit they have to fix it themselves.
Although to your point folks would be better served carefully reading the docs / git book than googling a specific solution to their specific error code.
For me, the value of things like this is in learning the terminology for what I broke and how to fix it. I'm not going to copy-and-paste advice off the Internet. I never have. It's still super helpful to see "oh, that thing I want to do is called frobnitzing the corple. Now I know what to Google!"
"Bro" is the furthest thing from "gender neutral". Not sure how you could think it's gender neutral. It originated from male behavior and is definitely not gender neutral. You can address women as "bro" and they might even respond to you but they'll think you're absolutely weird.
"bro", "bruh", it's more of an exclamation of surprise than a title conferred to the person being addressed, but even then, I don't know, people call folks "auntie" and "uncle" who aren't actually their auntie and uncle. language is flexible. it may reference the kind of fraternity between brothers but that feeling is not limited to the male sex.
I'm about 95% sure that if I ask my two school-age daughters if it's weird to address girls and women as "bro" or "bruh" in informal circumstances, they'll say no. Since I hear them do it with some regularity.
No, the female and non-binary people in my life both give and accept "bro" or "bruh" without complaint. I once asked one of my non-binary friends directly how they felt about "bro", "dude", etc and they consider those words to be gender neutral. They are like the word "man" now ("IDK man").
That's actually how it was originally, because in Old English "man" just meant a gender-neutral "person."
Gendered versions were "wer" and "wif", so you could have a "wer-man" and a "wif-man", the latter changing pronunciation to become "woman". I suppose this also means that there are both "werewolves" and "wifwolves".
Recipes like these aren't useless, but yes, they really need to be prefixed with whether they expect to start from a clean work tree and empty staging area. Or describe what they'll do to uncommitted changes, both staged & unstaged. Otherwise they pose a substantial risk of making the problem worse.
1. Always use `git switch` instead of `git checkout`
2. Avoid `reset --hard` at all costs. So for the "accidentally committed something to master that should have been on a brand new branch" issue, I would do this instead:
3. I'd apply the same to the `cherry-pick` version of "accidentally committed to the wrong branch": 4. And also to the "git-approved" way for "Fuck this noise, I give up.":reply