Class: FlashFlow::Git

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

Direct Known Subclasses

ShadowGit

Constant Summary collapse

ATTRIBUTES =
[:remote, :merge_branch, :master_branch, :release_branch, :use_rerere]
UNMERGED_STATUSES =
%w{DD AU UD UA DU AA UU}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, logger = nil) ⇒ Git

Returns a new instance of Git.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/flash_flow/git.rb', line 13

def initialize(config, logger=nil)
  @cmd_runner = CmdRunner.new(logger: logger)

  config['release_branch'] ||= config['master_branch']
  config['remote'] ||= config['merge_remote'] # For backwards compatibility

  ATTRIBUTES.each do |attr|
    unless config.has_key?(attr.to_s)
      raise RuntimeError.new("git configuration missing. Required config parameters: #{ATTRIBUTES}")
    end

    instance_variable_set("@#{attr}", config[attr.to_s])
  end

  @working_branch = current_branch
end

Instance Attribute Details

#working_branchObject (readonly)

Returns the value of attribute working_branch.



9
10
11
# File 'lib/flash_flow/git.rb', line 9

def working_branch
  @working_branch
end

Instance Method Details

#add_and_commit(files, message, opts = {}) ⇒ Object



52
53
54
55
56
# File 'lib/flash_flow/git.rb', line 52

def add_and_commit(files, message, opts={})
  files = [files].flatten
  run("add #{'-f ' if opts[:add] && opts[:add][:force]}#{files.join(' ')}")
  run("commit -m '#{message}'")
end

#ahead_of_master?(branch) ⇒ Boolean

Returns:

  • (Boolean)


230
231
232
# File 'lib/flash_flow/git.rb', line 230

def ahead_of_master?(branch)
  branch_exists?(branch) && !master_branch_contains?(get_sha(branch))
end

#branch_contains?(branch, ref) ⇒ Boolean

Returns:

  • (Boolean)


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

def branch_contains?(branch, ref)
  run("branch -a --contains #{ref}", log: CmdRunner::LOG_CMD)
  last_stdout.split("\n").detect { |str| str[2..-1] == branch }
end

#branch_exists?(branch) ⇒ Boolean

Returns:

  • (Boolean)


225
226
227
228
# File 'lib/flash_flow/git.rb', line 225

def branch_exists?(branch)
  run("rev-parse --verify #{branch}")
  last_success?
end

#commit_rerere(current_rereres) ⇒ Object



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

def commit_rerere(current_rereres)
  return unless use_rerere
  @cmd_runner.run('mkdir rr-cache')
  @cmd_runner.run('rm -rf rr-cache/*')
  current_rereres.each do |rerere|
    @cmd_runner.run("cp -R .git/rr-cache/#{rerere} rr-cache/")
  end

  run('add rr-cache/')
  run("commit -m 'Update rr-cache'")
end

#conflicted_filesObject



151
152
153
154
# File 'lib/flash_flow/git.rb', line 151

def conflicted_files
  run("diff --name-only --diff-filter=U")
  last_stdout.split("\n")
end

#copy_temp_to_branch(branch, squash_message = nil) ⇒ Object



178
179
180
181
182
183
184
185
# File 'lib/flash_flow/git.rb', line 178

def copy_temp_to_branch(branch, squash_message = nil)
  run("checkout #{temp_merge_branch}")
  run("merge --strategy=ours --no-edit #{branch}")
  run("checkout #{branch}")
  run("merge #{temp_merge_branch}")

  squash_commits(branch, squash_message) if squash_message
end

#current_branchObject



156
157
158
159
# File 'lib/flash_flow/git.rb', line 156

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

#delete_temp_merge_branchObject



187
188
189
190
191
# File 'lib/flash_flow/git.rb', line 187

def delete_temp_merge_branch
  in_branch(master_branch) do
    run("branch -d #{temp_merge_branch}")
  end
end

#get_sha(branch, opts = {}) ⇒ Object



216
217
218
219
220
221
222
223
# File 'lib/flash_flow/git.rb', line 216

def get_sha(branch, opts={})
  if opts[:short]
    run("rev-parse --short #{branch}")
  else
    run("rev-parse #{branch}")
  end
  last_stdout.strip if last_success?
end

#in_branch(branch) ⇒ Object



201
202
203
204
205
206
207
208
209
210
# File 'lib/flash_flow/git.rb', line 201

def in_branch(branch)
  begin
    starting_branch = current_branch
    run("checkout #{branch}")

    yield
  ensure
    run("checkout #{starting_branch}")
  end
end

#in_dirObject



30
31
32
33
34
# File 'lib/flash_flow/git.rb', line 30

def in_dir
  Dir.chdir(@cmd_runner.dir) do
    yield
  end
end

#in_merge_branch(&block) ⇒ Object



197
198
199
# File 'lib/flash_flow/git.rb', line 197

def in_merge_branch(&block)
  in_branch(merge_branch, &block)
end

#in_original_merge_branchObject



71
72
73
# File 'lib/flash_flow/git.rb', line 71

