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` and `bash`, `@(.*)` is one case where you explicitly request dotfiles.

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.