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