30 ms·
Git rebase in depth
- human20190310 7y agoI fear that any process that allows for a git push -f at any time is eventually going to result in someone hammering a shared/public repo when they meant to hammer their personal repo.
- oauea 7y agoJust block force pushes to shared branches.
- blueline 7y agoone of us owes the other a coke :-)
- human20190310 7y agoThe author should add "block force push to master" to the comprehensive guide :)
- blueline 7y agodisable force pushes on master
- Sir_Cmpwn 7y agoOn sourcehut in particular, you actually hardly use `git push` in the first place, opting instead to use git send-email[0] from your local copy. The GitHub concept of "forks" is not used on sourcehut. [0] https://git-send-email.io https://git-send-email.io Still, rebasing is an important workflow to master even if you work on GitHub or on shared branches with others. If you mess up and force push to the wrong branch, you can always go back to the reflog[1] and check out the old version, then force push it. [1] https://git-rebase.io/#reflog https://git-rebase.io/#reflog And in the immortal words of Doug Gwyn(?): "UNIX was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things."
- talldan 7y agoThere's git push --force-with-lease, which is a safer alternative.
- fulafel 7y agoCan confirm.
- pricechild 7y agoI'm missing something obvious but all of the history you show is in the reverse order to default behaviour? Am I missing a config setting you explain somewhere?
- Sir_Cmpwn 7y agoHm, not as far as I can tell. I use a mostly stock git config and these are copy-pasted from my terminal. Is there a particular part that looks off?
- pricechild 7y agoSorry for your time, yes, you're right.
- raimue 7y agoThis looks normal. The order of the 'git rebase' plan shows the sequence in which commits will be applied from top to bottom. You are probably thinking of 'git log', which uses the reverse order with the newest commit at the top.
- pricechild 7y agoHuh... how long have I been using git...?! Not sure why I was convinced otherwise, must be log's behaviour as you say. Had to test to believe it!
- sktrdie 7y agoWeird they don't talk of the `git commit --fixup=` command. And then `--autosquash` when rebasing.
- masklinn 7y agoAlso no mention of `--onto` to e.g. move a bunch of commits from one branch to an other.
- Sir_Cmpwn 7y agoI don't use --fixup in my own workflow, but I would be happy to accept a patch adding a note about it: https://git.sr.ht/~sircmpwn/git-rebase.io https://git.sr.ht/~sircmpwn/git-rebase.io
- umvi 7y agoIs --fixup the same as --amend?
- Liskni_si 7y agoConceptually, it's similar, and after git rebase --autosquash, the result is the same. But you can amend/fixup (call it whatever you want) any commit in your branch, not just the last one.
- u801e 7y agoYou can achieve the same thing by just titling the commit: fixup! Exact title of commit We actually use that workflow during PR reviews on Github. That is, someone comments on a PR and the person who opened the PR will make a fixup commit with the appropriate title and reply to the comment stating that it was addressed and link the fixup commit by its abbreviated sha1 value. At the end of the PR review, the person who will merge the PR will run: git fetch origin git rebase -i --autosquash origin/master git diff @{u}.. This basically updates the remote tracking branches (including origin/master), rebases the feature branch on top of the remote tracking branch for master, and then checks if any changes were introduced in the rebase process by running a diff against the remote tracking branch. If there is no diff or the diff only shows changes made on the upstream master branch since the feature branch was created, then they can run: git push -f origin feature-branch-name and then merge the PR.
- Sahhaese 7y agoWhen you have a command so confusing that you need an entire website dedicated to a single command, and still need to warn against using it, then perhaps you've got the UX wrong.
- Sir_Cmpwn 7y agogit is a version control framework more so than a version control system. It starts from simple primitives, exposes them to the user, then builds complex and powerful tools on top of them. Because git rebase gives you primitives to accomplish high-level tasks (e.g. "reorder these commits"), the learning experience is different because you have to learn the low-level details to accomplish your high-level task. However, because those low-level details are accessible to you, you are afforded a greater flexibility in inventing new high-level tasks.
- Sahhaese 7y agoBut those high level tasks could also be exposed directly. There's nothing to stop there being more commands which more directly accomplish the desired tasks. The idea that git is good because it is difficult to use is just "git snobbery", as is the idea that it must be difficult because it's a DVCS. There's nothing to stop git having two levels of the API, one exposed for tools to build off of with the full complexity and another for every day use.
- Sir_Cmpwn 7y agoThis is the same argument that suggests Squarespace is better than HTML & CSS. Maybe true for some people - but not the typical HN audience, I imagine. You use git all day, every day. It's worth it to learn it inside and out, and the design assumes users who are willing to make that investment. If you assume everyone learns the primitives, then the rest of git's design makes sense as an organic evolution of that. And for anyone who doesn't know the primitives, I posted a brief summary on Mastodon the other day: https://cmpwn.com/@sir/102038690003388821 https://cmpwn.com/@sir/102038690003388821 Also recommend Pro Git's chapter on git internals.
- andreareina 7y agoThe reflog has saved my bacon more times than I care to admit.
- dbaupp 7y agoThe 'push -f' suggestion could instead recommend 'push --force-with-lease' to be less error prone in case one is accidentally pushing to a concurrently modified branch. Also, unless the diagram is confusing with the alignment, the "rebase to rebase" example seems to be implicitly assuming --onto, because the last common ancestor of 'master' and 'feature-2' includes 2 commits on that branch: the first of 'feature-1' and then the one that is only on 'feature-2'.
- Sir_Cmpwn 7y agoI've heard this before, but I felt that `--force-with-lease` requires a lengthier explanation in an an already intimidating article, is harder to type, and generally isn't useful for users of the Sourcehut workflow. It's definitely a useful tool, though. Maybe I should add a footnote.
- guitarbill 7y agoI do sympathise, it's hard to keep it at a high level, but many people just copy what they see. One thing I do in our team is provide git novices with some useful aliases, one of them being `fpush = push --force-with-lease`. It's saved a few people losing work along the years
- chris_mc 7y agoI don't get the "extra letters" argument as a Computer Scientist. Most of my time is spent figuring out what I'm going to do before I type anything, whether that's research, staring at code for hours to see how it works, or something else. I could type 3x as many characters each day and would probably only work an extra 10 minutes per day. Maybe I'm the exception, but I don't like brevity for brevity sake.
- recursive 7y agoYou could also consider it "harder to remember".
- 7y ago
- jaequery 7y agoFWIW i've never really needed rebase. i am pretty happy with seeing all the commits that ever happened.
- Sir_Cmpwn 7y agoI've heard this before, and it seems reasonable on the surface. The argument I make against this viewpoint is: "git rebase gives us powerful tools that allow us to curate a good commit history in the same way we use refactoring to uphold good software design practices."
- mixmastamyk 7y agoMost shops don't follow good software design practices, so how likely is it to get a practice one-step-removed from that, with a difficult UI to boot, adopted?
- Sir_Cmpwn 7y agoSo you're saying we shouldn't argue for the adoption of good software design practices? There are a lot of software teams that do care, you know.
- mixmastamyk 7y agoBut this isn't that. It's one step removed. Maybe if the UI gets better. Even then an uphill battle.
- fulafel 7y agoPRs are a good unit of changes for examining meaningful units of changes. No reason to lose info about how the sausage was actually made, it is also a valuable record
- gshulegaard 7y agoMy biggest issue with that argument though is what constitutes "good" is subjective. For me a good commit history is one that faithfully chronicles what happened. With this in mind, acceptable curation of the history for me is squashing or separating commits and neither of these require rebase. But I wouldn't object if a colleague chose to use rebase to accomplish this. I would, however, object to reordering commits or rebasing since this alters the chronicle. Which I guess leaves me with my opinion on rebase: You can, but you don't need to. If you are going to, be sure you understand what you are doing and don't alter the chronicle.
- Amorymeltzer 7y agoThe warning regarding "public, shared, or stable branches" is always warranted, but I think those warnings end up reverberating where they needn't. Before interacting with anything public or shared — when working solo or locally — `rebase` can be hugely helpful. When starting out with something complicated, I often make separate commits for different files or steps; using rebase to reorder commits or amend can make turning your first steps into a viable change much easier, and can help with merge conflicts down the line. I also find it helpful for large commit messages; rather than needing to write everything at once, making liberal use of `fixup` or `squash` can keep disparate thoughts or bug fixes manageable. I'd never use it on a public or shared branch, but `rebase --interactive` and `--exec` are some of my most-mused git aliases.
- falsedan 7y agorebasing a shared branch is fine, if the audience who needs to know about it is within arms length (or the remote chat equivalent). Rebasing a public branch that's used by hundreds requires special notification tooling and email templates and elaborate public shaming rituals, which people are happy to come up with but seems unnecessary in my opinion if you think twice before force pushing also: 'git push --force-with-lease' (worth repeating)
- V-2 7y agoI'd use rebase --interactive quite aggressively on a public branch when it's a feature branch that's not yet been merged. As I'm the only owner of it, the way I see it I owe no guarantees to anyone. You're welcome to watch it, but it's work in progress in every aspect.
- Amorymeltzer 7y agoGithub and refined-github[1] make it easier, by keeping track of changes from a `push -f` and having merge/squash/rebase options on merging, but I know of large projects with an explicit "no rebasing" rule as it can get confusing for reviewers. I'd say it depends on the project, maintainers, and general workflow. Which is good! 1: https://github.com/sindresorhus/refined-github https://github.com/sindresorhus/refined-github
- falsedan 7y ago> One of Git's core value-adds is the ability to edit history I didn't make it past this line. git is extremely good at keeping history immutable; it's also good at creating new, alternative histories and moving between them.
- kadoban 7y agoEditing history in git is also well supported and very commonly used, and git does it well. It's just not for absolute beginners, and the UI is occasionally not great. If git did not want you to ever change history, filter-branch, rebase, rebase -i, fixups, squashes and a bunch of other inclusions are odd, to say the least.
- falsedan 7y agoMy point is, git doesn't let you edit history; it lets you make branches easily & the original history remains unaltered. It's trivially easy to detect altered history, and talking about 'editing history' gives the impression that repos are vulnerable to attack.
- kadoban 7y agoWhat wording would you suggest to refer to these kinds of operations? Replacing history? You mentioned "altered" history, which to me isn't much different from "edited" or "modified", though maybe that was just a slip and not your preference? There does need to be some term for it. If I push a reordered branch, I clearly did more than nothing, even if we don't want to call that modifying history. I do question if any term will work perfectly, or if there is any full solution other than one learning git to an intermediate level before discussing it. The problem, as I see it, is that there are (at least) two concepts of history, there is the DAG of commits, where ancestors are older, and there are branch-heads where you can change what they point to in unrestricted ways (pretty much the reflog). I'm not sure English has a really good metaphor that's going to capture everything.
- gwright 7y agoAbout 99.9% of the time when people talk about rebase they talk about ‘editing’ history or ‘rewriting’ history as in the first sentence of the article. I find that terminology terribly misleading and when I was learning git and rebase it confused the heck out of me. No commits are harmed in the operation of `git rebase`. All the commits you had in the repo before the rebase are still in the repo. Git rebase creates a new sequence of commits and after doing its work relocates the branch name to the tip of the new sequence but you can easily access the previous commits if need be: $ git co feature-branch $ git rebase develop $ git co -b before-rebase-feature-branch feature-branch@{1}
- dan00 7y ago> No commits are harmed in the operation of `git rebase`. All the commits you had in the repo before the rebase are still in the repo. The same changes are still in the repo (edit: I should have said branch here), but not the same commits, because the parents and children change and therefore the hash of the commits. It is very important to be aware that the history is changed, because the previous history can not be without issues merged with the new one, which is the main pain point and the most problems that arise from a rebase.
- Sir_Cmpwn 7y agoNo, gwright is correct, and my guide fails to capture the nuance of this detail. Each commit has a link to its parent, and represents the tip of a linked list. .git/objects is a heap of all commits (and other objects), and .git/refs contains a list commit IDs that define each head (e.g. master). git rebase will often introduce new versions of a commit to the heap and update the heads to reference new histories, but the old commits stick around and can be accessed through the reflog - with their full original history intact.
- PhaseLockk 7y agoMy understanding is that the original commits will eventually be cleaned up during garbage collection if there is nothing else pointing to them. Is that correct?
- rich-tea 7y agoWhat I really like to do is make actual fixup (or squash) commits during a code review and just push these to the branch normally. That way reviewers can easily keep up with the changes. Right at the end, the maintainer requests that the original developer does a rebase --autosquash before the branch is actually merged. This works really well but I rarely see people talk about it. It means you don't have to use something complicated like github or gitlab to keep up with rebases. It works for everyone.
- sktrdie 7y agoIndeed this is the whole reason I like fixups. Reviewers can actually keep up with the requested changes and know exactly which fixup-commit is related to which change.
- madlynormal 7y agoIs there a benefit to rebasing vs merging, specifically if your flow is to squash commits before merging back into base?
- Sir_Cmpwn 7y agoRebasing picks commits, merging merges. Unless you're actually trying to merge two histories, I don't recommend using merge. Just think about what you're logically trying to accomplish and choose that tool.
- zemo 7y agoMost people's aversion to rebase is an aversion to altering history. Since squashing alters history anyway, I'd argue that you might as well just use rebase when you do it. In a rebase-oriented workflow, every commit has exactly one parent, and the commit history is entirely linearized. Merge commits have more than one parent, which means going backwards through the history means that you have to essentially navigate the branching structure to navigate the history. That makes it pretty hard to do! A linear history is comparatively easier to reason about than a history with all of its branches. It's also, in a sense, less "true"! So it depends what you care about. Do you care about telling the exact story of everything that happened to everyone on the project, or do you care about the history of changes to the one shared copy? Linear histories also make it easy to step backwards through the history one commit at a time. I personally just do `get checkout HEAD^` over and over, walking through the history in reverse, since most breakages are noticed within a few commits of their occurrence. I really like being able to do that. A lot of people think that's useless! If you have one copy of the project that is considered authoritative, and all developers are synchronizing via that single authoritative copy, creating a clean and linear history is possible.
- limxto0 7y agoAll the git-rebase-fu I have ever needed while working with pull requests, I have found in this well written guide: https://github.com/susam/gitpr https://github.com/susam/gitpr Quoting from this document below. # Rebase topic branch on the main development branch (optional). git checkout TOPIC-BRANCH git rebase master # Edit commits, e.g., last 3 commits in topic branch (optional). git checkout TOPIC-BRANCH git rebase -i HEAD~3 # Force push rebased/edited commits to the pull request (optional). git push -f origin TOPIC-BRANCH
- gitzen 7y agoThank you for sharing this link. Until now, I never understood properly how rebase and fast-forward merges work. This simple note in this document made it all clear. "Beginners to this workflow should always remember that a Git branch is not a container of commits, but rather a lightweight moving pointer that points to a commit in the commit history. A---B---C ↑ (master) When a new commit is made in a branch, its branch pointer simply moves to point to the last commit in the branch. A---B---C---D ↑ (master) A branch is merely a pointer to the tip of a series of commits. With this little thing in mind, seemingly complex operations like rebase and fast-forward merges become easy to understand and use." Suddenly everything makes sense now!
- amelius 7y agoRegarding the rewriting of history: can't the git history itself be stored in git somehow, so its change over time (and e.g. by tools such as git-rebase) is properly tracked? Of course you'd need a meta tool to change the history of the history, but perhaps it's not needed?
- pizzapill 7y agoI've you use rebase to rewrite history a lot I have found/developed a couple helpful commands (tested & working on Linux bash). Put those in your ~/.gitconfig: [alias] fu = "!f() { local msg=\"fixup! $(git log --oneline -n1 | cut -d ' ' -f2-)\"; git commit -am \"${msg}\" && git rebase -i --autosquash HEAD~2; }; f" fuc = "!f() { local msg=\"$(git log --oneline -n1 | cut -d ' ' -f2-)\"; if [[ \"${msg}\" != "fixup!"* ]]; then msg=\"fixup! ${msg}\"; fi; git commit -am \"${msg}\"; }; f" xx = "!f() { git reset --hard && git clean -f -d; }; f" From now on: git fu = (git fix up) combines last commit and all staged changes into one commit with the last commit message git fuc = (git fix up comment) commits all staged changes as a new commit with the last commit message, but prefixed with "fixup!". Except if the last commit message is already prefixed with "fixup!". Now you can work on the same thing but commit incremental steps. In the end you just do: git rebase -i master (or similar) and they will be all in one commit. git xx = get rid off all unstaged/uncommited changes in current directory. Very destructive, very useful. Last but not least. If you have a lot of "fixup!" or "squash!" commits and need to interactively rebase without autosquashing them do: git rebase --no-autosquash -i master
- deleted 7y ago[deleted]
- cryptonector 7y agoI've a script I need to publish that does a bisect-like thing to rebase across thousands of commits quickly and ensuring that when there are conflicts you are asked to resolve them at the commit that caused them. It was original written by @vdukhovni (GitHub). Also, I've this gist on how to think about git: https://gist.github.com/nicowilliams/a6e5c9131767364ce2f4b3996549748d https://gist.github.com/nicowilliams/a6e5c9131767364ce2f4b39...
- zemo 7y agoGit rebase is great. Honestly I think the argument that "if you have to push -f that means rebase is wrong" is making a huge assumption about how people use branches and why people are force pushing branches. Force pushing branches is what you do when you have pushed a branch that you expect to modify. Why would you do that? Because that's how Github and Bitbucket have taught people to conduct PR's. If your immediate reaction is that "rebase is bad UX", ask yourself whether or not pull requests are good UX. I honestly think rebase is great, but that pull requests are extremely bad UX, and the UX blame is misplaced on rebase when where it really belongs is on pull requests.
- paulddraper 7y agoI should add that `rebase` in no way requires `push -f`, if these are private, unpublished changes.
- u801e 7y ago> Force pushing branches is what you do when you have pushed a branch that you expect to modify. Why would you do that? Because that's how Github and Bitbucket have taught people to conduct PR's. I thought that Github and Bitbucket encouraged people to push up additional commits to fix issues in their PR. So, a typical PR will end up with a commit history like: Implement a feature method Add calls to new feature method Update to version 1.2.3 fixing missing semi-colon addressed comments one more thing now its working People who force-push are the ones who are trying to keep a clean commit history (meaning you don't have those extra 4 commits). So, your point about a PR being bad UX versus a rebase is correct, but not for the reason you state.
- malingo 7y agoMy eyes were opened on git-rebase when I read https://matthew-brett.github.io/pydagogue/rebase_without_tears.html https://matthew-brett.github.io/pydagogue/rebase_without_tea... The full version of the command as $ git rebase --onto new-base start end takes the commit range (start,end] and re-commits them on top of the new-base commit. The commit range doesn't have to be a full branch and you don't even need to be on the branch to run the command this way. It's very intuitive and I nearly always use the full version now. I've also gotten into the habit of "pinning" my branch before I rebase so that I have it in its original form. If the branch name is my-branch, then the command $ git branch my-branch{-hold,} which is a handy (bash-specific?) shortcut of $ git branch my-branch-hold my-branch leaves you on my-branch and creates a new branch label called my-branch-hold that points to the same place. EDIT: clarification of pre-rebase branching
- gbacon 7y agoI’ll sometimes do the same for a rebase that looks like it will be hairy, and I like to call the bookmarks, for example, git branch my-branch-mulligan
- module0000 7y agoI just recently(last month) broke my habit of `git checkout feature && git merge master`, replacing it with `git checkout feature && git rebase master`. Don't know why I spent so long doing it with merge, and just mentally trying to ignore the useless commit messages that resulted. EDIT: I meant to include this link, which is a pretty brief yet thorough explanation of when/how/why to rebase: https://www.atlassian.com/git/tutorials/merging-vs-rebasing https://www.atlassian.com/git/tutorials/merging-vs-rebasing
- chrismorgan 7y agoA few markup errors: • In a number of code blocks, angle brackets are not escaped, and so text doesn’t appear in the end result. • `<span style="color: maroon">#include</span> <stdio.h>` lacks the semicolon • The paragraph immediately after the #conflicts heading lacks its <p>. I also think it would be worthwhile mentioning in the first footnote a hazard of empty commits: that they will be dropped by default when rebasing.