It sounds like you have a few problems here:
1. Identifying features for a specific release
This is a project management issue, and a coordination issue. Will this feature be released before, at the same time as, or after this other feature? If releases want to happen one feature at a time, then identify that. If features are going to be grouped into releases, then figure out what the groupings are, and enforce it with the devs and the decision-makers. Use your issue tracking or ticketing system to tag releases. Make it clear that if one feature of a specific release is a no-go, then all of them are.
2. Branching strategies
Git-flow is the easy answer for issues like these, and often people use a variant of git-flow even if they don't know what it is. I'm not going to say that it's a catch-all for all problems, but it helps a lot.
It sounds like you're running into an issue with non-deterministic release strategies, where features are approved scattershot and something that started development a long time ago might be released after something that started more recently - leap-frog features.
Long-lived feature branches or simultaneous release branches are probably the best answer for these kinds of issues. Merge (or rebase, if you're comfortable with it) the latest from master into your long-running branches. Be careful to only merge in features that are already live, otherwise you'll run into the issues that you've been having now (too many mixed up features on one branch).
"Hotfix" or "bugfix" branches are an essential part of this process; use them for small one-off fixes that have a short QA cycle.
From your description, it might even be better to not maintain an offical 'development' branch. Rather, branch all features off of master, and create merged release branches once a release is identified.
Don't match up git branches to your environments, except for production == master. The 'development' branch should be assumed broken. Release branches are pushed to test environments, whether that's a QA environment or a staging envirement. If you need to, push a specific feature branch to an environment.
If you have more than one feature branch that need to be released separately but are being tested at the same time..... ¯\_(ツ)_/¯ .... spin up another server? Maybe merge them together into a throw-away branch... commit fixes/changes to the original branches and re-merge into the throw-away branch; do final approval and UAT on individual release branches.
4. Removing non-approved features from a branch
This is what the above thoughts are trying to avoid, because this is without a doubt the most painful thing to try and do. If you're lucky, features have been merged into your development or test branches atomically using merge commits. If you're unlucky, devs have committed directly to the development/test branch.
Either way, if you're preparing for a release and have unapproved changes, you'll need to use Git to back out those unapproved commits from the release branch; the best idea is to do that before testing the release.
Best of luck.