story_branch 0.4.0 → 0.4.1

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: 46d477c2727de7887ac34b2d2e4a5bb1082db251
4
- data.tar.gz: 4e0e0c00d4eb031bd680c39476e880e71fc98adb
3
+ metadata.gz: 3d7d4d41d52f404121543545261d35ee4549bf4c
4
+ data.tar.gz: 501c4cbf920c86d5d3e161e232670b2e3f813570
5
5
  SHA512:
6
- metadata.gz: b39a085f360679091be09fdacf51d5905ec1f85293ea1f4987c9b3fd4dd2f87a663308182bcd23c9b465fff2c408d7514fc349469b55bebcf9d469518b366123
7
- data.tar.gz: c8adc6a6c9c6630ff2868573654898cf0b1e4541bdc108701a4a467bf3df004c9e22eeeeb11ce3b5aa6d7f6dffb863a7d16623da071360e939bc13827b76061e
6
+ metadata.gz: d41edd251bb11ec5adb61e0e852574f6d3bd27a7d475a00c91d025e8a6bed44f1c2d7356c169b0eb0c4eab864de1f270d3c410a357564f378b948b186e9526d7
7
+ data.tar.gz: 3d3016b1ae2ea18df7b6c7c5cf1730d7bb011567fd55eec5866698e4dfff8456d45a508fd3095a6d46ea864c194dbcaa21da359d06279332b1298a0f02b455d0
data/Gemfile.lock CHANGED
@@ -1,9 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- story_branch (0.4.0)
4
+ story_branch (0.4.1)
5
5
  blanket_wrapper (~> 3.0)
6
- git (~> 1.2)
7
6
  levenshtein-ffi (~> 1.0)
8
7
  pastel (~> 0.7.2)
9
8
  thor (~> 0.20.0)
@@ -23,10 +22,14 @@ GEM
23
22
  fakefs (0.18.0)
24
23
  ffi (1.9.25)
25
24
  git (1.5.0)
26
- httparty (0.16.2)
25
+ httparty (0.16.3)
26
+ mime-types (~> 3.0)
27
27
  multi_xml (>= 0.5.2)
28
28
  levenshtein-ffi (1.1.0)
29
29
  ffi (~> 1.9)
30
+ mime-types (3.2.2)
31
+ mime-types-data (~> 3.2015)
32
+ mime-types-data (3.2018.0812)
30
33
  multi_xml (0.6.0)
31
34
  necromancer (0.4.0)
32
35
  pastel (0.7.2)
@@ -54,7 +57,7 @@ GEM
54
57
  unicode-display_width (~> 1.4.0)
55
58
  unicode_utils (~> 1.4.0)
56
59
  strings-ansi (0.1.0)
57
- thor (0.20.0)
60
+ thor (0.20.3)
58
61
  timers (4.2.0)
59
62
  tty-color (0.4.3)
60
63
  tty-command (0.8.2)
@@ -87,6 +90,7 @@ PLATFORMS
87
90
  DEPENDENCIES
88
91
  bundler (~> 1.16)
89
92
  fakefs (~> 0.14)
93
+ git (~> 1.5)
90
94
  rake (~> 10.0)
91
95
  rspec (~> 3)
92
96
  rspec_junit_formatter (~> 0.4)
@@ -1,21 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'git'
4
3
  require 'levenshtein'
4
+ require_relative './git_wrapper'
5
5
 
6
6
  module StoryBranch
7
7
  # Class used to interact with git. It relies on git gem as the wrapper
8
8
  # and levenshtein algo to determine branch name proximity
9
9
  class GitUtils
10
- def self.g
11
- ::Git.open '.'
12
- end
13
-
14
10
  def self.existing_branch?(name)
15
- branch_names.each do |n|
11
+ GitWrapper.branch_names.each do |n|
16
12
  return true if Levenshtein.distance(n, name) < 3
17
- branch_name_match = n.match(/(.*)(-[1-9][0-9]+$)/)
13
+
14
+ branch_name_match = n.match(/(.*)(-[1-9]+[0-9]*$)/)
18
15
  next unless branch_name_match
16
+
19
17
  levenshtein_distance = Levenshtein.distance branch_name_match[1], name
20
18
  return true if levenshtein_distance < 3
21
19
  end
@@ -23,64 +21,31 @@ module StoryBranch
23
21
  end
24
22
 
