repo_timetracker 1.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 06c97cf5c1df3cf2c5267ac65d0d76fea7620550
4
+ data.tar.gz: 0292470662a50a0f57d35c50efbeb24fe7cb9e39
5
+ SHA512:
6
+ metadata.gz: dbca4eb49f6714d723b8d5cdbbb7357890bc38790b530c5917cd2d1af4dc11db8cd802ece8542fb2b49ca2452d9afb9c7424a4c77260a8238f64d8e8ecea2895
7
+ data.tar.gz: 8c1e50551bf28a8e0887fb58e4cd4eb91a546cb4524c63b32e1e7f6bad20bc1d388bbc46846210649ceb3370aeaf373f52a92782d885c2911563efbe945aa417
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .ruby-*
16
+ .repo_timeline
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in repo_timetracker.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 M
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # README
2
+
3
+ This program times how long you spend on each git commit in a repo without needing a user to start or stop the timer manually.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'repo_timetracker'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install repo_timetracker
20
+
21
+ ### How it works
22
+
23
+ It keeps track of work time by recording the timing of file changes, and of commands used within a project folder.
24
+
25
+ The exact time of each command recorded with the 'record' command and every file change in the repo is saved, along with whether or not the program thinks you were working during the intervals between these events. When commit_time is called, it adds up all of the time intervals during which it thinks you were working and returns the total.
26
+
27
+ If two sequential events occur more than half an hour apart, it assumes you weren't working during that time. Less than half an hour, it assumes you were.
28
+
29
+ Here's an example sequence of events:
30
+
31
+ ```
32
+ > rpt record
33
+
34
+ ...5 minutes pass...
35
+
36
+ file change
37
+
38
+ ...45 minutes pass...
39
+
40
+ > rpt record 'git status'
41
+
42
+ ...5 minutes pass...
43
+
44
+ file change
45
+
46
+ ...5 minutes pass...
47
+
48
+ file change
49
+
50
+ > rpt commit_time
51
+ "00:15:00"
52
+ ```
53
+
54
+ At the end, the commit_time command says 15 minutes have been spent working, because it counted the 3 5-minute intervals, but not the one 45-minute interval.
55
+
56
+ All data is stored in YAML files in the **.repo_timeline** directory, and is pretty easy to read and modify by hand if you're so inclined (for example, to correct one of the intervals from working to not or vice versa if the 30-minute rule fails you).
57
+
58
+ ## How to use
59
+
60
+ To initialize recording for a repo (only needs to happen once per repo, ever):
61
+
62
+ ```
63
+ > rpt record
64
+
65
+ ```
66
+ To get time spent on current commit (in hh:mm:ss):
67
+
68
+ ```
69
+ > rpt commit_time
70
+ "00:23:34"
71
+
72
+ ```
73
+ To get total time spent on all commits in this repo (in hh:mm:ss):
74
+
75
+ ```
76
+ > rpt project_time
77
+ "07:38:55"
78
+
79
+ ```
80
+ Recording a git commit starts a new commit timer from zero:
81
+
82
+ ```
83
+ > rpt commit_time
84
+ "00:35:23"
85
+ > git commit -am "awesome commit message $(rpt commit_time)"
86
+ [master c2f2492] awesome commit message 00:35:33
87
+ 1 file changed, 0 insertions(+), 0 deletions(-)
88
+ rename cool_file.rb => rad_file.rb (100%)
89
+
90
+ > rpt record "git commit"
91
+ > rpt commit_time
92
+ "00:00:06"
93
+ ```
94
+
95
+ ## The commands
96
+
97
+ Commands are called with **rpt** followed by the name of the command.
98
+
99
+ #### record
100
+ 'record' or 'rec' makes a record of an event that occurs within the project directory. The first time this command is run in a directory it will initialize the **.repo_timeline** folder in the root directory of that repo*. This folder is where the timing data is stored. It should be added to your **.gitignore** file.
101
+
102
+ ***For repo_timetracker to work properly, 'git commit' and 'git commit --amend' calls must always be recorded.***
103
+
104
+ Example call: `rpt record 'git status'`
105
+
106
+ #### commit_time
107
+ 'commit_time' or 'ct' returns the amount of time that has been spent on the current commit thus far in *hh:mm:ss* format.
108
+
109
+ Example call: `rpt commit_time`
110
+
111
+ #### project_time
112
+ 'project_time' or 'pt' returns the amount of time that has been spent on the entire repo thus far in *hh:mm:ss* format.
113
+
114
+ Example call: `rpt project_time`
115
+
116
+
117
+ ### What I use it for
118
+
119
+ For my purposes, I have setup a function, `rpt`, that runs the rpt script, and some functions that call `rpt record` automatically after common git commands (e.g. `git status` will be followed by `rpt record 'git status'`, `git commit` by `rpt record 'git commit'`, etc.). I use the [fish shell](http://fishshell.com/), so for me these functions look like these:
120
+
121
+ ```
122
+ function gs
123
+ git status $argv
124
+ rpt rec "git status $argv"
125
+ end
126
+ ```
127
+ ```
128
+ function gca
129
+ git commit --amend $argv
130
+ rpt rec "git commit --amend $argv"
131
+ end
132
+ ```
133
+
134
+ The function I use for git commits is setup so that it automatically appends the time spent on the commit to the end of the commit message:
135
+
136
+ ```
137
+ function gc
138
+ set -x totaltime (rpt commit_time)
139
+ git commit -m "$argv $totaltime" # Commits with time elapsed added on to commit message
140
+ rpt rec "git commit -m \"$argv $totaltime\"" # Records commit event
141
+ end
142
+ ```
143
+ Thus:
144
+
145
+ ```
146
+ > gc "awesome commit message"
147
+ ```
148
+ Might produce a commit like:
149
+
150
+ ```
151
+ [master c2f2492] awesome commit message 00:35:33
152
+ 1 file changed, 0 insertions(+), 0 deletions(-)
153
+ rename cool_file.rb => rad_file.rb (100%)
154
+ ```
155
+
156
+ I also have a display of the time spent in the current commit show in my terminal prompt, like this:
157
+ ```
158
+ 00:34:16 >
159
+ ```
160
+
161
+
162
+ ## Contributing
163
+
164
+ 1. Fork it ( https://github.com/[my-github-username]/repo_timetracker/fork )
165
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
166
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
167
+ 4. Push to the branch (`git push origin my-new-feature`)
168
+ 5. Create a new Pull Request
169
+
170
+
171
+ -----
172
+
173
+ \* It automatically determines the relevant repo as either a repo in the directory it is called from, or the nearest repo that appears in a parent of that directory.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/rpt ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/repo_timetracker'
4
+
5
+ case ARGV[0]
6
+
7
+ when /^((rec)|(record))$/
8
+ RepoTimetracker.record((ARGV[1] or "event not specified"), Dir.pwd)
9
+
10
+ when /^((ct)|(commit_time))$/
11
+ p RepoTimetracker.current_commit_time(Dir.pwd)
12
+
13
+ when /^((pt)|(project_time))$/
14
+ p RepoTimetracker.project_time(Dir.pwd)
15
+
16
+ else
17
+ puts "Run 'rpt rec' to setup timetracking in a repo."
18
+ puts "Run 'rpt rec [event to record, e.g. '\"git commit\"]' to record an event."
19
+ puts "Run 'rpt ct' to see time spent on the current commit."
20
+ puts "Run 'rpt pt' to see total time spent on the current project."
21
+ end
@@ -0,0 +1,94 @@
1
+ require 'yaml'
2
+ require_relative "commit_record_class"
3
+
4
+ class CommitRecord
5
+ require_relative 'event'
6
+
7
+ attr_accessor :events, :file_path
8
+
9
+ A_LONG_TIME = 30*60 # 30 minutes
10
+
11
+ def initialize(project_directory, first_event_string = nil)
12
+ @project_name = project_directory.slice(/[^\/]*$/)
13
+ @timeline_directory = "#{project_directory}/.repo_timeline"
14
+ @file_path = generate_file_path(project_directory)
15
+
16
+ @events = []
17
+ @events << Event.new(first_event_string) if first_event_string
18
+ end
19
+
20
+ def generate_new_event(event_string, following_time_spent = :working)
21
+ @events << Event.new(event_string, following_time_spent)
22
+ @events[-2].following_time_spent = :not_working if long_after_previous_event?(@events[-1])
23
+ save
24
+ end
25
+
26
+ def total_time
27
+ time = 0
28
+ @events.each_cons(2) do |pair|
29
+ time += pair[1].time_recorded - pair[0].time_recorded if pair[0].following_time_spent_working?
30
+ end
31
+
32
+ time.round
33
+ end
34
+
35
+ def add_events(events)
36
+ @events += events
37
+ save
38
+ end
39
+
40
+ def get_tail
41
+ @events[1..-1]
42
+ end
43
+
44
+ def clear_events
45
+ @events = []
46
+ save
47
+ end
48
+
49
+ def save
50
+ File.open(@file_path, "w") { |f| f.puts YAML::dump(self) }
51
+ self
52
+ end
53
+
54
+ def ==(other_commit)
55
+ all_events_equal(other_commit) and
56
+ @file_path == other_commit.file_path
57
+ end
58
+
59
+
60
+
61
+ private
62
+
63
+ def generate_file_path(directory)
64
+ "#{@timeline_directory}/#{generate_file_name}"
65
+ end
66
+
67
+ def generate_file_name
68
+ time_string = Time.now.strftime('%y-%m-%d_%Hh%Mm%Ss')
69
+ "#{@project_name}__commit__#{time_string}.yaml"
70
+ end
71
+
72
+ def long_after_previous_event?(event)
73
+ index_of_this_event = @events.index(event)
74
+
75
+ previous_event = @events[index_of_this_event - 1]
76
+
77
+ if previous_event
78
+ time_difference =
79
+ (event.time_recorded - previous_event.time_recorded).ceil
80
+
81
+ time_difference >= A_LONG_TIME
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ def all_events_equal(other_commit)
88
+ all_equal = true
89
+ @events.each_with_index do |c, i|
90
+ all_equal = false unless c == other_commit.events[i]
91
+ end
92
+ all_equal
93
+ end
94
+ end
@@ -0,0 +1,14 @@
1
+ class CommitRecord
2
+ class << self
3
+
4
+ def create(project_directory, first_event_string = nil)
5
+ commit = new(project_directory, first_event_string)
6
+ commit.save
7
+ end
8
+
9
+ def load(commit_file_path)
10
+ YAML::load(IO.read(commit_file_path).to_s)
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ class Event
2
+ attr_accessor :string, :time_recorded, :following_time_spent
3
+
4
+ def initialize(string, following_time_spent = nil)
5
+ @string = string
6
+ @time_recorded = Time.now
7
+ @following_time_spent = following_time_spent || :working
8
+ end
9
+
10
+ def following_time_spent_working?
11
+ following_time_spent == :working
12
+ end
13
+
14
+ def ==(other_event)
15
+ @string == other_event.string and
16
+ @time_recorded == other_event.time_recorded and
17
+ @following_time_spent == other_event.following_time_spent
18
+ end
19
+ end
@@ -0,0 +1,130 @@
1
+ require 'filewatcher'
2
+ require 'fileutils'
3
+ require_relative 'repo_timeline_class'
4
+ require_relative 'commit_record'
5
+
6
+ class RepoTimeline
7
+
8
+ def initialize(repo_directory)
9
+ @repo_directory = repo_directory.sub(/\/$/, '') #no trailing slash
10
+ @project_name = @repo_directory.slice(/[^\/]*$/)
11
+
12
+ @timeline_directory = initialize_timeline_directory_for(@repo_directory)
13
+ @commit_records = load_commit_records
14
+ watch_for_file_change_events
15
+ end
16
+
17
+ def add_event(event_string)
18
+ case event_string
19
+ when /git commit --amend/
20
+ amend(event_string)
21
+
22
+ when /git commit/
23
+ commit(event_string)
24
+
25
+ else
26
+ staging.generate_new_event(event_string)
27
+ end
28
+ end
29
+
30
+ def current_commit_time
31
+ staging.total_time
32
+ end
33
+
34
+ def project_time
35
+ time = 0
36
+ @commit_records.inject(0) { |total_time, cr|
37
+ total_time + cr.total_time
38
+ }
39
+ end
40
+
41
+
42
+ private
43
+
44
+ def commit(event_string)
45
+ staging.generate_new_event(event_string)
46
+
47
+ @commit_records << CommitRecord.create(
48
+ @repo_directory,
49
+ event_string
50
+ )
51
+ end
52
+
53
+ def amend(event_string)
54
+ last_completed_commit.add_events(staging.get_tail)
55
+ last_completed_commit.generate_new_event(event_string)
56
+
57
+ staging.clear_events
58
+ staging.generate_new_event(event_string)
59
+ end
60
+
61
+ def staging
62
+ if @commit_records.empty?
63
+ @commit_records << CommitRecord.create(@timeline_directory)
64
+ else
65
+ @commit_records.last
66
+ end
67
+ end
68
+
69
+ def last_completed_commit
70
+ @commit_records[-2]
71
+ end
72
+
73
+ def load_commit_records
74
+ if commit_file_paths.empty?
75
+ CommitRecord.create(@repo_directory)
76
+ end
77
+
78
+ commit_file_paths.map { |p| CommitRecord.load(p) }
79
+ end
80
+
81
+ def initialize_timeline_directory_for(repo_directory)
82
+ timeline_directory = "#{repo_directory}/.repo_timeline"
83
+ gitignore_path = "#{repo_directory}.gitignore"
84
+
85
+ ensure_gitignored(timeline_directory)
86
+
87
+ timeline_directory
88
+ end
89
+
90
+ def ensure_gitignored(timeline_directory)
91
+ gitignore_path = timeline_directory.sub('.repo_timeline', '.gitignore')
92
+
93
+ FileUtils.touch(gitignore_path)
94
+
95
+ unless File.readlines(gitignore_path).grep(/\.repo_timeline/)
96
+ open(gitignore_path, 'a') { |f| f.puts "\n.repo_timeline" }
97
+ end
98
+ end
99
+
100
+ def commit_file_paths
101
+ dir_filenames = Dir.entries(@timeline_directory)
102
+ commit_filenames = dir_filenames.select { |f| f.include? '__commit__' }
103
+ commit_filenames.map { |fn| "#{@timeline_directory}/#{fn}" }
104
+ end
105
+
106
+
107
+ def watch_for_file_change_events
108
+ if defined? Process.daemon
109
+ kill_previous_commit_timeline_process
110
+
111
+ Process.daemon
112
+
113
+ FileWatcher.new([@repo_directory]).watch do |filename|
114
+ staging.generate_new_event("File changed: #{filename}")
115
+ end
116
+ end
117
+ end
118
+
119
+ def kill_previous_commit_timeline_process
120
+ similar_processes = `ps -ax | grep repo_timetracker.rb`.split("\n")
121
+
122
+ if previous_commit_timeline_process = similar_processes.find { |p| not p.include? 'grep' }
123
+ previous_commit_timeline_pid = previous_commit_timeline_process.match(/\d+/)[0]
124
+ if previous_commit_timeline_pid != Process.pid.to_s
125
+ Process.kill("HUP", Integer(previous_commit_timeline_pid))
126
+ end
127
+
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,32 @@
1
+ class RepoTimeline
2
+ class << self
3
+
4
+ def load_or_initialize_for(directory_called_from)
5
+ directory = find_in_or_above(directory)
6
+ timeline_directory = "#{directory}/.repo_timeline"
7
+
8
+ return 'No repo found.' if directory.nil?
9
+
10
+ RepoTimeline.new(directory)
11
+ end
12
+
13
+ def find_in_or_above(directory)
14
+
15
+ if contains_repo? directory
16
+ directory
17
+ elsif directory.slice!(/\/\w+(\/?)$/)
18
+ get_closest_repository_root(directory)
19
+ else
20
+ nil
21
+ end
22
+ end
23
+
24
+
25
+
26
+ private
27
+
28
+ def contains_repo?(directory_path)
29
+ Dir.glob("#{directory_path}/.git").any?
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module RepoTimetracker
2
+ VERSION = "1.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ require_relative "repo_timetracker/version"
2
+ require_relative "repo_timetracker/repo_timeline"
3
+
4
+ module RepoTimetracker
5
+ class << self
6
+ def record(event_string, directory)
7
+ repo_timeline = RepoTimeline.load_or_initialize_for(directory)
8
+ repo_timeline.add_event(event_string)
9
+ end
10
+
11
+ def current_commit_time(directory)
12
+ repo_timeline = RepoTimeline.load_or_initialize_for(directory)
13
+
14
+ time = repo_timeline.current_commit_time
15
+
16
+ Time.at(time).utc.strftime("%H:%M:%S")
17
+ end
18
+
19
+ def project_time(directory)
20
+ repo_timeline = RepoTimeline.load_or_initialize_for(directory)
21
+
22
+ time = repo_timeline.project_time
23
+
24
+ Time.at(time).utc.strftime("%H:%M:%S")
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'repo_timetracker/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "repo_timetracker"
8
+ spec.version = RepoTimetracker::VERSION
9
+ spec.authors = ["neurodynamic"]
10
+ spec.email = ["developer@neurodynamic.io"]
11
+ spec.summary = %q{A timetracker for git commits.}
12
+ spec.description = %q{}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "filewatcher", "~> 0.4.0"
24
+ spec.add_development_dependency "minitest", ">= 5.4.0"
25
+ spec.add_development_dependency "timecop", "~> 0.7.1"
26
+ end
@@ -0,0 +1,177 @@
1
+ require 'minitest/autorun'
2
+ require 'timecop'
3
+ require 'yaml'
4
+ require_relative "../lib/repo_timetracker/commit_record"
5
+ require_relative "spec_helper"
6
+
7
+ describe CommitRecord do
8
+ before(:each) do
9
+ Timecop.return
10
+ @app_folder = "./spec/test_app"
11
+ FileUtils::mkdir_p "#{@app_folder}/.repo_timeline" unless File.directory?("#{@app_folder}/.repo_timeline")
12
+
13
+ clear_test_app_timeline_folder
14
+ end
15
+
16
+ describe "new" do
17
+ it "should create a new commit with no events if no event string given" do
18
+ CommitRecord.new(@app_folder).events.any?.must_equal false
19
+ end
20
+
21
+ it "should create a new commit with a events if event string given" do
22
+ CommitRecord.new(@app_folder, 'bangin_event').events.length.must_equal 1
23
+ end
24
+
25
+ it "should set file_path based on project name and time" do
26
+ Timecop.freeze(Time.now) do
27
+ commit = CommitRecord.new(@app_folder, 'overzealous_event')
28
+ commit.file_path.must_equal "#{@app_folder}/.repo_timeline/test_app__commit__#{Time.now.strftime('%y-%m-%d_%Hh%Mm%Ss')}.yaml"
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "generate_new_event" do
34
+ before(:each) do
35
+ @commit = CommitRecord.new(@app_folder)
36
+ end
37
+
38
+ it "should add a new event" do
39
+ @commit.events.must_be :empty?
40
+
41
+ @commit.generate_new_event 'awesome_event'
42
+
43
+ @commit.events.length.must_equal 1
44
+ @commit.events.last.string.must_equal 'awesome_event'
45
+ end
46
+
47
+ it "should set previous event to :not_working if event issued 30+ min after previous event" do
48
+ @commit.generate_new_event 'cool_event'
49
+
50
+ Timecop.freeze(Time.now + 30*60) do
51
+ @commit.generate_new_event 'much_later_event_that_is_still_cool'
52
+ end
53
+
54
+ @commit.events.first.following_time_spent.must_equal :not_working
55
+ end
56
+
57
+ it "should NOT set previous event to :not_working if less than 30min time difference" do
58
+ @commit.generate_new_event 'cool_event'
59
+
60
+ Timecop.freeze(Time.now + 30*60 - 2) do
61
+ @commit.generate_new_event 'temporally_proximitous_event'
62
+ end
63
+
64
+ @commit.events.first.following_time_spent.must_equal :working
65
+ end
66
+ end
67
+
68
+ describe "total_time" do
69
+ before(:each) do
70
+ @commit = CommitRecord.new(@app_folder)
71
+ end
72
+
73
+ it "should return zero if no events in commit" do
74
+ @commit.total_time.must_equal 0
75
+ end
76
+
77
+ it "should return zero if only one event in commit" do
78
+ @commit.generate_new_event('sad_event')
79
+ @commit.total_time.must_equal 0
80
+ end
81
+
82
+ it "should total time between all event pair intervals of less than 30 minutes" do
83
+
84
+ Timecop.freeze(Time.now) do
85
+ @commit.generate_new_event('sad_event')
86
+ end
87
+
88
+ Timecop.freeze(Time.now + 25*60) do
89
+ @commit.generate_new_event 'lonely_event'
90
+ end
91
+
92
+ Timecop.freeze(Time.now + 60*60) do
93
+ @commit.generate_new_event 'philosophically_confused_event'
94
+ end
95
+
96
+ Timecop.freeze(Time.now + 75*60) do
97
+ @commit.generate_new_event 'untrustworthy_event'
98
+ end
99
+
100
+ @commit.total_time.must_equal(75*60 - 35*60)
101
+ end
102
+ end
103
+
104
+ describe "add_events" do
105
+ it "should add array of events to current events" do
106
+ @commit = CommitRecord.new(@app_folder, 'eventant')
107
+
108
+ @commit.events.length == 1
109
+ @commit.add_events([
110
+ Event.new('badly_spelllled_event'),
111
+ Event.new('obsequious_event'),
112
+ Event.new('event_which_must_not_be_named')
113
+ ])
114
+
115
+ @commit.events.length.must_equal 4
116
+ end
117
+ end
118
+
119
+ describe "get_tail" do
120
+ it "should return tail of events array" do
121
+ @commit = CommitRecord.new(@app_folder, 'coming_up_with_names_for_these_is_hard')
122
+
123
+ @commit.get_tail.must_equal []
124
+ @commit.generate_new_event 'oh_well'
125
+ @commit.get_tail.length.must_equal 1
126
+ end
127
+ end
128
+
129
+ describe "clear_events" do
130
+ it "should make events array equal to []" do
131
+ @commit = CommitRecord.new(@app_folder, 'blah')
132
+ @commit.events.wont_equal []
133
+ @commit.clear_events
134
+ @commit.events.must_equal []
135
+ end
136
+ end
137
+
138
+ describe "==" do
139
+ it "should return true if all events and file_path are equal" do
140
+ Timecop.freeze(Time.now) do
141
+ @commit_1 = CommitRecord.new(@app_folder, 'blah')
142
+ @commit_2 = CommitRecord.new(@app_folder, 'blah')
143
+ end
144
+
145
+ @commit_1.must_equal @commit_2
146
+ end
147
+
148
+ it "should return false if any events are not equal" do
149
+ Timecop.freeze(Time.now) do
150
+ @commit_1 = CommitRecord.new(@app_folder, 'blah')
151
+ @commit_2 = CommitRecord.new(@app_folder, 'blahblah')
152
+ end
153
+
154
+ @commit_1.wont_equal @commit_2
155
+ end
156
+
157
+ it "should return false if filepaths are not equal" do
158
+ @commit_1 = CommitRecord.new(@app_folder, 'blah')
159
+ @commit_2 = @commit_1.dup
160
+ @commit_2.file_path = 'fkdlsajfl'
161
+
162
+ @commit_1.wont_equal @commit_2
163
+ end
164
+ end
165
+
166
+ describe "save" do
167
+ it "should write commit info to yaml file named by file_path variable" do
168
+ file_path = "#{@app_folder}/.repo_timeline/test_save_file.yml"
169
+
170
+ @commit = CommitRecord.new(@app_folder, 'blah')
171
+ @commit.file_path = file_path
172
+ @commit.save
173
+
174
+ @commit.must_equal YAML::load(IO.read(file_path))
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,78 @@
1
+ require 'minitest/autorun'
2
+ require 'timecop'
3
+ require_relative "../lib/repo_timetracker/event"
4
+ require_relative "spec_helper"
5
+
6
+ describe Event do
7
+ before(:each) do
8
+ Timecop.return
9
+ end
10
+
11
+ describe 'new' do
12
+ it 'should create a new event' do
13
+ Timecop.freeze
14
+ event = Event.new('test event')
15
+
16
+ event.string.must_equal 'test event'
17
+ event.time_recorded.must_equal Time.now
18
+ end
19
+
20
+ it 'should set following_time_spent if specified' do
21
+ event = Event.new('test event', :not_working)
22
+
23
+ event.following_time_spent.must_equal :not_working
24
+ end
25
+
26
+ it 'should set following_time_spent to :working if not specified' do
27
+ event = Event.new('test event')
28
+
29
+ event.following_time_spent.must_equal :working
30
+ end
31
+ end
32
+
33
+ describe 'following_time_spent_working?' do
34
+
35
+ it 'should return true if :working' do
36
+ Event.new('test event', :working).following_time_spent_working?.must_equal true
37
+ end
38
+
39
+ it 'should return false if :not_working' do
40
+ Event.new('test event', :not_working).following_time_spent_working?.must_equal false
41
+ end
42
+ end
43
+
44
+ describe '==' do
45
+ before(:each) do
46
+ @event = Event.new('test event')
47
+ end
48
+
49
+ it 'should return true if all attributes equal' do
50
+ @event.must_equal @event.dup
51
+ end
52
+
53
+ it 'should return false if any attributes inequal' do
54
+ @event.wont_equal Event.new('different test event')
55
+
56
+ @different_time_event = @event.dup
57
+ @different_time_event.time_recorded =
58
+ @different_time_event.time_recorded + 5
59
+
60
+ @event.wont_equal @different_time_event
61
+
62
+ @different_time_event = @event.dup
63
+ @different_time_event.following_time_spent = :not_working
64
+ @event.wont_equal @different_time_event
65
+ end
66
+
67
+ describe 'following_time_spent_working?' do
68
+
69
+ it 'should return true if :working' do
70
+ Event.new('test event', :working).following_time_spent_working?.must_equal true
71
+ end
72
+
73
+ it 'should return false if :not_working' do
74
+ Event.new('test event', :not_working).following_time_spent_working?.must_equal false
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,119 @@
1
+ require 'minitest/autorun'
2
+ require "timecop"
3
+ require_relative '../lib/repo_timetracker/repo_timeline'
4
+
5
+ # redefining this method so that test_app
6
+ # folder will be used instead of the real repo_timetracker repo
7
+ class RepoTimeline
8
+ def self.find_in_or_above(directory)
9
+ './spec/test_app'
10
+ end
11
+ end
12
+
13
+ class RepoTimeline
14
+ def watch_for_file_change_events
15
+ # Disabled for testing because forking screws with tests running
16
+ end
17
+ end
18
+
19
+
20
+ describe RepoTimeline do
21
+ before(:each) do
22
+ Timecop.return
23
+ clear_test_app_timeline_folder
24
+ @repo_timeline = RepoTimeline.new('./spec/test_app')
25
+ end
26
+
27
+ describe "current_commit_time" do
28
+ it 'should return zero if no data' do
29
+
30
+ @repo_timeline.current_commit_time
31
+ end
32
+ end
33
+
34
+ it 'should return no time spent if only one event recorded' do
35
+ @repo_timeline.add_event('event')
36
+ @repo_timeline.current_commit_time.must_equal 0
37
+ @repo_timeline.project_time.must_equal 0
38
+ end
39
+
40
+ it 'should return sum of time differences of all recorded events under 30min apart' do
41
+ Timecop.freeze(Time.now)
42
+ @repo_timeline.add_event('event 1')
43
+
44
+ Timecop.freeze(Time.now + 30)
45
+ @repo_timeline.add_event('event 2')
46
+ @repo_timeline.current_commit_time.must_equal 30
47
+ @repo_timeline.project_time.must_equal 30
48
+
49
+ Timecop.freeze(Time.now + 30)
50
+ @repo_timeline.add_event('event 3')
51
+ @repo_timeline.current_commit_time.must_equal 60
52
+ @repo_timeline.project_time.must_equal 60
53
+
54
+ Timecop.freeze(Time.now + 30*60)
55
+ @repo_timeline.add_event('event 4')
56
+ @repo_timeline.current_commit_time.must_equal 60
57
+ @repo_timeline.project_time.must_equal 60
58
+
59
+ Timecop.freeze(Time.now + 29*60)
60
+ @repo_timeline.add_event('event 5')
61
+ @repo_timeline.current_commit_time.must_equal 30*60
62
+ @repo_timeline.project_time.must_equal 30*60
63
+ end
64
+
65
+ describe "when a commit happens" do
66
+ before(:each) do
67
+ @repo_timeline = RepoTimeline.new('./spec/test_app')
68
+
69
+ Timecop.freeze(Time.now)
70
+ @repo_timeline.add_event('event 1')
71
+ Timecop.freeze(Time.now + 30)
72
+ @repo_timeline.add_event('event 2')
73
+ Timecop.freeze(Time.now + 30)
74
+ @repo_timeline.add_event('event 3')
75
+ Timecop.freeze(Time.now + 29*60)
76
+ @repo_timeline.add_event('event 4')
77
+ end
78
+
79
+ it "should return time in new commit only" do
80
+ Timecop.freeze(Time.now + 5*60)
81
+ @repo_timeline.add_event('git commit "commit message"')
82
+ @repo_timeline.current_commit_time.must_equal 0
83
+ @repo_timeline.project_time.must_equal 35*60
84
+
85
+ Timecop.freeze(Time.now + 5*60)
86
+ @repo_timeline.add_event('event 6"')
87
+ @repo_timeline.current_commit_time.must_equal 5*60
88
+ @repo_timeline.project_time.must_equal 40*60
89
+
90
+ end
91
+ end
92
+
93
+ describe "when an amend happens" do
94
+ before(:each) do
95
+ Timecop.freeze(Time.now)
96
+ @repo_timeline.add_event('event 1')
97
+ Timecop.freeze(Time.now + 30)
98
+ @repo_timeline.add_event('event 2')
99
+ Timecop.freeze(Time.now + 30)
100
+ @repo_timeline.add_event('event 3')
101
+ Timecop.freeze(Time.now + 29*60)
102
+ @repo_timeline.add_event('event 4')
103
+ Timecop.freeze(Time.now + 5*60)
104
+ @repo_timeline.add_event('git commit "commit message"')
105
+ end
106
+
107
+ it "should return time in new commit only" do
108
+ Timecop.freeze(Time.now + 5*60)
109
+ @repo_timeline.add_event('event 6"')
110
+ @repo_timeline.current_commit_time.must_equal 5*60
111
+ @repo_timeline.project_time.must_equal 40*60
112
+
113
+ Timecop.freeze(Time.now + 5*60)
114
+ @repo_timeline.add_event('git commit --amend"')
115
+ @repo_timeline.current_commit_time.must_equal 0
116
+ @repo_timeline.project_time.must_equal 45*60
117
+ end
118
+ end
119
+ end
data/spec/spec.rb ADDED
@@ -0,0 +1,7 @@
1
+
2
+
3
+ require 'minitest/autorun'
4
+ require_relative "spec_helper"
5
+ require_relative "event_spec"
6
+ require_relative "commit_record_spec"
7
+ require_relative "repo_timeline_spec"
@@ -0,0 +1,9 @@
1
+ def commit_filenames
2
+ Dir.entries("./spec/test_app/.repo_timeline").select { |f| f.include? '__commit__' }
3
+ end
4
+
5
+ def clear_test_app_timeline_folder
6
+ Dir.entries("./spec/test_app/.repo_timeline").select { |f| f.include? '__commit__' }.each do |f|
7
+ File.delete("./spec/test_app/.repo_timeline/#{f}")
8
+ end
9
+ end
File without changes
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: repo_timetracker
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - neurodynamic
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: filewatcher
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.4.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.4.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 5.4.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 5.4.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.7.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.7.1
83
+ description: ''
84
+ email:
85
+ - developer@neurodynamic.io
86
+ executables:
87
+ - rpt
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - bin/rpt
97
+ - lib/repo_timetracker.rb
98
+ - lib/repo_timetracker/commit_record.rb
99
+ - lib/repo_timetracker/commit_record_class.rb
100
+ - lib/repo_timetracker/event.rb
101
+ - lib/repo_timetracker/repo_timeline.rb
102
+ - lib/repo_timetracker/repo_timeline_class.rb
103
+ - lib/repo_timetracker/version.rb
104
+ - repo_timetracker.gemspec
105
+ - spec/commit_record_spec.rb
106
+ - spec/event_spec.rb
107
+ - spec/repo_timeline_spec.rb
108
+ - spec/spec.rb
109
+ - spec/spec_helper.rb
110
+ - spec/test_app/.gitignore
111
+ homepage: ''
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.4.5
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: A timetracker for git commits.
135
+ test_files:
136
+ - spec/commit_record_spec.rb
137
+ - spec/event_spec.rb
138
+ - spec/repo_timeline_spec.rb
139
+ - spec/spec.rb
140
+ - spec/spec_helper.rb
141
+ - spec/test_app/.gitignore