| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/python | |
| 2 # Copyright 2011 The Native Client Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can | |
| 4 # be found in the LICENSE file. | |
| 5 | |
| 6 import sys | |
| 7 import subprocess | |
| 8 import os | |
| 9 import tempfile | |
| 10 | |
| 11 def main(): | |
| 12 if len(sys.argv) not in (2,3): | |
| 13 print "Syntax: %s <repo_dir> [merge_rev]" % (sys.argv[0]) | |
| 14 print "If merge_rev is omitted, this script produces a" | |
| 15 print "diff-diff of the merge in the working directory." | |
| 16 sys.exit(0) | |
| 17 | |
| 18 repo_dir = sys.argv[1] | |
| 19 merge_rev = None | |
| 20 if len(sys.argv) == 3: | |
| 21 merge_rev = sys.argv[2] | |
| 22 | |
| 23 diffdiff(repo_dir, merge_rev) | |
| 24 return 0 | |
| 25 | |
| 26 def diffdiff(repo_dir, merge_rev = None): | |
| 27 """ Print the 'diff diff' of a merge. | |
| 28 | |
| 29 This is roughly equivalent to doing: | |
| 30 hg diff vendor:pnacl-sfi > tmp1 (before the merge) | |
| 31 hg diff vendor:pnacl-sfi > tmp2 (after the merge) | |
| 32 diff tmp1 tmp2 (this is the diff-diff) | |
| 33 | |
| 34 If merge_rev is None, prints the diff-diff of the | |
| 35 current merge in progress (in the working directory). | |
| 36 If merge_rev is specified, the diff-diff of an previous | |
| 37 (already committed) merge is generated.""" | |
| 38 | |
| 39 entries = GetMercurialLog(repo_dir) | |
| 40 | |
| 41 if merge_rev: | |
| 42 if merge_rev not in entries: | |
| 43 Fatal("Can't find revision '%s'" % merge_rev) | |
| 44 cur = entries[merge_rev] | |
| 45 if len(cur['parent']) != 2: | |
| 46 Fatal("Specified revision is not a merge!") | |
| 47 p1,p2 = cur['parent'] | |
| 48 else: | |
| 49 cur = None | |
| 50 p1,p2 = GetWorkingDirParents(entries, repo_dir) | |
| 51 | |
| 52 # Make p1 the "vendor" branch parent | |
| 53 if p2['branch'] == 'vendor': | |
| 54 p1,p2 = p2,p1 | |
| 55 | |
| 56 # Identify and verify the situation | |
| 57 if cur: | |
| 58 assert(cur['branch'] == 'pnacl-sfi') | |
| 59 assert(p1['branch'] == 'vendor') | |
| 60 assert(p2['branch'] == 'pnacl-sfi') | |
| 61 assert(len(p1['parent']) == 1) | |
| 62 prev_vendor = p1['parent'][0] | |
| 63 prev_merge = FindPreviousMerge(p2) | |
| 64 assert(prev_vendor['branch'] == 'vendor') | |
| 65 assert(prev_merge['branch'] == 'pnacl-sfi') | |
| 66 assert(prev_vendor in prev_merge['parent']) | |
| 67 | |
| 68 diff1 = MakeTemp(hg_diff(repo_dir, prev_vendor, p2)) | |
| 69 diff2 = MakeTemp(hg_diff(repo_dir, p1, cur)) | |
| 70 | |
| 71 out,err,ret = Run(None, ['diff',diff1,diff2], errexit=False) | |
| 72 if ret == 2: | |
| 73 Fatal("diff failed (%d): %s" % (ret, err)) | |
| 74 if ret == 0: | |
| 75 Fatal("diff output empty") | |
| 76 if ret != 1: | |
| 77 Fatal("Unexpected return value from diff (%d)" % ret) | |
| 78 sys.stdout.write(out) | |
| 79 | |
| 80 DeleteTemp(diff1) | |
| 81 DeleteTemp(diff2) | |
| 82 return 0 | |
| 83 | |
| 84 def hg_diff(dir, r1, r2): | |
| 85 assert(r1 is not None) | |
| 86 if r2 is None: | |
| 87 return Run(dir, "hg diff -r %s" % r1['changeset']) | |
| 88 else: | |
| 89 return Run(dir, "hg diff -r %s:%s" % (r1['changeset'], r2['changeset'])) | |
| 90 | |
| 91 def MakeTemp(contents): | |
| 92 """ Create a temporary file with specific contents, | |
| 93 and return the filename """ | |
| 94 | |
| 95 f,tempname = tempfile.mkstemp(prefix='diff-diff-tmp') | |
| 96 f = open(tempname, 'w') | |
| 97 f.write(contents) | |
| 98 f.close() | |
| 99 return tempname | |
| 100 | |
| 101 def DeleteTemp(tempname): | |
| 102 os.remove(tempname) | |
| 103 | |
| 104 def Fatal(msg): | |
| 105 print msg | |
| 106 sys.exit(1) | |
| 107 | |
| 108 def Run(dir, args, errexit=True): | |
| 109 if isinstance(args, str): | |
| 110 args = args.split(' ') | |
| 111 p = subprocess.Popen(args, cwd=dir, | |
| 112 stdout=subprocess.PIPE, | |
| 113 stderr=subprocess.PIPE) | |
| 114 out, err = p.communicate() | |
| 115 if not errexit: | |
| 116 return out, err, p.returncode | |
| 117 if p.returncode: | |
| 118 Fatal("%s (%d): (STDERR): %s" % (' '.join(args), p.returncode, err)) | |
| 119 if len(err) > 0: | |
| 120 Fatal("%s (STDERR): %s" % (' '.join(args), err)) | |
| 121 return out | |
| 122 | |
| 123 | |
| 124 def GetWorkingDirParents(entries, dir): | |
| 125 out = Run(dir, 'hg identify -i') | |
| 126 revs = out.split('+') | |
| 127 | |
| 128 # If this is a merge, hg identify -i returns: | |
| 129 # changeset1+changeset2+ | |
| 130 if len(revs) != 3: | |
| 131 Fatal("Working directory is not a merge. Please specify a revision!") | |
| 132 return entries[revs[0]], entries[revs[1]] | |
| 133 | |
| 134 def GetMercurialLog(dir): | |
| 135 """ Read the Mercurial log into a dictionary of log entries """ | |
| 136 entries = [] | |
| 137 out = Run(dir, 'hg log') | |
| 138 blocks = out.strip().split('\n\n') | |
| 139 for b in blocks: | |
| 140 entry = dict() | |
| 141 entry['parent'] = [] | |
| 142 for line in b.split('\n'): | |
| 143 sep = line.find(':') | |
| 144 tag = line[0:sep] | |
| 145 val = line[sep+1:].strip() | |
| 146 if tag == 'parent': | |
| 147 entry[tag] = entry[tag] + [ val ] | |
| 148 else: | |
| 149 entry[tag] = val | |
| 150 entries.append(entry) | |
| 151 | |
| 152 # Remove revision ids | |
| 153 for entry in entries: | |
| 154 entry['changeset'] = entry['changeset'].split(':')[1] | |
| 155 entry['parent'] = [ s.split(':')[1] for s in entry['parent'] ] | |
| 156 | |
| 157 # Add missing parent info for consecutive entries | |
| 158 last_entry = [] | |
| 159 for entry in reversed(entries): | |
| 160 if len(entry['parent']) == 0: | |
| 161 entry['parent'] = last_entry | |
| 162 last_entry = [ entry['changeset'] ] | |
| 163 | |
| 164 # Convert to a dictionary indexed by changeset | |
| 165 entries = dict([(entry['changeset'], entry) for entry in entries]) | |
| 166 | |
| 167 # Convert the parents to direct entry references | |
| 168 for cs, entry in entries.iteritems(): | |
| 169 entry['parent'] = [ entries[r] for r in entry['parent'] ] | |
| 170 | |
| 171 return entries | |
| 172 | |
| 173 def FindPreviousMerge(entry): | |
| 174 p = entry | |
| 175 while len(p['parent']) == 1: | |
| 176 p = p['parent'][0] | |
| 177 if len(p['parent']) != 2: | |
| 178 Fatal("Couldn't find previous merge for %s!" % entry['changeset']) | |
| 179 return p | |
| 180 | |
| 181 if __name__ == '__main__': | |
| 182 sys.exit(main()) | |
| OLD | NEW |