gemstack 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08b7f22bd22a410de8638e6249f629fc05d73c55c451ee15aaf5060aaceed7e9'
4
- data.tar.gz: c4729f7d3833d7645abf7f1ca70f7e281b9a4ec8406d0482baedf0f039668162
3
+ metadata.gz: 470fa3d30b1554cfa0aa75c71518f3ec47807a2f09f634c964720133c3cbe0d8
4
+ data.tar.gz: 1b11a77cbac038b67fc22c1b746ef2dd8011bf729a3523b7d99bb32857c62ea5
5
5
  SHA512:
6
- metadata.gz: 8892c3a10498fda83a04ab4323c86f29898eb250f9a4cae8f3c90aa2b32c83ae60f0c79e87459c28f6eaa7bdc99812d0acb675062a79de981ec942ec450052f1
7
- data.tar.gz: b3d2dc8f1d33abb5eca014e4679e5aa8a6a37772c1fb30d5720fcc638923787902a5324aeaaa2652476e1ce0e9815693f22f149d3a8e8b33e28a4bd288868ed6
6
+ metadata.gz: 7f52cbd61e22fc289cc33b7eaa9097ad5b3f8271a5b284599a164bd688547ccbda631268256d78c8f0bee9eb253f3b9b3adc74a86ac085292ffcd6896294f516
7
+ data.tar.gz: ff4886ab02478e6a1355f47fbe0d072886b49be8c8c33060a06b706137f47836dfe31f2ee52db0335c71de9b82a46f980728b1a9a26e5f0614e01c8dd584b741
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2
4
+
5
+ See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
6
+
3
7
  ## 0.4.1
4
8
 
5
9
  See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
@@ -76,7 +76,11 @@ module GemStack
76
76
  # template unchanged skipped (your edits are yours)
77
77
  # new in this version created
78
78
  # you never edited the file updated
79
- # you edited it you choose: overwrite, skip, diff, or save as FILE.new
79
+ # you edited it you choose: merge, overwrite, skip, diff, or save as FILE.new
80
+ #
81
+ # Merge is a three-way merge (git merge-file): the release's changes to the
82
+ # template are applied to your copy, keeping your edits. Where both changed
83
+ # the same lines the file gets <<<<<<< conflict markers to resolve.
80
84
  #
81
85
  # Apps record the templates they're on in .gemstack/version. Older apps don't,
82
86
  # so each file is compared with every earlier release's template: matching
@@ -88,9 +92,10 @@ module GemStack
88
92
  # (db/migrations/) are the app's history and never part of an update.
89
93
  MANAGED = %w[Gemfile .gemstack/version].freeze
90
94
 
91
- # created/updated/saved (as FILE.new) are done; conflicts were left for you;
92
- # removed are files you deleted, which stay deleted.
93
- Result = Struct.new(:created, :updated, :saved, :conflicts, :removed, keyword_init: true)
95
+ # created/updated/merged/saved (as FILE.new) are done; marked were merged
96
+ # with conflict markers to resolve; conflicts were left for you; removed
97
+ # are files you deleted, which stay deleted.
98
+ Result = Struct.new(:created, :updated, :merged, :marked, :saved, :conflicts, :removed, keyword_init: true)
94
99
 
95
100
  def self.recorded_version(root)
96
101
  path = File.join(root, VERSION_FILE)
@@ -109,7 +114,7 @@ module GemStack
109
114
  $stdin.gets.to_s.strip.downcase
110
115
  }
111
116
  @old_templates = old_templates
112
- @result = Result.new(created: [], updated: [], saved: [], conflicts: [], removed: [])
117
+ @result = Result.new(created: [], updated: [], merged: [], marked: [], saved: [], conflicts: [], removed: [])
113
118
  end
114
119
 
115
120
  def run
@@ -210,7 +215,7 @@ module GemStack
210
215
  return @result.removed << path if mine.nil?
211
216
  return update(path, file, :updated) if olds.include?(mine) # an unedited, older copy
212
217
 
213
- conflict(path, file, mine)
218
+ conflict(path, file, mine, olds.compact.uniq)
214
219
  end
215
220
 
216
221
  def create(path, file)
@@ -225,12 +230,16 @@ module GemStack
225
230
  status("update", path)
226
231
  end
227
232
 
