olelo 0.9.8 → 0.9.9

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.
Files changed (71) hide show
  1. data/.gitignore +1 -0
  2. data/Rakefile +44 -10
  3. data/config.ru +4 -1
  4. data/lib/olelo/application.rb +1 -54
  5. data/lib/olelo/helper.rb +12 -7
  6. data/lib/olelo/initializer.rb +10 -13
  7. data/lib/olelo/locale.rb +7 -12
  8. data/lib/olelo/locale.yml +1 -36
  9. data/lib/olelo/plugin.rb +0 -12
  10. data/lib/olelo/routing.rb +1 -1
  11. data/lib/olelo/templates.rb +2 -3
  12. data/lib/olelo/util.rb +1 -1
  13. data/lib/olelo/version.rb +1 -1
  14. data/{views → lib/olelo/views}/delete.slim +0 -0
  15. data/{views → lib/olelo/views}/deleted.slim +0 -0
  16. data/{views → lib/olelo/views}/edit.slim +3 -3
  17. data/{views → lib/olelo/views}/error.slim +0 -0
  18. data/{views → lib/olelo/views}/layout.slim +1 -1
  19. data/{views → lib/olelo/views}/login.slim +1 -1
  20. data/{views → lib/olelo/views}/move.slim +0 -0
  21. data/{views → lib/olelo/views}/not_found.slim +0 -0
  22. data/{views → lib/olelo/views}/profile.slim +0 -0
  23. data/lib/olelo/views/show.slim +8 -0
  24. data/lib/olelo/virtualfs.rb +5 -29
  25. data/plugins/aspects/changelog.rb +3 -5
  26. data/plugins/aspects/documentbrowser.rb +24 -2
  27. data/plugins/aspects/download.rb +12 -0
  28. data/plugins/aspects/gallery/main.rb +9 -0
  29. data/plugins/aspects/highlight.rb +11 -0
  30. data/plugins/aspects/image.rb +12 -0
  31. data/plugins/aspects/imageinfo.rb +36 -2
  32. data/plugins/aspects/main.rb +16 -1
  33. data/plugins/aspects/pageinfo.rb +20 -2
  34. data/plugins/aspects/source.rb +12 -0
  35. data/plugins/aspects/subpages.rb +17 -1
  36. data/plugins/aspects/text.rb +12 -0
  37. data/plugins/blog/main.rb +4 -4
  38. data/plugins/editor/preview.rb +16 -0
  39. data/plugins/editor/recaptcha.rb +19 -0
  40. data/plugins/filters/editsection.rb +19 -0
  41. data/plugins/filters/locale.yml +11 -16
  42. data/plugins/filters/toc.rb +12 -0
  43. data/{views → plugins/history}/changes.slim +0 -0
  44. data/{views → plugins/history}/compare.slim +2 -2
  45. data/{views → plugins/history}/history.slim +1 -1
  46. data/plugins/history/locale.yml +40 -0
  47. data/plugins/history/main.rb +67 -0
  48. data/plugins/history/script.js +2 -0
  49. data/{static/script/12-olelo.historytable.js → plugins/history/script/00-historytable.js} +0 -0
  50. data/plugins/history/script/init.js +7 -0
  51. data/plugins/login/persistent.rb +11 -0
  52. data/plugins/repositories/git_grep.rb +19 -2
  53. data/plugins/repositories/gitrb_repository.rb +8 -0
  54. data/plugins/repositories/rugged_repository.rb +10 -0
  55. data/plugins/security/acl.rb +23 -0
  56. data/plugins/security/private_wiki.rb +11 -0
  57. data/plugins/utils/assets.rb +15 -21
  58. data/plugins/utils/store.rb +40 -19
  59. data/static/{images/favicon.png → favicon.png} +0 -0
  60. data/static/script.js +3 -5
  61. data/static/script/init.js +0 -1
  62. data/static/themes/atlantis/screen.scss +3 -3
  63. data/static/themes/atlantis/style.css +1 -1
  64. data/test/templates_test.rb +3 -11
  65. metadata +47 -49
  66. data/plugins/aspects/locale.yml +0 -87
  67. data/plugins/editor/locale.yml +0 -20
  68. data/plugins/login/locale.yml +0 -8
  69. data/plugins/repositories/locale.yml +0 -16
  70. data/plugins/security/locale.yml +0 -24
  71. data/views/show.slim +0 -8