25
23
  def self.branch_for_story_exists?(id)
26
- branch_names.each do |n|
27
- branch_id = n.match(/-[1-9][0-9]+$/)
24
+ GitWrapper.branch_names.each do |n|
25
+ branch_id = n.match(/-[1-9]+[0-9]*$/)
28
26
  next unless branch_id
29
27
  return true if branch_id.to_s == "-#{id}"
30
28
  end
31
29
  false
32
30
  end
33
31
 
34
- def self.branch_names
35
- g.branches.map(&:name)
36
- end
37
-
38
- def self.current_branch
39
- g.current_branch
40
- end
41
-
42
32
  def self.current_story
43
- /(.*)-(\d+$)/.match current_branch
33
+ /(.*)-(\d+$)/.match GitWrapper.current_branch
44
34
  end
45
35
 
46
36
  def self.current_branch_story_parts
47
37
  matches = current_story
48
38
  return {} unless matches.length == 3
49
- { title: matches[1], id: matches[2].to_i }
50
- end
51
-
52
- def self.create_branch(name)
53
- g.branch(name).create
54
- g.branch(name).checkout
55
- end
56
-
57
- def self.status_collect(status, regex)
58
- chosen_stati = status.select { |e| e.match(regex) }
59
- chosen_stati.map { |e| e.match(regex)[1] }
60
- end
61
39
 
62
- def self.status
63
- modified_rx = /^ M (.*)/
64
- untracked_rx = /^\?\? (.*)/
65
- staged_rx = /^M (.*)/
66
- added_rx = /^A (.*)/
67
- status = g.lib.send(:command, 'status', '-s').lines
68
- return nil if status.empty?
69
- {
70
- modified: status_collect(status, modified_rx),
71
- untracked: status_collect(status, untracked_rx),
72
- added: status_collect(status, added_rx),
73
- staged: status_collect(status, staged_rx)
74
- }
40
+ title = matches[1].tr('-', ' ')
41
+ { title: title, id: matches[2].to_i }
75
42
  end
76
43
 
77
44
  def self.status?(state)
45
+ status = GitWrapper.status
78
46
  return false unless status
79
- !status[state].empty?
80
- end
81
47
 
82
- def self.commit(message)
83
- g.commit(message)
48
+ !status[state].empty?
84
49
  end
85
50
  end
86
51
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: Consider extracting this to a separate gem
4
+ module StoryBranch
5
+ # GitWrapper to help running git commands with direct system calls
6
+ # Essentially it provides a couple of commands to interact with git
7
+ # - StoryBranch::GitWrapper.command('<cmd>', [<opts>])
8
+ # Returns the output as is
9
+ #
10
+ # - StoryBranch::GitWrapper.command_lines('<cmd>', [<opts>])
11
+ # Returns the output split into an array of lines, stripped and chomped
12
+ #
13
+ # - StoryBranch::GitWrapper.branch_names
14
+ # Returns the list of available branch names, locally and configured remotes
15
+ class GitWrapper
16
+ STATI_MATCHERS = {
17
+ modified_rx: /^ M (.*)/,
18
+ untracked_rx: /^\?\? (.*)/,
19
+ staged_rx: /^M (.*)/,
20
+ added_rx: /^A (.*)/
21
+ }.freeze
22
+
23
+ def self.command(cmd, opts = [])
24
+ gw = new
25
+ gw.call(cmd, opts)
26
+ end
27
+
28
+ def self.command_lines(cmd, opts = [])
29
+ result = command(cmd, opts)
30
+ lines = result.split("\n")
31
+ lines.each(&:strip!)
32
+ end
33
+
34
+ def self.branch_names
35
+ # NOTE: Regex matcher for cases as:
36
+ # remotes/origin/allow.... <- remote branch (remove 'remotes/origin')
37
+ # * allow.... <- * indicates current branch (remove '* ')
38
+ # allow <- local branch (do nothing)
39
+ regex = %r{(^remotes\/.*\/|\s|[*])}
40
+ all_branches.map do |line|
41
+ line = line.sub(regex, '')
42
+ line
43
+ end
44
+ end
45
+
46
+ def self.current_branch
47
+ current_branch_line = all_branches.detect do |line|
48
+ line.match(/\*/)
49
+ end
50
+ current_branch_line.tr('*', ' ')
51
+ end
52
+
53
+ def self.all_branches
54
+ command_lines('branch', '-a')
55
+ end
56
+
57
+ def self.create_branch(name)
58
+ command('checkout', ['-b', name])
59
+ end
60
+
61
+ def self.status
62
+ g_status = command_lines('status', '-s')
63
+ return nil if g_status.empty?
64
+
65
+ {
66
+ modified: status_collect(g_status, STATI_MATCHERS[:modified_rx]),
67
+ untracked: status_collect(g_status, STATI_MATCHERS[:untracked_rx]),
68
+ added: status_collect(g_status, STATI_MATCHERS[:added_rx]),
69
+ staged: status_collect(g_status, STATI_MATCHERS[:staged_rx])
70
+ }
71
+ end
72
+
73
+ def self.status_collect(status, regex)
74
+ chosen_stati = status.select { |e| e.match(regex) }
75
+ chosen_stati.map { |e| e.match(regex)[1] }
76
+ end
77
+
78
+ def self.commit(message)
79
+ command('commit', ['-m', message])
80
+ end
81
+
82
+ def initialize
83
+ @system_git = 'git'
84
+ end
85
+
86
+ def call(cmd, opts = [])
87
+ opts = prepare_opts(opts)
88
+ git_cmd = "#{@system_git} #{cmd} #{opts}"
89
+ `#{git_cmd}`.chomp.strip
90
+ end
91
+
92
+ private
93
+
94
+ # NOTE: Taken from ruby git gem
95
+ def escape(str = '')
96
+ str = str.to_s
97
+ return "'#{str.gsub('\'', '\'"\'"\'')}'" if RUBY_PLATFORM !~ /mingw|mswin/
98
+
99
+ # Keeping the old escape format for windows users
100
+ escaped = str.gsub('\'', '\'\\\'\'')
101
+ %("#{escaped}")
102
+ end
103
+
104
+ def prepare_opts(opts = [])
105
+ [opts].flatten.map { |s| escape(s) }.join(' ')
106
+ end
107
+ end
108
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative './pivotal_utils'
4
4
  require_relative './git_utils'
