story_branch 0.4.0 → 0.4.1

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: 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