Class: Piston::WorkingCopy

Inherits:
Object
  • Object
show all
Defined in:
lib/piston/working_copy.rb

Direct Known Subclasses

Git::WorkingCopy, Svn::WorkingCopy

Defined Under Namespace

Classes: NotWorkingCopy, UnhandledWorkingCopy

Constant Summary collapse

@@handlers =
Array.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ WorkingCopy

Returns a new instance of WorkingCopy.



37
38
39
40
41
42
43
44
45
# File 'lib/piston/working_copy.rb', line 37

def initialize(path)
  if path.kind_of?(Pathname)
    raise ArgumentError, "#{path} must be absolute" unless path.absolute?
    @path = path
  else
    @path = Pathname.new(File.expand_path(path))
  end
  logger.debug {"Initialized on #{@path}"}
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



35
36
37
# File 'lib/piston/working_copy.rb', line 35

def path
  @path
end

Class Method Details

.add_handler(handler) ⇒ Object



25
26
27
# File 'lib/piston/working_copy.rb', line 25

def add_handler(handler)
  @@handlers << handler
end

.guess(path) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/piston/working_copy.rb', line 11

def guess(path)
  path = path.kind_of?(Pathname) ? path : Pathname.new(path.to_s)
  try_path = path.exist? ? path : path.parent
  logger.info {"Guessing the working copy type of #{try_path.inspect}"}
  handler = handlers.detect do |handler|
    logger.debug {"Asking #{handler.name} if it understands #{try_path}"}
    handler.understands_dir?(try_path)
  end

  raise UnhandledWorkingCopy, "Don't know what working copy type #{path} is." if handler.nil?
  handler.new(File.expand_path(path))
end

.handlersObject



29
30
31
# File 'lib/piston/working_copy.rb', line 29

def handlers
  @@handlers
end

.loggerObject



7
8
9
# File 'lib/piston/working_copy.rb', line 7

def logger
  @@logger ||= Log4r::Logger["handler"]
end

Instance Method Details

#add(added) ⇒ Object

add some files to working copy



116
117
118
# File 'lib/piston/working_copy.rb', line 116

def add(added)
  raise SubclassResponsibilityError, "Piston::WorkingCopy#add should be implemented by a subclass."
end

#after_remember(path) ⇒ Object

Callback after #remember is done, to do whatever the working copy needs to do with the file.



160
161
# File 'lib/piston/working_copy.rb', line 160

def after_remember(path)
end

#copy_from(revision) ⇒ Object

Copy files from revision. revision must #respond_to?(:each), and return each file that is to be copied. Only files must be returned.

Each item yielded by Revision#each must be a relative path.

WorkingCopy will call Revision#copy_to with the full path to where the file needs to be copied.



88
89
90
91
92
93
94
95
96
# File 'lib/piston/working_copy.rb', line 88

def copy_from(revision)
  revision.each do |relpath|
    target = path + relpath
    target.dirname.mkdir rescue nil

    logger.debug {"Copying #{relpath} to #{target}"}
    revision.copy_to(relpath, target)
  end
end

#copy_to(revision) ⇒ Object

Copy files to revision to keep local changes. revision must #respond_to?(:each), and return each file that is to be copied. Only files must be returned.

Each item yielded by Revision#each must be a relative path.

WorkingCopy will call Revision#copy_from with the full path from where the file needs to be copied.



106
107
108
109
110
111
112
113
# File 'lib/piston/working_copy.rb', line 106

def copy_to(revision)
  revision.each do |relpath|
    source = path + relpath

    logger.debug {"Copying #{source} to #{relpath}"}
    revision.copy_from(source, relpath)
  end
end

#createObject

Creates the initial working copy for pistonizing a new repository.



76
77
78
# File 'lib/piston/working_copy.rb', line 76

def create
  logger.debug {"Creating working copy at #{path}"}
end

#delete(deleted) ⇒ Object

delete some files from working copy



121
122
123
# File 'lib/piston/working_copy.rb', line 121

def delete(deleted)
  raise SubclassResponsibilityError, "Piston::WorkingCopy#delete should be implemented by a subclass."
end

#diffObject



257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/piston/working_copy.rb', line 257

def diff
  tmpdir = temp_dir_name
  begin
    logger.info {"Checking out the repository at #{revision.revision}"}
    revision = repository.at(:head)
    revision.checkout_to(tmpdir)

    excludes = (['.piston.yml'] + exclude_for_diff + revision.exclude_for_diff).uniq.collect {|pattern| "--exclude=#{pattern}"}.join ' '
    system("diff -urN #{excludes} '#{tmpdir}' '#{path}'")
  ensure
    logger.debug {"Removing temporary directory: #{tmpdir}"}
    tmpdir.rmtree rescue nil
  end
end

#downgrade_to(revision) ⇒ Object

Downgrade this working copy to revision.



131
132
133
# File 'lib/piston/working_copy.rb', line 131

def downgrade_to(revision)
  raise SubclassResponsibilityError, "Piston::WorkingCopy#downgrade_to should be implemented by a subclass."
end

#exclude_for_diffObject



284
285
286
# File 'lib/piston/working_copy.rb', line 284

def exclude_for_diff
  raise SubclassResponsibilityError, "Piston::WorkingCopy#exclude_for_diff should be implemented by a subclass."
end

#exist?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/piston/working_copy.rb', line 55

def exist?
  @path.exist? && @path.directory?
end

#finalizeObject



168
169
170
# File 'lib/piston/working_copy.rb', line 168

def finalize
  logger.debug {"Finalizing #{path}"}
end

