git_recent 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in git_recent.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Tom Wey
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # GitRecent
2
+
3
+ [![Build Status](https://travis-ci.org/tjmw/ruby-GitRecent.png)](https://travis-ci.org/tjmw/ruby-GitRecent)
4
+ [![Code Climate](https://codeclimate.com/github/tjmw/ruby-GitRecent.png)](https://codeclimate.com/github/tjmw/ruby-GitRecent)
5
+
6
+ Command line tool (wrapped in a gem) for displaying and interacting with recently checked-out
7
+ git branches
8
+
9
+ ## Installation
10
+
11
+ $ gem install git_recent
12
+
13
+ ## Usage
14
+
15
+ $ git_recent
16
+ Commands:
17
+ git_recent checkout # Interactively checkout a recently checked-out git branch
18
+ git_recent help [COMMAND] # Describe available commands or one specific command
19
+ git_recent list # List recently checked-out git branches
20
+
21
+ Options:
22
+ [--max=N]
23
+ # Default: 5
24
+
25
+ I like to alias the following in my .gitconfig:
26
+
27
+ [alias]
28
+ recent = ! git_recent list
29
+ rc = ! git_recent checkout
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/git_recent ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Trap interrupts to quit cleanly
4
+ Signal.trap("INT") { exit 1 }
5
+
6
+ require 'git_recent'
7
+
8
+ GitRecent::Cli.start ARGV
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'git_recent/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'git_recent'
8
+ spec.version = GitRecent::VERSION
9
+ spec.authors = ['Tom Wey']
10
+ spec.email = ['tjmwey@gmail.com']
11
+ spec.description = %q{CLI for displaying and interacting with recently checked-out git branches}
12
+ spec.summary = %q{List and interactively checkout recently checked-out git branches}
13
+ spec.homepage = 'https://github.com/tjmw/ruby-GitRecent'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.required_ruby_version = '>= 1.9.2'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.3'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+
27
+ spec.add_dependency 'git', '~> 1.2.5'
28
+ spec.add_dependency 'thor', '~> 0.18.1'
29
+ end
@@ -0,0 +1,38 @@
1
+ module GitRecent
2
+ class BranchChooser
3
+ private
4
+
5
+ attr_reader :choices, :input_stream, :output_stream
6
+
7
+ public
8
+
9
+ def initialize(choices, input_stream=$stdin, output_stream=$stdout)
10
+ @choices = choices
11
+ @input_stream = input_stream
12
+ @output_stream = output_stream
13
+ end
14
+
15
+ def request_choice
16
+ display_choices
17
+
18
+ output_stream.print 'Please select a branch: '
19
+ input = input_stream.gets.strip
20
+ choice_index = input.to_i - 1
21
+
22
+ return choices.fetch(choice_index) if valid_choice?(choice_index)
23
+ nil
24
+ end
25
+
26
+ private
27
+
28
+ def display_choices
29
+ choices.each_with_index do |branch, i|
30
+ output_stream.puts "[#{i+1}] #{branch}"
31
+ end
32
+ end
33
+
34
+ def valid_choice?(choice_index)
35
+ (0..choices.length-1).cover? choice_index
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ module GitRecent
2
+ class BranchLister
3
+ private
4
+
5
+ attr_accessor :local_branches, :reflog_iterator
6
+
7
+ public
8
+
9
+ def initialize(reflog_iterator=GitRecent::ReflogIterator.new,
10
+ local_branches=GitRecent::LocalBranchReader.local_branches_excluding_current)
11
+ @reflog_iterator = reflog_iterator
12
+ @local_branches = local_branches
13
+ end
14
+
15
+ def branch_names(max)
16
+ @recent_branch_names ||= begin
17
+ recent_branches = {}
18
+
19
+ reflog_iterator.each do |reflog_line|
20
+ to_branch = reflog_line.to_branch
21
+ recent_branches[to_branch] = true if should_include_branch? to_branch
22
+
23
+ return recent_branches.keys if recent_branches.keys.length == max
24
+
25
+ from_branch = reflog_line.from_branch
26
+ recent_branches[from_branch] = true if should_include_branch? from_branch
27
+
28
+ return recent_branches.keys if recent_branches.keys.length == max
29
+ end
30
+
31
+ recent_branches.keys
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def local_branch_exists?(checked_out_entity)
38
+ local_branches.include? checked_out_entity
39
+ end
40
+
41
+ def should_include_branch?(branch)
42
+ branch and local_branch_exists? branch
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ module GitRecent
2
+ require 'thor'
3
+
4
+ class Cli < Thor
5
+ class_option :max, type: :numeric, default: 5
6
+
7
+ desc 'list', 'List recently checked-out git branches'
8
+ def list
9
+ recent_branch_names.each do |branch_name|
10
+ puts branch_name
11
+ end
12
+ end
13
+
14
+ desc 'checkout', 'Interactively checkout a recently checked-out git branch'
15
+ def checkout
16
+ chooser = GitRecent::BranchChooser.new recent_branch_names
17
+
18
+ selected_branch = chooser.request_choice
19
+
20
+ if selected_branch
21
+ Git.open('.').checkout(selected_branch)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def recent_branch_names
28
+ branch_lister = GitRecent::BranchLister.new
29
+ recent_branch_names = branch_lister.branch_names(options[:max].to_i)
30
+ abort 'No recent branches' if recent_branch_names.empty?
31
+ recent_branch_names
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ module GitRecent
2
+ class LocalBranchReader
3
+ class << self
4
+ def local_branches_excluding_current
5
+ local_branches.reject { |b| b.current }.map(&:name).to_set
6
+ end
7
+
8
+ private
9
+
10
+ def local_branches
11
+ g = Git.open('.')
12
+ g.branches.local
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module GitRecent
2
+ require 'open3'
3
+
4
+ class ReflogIterator
5
+ def each
6
+ Open3.pipeline_r('git reflog') do |o, ts|
7
+ o.each_line do |line_string|
8
+ yield GitRecent::ReflogLine.new(line_string)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module GitRecent
2
+ class ReflogLine
3
+ def initialize(line)
4
+ @line = line
5
+ end
6
+
7
+ def from_branch
8
+ return unless is_checkout?
9
+ split_line[-3]
10
+ end
11
+
12
+ def to_branch
13
+ return unless is_checkout?
14
+ split_line.last
15
+ end
16
+
17
+ private
18
+
19
+ def is_checkout?
20
+ @is_checkout ||= begin
21
+ split_line[2] == 'checkout:'
22
+ end
23
+ end
24
+
25
+ def split_line
26
+ @split_line ||= @line.split(' ')
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module GitRecent
2
+ VERSION = "0.0.1"
3
+ end
data/lib/git_recent.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'git'
2
+ require 'git_recent/branch_chooser'
3
+ require 'git_recent/branch_lister'
4
+ require 'git_recent/cli'
5
+ require 'git_recent/local_branch_reader'
6
+ require 'git_recent/reflog_iterator'
7
+ require 'git_recent/reflog_line'
8
+ require 'git_recent/version'
9
+ require 'set'
10
+
11
+ module GitRecent
12
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe GitRecent::BranchChooser do
4
+ let(:input_stream) { StringIO.new('2') }
5
+ let(:output_stream) { StringIO.new }
6
+
7
+ let(:chooser) {
8
+ GitRecent::BranchChooser.new(
9
+ %w(master my_feature production),
10
+ input_stream,
11
+ output_stream
12
+ )
13
+ }
14
+
15
+ describe '#request_choice' do
16
+ it 'presents the correct options' do
17
+ chooser.request_choice
18
+
19
+ output_stream.string.should include('[1] master')
20
+ output_stream.string.should include('[2] my_feature')
21
+ output_stream.string.should include('[3] production')
22
+ end
23
+
24
+ context 'when a valid branch is chosen' do
25
+ let(:input_stream) { StringIO.new('2') }
26
+
27
+ it 'returns the chosen branch name' do
28
+ chooser.request_choice.should eql('my_feature')
29
+ end
30
+ end
31
+
32
+ context 'when an invalid branch is chosen' do
33
+ let(:input_stream) { StringIO.new('foo') }
34
+
35
+ it 'returns nil' do
36
+ chooser.request_choice.should be_nil
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe GitRecent::BranchLister do
4
+ let(:lister) {
5
+ GitRecent::BranchLister.new(dummy_reflog_iterator, local_branches)
6
+ }
7
+
8
+ let(:dummy_reflog_iterator) {
9
+ reflog_lines.collect { |line| GitRecent::ReflogLine.new(line) }
10
+ }
11
+
12
+ let(:reflog_lines) {
13
+ [
14
+ '1e3b9a9 HEAD@{0}: checkout: moving from master to master',
15
+ '1e3b9a9 HEAD@{1}: commit: Remove unnecessary constructor',
16
+ '54b74f2 HEAD@{2}: checkout: moving from master to master',
17
+ '54b74f2 HEAD@{3}: commit: Add code climate to README',
18
+ '57372d4 HEAD@{4}: commit: Use git gem to checkout branches',
19
+ 'a7eb16d HEAD@{5}: checkout: moving from my_feature to master',
20
+ 'a7eb16d HEAD@{6}: checkout: moving from master to my_feature',
21
+ 'a7eb16d HEAD@{7}: checkout: moving from my_feature to master',
22
+ 'a7eb16d HEAD@{8}: checkout: moving from test to my_feature',
23
+ 'a7eb16d HEAD@{9}: checkout: moving from master to test'
24
+ ]
25
+ }
26
+
27
+ let(:local_branches) {
28
+ Set.new(%w(master my_feature test my_other_feature))
29
+ }
30
+
31
+ context 'when a max > than the number of branches is specified' do
32
+ it 'returns the correctly ordered list of branches' do
33
+ lister.branch_names(5).should eql(%w(master my_feature test))
34
+ end
35
+ end
36
+
37
+ context 'when a max < than the number of branches is specified' do
38
+ it 'returns the correctly ordered list of branches' do
39
+ lister.branch_names(2).should eql(%w(master my_feature))
40
+ end
41
+ end
42
+
43
+ context 'when a branch is renamed' do
44
+ let(:local_branches) {
45
+ Set.new(%w(master my_feature test my_other_feature new_branch_name))
46
+ }
47
+
48
+ let(:reflog_lines) {
49
+ [
50
+ '43eaa15 HEAD@{0}: checkout: moving from new_branch_name to master',
51
+ '43eaa15 HEAD@{1}: checkout: moving from master to initial_branch_name',
52
+ 'c2105f6 HEAD@{2}: checkout: moving from test to master',
53
+ '7f3d9cc HEAD@{3}: checkout: moving from master to test'
54
+ ]
55
+ }
56
+
57
+ it 'returns the correctly ordered list of branches' do
58
+ lister.branch_names(5).should eql(%w(master new_branch_name test))
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe GitRecent::ReflogLine do
4
+ let(:reflog_line) { GitRecent::ReflogLine.new(reflog_string) }
5
+
6
+ context 'when the reflog line is for a checkout' do
7
+ let(:reflog_string) { '061ead5 HEAD@{9}: checkout: moving from master to test' }
8
+
9
+ describe '#from_branch' do
10
+ it 'returns the correct branch name' do
11
+ reflog_line.from_branch.should eql('master')
12
+ end
13
+ end
14
+
15
+ describe '#to_branch' do
16
+ it 'returns the correct branch name' do
17
+ reflog_line.to_branch.should eql('test')
18
+ end
19
+ end
20
+ end
21
+
22
+ context 'when the reflog line is not for a checkout' do
23
+ let(:reflog_string) { 'c58e2b4 HEAD@{16}: commit: Initial commit' }
24
+
25
+ describe '#from_branch' do
26
+ it 'returns nil' do
27
+ reflog_line.from_branch.should be_nil
28
+ end
29
+ end
30
+
31
+ describe '#to_branch' do
32
+ it 'returns nil' do
33
+ reflog_line.to_branch.should be_nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1 @@
1
+ require 'git_recent'
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git_recent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tom Wey
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: git
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 1.2.5
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.2.5
78
+ - !ruby/object:Gem::Dependency
79
+ name: thor
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.18.1
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.18.1
94
+ description: CLI for displaying and interacting with recently checked-out git branches
95
+ email:
96
+ - tjmwey@gmail.com
97
+ executables:
98
+ - git_recent
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - .gitignore
103
+ - .travis.yml
104
+ - Gemfile
105
+ - LICENSE.txt
106
+ - README.md
107
+ - Rakefile
108
+ - bin/git_recent
109
+ - git_recent.gemspec
110
+ - lib/git_recent.rb
111
+ - lib/git_recent/branch_chooser.rb
112
+ - lib/git_recent/branch_lister.rb
113
+ - lib/git_recent/cli.rb
114
+ - lib/git_recent/local_branch_reader.rb
115
+ - lib/git_recent/reflog_iterator.rb
116
+ - lib/git_recent/reflog_line.rb
117
+ - lib/git_recent/version.rb
118
+ - spec/git_recent/branch_chooser_spec.rb
119
+ - spec/git_recent/branch_lister_spec.rb
120
+ - spec/git_recent/reflog_line_spec.rb
121
+ - spec/spec_helper.rb
122
+ homepage: https://github.com/tjmw/ruby-GitRecent
123
+ licenses:
124
+ - MIT
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: 1.9.2
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 1.8.25
144
+ signing_key:
145
+ specification_version: 3
146
+ summary: List and interactively checkout recently checked-out git branches
147
+ test_files:
148
+ - spec/git_recent/branch_chooser_spec.rb
149
+ - spec/git_recent/branch_lister_spec.rb
150
+ - spec/git_recent/reflog_line_spec.rb
151
+ - spec/spec_helper.rb