def diff_directories(self, old_dir, new_dir): """Return uniffied diff between two directories content. Function save two version's content of directory to temp files and treate them as casual diff between two files. """ old_content = self._directory_content(old_dir) new_content = self._directory_content(new_dir) old_tmp = make_tempfile(content=old_content) new_tmp = make_tempfile(content=new_content) diff_cmd = ["diff", "-uN", old_tmp, new_tmp] dl = execute(diff_cmd, extra_ignore_errors=(1,2), translate_newlines=False, split_lines=True) # Replacing temporary filenames to # real directory names and add ids if dl: dl[0] = dl[0].replace(old_tmp, old_dir) dl[1] = dl[1].replace(new_tmp, new_dir) old_oid = execute(["cleartool", "describe", "-fmt", "%On", old_dir]) new_oid = execute(["cleartool", "describe", "-fmt", "%On", new_dir]) dl.insert(2, "==== %s %s ====\n" % (old_oid, new_oid)) return dl
def _changenum_diff(self, changenum): """ Process a diff for a particular change number. This handles both pending and submitted changelists. See _path_diff for the alternate version that does diffs of depot paths. """ # TODO: It might be a good idea to enhance PerforceDiffParser to # understand that newFile could include a revision tag for post-submit # reviewing. cl_is_pending = False logging.info("Generating diff for changenum %s" % changenum) description = [] if changenum == "default": cl_is_pending = True else: describeCmd = ["p4"] if self._options.p4_passwd: describeCmd.append("-P") describeCmd.append(self._options.p4_passwd) describeCmd = describeCmd + ["describe", "-s", changenum] description = execute(describeCmd, split_lines=True) if re.search("no such changelist", description[0]): die("CLN %s does not exist." % changenum) # Some P4 wrappers are addding an extra line before the description if '*pending*' in description[0] or '*pending*' in description[1]: cl_is_pending = True v = self.p4d_version if cl_is_pending and (v[0] < 2002 or (v[0] == "2002" and v[1] < 2) or changenum == "default"): # Pre-2002.2 doesn't give file list in pending changelists, # or we don't have a description for a default changeset, # so we have to get it a different way. info = execute(["p4", "opened", "-c", str(changenum)], split_lines=True) if len(info) == 1 and info[0].startswith("File(s) not opened on this client."): die("Couldn't find any affected files for this change.") for line in info: data = line.split(" ") description.append("... %s %s" % (data[0], data[2])) else: # Get the file list for line_num, line in enumerate(description): if 'Affected files ...' in line: break else: # Got to the end of all the description lines and didn't find # what we were looking for. die("Couldn't find any affected files for this change.") description = description[line_num+2:] diff_lines = [] empty_filename = make_tempfile() tmp_diff_from_filename = make_tempfile() tmp_diff_to_filename = make_tempfile() for line in description: line = line.strip() if not line: continue m = re.search(r'\.\.\. ([^#]+)#(\d+) ' r'(add|edit|delete|integrate|branch|move/add' r'|move/delete)', line) if not m: die("Unsupported line from p4 opened: %s" % line) depot_path = m.group(1) base_revision = int(m.group(2)) if not cl_is_pending: # If the changelist is pending our base revision is the one that's # currently in the depot. If we're not pending the base revision is # actually the revision prior to this one base_revision -= 1 changetype = m.group(3) logging.debug('Processing %s of %s' % (changetype, depot_path)) old_file = new_file = empty_filename old_depot_path = new_depot_path = None changetype_short = None if changetype in ['edit', 'integrate']: # A big assumption new_revision = base_revision + 1 # We have an old file, get p4 to take this old version from the # depot and put it into a plain old temp file for us old_depot_path = "%s#%s" % (depot_path, base_revision) self._write_file(old_depot_path, tmp_diff_from_filename) old_file = tmp_diff_from_filename # Also print out the new file into a tmpfile if cl_is_pending: new_file = self._depot_to_local(depot_path) else: new_depot_path = "%s#%s" %(depot_path, new_revision) self._write_file(new_depot_path, tmp_diff_to_filename) new_file = tmp_diff_to_filename changetype_short = "M" elif changetype in ['add', 'branch', 'move/add']: # We have a new file, get p4 to put this new file into a pretty # temp file for us. No old file to worry about here. if cl_is_pending: new_file = self._depot_to_local(depot_path) else: self._write_file(depot_path, tmp_diff_to_filename) new_file = tmp_diff_to_filename changetype_short = "A" elif changetype in ['delete', 'move/delete']: # We've deleted a file, get p4 to put the deleted file into a temp # file for us. The new file remains the empty file. old_depot_path = "%s#%s" % (depot_path, base_revision) self._write_file(old_depot_path, tmp_diff_from_filename) old_file = tmp_diff_from_filename changetype_short = "D" else: die("Unknown change type '%s' for %s" % (changetype, depot_path)) dl = self._do_diff(old_file, new_file, depot_path, base_revision, changetype_short) diff_lines += dl os.unlink(empty_filename) os.unlink(tmp_diff_from_filename) os.unlink(tmp_diff_to_filename) return (''.join(diff_lines), None)
def changenum_diff(self, changenum): logging.debug("changenum_diff: %s" % (changenum)) files = execute(["cm", "log", "cs:" + changenum, "--csFormat={items}", "--itemFormat={shortstatus} {path} " "rev:revid:{revid} rev:revid:{parentrevid} " "src:{srccmpath} rev:revid:{srcdirrevid} " "dst:{dstcmpath} rev:revid:{dstdirrevid}{newline}"], split_lines = True) logging.debug("got files: %s" % (files)) # Diff generation based on perforce client diff_lines = [] empty_filename = make_tempfile() tmp_diff_from_filename = make_tempfile() tmp_diff_to_filename = make_tempfile() for f in files: f = f.strip() if not f: continue m = re.search(r'(?P<type>[ACIMR]) (?P<file>.*) ' r'(?P<revspec>rev:revid:[-\d]+) ' r'(?P<parentrevspec>rev:revid:[-\d]+) ' r'src:(?P<srcpath>.*) ' r'(?P<srcrevspec>rev:revid:[-\d]+) ' r'dst:(?P<dstpath>.*) ' r'(?P<dstrevspec>rev:revid:[-\d]+)$', f) if not m: die("Could not parse 'cm log' response: %s" % f) changetype = m.group("type") filename = m.group("file") if changetype == "M": # Handle moved files as a delete followed by an add. # Clunky, but at least it works oldfilename = m.group("srcpath") oldspec = m.group("srcrevspec") newfilename = m.group("dstpath") newspec = m.group("dstrevspec") self.write_file(oldfilename, oldspec, tmp_diff_from_filename) dl = self.diff_files(tmp_diff_from_filename, empty_filename, oldfilename, "rev:revid:-1", oldspec, changetype) diff_lines += dl self.write_file(newfilename, newspec, tmp_diff_to_filename) dl = self.diff_files(empty_filename, tmp_diff_to_filename, newfilename, newspec, "rev:revid:-1", changetype) diff_lines += dl else: newrevspec = m.group("revspec") parentrevspec = m.group("parentrevspec") logging.debug("Type %s File %s Old %s New %s" % (changetype, filename, parentrevspec, newrevspec)) old_file = new_file = empty_filename if (changetype in ['A'] or (changetype in ['C', 'I'] and parentrevspec == "rev:revid:-1")): # File was Added, or a Change or Merge (type I) and there # is no parent revision self.write_file(filename, newrevspec, tmp_diff_to_filename) new_file = tmp_diff_to_filename elif changetype in ['C', 'I']: # File was Changed or Merged (type I) self.write_file(filename, parentrevspec, tmp_diff_from_filename) old_file = tmp_diff_from_filename self.write_file(filename, newrevspec, tmp_diff_to_filename) new_file = tmp_diff_to_filename elif changetype in ['R']: # File was Removed self.write_file(filename, parentrevspec, tmp_diff_from_filename) old_file = tmp_diff_from_filename else: die("Don't know how to handle change type '%s' for %s" % (changetype, filename)) dl = self.diff_files(old_file, new_file, filename, newrevspec, parentrevspec, changetype) diff_lines += dl os.unlink(empty_filename) os.unlink(tmp_diff_from_filename) os.unlink(tmp_diff_to_filename) return ''.join(diff_lines)
def _path_diff(self, args): """ Process a path-style diff. See _changenum_diff for the alternate version that handles specific change numbers. Multiple paths may be specified in `args`. The path styles supported are: //path/to/file Upload file as a "new" file. //path/to/dir/... Upload all files as "new" files. //path/to/file[@#]rev Upload file from that rev as a "new" file. //path/to/file[@#]rev,[@#]rev Upload a diff between revs. //path/to/dir/...[@#]rev,[@#]rev Upload a diff of all files between revs in that directory. """ r_revision_range = re.compile(r'^(?P<path>//[^@#]+)' + r'(?P<revision1>[#@][^,]+)?' + r'(?P<revision2>,[#@][^,]+)?$') empty_filename = make_tempfile() tmp_diff_from_filename = make_tempfile() tmp_diff_to_filename = make_tempfile() diff_lines = [] for path in args: m = r_revision_range.match(path) if not m: die('Path %r does not match a valid Perforce path.' % (path,)) revision1 = m.group('revision1') revision2 = m.group('revision2') first_rev_path = m.group('path') if revision1: first_rev_path += revision1 records = self._run_p4(['files', first_rev_path]) # Make a map for convenience. files = {} # Records are: # 'rev': '1' # 'func': '...' # 'time': '1214418871' # 'action': 'edit' # 'type': 'ktext' # 'depotFile': '...' # 'change': '123456' for record in records: if record['action'] not in ('delete', 'move/delete'): if revision2: files[record['depotFile']] = [record, None] else: files[record['depotFile']] = [None, record] if revision2: # [1:] to skip the comma. second_rev_path = m.group('path') + revision2[1:] records = self._run_p4(['files', second_rev_path]) for record in records: if record['action'] not in ('delete', 'move/delete'): try: m = files[record['depotFile']] m[1] = record except KeyError: files[record['depotFile']] = [None, record] old_file = new_file = empty_filename changetype_short = None for depot_path, (first_record, second_record) in files.items(): old_file = new_file = empty_filename if first_record is None: self._write_file(depot_path + '#' + second_record['rev'], tmp_diff_to_filename) new_file = tmp_diff_to_filename changetype_short = 'A' base_revision = 0 elif second_record is None: self._write_file(depot_path + '#' + first_record['rev'], tmp_diff_from_filename) old_file = tmp_diff_from_filename changetype_short = 'D' base_revision = int(first_record['rev']) elif first_record['rev'] == second_record['rev']: # We when we know the revisions are the same, we don't need # to do any diffing. This speeds up large revision-range # diffs quite a bit. continue else: self._write_file(depot_path + '#' + first_record['rev'], tmp_diff_from_filename) self._write_file(depot_path + '#' + second_record['rev'], tmp_diff_to_filename) new_file = tmp_diff_to_filename old_file = tmp_diff_from_filename changetype_short = 'M' base_revision = int(first_record['rev']) dl = self._do_diff(old_file, new_file, depot_path, base_revision, changetype_short, ignore_unmodified=True) diff_lines += dl os.unlink(empty_filename) os.unlink(tmp_diff_from_filename) os.unlink(tmp_diff_to_filename) return (''.join(diff_lines), None)
def branch_diff(self, args): logging.debug("branch diff: %s" % (args)) if len(args) > 0: branch = args[0] else: branch = args if not branch.startswith("br:"): return None if not self._options.branch: self._options.branch = branch files = execute(["cm", "fbc", branch, "--format={3} {4}"], split_lines = True) logging.debug("got files: %s" % (files)) diff_lines = [] empty_filename = make_tempfile() tmp_diff_from_filename = make_tempfile() tmp_diff_to_filename = make_tempfile() for f in files: f = f.strip() if not f: continue m = re.search(r'^(?P<branch>.*)#(?P<revno>\d+) (?P<file>.*)$', f) if not m: die("Could not parse 'cm fbc' response: %s" % f) filename = m.group("file") branch = m.group("branch") revno = m.group("revno") # Get the base revision with a cm find basefiles = execute(["cm", "find", "revs", "where", "item='" + filename + "'", "and", "branch='" + branch + "'", "and", "revno=" + revno, "--format={item} rev:revid:{id} " "rev:revid:{parent}", "--nototal"], split_lines = True) # We only care about the first line m = re.search(r'^(?P<filename>.*) ' r'(?P<revspec>rev:revid:[-\d]+) ' r'(?P<parentrevspec>rev:revid:[-\d]+)$', basefiles[0]) basefilename = m.group("filename") newrevspec = m.group("revspec") parentrevspec = m.group("parentrevspec") # Cope with adds/removes changetype = "C" if parentrevspec == "rev:revid:-1": changetype = "A" elif newrevspec == "rev:revid:-1": changetype = "R" logging.debug("Type %s File %s Old %s New %s" % (changetype, basefilename, parentrevspec, newrevspec)) old_file = new_file = empty_filename if changetype == "A": # File Added self.write_file(basefilename, newrevspec, tmp_diff_to_filename) new_file = tmp_diff_to_filename elif changetype == "R": # File Removed self.write_file(basefilename, parentrevspec, tmp_diff_from_filename) old_file = tmp_diff_from_filename else: self.write_file(basefilename, parentrevspec, tmp_diff_from_filename) old_file = tmp_diff_from_filename self.write_file(basefilename, newrevspec, tmp_diff_to_filename) new_file = tmp_diff_to_filename dl = self.diff_files(old_file, new_file, basefilename, newrevspec, parentrevspec, changetype) diff_lines += dl os.unlink(empty_filename) os.unlink(tmp_diff_from_filename) os.unlink(tmp_diff_to_filename) return ''.join(diff_lines)