tekkub-fugit 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :minor: 0
3
- :patch: 4
3
+ :patch: 5
4
4
  :major: 0
data/fugit.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{fugit}
3
- s.version = "0.0.4"
3
+ s.version = "0.0.5"
4
4
 
5
5
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
6
  s.authors = ["Tekkub"]
7
- s.date = %q{2009-02-18}
7
+ s.date = %q{2009-02-27}
8
8
  s.default_executable = %q{fugit}
9
9
  s.description = %q{A cross-platform replacement for git-gui based on wxruby}
10
10
  s.email = %q{tekkub@gmail.com}
11
11
  s.executables = ["fugit"]
12
- s.files = ["fugit.gemspec", "SciTE.properties", "VERSION.yml", "bin/fugit", "lib/fugit", "lib/fugit/commit.rb", "lib/fugit/commit_tab.rb", "lib/fugit/console.rb", "lib/fugit/diff.rb", "lib/fugit/graph_renderer.rb", "lib/fugit/history_list.rb", "lib/fugit/history_tab.rb", "lib/fugit/icon_loader.rb", "lib/fugit/index_list.rb", "lib/fugit/io_get_line.rb", "lib/fugit/main_frame.rb", "lib/fugit/messages.rb", "lib/fugit/push_dialog.rb", "lib/fugit/SciTE.properties", "lib/fugit.rb", "lib/icons", "lib/icons/asterisk_yellow.png", "lib/icons/disk.png", "lib/icons/folder_add.png", "lib/icons/folder_delete.png", "lib/icons/page_add.png", "lib/icons/page_delete.png", "lib/icons/page_down.gif", "lib/icons/page_up.gif", "lib/icons/plus_minus.gif", "lib/icons/script.png", "lib/icons/script_add.png", "lib/icons/script_delete.png", "lib/icons/script_edit.png", "lib/icons/text_signature.png", "lib/icons/tick.png"]
12
+ s.files = ["fugit.gemspec", "SciTE.properties", "VERSION.yml", "bin/fugit", "lib/fugit", "lib/fugit/commit_dialog.rb", "lib/fugit/commit_tab.rb", "lib/fugit/commit_tab_toolbar.rb", "lib/fugit/console.rb", "lib/fugit/delete_branch_dialog.rb", "lib/fugit/diff.rb", "lib/fugit/graph_renderer.rb", "lib/fugit/history_list.rb", "lib/fugit/history_tab.rb", "lib/fugit/icon_loader.rb", "lib/fugit/index_list.rb", "lib/fugit/io_get_line.rb", "lib/fugit/main_frame.rb", "lib/fugit/messages.rb", "lib/fugit/push_dialog.rb", "lib/fugit/SciTE.properties", "lib/fugit.rb", "lib/icons", "lib/icons/arrow_divide.png", "lib/icons/arrow_divide_delete.png", "lib/icons/arrow_join.png", "lib/icons/arrow_undo.png", "lib/icons/asterisk_yellow.png", "lib/icons/cherry.png", "lib/icons/disk.png", "lib/icons/folder_add.png", "lib/icons/folder_delete.png", "lib/icons/page_add.png", "lib/icons/page_delete.png", "lib/icons/page_down.gif", "lib/icons/page_up.gif", "lib/icons/plus_minus.gif", "lib/icons/script.png", "lib/icons/script_add.png", "lib/icons/script_delete.png", "lib/icons/script_edit.png", "lib/icons/text_signature.png", "lib/icons/tick.png"]
13
13
  s.has_rdoc = true
