git-duet 0.1.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 +5 -0
- data/.jrubyrc +1 -0
- data/.rbenv-version +1 -0
- data/.rspec +2 -0
- data/.simplecov +5 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +268 -0
- data/Rakefile +10 -0
- data/bin/git-duet +3 -0
- data/bin/git-duet-commit +3 -0
- data/bin/git-duet-install-hook +3 -0
- data/bin/git-duet-pre-commit +3 -0
- data/bin/git-duet-pre-commit-tk +3 -0
- data/bin/git-solo +3 -0
- data/git-duet.gemspec +36 -0
- data/lib/git-duet.rb +1 -0
- data/lib/git/duet.rb +8 -0
- data/lib/git/duet/author_mapper.rb +77 -0
- data/lib/git/duet/cli.rb +119 -0
- data/lib/git/duet/command_methods.rb +109 -0
- data/lib/git/duet/commit_command.rb +76 -0
- data/lib/git/duet/duet_command.rb +49 -0
- data/lib/git/duet/install_hook_command.rb +33 -0
- data/lib/git/duet/key_error.rb +3 -0
- data/lib/git/duet/pre_commit_command.rb +43 -0
- data/lib/git/duet/script_die_error.rb +11 -0
- data/lib/git/duet/solo_command.rb +45 -0
- data/lib/git/duet/version.rb +7 -0
- data/spec/integration/end_to_end_spec.rb +304 -0
- data/spec/lib/git/duet/author_mapper_spec.rb +170 -0
- data/spec/lib/git/duet/cli_spec.rb +46 -0
- data/spec/lib/git/duet/command_methods_spec.rb +51 -0
- data/spec/lib/git/duet/duet_command_spec.rb +88 -0
- data/spec/lib/git/duet/pre_commit_command_spec.rb +36 -0
- data/spec/lib/git/duet/solo_command_spec.rb +115 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/author_mapper_helper.rb +28 -0
- metadata +172 -0
data/.jrubyrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
compat.version=1.9
|
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p327
|
data/.rspec
ADDED
data/.simplecov
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 ModCloth, Inc.
|
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,268 @@
|
|
1
|
+
# Git Duet
|
2
|
+
|
3
|
+
<a href="https://travis-ci.org/modcloth/git-duet">
|
4
|
+
<img src="https://api.travis-ci.org/modcloth/git-duet.png"
|
5
|
+
title="Build Status"
|
6
|
+
/></a>
|
7
|
+
|
8
|
+
Pair harmoniously! Working in a pair doesn't mean you've both lost your
|
9
|
+
identity. Git Duet helps with blaming/praising by using stuff that's
|
10
|
+
already in `git` without littering your repo history with fictitous user
|
11
|
+
identities.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Install it with `gem`:
|
16
|
+
|
17
|
+
~~~~~ bash
|
18
|
+
gem install git-duet
|
19
|
+
~~~~~
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Setup
|
24
|
+
|
25
|
+
Make an authors file with email domain, or if you're already using
|
26
|
+
[git pair](https://github.com/pivotal/git_scripts), just symlink your
|
27
|
+
`~/.pairs` file over to `~/.git-authors`.
|
28
|
+
|
29
|
+
~~~~~ yaml
|
30
|
+
authors:
|
31
|
+
jd: Jane Doe; jane
|
32
|
+
fb: Frances Bar
|
33
|
+
email:
|
34
|
+
domain: awesometown.local
|
35
|
+
~~~~~
|
36
|
+
|
37
|
+
`git duet` will use the `git pair` YAML structure if it has to (the
|
38
|
+
difference is the top-level key being `pairs` instead of `authors`,) e.g.:
|
39
|
+
|
40
|
+
~~~~~ yaml
|
41
|
+
pairs:
|
42
|
+
jd: Jane Doe; jane
|
43
|
+
fb: Frances Bar
|
44
|
+
email:
|
45
|
+
domain: awesometown.local
|
46
|
+
~~~~~
|
47
|
+
|
48
|
+
If you want your authors file to live somwhere else, just tell
|
49
|
+
Git Duet about it via the `GIT_DUET_AUTHORS_FILE` environmental
|
50
|
+
variable, e.g.:
|
51
|
+
|
52
|
+
~~~~~ bash
|
53
|
+
export GIT_DUET_AUTHORS_FILE=$HOME/.secret-squirrel/git-authors
|
54
|
+
# ...
|
55
|
+
git duet jd am
|
56
|
+
~~~~~
|
57
|
+
|
58
|
+
### Workflow stuff
|
59
|
+
|
60
|
+
Set the author and committer via `git duet`:
|
61
|
+
|
62
|
+
~~~~~ bash
|
63
|
+
git duet jd fb
|
64
|
+
~~~~~
|
65
|
+
|
66
|
+
When you're ready to commit, use `git duet-commit` (or add an alias like
|
67
|
+
a normal person. Something like `dci = duet-commit` should work.)
|
68
|
+
|
69
|
+
~~~~~ bash
|
70
|
+
git duet-commit -v [any other git options]
|
71
|
+
~~~~~
|
72
|
+
|
73
|
+
When you're done pairing, set the author back to yourself with `git solo`:
|
74
|
+
|
75
|
+
~~~~~ bash
|
76
|
+
git solo jd
|
77
|
+
~~~~~
|
78
|
+
|
79
|
+
### Global Config Support
|
80
|
+
|
81
|
+
If you're jumping between projects and don't want to think about
|
82
|
+
managing them all individually, you can operate on the global git
|
83
|
+
config:
|
84
|
+
|
85
|
+
~~~~~ bash
|
86
|
+
git solo -g jd
|
87
|
+
~~~~~
|
88
|
+
|
89
|
+
~~~~~ bash
|
90
|
+
git duet --global jd fb
|
91
|
+
~~~~~
|
92
|
+
|
93
|
+
### Email Configuration
|
94
|
+
|
95
|
+
By default, email addresses are constructed from the first initial and
|
96
|
+
last name ( *or* optional username after a `;`) plus email domain, e.g.
|
97
|
+
with the following authors file:
|
98
|
+
|
99
|
+
~~~~~ yaml
|
100
|
+
pairs:
|
101
|
+
jd: Jane Doe; jane
|
102
|
+
fb: Frances Bar
|
103
|
+
email:
|
104
|
+
domain: eternalstench.bog.local
|
105
|
+
~~~~~
|
106
|
+
|
107
|
+
After invoking:
|
108
|
+
|
109
|
+
~~~~~ bash
|
110
|
+
git duet jd fb
|
111
|
+
~~~~~
|
112
|
+
|
113
|
+
Then the configured email addresses will show up like this:
|
114
|
+
|
115
|
+
~~~~~ bash
|
116
|
+
git config user.email
|
117
|
+
# -> jane@eternalstench.bog.local
|
118
|
+
git config duet.env.git-author-email
|
119
|
+
# -> jane@eternalstench.bog.local
|
120
|
+
git config duet.env.git-committer-email
|
121
|
+
# -> f.bar@eternalstench.bog.local
|
122
|
+
~~~~~
|
123
|
+
|
124
|
+
A custom email template may be provided via the `email_template` config
|
125
|
+
variable. The template should be a valid ERB string and the variables
|
126
|
+
available are `author` which is the full first and last name value
|
127
|
+
associated with each set of initials, `initials` which are the initials
|
128
|
+
key, and `username` which is the part following `;` in the author value.
|
129
|
+
|
130
|
+
~~~~~ yaml
|
131
|
+
pairs:
|
132
|
+
jd: Jane Doe
|
133
|
+
fb: Frances Bar
|
134
|
+
email_template: '<%= "#{author.gsub(/ /, "-").downcase}@hamster.local" =%>'
|
135
|
+
~~~~~
|
136
|
+
|
137
|
+
After invoking:
|
138
|
+
|
139
|
+
~~~~~ bash
|
140
|
+
git duet jd fb
|
141
|
+
~~~~~
|
142
|
+
|
143
|
+
Then the configured email addresses will show up like this:
|
144
|
+
|
145
|
+
~~~~~ bash
|
146
|
+
git config user.email
|
147
|
+
# -> jane-doe@hamster.local
|
148
|
+
git config duet.env.git-author-email
|
149
|
+
# -> jane-doe@hamster.local
|
150
|
+
git config duet.env.git-committer-email
|
151
|
+
# -> frances-bar@hamster.local
|
152
|
+
~~~~~
|
153
|
+
|
154
|
+
If there are any exceptions to either the default format or a provided
|
155
|
+
`email_template` config var, explicitly setting email addresses by
|
156
|
+
initials is supported.
|
157
|
+
|
158
|
+
~~~~~ yaml
|
159
|
+
pairs:
|
160
|
+
jd: Jane Doe; jane
|
161
|
+
fb: Frances Bar
|
162
|
+
email:
|
163
|
+
domain: awesometown.local
|
164
|
+
email_addresses:
|
165
|
+
jd: jane@awesome.local
|
166
|
+
~~~~~
|
167
|
+
|
168
|
+
Then Jane Doe's email will show up like this:
|
169
|
+
|
170
|
+
~~~~~ bash
|
171
|
+
git solo jd
|
172
|
+
# ...
|
173
|
+
git config user.email
|
174
|
+
# -> jane@awesome.local
|
175
|
+
~~~~~
|
176
|
+
|
177
|
+
Alternatively, if you have some other preferred way to look up email
|
178
|
+
addresses by initials, name or username, just use that instead:
|
179
|
+
|
180
|
+
~~~~~ bash
|
181
|
+
export GIT_DUET_EMAIL_LOOKUP_COMMAND="$HOME/bin/custom-ldap-thingy"
|
182
|
+
# ... do work
|
183
|
+
git duet jd fb
|
184
|
+
# ... observe emails being set via the specified executable
|
185
|
+
~~~~~
|
186
|
+
|
187
|
+
The initials, name, and username will be passed as arguments to the
|
188
|
+
lookup executable. Anything written to standard output will be used as
|
189
|
+
the email address:
|
190
|
+
|
191
|
+
~~~~~ bash
|
192
|
+
$HOME/bin/custom-ldap-thingy 'jd' 'Jane Doe' 'jane'
|
193
|
+
# -> doej@behemoth.company.local
|
194
|
+
~~~~~
|
195
|
+
|
196
|
+
If nothing is returned on standard output, email construction falls back
|
197
|
+
to the decisions described above.
|
198
|
+
|
199
|
+
#### Order of Precedence
|
200
|
+
|
201
|
+
Since there are multiple ways to determine an author or committer's
|
202
|
+
email, it is important to note the order of precedence used by Git Duet:
|
203
|
+
|
204
|
+
1. Email lookup executable configured via the
|
205
|
+
`GIT_DUET_EMAIL_LOOKUP_COMMAND` environmental variable
|
206
|
+
2. Email lookup from `email_addresses` in your configuration file
|
207
|
+
3. Custom email address from ERB template defined in `email_template` in
|
208
|
+
your configuration file
|
209
|
+
4. The username after the `;`, followed by `@` and the configured email
|
210
|
+
domain
|
211
|
+
5. The lower-cased first letter of the author or committer's first name,
|
212
|
+
followed by `.` followed by the lower-cased last name of the author
|
213
|
+
or committer, followed by `@` and the configured email domain (e.g.
|
214
|
+
`f.bar@baz.local`)
|
215
|
+
|
216
|
+
### Git hook integration
|
217
|
+
|
218
|
+
If you'd like to regularly remind yourself to set the solo or duet
|
219
|
+
initials, use `git duet-pre-commit` in your pre-commit hook:
|
220
|
+
|
221
|
+
*(in $REPO_ROOT/.git/hooks/pre-commit)*
|
222
|
+
~~~~~ bash
|
223
|
+
#!/bin/bash
|
224
|
+
exec git duet-pre-commit
|
225
|
+
~~~~~
|
226
|
+
|
227
|
+
The `duet-pre-commit` command will exit with a non-zero status if the
|
228
|
+
cached author and committer settings are missing or stale. The default
|
229
|
+
staleness cutoff is [20 minutes](http://en.wikipedia.org/wiki/Pomodoro_Technique),
|
230
|
+
but may be configured via the `GIT_DUET_SECONDS_AGO_STALE` environmental variable,
|
231
|
+
which should be an integer of seconds, e.g.:
|
232
|
+
|
233
|
+
~~~~~ bash
|
234
|
+
export GIT_DUET_SECONDS_AGO_STALE=60
|
235
|
+
# ... do work for more than a minute
|
236
|
+
git commit -v
|
237
|
+
# ... pre-commit hook fires
|
238
|
+
~~~~~
|
239
|
+
|
240
|
+
If you want to use the default hook (as shown above), install it while
|
241
|
+
in your repo like so:
|
242
|
+
|
243
|
+
~~~~~ bash
|
244
|
+
git duet-install-hook
|
245
|
+
~~~~~
|
246
|
+
|
247
|
+
Don't worry if you forgot you already had a `pre-commit` hook installed.
|
248
|
+
The `git duet-install-hook` command will refuse to overwrite it.
|
249
|
+
|
250
|
+
## Compatibility
|
251
|
+
|
252
|
+
Git Duet has been tested on a bunch of platform/interpreter combinations
|
253
|
+
provided by Travis CI *not including* Rubinius.
|
254
|
+
|
255
|
+
While JRuby works it is not recommended as the VM startup time is
|
256
|
+
usually longer than it takes most Git Duet commands to execute.
|
257
|
+
|
258
|
+
If you experience badness, please [let us know via
|
259
|
+
email](mailto:github@modcloth.com) or pretty please [create an issue on
|
260
|
+
github](https://github.com/modcloth/git-duet/issues/new).
|
261
|
+
|
262
|
+
## Contributing
|
263
|
+
|
264
|
+
1. Fork it
|
265
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
266
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
267
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
268
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/git-duet
ADDED
data/bin/git-duet-commit
ADDED
data/bin/git-solo
ADDED
data/git-duet.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
require File.expand_path('../lib/git/duet/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = [
|
6
|
+
'Dan Buch',
|
7
|
+
'Jesse Szwedko',
|
8
|
+
'Rafe Colton',
|
9
|
+
'Sheena McCoy',
|
10
|
+
]
|
11
|
+
gem.email = %w(
|
12
|
+
d.buch@modcloth.com
|
13
|
+
j.szwedko@modcloth.com
|
14
|
+
r.colton@modcloth.com
|
15
|
+
sp.mccoy@modcloth.com
|
16
|
+
)
|
17
|
+
gem.description = %q{Pair programming git identity thingy}
|
18
|
+
gem.summary = "Pair harmoniously! Decide who's driving. " <<
|
19
|
+
"Commit along the way. Don't make a mess of " <<
|
20
|
+
"the repository history."
|
21
|
+
gem.homepage = ''
|
22
|
+
gem.license = 'MIT'
|
23
|
+
|
24
|
+
gem.files = `git ls-files`.split($\)
|
25
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
26
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
27
|
+
gem.name = 'git-duet'
|
28
|
+
gem.require_paths = %w(lib)
|
29
|
+
gem.version = Git::Duet::VERSION
|
30
|
+
gem.required_ruby_version = '>= 1.8.7'
|
31
|
+
|
32
|
+
gem.add_development_dependency 'nyan-cat-formatter', '>= 0.2.0'
|
33
|
+
gem.add_development_dependency 'rake', '>= 0.9.2.2'
|
34
|
+
gem.add_development_dependency 'rspec', '>= 2.0.0'
|
35
|
+
gem.add_development_dependency 'simplecov', '>= 0.7.0'
|
36
|
+
end
|
data/lib/git-duet.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'git/duet'
|
data/lib/git/duet.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
require 'git/duet'
|
4
|
+
|
5
|
+
class Git::Duet::AuthorMapper
|
6
|
+
attr_accessor :authors_file
|
7
|
+
|
8
|
+
def initialize(authors_file = nil, email_lookup = nil)
|
9
|
+
@authors_file = authors_file ||
|
10
|
+
ENV['GIT_DUET_AUTHORS_FILE'] ||
|
11
|
+
File.join(ENV['HOME'], '.git-authors')
|
12
|
+
@email_lookup = email_lookup ||
|
13
|
+
ENV['GIT_DUET_EMAIL_LOOKUP_COMMAND']
|
14
|
+
end
|
15
|
+
|
16
|
+
def map(*initials_list)
|
17
|
+
author_map = {}
|
18
|
+
initials_list.each do |initials|
|
19
|
+
author_map[initials] = author_info(initials)
|
20
|
+
end
|
21
|
+
author_map
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def author_info(initials)
|
26
|
+
author, username = author_map.fetch(initials).split(/;/).map(&:strip)
|
27
|
+
{
|
28
|
+
:name => author,
|
29
|
+
:email => lookup_author_email(initials, author, username)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def lookup_author_email(initials, author, username)
|
34
|
+
if @email_lookup
|
35
|
+
author_email = `#{@email_lookup} '#{initials}' '#{author}' '#{username}'`.strip
|
36
|
+
return author_email if !author_email.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
return email_addresses[initials] if email_addresses[initials]
|
40
|
+
return email_from_template(initials, author, username) if email_template
|
41
|
+
return "#{username}@#{email_domain}" if username
|
42
|
+
|
43
|
+
author_name_parts = author.split
|
44
|
+
return "#{author_name_parts.first[0,1].downcase}." <<
|
45
|
+
"#{author_name_parts.last.downcase}@#{email_domain}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def email_from_template(initials, author, username)
|
49
|
+
return ERB.new(email_template).result(binding)
|
50
|
+
rescue StandardError => e
|
51
|
+
STDERR.puts("git-duet: email template rendering error: #{e.message}")
|
52
|
+
raise Git::Duet::ScriptDieError.new(8)
|
53
|
+
end
|
54
|
+
|
55
|
+
def author_map
|
56
|
+
@author_map ||= (cfg['authors'] || cfg['pairs'])
|
57
|
+
end
|
58
|
+
|
59
|
+
def email_addresses
|
60
|
+
@email_addresses ||= (cfg['email_addresses'] || {})
|
61
|
+
end
|
62
|
+
|
63
|
+
def email_domain
|
64
|
+
@email_domain ||= cfg.fetch('email').fetch('domain')
|
65
|
+
end
|
66
|
+
|
67
|
+
def email_template
|
68
|
+
@email_template || cfg['email_template']
|
69
|
+
end
|
70
|
+
|
71
|
+
def cfg
|
72
|
+
@cfg ||= YAML.load(IO.read(@authors_file))
|
73
|
+
rescue StandardError => e
|
74
|
+
STDERR.puts("git-duet: Missing or corrupt authors file: #{e.message}")
|
75
|
+
raise Git::Duet::ScriptDieError.new(3)
|
76
|
+
end
|
77
|
+
end
|