3

Part of a project I'm working on (Windows, C#, MVC4 WebAPI) requires some integration with git. None of the existing C# git libraries supported remote cloning, so I wound up porting the parts of the JavaGit project we needed (checkout, fetch, status), and writing clone myself. All it really is is a wrapper to the git command line executable. The relevant code it calls is here:

public static void RunGitCommand(string repositoryPath, string gitArguments)
{
    // GitCommand is the full path to git.exe (currently using Github for Windows)
    if (null == GitCommand || String.IsNullOrWhiteSpace(GitCommand))
    {
        throw new FileNotFoundException("Unable to find git.exe on your system PATH.");
    }

    // gitArguments contains the command to run (e.g. "clone -- git@repo:projectName c:\repositories\repo_a8c0dd321f")
    var startInfo = new ProcessStartInfo(GitCommand, gitArguments)
    {
        WorkingDirectory = (null != repositoryPath && Directory.Exists(repositoryPath)) ? repositoryPath : String.Empty,
        CreateNoWindow = true,
        UseShellExecute = false,
        RedirectStandardOutput = true,
        RedirectStandardInput = true,
        RedirectStandardError = true
    };

    using (var p = new Process
    {
        EnableRaisingEvents = true,
        StartInfo = startInfo
    })
    {
        p.OutputDataReceived += (sender, args) => Log.Debug(args.Data);

        p.ErrorDataReceived += (sender, args) => Log.Debug(args.Data);

        p.Start();

        p.BeginOutputReadLine();
        p.BeginErrorReadLine();

        p.WaitForExit();
    }
}

Which would be called by the code as:

// names changed to protect the innocent
string localRepo = @"c:\repositories\repo_a8c0dd321f";
string gitArgs = "clone -- git@repo:projectName c:\repositories\repo_a8c0dd321f";

GitConfiguration.RunGitCommand(localRepo, gitArgs);

From within the MVC API, we use impersonation to make sure it's running as a user with a valid git login and key (no passphrase). The command above works perfectly from a command line as myself, and in an quick unit test, as well (I know it's really an integration test).

However, when it's actually called from the API as shown above, it hangs. Looking in Task Manager shows git.exe running, with the command line showing the full path to git.exe followed by the arguments above. It's not using any processor time, and only 2604K of RAM, but it claims to be running. Likewise, there is an ssh.exe process running, also with no processor usage, and 1212K of RAM, with the command line:

ssh git@repo "git-upload-pack 'projectName'"

Both processes are listed as running under my username, so it appears impersonation is working correctly.

Looking in the localRepo directory, it creates the .git directory, then hangs, leaving around 13K worth of git files in there, but none of our code. Thinking it was due to our repo being huge, I let it run overnight. Still no movement as of this morning.

Brought up LINQPad, ran:

Process.GetProcessById($gitPID).Dump()

Did the same for the SSH process as well. The threads showed them as being in the Wait state, and the WaitReason was Executive (waiting for thread scheduler). I initially assumed it was waiting for a passphrase, as my key had one. I switched to a working key without a passphrase, same result.

Git/SSH versions (from latest GitHub for Windows):
    git version
        git version 1.7.11.msysgit.1
    ssh -v
        OpenSSH_4.6p1, OpenSSL 0.9.8e 23 Feb 2007

The only idea I have left is that maybe it can't communicate with ssh-agent, which is running. It is impersonating me properly, though, so I don't know why it wouldn't work from the WebApi framework, but works fine from the "unit" test and Git Shell. I tried making sure the HOME, PLINK_PROTOCOL, TERM, SSH_AUTH_SOCK, and SSH_AGENT_PID environment variables were set, after looking through the Github for Windows setup scripts, just to make sure I wasn't missing anything.

I'm at a total loss. Here's a snippet of the log file, with some comments afterward:

