lib/gitdocs/runner.rb in gitdocs-0.5.0.pre1 vs lib/gitdocs/runner.rb in gitdocs-0.5.0.pre2
- old
+ new
@@ -1,53 +1,32 @@
module Gitdocs
class Runner
- include ShellTools
-
- attr_reader :root, :listener
-
def self.start_all(shares)
runners = shares.map { |share| Runner.new(share) }
runners.each(&:run)
runners
end
def initialize(share)
@share = share
- @root = share.path.sub(%r{/+$}, '') if share.path
@polling_interval = share.polling_interval
- @icon = File.expand_path('../../img/icon.png', __FILE__)
+ @notifier = Gitdocs::Notifier.new(@share.notification)
+ @repository = Gitdocs::Repository.new(share)
end
- SearchResult = Struct.new(:file, :context)
- def search(term)
- return [] if term.empty?
-
- results = []
- if result_test = sh_string("git grep -i #{ShellTools.escape(term)}")
- result_test.scan(/(.*?):([^\n]*)/) do |(file, context)|
- if result = results.find { |s| s.file == file }
- result.context += ' ... ' + context
- else
- results << SearchResult.new(file, context)
- end
- end
- end
- results
+ def root
+ @repository.root
end
def run
- return false unless self.valid?
+ return false unless @repository.valid?
- @show_notifications = @share.notification
- @current_remote = @share.remote_name
- @current_branch = @share.branch_name
- @current_revision = sh_string('git rev-parse HEAD')
- Guard::Notifier.turn_on if @show_notifications
+ @last_synced_revision = @repository.current_oid
mutex = Mutex.new
- info('Running gitdocs!', "Running gitdocs in `#{@root}'")
+ @notifier.info('Running gitdocs!', "Running gitdocs in '#{root}'")
# Pull changes from remote repository
syncer = proc do
EM.defer(proc do
mutex.synchronize { sync_changes }
@@ -59,11 +38,11 @@
end
syncer.call
# Listen for changes in local repository
EM.defer(proc do
- listener = Guard::Listener.select_and_init(@root, watch_all_modifications: true)
+ listener = Guard::Listener.select_and_init(root, watch_all_modifications: true)
listener.on_change do |directories|
directories.uniq!
directories.delete_if { |d| d =~ /\/\.git/ }
unless directories.empty?
EM.next_tick do
@@ -80,194 +59,92 @@
def clear_state
@state = nil
end
def sync_changes
- out, status = sh_with_code("git fetch --all && git merge #{@current_remote}/#{@current_branch}")
- if status.success?
- changes = get_latest_changes
- unless changes.empty?
- author_list = changes.reduce(Hash.new { |h, k| h[k] = 0 }) { |h, c| h[c['author']] += 1; h }.to_a.sort { |a, b| b[1] <=> a[1] }.map { |(name, count)| "* #{name} (#{count} change#{count == 1 ? '' : 's'})" }.join("\n")
- info("Updated with #{changes.size} change#{changes.size == 1 ? '' : 's'}", "In `#{@root}':\n#{author_list}")
+ result = @repository.pull
+
+ return if result.nil? || result == :no_remote
+
+ if result.kind_of?(String)
+ @notifier.error(
+ 'There was a problem synchronizing this gitdoc',
+ "A problem occurred in #{root}:\n#{result}"
+ )
+ return
+ end
+
+ if result == :ok
+ author_change_count = latest_author_count
+ unless author_change_count.empty?
+ author_list = author_change_count.map { |author, count| "* #{author} (#{change_count(count)})" }.join("\n")
+ @notifier.info(
+ "Updated with #{change_count(author_change_count)}",
+ "In '#{root}':\n#{author_list}"
+ )
end
- push_changes
- elsif out[/CONFLICT/]
- conflicted_files = sh('git ls-files -u --full-name -z').split("\0")
- .reduce(Hash.new { |h, k| h[k] = [] }) do|h, line|
- parts = line.split(/\t/)
- h[parts.last] << parts.first.split(/ /)
- h
- end
- warn('There were some conflicts', "#{conflicted_files.keys.map { |f| "* #{f}" }.join("\n")}")
- conflicted_files.each do |conflict, ids|
- conflict_start, conflict_end = conflict.scan(/(.*?)(|\.[^\.]+)$/).first
- ids.each do |(mode, sha, id)|
- author = ' original' if id == '1'
- system("cd #{@root} && git show :#{id}:#{conflict} > '#{conflict_start} (#{sha[0..6]}#{author})#{conflict_end}'")
- end
- system("cd #{@root} && git rm #{conflict}") || fail
- end
- push_changes
- elsif sh_string('git remote').nil? # no remote to pull from
- # Do nothing, no remote repo yet
else
- error('There was a problem synchronizing this gitdoc', "A problem occurred in #{@root}:\n#{out}")
+ #assert result.kind_of?(Array)
+ @notifier.warn(
+ 'There were some conflicts',
+ result.map { |f| "* #{f}" }.join("\n")
+ )
end
+
+ push_changes
end
def push_changes
- message_file = File.expand_path('.gitmessage~', @root)
- if File.exist? message_file
- message = File.read message_file
- File.delete message_file
+ message_file = File.expand_path('.gitmessage~', root)
+ if File.exist?(message_file)
+ message = File.read(message_file)
+ File.delete(message_file)
else
message = 'Auto-commit from gitdocs'
end
- sh 'find . -type d -regex ``./[^.].*'' -empty -exec touch \'{}/.gitignore\' \;'
- sh 'git add .'
- sh "git commit -a -m #{ShellTools.escape(message)}" unless sh('git status -s').strip.empty?
- if @current_revision.nil? || sh('git status')[/branch is ahead/]
- out, code = sh_with_code("git push #{@current_remote} #{@current_branch}")
- if code.success?
- changes = get_latest_changes
- info("Pushed #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been pushed")
- elsif @current_revision.nil?
- # ignorable
- elsif out[/\[rejected\]/]
- warn("There was a conflict in #{@root}, retrying", '')
- else
- error("BAD Could not push changes in #{@root}", out)
- # TODO: need to add a status on shares so that the push problem can be
- # displayed.
- end
+
+ result = @repository.push(@last_synced_revision, message)
+
+ return if result.nil? || result == :no_remote || result == :nothing
+ level, title, message = case result
+ when :ok then [:info, "Pushed #{change_count(latest_author_count)}", "'#{root}' has been pushed"]
+ when :conflict then [:warn, "There was a conflict in #{root}, retrying", '']
+ else
+ # assert result.kind_of?(String)
+ [:error, "BAD Could not push changes in #{root}", result]
+ # TODO: need to add a status on shares so that the push problem can be
+ # displayed.
end
- rescue
+ @notifier.send(level, title, message)
+ rescue => e
# Rescue any standard exceptions which come from the push related
# commands. This will prevent problems on a single share from killing
# the entire daemon.
- error("Unexpected error pushing changes in #{@root}")
+ @notifier.error("Unexpected error pushing changes in #{root}", "#{e}")
# TODO: get logging and/or put the error message into a status field in the database
end
- def get_latest_changes
- if @current_revision
- out = sh "git log #{@current_revision}.. --pretty='format:{\"commit\": \"%H\",%n \"author\": \"%an <%ae>\",%n \"date\": \"%ad\",%n \"message\": \"%s\"%n}'"
- if out.empty?
- []
- else
- lines = []
- Yajl::Parser.new.parse(out) do |obj|
- lines << obj
- end
- @current_revision = sh('git rev-parse HEAD').strip
- lines
- end
- else
- []
- end
- end
+ ############################################################################
+ private
- IGNORED_FILES = ['.gitignore']
- # Returns the list of files in a given directory
- # dir_files("some/dir") => [<Docfile>, <Docfile>]
- def dir_files(dir_path)
- Dir[File.join(dir_path, '*')].to_a.map { |path| Docfile.new(path) }
- end
+ # Update the author count for the last synced changes, and then update the
+ # last synced revision id.
+ #
+ # @return [Hash<String,Int>]
+ def latest_author_count
+ last_oid = @last_synced_revision
+ @last_synced_revision = @repository.current_oid
- # Returns file meta data based on relative file path
- # file_meta("path/to/file")
- # => { :author => "Nick", :size => 1000, :modified => ... }
- def file_meta(file)
- file = file.gsub(%r{^/}, '')
- full_path = File.expand_path(file, @root)
- log_result = sh_string("git log --format='%aN|%ai' -n1 #{ShellTools.escape(file)}")
- author, modified = log_result.split('|')
- modified = Time.parse(modified.sub(' ', 'T')).utc.iso8601
- size = if File.directory?(full_path)
- Dir[File.join(full_path, '**', '*')].reduce(0) do |size, file|
- File.symlink?(file) ? size : size += File.size(file)
- end
- else
- File.symlink?(full_path) ? 0 : File.size(full_path)
- end
- size = -1 if size == 0 # A value of 0 breaks the table sort for some reason
-
- { author: author, size: size, modified: modified }
+ @repository.author_count(last_oid)
end
- # Returns the revisions available for a particular file
- # file_revisions("README")
- def file_revisions(file)
- file = file.gsub(%r{^/}, '')
- output = sh_string("git log --format='%h|%s|%aN|%ai' -n100 #{ShellTools.escape(file)}")
- output.to_s.split("\n").map do |log_result|
- commit, subject, author, date = log_result.split('|')
- date = Time.parse(date.sub(' ', 'T')).utc.iso8601
- { commit: commit, subject: subject, author: author, date: date }
- end
- end
-
- # Returns the temporary path of a particular revision of a file
- # file_revision_at("README", "a4c56h") => "/tmp/some/path/README"
- def file_revision_at(file, ref)
- file = file.gsub(%r{^/}, '')
- content = sh_string("git show #{ref}:#{ShellTools.escape(file)}")
- tmp_path = File.expand_path(File.basename(file), Dir.tmpdir)
- File.open(tmp_path, 'w') { |f| f.puts content }
- tmp_path
- end
-
- # Revert a file to a particular revision
- def file_revert(file, ref)
- if file_revisions(file).map { |r| r[:commit] }.include? ref
- file = file.gsub(%r{^/}, '')
- full_path = File.expand_path(file, @root)
- content = File.read(file_revision_at(file, ref))
- File.open(full_path, 'w') { |f| f.puts content }
- end
- end
-
- def valid?
- out, status = sh_with_code 'git status'
- @root.present? && status.success?
- end
-
- def warn(title, msg)
- if @show_notifications
- Guard::Notifier.notify(msg, title: title)
+ def change_count(count_or_hash)
+ count = if count_or_hash.respond_to?(:values)
+ count_or_hash .values.reduce(:+)
else
- Kernel.warn("#{title}: #{msg}")
+ count_or_hash
end
- rescue # Prevent StandardErrors from stopping the daemon.
- end
- def info(title, msg)
- if @show_notifications
- Guard::Notifier.notify(msg, title: title, image: @icon)
- else
- puts("#{title}: #{msg}")
- end
- rescue # Prevent StandardErrors from stopping the daemon.
- end
-
- def error(title, msg)
- if @show_notifications
- Guard::Notifier.notify(msg, title: title, image: :failure)
- else
- Kernel.warn("#{title}: #{msg}")
- end
- rescue # Prevent StandardErrors from stopping the daemon.
- end
-
- # sh_string("git config branch.`git branch | grep '^\*' | sed -e 's/\* //'`.remote", "origin")
- def sh_string(cmd, default = nil)
- val = sh(cmd).strip rescue nil
- val.nil? || val.empty? ? default : val
- end
-
- # Run in shell, return both status and output
- # @see #sh
- def sh_with_code(cmd)
- ShellTools.sh_with_code(cmd, @root)
+ "#{count} change#{count == 1 ? '' : 's'}"
end
end
end