Hacker News new | past | comments | ask | show | jobs | submit login
This is how I git (haxx.se)
232 points by ingve on Nov 10, 2020 | hide | past | favorite | 136 comments

>Never stash

Er, no. Right tool for the right job.

Whilst I agree that the idea of putting differing lines of work into different branches is great, that's not what stash is for. That's what checkout is for.

Stash is used to record the current state of the working directory and index while also returning to a clean working state.

I guess for the author, stash is used to preserve changes when pulling in upstream changes you missed. It can, but it does waaaay more.

What happens if, like many people, you're working on a few branches at a time. Could be a monorepo, could be a couple long living branches, w/e. What do you do if you've done work against a branch and want to lift and shift to another branch?

You could commit and rebase and all sorts of clever mangling of the history.

OR you could use the right tool for the job


Follow up

[0] https://medium.com/@yankee.exe/mastering-git-stash-workflow-...

[1] https://git-scm.com/book/fa/v2/Git-Tools-Stashing-and-Cleani...

The problem with stash is that its easy to create a mess.

Yes, it's useful to quickly stash your work, switch to a different branch, do some quick fix, then go back to your original branch.

But in practice, what often happens is that your quick fix takes way longer than expected, and you don't immediately go back to your original branch.

Then you pull in some changes, maybe someone else pushed something to the branch you were working on, and you end up with a bunch of stashes on outdated branches and it's really hard to make sense of it all. Most of my working copies have at least half a dozen old stashes that I don't remember any details about anymore.

One approach to avoid this mess that git stash can lead to is to never use it, and just commit all your work before switching to a different branch. Clean up the current state of the repo, try to document your changes in the commit message, and commit them. Then switch branches.

You can always squash or rewrite the commits later, and it's a lot easier rebasing or merging a few old commits than working with stashes.

If you like stashes, please use them!

But I agree with the author, using them a lot can lead to a mess, and temporary branches are usually a cleaner way of saving your work.

I personally only use stashes when I've given up on my current approach and want to reset but I might change my mind later (rarely happens). I don't think they're appropriate for stuff you want to keep across branches, there's no real visibility and they'd be easily forgotten.

I use them to stash changes to config files that remain fairly stable across branches but require local paths, etc. I realize this isn't a best practice for config files but it is what I need to do for my particular mountain of technical debt. This works particularly well since the config files rarely change in a permanent way. I will only do it short term, though. For me, Stash is a tool I use sparingly as a convenience.

> I use them to stash changes to config files that remain fairly stable across branches but require local paths, etc.

I had to resort to git's assume-unchanged[O] to keep these changes out of commits made in the heat of battle.

> this isn't a best practice for config files

Have had great results (in dotnet land) with MS' Secrets Manager[1].

[0] https://www.git-scm.com/docs/git-update-index [1] https://docs.microsoft.com/en-us/aspnet/core/security/app-se...

Thanks, I will review that. We are working on ASP.NET stuff and using Web.configs in a non-standard way (don't get me started) to store encrypted strings for... anyway, you get the idea. This is in the queue to get overhauled soon, so hopefully won't be an issue much longer (if you squint, you can see the end of the todo list wayyy back there). Maybe I should check this out.

I don't think there's anything wrong with stashes, but there's another interesting trick that you might want to try for this purpose: commit the dead-end, then immediately make a revert commit. You can even add a rationale in the revert commit message.

The technique is not applicable all the time, but for those instances when you really think it's valuable to preserve the knowledge that was gained in trying and abandoning a particular approach it's great.

Same for me. I only ever use stash in cases where I don't materially care if I never see the content again, as with an experimental approach.

I started using emacs recently, largely for org-mode and magit. The default stash behavior of prompting for a message in magit helps me a lot with the problem of keeping stashes organized and knowing when old stashes I’ve forgotten about are safe to get rid of. Even when working on the command line now, I try to always stash with a message to provide some context for future me.

    git add .
    git commit -m "wip"
when you're back

    git reset HEAD^
Keeps working changes nice and tidy in the branch they belong to, you can push them, and they are easily recoverable from the ref log, unlike stashes.

Also, god help you when a stash fails to apply.

I needed this comment today. I am an avid stasher, despite knowing the downsides. It’s just so convenient (until it’s not...at which point I usually give up and trash it, and yell at myself for not settling into a partial commit sooner).

But this workflow (which seems so very obvious in retrospect), keeps my current workflow, with so many benefits. The only place where it even requires extra steps is when you want to apply the stash to a different branch, and even there, it just feels right:

“Oh yeah, I was working on that feature over there, let’s pop it off and go put it on this feature over here.”

Since this generally requires careful diffing anyway to make sure things end up in the right place, there’s not much lost here, and without the downsides of stash.

If anyone is interested, there's a tiny utility I've used daily for years to do this: https://github.com/bitjson/wip

Each time you run `wip`, all current work is committed. When you run the squashing utility `naenae` (autocomplete: `nae + Tab`), the most recent line of "WIP" commits is saved to a `wip-archives/[branch]/[timestamp]` branch for safe keeping, the commits are all squashed in place, and you're left in your default git editor to define the commit message. It also handles unusual situations, like using `wip` for the first commit (in which case, `git reset HEAD^` will return an error).

Being able to set a sort of "WIP checkpoint" in the current working directory makes it really easy to start or back out of big changes, and it can be very useful to check back in past WIP commits to grab segments of code you previously threw away.

Alternatively, instead of `git reset HEAD^` you could do `git commit --amend` to merge further changes into the latest commit

This. I have these aliased as `git save` and `git unsave`, which makes it as convenient as `git stash`.

I do this all the time, it’s the only real way of saving state when you are abruptly asked to do something else

> You could commit and rebase and all sorts of clever mangling of the history. > OR you could use the right tool for the job

Stashes are just another form of DAG node with their own special syntax and commands and quirks. I've already learned one set of commands for all that, why learn a second, less general set of commands?

I have started using stashes in one very specific case:

1. I realize I'm on the wrong branch 2. I've made no commits 3. The right branch has changes to files I've modified

    git stash push
    git checkout -b newbranch upstream/right
    git stash apply
    git stash list
    git stash drop [id]
Careful and safe, but not actually much faster than my old workflow, and I haven't the foggiest idea how to fix things up with stash if I've made several commits, if that's even a thing.

    git checkout -b dead/wrong
    git add -A .
    git commit -m "WIP"
    git checkout -b newbranch
    git rebase --onto upstream/right upstream/wrong
    # optional
    # git branch -D dead/wrong
    # git reset --soft HEAD~1
Depending on if you count the optional commands, that's one more or one less command. "Those aren't optional!" you might say - but yes, they are. I'll often merely amend my WIP commit into a real one instead of resetting. I'll often keep dead branches long term - perhaps some of my work relied on things that haven't landed on upstream/right yet, and can't be brought over sanely until upstream/wrong lands on upstream/right, or just for peace of mind that I might've forgotten something. Good dead branch names makes this less painful than... numbered stashes? Eww, gross.

stash is supposed to be a short term thing, if you are doing the whole "apply/verify/list/drop" cycle, named branches are a superior approach.

I find the power of the stash in "stash pop", which applies, and drops if there were no conflicts:

    git stash push
    git checkout -b newbranch upstream/right
    git stash pop
and yeah, if your keep your branches long-term stash is not a good solution for this.

Yea, exactly. The author's git workflow is eerily like the one I've converged too over a decade, but "never stash" (without acknowledging the existence of the quick stash use-case) is wrong enough that it's enough to give me pause about his general competency with git (or more likely and more charitably, that he just didn't put too much effort into that section of the article).