5
+ require_relative './git_wrapper'
5
6
  require_relative './config_manager'
6
7
  require 'tty-prompt'
7
8
 
@@ -65,7 +66,7 @@ module StoryBranch
65
66
  commit_message = "[#{finish_tag} ##{current_story[:id]}] #{current_story[:title]}"
66
67
  proceed = prompt.yes?("Commit with standard message? #{commit_message}")
67
68
  if proceed
68
- GitUtils.commit commit_message
69
+ GitWrapper.commit commit_message
69
70
  else
70
71
  prompt.say 'Aborted'
71
72
  end
@@ -130,7 +131,7 @@ module StoryBranch
130
131
  def create_feature_branch(story)
131
132
  return if story.nil?
132
133
 
133
- current_branch = GitUtils.current_branch
134
+ current_branch = GitWrapper.current_branch
134
135
  prompt.say "You are checked out at: #{current_branch}"
135
136
  branch_name = prompt.ask('Provide a new branch name',
136
137
  default: story.dashed_title)
@@ -139,7 +140,7 @@ module StoryBranch
139
140
 
140
141
  feature_branch_name_with_story_id = "#{feature_branch_name}-#{story.id}"
141
142
  prompt.say("Creating: #{feature_branch_name_with_story_id} with #{current_branch} as parent")
142
- GitUtils.create_branch feature_branch_name_with_story_id
143
+ GitWrapper.create_branch feature_branch_name_with_story_id
143
144
  end
144
145
 
145
146
  # Branch name validation
@@ -6,6 +6,7 @@ require_relative './string_utils'
6
6
  module StoryBranch
7
7
  # PivotalTracker Story representation
8
8
  class Story
9
+ NON_ESTIMATED_TYPES = %w[chore bug release].freeze
9
10
  attr_accessor :title, :id
10
11
 
11
12
  def initialize(blanket_story, project)
@@ -13,6 +14,8 @@ module StoryBranch
13
14
  @story = blanket_story
14
15
  @title = blanket_story.name
15
16
  @id = blanket_story.id
17
+ @story_type = blanket_story.story_type
18
+ @estimate = blanket_story.estimate
16
19
  end
17
20
 
18
21
  def update_state(new_state)
@@ -27,6 +30,11 @@ module StoryBranch
27
30
  def dashed_title
28
31
  StoryBranch::StringUtils.normalised_branch_name @title
