Class: QB::Repo::Git

Inherits:
QB::Repo show all
Defined in:
lib/qb/repo/git.rb

Overview

Encapsulate information about a Git repository and expose useful operations as instance methods.

The main entry point is Git.from_path, which creates a

Direct Known Subclasses

GitHub

Defined Under Namespace

Classes: GitHub, User

Instance Attribute Summary

Attributes inherited from QB::Repo

#name, #ref_path, #root_path

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from QB::Repo

from_path!

Class Method Details

.from_path(path, use_github_api: false) ⇒ QB::Repo::Git?

Instantiate a Package::Git resource for whatever Git repo path is in, or return nil if path is not in a Git repo.

Parameters:

  • path (String, Pathname)

    A path that may be in the Git repo.

  • use_github_api: (Boolean) (defaults to: false)

    When true will will contact the GitHub API for information to populate the #github property for repos that have a GitHub origin URL.

    Otherwise we will just assume GitHub repos are private since it's the safe guess, resulting in a #github value of {private: true}.

Returns:

  • (QB::Repo::Git)

    If path is in a Git repo.

  • (nil)

    If path is not in a Git repo.

Raises:

  • (QB::FSStateError)
    • If we can't find any existing directory to look in based on input_path.


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/qb/repo/git.rb', line 170

def self.from_path path, use_github_api: false
  ref_path = path
  
  # Create a Pathname from the input
  input_path = Pathname.new ref_path
  
  # input_path may point to a file, or something that doesn't even exist.
  # We want to ascend until we find an existing directory that we can cd into.
  closest_dir = input_path.ascend.find {|p| p.directory?}
  
  # Make sure we found something
  if closest_dir.nil?
    raise QB::FSStateError,
          "Unable to find any existing directory from path " +
          "#{ ref_path.inspect }"
  end
  
  # Change into the directory to make shell life easier
  Dir.chdir closest_dir do
    root_result = Cmds.capture "git rev-parse --show-toplevel"
    
    unless root_result.ok?
      return nil
    end
    
    root_path = Pathname.new root_result.out.chomp
    
    user = User.new **NRSER.map_values(User.props.keys) {|key, _|
      begin
        Cmds.chomp! "git config user.#{ key }"
      rescue
      end
    }
    
    is_clean = Cmds.chomp!('git status --porcelain 2>/dev/null').empty?
    
    rev_parse = Cmds.capture 'git rev-parse HEAD'
    
    head = if rev_parse.ok?
      rev_parse.out.chomp
    end
    
    branch_result = Cmds.capture 'git branch --no-color 2> /dev/null'
    
    branch = if branch_result.ok?
      if line = branch_result.out.lines.find {|line| line.start_with? '*'}
        if m = line.match(/\*\s+(\S+)/)
          m[1]
        end
      end
    end
    
    origin = begin
      Cmds.chomp! "git remote get-url origin"
    rescue
    end
    
    owner = nil
    name = nil
    github = nil
    
    if origin && QB::Repo::Git::GitHub.url?( origin )
      
      repo_id = QB::Repo::Git::GitHub.parse_url origin
      
      if use_github_api
        github = OpenStruct.new
        github.api_url = "https://api.github.com/repos/#{ owner }/#{ name }"
        
        response = Net::HTTP.get_response URI(github.api_url)
        
        if response.is_a? Net::HTTPSuccess
          # parse response body and add everything to github result
          parsed = JSON.parse response.body
          parsed.each {|k, v| github[k] = v}
        else
          # assume it's private if we failed to find it
          github.private = true
        end
        
        github = github.to_h
      end
      
      return QB::Repo::Git::GitHub.new(
        ref_path: ref_path,
        input_path: input_path,
        root_path: root_path,
        user: user,
        is_clean: is_clean,
        head: head,
        branch: branch,
        origin: origin,
        repo_id: repo_id,
        owner: repo_id.owner,
        name: repo_id.name,
        github: github,
      )
      
    end
    
    new(
      ref_path: ref_path,
      input_path: input_path,
      root_path: root_path,
      user: user,
      is_clean: is_clean,
      head: head,
      branch: branch,
      origin: origin,
      owner: owner,
      name: name,
      github: github,
    )
    
  end # chdir
  
end

Instance Method Details

#apiObject



343
344
345
# File 'lib/qb/repo/git.rb', line 343

def api
  @api ||= ::Git.open root_path
end

#clean?(ignore: nil) ⇒ Boolean

Returns false if the repo has any uncommitted changes or untracked files.

Returns:

  • (Boolean)

    false if the repo has any uncommitted changes or untracked files.



364
365
366
367
368
369
370
371
372
373
# File 'lib/qb/repo/git.rb', line 364

def clean? ignore: nil
  if ignore
    ignore = [*ignore]
    status.reject { |path, mode|
      ignore.any? { |pattern| File.fnmatch? pattern, path }
    }.empty?
  else
    status.empty?
  end
end

#from_path!(path, use_github_api: false) ⇒ QB::Repo::Git

Instantiate a Package::Git resource for whatever Git repo path is in, raising an error if it's not in one.

Parameters:

  • path (String, Pathname)

    A path that is in the Git repo.

  • use_github_api: (defaults to: false)

    see #from_path

Returns:

Raises:

  • (QB::FSStateError)
    • If we can't find any existing directory to look in based on input_path.

    • If the directory we do find to look in does not seems to be part of a Git repo.



306
307
308
309
310
311
312
313
314
# File 'lib/qb/repo/git.rb', line 306

def from_path! path, use_github_api: false
  from_path( path, use_github_api: use_github_api ).tap { |git|
    if git.nil?
      raise QB::FSStateError,
            "Path #{ path.inspect } does not appear to be in a " +
            "Git repo."
    end
  }
end

#full_nameObject

Instance Methods



320
321
322
# File 'lib/qb/repo/git.rb', line 320

def full_name
  "#{ owner }/#{ name }" if owner && name
end

#github?false

Always returns false, where QB::Repo::Git::GitHub#github? always returns true.

Use from_path to construct instances so you end up with the right class.

Returns:

  • (false)


337
338
339
340
# File 'lib/qb/repo/git.rb', line 337

def github?
  # origin && QB::Repo::Git::GitHub.url?( origin )
  false
end

#head_shortObject



324
325
326
# File 'lib/qb/repo/git.rb', line 324

def head_short
  head[0...7] if head
end

#statusObject

Reading Repo State



351
352
353
354
355
356
357
358
# File 'lib/qb/repo/git.rb', line 351

def status
  Cmds.new( 'git status --porcelain', chdir: root_path ).
    out!.lines.map( &:chomp ).map { |line|
      m = /\A\s*(?<mode>\S+)\s*(?<path>.*)\z/.match line
      
      [m['path'], m['mode']]
    }.to_h
end

#tagsObject



376
377
378
# File 'lib/qb/repo/git.rb', line 376

def tags
  api.tags.map &:name
end