Class: Cimas::WorkingCopy

Inherits:
Object
  • Object
show all
Defined in:
lib/cimas/working_copy.rb

Overview

Deep module over one repository's working copy: every git-gem call, exception rescue, and porcelain/CLI parsing the orchestrator needs lives behind ~10 domain verbs. Subcommands then read as wave prose instead of git incantations.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(git) ⇒ WorkingCopy

Returns a new instance of WorkingCopy.



18
19
20
# File 'lib/cimas/working_copy.rb', line 18

def initialize(git)
  @git = git
end

Class Method Details

.clone(remote, name, path:) ⇒ Object



14
15
16
# File 'lib/cimas/working_copy.rb', line 14

def self.clone(remote, name, path:)
  Git.clone(remote, name, path: path)
end

.open(dir) ⇒ Object



10
11
12
# File 'lib/cimas/working_copy.rb', line 10

def self.open(dir)
  new(Git.open(dir))
end

Instance Method Details

#clean?Boolean

True when nothing is staged or modified — push skips the commit in this case.

Returns:

  • (Boolean)


56
57
58
59
# File 'lib/cimas/working_copy.rb', line 56

def clean?
  status = @git.status
  status.changed.empty? && status.added.empty? && status.deleted.empty?
end

#commit(message) ⇒ Object



65
66
67
# File 'lib/cimas/working_copy.rb', line 65

def commit(message)
  @git.commit(message)
end

#commit_all(message) ⇒ Object



61
62
63
# File 'lib/cimas/working_copy.rb', line 61

def commit_all(message)
  @git.commit_all(message)
end

#diff_patchObject



116
117
118
# File 'lib/cimas/working_copy.rb', line 116

def diff_patch
  @git.diff.patch
end

#drift?Boolean

True when the working copy has any uncommitted change.

Returns:

  • (Boolean)


31
32
33
34
35
36
# File 'lib/cimas/working_copy.rb', line 31

def drift?
  out, = Open3.capture3("git", "-C", dir, "status", "--porcelain")
  !out.strip.empty?
rescue StandardError
  false
end

#each_staged_changeObject



120
121
122
123
124
# File 'lib/cimas/working_copy.rb', line 120

def each_staged_change
  @git.status.changed.each do |file, status|
    yield(file, status.blob(:index).contents)
  end
end

#fetch_reset_pull(branch) ⇒ Object

pull's dance: fetch origin, best-effort reset (a fresh clone may not hold the branch ref yet), checkout, pull.



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/cimas/working_copy.rb', line 87

def fetch_reset_pull(branch)
  @git.remote("origin").fetch
  begin
    @git.reset_hard(branch)
  rescue Git::Error
    # reset can fail when the branch ref is absent on a fresh
    # clone; checkout/pull below still applies.
    nil
  end
  checkout(branch)
  @git.pull("origin", branch)
end

#head_shaObject



22
23
24
# File 'lib/cimas/working_copy.rb', line 22

def head_sha
  @git.object("HEAD").sha
end

#push(branch, force: false, remote: nil) ⇒ Object

Returns :pushed, :behind_remote (non-force push behind the remote tip), or [:rejected, error]. The caller decides presentation. "Updates were rejected" covers both the classic git phrasing ("tip of your current branch is behind") and the modern one ("the remote contains work that you do not have locally").



105
106
107
108
109
110
111
112
113
114
# File 'lib/cimas/working_copy.rb', line 105

def push(branch, force: false, remote: nil)
  @git.push(remote || remote_name, branch, force: force)
  :pushed
rescue Git::GitExecuteError, Git::FailedError => e
  if e.message.match?(/Updates were rejected/)
    :behind_remote
  else
    [:rejected, e]
  end
end

#remote_nameObject



26
27
28
# File 'lib/cimas/working_copy.rb', line 26

def remote_name
  @git.remotes.first.to_s
end

#remove(*paths) ⇒ Object



50
51
52
# File 'lib/cimas/working_copy.rb', line 50

def remove(*paths)
  paths.each { |path| @git.remove(path) }
end

#reset_clean(branch, include_untracked: false) ⇒ Object

checkout + reset_hard + clean: the "start from a pristine branch" preamble of sync and cleanup-orphan-files.



40
41
42
43
44
# File 'lib/cimas/working_copy.rb', line 40

def reset_clean(branch, include_untracked: false)
  checkout(branch)
  @git.reset_hard(branch)
  @git.clean(force: true, d: include_untracked)
end

#reset_onto(base, discard_branch: nil) ⇒ Object

push's keep_changes=false preamble: stand on the base branch, soft-reset onto it, and discard an existing branch of the given name so it can be provisioned fresh.



72
73
74
75
76
# File 'lib/cimas/working_copy.rb', line 72

def reset_onto(base, discard_branch: nil)
  checkout(base)
  @git.reset(base)
  @git.branch(discard_branch).delete if discard_branch && @git.is_branch?(discard_branch)
end

#stage(*paths) ⇒ Object



46
47
48
# File 'lib/cimas/working_copy.rb', line 46

def stage(*paths)
  paths.each { |path| @git.add(path) }
end

#switch_branch(name, fresh: false) ⇒ Object

Switch to a branch; :fresh discards an existing branch of the same name first (cleanup-orphan-files' shape).



80
81
82
83
# File 'lib/cimas/working_copy.rb', line 80

def switch_branch(name, fresh: false)
  @git.branch(name).delete if fresh && @git.is_branch?(name)
  @git.branch(name).checkout
end