29
32
  end
33
+
34
+ def estimated
35
+ (@story_type == 'feature' && !@estimate.nil?) ||
36
+ NON_ESTIMATED_TYPES.include?(@story_type)
37
+ end
30
38
  end
31
39
 
32
40
  # PivotalTracker Project representation
@@ -37,17 +45,19 @@ module StoryBranch
37
45
 
38
46
  # NOTE: takes in possible keys:
39
47
  # - with_state
40
- # - estimated
41
48
  # Returns an array of PT Stories (Story Class)
42
49
  # TODO: add other possible args
43
- def stories(options = {})
50
+ def stories(options = {}, estimated = true)
44
51
  stories = if options[:id]
45
52
  [@project.stories(options[:id])]
46
53
  else
47
54
  params = { with_state: options[:with_state] }
48
55
  @project.stories.get(params: params).payload
49
56
  end
50
- stories.map { |s| Story.new(s, @project) }
57
+ stories = stories.map { |s| Story.new(s, @project) }
58
+ return stories if estimated == false
59
+
60
+ stories.select(&:estimated)
51
61
  end
52
62
  end
53
63
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StoryBranch
4
- VERSION = '0.4.0'
4
+ VERSION = '0.4.1'
5
5
  end
data/story_branch.gemspec CHANGED
@@ -47,7 +47,6 @@ Gem::Specification.new do |spec|
47
47
  spec.require_paths = ['lib']
48
48
 
49
49
  spec.add_runtime_dependency 'blanket_wrapper', '~> 3.0'
50
- spec.add_runtime_dependency 'git', '~> 1.2'
51
50
  spec.add_runtime_dependency 'levenshtein-ffi', '~> 1.0'
52
51
  spec.add_runtime_dependency 'pastel', '~> 0.7.2'
53
52
  spec.add_runtime_dependency 'thor', '~> 0.20.0'
@@ -58,6 +57,7 @@ Gem::Specification.new do |spec|
58
57
 
59
58
  spec.add_development_dependency 'bundler', '~> 1.16'
60
59
  spec.add_development_dependency 'fakefs', '~> 0.14'
60
+ spec.add_development_dependency 'git', '~> 1.5'
61
61
  spec.add_development_dependency 'rake', '~> 10.0'
62
62
  spec.add_development_dependency 'rspec', '~> 3'
63
63
  spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: story_branch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rui Baltazar
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: exe
14
14
  cert_chain: []
15
- date: 2018-11-14 00:00:00.000000000 Z
15
+ date: 2018-11-23 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: blanket_wrapper
@@ -28,20 +28,6 @@ dependencies:
28
28
  - - "~>"
29
29
  - !ruby/object:Gem::Version
30
30
  version: '3.0'
31
- - !ruby/object:Gem::Dependency
32
- name: git
33
- requirement: !ruby/object:Gem::Requirement
34
- requirements:
35
- - - "~>"
36
- - !ruby/object:Gem::Version
37
- version: '1.2'
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- requirements:
42
- - - "~>"
43
- - !ruby/object:Gem::Version
44
- version: '1.2'
45
31
  - !ruby/object:Gem::Dependency
46
32
  name: levenshtein-ffi
47
33
  requirement: !ruby/object:Gem::Requirement
@@ -168,6 +154,20 @@ dependencies:
168
154
  - - "~>"
169
155
  - !ruby/object:Gem::Version
170
156
  version: '0.14'
157
+ - !ruby/object:Gem::Dependency
158
+ name: git
159
+ requirement: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - "~>"
162
+ - !ruby/object:Gem::Version
163
+ version: '1.5'
164
+ type: :development
165
+ prerelease: false
166
+ version_requirements: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - "~>"
169
+ - !ruby/object:Gem::Version
170
+ version: '1.5'
171
171
  - !ruby/object:Gem::Dependency
172
172
  name: rake
173
173
  requirement: !ruby/object:Gem::Requirement
@@ -260,6 +260,7 @@ files:
260
260
  - lib/story_branch/commands/unstart.rb
261
261
  - lib/story_branch/config_manager.rb
262
262
  - lib/story_branch/git_utils.rb
263
+ - lib/story_branch/git_wrapper.rb
263
264
  - lib/story_branch/main.rb
264
265
  - lib/story_branch/pivotal_utils.rb
265
266
  - lib/story_branch/string_utils.rb