composer/composer — pull request #3229 by deizel (2014-08-21 15:12:43+00:00)
Title: Replace dirname call with shell substitution

- faster because it doesn't fork (http://en.wikipedia.org/wiki/Dirname#Performance)
- adds support for Windows paths (e.g. when called via msysgit)

- - -

According to the Wikipedia article (linked above) and a few other sources, this change is usually done for performance reasons (because launching subshells in a loop is *slow*).

I'm suggesting this fix because it would be nice to allow Windows users (msysgit) to install Git commands using Composer and not have to add `.bat` to their invocations.

Bear in mind that the file this patch is for only gets created on Windows so we only have to consider impact to cygwin and similar environments on Windows (`defined('PHP_WINDOWS_VERSION_BUILD')`).

- - -

Before I get started here is a quick example, trying to execute a Git command (eg. `git-mycommand`) installed in the global `bin` dir using `composer global require`:

Before fix:

```
C:\> git mycommand.bat
It works!
C:\> git mycommand
C:\Users\deizel\AppData\Roaming\Composer\vendor\bin\git-mycommand: line 4: cd: ../deizel/git-mycommand/bin: No such file or directory
C:\Users\deizel\AppData\Roaming\Composer\vendor\bin\git-mycommand: line 7: /c/Users/deizel/Documents/Projects/git-mycommand: No such file or directory
fatal: 'mycommand' appears to be a git command, but we were not
able to execute it. Maybe git-mycommand is broken?
```

After fix:

```
C:\> git mycommand.bat
It works!
C:\> git mycommand
It works!
```

After fix (from cygwin):

```
/cygdrive/c/ $ git mycommand.bat
It works!
/cygdrive/c/ $ git mycommand
It (still) works!
```

- - -

This happens simply because `dirname` does not support backslashes. From the `man` page:

> Print NAME with its trailing /component removed; if NAME contains no /'s, output '.' (meaning the current directory).

But when using Git from command prompt (MSYS-provided `sh.exe`), `$0` holds a Windows path.

```
C:\> git --version
git version 1.9.0.msysgit.0
```

- - -

Here is a test script showing the issue and the proposed solution:

```sh
#!/usr/bin/env sh
win=C:\\Users\\deizel\\AppData\\Roaming\\Composer\\vendor\\bin\\git-mycommand
cyg=c/Users/deizel/AppData/Roaming/Composer/vendor/bin/git-mycommand

echo "Command prompt (`dirname` doesn't work, proposed solution does)"
echo "---"
echo " \$0 => $win"
echo "\`dirname \$0\` => `dirname $win`"
echo " \${0%[/\\\\]*} => ${win%[/\\]*}"
echo
echo "Cygwin (proposed solution works, proposed solution does too)"
echo "---"
echo " \$0 => $cyg"
echo "\`dirname \$0\` => `dirname $cyg`"
echo " \${0%[/\\\\]*} => ${cyg%[/\\]*}"
```

Output:

```
Command prompt (`dirname` doesn't work, proposed solution does)
---
$0 => C:\Users\deizel\AppData\Roaming\Composer\vendor\bin\git-mycommand
`dirname $0` => .
${0%[/\\]*} => C:\Users\deizel\AppData\Roaming\Composer\vendor\bin

Cygwin (`dirname` does work, proposed solution does too)
---
$0 => c/Users/deizel/AppData/Roaming/Composer/vendor/bin/git-mycommand
`dirname $0` => c/Users/deizel/AppData/Roaming/Composer/vendor/bin
${0%[/\\]*} => c/Users/deizel/AppData/Roaming/Composer/vendor/bin
```