In bash, without the dotglob option, hidden files are ignored unless the glob explicitly (that is with a literal leading .) asks for them. And . and .. are not ignored (contrary to what more sensible shells like pdksh, zsh or fish do).
With dotglob, only . and .. are ignored unless the glob starts with ..
With extglob, bash adds support for some of ksh extended glob operators. However, here with a bug/misfeature when it comes to the !(...) one.
In both ksh (though not pdksh) and bash, @(.*) is one case where you explicitly request dotfiles (though the documentation doesn't make that clear).
But in !(.git) you're not requesting dotfiles. ksh and zsh in ksh emulation handle it correctly, but bash presumes you're requesting dotfiles. And that includes . and .. even with dotglob. So .git will be copied as part of the copying of ..
To work around that, you can use !([.]git) (here [.] makes the . not explicit) in combination with dotglob, or exclude . and .. explicitly with !(.git|.|..):
$ bash -O extglob -c 'echo !(.git)' . .. foo .foo $ bash -O extglob -O dotglob -c 'echo !(.git)' . .. foo .foo $ bash -O extglob -O dotglob -c 'echo !([.]git)' foo .foo $ bash -O extglob -c 'echo !(.git|.|..)' foo .foo In the latter case, I would still add the dotglob option because bash in a future version may be fixed to stop including dotfiles here like in other shells.
As I use zsh which has its own extended operators (with the extendedglob option not enabled by default to keep Bourne compatibility; it also supports ksh globs with the kshglob option but those are more awkward), I'd do:
set -o extendedglob # (in my ~/.zshrc) cp -a -- ^.git(D) target/ (note that -a implies -r and you need the -- as we can't guarantee file names won't start with -).
^.git is zsh's equivalent of ksh's !(.git). (D) to include dotfiles (but never . nor ..) for that glob only.