pro 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5ce1007c92dc6bd6f0db3974744f220c0a2feb18
4
- data.tar.gz: 0eaa44825c4d8e106bbc2b92c481af03da126de0
3
+ metadata.gz: 218f37b379ff1daf81b8fe9d35b07b8966aedcbb
4
+ data.tar.gz: 1a00825b3e77f87452f237b30c11e3827c9a6e6c
5
5
  SHA512:
6
- metadata.gz: 54262864147d3607af7b4f531b811244de89f2f4993489ec58bbf9ee389cde3eb2e3efec4fa5da6a3cc6f7d976fbc6ff7084cab63f35a391851908185d48651b
7
- data.tar.gz: e372b86ce017c084bcfc7f7c7a20ea43014c0ee17c45391787056bed31b16895f41180add6448a0aae7e28241b9bf5e4af5c7d9d480186ccc2b4e21d22eec0c0
6
+ metadata.gz: 237b2f95989c62e6807f5c17ee318ec2b12f23c7f5bf3db54cb192bb5a75d679e78dc6e4b991bf51b8ab1c37a9c2039131069d1f5a2a2f6ad76130b6a8dba599
7
+ data.tar.gz: 507ac61607b98f9c38138bf0ef82680efb5a766663265666cc8407ca432d7e667edbe93888d881018e037afed6428af037ab54fe562d82eb1014dffa5f64b03d
data/.gitignore CHANGED
@@ -1,5 +1,7 @@
1
1
  *.gem
2
2
  *.rbc
3
+ *.sublime-workspace
4
+ *.sublime-project
3
5
  .bundle
4
6
  .config
5
7
  .yardoc
data/bin/pro CHANGED
@@ -2,6 +2,15 @@
2
2
  # vi: set filetype=ruby fileencoding=UTF-8 shiftwidth=2 tabstop=2 expandtab
3
3
 
4
4
  require "pro"
5
+ require "yaml"
6
+
7
+ $pryAvailable = true
8
+ begin
9
+ require "pry"
10
+ rescue LoadError
11
+ $pryAvailable = false
12
+ end
13
+
5
14
 
6
15
  HELP = <<END
7
16
  pro is a command to help you manage your git repositories.
@@ -23,6 +32,8 @@ pro status - prints a list of all repos with uncommitted or unpushed changes.
23
32
  pro status <name> - prints the output of 'git status' on the repo.
24
33
  pro run - prompts for a command to run in all git repos.
25
34
  pro run <command> - runs the given command in all git repos.
35
+ pro list - prints the paths to all repos. Intended for scripting.
36
+ pro bases - prints the paths to all bases. Intended for scripting.
26
37
  pro install - Install the pro cd command. cd to a directory by fuzzy git repo matching.
27
38
  pro help - display help
28
39
 
@@ -43,21 +54,32 @@ Example:
43
54
 
44
55
  END
45
56
 
57
+ # spins of indexer even if index
58
+ # unnecessary such as version command
59
+ index = Pro::Indexer.new.index
60
+ pro = Pro::Commands.new(index)
61
+
46
62
  command = ARGV.shift || 'help'
47
63
 
48
64
  case command
65
+ when 'debug'
66
+ binding.pry if $pryAvailable
49
67
  when 'search'
50
- puts Pro.find_repo(ARGV.first)
68
+ puts pro.find_repo(ARGV.first)
51
69
  when 'install'
52
- Pro.install_cd
70
+ pro.install_cd
71
+ when 'list'
72
+ pro.list_repos
73
+ when 'bases'
74
+ pro.list_bases
53
75
  when 'status'
54
76
  if ARGV.first
55
- path = Pro.find_repo(ARGV.first)
77
+ path = pro.find_repo(ARGV.first)
56
78
  Dir.chdir(path) do
57
79
  puts `git status`
58
80
  end
59
81
  else
60
- Pro.status
82
+ pro.status
61
83
  end
62
84
  when 'run'
63
85
  unless ARGV.empty?
@@ -66,10 +88,12 @@ when 'run'
66
88
  print "Command: "
67
89
  command = STDIN.gets.chomp
68
90
  end
69
- Pro.run_command(command)
91
+ pro.run_command(command)
70
92
  when 'help'
71
93
  puts HELP
72
94
  when '-v'
