gitwakatime 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 608325483e8f379265e87a1a1043e1243994c36a
4
- data.tar.gz: 818c9c1e91c25af377fdc13fc9fd06dc26a2a1ac
3
+ metadata.gz: b91819c201663c8522a6791246e37ae43633f66d
4
+ data.tar.gz: f8d581584110d02b8b3178dc8bd6e2902fe38bf1
5
5
  SHA512:
6
- metadata.gz: 52151087d7120140ca3a55e85f223fa098fb3f73a881623b8bbe3251c877d6548af46d7236944c68345e17eeff07b29a22a079f0767b1f4b9eeb26a5a3d772ca
7
- data.tar.gz: 3607317bf5cbea8d9265e17f6eb88f1fa005a97d975c3219d2092653881827f5802df269737452c9cd3d1710a7fbd9eb3a66f367d7314e4241e311e0fcd90740
6
+ metadata.gz: d94736601a38c5b028805eb78b67d83d9eba9c334fd7825cef8bb5cb8261c36b49a50a6c39fda6b267d80b314030414c5c2e04993d79d6a7e88c74a73608d6ca
7
+ data.tar.gz: f803b86f5fd86704f90609d8ff214e0d8147e5aba7139fc74ff07c966e9f589600edef32d0592c1f0566c7d29c74be494d3af717b792e8f6394526014df4e041
@@ -0,0 +1,22 @@
1
+ ---
2
+ engines:
3
+ fixme:
4
+ enabled: true
5
+ rubocop:
6
+ enabled: true
7
+ brakeman:
8
+ enabled: true
9
+ bundler-audit:
10
+ enabled: true
11
+ duplication:
12
+ enabled: true
13
+ config:
14
+ languages:
15
+ - ruby
16
+ ratings:
17
+ paths:
18
+ - "**.rb"
19
+ exclude_paths:
20
+ - spec/**/*
21
+ - ".codeclimate.yml"
22
+ - ".rubocop.yml"
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ bin/rspec
@@ -1,8 +1,9 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
3
4
  - 2.1.2
4
5
  script: bundle exec rake spec
5
6
 
6
7
  addons:
7
8
  code_climate:
