10 ms·
One of the things I hate about the traditional git workflows describe there is that there is no squashing and the history is basically unusable. We have devel
by drewg123 10y ago
One of the things I hate about the traditional git workflows describe there is that there is no squashing and the history is basically unusable. We have developers where I work that use our repo as a backup, then when things are merged to master, the history is littered with utter garbage commits like the following:
"commit this before I get on the plane"
"whoops, make this compile"
"WTF?"
These add no benefit to history, and actually provide an impediment to bisecting (since a lot of these intermediate revisions will not even compile).
At my previous job, we used gerrit. The nice thing about gerrit from my perspective is that it kind of "hid" all of the intermediary stages in the gerrit review. So you could push all you wanted to your gerrit review fake-branch thing, and when you finally pushed to master, there would just be a nice, clean atomic change for your feature. If you needed more detailed history of the steps during review, it was there in gerrit. But master was clean, and bisectable.
Is there any git other git tool or workflow which both allows people to back things up to the central git repo AND which allows squashing changes down to meaningful bits, AND which does not loose the history of review iterations?
- edu 10y agoYou could use `git merge --squash` with Feature Branch or Gitflow. But IMHO it's better not to squash.
- jedberg 10y agoA couple weeks ago GitHub added merge squash as an option for accepting pull requests (via the UI, you could always do it manually). As long as you don't delete the feature branch you'll still have the history of iterations in that branch.
- sethherr 10y agoTAKE HEED! This is the solution to everything
- heavenlyhash 10y ago> As long as you don't delete the feature branch This, unfortunately, is not a great thing to propose. Many of git's operations on refs are basically O(n). You really don't want to accumulate an unbounded number of them over time.
- jedberg 10y agoI agree with you, but I was responding to the request to keep "all the history". My understanding is the ref count would be the same if you merge in all the history or keep the branches around, so in those cases it wouldn't make a difference. I could be wrong though -- I don't know enough about git internals to know if one is worse than the other.
- herge 10y agoI wonder if 'expandable commits' would be useful, where in the full history, a change (with all it's code review fixes, etc) would appear as one commit, but if you wanted to dig deeper, you could 'expand' that commit into all it's gory details of 'draft version before review', 'tried refactoring this part but gave up', etc.
- icebraining 10y agoI think you can do that if you merge with --no-ff, then use git log --first-parent. Only the merge commit will appear on the list, hiding the commits from the merged branch.
- herge 10y agoDoes git blame let you see the same thing? I don't see --first-parent as an argument to it. It's only helpful if the 'un-expanded' commit is used through the whole system, from git blame, to rebases, etc.
- mglover 10y agoI have used git blame with --first-parent to get the 'un-expanded' commit identifier in the blame view. I'm rather new to the --first-parent flag myself but I think it works with anything that supports revision options. Here's what I see in git 2.8.1: usage: git blame [<options>] [<rev-opts>] [<rev>] [--] <file> <rev-opts> are documented in git-rev-list(1) And --first-parent is listed as an option here: https://www.git-scm.com/docs/git-rev-list https://www.git-scm.com/docs/git-rev-list
- tommyd 10y agoI thought the very same thing, as I can see the benefits to both having a single atomic commit for ease of reading history, and to being able to see the individual commits that made it up to avoid losing valuable information about changes. It turns out that the Github "Squash and Merge" option for Pull Requests does basically this – you review the PR as a bunch separate commits, when you click "Squash and Merge" a single commit is created in the target branch with all the commits squashed, but you can still go back to the original closed and merged PR (e.g. by following a link in the commit message) to view the individual commits. Obviously this doesn't help if you want to bisect among more granular commits, but (without having actually used it), it sounds like a good middle ground to me. The previous workflow I was used to involved squashing and force pushing to your branch, therefore overwriting the individual commits in the PR for ever.
- hboon 10y agogit flow has added a -S option in v0.4.2 which hasn't been released yet https://github.com/nvie/gitflow/issues/14 https://github.com/nvie/gitflow/issues/14. Should do what you want if it's combined with -k (to keep the feature branch) ?
- raju 10y agoDo you use feature branches, or does everyone work off `master` ? (From your comment it seems like you do) If you use feature branches, then it might help to - rebase interactively to clean up/edit/remove commits that are not relevant before merging - merge into master with the `--no-ff` flag - this forces Git to create _one_ merge commit, even if it is a fast-forward merge FWIW the two above can be used individually or together. The way I work (and many others I work with) is - create a feature branch off master - hack, commit, hack, commit - rebase interactively to clean up history - issue PR on Github - Merge using Github (which under the covers does a `--no-ff` merge so you get ONE merge commit) Bisecting with this workflow is a bit more coarse grained than if you did a workflow allowing fast-forward merges b/c usually the closest I get is to know that it was a PARTICULAR merge introduced a bug or a regression. That merge commit might have any number of commits that constitute it. Hope that helps. [Edit: Formatting] [Update 1] - If you use the CLI to merge, perhaps alias merge to merge --no-ff so you don't forget?
- drewg123 10y agoWe use feature branches, using atlassian stash rather than github. I'm confused though -- I thought that if you rebased & squashed something after you pushed it, then it would confuse the git clients of anybody who had pulled before the squash? Thanks so much for all the suggestions!
- ep103 10y agoIt does confuse anyone who has pulled before the squash, but if you're working on feature branches, then there shouldn't be anyone else who's working on the branch. If the feature is too large for one person, let's say you have 5 people working on a feature branch; then each one of those 5 people should be branched off the feature branch, for the same reason.
- roblabla 10y agoUsually, a feature branch is a branch from which only a single person works from. So other git clients aren't part of the equation, there is only you. As such, rewriting history/git push --force is allowed there.
- forrestthewoods 10y agoGross. In a private environment squashing history is the precise opposite of what I want. I want immutable history. Anything anyone every checks in is there forever. Safe and secure. Impossible to lose. Impossible to screw up. Furthermore, I want all the changes they made along the way to their feature. Because lord knows there will be a moment down the road where there's a line of code that doesn't quite make sense. And I'll want _full_ history to understand where that line came from. See how it evolved. Git still doesn't have anything as good as p4 timelapse view. Which is deeply unfortunate. That's a great tool for spelunking the past.
- newjersey 10y ago> Gross. > In a private environment squashing history is the precise opposite of what I want. I want immutable history. Anything anyone every checks in is there forever. Safe and secure. Impossible to lose. Impossible to screw up. > Furthermore, I want all the changes they made along the way to their feature. Because lord knows there will be a moment down the road where there's a line of code that doesn't quite make sense. And I'll want _full_ history to understand where that line came from. See how it evolved. > Git still doesn't have anything as good as p4 timelapse view. Which is deeply unfortunate. That's a great tool for spelunking the past. Seems like this is an unpopular opinion here. I can only imagine the down voters are Linux kernel developers? With 600k+ commits, they have some standing to make rules forcing people to squash commits. Of course nobody can force (for example) the discourse project to accept unsquashed commits. Any project is fre to refuse contributions on any reason. Who am I to say anything if a project won't accept my pull request because of my gender or race or because I am a moron who uses spaces in some places and tabs in others? I've said before, developer is free to squash their own commits before they push. I will indulge in a lot of bike-shedding (latest of which has been gpg signing commits). I will say though that there is some merit to the idea of people who want to force squash down our throats. Firstly, I'd like to present my point of view. My point of view is that if we keep all commits, then we have all commits. We can drill down the list of commits if we ever need to do so. Why would I want to throw away information? Unlike the kernel project which is 1.6 GB of source code and 600k+ commits just on torvalds/master we will probably never have this issue. This is not to say I think the opposite idea is completely without merit though. I understand the opposition to keeping all commits. In keeping all commits, you could argue that we are actually throwing away information because we can keep only so much information in the "staging area" of our brains. If we just start using commit as an alternative to ctrl + s, we might be throwing away information because we will lose the context of what we were thinking when we made that commit when we come back and look at it in six years. I'm a big fan of Uncle Bob. I don't always do TDD but I think only committing once unit tests pass locally is a good compromise here. Also, would the downvoters please care to elaborate why they downvoted forestthewoods?
- zzleeper 10y agoI am guilty of that. But what can I do? I use dropbox as a sort of backup (in case of a lost laptop or HDD failure), which doesn't work with git. When pushing to personal & private projects that's fine, but I need a better backup soln when pushing to a public repo..
- icebraining 10y agoYou can squash commits locally before pushing. I'm not a fan of "one commit per feature", but I still use interactive rebase on my personal branch to combine (or split) commits before merging it with an upstream branch. To avoid cluttering the main repo but still have a backup, you can just use a fork (even just a directory on a remote server).
- pkamb 10y agoIf you're working on branch `myFeature`, simply branch to `myFeature_BACKUP` and commit your entire working copy with commit message "WIP for the night; for remote backup only". Push this to the remote to back it up and protect against a lost laptop. The next day, do a "reset --mixed" from the tip of your backup branch back to your last good commit on the feature branch. This brings all of the remote backup code into your working copy. From there, start work again and begin making staged commits of atomic changes.
- jmount 10y agoAnd I've worked at places where developers are so intimidated by the "don't check on a commit that so-and so wouldn't approve of" that they have lost significant work. I would say help people work in a branch or some such and encourage micro-commits. A clean bisect-able history is a laudable goal, but it is one concern among many.
- mikegerwitz 10y ago> the history is littered with utter garbage commits like the following: "commit this before I get on the plane" "whoops, make this compile" "WTF?" You have a problem with commits here, not the lack of squashing. A messy history is only messy if you make it so. > These add no benefit to history, and actually provide an impediment to bisecting (since a lot of these intermediate revisions will not even compile). This is also a problem---every commit should compile, even if only for precisely the reason of bisecting. Squashing commits is avoiding the problem; instead, perhaps educate the others on proper practices for committing, and enforce those practices.
- tenken 10y agoYa I do an end of the day commit of alot of things (to start fresh in the morning) it doesnt mean commit trash at the end of the day. It implies to stop 5 minutes early, make a clean commit of the WIP (which compiles, or throws no syntax errors in the PHP world) and call it day.
- mikegerwitz 10y agoTo aid in comprehension, fine-grained bisecting, and debugging, I commit as small of a patch as I can for a given change; if I have to use "and" in the commit message, I might have gone too far. I also commit a ChangeLog-style message describing the changes so that they can be eyeballed without having to read through the diff. I find that especially important for bisecting, and on that merit alone (I have other reasons as well) reject squashing commits---it makes bisecting useless for large changes. When someone commits what you can better call a project, you're going to struggle, even if it's easy to comprehend the code. Same goes for review requests---we encourage our team to post small reviews, and all of us get frustrated when we have to try to grok a 500+ line diff (unless those lines are entire new or entirely removed large hunks). At the end of the day, my WIP is usually quite small, with some exceptions. Even on complicated changes---much of that planning is on paper (or mental paper) or writing test cases. WIP can be hard to get back into when flow is broken, letalone the next day, or after the weekend.
- cortesoft 10y ago
- sampo 10y ago> history is basically unusable. I can see how, in the second workflow (feature branch workflow), this can become a problem. If the history looks like this --------- / \ --A E-- \ / B---C---D then we can sort of guess that commit B started a feature branch and E is the merge commit that merged the feature back to master. And commits B, C and D can be "unusable" intermediate work, maybe even uncompilable. But if nobody ever works directly on master, all development is done in the feature branches, then when E has two parents, A and D, and A is a merge commit and D is not, then you know that A is in the master branch and D was in a finished feature branch. This gets more complicated in the third (gitflow) workflow, when all feature branches also have their own "feature master" branch and developer branches. Then all commits in the several "feature master" braches are merge commits, and then all the commits in the main master are merges from those merge commits. Then a commit in the master branch has only merge commits as its parents, so how can you tell which one of the parents is the previous commit in the master, and which one in a closed, finished feature branch? If you could tell, then when bisecting or whatever, you could first just go back in history in the master, and only when needed, take a look at more details in the feature branch commits which contributed to the single merge commit in the master. All this is solved by the named branches in Mercurial, where a commit carries forever a nametag, to which branch in originally belonged. You can add informal extra info in git commits in commit messages, so with some extra tooling you could make the history in these branching models usable again. Then again, in the fourth workflow (forking workflow) when developers pull directly from each other, and there is no central repo, permanently tagging a commit with a branch name would make no sense. And this is the use case with Linux kernel work, for which git was originally designed.
- turaw 10y ago> ...this is solved by the named branches in Mercurial... I'm not super-familiar with Mercurial (and only somewhat-familiar with git), but couldn't you get the same effect as named branches by just not deleting fully merged branches in git? > ... how can you tell which one of the parents is the previous commit in the master ... You can use --first-parent [1] to disambiguate that. In a nutshell, the master branch in your example would be the first parent, letting you disambiguate (and there's support for this in a lot of other git tools!) [1]: https://git-blame.blogspot.ca/2012/03/fun-with-first-parent.html https://git-blame.blogspot.ca/2012/03/fun-with-first-parent....
- outworlder 10y agoGerrit by itself won't fix the problem you are describing. It is still possible for a commit to pass code review and not compile at all. Someone needs to start enforcing proper commits, and a CI system need to be setup to disallow broken commits.
- rakoo 10y agoOk, I think there's something that could be tried with git, that both * saves the full history of work * shows only meaningful history on master * keep the history of pull requests I've never done this, and I'm only running from approximation, but you actually made me curious enough to think about it. I must admit that I'd seriously like the more successful git hosting platforms to seriously consider point 3, because the history of a given pull-request is impossible to follow (you never know if the committer added a new commit, or if they amended the last commit) So: 1. Hack and commit along the way ----A----B----C \ --D---E---F 2. When you're ready for a pull-request, create a merge commit and put a tag on it saying "PR 123 attempt 1". The merge commit should contain a summary of whatever happened in the initial branch. Put the merge commit in its own branch, like "PR 123" and create a pull-request from this merge commit to master ----A----B----C \ \-------------M \ / --D---E---F 3. If you want to modify the commits, do the changes on the feature branch, and when you're done create another merge commit. Put it after the first one in the "PR 123" branch, and put a "PR 123 attempt 2" tag to it. Using standard tools such as github or bitbucket, by putting the merge commit in the same branch the PR will automatically be updated ----A----B----C \ \-------------M1-----M2 \ / / --D---E---F---G---H 4. You don't have to put changes after the ones in the previous attempt, they can totally be in parallel ----A----B----C \ \-------------M1-----M2--M3 \ / / / |-D---E---F---G---H / \ / \ / ---I---J---K------ 5. When you like the changes, just merge Mx into master ----A----B----C -----------------------------L-- \ / \-------------M1-----M2--M3 \ / / / |-D---E---F---G---H / \ / \ / ---I---J---K------ With this scheme you have the history of the PR itself through the branch, where you can see the different attempts, and the summary at each attempt. When you're on master and you want to have a quick overview of the history, you can use "git log --first-parent" to only see the commits that are part of this branch and not all the parent. In the poor drawings I attempted, you'd see L, then C, then B, then A. The history is easy to see, however a little googling shows that bisecting this is not exactly trivial. You'll have to instruct git bisect to skip over M3^C, ie "everything that is in M3 but not in C". Unfortunately the standard git isn't smart enough to show two parallel branches as truly parallel, it tries to squeeze the graph to as few parallel branches it can show, meaning they all needlessly look intertwined. Moreover it requires manual fiddling in merge commits to properly tag them and edit their message for it to be meaningful, but nothing that can't be easily programmed.
- cortesoft 10y agoWhat about just using --fixup and --squash with your commits, and then use autosquash rebasing?
- jaimeyap 10y agoGit reflow does this (and more). If you want to do presubmit code reviews, and squash merge to master to preserve history. You might like it. https://github.com/reenhanced/gitreflow https://github.com/reenhanced/gitreflow
- lmm 10y agoCommit messages are very rarely read. I'd say it's well worth committing anything that compiles (that can only help bisection, which is a much more important use for history than human readability). Even a non-compiling commit isn't really a hindrance - if it doesn't compile mark it as unknown (you're using an automated bisect script that does that, right?), and you end up with the same result as if that commit hadn't been made.
- justincc 10y agoI am always reading commit messages to find out why such -and-such a thing changed. I agree that making frequent commits is really great but I always want to see every commit have a meaningful message, either at the time it is made or in cleanup before pushing to the remote branch.
- nhaehnle 10y agoI find it mildly amusing what you're calling "traditional git workflows". It seems pretty clear to me that the traditional git workflow is the one used by the Linux kernel, which is after all the reason why git exists in the first place. The traditional git workflow strikes the correct balance between locally rebasing to create a logical sequence of meaningful commits without squashing everything when your work gets published and merged. A lot of the noise on the git-related threads here on HN would probably be avoided or at least reduced if everybody was actually aware how the traditional git workflow f- i.e. the Linux kernel workflow - works.
- cyphar 10y agoI completely agree. The Linux kernel workflow is the only way to use git effectively and the history is always useful and each commit is nice and self-contained. Some people apparently don't understand that "no squashing of patchsets" means that the merger shouldn't squash a patchset (not that the developer can't make the patchset clean) and that "no rewriting history" refers to master once things have been merged into it.
- seanp2k2 10y agoIs there some ~concise guidelines or post about how the Linux kernel developers use git?
- liveoneggs 10y agoread these three man pages for the original "pull request": https://git-scm.com/docs/git-format-patch https://git-scm.com/docs/git-format-patch https://git-scm.com/docs/git-send-email https://git-scm.com/docs/git-send-email https://git-scm.com/docs/git-am https://git-scm.com/docs/git-am
- juped 10y agoIt's not the only way to use git effectively, but it's definitely the canonical one. I think a Chesterton's fence rule should apply to git: if you're doing something different from LKML, you should be able to clearly explain exactly why. No linking to some blog post you found, either, unless it's by Junio Hamano (and you still have to explain, you can't just link).
- adamc 10y agoFor those of us working in domains where auditors might care what has happened, there's a constraint: Auditors want the commit history to reflect what really happened, not the sanitized version of same. One of the nice things about git is that you can have a fork with a clean commit history and an "official" repo that reflects the real history of its merges.
- apparentlymart 10y agoThat would seem to suggest that you commit either after every keypress or at least before using the backspace or delete keys, right? It feels to me that "what really happened" only really applies to "what did we release?" rather than "how did we get to what we released?". Completely agreed that every release should be tracked in version control without modifications, but I'm skeptical that auditors care that you forgot to run the linter before committing and then you did a follow-up commit to add a semicolon where one was missed, but all of that happened between releases.
- adamc 10y agoIt really doesn't matter why you (or I) think. It matters what the auditors will accept.