Class: Gitdocs::Runner

Inherits:
Object
  • Object
show all
Includes:
ShellTools
Defined in:
lib/gitdocs/runner.rb

Defined Under Namespace

Classes: SearchResult

Constant Summary collapse

IGNORED_FILES =
['.gitignore']

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(share) ⇒ Runner

Returns a new instance of Runner.



7
8
9
10
11
12
# File 'lib/gitdocs/runner.rb', line 7

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__)
end

Instance Attribute Details

#listenerObject (readonly)

Returns the value of attribute listener.



5
6
7
# File 'lib/gitdocs/runner.rb', line 5

def listener
  @listener
end

#rootObject (readonly)

Returns the value of attribute root.



5
6
7
# File 'lib/gitdocs/runner.rb', line 5

def root
  @root
end

Instance Method Details

#clear_stateObject



74
75
76
# File 'lib/gitdocs/runner.rb', line 74

def clear_state
  @state = nil
end

#dir_files(dir_path) ⇒ Object

Returns the list of files in a given directory dir_files(“some/dir”) => [<Docfile>, <Docfile>]



159
160
161
# File 'lib/gitdocs/runner.rb', line 159

def dir_files(dir_path)
  Dir[File.join(dir_path, "*")].to_a.map { |path| Docfile.new(path) }
end

#error(title, msg) ⇒ Object



239
240
241
242
243
244
245
# File 'lib/gitdocs/runner.rb', line 239

def error(title, msg)
  if @show_notifications
    Guard::Notifier.notify(msg, :title => title, :image => :failure)
  else
    Kernel.warn("#{title}: #{msg}")
  end
end

#file_meta(file) ⇒ Object

Returns file meta data based on relative file path file_meta(“path/to/file”)

=> { :author => "Nick", :size => 1000, :modified => ... }


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/gitdocs/runner.rb', line 166

def file_meta(file)
  result = {}
  file = file.gsub(%r{^/}, '')
  full_path = File.expand_path(file, @root)
  log_result = sh_string("git log --format='%aN|%ai' -n1 #{ShellTools.escape(file)}")
  result =  {} unless File.exist?(full_path) && log_result
  author, modified = log_result.split("|")
  modified = Time.parse(modified.sub(' ', 'T')).utc.iso8601
  size = if File.directory?(full_path)
           Dir[File.join(full_path, '**', '*')].inject(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
  result = { :author => author, :size => size, :modified => modified }
  result
end

#file_revert(file, ref) ⇒ Object

Revert a file to a particular revision



209
210
211
212
213
214
215
216
# File 'lib/gitdocs/runner.rb', line 209

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

#file_revision_at(file, ref) ⇒ Object

Returns the temporary path of a particular revision of a file file_revision_at(“README”, “a4c56h”) => “/tmp/some/path/README”



200
201
202
203
204
205
206
# File 'lib/gitdocs/runner.rb', line 200

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

#file_revisions(file) ⇒ Object

Returns the revisions available for a particular file file_revisions(“README”)



188
189
190
191
192
193
194
195
196
# File 'lib/gitdocs/runner.rb', line 188

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

#get_latest_changesObject



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/gitdocs/runner.rb', line 138

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

#info(title, msg) ⇒ Object



231
232
233
234
235
236
237
# File 'lib/gitdocs/runner.rb', line 231

def info(title, msg)
  if @show_notifications
    Guard::Notifier.notify(msg, :title => title, :image => @icon)
  else
    puts("#{title}: #{msg}")
  end
end

#push_changesObject



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/gitdocs/runner.rb', line 111

def push_changes
  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)
      exit
    end
  end
end

#runObject



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/gitdocs/runner.rb', line 31

def run
  return false unless self.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

  mutex = Mutex.new

  info("Running gitdocs!", "Running gitdocs in `#{@root}'")

  # Pull changes from remote repository
  syncer = proc do
    EM.defer(proc do
      mutex.synchronize { sync_changes }
    end, proc do
      EM.add_timer(@polling_interval) {
        syncer.call
      }
    end)
  end
  syncer.call
  # Listen for changes in local repository

  EM.defer(proc{
    listener = Guard::Listener.select_and_init(@root, :watch_all_modifications => true)
    listener.on_change { |directories|
      directories.uniq!
      directories.delete_if {|d| d =~ /\/\.git/}
      unless directories.empty?
        EM.next_tick do
          EM.defer(proc {
            mutex.synchronize { push_changes }
          }, proc {} )
        end
      end
    }
    listener.start
  }, proc{EM.stop_reactor})
end

#search(term) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/gitdocs/runner.rb', line 15

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
end

#sh_string(cmd, default = nil) ⇒ Object

sh_string(“git config branch.‘git branch | grep ’^*‘ | sed -e ’s/* //‘`.remote”, “origin”)



248
249
250
251
# File 'lib/gitdocs/runner.rb', line 248

def sh_string(cmd, default=nil)
  val = sh(cmd).strip rescue nil
  (val.nil? || val.empty?) ? default : val
end

#sh_with_code(cmd) ⇒ Object

Run in shell, return both status and output

See Also:

  • #sh


255
256
257
# File 'lib/gitdocs/runner.rb', line 255

def sh_with_code(cmd)
  ShellTools.sh_with_code(cmd, @root)
end

#sync_changesObject



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/gitdocs/runner.rb', line 78

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.inject(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}")
    end
    push_changes
  elsif out[/CONFLICT/]
    conflicted_files = sh("git ls-files -u --full-name -z").split("\0").
      inject(Hash.new{|h, k| h[k] = []}) {|h, line|
        parts = line.split(/\t/)
        h[parts.last] << parts.first.split(/ /)
        h
      }
    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}") or raise
    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}")
  end
end

#valid?Boolean

Returns:

  • (Boolean)


218
219
220
221
# File 'lib/gitdocs/runner.rb', line 218

def valid?
  out, status = sh_with_code "git status"
  @root.present? && status.success?
end

#warn(title, msg) ⇒ Object



223
224
225
226
227
228
229
# File 'lib/gitdocs/runner.rb', line 223

def warn(title, msg)
  if @show_notifications
    Guard::Notifier.notify(msg, :title => title)
  else
    Kernel.warn("#{title}: #{msg}")
  end
end