gitdocs 0.5.0.pre1 → 0.5.0.pre2
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 +8 -8
- data/CHANGELOG +6 -2
- data/README.md +1 -0
- data/gitdocs.gemspec +3 -1
- data/lib/gitdocs.rb +4 -0
- data/lib/gitdocs/cli.rb +22 -21
- data/lib/gitdocs/configuration.rb +0 -14
- data/lib/gitdocs/manager.rb +2 -13
- data/lib/gitdocs/notifier.rb +38 -0
- data/lib/gitdocs/repository.rb +348 -0
- data/lib/gitdocs/runner.rb +69 -192
- data/lib/gitdocs/server.rb +20 -22
- data/lib/gitdocs/version.rb +1 -1
- data/lib/gitdocs/views/settings.haml +3 -3
- data/test/configuration_test.rb +2 -0
- data/test/integration/full_sync_test.rb +67 -0
- data/test/integration/share_management_test.rb +46 -0
- data/test/integration/status_test.rb +19 -0
- data/test/integration/test_helper.rb +130 -0
- data/test/notifier_test.rb +68 -0
- data/test/repository_test.rb +578 -0
- data/test/runner_test.rb +133 -16
- data/test/test_helper.rb +0 -1
- metadata +46 -4
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZWNmOThmZjU3ZTIzNGE5ODQ3ZmM3YjEzODQ0NTNiZTgyODk5ZmQ4Ng==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MjBjMWEzNzQ5NTA0Y2Q5MzA0M2Q3MTlhMzE1YWE2OTViZWU1YTZhNg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZjU3NDJjNTQ3YTU4OGE1OTQzNDU5ZmYzMWRiZjU4MGE5MDY3NjQ5ZGIzOTNl
|
10
|
+
YjA3MDYzNjNkNTBlNmQ2OTcwYzg1ZDRlMTA2ODUwYmU2ZGVhMzM4Mzg2NGFm
|
11
|
+
YmNlMWJkYWY0NzEzZTgxOThmMWM4NTdmOTEyOGZhMTc5MDdlMzM=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MjExM2Y3OGU1NDg0ODdhYzA1NDY5YWIxNTU0ZmRkMjY2MDNkZWI2MTk1NzFl
|
14
|
+
ZjVhZWRkYTYxY2MxZDRjMjhmMzU3OTRlMzhjZjkyNDQzYzI3M2JiMDYwNTgy
|
15
|
+
MDdhODZjOTEwODEzOTRmYzc4YTExZDZjYjJjZmQwNDRmNDMxZDc=
|
data/CHANGELOG
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
0.5.0.pre2 (3/9/2014)
|
2
|
+
|
3
|
+
* Convert to use rugged and Grit (@acant)
|
4
|
+
|
1
5
|
0.5.0.pre1 (11/25/2013)
|
2
6
|
|
3
|
-
* Upgrade thin gem to v1.5.1 (
|
4
|
-
* Add TravisCI configuration (
|
7
|
+
* Upgrade thin gem to v1.5.1 (@acant)
|
8
|
+
* Add TravisCI configuration (@acant)
|
5
9
|
* Rescue StandardError and better error notifications to fix crashes (@acant)
|
6
10
|
* Reduce unexpected exists caused by repository and file system errors
|
7
11
|
* Add notification of unexpected daemon exist
|
data/README.md
CHANGED
@@ -176,6 +176,7 @@ We also have had several contributors:
|
|
176
176
|
* [Chris Kempson](https://github.com/ChrisKempson) - Encoding issues
|
177
177
|
* [Evan Tatarka](https://github.com/evant) - Front-end style fixes
|
178
178
|
* [Kale Worsley](https://github.com/kaleworsley) - Custom commit msgs, revert revisions, front-end cleanup
|
179
|
+
* [Andrew Sullivan Cant](https://github.com/acant) - Major improvements, grit support, core contributor
|
179
180
|
|
180
181
|
Gitdocs is still a young project with a lot of opportunity for contributions. Patches welcome!
|
181
182
|
|
data/gitdocs.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.add_dependency 'joshbuddy-guard', '~> 0.10.0'
|
24
24
|
s.add_dependency 'thin', '~> 1.5.1'
|
25
25
|
s.add_dependency 'renee', '~> 0.3.11'
|
26
|
-
s.add_dependency 'redcarpet', '~>
|
26
|
+
s.add_dependency 'redcarpet', '~> 3.1.1'
|
27
27
|
s.add_dependency 'thor', '~> 0.14.6'
|
28
28
|
s.add_dependency 'coderay', '~> 1.0.4'
|
29
29
|
s.add_dependency 'dante', '~> 0.1.2'
|
@@ -37,10 +37,12 @@ Gem::Specification.new do |s|
|
|
37
37
|
s.add_dependency 'mimetype-fu', "~> 0.1.2"
|
38
38
|
s.add_dependency 'eventmachine', '>= 1.0.3'
|
39
39
|
s.add_dependency 'launchy', '~> 2.4.2'
|
40
|
+
s.add_dependency 'rugged', '~> 0.19.0'
|
40
41
|
|
41
42
|
s.add_development_dependency 'minitest', "~> 5.0.8"
|
42
43
|
s.add_development_dependency 'rake'
|
43
44
|
s.add_development_dependency 'mocha'
|
44
45
|
s.add_development_dependency 'fakeweb'
|
45
46
|
s.add_development_dependency 'metric_fu'
|
47
|
+
s.add_development_dependency 'aruba'
|
46
48
|
end
|
data/lib/gitdocs.rb
CHANGED
@@ -4,6 +4,8 @@ require 'dante'
|
|
4
4
|
require 'socket'
|
5
5
|
require 'shell_tools'
|
6
6
|
require 'guard'
|
7
|
+
require 'grit'
|
8
|
+
require 'rugged'
|
7
9
|
|
8
10
|
require 'gitdocs/version'
|
9
11
|
require 'gitdocs/configuration'
|
@@ -13,6 +15,8 @@ require 'gitdocs/cli'
|
|
13
15
|
require 'gitdocs/manager'
|
14
16
|
require 'gitdocs/docfile'
|
15
17
|
require 'gitdocs/rendering'
|
18
|
+
require 'gitdocs/notifier'
|
19
|
+
require 'gitdocs/repository'
|
16
20
|
|
17
21
|
module Gitdocs
|
18
22
|
DEBUG = ENV['DEBUG']
|
data/lib/gitdocs/cli.rb
CHANGED
@@ -9,6 +9,7 @@ module Gitdocs
|
|
9
9
|
desc 'start', 'Starts a daemonized gitdocs process'
|
10
10
|
method_option :debug, type: :boolean, aliases: '-D'
|
11
11
|
method_option :port, type: :string, aliases: '-p'
|
12
|
+
method_option :pid, type: :string, aliases: '-P'
|
12
13
|
def start
|
13
14
|
unless stopped?
|
14
15
|
say 'Gitdocs is already running, please use restart', :red
|
@@ -28,6 +29,7 @@ module Gitdocs
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
32
|
+
method_option :pid, type: :string, aliases: '-P'
|
31
33
|
desc 'stop', 'Stops the gitdocs process'
|
32
34
|
def stop
|
33
35
|
unless running?
|
@@ -39,12 +41,14 @@ module Gitdocs
|
|
39
41
|
say 'Stopped gitdocs', :red
|
40
42
|
end
|
41
43
|
|
44
|
+
method_option :pid, type: :string, aliases: '-P'
|
42
45
|
desc 'restart', 'Restarts the gitdocs process'
|
43
46
|
def restart
|
44
47
|
stop
|
45
48
|
start
|
46
49
|
end
|
47
50
|
|
51
|
+
method_option :pid, type: :string, aliases: '-P'
|
48
52
|
desc 'add PATH', 'Adds a path to gitdocs'
|
49
53
|
def add(path)
|
50
54
|
config.add_path(path)
|
@@ -52,6 +56,7 @@ module Gitdocs
|
|
52
56
|
restart if running?
|
53
57
|
end
|
54
58
|
|
59
|
+
method_option :pid, type: :string, aliases: '-P'
|
55
60
|
desc 'rm PATH', 'Removes a path from gitdocs'
|
56
61
|
def rm(path)
|
57
62
|
config.remove_path(path)
|
@@ -65,14 +70,15 @@ module Gitdocs
|
|
65
70
|
say 'Cleared paths from gitdocs'
|
66
71
|
end
|
67
72
|
|
73
|
+
method_option :pid, type: :string, aliases: '-P'
|
68
74
|
desc 'create PATH REMOTE', 'Creates a new gitdoc root based on an existing remote'
|
69
75
|
def create(path, remote)
|
70
|
-
|
71
|
-
system("git clone -q #{remote} #{ShellTools.escape(path)}") || fail("Unable to clone into #{path}")
|
76
|
+
Gitdocs::Repository.clone(path, remote)
|
72
77
|
add(path)
|
73
78
|
say "Created #{path} path for gitdoc"
|
74
79
|
end
|
75
80
|
|
81
|
+
method_option :pid, type: :string, aliases: '-P'
|
76
82
|
desc 'status', 'Retrieve gitdocs status'
|
77
83
|
def status
|
78
84
|
say "GitDoc v#{VERSION}"
|
@@ -95,10 +101,10 @@ module Gitdocs
|
|
95
101
|
Launchy.open("http://localhost:#{web_port}/")
|
96
102
|
end
|
97
103
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
104
|
+
# TODO: make this work
|
105
|
+
#desc 'config', 'Configuration options for gitdocs'
|
106
|
+
#def config
|
107
|
+
#end
|
102
108
|
|
103
109
|
desc 'help', 'Prints out the help'
|
104
110
|
def help(task = nil, subcommand = false)
|
@@ -113,7 +119,7 @@ module Gitdocs
|
|
113
119
|
'gitdocs',
|
114
120
|
debug: false,
|
115
121
|
daemonize: true,
|
116
|
-
pid_path:
|
122
|
+
pid_path: pid_path
|
117
123
|
)
|
118
124
|
end
|
119
125
|
|
@@ -130,25 +136,20 @@ module Gitdocs
|
|
130
136
|
end
|
131
137
|
|
132
138
|
def pid_path
|
133
|
-
'/tmp/gitdocs.pid'
|
139
|
+
options[:pid] || '/tmp/gitdocs.pid'
|
134
140
|
end
|
135
141
|
|
136
142
|
# @return [Symbol] to indicate how the file system is being watched
|
137
143
|
def file_system_watch_method
|
138
|
-
if Guard::Listener.mac?
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
elsif Guard::Listener.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
elsif Guard::Listener.windows?
|
147
|
-
begin
|
148
|
-
return :notification if Guard::Listener::Windows.usable?
|
149
|
-
rescue NameError ; end
|
144
|
+
if Guard::Listener.mac? && Guard::Darwin.usable?
|
145
|
+
:notification
|
146
|
+
elsif Guard::Listener.linux? && Guard::Linux.usable?
|
147
|
+
:notification
|
148
|
+
elsif Guard::Listener.windows? && Guard::Windows.usable?
|
149
|
+
:notification
|
150
|
+
else
|
151
|
+
:polling
|
150
152
|
end
|
151
|
-
:polling
|
152
153
|
end
|
153
154
|
end
|
154
155
|
end
|
@@ -18,20 +18,6 @@ module Gitdocs
|
|
18
18
|
|
19
19
|
class Share < ActiveRecord::Base
|
20
20
|
attr_accessible :polling_interval, :path, :notification, :branch_name, :remote_name
|
21
|
-
|
22
|
-
def available_remotes
|
23
|
-
repo = Grit::Repo.new(path)
|
24
|
-
repo.remotes.map { |r| r.name }
|
25
|
-
rescue
|
26
|
-
nil
|
27
|
-
end
|
28
|
-
|
29
|
-
def available_branches
|
30
|
-
repo = Grit::Repo.new(path)
|
31
|
-
repo.heads.map { |r| r.name }
|
32
|
-
rescue
|
33
|
-
nil
|
34
|
-
end
|
35
21
|
end
|
36
22
|
|
37
23
|
class Config < ActiveRecord::Base
|
data/lib/gitdocs/manager.rb
CHANGED
@@ -11,18 +11,6 @@ module Gitdocs
|
|
11
11
|
yield @config if block_given?
|
12
12
|
end
|
13
13
|
|
14
|
-
RepoDescriptor = Struct.new(:name, :index)
|
15
|
-
|
16
|
-
def search(term)
|
17
|
-
results = {}
|
18
|
-
@runners.each_with_index do |runner, index|
|
19
|
-
descriptor = RepoDescriptor.new(runner.root, index)
|
20
|
-
repo_results = runner.search(term)
|
21
|
-
results[descriptor] = repo_results unless repo_results.empty?
|
22
|
-
end
|
23
|
-
results
|
24
|
-
end
|
25
|
-
|
26
14
|
def start(web_port = nil)
|
27
15
|
log("Starting Gitdocs v#{VERSION}...")
|
28
16
|
log("Using configuration root: '#{config.config_root}'")
|
@@ -38,7 +26,8 @@ module Gitdocs
|
|
38
26
|
# Start the web front-end
|
39
27
|
if config.global.start_web_frontend
|
40
28
|
web_port ||= config.global.web_frontend_port
|
41
|
-
|
29
|
+
repositories = config.shares.map { |x| Repository.new(x) }
|
30
|
+
web_server = Server.new(self, web_port, repositories)
|
42
31
|
web_server.start
|
43
32
|
web_server.wait_for_start_and_open(restarting)
|
44
33
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# Wrapper for the UI notifier
|
4
|
+
class Gitdocs::Notifier
|
5
|
+
INFO_ICON = File.expand_path('../../img/icon.png', __FILE__)
|
6
|
+
|
7
|
+
def initialize(show_notifications)
|
8
|
+
@show_notifications = show_notifications
|
9
|
+
Guard::Notifier.turn_on if @show_notifications
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(title, message)
|
13
|
+
if @show_notifications
|
14
|
+
Guard::Notifier.notify(message, title: title, image: INFO_ICON)
|
15
|
+
else
|
16
|
+
puts("#{title}: #{message}")
|
17
|
+
end
|
18
|
+
rescue # Prevent StandardErrors from stopping the daemon.
|
19
|
+
end
|
20
|
+
|
21
|
+
def warn(title, msg)
|
22
|
+
if @show_notifications
|
23
|
+
Guard::Notifier.notify(msg, title: title)
|
24
|
+
else
|
25
|
+
Kernel.warn("#{title}: #{msg}")
|
26
|
+
end
|
27
|
+
rescue # Prevent StandardErrors from stopping the daemon.
|
28
|
+
end
|
29
|
+
|
30
|
+
def error(title, message)
|
31
|
+
if @show_notifications
|
32
|
+
Guard::Notifier.notify(message, title: title, image: :failure)
|
33
|
+
else
|
34
|
+
Kernel.warn("#{title}: #{message}")
|
35
|
+
end
|
36
|
+
rescue # Prevent StandardErrors from stopping the daemon.
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,348 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# Wrapper for accessing the shared git repositories.
|
4
|
+
# Rugged, grit, or shell will be used in that order of preference depending
|
5
|
+
# upon the features which are available with each option.
|
6
|
+
#
|
7
|
+
# @note If a repository is invalid then query methods will return nil, and
|
8
|
+
# command methods will raise exceptions.
|
9
|
+
#
|
10
|
+
class Gitdocs::Repository
|
11
|
+
include ShellTools
|
12
|
+
attr_reader :invalid_reason
|
13
|
+
|
14
|
+
# Initialize the repository on the specified path. If the path is not valid
|
15
|
+
# for some reason, the object will be initialized but it will be put into an
|
16
|
+
# invalid state.
|
17
|
+
# @see #valid?
|
18
|
+
# @see #invalid_reason
|
19
|
+
#
|
20
|
+
# @param [String, Configuration::Share] path_or_share
|
21
|
+
def initialize(path_or_share)
|
22
|
+
path = path_or_share
|
23
|
+
if path_or_share.respond_to?(:path)
|
24
|
+
path = path_or_share.path
|
25
|
+
@remote_name = path_or_share.remote_name
|
26
|
+
@branch_name = path_or_share.branch_name
|
27
|
+
end
|
28
|
+
|
29
|
+
@rugged = Rugged::Repository.new(path)
|
30
|
+
@grit = Grit::Repo.new(path)
|
31
|
+
@invalid_reason = nil
|
32
|
+
rescue Rugged::OSError
|
33
|
+
@invalid_reason = :directory_missing
|
34
|
+
rescue Rugged::RepositoryError
|
35
|
+
@invalid_reason = :no_repository
|
36
|
+
end
|
37
|
+
|
38
|
+
# Clone a repository, and create the destination path if necessary.
|
39
|
+
#
|
40
|
+
# @param [String] path to clone the repository to
|
41
|
+
# @param [String] remote URI of the git repository to clone
|
42
|
+
#
|
43
|
+
# @raise [RuntimeError] if the clone fails
|
44
|
+
#
|
45
|
+
# @return [Gitdocs::Repository]
|
46
|
+
def self.clone(path, remote)
|
47
|
+
FileUtils.mkdir_p(File.dirname(path))
|
48
|
+
# TODO: determine how to do this with rugged, and handle SSH and HTTPS
|
49
|
+
# credentials.
|
50
|
+
Grit::Git.new(path).clone({ raise: true, quiet: true }, remote, path)
|
51
|
+
|
52
|
+
repository = new(path)
|
53
|
+
fail("Unable to clone into #{path}") unless repository.valid?
|
54
|
+
repository
|
55
|
+
rescue Grit::Git::GitTimeout => e
|
56
|
+
fail("Unable to clone into #{path} because it timed out")
|
57
|
+
rescue Grit::Git::CommandFailed => e
|
58
|
+
fail("Unable to clone into #{path} because of #{e.err}")
|
59
|
+
end
|
60
|
+
|
61
|
+
RepoDescriptor = Struct.new(:name, :index)
|
62
|
+
|
63
|
+
# Search across multiple repositories
|
64
|
+
#
|
65
|
+
# @param [String] term
|
66
|
+
# @param [Array<Repository>} repositories
|
67
|
+
#
|
68
|
+
# @return [Hash<RepoDescriptor, Array<SearchResult>>]
|
69
|
+
def self.search(term, repositories)
|
70
|
+
results = {}
|
71
|
+
repositories.each_with_index do |repository, index|
|
72
|
+
descriptor = RepoDescriptor.new(repository.root, index)
|
73
|
+
results[descriptor] = repository.search(term)
|
74
|
+
end
|
75
|
+
results.delete_if { |key, value| value.empty? }
|
76
|
+
end
|
77
|
+
|
78
|
+
SearchResult = Struct.new(:file, :context)
|
79
|
+
|
80
|
+
# Search a single repository
|
81
|
+
#
|
82
|
+
# @param [String] term
|
83
|
+
#
|
84
|
+
# @return [Array<SearchResult>]
|
85
|
+
def search(term)
|
86
|
+
return [] if term.empty?
|
87
|
+
|
88
|
+
results = []
|
89
|
+
options = { raise: true, bare: false, chdir: root, ignore_case: true }
|
90
|
+
@grit.git.grep(options, term).scan(/(.*?):([^\n]*)/) do |(file, context)|
|
91
|
+
if result = results.find { |s| s.file == file }
|
92
|
+
result.context += ' ... ' + context
|
93
|
+
else
|
94
|
+
results << SearchResult.new(file, context)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
results
|
98
|
+
rescue Grit::Git::GitTimeout => e
|
99
|
+
# TODO: add logging to record the error details
|
100
|
+
[]
|
101
|
+
rescue Grit::Git::CommandFailed => e
|
102
|
+
# TODO: add logging to record the error details if they are not just
|
103
|
+
# nothing found
|
104
|
+
[]
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [String]
|
108
|
+
def root
|
109
|
+
return nil unless valid?
|
110
|
+
@rugged.path.sub(/.\.git./, '')
|
111
|
+
end
|
112
|
+
|
113
|
+
# @return [Boolean]
|
114
|
+
def valid?
|
115
|
+
!@invalid_reason
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [nil] if the repository is invalid
|
119
|
+
# @return [Array<String>] sorted list of remote branches
|
120
|
+
def available_remotes
|
121
|
+
return nil unless valid?
|
122
|
+
Rugged::Branch.each_name(@rugged, :remote).sort
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [nil] if the repository is invalid
|
126
|
+
# @return [Array<String>] sorted list of local branches
|
127
|
+
def available_branches
|
128
|
+
return nil unless valid?
|
129
|
+
Rugged::Branch.each_name(@rugged, :local).sort
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [String] oid of the HEAD of the working directory
|
133
|
+
def current_oid
|
134
|
+
@rugged.head.target
|
135
|
+
rescue Rugged::ReferenceError
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
139
|
+
# Fetch and merge the repository
|
140
|
+
#
|
141
|
+
# @raise [RuntimeError] if there is a problem processing conflicted files
|
142
|
+
#
|
143
|
+
# @return [nil] if the repository is invalid
|
144
|
+
# @return [:no_remote] if the remote is not yet set
|
145
|
+
# @return [String] if there is an error return the message
|
146
|
+
# @return [Array<String>] if there is a conflict return the Array of
|
147
|
+
# conflicted file names
|
148
|
+
# @return [:ok] if pulled and merged with no errors or conflicts
|
149
|
+
def pull
|
150
|
+
return nil unless valid?
|
151
|
+
return :no_remote unless has_remote?
|
152
|
+
|
153
|
+
out, status = sh_with_code("cd #{root} ; git fetch --all 2>/dev/null && git merge #{@remote_name}/#{@branch_name} 2>/dev/null")
|
154
|
+
|
155
|
+
if status.success?
|
156
|
+
:ok
|
157
|
+
elsif out[/CONFLICT/]
|
158
|
+
# Find the conflicted files
|
159
|
+
conflicted_files = sh('git ls-files -u --full-name -z').split("\0")
|
160
|
+
.reduce(Hash.new { |h, k| h[k] = [] }) do|h, line|
|
161
|
+
parts = line.split(/\t/)
|
162
|
+
h[parts.last] << parts.first.split(/ /)
|
163
|
+
h
|
164
|
+
end
|
165
|
+
|
166
|
+
# Mark the conflicted files
|
167
|
+
conflicted_files.each do |conflict, ids|
|
168
|
+
conflict_start, conflict_end = conflict.scan(/(.*?)(|\.[^\.]+)$/).first
|
169
|
+
ids.each do |(mode, sha, id)|
|
170
|
+
author = ' original' if id == '1'
|
171
|
+
system("cd #{root} && git show :#{id}:#{conflict} > '#{conflict_start} (#{sha[0..6]}#{author})#{conflict_end}'")
|
172
|
+
end
|
173
|
+
system("cd #{root} && git rm --quiet #{conflict} >/dev/null 2>/dev/null") || fail
|
174
|
+
end
|
175
|
+
|
176
|
+
conflicted_files.keys
|
177
|
+
else
|
178
|
+
out # return the output on error
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Commit and push the repository
|
183
|
+
#
|
184
|
+
# @return [nil] if the repository is invalid
|
185
|
+
# @return [:no_remote] if the remote is not yet set
|
186
|
+
# @return [:nothing] if there was nothing to do
|
187
|
+
# @return [String] if there is an error return the message
|
188
|
+
# @return [:ok] if commited and pushed without errors or conflicts
|
189
|
+
def push(last_synced_oid, message='Auto-commit from gitdocs')
|
190
|
+
return nil unless valid?
|
191
|
+
return :no_remote unless has_remote?
|
192
|
+
|
193
|
+
#add and commit
|
194
|
+
sh_string('find . -type d -regex ``./[^.].*'' -empty -exec touch \'{}/.gitignore\' \;')
|
195
|
+
sh_string('git add .')
|
196
|
+
sh_string("git commit -a -m #{ShellTools.escape(message)}") unless sh("cd #{root} ; git status -s").empty?
|
197
|
+
|
198
|
+
if last_synced_oid.nil? || sh_string('git status')[/branch is ahead/]
|
199
|
+
out, code = sh_with_code("git push #{@remote_name} #{@branch_name}")
|
200
|
+
if code.success?
|
201
|
+
:ok
|
202
|
+
elsif last_synced_oid.nil?
|
203
|
+
:nothing
|
204
|
+
elsif out[/\[rejected\]/]
|
205
|
+
:conflict
|
206
|
+
else
|
207
|
+
out # return the output on error
|
208
|
+
end
|
209
|
+
else
|
210
|
+
:nothing
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Get the count of commits by author from the head to the specified oid.
|
215
|
+
#
|
216
|
+
# @param [String] last_oid
|
217
|
+
#
|
218
|
+
# @return [Hash<String, Int>]
|
219
|
+
def author_count(last_oid)
|
220
|
+
walker = head_walker
|
221
|
+
walker.hide(last_oid) if last_oid
|
222
|
+
walker.inject(Hash.new(0)) do |result, commit|
|
223
|
+
result["#{commit.author[:name]} <#{commit.author[:email]}>"] += 1
|
224
|
+
result
|
225
|
+
end
|
226
|
+
rescue Rugged::ReferenceError
|
227
|
+
{}
|
228
|
+
rescue Rugged::OdbError
|
229
|
+
{}
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns file meta data based on relative file path
|
233
|
+
#
|
234
|
+
# @example
|
235
|
+
# file_meta("path/to/file")
|
236
|
+
# => { :author => "Nick", :size => 1000, :modified => ... }
|
237
|
+
#
|
238
|
+
# @param [String] file relative path to file in repository
|
239
|
+
#
|
240
|
+
# @raise [RuntimeError] if the file is not found in any commits
|
241
|
+
#
|
242
|
+
# @return [Hash<Symbol=>String,Integer,Time>] the author, size and
|
243
|
+
# modification date of the file
|
244
|
+
def file_meta(file)
|
245
|
+
file = file.gsub(%r{^/}, '')
|
246
|
+
|
247
|
+
commit = head_walker.find { |x| x.diff(paths: [file]).size > 0 }
|
248
|
+
|
249
|
+
fail "File #{file} not found" unless commit
|
250
|
+
|
251
|
+
full_path = File.expand_path(file, root)
|
252
|
+
size = if File.directory?(full_path)
|
253
|
+
Dir[File.join(full_path, '**', '*')].reduce(0) do |size, file|
|
254
|
+
File.symlink?(file) ? size : size += File.size(file)
|
255
|
+
end
|
256
|
+
else
|
257
|
+
File.symlink?(full_path) ? 0 : File.size(full_path)
|
258
|
+
end
|
259
|
+
size = -1 if size == 0 # A value of 0 breaks the table sort for some reason
|
260
|
+
|
261
|
+
{ author: commit.author[:name], size: size, modified: commit.author[:time] }
|
262
|
+
end
|
263
|
+
|
264
|
+
# Returns the revisions available for a particular file
|
265
|
+
#
|
266
|
+
# @example
|
267
|
+
# file_revisions("README")
|
268
|
+
#
|
269
|
+
# @param [String] file
|
270
|
+
#
|
271
|
+
# @return [Array<Hash>]
|
272
|
+
def file_revisions(file)
|
273
|
+
file = file.gsub(%r{^/}, '')
|
274
|
+
# Excluding the initial commit (without a parent) which keeps things
|
275
|
+
# consistent with the original behaviour.
|
276
|
+
# TODO: reconsider if this is the correct behaviour
|
277
|
+
head_walker.select{|x| x.parents.size == 1 && x.diff(paths: [file]).size > 0 }
|
278
|
+
.first(100)
|
279
|
+
.map do |commit|
|
280
|
+
{
|
281
|
+
commit: commit.oid[0, 7],
|
282
|
+
subject: commit.message.split("\n")[0],
|
283
|
+
author: commit.author[:name],
|
284
|
+
date: commit.author[:time]
|
285
|
+
}
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Put the contents of the specified file revision into a temporary file
|
290
|
+
#
|
291
|
+
# @example
|
292
|
+
# file_revision_at("README", "a4c56h")
|
293
|
+
# => "/tmp/some/path/README"
|
294
|
+
#
|
295
|
+
# @param [String] file
|
296
|
+
# @param [String] ref
|
297
|
+
#
|
298
|
+
# @return [String] path of the temporary file
|
299
|
+
def file_revision_at(file, ref)
|
300
|
+
file = file.gsub(%r{^/}, '')
|
301
|
+
content = @rugged.blob_at(ref, file).text
|
302
|
+
tmp_path = File.expand_path(File.basename(file), Dir.tmpdir)
|
303
|
+
File.open(tmp_path, 'w') { |f| f.puts content }
|
304
|
+
tmp_path
|
305
|
+
end
|
306
|
+
|
307
|
+
# Revert file to the specified ref
|
308
|
+
#
|
309
|
+
# @param [String] file
|
310
|
+
# @param [String] ref
|
311
|
+
def file_revert(file, ref)
|
312
|
+
file = file.gsub(%r{^/}, '')
|
313
|
+
blob = @rugged.blob_at(ref, file)
|
314
|
+
# Silently fail if the file/ref do not existing in the repository.
|
315
|
+
# Which is consistent with the original behaviour.
|
316
|
+
# TODO: should consider throwing an exception on this condition
|
317
|
+
return unless blob
|
318
|
+
|
319
|
+
File.open(File.expand_path(file, root), 'w') { |f| f.puts(blob.text) }
|
320
|
+
end
|
321
|
+
|
322
|
+
##############################################################################
|
323
|
+
|
324
|
+
private
|
325
|
+
|
326
|
+
def has_remote?
|
327
|
+
sh_string('git remote')
|
328
|
+
end
|
329
|
+
|
330
|
+
def head_walker
|
331
|
+
walker = Rugged::Walker.new(@rugged)
|
332
|
+
walker.sorting(Rugged::SORT_DATE)
|
333
|
+
walker.push(@rugged.head.target)
|
334
|
+
walker
|
335
|
+
end
|
336
|
+
|
337
|
+
# sh_string("git config branch.`git branch | grep '^\*' | sed -e 's/\* //'`.remote", "origin")
|
338
|
+
def sh_string(cmd, default = nil)
|
339
|
+
val = sh("cd #{root} ; #{cmd}").strip rescue nil
|
340
|
+
val.nil? || val.empty? ? default : val
|
341
|
+
end
|
342
|
+
|
343
|
+
# Run in shell, return both status and output
|
344
|
+
# @see #sh
|
345
|
+
def sh_with_code(cmd)
|
346
|
+
ShellTools.sh_with_code(cmd, root)
|
347
|
+
end
|
348
|
+
end
|