#import(revision, lock) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/piston/working_copy.rb', line 177

def import(revision, lock)
  lock ||= false
  repository = revision.repository
  tmpdir = temp_dir_name
  begin
    logger.info {"Checking out the repository"}
    revision.checkout_to(tmpdir)

    logger.debug {"Creating the local working copy"}
    create

    logger.info {"Copying from #{revision}"}
    copy_from(revision)

    logger.debug {"Remembering values"}
    remember(
      {:repository_url => repository.url, :lock => lock, :repository_class => repository.class.name},
      revision.remember_values
    )

    logger.debug {"Finalizing working copy"}
    finalize

    logger.info {"Checked out #{repository.url.inspect} #{revision.name} to #{path.to_s}"}
  ensure
    logger.debug {"Removing temporary directory: #{tmpdir}"}
    tmpdir.rmtree rescue nil
  end
end

#infoObject

Returns basic information about this working copy.



173
174
175
# File 'lib/piston/working_copy.rb', line 173

def info
  recall
end

#locally_modifiedObject



276
277
278
# File 'lib/piston/working_copy.rb', line 276

def locally_modified
  raise SubclassResponsibilityError, "Piston::WorkingCopy#locally_modified should be implemented by a subclass."
end

#loggerObject



47
48
49
# File 'lib/piston/working_copy.rb', line 47

def logger
  self.class.logger
end

#merge_local_changes(revision) ⇒ Object

Merge remote changes with local changes in revision.



136
137
138
# File 'lib/piston/working_copy.rb', line 136

def merge_local_changes(revision)
  raise SubclassResponsibilityError, "Piston::WorkingCopy#merge_local_changes should be implemented by a subclass."
end

#pistonized?Boolean

Returns:

  • (Boolean)


59
60
61
# File 'lib/piston/working_copy.rb', line 59

def pistonized?
  yaml_path.exist? && yaml_path.file?
end

#recallObject

Recalls a Hash of values from the working copy.



164
165
166
# File 'lib/piston/working_copy.rb', line 164

def recall
  YAML.load_file(yaml_path)
end

#remember(values, handler_values) ⇒ Object

Stores a Hash of values that can be retrieved later.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/piston/working_copy.rb', line 141

def remember(values, handler_values)
  values["format"] = 1

  # Stringify keys
  values.keys.each do |key|
    values[key.to_s] = values.delete(key)
  end

  logger.debug {"Remembering #{values.inspect} as well as #{handler_values.inspect}"}
  File.open(yaml_path, "w+") do |f|
    f.write(values.merge("handler" => handler_values).to_yaml)
  end

  logger.debug {"Calling \#after_remember on #{yaml_path}"}
  after_remember(yaml_path)
end

#remotely_modifiedObject



280
281
282
# File 'lib/piston/working_copy.rb', line 280

def remotely_modified
  repository.at(recall["handler"]).remotely_modified
end

#rename(renamed) ⇒ Object

rename some files in working copy



126
127
128
# File 'lib/piston/working_copy.rb', line 126

def rename(renamed)
  raise SubclassResponsibilityError, "Piston::WorkingCopy#rename should be implemented by a subclass."
end

#repositoryObject



68
69
70
71
72
73
# File 'lib/piston/working_copy.rb', line 68

def repository
  values = self.recall
  repository_class = values["repository_class"]
  repository_url = values["repository_url"]
  repository_class.constantize.new(repository_url)
end

#temp_dir_nameObject



272
273
274
# File 'lib/piston/working_copy.rb', line 272

def temp_dir_name
  path.parent + ".#{path.basename}.tmp"
end

#to_sObject



51
52
53
# File 'lib/piston/working_copy.rb', line 51

def to_s
  "Piston::WorkingCopy(#{@path})"
end

#update(revision, to, lock) ⇒ Object

Update this working copy from from to to, which means merging local changes back in Return true if changed, false if not



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/piston/working_copy.rb', line 209

def update(revision, to, lock)
  lock ||= false
  tmpdir = temp_dir_name
  begin
    logger.info {"Checking out the repository at #{revision.revision}"}
    revision.checkout_to(tmpdir)

    revision_to_return = current_revision
    revision_to_downgrade = last_changed_revision(yaml_path)
    logger.debug {"Downgrading to #{revision_to_downgrade}"}
    downgrade_to(revision_to_downgrade)

    logger.debug {"Copying old changes to temporary directory in order to keep them"}
    copy_to(revision)

    logger.info {"Looking changes from #{revision.revision} to #{to.revision}"}
    added, deleted, renamed = revision.update_to(to.revision)

    logger.info {"Updating working copy"}

    # rename before copy because copy_from will copy these files
    logger.debug {"Renaming files"}
    rename(renamed)

    logger.debug {"Copying files from temporary directory"}
    copy_from(revision)

    logger.debug {"Adding new files to version control"}
    add(added)

    logger.debug {"Deleting files from version control"}
    delete(deleted)

    # merge local changes updating to revision before downgrade was made
    logger.debug {"Merging local changes"}
    merge_local_changes(revision_to_return)

    remember(recall.merge(:lock => lock), to.remember_values)

    status = status(path)
    logger.debug { {:added => added, :deleted => deleted, :renamed => renamed, :status => status}.to_yaml }
    !status.empty?
  ensure
    logger.debug {"Removing temporary directory: #{tmpdir}"}
    tmpdir.rmtree rescue nil
  end
end

#validate!Object

Raises:



63
64
65
66
# File 'lib/piston/working_copy.rb', line 63

def validate!
  raise NotWorkingCopy unless self.pistonized?
  self
end