@@ -1,6 +1,18 @@
1
+ # -*- coding: utf-8 -*-
1
2
  description 'Source aspect'
2
3
 
3
4
  Aspect.create(:source, priority: 3, layout: true, cacheable: true) do
4
5
  def accepts?(page); page.mime.text?; end
5
6
  def call(context, page); "<pre>#{escape_html page.content}</pre>"; end
6
7
  end
8
+
9
+ __END__
10
+ @@ locale.yml
11
+ cs_CZ:
12
+ aspect_source: 'Zdroj stránky'
13
+ de:
14
+ aspect_source: 'Quellcode'
15
+ en:
16
+ aspect_source: 'Page Source'
17
+ fr:
18
+ aspect_source: "Source de la page"
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  description 'Subpages aspect'
2
3
 
3
4
  Aspect.create(:subpages, priority: 2, layout: true, cacheable: true) do
@@ -15,7 +16,7 @@ end
15
16
  __END__
16
17
  @@ subpages.slim
17
18
  = pagination(@page, @page_count, @page_nr, aspect: 'subpages')
18
- table#subpages-table
19
+ table#subpages
19
20
  thead
20
21
  tr
21
22
  th= :name.t
@@ -42,3 +43,18 @@ table#subpages-table
42
43
  a.action-move href=build_path(child, action: :move) title=:move.t = :move.t
43
44
  a.action-delete href=build_path(child, action: :delete) title=:delete.t = :delete.t
44
45
  = pagination(@page, @page_count, @page_nr, aspect: 'subpages')
46
+
47
+ __END__
48
+ @@ locale.yml
49
+ cs_CZ:
50
+ aspect_subpages: 'Podstránky'
51
+ author: 'Autor'
52
+ de:
53
+ aspect_subpages: 'Unterseiten'
54
+ author: 'Autor'
55
+ en:
56
+ aspect_subpages: 'Subpages'
57
+ author: 'Author'
58
+ fr:
59
+ aspect_subpages: "Sous-pages"
60
+ author: "Auteur"
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  description 'Text aspect'
2
3
 
3
4
  Aspect.create(:text, mime: 'text/plain; charset=utf-8') do
@@ -7,3 +8,14 @@ Aspect.create(:text, mime: 'text/plain; charset=utf-8') do
7
8
  page.content
8
9
  end
9
10
  end
11
+
12
+ __END__
13
+ @@ locale.yml
14
+ cs_CZ:
15
+ aspect_text: 'Stažení textu'
16
+ de:
17
+ aspect_text: 'Quellcode herunterladen'
18
+ en:
19
+ aspect_text: 'Text Download'
20
+ fr:
21
+ aspect_text: "Téléchargement en texte"
@@ -15,7 +15,7 @@ Tags::Tag.define 'menu', optional: 'path', description: 'Show blog menu', dynami
15
15
  page.children.each do |child|
16
16
  (years[child.version.date.year] ||= [])[child.version.date.month] = true
17
17
  end
18
- render :menu, locals: {years: years, page: page}
18
+ render :blog_menu, locals: {years: years, page: page}
19
19
  end
20
20
  end
21
21
  end
