> 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).
[0] https://github.com/git/git/blob/757161efcca150a9a96b312d9e78...
[1] https://github.com/git/git/releases/tag/v2.23.0
[2] https://github.com/git/git/commit/4e43b7ff1ea4b6f16b93a432b6...
# 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~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.
git update-ref <branch-name> <commit-sha> git branch -f branch_name commit
if it's checked out: git reset --hard commit...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.
So much for "a branch is simply a pointer to a commit"...
If a git repo was purely a collection of meaningless pointers and graph nodes, git would be a graph manipulation utility, not a version control system. The fact that some of those pointers have a meaning is what makes it useful and it doesn't contradict the fact that what you're working on is still just a bunch of pointers and nodes.
"error: ref in use by higher layers" makes much more sense to me in this case.
Turns out it's a tree of commits and pointers to within that tree and a master pointer that come in two versions: pointing towards the pointers or pointing towards the tree. And pointers behave very differently when the master pointer is pointing to them...
Elegant. Simple. :P
...because HEAD points to what's checked out. This pointer does not just exist and hang around, it has its semantics. Not understanding that reveals flaws in your mental model.
Besides, the side affect you find "not surprising" here is... rewriting HEAD to change what it points to. Then you ask "why would that change where HEAD is pointed". Sounds like you may be confused. Are you forgetting that a ref may point not just to a commit, but also to another ref? This is the whole idea behind branches after all, having HEAD point to a ref is exactly what makes branches semantically different from tags - if you don't understand it then no wonder you're confused.
(protip: if you find git's "pointers to pointers" confusing, perhaps because in C a "pointer" and "pointer to pointer" are separate types that make multiple dereferencing steps explicit, think of them as symlinks instead and it should become clearer - that's in fact how symrefs used to be implemented in the past)
When a pointer is in use by higher layers, a good UI will prevent you from making direct changes underneath it unless you force it or go low-level enough for it to not matter. The only sin of git I can see here is that `git` command provides you both high-level and low-level interfaces to manipulate the data structure you're working on with no clear distinction for the user.
It is surprising. You wanted to edit the value of `main` ref, yet suddenly you now edited `HEAD` too without meaning it. Bailing out and letting you actually decide whether you want to do it or not is the correct thing to do for a high-level command like `git branch` (alternatively it could ask you what to do interactively). If you don't want such safeguards and you know what you're doing, use `git update-ref` which will happily let you break whatever you want.
"git reset --hard" is "...and also change all the files in the working directory to match the new branch commit".
"git reset --soft" is "...but leave the working directory alone".
[1] https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified
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.
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.
git switch -c some-new-branch-name
git branch -f master HEAD~ git branch -f master origin/masterWouldn't that copy a branch rather than moving it?
git switch -C master HEAD~
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.
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.
gitg for something simple, graphical and widely available.
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.
git add -A
git stash
git reset --hard
git stash pop2. "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 bI've found myself using git to fix the mess in this particular instance.
$ jj file untrack {paths-or-pattern}
Alternatively if you have a bunch of files spewed everywhere with no rhyme or reason which can't be globbed or enumerated reasonably: $ jj status | grep '^A' | awk '{print $2}' | xargs jj file untrackMy post was a pretty naked attempt to showcase how much less convoluted basic operations are in jj vs. git and hopefully drum up some interest. Hopefully someone bites.
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.
> 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.
All branches are public.
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.
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!)
...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.
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.":