twig 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ _site/
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.8.7-p358@twig --create
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,26 @@
1
+ How to contribute
2
+ =================
3
+
4
+ Let's make life easier for people with lots of Git branches.
5
+
6
+ Found a bug or have a suggestion? [Please open an issue][issues] or ping
7
+ [@ronalddevera on Twitter][twitter].
8
+
9
+ If you want to hack on some code, even better! Here are the basics:
10
+
11
+ 1. Fork the Twig repo.
12
+ 2. Check out the [**`development`** branch][dev branch]; the `master` branch is
13
+ for stable builds only.
14
+ 3. Run the tests to make sure that they pass on your machine: `bundle && rake`
15
+ 4. Add one or more failing tests for your feature or bug fix.
16
+ 5. Write your feature or bug fix to make the test(s) pass.
17
+ 6. Test the change manually:
18
+ 1. `gem build twig.gemspec`
19
+ 2. `gem install twig-x.y.z.gem` (fill in the current version number)
20
+ 7. Push to your fork and submit a pull request.
21
+
22
+ Thanks for contributing!
23
+
24
+ [issues]: https://github.com/rondevera/twig/issues
25
+ [twitter]: https://twitter.com/ronalddevera
26
+ [dev branch]: https://github.com/rondevera/twig/commits/development
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/HISTORY.md ADDED
@@ -0,0 +1,6 @@
1
+ Twig
2
+ ====
3
+
4
+ 1.0
5
+ ---
6
+ * Initial release.
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ron DeVera
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,232 @@
1
+ Twig: Your personal Git branch assistant.
2
+ =========================================
3
+
4
+ It's hard enough trying to remember the names of all of your Git branches. You
5
+ also need those branches' issue tracker ids, issue statuses, and reminders of
6
+ what to do next with each branch. `git branch` only lists them in alphabetical
7
+ order, which just doesn't cut it.
8
+
9
+ **Twig shows you your most recent branches, and remembers branch details for
10
+ you.** It supports subcommands, like automatically fetching statuses from your
11
+ issue tracking system. It's flexible enough to fit your everyday Git workflow,
12
+ and will save you a ton of time.
13
+
14
+ Here's how Twig looks in action:
15
+
16
+ $ twig
17
+
18
+ issue status todo branch
19
+ ----- ------ ---- ------
20
+ 2013-01-26 18:00:21 (7m ago) 486 In progress Rebase optimize-all-the-things
21
+ 2013-01-26 16:49:21 (2h ago) 268 In progress - whitespace-all-the-things
22
+ 2013-01-23 18:35:21 (3d ago) 159 Shipped Test in prod * refactor-all-the-things
23
+ 2013-01-22 17:12:09 (4d ago) - - - development
24
+ 2013-01-20 19:45:42 (6d ago) - - - master
25
+
26
+
27
+ Installation
28
+ ============
29
+
30
+ gem install twig
31
+
32
+
33
+ Usage
34
+ =====
35
+
36
+ Twig lets you get/set custom properties for each branch, and list branches
37
+ chronologically with their properties.
38
+
39
+ * `twig`: List all branches with properties, newest first
40
+ * `twig <property>`: Get a property for the current branch
41
+ * `twig <property> <value>`: Set a property for the current branch
42
+ * `twig --unset <property>`: Unset a property for the current branch
43
+ * `twig <property> -b <branch>`: Get property for any branch
44
+ * `twig <property> <value> -b <branch>`: Set property for any branch
45
+ * `twig --unset <property> -b <branch>`: Unset property for any branch
46
+ * `twig --help`: More info
47
+
48
+
49
+ Filtering branches
50
+ ------------------
51
+
52
+ Twig lists all of your branches by default (newest first), but you can filter
53
+ them by name and age:
54
+
55
+ * `twig --only-branch <pattern>`:
56
+ Only list branches whose name matches a given pattern
57
+ * `twig --except-branch <pattern>`:
58
+ Don't list branches whose name matches a given pattern
59
+ * `twig --max-days-old <age>`:
60
+ Only list branches below a given age
61
+ * `twig --all`:
62
+ List all branches regardless of other filtering options
63
+
64
+ You can put your most frequently used options into `~/.twigrc`, and they'll be
65
+ automatically included when you run `twig`. Example:
66
+
67
+ # ~/.twigrc:
68
+ except-branch: staging
69
+ max-days-old: 30
70
+
71
+
72
+ Examples
73
+ --------
74
+
75
+ List your branches, and highlight the current branch:
76
+
77
+ $ twig
78
+
79
+ 2013-01-26 18:07:21 (7m ago) * refactor-all-the-things
80
+ 2013-01-24 17:12:09 (2d ago) development
81
+ 2013-01-23 19:45:42 (3d ago) master
82
+
83
+ Remember a branch's issue tracker id:
84
+
85
+ $ git checkout my-branch
86
+ Switched to branch 'my-branch'.
87
+
88
+ $ twig issue 123
89
+ Saved property "issue" as "123" for branch "my-branch".
90
+ # Nearly any property name will do, like "bug" or "ticket".
91
+
92
+ $ twig issue
93
+ 123
94
+
95
+ $ open "https://github.com/myname/myproject/issues/`twig issue`"
96
+ # Opens a browser window for this GitHub issue (in OS X).
97
+
98
+ Keep notes on what you need to do with each branch:
99
+
100
+ $ twig todo "Run tests"
101
+ Saved property "todo" as "Run tests" for branch "my-branch".
102
+
103
+ $ twig todo "Deploy" -b finished-branch
104
+ Saved property "todo" as "Deploy" for branch "finished-branch".
105
+
106
+ $ twig
107
+
108
+ todo branch
109
+ ---- ------
110
+ 2013-01-26 18:00:25 (7m ago) Run tests * my-branch
111
+ 2013-01-23 18:35:12 (3d ago) Deploy finished-branch
112
+ 2013-01-22 17:12:23 (4d ago) - master
113
+
114
+ Remember the order in which you were rebasing your stack of branches:
115
+
116
+ $ git checkout master
117
+ Switched to branch 'master'.
118
+
119
+ $ twig rebase-onto branch2 -b branch3
120
+ Saved property "rebase-onto" as "branch2" for branch "branch3".
121
+
122
+ $ twig rebase-onto branch1 -b branch2
123
+ Saved property "rebase-onto" as "branch1" for branch "branch2".
124
+
125
+ $ twig
126
+
127
+ rebase-onto branch
128
+ ----------- ------
129
+ 2013-01-26 18:00:25 (7m ago) branch2 branch3
130
+ 2013-01-26 16:49:47 (2h ago) branch1 branch2
131
+ 2013-01-23 18:35:12 (3d ago) - branch1
132
+ 2013-01-22 17:12:23 (4d ago) - * master
133
+
134
+ You can set just about any custom property you need to remember for each branch.
135
+
136
+
137
+ Subcommands
138
+ ===========
139
+
140
+ Twig comes with two subcommands, `gh-open` and `gh-update`, which are handy for
141
+ use with GitHub repositories.
142
+
143
+ While inside a Git repo, run `twig gh-open` to see the repo's GitHub URL, and open
144
+ a browser window if possible:
145
+
146
+ $ cd myproject
147
+
148
+ $ twig gh-open
149
+ GitHub URL: https://github.com/myname/myproject
150
+ # Also opens a browser window (OS X only).
151
+
152
+ If you're working on an issue for a GitHub repository, you can also use the
153
+ `gh-update` subcommand:
154
+
155
+ $ git checkout add-feature
156
+ Switched to branch 'add-feature'.
157
+
158
+ $ twig issue 222
159
+ Saved property "issue" as "222" for branch "add-feature".
160
+
161
+ $ twig
162
+
163
+ issue status branch
164
+ ----- ------ ------
165
+ 2013-01-26 18:00:25 (7m ago) 222 - * add-feature
166
+ 2013-01-23 18:35:12 (3d ago) 111 - fix-bug
167
+ 2013-01-22 17:12:23 (4d ago) - - master
168
+
169
+ $ twig gh-update
170
+ # Automatically looks up the GitHub issue status for each
171
+ # of your local branches, and saves it locally.
172
+
173
+ $ twig
174
+
175
+ issue status branch
176
+ ----- ------ ------
177
+ 2013-01-26 18:00:25 (7m ago) 222 open * add-feature
178
+ 2013-01-23 18:35:12 (3d ago) 111 closed fix-bug
179
+ 2013-01-22 17:12:23 (4d ago) - - master
180
+
181
+ Run `twig gh-update` periodically to keep up with GitHub issues locally.
182
+
183
+ You can write any Twig subcommand that fits your own Git workflow. To write a
184
+ Twig subcommand:
185
+
186
+ 1. Write a script. Any language will do. (If you want to take advantage of
187
+ Twig's option parsing and branch processing, you'll need Ruby. See
188
+ [`bin/twig-gh-update`][twig-gh-update] for an example.)
189
+ 2. Save it with the `twig-` prefix in your `$PATH`,
190
+ e.g., `~/bin/twig-my-subcommand`.
191
+ 3. Make it executable: `chmod ugo+x ~/bin/twig-my-subcommand`
192
+ 4. Run your subcommand: `twig my-subcommand` (with a *space* after `twig`)
193
+
194
+ [twig-gh-update]: https://github.com/rondevera/twig/blob/master/bin/twig-gh-update
195
+
196
+ Some ideas for subcommands:
197
+
198
+ * Get each branch's status for any issue tracking system that has an API,
199
+ like [JIRA](http://www.atlassian.com/software/jira/overview),
200
+ [FogBugz](http://www.fogcreek.com/fogbugz/), or
201
+ [Lighthouse](http://lighthouseapp.com/).
202
+ * Given an issue tracker id, check out that issue's branch locally. Great for
203
+ following teammates' branches, remembering their issue ids, and knowing when
204
+ they've shipped.
205
+ * Generate a formatted list of your branches from the past week. Useful for
206
+ emailing your team about what you're up to.
207
+
208
+ If you write a subcommand that others can appreciate, send a pull request or add
209
+ it to the [Twig wiki][wiki]!
210
+
211
+
212
+ More info
213
+ =========
214
+
215
+ - **Requirements:** Tested with Git 1.6.5 and Ruby 1.8.7. Probably works with
216
+ older software, but it's not guaranteed.
217
+ - **Contributing:** Found a bug or have a suggestion? [Please open an
218
+ issue][issues] or ping [@ronalddevera on Twitter][twitter]. If you want to
219
+ hack on some features or contribute a subcommand you've written, feel free to
220
+ fork and send a pull request for the **[development branch][dev branch]**.
221
+ (The master branch is for stable builds only.) See the full details in the
222
+ [Contributing][contributing] instructions.
223
+ - **History:** [History/changelog for Twig][history]
224
+ - **License:** Twig is released under the [MIT License][license].
225
+
226
+ [issues]: https://github.com/rondevera/twig/issues
227
+ [wiki]: https://github.com/rondevera/twig/wiki
228
+ [twitter]: https://twitter.com/ronalddevera
229
+ [dev branch]: https://github.com/rondevera/twig/commits/development
230
+ [contributing]: https://github.com/rondevera/twig/blob/master/CONTRIBUTING.md
231
+ [history]: https://github.com/rondevera/twig/blob/master/HISTORY.md
232
+ [license]: https://github.com/rondevera/twig/blob/master/LICENSE.md
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'rspec/core/rake_task'
3
+ require 'bundler'
4
+
5
+ desc 'Run specs'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+
10
+ Bundler::GemHelper.install_tasks
data/bin/twig ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twig')
4
+
5
+ twig = Twig.new
6
+ abort unless twig.repo?
7
+
8
+ # Gettin' twiggy wit' it.
9
+ twig.read_config_file!
10
+ twig.read_cli_args!(ARGV)
data/bin/twig-gh-open ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Opens a browser window for the current GitHub repo.
4
+ #
5
+ # Author: Ron DeVera <http://rondevera.com>
6
+
7
+ class TwigGithubRepo
8
+ def initialize
9
+ if origin_url.empty? || username.empty? || repository.empty?
10
+ abort_for_non_github_repo
11
+ end
12
+
13
+ yield(self)
14
+ end
15
+
16
+ def origin_url
17
+ @origin_url ||= `git config remote.origin.url`.strip
18
+ end
19
+
20
+ def origin_url_parts
21
+ @origin_url_parts ||= origin_url.split(/[\/:]/)
22
+ end
23
+
24
+ def username
25
+ @username ||= origin_url_parts[-2] || ''
26
+ end
27
+
28
+ def repository
29
+ @repo ||= origin_url_parts[-1].sub(/\.git$/, '') || ''
30
+ end
31
+
32
+ def abort_for_non_github_repo
33
+ abort 'This does not appear to be a GitHub repository.'
34
+ end
35
+ end
36
+
37
+ TwigGithubRepo.new do |gh_repo|
38
+ url = "https://github.com/#{gh_repo.username}/#{gh_repo.repository}"
39
+ puts "GitHub URL: #{url}"
40
+ `which open && open #{url}`
41
+ end
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Updates each branch with the latest issue status on GitHub.
4
+ #
5
+ # Author: Ron DeVera <http://rondevera.com>
6
+
7
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twig')
8
+ require 'rubygems'
9
+ require 'json'
10
+ require 'net/https'
11
+ require 'uri'
12
+
13
+ class TwigGithubRepo
14
+ def initialize
15
+ if origin_url.empty? || username.empty? || repository.empty?
16
+ abort_for_non_github_repo
17
+ end
18
+
19
+ yield(self)
20
+ end
21
+
22
+ def origin_url
23
+ @origin_url ||= `git config remote.origin.url`.strip
24
+ end
25
+
26
+ def origin_url_parts
27
+ @origin_url_parts ||= origin_url.split(/[\/:]/)
28
+ end
29
+
30
+ def username
31
+ @username ||= origin_url_parts[-2] || ''
32
+ end
33
+
34
+ def repository
35
+ @repo ||= origin_url_parts[-1].sub(/\.git$/, '') || ''
36
+ end
37
+
38
+ def abort_for_non_github_repo
39
+ abort 'This does not appear to be a GitHub repository.'
40
+ end
41
+ end
42
+
43
+ twig = Twig.new
44
+ twig.read_config_file!
45
+ twig.read_cli_options!(ARGV)
46
+
47
+ TwigGithubRepo.new do |gh_repo|
48
+ $stdout.sync = true
49
+ print 'Getting latest states for GitHub issues...'
50
+
51
+ issues = {}
52
+ issues_uri_base =
53
+ "https://api.github.com/repos/#{gh_repo.username}/#{gh_repo.repository}/issues"
54
+ issues_uris = [
55
+ URI.parse("#{issues_uri_base}?state=open"),
56
+ URI.parse("#{issues_uri_base}?state=closed")
57
+ ]
58
+
59
+ begin
60
+ issues_uris.each do |issues_uri|
61
+ http = Net::HTTP.new(issues_uri.host, issues_uri.port)
62
+ http.use_ssl = true
63
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
64
+ request = Net::HTTP::Get.new(issues_uri.path + '?' + issues_uri.query)
65
+ response = http.request(request)
66
+
67
+ if response.code.to_i == 200
68
+ issues_data = JSON.parse(response.body)
69
+ issues_data.each do |issue_data|
70
+ issues[issue_data['number']] = issue_data
71
+ end
72
+ else
73
+ puts "\nERROR: Couldn't get open issues from GitHub. " <<
74
+ "(Response: #{response.code})"
75
+ end
76
+ end
77
+ end
78
+
79
+ twig.branches.each do |branch|
80
+ issue_number = branch.get_property('issue').to_i
81
+ next unless issue_number > 0
82
+
83
+ issue = issues[issue_number]
84
+ next unless issue
85
+
86
+ state = issue['state']
87
+ branch.set_property('status', state) unless state.nil? || state.empty?
88
+
89
+ print '.'
90
+ end
91
+
92
+ puts
93
+ end
data/bin/twig-help ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Your friendly neighborhood `twig --help` alias.
4
+ puts `twig --help`
data/install ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ twig_bin_path = File.expand_path(File.dirname(__FILE__)) + '/bin/**/twig*'
4
+ twig_bins = Dir.glob(twig_bin_path)
5
+ user_bin_path = '~/bin'
6
+
7
+ twig_bins.each do |bin|
8
+ `ln -fs #{bin} #{user_bin_path}`
9
+ end
10
+
11
+ if `which twig` && $?.success?
12
+ puts 'All set! Run `twig` to list your local branches.'
13
+ puts 'For more info, run `twig --help`.'
14
+ else
15
+ puts 'Argh! Something went wrong. Please try symlinking the files in '
16
+ puts '`./bin/` to `~/bin/` and ensure that `~/bin/` is in your `$PATH`, or '
17
+ puts 'contact rondevera on GitHub.'
18
+ end
@@ -0,0 +1,72 @@
1
+ class Twig
2
+ class Branch
3
+
4
+ RESERVED_BRANCH_PROPERTIES = %w[merge rebase remote]
5
+ PROPERTY_NAME_FROM_GIT_CONFIG = /^branch\.[^.]+\.([^=]+)=.*$/
6
+
7
+ attr_accessor :name, :last_commit_time
8
+
9
+ def self.all_properties
10
+ @_all_properties ||= begin
11
+ config_lines = Twig.run('git config --list').split("\n")
12
+
13
+ properties = config_lines.map do |line|
14
+ # Split by rightmost `=`, allowing branch names to contain `=`:
15
+ key, value = line.match(/(.+)=(.+)/)[1..2]
16
+
17
+ key_parts = key.split('.')
18
+ key_parts.last if key_parts[0] == 'branch' && key_parts.size > 2
19
+ end.compact
20
+
21
+ properties.uniq.sort - RESERVED_BRANCH_PROPERTIES
22
+ end
23
+ end
24
+
25
+ def initialize(name, attrs = {})
26
+ self.name = name
27
+ raise ArgumentError, '`name` is required' if name.empty?
28
+
29
+ self.last_commit_time = attrs[:last_commit_time]
30
+ end
31
+
32
+ def to_s ; name ; end
33
+
34
+ def sanitize_property(property_name)
35
+ property_name.gsub(/[ _]+/, '')
36
+ end
37
+
38
+ def get_property(property_name)
39
+ Twig.run("git config branch.#{name}.#{property_name}")
40
+ end
41
+
42
+ def set_property(property_name, value)
43
+ property_name = sanitize_property(property_name)
44
+ value = value.to_s.strip
45
+
46
+ if RESERVED_BRANCH_PROPERTIES.include?(property_name)
47
+ %{Can't modify the reserved property "#{property_name}".}
48
+ elsif value.empty?
49
+ %{Can't set a branch property to an empty string.}
50
+ else
51
+ Twig.run(%{git config branch.#{name}.#{property_name} "#{value}"})
52
+ result_body = %{property "#{property_name}" as "#{value}" for branch "#{name}".}
53
+ if $?.success?
54
+ "Saved #{result_body}"
55
+ else
56
+ "Could not save #{result_body}"
57
+ end
58
+ end
59
+ end
60
+
61
+ def unset_property(property_name)
62
+ value = get_property(property_name)
63
+ if value && !value.empty?
64
+ Twig.run(%{git config --unset branch.#{name}.#{property_name}})
65
+ %{Removed property "#{property_name}" for branch "#{name}".}
66
+ else
67
+ %{The branch "#{name}" does not have the property "#{property_name}".}
68
+ end
69
+ end
70
+
71
+ end
72
+ end