@@ -46,12 +46,14 @@ def __init__(self, new_path, old_path):
4646 self .deltas = OrderedDict ([
4747 ('added' , []),
4848 ('removed' , []),
49+ ('moved' , []),
4950 ('modified' , []),
5051 ('unmodified' , [])
5152 ])
5253
5354 if self .new .path != '' and self .old .path != '' :
5455 self .determine_delta ()
56+ self .determine_moved ()
5557
5658 def align_scan (self ):
5759 """
@@ -128,30 +130,86 @@ def determine_delta(self):
128130 assert new_files_visited == self .new .files_count , "Number of visited files({})) does not match total_files({}) in the new scan" .format (new_files_visited , self .new .files_count )
129131 assert old_files_visited == self .old .files_count , "Number of visited files({})) does not match total_files({}) in the old scan" .format (old_files_visited , self .old .files_count )
130132
133+ def determine_moved (self ):
134+ """
135+ Modify the OrderedDict of Delta objects by creating an index of
136+ 'removed' Delta objects and an index of 'added' Delta objects indexed
137+ by their 'sha1' attribute, identifying any unique pairs of Deltas in
138+ both indices with the same 'sha1' and File 'name' attributes, and
139+ converting each such pair of 'added' and 'removed' Delta objects to a
140+ 'moved' Delta object.
141+ """
142+ added = self .index_deltas ('sha1' , [i for i in self .deltas ['added' ]])
143+ removed = self .index_deltas ('sha1' , [i for i in self .deltas ['removed' ]])
144+
145+ # TODO: should it be iteritems() or items()
146+ for added_sha1 , added_deltas in added .iteritems ():
147+ for removed_sha1 , removed_deltas in removed .iteritems ():
148+
149+ # check for matching sha1s on both sides
150+ if utils .check_moved (added_sha1 , added_deltas , removed_sha1 , removed_deltas ):
151+ self .update_deltas (added_deltas .pop (), removed_deltas .pop ())
152+
153+ def update_deltas (self , added , removed ):
154+ """
155+ Convert the matched 'added' and 'removed' Delta objects to a combined
156+ 'moved' Delta object and delete the 'added' and 'removed' objects.
157+ """
158+ self .deltas .get ('moved' ).append (Delta (added .new_file , removed .old_file , 'moved' ))
159+ self .deltas .get ('added' ).remove (added )
160+ self .deltas .get ('removed' ).remove (removed )
161+
162+ def index_deltas (self , index_key = 'path' , delta_list = []):
163+ """
164+ Return a dictionary of a list of Delta objects indexed by the key
165+ passed via the 'index_key' variable. If no 'index_key' variable is
166+ passed, the dict is keyed by the Delta object's 'path' variable. For a
167+ 'removed' Delta object, use the variable from the 'old_file'; for all
168+ other Delta objects (e.g., 'added'), use the 'new_file'. This function
169+ does not currently catch the AttributeError exception.
170+ """
171+ index = {}
172+
173+ for delta in delta_list :
174+ if delta .category == 'removed' :
175+ key = getattr (delta .old_file , index_key )
176+ else :
177+ key = getattr (delta .new_file , index_key )
178+
179+ if index .get (key ) is None :
180+ index [key ] = []
181+ index [key ].append (delta )
182+ else :
183+ index [key ].append (delta )
184+
185+ return index
186+
131187 def get_stats (self ):
132188 """
133189 Given a list of Delta objects, return a 'counts' dictionary keyed by
134190 category -- i.e., the keys of the determine_delta() OrderedDict of
135191 Delta objects -- that contains the count as a value for each category.
136192 """
137- added , modified , removed , unmodified = 0 , 0 , 0 , 0
193+ added , modified , moved , removed , unmodified = 0 , 0 , 0 , 0 , 0
138194
139195 added = len (self .deltas ['added' ])
140196 modified = len (self .deltas ['modified' ])
197+ moved = len (self .deltas ['moved' ])
141198 removed = len (self .deltas ['removed' ])
142199 unmodified = len (self .deltas ['unmodified' ])
143200
144- return OrderedDict ([('added' , added ), ('modified' , modified ), ('removed' , removed ), ('unmodified' , unmodified )])
201+ return OrderedDict ([('added' , added ), ('modified' , modified ), ('moved' , moved ), ( ' removed' , removed ), ('unmodified' , unmodified )])
145202
146203 def to_dict (self ):
147204 """
148205 Given an OrderedDict of Delta objects, return an OrderedDict of Delta
149- objects grouping the objects under the keys 'added', 'removed',
206+ objects grouping the objects under the keys 'added', 'removed', 'moved',
150207 'modified' or 'unmodified'.
151208 """
152209 return OrderedDict ([
153210 ('added' , [d .to_dict () for d in self .deltas .get ('added' )]),
154211 ('removed' , [d .to_dict () for d in self .deltas .get ('removed' )]),
212+ ('moved' , [d .to_dict () for d in self .deltas .get ('moved' )]),
155213 ('modified' , [d .to_dict () for d in self .deltas .get ('modified' )]),
156214 ('unmodified' , [d .to_dict () for d in self .deltas .get ('unmodified' )]),
157215 ])
@@ -161,7 +219,7 @@ class Delta(object):
161219 """
162220 A tuple reflecting a comparison of two files -- each of which is a File
163221 object -- and the category that characterizes the comparison:
164- 'added', 'modified', 'removed' or 'unmodified'.
222+ 'added', 'modified', 'moved', ' removed' or 'unmodified'.
165223 """
166224 def __init__ (self , new_file = None , old_file = None , delta_type = None ):
167225 self .new_file = new_file if new_file else File ()
@@ -219,6 +277,15 @@ def to_dict(self):
219277 ('type' , self .old_file .type ),
220278 ('size' , self .old_file .size )
221279 ])
280+ elif self .category == 'moved' :
281+ return OrderedDict ([
282+ ('category' , 'moved' ),
283+ ('path' , self .new_file .path ),
284+ ('old_path' , self .old_file .path ),
285+ ('name' , self .new_file .name ),
286+ ('type' , self .new_file .type ),
287+ ('size' , self .new_file .size )
288+ ])
222289 elif self .category == 'modified' :
223290 return OrderedDict ([
224291 ('category' , 'modified' ),
0 commit comments