14
14
  s.homepage = %q{http://github.com/tekkub/fugit}
15
15
  s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
data/lib/fugit.rb CHANGED
@@ -3,18 +3,21 @@ $:.unshift File.dirname(__FILE__)
3
3
 
4
4
  require 'wx'
5
5
 
6
- require "fugit/io_get_line"
7
- require "fugit/messages"
8
6
  require "fugit/icon_loader"
9
- require "fugit/main_frame"
7
+
8
+ require "fugit/commit_dialog"
10
9
  require "fugit/commit_tab"
11
- require "fugit/commit"
10
+ require "fugit/commit_tab_toolbar"
12
11
  require "fugit/console"
12
+ require "fugit/delete_branch_dialog"
13
13
  require "fugit/diff"
14
- require "fugit/index_list"
15
- require "fugit/history_tab"
16
14
  require "fugit/graph_renderer"
17
15
  require "fugit/history_list"
16
+ require "fugit/history_tab"
17
+ require "fugit/index_list"
18
+ require "fugit/io_get_line"
19
+ require "fugit/main_frame"
20
+ require "fugit/messages"
18
21
  require "fugit/push_dialog"
19
22
 
20
23
 
@@ -2,58 +2,66 @@ include Wx
2
2
  include IconLoader
3
3
 
4
4
  module Fugit
5
- class Commit < Panel
5
+ class CommitDialog < Dialog
6
6
  def initialize(parent)
7
- super(parent, ID_ANY)
7
+ super(parent, ID_ANY, "Commit changes", :size => Size.new(500, 250))
8
8
 
9
9
  @input = TextCtrl.new(self, ID_ANY, nil, nil, nil, TE_MULTILINE|TE_DONTWRAP)
10
+ @amend_check = CheckBox.new(self, ID_ANY)
11
+ @amend_check.set_label("&Amend previous commit")
10
12
  @author = TextCtrl.new(self, ID_ANY)
11
13
  @committer = TextCtrl.new(self, ID_ANY)
12
14
  @committer.disable
13
15
 
14
- @toolbar = ToolBar.new(self, ID_ANY)
15
- @toolbar.set_tool_bitmap_size(Size.new(16,16))
16
- @toolbar.add_tool(101, "Commit", get_icon("disk.png"), "Commit")
17
- @toolbar.add_tool(102, "Sign off", get_icon("text_signature.png"), "Sign off")
18
- @toolbar.add_separator
19
- @toolbar.add_tool(103, "Push", get_icon("page_up.gif"), "Push")
20
- #~ @toolbar.add_tool(104, "Pull", get_icon("page_down.gif"), "Pull")
21
- @toolbar.realize
22
-
23
- box = BoxSizer.new(HORIZONTAL)
24
- box.add(@committer, 1, EXPAND)
25
- box.add(@author, 1, EXPAND)
26
-
27
- flex = FlexGridSizer.new(2,2,0,0)
28
- flex.add(StaticText.new(self, ID_ANY, "Committer/Author:"), 0, EXPAND)
29
- flex.add(box, 0, EXPAND)
30
- flex.add(StaticText.new(self, ID_ANY, "Commit message:"), 0, EXPAND)
16
+ evt_checkbox(@amend_check, :on_amend_checked)
17
+
18
+ flex = FlexGridSizer.new(4,2,4,4)
19
+ flex.add(StaticText.new(self, ID_ANY, "Committer:"), 0, ALIGN_RIGHT)
20
+ flex.add(@committer, 0, EXPAND)
21
+ flex.add(StaticText.new(self, ID_ANY, "Author:"), 0, ALIGN_RIGHT)
22
+ flex.add(@author, 0, EXPAND)
23
+ flex.add(StaticText.new(self, ID_ANY, "Commit message:"), 0, ALIGN_RIGHT)
31
24
  flex.add(@input, 0, EXPAND)
32
- flex.add_growable_row(1)
25
+ flex.add(1,1) # Filler
26
+ flex.add(@amend_check, 0)
27
+ flex.add_growable_row(2)
33
28
  flex.add_growable_col(1)
34
29
 
30
+ butt_sizer = create_button_sizer(OK|CANCEL)
31
+ butt_sizer.get_children.map {|s| s.get_window}.compact.each {|b| b.set_label("Commit") if b.get_label == "OK"}
32
+ evt_button(get_affirmative_id, :on_ok)
33
+
35
34
  box = BoxSizer.new(VERTICAL)
36
- box.add(@toolbar, 0, EXPAND)
37
- box.add_spacer(4)
38
- box.add(flex, 1, EXPAND)
35
+ box.add(flex, 1, EXPAND|ALL, 4)
36
+ box.add(butt_sizer, 0, EXPAND|BOTTOM, 4)
39
37
  self.set_sizer(box)
40
38
 
41
- evt_tool(101, :on_commit_clicked)
42
- evt_tool(103, :on_push_clicked)
43
-
44
- register_for_message(:save_clicked, :on_commit_clicked)
45
- register_for_message(:commit_saved, :on_commit_saved)
46
- register_for_message(:refresh, :update)
39
+ self.accelerator_table = AcceleratorTable.new(AcceleratorEntry.new(MOD_CMD, ?s, ID_OK))
40
+ end
47
41
 
42
+ def show_modal
48
43
  name = `git config user.name`
49
44
  email = `git config user.email`
50
45
  @committer.set_value("#{name.chomp} <#{email.chomp}>")
51
46
  @author.set_value("#{name.chomp} <#{email.chomp}>")
47
+ @input.set_value("")
48
+ @amend_check.set_value(false)
49
+ @input.set_focus
50
+
51
+ super
52
+ end
53
+
54
+ def on_amend_checked(event)
55
+ return unless event.is_checked && @input.get_value.empty?
56
+
57
+ raw_log = `git log -1 --pretty=raw`
58
+ @author.set_value($1) if raw_log =~ /author (.+>)/
59
+ @input.set_value($1.split("\n").map {|l| l.strip}.join("\n")) if raw_log =~ /\n\n (.+)\n\Z/m
52
60
  end
53
61
 
54
- def on_commit_clicked
62
+ def on_ok
55
63
  msg = @input.get_value
56
- if !has_staged_changes?
64
+ if !has_staged_changes? && !@amend_check.is_checked
57
65
  @nothing_to_commit_error ||= MessageDialog.new(self, "No changes are staged to commit.", "Commit error", OK|ICON_ERROR)
58
66
  @nothing_to_commit_error.show_modal
59
67
  elsif msg.empty?
@@ -62,19 +70,13 @@ module Fugit
62
70
  else
63
71
  commit_file = File.join(Dir.pwd, ".git", "fugit_commit.txt")
64
72
  File.open(commit_file, "w") {|f| f << msg}
65
- `git commit --file=.git/fugit_commit.txt --author="#{@author.get_value}"`
73
+ amend = @amend_check.is_checked ? "--amend " : ""
74
+ `git commit #{amend}--file=.git/fugit_commit.txt --author="#{@author.get_value}"`
66
75
  File.delete(commit_file)
67
- send_message(:commit_saved)
76
+ end_modal ID_OK
68
77
  end
69
78
  end
70
79
 
71
- def on_commit_saved
72
- name = `git config user.name`
73
- email = `git config user.email`
74
- @author.set_value("#{name.chomp} <#{email.chomp}>")
75
- @input.set_value("")
76
- end
77
-
78
80
  def has_staged_changes?
79
81
  staged = `git ls-files --stage`
80
82
  last_commit = `git ls-tree -r HEAD`
@@ -95,10 +97,5 @@ module Fugit
95
97
  !staged.empty?
96
98
  end
97
99
 
98
- def on_push_clicked
99
- @push_dialog ||= PushDialog .new(self)
100
- @push_dialog.show
101
- end
102
-
103
100
  end
104
101
  end
@@ -9,18 +9,16 @@ module Fugit
9
9
  @vsplitter = SplitterWindow.new(self, ID_ANY)
10
10
  @vsplitter.set_minimum_pane_size(150)
11
11
 
12
- @hsplitter = SplitterWindow.new(@vsplitter, ID_ANY)
13
- @hsplitter.set_minimum_pane_size(150)
14
- @hsplitter.set_sash_gravity(1.0)
15
12
 
16
- @diff = Diff.new(@hsplitter)
17
- @commit = Commit.new(@hsplitter)
18
- @hsplitter.split_horizontally(@diff, @commit)
13
+ @toolbar = CommitTabToolbar.new(self)
14
+ @diff = Diff.new(@vsplitter)
19
15
 
20
16
  @index = IndexList.new(@vsplitter)
21
- @vsplitter.split_vertically(@index, @hsplitter, 200)
17
+ @vsplitter.split_vertically(@index, @diff, 200)
22
18
 
23
19
  box = BoxSizer.new(VERTICAL)
20
+ box.add(@toolbar, 0, EXPAND)
21
+ box.add_spacer(3)
24
22
  box.add(@vsplitter, 1, EXPAND)
25
23
  self.set_sizer(box)
26
24
 
@@ -0,0 +1,111 @@
1
+ include Wx
2
+ include IconLoader
3
+
4
+ module Fugit
5
+ class CommitTabToolbar < ToolBar
6
+ def initialize(parent)
7
+ super(parent, ID_ANY, nil, nil, TB_HORIZONTAL|NO_BORDER|TB_NODIVIDER)
8
+
9
+ self.set_tool_bitmap_size(Size.new(16,16))
10
+
11
+ stage_all_button = self.add_tool(ID_ANY, "Stage all", get_icon("folder_add.png"), "Stage all")
12
+ evt_tool(stage_all_button, :on_stage_all_clicked)
13
+
14
+ stage_button = self.add_tool(ID_ANY, "Stage", get_icon("page_add.png"), "Stage changed files")
15
+ evt_tool(stage_button, :on_stage_changed_clicked)
16
+
17
+ unstage_all_button = self.add_tool(ID_ANY, "Unstage all", get_icon("folder_delete.png"), "Unstage all")
18
+ evt_tool(unstage_all_button, :on_unstage_all_clicked)
19
+
20
+ self.add_separator
21
+
22
+ commit = self.add_tool(ID_ANY, "Commit", get_icon("disk.png"), "Commit")
23
+ evt_tool(commit, :on_commit_clicked)
24
+
25
+ self.add_separator
26
+
27
+ push = self.add_tool(ID_ANY, "Push", get_icon("page_up.gif"), "Push")
28
+ evt_tool(push, :on_push_clicked)
29
+
30
+ pull = self.add_tool(ID_ANY, "Pull", get_icon("page_down.gif"), "Pull")
31
+ self.enable_tool(pull.get_id, false)
32
+
33
+ self.add_separator
34
+
35
+ self.add_control(@branch = Choice.new(self, ID_ANY))
36
+ set_branches
37
+ evt_choice(@branch, :on_branch_choice)
38
+
39
+ merge_branch_button = self.add_tool(ID_ANY, "Merge branch", get_icon("arrow_join.png"), "Merge branch")
40
+ self.enable_tool(merge_branch_button.get_id, false)
41
+
42
+ delete_branch_button = self.add_tool(ID_ANY, "Delete branch", get_icon("arrow_divide_delete.png"), "Delete branch")
43
+ evt_tool(delete_branch_button, :on_delete_branch_clicked)
44
+
45
+ self.realize
46
+
47
+ register_for_message(:tab_switch, :update_tools)
48
+ register_for_message(:branch_deleted, :update_tools)
49
+ register_for_message(:refresh, :update_tools)
50
+ register_for_message(:save_clicked, :on_commit_clicked)
51
+ register_for_message(:push_clicked, :on_push_clicked)
52
+ end
53
+
54
+ def update_tools
55
+ return unless is_shown_on_screen
56
+ set_branches
57
+ end
58
+
59
+ def set_branches
60
+ branches = `git branch`
61
+ current = branches.match(/\* (.+)/).to_a.last
62
+ @branch.clear
63
+ branches.split("\n").each {|b| @branch.append(b.split(" ").last)}
64
+ @branch.set_string_selection(current) if current
65
+ end
66
+
67
+ def on_stage_all_clicked(event)
68
+ `git add --all 2>&1`
69
+ send_message(:index_changed)
70
+ end
71
+
72
+ def on_stage_changed_clicked(event)
73
+ `git add --update 2>&1`
74
+ send_message(:index_changed)
75
+ end
76
+
77
+ def on_unstage_all_clicked(event)
78
+ `git reset 2>&1`
79
+ send_message(:index_changed)
80
+ end
81
+
82
+ def on_commit_clicked
83
+ @commit_dialog ||= CommitDialog.new(self)
84
+ send_message(:commit_saved) if @commit_dialog.show_modal == ID_OK
85
+ end
86
+
87
+ def on_push_clicked
88
+ @push_dialog ||= PushDialog.new(self)
89
+ @push_dialog.show
90
+ end
91
+
92
+ def on_branch_choice(event)
93
+ branch = @branch.get_string(event.get_selection)
94
+ err = `git checkout #{branch} 2>&1`
95
+ if err =~ /Switched to branch "#{branch}"/
96
+ send_message(:branch_checkout)
97
+ else
98
+ MessageDialog.new(self, err, "Branch checkout error", OK|ICON_ERROR).show_modal
99
+ branches = `git branch`
100
+ current = branches.match(/\* (.+)/).to_a.last
101
+ @branch.set_string_selection(current) if current
102
+ end
103
+ end
104
+
105
+ def on_delete_branch_clicked
106
+ @delete_branch_dialog ||= DeleteBranchDialog.new(self)
107
+ @delete_branch_dialog.show
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,48 @@
1
+ include Wx
2
+
3
+ module Fugit
4
+ class DeleteBranchDialog < Dialog
5
+ def initialize(parent)
6
+ super(parent, ID_ANY, "Delete branches", :size => Size.new(250, 300))
7
+
8
+ @branch_list = CheckListBox.new(self, ID_ANY)
9
+
10
+ butt_sizer = create_button_sizer(OK|CANCEL)
11
+ butt_sizer.get_children.map {|s| s.get_window}.compact.each {|b| b.set_label("Delete") if b.get_label == "OK"}
12
+ evt_button(get_affirmative_id, :on_ok)
13
+
14
+ box = BoxSizer.new(VERTICAL)
15
+ box.add(StaticText.new(self, ID_ANY, "Select branches:"), 0, EXPAND|ALL, 4)
16
+ box.add(@branch_list, 1, EXPAND|LEFT|RIGHT|BOTTOM, 4)
17
+ box.add(butt_sizer, 0, EXPAND|BOTTOM, 4)
18
+
19
+ self.set_sizer(box)
20
+ end
21
+
22
+ def show
23
+ branches = `git branch`
24
+ branches = branches.split("\n").reject {|b| b[0..0] == "*"}.map {|b| b.strip}
25
+ @branch_list.set(branches)
26
+
27
+ super
28
+ end
29
+
30
+ def on_ok
31
+ unmerged = `git branch --no-merged`
32
+ unmerged.split("\n").map {|b| b.strip}
33
+
34
+ branches = @branch_list.get_checked_items.map {|i| @branch_list.get_string(i)}
35
+ unless (unmerged_to_delete = branches.reject {|b| !unmerged.include?(b)}).empty?
36
+ dialog = MessageDialog.new(self, "These branches are not merged into the current HEAD:\n #{unmerged_to_delete.join("\n ")}\n\nDeleting them may cause data loss, continue?",
37
+ "Unmerged branches", YES_NO|NO_DEFAULT|ICON_EXCLAMATION)
38
+ return if dialog.show_modal != ID_YES
39
+ end
40
+ `git branch -D #{branches.join(" ")} 2>&1`
41
+
42
+ send_message(:branch_deleted)
43
+
44
+ end_modal(ID_OK)
45
+ end
46
+
47
+ end
48
+ end
data/lib/fugit/diff.rb CHANGED
@@ -3,11 +3,10 @@ include Wx
3
3
  module Fugit
4
4
  class Diff < Panel
5
5
  def initialize(parent)
6
- super(parent, ID_ANY)
6
+ super(parent, ID_ANY, :style => SIMPLE_BORDER)
7
7
  self.set_font(Font.new(8, FONTFAMILY_TELETYPE, FONTSTYLE_NORMAL, FONTWEIGHT_NORMAL))
8
8
 
9
- @list = TreeCtrl.new(self, ID_ANY, nil, nil, NO_BORDER|TR_MULTIPLE|TR_HIDE_ROOT|TR_FULL_ROW_HIGHLIGHT|TR_NO_LINES)
10
- @root = @list.add_root("root")
9
+ @list = ListCtrl.new(self, ID_ANY, :style => LC_REPORT|LC_VRULES|NO_BORDER|LC_NO_HEADER)
11
10
  @list.hide
12
11
 
13
12
  @list_menu = Menu.new
@@ -25,8 +24,8 @@ module Fugit
25
24
  self.set_sizer(@box)
26
25
 
27
26
  #~ evt_tree_sel_changed(@list.get_id, :on_click)
28
- evt_tree_item_menu(@list.get_id, :on_item_menu_request)
29
- evt_tree_item_activated(@list.get_id, :on_double_click)
27
+ evt_list_item_right_click(@list.get_id, :on_item_menu_request)
28
+ evt_list_item_activated(@list.get_id, :on_double_click)
30
29
 
31
30
  register_for_message(:commit_saved, :clear)
32
31
  register_for_message(:diff_clear, :clear)
@@ -43,8 +42,11 @@ module Fugit
43
42
  @text.hide
44
43
  @list.hide
45
44
 
46
- @list.delete_children(@root)
45
+ @list.clear_all
46
+ @list.insert_column(0, "Graph")
47
47
 
48
+ reverse_diff = type == :staged ? ["-", "\\+"] : ["+", "-"]
49
+ last_id = -1
48
50
  chunks.each do |chunk|
49
51
  chunk_diff = header + "\n" + chunk
50
52
  chunk_diff += "\n" if chunk_diff[-1..-1] != "\n" # git bitches if we don't have a proper newline at the end of the diff
@@ -57,16 +59,14 @@ module Fugit
57
59
  diff_val = chunk_lines.first.match(/\A@@ -\d+,(\d+)/)[1].to_i + (line[0..0] == "+" ? 1 : -1)
58
60
  chunk_lines[0] = chunk_lines.first.gsub(/\+(\d+),\d+/, '+\1,' + diff_val.to_s)
59
61
  chunk_lines.delete_at(i)
60
- chunk_lines.map! {|l| l.gsub(/\A-/, " ")}
62
+ chunk_lines.map! {|l| l[0..0] == reverse_diff[0] ? "#{line}~~~DELETE~~~" : l.gsub(/\A#{reverse_diff[1]}/, " ")}
61
63
  chunk_lines.insert(i, line)
62
- chunk_lines.reject! {|l| l[0..0] == "+" && l != line}
64
+ chunk_lines.reject! {|l| l == "#{line}~~~DELETE~~~"}
63
65
  header + "\n" + chunk_lines.join("\n") + "\n"
64
66
  else
65
67
  ""
66
68
  end
67
69
 
68
- id = @list.append_item(@root, line.gsub("\t", " "), -1, -1, [chunk_diff, line_diff, type])
69
-
70
70
  color = case line[0..0]
71
71
  when "+"
72
72
  Colour.new(0, 96, 0)
@@ -83,11 +83,22 @@ module Fugit
83
83
  when "@"
84
84
  Colour.new(220, 220, 225)
85
85
  end
86
- @list.set_item_text_colour(id, color) if color
87
- @list.set_item_background_colour(id, bgcolor) if bgcolor
86
+
87
+ item = ListItem.new
88
+ item.set_id(last_id += 1)
89
+ item.set_column(0)
90
+ item.set_data([chunk_diff, line_diff, type])
91
+ item.set_text(line.gsub("\t", " "))
92
+ item.set_text_colour(color) if color
93
+ item.set_background_colour(bgcolor) if bgcolor
94
+
95
+ @list.insert_item(item)
88
96
  end
89
97
  end
90
98
 
99
+ @list.set_column_width(0, -1)
100
+ @list.set_column_width(0, [@list.get_column_width(0), self.size.width].max)
101
+
91
102
  @list.show
92
103
  @list.set_focus
93
104
  @box.layout
@@ -107,15 +118,11 @@ module Fugit
107
118
  end
108
119
 
109
120
  def on_item_menu_request(event)
110
- i = event.get_item
111
- @menu_data = nil
112
- unless @root == i
113
- @menu_data = @list.get_item_data(i)
114
- @list_menu.set_label(@menu_stage_chunk.get_id, (@menu_data[2] == :staged ? "Unstage chunk" : "Stage chunk"))
115
- @list_menu.set_label(@menu_stage_line.get_id, (@menu_data[2] == :staged ? "Unstage line" : "Stage line"))
116
- @menu_stage_line.enable(!@menu_data[1].empty?)
117
- @list.popup_menu(@list_menu)
118
- end
121
+ @menu_data = event.get_item.get_data
122
+ @list_menu.set_label(@menu_stage_chunk.get_id, (@menu_data[2] == :staged ? "Unstage chunk" : "Stage chunk"))
123
+ @list_menu.set_label(@menu_stage_line.get_id, (@menu_data[2] == :staged ? "Unstage line" : "Stage line"))
124
+ @menu_stage_line.enable(!@menu_data[1].empty?)
125
+ @list.popup_menu(@list_menu)
119
126
  end
120
127
 
121
128
  def on_menu_stage_chunk(event)
@@ -127,11 +134,8 @@ module Fugit
127
134
  end
128
135
 
129
136
  def on_double_click(event)
130
- i = event.get_item
131
- unless @root == i
132
- menu_data = @list.get_item_data(i)
133
- apply_diff(menu_data[1], menu_data[2]) if menu_data
134
- end
137
+ menu_data = event.get_item.get_data
138
+ apply_diff(menu_data[1], menu_data[2]) if menu_data
135
139
  end
136
140
 
137
141
  def apply_diff(diff, type)
@@ -139,8 +143,12 @@ module Fugit
139
143
  reverse = (type == :staged ? "--reverse" : "")
140
144
  diff_file = File.join(Dir.pwd, ".git", "fugit_partial.diff")
141
145
  File.open(diff_file, "wb") {|f| f << diff} # Write out in binary mode to preserve newlines, otherwise git freaks out
142
- `git apply --cached #{reverse} .git/fugit_partial.diff`
143
- send_message(:index_changed)
146
+ err = `git apply --cached #{reverse} .git/fugit_partial.diff 2>&1`
147
+ if err.empty?
148
+ send_message(:index_changed)
149
+ else
150
+ MessageDialog.new(self, err, "Error applying diff", OK|ICON_ERROR).show_modal
151
+ end
144
152
  end
145
153
  end
146
154
  end
@@ -2,7 +2,9 @@
2
2
 
3
3
  module Fugit
4
4
  module GraphRenderer
5
- def graphify(commits)
5
+ def graphify(commits, branch_refs)
6
+ branch_refs = branch_refs.map {|b| b[1]}.uniq
7
+
6
8
  graph = []
7
9
  branch_parents = [commits.first[0]]
8
10
  commits.each do |sha, parents, comment|
@@ -14,7 +16,8 @@ module Fugit
14
16
  indicator = "│"
15
17
  indicator = " " if parent.empty?
16
18
  indicator = "┼" if parent_found && children.size > 1 && !parent.empty?
17
- indicator = "" if sha == parent && !parent_found
19
+ indicator = "" if sha == parent && !parent_found
20
+ indicator = "□" if sha == "uncomitted"
18
21
  indicator = "┘" if sha == parent && parent_found
19
22
  children.shift if sha == parent && parent_found
20
23
  parent_found = true if sha == parent
@@ -35,7 +38,7 @@ module Fugit
35
38
  branch_parents << p
36
39
  branches << "┐" if sha_index
37
40
  end
38
- branches << "" unless sha_index
41
+ branches << "" unless sha_index
39
42
  if parents.empty?
40
43
  found = false
41
44
  branchoffs = branches.select{|b| b == "┘"}
@@ -49,7 +52,7 @@ module Fugit
49
52
  else
50
53
  b
51
54
  end
52
- found ||= b == ""
55
+ found ||= b == ""
53
56
  val
54
57
  end
55
58
  branches.reverse!
@@ -73,7 +76,7 @@ module Fugit
73
76
  else
74
77
  b
75
78
  end
76
- found = b == "" if !found
79
+ found = b == "" if !found
77
80
  val
78
81
  end
79
82
  end
@@ -86,6 +89,7 @@ module Fugit
86
89
  match
87
90
  end
88
91
  branch_parents.reverse!
92
+ branches.map! {|b| b == "○" ? "●" : b} if branch_refs.include?(sha)
89
93
  graph << [branches.join, comment, sha]
90
94
  end
91
95
  graph
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
  include Wx
3
+ include IconLoader
3
4
  include Fugit::GraphRenderer
4
5
 
5
6
  module Fugit
@@ -10,45 +11,172 @@ module Fugit
10
11
 
11
12
  @list = ListCtrl.new(self, ID_ANY, :style => LC_REPORT|LC_VRULES|NO_BORDER)
12
13
 
14
+ @list_menu = Menu.new
15
+ @menu_create_branch = MenuItem.new(@list_menu, ID_ANY, 'Create new branch here')
16
+ @menu_create_branch.set_bitmap(get_icon("arrow_divide.png"))
17
+ @list_menu.append_item(@menu_create_branch)
18
+ evt_menu(@menu_create_branch, :on_menu_create_branch)
19
+
20
+ @menu_cherry_pick = MenuItem.new(@list_menu, ID_ANY, 'Cherry-pick this commit')
21
+ @menu_cherry_pick.set_bitmap(get_icon("cherry.png"))
22
+ @list_menu.append_item(@menu_cherry_pick)
23
+ evt_menu(@menu_cherry_pick, :on_menu_cherry_pick)
24
+
25
+ @list_menu.append_separator
26
+
27
+ @menu_soft_reset = @list_menu.append('Soft-reset branch to here')
28
+ evt_menu(@menu_soft_reset, :on_menu_soft_reset)
29
+
30
+ @menu_mixed_reset = @list_menu.append('Mixed-reset branch to here')
31
+ evt_menu(@menu_mixed_reset, :on_menu_mixed_reset)
32
+
33
+ @menu_hard_reset = MenuItem.new(@list_menu, ID_ANY, 'Hard-reset branch to here')
34
+ @menu_hard_reset.set_bitmap(get_icon("arrow_undo.png"))
35
+ @list_menu.append_item(@menu_hard_reset)
36
+ evt_menu(@menu_hard_reset, :on_menu_hard_reset)
37
+
13
38
  @box = BoxSizer.new(VERTICAL)
14
39
  @box.add(@list, 1, EXPAND)
15
40
  self.set_sizer(@box)
16
41
 
17
- register_for_message(:history_tab_shown) do
18
- update_list unless @has_initialized
19
- @list.set_focus
20
- end
42
+ evt_list_item_right_click(@list.get_id, :on_list_menu_request)
43
+
44
+ register_for_message(:history_tab_shown, :update_list)
45
+ register_for_message(:tab_switch, :update_list)
46
+ register_for_message(:refresh, :update_list)
21
47
  register_for_message(:exiting) {self.hide} # Things seem to run smoother if we hide before destruction
22
48
  end
23
49
 
24
50
  def update_list
51
+ return unless is_shown_on_screen
25
52
  @list.hide
26
53
  @list.clear_all
27
54
 
28
- @list.insert_column(0, "Graph")
29
- @list.insert_column(1, "SHA1")
30
- @list.insert_column(2, "Commit note")
55
+ @list.insert_column(0, "")
56
+ @list.insert_column(1, "Branches")
57
+ @list.insert_column(2, "SHA1")
58
+ @list.insert_column(3, "Commit note")
31
59
 
32
60
  mono_font = Font.new(8, FONTFAMILY_TELETYPE, FONTSTYLE_NORMAL, FONTWEIGHT_NORMAL)
33
61
 
62
+ branches = `git branch -v -a --no-abbrev`
63
+ branches = branches.split("\n").map {|b| [b[2..-1].split(" ")[0..1], b[0..0] == "*"].flatten}
64
+
34
65
  output = `git log --pretty=format:"%H\t%P\t%s" --date-order --all`
35
66
  lines = output.split("\n").map! {|line| line.split("\t")}
36
- log = graphify(lines)
67
+ current_sha = branches.reject {|b| !b.last}.first[1]
68
+ lines.insert(0, ["uncomitted", current_sha, "<Uncomitted changes>"]) if has_uncomitted_changes?
69
+ log = graphify(lines, branches)
37
70
 
38
71
  log.each_index do |i|
39
72
  (graph, comment, sha) = log[i]
73
+ comment_branches = branches.reject {|b| b[1] != sha}.map {|b| (b.last ? "*" : "") + b.first}
40
74
  @list.insert_item(i, sha)
41
75
  @list.set_item(i, 0, graph)
42
- @list.set_item(i, 1, sha[0..7])
43
- @list.set_item(i, 2, (comment.nil? || comment.empty?) ? "<No comment>" : comment)
76
+ @list.set_item(i, 1, comment_branches.join(" "))
77
+ @list.set_item(i, 2, sha == "uncomitted" ? "" : sha[0..7])
78
+ @list.set_item(i, 3, (comment.nil? || comment.empty?) ? "<No comment>" : comment)
79
+ @list.set_item_data(i, sha)
80
+ @list.set_item_background_colour(i, Colour.new(255, 220, 220)) if sha == "uncomitted"
81
+ @list.set_item_background_colour(i, Colour.new(220, 220, 225)) if comment_branches.join =~ /\*/
44
82
  end
45
83
 
46
84
  @list.set_column_width(0, -1)
85
+ @list.set_column_width(0, [150, @list.get_column_width(0)].min)
47
86
  @list.set_column_width(1, -1)
48
- @list.set_column_width(2, -2)
87
+ @list.set_column_width(1, [150, @list.get_column_width(1)].min)
88
+ @list.set_column_width(2, -1)
89
+ @list.set_column_width(3, -1)
49
90
 
50
91
  @list.show
51
- @has_initialized = true
92
+ @list.set_focus
93
+ end
94
+
95
+ def on_list_menu_request(event)
96
+ @menu_data = event.get_item.get_data
97
+ @list.popup_menu(@list_menu)
98
+ end
99
+
100
+ def on_menu_create_branch(event)
101
+ @new_branch_dialog ||= TextEntryDialog.new(self, "New branch name:", "Create branch")
102
+ @new_branch_dialog.set_value("")
103
+ if @new_branch_dialog.show_modal == ID_OK
104
+ err = `git branch #{@new_branch_dialog.get_value} #{@menu_data} 2>&1`
105
+ if err.empty?
106
+ send_message(:refresh)
107
+ else
108
+ MessageDialog.new(self, err, "Error creating branch", OK|ICON_ERROR).show_modal
109
+ end
110
+ end
111
+ end
112
+
113
+ def on_menu_cherry_pick(event)
114
+ err = `git cherry-pick #{@menu_data} 2>&1`
115
+ if err =~ /Automatic cherry-pick failed/
116
+ MessageDialog.new(self, err, "Error cherry-picking", OK|ICON_ERROR).show_modal
117
+ else
118
+ send_message(:refresh)
119
+ end
120
+ end
121
+
122
+ def on_menu_soft_reset(event)
123
+ err = `git reset --soft #{@menu_data} 2>&1`
124
+ if !err.empty?
125
+ MessageDialog.new(self, err, "Error resetting", OK|ICON_ERROR).show_modal
126
+ else
127
+ send_message(:refresh)
128
+ end
129
+ end
130
+
131
+ def on_menu_mixed_reset(event)
132
+ err = `git reset --mixed #{@menu_data} 2>&1`
133
+ #~ if !(err =~ /HEAD is now at/)
134
+ #~ MessageDialog.new(self, err, "Error resetting", OK|ICON_ERROR).show_modal
135
+ #~ else
136
+ send_message(:refresh)
137
+ #~ end
138
+ end
139
+
140
+ def on_menu_hard_reset(event)
141
+ if has_uncomitted_changes?
142
+ @uncomitted_hard_dialog ||= MessageDialog.new(self, "Uncommitted changes will be lost, continue?", "Uncomitted changes", YES_NO|NO_DEFAULT|ICON_EXCLAMATION)
143
+ return if @uncomitted_hard_dialog.show_modal != ID_YES
144
+ end
145
+
146
+ err = `git reset --hard #{@menu_data} 2>&1`
147
+ if !(err =~ /HEAD is now at/)
148
+ MessageDialog.new(self, err, "Error resetting", OK|ICON_ERROR).show_modal
149
+ else
150
+ send_message(:refresh)
151
+ end
152
+ end
153
+
154
+ def has_uncomitted_changes?
155
+ deleted = `git ls-files --deleted`
156
+ modified = `git ls-files --modified`
157
+ staged = `git ls-files --stage`
158
+ last_commit = `git ls-tree -r HEAD`
159
+
160
+ committed = {}
161
+ last_commit.split("\n").map do |line|
162
+ (info, file) = line.split("\t")
163
+ sha = info.match(/[a-f0-9]{40}/)[0]
164
+ committed[file] = sha
165
+ end
166
+
167
+ deleted = deleted.split("\n")
168
+ modified = modified.split("\n")
169
+ staged = staged.split("\n").map do |line|
170
+ (info, file) = line.split("\t")
171
+ sha = info.match(/[a-f0-9]{40}/)[0]
172
+ [file, sha]
173
+ end
174
+ committed.each_pair do |file, sha|
175
+ staged << [file, ""] unless staged.assoc(file)
176
+ end
177
+ staged.reject! {|file, sha| committed[file] == sha}
178
+
179
+ return !(deleted + modified + staged).empty?
52
180
  end
53
181
 
54
182
  end
@@ -2,7 +2,7 @@
2
2
  module IconLoader
3
3
  IconBasePath = File.expand_path(File.join(File.dirname(__FILE__), "..", "icons"))
4
4
 
5
- def get_icon(name, type = "png")
5
+ def get_icon(name)
6
6
  icon = File.join(IconBasePath, name)
7
7
  case name[-3..-1].downcase
8
8
  when "png"
@@ -6,7 +6,7 @@ module Fugit
6
6
  def initialize(parent)
7
7
  super(parent, ID_ANY)
8
8
 
9
- @index = TreeCtrl.new(self, ID_ANY, nil, nil, NO_BORDER|TR_MULTIPLE|TR_HIDE_ROOT|TR_FULL_ROW_HIGHLIGHT|TR_NO_LINES)
9
+ @index = TreeCtrl.new(self, ID_ANY, nil, nil, TR_MULTIPLE|TR_HIDE_ROOT|TR_FULL_ROW_HIGHLIGHT|TR_NO_LINES)
10
10
 
11
11
  imagelist = ImageList.new(16, 16)
12
12
  imagelist << get_icon("asterisk_yellow.png")
@@ -23,15 +23,6 @@ module Fugit
23
23
  @index.set_item_bold(@unstaged)
24
24
  @index.set_item_bold(@staged)
25
25
 
26
- @toolbar = ToolBar.new(self, ID_ANY, nil, nil, TB_HORIZONTAL|NO_BORDER|TB_NODIVIDER)
27
- @toolbar.set_tool_bitmap_size(Size.new(16,16))
28
- stage_all_button = @toolbar.add_tool(ID_ANY, "Stage all", get_icon("folder_add.png"), "Stage all")
29
- stage_button = @toolbar.add_tool(ID_ANY, "Stage", get_icon("page_add.png"), "Stage file")
30
- @toolbar.add_separator
31
- unstage_button = @toolbar.add_tool(ID_ANY, "Unstage", get_icon("page_delete.png"), "Unstage file")
32
- unstage_all_button = @toolbar.add_tool(ID_ANY, "Unstage all", get_icon("folder_delete.png"), "Unstage all")
33
- @toolbar.realize
34
-
35
26
  @unstaged_menu = Menu.new
36
27
  @menu_stage_file = @unstaged_menu.append('Stage file')
37
28
  @menu_revert_changes = @unstaged_menu.append('Revert changes')
@@ -43,19 +34,16 @@ module Fugit
43
34
  evt_tree_item_menu(@index.get_id, :on_menu_request)
44
35
 
45
36
  box = BoxSizer.new(VERTICAL)
46
- box.add(@toolbar, 0, EXPAND)
47
37
  box.add(@index, 1, EXPAND)
48
38
  self.set_sizer(box)
49
39
 
50
40
  evt_tree_sel_changed(@index.get_id, :on_click)
51
41
  evt_tree_item_activated(@index.get_id, :on_double_click)
52
42
 
53
- evt_tool(stage_all_button, :on_stage_all_clicked)
54
- evt_tool(unstage_all_button, :on_unstage_all_clicked)
55
-
56
43
  evt_tree_item_collapsing(@index.get_id) {|event| event.veto}
57
44
 
58
- register_for_message(:refresh, :update_tree)
45
+ register_for_message(:refresh) {update_tree if is_shown_on_screen}
46
+ register_for_message(:branch_checkout) {update_tree if is_shown_on_screen}
59
47
  register_for_message(:commit_saved, :update_tree)
60
48
  register_for_message(:index_changed, :update_tree)
61
49
  register_for_message(:exiting) {self.hide} # Things seem to run smoother if we hide before destruction
@@ -145,21 +133,6 @@ module Fugit
145
133
  end
146
134
  end
147
135
 
148
- def on_stage_all_clicked(event)
149
- children = @index.get_children(@unstaged).map {|child| @index.get_item_data(child)}
150
- to_delete = children.reject {|file, change, status| change != :deleted}.map {|f,c,s| f}
151
- to_add = children.map {|f,c,s| f} - to_delete
152
- `git rm --cached "#{to_delete.join('" "')}"` unless to_delete.empty?
153
- `git add "#{to_add.join('" "')}"` unless to_add.empty?
154
- send_message(:index_changed)
155
- end
156
-
157
- def on_unstage_all_clicked(event)
158
- children = @index.get_children(@staged).map {|child| @index.get_item_data(child)[0]}
159
- `git reset "#{children.join('" "')}"` unless children.empty?
160
- send_message(:index_changed)
161
- end
162
-
163
136
  def set_diff(file, change, status)
164
137
  case status
165
138
  when :unstaged
@@ -212,12 +185,12 @@ module Fugit
212
185
  when :unstaged
213
186
  case change
214
187
  when :deleted
215
- `git rm --cached "#{file}"`
188
+ `git rm --cached "#{file}" 2>&1`
216
189
  else
217
- `git add "#{file}"`
190
+ `git add "#{file}" 2>&1`
218
191
  end
219
192
  when :staged
220
- `git reset "#{file}"`
193
+ `git reset "#{file}" 2>&1`
221
194
  end
222
195
 
223
196
  send_message(:index_changed)
@@ -24,7 +24,7 @@ module Fugit
24
24
  icon_file = File.expand_path(File.join(IconBasePath, "plus_minus.gif"))
25
25
  self.icon = Icon.new(icon_file, BITMAP_TYPE_GIF)
26
26
 
27
- evt_notebook_page_changed(@notebook) {|event| send_message(:history_tab_shown) if event.get_selection == 1}
27
+ evt_notebook_page_changed(@notebook) {|event| send_message(:tab_switch)}
28
28
 
29
29
  menu_bar = MenuBar.new
30
30
 
@@ -32,6 +32,7 @@ module Fugit
32
32
  menu_file = Menu.new
33
33
  # Using ID_EXIT standard id means the menu item will be given the right label for the platform and language, and placed in the correct platform-specific menu - eg on OS X, in the Application's menu
34
34
  menu_file.append(ID_SAVE, "&Save commit\tCtrl-S", "Save commit")
35
+ push = menu_file.append(ID_ANY, "&Push\tCtrl-P", "Push commits to a remote repo")
35
36
  refresh = menu_file.append(ID_ANY, "&Refresh\tF5", "Refresh the index list")
36
37
  menu_file.append(ID_EXIT, "E&xit", "Quit this program")
37
38
  menu_bar.append(menu_file, "&File")
@@ -45,6 +46,7 @@ module Fugit
45
46
  self.menu_bar = menu_bar
46
47
 
47
48
  evt_menu(ID_SAVE) {|event| send_message(:save_clicked)}
49
+ evt_menu(push) {|event| send_message(:push_clicked)}
48
50
  evt_menu(refresh) {|event| send_message(:refresh)}
49
51
  evt_menu(ID_EXIT) {|event| close} # End the application; it should finish automatically when the last window is closed.
50
52
  evt_menu(ID_ABOUT) do |event|
@@ -3,35 +3,30 @@ include Wx
3
3
  module Fugit
4
4
  class PushDialog < Dialog
5
5
  def initialize(parent)
6
- super(parent, ID_ANY, "Push branches", :size => Size.new(350, 500))
6
+ super(parent, ID_ANY, "Push branches", :size => Size.new(400, 500))
7
7
 
8
8
  @branch_list = CheckListBox.new(self, ID_ANY)
9
+ @tag_check = CheckBox.new(self, ID_ANY)
10
+ @tag_check.set_label("Include &tags")
11
+ @force_check = CheckBox.new(self, ID_ANY)
12
+ @force_check.set_label("&Force update")
13
+ @remote = ComboBox.new(self, ID_ANY)
9
14
  @log = TextCtrl.new(self, ID_ANY, :size => Size.new(20, 150), :style => TE_MULTILINE|TE_DONTWRAP|TE_READONLY)
10
15
  @progress = Gauge.new(self, ID_ANY, 100, :size => Size.new(20, 20))
11
16
 
12
- @remote = ComboBox.new(self, ID_ANY)
13
-
14
- check_panel = Panel.new(self, ID_ANY)
15
- @tag_check = CheckBox.new(check_panel, ID_ANY)
16
- @force_check = CheckBox.new(check_panel, ID_ANY)
17
-
18
- flex = FlexGridSizer.new(1,2,4,4)
19
- flex.add(@tag_check, 0, EXPAND)
20
- flex.add(StaticText.new(check_panel, ID_ANY, "Include tags"), 0, EXPAND)
21
- flex.add(@force_check, 0, EXPAND)
22
- flex.add(StaticText.new(check_panel, ID_ANY, "Force update"), 0, EXPAND)
23
-
24
- check_panel.set_sizer(flex)
25
-
26
17
  butt_sizer = create_button_sizer(OK|CANCEL)
27
18
  butt_sizer.get_children.map {|s| s.get_window}.compact.each {|b| b.set_label(b.get_label == "OK" ? "Push" : "Close")}
28
19
  evt_button(get_affirmative_id, :on_ok)
29
20
 
30
21
  box = BoxSizer.new(VERTICAL)
31
22
  box2 = BoxSizer.new(HORIZONTAL)
23
+ box3 = BoxSizer.new(VERTICAL)
24
+
25
+ box3.add(@tag_check, 1)
26
+ box3.add(@force_check, 1, TOP, 4)
32
27
 
33
28
  box2.add(@branch_list, 1, EXPAND|LEFT|RIGHT|BOTTOM, 4)
34
- box2.add(check_panel, 1)
29
+ box2.add(box3, 1, ALL, 4)
35
30
 
36
31
  box.add(StaticText.new(self, ID_ANY, "Select branches:"), 0, EXPAND|ALL, 4)
37
32
  box.add(box2, 1, EXPAND)
@@ -49,8 +44,9 @@ module Fugit
49
44
  branches = `git branch`
50
45
  remotes = `git remote`
51
46
  @remote.clear
52
- remotes.split("\n").each {|r| @remote.append(r)}
53
- @remote.set_value(@remote.get_string(0))
47
+ remotes = remotes.split("\n")
48
+ remotes.each {|r| @remote.append(r)}
49
+ @remote.set_value(remotes.include?("origin") ? "origin" : remotes[0])
54
50
  current = branches.match(/\* (.+)/).to_a.last
55
51
  branches = branches.split("\n").map {|b| b.split(" ").last}
56
52
  @branch_list.set(branches)
Binary file
Binary file
Binary file
Binary file
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tekkub-fugit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tekkub
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-18 00:00:00 -08:00
12
+ date: 2009-02-27 00:00:00 -08:00
13
13
  default_executable: fugit
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -36,9 +36,11 @@ files:
36
36
  - VERSION.yml
37
37
  - bin/fugit
38
38
  - lib/fugit
39
- - lib/fugit/commit.rb
39
+ - lib/fugit/commit_dialog.rb
40
40
  - lib/fugit/commit_tab.rb
41
+ - lib/fugit/commit_tab_toolbar.rb
41
42
  - lib/fugit/console.rb
43
+ - lib/fugit/delete_branch_dialog.rb
42
44
  - lib/fugit/diff.rb
43
45
  - lib/fugit/graph_renderer.rb
44
46
  - lib/fugit/history_list.rb
@@ -52,7 +54,12 @@ files:
52
54
  - lib/fugit/SciTE.properties
53
55
  - lib/fugit.rb
54
56
  - lib/icons
57
+ - lib/icons/arrow_divide.png
58
+ - lib/icons/arrow_divide_delete.png
59
+ - lib/icons/arrow_join.png
60
+ - lib/icons/arrow_undo.png
55
61
  - lib/icons/asterisk_yellow.png
62
+ - lib/icons/cherry.png
56
63
  - lib/icons/disk.png
57
64
  - lib/icons/folder_add.png
58
65
  - lib/icons/folder_delete.png