For a custom CI/CD implementation I would like Rugged to checkout specific references from a bare repository into multiple directories (one for each deployment environment). Since multiple worktrees from Git 2.5 does not seem to be implemented in Rugged yet, I tried making my own workaround.
Fortunately Rugged::Repository does have a workdir= instance method which states:
Sets the working directory of
repotopath. All internal operations onrepothat affect the working directory will instead usepath.The
workdircan be set on bare repositories to temporarily turn them into normal repositories.
I ended up with the following implementation which seems to work, but I don't understand why it works.
First some initialization, nothing special here:
if Dir.exist?(repo_path)
repo = Rugged::Repository.bare(repo_path)
repo.remotes.first.fetch
else
repo = Rugged::Repository.clone_at(repo_url, repo_path, bare: true)
end
Dir.mkdir(env_path) unless Dir.exist?(env_path)
target_ref = 'some_sha1_ref'
Then the meat of the operation:
repo.workdir = env_path
# Read the currently checked out ref in env_path from a file (if any)
head_path = File.join(env_path, '.git_HEAD')
prev_head = File.read(head_path) if File.exist?(head_path)
if prev_head.nil?
# First checkout into this dir => start with an empty index
repo.index.clear
else
# Not the first checkout => reset index to the currently checked out ref
repo.reset(prev_head, :mixed)
end
repo.checkout(target_ref)
repo.reset(target_ref, :hard) # <<< Why is this necessary?
repo.close
# Write currently checked out ref in env_path to a file
File.write(head_path, target_ref)
I understand the need to clear/reset the index of the bare repository, otherwise it can be incorrectly set for this worktree which results in checkout conflicts or other unexpected behaviour.
What I don't understand is why I need to do the hard reset after the checkout. The checkout itself does not actually update the working directory, that's what the hard reset is for.
If I don't clear the index before the first checkout, but instead delete the index file (as would be the case in a bare repository that has never been checked out), then the checkout does create the files but only the very first time. Consecutive checkouts to the same directory don't do anything, and if I delete the index file again (rather than resetting it) I get checkout conflicts with the existing files.
I suspect it may have something to do with the bare repository's HEAD not being set correctly, but I don't know of any way to set HEAD to "nothing" for the initial checkout.
Am I just Doing It Wrong™?