royw-git_shoes 0.0.0
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.
- data/LICENSE +20 -0
- data/README.rdoc +15 -0
- data/Rakefile +48 -0
- data/VERSION.yml +4 -0
- data/lib/controllers/controller.rb +115 -0
- data/lib/loader.rb +19 -0
- data/lib/logger_facade.rb +29 -0
- data/lib/models/dir_item.rb +47 -0
- data/lib/models/directory.rb +146 -0
- data/lib/models/git_helper.rb +162 -0
- data/lib/views/action.rb +10 -0
- data/lib/views/bullet_list.rb +66 -0
- data/lib/views/dir_action.rb +217 -0
- data/spec/git_helper_spec.rb +35 -0
- data/spec/spec_helper.rb +9 -0
- metadata +69 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Roy Wright
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
= git_shoes
|
2
|
+
|
3
|
+
This is an exploration of using shoes for a real application.
|
4
|
+
As this is a learning experience, the files are likely to be
|
5
|
+
in a high state of flux.
|
6
|
+
|
7
|
+
If the shoes UI works out, then this project's goal is to
|
8
|
+
facilitate git workflows and not be a simple porting of the
|
9
|
+
CLI to a GUI.
|
10
|
+
|
11
|
+
Onward thru the fog...
|
12
|
+
|
13
|
+
== Copyright
|
14
|
+
|
15
|
+
Copyright (c) 2009 Roy Wright. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "git_shoes"
|
8
|
+
gem.summary = %Q{TODO}
|
9
|
+
gem.email = "roy@wright.org"
|
10
|
+
gem.homepage = "http://github.com/royw/git_shoes"
|
11
|
+
gem.authors = ["Roy Wright"]
|
12
|
+
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
21
|
+
spec.libs << 'lib' << 'spec'
|
22
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
23
|
+
end
|
24
|
+
|
25
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
28
|
+
spec.rcov = true
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
task :default => :spec
|
33
|
+
|
34
|
+
require 'rake/rdoctask'
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
if File.exist?('VERSION.yml')
|
37
|
+
config = YAML.load(File.read('VERSION.yml'))
|
38
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
39
|
+
else
|
40
|
+
version = ""
|
41
|
+
end
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "git_shoes #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
48
|
+
|
data/VERSION.yml
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# This is the start of the application controller.
|
2
|
+
|
3
|
+
class Controller
|
4
|
+
class << self
|
5
|
+
include GitHelper
|
6
|
+
end
|
7
|
+
|
8
|
+
ALL = 'All'
|
9
|
+
MANAGED = 'Managed'
|
10
|
+
MODIFIED = 'Modified'
|
11
|
+
CANDIDATE = 'Unmanaged'
|
12
|
+
GIT_FILTER_NAMES = [ALL, MANAGED, MODIFIED, CANDIDATE]
|
13
|
+
FILTER_NAMES = [ALL]
|
14
|
+
|
15
|
+
NAME = 'Name'
|
16
|
+
REVERSE_NAME = 'Reverse Name'
|
17
|
+
DATE = 'Newest First'
|
18
|
+
REVERSE_DATE = 'Oldest First'
|
19
|
+
SORT_NAMES = [NAME, REVERSE_NAME, DATE, REVERSE_DATE]
|
20
|
+
|
21
|
+
# current working directory
|
22
|
+
def self.cwd
|
23
|
+
Directory.cwd
|
24
|
+
# Dir.getwd
|
25
|
+
end
|
26
|
+
|
27
|
+
# change directory
|
28
|
+
# Yes, could just use Dir.chdir but I'm wanting project
|
29
|
+
# accesses encapsulated by the controller/model and not
|
30
|
+
# in the view.
|
31
|
+
def self.chdir(path)
|
32
|
+
Directory.change(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
# return nil or the top level directory of the current
|
36
|
+
# git repository
|
37
|
+
def self.git_project_dir
|
38
|
+
Directory.repository_path
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.in_repository?
|
42
|
+
Directory.git?
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.repository_info
|
46
|
+
Directory.repository_info
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.branch(verbose=false)
|
50
|
+
Directory.branch(verbose)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.branches
|
54
|
+
Directory.branches
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.branch=(name)
|
58
|
+
Directory.branch = name
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.stash
|
62
|
+
Directory.stash
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.stash_pop
|
66
|
+
Directory.stash_pop
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.get_git_log(name)
|
70
|
+
Directory.get_git_log(name)
|
71
|
+
end
|
72
|
+
|
73
|
+
# return the list of files in the current directory
|
74
|
+
def self.files(&blk)
|
75
|
+
items = Directory.contents.collect do |item|
|
76
|
+
item.options[:click] = lambda {Controller.chdir(item.path); blk.call(nil)} if item.directory?
|
77
|
+
item.options[:click] = lambda {blk.call(item.path)} if item.file?
|
78
|
+
item
|
79
|
+
end
|
80
|
+
# puts "files items => #{items.inspect}"
|
81
|
+
items
|
82
|
+
end
|
83
|
+
|
84
|
+
# filter the list of files
|
85
|
+
def self.filter_by(filter_name, values)
|
86
|
+
case filter_name
|
87
|
+
when ALL
|
88
|
+
# leave values alone
|
89
|
+
when MANAGED
|
90
|
+
values = values.select{|item| item.managed? || item.directory?}
|
91
|
+
when MODIFIED
|
92
|
+
values = values.select{|item| item.modified? || item.directory?}
|
93
|
+
when CANDIDATE
|
94
|
+
values = values.select{|item| item.candidate? || item.directory?}
|
95
|
+
end
|
96
|
+
# puts "filter_by values => #{values.inspect}"
|
97
|
+
values
|
98
|
+
end
|
99
|
+
|
100
|
+
# sort the list of files
|
101
|
+
def self.sort_by(sort_name, values)
|
102
|
+
case sort_name
|
103
|
+
when NAME
|
104
|
+
values = values.sort {|a,b| a.path <=> b.path}
|
105
|
+
when REVERSE_NAME
|
106
|
+
values = values.sort {|a,b| b.path <=> a.path}
|
107
|
+
when DATE
|
108
|
+
values = values.sort {|a,b| a.mtime <=> b.mtime}
|
109
|
+
when REVERSE_DATE
|
110
|
+
values = values.sort {|a,b| b.mtime <=> a.mtime}
|
111
|
+
end
|
112
|
+
# puts "sort_by values => #{values.inspect}"
|
113
|
+
values
|
114
|
+
end
|
115
|
+
end
|
data/lib/loader.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
APP_ENV = "development" unless defined? APP_ENV
|
2
|
+
$:.push(File.join(File.dirname(__FILE__)))
|
3
|
+
require 'yaml'
|
4
|
+
require 'logger'
|
5
|
+
require 'singleton'
|
6
|
+
# require 'ruby-debug'
|
7
|
+
|
8
|
+
require 'logger_facade'
|
9
|
+
|
10
|
+
require 'models/git_helper'
|
11
|
+
require 'models/directory'
|
12
|
+
require 'models/dir_item'
|
13
|
+
|
14
|
+
require 'views/bullet_list'
|
15
|
+
require 'views/action'
|
16
|
+
require 'views/dir_action'
|
17
|
+
|
18
|
+
require 'controllers/controller'
|
19
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# From Matt Payne's slimtimeronshoes
|
2
|
+
class LoggerFacade
|
3
|
+
|
4
|
+
@@logger = Logger.new(APP_ENV == "development" ? STDOUT :
|
5
|
+
File.open('SlimTimerOnShoesErrors.log', File::WRONLY | File::APPEND | File::CREAT))
|
6
|
+
@@logger.level = APP_ENV == "production" ? Logger::WARN : Logger::INFO
|
7
|
+
|
8
|
+
FATAL = Logger::FATAL
|
9
|
+
ERROR = Logger::ERROR
|
10
|
+
WARN = Logger::WARN
|
11
|
+
INFO = Logger::INFO
|
12
|
+
DEBUG = Logger::DEBUG
|
13
|
+
|
14
|
+
def self.log(message, status=LoggerFacade::INFO)
|
15
|
+
case status
|
16
|
+
when LoggerFacade::DEBUG
|
17
|
+
@@logger.debug(message)
|
18
|
+
when LoggerFacade::ERROR
|
19
|
+
@@logger.error(message)
|
20
|
+
when LoggerFacade::FATAL
|
21
|
+
@@logger.fatal(message)
|
22
|
+
when LoggerFacade::INFO
|
23
|
+
@@logger.info(message)
|
24
|
+
when LoggerFacade::WARN
|
25
|
+
@@logger.warn(message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class DirItem
|
2
|
+
include GitHelper
|
3
|
+
|
4
|
+
attr_reader :path, :options
|
5
|
+
attr_writer :managed, :modified, :candidate
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
@path = path
|
9
|
+
@managed = false
|
10
|
+
@modified = false
|
11
|
+
@candidate = false
|
12
|
+
@options = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def managed?
|
16
|
+
@managed
|
17
|
+
end
|
18
|
+
|
19
|
+
def modified?
|
20
|
+
@modified
|
21
|
+
end
|
22
|
+
|
23
|
+
def candidate?
|
24
|
+
@candidate
|
25
|
+
end
|
26
|
+
|
27
|
+
def directory?
|
28
|
+
File.directory?(@path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def file?
|
32
|
+
File.file?(@path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def mtime
|
36
|
+
File.mtime(@path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def diff
|
40
|
+
git_diff(@path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def log
|
44
|
+
git_log(@path)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
class Directory
|
2
|
+
include Singleton
|
3
|
+
include GitHelper
|
4
|
+
|
5
|
+
def self.change(path,&blk)
|
6
|
+
instance.change(path, &blk)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.git?
|
10
|
+
instance.git?
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.repository_path
|
14
|
+
instance.repository_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.repository_info
|
18
|
+
instance.repository_info
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.branch(verbose=false)
|
22
|
+
instance.branch(verbose)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.branches
|
26
|
+
instance.branches
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.branch=(name)
|
30
|
+
instance.branch = name
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.stash
|
34
|
+
instance.stash
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.stash_pop
|
38
|
+
instance.stash_pop
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.get_git_log(name)
|
42
|
+
instance.get_git_log(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.contents
|
46
|
+
instance.contents
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.cwd
|
50
|
+
instance.cwd
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
@cwd = Dir.getwd
|
57
|
+
end
|
58
|
+
|
59
|
+
public
|
60
|
+
|
61
|
+
attr_reader :cwd
|
62
|
+
|
63
|
+
def change(path,&blk)
|
64
|
+
Dir.chdir(path, &blk)
|
65
|
+
@cwd = Dir.getwd
|
66
|
+
end
|
67
|
+
|
68
|
+
def git?
|
69
|
+
git_dir?(@cwd)
|
70
|
+
end
|
71
|
+
|
72
|
+
def repository_path
|
73
|
+
git_dir(@cwd)
|
74
|
+
end
|
75
|
+
|
76
|
+
def repository_info
|
77
|
+
repo_info = {}
|
78
|
+
if git?
|
79
|
+
# auto pop the directory when we are done
|
80
|
+
Dir.chdir(repository_path) do
|
81
|
+
repo_info['Managed'] = git_ls_files.length
|
82
|
+
repo_info['Modified'] = git_modified_files.length
|
83
|
+
repo_info['Unmanaged'] = git_new_files.length
|
84
|
+
end
|
85
|
+
end
|
86
|
+
repo_info
|
87
|
+
end
|
88
|
+
|
89
|
+
def branch(verbose=false)
|
90
|
+
git? ? git_branch(verbose) : ''
|
91
|
+
end
|
92
|
+
|
93
|
+
def branches
|
94
|
+
git_branch_names
|
95
|
+
end
|
96
|
+
|
97
|
+
def branch=(name)
|
98
|
+
git_checkout_branch(name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def stash
|
102
|
+
git_stash
|
103
|
+
end
|
104
|
+
|
105
|
+
def stash_pop
|
106
|
+
git_stash_pop
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_git_log(name)
|
110
|
+
git_log(name)
|
111
|
+
end
|
112
|
+
|
113
|
+
def contents
|
114
|
+
git? ? git_contents : non_git_contents
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def git_contents
|
120
|
+
managed_files = git_ls_files
|
121
|
+
managed_dirs = managed_files.collect{|path| path =~ /^([^\/]+)\// ? $1 : nil}.compact.uniq
|
122
|
+
modified_files = git_modified_files
|
123
|
+
candidate_files = git_new_files
|
124
|
+
entries = Dir.entries(@cwd)
|
125
|
+
entries.delete('.')
|
126
|
+
items = entries.collect do |filename|
|
127
|
+
item = DirItem.new(filename)
|
128
|
+
item.managed = managed_files.include?(filename)
|
129
|
+
item.modified = modified_files.include?(filename)
|
130
|
+
item.candidate = candidate_files.include?(filename)
|
131
|
+
item
|
132
|
+
end
|
133
|
+
# puts "contents items => #{items.inspect}"
|
134
|
+
items
|
135
|
+
end
|
136
|
+
|
137
|
+
def non_git_contents
|
138
|
+
entries = Dir.entries(@cwd)
|
139
|
+
entries.delete('.')
|
140
|
+
items = entries.collect do |filename|
|
141
|
+
DirItem.new(filename)
|
142
|
+
end
|
143
|
+
items
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# This is a temporary hack while I learn more about git.
|
2
|
+
# As such I'm using very ugly, non-portable code to invoke
|
3
|
+
# git via the command line. This ugliness will eventually
|
4
|
+
# be replaced with a gem library.
|
5
|
+
#
|
6
|
+
# The current goal is to define the high level interface
|
7
|
+
# needed to git.
|
8
|
+
#
|
9
|
+
# A really good git reference:
|
10
|
+
# http://skwpspace.com/git-workflows-book/
|
11
|
+
module GitHelper
|
12
|
+
|
13
|
+
# is the given path in a git repository?
|
14
|
+
def git_dir?(path)
|
15
|
+
# debug "git_dir?(#{path})"
|
16
|
+
result = false
|
17
|
+
unless path.nil? || path.strip.empty?
|
18
|
+
if File.directory?(path)
|
19
|
+
result = File.exist?(File.join(path, '.git'))
|
20
|
+
end
|
21
|
+
unless result
|
22
|
+
unless File.dirname(path) == path
|
23
|
+
result = git_dir?(File.dirname(path))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
# return nil or the path to the top level directory of the
|
31
|
+
# git repository that includes the given path
|
32
|
+
def git_dir(path)
|
33
|
+
# debug "git_dir?(#{path})"
|
34
|
+
result = nil
|
35
|
+
unless path.nil? || path.strip.empty?
|
36
|
+
# if the given path is a directory then check if it has a .git sub-directory
|
37
|
+
if File.directory?(path)
|
38
|
+
git_pathspec = File.join(path, '.git')
|
39
|
+
if File.exist?(git_pathspec) && File.directory?(git_pathspec)
|
40
|
+
result = path
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# only recurse if we haven't found a .git directory
|
44
|
+
if result.nil?
|
45
|
+
# stop recurse when we can't traverse up the tree any farther
|
46
|
+
unless File.dirname(path) == path
|
47
|
+
# recurse into the parent directory
|
48
|
+
result = git_dir(File.dirname(path))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
# return an array of filespecs of all the files in the
|
56
|
+
# current directory that are already in the git repository
|
57
|
+
def git_ls_files
|
58
|
+
`git ls-files`.split("\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
# return an array of filespecs of all the files that have
|
62
|
+
# been modified in the working tree relative to the current
|
63
|
+
# directory
|
64
|
+
def git_modified_files
|
65
|
+
s1 = `git status`
|
66
|
+
parse_modified_files(s1)
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_modified_files(s1)
|
70
|
+
s1.split("\n").collect{ |line| line =~ /^\#\s+modified\:\s+(\S.*)/ ? $1 : nil}.compact
|
71
|
+
end
|
72
|
+
|
73
|
+
# return an array of filespecs relative to the current directory
|
74
|
+
# of all the files in the working tree that are not ignored by
|
75
|
+
# .gitignore and are not currently in the repository.
|
76
|
+
def git_new_files
|
77
|
+
s1 = `git status`
|
78
|
+
parse_new_files(s1)
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_new_files(s1)
|
82
|
+
filenames = []
|
83
|
+
re1 = /^\#\s+Untracked files\:/m
|
84
|
+
md1 = re1.match(s1)
|
85
|
+
unless md1.nil?
|
86
|
+
s2 = md1.post_match
|
87
|
+
re2 = /^#\s+\(use \"git add \<file\>\.\.\.\" to include in what will be committed\)/m
|
88
|
+
md2 = re2.match(s2)
|
89
|
+
unless md2.nil?
|
90
|
+
s3 = md2.post_match
|
91
|
+
filenames = s3.split("\n").collect{|line| line =~ /^#\s+(\S.*)/ ? $1 : nil}.compact
|
92
|
+
end
|
93
|
+
end
|
94
|
+
filenames
|
95
|
+
end
|
96
|
+
|
97
|
+
def git_branch(verbose=false)
|
98
|
+
branch = ''
|
99
|
+
`git branch#{verbose ? ' -v' : ''}`.split("\n").each do |s|
|
100
|
+
if s =~ /^\*\s+(\S.*)\s*$/
|
101
|
+
branch = $1
|
102
|
+
break
|
103
|
+
end
|
104
|
+
end
|
105
|
+
branch
|
106
|
+
end
|
107
|
+
|
108
|
+
def git_branch_names
|
109
|
+
`git branch`.split("\n").collect{|name| (name =~ /^\*\s+(\S.*)\s*$/) ? $1 : name }
|
110
|
+
end
|
111
|
+
|
112
|
+
def git_create_branch(name)
|
113
|
+
`git branch #{name}`
|
114
|
+
end
|
115
|
+
|
116
|
+
def git_checkout_branch(name)
|
117
|
+
unless git_branch == name
|
118
|
+
`git checkout #{name}`
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def git_squash(from_branch_name)
|
123
|
+
`git merge --squash #{from_branch_name}`
|
124
|
+
end
|
125
|
+
|
126
|
+
def git_delete_branch(name)
|
127
|
+
`git branch -D #{name}`
|
128
|
+
end
|
129
|
+
|
130
|
+
def git_uncommit
|
131
|
+
`git uncommit`
|
132
|
+
end
|
133
|
+
|
134
|
+
def git_stash
|
135
|
+
`git stash`
|
136
|
+
end
|
137
|
+
|
138
|
+
def git_stash_pop
|
139
|
+
`git stash pop`
|
140
|
+
end
|
141
|
+
|
142
|
+
def git_log(name)
|
143
|
+
`git log #{name}`
|
144
|
+
end
|
145
|
+
|
146
|
+
def git_diff(name)
|
147
|
+
`git diff #{name}`
|
148
|
+
end
|
149
|
+
|
150
|
+
# def git_config_list_popup
|
151
|
+
# window :title => 'Git Repository Config' do
|
152
|
+
# stack do
|
153
|
+
# para `git config --list`
|
154
|
+
# stack(:align => 'center') do
|
155
|
+
# button('OK') do
|
156
|
+
# close
|
157
|
+
# end
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
end
|
data/lib/views/action.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# As shoes does not provide a listview, here's the start
|
2
|
+
# of one. Yes, I know, pretty crude...
|
3
|
+
module BulletList
|
4
|
+
|
5
|
+
STAR_BULLET = :star
|
6
|
+
CIRCLE_BULLET = :circle
|
7
|
+
PLUS_BULLET = :plus
|
8
|
+
SPACE_BULLET = :space
|
9
|
+
|
10
|
+
BULLETS = {
|
11
|
+
:star => lambda{|app| app.star(-6,13,5,6,3)},
|
12
|
+
:circle => lambda{|app| app.oval(-7,10,7)},
|
13
|
+
:plus => lambda do |app|
|
14
|
+
# rect(x,y,w,h) NOTE, docs are wrong
|
15
|
+
length = 9
|
16
|
+
beam = 1
|
17
|
+
app.rect(-5,8,beam,length,1) # vertical bar
|
18
|
+
app.rect(-9,12,length,beam,1) # horizontal bar
|
19
|
+
end,
|
20
|
+
:space => lambda {|app| }
|
21
|
+
}
|
22
|
+
|
23
|
+
public
|
24
|
+
|
25
|
+
# create the bullet list and return as a stack instance
|
26
|
+
# values is an array of DirItem instances
|
27
|
+
#
|
28
|
+
# list_opts is a hash of options for the list itself
|
29
|
+
# Example:
|
30
|
+
# list_opts => { :bullet => lambda{ |filename| star_bullet } }
|
31
|
+
# TODO a lot more docs here
|
32
|
+
def bullet_list(values, list_opts={})
|
33
|
+
# app.stroke = app.black
|
34
|
+
# app.fill = app.red
|
35
|
+
app.stack(:margin_bottom => 10) do
|
36
|
+
values.each do |dir_item|
|
37
|
+
opts = dir_item.options
|
38
|
+
slot_opts = {}
|
39
|
+
slot_opts[:height] = opts[:height] unless opts[:height].nil?
|
40
|
+
slot_opts[:scroll] = opts[:scroll] unless opts[:scroll].nil?
|
41
|
+
right = 0
|
42
|
+
right += opts[:margin_right] unless opts[:margin_right].nil?
|
43
|
+
left = 10
|
44
|
+
app.flow(:margin_left => 15) do
|
45
|
+
unless list_opts[:bullet].nil?
|
46
|
+
# app.debug "list_opts[:bullet] => #{list_opts[:bullet].inspect}.call(#{dir_item})"
|
47
|
+
BULLETS[list_opts[:bullet].call(dir_item)].call(app)
|
48
|
+
end
|
49
|
+
app.stack(slot_opts) do
|
50
|
+
if opts[:background]
|
51
|
+
app.background(opts[:background], :margin_left => left)
|
52
|
+
left += 5
|
53
|
+
end
|
54
|
+
if opts[:click].nil?
|
55
|
+
app.para(dir_item.path, :margin_left => left, :margin_right => right, :margin_bottom => 0)
|
56
|
+
else
|
57
|
+
app.para(app.link(dir_item.path, :click => opts[:click]), :margin_left => left, :margin_right => right, :margin_bottom => 0)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,217 @@
|
|
1
|
+
class DirAction < Action
|
2
|
+
include BulletList
|
3
|
+
|
4
|
+
DIR_PAGE = '/'
|
5
|
+
FILE_PAGE = '/file'
|
6
|
+
VIEW_AREA_OPTS = {:width => -100, :margin_left => 10, :margin_right => @gutter}
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
@filter_selection = Controller::FILTER_NAMES.first
|
11
|
+
@sort_selection = Controller::SORT_NAMES.first
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def execute(filename=nil)
|
16
|
+
info "dir_action.execute(#{filename})"
|
17
|
+
dir_item = @values.select{|item| item.path == filename}.first rescue nil
|
18
|
+
@gutter = app.gutter
|
19
|
+
app.background app.palegreen
|
20
|
+
app.style(Shoes::Link, :underline => false, :stroke => app.blue)
|
21
|
+
app.style(Shoes::LinkHover, :underline => true, :stroke => app.red)
|
22
|
+
|
23
|
+
app.flow(:width => '100%') do
|
24
|
+
title_stack = app.stack(:width => '100%') do
|
25
|
+
app.background app.gradient(app.rgb(0, 255, 0), app.rgb(225, 255, 0), :angle => 135)
|
26
|
+
app.title("Git Shoes", :align => 'center')
|
27
|
+
end
|
28
|
+
command_widget(dir_item, :width => 100)
|
29
|
+
if dir_item.nil?
|
30
|
+
@view_area = directory_widget(VIEW_AREA_OPTS)
|
31
|
+
else
|
32
|
+
@view_area = file_widget(dir_item, VIEW_AREA_OPTS)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def command_widget(dir_item, opt={})
|
38
|
+
app.stack(opt) do
|
39
|
+
app.background app.gradient(app.rgb(0, 255, 0), app.rgb(255, 255, 0), :angle => -35)
|
40
|
+
app.para app.link("Files", :click => lambda{app.visit(DIR_PAGE)})
|
41
|
+
app.rect(5, 30, 90, 1)
|
42
|
+
if Controller.in_repository?
|
43
|
+
app.para app.link("Status", :click => lambda{status_action})
|
44
|
+
if !dir_item.nil? && (dir_item.modified? || dir_item.candidate?)
|
45
|
+
app.para app.link("Add", :click => lambda{add_action})
|
46
|
+
else
|
47
|
+
app.para "Add"
|
48
|
+
end
|
49
|
+
if !dir_item.nil? && dir_item.modified?
|
50
|
+
app.para app.link("Diff", :click => lambda{diff_action})
|
51
|
+
else
|
52
|
+
app.para "Diff"
|
53
|
+
end
|
54
|
+
app.para app.link("Commit", :click => lambda{commit_action})
|
55
|
+
app.para "Config"
|
56
|
+
else
|
57
|
+
# app.para 'Status'
|
58
|
+
# app.para "Diff"
|
59
|
+
# app.para "Commit"
|
60
|
+
# app.para "Branch"
|
61
|
+
# app.para "Config"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_action
|
67
|
+
# TODO implement
|
68
|
+
puts "need to implement add_action"
|
69
|
+
end
|
70
|
+
|
71
|
+
def diff_action
|
72
|
+
# TODO implement
|
73
|
+
puts "need to implement diff_action"
|
74
|
+
end
|
75
|
+
|
76
|
+
def commit_action
|
77
|
+
# TODO implement
|
78
|
+
puts "need to implement commit_action"
|
79
|
+
end
|
80
|
+
|
81
|
+
def status_action
|
82
|
+
if Controller.in_repository?
|
83
|
+
@view_area.clear {status_widget(VIEW_AREA_OPTS)}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def status_widget(opt={})
|
88
|
+
app.stack(opt) do
|
89
|
+
app.para `git status`
|
90
|
+
app.info app.para.text
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def file_widget(dir_item, opt={})
|
95
|
+
app.stack(opt) do
|
96
|
+
app.para "filename: #{dir_item.path}"
|
97
|
+
# TODO: everything there is to know about the file ;)
|
98
|
+
app.para(app.strong('Log'))
|
99
|
+
app.flow(:height => 200, :margin_right => @gutter, :scroll => true) do
|
100
|
+
app.background app.turquoise
|
101
|
+
app.para(dir_item.log)
|
102
|
+
end
|
103
|
+
if dir_item.modified?
|
104
|
+
app.para
|
105
|
+
app.para(app.strong('Diff'))
|
106
|
+
app.flow(:height => 200, :margin_right => @gutter, :scroll => true) do
|
107
|
+
app.background app.turquoise
|
108
|
+
app.para dir_item.diff
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def directory_widget(opt={})
|
115
|
+
app.stack(opt) do
|
116
|
+
cwd = Controller.cwd
|
117
|
+
git_project_dir = Controller.git_project_dir
|
118
|
+
if git_project_dir.nil?
|
119
|
+
app.caption("Directory", :align => 'center')
|
120
|
+
app.para(cwd)
|
121
|
+
else
|
122
|
+
repository_widget(cwd, git_project_dir)
|
123
|
+
end
|
124
|
+
|
125
|
+
file_selection(git_project_dir)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def repository_widget(cwd, git_project_dir)
|
130
|
+
app.caption("Git Repository", :align => 'center')
|
131
|
+
app.flow do
|
132
|
+
app.para "Branch: "
|
133
|
+
@branch_listbox = app.list_box(:items => Controller.branches) do |list|
|
134
|
+
Controller.branch = list.text
|
135
|
+
file_selection_changed
|
136
|
+
end
|
137
|
+
@branch_listbox.choose(Controller.branch(false))
|
138
|
+
app.button("New", :margin_left => 10) # TODO Hook it up
|
139
|
+
app.button("Delete", :margin_left => 10) # TODO Hook it up
|
140
|
+
app.button("Stash", :margin_left => 10) { Controller.stash }
|
141
|
+
app.button("Stash Pop", :margin_left => 10) { Controller.stash_pop }
|
142
|
+
end
|
143
|
+
app.para("Branch: ", app.strong(Controller.branch(true)))
|
144
|
+
app.para(app.strong(git_project_dir, app.em(cwd.gsub(git_project_dir, ''))))
|
145
|
+
buf = []
|
146
|
+
repository_info = Controller.repository_info
|
147
|
+
repository_info.each do |key, value|
|
148
|
+
buf << "#{value} #{key} files"
|
149
|
+
end
|
150
|
+
app.para buf.join(", ")
|
151
|
+
end
|
152
|
+
|
153
|
+
def file_selection(git_project_dir)
|
154
|
+
app.flow do
|
155
|
+
app.para 'Filter by: '
|
156
|
+
if git_project_dir.nil?
|
157
|
+
filter_items = Controller::FILTER_NAMES
|
158
|
+
unless filter_items.include?(@filter_selection)
|
159
|
+
@filter_selection = filter_items.first
|
160
|
+
end
|
161
|
+
else
|
162
|
+
filter_items = Controller::GIT_FILTER_NAMES
|
163
|
+
end
|
164
|
+
@filter_by = app.list_box(:items => filter_items) do |list|
|
165
|
+
file_selection_changed
|
166
|
+
end
|
167
|
+
app.para ' Sort by: '
|
168
|
+
@sort_by = app.list_box(:items => Controller::SORT_NAMES) do |list|
|
169
|
+
file_selection_changed
|
170
|
+
end
|
171
|
+
@listing = app.stack
|
172
|
+
@filter_by.choose(@filter_selection)
|
173
|
+
@sort_by.choose(@sort_selection)
|
174
|
+
|
175
|
+
# need to fire a selection changed to get the intial loading of @listing
|
176
|
+
file_selection_changed
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def file_selection_changed
|
181
|
+
@filter_selection = @filter_by.text
|
182
|
+
@sort_selection = @sort_by.text
|
183
|
+
@values = Controller.files do |filename|
|
184
|
+
if filename.nil?
|
185
|
+
app.visit(DIR_PAGE)
|
186
|
+
else
|
187
|
+
app.visit(FILE_PAGE + '/' + filename)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
@values = Controller.filter_by(@filter_by.text, @values)
|
191
|
+
@values = Controller.sort_by(@sort_by.text, @values)
|
192
|
+
@listing.clear {bullet_list(@values, :bullet => fs_bullets)}
|
193
|
+
end
|
194
|
+
|
195
|
+
def fs_bullets
|
196
|
+
lambda do |item|
|
197
|
+
if item.path == '..'
|
198
|
+
bullet = BulletList::SPACE_BULLET
|
199
|
+
else
|
200
|
+
if item.directory?
|
201
|
+
app.fill app.black
|
202
|
+
bullet = BulletList::PLUS_BULLET
|
203
|
+
else
|
204
|
+
if item.modified?
|
205
|
+
app.fill app.red
|
206
|
+
elsif item.managed?
|
207
|
+
app.fill app.lime
|
208
|
+
else
|
209
|
+
app.fill app.black
|
210
|
+
end
|
211
|
+
bullet = BulletList::CIRCLE_BULLET
|
212
|
+
end
|
213
|
+
end
|
214
|
+
bullet
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
STATUS_1 = <<END_STATUS_1
|
4
|
+
# On branch master
|
5
|
+
# Changed but not updated:
|
6
|
+
# (use "git add <file>..." to update what will be committed)
|
7
|
+
# (use "git checkout -- <file>..." to discard changes in working directory)
|
8
|
+
#
|
9
|
+
# modified: lib/dvdprofiler2xbmc/controllers/app.rb
|
10
|
+
# modified: lib/dvdprofiler2xbmc/models/dvdprofiler_info.rb
|
11
|
+
#
|
12
|
+
# Untracked files:
|
13
|
+
# (use "git add <file>..." to include in what will be committed)
|
14
|
+
#
|
15
|
+
# t1
|
16
|
+
# t2
|
17
|
+
no changes added to commit (use "git add" and/or "git commit -a")
|
18
|
+
END_STATUS_1
|
19
|
+
|
20
|
+
describe "GitHelper" do
|
21
|
+
it "should parse the status and find modified files" do
|
22
|
+
class A
|
23
|
+
include GitHelper
|
24
|
+
end
|
25
|
+
a = A.new
|
26
|
+
a.parse_modified_files(STATUS_1).should == ['lib/dvdprofiler2xbmc/controllers/app.rb', 'lib/dvdprofiler2xbmc/models/dvdprofiler_info.rb']
|
27
|
+
end
|
28
|
+
it "should parse the status and find new files" do
|
29
|
+
class A
|
30
|
+
include GitHelper
|
31
|
+
end
|
32
|
+
a = A.new
|
33
|
+
a.parse_new_files(STATUS_1).should == ['t1', 't2']
|
34
|
+
end
|
35
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: royw-git_shoes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roy Wright
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-27 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: roy@wright.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- README.rdoc
|
28
|
+
- Rakefile
|
29
|
+
- VERSION.yml
|
30
|
+
- lib/controllers/controller.rb
|
31
|
+
- lib/loader.rb
|
32
|
+
- lib/logger_facade.rb
|
33
|
+
- lib/models/dir_item.rb
|
34
|
+
- lib/models/directory.rb
|
35
|
+
- lib/models/git_helper.rb
|
36
|
+
- lib/views/action.rb
|
37
|
+
- lib/views/bullet_list.rb
|
38
|
+
- lib/views/dir_action.rb
|
39
|
+
- spec/git_helper_spec.rb
|
40
|
+
- spec/spec_helper.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/royw/git_shoes
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --charset=UTF-8
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: TODO
|
67
|
+
test_files:
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
- spec/git_helper_spec.rb
|