class PatchRecursiveMission(object): OLD_DIR = os.path.join(get_mission_data_path('diffpatch'), 'recipes') NEW_DIR = os.path.join( get_mission_data_path('diffpatch'), 'recipes.more-garlic') BASE_NAME = 'recipes' ANSWERS = {'amount_of_garlic': 3} # NOTE: You should rely on the DiffRecursiveMission for the tarball. @classmethod def get_patch(cls): patchfile = StringIO() for name in os.listdir(cls.OLD_DIR): oldname = os.path.join(cls.OLD_DIR, name) newname = os.path.join(cls.NEW_DIR, name) if not os.path.isfile(oldname): continue oldlines = open(oldname).readlines() newlines = open(newname).readlines() patchfile.writelines( difflib.unified_diff(oldlines, newlines, 'a/%s/%s' % (cls.BASE_NAME, name), 'b/%s/%s' % (cls.BASE_NAME, name))) return patchfile.getvalue()
class PatchSingleFileMission(SingleFilePatch): OLD_FILE = os.path.join(get_mission_data_path('diffpatch'), 'oven-pancake.txt') NEW_FILE = os.path.join(get_mission_data_path('diffpatch'), 'oven-pancake_result.txt') # This does not correspond to a real file but is merely the filename the download is presented as. PATCH_FILENAME = 'add-oven-temp.patch'
def synthesize_tarball(cls): tdata = StringIO() tfile = tarfile.open(fileobj=tdata, mode='w:gz') tfile.add(os.path.join(get_mission_data_path('diffpatch'), cls.ORIG_DIR), cls.ORIG_DIR) tfile.close() return tdata.getvalue()
def validate_patch(cls, patchdata): the_patch = patch.fromstring(patchdata) # Strip one level of directories from the left of the filenames. for i, filename in enumerate(the_patch.source): cls.check_for_leading_dot_slash_in_filename(filename) the_patch.source[i] = cls.strip_filename_one_path_level(filename) for i, filename in enumerate(the_patch.target): cls.check_for_leading_dot_slash_in_filename(filename) the_patch.target[i] = cls.strip_filename_one_path_level(filename) # Go through the files and check that ones that should be mentioned in # the patch are so mentioned. path_to_mission_files = os.path.join( get_mission_data_path('diffpatch'), cls.ORIG_DIR) for filename in os.listdir(path_to_mission_files): old_style_filename = filename full_filename = os.path.join(path_to_mission_files, filename) if not os.path.isfile(full_filename): continue old_contents = open(full_filename).read() new_contents = old_contents for old, new in cls.SUBSTITUTIONS: new_contents = new_contents.replace(old, new) if old_contents == new_contents: continue # So it's a file that the patch should modify. try: index = the_patch.source.index(filename) except ValueError: # A file the user was supposed to modify was not included in # the patch. # # We did rename a bunch of the files a little while ago. # Maybe we can process their submission anyway by looking for # the old filename in the patch header. old_style_filename = DiffRecursiveMission.name_new2old( filename) try: index = the_patch.source.index(old_style_filename) except ValueError: raise IncorrectPatch, 'Patch does not modify file "%s", which it should modify.' % filename if (the_patch.target[index] != filename and the_patch.target[index] != old_style_filename): raise IncorrectPatch, 'Patch headers for file "%s" have inconsistent filenames.' % filename hunks = the_patch.hunks[index] del the_patch.source[index] del the_patch.target[index] del the_patch.hunks[index] del the_patch.hunkends[index] # Check that it will apply correctly to the file. if not the_patch._match_file_hunks(full_filename, hunks): # Check for reverse patch by seeing if there is "-firstSubstitute" and "+firstOriginal" in the diff. # (If they did everything perfectly and reversed the patch, there will be two lines following these conditions) if patchdata.find('-' + cls.SUBSTITUTIONS[1][1]) != -1 and patchdata.find('+' + cls.SUBSTITUTIONS[1][0]) != -1: raise IncorrectPatch, 'You submitted a patch that would revert the correct changes back to the originals. You may have mixed the parameters for diff, or performed a reverse patch.' else: raise IncorrectPatch, 'The modifications to "%s" will not apply correctly to the original file.' % filename # Check for BOM issues. Likely a Windows-only issue, and only when using a text editor that # includes UTF-8 BOM markers when saving files. if '\xef\xbb\xbf' in ''.join(the_patch.patch_stream(StringIO(old_contents), hunks)): raise IncorrectPatch, 'It appears the text editor you used to modify "%s" leaves UTF-8 BOM characters. Try an editor like Notepad++ or something similar.' % filename # Check that the resulting file matches what is expected. if ''.join(the_patch.patch_stream(StringIO(old_contents), hunks)) != new_contents: raise IncorrectPatch, 'The modifications to "%s" do not result in the correct contents. Make sure you replaced "Aubergine", too!' % filename if len(the_patch.source) != 0: raise IncorrectPatch, 'The patch modifies files that it should not modify: %s' % ', '.join( the_patch.source)
class DiffSingleFileMission(SingleFilePatch): OLD_FILE = os.path.join( get_mission_data_path('diffpatch'), 'nutty-pancake.txt') NEW_FILE = os.path.join( get_mission_data_path('diffpatch'), 'nutty-pancake_result.txt')
class DiffForm(django.forms.Form): diff = django.forms.CharField( error_messages={'required': 'No svn diff output was given.'}, widget=django.forms.Textarea()) FILE_TO_BE_PATCHED = 'README' NEW_CONTENT = os.path.join(get_mission_data_path('svn'), 'README-new-for-svn-diff') def __init__(self, username=None, wcdir=None, request=None, *args, **kwargs): super(DiffForm, self).__init__(request, *args, **kwargs) self.username = username self.wcdir = wcdir if wcdir: self.file_to_patch = os.path.join(wcdir, self.FILE_TO_BE_PATCHED) self.the_patch = None self.new_content = None def clean_diff(self): """ Validate the diff form. This function will be invoked by django.form.Forms.is_valid(), and will raise the exception ValidationError """ self.the_patch = patch.fromstring(self.cleaned_data['diff']) # Check that the submitted diff patches the correct number of files if len(self.the_patch.hunks) != 1: raise ValidationError, 'The patch affects more than one file.' # Check that the filename it patches is correct. if self.FILE_TO_BE_PATCHED not in self.cleaned_data['diff']: raise ValidationError, 'The patch affects the wrong file.' # Now we need to generate a working copy to apply the patch to. # We can also use this working copy to commit the patch if it's OK. repo = view_helpers.SvnRepository(self.username) view_helpers.subproc_check_output( ['svn', 'co', repo.file_trunk_url(), self.wcdir]) # Check that it will apply correctly to the working copy. if not self.the_patch._match_file_hunks(self.file_to_patch, self.the_patch.hunks[0]): raise ValidationError, 'The patch will not apply correctly to the lastest revision.' # Check that the resulting file matches what is expected. self.new_content = ''.join( self.the_patch.patch_stream(open(self.file_to_patch), self.the_patch.hunks[0])) if self.new_content != open(self.NEW_CONTENT).read(): raise ValidationError, 'The file resulting from patching does not have the correct contents.' def commit_diff(self): """Commit the diff form and the patch.""" open(self.file_to_patch, 'w').write(self.new_content) commit_message = '''Fix a typo in %s. Thanks for reporting this, %s!''' % ( self.FILE_TO_BE_PATCHED, self.username) view_helpers.subproc_check_output([ 'svn', 'commit', '-m', commit_message, '--username', 'mr_bad', self.wcdir ])