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.

Defined Under Namespace

Classes: Commit, GitError

Instance Method Summary collapse

Constructor Details

#initialize(root: nil) ⇒ Git

Returns a new instance of Git.



17
18
19
# File 'lib/rails_wayback/git.rb', line 17

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

Instance Method Details

#branchesObject



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

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).



68
69
70
71
72
73
# File 'lib/rails_wayback/git.rb', line 68

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



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/rails_wayback/git.rb', line 111

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



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/rails_wayback/git.rb', line 40

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



27
28
29
# File 'lib/rails_wayback/git.rb', line 27

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

#current_commitObject



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

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.



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/rails_wayback/git.rb', line 99

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)


21
22
23
24
25
# File 'lib/rails_wayback/git.rb', line 21

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).


83
84
85
86
87
88
89
90
91
# File 'lib/rails_wayback/git.rb', line 83

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



54
55
56
57
58
# File 'lib/rails_wayback/git.rb', line 54

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

#rootObject



124
125
126
# File 'lib/rails_wayback/git.rb', line 124

def root
  @root
end

#show(ref, path) ⇒ Object



60
61
62
# File 'lib/rails_wayback/git.rb', line 60

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