How does a team work collaboratively on a tightly integrated feature without producing a messy commit history?
First of all, see the caveats section below. Each team member has their own remote fork on Github, and dev and test local branches.
Let's walk thorugh how this works.
You have some work that diverges from your team. You make this commit in your dev branch.
Now switch to a test branch with
git checkout -b test. You'll be spending most of your time with this branch checked out, but you will never commit to it.
One of your team members has made some functionality that you need in order to continue developing.
With your test branch checked out, pull from your team member's fork on Github. This will create a merge commit, but we don't care (read on).
You have been developing with your test branch checked out so that you have the team's work in your working directory. When you're ready to commit, stash the changes you've made with
git stash, checkout your dev branch, play the changes you've made over your dev branch with
git stash popand commit.
Now switch back to your test branch and merge in the commit you just made.
Commits 7-9 are here for illustration, but are simply more of this same workflow.
Once the feature you are working on as a team is ready to go, each team member will squash the contents of their dev branch to a single commit and force push over their remote fork. Each person will also delete their test branch.
Finally, one team member will pull everyone's work into their dev branch, fix merge conflicts, do any final testing, and make a single pull request to the master repo.
You should now have a handful of commits representing the work of everyone who worked on your feature, as well as a pull request representing the entirety of a feature that should be safe.
It should be noted that this workflow falls into the "dangerous and borderline insane" category. Any time you see the term "force push" in an article on git you should treat that article with a healthy dose of skepticism, and I encourage you to do the same here.
It would be much preferable for each team member (or pair) to be working on a self-contained feature with a pre-defined interface, but sometimes that just isn't feasible.
All that said, this flow did work for our team in one specific instance. The constraints at the time required as few commits as possible while ripping out a working feature and replacing it with a better one with a single pull request.