git_curate 0.7.1 → 0.7.2

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
  SHA256:
3
- metadata.gz: a6996bc4363bed00c0ed66d7ac49427a681f633e199e3b8f7b0e504bed64af52
4
- data.tar.gz: 5a1766c37e6460a3b6081ba876443359b8fe01c44a898e234a4a96527ac13306
3
+ metadata.gz: 81714cf7534f51e20f211a41f3b93173c51fc066c8605e8fdd3c4b2887208309
4
+ data.tar.gz: 2325066c76763a77dcc7734120da18d0cc71083b5f8586d4d10d8ae7cf647abd
5
5
  SHA512:
6
- metadata.gz: ed0bee6ea221fb9d3a50b8432151409604d0fee5ee63c7c423daf19fade386bc43a1c5d2645a178bdf9bc873d4318ec82d9f2468506f9b0643a59b9f48308f59
7
- data.tar.gz: 909f9f0e1bc401c5e110a830b910192fd7fc959fc457ab8b1d19e5d7db0b1b0e6b1aa7a2d558f4d253b70a0ceb6dd8633d2152f7eaaf670c10355bee3255f321
6
+ metadata.gz: f2a73379b744efb995a7aef5727f9be1b77617b26395f649eea0d16cfda72da49d675bfd8f270873e3d478918180e93d45e10fa43078f08da0d0eab3b43ad38c
7
+ data.tar.gz: 822e799c402d44bd7bb7d7e32d12c88bf411385e628d3d2bfdfba9479b95c7d3b5e4457903f13df2fecabe9508cd439cfa5c753f15bbff43c6a0b94fd0d6d6bf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.7.2
4
+
5
+ * Nicer error messages; for example, if `git curate` is run outside of a git repo, the user will now see
6
+ the same error message as they would see from running `git branch` (as opposed to seeing an unsightly stack trace)
7
+ * Performance improvements: it now runs faster, as the number of system calls made by the
8
+ application is reduced
9
+ * Improvements to code structure and internal documentation
10
+
3
11
  ### v0.7.1
4
12
 
5
13
  * Fix errors on -h, -v and --version options due to incorrect exit code handling
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.1
1
+ 0.7.2
data/lib/git_curate.rb CHANGED
@@ -2,6 +2,7 @@ require "git_curate/app"
2
2
  require "git_curate/branch"
3
3
  require "git_curate/cli_parser"
4
4
  require "git_curate/copyright"
5
+ require "git_curate/exceptions"
5
6
  require "git_curate/runner"
6
7
  require "git_curate/util"
7
8
  require "git_curate/version"
@@ -4,17 +4,23 @@ module GitCurate
4
4
 
5
5
  module App
6
6
 
7
+ # Runs the application and returns an exit status which should be passed to exit() by
8
+ # the caller of this function.
7
9
  def self.main
8
10
  parser = GitCurate::CLIParser.new
9
11
  continue = parser.parse(ARGV) # will throw on error
10
- return 0 unless continue
12
+ return EXIT_SUCCESS unless continue
11
13
 
12
14
  runner = GitCurate::Runner.new(parser.parsed_options)
15
+ # Args not parsed by the CLIParser are passed to Runner.
13
16
  runner.run(ARGV)
17
+ rescue SystemCommandError => error
18
+ $stderr.puts(error.message)
19
+ error.exit_status
14
20
  rescue OptionParser::InvalidOption
15
21
  puts "Unrecognized option"
16
22
  puts "For help, enter `git curate -h`"
17
- 1
23
+ EXIT_FAILURE
18
24
  end
19
25
 
20
26
  end
@@ -10,21 +10,30 @@ module GitCurate
10
10
  LEADING_STAR_REGEX = /^\* /
11
11
  REMOTE_INFO_REGEX = /^[^\s]+\s+[^\s]+\s+\[(.+?)\]/
12
12
 
13
+ # Returns the branch name, with "* " prefixed if it's the current branch.
13
14
  attr_reader :raw_name
14
15
 
15
- # raw_name should start in "* " if the current branch, but should otherwise have not whitespace.
16
- def initialize(raw_name)
17
- @raw_name = raw_name
18
- end
16
+ # Returns a human-friendly string describing the status of the branch relative to the upstream branch
17
+ # it's tracking, if any.
18
+ attr_reader :upstream_info
19
19
 
20
+ # Returns simply the name of the branch, without any other "decoration".
20
21
  def proper_name
21
22
  @proper_name ||= @raw_name.lstrip.sub(CURRENT_BRANCH_REGEX, '')
22
23
  end
23
24
 
25
+ # Returns truthy if and only if this is the currently checked out branch.
24
26
  def current?
25
27
  @current ||= (@raw_name =~ CURRENT_BRANCH_REGEX)
26
28
  end
27
29
 
