gitwakatime 0.0.1 → 0.0.2
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/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
|