autowow 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Guardfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +114 -0
- data/Rakefile +12 -0
- data/autowow.gemspec +38 -0
- data/bin/autowow +5 -0
- data/bin/aw +5 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/autowow.rb +12 -0
- data/lib/autowow/cli.rb +55 -0
- data/lib/autowow/command.rb +60 -0
- data/lib/autowow/decorators/string_decorator.rb +7 -0
- data/lib/autowow/fs.rb +41 -0
- data/lib/autowow/gem.rb +28 -0
- data/lib/autowow/log_formatter.rb +24 -0
- data/lib/autowow/time_difference.rb +29 -0
- data/lib/autowow/vcs.rb +281 -0
- data/lib/autowow/version.rb +3 -0
- metadata +237 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 10f7a14f0576ced3352895a30cc324a4bc230ab8
|
4
|
+
data.tar.gz: 2965fb8eac0567e25d45ace1225deb8e4920f90b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2f6f8ca60672d34f473877980e17f8b503462ef27a2ff1860a31703f1bff69cd12ef2fa6ede088b59b54c78687c4815eff43835c11c14e8c8b83dbd41de5c092
|
7
|
+
data.tar.gz: 5b900454d685e75c10299da0836945ac5dd67a47a54db11e784df2c85808b16d41f752d1cee794ba25315043383c8239408dbf066d065a72ca108c96468c5c89
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
guard 'bundler' do
|
2
|
+
watch('Gemfile')
|
3
|
+
watch(%r{^(.+)\.gemspec$})
|
4
|
+
end
|
5
|
+
|
6
|
+
guard 'rspec', cmd: "bundle exec spring rspec #{ENV['FOCUS']}", all_after_pass: ENV['FOCUS'].nil? do
|
7
|
+
watch(%r{^spec/.+_spec\.rb$})
|
8
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
9
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
10
|
+
|
11
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
12
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
13
|
+
watch(%r{^app/models/(.+)\.rb$}) { |m| "spec/builders/#{m[1]}_builder_spec.rb" }
|
14
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
15
|
+
watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
|
16
|
+
watch(%r{^spec/factories/(.+)\.rb$}) { 'spec' }
|
17
|
+
watch('config/routes.rb') { 'spec/routing' }
|
18
|
+
watch('app/controllers/application_controller.rb') { 'spec/controllers' }
|
19
|
+
|
20
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
21
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Csaba Apagyi
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# Autowow
|
2
|
+
|
3
|
+
#### Set of commands to AUTOmate Way Of Working
|
4
|
+
|
5
|
+
| Branch | Status |
|
6
|
+
| ------ | ------ |
|
7
|
+
| Release | [![Build Status](https://travis-ci.org/thisismydesign/autowow.svg?branch=release)](https://travis-ci.org/thisismydesign/autowow) [![Coverage Status](https://coveralls.io/repos/github/thisismydesign/autowow/badge.svg?branch=release)](https://coveralls.io/github/thisismydesign/autowow?branch=release) [![Gem Version](https://badge.fury.io/rb/autowow.svg)](https://badge.fury.io/rb/autowow) [![Total Downloads](http://ruby-gem-downloads-badge.herokuapp.com/autowow?type=total)](https://rubygems.org/gems/autowow) |
|
8
|
+
| Development | [![Build Status](https://travis-ci.org/thisismydesign/autowow.svg?branch=master)](https://travis-ci.org/thisismydesign/autowow) [![Coverage Status](https://coveralls.io/repos/github/thisismydesign/autowow/badge.svg?branch=master)](https://coveralls.io/github/thisismydesign/autowow?branch=master) |
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
Install from source as `rake install`.
|
13
|
+
|
14
|
+
Run `autowow` or `aw` to see available commands.
|
15
|
+
|
16
|
+
*Disclaimer: Use it on your own responsibility. Some commands may be tailored to my use case but as always pull requests and issues are welcomed.*
|
17
|
+
|
18
|
+
## Commands
|
19
|
+
|
20
|
+
Commands in general
|
21
|
+
* start by outputting the status before execution
|
22
|
+
* end by outputting the status after execution
|
23
|
+
* are safe
|
24
|
+
* only touch files via other commands (e.g. `git`)
|
25
|
+
* do not cause conflicted state
|
26
|
+
* hard check for prerequisites
|
27
|
+
* store and restore uncommitted changes
|
28
|
+
* output executed commands that cause any change
|
29
|
+
* execute in current directory
|
30
|
+
|
31
|
+
### VCS
|
32
|
+
|
33
|
+
Commands related to version control systems.
|
34
|
+
Currently only Git and the GitHub API are supported.
|
35
|
+
|
36
|
+
#### Branch merged
|
37
|
+
|
38
|
+
* Switches to master and pulls your merged changes
|
39
|
+
* Removes local working branch
|
40
|
+
|
41
|
+
Prerequisites: not on master
|
42
|
+
|
43
|
+
#### Update projects
|
44
|
+
|
45
|
+
* Updates local repositories
|
46
|
+
* Updates remote forks
|
47
|
+
* Searches for repositories on paths: `.`, `./*/`
|
48
|
+
|
49
|
+
Prerequisites: no uncommitted changes on master
|
50
|
+
|
51
|
+
#### Clear branches
|
52
|
+
|
53
|
+
* Removes branches without not pushed changes
|
54
|
+
* Keeps current and master branches
|
55
|
+
|
56
|
+
#### Add upstream
|
57
|
+
|
58
|
+
* Adds parent repository as remote 'upstream'
|
59
|
+
|
60
|
+
Prerequisites: doesn't have remote called 'upstream'
|
61
|
+
|
62
|
+
#### Hi
|
63
|
+
|
64
|
+
Day starter routine
|
65
|
+
|
66
|
+
* Updates projects (runs 'Update projects')
|
67
|
+
* Shows latest and deprecated repos
|
68
|
+
|
69
|
+
#### Hi!
|
70
|
+
|
71
|
+
Day starter routine for a new start
|
72
|
+
|
73
|
+
* Runs 'Add upstream' and 'Clear branches' for all projects
|
74
|
+
* Runs 'Hi'
|
75
|
+
|
76
|
+
#### Open
|
77
|
+
|
78
|
+
* Opens project in browser
|
79
|
+
|
80
|
+
### Gem
|
81
|
+
|
82
|
+
#### Gem release
|
83
|
+
|
84
|
+
* Rebases `release` branch to master
|
85
|
+
* Releases gem via `rake release`
|
86
|
+
|
87
|
+
Prerequisites: on master
|
88
|
+
|
89
|
+
## Feedback
|
90
|
+
|
91
|
+
Any feedback is much appreciated.
|
92
|
+
|
93
|
+
I can only tailor this project to fit use-cases I know about - which are usually my own ones. If you find that this might be the right direction to solve your problem too but you find that it's suboptimal or lacks features don't hesitate to contact me.
|
94
|
+
|
95
|
+
Please let me know if you make use of this project so that I can prioritize further efforts.
|
96
|
+
|
97
|
+
## Conventions
|
98
|
+
|
99
|
+
This gem is developed using the following conventions:
|
100
|
+
- [Bundler's guide for developing a gem](http://bundler.io/v1.14/guides/creating_gem.html)
|
101
|
+
- [Better Specs](http://www.betterspecs.org/)
|
102
|
+
- [Semantic versioning](http://semver.org/)
|
103
|
+
- [RubyGems' guide on gem naming](http://guides.rubygems.org/name-your-gem/)
|
104
|
+
- [RFC memo about key words used to Indicate Requirement Levels](https://tools.ietf.org/html/rfc2119)
|
105
|
+
- [Bundler improvements](https://github.com/thisismydesign/bundler-improvements)
|
106
|
+
- [Minimal dependencies](http://www.mikeperham.com/2016/02/09/kill-your-dependencies/)
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/your_gem.
|
111
|
+
|
112
|
+
## License
|
113
|
+
|
114
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
|
7
|
+
desc "Check if source can be required locally"
|
8
|
+
task :require do
|
9
|
+
sh "ruby -e \"require '#{File.dirname __FILE__}/lib/autowow'\""
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => [:require, :spec]
|
data/autowow.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "autowow/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "autowow"
|
8
|
+
spec.version = Autowow::VERSION
|
9
|
+
spec.authors = ["thisismydesign"]
|
10
|
+
spec.email = ["thisismydesign@users.noreply.github.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Set of commands to AUTOmate Way Of Working}
|
13
|
+
spec.homepage = "https://github.com/thisismydesign/autowow"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "bin"
|
20
|
+
spec.executables << 'autowow'
|
21
|
+
spec.executables << 'aw'
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency "easy_logging", ">= 0.3.0"
|
25
|
+
spec.add_dependency "thor"
|
26
|
+
spec.add_dependency "colorize"
|
27
|
+
# Fixed version because we refine it :(
|
28
|
+
spec.add_dependency "time_difference", "= 0.5.0"
|
29
|
+
spec.add_dependency "launchy"
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
34
|
+
spec.add_development_dependency "guard"
|
35
|
+
spec.add_development_dependency "guard-bundler"
|
36
|
+
spec.add_development_dependency "guard-rspec"
|
37
|
+
spec.add_development_dependency "coveralls"
|
38
|
+
end
|
data/bin/autowow
ADDED
data/bin/aw
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "autowow"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/autowow.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'easy_logging'
|
2
|
+
|
3
|
+
require_relative 'autowow/log_formatter'
|
4
|
+
|
5
|
+
EasyLogging.level = Logger::DEBUG
|
6
|
+
EasyLogging.formatter = proc do |severity, datetime, progname, msg|
|
7
|
+
Autowow::LogFormatter.beautify(severity, msg)
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative 'autowow/cli'
|
11
|
+
|
12
|
+
module Autowow; end
|
data/lib/autowow/cli.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
require_relative 'vcs'
|
4
|
+
require_relative 'gem'
|
5
|
+
|
6
|
+
module Autowow
|
7
|
+
class CLI < Thor
|
8
|
+
|
9
|
+
map %w[bm] => :branch_merged
|
10
|
+
map %w[grls] => :gem_release
|
11
|
+
map %w[up] => :update_projects
|
12
|
+
map %w[cb] => :clear_branches
|
13
|
+
map %w[au] => :add_upstream
|
14
|
+
|
15
|
+
desc "branch_merged", "clean working branch and return to master"
|
16
|
+
def branch_merged
|
17
|
+
Autowow::Vcs.branch_merged
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "gem_release", "release gem and return to master"
|
21
|
+
def gem_release
|
22
|
+
Autowow::Gem.gem_release
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "update_projects", "updates idle projects"
|
26
|
+
def update_projects
|
27
|
+
Autowow::Vcs.update_projects
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "clear_branches", "removes unused branches"
|
31
|
+
def clear_branches
|
32
|
+
Autowow::Vcs.clear_branches
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "add_upstream", "adds upstream branch if available"
|
36
|
+
def add_upstream
|
37
|
+
Autowow::Vcs.add_upstream
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "hi", "day starter routine"
|
41
|
+
def hi
|
42
|
+
Autowow::Vcs.hi
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "hi!", "day starter routine for a new start"
|
46
|
+
def hi!
|
47
|
+
Autowow::Vcs.hi!
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "open", "opens project URL in browser"
|
51
|
+
def open
|
52
|
+
Autowow::Vcs.open
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Autowow
|
4
|
+
class Command
|
5
|
+
|
6
|
+
include EasyLogging
|
7
|
+
|
8
|
+
def self.run(*args)
|
9
|
+
Command.new(*args).explain.chronic_execute
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.run_dry(*args)
|
13
|
+
Command.new(*args).execute
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.popen3_reader(*args)
|
17
|
+
args.each do |arg|
|
18
|
+
reader = <<-EOF
|
19
|
+
def #{arg}
|
20
|
+
@#{arg} = @#{arg}.read.rstrip unless @#{arg}.is_a?(String)
|
21
|
+
return @#{arg}
|
22
|
+
end
|
23
|
+
EOF
|
24
|
+
class_eval(reader)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
popen3_reader :stdin, :stdout, :stderr
|
29
|
+
attr_reader :wait_thr
|
30
|
+
|
31
|
+
def initialize(*args)
|
32
|
+
@cmd = args
|
33
|
+
end
|
34
|
+
|
35
|
+
def explain
|
36
|
+
logger.debug(@cmd.join(' ')) unless @cmd.empty?
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def execute
|
41
|
+
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3(*@cmd)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def chronic_execute
|
46
|
+
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3(*@cmd)
|
47
|
+
logger.error(stderr) unless stderr.empty?
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def output_matches?(matcher)
|
52
|
+
stdout.match(matcher)
|
53
|
+
end
|
54
|
+
|
55
|
+
def output_does_not_match?(matcher)
|
56
|
+
!output_matches?(matcher)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/lib/autowow/fs.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative 'time_difference'
|
2
|
+
|
3
|
+
module Autowow
|
4
|
+
class Fs
|
5
|
+
using RefinedTimeDifference
|
6
|
+
|
7
|
+
def self.ls_dirs
|
8
|
+
Dir.glob(File.expand_path('./*/'))
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.latest(files)
|
12
|
+
files.sort_by{ |f| File.mtime(f) }.reverse!.first
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.older_than(files, quantity, unit)
|
16
|
+
files.select do |dir|
|
17
|
+
TimeDifference.between(File.mtime(dir), Time.now).public_send("in_#{unit}") > quantity
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.for_dirs(dirs)
|
22
|
+
dirs.each do |working_dir|
|
23
|
+
# TODO: add handling of directories via extra param to popen3
|
24
|
+
# https://stackoverflow.com/a/10148084/2771889
|
25
|
+
Dir.chdir(working_dir) do
|
26
|
+
yield working_dir
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.in_place_or_subdirs(in_place)
|
32
|
+
if in_place
|
33
|
+
yield
|
34
|
+
else
|
35
|
+
for_dirs(ls_dirs) do
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/autowow/gem.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'vcs'
|
2
|
+
require_relative 'command'
|
3
|
+
|
4
|
+
module Autowow
|
5
|
+
class Gem
|
6
|
+
include EasyLogging
|
7
|
+
|
8
|
+
def self.gem_release
|
9
|
+
start_status = Vcs.status
|
10
|
+
logger.info(start_status)
|
11
|
+
working_branch = Vcs.current_branch
|
12
|
+
logger.error("Not on master.") and return unless working_branch.eql?('master')
|
13
|
+
Vcs.push
|
14
|
+
|
15
|
+
Vcs.on_branch('release') do
|
16
|
+
Vcs.pull
|
17
|
+
Vcs.rebase(working_branch)
|
18
|
+
release
|
19
|
+
end
|
20
|
+
|
21
|
+
logger.info(Vcs.status)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.release
|
25
|
+
Command.run('rake', 'release')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module Autowow
|
4
|
+
class LogFormatter
|
5
|
+
def self.beautify(severity, msg)
|
6
|
+
log_msg = msg.end_with?($/) ? msg : "#{msg}#{$/}"
|
7
|
+
log_msg = " $ #{log_msg}" if severity.eql?('DEBUG')
|
8
|
+
color(severity, log_msg)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.color(severity, msg)
|
12
|
+
case severity
|
13
|
+
when 'DEBUG'
|
14
|
+
msg.yellow
|
15
|
+
when 'WARN'
|
16
|
+
msg.light_red
|
17
|
+
when 'ERROR'
|
18
|
+
msg.red
|
19
|
+
else
|
20
|
+
msg
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'time_difference'
|
2
|
+
|
3
|
+
module RefinedTimeDifference
|
4
|
+
refine(TimeDifference) do
|
5
|
+
def humanize_higher_than(limit)
|
6
|
+
limit_index = TimeDifference::TIME_COMPONENTS.index(limit)
|
7
|
+
|
8
|
+
diff_parts = []
|
9
|
+
in_general.each_with_index do |array, index|
|
10
|
+
part, quantity = array
|
11
|
+
next if quantity <= 0 or limit_index < index
|
12
|
+
part = part.to_s.humanize
|
13
|
+
|
14
|
+
if quantity <= 1
|
15
|
+
part = part.singularize
|
16
|
+
end
|
17
|
+
|
18
|
+
diff_parts << "#{quantity} #{part}"
|
19
|
+
end
|
20
|
+
|
21
|
+
last_part = (diff_parts.pop or '')
|
22
|
+
if diff_parts.empty?
|
23
|
+
return last_part
|
24
|
+
else
|
25
|
+
return [diff_parts.join(', '), last_part].join(' and ')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/autowow/vcs.rb
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/https'
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'launchy'
|
6
|
+
|
7
|
+
require_relative 'command'
|
8
|
+
require_relative 'decorators/string_decorator'
|
9
|
+
require_relative 'fs'
|
10
|
+
require_relative 'time_difference'
|
11
|
+
|
12
|
+
module Autowow
|
13
|
+
class Vcs
|
14
|
+
include EasyLogging
|
15
|
+
include StringDecorator
|
16
|
+
|
17
|
+
using RefinedTimeDifference
|
18
|
+
|
19
|
+
def self.branch_merged
|
20
|
+
start_status = status
|
21
|
+
logger.info(start_status)
|
22
|
+
working_branch = current_branch
|
23
|
+
logger.error("Nothing to do.") and return if working_branch.eql?('master')
|
24
|
+
|
25
|
+
keep_changes do
|
26
|
+
checkout('master')
|
27
|
+
pull
|
28
|
+
end
|
29
|
+
branch_force_delete(working_branch)
|
30
|
+
|
31
|
+
logger.info(status)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.update_projects
|
35
|
+
Fs.in_place_or_subdirs(is_git?(status_dry)) do
|
36
|
+
update_project
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.update_project
|
41
|
+
start_status = status_dry
|
42
|
+
return unless is_git?(start_status)
|
43
|
+
logger.info("Updating #{File.expand_path('.')} ...")
|
44
|
+
logger.warn("Skipped: uncommitted changes on master.") and return if uncommitted_changes?(start_status) and current_branch.eql?('master')
|
45
|
+
|
46
|
+
on_branch('master') do
|
47
|
+
has_upstream?(remotes.stdout) ? pull_upstream : pull
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.clear_branches
|
52
|
+
logger.info(branch.stdout)
|
53
|
+
working_branch = current_branch
|
54
|
+
master_branch = 'master'
|
55
|
+
|
56
|
+
(branches - [master_branch, working_branch]).each do |branch|
|
57
|
+
branch_force_delete(branch) if branch_pushed(branch)
|
58
|
+
end
|
59
|
+
|
60
|
+
logger.info(branch.stdout)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.add_upstream
|
64
|
+
start_status = status_dry
|
65
|
+
logger.error("Not a git repository.") and return unless is_git?(start_status)
|
66
|
+
remote_list = remotes.stdout
|
67
|
+
logger.warn("Already has upstream.") and return if has_upstream?(remote_list)
|
68
|
+
logger.info(remote_list)
|
69
|
+
|
70
|
+
url = URI.parse(origin_push_url(remote_list))
|
71
|
+
host = "api.#{url.host}"
|
72
|
+
path = "/repos#{url.path}"
|
73
|
+
request = Net::HTTP.new(host, url.port)
|
74
|
+
request.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
75
|
+
request.use_ssl = url.scheme == 'https'
|
76
|
+
response = request.get(path)
|
77
|
+
logger.error("Github API (#{url.scheme}://#{host}#{path}) could not be reached: #{response.body}") and return unless response.kind_of?(Net::HTTPSuccess)
|
78
|
+
parsed_response = JSON.parse(response.body)
|
79
|
+
logger.warn('Not a fork.') and return unless parsed_response['fork']
|
80
|
+
parent_url = parsed_response.dig('parent', 'html_url')
|
81
|
+
add_remote('upstream', parent_url) unless parent_url.to_s.empty?
|
82
|
+
|
83
|
+
logger.info(remotes.stdout)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.hi
|
87
|
+
latest_project_info = get_latest_project_info
|
88
|
+
logger.info("\nHang on, updating your local projects and remote forks...\n\n")
|
89
|
+
git_projects.each do |project|
|
90
|
+
Dir.chdir(project) do
|
91
|
+
logger.info("\nGetting #{project} in shape...")
|
92
|
+
yield if block_given?
|
93
|
+
update_project
|
94
|
+
end
|
95
|
+
end
|
96
|
+
logger.info("\nGood morning!\n\n")
|
97
|
+
logger.info(latest_project_info)
|
98
|
+
check_projects_older_than(1, :months)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.hi!
|
102
|
+
hi do
|
103
|
+
logger.info('Removing unused branches...')
|
104
|
+
clear_branches
|
105
|
+
logger.info('Adding upstream...')
|
106
|
+
add_upstream
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.open
|
111
|
+
Launchy.open(origin_push_url(remotes.stdout))
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.get_latest_project_info
|
115
|
+
latest = latest_repo
|
116
|
+
time_diff = TimeDifference.between(File.mtime(latest), Time.now).humanize_higher_than(:days).downcase
|
117
|
+
time_diff_text = time_diff.empty? ? 'recently' : "#{time_diff} ago"
|
118
|
+
"It looks like you were working on #{File.basename(latest)} #{time_diff_text}.\n\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.latest_repo
|
122
|
+
Fs.latest(git_projects)
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.check_projects_older_than(quantity, unit)
|
126
|
+
old_projects = Fs.older_than(git_projects, quantity, unit)
|
127
|
+
deprecated_projects = old_projects.reject do |project|
|
128
|
+
Dir.chdir(project) { branches.reject{ |branch| branch_pushed(branch) }.any? }
|
129
|
+
end
|
130
|
+
|
131
|
+
logger.info("The following projects have not been touched for more than #{quantity} #{unit} and all changes have been pushed, maybe consider removing them?") unless deprecated_projects.empty?
|
132
|
+
deprecated_projects.each do |project|
|
133
|
+
time_diff = TimeDifference.between(File.mtime(project), Time.now).humanize_higher_than(:weeks).downcase
|
134
|
+
logger.info(" #{File.basename(project)} (#{time_diff})")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.stash
|
139
|
+
Command.run('git', 'stash').output_does_not_match?(%r{No local changes to save})
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.current_branch
|
143
|
+
Command.run_dry('git', 'symbolic-ref', '--short', 'HEAD').stdout
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.status
|
147
|
+
status = Command.run('git', 'status')
|
148
|
+
status.stdout + status.stderr
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.status_dry
|
152
|
+
status = Command.run_dry('git', 'status')
|
153
|
+
status.stdout + status.stderr
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.checkout(existing_branch)
|
157
|
+
Command.run('git', 'checkout', existing_branch)
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.create(branch)
|
161
|
+
Command.run('git', 'checkout', '-b', branch)
|
162
|
+
Command.run('git', 'push', '--set-upstream', 'origin', branch)
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.pull
|
166
|
+
Command.run('git', 'pull')
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.pull_upstream
|
170
|
+
Command.run('git', 'fetch', 'upstream')
|
171
|
+
Command.run('git', 'merge', 'upstream/master')
|
172
|
+
Command.run('git', 'push', 'origin', 'master')
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.branch
|
176
|
+
Command.run('git', 'branch')
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.stash_pop
|
180
|
+
Command.run('git', 'stash', 'pop')
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.branch_force_delete(branch)
|
184
|
+
Command.run('git', 'branch', '-D', branch)
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.remotes
|
188
|
+
Command.run_dry('git', 'remote', '-v')
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.has_upstream?(remotes)
|
192
|
+
remotes.include?('upstream')
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.uncommitted_changes?(start_status)
|
196
|
+
!(start_status.include?('nothing to commit, working tree clean') or start_status.include?('nothing added to commit but untracked files present'))
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.is_git?(start_status)
|
200
|
+
!start_status.include?('Not a git repository')
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.origin_push_url(remotes)
|
204
|
+
# Order is important: first try to match "url" in "#{url}.git" as non-dot_git matchers would include ".git" in the match
|
205
|
+
origin_push_url_ssl_dot_git(remotes) or
|
206
|
+
origin_push_url_ssl(remotes)or
|
207
|
+
origin_push_url_https_dot_git(remotes) or
|
208
|
+
origin_push_url_https(remotes)
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.origin_push_url_https(remotes)
|
212
|
+
remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\s)\(push\))}]
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.origin_push_url_https_dot_git(remotes)
|
216
|
+
remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\.)git(\s)\(push\))}]
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.origin_push_url_ssl_dot_git(remotes)
|
220
|
+
url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\.)git(\s)\(push\))}]
|
221
|
+
"https://#{url.gsub(':', '/')}" if url
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.origin_push_url_ssl(remotes)
|
225
|
+
url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\s)\(push\))}]
|
226
|
+
"https://#{url.gsub(':', '/')}" if url
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.add_remote(name, url)
|
230
|
+
Command.run('git', 'remote', 'add', name, url)
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.on_branch(branch)
|
234
|
+
keep_changes do
|
235
|
+
working_branch = current_branch
|
236
|
+
switch_needed = !working_branch.eql?(branch)
|
237
|
+
if switch_needed
|
238
|
+
result = checkout(branch)
|
239
|
+
create(branch) if result.stderr.eql?("error: pathspec '#{branch}' did not match any file(s) known to git.")
|
240
|
+
end
|
241
|
+
|
242
|
+
yield
|
243
|
+
|
244
|
+
checkout(working_branch) if switch_needed
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.keep_changes
|
249
|
+
status = status_dry
|
250
|
+
pop_stash = uncommitted_changes?(status)
|
251
|
+
stash if pop_stash
|
252
|
+
yield
|
253
|
+
stash_pop if pop_stash
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.git_projects
|
257
|
+
Fs.ls_dirs.select do |dir|
|
258
|
+
Dir.chdir(dir) do
|
259
|
+
is_git?(status_dry)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def self.branch_pushed(branch)
|
265
|
+
Command.run_dry('git', 'log', branch, '--not', '--remotes').stdout.empty?
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.branches
|
269
|
+
branches = Command.run_dry('git', 'for-each-ref', "--format='%(refname)'", 'refs/heads/').stdout
|
270
|
+
branches.each_line.map { |line| line.strip[%r{(?<='refs/heads/)(.*)(?=')}] }
|
271
|
+
end
|
272
|
+
|
273
|
+
def self.push
|
274
|
+
Command.run('git', 'push')
|
275
|
+
end
|
276
|
+
|
277
|
+
def self.rebase(branch)
|
278
|
+
Command.run('git', 'rebase', branch)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
metadata
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: autowow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- thisismydesign
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: easy_logging
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.3.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: colorize
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: time_difference
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.5.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.5.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: launchy
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.15'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.15'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '10.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '10.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: guard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: guard-bundler
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: guard-rspec
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: coveralls
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
description:
|
182
|
+
email:
|
183
|
+
- thisismydesign@users.noreply.github.com
|
184
|
+
executables:
|
185
|
+
- autowow
|
186
|
+
- aw
|
187
|
+
extensions: []
|
188
|
+
extra_rdoc_files: []
|
189
|
+
files:
|
190
|
+
- ".gitignore"
|
191
|
+
- ".rspec"
|
192
|
+
- ".travis.yml"
|
193
|
+
- Gemfile
|
194
|
+
- Guardfile
|
195
|
+
- LICENSE.txt
|
196
|
+
- README.md
|
197
|
+
- Rakefile
|
198
|
+
- autowow.gemspec
|
199
|
+
- bin/autowow
|
200
|
+
- bin/aw
|
201
|
+
- bin/console
|
202
|
+
- bin/setup
|
203
|
+
- lib/autowow.rb
|
204
|
+
- lib/autowow/cli.rb
|
205
|
+
- lib/autowow/command.rb
|
206
|
+
- lib/autowow/decorators/string_decorator.rb
|
207
|
+
- lib/autowow/fs.rb
|
208
|
+
- lib/autowow/gem.rb
|
209
|
+
- lib/autowow/log_formatter.rb
|
210
|
+
- lib/autowow/time_difference.rb
|
211
|
+
- lib/autowow/vcs.rb
|
212
|
+
- lib/autowow/version.rb
|
213
|
+
homepage: https://github.com/thisismydesign/autowow
|
214
|
+
licenses:
|
215
|
+
- MIT
|
216
|
+
metadata: {}
|
217
|
+
post_install_message:
|
218
|
+
rdoc_options: []
|
219
|
+
require_paths:
|
220
|
+
- lib
|
221
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
222
|
+
requirements:
|
223
|
+
- - ">="
|
224
|
+
- !ruby/object:Gem::Version
|
225
|
+
version: '0'
|
226
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
227
|
+
requirements:
|
228
|
+
- - ">="
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '0'
|
231
|
+
requirements: []
|
232
|
+
rubyforge_project:
|
233
|
+
rubygems_version: 2.6.11
|
234
|
+
signing_key:
|
235
|
+
specification_version: 4
|
236
|
+
summary: Set of commands to AUTOmate Way Of Working
|
237
|
+
test_files: []
|