summaryrefslogtreecommitdiff
path: root/lib/codereview/codereview.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/codereview/codereview.py')
-rw-r--r--lib/codereview/codereview.py141
1 files changed, 116 insertions, 25 deletions
diff --git a/lib/codereview/codereview.py b/lib/codereview/codereview.py
index fa703c711..766e827fc 100644
--- a/lib/codereview/codereview.py
+++ b/lib/codereview/codereview.py
@@ -779,7 +779,7 @@ def Incoming(ui, repo, opts):
_, incoming, _ = findcommonincoming(repo, getremote(ui, repo, opts))
return incoming
-desc_re = '^(.+: |tag release\.|release\.|fix build)'
+desc_re = '^(.+: |(tag )?(release|weekly)\.|fix build)'
desc_msg = '''Your CL description appears not to use the standard form.
@@ -1135,12 +1135,27 @@ def clpatch(ui, repo, clname, **opts):
if missing_codereview:
return missing_codereview
- cl, patch, err = DownloadCL(ui, repo, clname)
+ cl, vers, patch, err = DownloadCL(ui, repo, clname)
if err != "":
return err
if patch == emptydiff:
return "codereview issue %s has no diff" % clname
+ if not repo[vers]:
+ return "codereview issue %s is newer than the current repository; hg sync" % clname
+
+ # find current hg version (hg identify)
+ ctx = repo[None]
+ parents = ctx.parents()
+ id = '+'.join([short(p.node()) for p in parents])
+
+ # if version does not match the patch version,
+ # try to update the patch line numbers.
+ if id != vers:
+ patch, err = portPatch(repo, patch, vers, id)
+ if err != "":
+ return "codereview issue %s is out of date: %s" % (clname, err)
+
argv = ["hgpatch"]
if opts["no_incoming"]:
argv += ["--checksync=false"]
@@ -1163,6 +1178,67 @@ def clpatch(ui, repo, clname, **opts):
cl.Flush(ui, repo)
ui.write(cl.PendingText() + "\n")
+# portPatch rewrites patch from being a patch against
+# oldver to being a patch against newver.
+def portPatch(repo, patch, oldver, newver):
+ lines = patch.splitlines(True) # True = keep \n
+ delta = None
+ for i in range(len(lines)):
+ line = lines[i]
+ if line.startswith('--- a/'):
+ file = line[6:-1]
+ delta = fileDeltas(repo, file, oldver, newver)
+ if not delta or not line.startswith('@@ '):
+ continue
+ # @@ -x,y +z,w @@ means the patch chunk replaces
+ # the original file's line numbers x up to x+y with the
+ # line numbers z up to z+w in the new file.
+ # Find the delta from x in the original to the same
+ # line in the current version and add that delta to both
+ # x and z.
+ m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
+ if not m:
+ return None, "error parsing patch line numbers"
+ n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
+ d, err = lineDelta(delta, n1, len1)
+ if err != "":
+ return "", err
+ n1 += d
+ n2 += d
+ lines[i] = "@@ -%d,%d +%d,%d @@\n" % (n1, len1, n2, len2)
+
+ newpatch = ''.join(lines)
+ return newpatch, ""
+
+# fileDelta returns the line number deltas for the given file's
+# changes from oldver to newver.
+# The deltas are a list of (n, len, newdelta) triples that say
+# lines [n, n+len) were modified, and after that range the
+# line numbers are +newdelta from what they were before.
+def fileDeltas(repo, file, oldver, newver):
+ cmd = ["hg", "diff", "--git", "-r", oldver + ":" + newver, "path:" + file]
+ data = RunShell(cmd, silent_ok=True)
+ deltas = []
+ for line in data.splitlines():
+ m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
+ if not m:
+ continue
+ n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
+ deltas.append((n1, len1, n2+len2-(n1+len1)))
+ return deltas
+
+# lineDelta finds the appropriate line number delta to apply to the lines [n, n+len).
+# It returns an error if those lines were rewritten by the patch.
+def lineDelta(deltas, n, len):
+ d = 0
+ for (old, oldlen, newdelta) in deltas:
+ if old >= n+len:
+ break
+ if old+len > n:
+ return 0, "patch and recent changes conflict"
+ d = newdelta
+ return d, ""
+
def download(ui, repo, clname, **opts):
"""download a change from the code review server
@@ -1172,7 +1248,7 @@ def download(ui, repo, clname, **opts):
if missing_codereview:
return missing_codereview
- cl, patch, err = DownloadCL(ui, repo, clname)
+ cl, vers, patch, err = DownloadCL(ui, repo, clname)
if err != "":
return err
ui.write(cl.EditorText() + "\n")
@@ -1333,16 +1409,16 @@ def reposetup(ui, repo):
def CheckContributor(ui, repo, user=None):
set_status("checking CONTRIBUTORS file")
- if not user:
- user = ui.config("ui", "username")
- if not user:
- raise util.Abort("[ui] username is not configured in .hgrc")
_, userline = FindContributor(ui, repo, user, warn=False)
if not userline:
raise util.Abort("cannot find %s in CONTRIBUTORS" % (user,))
return userline
-def FindContributor(ui, repo, user, warn=True):
+def FindContributor(ui, repo, user=None, warn=True):
+ if not user:
+ user = ui.config("ui", "username")
+ if not user:
+ raise util.Abort("[ui] username is not configured in .hgrc")
user = user.lower()
m = re.match(r".*<(.*)>", user)
if m:
@@ -1463,7 +1539,7 @@ def submit(ui, repo, *pats, **opts):
# we're committed. upload final patch, close review, add commit message
changeURL = short(node)
url = other.url()
- m = re.match("^https?://([^@/]+@)?([^.]+)\.googlecode\.com/hg/", url)
+ m = re.match("^https?://([^@/]+@)?([^.]+)\.googlecode\.com/hg/?", url)
if m:
changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(2), changeURL)
else:
@@ -1558,7 +1634,10 @@ def sync_changes(ui, repo):
cl.files = Sub(cl.files, extra)
cl.Flush(ui, repo)
if not cl.files:
- ui.warn("CL %s has no files; suggest hg change -d %s\n" % (cl.name, cl.name))
+ if not cl.copied_from:
+ ui.warn("CL %s has no files; delete with hg change -d %s\n" % (cl.name, cl.name))
+ else:
+ ui.warn("CL %s has no files; delete locally with hg change -D %s\n" % (cl.name, cl.name))
return
def upload(ui, repo, name, **opts):
@@ -1738,25 +1817,35 @@ def DownloadCL(ui, repo, clname):
set_status("downloading CL " + clname)
cl, err = LoadCL(ui, repo, clname)
if err != "":
- return None, None, "error loading CL %s: %s" % (clname, ExceptionDetail())
+ return None, None, None, "error loading CL %s: %s" % (clname, err)
# Grab RSS feed to learn about CL
feed = XMLGet(ui, "/rss/issue/" + clname)
if feed is None:
- return None, None, "cannot download CL"
+ return None, None, None, "cannot download CL"
# Find most recent diff
diff = None
prefix = 'http://' + server + '/'
- for link in feed.findall("{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}link"):
- if link.get('rel') != 'alternate':
- continue
- text = link.get('href')
- if not text.startswith(prefix) or not text.endswith('.diff'):
+ vers = ""
+ for entry in feed.findall("{http://www.w3.org/2005/Atom}entry"):
+ thisVers = ""
+ for title in entry.findall("{http://www.w3.org/2005/Atom}title"):
+ m = re.search('diff -r ([0-9a-f]+) ', title.text)
+ if m:
+ thisVers = m.group(1)
+ if thisVers == "":
continue
- diff = text[len(prefix)-1:]
+ for link in entry.findall("{http://www.w3.org/2005/Atom}link"):
+ if link.get('rel') != 'alternate':
+ continue
+ text = link.get('href')
+ if not text.startswith(prefix) or not text.endswith('.diff'):
+ continue
+ diff = text[len(prefix)-1:]
+ vers = thisVers
if diff is None:
- return None, None, "CL has no diff"
+ return None, None, None, "CL has no diff"
diffdata = MySend(diff, force_auth=False)
# Find author - first entry will be author who created CL.
@@ -1765,7 +1854,7 @@ def DownloadCL(ui, repo, clname):
nick = author.text.strip()
break
if not nick:
- return None, None, "CL has no author"
+ return None, None, None, "CL has no author"
# The author is just a nickname: get the real email address.
try:
@@ -1775,7 +1864,7 @@ def DownloadCL(ui, repo, clname):
except:
ui.warn("error looking up %s: %s\n" % (nick, ExceptionDetail()))
cl.copied_from = nick+"@needtofix"
- return cl, diffdata, ""
+ return cl, vers, diffdata, ""
match = re.match(r"<b>(.*) \((.*)\)</b>", data)
if not match:
return None, None, "error looking up %s: cannot parse result %s" % (nick, repr(data))
@@ -1784,10 +1873,12 @@ def DownloadCL(ui, repo, clname):
email = match.group(1)
# Print warning if email is not in CONTRIBUTORS file.
- FindContributor(ui, repo, email)
- cl.copied_from = email
+ him = FindContributor(ui, repo, email)
+ me = FindContributor(ui, repo, None)
+ if him != me:
+ cl.copied_from = email
- return cl, diffdata, ""
+ return cl, vers, diffdata, ""
def MySend(request_path, payload=None,
content_type="application/octet-stream",
@@ -1797,7 +1888,7 @@ def MySend(request_path, payload=None,
try:
return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
except Exception, e:
- if type(e) == urllib2.HTTPError and e.code == 403: # forbidden, it happens
+ if type(e) != urllib2.HTTPError or e.code != 500: # only retry on HTTP 500 error
raise
print >>sys.stderr, "Loading "+request_path+": "+ExceptionDetail()+"; trying again in 2 seconds."
time.sleep(2)