73
- puts "Pro version #{Pro::VERSION} by Tristan Hume and contributors.\nhttp://github.com/trishume/pro"
95
+ puts "Pro version #{Pro::VERSION} by Tristan Hume and contributors."
96
+ puts "Running on Ruby version #{RUBY_VERSION} on platform #{RUBY_PLATFORM}."
97
+ puts "http://github.com/trishume/pro"
74
98
  end
75
99
 
@@ -0,0 +1,144 @@
1
+ require "pro/index"
2
+ require "find"
3
+ require "fuzzy_match"
4
+ require "colored"
5
+
6
+ SHELL_FUNCTION = <<END
7
+
8
+ # pro cd function
9
+ {{name}}() {
10
+ local projDir=$(pro search $1)
11
+ cd ${projDir}
12
+ }
13
+ END
14
+
15
+ CD_INFO = <<END
16
+ This installs a command to allow you to cd to a git repo
17
+ arbitrarily deep in your PRO_BASE based on fuzzy matching.
18
+
19
+ Example:
20
+
21
+ ~/randomFolder/ $ pd pro
22
+ pro/ $ pwd
23
+ /Users/tristan/Box/Dev/Projects/pro
24
+
25
+ ========
26
+ END
27
+
28
+
29
+ module Pro
30
+ class Commands
31
+ DIRTY_MESSAGE = 'uncommitted'.red
32
+ UNPUSHED_MESSAGE = 'unpushed'.blue
33
+ JOIN_STRING = ' + '
34
+
35
+ def initialize(index)
36
+ @index = index
37
+ end
38
+
39
+ # Fuzzy search for a git repository by name
40
+ # Returns the full path to the repository.
41
+ #
42
+ # If name is nil return the pro base.
43
+ def find_repo(name)
44
+ return @index.base_dirs.first unless name
45
+ match = FuzzyMatch.new(@index.to_a, :read => :name).find(name)
46
+ match[1] unless match.nil?
47
+ end
48
+
49
+ def run_command(command, confirm = true)
50
+ if confirm
51
+ print "Do you really want to run '#{command.bold}' on all repos [Y/n]? "
52
+ ans = STDIN.gets
53
+ return if ans.chomp != "Y"
54
+ end
55
+ @index.each do |r|
56
+ Dir.chdir(r.path)
57
+ result = `#{command}`
58
+ puts "#{r.name}:".bold.red
59
+ puts result
60
+ end
61
+ end
62
+
63
+ # Prints out the paths to all repositories in all bases
64
+ def list_repos()
65
+ @index.each do |r|
66
+ puts r.path
67
+ end
68
+ end
69
+
70
+ # prints out all the base directories
71
+ def list_bases
72
+ @index.base_dirs.each do |b|
73
+ puts b
74
+ end
75
+ end
76
+
77
+ # prints a status list showing repos with
78
+ # unpushed commits or uncommitted changes
79
+ def status()
80
+ max_name = @index.map {|repo| repo.name.length}.max + 1
81
+ @index.each do |r|
82
+ status = repo_status(r.path)
83
+ next if status.empty?
84
+ name = format("%-#{max_name}s",r.name).bold
85
+ puts "#{name} > #{status}"
86
+ end
87
+ end
88
+
89
+ # returns a short status message for the repo
90
+ def repo_status(path)
91
+ messages = []
92
+ messages << DIRTY_MESSAGE unless repo_clean?(path)
93
+ messages << UNPUSHED_MESSAGE if repo_unpushed?(path)
94
+ messages.join(JOIN_STRING)
95
+ end
96
+
97
+ # Checks if there are any uncommitted changes
98
+ def repo_clean?(path)
99
+ status = ""
100
+ Dir.chdir(path) do
101
+ status = `git status 2>/dev/null`
102
+ end
103
+ return status.end_with?("(working directory clean)\n") || status.end_with?("working directory clean\n")
104
+ end
105
+
106
+ # Finds if there are any commits which have not been pushed to origin
107
+ def repo_unpushed?(path)
108
+ unpushed = ""
109
+ Dir.chdir(path) do
110
+ branch_ref = `/usr/bin/git symbolic-ref HEAD 2>/dev/null`
111
+ branch = branch_ref.chomp.split('/').last
112
+ unpushed = `git cherry -v origin/#{branch} 2>/dev/null`
113
+ end
114
+ return !(unpushed.empty?)
115
+ end
116
+
117
+ # Adds a shell function to the shell config files that
118
+ # allows easy directory changing.
119
+ def install_cd
120
+ puts CD_INFO
121
+ print "Continue with installation (yN)? "
122
+ return unless gets.chomp == "y"
123
+ # get name
124
+ print "Name of pro cd command (default 'pd'): "
125
+ name = gets.strip
126
+ name = 'pd' if name.empty?
127
+ # sub into function
128
+ func = SHELL_FUNCTION.sub("{{name}}",name)
129
+ ['~/.profile', '~/.bashrc','~/.zshrc'].each do |rel_path|
130
+ # check if file exists
131
+ path = File.expand_path(rel_path)
132
+ next unless File.exists?(path)
133
+ # ask the user if they want to add it
134
+ print "Install #{name} function to #{rel_path} (yN): "
135
+ next unless gets.chomp == "y"
136
+ # add it on to the end of the file
137
+ File.open(path,'a') do |file|
138
+ file.puts func
139
+ end
140
+ end
141
+ puts "Done! #{name} will be available in new shells."
142
+ end
143
+ end
144
+ end
data/lib/pro/index.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Pro
2
+ Repo = Struct.new(:name,:path)
3
+ class Index
4
+ include Enumerable
5
+ attr_reader :bases,:base_dirs,:created_version
6
+ def initialize(bases,base_dirs)
7
+ @bases, @base_dirs = bases, base_dirs
8
+ @created_version = Pro::VERSION
9
+ end
10
+ # yields all repo objects in all bases
11
+ def each
12
+ bases.each do |key,val|
13
+ val.each {|r| yield r}
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,151 @@
1
+ require "pro/index"
2
+ require "colored"
3
+ module Pro
4
+ # creates an index object from cache
5
+ # or by searching the file system
6
+ class Indexer
7
+ CACHE_PATH = File.expand_path("~/.proCache")
8
+ INDEXER_LOCK_PATH = File.expand_path("~/.proCacheLock")
9
+ def initialize
10
+ @base_dirs = find_base_dirs
11
+ @low_cpu = false
12
+ end
13
+
14
+ def index
15
+ # most of the time the cache should exist
16
+ if res = read_cache
17
+ # index in the background for next time.
18
+ run_index_process
19
+ else
20
+ STDERR.puts "Indexing... This should only happen after updating.".red
21
+ res = build_index
22
+ end
23
+ res
24
+ end
25
+ # unserializes the cache file and returns
26
+ # the index object
27
+ def read_cache
28
+ return nil unless File.readable_real?(CACHE_PATH)
29
+ index = YAML::load_file(CACHE_PATH)
30
+ return nil unless index.created_version == Pro::VERSION
31
+ return nil unless index.base_dirs == @base_dirs
32
+ index
33
+ end
34
+
35
+ # spins off a background process to update the cache file
36
+ def run_index_process
37
+ fork {
38
+ index_process unless File.exists?(INDEXER_LOCK_PATH)
39
+ }
40
+ end
41
+
42
+ def index_process
43
+ @low_cpu = true
44
+ # create lock so no work duplicated
45
+ begin
46
+ File.open(INDEXER_LOCK_PATH, "w") {}
47
+ build_index
48
+ ensure
49
+ File.delete(INDEXER_LOCK_PATH)
50
+ end
51
+ end
52
+
53
+ # scan the base directories for git repos
54
+ # and build an index then cache it
55
+ # returns an index
56
+ def build_index
57
+ index = scan_into_index
58
+ cache_index(index)
59
+ index
60
+ end
61
+
62
+ # serialize the index to a cache file
63
+ def cache_index(index)
64
+ # TODO: atomic rename. Right now we just hope.
65
+ File.open(CACHE_PATH, 'w' ) do |out|
66
+ YAML::dump( index, out )
67
+ end
68
+ end
69
+
70
+ # compile base directories and scan them
71
+ # use this info to create an index object
72
+ # and return it
73
+ def scan_into_index
74
+ repos = scan_bases
75
+ Index.new(repos,@base_dirs)
76
+ end
77
+
78
+ # add all git repos in all bases to the index
79
+ def scan_bases
80
+ bases = {}
81
+ @base_dirs.each do |base|
82
+ bases[base] = index_repos(base)
83
+ end
84
+ bases
85
+ end
86
+
87
+
88
+ # find all repos in a certain base directory
89
+ # returns an array of Repo objects
90
+ def index_repos(base)
91
+ if system("which find > /dev/null")
92
+ index_repos_fast(base)
93
+ else
94
+ index_repos_slow(base)
95
+ end
96
+ end
97
+
98
+ def index_repos_fast(base)
99
+ res = `find #{base} -name .git`
100
+ # turn the output into a list of repos
101
+ repos = []
102
+ res.each_line do |line|
103
+ next if line.empty?
104
+ git_path = File.expand_path(line.chomp)
105
+ path = File.dirname(git_path)
106
+ repo_name = File.basename(path)
107
+ repos << Repo.new(repo_name,path)
108
+ end
109
+ repos
110
+ end
111
+
112
+ # recursive walk in ruby
113
+ def index_repos_slow(base)
114
+ STDERR.puts "WARNING: pro is indexing slowly, please install the 'find' command."
115
+ repos = []
116
+ Find.find(base) do |path|
117
+ # dir must exist and be a git repo
118
+ if FileTest.directory?(path) && File.exists?(path+"/.git")
119
+ base_name = File.basename(path)
120
+ repos << Repo.new(base_name,path)
121
+ Find.prune
122
+ end
123
+ # slow down
124
+ sleep(1.0/10000.0) if @low_cpu
125
+ end
126
+ repos
127
+ end
128
+
129
+ # Finds the base directory where repos are kept
130
+ # Checks the environment variable PRO_BASE and the
131
+ # file .proBase
132
+ def find_base_dirs()
133
+ bases = []
134
+ # check environment first
135
+ base = ENV['PRO_BASE']
136
+ bases << base if base
137
+ # next check proBase file
138
+ path = ENV['HOME'] + "/.proBase"
139
+ if File.exists?(path)
140
+ # read lines of the pro base file
141
+ bases += IO.read(path).split("\n").map {|p| File.expand_path(p.strip)}
142
+ end
143
+ # strip bases that do not exist
144
+ # I know about select! but it doesn't exist in 1.8
145
+ bases = bases.select {|b| File.exists?(b)}
146
+ # if no bases then return home
147
+ bases << ENV['HOME'] if bases.empty?
148
+ bases
149
+ end
150
+ end
151
+ end
data/lib/pro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pro
2
- VERSION = "1.0.3"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/pro.rb CHANGED
@@ -1,168 +1,4 @@
1
1
  require "pro/version"
