/ Adam's Pile of Hacks / blog

Teaching Git about Spotify URLs

October 6, 2019

After losing some songs off a Spotify playlist, I started thinking about how I’d version control the playlist itself. What if I could git clone a Spotify playlist? git push to add new songs? git merge two playlists together? Sounds like an interesting experiment, at the very least.

When you git clone from a https:// URL, git calls out to git-remote-https and does something to download the repository you asked for. Let’s start finding out what that is. https://git-scm.com/docs/git-remote-helpers says that git calls the relevant helper (git-remote-something) and sends text via stdin, waiting for a response on stdout. I can do that.

Some code, and rabbit holes:

static void Main(string[] args)
{
    var input = Console.ReadLine();    
    switch (input)
    {
        case "capabilities":
            Console.Error.WriteLine();
            Console.Error.WriteLine("Got capabilties request from Git");
            Console.WriteLine("none");
            Console.WriteLine();
            Main(null);
            break;
        default: 
            Console.Error.WriteLine($"Got unknown thing from git: {input}");
            Console.WriteLine();
            Main(null);
            break;
    }
}
PS C:\temp\spotify-test> git clone spotify://aaaaa
Cloning into 'aaaaa'...

Got capabilties request from Git
Got unknown thing from git: list

Steps not shown: setting up a .NET Core project, adding bin\Debug to PATH so git could find the right handler: if the handler isn’t found, you’ll get git: 'remote-spotify' is not a git command. See 'git --help'.

Okay, Git is sending me a list command. https://git-scm.com/docs/git-remote-helpers#git-remote-helpers-emlistem says lots of things about this but it didn’t immediately make a lot of sense to me. Instead, I wanted to see how git-remote-https behaved.

If I set an environment variable, GIT_TRACE=2 and then git clone https://github.com/voltagex/junkcode, I can see what git is doing behind the scenes - in this case run-command.c:663 trace: run_command: git remote-https origin https://github.com/voltagex/junkcode

That matches with what I read in the documentation before, so I can just run the command (after working out that that binary lives in C:\Program Files\Git\mingw64\libexec\git-core)

PS C:\temp> & "C:\program files\Git\mingw64\libexec\git-core\git-remote-https.exe" origin https://github.com/voltagex/junkcode 

capabilities
error: remote-curl: unknown command 'capabilities?' from git

Huh? I sent capabilities and got capabilities? back. I think I know what’s going on here. If I run it again and send just a blank line, I get error: remote-curl: unknown command '?' from git. It seems like Powershell is sending CRLF while git-remote-https is looking for LF. Hopping over to WSL, I get a bit further:

/mnt/c/temp$ /usr/lib/git-core/git-remote-https origin https://github.com/voltagex/junkcode
list

@refs/heads/master HEAD
...

Updating the switch statement in my code to match

                case "list":
                    Console.Error.WriteLine("Got list request from Git");
                    Console.WriteLine("@refs/heads/master HEAD");
                    Console.WriteLine();
                    Main(null);
                    break;

That gets me a bit further with git:

Cloning into 'test'...
Main called with args: origin,spotify://test
Got capabilties request from Git
Got list request from Git
Got unknown thing from git: fetch 0000000000000000000000000000000000000000 refs/heads/master
warning: remote HEAD refers to nonexistent ref, unable to checkout.

Now I’m getting outside of what a regular switch statement can do, assuming Git will eventually send me requests for things other than master / all zeroes. Luckily, pattern matching was added to C# at some point (https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching#when-clauses-in-case-expressions), so my switch statement can be modified as follows:

                case string s when s.StartsWith("fetch"):
                    Console.Error.WriteLine($"Got fetch request with pattern match: {s}");
                    Console.WriteLine();
                    Main(null);
                    break;

which fixes the ’error’ from my program, but doesn’t get me any futher with git:

Got fetch request with pattern match: fetch 0000000000000000000000000000000000000000 refs/heads/master
warning: remote HEAD refers to nonexistent ref, unable to checkout.

I’ve started to dig into what happens after fetch is sent, but I think that’ll be a project for another day. I’m a little annoyed that this seems like a roadblock for now - git’s expecting me to do something after the fetch request, but I’m not going to start reimplementing the git on-disk format (that’s libgit2sharp’s job).

Future research

Fin (for now)

Hopefully I’ll come back to this (and this blog)