Class: RailsWayback::Git

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_wayback/git.rb

Overview

Thin wrapper around shell git for the host application repository.

We shell out to git instead of pulling in a dependency to keep the gem light. All commands are scoped to the host app root and return plain Ruby data structures.

NOTE: Maybe we should need to use a git library instead of shelling out to git. Only if this functionality is not enough for the gem on the future.

Defined Under Namespace

Classes: Commit, GitError

Instance Method Summary collapse

Constructor Details

#initialize(root: nil) ⇒ Git

Returns a new instance of Git.



20
21
22
# File 'lib/rails_wayback/git.rb', line 20

def initialize(root: nil)
  @root = Pathname.new(root || RailsWayback.configuration.app_root_path)
end

Instance Method Details

#branchesObject



38
39
40
41
# File 'lib/rails_wayback/git.rb', line 38

def branches
  output = run("for-each-ref", "--format=%(refname:short)", "refs/heads/")
  output.split("\n").map(&:strip).reject(&:empty?)
end

#branches_containing(sha) ⇒ Object

Returns the local branch names that contain sha in their history. Used by the bar to reflect the branch you are RENDERING with when you travel, instead of the branch that is checked out on disk (which never moves — the gem never touches your working tree).



71
72
73
74
75
76
# File 'lib/rails_wayback/git.rb', line 71

def branches_containing(sha)
  output = run("branch", "--contains", sha, "--format=%(refname:short)")
  output.split("\n").map(&:strip).reject(&:empty?)
rescue GitError
  []
end

#checkout_ref(ref, into:) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/rails_wayback/git.rb', line 114

def checkout_ref(ref, into:)
  target = Pathname.new(into)
  FileUtils.mkdir_p(target)
  # `git --work-tree` + `checkout` writes the ref's contents into a
  # detached directory without touching the developer's HEAD or index.
  env = { "GIT_INDEX_FILE" => target.join(".rails_wayback_index").to_s }
  run_with_env(env, "--work-tree=#{target}", "read-tree", ref)
  run_with_env(env, "--work-tree=#{target}", "checkout-index", "--all", "--force")
  target
ensure
  FileUtils.rm_f(target.join(".rails_wayback_index")) if target
end

#commits(branch, limit: RailsWayback.configuration.max_commits) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/rails_wayback/git.rb', line 43

def commits(branch, limit: RailsWayback.configuration.max_commits)
  output = run(
    "log",
    branch,
    "--max-count=#{limit.to_i}",
    "--pretty=format:%H%x1f%h%x1f%s%x1f%an%x1f%ad",
    "--date=iso-strict"
  )
  output.split("\n").reject(&:empty?).map do |line|
    sha, short, subject, author, date = line.split("\x1f", 5)
    Commit.new(sha: sha, short_sha: short, subject: subject, author: author, date: date)
  end
end

#current_branchObject



30
31
32
# File 'lib/rails_wayback/git.rb', line 30

def current_branch
  run("rev-parse", "--abbrev-ref", "HEAD").strip
end

#current_commitObject



34
35
36
# File 'lib/rails_wayback/git.rb', line 34

def current_commit
  run("rev-parse", "HEAD").strip
end

#diff_paths(ref, paths: []) ⇒ Object

Returns the list of files that differ between ref and the current working tree (unstaged changes included). Optionally scoped to a set of pathspecs so we only report files inside the paths the gem actually swaps for rendering. Never raises: on git failure we log and return an empty list so a missing/broken ref never breaks the bar.



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/rails_wayback/git.rb', line 102

def diff_paths(ref, paths: [])
  args = ["diff", "--name-only", ref]
  args += ["--", *paths] unless paths.empty?
  output = run(*args)
  output.split("\n").map(&:strip).reject(&:empty?)
rescue GitError => e
  if defined?(Rails) && Rails.respond_to?(:logger)
    Rails.logger.warn("[rails-wayback] diff_paths(#{ref.inspect}) failed: #{e.message}")
  end
  []
end

#repository?Boolean

Returns:

  • (Boolean)


24
25
26
27
28
# File 'lib/rails_wayback/git.rb', line 24

def repository?
  Dir.exist?(@root.join(".git")) || run("rev-parse", "--is-inside-work-tree").strip == "true"
rescue GitError
  false
end

#resolve_branch_for(sha) ⇒ Object

Same as branches_containing, but picks the most contextual one for display in the bar when the developer did NOT provide an explicit branch alongside the sha:

  • if the current branch contains the sha, prefer it (keeps you in your own context — the sha is on your history line),
  • otherwise pick the first non-current local branch that contains it (typical case of traveling AWAY from your branch),
  • returns nil if no local branch contains the sha (detached ref).


86
87
88
89
90
91
92
93
94
# File 'lib/rails_wayback/git.rb', line 86

def resolve_branch_for(sha)
  candidates = branches_containing(sha)
  return nil if candidates.empty?

  here = current_branch
  return here if candidates.include?(here)

  candidates.first
end

#resolve_ref(ref) ⇒ Object



57
58
59
60
61
# File 'lib/rails_wayback/git.rb', line 57

def resolve_ref(ref)
  run("rev-parse", "--verify", "#{ref}^{commit}").strip
rescue GitError
  raise RefNotFoundError, "Unknown git ref: #{ref.inspect}"
end

#rootObject



127
128
129
# File 'lib/rails_wayback/git.rb', line 127

def root
  @root
end

#show(ref, path) ⇒ Object



63
64
65
# File 'lib/rails_wayback/git.rb', line 63

def show(ref, path)
  run("show", "#{ref}:#{path}")
end