@@ -39,20 +39,29 @@ def self.config
3939 end
4040
4141 def self . binary_version ( binary_path )
42- result = nil
43- status = nil
44-
45- begin
46- result , status = Open3 . capture2e ( binary_path , '-c' , 'core.quotePath=true' , '-c' , 'color.ui=false' , 'version' )
47- result = result . chomp
48- rescue Errno ::ENOENT
49- raise "Failed to get git version: #{ binary_path } not found"
50- end
42+ result , status = execute_git_version ( binary_path )
5143
5244 raise "Failed to get git version: #{ status } \n #{ result } " unless status . success?
5345
54- version = result [ /\d +(\. \d +)+/ ]
55- version_parts = version . split ( '.' ) . collect ( &:to_i )
46+ parse_version_string ( result )
47+ end
48+
49+ private_class_method def self . execute_git_version ( binary_path )
50+ Open3 . capture2e (
51+ binary_path ,
52+ '-c' , 'core.quotePath=true' ,
53+ '-c' , 'color.ui=false' ,
54+ 'version'
55+ )
56+ rescue Errno ::ENOENT
57+ raise "Failed to get git version: #{ binary_path } not found"
58+ end
59+
60+ private_class_method def self . parse_version_string ( raw_string )
61+ version_match = raw_string . match ( /\d +(\. \d +)+/ )
62+ return [ 0 , 0 , 0 ] unless version_match
63+
64+ version_parts = version_match [ 0 ] . split ( '.' ) . map ( &:to_i )
5665 version_parts . fill ( 0 , version_parts . length ...3 )
5766 end
5867
@@ -85,24 +94,28 @@ def self.init(directory = '.', options = {})
8594 end
8695
8796 def self . root_of_worktree ( working_dir )
88- result = working_dir
89- status = nil
90-
9197 raise ArgumentError , "'#{ working_dir } ' does not exist" unless Dir . exist? ( working_dir )
9298
93- begin
94- result , status = Open3 . capture2e (
95- Git ::Base . config . binary_path , '-c' , 'core.quotePath=true' , '-c' ,
96- 'color.ui=false' , 'rev-parse' , '--show-toplevel' , chdir : File . expand_path ( working_dir )
97- )
98- result = result . chomp
99- rescue Errno ::ENOENT
100- raise ArgumentError , 'Failed to find the root of the worktree: git binary not found'
101- end
99+ result , status = execute_rev_parse_toplevel ( working_dir )
100+ process_rev_parse_result ( result , status , working_dir )
101+ end
102+
103+ private_class_method def self . execute_rev_parse_toplevel ( working_dir )
104+ Open3 . capture2e (
105+ Git ::Base . config . binary_path ,
106+ '-c' , 'core.quotePath=true' ,
107+ '-c' , 'color.ui=false' ,
108+ 'rev-parse' , '--show-toplevel' ,
109+ chdir : File . expand_path ( working_dir )
110+ )
111+ rescue Errno ::ENOENT
112+ raise ArgumentError , 'Failed to find the root of the worktree: git binary not found'
113+ end
102114
115+ private_class_method def self . process_rev_parse_result ( result , status , working_dir )
103116 raise ArgumentError , "'#{ working_dir } ' is not in a git working tree" unless status . success?
104117
105- result
118+ result . chomp
106119 end
107120
108121 # (see Git.open)
@@ -879,19 +892,58 @@ def diff_path_status(objectish = 'HEAD', obj2 = nil)
879892 # 2. the working directory if NOT working with a bare repository
880893 #
881894 private_class_method def self . normalize_repository ( options , default :, bare : false )
882- repository =
883- if bare
884- File . expand_path ( options [ :repository ] || default || Dir . pwd )
885- else
886- File . expand_path ( options [ :repository ] || '.git' , options [ :working_directory ] )
887- end
895+ initial_path = initial_repository_path ( options , default : default , bare : bare )
896+ final_path = resolve_gitdir_if_present ( initial_path , options [ :working_directory ] )
897+ options [ :repository ] = final_path
898+ end
888899
889- if File . file? ( repository )
890- repository = File . expand_path ( File . read ( repository ) [ 8 ..] . strip ,
891- options [ :working_directory ] )
900+ # Determines the initial, potential path to the repository directory
901+ #
902+ # This path is considered 'initial' because it is not guaranteed to be the
903+ # final repository location. For features like submodules or worktrees,
904+ # this path may point to a text file containing a `gitdir:` pointer to the
905+ # actual repository directory elsewhere. This initial path must be
906+ # subsequently resolved.
907+ #
908+ # @api private
909+ #
910+ # @param options [Hash] The options hash, checked for `[:repository]`.
911+ #
912+ # @param default [String] A fallback path if `options[:repository]` is not set.
913+ #
914+ # @param bare [Boolean] Whether the repository is bare, which changes path resolution.
915+ #
916+ # @return [String] The initial, absolute path to the `.git` directory or file.
917+ #
918+ private_class_method def self . initial_repository_path ( options , default :, bare :)
919+ if bare
920+ File . expand_path ( options [ :repository ] || default || Dir . pwd )
921+ else
922+ File . expand_path ( options [ :repository ] || '.git' , options [ :working_directory ] )
892923 end
924+ end
925+
926+ # Resolves the path to the actual repository if it's a `gitdir:` pointer file.
927+ #
928+ # If `path` points to a file (common in submodules and worktrees), this
929+ # method reads the `gitdir:` path from it and returns the real repository
930+ # path. Otherwise, it returns the original path.
931+ #
932+ # @api private
933+ #
934+ # @param path [String] The initial path to the repository, which may be a pointer file.
935+ #
936+ # @param working_dir [String] The working directory, used as a base to resolve the path.
937+ #
938+ # @return [String] The final, resolved absolute path to the repository directory.
939+ #
940+ private_class_method def self . resolve_gitdir_if_present ( path , working_dir )
941+ return path unless File . file? ( path )
893942
894- options [ :repository ] = repository
943+ # The file contains `gitdir: <path>`, so we read the file,
944+ # extract the path part, and expand it.
945+ gitdir_pointer = File . read ( path ) . sub ( /\A gitdir: / , '' ) . strip
946+ File . expand_path ( gitdir_pointer , working_dir )
895947 end
896948
897949 # Normalize options[:index]
0 commit comments