2012-11-20 13:42:59.5898 Info Initializing repo at path: c:\repositories\repo_a8c0dd321f 
2012-11-20 13:42:59.5898 Debug Working Directory: c:\repositories\repo_a8c0dd321f 
2012-11-20 13:42:59.6053 Debug C:\Users\christian.doggett\AppData\Local\GitHub\PortableGit_8810fd5c2c79c73adcc73fd0825f3b32fdb816e7\bin\git.exe status --branch 
2012-11-20 13:42:59.6209 Debug HOME=H:\ 
2012-11-20 13:42:59.6209 Debug PLINK_PROTOCOL=ssh 
2012-11-20 13:42:59.6365 Debug TERM=msys 
2012-11-20 13:42:59.6365 Debug SSH_AGENT_PID=58416 
2012-11-20 13:42:59.6365 Debug SSH_AUTH_SOCK=/tmp/ssh-IgTHj19056/agent.19056 
2012-11-20 13:42:59.6521 Info git status --branch
Exit code: 128

2012-11-20 13:42:59.6521 Error  
2012-11-20 13:42:59.6677 Info Cloning repo from origin: git@repo:projectName 
2012-11-20 13:43:01.8674 Debug Cloning into 'c:\repositories\repo_a8c0dd321f'... 
2012-11-20 13:43:03.2090 Debug Could not create directory 'h/.ssh'. 
2012-11-20 13:43:03.2870 Debug Warning: Permanently added 'repo,359.33.9.234' (RSA) to the list of known hosts. 
2012-11-20 13:44:41.4593 Debug fatal: The remote end hung up unexpectedly 

I always get the "Could not create directory 'h/.ssh'" and "Warning: Permanently added * to the list of known hosts." messages, even on the command line. My H:.ssh\known_hosts remains empty, but my keys are in that directory, and git finds those just fine. The "remote end hung up unexpectedly" error was when I killed the git and ssh processes.

I may wind up switching to LibGit2Sharp for most of my needs, but that still doesn't solve my clone problem. Is something screwed up with my key setup, which again, works perfectly outside of the w3wp.exe process? Does it need to be able to communicate with ssh-agent.exe, and is not able to? Has anyone cloned a remote git repository via System.Diagnostics.Process and lived to tell the tale?

UPDATE (11/25/2012 6:54PM):

mvp was correct in pointing out that impersonation and mapped network drives don't play well together. I added the following before starting the process:

var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
Environment.SetEnvironmentVariable("HOME", userProfile);

I ran it again, and am now at least seeing a new git process in Task Manager that's taking 0 processor time, but is using an increasing amount of memory, I believe as part of the clone process. The command line was:

git index-pack --stdin --fix-thin "--keep=fetch-pack 6024 on MACHINENAME"

