From 998a9d6fa8695c078ba9801aeb6c6edd094b03cf Mon Sep 17 00:00:00 2001 From: James Couball Date: Sun, 20 Dec 2020 10:00:05 -0800 Subject: [PATCH] Calculate the default for index relative to git_dir instead of work_tree Signed-off-by: James Couball --- lib/git/base.rb | 147 +++++++++++++++++++----------------- tests/test_helper.rb | 20 +++-- tests/units/test_git_dir.rb | 97 ++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 79 deletions(-) create mode 100644 tests/units/test_git_dir.rb diff --git a/lib/git/base.rb b/lib/git/base.rb index 4e472abe..21a26621 100644 --- a/lib/git/base.rb +++ b/lib/git/base.rb @@ -1,7 +1,7 @@ require 'git/base/factory' module Git - + class Base include Git::Base::Factory @@ -10,7 +10,7 @@ class Base def self.bare(git_dir, opts = {}) self.new({:repository => git_dir}.merge(opts)) end - + # clones a git repository locally # # repository - http://repo.or.cz/w/sinatra.git @@ -20,15 +20,15 @@ def self.bare(git_dir, opts = {}) # :repository # # :bare - # or + # or # :working_directory # :index_file # def self.clone(repository, name, opts = {}) - # run git-clone + # run git-clone self.new(Git::Lib.new.clone(repository, name, opts)) end - + # Returns (and initialize if needed) a Git::Config instance # # @return [Git::Config] the current config instance. @@ -44,41 +44,52 @@ def self.config # :repository # def self.init(working_dir, opts = {}) - opts[:working_directory] ||= working_dir + opts[:working_directory] ||= working_dir opts[:repository] ||= File.join(opts[:working_directory], '.git') - + FileUtils.mkdir_p(opts[:working_directory]) if opts[:working_directory] && !File.directory?(opts[:working_directory]) - + init_opts = { :bare => opts[:bare] } opts.delete(:working_directory) if opts[:bare] - + # Submodules have a .git *file* not a .git folder. # This file's contents point to the location of # where the git refs are held (In the parent repo) - if File.file?('.git') + if opts[:working_directory] && File.file?(File.join(opts[:working_directory], '.git')) git_file = File.open('.git').read[8..-1].strip opts[:repository] = git_file opts[:index] = git_file + '/index' end Git::Lib.new(opts).init(init_opts) - + self.new(opts) end - + # opens a new Git Project from a working directory # you can specify non-standard git_dir and index file in the options def self.open(working_dir, opts={}) - self.new({:working_directory => working_dir}.merge(opts)) + opts[:working_directory] ||= working_dir + opts[:repository] ||= File.join(opts[:working_directory], '.git') + + # Submodules have a .git *file* not a .git folder. + # This file's contents point to the location of + # where the git refs are held (In the parent repo) + if opts[:working_directory] && File.file?(File.join(opts[:working_directory], '.git')) + git_file = File.open('.git').read[8..-1].strip + opts[:repository] = git_file + opts[:index] = git_file + '/index' + end + self.new(opts) end - + def initialize(options = {}) if working_dir = options[:working_directory] options[:repository] ||= File.join(working_dir, '.git') - options[:index] ||= File.join(working_dir, '.git', 'index') + options[:index] ||= File.join(options[:repository], 'index') end if options[:log] @logger = options[:log] @@ -86,17 +97,17 @@ def initialize(options = {}) else @logger = nil end - + @working_directory = options[:working_directory] ? Git::WorkingDirectory.new(options[:working_directory]) : nil - @repository = options[:repository] ? Git::Repository.new(options[:repository]) : nil + @repository = options[:repository] ? Git::Repository.new(options[:repository]) : nil @index = options[:index] ? Git::Index.new(options[:index], false) : nil end - + # changes current working directory for a block # to the git working directory # # example - # @git.chdir do + # @git.chdir do # # write files # @git.add # @git.commit('message') @@ -106,7 +117,7 @@ def chdir # :yields: the Git::Path yield dir.path end end - + #g.config('user.name', 'Scott Chacon') # sets value #g.config('user.email', 'email@email.com') # sets value #g.config('user.name') # returns 'Scott Chacon' @@ -123,14 +134,14 @@ def config(name = nil, value = nil) lib.config_list end end - + # returns a reference to the working directory # @git.dir.path # @git.dir.writeable? def dir @working_directory end - + # returns reference to the git index file def index @index @@ -141,28 +152,28 @@ def index def repo @repository end - + # returns the repository size in bytes def repo_size - Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH).reject do |f| + Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH).reject do |f| f.include?('..') - end.map do |f| + end.map do |f| File.expand_path(f) - end.uniq.map do |f| + end.uniq.map do |f| File.stat(f).size.to_i end.reduce(:+) end - + def set_index(index_file, check = true) @lib = nil @index = Git::Index.new(index_file.to_s, check) end - + def set_working(work_dir, check = true) @lib = nil @working_directory = Git::WorkingDirectory.new(work_dir.to_s, check) end - + # returns +true+ if the branch exists locally def is_local_branch?(branch) branch_names = self.branches.local.map {|b| b.name} @@ -181,15 +192,15 @@ def is_branch?(branch) branch_names.include?(branch) end - # this is a convenience method for accessing the class that wraps all the + # this is a convenience method for accessing the class that wraps all the # actual 'git' forked system calls. At some point I hope to replace the Git::Lib # class with one that uses native methods or libgit C bindings def lib @lib ||= Git::Lib.new(self, @logger) end - + # will run a grep for 'string' on the HEAD of the git repository - # + # # to be more surgical in your grep, you can call grep() off a specific # git object. for example: # @@ -210,7 +221,7 @@ def lib def grep(string, path_limiter = nil, opts = {}) self.object('HEAD').grep(string, path_limiter, opts) end - + # updates the repository index using the working directory content # # @git.add('path/to/file') @@ -286,7 +297,7 @@ def revert(commitish = nil, opts = {}) end # commits all pending changes in the index file to the git repository - # + # # options: # :all # :allow_empty @@ -296,10 +307,10 @@ def revert(commitish = nil, opts = {}) def commit(message, opts = {}) self.lib.commit(message, opts) end - + # commits all pending changes in the index file to the git repository, # but automatically adds all modified files without having to explicitly - # calling @git.add() on them. + # calling @git.add() on them. def commit_all(message, opts = {}) opts = {:add_all => true}.merge(opts) self.lib.commit(message, opts) @@ -309,7 +320,7 @@ def commit_all(message, opts = {}) def checkout(branch = 'master', opts = {}) self.lib.checkout(branch, opts) end - + # checks out an old version of a file def checkout_file(version, file) self.lib.checkout_file(version,file) @@ -332,7 +343,7 @@ def push(remote = 'origin', branch = 'master', opts = {}) self.lib.push(remote, branch, opts) end - + # merges one or more branches into the current working branch # # you can specify more than one branch to merge by passing an array of branches @@ -354,7 +365,7 @@ def each_conflict(&block) # :yields: file, your_version, their_version def pull(remote='origin', branch='master') self.lib.pull(remote, branch) end - + # returns an array of Git:Remote objects def remotes self.lib.remotes.map { |r| Git::Remote.new(self, r) } @@ -362,7 +373,7 @@ def remotes # adds a new remote to this repository # url can be a git url or a Git::Base object if it's a local reference - # + # # @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git') # @git.fetch('scotts_git') # @git.merge('scotts_git/master') @@ -411,37 +422,37 @@ def tags # :f -> true # :m | :message -> String # :s -> true - # + # def add_tag(name, *opts) self.lib.tag(name, *opts) self.tag(name) end - - # deletes a tag - def delete_tag(name) + + # deletes a tag + def delete_tag(name) self.lib.tag(name, {:d => true}) end - + # creates an archive file of the given tree-ish def archive(treeish, file = nil, opts = {}) self.object(treeish).archive(file, opts) end - + # repacks the repository def repack self.lib.repack end - + def gc self.lib.gc end - + def apply(file) if File.exist?(file) self.lib.apply(file) end end - + def apply_mail(file) self.lib.apply_mail(file) if File.exist?(file) end @@ -454,9 +465,9 @@ def apply_mail(file) def show(objectish=nil, path=nil) self.lib.show(objectish, path) end - + ## LOWER LEVEL INDEX OPERATIONS ## - + def with_index(new_index) # :yields: new_index old_index = @index set_index(new_index, false) @@ -464,10 +475,10 @@ def with_index(new_index) # :yields: new_index set_index(old_index) return_value end - + def with_temp_index &blk # Workaround for JRUBY, since they handle the TempFile path different. - # MUST be improved to be safer and OS independent. + # MUST be improved to be safer and OS independent. if RUBY_PLATFORM == 'java' temp_path = "/tmp/temp-index-#{(0...15).map{ ('a'..'z').to_a[rand(26)] }.join}" else @@ -479,29 +490,29 @@ def with_temp_index &blk with_index(temp_path, &blk) end - + def checkout_index(opts = {}) self.lib.checkout_index(opts) end - + def read_tree(treeish, opts = {}) self.lib.read_tree(treeish, opts) end - + def write_tree self.lib.write_tree end - + def write_and_commit_tree(opts = {}) tree = write_tree commit_tree(tree, opts) end - + def update_ref(branch, commit) branch(branch).update_ref(commit) end - - + + def ls_files(location=nil) self.lib.ls_files(location) end @@ -509,14 +520,14 @@ def ls_files(location=nil) def with_working(work_dir) # :yields: the Git::WorkingDirectory return_value = false old_working = @working_directory - set_working(work_dir) + set_working(work_dir) Dir.chdir work_dir do return_value = yield @working_directory end set_working(old_working) return_value end - + def with_temp_working &blk tempfile = Tempfile.new("temp-workdir") temp_dir = tempfile.path @@ -525,8 +536,8 @@ def with_temp_working &blk Dir.mkdir(temp_dir, 0700) with_working(temp_dir, &blk) end - - + + # runs git rev-parse to convert the objectish to a full sha # # @git.revparse("HEAD^^") @@ -536,11 +547,11 @@ def with_temp_working &blk def revparse(objectish) self.lib.revparse(objectish) end - + def ls_tree(objectish) self.lib.ls_tree(objectish) end - + def cat_file(objectish) self.lib.object_contents(objectish) end @@ -549,7 +560,7 @@ def cat_file(objectish) def current_branch self.lib.branch_current end - + end - + end diff --git a/tests/test_helper.rb b/tests/test_helper.rb index de8bdd4b..b04f3f4d 100644 --- a/tests/test_helper.rb +++ b/tests/test_helper.rb @@ -7,7 +7,7 @@ require "git" class Test::Unit::TestCase - + def set_file_paths cwd = FileUtils.pwd if File.directory?(File.join(cwd, 'files')) @@ -17,21 +17,19 @@ def set_file_paths elsif File.directory?(File.join(cwd, 'tests', 'files')) @test_dir = File.join(cwd, 'tests', 'files') end - + @wdir_dot = File.expand_path(File.join(@test_dir, 'working')) @wbare = File.expand_path(File.join(@test_dir, 'working.git')) @index = File.expand_path(File.join(@test_dir, 'index')) - + @wdir = create_temp_repo(@wdir_dot) end - + teardown def git_teardown - if @tmp_path - FileUtils.rm_r(@tmp_path) - end + FileUtils.rm_r(@tmp_path) if instance_variable_defined?(:@tmp_path) end - + def create_temp_repo(clone_path) filename = 'git_test' + Time.now.to_i.to_s + rand(300).to_s.rjust(3, '0') @tmp_path = File.expand_path(File.join("/tmp/", filename)) @@ -43,7 +41,7 @@ def create_temp_repo(clone_path) end tmp_path end - + def in_temp_dir(remove_after = true) # :yields: the temporary dir's path tmp_path = nil while tmp_path.nil? || File.directory?(tmp_path) @@ -56,7 +54,7 @@ def in_temp_dir(remove_after = true) # :yields: the temporary dir's path end FileUtils.rm_r(tmp_path) if remove_after end - + def create_file(path, content) File.open(path,'w') do |file| file.puts(content) @@ -74,7 +72,7 @@ def delete_file(path) def move_file(source_path, target_path) File.rename source_path, target_path end - + def new_file(name, contents) create_file(name,contents) end diff --git a/tests/units/test_git_dir.rb b/tests/units/test_git_dir.rb new file mode 100644 index 00000000..551b0f34 --- /dev/null +++ b/tests/units/test_git_dir.rb @@ -0,0 +1,97 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../test_helper' + +class TestGitDir < Test::Unit::TestCase + def test_index_calculated_from_git_dir + Dir.mktmpdir do |work_tree| + Dir.mktmpdir do |git_dir| + git = Git.open(work_tree, repository: git_dir) + + assert_equal(work_tree, git.dir.path) + assert_equal(git_dir, git.repo.path) + + # Since :index was not given in the options to Git#open, index should + # be defined automatically based on the git_dir. + # + index = File.join(git_dir, 'index') + assert_equal(index, git.index.path) + end + end + end + + # Test the case where the git-dir is not a subdirectory of work-tree + # + def test_git_dir_outside_work_tree + Dir.mktmpdir do |work_tree| + Dir.mktmpdir do |git_dir| + # Setup a bare repository + # + source_git_dir = File.expand_path(File.join('tests', 'files', 'working.git')) + FileUtils.cp_r(Dir["#{source_git_dir}/*"], git_dir, preserve: true) + git = Git.open(work_tree, repository: git_dir) + + assert_equal(work_tree, git.dir.path) + assert_equal(git_dir, git.repo.path) + + # Reconstitute the work tree from the bare repository + # + branch = 'master' + git.checkout(branch, force: true) + + # Make sure the work tree contains the expected files + # + expected_files = %w[ex_dir example.txt].sort + actual_files = Dir[File.join(work_tree, '*')].map { |f| File.basename(f) }.sort + assert_equal(expected_files, actual_files) + + # None of the expected files should have a status that says it has been changed + # + expected_files.each do |file| + assert_equal(false, git.status.changed?(file)) + end + + # Change a file and make sure it's status says it has been changed + # + file = 'example.txt' + File.open(File.join(work_tree, file), "a") { |f| f.write("A new line") } + assert_equal(true, git.status.changed?(file)) + + # Add and commit the file and then check that: + # * the file is not flagged as changed anymore + # * the commit was added to the log + # + max_log_size = 100 + assert_equal(64, git.log(max_log_size).size) + git.add(file) + git.commit('This is a new commit') + assert_equal(false, git.status.changed?(file)) + assert_equal(65, git.log(max_log_size).size) + end + end + end + + # Test that Git::Lib::Diff.to_a works from a linked working tree (not the + # main working tree). See https://git-scm.com/docs/git-worktree for a + # description of 'main' and 'linked' working tree. + # + # This is a real world case where '.git' in the working tree is a file + # instead of a directory and where the value of GIT_INDEX_FILE is relevant. + # + def test_git_diff_to_a + work_tree = Dir.mktmpdir + begin + Dir.chdir(work_tree) do + `git init` + `git commit --allow-empty -m 'init'` + `git worktree add child` + Dir.chdir('child') do + result = Git.open('.').diff.to_a + assert_equal([], result) + end + end + ensure + FileUtils.rm_rf(work_tree) + end + end +end