leg 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +34 -0
- data/LICENSE +21 -0
- data/README.md +59 -0
- data/Rakefile +11 -0
- data/TUTORIAL.md +243 -0
- data/bin/console +9 -0
- data/bin/setup +6 -0
- data/exe/leg +6 -0
- data/leg.gemspec +31 -0
- data/lib/leg.rb +27 -0
- data/lib/leg/cli.rb +42 -0
- data/lib/leg/commands.rb +19 -0
- data/lib/leg/commands/amend.rb +49 -0
- data/lib/leg/commands/base_command.rb +99 -0
- data/lib/leg/commands/build.rb +126 -0
- data/lib/leg/commands/commit.rb +48 -0
- data/lib/leg/commands/diff.rb +29 -0
- data/lib/leg/commands/help.rb +43 -0
- data/lib/leg/commands/init.rb +46 -0
- data/lib/leg/commands/reset.rb +26 -0
- data/lib/leg/commands/resolve.rb +31 -0
- data/lib/leg/commands/save.rb +31 -0
- data/lib/leg/commands/status.rb +54 -0
- data/lib/leg/commands/step.rb +31 -0
- data/lib/leg/config.rb +42 -0
- data/lib/leg/default_templates.rb +340 -0
- data/lib/leg/diff.rb +151 -0
- data/lib/leg/diff_transformers.rb +11 -0
- data/lib/leg/diff_transformers/base_transformer.rb +13 -0
- data/lib/leg/diff_transformers/fold_sections.rb +89 -0
- data/lib/leg/diff_transformers/omit_adjacent_removals.rb +38 -0
- data/lib/leg/diff_transformers/syntax_highlight.rb +32 -0
- data/lib/leg/diff_transformers/trim_blank_lines.rb +25 -0
- data/lib/leg/line.rb +83 -0
- data/lib/leg/markdown.rb +20 -0
- data/lib/leg/page.rb +27 -0
- data/lib/leg/representations.rb +9 -0
- data/lib/leg/representations/base_representation.rb +42 -0
- data/lib/leg/representations/git.rb +388 -0
- data/lib/leg/representations/litdiff.rb +85 -0
- data/lib/leg/step.rb +16 -0
- data/lib/leg/template.rb +95 -0
- data/lib/leg/tutorial.rb +49 -0
- data/lib/leg/version.rb +3 -0
- metadata +112 -38
- data/bin/leg +0 -9
- data/lib/snaptoken.rb +0 -24
- data/lib/snaptoken/cli.rb +0 -61
- data/lib/snaptoken/commands.rb +0 -13
- data/lib/snaptoken/commands/amend.rb +0 -27
- data/lib/snaptoken/commands/base_command.rb +0 -92
- data/lib/snaptoken/commands/build.rb +0 -107
- data/lib/snaptoken/commands/commit.rb +0 -27
- data/lib/snaptoken/commands/help.rb +0 -38
- data/lib/snaptoken/commands/resolve.rb +0 -27
- data/lib/snaptoken/commands/status.rb +0 -21
- data/lib/snaptoken/commands/step.rb +0 -35
- data/lib/snaptoken/default_templates.rb +0 -287
- data/lib/snaptoken/diff.rb +0 -180
- data/lib/snaptoken/diff_line.rb +0 -54
- data/lib/snaptoken/diff_transformers.rb +0 -9
- data/lib/snaptoken/diff_transformers/base_transformer.rb +0 -9
- data/lib/snaptoken/diff_transformers/fold_sections.rb +0 -85
- data/lib/snaptoken/diff_transformers/omit_adjacent_removals.rb +0 -28
- data/lib/snaptoken/diff_transformers/trim_blank_lines.rb +0 -21
- data/lib/snaptoken/markdown.rb +0 -18
- data/lib/snaptoken/page.rb +0 -64
- data/lib/snaptoken/representations.rb +0 -8
- data/lib/snaptoken/representations/base_representation.rb +0 -38
- data/lib/snaptoken/representations/git.rb +0 -262
- data/lib/snaptoken/representations/litdiff.rb +0 -81
- data/lib/snaptoken/step.rb +0 -27
- data/lib/snaptoken/template.rb +0 -53
- data/lib/snaptoken/tutorial.rb +0 -64
data/lib/snaptoken/diff_line.rb
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
class Snaptoken::DiffLine
|
2
|
-
TYPES = [:added, :removed, :unchanged, :folded]
|
3
|
-
|
4
|
-
attr_reader :type, :source, :line_numbers
|
5
|
-
attr_writer :source, :line_numbers
|
6
|
-
|
7
|
-
def initialize(type, source, line_numbers)
|
8
|
-
unless TYPES.include? type
|
9
|
-
raise ArgumentError, "type must be one of: #{TYPES.inspect}"
|
10
|
-
end
|
11
|
-
@type = type
|
12
|
-
@source = source.chomp
|
13
|
-
@line_numbers = line_numbers
|
14
|
-
end
|
15
|
-
|
16
|
-
def clone
|
17
|
-
Snaptoken::DiffLine.new(@type, @source.dup, @line_numbers.dup)
|
18
|
-
end
|
19
|
-
|
20
|
-
def type=(type)
|
21
|
-
unless TYPES.include? type
|
22
|
-
raise ArgumentError, "type must be one of: #{TYPES.inspect}"
|
23
|
-
end
|
24
|
-
@type = type
|
25
|
-
end
|
26
|
-
|
27
|
-
def blank?
|
28
|
-
@source.strip.empty?
|
29
|
-
end
|
30
|
-
|
31
|
-
def line_number
|
32
|
-
case @type
|
33
|
-
when :removed, :folded
|
34
|
-
@line_numbers[0]
|
35
|
-
when :added, :unchanged
|
36
|
-
@line_numbers[1]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def to_patch(options = {})
|
41
|
-
options[:unchanged_char] ||= " "
|
42
|
-
|
43
|
-
case @type
|
44
|
-
when :added
|
45
|
-
"+#{@source}\n"
|
46
|
-
when :removed
|
47
|
-
"-#{@source}\n"
|
48
|
-
when :unchanged
|
49
|
-
"#{options[:unchanged_char]}#{@source}\n"
|
50
|
-
when :folded
|
51
|
-
raise "can't convert folded line to patch"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,9 +0,0 @@
|
|
1
|
-
module Snaptoken::DiffTransformers
|
2
|
-
end
|
3
|
-
|
4
|
-
require 'snaptoken/diff_transformers/base_transformer'
|
5
|
-
|
6
|
-
require 'snaptoken/diff_transformers/fold_sections'
|
7
|
-
require 'snaptoken/diff_transformers/omit_adjacent_removals'
|
8
|
-
require 'snaptoken/diff_transformers/trim_blank_lines'
|
9
|
-
|
@@ -1,85 +0,0 @@
|
|
1
|
-
class Snaptoken::DiffTransformers::FoldSections < Snaptoken::DiffTransformers::BaseTransformer
|
2
|
-
def transform(diff)
|
3
|
-
sections = @options[:section_types].map { [] }
|
4
|
-
|
5
|
-
cur_sections = @options[:section_types].map { nil }
|
6
|
-
diff.lines.each.with_index do |line, idx|
|
7
|
-
@options[:section_types].each.with_index do |section_type, level|
|
8
|
-
if line.source =~ Regexp.new(section_type[:start])
|
9
|
-
if !section_type[:end] && cur_sections[level]
|
10
|
-
cur_sections[level].end_line = idx - 1
|
11
|
-
if @options[:unfold_before_new_section]
|
12
|
-
cur_sections[level].dirty! if [:added, :removed].include? line.type
|
13
|
-
end
|
14
|
-
sections[level] << cur_sections[level]
|
15
|
-
end
|
16
|
-
|
17
|
-
cur_sections[level] = Section.new(level, idx)
|
18
|
-
|
19
|
-
if [:added, :removed].include? line.type
|
20
|
-
cur_sections[level].dirty!
|
21
|
-
end
|
22
|
-
elsif section_type[:end] && line.source =~ Regexp.new(section_type[:end])
|
23
|
-
if [:added, :removed].include? line.type
|
24
|
-
cur_sections[level].dirty!
|
25
|
-
end
|
26
|
-
|
27
|
-
cur_sections[level].end_line = idx
|
28
|
-
sections[level] << cur_sections[level]
|
29
|
-
cur_sections[level] = nil
|
30
|
-
elsif cur_sections[level]
|
31
|
-
if [:added, :removed].include? line.type
|
32
|
-
cur_sections[level].dirty!
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
cur_sections.each.with_index do |section, level|
|
38
|
-
unless section.nil?
|
39
|
-
section.end_line = diff.lines.length - 1
|
40
|
-
sections[level] << section
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
new_diff = diff.clone
|
45
|
-
sections.each.with_index do |level_sections, level|
|
46
|
-
level_sections.each do |section|
|
47
|
-
if !section.dirty? && !new_diff.lines[section.to_range].any?(&:nil?)
|
48
|
-
start_line = new_diff.lines[section.start_line]
|
49
|
-
end_line = new_diff.lines[section.end_line]
|
50
|
-
|
51
|
-
summary_lines = [start_line]
|
52
|
-
summary_lines << end_line if @options[:section_types][level][:end]
|
53
|
-
summary = summary_lines.map(&:source).join(" … ")
|
54
|
-
|
55
|
-
line_numbers = [start_line.line_number, end_line.line_number]
|
56
|
-
|
57
|
-
folded_line = Snaptoken::DiffLine.new(:folded, summary, line_numbers)
|
58
|
-
|
59
|
-
section.to_range.each do |idx|
|
60
|
-
new_diff.lines[idx] = nil
|
61
|
-
end
|
62
|
-
|
63
|
-
new_diff.lines[section.start_line] = folded_line
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
new_diff.lines.compact!
|
68
|
-
new_diff
|
69
|
-
end
|
70
|
-
|
71
|
-
class Section
|
72
|
-
attr_accessor :level, :start_line, :end_line, :dirty
|
73
|
-
|
74
|
-
def initialize(level, start_line, end_line = nil, dirty = false)
|
75
|
-
@level, @start_line, @end_line, @dirty = level, start_line, end_line, dirty
|
76
|
-
end
|
77
|
-
|
78
|
-
def to_range
|
79
|
-
start_line..end_line
|
80
|
-
end
|
81
|
-
|
82
|
-
def dirty?; @dirty; end
|
83
|
-
def dirty!; @dirty = true; end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
class Snaptoken::DiffTransformers::OmitAdjacentRemovals < Snaptoken::DiffTransformers::BaseTransformer
|
2
|
-
def transform(diff)
|
3
|
-
new_diff = diff.clone
|
4
|
-
|
5
|
-
removed_lines = []
|
6
|
-
saw_added_line = false
|
7
|
-
new_diff.lines.each.with_index do |line, idx|
|
8
|
-
case line.type
|
9
|
-
when :unchanged, :folded
|
10
|
-
if saw_added_line
|
11
|
-
removed_lines.each do |removed_idx|
|
12
|
-
new_diff.lines[removed_idx] = nil
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
removed_lines = []
|
17
|
-
saw_added_line = false
|
18
|
-
when :added
|
19
|
-
saw_added_line = true
|
20
|
-
when :removed
|
21
|
-
removed_lines << idx
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
new_diff.lines.compact!
|
26
|
-
new_diff
|
27
|
-
end
|
28
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
class Snaptoken::DiffTransformers::TrimBlankLines < Snaptoken::DiffTransformers::BaseTransformer
|
2
|
-
def transform(diff)
|
3
|
-
new_diff = diff.clone_empty
|
4
|
-
diff.lines.each.with_index do |line, idx|
|
5
|
-
line = line.clone
|
6
|
-
if line.blank? && [:added, :removed].include?(line.type)
|
7
|
-
prev_line = idx > 0 ? diff.lines[idx - 1] : nil
|
8
|
-
next_line = idx < diff.lines.length - 1 ? diff.lines[idx + 1] : nil
|
9
|
-
|
10
|
-
prev_changed = prev_line && [:added, :removed].include?(prev_line.type)
|
11
|
-
next_changed = next_line && [:added, :removed].include?(next_line.type)
|
12
|
-
|
13
|
-
if !prev_changed || !next_changed
|
14
|
-
line.type = :unchanged
|
15
|
-
end
|
16
|
-
end
|
17
|
-
new_diff.lines << line
|
18
|
-
end
|
19
|
-
new_diff
|
20
|
-
end
|
21
|
-
end
|
data/lib/snaptoken/markdown.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module Snaptoken::Markdown
|
2
|
-
class HTMLRouge < Redcarpet::Render::HTML
|
3
|
-
include Rouge::Plugins::Redcarpet
|
4
|
-
end
|
5
|
-
|
6
|
-
HTML_RENDERER = HTMLRouge.new(with_toc_data: true)
|
7
|
-
MARKDOWN_RENDERER = Redcarpet::Markdown.new(HTML_RENDERER, fenced_code_blocks: true)
|
8
|
-
|
9
|
-
def self.render(source)
|
10
|
-
html = MARKDOWN_RENDERER.render(source)
|
11
|
-
html = Redcarpet::Render::SmartyPants.render(html)
|
12
|
-
html.gsub!(/<\/code>‘/) { "</code>’" }
|
13
|
-
html.gsub!(/^\s*<h([23456]) id="([^"]+)">(.+)<\/h\d>$/) {
|
14
|
-
"<h#{$1} id=\"#{$2}\"><a href=\"##{$2}\">#{$3}</a></h#{$1}>"
|
15
|
-
}
|
16
|
-
html
|
17
|
-
end
|
18
|
-
end
|
data/lib/snaptoken/page.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
class Snaptoken::Page
|
2
|
-
attr_accessor :filename, :steps, :footer_text
|
3
|
-
|
4
|
-
def initialize(filename = "tutorial")
|
5
|
-
@filename = filename
|
6
|
-
@steps = []
|
7
|
-
@footer_text = nil
|
8
|
-
end
|
9
|
-
|
10
|
-
def <<(step)
|
11
|
-
@steps << step
|
12
|
-
self
|
13
|
-
end
|
14
|
-
|
15
|
-
def empty?
|
16
|
-
@steps.empty?
|
17
|
-
end
|
18
|
-
|
19
|
-
def title
|
20
|
-
first_line = @steps.first ? @steps.first.text.lines.first : (@footer_text ? @footer_text.lines.first : nil)
|
21
|
-
if first_line && first_line.start_with?("# ")
|
22
|
-
first_line[2..-1].strip
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_html(tutorial, offline)
|
27
|
-
content = ""
|
28
|
-
@steps.each do |step|
|
29
|
-
if !step.text.strip.empty?
|
30
|
-
html = Snaptoken::Markdown.render(step.text)
|
31
|
-
html.gsub!(/<p>{{step (\d+)}}<\/p>/) do
|
32
|
-
step = tutorial.step($1.to_i)
|
33
|
-
step.syntax_highlight!
|
34
|
-
step.to_html(tutorial, offline)
|
35
|
-
end
|
36
|
-
content << html
|
37
|
-
end
|
38
|
-
|
39
|
-
step.syntax_highlight!
|
40
|
-
content << step.to_html(tutorial, offline)
|
41
|
-
end
|
42
|
-
if @footer_text
|
43
|
-
# TODO: DRY this up. Please.
|
44
|
-
html = Snaptoken::Markdown.render(@footer_text)
|
45
|
-
html.gsub!(/<p>{{step (\d+)}}<\/p>/) do
|
46
|
-
step = tutorial.step($1.to_i)
|
47
|
-
step.syntax_highlight!
|
48
|
-
step.to_html(tutorial, offline)
|
49
|
-
end
|
50
|
-
content << html
|
51
|
-
end
|
52
|
-
|
53
|
-
page_number = tutorial.pages.index(self) + 1
|
54
|
-
|
55
|
-
Snaptoken::Template.new(tutorial.page_template, tutorial,
|
56
|
-
offline: offline,
|
57
|
-
page_title: title,
|
58
|
-
content: content,
|
59
|
-
page_number: page_number,
|
60
|
-
prev_page: page_number > 1 ? tutorial.pages[page_number - 2] : nil,
|
61
|
-
next_page: page_number < tutorial.pages.length ? tutorial.pages[page_number] : nil
|
62
|
-
).render_template
|
63
|
-
end
|
64
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
class Snaptoken::Representations::BaseRepresentation
|
2
|
-
def initialize(tutorial)
|
3
|
-
@tutorial = tutorial
|
4
|
-
end
|
5
|
-
|
6
|
-
# Should save @tutorial to disk.
|
7
|
-
def save!(options = {})
|
8
|
-
raise NotImplementedError
|
9
|
-
end
|
10
|
-
|
11
|
-
# Should load @tutorial (in place) from disk, and return it.
|
12
|
-
def load!(options = {})
|
13
|
-
raise NotImplementedError
|
14
|
-
end
|
15
|
-
|
16
|
-
# Returns true if this representation has been modified by the user since the
|
17
|
-
# last sync.
|
18
|
-
def modified?
|
19
|
-
synced_at = @tutorial.last_synced_at
|
20
|
-
repr_modified_at = modified_at
|
21
|
-
return false if synced_at.nil? or repr_modified_at.nil?
|
22
|
-
|
23
|
-
repr_modified_at > synced_at
|
24
|
-
end
|
25
|
-
|
26
|
-
# Returns true if this representation currently exists on disk.
|
27
|
-
def exists?
|
28
|
-
!modified_at.nil?
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
# Should return the Time the representation on disk was last modified, or nil
|
34
|
-
# if the representation doesn't exist.
|
35
|
-
def modified_at
|
36
|
-
raise NotImplementedError
|
37
|
-
end
|
38
|
-
end
|
@@ -1,262 +0,0 @@
|
|
1
|
-
class Snaptoken::Representations::Git < Snaptoken::Representations::BaseRepresentation
|
2
|
-
def save!(options = {})
|
3
|
-
FileUtils.rm_rf(repo_path)
|
4
|
-
FileUtils.mkdir_p(repo_path)
|
5
|
-
|
6
|
-
FileUtils.cd(repo_path) do
|
7
|
-
repo = Rugged::Repository.init_at(".")
|
8
|
-
|
9
|
-
step_num = 1
|
10
|
-
@tutorial.pages.each do |page|
|
11
|
-
message = "~~~ #{page.filename}"
|
12
|
-
message << "\n\n#{page.footer_text}" if page.footer_text
|
13
|
-
add_commit(repo, nil, message, step_num)
|
14
|
-
page.steps.each do |step|
|
15
|
-
message = "#{step.summary}\n\n#{step.text}".strip
|
16
|
-
add_commit(repo, step.to_patch, message, step_num)
|
17
|
-
|
18
|
-
yield step_num if block_given?
|
19
|
-
step_num += 1
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
#if options[:extra_path]
|
24
|
-
# FileUtils.cp_r(File.join(options[:extra_path], "."), ".")
|
25
|
-
# add_commit(repo, nil, "-", step_num, counter)
|
26
|
-
#end
|
27
|
-
|
28
|
-
repo.checkout_head(strategy: :force)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Options:
|
33
|
-
# full_diffs: If true, diffs contain the entire file in one hunk instead of
|
34
|
-
# multiple contextual hunks.
|
35
|
-
# diffs_ignore_whitespace: If true, diffs don't show changes to lines when
|
36
|
-
# only the amount of whitespace is changed.
|
37
|
-
def load!(options = {})
|
38
|
-
git_diff_options = {}
|
39
|
-
git_diff_options[:context_lines] = 100_000 if options[:full_diffs]
|
40
|
-
git_diff_options[:ignore_whitespace_change] = true if options[:diffs_ignore_whitespace]
|
41
|
-
|
42
|
-
page = nil
|
43
|
-
@tutorial.clear
|
44
|
-
each_step(git_diff_options) do |step_num, commit, summary, text, patches|
|
45
|
-
if patches.empty?
|
46
|
-
if summary =~ /^~~~ (.+)$/
|
47
|
-
@tutorial << page unless page.nil?
|
48
|
-
|
49
|
-
page = Snaptoken::Page.new($1)
|
50
|
-
page.footer_text = text unless text.empty?
|
51
|
-
else
|
52
|
-
puts "Warning: ignoring empty commit."
|
53
|
-
end
|
54
|
-
else
|
55
|
-
patch = patches.map(&:to_s).join("\n")
|
56
|
-
step_diffs = Snaptoken::Diff.parse(patch)
|
57
|
-
|
58
|
-
page ||= Snaptoken::Page.new
|
59
|
-
page << Snaptoken::Step.new(step_num, summary, text, step_diffs)
|
60
|
-
|
61
|
-
yield step_num if block_given?
|
62
|
-
end
|
63
|
-
end
|
64
|
-
@tutorial << page unless page.nil?
|
65
|
-
@tutorial
|
66
|
-
end
|
67
|
-
|
68
|
-
def copy_repo_to_step!
|
69
|
-
FileUtils.mkdir_p(step_path)
|
70
|
-
FileUtils.rm_rf(File.join(step_path, "."), secure: true)
|
71
|
-
FileUtils.cd(repo_path) do
|
72
|
-
files = Dir.glob("*", File::FNM_DOTMATCH) - [".", "..", ".git"]
|
73
|
-
files.each do |f|
|
74
|
-
FileUtils.cp_r(f, File.join(step_path, f))
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def copy_step_to_repo!
|
80
|
-
FileUtils.mv(
|
81
|
-
File.join(repo_path, ".git"),
|
82
|
-
File.join(repo_path, "../.gittemp")
|
83
|
-
)
|
84
|
-
FileUtils.rm_rf(File.join(repo_path, "."), secure: true)
|
85
|
-
FileUtils.mv(
|
86
|
-
File.join(repo_path, "../.gittemp"),
|
87
|
-
File.join(repo_path, ".git")
|
88
|
-
)
|
89
|
-
FileUtils.cd(step_path) do
|
90
|
-
files = Dir.glob("*", File::FNM_DOTMATCH) - [".", ".."]
|
91
|
-
files.each do |f|
|
92
|
-
FileUtils.cp_r(f, File.join(repo_path, f))
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def repo_path
|
98
|
-
File.join(@tutorial.config[:path], ".leg/repo")
|
99
|
-
end
|
100
|
-
|
101
|
-
def repo
|
102
|
-
@repo ||= Rugged::Repository.new(repo_path)
|
103
|
-
end
|
104
|
-
|
105
|
-
def each_commit(options = {})
|
106
|
-
walker = Rugged::Walker.new(repo)
|
107
|
-
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
|
108
|
-
|
109
|
-
master_commit = repo.branches["master"].target
|
110
|
-
walker.push(master_commit)
|
111
|
-
|
112
|
-
return [] if master_commit.oid == options[:after]
|
113
|
-
walker.hide(options[:after]) if options[:after]
|
114
|
-
|
115
|
-
return walker.to_a if not block_given?
|
116
|
-
|
117
|
-
walker.each do |commit|
|
118
|
-
yield commit
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
alias_method :commits, :each_commit
|
123
|
-
|
124
|
-
def each_step(git_diff_options = {})
|
125
|
-
empty_tree = Rugged::Tree.empty(repo)
|
126
|
-
step_num = 1
|
127
|
-
each_commit do |commit|
|
128
|
-
commit_message = commit.message.strip
|
129
|
-
summary = commit_message.lines.first.strip
|
130
|
-
text = (commit_message.lines[2..-1] || []).join.strip
|
131
|
-
next if commit_message == "-"
|
132
|
-
commit_message = "" if commit_message == "~"
|
133
|
-
last_commit = commit.parents.first
|
134
|
-
diff = (last_commit || empty_tree).diff(commit, git_diff_options)
|
135
|
-
patches = diff.each_patch.to_a
|
136
|
-
|
137
|
-
if patches.empty?
|
138
|
-
yield nil, commit, summary, text, patches
|
139
|
-
else
|
140
|
-
yield step_num, commit, summary, text, patches
|
141
|
-
step_num += 1
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def commit!(options = {})
|
147
|
-
copy_step_to_repo!
|
148
|
-
remaining_commits = commits(after: repo.head.target).map(&:oid)
|
149
|
-
FileUtils.cd(repo_path) do
|
150
|
-
`git add -A`
|
151
|
-
`git commit #{'--amend' if options[:amend]} -m"TODO: let user specify commit message"`
|
152
|
-
end
|
153
|
-
rebase!(remaining_commits)
|
154
|
-
end
|
155
|
-
|
156
|
-
def resolve!
|
157
|
-
copy_step_to_repo!
|
158
|
-
FileUtils.cd(repo_path) do
|
159
|
-
`git add -A`
|
160
|
-
`git -c core.editor=true cherry-pick --allow-empty --allow-empty-message --keep-redundant-commits --continue`
|
161
|
-
end
|
162
|
-
rebase!(load_remaining_commits)
|
163
|
-
end
|
164
|
-
|
165
|
-
def rebase!(remaining_commits)
|
166
|
-
FileUtils.cd(repo_path) do
|
167
|
-
remaining_commits.each.with_index do |commit, commit_idx|
|
168
|
-
`git cherry-pick --allow-empty --allow-empty-message --keep-redundant-commits #{commit}`
|
169
|
-
|
170
|
-
if not $?.success?
|
171
|
-
copy_repo_to_step!
|
172
|
-
save_remaining_commits(remaining_commits[(commit_idx+1)..-1])
|
173
|
-
return false
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
save_remaining_commits(nil)
|
179
|
-
|
180
|
-
repo.references.update(repo.branches["master"], repo.head.target_id)
|
181
|
-
repo.head = "refs/heads/master"
|
182
|
-
|
183
|
-
true
|
184
|
-
end
|
185
|
-
|
186
|
-
private
|
187
|
-
|
188
|
-
def step_path
|
189
|
-
File.join(@tutorial.config[:path], "step")
|
190
|
-
end
|
191
|
-
|
192
|
-
def remaining_commits_path
|
193
|
-
File.join(@tutorial.config[:path], ".leg/remaining_commits")
|
194
|
-
end
|
195
|
-
|
196
|
-
def modified_at
|
197
|
-
if File.exist? repo_path
|
198
|
-
repo = Rugged::Repository.new(repo_path)
|
199
|
-
if master = repo.branches.find { |b| b.name == "master" }
|
200
|
-
master.target.time
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def load_remaining_commits
|
206
|
-
if File.exist?(remaining_commits_path)
|
207
|
-
File.readlines(remaining_commits_path).map(&:strip).reject(&:empty?)
|
208
|
-
else
|
209
|
-
[]
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def save_remaining_commits(remaining_commits)
|
214
|
-
if remaining_commits && !remaining_commits.empty?
|
215
|
-
File.write(remaining_commits_path, remaining_commits.join("\n"))
|
216
|
-
else
|
217
|
-
FileUtils.rm_f(remaining_commits_path)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
def add_commit(repo, diff, message, step_num)
|
222
|
-
message ||= "~"
|
223
|
-
message.strip!
|
224
|
-
message = "~" if message.empty?
|
225
|
-
|
226
|
-
if diff
|
227
|
-
stdin = IO.popen("git apply -", "w")
|
228
|
-
stdin.write diff
|
229
|
-
stdin.close
|
230
|
-
end
|
231
|
-
|
232
|
-
index = repo.index
|
233
|
-
index.read_tree(repo.head.target.tree) unless repo.empty?
|
234
|
-
|
235
|
-
Dir["**/*"].each do |path|
|
236
|
-
unless File.directory?(path)
|
237
|
-
oid = repo.write(File.read(path), :blob)
|
238
|
-
index.add(path: path, oid: oid, mode: 0100644)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
options = {}
|
243
|
-
options[:tree] = index.write_tree(repo)
|
244
|
-
if @tutorial.config[:repo_author_name]
|
245
|
-
options[:author] = {
|
246
|
-
name: @tutorial.config[:repo_author_name],
|
247
|
-
email: @tutorial.config[:repo_author_email],
|
248
|
-
time: Time.now
|
249
|
-
}
|
250
|
-
options[:committer] = options[:author]
|
251
|
-
end
|
252
|
-
options[:message] = message
|
253
|
-
options[:parents] = repo.empty? ? [] : [repo.head.target]
|
254
|
-
options[:update_ref] = "HEAD"
|
255
|
-
|
256
|
-
commit_oid = Rugged::Commit.create(repo, options)
|
257
|
-
|
258
|
-
if diff
|
259
|
-
repo.references.create("refs/tags/step-#{step_num}", commit_oid)
|
260
|
-
end
|
261
|
-
end
|
262
|
-
end
|