olelo 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
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)