30
+ # Return truthy if and only if this branch has been merged into the current HEAD.
31
+ def merged?
32
+ @merged
33
+ end
34
+
35
+ # Returns the branch's name, with a prefix of "* " if it's the currently checked out branch, or else a prefix
36
+ # of " ". Branch displayable names are designed to be aligned with each other for display in a vertical column.
28
37
  def displayable_name(pad:)
29
38
  if pad && !current?
30
39
  " #{@raw_name}"
@@ -33,58 +42,74 @@ module GitCurate
33
42
  end
34
43
  end
35
44
 
36
- def last_author
37
- Util.command_output("git log -n1 --format=format:%an #{proper_name} --")
45
+ def last_commit_date
46
+ initialize_last_commit_data
47
+ @last_commit_date
38
48
  end
39
49
 
40
- def last_commit_date
41
- Util.command_output("git log -n1 --date=short --format=format:%cd #{proper_name} --")
50
+ def last_author
51
+ initialize_last_commit_data
52
+ @last_author
42
53
  end
43
54
 
44
55
  def last_subject
45
- Util.command_output("git log -n1 --format=format:%s #{proper_name} --")
56
+ initialize_last_commit_data
57
+ @last_subject
46
58
  end
47
59
 
48
60
  # Returns the local branches
49
61
  def self.local
50
- command_to_branches("git branch")
51
- end
62
+ merged_branch_raw_names = Util.command_to_a("git branch --merged").to_set
52
63
 
53
- # Returns local branches that are merged into current HEAD
54
- def self.local_merged
55
- command_to_branches("git branch --merged")
64
+ branch_info.map do |raw_name, info|
65
+ new(raw_name, merged: merged_branch_raw_names.include?(raw_name), upstream_info: info)
66
+ end
56
67
  end
57
68
 
58
- # Returns a Hash containing, as keys, the proper names of all local branches that have upstream branches,
59
- # and, as values, a brief description of each branch's status relative to its upstream
60
- # branch (up to date, or ahead/behind)
61
- def self.upstream_info
69
+ private
70
+
71
+ # Returns a Hash containing, as keys, the raw names of all local branches and, as values,
72
+ # a brief description of each branch's status relative to its upstream branch (up to
73
+ # date, or ahead/behind).
74
+ def self.branch_info
62
75
  Util.command_to_a("git branch -vv").map do |line|
63
- line.gsub!(LEADING_STAR_REGEX, "")
64
- branch_name = line.split(BRANCH_NAME_REGEX)[0]
65
- remote_info = line[REMOTE_INFO_REGEX, 1]
66
- if remote_info.nil?
67
- nil
68
- else
69
- comparison_raw = remote_info.split(":")
70
- comparison = if comparison_raw.length < 2
71
- "Up to date"
72
- else
73
- comparison_raw[1].strip.capitalize
74
- end
75
- [branch_name, comparison]
76
- end
77
- end.compact.to_h
76
+ line_is_current_branch = (line =~ LEADING_STAR_REGEX)
77
+ tidied_line = (line_is_current_branch ? line.gsub(LEADING_STAR_REGEX, "") : line)
78
+ proper_branch_name = tidied_line.split(BRANCH_NAME_REGEX)[0]
79
+ raw_branch_name = (line_is_current_branch ? "* #{proper_branch_name}" : proper_branch_name)
80
+ remote_info = tidied_line[REMOTE_INFO_REGEX, 1]
81
+ upstream_info =
82
+ if remote_info.nil?
83
+ "No upstream"
84
+ else
85
+ comparison_raw = remote_info.split(":")
86
+ comparison_raw.length < 2 ? "Up to date" : comparison_raw[1].strip.capitalize
87
+ end
88
+ [raw_branch_name, upstream_info]
89
+ end.to_h
78
90
  end
79
91
 
80
92
  def self.delete_multi(*branches)
81
93
  Util.command_output("git branch -D #{branches.map(&:proper_name).join(" ")} --")
82
94
  end
83
95
 
