def test_diff_with_rename(self): output = StringProcessAdapter(fixture('diff_rename')) diffs = Diff._index_from_patch_format(self.rorepo, output) self._assert_diff_format(diffs) assert_equal(1, len(diffs)) diff = diffs[0] assert_true(diff.renamed_file) assert_true(diff.renamed) assert_equal(diff.rename_from, u'Jérôme') assert_equal(diff.rename_to, u'müller') assert_equal(diff.raw_rename_from, b'J\xc3\xa9r\xc3\xb4me') assert_equal(diff.raw_rename_to, b'm\xc3\xbcller') assert isinstance(str(diff), str) output = StringProcessAdapter(fixture('diff_rename_raw')) diffs = Diff._index_from_raw_format(self.rorepo, output) self.assertEqual(len(diffs), 1) diff = diffs[0] self.assertIsNotNone(diff.renamed_file) self.assertIsNotNone(diff.renamed) self.assertEqual(diff.rename_from, 'this') self.assertEqual(diff.rename_to, 'that') self.assertEqual(diff.change_type, 'R') self.assertEqual(diff.score, 100) self.assertEqual(len(list(diffs.iter_change_type('R'))), 1)
def test_diff_with_change_in_type(self): output = StringProcessAdapter(fixture('diff_change_in_type')) diffs = Diff._index_from_patch_format(self.rorepo, output) self._assert_diff_format(diffs) assert_equal(2, len(diffs)) diff = diffs[0] self.assertIsNotNone(diff.deleted_file) assert_equal(diff.a_path, 'this') assert_equal(diff.b_path, 'this') assert isinstance(str(diff), str) diff = diffs[1] assert_equal(diff.a_path, None) assert_equal(diff.b_path, 'this') self.assertIsNotNone(diff.new_file) assert isinstance(str(diff), str) output = StringProcessAdapter(fixture('diff_change_in_type_raw')) diffs = Diff._index_from_raw_format(self.rorepo, output) self.assertEqual(len(diffs), 1) diff = diffs[0] self.assertEqual(diff.rename_from, None) self.assertEqual(diff.rename_to, None) self.assertEqual(diff.change_type, 'T') self.assertEqual(len(list(diffs.iter_change_type('T'))), 1)
def test_diff_with_rename(self): output = StringProcessAdapter(fixture('diff_rename')) diffs = Diff._index_from_patch_format(self.rorepo, output.stdout) self._assert_diff_format(diffs) assert_equal(1, len(diffs)) diff = diffs[0] assert_true(diff.renamed_file) assert_true(diff.renamed) assert_equal(diff.rename_from, u'Jérôme') assert_equal(diff.rename_to, u'müller') assert_equal(diff.raw_rename_from, b'J\xc3\xa9r\xc3\xb4me') assert_equal(diff.raw_rename_to, b'm\xc3\xbcller') assert isinstance(str(diff), str) output = StringProcessAdapter(fixture('diff_rename_raw')) diffs = Diff._index_from_raw_format(self.rorepo, output.stdout) assert len(diffs) == 1 diff = diffs[0] assert diff.renamed_file assert diff.renamed assert diff.rename_from == 'this' assert diff.rename_to == 'that' assert len(list(diffs.iter_change_type('R'))) == 1
def test_diff_patch_format(self): # test all of the 'old' format diffs for completness - it should at least # be able to deal with it fixtures = ("diff_2", "diff_2f", "diff_f", "diff_i", "diff_mode_only", "diff_new_mode", "diff_numstat", "diff_p", "diff_rename", "diff_tree_numstat_root") for fixture_name in fixtures: diff_proc = StringProcessAdapter(fixture(fixture_name)) Diff._index_from_patch_format(self.rorepo, diff_proc.stdout)
def test_list_from_string_new_mode(self): output = StringProcessAdapter(fixture('diff_new_mode')) diffs = Diff._index_from_patch_format(self.rorepo, output.stdout) self._assert_diff_format(diffs) assert_equal(1, len(diffs)) assert_equal(10, len(diffs[0].diff.splitlines()))
def test_diff_unsafe_paths(self): output = StringProcessAdapter(fixture('diff_patch_unsafe_paths')) res = Diff._index_from_patch_format(None, output.stdout) # The "Additions" self.assertEqual(res[0].b_path, u'path/ starting with a space') self.assertEqual(res[1].b_path, u'path/"with-quotes"') self.assertEqual(res[2].b_path, u"path/'with-single-quotes'") self.assertEqual(res[3].b_path, u'path/ending in a space ') self.assertEqual(res[4].b_path, u'path/with\ttab') self.assertEqual(res[5].b_path, u'path/with\nnewline') self.assertEqual(res[6].b_path, u'path/with spaces') self.assertEqual(res[7].b_path, u'path/with-question-mark?') self.assertEqual(res[8].b_path, u'path/¯\\_(ツ)_|¯') self.assertEqual(res[9].b_path, u'path/💩.txt') self.assertEqual(res[9].b_rawpath, b'path/\xf0\x9f\x92\xa9.txt') self.assertEqual(res[10].b_path, u'path/�-invalid-unicode-path.txt') self.assertEqual(res[10].b_rawpath, b'path/\x80-invalid-unicode-path.txt') # The "Moves" # NOTE: The path prefixes a/ and b/ here are legit! We're actually # verifying that it's not "a/a/" that shows up, see the fixture data. self.assertEqual(res[11].a_path, u'a/with spaces') # NOTE: path a/ here legit! self.assertEqual(res[11].b_path, u'b/with some spaces') # NOTE: path b/ here legit! self.assertEqual(res[12].a_path, u'a/ending in a space ') self.assertEqual(res[12].b_path, u'b/ending with space ') self.assertEqual(res[13].a_path, u'a/"with-quotes"') self.assertEqual(res[13].b_path, u'b/"with even more quotes"')
def get_local_changes(self): repo = self._repo diff_str = repo.git.diff('HEAD', '--full-index') diff_str = StringIO(diff_str) diff_str.seek(0) diff = Diff._index_from_patch_format(repo, diff_str) root = self.path return [os.path.relpath(di.a_blob.abspath, root) for di in diff.iter_change_type('M')]
def test_diff_of_modified_files_not_added_to_the_index(self): output = StringProcessAdapter(fixture('diff_abbrev-40_full-index_M_raw_no-color')) diffs = Diff._index_from_raw_format(self.rorepo, output.stdout) assert len(diffs) == 1, 'one modification' assert len(list(diffs.iter_change_type('M'))) == 1, 'one modification' assert diffs[0].change_type == 'M' assert diffs[0].b_blob is None
def get_local_changes(self, repo=None): repo = self._get_repo(repo) diff_str = repo.git.diff('--full-index') patches = map(str.strip, diff_str.split('diff --git')) patches = ['\n'.join(p.split('\n')[2:]) for p in patches[1:]] diff_str = StringIO(diff_str) diff_str.seek(0) index = Diff._index_from_patch_format(repo, diff_str) return index, patches
def get_local_changes(self, repo=None): repo=self._get_repo(repo) diff_str=repo.git.diff( '--full-index') patches=map(str.strip, diff_str.split('diff --git')) patches=['\n'.join(p.split('\n')[2:]) for p in patches[1:]] diff_str=StringIO(diff_str) diff_str.seek(0) index=Diff._index_from_patch_format(repo, diff_str) return index, patches
def test_diff_index(self): output = StringProcessAdapter(fixture('diff_index_patch')) res = Diff._index_from_patch_format(None, output.stdout) assert len(res) == 6 for dr in res: assert dr.diff assert str(dr), "Diff to string conversion should be possible" # end for each diff dr = res[3] assert dr.diff.endswith(b"+Binary files a/rps and b/rps differ\n")
def test_diff_index(self): output = StringProcessAdapter(fixture('diff_index_patch')) res = Diff._index_from_patch_format(None, output) self.assertEqual(len(res), 6) for dr in res: self.assertTrue(dr.diff.startswith(b'@@'), dr) self.assertIsNotNone(str(dr), "Diff to string conversion should be possible") # end for each diff dr = res[3] assert dr.diff.endswith(b"+Binary files a/rps and b/rps differ\n")
def get_local_changes(self): repo = self._repo diff_str = repo.git.diff('HEAD', '--full-index') diff_str = StringIO(diff_str) diff_str.seek(0) diff = Diff._index_from_patch_format(repo, diff_str) root = self.path return [ os.path.relpath(di.a_blob.abspath, root) for di in diff.iter_change_type('M') ]
def test_diff_with_rename(self): output = StringProcessAdapter(fixture('diff_rename')) diffs = Diff._index_from_patch_format(self.rorepo, output.stdout) self._assert_diff_format(diffs) assert_equal(1, len(diffs)) diff = diffs[0] assert_true(diff.renamed) assert_equal(diff.rename_from, u'Jérôme') assert_equal(diff.rename_to, u'müller') assert isinstance(str(diff), str) output = StringProcessAdapter(fixture('diff_rename_raw')) diffs = Diff._index_from_raw_format(self.rorepo, output.stdout) assert len(diffs) == 1 diff = diffs[0] assert diff.renamed assert diff.rename_from == 'this' assert diff.rename_to == 'that' assert len(list(diffs.iter_change_type('R'))) == 1
def test_diff_with_copied_file(self): output = StringProcessAdapter(fixture('diff_copied_mode')) diffs = Diff._index_from_patch_format(self.rorepo, output) self._assert_diff_format(diffs) self.assertEqual(1, len(diffs)) diff = diffs[0] self.assertTrue(diff.copied_file) self.assertTrue(diff.a_path, 'test1.txt') self.assertTrue(diff.b_path, 'test2.txt') assert isinstance(str(diff), str) output = StringProcessAdapter(fixture('diff_copied_mode_raw')) diffs = Diff._index_from_raw_format(self.rorepo, output) self.assertEqual(len(diffs), 1) diff = diffs[0] self.assertEqual(diff.change_type, 'C') self.assertEqual(diff.score, 100) self.assertEqual(diff.a_path, 'test1.txt') self.assertEqual(diff.b_path, 'test2.txt') self.assertEqual(len(list(diffs.iter_change_type('C'))), 1)
def getChanges(self): parents = self.commit.parents if len(parents) > 0: for p in parents: pc = self.commit.repo.commit(p) return pc.diff(self.commit, create_patch=True) else: files = self.getTree().getAllFiles() diffs = [] for fl in files: diffs.append( Diff(self.commit.repo, None, fl.blob.path, None, fl.blob.hexsha, None, str(fl.blob.mode), True, False, None, None, '')) return diffs
def test_diff_index_raw_format(self): output = StringProcessAdapter(fixture('diff_index_raw')) res = Diff._index_from_raw_format(None, output) self.assertIsNotNone(res[0].deleted_file) self.assertIsNone(res[0].b_path,)
def test_diff_with_spaces(self): data = StringProcessAdapter(fixture('diff_file_with_spaces')) diff_index = Diff._index_from_patch_format(self.rorepo, data.stdout) assert diff_index[0].a_path is None, repr(diff_index[0].a_path) assert diff_index[0].b_path == u'file with spaces', repr( diff_index[0].b_path)
def test_diff_index_raw_format(self): output = StringProcessAdapter(fixture('diff_index_raw')) res = Diff._index_from_raw_format(None, output.stdout) assert res[0].deleted_file assert res[0].b_path is None
def test_diff_with_spaces(self): data = StringProcessAdapter(fixture('diff_file_with_spaces')) diff_index = Diff._index_from_patch_format(self.rorepo, data) self.assertIsNone(diff_index[0].a_path, repr(diff_index[0].a_path)) self.assertEqual(diff_index[0].b_path, 'file with spaces', repr(diff_index[0].b_path))
def test_diff_with_spaces(self): data = StringProcessAdapter(fixture('diff_file_with_spaces')) diff_index = Diff._index_from_patch_format(self.rorepo, data) self.assertIsNone(diff_index[0].a_path, repr(diff_index[0].a_path)) self.assertEqual(diff_index[0].b_path, u'file with spaces', repr(diff_index[0].b_path))
def test_diff_with_spaces(self): data = StringProcessAdapter(fixture('diff_file_with_spaces')) diff_index = Diff._index_from_patch_format(self.rorepo, data.stdout) assert diff_index[0].a_path is None, repr(diff_index[0].a_path) assert diff_index[0].b_path == u'file with spaces', repr(diff_index[0].b_path)
def test_diff_index_raw_format(self): output = StringProcessAdapter(fixture('diff_index_raw')) res = Diff._index_from_raw_format(None, output.stdout) assert res[0].deleted_file assert res[0].b_path == ''
def generate(): ''' Generate patch for specific branch. ''' repo = Repo('.') if repo.is_dirty: abort('Working tree is dirty. Working tree must be clean to perform this operation.') if confirm('Use master as base?'): for branch in repo.branches: if branch.name == 'master': master = branch else: branch, master = prompt_branch_select(repo, 'Select base branch') name, selected_branch = prompt_branch_select(repo) while exists(path(name)): name = prompt('Directory already exists: %s.\nWhat should I call this patch?'%path(name)) os.mkdir(path(name)) rawpath = join(path(name), 'raw.diff') local('git diff --binary %s %s > %s'%(selected_branch.commit, master.commit, rawpath)) with open(rawpath, 'r') as raw: changed = raw.read() config = RawConfigParser() config.add_section('target') config.add_section('iteration') config.set('target', 'branch', master.name) config.set('target', 'commit', master.commit) config.set('iteration', 'branch', selected_branch.name) config.set('iteration', 'commit', selected_branch.commit) print("Generated %s patch directory."%name) diffs = Diff.list_from_string(repo, changed) def filter(diff): def match(pattern): if fnmatch.fnmatch(diff.b_path, pattern): return pattern matches = [match for match in map(match, patterns) if not match is None] if not bool(matches): return diff ignore = os.path.join(os.getcwd(), '.diffignore') if os.path.exists(ignore): patterns = open(ignore, 'r').read().split('\n') diffs = [diff for diff in map(filter, diffs) if not diff is None] binary_ext = ['.png', '.gif', '.jpg', '.jpeg', '.flv', '.swf','.zip', '.gz', '.rar', '.fla'] # TODO: change this to use git generated binary marker binaries = [diff for diff in diffs if splitext(diff.b_path)[1] in binary_ext] text = set(diffs) - set(binaries) config.add_section('patch') config.set('patch', 'binary', int(len(binaries) > 0)) config.set('patch', 'text', int(len(text) > 0)) print 'Generated patch config file' if text: text_patch = join(path(name), 'text.patch') with open(text_patch, 'w') as pf: for diff in text: pf.write('%s\n\n'%diff.diff) print 'Generated %s text patch'%name else: text_patch = None if binaries: binary_list = join(path(name), 'binary.changes') binary_base = join(path(name), 'binaries') os.mkdir(binary_base) with open(binary_list, 'w') as pf: for binary in binaries: def mkdirs(path): path = join(binary_base, path) try: os.makedirs(path) except: pass return path def extractfile(commit, path): dirs, file = os.path.split(path) dirs = mkdirs('%s/%s'%(commit, dirs)) with settings(show('stdout'), warn_only = True): if commit == 'target': commit_id = master.commit else: commit_id = selected_branch.commit get_file(commit_id, path, join(binary_base, commit, path)) if binary.a_commit is None: # new file is being created extractfile('iteration', binary.b_path) elif binary.b_commit is None: # file is being removed extractfile('target', binary.a_path) else: # file is being updated or renamed extractfile('target', binary.a_path) extractfile('iteration', binary.b_path) if binary.new_file: pf.write('new %s\n'%binary.b_path) elif binary.deleted_file: pf.write('rm %s\n'%binary.b_path) elif binary.renamed: pf.write('mv %s %s\n'%(binary.a_path, binary.b_path)) else: pf.write('up %s\n'%binary.b_path) print 'Generated %s'%binary_list else: binary_list = None with open(join(path(name), 'patch.cfg'), 'wb') as configfile: config.write(configfile) os.mkdir(join(path(name), 'db')) source = join(path(name), 'db', 'source.sql') target = join(path(name), 'db', 'target.sql') get_file(selected_branch.commit, join('db', 'last'), source) get_file(master.commit, join('db', 'last'), target) with open(join(path(name), 'db', 'source.sql'), 'r') as sfp: with open(join(path(name), 'db', 'target.sql'), 'r') as tfp: dbchanged = md5sum(sfp) != md5sum(tfp) if dbchanged and confirm('Generate database patch?'): dbpatch.generate(join(path(name), 'db'), source, target) else: shutil.rmtree(join(path(name), 'db'), ignore_errors=True) config.set('patch', 'schema', 0) config.set('patch', 'data', 0) with open(join(path(name), 'patch.cfg'), 'wb') as configfile: config.write(configfile) return name, path(name)