gitwakatime 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -11
- data/Rakefile +1 -1
- data/bin/{gitlog.rb → gitwakatime} +0 -0
- data/gitwakatime.gemspec +19 -17
- data/lib/gitwakatime/actions.rb +8 -2
- data/lib/gitwakatime/cli.rb +23 -7
- data/lib/gitwakatime/commit.rb +15 -5
- data/lib/gitwakatime/commited_file.rb +28 -12
- data/lib/gitwakatime/mapper.rb +3 -6
- data/lib/gitwakatime/query.rb +48 -0
- data/lib/gitwakatime/timer.rb +22 -29
- data/lib/gitwakatime/version.rb +1 -1
- data/lib/gitwakatime.rb +1 -0
- data/spec/fixtures/actions.json +29854 -0
- data/spec/mapper_spec.rb +21 -0
- data/spec/query_spec.rb +23 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/timer_spec.rb +19 -0
- metadata +45 -9
- data/spec/commit_spec.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22675c8683f7ed9526d4715687b666c6b588387c
|
4
|
+
data.tar.gz: a72221a76657b3daea13882df4b5f18eb30021a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d53f9a01279650d49f4da02104108b0a0dd94ceffc5aa552dbc1a8ae5c70f55e64cb302b1b790e423e38e5bcf7807ceeb13d6fcff61ffc4efcfd380f7ad59c61
|
7
|
+
data.tar.gz: 814d9fcbb9f90a1b7138295bb3ea74e203c6367245e04067c598e91f356c58f7a9385bf148a262a7a67949f86060b4befeacd8741caec39225d36b8dc87d0492
|
data/README.md
CHANGED
@@ -1,24 +1,22 @@
|
|
1
1
|
# GitWakaTime
|
2
2
|
|
3
|
-
|
3
|
+
GitWakaTime is a mashup between data obtained through "wakatime" and the data we all create using git.
|
4
|
+
The prinicpal is to capture a baseline of activity for a task and answer the age old question "How much time did I spend on this?"
|
4
5
|
|
5
6
|
## Installation
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
gem 'gitwakatime'
|
10
|
-
|
11
|
-
And then execute:
|
12
|
-
|
13
|
-
$ bundle
|
14
|
-
|
15
|
-
Or install it yourself as:
|
8
|
+
Install the gem:
|
16
9
|
|
17
10
|
$ gem install gitwakatime
|
18
11
|
|
19
12
|
## Usage
|
20
13
|
|
21
|
-
|
14
|
+
Creates a .gitwakatime.yml file on the user's home directory ~/.gitwakatime.yml which will contain your api keys
|
15
|
+
$ gitwakatime init
|
16
|
+
|
17
|
+
Process the current directory
|
18
|
+
|
19
|
+
$ gitwakatime tally
|
22
20
|
|
23
21
|
## Contributing
|
24
22
|
|
data/Rakefile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
File without changes
|
data/gitwakatime.gemspec
CHANGED
@@ -4,27 +4,29 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'gitwakatime/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'gitwakatime'
|
8
8
|
spec.version = GitWakaTime::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
9
|
+
spec.authors = ['Russell Osborne']
|
10
|
+
spec.email = ['russosborn@gmail.com']
|
11
|
+
spec.summary = 'A Tool that will compile git data with wakatime data to establish time per commit'
|
12
|
+
spec.description = 'A Tool that will compile git data with wakatime data to establish time per commit '
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_runtime_dependency
|
22
|
-
spec.add_runtime_dependency
|
23
|
-
spec.add_runtime_dependency
|
24
|
-
spec.add_runtime_dependency
|
25
|
-
spec.add_runtime_dependency
|
26
|
-
spec.add_runtime_dependency
|
27
|
-
spec.
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
21
|
+
spec.add_runtime_dependency 'git', '>= 0'
|
22
|
+
spec.add_runtime_dependency 'wakatime', '>= 0.0.2'
|
23
|
+
spec.add_runtime_dependency 'logger', '>= 0'
|
24
|
+
spec.add_runtime_dependency 'thor', '>= 0'
|
25
|
+
spec.add_runtime_dependency 'chronic_duration', '>=0'
|
26
|
+
spec.add_runtime_dependency 'colorize'
|
27
|
+
spec.add_runtime_dependency 'activesupport'
|
28
|
+
spec.add_development_dependency('bundler', ['>= 0'])
|
29
|
+
spec.add_development_dependency 'rake'
|
30
|
+
spec.add_development_dependency 'rspec'
|
31
|
+
spec.add_development_dependency('webmock', ['>= 0'])
|
30
32
|
end
|
data/lib/gitwakatime/actions.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
module GitWakaTime
|
2
2
|
# Extract Duration Data from Actions for the WAKATIME API
|
3
3
|
class Actions
|
4
|
+
attr_accessor :actions
|
4
5
|
def initialize(args)
|
6
|
+
return @actions = args[:actions] if args[:actions]
|
5
7
|
fail if args[:project].nil?
|
6
8
|
@project = args[:project]
|
7
9
|
@args = args
|
@@ -15,12 +17,16 @@ module GitWakaTime
|
|
15
17
|
time = Benchmark.realtime do
|
16
18
|
@actions = @client.actions(@args)
|
17
19
|
# remove returned actions that do not have the project we want
|
18
|
-
@actions.keep_if
|
20
|
+
@actions = @actions.keep_if do |a|
|
21
|
+
a['project'] == @project
|
22
|
+
end
|
23
|
+
|
19
24
|
end
|
20
25
|
Log.new "API took #{time}s"
|
26
|
+
@actions
|
21
27
|
end
|
22
28
|
|
23
|
-
def actions_to_durations(
|
29
|
+
def actions_to_durations(_project = nil, timeout = 15)
|
24
30
|
durations = []
|
25
31
|
current = []
|
26
32
|
@actions.each do | action |
|
data/lib/gitwakatime/cli.rb
CHANGED
@@ -4,6 +4,11 @@ require 'wakatime'
|
|
4
4
|
require 'chronic_duration'
|
5
5
|
require 'yaml'
|
6
6
|
require 'thor'
|
7
|
+
require 'active_support/core_ext/date/calculations'
|
8
|
+
require 'active_support/core_ext/date_and_time/calculations'
|
9
|
+
require 'active_support/core_ext/integer/time'
|
10
|
+
require 'active_support/core_ext/time'
|
11
|
+
|
7
12
|
module GitWakaTime
|
8
13
|
# Provides two CLI actions init and tally
|
9
14
|
class Cli < Thor
|
@@ -22,16 +27,27 @@ module GitWakaTime
|
|
22
27
|
|
23
28
|
desc 'tally', 'Produce time spend for each commit and file in each commit'
|
24
29
|
method_option :file, aliases: '-f', default: '.'
|
30
|
+
method_option :start_on, aliases: '-s', default: nil
|
25
31
|
def tally
|
26
|
-
path
|
32
|
+
path, GitWakaTime.config.root = File.expand_path(options.file)
|
33
|
+
date = Date.parse(options.start_on) if options.start_on
|
34
|
+
date = 1.month.ago.beginning_of_month unless options.start_on
|
27
35
|
GitWakaTime.config.load_config_yaml
|
28
|
-
@
|
36
|
+
@git_map = Mapper.new(path, start_at: date)
|
37
|
+
@actions = Query.new(@git_map.commits, File.basename(path)).get
|
38
|
+
|
39
|
+
@timer = Timer.new(@git_map.commits, @actions, File.basename(path)).process
|
29
40
|
|
30
|
-
@timer
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
41
|
+
@timer.each do |date, commits|
|
42
|
+
Log.new format('%-40s %-40s'.blue,
|
43
|
+
date,
|
44
|
+
"Total #{ChronicDuration.output commits.map(&:time_in_seconds).reduce(&:+)}"
|
45
|
+
)
|
46
|
+
commits.each do |commit|
|
47
|
+
# Log.new commit.message
|
48
|
+
Log.new commit.to_s
|
49
|
+
commit.files.each { |file| Log.new file.to_s }
|
50
|
+
end
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
data/lib/gitwakatime/commit.rb
CHANGED
@@ -1,20 +1,25 @@
|
|
1
1
|
module GitWakaTime
|
2
2
|
class Commit
|
3
|
-
attr_accessor :sha, :date, :message, :files, :time_in_seconds
|
3
|
+
attr_accessor :raw_commit, :sha, :date, :message, :files, :time_in_seconds, :git, :author
|
4
4
|
|
5
5
|
def initialize(git, commit, load_files = true)
|
6
6
|
@raw_commit = commit
|
7
7
|
@sha = @raw_commit.sha
|
8
8
|
@date = @raw_commit.date
|
9
9
|
@message = @raw_commit.message
|
10
|
+
@author = @raw_commit.author
|
10
11
|
@time_in_seconds = 0
|
11
12
|
@git = git
|
12
13
|
@load_files = load_files
|
13
|
-
@files =
|
14
|
+
@files = get_files if load_files
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
[sha[0..8], time_in_seconds]
|
14
19
|
end
|
15
20
|
|
16
21
|
def to_s
|
17
|
-
format('%-8s %8s %-30s %-80s'.green,
|
22
|
+
format(' %-8s %8s %-30s %-80s'.green,
|
18
23
|
sha[0..8],
|
19
24
|
date,
|
20
25
|
ChronicDuration.output(time_in_seconds),
|
@@ -22,12 +27,17 @@ module GitWakaTime
|
|
22
27
|
)
|
23
28
|
end
|
24
29
|
|
30
|
+
def oldest_dependent
|
31
|
+
@files.sort { |f| f.commit.date }.first
|
32
|
+
end
|
33
|
+
|
25
34
|
private
|
26
35
|
|
27
|
-
def
|
36
|
+
def get_files
|
37
|
+
# TODO: Assume gap time to lookup time prior to first commit.
|
28
38
|
return [] unless @raw_commit.parent
|
29
39
|
@raw_commit.diff_parent.stats[:files].keys.map do |file|
|
30
|
-
CommitedFile.new(git: @git
|
40
|
+
CommitedFile.new(git: @git, commit: @raw_commit, name: file, dependent: false)
|
31
41
|
end
|
32
42
|
end
|
33
43
|
end
|
@@ -5,31 +5,47 @@ module GitWakaTime
|
|
5
5
|
def initialize(args)
|
6
6
|
@git = args[:git]
|
7
7
|
@name = args[:name]
|
8
|
-
@
|
8
|
+
@commit = args[:commit]
|
9
9
|
@time_in_seconds = 0
|
10
|
-
@find_dependent = args[:dependent] || true
|
11
10
|
|
12
|
-
|
11
|
+
@dependent_commit = find_dependent_commit(name)
|
13
12
|
end
|
14
13
|
|
15
14
|
def to_s
|
16
|
-
format('
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
format(' %-20s %-40s %-100s '.blue,
|
16
|
+
(dependent_commit.sha[0..8] if @dependent_commit),
|
17
|
+
ChronicDuration.output(@time_in_seconds.to_f),
|
18
|
+
name
|
19
|
+
|
20
20
|
)
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
25
|
+
def find_dependent_commit(name)
|
26
|
+
i = 1
|
27
|
+
dependent = nil
|
28
|
+
commit = 1
|
29
|
+
|
30
|
+
begin
|
31
|
+
commit = load_dependent_commit(name, i: i)
|
32
|
+
dependent = Commit.new(@git, commit, false) if allowed_commit(commit)
|
33
|
+
i += 1
|
34
|
+
end until !dependent.nil? || commit.nil?
|
35
|
+
dependent
|
36
|
+
end
|
37
|
+
|
38
|
+
def allowed_commit(commit)
|
39
|
+
return false if commit.nil?
|
40
|
+
return false if commit.author.name != @git.config('user.name')
|
41
|
+
return false if commit.message.include?('Merge branch')
|
42
|
+
true
|
28
43
|
end
|
29
44
|
|
30
|
-
def load_dependent_commit(name)
|
31
|
-
@git.log
|
45
|
+
def load_dependent_commit(name, i: 1)
|
46
|
+
@git.log.object(@commit.sha).path(name)[i]
|
32
47
|
rescue Git::GitExecuteError
|
48
|
+
puts error
|
33
49
|
nil
|
34
50
|
end
|
35
51
|
end
|
data/lib/gitwakatime/mapper.rb
CHANGED
@@ -2,19 +2,16 @@ module GitWakaTime
|
|
2
2
|
# Th
|
3
3
|
class Mapper
|
4
4
|
attr_accessor :commits, :git
|
5
|
-
def initialize(path, commits
|
5
|
+
def initialize(path, commits: 500, start_at: Date.today)
|
6
6
|
Log.new 'Mapping commits for dependent commits'
|
7
7
|
time = Benchmark.realtime do
|
8
8
|
@git = Git.open(path)
|
9
|
-
# TODO: Expose since timestamp as a CLI option
|
10
|
-
# TODO: Expose number of commits as a CLI option
|
11
|
-
first_of_month = Date.new(Date.today.year, Date.today.month, 1)
|
12
9
|
|
13
|
-
logs = @git.log(commits).since(
|
10
|
+
logs = @git.log(commits).since(start_at).until(Date.today)
|
14
11
|
|
15
12
|
@commits = logs.map do |git_commit|
|
16
13
|
Commit.new(@git, git_commit)
|
17
|
-
end
|
14
|
+
end.compact
|
18
15
|
end
|
19
16
|
Log.new "Map Completed took #{time}s"
|
20
17
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'colorize'
|
3
|
+
require 'active_support/core_ext/time'
|
4
|
+
|
5
|
+
module GitWakaTime
|
6
|
+
# Integrates the nested hash from mapper with actions api
|
7
|
+
class Query
|
8
|
+
def initialize(commits, project, _path = nil)
|
9
|
+
@commits = commits
|
10
|
+
@api_limit = 15
|
11
|
+
@project = project
|
12
|
+
@actions = []
|
13
|
+
@requests = time_params
|
14
|
+
end
|
15
|
+
|
16
|
+
def get
|
17
|
+
@requests.each do |params|
|
18
|
+
Log.new "Requesting actions #{params[:start].to_date} to #{params[:end].to_date}".red
|
19
|
+
@actions = @actions.concat Actions.new(params).actions
|
20
|
+
end
|
21
|
+
|
22
|
+
Actions.new(actions: @actions.uniq(&:id)).actions_to_durations
|
23
|
+
end
|
24
|
+
|
25
|
+
def time_params
|
26
|
+
commits = @commits.map(&:date)
|
27
|
+
d_commits = @commits.map do |c|
|
28
|
+
c.files.map(&:dependent_commit).compact.map(&:date)
|
29
|
+
end
|
30
|
+
timestamps = (commits + d_commits.flatten).uniq
|
31
|
+
num_requests = (timestamps.first.to_date - timestamps.last.to_date) / @api_limit
|
32
|
+
i = 0
|
33
|
+
request_params = num_requests.to_f.ceil.times.map do
|
34
|
+
|
35
|
+
params = {
|
36
|
+
start: (timestamps.last.to_date + (i * @api_limit)).to_time.beginning_of_day,
|
37
|
+
end: (timestamps.last.to_date + ((i + 1) * @api_limit)).to_time.end_of_day,
|
38
|
+
project: @project
|
39
|
+
}
|
40
|
+
i += 1
|
41
|
+
params
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
request_params
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/gitwakatime/timer.rb
CHANGED
@@ -1,42 +1,31 @@
|
|
1
1
|
require 'benchmark'
|
2
2
|
require 'colorize'
|
3
|
-
require '
|
3
|
+
require 'active_support/core_ext/time'
|
4
|
+
|
4
5
|
module GitWakaTime
|
5
6
|
# Integrates the nested hash from mapper with actions api
|
6
7
|
class Timer
|
7
|
-
def initialize(commits,
|
8
|
-
@commits
|
9
|
-
|
10
|
-
|
11
|
-
@actions = Actions.new(params)
|
12
|
-
@actions_with_durations = @actions.actions_to_durations
|
13
|
-
end
|
14
|
-
|
15
|
-
def time_params
|
16
|
-
commits = @commits.map(&:date)
|
17
|
-
d_commits = @commits.map do |c|
|
18
|
-
c.files.map(&:dependent_commit).compact.map(&:date)
|
19
|
-
end
|
20
|
-
timestamps = (commits + d_commits.flatten).uniq
|
21
|
-
api_limit = Time.now - 60 * 60 * 24 * 60
|
22
|
-
min = api_limit > timestamps.min ? api_limit : timestamps.min
|
23
|
-
{ start: min, end: timestamps.max }
|
8
|
+
def initialize(commits, actions_with_durations, project)
|
9
|
+
@commits = commits
|
10
|
+
@actions_with_durations = actions_with_durations
|
11
|
+
@project = project
|
24
12
|
end
|
25
13
|
|
26
14
|
def total
|
27
|
-
|
28
|
-
Log.new "Total Recorded time #{ChronicDuration.output
|
15
|
+
total_time = sum_actions @actions_with_durations
|
16
|
+
Log.new "Total Recorded time #{ChronicDuration.output total_time}", :red
|
29
17
|
end
|
30
18
|
|
31
19
|
def total_commited
|
32
|
-
|
33
|
-
.map
|
20
|
+
total_commited = ChronicDuration.output(@commits_with_duration
|
21
|
+
.map(&:time_in_seconds)
|
34
22
|
.reduce(:+).to_f)
|
35
|
-
Log.new "Total Commited Time #{
|
23
|
+
Log.new "Total Commited Time #{total_commited} ".red
|
36
24
|
end
|
37
25
|
|
38
26
|
def process
|
39
27
|
@commits_with_duration = @commits.each do |commit|
|
28
|
+
|
40
29
|
if !commit.files.empty?
|
41
30
|
commit.files.each_with_index do |file, i|
|
42
31
|
time = sum_actions relevant_actions(commit, file)
|
@@ -44,12 +33,13 @@ module GitWakaTime
|
|
44
33
|
commit.time_in_seconds += time
|
45
34
|
end
|
46
35
|
else
|
47
|
-
commit.time_in_seconds = sum_actions(actions_before(commit.date))
|
36
|
+
# commit.time_in_seconds = sum_actions(actions_before(commit.date))
|
48
37
|
end
|
49
|
-
end
|
38
|
+
end.compact
|
50
39
|
total
|
51
40
|
total_commited
|
52
41
|
@commits_with_duration
|
42
|
+
@commits_with_duration_by_date = @commits_with_duration.group_by { |c| c.date.to_date }
|
53
43
|
end
|
54
44
|
|
55
45
|
private
|
@@ -66,11 +56,8 @@ module GitWakaTime
|
|
66
56
|
# If this file had an earlier commit ensure the actions timestamp
|
67
57
|
# is after that commit
|
68
58
|
if file.dependent_commit
|
69
|
-
actions = actions.
|
70
|
-
Time.at(action['time']) >= file.dependent_commit.date
|
71
|
-
end
|
59
|
+
actions = actions_after(actions, file.dependent_commit.date)
|
72
60
|
end
|
73
|
-
|
74
61
|
actions
|
75
62
|
end
|
76
63
|
|
@@ -80,6 +67,12 @@ module GitWakaTime
|
|
80
67
|
end
|
81
68
|
end
|
82
69
|
|
70
|
+
def actions_after(actions, date)
|
71
|
+
actions.select do |action|
|
72
|
+
Time.at(action['time']) >= date
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
83
76
|
def sum_actions(actions)
|
84
77
|
actions.map { |action| action['duration'] }
|
85
78
|
.reduce(:+).to_f
|