228
- def conflict(path, file, mine)
233
+ def conflict(path, file, mine, bases)
229
234
  return record_conflict(path) unless @interactive
230
235
 
231
236
  loop do
232
- case @prompt.call("#{path} changed in this version and you edited it — [o]verwrite, [s]kip, " \
233
- "[d]iff, save as [n]ew file?")
237
+ case @prompt.call("#{path} changed in this version and you edited it — [m]erge (keeps your edits), " \
238
+ "[o]verwrite, [s]kip, [d]iff, save as [n]ew file?")
239
+ when "m"
240
+ return if merge(path, file, mine, bases)
241
+
242
+ @output.puts(" Can't merge: it needs git and the earlier template (not available). Choose another option.")
234
243
  when "o" then return update(path, file, :updated)
235
244
  when "n" then return save_new(path, file)
236
245
  when "d" then @output.puts(diff(mine, file.content, path))
@@ -239,6 +248,39 @@ module GemStack
239
248
  end
240
249
  end
241
250
 
251
+ # Applies the release's template changes to your copy. Without a recorded
252
+ # version the starting template isn't known: each earlier release's is
253
+ # tried (newest first) and the merge with the fewest conflicts wins.
254
+ def merge(path, file, mine, bases)
255
+ merges = bases.reverse.filter_map { |base| merge_file(path, mine, base, file.content) }
256
+ return false if merges.empty?
257
+
258
+ text, conflicts = merges.min_by { |_, count| count }
259
+ write(path, AppFile.new(text, file.mode))
260
+ if conflicts.zero?
261
+ @result.merged << path
262
+ status("merge", path)
263
+ else
264
+ @result.marked << path
265
+ status("merge", "#{path} — #{conflicts} conflict#{"s" if conflicts > 1}: resolve the <<<<<<< markers")
266
+ end
267
+ true
268
+ end
269
+
270
+ # [merged text, number of conflicts], or nil without git.
271
+ def merge_file(path, mine, base, theirs)
272
+ Dir.mktmpdir("gemstack-merge") do |dir|
273
+ files = { "yours" => mine, "base" => base, "new" => theirs }.map do |name, content|
274
+ File.join(dir, name).tap { |file| File.binwrite(file, content) }
275
+ end
276
+ labels = ["#{path} (yours)", "#{path} (earlier GemStack)", "#{path} (GemStack #{GemStack::VERSION})"]
277
+ out, status = Open3.capture2("git", "merge-file", "-p", *labels.flat_map { |label| ["-L", label] }, *files)
278
+ [out, status.exitstatus] if status.exitstatus.between?(0, 127)
279
+ end
280
+ rescue SystemCallError
281
+ nil
282
+ end
283
+
242
284
  def save_new(path, file)
243
285
  write("#{path}.new", file)
244
286
  @result.saved << path
@@ -275,7 +317,8 @@ module GemStack
275
317
  end
276
318
 
277
319
  def report
278
- done = { "created" => @result.created, "updated" => @result.updated, "saved as .new" => @result.saved,
320
+ done = { "created" => @result.created, "updated" => @result.updated, "merged" => @result.merged,
321
+ "merged with conflicts to resolve" => @result.marked, "saved as .new" => @result.saved,
279
322
  "left for you" => @result.conflicts }.reject { |_, paths| paths.empty? }
280
323
  if done.empty?
281
324
  @output.puts(" nothing to update")
data/lib/gemstack/cli.rb CHANGED
@@ -241,7 +241,7 @@ module GemStack
241
241
  are updated, and for ones you edited you choose: overwrite, skip, see the diff, or FILE.new.
242
242
 
243
243
  gemstack update # the latest release
244
- gemstack update 0.4.1 # a specific version
244
+ gemstack update 0.4.2 # a specific version
245
245
  gemstack update --templates --dry-run # only the template step, preview
246
246
  gemstack update --templates --from 0.3.5
247
247
  DESC
@@ -2,5 +2,5 @@
2
2
 
3
3
  module GemStack
4
4
  # All GemStack gems are released together under a single version.
5
- VERSION = "0.4.1"
5
+ VERSION = "0.4.2"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemstack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adware Technologies
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 0.4.1
47
+ version: 0.4.2
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 0.4.1
54
+ version: 0.4.2
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: json
57
57
  requirement: !ruby/object:Gem::Requirement