If "git pull --rebase" succeeds without reporting a merge conflict, then it's tantamount to having executed the command with "--ff-only".

I believe you are incorrect however. "git pulll --rebase" will not ever rewrite history from the origin repository. It will only ever modify your local unpublished commits to account for new commits from the origin branch. If your change and the new commits from origin aren't touching the same lines or files, then the update is seamless [1].

If there is a conflict because your change and a new commit from origin both changed the same line in the same file, this can result in a merge commit that you need to resolve. But you resolve this locally, by updating your (unpushed, local) commit(s) to account for the new history. When you complete the merge, it will not show up as a merge commit in the repository -- you will simply have simply amended your local unpublished commit(s) only to account for the changes in upstream, and you will have seen every conflict and resolved each one yourself. When the process is complete, and you've resolved the merge, you'll have a nice linear branch where your commit(s) are on the tip of the origin branch.

The flag "--ff-only" basically just means "refuse to start a local merge while performing this operation, and instead fail".

Because of the potential for these merge conflicts, it's a best practice to "git pull" frequently, so that if there are conflicts you can deal with them incrementally (and possibly discuss the software design with coworkers/project partners if the design is beginning to diverge -- and so people can coordinate what they're working on and try to stay out of each other's way), instead of working through a massive pile of conflicts at the end of your "local 'branch'" (i.e. the code in your personal Git repo that constitutes unpushed commits to the upstream branch).

Additionally, all of the central Git repositories I've worked in in a professional context were also configured to disallow "git push --force" (a command that can rewrite history), for all "real" branches. These systems gave users their own private Git repo sandbox (just like you can fork a repo on GitHub) where they can force push if they want to (but is backed up centrally like GitHub to avoid the loss of work). This personal repo was very useful for saving my work. I was in the habit of committing and pushing to the private repo about once every 30m-1h (to eliminate the chance of major work loss due to hardware failure). Almost always I'd squash all of these commits into one before rebasing onto master, so that the change comes in as a single commit, unless it would be too large to review.

In the occasional circumstances where I've legitimately needed to rewrite history for some reason -- say credentials got into the repository, or someone generated one of these "merge master into master" commits -- then I would change HEAD from master to another branch, delete master, and then recreate it with the desired state. (And even that operation would show up in the system's logs, so in the case of something like credentials you'd additionally contact the security or source control team to make sure the commit objects containing the credentials were actually deleted out of the repo history completely, including stuff you can find only via the reflog.) Then contact the team working on the repo to let them know that you had to rewrite history to correct a problem.

I would recommend disabling "git push --force" for all collaborative projects. If you're operating the repository, you can do this by invoking "git config --system receive" and setting "denyNonFastForwards true". In GitHub there's probably a repository setting switch somewhere.

Once professional software engineers start working with Git all day long, they quickly get past the beginner stage and the need to do this kind of stuff is very rare.

[1] It doesn't mean the software will work though, even if both changes would have worked in isolation. You still need to inspect and test the results of "git pull (--ff-only)", since even if there are no conflicts like commits that modify the same lines as yours, or there are conflicts that Git can resolve automatically, it's possible for the resultant software logic to be defective, since Git has no semantic understanding of the code.