The advantage of stash is that it's quicker and easier than creating and cleaning up branches. If you're naming stashes and keeping them longterm, then you're basically using them like branches and should consider what benefit you're getting from adding a separate workflow.

A lot of my "short term" things end up being multi-month distractions or worse - and the verify/list/drop cycle is reflexive paranoia after one too many botched merges lost data.

Sometimes those botched mergeds have been due to my own incompetence, sometimes due to P4Merge literally corrupting my file and saving out something totally different than what I'd resolved, and sometimes due to unexpected refactoring between branches causing enough of a mess that reapplying the work by hand is easier than "resolving" the conflict.

I usually do comment my stashes for this reason (git stash save “words to remind me what this is”). But I’ve also been convinced by comments here that I’m going to be happier with WIP commits.

I like making all my commits “canonical at all times,” but that’s just something I can see it’s time for me to evolve beyond.

Good points all round.

But what I'm not doing is saying "you should always use stash". I'm providing use cases, for me personally, where stash is the correct approach.

"Ah", but you might be inclined to argue, "such and such a reason". Yes, but also no.

Right tool for the right job is also an argument for competency and context. Sometimes a hammer is useful for more than just hammering, but very rarely.

The point is there is usually more than just 1 approach, and niche/edge cases can be real nasty if approached with the attitude that everything is a nail

My personal rule is "never go to sleep with a stash on the stack".

They are for short terms manipulations, but it's easy to make mistakes with them and lose data (dropped stashes don't show up in the reflog for instance).

Things to be preserved for longer should be committed and saved in temporary branches.

The author does explain their alternative to stash (a temporary commit in the new branch that will eventually be cleaned up / removed during 'rebase -i' before pushing) and why that fits with the way they work.

Why is stash better than this? Because it avoids the rebase? Many people do this anyway, early commit messages often need rewording. Because it maintains the separate state of the current working directory and the index? Is that it? I don't think that's really a compelling enough reason to claim the author's avoidance of stash is the wrong way to work.

I don't understand your comment about tools. Checkout is a git command (with an admittedly terrible ux). It is not part of git data model.

Like the article writer, I never myself use stash. I personally don't understand why they were added to the git ux. I think they needlessly make git more complex. Under the hood, a stash is just a commit anyway. You might retort that this is an implementation detail but as usual with Git, this leaks into the way it is used: if you ever lose a stash by mistake, you will have to go fish the corresponding commit in the reflog.

I use stashes via the autostash feature. For unspeakable reasons, I have to have locally modified files in my repo to run the software and the tests. (Of course, it shouldn't be like this.)

So I can `git pull --rebase --autostash` and this works even with local modifications.

TIL, thanks! I've been stash; rebase; pop'ing

I have a vague recollection of feature parity with Mercurial's shelves but I don't remember which came first. They look very similar: https://www.mercurial-scm.org/wiki/ShelveExtension

Perhaps a Mercurial user can say if shelves are more useful? (And if so, why?).

I'm a Mercurial user and I use bookmarks instead, aka git-like branches.

> What happens if, like many people, you're working on a few branches at a time. Could be a monorepo, could be a couple long living branches, w/e. What do you do if you've done work against a branch and want to lift and shift to another branch?

One "right tool for the job" I really underappreciated until recently is git worktree, which fits this use case neatly, I think, without the risk of losing your stash.

>Never stash

Er, no. Right tool for the right job.

Yup. pull --rebase --autostash for example. Technically also stashing, and would be insane not to use it :)

More serious note: agreed. I use stashing multiple times a day and never have problems with things being messy. Though I admit I usually look t stashed in a gui, not from the terminal.

