git_time_extractor 0.2.3 → 0.3.0
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 +4 -4
- data/History.txt +3 -0
- data/bin/git_time_extractor +61 -26
- data/lib/author.rb +146 -0
- data/lib/autoload.rb +9 -0
- data/lib/git_time_extractor.rb +100 -125
- data/lib/pivotal_ids_extractor.rb +28 -0
- data/readme.md +50 -0
- metadata +12 -9
- data/README.txt +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5cd63888dc7235ee55baee19108e6b576dbdf474
|
4
|
+
data.tar.gz: a0ac045a5dd7ad3d81e763c4a76d74880dcaaf42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0363407a1a542759865af72d0958e2af5c4fa1dc235e78bdd56ac6fb2a4809f47275d25618b45bd8769a4cbb54c27751fcaefbb49fc5c0d942f76d5caa90756a
|
7
|
+
data.tar.gz: 503fe4be1588f616c93911c073a59e1bc8a563e12790cc5532c266f39108967e30a2ebbbb7c4c2415a8ad685b29b85068f91fa0ba707e19b40e828664e1d2ac7
|
data/History.txt
CHANGED
data/bin/git_time_extractor
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# Extract Reasonable Developer Time Records from a GIT Repository's Commit Log
|
3
3
|
#
|
4
|
-
# This is inspired by a RAKE task publicly posted by Sharad at
|
5
|
-
# http://www.tatvartha.com/2010/01/generating-time-entry-from-git-log/.
|
4
|
+
# This is inspired by a RAKE task publicly posted by Sharad at
|
5
|
+
# http://www.tatvartha.com/2010/01/generating-time-entry-from-git-log/.
|
6
6
|
# However, it has been adapted to run without Rails from the command line.
|
7
7
|
#
|
8
8
|
# Portions (C) 2012 Rietta Inc. and licensed under the terms of the BSD license.
|
@@ -10,36 +10,71 @@
|
|
10
10
|
# Adjust path in case called directly and not through gem
|
11
11
|
$:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
|
12
12
|
|
13
|
+
require 'optparse'
|
13
14
|
require 'git_time_extractor'
|
14
15
|
|
15
|
-
|
16
|
+
|
17
|
+
options = { :project => '',
|
18
|
+
:path_to_repo => Dir.pwd,
|
19
|
+
:output_file => '-',
|
20
|
+
:max_commits => 10000,
|
21
|
+
:initial_effort_mins => 30,
|
22
|
+
:merge_effort_mins => 30,
|
23
|
+
:session_duration_hrs => 3
|
24
|
+
}
|
16
25
|
|
17
26
|
valid_usage = false
|
18
27
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
option_parser = OptionParser.new do |opts|
|
29
|
+
opts.on('-p PROJECT', '--project PROJECT') do |project|
|
30
|
+
options[:project] = project
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('-i REPOPATH', '--repo-path REPOPATH') do |repopath|
|
34
|
+
options[:path_to_repo] = repopath
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-o OUTPUT_CSV', '--out OUTPUT_CSV') do |output_file|
|
38
|
+
options[:output_file] = output_file
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-c MAX_COMMITS', '--max-commits MAX_COMMITS',
|
42
|
+
'Maximum number of commits to read from Git. Default: ' + options[:max_commits].to_s ) do |max_commits|
|
43
|
+
options[:max_commits] = max_commits
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on('-e INITIAL_EFFORT', '--initial-effort INITIAL_EFFORT',
|
47
|
+
'Initial Effort before each commit, in minutes. Default: ' + options[:initial_effort_mins].to_s ) do |initial_effort|
|
48
|
+
options[:initial_effort_mins] = initial_effort
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on('-m MERGE_EFFORT', '--merge-effort MERGE_EFFORT',
|
52
|
+
'Effort spent merging, in minutes. Default: ' + options[:merge_effort_mins].to_s) do |initial_effort|
|
53
|
+
options[:initial_effort_mins] = initial_effort
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on('-s SESSION_DURATION', '--session-duration SESSION_DURATION',
|
57
|
+
'Session duration, in hours. Default: ' + options[:session_duration_hrs].to_s ) do |session_duration|
|
58
|
+
options[:session_duration_hrs] = session_duration
|
59
|
+
end
|
34
60
|
end
|
35
61
|
|
36
|
-
|
37
|
-
|
38
|
-
|
62
|
+
option_parser.parse!
|
63
|
+
|
64
|
+
if options[:project] && options[:path_to_repo] && options[:output_file]
|
65
|
+
extractor = GitTimeExtractor.new(options)
|
66
|
+
rows = extractor.process_git_log_into_time
|
67
|
+
if options[:output_file] == '-'
|
68
|
+
rows.each do |row|
|
69
|
+
puts row.map!{|value| value.to_s.gsub('"', '\"').gsub("\n", '\n') }.to_csv
|
70
|
+
end
|
71
|
+
else
|
72
|
+
puts 'Output to file not implemented yet'
|
73
|
+
end # options
|
74
|
+
|
75
|
+
else
|
76
|
+
puts 'Usage: git_time_extractor -p PROJECT_NAME -i [PATH_TO_REPO] -o [OUTPUT_FILE] -c [MAX_COMMITS] -e [INITIAL_EFFORT_MINS] -m [MERGE_EFFORT_MINS] -s [SESSION_DURATION_HRS]'
|
77
|
+
puts 'Copyright 2014 Rietta Inc. https://rietta.com'
|
39
78
|
exit 0
|
40
|
-
|
41
|
-
#path_to_repo = File.expand_path(File.dirname(path_to_repo)) if nil != path_to_repo && "" != path_to_repo
|
42
|
-
#puts "\n\nGit Repo Path: #{path_to_repo}\nOutput: #{output_file}\n"
|
43
|
-
GitTimeExtractor.process_git_log_into_time(path_to_repo, output_file)
|
44
|
-
end
|
79
|
+
end # options
|
45
80
|
|
data/lib/author.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
class Author
|
2
|
+
|
3
|
+
attr_accessor :commits, :worklog, :rows, :options
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
@commits = Array.new
|
8
|
+
@worklog = Hash.new
|
9
|
+
@rows = Array.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# Each commit is added into the list
|
13
|
+
def add_commit(commit)
|
14
|
+
@commits << commit
|
15
|
+
end
|
16
|
+
|
17
|
+
# Then the tabulation is called after the commits are added into this author's list
|
18
|
+
def tabulate_days
|
19
|
+
@commits.each_with_index do |commit, index|
|
20
|
+
roll_up_to_days commit, index
|
21
|
+
end
|
22
|
+
|
23
|
+
# Go through the work log
|
24
|
+
@worklog.keys.sort.each do |date|
|
25
|
+
@rows << summarize(@worklog[date], date)
|
26
|
+
end # worklog each
|
27
|
+
end # tabulate_days
|
28
|
+
|
29
|
+
def summarize(summary, date)
|
30
|
+
start_time = DateTime.parse(date.to_s)
|
31
|
+
duration_in_seconds = (summary.duration.to_f * 60.0).round(0)
|
32
|
+
duration_in_minutes = summary.duration.to_i
|
33
|
+
duration_in_hours = (summary.duration / 60.0).round(1)
|
34
|
+
|
35
|
+
return summarize_helper(
|
36
|
+
start_time,
|
37
|
+
duration_in_seconds,
|
38
|
+
duration_in_minutes,
|
39
|
+
duration_in_hours,
|
40
|
+
summary
|
41
|
+
)
|
42
|
+
end # summarize
|
43
|
+
|
44
|
+
def summarize_helper(
|
45
|
+
start_time,
|
46
|
+
duration_in_seconds,
|
47
|
+
duration_in_minutes,
|
48
|
+
duration_in_hours,
|
49
|
+
summary
|
50
|
+
)
|
51
|
+
[
|
52
|
+
start_time.strftime("%m/%d/%Y"),
|
53
|
+
summary.commit_count,
|
54
|
+
summary.pivotal_stories.count,
|
55
|
+
duration_in_minutes,
|
56
|
+
duration_in_hours,
|
57
|
+
summary.author.name,
|
58
|
+
summary.author.email,
|
59
|
+
project_name,
|
60
|
+
summary.message,
|
61
|
+
summary.pivotal_stories.map(&:inspect).join('; '),
|
62
|
+
start_time.strftime("%W").to_i,
|
63
|
+
start_time.strftime("%Y").to_i
|
64
|
+
]
|
65
|
+
end # summarize_helper
|
66
|
+
|
67
|
+
def roll_up_to_days(commit, index)
|
68
|
+
# Get the appropriate worklog
|
69
|
+
author_date = commit.author_date.to_date
|
70
|
+
|
71
|
+
#puts "DEBUG: Reading #{commit} #{author_date} from #{commit.author.email}"
|
72
|
+
daylog = get_or_create_new_daylog(author_date)
|
73
|
+
@worklog[author_date] = process_day_log(commit, index, daylog)
|
74
|
+
end # roll_up_to_days
|
75
|
+
|
76
|
+
def get_or_create_new_daylog(author_date)
|
77
|
+
if @worklog[author_date]
|
78
|
+
return @worklog[author_date]
|
79
|
+
else
|
80
|
+
return OpenStruct.new(
|
81
|
+
:date => author_date,
|
82
|
+
:duration => 0,
|
83
|
+
:commit_count => 0,
|
84
|
+
:pivotal_stories => Set.new
|
85
|
+
)
|
86
|
+
end # if
|
87
|
+
end # daylog
|
88
|
+
|
89
|
+
def pivotal_ids(text)
|
90
|
+
::PivotalIdsExtractor.new(text).stories
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_day_log(commit, index, daylog)
|
94
|
+
#puts "DEBUG: Processing #{commit} - #{commit.author}"
|
95
|
+
daylog.author = commit.author
|
96
|
+
daylog.message = "#{daylog.message} --- #{commit.message}"
|
97
|
+
daylog.duration = daylog.duration + calc_duration_in_minutes(@commits, index)
|
98
|
+
|
99
|
+
# The git commit count
|
100
|
+
daylog.commit_count += 1
|
101
|
+
|
102
|
+
# Pivotal Stories
|
103
|
+
stories = pivotal_ids(commit.message)
|
104
|
+
if stories
|
105
|
+
# It's a set, so each story only gets added once per day
|
106
|
+
stories.each do |sid|
|
107
|
+
daylog.pivotal_stories << sid
|
108
|
+
end # each
|
109
|
+
end # if stories
|
110
|
+
|
111
|
+
return daylog
|
112
|
+
end # process_day_log
|
113
|
+
|
114
|
+
# Calculate the duration of work in minutes
|
115
|
+
def calc_duration_in_minutes(log_entries, index)
|
116
|
+
if index > 1
|
117
|
+
# Compute the time between this and this previous entry
|
118
|
+
return compute_commit_time_duration(log_entries[index], log_entries[index - 1])
|
119
|
+
else
|
120
|
+
# This is the first commit in the log
|
121
|
+
return @options[:initial_effort_mins].to_f
|
122
|
+
end
|
123
|
+
return duration
|
124
|
+
end # calc_duration_in_minutes
|
125
|
+
|
126
|
+
def compute_commit_time_duration(commit, previous_commit)
|
127
|
+
# Default duration in Ruby is in seconds, so lets make it minutes
|
128
|
+
duration = (commit.author_date - previous_commit.author_date) / 60.0
|
129
|
+
|
130
|
+
#initial_effort_mins: 30, session_duration_hrs: 3, max_commits: 1000
|
131
|
+
if duration > @options[:session_duration_hrs].to_f * 60.0
|
132
|
+
# first commit in this session
|
133
|
+
duration = @options[:initial_effort_mins].to_f
|
134
|
+
elsif duration < 0
|
135
|
+
# probably a merge.
|
136
|
+
duration = @options[:merge_effort_mins].to_f
|
137
|
+
end
|
138
|
+
|
139
|
+
return duration.to_f
|
140
|
+
end # compute_commit_time_duration
|
141
|
+
|
142
|
+
def project_name
|
143
|
+
@options[:project]
|
144
|
+
end
|
145
|
+
|
146
|
+
end # class
|
data/lib/autoload.rb
ADDED
data/lib/git_time_extractor.rb
CHANGED
@@ -1,151 +1,126 @@
|
|
1
1
|
#
|
2
2
|
# Extract Reasonable Developer Time Records from a GIT Repository's Commit Log
|
3
3
|
#
|
4
|
-
# This is inspired by a RAKE task publicly posted by Sharad at
|
5
|
-
# http://www.tatvartha.com/2010/01/generating-time-entry-from-git-log/.
|
4
|
+
# This is inspired by a RAKE task publicly posted by Sharad at
|
5
|
+
# http://www.tatvartha.com/2010/01/generating-time-entry-from-git-log/.
|
6
6
|
# However, it has been adapted to run without Rails from the command line.
|
7
7
|
#
|
8
|
-
# Portions (C)
|
8
|
+
# Portions (C) 2014 Rietta Inc. and licensed under the terms of the BSD license.
|
9
9
|
#
|
10
10
|
class GitTimeExtractor
|
11
|
-
VERSION = '0.2.
|
12
|
-
|
13
|
-
require '
|
14
|
-
require 'ostruct'
|
15
|
-
require 'logger'
|
16
|
-
require 'git'
|
17
|
-
require 'csv'
|
11
|
+
VERSION = '0.2.3'
|
12
|
+
|
13
|
+
require 'autoload'
|
18
14
|
require 'set'
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
16
|
+
attr_accessor :options, :summary, :authors
|
17
|
+
|
18
|
+
def initialize( opts = {
|
19
|
+
project: "Untitled",
|
20
|
+
path_to_repo: "./",
|
21
|
+
output_file: "-",
|
22
|
+
initial_effort_mins: 30,
|
23
|
+
merge_effort_mins: 30,
|
24
|
+
session_duration_hrs: 3,
|
25
|
+
max_commits: 1000
|
26
|
+
} )
|
27
|
+
@options = opts
|
28
|
+
@authors_hash = Hash.new
|
29
|
+
@authors = Array.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def path_to_git_repo
|
33
|
+
options[:path_to_repo]
|
34
|
+
end
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
36
|
+
def path_to_output_file
|
37
|
+
options[:output_file]
|
38
|
+
end
|
39
|
+
|
40
|
+
def project_name
|
41
|
+
options[:project]
|
42
|
+
end
|
43
|
+
|
44
|
+
def load_git_log_entries(path_to_git_repo)
|
35
45
|
# Open the GIT Repository for Reading
|
36
46
|
logger = Logger.new(STDOUT)
|
37
47
|
logger.level = Logger::WARN
|
38
48
|
g = Git.open(path_to_git_repo, :log => logger)
|
39
|
-
logs = g.log(
|
40
|
-
|
41
|
-
|
49
|
+
logs = g.log(@options[:max_commits])
|
50
|
+
return logs.entries.reverse
|
51
|
+
end
|
42
52
|
|
53
|
+
# Scan through the commit log entries and split into each author's accounting
|
54
|
+
def distribute_entries_to_authors(log_entries)
|
43
55
|
# Go through the GIT commit records and construct the time
|
44
56
|
log_entries.each_with_index do |commit, index|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
stories.each do |sid|
|
59
|
-
daylog.pivotal_stories << sid
|
57
|
+
key = commit.author.email
|
58
|
+
if @authors_hash[key].nil?
|
59
|
+
@authors_hash[key] = Author.new(@options)
|
60
|
+
end
|
61
|
+
@authors_hash[key].add_commit(commit)
|
62
|
+
end # log_entries.each_with_index
|
63
|
+
|
64
|
+
if @authors_hash
|
65
|
+
@authors_hash.each do |key, value|
|
66
|
+
if value
|
67
|
+
value.tabulate_days
|
68
|
+
@authors << value
|
69
|
+
#puts "DEBUG: Looking at #{key}, #{value}, with #{value.rows.count} commits"
|
60
70
|
end
|
61
71
|
end
|
62
|
-
|
63
|
-
|
64
|
-
worklog[author_date] = daylog
|
65
|
-
end # log_entries.each_with_index
|
72
|
+
end
|
66
73
|
|
67
|
-
#
|
68
|
-
puts [
|
69
|
-
'Date',
|
70
|
-
'Git Commits Count',
|
71
|
-
'Pivotal Stories Count',
|
72
|
-
'Minutes',
|
73
|
-
'Hours',
|
74
|
-
'Person',
|
75
|
-
'Email',
|
76
|
-
'Project',
|
77
|
-
'Notes',
|
78
|
-
'Pivotal Stories',
|
79
|
-
'Week Number',
|
80
|
-
'Year'
|
81
|
-
].to_csv
|
82
|
-
|
83
|
-
# Go through the work log
|
84
|
-
worklog.keys.sort.each do |date|
|
85
|
-
summary = worklog[date]
|
86
|
-
start_time = DateTime.parse(date.to_s)
|
87
|
-
duration_in_seconds = (summary.duration.to_f * 60.0).round(0)
|
88
|
-
duration_in_minutes = summary.duration.to_i
|
89
|
-
duration_in_hours = (summary.duration / 60.0).round(1)
|
90
|
-
|
91
|
-
stop_time = start_time + duration_in_seconds
|
92
|
-
|
93
|
-
row = [
|
94
|
-
start_time.strftime("%m/%d/%Y"),
|
95
|
-
summary.commit_count,
|
96
|
-
summary.pivotal_stories.count,
|
97
|
-
duration_in_minutes,
|
98
|
-
duration_in_hours,
|
99
|
-
summary.author.name,
|
100
|
-
summary.author.email,
|
101
|
-
project_name,
|
102
|
-
summary.message,
|
103
|
-
summary.pivotal_stories.map(&:inspect).join('; '),
|
104
|
-
start_time.strftime("%W").to_i,
|
105
|
-
start_time.strftime("%Y").to_i]
|
106
|
-
puts row.to_csv
|
107
|
-
end # worklog each
|
74
|
+
#puts "DEBUG: #{@authors.count} authors"
|
108
75
|
|
109
|
-
|
110
|
-
|
111
|
-
# Calculate the duration of work in minutes
|
112
|
-
def self.calc_duration_in_minutes(log_entries, index)
|
113
|
-
commit = log_entries[index]
|
114
|
-
if index > 1
|
115
|
-
previous_commit = log_entries[index-1]
|
116
|
-
# Default duration in Ruby is in seconds
|
117
|
-
duration = commit.author_date - previous_commit.author_date
|
118
|
-
|
119
|
-
# ASSUMPTION: if the gap between 2 commits is more than 3 hours, reduce it to 1/2 hour
|
120
|
-
# Also, if the time is negative then this is usually a merge operation. Assume the developer spent
|
121
|
-
# 30 minutes reviewing it
|
122
|
-
duration = 30 * 60 if duration < 0 || duration > 3 * 3600
|
123
|
-
else
|
124
|
-
# ASSUMPTION: first commit took 1/2 hour
|
125
|
-
duration = 30 * 60
|
126
|
-
end
|
127
|
-
return duration.to_f / 60.0
|
128
|
-
end # calc_duration_in_minutes
|
76
|
+
return @authors
|
77
|
+
end # distribute_entries_to_authors
|
129
78
|
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
# m = s.scan /\[[A-Za-z ]{0,20}#[0-9]{1,20}\]/
|
137
|
-
def self.pivotal_ids(text)
|
138
|
-
stories = Array.new
|
139
|
-
# Extract the unique ids between brackets
|
140
|
-
if text
|
141
|
-
candidates = text.scan /\[[A-Za-z \t]{0,20}#[0-9]{1,35}[ \t]{0,5}\]/
|
142
|
-
if candidates
|
143
|
-
candidates.uniq.each do |story|
|
144
|
-
story_num = story.match(/[0-9]{1,35}/).to_s.to_i
|
145
|
-
stories << story_num if story_num > 0
|
146
|
-
end
|
79
|
+
def prepare_rows_for_csv
|
80
|
+
rows = Array.new
|
81
|
+
rows << header_row_template()
|
82
|
+
@authors.each do |author|
|
83
|
+
author.rows.each do |author_row|
|
84
|
+
rows << author_row
|
147
85
|
end
|
148
86
|
end
|
149
|
-
|
87
|
+
return rows
|
150
88
|
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Go through the GIT commit log, to compute the elapsed working time of each committing developer, based
|
92
|
+
# on a few assumptions:
|
93
|
+
#
|
94
|
+
# (1) A series of commits within a 3 hour window are part of the same development session
|
95
|
+
# (2) A single commit (or the first commit of the session) is considered to represent 30 minutes of work time
|
96
|
+
# (3) The more frequent a developer commits to the repository while working, the more accurate the time report will be
|
97
|
+
#
|
98
|
+
#
|
99
|
+
def process_git_log_into_time
|
100
|
+
distribute_entries_to_authors( load_git_log_entries( path_to_git_repo ) )
|
101
|
+
return prepare_rows_for_csv
|
102
|
+
end # process_git_log_into_time
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
#####################################
|
107
|
+
private
|
108
|
+
|
109
|
+
def header_row_template
|
110
|
+
[
|
111
|
+
'Date',
|
112
|
+
'Git Commits Count',
|
113
|
+
'Pivotal Stories Count',
|
114
|
+
'Minutes',
|
115
|
+
'Hours',
|
116
|
+
'Person',
|
117
|
+
'Email',
|
118
|
+
'Project',
|
119
|
+
'Notes',
|
120
|
+
'Pivotal Stories',
|
121
|
+
'Week Number',
|
122
|
+
'Year'
|
123
|
+
]
|
124
|
+
end # header_row_template
|
125
|
+
|
151
126
|
end # class GitTimeExtractor
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class PivotalIdsExtractor
|
2
|
+
|
3
|
+
attr_accessor :source_text, :stories
|
4
|
+
|
5
|
+
# --- [#62749778] New Email Page --- Merge branch 'development' of bitbucket.org:rietta/roofregistry-web into development --- [#62749778] Roughed out email form. --- Added delete Attachment functionality --- Merge branch 'development' of bitbucket.org:rietta/roofregistry-web into development --- [#62749778] Refactored controller to be plural. --- [#62749778] Added to the Email model. --- [62749778] The email this report view formatting. --- [#62749778] Breadcrumbs in the navigation. --- [#62749778] The Emails controller routes. --- The report list is now sorted with newest first - and it shows how long ago that the change was made. --- [#62749778] The share link is bold. --- [#62749778] Recipient parsing and form fields --- [#62749778] List of emails that have received it. --- [#62749778] The email form will validate that at least one email is provided. --- [#62749778] Send Roof Report AJAX form. --- [#62749778] Default messages and the mailer --- [Finishes #62749778] The emails are sent! --- removed delete from show --- added txt and xpf to permitted file types --- Attachments can only be deleted by the owner of the roof report. --- Merge branch 'development' of bitbucket.org:rietta/roofregistry-web into development --- The test server is using production. --- Returns all recommended options across all sections with roof_report.recommedations --- patial commit --- Finished summary section --- Added caps to permitted --- added to_s to inspection --- E-mail spec is not focused at the moment. --- Merge branch 'development' of bitbucket.org:rietta/roofregistry-web into development --- fixed a few bugs --- Merge branch 'development' of bitbucket.org:rietta/roofregistry-web into development --- Disable ajax save. --- Merge branch 'development' of bitbucket.org:rietta/roofregistry-web into development
|
6
|
+
# s = "[#62749778] [#62749778] [#6274977] [#1] [#231]"
|
7
|
+
# m = s.scan /\[[A-Za-z ]{0,20}#[0-9]{1,20}\]/
|
8
|
+
|
9
|
+
def initialize(text)
|
10
|
+
stories = Array.new
|
11
|
+
# Extract the unique ids between brackets
|
12
|
+
if text
|
13
|
+
# Require brackets
|
14
|
+
#candidates = text.scan /\[[A-Za-z \t]{0,20}#[0-9]{1,35}[ \t]{0,5}\]/
|
15
|
+
candidates = text.scan /[A-Za-z \t]{0,20}#[0-9]{1,35}[ \t]{0,5}/
|
16
|
+
if candidates
|
17
|
+
candidates.uniq.each do |story|
|
18
|
+
story_num = story.match(/[0-9]{1,35}/).to_s.to_i
|
19
|
+
stories << story_num if story_num > 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
@source_text = text
|
24
|
+
@stories = stories.sort
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
[](http://badge.fury.io/rb/git_time_extractor)
|
2
|
+
[](https://codeclimate.com/github/rietta/git_time_extractor)
|
3
|
+
# EXTRACT REASONABLE TIME RECORDS FROM A GIT REPOSITORY
|
4
|
+
|
5
|
+
This tool goes through a GIT repository's commit log and prints a CSV dump of per developer, per day working time based on a few assumptions:
|
6
|
+
|
7
|
+
- A series of commits within a 3 hour window are part of the same development session
|
8
|
+
- A single commit (or the first commit of the session) is considered to represent 30 minutes of work time
|
9
|
+
- The more frequent a developer commits to the repository while working, the more accurate the time report will be
|
10
|
+
|
11
|
+
This script is based on previous code published publicly by *Sharad* at http://www.tatvartha.com/2010/01/generating-time-entry-from-git-log/. However, it has been adapted to run without Rails from the command line. The portions of the code written by Rietta are licensed under the terms of the BSD license (see section 3 below).
|
12
|
+
|
13
|
+
## 1. Running
|
14
|
+
|
15
|
+
### Usage
|
16
|
+
|
17
|
+
Command line configurable options are supported!
|
18
|
+
|
19
|
+
```
|
20
|
+
Usage: git_time_extractor [options]
|
21
|
+
-p, --project PROJECT
|
22
|
+
-i, --repo-path REPOPATH
|
23
|
+
-o, --out OUTPUT_CSV
|
24
|
+
-c, --max-commits MAX_COMMITS Maximum number of commits to read from Git. Default: 10000
|
25
|
+
-e INITIAL_EFFORT, Initial Effort before each commit, in minutes. Default: 30
|
26
|
+
--initial-effort
|
27
|
+
-m, --merge-effort MERGE_EFFORT Effort spent merging, in minutes. Default: 30
|
28
|
+
-s SESSION_DURATION, Session duration, in hours. Default: 3
|
29
|
+
--session-duration
|
30
|
+
```
|
31
|
+
### Most basic usage
|
32
|
+
|
33
|
+
- `cd /path/to/your/repository`
|
34
|
+
- `git_time_extractor > time_log.csv`
|
35
|
+
|
36
|
+
## 2. Analysis
|
37
|
+
|
38
|
+
Once the you have used Git Time Extractor to prepare a CSV file, you can perform a lot of different analysis operations using a spreadsheet. See the [examples spreadsheets](examples/) for some ideas.
|
39
|
+
|
40
|
+
## 3. License Terms (BSD License)
|
41
|
+
|
42
|
+
© 2014 Rietta Inc. All rights reserved.
|
43
|
+
|
44
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
45
|
+
|
46
|
+
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
47
|
+
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
48
|
+
|
49
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
50
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_time_extractor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Rietta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git
|
@@ -16,41 +16,44 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.2
|
19
|
+
version: '1.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.2
|
26
|
+
version: '1.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: logger
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.2
|
33
|
+
version: '1.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.2
|
40
|
+
version: '1.2'
|
41
41
|
description: Compute the estimated time spent by developers working on code within
|
42
42
|
a GIT repository. Useful for verifying developer time sheets and for tax purposes.
|
43
43
|
See https://github.com/rietta/git_time_extractor/wiki.
|
44
|
-
email:
|
44
|
+
email: hello@rietta.com
|
45
45
|
executables:
|
46
46
|
- git_time_extractor
|
47
47
|
extensions: []
|
48
48
|
extra_rdoc_files: []
|
49
49
|
files:
|
50
50
|
- History.txt
|
51
|
-
- README.txt
|
52
51
|
- bin/git_time_extractor
|
52
|
+
- lib/author.rb
|
53
|
+
- lib/autoload.rb
|
53
54
|
- lib/git_time_extractor.rb
|
55
|
+
- lib/pivotal_ids_extractor.rb
|
56
|
+
- readme.md
|
54
57
|
homepage: https://github.com/rietta/git_time_extractor
|
55
58
|
licenses:
|
56
59
|
- BSD
|
@@ -71,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
74
|
version: '0'
|
72
75
|
requirements: []
|
73
76
|
rubyforge_project:
|
74
|
-
rubygems_version: 2.2.
|
77
|
+
rubygems_version: 2.2.2
|
75
78
|
signing_key:
|
76
79
|
specification_version: 4
|
77
80
|
summary: Reasonable developer time log extractor that uses a GIT repository's commit
|
data/README.txt
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
= git_time_extractor
|
2
|
-
|
3
|
-
RubyGem Page: https://rubygems.org/gems/git_time_extractor
|
4
|
-
Project Wiki: https://github.com/rietta/git_time_extractor/wiki
|
5
|
-
|
6
|
-
== DESCRIPTION:
|
7
|
-
|
8
|
-
EXTRACT REASONABLE TIME RECORDS FROM A GIT REPOSITORY
|
9
|
-
|
10
|
-
git_time_extractor is a free tool that goes through a GIT, the popular revision control system, repository's commit log and produces a Comma Separated Values (CSV) file that indicates the time spent by each developer, per day.
|
11
|
-
|
12
|
-
The working time estimates are based on three assumptions:
|
13
|
-
|
14
|
-
1. A series of commits within a three (3) hour window are part of the same development session
|
15
|
-
2. A single commit (or the first commit of the session) is considered to represent 30 minutes of work time
|
16
|
-
3. The more frequently a developer commits to the repository while working, the more accurate the time report will be
|
17
|
-
|
18
|
-
This script is based on previous code published publicly by Sharad at http://www.tatvartha.com/2010/01/generating-time-entry-from-git-log/. However, it has been adapted to run without Rails from the command line. The portions of the code written by Rietta are licensed under the terms of the BSD license (see section 3 below).
|
19
|
-
|
20
|
-
git_time_extractor is useful for software development companies that want to discover the approximate time spent by developers on particular projects based on real data in the GIT revision control system. This is useful for managers, accountants, and for those who need to back up records for tax purposes.
|
21
|
-
|
22
|
-
For example, in the United States there is a Research & Development (R&D) tax credit available for companies who build or improve software for certain purposes. To claim this credit requires certain types of records. Check with your accountant to see if the results from this program is appropriate in your particular situation. You can learn more information about this particular credit at http://www.irs.gov/businesses/article/0,,id=156366,00.html.
|
23
|
-
|
24
|
-
== BENEFITS:
|
25
|
-
|
26
|
-
* Compute time records based on the timestamps of each code commit by each developer
|
27
|
-
* Compare these results with other time-sheets or metrics to measure the effectiveness of your team members
|
28
|
-
* Save money on taxes by producing documents required by your accountant to properly apply for certain tax credits
|
29
|
-
* It's a Free Open Source Tool
|
30
|
-
|
31
|
-
== FEATURES:
|
32
|
-
|
33
|
-
* Easy command-line operation
|
34
|
-
* Reads from a local GIT branch in the current directory
|
35
|
-
* Writes to a CSV-file that is compatible with Microsoft Excel, OpenOffice Calc, or Google Docs
|
36
|
-
|
37
|
-
== SYNOPSIS:
|
38
|
-
|
39
|
-
cd /path/to/your/repository
|
40
|
-
git_time_extractor > time_log.csv
|
41
|
-
|
42
|
-
== REQUIREMENTS:
|
43
|
-
|
44
|
-
The following GEMS are required as dependencies:
|
45
|
-
* git
|
46
|
-
* logger
|
47
|
-
|
48
|
-
== INSTALL:
|
49
|
-
|
50
|
-
gem install git_time_extractor
|
51
|
-
|
52
|
-
== UNINSTALL:
|
53
|
-
|
54
|
-
gem install git_time_extractor
|
55
|
-
|
56
|
-
== DEVELOPERS:
|
57
|
-
|
58
|
-
After checking out the source, run:
|
59
|
-
|
60
|
-
$ rake newb
|
61
|
-
|
62
|
-
This task will install any missing dependencies, run the tests/specs,
|
63
|
-
and generate the RDoc.
|
64
|
-
|
65
|
-
== LICENSE:
|
66
|
-
|
67
|
-
(The BSD License)
|
68
|
-
|
69
|
-
Copyright (c) 2012 Rietta Inc. All rights reserved.
|
70
|
-
|
71
|
-
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
72
|
-
|
73
|
-
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
74
|
-
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
75
|
-
|
76
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
77
|
-
|