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
strace a git clone from start to finish and find out what exactly Git needs to successfully check out
Create a second repo (using GIT_DIR) to do the ‘real’ work of maintaining a valid repository and then script the rest and fake my way to a Spotify remote.
Start reading and annotating Git’s source code, or maybe stepping through it in gdb - has anyone done this already?
Step through git-tfs’s source code - see how much work it’d be to get something going that’s at least capable of cloning a playlist.
Fin (for now)
Hopefully I’ll come back to this (and this blog)