And another episode of "how did I not know git did this". Thanks for this.

Agreed! I have trouble taking anyone's advice seriously(even if they contribute to a major library) when they say the phrase "Never do X". The only thing I feel confident in saying I'll never do is committing to the prospect of never doing something.

"One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision."

-Bertrand Russel

> Stash is used to record the current state of the working directory and index while also returning to a clean working state.

Er, no. Stash does not return to a clean working state. Stash stashes modifications, not new files. And it makes a real mess if those new files happen to exist on another branch you want to check out.

Stash seems like a half-implemented feature tbh, if only for the infuriating limitation above. It's really just not safe to use.

> Adding the -u option (or --include-untracked) tells git stash to also stash your untracked files.


Adopting a policy to never stash is a good idea, because "If you mistakenly drop or clear stash entries, they cannot be recovered through the normal safety mechanisms." https://git-scm.com/docs/git-stash

If you're working on something and have to switch to another branch to do something else midway, that's what git-worktree is for.

git is versatile enough to enable the authors workflow and your workflow, where neither are invalid.

I’d be willing to bet they sometimes, even if rarely, use stash when as you say it’s the right tool to use.

I think the point is not stashing meaningful amounts of changes, as if they can be recalled later. Sounds like solid advice that requires some discipline

Well this blew up and lots of you missed the point

> right tool for the right job

I'll give you that this is (slightly) ambigious. But here's the gist

>you can safely ignore the opinion of anyone who says "never do X"

There are lots of alternative tools listed in this comment tree.

Stash has a purpose. You should know what that purpose is. A tool is only as good as its user

Just to add a datapoint. This[0] is what Linus has to say about rebasing and merging, and these[1] are the kernel development guidelines, when it comes to using git.

Of course, not every project is the kernel and to each their own, but I find it's useful to know how git is used in the project for which it was originally made.

[0] https://www.mail-archive.com/dri-devel@lists.sourceforge.net...

[1] https://www.kernel.org/doc/html/latest/maintainer/rebasing-a...

The most important bit in my opinion:

> When merging fixes and features into master, we avoid merge commits and use rebases and fast-forward as much as possible.

Rebasing feature branches, and avoiding non-ff merges has been the best change I've ever made to my workflow. Makes it very easy to keep a clean, readable history that is actually useful when doing code archeology months later. Resolving conflicts is far easier than via merging too.

If you have tooling to support it, rebase-and-ff-merge is perhaps an even better workflow. In this model every commit on the main branch has a first parent that's the previous merge, and a second parent that's a linear set of the commits that were landed together, with the parent of the first commit being the previous state of master. This has the following advantages:

* It's clear which states master has actually been in, without having to resort to squashing each merge into a single commit. This means that you know which commits actually passed CI and are therefore good rebase targets. People often claim that when merging multiple commits each commit should individually pass CI, but that's almost impossible to achieve in practice.

* Development can split single features into multiple commits for easier review and understanding later. This can be particuarly important if the changes depend on each other, but different people need to review different parts of the overall change. That's another thing that most tooling is really bad at.

* The history is basically linear. The merge commits are all empty, and there is one linear history for all the states of master (the first parent of each merge commit) and one linear history for all the individual commits (the second parent of each merge commit and only parent of each non-merge commit).

Of course this approach isn't well supported by mainstream tooling (e.g. GitHub) and probably requires a custom bot to do all the rebase-and-merge operations. It is pretty well supported by git itself, however.

Does anyone know how to make this "cactus" workflow work better on github?

We use it in our organization and between us we know to always manually rebase before merging. However, when we receive an external PR from someone else it's a pain. It's also easy to accidentally forget to rebase before merging.

You can allow rebase merging [1] while disabling other PR options, and require linear commit history [2] if desired.

[1]: https://docs.github.com/en/free-pro-team@latest/github/admin...

[2]: https://docs.github.com/en/free-pro-team@latest/github/admin...

Thanks, but I don't think that is quite what I want. What I mean by "cactus" history is that the feature branches have a linear history that starts at the tip of the master branch but when we merge them back into master we still use a merge commit. Looks like this:

      o-o-o   o-o-o   o-o
     /     \ /     \ /   \
We like doing that because, as the grandparent poster said, it clearly delineates the point where each PR was merged. AFAIK, the Gihub option to require a linear history assumes that you would want to flatten all the commits without any merge commits at all:


I don't know a way to enforce this, but what may help is knowing that by default, you can push into the PR branch even if it is from someone else's fork: https://docs.github.com/en/free-pro-team@latest/github/colla...

I always go into each new repo I create and turn off the ability to merge and require commits to be up to date with master, I think this might get what you want?

Do you know can I enforce that a PR must be up to date with master before it can be merged? I don't remember seeing that option in the branch protection settings.

"Require branches to be up to date before merging" is a check box under branch protection rules, I always turn it it. It's under the required status checks section.

Thanks! Turns out that this setting is hidden unless you first check the "Require status checks" option.

Oh good to know! I forgot it's hidden until that section is enabled. Does feel like a feature that should be top level, OSIT!

The issue is that Git sucks at rebasing. Every conflict you fix when rebasing will need to be fixed again next time you rebase.

Enter git rerere: https://git-scm.com/docs/git-rerere

This will "REuse REcorded REsolutions" of conflicted merges.

Once enabled, it will record how a merge conflict was resolved, and replay it back the next time it encounters it. Saves a lot of time in cases where you already solved the merge once before.


rerere gives me nightmares. Much prefer some delicate merging footwork if at all possible.