2
- require "find"
3
- require "fuzzy_match"
4
- require "colored"
5
-
6
- SHELL_FUNCTION = <<END
7
-
8
- # pro cd function
9
- {{name}}() {
10
- local projDir=$(pro search $1)
11
- cd ${projDir}
12
- }
13
- END
14
-
15
- CD_INFO = <<END
16
- This installs a command to allow you to cd to a git repo
17
- arbitrarily deep in your PRO_BASE based on fuzzy matching.
18
-
19
- Example:
20
-
21
- ~/randomFolder/ $ pd pro
22
- pro/ $ pwd
23
- /Users/tristan/Box/Dev/Projects/pro
24
-
25
- ========
26
- END
27
-
28
-
29
- module Pro
30
- DIRTY_MESSAGE = 'uncommitted'.red
31
- UNPUSHED_MESSAGE = 'unpushed'.blue
32
- JOIN_STRING = ' + '
33
- # Finds the base directory where repos are kept
34
- # Checks the environment variable PRO_BASE and the
35
- # file .proBase
36
- def self.base_dirs()
37
- bases = []
38
- # check environment first
39
- base = ENV['PRO_BASE']
40
- bases << base if base
41
- # next check proBase file
42
- path = ENV['HOME'] + "/.proBase"
43
- if File.exists?(path)
44
- # read lines of the pro base file
45
- bases += IO.read(path).split("\n").map {|p| File.expand_path(p.strip)}
46
- end
47
- # strip bases that do not exist
48
- # I know about select! but it doesn't exist in 1.8
49
- bases = bases.select {|b| File.exists?(b)}
50
- # if no bases then return home
51
- bases << ENV['HOME'] if bases.empty?
52
- bases
53
- end
54
-
55
- # Searches for all the git repositories in the base directory.
56
- # returns an array of [repo_name, path] pairs.
57
- def self.repo_list
58
- repos = []
59
- Pro.base_dirs.each do |base|
60
- Find.find(base) do |path|
61
- if FileTest.directory?(path)
62
- # is this folder a git repo
63
- if File.exists?(path+"/.git")
64
- base_name = File.basename(path)
65
- repos << [base_name,path]
66
- Find.prune
67
- end
68
- end
69
- end
70
- end
71
- repos
72
- end
73
-
74
- # Fuzzy search for a git repository by name
75
- # Returns the full path to the repository.
76
- #
77
- # If name is nil return the pro base.
78
- def self.find_repo(name)
79
- return Pro.base_dirs.first unless name
80
- repos = Pro.repo_list
81
- match = FuzzyMatch.new(repos, :read => :first).find(name)
82
- match[1] unless match.nil?
83
- end
84
-
85
- def self.run_command(command, confirm = true)
86
- if confirm
87
- print "Do you really want to run '#{command.bold}' on all repos [Y/n]? "
88
- ans = STDIN.gets
89
- return if ans.chomp != "Y"
90
- end
91
- repos = Pro.repo_list
92
- repos.each do |r|
93
- Dir.chdir(r[1])
94
- result = `#{command}`
95
- puts "#{r.first}:".bold.red
96
- puts result
97
- end
98
- end
99
-
100
- # prints a status list showing repos with
101
- # unpushed commits or uncommitted changes
102
- def self.status
103
- repos = Pro.repo_list
104
- max_name = repos.map {|pair| pair.first.length}.max + 1
105
- repos.each do |pair|
106
- path = pair.last
107
- status = Pro.repo_status(path)
108
- next if status.empty?
109
- name = format("%-#{max_name}s",pair.first).bold
110
- puts "#{name} > #{status}"
111
- end
112
- end
113
-
114
- # returns a short status message for the repo
115
- def self.repo_status(path)
116
- messages = []
117
- messages << DIRTY_MESSAGE unless Pro.repo_clean?(path)
118
- messages << UNPUSHED_MESSAGE if Pro.repo_unpushed?(path)
119
- messages.join(JOIN_STRING)
120
- end
121
-
122
- # Checks if there are any uncommitted changes
123
- def self.repo_clean?(path)
124
- status = ""
125
- Dir.chdir(path) do
126
- status = `git status 2>/dev/null`
127
- end
128
- return status.end_with?("(working directory clean)\n") || status.end_with?("working directory clean\n")
129
- end
130
-
131
- # Finds if there are any commits which have not been pushed to origin
132
- def self.repo_unpushed?(path)
133
- unpushed = ""
134
- Dir.chdir(path) do
135
- branch_ref = `/usr/bin/git symbolic-ref HEAD 2>/dev/null`
136
- branch = branch_ref.chomp.split('/').last
137
- unpushed = `git cherry -v origin/#{branch} 2>/dev/null`
138
- end
139
- return !(unpushed.empty?)
140
- end
141
-
142
- # Adds a shell function to the shell config files that
143
- # allows easy directory changing.
144
- def self.install_cd
145
- puts CD_INFO
146
- print "Continue with installation (yN)? "
147
- return unless gets.chomp == "y"
148
- # get name
149
- print "Name of pro cd command (default 'pd'): "
150
- name = gets.strip
151
- name = 'pd' if name.empty?
152
- # sub into function
153
- func = SHELL_FUNCTION.sub("{{name}}",name)
154
- ['~/.profile', '~/.bashrc','~/.zshrc'].each do |rel_path|
155
- # check if file exists
156
- path = File.expand_path(rel_path)
157
- next unless File.exists?(path)
158
- # ask the user if they want to add it
159
- print "Install #{name} function to #{rel_path} (yN): "
160
- next unless gets.chomp == "y"
161
- # add it on to the end of the file
162
- File.open(path,'a') do |file|
163
- file.puts func
164
- end
165
- end
166
- puts "Done! #{name} will be available in new shells."
167
- end
168
- end
2
+ require "pro/indexer"
3
+ require "pro/index"
4
+ require "pro/commands"
data/pro.gemspec CHANGED
@@ -24,4 +24,5 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_development_dependency "bundler", "~> 1.3"
26
26
  spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "pry"
27
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pro
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tristan Hume
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-19 00:00:00.000000000 Z
11
+ date: 2013-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fuzzy_match
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: Lightweight git project tool.
70
84
  email:
71
85
  - tris.hume@gmail.com
@@ -81,6 +95,9 @@ files:
81
95
  - Rakefile
82
96
  - bin/pro
83
97
  - lib/pro.rb
98
+ - lib/pro/commands.rb
99
+ - lib/pro/index.rb
100
+ - lib/pro/indexer.rb
84
101
  - lib/pro/version.rb
85
102
  - pro.gemspec
86
103
  homepage: http://github.com/trishume/pro