pro 1.0.3 → 1.1.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.
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