I can confidently say I've never encountered this. To diagnose I'd have to see what exactly you are doing, but I suspect you have issues with the way you organize your work. Git is not a magic wand. Making effective use of a hammer starts with the boards and nails.

True, it's one of the downsides. You have to also be in the 'short-lived branches' camp for it to work well - which you should!

Maybe that's because I came from Mercurial but I dislike rebase workflows. You can rebase as long as it is in your repository and you didn't publish anything, you are free to do whatever you want on your PC. But as soon as it becomes public, it is history, and you don't change history.

Git is decentralized, and works kind of like a blockchain. With Bitcoin, the foundation is that everyone share the same history, no one should be able to rewrite transactions. I expect the same behavior from decentralized version control systems.

And if you make a mess, too bad, it is here to stay. It is ugly, but it is what really happened.

If it really is a huge mess, call the admin so that he can fix the thing, notify everyone that the history has changed, and have your name added to the hall of shame.

But that's a personal opinion. I know some people prefer a clean (but fake) history over a messy (but true) history. Continuous integration tools tend to like clean histories for instance. You can even have both system coexist. A messy/true history branch where developers work and a whitewashed version running in parallel for integrators.

The problem with rebase is that you must resolve conflicts for every commit. This can take a lot of effort versus a single merge at the end.

You end up doing a lot of conflict resolution when rebasing, increasing the chances that you make an error.

And if you do make an error during conflict resolution, there will be no record of it because you've rewritten history.

We stopped most rebases for this reason, and just do normal merges. Conflict resolution is easier, and if we do make a mistake, it's at least documented in the history, and it's easier to fix.

> Ordinary days, I issue git commands several hundred times.

I wonder if this is an exaggeration, or if the author truly has that git-heavy of a workload?

Assuming you're working an 8-hour day, that's 480 minutes. If you're issuing "several hundred" git commands, I'd take that to mean a minimum of 300. So that's a git command every 96 seconds. When do you have the time to write the actual software in between all of those git commands?!

I decided to explore this a bit. In reality, it's not that the git commands are spaced out equidistantly, it's that you have a flurry of git activity then go back to coding.

So, an individual commit can easily hit 5 commands. I always do a `git status` before and after (2), plus maybe a few git adds (2), the commit (1), then potentially a push (1). That's already 6 commands for each commit. In a day I usually do approx 15 commits(±10), as well as re-bases. That's around 90 git commands just for committing. Through in a few rebases, a fetch, and an amend, and you're easily over 100.

The quoted OP said "several hundred" commands. Just with commits on a pretty typical workflow, I'm at over a hundred. It doesn't surprise me that someone could double it.

I often wear the hat of the "git expert" on a project, and there are some days I'm doing significantly more than just commiting and pushing code too. I have a lot of extra commands I'll run like `git log HEAD..@{u}` which shows the diff in the logs between my current branch and the upstream. And I use `git diff --cached` pretty heavily. Git blame is also invaluable when debugging. These tools don't take a way from actually writing software, they enhance it.

Could be some back to back commands:

git add .

git commit -m “update”

git push

Although if you’re doing it that often I’d want to set up .bashrc to condense it to a single command.

> I use git almost exclusively from the command line in a terminal.

Is certainly key. Most git frontends seem to be really quite bad for many reasons. Though "Push failed, want to pull, merge and push again? [Yes]" => constant "merged ssh://upstream.server/foo/bar/repo.git merged into branch master" commits being added to the history certainly irks my OCD the most. These frontends also seem to try and hide what git does, which leads to incorrect and confused mental models and much more frustration down the line than just learning to do it the git way directly. Yes, git is a "my way or the high way" tool, that's not great or something to emulate, but it is what it is. Trying to work around that only results in more pain.

Only use the git command line, use gitk for browsing history graphically if need be, and git-gui for preparing commits. git-gui seems to be the best GUI for making commits (and is probably written in Perl with Tk).

Logical conclusion from "only use the git command line": Also never make a dirty merge from your web browser.

I would like to say that not all git front ends are like that. I used to be a CLI only user, but now you can pry magit from my cold, dead hands. It's very close to the command line in what it does, it's very easy to see what exactly it does, and where it abstracts from git commands, it does so while following Git's mental model very well.

It has great discoverability while at the same time being an amazingly streamlined power user tool.

In other words, there can be great git front ends. It's just that most of them suck, usually because of a desire to dumb down the git UI. If you don't do that, and accept Git's complexity it can work out great.

I’ll second this and note that the discoverability of magit has helped me learn about features of git I didn’t even know existed, while simultaneously making them super easy to work with. Worktrees is the particular example that springs to mind.

It also helps to provide sane defaults, for example -f being force-push-with-lease instead of a regular force push when pushing.

The thing I love about magit is that it teaches you how git works instead of trying to hide it from you.

I'm an old school developer, but really hate the complexity and syntax of git.

Therefore, I use the GUI that comes with Visual Studio Code and the "Git Graph" plugin.

I work with juniors, which for some reason always want to do things in the command line. A lot of times I have to help them out with git issues, and can always to that using the GUI. When I see them work in the command line, it's always so slow for them to type in things.

Want to switch branches? Click lower left and select your branch. Want to sync with server? Click lower left on the sync icon Want to commit? Just type your commit message and CTRL-Enter. Stop wasting time adding files on the command line. Want to prune your origin branches? Click on the Git menu and prune pull.

For this last one, I always need to look up the syntax when doing it on the command line.

Git is already complex enough (compared to SVN), so if you're junior, for god sake use the GUI menu!


I have had precisely the opposite. Junior Devs using the GUI ( in VSCode ) but breaking things badly.

