Skip to content

Commit

Permalink
Add support for single-file mirrors.
Browse files Browse the repository at this point in the history
Fixes cristibalan#64.

- Remove the non-integration tests of Mirror::diff.  I've removed the
  line of code they were testing, and I don't think it's worth writing
  new ones; we can rely on the integration tests.
  • Loading branch information
mattmccutchen committed Jun 7, 2017
1 parent 2de0474 commit c202a53
Show file tree
Hide file tree
Showing 15 changed files with 635 additions and 79 deletions.
4 changes: 0 additions & 4 deletions lib/braid/command.rb
Expand Up @@ -133,9 +133,5 @@ def validate_new_revision(mirror, revision)
new_revision
end
end

def determine_target_revision(mirror, new_revision)
git.rev_parse(mirror.versioned_path(new_revision))
end
end
end
6 changes: 3 additions & 3 deletions lib/braid/commands/add.rb
Expand Up @@ -21,10 +21,10 @@ def run(url, options = {})
setup_remote(mirror)
mirror.fetch

new_revision = validate_new_revision(mirror, options['revision'])
target_revision = determine_target_revision(mirror, new_revision)
new_revision = validate_new_revision(mirror, options['revision'])
target_item = mirror.upstream_item_for_revision(new_revision)

git.read_tree_prefix_u(target_revision, mirror.path)
git.add_item_to_index(target_item, mirror.path, true)

mirror.revision = new_revision
config.update(mirror)
Expand Down
13 changes: 1 addition & 12 deletions lib/braid/commands/diff.rb
Expand Up @@ -32,20 +32,9 @@ def show_diff(path, options = {})
mirror = config.get!(path)
setup_remote(mirror)

# We do not need to spend the time to copy the content outside the
# mirror from HEAD because --relative will exclude it anyway. Rename
# detection seems to apply only to the files included in the diff, so we
# shouldn't have another bug like
# https://github.com/cristibalan/braid/issues/41.
base_tree = git.make_tree_with_subtree(nil, mirror.path,
mirror.versioned_path(mirror.base_revision))
# Git 1.7.2.2 release notes mention a bug when --relative is used
# without a trailing slash, and our minimum git version is 1.6.0, so
# attempt to work around the bug here.
#
# XXX: Warn if the user specifies file paths that are outside the
# mirror? Currently, they just won't match anything.
git.diff_to_stdout("--relative=#{mirror.path}/", base_tree, options['git_diff_args'])
git.diff_to_stdout(*mirror.diff_args, *options['git_diff_args'])

clear_remote(mirror, options)
end
Expand Down
6 changes: 2 additions & 4 deletions lib/braid/commands/push.rb
Expand Up @@ -33,7 +33,7 @@ def run(path, options = {})
clear_remote(mirror, options)
return
end
local_mirror_tree = git.rev_parse("HEAD:#{mirror.path}")
local_mirror_item = git.get_tree_item("HEAD", mirror.path)

odb_paths = [File.expand_path(git.repo_file_path('objects'))]
if File.exist?(mirror.cached_url)
Expand Down Expand Up @@ -68,9 +68,7 @@ def run(path, options = {})
git.fetch(remote_url, mirror.remote_ref)
git.checkout(base_revision)
git.rm_r(mirror.remote_path || '.')
# Yes, when mirror.remote_path is unset, "git read-tree --prefix=/"
# seems to work. :/
git.read_tree_prefix_u(local_mirror_tree, mirror.remote_path || '')
git.add_item_to_index(local_mirror_item, mirror.remote_path || '', true)
system('git commit -v')
msg "Pushing changes to remote branch #{branch}."
git.push(remote_url, "HEAD:refs/heads/#{branch}")
Expand Down
7 changes: 4 additions & 3 deletions lib/braid/commands/update.rb
Expand Up @@ -56,7 +56,6 @@ def update_one(path, options = {})
rescue InvalidRevision
# Ignored as it means the revision matches expected
end
target_revision = determine_target_revision(mirror, new_revision)