@@ -53,12 +53,12 @@ Aspects::Aspect.create(:blog, priority: 3, layout: true, cacheable: true, hidden
53
53
  end
54
54
  [article, content]
55
55
  end
56
- render :blog, locals: {full: context.params[:full]}
56
+ render :blog_page, locals: {full: context.params[:full]}
57
57
  end
58
58
  end
59
59
 
60
60
  __END__
61
- @@ blog.slim
61
+ @@ blog_page.slim
62
62
  - if @articles.empty?
63
63
  .error= :no_articles.t
64
64
  - else
@@ -73,7 +73,7 @@ __END__
73
73
  - if !full
74
74
  a.full href=build_path(page.path) = :full_article.t
75
75
  = pagination(@page, @page_count, @page_nr, aspect: 'blog')
76
- @@ menu.slim
76
+ @@ blog_menu.slim
77
77
  table.blog-menu
78
78
  - years.keys.sort.each do |year|
79
79
  tr
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  description 'Enhance editor form with preview and diff'
2
3
  dependencies 'aspects'
3
4
 
@@ -50,3 +51,18 @@ class ::Olelo::Application
50
51
  halt render(request.xhr? ? flash.now[:changes] : :edit)
51
52
  end
52
53
  end
54
+
55
+ __END__
56
+ @@ locale.yml
57
+ cs_CZ:
58
+ changes: 'Změny'
59
+ preview: 'Náhled'
60
+ de:
61
+ changes: 'Änderungen'
62
+ preview: Vorschau
63
+ en:
64
+ changes: Changes
65
+ preview: Preview
66
+ fr:
67
+ changes: "Changement"
68
+ preview: "Prévisualisez"
@@ -58,3 +58,22 @@ class ::Olelo::Application
58
58
  end
59
59
  end
60
60
  end
61
+
62
+ __END__
63
+ @@ locale.yml
64
+ cs_CZ:
65
+ captcha_invalid: 'Neplatný captcha kód'
66
+ captcha_valid: 'Platný captcha kód'
67
+ enter_captcha: 'Vložte laskavě captcha kód.'
68
+ de:
69
+ captcha_invalid: 'Ungültiges Captcha'
70
+ captcha_valid: 'Gültiges Captcha'
71
+ enter_captcha: 'Bitte geben Sie ein Captcha ein.'
72
+ en:
73
+ captcha_invalid: Invalid captcha
74
+ captcha_valid: Valid captcha
75
+ enter_captcha: 'Please enter the captcha.'
76
+ fr:
77
+ captcha_invalid: "Captcha invalide"
78
+ captcha_valid: "Captcha valide"
79
+ enter_captcha: "Veuillez entrer le captcha."
@@ -65,3 +65,22 @@ class EditSection < Filters::NestingFilter
65
65
  end
66
66
 
67
67
  Filters::Filter.register :editsection, EditSection
68
+
69
+ __END__
70
+ @@ locale.yml
71
+ cs_CZ:
72
+ attribute_no_editsection: 'Zablokovat editaci sekcí'
73
+ edit_section: 'Edituj sekci "%{section}"'
74
+ section_edited: 'Sekce "%{section}" editována'
75
+ de:
76
+ attribute_no_editsection: 'Bearbeiten von Bereichen deaktivieren'
77
+ edit_section: 'Bearbeite Bereich "%{section}"'
78
+ section_edited: 'Bereich "%{section}" bearbeitet'
79
+ en:
80
+ attribute_no_editsection: 'Disable Section Editing'
81
+ edit_section: 'Edit section "%{section}"'
82
+ section_edited: 'Section "%{section}" edited'
83
+ fr:
84
+ attribute_no_editsection: "Désactiver l'édition"
85
+ edit_section: "Éditer la section \"%{section}\""
86
+ section_edited: "Section \"%{section}\" éditée"
@@ -1,20 +1,15 @@
1
1
  cs_CZ:
2
- attribute_no_editsection: 'Zablokovat editaci sekcí'
3
- attribute_toc: 'Vytvořit obsah'
4
- edit_section: 'Edituj sekci "%{section}"'
5
- section_edited: 'Sekce "%{section}" editována'
2
+ aspect_latex: 'LaTeX'
3
+ aspect_page: 'Stránce'
6
4
  de:
7
- attribute_no_editsection: 'Bearbeiten von Bereichen deaktivieren'
8
- attribute_toc: 'Inhaltsverzeichnis erzeugen'
9
- edit_section: 'Bearbeite Bereich "%{section}"'
10
- section_edited: 'Bereich "%{section}" bearbeitet'
5
+ aspect_latex: 'LaTeX'
6
+ aspect_page: 'Seite'
7
+ aspect_s5: 'S5 Präsentation'
11
8
  en:
12
- attribute_no_editsection: 'Disable Section Editing'
13
- attribute_toc: 'Generate Table of Contents'
14
- edit_section: 'Edit section "%{section}"'
15
- section_edited: 'Section "%{section}" edited'
9
+ aspect_latex: 'LaTeX'
10
+ aspect_page: 'Page'
11
+ aspect_s5: 'S5 Presentation'
16
12
  fr:
17
- attribute_no_editsection: "Désactiver l'édition"
18
- attribute_toc: "Générer Table des Matières"
19
- edit_section: "Éditer la section \"%{section}\""
20
- section_edited: "Section \"%{section}\" éditée"
13
+ aspect_latex: "LaTeX"
14
+ aspect_page: "Page"
15
+ aspect_s5: "Presentation S5"
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  description 'Auto-generated table of contents'
2
3
  dependencies 'utils/xml'
3
4
 
@@ -48,3 +49,14 @@ Filter.create :toc do |context, content|
48
49
 
49
50
  toc + doc.to_xhtml
50
51
  end
52
+
53
+ __END__
54
+ @@ locale.yml
55
+ cs_CZ:
56
+ attribute_toc: 'Vytvořit obsah'
57
+ de:
58
+ attribute_toc: 'Inhaltsverzeichnis erzeugen'
59
+ en:
60
+ attribute_toc: 'Generate Table of Contents'
61
+ fr:
62
+ attribute_toc: "Générer Table des Matières"
File without changes
@@ -1,7 +1,7 @@
1
1
  - title :compare.t(name: page.title, from: @diff.from.short, to: @diff.to.short)
2
2
  = define_block :from do
3
3
  a.version href=build_path(page, version: @diff.from) = @diff.from.short
4
- = define_block :to do
4
+ = define_block :to do
5
5
  a.version href=build_path(page, version: @diff.to) = @diff.to.short
6
- h1== :compare.t(name: escape_html(page.title), from: blocks[:from], to: blocks[:to])
6
+ h1== :compare.t(name: escape_html(page.title), from: render_block(:from), to: render_block(:to))
7
7
  = format_diff(@diff)
@@ -1,7 +1,7 @@
1
1
  - title :history_of.t(page: page.title)
2
2
  h1= title
3
3
  = pagination(page, @page_count, @page_nr, action: :history, unlimited: true)
4
- table#history-table
4
+ table#history
5
5
  thead
6
6
  tr
7
7
  th= :comment.t
@@ -0,0 +1,40 @@
1
+ cs_CZ:
2
+ author: 'Autor'
3
+ changes: 'Změny'
4
+ changes_of: 'Změny stránky %{page}'
5
+ compare: 'Porovnat %{name}: %{from} → %{to}'
6
+ date: 'Datum'
7
+ history_of: 'Historie stránky %{page}'
8
+ menu_actions_history: 'Historie'
9
+ menu_actions_history_head: 'Aktuální'
10
+ menu_actions_history_newer: 'Novější'
11
+ menu_actions_history_older: 'Starší'
12
+ parents: 'Rodiče'
13
+ de:
14
+ author: 'Autor'
15
+ changes: 'Änderungen'
16
+ changes_of: 'Änderungen von %{page}'
17
+ compare: 'Vergleiche %{name}: %{from} → %{to}'
18
+ date: 'Datum'
19
+ history_of: 'Historie von %{page}'
20
+ menu_actions_history: 'Historie'
21
+ menu_actions_history_newer: 'Neuer'
22
+ menu_actions_history_older: 'Älter'
23
+ parents: 'Eltern'
24
+ en:
25
+ author: 'Author'
26
+ changes: 'Changes'
27
+ changes_of: 'Changes of %{page}'
28
+ compare: 'Compare %{name}: %{from} → %{to}'
29
+ date: 'Date'
30
+ history_of: 'History of %{page}'
31
+ parents: 'Parents'
32
+ fr:
33
+ author: "Auteur"
34
+ changes: "Changement"
35
+ changes_of: "Chamgements de %{page}"
36
+ compare: "Comparer %{name}: %{from} → %{to}"
37
+ date: "Date"
38
+ history_of: "Historique de %{page}"
39
+ menu_actions_history_newer: "Plus récent"
40
+ parents: "Parents"
@@ -0,0 +1,67 @@
1
+ description 'History menu'
2
+ dependencies 'utils/assets'
3
+ export_scripts '*.js'
4
+
5
+ class ::Olelo::Application
6
+ get '/compare/:versions(/:path)', versions: '(?:\w+)\.{2,3}(?:\w+)' do
7
+ @page = Page.find!(params[:path])
8
+ versions = params[:versions].split(/\.{2,3}/)
9
+ begin
10
+ @diff = page.diff(versions.first, versions.last)
11
+ rescue => ex
12
+ Olelo.logger.debug ex
13
+ raise NotFound
14
+ end
15
+ render :compare
16
+ end
17
+
18
+ get '/compare(/:path)' do
19
+ versions = params[:versions] || []
20
+ redirect build_path(params[:path], action: versions.size < 2 ? :history : "compare/#{versions.first}...#{versions.last}")
21
+ end
22
+
23
+ get '/changes/:version(/:path)' do
24
+ @page = Page.find!(params[:path])
25
+ begin
26
+ @diff = page.diff(nil, params[:version])
27
+ rescue => ex
28
+ Olelo.logger.debug ex
29
+ raise NotFound
30
+ end
31
+ @version = @diff.to
32
+ cache_control etag: @version.to_s
33
+ render :changes
34
+ end
35
+
36
+ get '/history(/:path)' do
37
+ per_page = 30
38
+ @page = Page.find!(params[:path])
39
+ @page_nr = [params[:page].to_i, 1].max
40
+ @history = page.history((@page_nr - 1) * per_page, per_page + 1)
41
+ @page_count = @page_nr + (@history.length > per_page ? 1 : 0)
42
+ @history = @history[0...per_page]
43
+ cache_control etag: page.etag
44
+ render :history
45
+ end
46
+
47
+ before :action do |method, path|
48
+ @history_versions_menu = method == :get && (path == '/version/:version(/:path)' || path == '/(:path)')
49
+ end
50
+
51
+ hook :menu do |menu|
52
+ if menu.name == :actions && page && !page.new?
53
+ history_menu = menu.item(:history, href: build_path(page, action: :history), accesskey: 'h')
54
+
55
+ if @history_versions_menu
56
+ head = !page.head? && (Olelo::Page.find(page.path) rescue nil)
57
+ if page.previous_version || head || page.next_version
58
+ history_menu.item(:older, href: build_path(page, original_params.merge(version: page.previous_version)),
59
+ accesskey: 'o') if page.previous_version
60
+ history_menu.item(:head, href: build_path(page.path, original_params), accesskey: 'c') if head
61
+ history_menu.item(:newer, href: build_path(page, original_params.merge(version: page.next_version)),
62
+ accesskey: 'n') if page.next_version
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,2 @@
1
+ (function(a){a.fn.historyTable=function(){function f(){var b=[];g.each(function(){this.checked&&b.push(this.name)});return b}a("thead tr",this).prepend('<th class="compare"><button>&#177;</button></th>');a("tbody tr",this).each(function(){var b=a(this).attr("id").substr(8);a(this).prepend('<td class="compare"><input type="checkbox" name="'+b+'"/></td>')});var c=a.storage.get("historyTable");if(c)for(var d=0;d<c.length;++d)a("input[name="+c[d]+"]").attr("checked","checked");var g=a("tbody input",this),
2
+ e=a("th button",this);e.click(function(){var b=f();a.storage.set("historyTable",b);location.href=location.pathname.replace("/history","/compare/"+b[b.length-1]+"..."+b[0])});a("td input",this).change(function(){f().length>1?e.removeAttr("disabled"):e.attr("disabled","disabled")}).change()}})(jQuery);$(function(){$("#content").bind("pageLoaded",function(){$("#history",this).historyTable()});$("#history").historyTable()});
@@ -0,0 +1,7 @@
1
+ $(function() {
2
+ "use strict";
3
+ $('#content').bind('pageLoaded', function pageLoaded() {
4
+ $('#history', this).historyTable();
5
+ });
6
+ $('#history').historyTable();
7
+ });
@@ -30,3 +30,14 @@ class ::Olelo::Application
30
30
  end
31
31
  end
32
32
  end
33
+
34
+ __END__
35
+ @@ locale.yml
36
+ cs_CZ:
37
+ persistent_login: 'Trvalé přihlášení'
38
+ de:
39
+ persistent_login: 'Dauerhaft anmelden'
40
+ en:
41
+ persistent_login: 'Persistent login'
42
+ fr:
43
+ persistent_login: 'Connexion persistente'
@@ -46,7 +46,7 @@ class ::Olelo::Application
46
46
  a[1].length == b[1].length ? a[0] <=> b[0] : b[1].length <=> a[1].length
47
47
  end.map {|path, content| [path, content.join] }
48
48
 
49
- render :grep
49
+ render :git_grep
50
50
  end
51
51
 
52
52
  private
@@ -57,7 +57,7 @@ class ::Olelo::Application
57
57
  end
58
58
 
59
59
  __END__
60
- @@ grep.slim
60
+ @@ git_grep.slim
61
61
  - title :search_results.t(pattern: params[:pattern])
62
62
  h1= title
63
63
  p= :match.t(count: @matches.length)
@@ -67,3 +67,20 @@ p= :match.t(count: @matches.length)
67
67
  h2
68
68
  a.name href=build_path(path) = emphasize(path)
69
69
  .content= emphasize(content)
70
+ @@ locale.yml
71
+ cs_CZ:
72
+ match: 'Jeden výsledek'
73
+ match_plural: '%{count} výsledků'
74
+ search_results: 'Výsledky hledání pro %{pattern}'
75
+ de:
76
+ match: 'Ein Treffer'
77
+ match_plural: '%{count} Treffer'
78
+ search_results: 'Suchergebnisse für %{pattern}'
79
+ en:
80
+ match: 'One match'
81
+ match_plural: '%{count} matches'
82
+ search_results: 'Search results for %{pattern}'
83
+ fr:
84
+ match: "Une correspondance"
85
+ match_plural: "%{count} correspondaces"
86
+ search_results: "Chercher les résultats pour %{pattern}"
@@ -40,6 +40,7 @@ class GitrbRepository < Repository
40
40
 
41
41
  # @override
42
42
  def get_history(path, skip, limit)
43
+ check_path(path)
43
44
  @git.log(max_count: limit, skip: skip,
44
45
  path: [path, path + ATTRIBUTE_EXT, path + CONTENT_EXT]).map do |c|
45
46
  commit_to_version(c)
@@ -48,6 +49,8 @@ class GitrbRepository < Repository
48
49
 
49
50
  # @override
50
51
  def get_path_version(path, version)
52
+ check_path(path)
53
+
51
54
  commits = @git.log(max_count: 2, start: version, path: [path, path + ATTRIBUTE_EXT, path + CONTENT_EXT])
52
55
 
53
56
  succ = nil
@@ -65,12 +68,14 @@ class GitrbRepository < Repository
65
68
 
66
69
  # @override
67
70
  def get_children(path, version)
71
+ check_path(path)
68
72
  object = get_object(path, version)
69
73
  object && object.type != :tree ? [] : object.names.reject {|name| reserved_name?(name) }
70
74
  end
71
75
 
72
76
  # @override
73
77
  def get_content(path, version)
78
+ check_path(path)
74
79
  tree = get_commit(version).tree
75
80
  object = tree[path]
76
81
  object = tree[path + CONTENT_EXT] if object && object.type == :tree
@@ -85,6 +90,7 @@ class GitrbRepository < Repository
85
90
 
86
91
  # @override
87
92
  def get_attributes(path, version)
93
+ check_path(path)
88
94
  object = get_object(path + ATTRIBUTE_EXT, version)
89
95
  object && object.type == :blob ? YAML.load(object.data) : {}
90
96
  end
@@ -129,6 +135,7 @@ class GitrbRepository < Repository
129
135
 
130
136
  # @override
131
137
  def delete(path)
138
+ check_path(path)
132
139
  @git.root.delete(path)
133
140
  @git.root.delete(path + CONTENT_EXT)
134
141
  @git.root.delete(path + ATTRIBUTE_EXT)
@@ -137,6 +144,7 @@ class GitrbRepository < Repository
137
144
 
138
145
  # @override
139
146
  def diff(path, from, to)
147
+ check_path(path)
140
148
  diff = @git.diff(from: from && from.to_s, to: to.to_s,
141
149
  path: [path, path + CONTENT_EXT, path + ATTRIBUTE_EXT], detect_renames: true)
142
150
  Diff.new(commit_to_version(diff.from), commit_to_version(diff.to), diff.patch)