And then I have to help them out using the command-line

Where I work all the juniors try to use the git cli because the Internet has them thinking that using a GUI is looked down upon, but they don't even bother to learn the cli very deeply so they fail, break things and make messes.

One guy kept having to comb through dozens of local branches that had since been deleted from GitLab because he never pruned any of them. Fortunately, this is the most common type of breakage or mess - one that only affects the developer locally, because we protect the master branch on our server and use pull requests.

In VS Code I only have to know `Ctrl+Shift+P` to use their git "GUI" (if you can even call it that). One thing I like about it that you don't get with the cli (out of the box) is an instant overview of all the available commands without having to type much or switch views.

The juniors where I work have also been brainwashed to use Macs which they struggle with; mostly due to the bad window management, non-discoverable features and their lack of will to spend any time learning their tools deeply or customizing them. Meanwhile, I've been using Linux since the start of my career in the late 90s, I learned the basic Git flow over 10 years ago and have almost never had to use the git cli in that entire time while churning out hundreds of project.

> In VS Code I only have to know `Ctrl+Shift+P` to use their git "GUI"

You don't even need to know that; the operations are available through a fairly straightforward, reasonably discoverable GUI, without using the command palette, which has a nice heiriachical structure.

OTOH, neither of those paths is much good if you don't already understand the semantics of git operations.

Maybe you have a point. I can imagine when things are failing, they start clicking all kinds of things.

My juniors get stuck on the command line and then contact me :).

So maybe it is indeed better that they use their limited knowledge of the command line ;).

Push and Pull almost always from the cli. But one of the things I find tools like Sourcetree useful for is selecting which changes (hunks, files or selected lines) to stage for commit, which is in my opinion much easier than with the CLI only.

> selecting which changes (hunks, files or selected lines) to stage for commit

I never understood why people feel the need do this. If you are working on a bug or feature, why put unrelated changes in that branch? It only creates confusion and probably messes up your tests too. And for what? It seems like a chaotic way of working to me.

Who said they were unrelated? Sometimes a single commit is the best presentation of a bug fix/feature implementation, sometimes it makes more sense to split the work into multiple commits.

I can understand that. But in my mind, if things are separate enough that I want multiple commits, then I would work on them separately. If a feature needs A, but A needs B, then I would work on B, commit that and only then work on A. Sometimes working on A will cause me to change the committed B part a little, but that's fine. At the moment in time that I committed B it was a perfectly reasonable solution that I can revert back to.

You know what, typing it out I can totally see why people do it the other way. A matter of personal preference probably.

For me, I often don't know exactly how I am going to do something until I have done it.

Once it's done I will split the changes into commits that make logical sense, for someone wondering how the new code changes the behaviour of the existing code.

For example I might be working on a specific feature and discover that my dev environment is missing something (For example replacing node by nodemon). I will make the change on the build script but I will not commit before testing the process for a couple of days, and certainly not before finishing the feature/bug I'm working on.

Try git add -uip