8
- repo_token: 214446b5fc4f8697cc9ccc3ab1f2612c2e083ea1e71266648319663719cf85b9
9
+ repo_token: 214446b5fc4f8697cc9ccc3ab1f2612c2e083ea1e71266648319663719cf85b9
data/README.md CHANGED
@@ -3,11 +3,13 @@
3
3
  [![Build Status](https://travis-ci.org/rposborne/gitwakatime.svg?branch=master)](https://travis-ci.org/rposborne/gitwakatime)
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
+ [![Test Coverage](https://codeclimate.com/github/rposborne/gitwakatime/badges/coverage.svg)](https://codeclimate.com/github/rposborne/gitwakatime/coverage)
7
+ [![Issue Count](https://codeclimate.com/github/rposborne/gitwakatime/badges/issue_count.svg)](https://codeclimate.com/github/rposborne/gitwakatime)
6
8
 
7
- GitWakaTime is a mash up between data obtained through "(Wakatime)[https://wakatime.com]" and the data we all create using git.
9
+ GitWakaTime is a mash up between data obtained through "[Wakatime](https://wakatime.com)" and the data we all create using git.
8
10
  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".
9
11
 
10
- This implementation various form (Wakatime's)[https://wakatime.com/#features] commit feature as it compares time spent on each file, vs comparing the time between commits. It tends to be significantly more accurate for those who do per line commits.
12
+ This implementation varies form [Wakatime's](https://wakatime.com/#features) commit feature as it compares time spent on each file, vs comparing the time between commits. It tends to be significantly more accurate for those who do per line commits. Read more about it [here](http://burningpony.com/2015/02/that-feature-took-how-long/)
11
13
 
12
14
  ## Installation
13
15
 
@@ -15,7 +17,7 @@ Install the gem:
15
17
 
16
18
  $ gem install gitwakatime
17
19
 
18
- Run the setup command: (you will need your wakatime api key)[https://wakatime.com/settings]
20
+ Run the setup command: [you will need your wakatime api key](https://wakatime.com/settings)
19
21
 
20
22
  $ gitwakatime init
21
23
 
@@ -42,8 +44,8 @@ Hard reset of the local cache database, if you are getting odd numbers
42
44
  ## Assumptions
43
45
 
44
46
  There a currently a few limitations with this model
45
-
46
- * Merges are free, (no time is attributed a merge). This is true for most merges but conflict resolution will be attributed to git parent commit of that file for that merge.
47
+
48
+ * Merges are free, (no time is attributed a merge). This is true for most merges but conflict resolution will be attributed to git parent commit of that file for that merge.
47
49
 
48
50
  ## Output
49
51
  Total Recorded time 1 day 9 hrs 13 mins 32 secs
data/Rakefile CHANGED
@@ -7,7 +7,13 @@ rescue Bundler::BundlerError => e
7
7
  exit e.status_code
8
8
  end
9
9
  require 'rake'
10
- require 'rspec/core'
11
- require 'rspec/core/rake_task'
12
10
 
13
- RSpec::Core::RakeTask.new(:spec)
11
+ begin
12
+ require 'rspec/core/rake_task'
13
+
14
+ RSpec::Core::RakeTask.new(:spec)
15
+
16
+ task default: :spec
17
+ rescue LoadError
18
+ # no rspec available
19
+ end
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'gitwakatime'
2
3
  require 'thor'
3
4
 
4
- require_relative '../lib/gitwakatime'
5
-
6
5
  GitWakaTime::Cli.start
@@ -20,19 +20,13 @@ Gem::Specification.new do |s|
20
20
  s.executables = s.files.grep(/^bin\//) { |f| File.basename(f) }
21
21
 
22
22
  s.require_paths = ['lib']
23
- s.authors = ['Bozhidar Batsov', 'Jonas Arvidsson', 'Yuji Nakayama']
24
- s.description = <<-EOF
25
- Automatic Ruby code style checking tool.
26
- Aims to enforce the community-driven Ruby Style Guide.
27
- EOF
28
-
23
+ s.authors = ['Russell Osborne']
29
24
  s.add_runtime_dependency 'git', '>= 1.2.9.1'
30
25
  s.add_runtime_dependency 'wakatime', '>= 0.0.2'
31
- s.add_runtime_dependency 'logger', '>= 0'
32
26
  s.add_runtime_dependency 'thor', '>= 0'
33
27
  s.add_runtime_dependency 'chronic_duration', '>=0'
34
28
  s.add_runtime_dependency 'colorize'
35
- s.add_runtime_dependency 'activesupport'
29
+ s.add_runtime_dependency('activesupport', ['>= 0'])
36
30
  s.add_runtime_dependency 'sequel'
37
31
  s.add_runtime_dependency 'sqlite3'
38
32
  s.add_development_dependency('bundler', ['>= 0'])
@@ -1,4 +1,5 @@
1
1
  require 'sequel'
2
+
2
3
  if ENV['thor_env'] != 'test'
3
4
  DB = Sequel.connect("sqlite://#{File.join(Dir.home, '.wakatime.sqlite')}")
4
5
  else
@@ -7,14 +8,17 @@ else
7
8
  end
8
9
 
9
10
  Sequel::Model.plugin :json_serializer
11
+ Sequel.default_timezone = :utc
10
12
  DB.use_timestamp_timezones = false
11
13
 
12
14
  require 'gitwakatime/version'
13
- require 'gitwakatime/durations'
15
+ require 'gitwakatime/durations_calculator'
14
16
  require 'gitwakatime/heartbeat'
15
17
  require 'gitwakatime/commit'
16
18
  require 'gitwakatime/mapper'
17
19
  require 'gitwakatime/query'
20
+ require 'gitwakatime/request'
21
+ require 'gitwakatime/request_builder'
18
22
  require 'gitwakatime/timer'
19
23
  require 'gitwakatime/log'
20
24
  require 'gitwakatime/commited_file'
@@ -72,6 +76,7 @@ module GitWakaTime
72
76
  integer :time_in_seconds, default: 0
73
77
  String :sha
74
78
  String :name
79
+ String :entity
75
80
  String :project
76
81
  index :dependent_sha
77
82
  index :sha
@@ -84,7 +89,8 @@ module GitWakaTime
84
89
  String :uuid
85
90
  DateTime :time
86
91
  integer :duration, default: 0
87
- String :file
92
+ String :entity
93
+ String :type
88
94
  String :branch
89
95
  String :project
90
96
  index :uuid, unique: true
@@ -93,7 +99,7 @@ module GitWakaTime
93
99
  end
94
100
 
95
101
  def self.config
96
- @configuration ||= Configuration.new
102
+ @configuration ||= Configuration.new
97
103
  end
98
104
 
99
105
  def self.configure
@@ -4,11 +4,12 @@ require 'wakatime'
4
4
  require 'chronic_duration'
5
5
  require 'yaml'
6
6
  require 'thor'
7
+ require 'active_support'
7
8
  require 'active_support/core_ext/date/calculations'
8
9
  require 'active_support/core_ext/date_and_time/calculations'
9
10
  require 'active_support/core_ext/integer/time'
10
11
  require 'active_support/core_ext/time'
11
- require 'pry'
12
+
12
13
  module GitWakaTime
13
14
  # Provides two CLI heartbeats init and tally
14
15
  class Cli < Thor
@@ -47,6 +48,7 @@ module GitWakaTime
47
48
  def tally
48
49
  date = Date.parse(options.start_on)
49
50
 
51
+
50
52
  @timer = GitWakaTime::Controller.new(
51
53
  path: File.expand_path(options.file), date: date
52
54
  ).timer
@@ -54,6 +56,16 @@ module GitWakaTime
54
56
  print_output(@timer, format: options.output)
55
57
  end
56
58
 
59
+ desc 'update', 'Cache the latest heartbeats locally'
60
+ method_option :start_on, aliases: '-s'
61
+ def update
62
+ GitWakaTime.config.setup_local_db
63
+ GitWakaTime.config.load_config_yaml
64
+ date = Date.parse(options.start_on || GitWakaTime::Heartbeat.max(:time))
65
+
66
+ GitWakaTime::Query.new(date, Date.today, @project).call
67
+ end
68
+
57
69
  no_commands do
58
70
  def print_output(timer, format: 'text')
59
71
  if format == 'text'
@@ -70,7 +82,7 @@ module GitWakaTime
70
82
  Log.new format(
71
83
  '%-40s %-40s'.blue,
72
84
  c_date,
73
- "Total #{ChronicDuration.output sum_c_time }"
85
+ "Total #{ChronicDuration.output sum_c_time}"
74
86
  )
75
87
  commits.each do |commit|
76
88
  # Log.new commit.message
@@ -5,7 +5,7 @@ module GitWakaTime
5
5
  class Commit < Sequel::Model
6
6
  one_to_many :commited_files
7
7
  def after_create
8
- extract_changed_files
8
+ extract_changed_files if GitWakaTime.config.git
9
9
  end
10
10
 
11
11
  def to_s
@@ -14,7 +14,7 @@ module GitWakaTime
14
14
  date,
15
15
  ChronicDuration.output(time_in_seconds.to_i),
16
16
  message
17
- )
17
+ )
18
18
  end
19
19
 
20
20
  def oldest_dependent
@@ -22,7 +22,11 @@ module GitWakaTime
22
22
  end
23
23
 
24
24
  def time_in_seconds
25
- commited_files.map(&:time_in_seconds).inject(:+)
25
+ commited_files.map(&:time_in_seconds).compact.inject(:+)
26
+ end
27
+
28
+ def date
29
+ self[:date].localtime
26
30
  end
27
31
 
28
32
  private
@@ -8,7 +8,7 @@ module GitWakaTime
8
8
  # means a split tree, and we should split time between the two, or
9
9
  # more, commits.
10
10
  def before_create
11
- find_dependent_commit(name)
11
+ find_dependent_commit(name) if GitWakaTime.config.git
12
12
  end
13
13
 
14
14
  def to_s
@@ -16,7 +16,7 @@ module GitWakaTime
16
16
  (dependent_sha[0..8] if dependent_sha),
17
17
  ChronicDuration.output(time_in_seconds.to_f),
18
18
  name
19
- )
19
+ )
20
20
  end
21
21
 
22
22
  private
@@ -25,7 +25,7 @@ module GitWakaTime
25
25
  # out of commits to check
26
26
  def find_dependent_commit(name, i = 1)
27
27
  commits = load_dependent_commits(name)
28
- begin
28
+ loop do
29
29
  commit = commits[i]
30
30
 
31
31
  if commit && allowed_commit(commit)
@@ -34,7 +34,8 @@ module GitWakaTime
34
34
  end
35
35
 
36
36
  i += 1
37
- end until !dependent_sha.nil? || commit.nil?
37
+ break if !dependent_sha.nil? || commit.nil?
38
+ end
38
39
  end
39
40
 
40
41
  def check_and_correct_split_tree(commit)
@@ -48,7 +49,7 @@ module GitWakaTime
48
49
  if self.commit.date < split_tree_file.commit.date
49
50
  self.dependent_date = split_tree_file.commit.date
50
51
  elsif self.commit.date > split_tree_file.commit.date
51
- split_tree_file.update(dependent_date: commit.date)
52
+ split_tree_file.update(dependent_date: commit.date.utc)
52
53
  end
53
54
  end
54
55
 
@@ -1,30 +1,33 @@
1
1
  module GitWakaTime
2
2
  # Extract Duration Data from Heartbeats for the WAKATIME API
3
3
  class Controller
4
+ attr_accessor :time_range, :heartbeats, :relevant_commits, :project
5
+
4
6
  def initialize(path: '.', date: nil)
5
7
  @path = path
6
8
  GitWakaTime.config.setup_local_db
7
9
  GitWakaTime.config.root = path
8
10
  GitWakaTime.config.load_config_yaml
9
11
  GitWakaTime.config.git = Git.open(path)
12
+ GitWakaTime::Query.new(date, Date.today, @project).call
13
+
10
14
  @git_map = Mapper.new(start_at: date)
11
15
  @project = File.basename(GitWakaTime.config.git.dir.path)
12
16
  @relevant_commits = Commit.where(
13
- 'date > ? and project = ?', date, @project
17
+ 'project = ?', @project
14
18
  )
15
19
 
20
+ # Scope by date if one has been passed
21
+ @relevant_commits = @relevant_commits.where('date > ? ', date) if date
22
+
16
23
  @files = CommitedFile.where(
17
- 'commit_id IN ?', @relevant_commits.select_map(:id)
24
+ 'commit_id IN ?', @relevant_commits.select_map(:id)
18
25
  ).where('project = ?', @project)
19
-
20
- @heartbeats = Query.new(
21
- @relevant_commits, @files, File.basename(path)
22
- ).get
23
26
  end
24
27
 
25
28
  def timer
26
29
  Timer.new(
27
- @relevant_commits.all, @heartbeats, File.basename(@path)
30
+ @relevant_commits.all, Heartbeat
28
31
  ).process
29
32
  end
30
33
  end
@@ -0,0 +1,42 @@
1
+ module GitWakaTime
2
+ # Extract Duration Data from Heartbeats for the WAKATIME API
3
+ class DurationsCalculator
4
+ attr_accessor :heartbeats
5
+ def initialize(args)
6
+ return @heartbeats = args[:heartbeats] if args[:heartbeats]
7
+ @args = args
8
+ @heartbeats = []
9
+ end
10
+
11
+ def heartbeats_to_durations(timeout = 15)
12
+ durations = []
13
+ current = nil
14
+ @heartbeats.each do |heartbeat|
15
+ # the first heartbeat just sets state and does nothing
16
+ unless current.nil?
17
+
18
+ # get duration since last heartbeat
19
+ duration = heartbeat.time.round - current.time.round
20
+
21
+ duration = 0.0 if duration < 0
22
+
23
+ # duration not logged if greater than the timeout
24
+ if duration < timeout * 60
25
+
26
+ # add duration to current heartbeat
27
+ current.duration = duration
28
+
29
+ # save to local db
30
+ current.save
31
+
32
+ # log current heartbeat as a duration
33
+ durations << current
34
+ end
35
+ end
36
+ # set state (re-start the clock)
37
+ current = heartbeat
38
+ end
39
+ durations
40
+ end
41
+ end
42
+ end
@@ -12,19 +12,20 @@ module GitWakaTime
12
12
  @commits = logs.map do |git_c|
13
13
  next if git_c.author.name != GitWakaTime.config.user_name
14
14
  next if git_c.parents.size > 1
15
+
15
16
  Commit.find_or_create(
16
- sha: git_c.sha,
17
- project: project
18
- ) do |c|
17
+ sha: git_c.sha,
18
+ project: project
19
+ ) do |c|
19
20
  c.update(
20
- author: git_c.author.name,
21
- message: git_c.message,
22
- date: git_c.date.utc
21
+ author: git_c.author.name,
22
+ message: git_c.message,
23
+ date: git_c.date.utc
23
24
  )
24
25
  end
25
26
  end.compact
26
27
  end
27
- Log.new "Map Completed took #{time}s with #{Commit.count}"
28
+ Log.new "Map Completed took #{time}s with #{@commits.size} commits"
28
29
  end
29
30
  end
30
31
  end
@@ -1,79 +1,51 @@
1
1
  require 'benchmark'
2
2
  require 'colorize'
3
- require 'active_support/core_ext/time'
4
3
 
5
4
  module GitWakaTime
6
5
  # Integrates the nested hash from mapper with heartbeats api
7
6
  class Query
8
- def initialize(commits, files, project, _path = nil)
9
- @commits = commits
10
- @files = files
11
- @api_limit = 1
12
- @project = project
13
- @requests = build_requests
7
+ def initialize(start_at, end_at, project)
8
+ @start_at = start_at
9
+ @end_at = end_at
10
+ @project = project
11
+ @requests = RequestBuilder.new(@start_at, @end_at).call
12
+ Log.new "Loading Committed time from #{@start_at} to #{@end_at}".red
14
13
  end
15
14
 
16
- def time_range
17
- commits = @commits.select_map(:date).sort
18
- d_commits = @files.select_map(:dependent_date).compact.sort
19
- timestamps = (commits + d_commits.flatten).uniq.sort
20
-
21
- # Don't query before the Wakatime Epoch
22
- first_commit_at = timestamps.first
23
- @start_at = if first_commit_at && first_commit_at >= Time.new(2013, 5, 1)
24
- first_commit_at
25
- else
26
- Time.new(2013, 5, 1)
27
- end
28
- @end_at = timestamps.last
29
- end
30
-
31
- def get
15
+ def call
32
16
  @requests.each do |params|
33
- Log.new "Gettting heartbeats
34
- #{params[:start].to_date} to #{params[:end].to_date}".red
35
- Durations.new(params).load_heartbeats
17
+ next if cached?(params[:date])
18
+ persist_heartbeats_localy(Request.new(params).call)
36
19
  end
37
20
 
38
- Durations.new(
39
- heartbeats: heartbeats.where('duration <= 0')
40
- ).heartbeats_to_durations
41
- heartbeats.all
21
+ DurationsCalculator.new(heartbeats: local_heartbeats.where('duration <= 0') ).heartbeats_to_durations
22
+ local_heartbeats.where(project: @project).all
42
23
  end
43
24
 
44
- def heartbeats
45
- Heartbeat.where(
46
- 'time >= ? and time <= ? ', @start_at, @end_at
47
- ).where(project: @project)
25
+ def cached?(date)
26
+ max_local_timestamp = Heartbeat.max(:time)
27
+ return false if max_local_timestamp.nil?
28
+ @max_local_timestamp ||= (Time.parse(max_local_timestamp + ' UTC'))
29
+ date.to_date < @max_local_timestamp.to_date
48
30
  end
49
31
 
50
- def build_requests
51
- time_range
52
-
53
- # Always have a date range great than 1 as the num request
54
- # will be 0/1 otherwise
55
- num_requests = ((@end_at.to_date + 1) - @start_at.to_date) / @api_limit
56
- i = 0
57
-
58
- request_params = num_requests.to_f.ceil.times.map do
59
-
60
- params = construct_params(i)
61
- i += 1
62
- params
32
+ private
63
33
 
34
+ def persist_heartbeats_localy(heartbeats)
35
+ heartbeats.map do |heartbeat|
36
+ heartbeat['uuid'] = heartbeat['id']
37
+ heartbeat['time'] = Time.at(heartbeat['time'])
38
+ heartbeat.delete('id')
39
+ Heartbeat.find_or_create(uuid: heartbeat['uuid']) do |a|
40
+ a.update(heartbeat)
41
+ end
64
42
  end
65
- request_params
66
43
  end
67
44
 
68
- def construct_params(i)
69
- {
70
- start: (
71
- @start_at.to_date + (i * @api_limit)
72
- ).to_time.beginning_of_day,
73
- end: (@start_at.to_date + (i * @api_limit)).to_time.end_of_day,
74
- project: @project,
75
- show: 'file,branch,project,time,id'
76
- }
45
+ def local_heartbeats
46
+ Heartbeat.where(
47
+ 'time >= ? and time <= ? ', @start_at, @end_at
48
+ )
77
49
  end
78
50
  end
79
51
  end