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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 67380a618ca6269c81cdbbcfeccbeacb07320737
4
- data.tar.gz: 13ac9b92971197d84aa25ddbcb234ae653c78531
3
+ metadata.gz: 5cd63888dc7235ee55baee19108e6b576dbdf474
4
+ data.tar.gz: a0ac045a5dd7ad3d81e763c4a76d74880dcaaf42
5
5
  SHA512:
6
- metadata.gz: 3f6c5fd9ed017ad1b09ae83bda466c1d0d63188e052340b6dbdb15d1260b31f88c11f27f90fe56604848e28dec2e9d154427c36f853bd6b86c45ce7140d3ead6
7
- data.tar.gz: d74d5556f3c677a1bfc2001cc4ca38d6558a5e71b1d7a68071deb8e64cede801bca5388480ea5f86fb1e4ab7a82cd960508d420026a1f63cefea36fad3ecd7a5
6
+ metadata.gz: 0363407a1a542759865af72d0958e2af5c4fa1dc235e78bdd56ac6fb2a4809f47275d25618b45bd8769a4cbb54c27751fcaefbb49fc5c0d942f76d5caa90756a
7
+ data.tar.gz: 503fe4be1588f616c93911c073a59e1bc8a563e12790cc5532c266f39108967e30a2ebbbb7c4c2415a8ad685b29b85068f91fa0ba707e19b40e828664e1d2ac7
data/History.txt CHANGED
@@ -1,3 +1,6 @@
1
+ === 0.3.0 / 2014-11-13
2
+ Major update. Command line configurable assumptions and multiple author support.
3
+ === 0.2.3
1
4
  === 0.1.0 / 2012-05-05
2
5
 
3
6
  * 1 major enhancement
@@ -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
- #puts "\n# Arguments #{ARGV.length}\n"
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
- if ARGV.empty?
20
- output_file = "-"
21
- path_to_repo = Dir.pwd
22
- project_name = ""
23
- valid_usage = true
24
- elsif "-h" == ARGV[0].downcase || "--help" == ARGV[0].downcase
25
- # Show help
26
- elsif ARGV.length == 2
27
- output_file = ARGV.pop
28
- path_to_repo = ARGV.pop
29
- valid_usage = true
30
- elsif ARGV.length == 1
31
- output_file = "-"
32
- path_to_repo = ARGV.pop
33
- valid_usage = true
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
- unless valid_usage
37
- puts "Usage: git_time_extractor PROJECT_NAME [PATH_TO_REPO] [OUTPUT_FILE]"
38
- puts "Copyright 2012 Rietta Inc. http://www.rietta.com"
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
- else
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
@@ -0,0 +1,9 @@
1
+ require 'ostruct'
2
+ require 'logger'
3
+ require 'git'
4
+ require 'csv'
5
+
6
+ autoload :Author, 'author'
7
+ autoload :GitTimeExtractor, 'git_time_extractor'
8
+ autoload :PivotalIdsExtractor, 'pivotal_ids_extractor'
9
+ autoload :TimeEntry, 'time_entry'
@@ -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) 2012 Rietta Inc. and licensed under the terms of the BSD license.
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.2'
12
-
13
- require 'rubygems'
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
- # Go through the GIT commit log, to compute the elapsed working time of each committing developer, based
22
- # on a few assumptions:
23
- #
24
- # (1) A series of commits within a 3 hour window are part of the same development session
25
- # (2) A single commit (or the first commit of the session) is considered to represent 30 minutes of work time
26
- # (3) The more frequent a developer commits to the repository while working, the more accurate the time report will be
27
- #
28
- #
29
- def self.process_git_log_into_time(path_to_git_repo = "./", path_to_output_file = "-", project_name = "")
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
- if "-" != path_to_output_file
32
- raise "Output path not yet implemented. Use a Unix pipe to write to your desired file. For example: git_time_extractor ./ > my_result.csv\n"
33
- end
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(1000)
40
- log_entries = logs.entries.reverse
41
- worklog = {}
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
- author_date = commit.author_date.to_date
46
- daylog = worklog[author_date] || OpenStruct.new(:date => author_date, :duration => 0, :commit_count => 0, :pivotal_stories => Set.new )
47
- daylog.author = commit.author
48
- daylog.message = "#{daylog.message} --- #{commit.message}"
49
- daylog.duration = daylog.duration + calc_duration_in_minutes(log_entries, index)
50
-
51
- # The git commit count
52
- daylog.commit_count += 1
53
-
54
- # Pivotal Stories
55
- stories = pivotal_ids(commit.message)
56
- if stories
57
- # It's a set, so each story only gets added once per day
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
- # Print the header row for the CSV
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
- end # process_git_log_into_time
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 self.say_hi
131
- "hi"
132
- end
133
-
134
- # --- [#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
135
- # s = "[#62749778] [#62749778] [#6274977] [#1] [#231]"
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
- stories.sort
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
+ [![Gem Version](https://badge.fury.io/rb/git_time_extractor.png)](http://badge.fury.io/rb/git_time_extractor)
2
+ [![Code Climate](https://codeclimate.com/github/rietta/git_time_extractor/badges/gpa.svg)](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
+ &copy; 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.2.3
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-02-22 00:00:00.000000000 Z
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.6
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.6
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.8
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.8
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: products@rietta.com
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.1
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
-