84
- private
96
+ # raw_name should start in "* " if the current branch, but should otherwise have not whitespace.
97
+ def initialize(raw_name, merged:, upstream_info:)
98
+ @raw_name = raw_name
99
+ @merged = merged
100
+ @upstream_info = upstream_info
101
+ end
102
+
103
+ # Returns an array with [date, author, subject], each as a string.
104
+ def initialize_last_commit_data
105
+ return if @last_commit_data
106
+
107
+ # For Windows compatibility we need double quotes around the format string, as well as spaces
108
+ # between the placeholders.
109
+ command = %Q(git log -n1 --date=short --format=format:"%cd %n %an %n %s" #{proper_name} --)
110
+ @last_commit_data = Util.command_to_a(command)
85
111
 
86
- def self.command_to_branches(command)
87
- Util.command_to_a(command).map { |raw_branch_name| self.new(raw_branch_name) }
112
+ @last_commit_date, @last_author, @last_subject = @last_commit_data
88
113
  end
89
114
 
90
115
  end
@@ -10,8 +10,10 @@ module GitCurate
10
10
  @parsed_options = {}
11
11
  end
12
12
 
13
- # Sets @parsed_options according to the options received, and return truthy
14
- # if and only if the program should continue after the options are passed.
13
+ # Sets @parsed_options according to the options received, and return truthy if and only if the program should
14
+ # continue after the options are passed. Usually this method should be passed ARGV. Any elements of `options` that
15
+ # are not processed by this method, will remain in `options`; elements that are processed, will be destructively
16
+ # removed from `options`.
15
17
  def parse(options)
16
18
  opt_parser = OptionParser.new do |opts|
17
19
  opts.banner = <<-EOF
@@ -0,0 +1,16 @@
1
+ module GitCurate
2
+
3
+ EXIT_SUCCESS = 0
4
+ EXIT_FAILURE = 1
5
+
6
+ # Error indicating that a system command has exited with a non-success exit status.
7
+ class SystemCommandError < StandardError
8
+
9
+ attr_reader :exit_status
10
+
11
+ def initialize(message, exit_status)
12
+ super(message)
13
+ @exit_status = exit_status
14
+ end
15
+ end
16
+ end
@@ -5,15 +5,21 @@ require "tty-screen"
5
5
 
6
6
  module GitCurate
7
7
 
8
- EXIT_SUCCESS = 0
9
- EXIT_FAILURE = 1
10
-
8
+ # Contains the main logic of the application.
11
9
  class Runner
12
10
 
11
+ # Accepts a Hash of options passed from the parsed command line. Currently there is only one option, :list,
12
+ # which is treated as a boolean, and determines whether the branches will simply be listed non-interactively
13
+ # (list: true), or interactively with opportunities for the user to select branches for deletion (list: false).
13
14
  def initialize(opts)
14
15
  @opts = opts
15
16
  end
16
17
 
18
+ # Runs the application, listing branches either interactively or non-interactively. Returns an exit status,
19
+ # suitable for passing to `exit()`.
20
+ # `args` should be passed an array of non-option/non-flag arguments received from the command
21
+ # line. If this array is of inappropriate length, EXIT_FAILURE will be returned. (The
22
+ # appropriate length may be 0.)
17
23
  def run(args)
18
24
  if args.length != 0
19
25
  $stderr.puts "This script does not accept any arguments."
@@ -22,8 +28,6 @@ module GitCurate
22
28
 
23
29
  branches = Branch.local
24
30
  branches.reject!(&:current?) if interactive?
25
- merged_branch_names = Branch.local_merged.map(&:proper_name).to_set
26
- upstream_branches = Branch.upstream_info
27
31
 
28
32
  table = Tabulo::Table.new(branches, vertical_rule_character: " ", intersection_character: " ",
29
33
  horizontal_rule_character: "-", column_padding: 0, align_header: :left) do |t|
@@ -32,8 +36,8 @@ module GitCurate
32
36
  t.add_column("Last commit", &:last_commit_date)
33
37
  t.add_column("Last author", &:last_author)
34
38
  t.add_column("Last subject", &:last_subject)
35
- t.add_column("Merged#{$/}into HEAD?") { |b| merged_branch_names.include?(b.proper_name) ? "Merged" : "Not merged" }
36
- t.add_column("Status vs#{$/}upstream") { |b| upstream_branches.fetch(b.proper_name, "No upstream") }
39
+ t.add_column("Merged#{$/}into HEAD?") { |b| b.merged? ? "Merged" : "Not merged" }
40
+ t.add_column("Status vs#{$/}upstream", &:upstream_info)
37
41
  end
38
42
 
39
43
  prompt = " Delete? [y/N/done/abort/help] "
@@ -15,9 +15,10 @@ module GitCurate
15
15
  # output as its message.
16
16
  def self.command_output(command)
17
17
  stdout_str, stderr_str, status = Open3.capture3(command)
18
+ exit_status = status.exitstatus
18
19
 
19
- if status.exitstatus != 0
20
- raise RuntimeError.new(stderr_str)
20
+ if exit_status != EXIT_SUCCESS
21
+ raise SystemCommandError.new(stderr_str, exit_status)
21
22
  end
22
23
 
23
24
  stdout_str
@@ -1,3 +1,3 @@
1
1
  module GitCurate
2
- VERSION = "0.7.1"
2
+ VERSION = "0.7.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git_curate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Harvey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-03 00:00:00.000000000 Z
11
+ date: 2019-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -163,6 +163,7 @@ files:
163
163
  - lib/git_curate/branch.rb
164
164
  - lib/git_curate/cli_parser.rb
165
165
  - lib/git_curate/copyright.rb
166
+ - lib/git_curate/exceptions.rb
166
167
  - lib/git_curate/runner.rb
167
168
  - lib/git_curate/util.rb
168
169
  - lib/git_curate/version.rb