For a shared branch, I would go with #3, and use it as an “integration” branch to consolidate their work.
The developers would have to use rebase to constantly replay their private branch on top of feature before merging back their work to feature, that way they are:
- solving any merge conflict locally (in their own repo)
- making the final merge (from their
privatebranch tofeature) a trivial one (normally fast-forward)
(as described in “git rebase vs. merge” answer)
The idea is that, once feature branch has to be merged in master, no more contribution is accepted on feature (the branch is “frozen”), and you can safely rebase it on top of master first, or merge it directly to master.
And then you start a new feature branch (which can actually start in parallel of the previous feature branch if needed)