Lazygit (https://github.com/jesseduffield/lazygit) is pretty awesome.

GitKraken allows you to set "pull fast-forward only" or "pull rebase" as the default, very nice. This gets you out of most of those problems.

You can also do merge by dragging branches in the GUI which for me lowers the risk of taking the wrong branch. And it has a nicer looking interactive rebase than the command line which is very good for beginners.

You should give GitKraken a chance: https://www.gitkraken.com/

I just did a week ago! And I still find Sourcetree's interface a lot clearer and cleaner for this specific purpose.

do yourself a favour and look at the eclipse git client

I use almost exactly this workflow with rclone.

This bit resonated particularly:

begin quote

Never merge with GitHub!

There’s a button GitHub that says “rebase and merge” that could theoretically be used for merging pull requests. I never use that (and if I could, I’d disable/hide it). The reasons are simply:

I don’t feel that I have the proper control of the commit message(s)

I can’t select to squash a subset of the commits, only all or nothing

I often want to cleanup the author parts too before push, which the UI doesn’t allow

end quote

Not being able to selectively squash and edit the multiple commit messages is a big missing feature in my workflow.

Personally, I always merge with Github. Never had an issue, and never had concerns about non-linear commit history. So much is made about commit history that I think there are flaws elsewhere in other people's processes, or people are just getting off on their git-fu.

But this comment will just descend into a dull, tedious flame war. I don't care. git merge is totally fine.

We do squash-and-merge for PRs. Works great so far. A minimal unit of change is thus not a single commit in a PR, but the whole diff introduced with a PR.

I use the rebase and merge button when the author has clean commits. Otherwise yeah.

only related by "git" but while I believe i'm fairly proficient in git I'm not sure how to go about explaining to an inexperienced person how to fix a merge conflict.

It feels like not a simple thing to explain.

As an example someone sent me a PR with 15 commits. github claims only 1 file has changed and it's a new file so no conflicts (which AFAIK is correct) but git complains that somewhere around commit 4 there's a conflict.

The person sending the PR is not git comfortable, otherwise they wouldn't have this PR with 14 old unrelated commits showing up.

I have no idea what state they are currently in as well. meaning, I know the state of the PR but I don't know the state of their local machine.

I can clone their branch and make the merge myself but their next PR will just be the same branch they continue to work on.

Explaining to them how to fix what they have and how to proceed going forward seems like it requires a very large reply. Several pages. (here's how to fix this PR, this how to fix the state of your main/master branch, here's how to make different branches, here's how to update after each PR, here's what to do if the conflicts are real, here's what to do if you haven't made any more changes to your main branch., here's what to do if you have already made changes to your main branch.

I have no suggestions for solutions except to try to point them to other articles/pages/tutorials on the web

I can relate, and I don't think there's a way around learning Git properly. Here's a quote from a post of mine:

> Version control is an underrated skill. Most software engineers use it daily, and yet, many are not willing to invest more than necessary to learn it. That’s fine, but knowing more than commit/push/pull will at least make you more efficient. It will also help you solve issues you (and your colleagues) may encounter.

I've had this issue a lot in the past, but mostly at work. So yes, I will spend the time to help my colleague debugging their issue. Eventually, they will get fewer issues, but the time I have to spend for their "learning by doing" would have been better invested in learning Git fundamentals.

I've also had some open source contributions during Hacktoberfest, and I went with the easy way out of doing the clean-up work myself. But as you've stated, people might not learn from this, so that's not optimal.

> git complains that somewhere around commit 4 there's a conflict.

That sounds like a rebase merge, which might be the setting in the repo. A normal merge wouldn't attempt commit by commit.

Looks like my flow. One thing I do wonder, how do people deal with the tests and the time it takes for a PR to be approved (including tests, discussion, people in different time zones)?

I often have interdependent branches (large pieces of related work chopped into small tasks) and take my previous branch along in my new one, this leads to an order in PRs sometimes when a big tasks is followed by a small/fast one. Sometimes I think, come on team, pick up my PR!

I don't often have that much interdependent work, but if I do, I usually just base the new PR off the old one, and rebase them after the first one is merged. Worst I've experienced is four of those PRs in a row, which wasn't too bad.

My PRs sometimes get queued up like that too. Right now I'm waiting to merge an OS upgrade behind a single python package upgrade. I try to anticipate it in sprint planning and do reviews while I'm waiting for reviews.

> The downside with not using the merge button is that the message in the PR says “closed by [hash]” instead of “merged in…” which causes confusion to a fair amount of users who don’t realize it means that it actually means the same thing! I consider this is a (long-standing) GitHub UX flaw.

This is so true, if the automatic detection of the PR merger vs. closure is hard (and I can see some reasons why) there should at least be a switch for maintainers to update the outcome message.

Does anyone know if there is a issue-tracker where this is reported? I'd love to +1 that.

This UX-flaw is why I'm currently "forced" by my coworkers to use the buttons on GH. My git merge-scripts have some "cleanup" which I now need do manually (and thus get forgotten) for example.

If you want more info in your prompt, I recommend

https://github.com/lyze/posh-git-sh (bash)

https://github.com/dahlbyk/posh-git (powershell)

Besides displaying which branch you're on, it also shows changed, added,... files and other useful info

I find stashes quite useful and intuitive. For me, rebasing is the root of all evil.

As soon as two people work on the same branch, rebasing becomes a problem.

As long as you're the only one modifying the branch, rebasing works nicely.

I don't like rebase in solo code either; I want my personal history to be the truth, not some convenient fiction.

In a team environment, somebody always insists on using rebase and then invariably they push a rebase to master and hijinks ensue. I wish there was a fork of git with rebase disabled.

Where I work we always rebase on branches for features/bug fixes/tickets/whatever, but when we merge into master we always do `git merge --no-ff origin/branch`. We don't really care what the history of our branches are generally, and we preserve our commit history when we go into master.

We never squash when we go into master either.

> I want my personal history to be the truth, not some convenient fiction.

I am mostly using the Git history when going through past changes. Why does this line do X? I just git blame this line and see exactly what happened. This is the time I really hate all those "oops, fixed mistake" and "forgot this change" commits. I don't care about those things, even if it's the "truth". They're just useless and distracting. Doing an amend/rebase/squash would have been perfectly fine in such a scenario. I have written more about rewriting Git history here [1].

> In a team environment, somebody always insists on using rebase and then invariably they push a rebase to master and hijinks ensue. I wish there was a fork of git with rebase disabled.

That's why shared branches like master are usually protected, so you cannot push an alternative history.

[1] https://darekkay.com/blog/git-rewriting-history/

I git like this: https://gist.github.com/chx/3a694c2a077451e3d446f85546bb9278

A few utility commands and a most important safety net against git reset --hard throwing away work.

I suspect OP meant `PS1='\u@\h:\w\$(brname)\$ '` rather than `PS1="\u@\h:\w\$(brname)$ "`:

- double quoting the command expansion would lead to `brname` being executed once at shell startup (unless this assignment is happening in a `PROMPT_COMMAND` function or something[1]).

- A literal backslash before the dollar sign gives a "#" at the end of the prompt for root.

[1] https://gitlab.com/victor-engmark/tilde/-/blob/4dc4077a19250...

I use git flow, and it took a while to get used to but I now do almost all of my git stuff in my IDE, pycharm.

I used to like tabbing over to terminal to run my git commands and do my diffs.

Now I use the git flow plugin and do my full process including amends, push, etc interactively.

Much of it can be done by keyboard and the closeness to the auto linting and reformatting has caught many small errors, left behind little items and generally improved code quality greatly.

> Never merge with GitHub!

While I agree with the spirit of this comment, in practice I do this:

1. Update:

    git pull --rebase
2. Clean up my commits:

    git rebase -i HEAD~X
3. Push:

    git push origin BRANCH -f
4. Merge in GitHub

Works pretty well for me. That being said, I would love to be able to take care of 2 and 3 within GitHub for branches that are already updated to the parent branch.

Seems pretty standard to me. Anything notable here?

If it’s not particularly contentious maybe it’s a good example for neophytes to follow?

This is more advanced than the git stuff I use - I only use it personally - so it's nice to "read ahead" a bit.

Standard for github, maybe.

Standard for other places as well.

> Seems pretty standard to me. Anything notable here?

I agree, it's pretty vanilla git usage. I was curious to see any insightful take of whimsical twist, but it boils down to a basic take of good old git flow, which is git 101 for a long time.


The only surprising thing in the article was the refusal to use git stash, which can't really be explained on a rational level.

It's not git-flow, as all commits are rebased on top of master to make the history of master linear.

It is one of the more standard workflows, and it's a concise overview of how the maintainer of curl does their job.

For a more complex workflow, I recommend reading the notes from the maintainer of the git project [0].

0: https://git.wiki.kernel.org/index.php/MaintNotes

> It's not git-flow, as all commits are rebased on top of master to make the history of master linear.

I fail to see how that is relevant. I mean, the only possible impact that has is if individual commits within a feature branch are recorded or not, which is arguably irrelevant. You wouldn't get a different workflow if instead of reading you simply did a squashed merge.

> It is one of the more standard workflows, and it's a concise overview of how the maintainer of curl does their job.

It's gitflow, and leaving out feature branch commits doesn't make a difference.

I don't know what you think git-flow is, but a linear history it is not.

Look at any post on git-flow (or even just the original [0]), and you will see a myriad of merges.

Quoting the original git-flow post:

> When the source code in the develop branch reaches a stable point and is ready to be released, all of the changes should be merged back into master somehow and then tagged with a release number. How this is done in detail will be discussed further on.

The examples further on all use `git merge --no-ff` (ie no fast forward).

This is very different to what is described in this post:

> When merging fixes and features into master, we avoid merge commits and use rebases and fast-forward as much as possible. This makes the branch very easy to browse, understand and work with – as it is 100% linear.

So no, this is not git-flow.

[0] https://nvie.com/posts/a-successful-git-branching-model/

> I don't know what you think git-flow is, but a linear history it is not.

Your statement makes no sense at all. Gitflow is a workflow. What do you believe it's supposed to be? It's irrelevant how you see commit histories, because with regards to the master/mainline branch it's always linear, isn't it?

What do you personally believe gitflow is?

> Quoting the original git-flow post:

I don't know what you expected to show, but you are only describing a workflow. Changes are made in dedicated branches, and later these changes are merged back into the master/mainline branch in a single commit, which is expected to have been validated and tested and working. That's a workflow. How the log ends up looking is entirely indifernet or irrelevant, isn't it?

Again, what do you personally believe is the whole point of a workflow like git flow, specially in light of allowing large teams to continuously integrate their work ? I mean, what do you believe is the whole point of getting devs to work on branches independent of what changes go into master/mainline, and leave merging as a last step that's the responsibility of the developer prposing a change?

Sorry for the late response, meant to reply hours ago!

I guess that first sentence should have been "I don't know what you think git-flow is, but it does not produce a linear history" or soemthing like that.

The history is very much not linear in git-flow, and for good reason - but it makes things more complicated and most people don't need it.

If, as you can find examples in other parts of thread, you use rebase-and-merge with git-flow you end up with something very similar to the methodology used in the article, but it is still different.

One of the main downsides to the featured method is that it's not clear which commits "go together" from the git history - you have to look at the pull requests in github. With git-flow, or rebase-and-merge, you always have a merge commit that lets you distinguish between commits that are 'releases' and commits that are part of a series.

So that is the key difference - the structure of the git log.

The person you are responding to is only making the point that the OP's rebase flow is a very different workflow from git-flow. The pros and cons of each approach are significantly different, and the commit history will demonstrate the two different approaches.

It's not git flow. Git flow is horrendous, completely unnecessary and designed by someone who doesn't understand git.

The article describes a pretty standard git rebase workflow that is simple and works well.

Git Flow actually makes some sense if you need to track and retain history of self-contained fixes and features across concurrent release trains, where "retain history" means more than just a commit message.

With that said, I don't think this need is particularly common. Most projects are happier to throw away merge history in favor of a linear history with ephemeral branches and semantic versioning, and that's perfectly fine. The repository layout should be dictated by the project's needs, be it a glorified personal Undo Log, or a management tool on top of a Subversion repository.

The author of "GitFlow Considered Harmful" put out a simpler workflow that achieves all the upsides of GitFlow with none of the downsides:


If you can tell me something GitFlow buys you which OneFlow doesn't, I'd be very intrigued.

> The author of "GitFlow Considered Harmful" put out a simpler workflow that achieves all the upsides of GitFlow with none of the downsides:

I see no relevant difference between that workflow and GitFlow, besides the rebranding of old concepts and some unexplained focus on merging options.

I've also revisited the author's old rant from 2015 regarding gitflow and the only tangible criticism he had was the separation of the master/mainline and development branches, which is extremely shortsighted and miopic as it fails to acknowledge the reality of non-CICD/old-fashined software release lifecycles.

GitFlow has one main concept: using feature branches to do develop work, use release-specific branches to integrate changes, establish and enforce upstream/downstream relationships between branches. The rest is just rebranding and nit-picking over semantics.

That's not git flow, that's just release/maintenance branches.

The main feature of git flow is having multiple redundant branches for no reason. Namely a separate develop and master. There is no point of that at all. Look at the diagram, move all tags from master to develop, delete master and rename develop to master. There, you now have the workflow you've described without redundant branches.

> The main feature of git flow is having multiple redundant branches for no reason. Namely a separate develop and master.

This statement is simply wrong and ignorant.

The reason why mainline and develop branches exist is due to the fact that production-ready code and unstable code are not the same ops-wise.

Nowadays, with the dissemination of CICD practices, that difference has been shifted away from the source code repository and into the Delivery/Deployment phase of a CICD pipeline. Yet, the overall principle is the same, and arguably its still here. Calling those differences "redundant and for no reason" is just ignorant at multiple levels, from software engineering to the fact that the sofware lifecycle process for commercial software projects, the kind that had to "go gold", had very specific requirements.

You don't have to look hard at the git flow diagram to see the redundancy. It's plain as day. Of course I'm talking about today. Why would I be talking about 20 years ago? We have continuous integration and automated build pipelines so there's no reason to have your build artifacts living in the git repository. git is for source code. There are other solutions better suited to storing artifacts.

IMO, git flow came about when people didn't want to or couldn't abandon the idea of trunks in TFS.

I've heard its benefits explained as "more security for the business", while in practice it only leads to extra complications and accomplishes nothing a git tag couldn't.

Yeah, that matches my own analysis. I think it also comes from a time when people considered the source code to be an artifact and creating a source distribution was just checkout+zip. So you'd make a release commit with a version of the source code where the version is hard coded (the git flow page uses a "bump_version" script). Nowadays most projects can build an artifact (source or binary) from any version of the source code and derive their version number from git at build time.

The rational explanation is that branches are much easier to work with. You can rebase and merge branches with your standard workflow, but stashes can only be popped in order. Branches can be reordered and changed at will. As beginner I started with some stashes, but never needed them since. You already got tons of tools for branches, but none for stashes. Stashes are like submodules for me, quirky.

I gave up on git stash because over time I would end up with too many stashes that I couldn't keep straight in my head. Now I just make temporary branches and cherry pick from them as needed.

Possibly controversial, I like to add a "Change(s) from PR review" commit to the original set of changes of a PR then force push (with lease) amendments to that commit. That way regardless of when a reviewer last read the PR they can see changes since the original. If the changes get significant, I make a new PR and reference the old closed one.

The ease of branching has people treat branches as if they were cookies. You cannot possibly have too many of them. But, actually, branches make refactoring more difficult. What if one person just added a parameter to a function while another person is adding new calls to that same function. The automated merge cannot fix this conflict. The KISS principle that is the corner stone of all software development would dictate that you should have the smallest number of branches possible so everyone gets the latest code as soon as possible and conflicts get minimized. For unfinished features one can have feature flags. While there are cases where one needs a feature branch I think it is best to only make feature branches if they are necessary and most development does not need to be on a feature branch. The git flow also features a development branch besides the master branch. This also seems an unneeded complication for most projects. If you have specialized testers you probably do need release branches for every release that you make but then again if you don't have specialized testers you probably don't. Here is a good talk about the harmful nature of feature branches. https://www.youtube.com/watch?v=h7LeD7VevyI

As said in one of the YouTube comments for the video you linked here, it's not so much feature branches that are the problem, but long-lived feature branches.

If you want to add a complex feature to a project, you could do it via several branches, one after the other. For instance, the first branch could introduce a feature flag and the second branch implement part of the new feature, and so on.

This is more of a semantic quibble than anything else. Yes, long-lived branches are not just a problem but absolutely terrible. I have seen one that lived for some 4 years or so. They are a problem to the extend that they live long. The shorter they live the less of a problem they are. So, take this to its conclusion and conclude that the best feature branch is one that is only one small commit long. And now the feature branch is completely redundant....

> What if one person just added a parameter to a function while another person is adding new calls to that same function. The automated merge cannot fix this conflict.

If you're working in a large codebase and the function in question is part of your API, don't mutate it, add another function and deprecate the older function.

In any case, either developer A or developer B has to pay the cost of mutating the function at some point.

The point is that the problem is the smallest if the times between the point where A and Bs changes get into the mainline is the smallest. If A adds his parameter on Monday and B adds that same day three new calls and on Tuesday both merge their changes three calls need to be adjusted. If they merge on Friday and B has added three more calls on every day before Friday they have to adjust 12 function calls. Also, when B does not agree that the addition of a parameter is a good idea they can discuss this on Tuesday but otherwise they may wait doing this until Friday. It is also good to get this discussion out of the way as soon as possible. What if they decide that indeed the parameter that B added was good as such but actually needs to be of a different type? In the meantime B also has perhaps also added 12 calls to the function. These 12 calls now also have to be adjusted alongside with the 12 calls in the code of A. The general idea is that one wants to diverge from mainline for the shortest amount of time possible. I think the optimal amount of time for diverging from the main line is one day. If it becomes shorter the problem becomes that one would also need code reviews more then once a day and that becomes a bit burdensome too but code review every day generally sounds like a good idea. Preferably one should also pick a fixed time of the day for the code review. At my previous job I would do this and produce perhaps 10 small commits a day that would all get into the main line the next morning. If everybody does that one minimizes the size of possible conflicts.

I really like the idea deleting feature branches after they are merged, but I rather would have a recycle bin or trash can concept where the branch is deleted after ~7-30 days in case I make a mistake and delete the wrong branch. Searching for a SHA to recover a branch is never fun.

+1 "Never merge with GitHub... I don’t feel that I have the proper control"

Me neither, not since I got bitten by the 'Resolve Conflicts' button on the PR page also merging the PR into the target branch.

Maybe it's PEBCAC, and to be fair it is documented[0], but the big red warning in the docs is missing from the page where you actually do the action. To make it more confusing there's also a disabled 'Merge pull request' button that implies that resolving the conflicts and merging the PR are separate actions

0: https://docs.github.com/en/free-pro-team@latest/github/colla...

Hm, stgit wasn't mentioned.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact