gitwakatime 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -1
- data/README.md +10 -6
- data/gitwakatime.gemspec +2 -0
- data/lib/gitwakatime.rb +55 -2
- data/lib/gitwakatime/action.rb +4 -0
- data/lib/gitwakatime/cli.rb +26 -10
- data/lib/gitwakatime/commit.rb +18 -21
- data/lib/gitwakatime/commited_file.rb +38 -25
- data/lib/gitwakatime/durations.rb +87 -0
- data/lib/gitwakatime/log.rb +1 -0
- data/lib/gitwakatime/mapper.rb +17 -11
- data/lib/gitwakatime/query.rb +17 -12
- data/lib/gitwakatime/timer.rb +23 -20
- data/lib/gitwakatime/version.rb +1 -1
- data/spec/commit_spec.rb +0 -0
- data/spec/commited_file_spec.rb +93 -0
- data/spec/dummy/dot_git/sourcetreeconfig +2 -2
- data/spec/mapper_spec.rb +10 -6
- data/spec/query_spec.rb +17 -7
- data/spec/spec_helper.rb +17 -1
- data/spec/timer_spec.rb +10 -5
- metadata +36 -3
- data/lib/gitwakatime/actions.rb +0 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46d9e8b02b43ba532f6631155c4beadff25d65e1
|
4
|
+
data.tar.gz: 7ea139ea0454e766c82186a526bc17cad859a542
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: baed9a3f9d7cd015d0148356e657cac46b792ae0b8276c4ad61848ec26cfc700bbfbf0b6b36abff102bf825cedf9723292143aa689033bdcd312ca1fa0bce1e9
|
7
|
+
data.tar.gz: a51aca840239070583c1f81f3326931f72cd023e893850b7e755c8da30308bfb5325f844e387891fca7ca98dffdb8dc0e5a70a0877848120de0291c180e99d5c
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -4,10 +4,8 @@
|
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/gitwakatime.svg)](http://badge.fury.io/rb/gitwakatime)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/rposborne/gitwakatime/badges/gpa.svg)](https://codeclimate.com/github/rposborne/gitwakatime)
|
6
6
|
|
7
|
-
GitWakaTime is a
|
8
|
-
The
|
9
|
-
|
10
|
-
|
7
|
+
GitWakaTime is a mash up between data obtained through "wakatime" and the data we all create using git.
|
8
|
+
The principal is to capture a baseline of activity for a task and answer the age old question "How much time did I spend on this?" or "What is the minimum amount I can charge for my time"
|
11
9
|
|
12
10
|
## Installation
|
13
11
|
|
@@ -15,15 +13,21 @@ Install the gem:
|
|
15
13
|
|
16
14
|
$ gem install gitwakatime
|
17
15
|
|
18
|
-
|
16
|
+
Run the setup command: (you will need your wakatime api key). Creates a .gitwakatime.yml file on the user's home directory ~/.gitwakatime.yml which will contain your api keys
|
19
17
|
|
20
|
-
Creates a .gitwakatime.yml file on the user's home directory ~/.gitwakatime.yml which will contain your api keys
|
21
18
|
$ gitwakatime init
|
22
19
|
|
20
|
+
|
21
|
+
## Usage
|
23
22
|
Process the current directory
|
24
23
|
|
25
24
|
$ gitwakatime tally
|
26
25
|
|
26
|
+
Hard reset of the local cache database
|
27
|
+
|
28
|
+
$ gitwakatime reset
|
29
|
+
|
30
|
+
|
27
31
|
## Output
|
28
32
|
Total Recorded time 21 hrs 59 mins 59 secs
|
29
33
|
Total Commited Time 18 hrs 50 mins 53 secs
|
data/gitwakatime.gemspec
CHANGED
@@ -25,6 +25,8 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_runtime_dependency 'chronic_duration', '>=0'
|
26
26
|
spec.add_runtime_dependency 'colorize'
|
27
27
|
spec.add_runtime_dependency 'activesupport'
|
28
|
+
spec.add_runtime_dependency 'sequel'
|
29
|
+
spec.add_runtime_dependency 'sqlite3'
|
28
30
|
spec.add_development_dependency('bundler', ['>= 0'])
|
29
31
|
spec.add_development_dependency 'rake'
|
30
32
|
spec.add_development_dependency 'rspec'
|
data/lib/gitwakatime.rb
CHANGED
@@ -1,5 +1,16 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
if ENV['thor_env'] != 'test'
|
3
|
+
DB = Sequel.connect("sqlite://#{File.join(Dir.home, '.wakatime.sqlite')}")
|
4
|
+
else
|
5
|
+
# Use a in memory db to have a nice clean testing bed.
|
6
|
+
DB = Sequel.sqlite
|
7
|
+
end
|
8
|
+
|
9
|
+
DB.use_timestamp_timezones = false
|
10
|
+
|
1
11
|
require 'gitwakatime/version'
|
2
|
-
require 'gitwakatime/
|
12
|
+
require 'gitwakatime/durations'
|
13
|
+
require 'gitwakatime/action'
|
3
14
|
require 'gitwakatime/commit'
|
4
15
|
require 'gitwakatime/mapper'
|
5
16
|
require 'gitwakatime/query'
|
@@ -7,21 +18,63 @@ require 'gitwakatime/timer'
|
|
7
18
|
require 'gitwakatime/log'
|
8
19
|
require 'gitwakatime/commited_file'
|
9
20
|
require 'gitwakatime/cli'
|
21
|
+
|
10
22
|
# Silence is golden
|
11
23
|
module GitWakaTime
|
12
24
|
class Configuration
|
13
|
-
attr_accessor :api_key, :log_level, :root, :project
|
25
|
+
attr_accessor :api_key, :log_level, :root, :project, :git
|
14
26
|
|
15
27
|
def initialize
|
16
28
|
self.api_key = nil
|
17
29
|
self.log_level = :info
|
18
30
|
end
|
19
31
|
|
32
|
+
def user_name
|
33
|
+
GitWakaTime.config.git.config('user.name')
|
34
|
+
end
|
35
|
+
|
20
36
|
def load_config_yaml
|
21
37
|
yaml = YAML.load_file(File.join(Dir.home, '.wakatime.yml'))
|
22
38
|
self.api_key = yaml[:api_key]
|
23
39
|
self.log_level = yaml[:log_level]
|
24
40
|
end
|
41
|
+
|
42
|
+
def setup_local_db
|
43
|
+
DB.create_table? :commits do
|
44
|
+
primary_key :id
|
45
|
+
String :sha
|
46
|
+
String :parent_sha
|
47
|
+
String :project
|
48
|
+
integer :time_in_seconds, default: 0
|
49
|
+
datetime :date
|
50
|
+
text :message
|
51
|
+
String :author
|
52
|
+
end
|
53
|
+
|
54
|
+
DB.create_table? :commited_files do
|
55
|
+
primary_key :id
|
56
|
+
integer :commit_id
|
57
|
+
String :dependent_sha
|
58
|
+
DateTime :dependent_date
|
59
|
+
integer :time_in_seconds, default: 0
|
60
|
+
String :sha
|
61
|
+
String :name
|
62
|
+
String :project
|
63
|
+
index :dependent_sha
|
64
|
+
index :sha
|
65
|
+
end
|
66
|
+
|
67
|
+
DB.create_table? :actions do
|
68
|
+
primary_key :id
|
69
|
+
String :uuid
|
70
|
+
DateTime :time
|
71
|
+
integer :duration, default: 0
|
72
|
+
String :file
|
73
|
+
String :branch
|
74
|
+
String :project
|
75
|
+
index :uuid, unique: true
|
76
|
+
end
|
77
|
+
end
|
25
78
|
end
|
26
79
|
|
27
80
|
def self.config
|
data/lib/gitwakatime/cli.rb
CHANGED
@@ -18,35 +18,51 @@ module GitWakaTime
|
|
18
18
|
method_option :file, aliases: '-f', default: '.'
|
19
19
|
|
20
20
|
def init
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
unless File.exist?(File.join(Dir.home, '.wakatime.yml'))
|
22
|
+
api_key = ask('What is your wakatime api key? ( Get it here https://wakatime.com/settings):')
|
23
|
+
say('Adding .wakatime.yml to home directory')
|
24
|
+
|
25
|
+
create_file File.join(Dir.home, '.wakatime.yml') do
|
26
|
+
YAML.dump(api_key: api_key, last_commit: nil, log_level: :info)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
reset
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'reset', 'Reset local sqlite db'
|
33
|
+
def reset
|
34
|
+
DB.disconnect
|
35
|
+
|
36
|
+
db_path = File.expand_path(File.join(Dir.home, '.wakatime.sqlite'))
|
37
|
+
FileUtils.rm_r(db_path) if File.exist?(db_path)
|
38
|
+
DB.connect("sqlite://#{db_path}")
|
39
|
+
GitWakaTime.config.setup_local_db
|
26
40
|
end
|
27
41
|
|
28
42
|
desc 'tally', 'Produce time spend for each commit and file in each commit'
|
29
43
|
method_option :file, aliases: '-f', default: '.'
|
30
44
|
method_option :start_on, aliases: '-s', default: nil
|
31
45
|
def tally
|
46
|
+
GitWakaTime.config.setup_local_db
|
32
47
|
path, GitWakaTime.config.root = File.expand_path(options.file)
|
33
48
|
date = Date.parse(options.start_on) if options.start_on
|
34
49
|
date = 1.month.ago.beginning_of_month unless options.start_on
|
35
50
|
GitWakaTime.config.load_config_yaml
|
36
|
-
|
37
|
-
@
|
51
|
+
GitWakaTime.config.git = Git.open(path)
|
52
|
+
@git_map = Mapper.new(start_at: date)
|
53
|
+
@actions = Query.new(Commit.all, File.basename(path)).get
|
38
54
|
|
39
|
-
@timer = Timer.new(
|
55
|
+
@timer = Timer.new(Commit.all, @actions, File.basename(path)).process
|
40
56
|
|
41
57
|
@timer.each do |date, commits|
|
42
58
|
Log.new format('%-40s %-40s'.blue,
|
43
59
|
date,
|
44
|
-
"Total #{ChronicDuration.output commits.map(&:time_in_seconds).reduce(&:+)}"
|
60
|
+
"Total #{ChronicDuration.output commits.map(&:time_in_seconds).compact.reduce(&:+).to_i}"
|
45
61
|
)
|
46
62
|
commits.each do |commit|
|
47
63
|
# Log.new commit.message
|
48
64
|
Log.new commit.to_s
|
49
|
-
commit.
|
65
|
+
commit.commited_files.each { |file| Log.new file.to_s }
|
50
66
|
end
|
51
67
|
end
|
52
68
|
end
|
data/lib/gitwakatime/commit.rb
CHANGED
@@ -1,28 +1,15 @@
|
|
1
1
|
module GitWakaTime
|
2
|
-
class Commit
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
@raw_commit = commit
|
7
|
-
@sha = @raw_commit.sha
|
8
|
-
@date = @raw_commit.date
|
9
|
-
@message = @raw_commit.message
|
10
|
-
@author = @raw_commit.author
|
11
|
-
@time_in_seconds = 0
|
12
|
-
@git = git
|
13
|
-
@load_files = load_files
|
14
|
-
@files = get_files if load_files
|
15
|
-
end
|
16
|
-
|
17
|
-
def inspect
|
18
|
-
[sha[0..8], time_in_seconds]
|
2
|
+
class Commit < Sequel::Model
|
3
|
+
one_to_many :commited_files
|
4
|
+
def after_create
|
5
|
+
get_files
|
19
6
|
end
|
20
7
|
|
21
8
|
def to_s
|
22
9
|
format(' %-8s %8s %-30s %-80s'.green,
|
23
10
|
sha[0..8],
|
24
11
|
date,
|
25
|
-
ChronicDuration.output(time_in_seconds),
|
12
|
+
ChronicDuration.output(time_in_seconds.to_i),
|
26
13
|
message
|
27
14
|
)
|
28
15
|
end
|
@@ -31,13 +18,23 @@ module GitWakaTime
|
|
31
18
|
@files.sort { |f| f.commit.date }.first
|
32
19
|
end
|
33
20
|
|
21
|
+
def time_in_seconds
|
22
|
+
commited_files.collect {|f| f.time_in_seconds}.inject(:+)
|
23
|
+
end
|
24
|
+
|
34
25
|
private
|
35
26
|
|
36
27
|
def get_files
|
28
|
+
@raw_commit = GitWakaTime.config.git.gcommit(sha)
|
37
29
|
# TODO: Assume gap time to lookup time prior to first commit.
|
38
|
-
|
39
|
-
|
40
|
-
|
30
|
+
if @raw_commit.parent
|
31
|
+
update(parent_sha: @raw_commit.parent.sha)
|
32
|
+
|
33
|
+
@raw_commit.diff_parent.stats[:files].keys.map do |file|
|
34
|
+
CommitedFile.find_or_create(commit_id: id, name: file) do |c|
|
35
|
+
c.update(sha: sha, project: project)
|
36
|
+
end
|
37
|
+
end
|
41
38
|
end
|
42
39
|
end
|
43
40
|
end
|
@@ -1,22 +1,18 @@
|
|
1
1
|
module GitWakaTime
|
2
|
-
class CommitedFile
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@time_in_seconds = 0
|
10
|
-
|
11
|
-
@dependent_commit = find_dependent_commit(name)
|
2
|
+
class CommitedFile < Sequel::Model
|
3
|
+
many_to_one :commit
|
4
|
+
# No two committed files should have the same name + dependent_date this
|
5
|
+
# means a split tree, and we should split time between the two, or more, commits.
|
6
|
+
|
7
|
+
def before_create
|
8
|
+
find_dependent_commit(name)
|
12
9
|
end
|
13
10
|
|
14
11
|
def to_s
|
15
|
-
format('
|
16
|
-
(
|
17
|
-
ChronicDuration.output(
|
12
|
+
format(' %-20s %-40s %-100s '.blue,
|
13
|
+
(dependent_sha[0..8] if dependent_sha),
|
14
|
+
ChronicDuration.output(time_in_seconds.to_f),
|
18
15
|
name
|
19
|
-
|
20
16
|
)
|
21
17
|
end
|
22
18
|
|
@@ -24,26 +20,43 @@ module GitWakaTime
|
|
24
20
|
|
25
21
|
def find_dependent_commit(name)
|
26
22
|
i = 1
|
27
|
-
dependent = nil
|
28
|
-
commit = 1
|
29
23
|
|
24
|
+
# Call git log for path, loop through till we find a valid commit or run
|
25
|
+
# out of commits to check
|
26
|
+
commits = load_dependent_commits(name)
|
30
27
|
begin
|
31
|
-
commit =
|
32
|
-
|
28
|
+
commit = commits[i]
|
29
|
+
|
30
|
+
if commit && allowed_commit(commit)
|
31
|
+
|
32
|
+
self.dependent_date = commit.date
|
33
|
+
self.dependent_sha = commit.sha
|
34
|
+
|
35
|
+
# This is the magical fix for the split tree issue
|
36
|
+
# Current though is this will fail if more than 2 split tree files
|
37
|
+
split_tree_file = CommitedFile.where(name: name, dependent_sha: commit.sha).first
|
38
|
+
if split_tree_file && split_tree_file.commit
|
39
|
+
if self.commit.date < split_tree_file.commit.date
|
40
|
+
self.dependent_date = split_tree_file.commit.date
|
41
|
+
elsif self.commit.date > split_tree_file.commit.date
|
42
|
+
split_tree_file.update(dependent_date: commit.date)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
33
47
|
i += 1
|
34
|
-
end until !
|
35
|
-
dependent
|
48
|
+
end until !dependent_sha.nil? || commit.nil?
|
36
49
|
end
|
37
50
|
|
38
51
|
def allowed_commit(commit)
|
39
|
-
return false if commit.
|
40
|
-
return false if commit.author.name !=
|
41
|
-
return false if commit.
|
52
|
+
return false if commit.sha == sha
|
53
|
+
return false if commit.author.name != GitWakaTime.config.user_name
|
54
|
+
return false if commit.parents.size > 1
|
42
55
|
true
|
43
56
|
end
|
44
57
|
|
45
|
-
def
|
46
|
-
|
58
|
+
def load_dependent_commits(name)
|
59
|
+
GitWakaTime.config.git.log.object(sha).path(name)
|
47
60
|
rescue Git::GitExecuteError
|
48
61
|
puts error
|
49
62
|
nil
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module GitWakaTime
|
2
|
+
# Extract Duration Data from Actions for the WAKATIME API
|
3
|
+
class Durations
|
4
|
+
attr_accessor :actions
|
5
|
+
def initialize(args)
|
6
|
+
return @actions = args[:actions] if args[:actions]
|
7
|
+
fail if args[:project].nil?
|
8
|
+
@project = args[:project]
|
9
|
+
@args = args
|
10
|
+
@session = Wakatime::Session.new(api_key: GitWakaTime.config.api_key)
|
11
|
+
@client = Wakatime::Client.new(@session)
|
12
|
+
@actions = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_actions
|
16
|
+
unless cached?(@args)
|
17
|
+
|
18
|
+
Log.new "querying WakaTime actions for #{@project}"
|
19
|
+
time = Benchmark.realtime do
|
20
|
+
@actions = @client.actions(@args)
|
21
|
+
|
22
|
+
# remove returned actions that do not have the project we want
|
23
|
+
@actions = @actions.keep_if do |a|
|
24
|
+
a['project'] == @project
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Log.new "API took #{time}s"
|
29
|
+
persist_actions_localy(@actions)
|
30
|
+
end
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def persist_actions_localy(actions)
|
35
|
+
actions.each do |action|
|
36
|
+
|
37
|
+
Action.find_or_create(uuid: action['id']) do |c|
|
38
|
+
action['uuid'] = action['id']
|
39
|
+
action['time'] = Time.at(action['time'])
|
40
|
+
action.delete('id')
|
41
|
+
c.update(action)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def cached?(params)
|
48
|
+
# Check to see if this date range might be stale?
|
49
|
+
max_local_timetamp = Action.max(:time)
|
50
|
+
max_local_timetamp = (Time.parse(max_local_timetamp) + 3.day).to_date if max_local_timetamp
|
51
|
+
!(params[:start].to_date..params[:end].to_date).include?(max_local_timetamp) if max_local_timetamp
|
52
|
+
end
|
53
|
+
|
54
|
+
def actions_to_durations(_project = nil, timeout = 15)
|
55
|
+
durations = []
|
56
|
+
current = nil
|
57
|
+
|
58
|
+
@actions.each do | action |
|
59
|
+
# the first action just sets state and does nothing
|
60
|
+
unless current.nil?
|
61
|
+
|
62
|
+
# get duration since last action
|
63
|
+
duration = action.time.round - current.time.round
|
64
|
+
|
65
|
+
duration = 0.0 if duration < 0
|
66
|
+
|
67
|
+
# duration not logged if greater than the timeout
|
68
|
+
if duration < timeout * 60
|
69
|
+
|
70
|
+
# add duration to current action
|
71
|
+
current.duration = duration
|
72
|
+
|
73
|
+
# save to local db
|
74
|
+
current.save
|
75
|
+
|
76
|
+
# log current action as a duration
|
77
|
+
durations << current
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# set state (re-start the clock)
|
81
|
+
current = action
|
82
|
+
|
83
|
+
end
|
84
|
+
durations
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/gitwakatime/log.rb
CHANGED
data/lib/gitwakatime/mapper.rb
CHANGED
@@ -2,23 +2,29 @@ module GitWakaTime
|
|
2
2
|
# Th
|
3
3
|
class Mapper
|
4
4
|
attr_accessor :commits, :git
|
5
|
-
def initialize(
|
5
|
+
def initialize(commits: 500, start_at: Date.today)
|
6
6
|
Log.new 'Mapping commits for dependent commits'
|
7
7
|
time = Benchmark.realtime do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
Git.bare(path)
|
12
|
-
end
|
8
|
+
g = GitWakaTime.config.git
|
9
|
+
project = File.basename(g.dir.path)
|
10
|
+
logs = g.log(commits).since(start_at).until(Date.today)
|
13
11
|
|
14
|
-
|
12
|
+
@commits = logs.map do |git_c|
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
next if git_c.author.name != GitWakaTime.config.user_name
|
15
|
+
Commit.find_or_create(
|
16
|
+
sha: git_c.sha,
|
17
|
+
project: project
|
18
|
+
) do |c|
|
19
|
+
c.update(
|
20
|
+
author: git_c.author.name,
|
21
|
+
message: git_c.message,
|
22
|
+
date: git_c.date
|
23
|
+
)
|
24
|
+
end
|
19
25
|
end.compact
|
20
26
|
end
|
21
|
-
Log.new "Map Completed took #{time}s"
|
27
|
+
Log.new "Map Completed took #{time}s with #{Commit.count}"
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
data/lib/gitwakatime/query.rb
CHANGED
@@ -9,39 +9,44 @@ module GitWakaTime
|
|
9
9
|
@commits = commits
|
10
10
|
@api_limit = 15
|
11
11
|
@project = project
|
12
|
-
@actions = []
|
13
12
|
@requests = time_params
|
14
13
|
end
|
15
14
|
|
16
15
|
def get
|
17
16
|
@requests.each do |params|
|
18
17
|
Log.new "Requesting actions #{params[:start].to_date} to #{params[:end].to_date}".red
|
19
|
-
|
18
|
+
Durations.new(params).load_actions
|
20
19
|
end
|
21
20
|
|
22
|
-
|
21
|
+
Durations.new(actions: actions).actions_to_durations
|
22
|
+
end
|
23
|
+
|
24
|
+
def actions
|
25
|
+
Action.where('time >= ? and time <= ? ', @start_at, @end_at).where(project: @project)
|
23
26
|
end
|
24
27
|
|
25
28
|
def time_params
|
26
29
|
commits = @commits.map(&:date)
|
27
|
-
d_commits =
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
d_commits = CommitedFile.select(:dependent_date).all.map { |f| f.values[:dependent_date] }.compact
|
31
|
+
timestamps = (commits + d_commits.flatten).uniq.sort
|
32
|
+
@start_at = timestamps.first
|
33
|
+
@end_at = timestamps.last
|
34
|
+
# Always have a date range great than 1 as the num request will be 0/1 otherwise
|
35
|
+
num_requests = ((timestamps.last.to_date + 1) - timestamps.first.to_date) / @api_limit
|
32
36
|
i = 0
|
37
|
+
|
33
38
|
request_params = num_requests.to_f.ceil.times.map do
|
34
39
|
|
35
40
|
params = {
|
36
|
-
start: (timestamps.
|
37
|
-
end:
|
38
|
-
project: @project
|
41
|
+
start: (timestamps.first.to_date + (i * @api_limit)).to_time.beginning_of_day,
|
42
|
+
end: (timestamps.first.to_date + ((i + 1) * @api_limit)).to_time.end_of_day,
|
43
|
+
project: @project,
|
44
|
+
show: 'file,branch,project,time,id'
|
39
45
|
}
|
40
46
|
i += 1
|
41
47
|
params
|
42
48
|
|
43
49
|
end
|
44
|
-
|
45
50
|
request_params
|
46
51
|
end
|
47
52
|
end
|
data/lib/gitwakatime/timer.rb
CHANGED
@@ -18,22 +18,25 @@ module GitWakaTime
|
|
18
18
|
|
19
19
|
def total_commited
|
20
20
|
total_commited = ChronicDuration.output(@commits_with_duration
|
21
|
-
.map(&:time_in_seconds)
|
21
|
+
.map(&:time_in_seconds).compact
|
22
22
|
.reduce(:+).to_f)
|
23
|
-
Log.new "Total
|
23
|
+
Log.new "Total Committed Time #{total_commited} ".red
|
24
24
|
end
|
25
25
|
|
26
26
|
def process
|
27
27
|
@commits_with_duration = @commits.each do |commit|
|
28
28
|
|
29
|
-
if
|
30
|
-
commit.
|
29
|
+
if commit.commited_files.count > 0 || commit.parent_sha
|
30
|
+
commit.commited_files.each_with_index do |file, _i|
|
31
31
|
time = sum_actions relevant_actions(commit, file)
|
32
|
-
|
33
|
-
commit.time_in_seconds
|
32
|
+
file.time_in_seconds = time
|
33
|
+
commit.time_in_seconds = time
|
34
|
+
|
35
|
+
file.save
|
34
36
|
end
|
37
|
+
commit.save
|
35
38
|
else
|
36
|
-
|
39
|
+
commit.time_in_seconds = sum_actions(actions_before(@actions_with_durations, commit.date))
|
37
40
|
end
|
38
41
|
end.compact
|
39
42
|
total
|
@@ -45,38 +48,38 @@ module GitWakaTime
|
|
45
48
|
private
|
46
49
|
|
47
50
|
def relevant_actions(commit, file)
|
48
|
-
# The timestamps should be before the expected commit
|
49
|
-
actions = actions_before(commit.date)
|
50
|
-
|
51
51
|
# The file should be the same file as we expect
|
52
52
|
# TODO: Might need to pass root_path down
|
53
|
-
actions =
|
54
|
-
action[
|
53
|
+
actions = @actions_with_durations.select do |action|
|
54
|
+
action[:file].include?(File.join(File.basename(GitWakaTime.config.git.dir.path), file.name))
|
55
55
|
end
|
56
56
|
|
57
|
+
# The timestamps should be before the expected commit
|
58
|
+
actions = actions_before(actions, commit.date)
|
59
|
+
|
57
60
|
# If this file had an earlier commit ensure the actions timestamp
|
58
61
|
# is after that commit
|
59
|
-
if file.
|
60
|
-
actions = actions_after(actions, file.
|
62
|
+
if file.dependent_date
|
63
|
+
actions = actions_after(actions, file.dependent_date)
|
61
64
|
end
|
62
65
|
actions
|
63
66
|
end
|
64
67
|
|
65
|
-
def actions_before(date)
|
66
|
-
|
67
|
-
Time.at(action[
|
68
|
+
def actions_before(actions, date)
|
69
|
+
actions.select do |action|
|
70
|
+
Time.at(action[:time]) <= date
|
68
71
|
end
|
69
72
|
end
|
70
73
|
|
71
74
|
def actions_after(actions, date)
|
72
75
|
actions.select do |action|
|
73
|
-
Time.at(action[
|
76
|
+
Time.at(action[:time]) >= date
|
74
77
|
end
|
75
78
|
end
|
76
79
|
|
77
80
|
def sum_actions(actions)
|
78
|
-
actions.map { |action| action[
|
79
|
-
.reduce(:+).
|
81
|
+
actions.map { |action| action[:duration] }
|
82
|
+
.reduce(:+).to_i
|
80
83
|
end
|
81
84
|
end
|
82
85
|
end
|
data/lib/gitwakatime/version.rb
CHANGED
data/spec/commit_spec.rb
ADDED
File without changes
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
# * commit a4c26aeb79acb1f012201fe96e4d68e8d17c75d9 (HEAD, origin/master, origin/HEAD, master)
|
3
|
+
# | Author: rpo <rother@gmail.com>
|
4
|
+
# | Date: Sat Jan 31 15:49:07 2015 -0500
|
5
|
+
# |
|
6
|
+
# | I was edited online using git hub's editor
|
7
|
+
# |
|
8
|
+
# * commit 570f1df0505ed828656eeaf9411ddd6a6068b095
|
9
|
+
# | Author: Russell Osborne <rother@gmail.com>
|
10
|
+
# | Date: Fri Jan 30 00:19:00 2015 -0500
|
11
|
+
# |
|
12
|
+
# | And one more
|
13
|
+
# |
|
14
|
+
# * commit dcd748bd06b8a0f239d779bee4f1eaf1f4aa500d
|
15
|
+
# | Author: Russell Osborne <rother@gmail.com>
|
16
|
+
# | Date: Fri Jan 30 00:01:54 2015 -0500
|
17
|
+
# |
|
18
|
+
# | a final commit on master
|
19
|
+
# |
|
20
|
+
# * commit 57b0d5cdb6da2b2b9ac7e9847716b0c54466d1c6
|
21
|
+
# |\ Merge: e493d6f d642b3c
|
22
|
+
# | | Author: Russell Osborne <rother@gmail.com>
|
23
|
+
# | | Date: Thu Jan 29 22:27:26 2015 -0500
|
24
|
+
# | |
|
25
|
+
# | | Merge branch 'dev'
|
26
|
+
# | |
|
27
|
+
# | * commit d642b3c04c3025655a9c33e32b9d530696dcf7cc
|
28
|
+
# | | Author: Russell Osborne <rother@gmail.com>
|
29
|
+
# | | Date: Thu Jan 29 22:26:05 2015 -0500
|
30
|
+
# | |
|
31
|
+
# | | another commit on dev.
|
32
|
+
# | |
|
33
|
+
# * | commit e493d6f2ab2a702fa7f9c168b852a3b44c524f08
|
34
|
+
# |/ Author: Russell Osborne <rother@gmail.com>
|
35
|
+
# | Date: Thu Jan 29 22:26:20 2015 -0500 or 1422570380
|
36
|
+
# |
|
37
|
+
# | conflicting commit on master.
|
38
|
+
# |
|
39
|
+
# * commit 4c1ea35f9a811a0ef79da15ec85f25fce4c446ba
|
40
|
+
# | Author: Russell Osborne <rother@gmail.com>
|
41
|
+
# | Date: Thu Jan 29 22:25:08 2015 -0500 or 1422570308
|
42
|
+
# |
|
43
|
+
# | commit on dev branch
|
44
|
+
# |
|
45
|
+
# * commit 2254dd56976b5f32a2289438842e42a35a18ff86
|
46
|
+
# | Author: Russell Osborne <rother@gmail.com>
|
47
|
+
# | Date: Thu Jan 29 21:49:31 2015 -0500
|
48
|
+
# |
|
49
|
+
# | testing
|
50
|
+
# |
|
51
|
+
# * commit 052ff8c0e8c7cd39880d1536f4e27cc554e698f6
|
52
|
+
# Author: Russell Osborne <rother@gmail.com>
|
53
|
+
# Date: Thu Jan 29 21:49:12 2015 -0500
|
54
|
+
|
55
|
+
# created readme
|
56
|
+
describe 'description' do
|
57
|
+
let (:git) { Git.open(@wdir) }
|
58
|
+
|
59
|
+
before do
|
60
|
+
GitWakaTime::Commit.dataset.destroy
|
61
|
+
GitWakaTime::Commit.dataset.destroy
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'can be created ' do
|
65
|
+
GitWakaTime.config.git = git
|
66
|
+
|
67
|
+
first_commit = GitWakaTime::Commit.find_or_create(
|
68
|
+
sha: 'e493d6f2ab2a702fa7f9c168b852a3b44c524f08',
|
69
|
+
author: 'Russell Osborne',
|
70
|
+
message: 'conflicting commit on blah.',
|
71
|
+
project: git.repo.to_s,
|
72
|
+
date: Time.at(1_422_570_380)
|
73
|
+
)
|
74
|
+
|
75
|
+
expect(first_commit.commited_files.first.dependent_sha).to eql('4c1ea35f9a811a0ef79da15ec85f25fce4c446ba')
|
76
|
+
expect(first_commit.commited_files.first.dependent_date.utc.to_s).to eql('2015-01-30 03:25:08 UTC')
|
77
|
+
|
78
|
+
second_commit = GitWakaTime::Commit.find_or_create(
|
79
|
+
sha: 'd642b3c04c3025655a9c33e32b9d530696dcf7cc',
|
80
|
+
author: 'Russell Osborne',
|
81
|
+
message: 'conflicting commit on master.',
|
82
|
+
project: git.repo.to_s,
|
83
|
+
date: 'Thu Jan 29 22:26:05 2015 -0500'
|
84
|
+
)
|
85
|
+
|
86
|
+
expect(second_commit.commited_files.first.dependent_sha).to eql('4c1ea35f9a811a0ef79da15ec85f25fce4c446ba')
|
87
|
+
expect(
|
88
|
+
GitWakaTime::Commit.find(id: first_commit.id).commited_files.first.dependent_date.utc.to_s
|
89
|
+
).to eql('2015-01-30 03:25:08 UTC')
|
90
|
+
expect(second_commit.commited_files.first.dependent_date.utc.to_s).to eql(Time.at(1_422_570_380).utc.to_s)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/spec/mapper_spec.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'gitwakatime'
|
3
|
-
require 'gitwakatime/mapper'
|
4
2
|
|
5
3
|
describe 'description' do
|
4
|
+
let (:git) { Git.open(@wdir) }
|
5
|
+
before(:each) do
|
6
|
+
GitWakaTime.config.git = git
|
7
|
+
GitWakaTime::Mapper.new(start_at: Date.new(2015, 1, 28))
|
8
|
+
end
|
9
|
+
|
6
10
|
it 'can be run on dummy' do
|
7
|
-
expect(GitWakaTime::
|
11
|
+
expect(GitWakaTime::Commit.all.size).to eq 8 # 9ths is lonely
|
8
12
|
end
|
9
13
|
it 'can be run on dummy' do
|
10
|
-
expect(GitWakaTime::
|
14
|
+
expect(GitWakaTime::Commit.order(:date).first.message).to eq 'created readme'
|
11
15
|
end
|
12
16
|
|
13
17
|
it 'maps files dependent commits' do
|
14
|
-
expect(GitWakaTime::
|
18
|
+
expect(GitWakaTime::Commit.all.first.commited_files.first.dependent_sha).to eq 'dcd748bd06b8a0f239d779bee4f1eaf1f4aa500d'
|
15
19
|
end
|
16
20
|
|
17
21
|
it 'maps files dependent commits' do
|
18
|
-
expect(GitWakaTime::
|
22
|
+
expect(GitWakaTime::Commit.all.select { |c| c.sha == 'dcd748bd06b8a0f239d779bee4f1eaf1f4aa500d' }.first.commited_files.first.dependent_sha).to eq '2254dd56976b5f32a2289438842e42a35a18ff86'
|
19
23
|
end
|
20
24
|
end
|
data/spec/query_spec.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'gitwakatime'
|
3
2
|
|
4
3
|
describe 'description' do
|
5
|
-
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
GitWakaTime.config.git = Git.open(@wdir)
|
7
|
+
GitWakaTime::Mapper.new(start_at: Date.new(2015, 1, 24))
|
8
|
+
end
|
6
9
|
|
7
10
|
before do
|
8
11
|
stub_request(:get, 'https://wakatime.com/api/v1/actions')
|
@@ -12,11 +15,18 @@ describe 'description' do
|
|
12
15
|
|
13
16
|
it 'can be run on dummy' do
|
14
17
|
|
15
|
-
|
18
|
+
actions = GitWakaTime::Query.new(GitWakaTime::Commit.all, File.basename(@wdir)).get
|
19
|
+
|
20
|
+
expect(actions).to be_a Array
|
21
|
+
expect(actions.size).to eq 6 # 9ths is lonely
|
22
|
+
expect(actions.last).to be_a GitWakaTime::Action
|
23
|
+
expect(actions.last.branch).to eq 'master'
|
24
|
+
end
|
25
|
+
it 'produces valid search for api' do
|
26
|
+
actions = GitWakaTime::Query.new(GitWakaTime::Commit.all, File.basename(@wdir)).time_params
|
16
27
|
|
17
|
-
expect(
|
18
|
-
expect(
|
19
|
-
expect(
|
20
|
-
expect(timer.last.branch).to eq 'master'
|
28
|
+
expect(actions).to be_a Array
|
29
|
+
expect(actions.first[:start].to_date).to eq Date.new(2015, 01, 30)
|
30
|
+
expect(actions.first[:end].to_date).to eq Date.new(2015, 02, 14)
|
21
31
|
end
|
22
32
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,12 +4,29 @@
|
|
4
4
|
# loaded once.
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
ENV['thor_env'] = 'test'
|
8
|
+
ENV['waka_log'] = 'false'
|
9
|
+
|
10
|
+
require 'gitwakatime'
|
11
|
+
|
7
12
|
RSpec.configure do |config|
|
8
13
|
config.run_all_when_everything_filtered = true
|
9
14
|
config.filter_run :focus
|
10
15
|
config.order = 'random'
|
11
16
|
config.before(:all) do
|
12
17
|
@wdir = set_file_paths
|
18
|
+
GitWakaTime.config.setup_local_db
|
19
|
+
GitWakaTime::Commit.new.columns
|
20
|
+
GitWakaTime::CommitedFile.new.columns
|
21
|
+
GitWakaTime::Action.new.columns
|
22
|
+
end
|
23
|
+
|
24
|
+
config.before(:each) do
|
25
|
+
GitWakaTime::Commit.truncate
|
26
|
+
GitWakaTime::CommitedFile.truncate
|
27
|
+
GitWakaTime::Action.truncate
|
28
|
+
|
29
|
+
expect(GitWakaTime.config).to receive('user_name').and_return('Russell Osborne').at_least(:once)
|
13
30
|
end
|
14
31
|
|
15
32
|
config.after(:all) do
|
@@ -37,6 +54,5 @@ def create_temp_repo(clone_path)
|
|
37
54
|
Dir.chdir(tmp_path) do
|
38
55
|
FileUtils.mv('dot_git', '.git')
|
39
56
|
end
|
40
|
-
puts tmp_path
|
41
57
|
tmp_path
|
42
58
|
end
|
data/spec/timer_spec.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'gitwakatime'
|
3
2
|
|
4
3
|
describe 'description' do
|
5
|
-
let (:
|
4
|
+
let (:git) { Git.open(@wdir) }
|
6
5
|
|
7
6
|
before do
|
8
7
|
stub_request(:get, 'https://wakatime.com/api/v1/actions')
|
@@ -11,8 +10,14 @@ describe 'description' do
|
|
11
10
|
end
|
12
11
|
|
13
12
|
it 'can be run on dummy' do
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
GitWakaTime.config.git = git
|
14
|
+
GitWakaTime::Mapper.new(start_at: Date.new(2015, 1, 24))
|
15
|
+
actions = GitWakaTime::Query.new(GitWakaTime::Commit.all, File.basename(@wdir)).get
|
16
|
+
timer = GitWakaTime::Timer.new(GitWakaTime::Commit.all, actions, File.basename(@wdir)).process
|
17
|
+
|
18
|
+
# # UTC breaks actions of 1 day
|
19
|
+
# expect(timer.size).to eq 1
|
20
|
+
# With 8 relevant commits
|
21
|
+
expect(timer[timer.keys.first].size).to eq 8
|
17
22
|
end
|
18
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitwakatime
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Russell Osborne
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git
|
@@ -108,6 +108,34 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sequel
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: sqlite3
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
140
|
name: bundler
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -183,15 +211,18 @@ files:
|
|
183
211
|
- bin/gitwakatime
|
184
212
|
- gitwakatime.gemspec
|
185
213
|
- lib/gitwakatime.rb
|
186
|
-
- lib/gitwakatime/
|
214
|
+
- lib/gitwakatime/action.rb
|
187
215
|
- lib/gitwakatime/cli.rb
|
188
216
|
- lib/gitwakatime/commit.rb
|
189
217
|
- lib/gitwakatime/commited_file.rb
|
218
|
+
- lib/gitwakatime/durations.rb
|
190
219
|
- lib/gitwakatime/log.rb
|
191
220
|
- lib/gitwakatime/mapper.rb
|
192
221
|
- lib/gitwakatime/query.rb
|
193
222
|
- lib/gitwakatime/timer.rb
|
194
223
|
- lib/gitwakatime/version.rb
|
224
|
+
- spec/commit_spec.rb
|
225
|
+
- spec/commited_file_spec.rb
|
195
226
|
- spec/dummy/dot_git/FETCH_HEAD
|
196
227
|
- spec/dummy/dot_git/HEAD
|
197
228
|
- spec/dummy/dot_git/config
|
@@ -271,6 +302,8 @@ specification_version: 4
|
|
271
302
|
summary: A Tool that will compile git data with wakatime data to establish time per
|
272
303
|
commit
|
273
304
|
test_files:
|
305
|
+
- spec/commit_spec.rb
|
306
|
+
- spec/commited_file_spec.rb
|
274
307
|
- spec/dummy/dot_git/FETCH_HEAD
|
275
308
|
- spec/dummy/dot_git/HEAD
|
276
309
|
- spec/dummy/dot_git/config
|
data/lib/gitwakatime/actions.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
module GitWakaTime
|
2
|
-
# Extract Duration Data from Actions for the WAKATIME API
|
3
|
-
class Actions
|
4
|
-
attr_accessor :actions
|
5
|
-
def initialize(args)
|
6
|
-
return @actions = args[:actions] if args[:actions]
|
7
|
-
fail if args[:project].nil?
|
8
|
-
@project = args[:project]
|
9
|
-
@args = args
|
10
|
-
@session = Wakatime::Session.new(api_key: GitWakaTime.config.api_key)
|
11
|
-
@client = Wakatime::Client.new(@session)
|
12
|
-
load_actions
|
13
|
-
end
|
14
|
-
|
15
|
-
def load_actions
|
16
|
-
Log.new "querying WakaTime actions for #{@project}"
|
17
|
-
time = Benchmark.realtime do
|
18
|
-
@actions = @client.actions(@args)
|
19
|
-
# remove returned actions that do not have the project we want
|
20
|
-
@actions = @actions.keep_if do |a|
|
21
|
-
a['project'] == @project
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
|
-
Log.new "API took #{time}s"
|
26
|
-
@actions
|
27
|
-
end
|
28
|
-
|
29
|
-
def actions_to_durations(_project = nil, timeout = 15)
|
30
|
-
durations = []
|
31
|
-
current = []
|
32
|
-
@actions.each do | action |
|
33
|
-
# the first action just sets state and does nothing
|
34
|
-
unless current.empty?
|
35
|
-
|
36
|
-
# get duration since last action
|
37
|
-
duration = action.time.round - current['time'].round
|
38
|
-
|
39
|
-
duration = 0.0 if duration < 0
|
40
|
-
|
41
|
-
# duration not logged if greater than the timeout
|
42
|
-
if duration < timeout * 60
|
43
|
-
|
44
|
-
# add duration to current action
|
45
|
-
current['duration'] = duration
|
46
|
-
|
47
|
-
# log current action as a duration
|
48
|
-
durations << current
|
49
|
-
end
|
50
|
-
end
|
51
|
-
# set state (re-start the clock)
|
52
|
-
current = action
|
53
|
-
current.delete('id')
|
54
|
-
|
55
|
-
end
|
56
|
-
durations
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|