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 +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +173 -0
- data/Rakefile +2 -0
- data/bin/rpt +21 -0
- data/lib/repo_timetracker/commit_record.rb +94 -0
- data/lib/repo_timetracker/commit_record_class.rb +14 -0
- data/lib/repo_timetracker/event.rb +19 -0
- data/lib/repo_timetracker/repo_timeline.rb +130 -0
- data/lib/repo_timetracker/repo_timeline_class.rb +32 -0
- data/lib/repo_timetracker/version.rb +3 -0
- data/lib/repo_timetracker.rb +27 -0
- data/repo_timetracker.gemspec +26 -0
- data/spec/commit_record_spec.rb +177 -0
- data/spec/event_spec.rb +78 -0
- data/spec/repo_timeline_spec.rb +119 -0
- data/spec/spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/test_app/.gitignore +0 -0
- metadata +141 -0
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
data/Gemfile
ADDED
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
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,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
|
data/spec/event_spec.rb
ADDED
|
@@ -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
data/spec/spec_helper.rb
ADDED
|
@@ -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
|