def in_original_merge_branch
  in_branch("#{remote}/#{merge_branch}") { yield }
end

#in_temp_merge_branch(&block) ⇒ Object



193
194
195
# File 'lib/flash_flow/git.rb', line 193

def in_temp_merge_branch(&block)
  in_branch(temp_merge_branch, &block)
end

#initialize_rerereObject



80
81
82
83
84
85
# File 'lib/flash_flow/git.rb', line 80

def initialize_rerere
  return unless use_rerere

  @cmd_runner.run('mkdir .git/rr-cache')
  @cmd_runner.run('cp -R rr-cache/* .git/rr-cache/')
end

#last_commandObject



40
41
42
# File 'lib/flash_flow/git.rb', line 40

def last_command
  @cmd_runner.last_command
end

#last_stdoutObject



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

def last_stdout
  @cmd_runner.last_stdout
end

#last_success?Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/flash_flow/git.rb', line 44

def last_success?
  @cmd_runner.last_success?
end

#master_branch_contains?(sha) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/flash_flow/git.rb', line 67

def master_branch_contains?(sha)
  branch_contains?("remotes/#{remote}/#{master_branch}", sha)
end

#merge(branch) ⇒ Object



58
59
60
# File 'lib/flash_flow/git.rb', line 58

def merge(branch)
  run("merge #{branch}")
end

#most_recent_commitObject



161
162
163
# File 'lib/flash_flow/git.rb', line 161

def most_recent_commit
  run("show -s --format=%cd head")
end

#push(branch, force = false) ⇒ Object



174
175
176
# File 'lib/flash_flow/git.rb', line 174

def push(branch, force=false)
  run("push #{'-f' if force} #{remote} #{branch}")
end

#read_file_from_merge_branch(filename) ⇒ Object



75
76
77
78
# File 'lib/flash_flow/git.rb', line 75

def read_file_from_merge_branch(filename)
  run("show #{remote}/#{merge_branch}:#{filename}", log: CmdRunner::LOG_CMD)
  last_stdout
end

#rerere_resolve!Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/flash_flow/git.rb', line 99

def rerere_resolve!
  return false unless use_rerere

  if unresolved_conflicts.empty?
    merging_files = staged_and_working_dir_files.select { |s| UNMERGED_STATUSES.include?(s[0..1]) }.map { |s| s[3..-1] }
    conflicts = conflicted_files

    run("add #{merging_files.join(" ")}")
    run('commit --no-edit')

    resolutions(conflicts)
  else
    false
  end
end

#reset_temp_merge_branchObject



165
166
167
168
169
170
171
172
# File 'lib/flash_flow/git.rb', line 165

def reset_temp_merge_branch
  in_branch(master_branch) do
    run("fetch #{remote}")
    run("branch -D #{temp_merge_branch}")
    run("checkout -b #{temp_merge_branch}")
    run("reset --hard #{remote}/#{master_branch}")
  end
end

#resolution_candidates(file) ⇒ Object

git rerere doesn’t give you a deterministic way to determine which resolution was used



132
133
134
135
136
137
138
139
140
# File 'lib/flash_flow/git.rb', line 132

def resolution_candidates(file)
  @cmd_runner.run("diff -q --from-file #{file} .git/rr-cache/*/postimage*", log: CmdRunner::LOG_CMD)
  different_files = split_diff_lines(@cmd_runner.last_stdout)

  @cmd_runner.run('ls -la .git/rr-cache/*/postimage*', log: CmdRunner::LOG_CMD)
  all_files = split_diff_lines(@cmd_runner.last_stdout)

  all_files - different_files
end

#resolutions(files) ⇒ Object



123
124
125
126
127
128
129
# File 'lib/flash_flow/git.rb', line 123

def resolutions(files)
  {}.tap do |hash|
    files.map do |file|
      hash[file] = resolution_candidates(file)
    end.flatten
  end
end

#run(cmd, opts = {}) ⇒ Object



48
49
50
# File 'lib/flash_flow/git.rb', line 48

def run(cmd, opts={})
  @cmd_runner.run("git #{cmd}", opts)
end

#split_diff_lines(arr) ⇒ Object



142
143
144
# File 'lib/flash_flow/git.rb', line 142

def split_diff_lines(arr)
  arr.split("\n").map { |s| s.split(".git/rr-cache/").last.split("/postimage").first }
end

#staged_and_working_dir_filesObject



146
147
148
149
# File 'lib/flash_flow/git.rb', line 146

def staged_and_working_dir_files
  run("status --porcelain")
  last_stdout.split("\n").reject { |line| line[0..1] == '??' }
end

#temp_merge_branchObject



212
213
214
# File 'lib/flash_flow/git.rb', line 212

def temp_merge_branch
  "flash_flow/#{merge_branch}"
end

#unresolved_conflictsObject



115
116
117
118
119
120
121
# File 'lib/flash_flow/git.rb', line 115

def unresolved_conflicts
  in_dir do
    conflicted_files.map do |file|
      File.open(file) { |f| f.grep(/>>>>/) }.empty? ? nil : file
    end.compact
  end
end