It just finally finished after 10 minutes (It's a huge repository), and threw the exception:

fatal: git checkout: updating paths is incompatible with switching branches.
Did you intend to checkout 'origin/8b243b8d9a5140673fc552ef7da8f0dfe9039d50' which can not be resolved as commit?

Looks like the clone worked after changing into the directory, though! The other problem is something to do with immediately calling checkout after the clone operation finishes, but is unrelated to the hanging problem.

I just need to verify my/mvp's solution on a production server, then I'll award the bounty.

Chris Doggett
  • 19,959
  • 4
  • 61
  • 86
  • What happens when you try this against another repo? Try setting up a local repository (folder), a tiny repository in a Linux box, etc, so you can see where the problem is. Try running `git clone -v` to get more info. The `359.33.9.234` is obviously obfuscated, right? – mgarciaisaia Nov 24 '12 at 23:07
  • What is your `GIT_SSH` variable is set to (if any)? Also, I don't think impersonated account would have any network drives mapped - it would explain why it thinks of odd `h/.ssh`, not `h:/.ssh` – mvp Nov 25 '12 at 08:43
  • @desert69: Yeah, it's a fake IP from CSI: Miami. I'm that kind of dork. I'll give that a try after looking through mvp's solution. Looks like it may be a problem with both the HOME variable in the impersonated process being set to a mapped network drive, and USERPROFILE being set to C:\Windows\system32\config\systemprofile. I'm getting somewhere now, and will edit the question with more details. – Chris Doggett Nov 26 '12 at 00:44

2 Answers2

2

I believe your main problem is that home directory for impersonated account is not what you think it is, mostly because network mapped drives for impersonated accounts don't really work.

As a workaround, you should set HOME environment variable for impersonated user to point to some local directory (say on drive C:) which should contain your ssh keys (without passphrases). You should test this by running git clone manually (while having fake HOME in effect) and accept known_host keys, so it would not prevent background git command from working automatically.

mvp
  • 111,019
  • 13
  • 122
  • 148
  • Thanks, getting closer, I think. Getting HOME and USERPROFILE from the impersonated process returns "H:\" and "C:\Windows\system32\config\systemprofile" respectively. Was hoping USERPROFILE would point at the impersonated user's directory under C:\Users, which is where I've moved my keys and known_hosts file. – Chris Doggett Nov 26 '12 at 00:23
  • I still can't figure out where git or ssh thinks my known_hosts file is located, and only got it to add an entry by specifically calling: ssh -v -T -o UserKnownKeysFile=%USERPROFILE%\.ssh\known_hosts git@repo_server. That added the server's key, but git still won't pick it up, and gives the "Warning: Permanently added" message all the time. – Chris Doggett Nov 26 '12 at 00:25
  • `USERPROFILE` of `C:\Windows\system32\config\systemprofile` sounds really bad - I would think impersonated user may not have permission to write there. From `C:\Program Files (x86)\Git\cmd\git.cmd`, HOME will be set as one of: `@if not exist "%HOME%" @set HOME=%HOMEDRIVE%%HOMEPATH%`, `@if not exist "%HOME%" @set HOME=%USERPROFILE%`. Try overriding to some HOME which is definitely writable after impersonation. Also, if you created known_hosts and keys manually, make sure they are readable by impersonated account – mvp Nov 26 '12 at 00:35
  • Yup, that was the problem. I'm using Github for Windows, which has the same batch script to set HOME. It was either looking in an inaccessible mapped network drive, or for a non-existant .ssh directory under the systemprofile directory. I updated the question with the fix that does set HOME for the process using SpecialFolders, and after 10 minutes of watching the "git index-pack " process use more and more memory, it finally worked! Just need to validate it in production, or maybe create a read-only key installed in a known location with the server, and the bounty's yours. Thanks again! – Chris Doggett Nov 26 '12 at 01:21
1

Home being h:\ is a little broad, but other than that, I would say your next step is to create a .ssh directory (h:.ssh) with the correct permissions, should be read only for the web user and no access for any other user.

jeremy
  • 4,294
  • 3
  • 22
  • 36
  • H:\ is just a mapped network drive that IT sets up for us pointing to a directory on a server somewhere. I think the "h/.ssh" is an artifact of cygwin in the MSYS git that Github for Windows is based on. When I tried just running "git pull origin" from the local repo in a non-Git-shell command prompt, it gives me "Could not create directory c:/repositories/repo_a8c0dd321f/h/.ssh". – Chris Doggett Nov 20 '12 at 21:23
  • What about key exchange? How are you setting public/private keys for the ssh connection? – jeremy Nov 20 '12 at 21:42
  • Server has my public key, private is in %HOME%\.ssh\id_rsa. It's passwordless, so I assumed (probably incorrectly) that since it was impersonating me, it would just work as it does in the git shell and unit test. I've just noticed that git clone/pull fail from a command prompt if it's not the Git Shell from Github for Windows, so something it's setting up in the scripts is required. – Chris Doggett Nov 20 '12 at 21:54
  • The guy who set up our git server thought it might be something with known_hosts, which never gets set with anything. 'keyscan -H repo_server' returns a blank line, as does 'keyscan -H repo_server_ip_address' – Chris Doggett Nov 20 '12 at 21:56
  • I'm thinking if you set up that h:\.ssh directory you can put the id_rsa and id_rsa.pub in that directory. – jeremy Nov 20 '12 at 22:00
  • I guess there's been a miscommunication. H:\.ssh exists, and it's where my working private and public keys reside, as well as an empty known_hosts file that never gets updated, which is a symptom of the "Warning: Permanently added 'repo,359.33.9.234' (RSA) to the list of known hosts." message I get with any destructive git command. git looks for %HOME%\.ssh, and I've made sure %HOME% is correctly pointing to that directory. I think it's something else. – Chris Doggett Nov 20 '12 at 22:03
  • Yes, I want to make sure that the .ssh directory has your keys too though, otherwise the server will reject your connection. – jeremy Nov 20 '12 at 23:11