from_desc =
original_tag ? "tag '#{original_tag}'" :
Expand Down Expand Up @@ -92,8 +91,10 @@ def update_one(path, options = {})
in_error = false
begin
local_hash = git.rev_parse('HEAD')
base_hash = git.make_tree_with_subtree('HEAD', mirror.path, mirror.versioned_path(base_revision))
remote_hash = git.make_tree_with_subtree('HEAD', mirror.path, target_revision)
base_hash = git.make_tree_with_item('HEAD', mirror.path,
mirror.upstream_item_for_revision(base_revision))
remote_hash = git.make_tree_with_item('HEAD', mirror.path,
mirror.upstream_item_for_revision(new_revision))
Operations::with_modified_environment({
"GITHEAD_#{local_hash}" => 'HEAD',
"GITHEAD_#{remote_hash}" => new_revision
Expand Down
43 changes: 38 additions & 5 deletions lib/braid/mirror.rb
Expand Up @@ -59,15 +59,48 @@ def merged?(commit)
!!base_revision && git.merge_base(commit, base_revision) == commit
end

def versioned_path(revision)
"#{revision}:#{self.remote_path}"
def upstream_item_for_revision(revision)
git.get_tree_item(revision, self.remote_path)
end

# Return the arguments that should be passed to "git diff" to diff this
# mirror (including uncommitted changes by default). Additional
# user-specified arguments may be appended. This violates encapsulation a
# little; it works now, but we may have to change the interface in order to
# add features.
def diff_args
upstream_item = upstream_item_for_revision(base_revision)

# We do not need to spend the time to copy the content outside the
# mirror from HEAD because --relative will exclude it anyway. Rename
# detection seems to apply only to the files included in the diff, so we
# shouldn't have another bug like
# https://github.com/cristibalan/braid/issues/41.
base_tree = git.make_tree_with_item(nil, path, upstream_item)

relative_path = path
if upstream_item.is_a?(git.BlobWithMode)
extra_opts = [
# This seems most reasonable: a little bit like what `git diff` does
# given two blobs as arguments.
"--src-prefix=a/" + File.basename(remote_path),
"--dst-prefix=b/" + File.basename(path)
]
else
# Git 1.7.2.2 release notes mention a bug when --relative is used
# without a trailing slash, and our minimum git version is 1.6.0, so
# attempt to work around the bug here.
relative_path += "/"
extra_opts = []
end

["--relative=" + relative_path, *extra_opts, base_tree]
end

# Precondition: the remote for this mirror is set up.
def diff
fetch
remote_hash = git.rev_parse(versioned_path(base_revision))
local_hash = git.tree_hash(path)
remote_hash != local_hash ? git.diff_tree(remote_hash, local_hash) : ''
git.diff(diff_args)
end

def fetch
Expand Down
71 changes: 51 additions & 20 deletions lib/braid/operations.rb
Expand Up @@ -289,17 +289,56 @@ def read_ls_files(prefix)
invoke('ls-files', prefix)
end

# Read tree into the index and working tree.
def read_tree_prefix_u(treeish, prefix)
invoke(:read_tree, "--prefix=#{prefix}/ -u", treeish)
true
class BlobWithMode
def initialize(hash, mode)
@hash = hash
@mode = mode
end
attr_reader :hash, :mode
end
# Allow the class to be referenced as `git.BlobWithMode`.
def BlobWithMode
Git::BlobWithMode
end

# Read tree into the index, regardless of the state of the working tree.
# Most useful with a temporary index file.
def read_tree_prefix_i(treeish, prefix)
invoke(:read_tree, "--prefix=#{prefix}/ -i", treeish)
true
# Get the item at the given path in the given tree. If it's a tree, just
# return its hash; if it's a blob, return a BlobWithMode object. (This is
# how we remember the mode for single-file mirrors.)
def get_tree_item(tree, path)
if path == nil || path == ''
tree
else
m = /^([^ ]*) ([^ ]*) ([^\t]*)\t.*$/.match(invoke(:ls_tree, tree, path))
mode = m[1]
type = m[2]
hash = m[3]
if type == "tree"
hash
elsif type == "blob"
return BlobWithMode.new(hash, mode)
else
raise ShellExecutionError, "Tree item is not a tree or a blob"
end
end
end

# Add the item (as returned by get_tree_item) to the index at the given
# path. If update_worktree is true, then update the worktree, otherwise
# disregard the state of the worktree (most useful with a temporary index
# file).
def add_item_to_index(item, path, update_worktree)
if item.is_a?(BlobWithMode)
# Our minimum git version is 1.6.0 and the new --cacheinfo syntax
# wasn't added until 2.0.0.
invoke(:update_index, "--add", "--cacheinfo", item.mode, item.hash, path)
if update_worktree
# XXX If this fails, we've already updated the index.
invoke(:checkout_index, path)
end
else
# Yes, if path == '', "git read-tree --prefix=/" works. :/
invoke(:read_tree, "--prefix=#{path}/", update_worktree ? "-u" : "-i", item)
end
end

# Read tree into the root of the index. This may not be the preferred way
Expand All @@ -324,14 +363,13 @@ def with_temporary_index
end
end

def make_tree_with_subtree(main_content, subtree_path, subtree_content)
def make_tree_with_item(main_content, item_path, item)
with_temporary_index do
if main_content
read_tree_im(main_content)
rm_r_cached(subtree_path)
rm_r_cached(item_path)
end
# Yes, if subtree_path == '', "git read-tree --prefix=/" works. :/
read_tree_prefix_i(subtree_content, subtree_path)
add_item_to_index(item, item_path, false)
write_tree
end
end
Expand All @@ -356,13 +394,6 @@ def tree_hash(path, treeish = 'HEAD')
out.split[2]
end

def diff_tree(src_tree, dst_tree, prefix = nil)
cmd = "git diff-tree -p --binary #{src_tree} #{dst_tree}"
cmd << " --src-prefix=a/#{prefix}/ --dst-prefix=b/#{prefix}/" if prefix
status, out, err = exec!(cmd)
out
end

def diff_to_stdout(*args)
# For now, ignore the exit code. It can be 141 (SIGPIPE) if the user
# quits the pager before reading all the output.
Expand Down

0 comments on commit c202a53

Please sign in to comment.