mirror of
https://github.com/golang/go
synced 2024-11-22 00:04:41 -07:00
codereview: handle file patterns better
If a file pattern is given and matches files that look like they need to be hg added or hg removed, offer to do so. If a file pattern is given and matches files in another CL, warn. If a file pattern doesn't match anything, point that out. Vet first line of CL description. Fixes #972. R=adg, niemeyer CC=bradfitzgo, golang-dev https://golang.org/cl/4099042
This commit is contained in:
parent
72084068a4
commit
17fc373af7
@ -11,6 +11,7 @@ syntax:glob
|
|||||||
[568a].out
|
[568a].out
|
||||||
*~
|
*~
|
||||||
*.orig
|
*.orig
|
||||||
|
*.rej
|
||||||
*.exe
|
*.exe
|
||||||
.*.swp
|
.*.swp
|
||||||
core
|
core
|
||||||
|
@ -573,6 +573,16 @@ def CodeReviewDir(ui, repo):
|
|||||||
typecheck(dir, str)
|
typecheck(dir, str)
|
||||||
return dir
|
return dir
|
||||||
|
|
||||||
|
# Turn leading tabs into spaces, so that the common white space
|
||||||
|
# prefix doesn't get confused when people's editors write out
|
||||||
|
# some lines with spaces, some with tabs. Only a heuristic
|
||||||
|
# (some editors don't use 8 spaces either) but a useful one.
|
||||||
|
def TabsToSpaces(line):
|
||||||
|
i = 0
|
||||||
|
while i < len(line) and line[i] == '\t':
|
||||||
|
i += 1
|
||||||
|
return ' '*(8*i) + line[i:]
|
||||||
|
|
||||||
# Strip maximal common leading white space prefix from text
|
# Strip maximal common leading white space prefix from text
|
||||||
def StripCommon(text):
|
def StripCommon(text):
|
||||||
typecheck(text, str)
|
typecheck(text, str)
|
||||||
@ -581,6 +591,7 @@ def StripCommon(text):
|
|||||||
line = line.rstrip()
|
line = line.rstrip()
|
||||||
if line == '':
|
if line == '':
|
||||||
continue
|
continue
|
||||||
|
line = TabsToSpaces(line)
|
||||||
white = line[:len(line)-len(line.lstrip())]
|
white = line[:len(line)-len(line.lstrip())]
|
||||||
if ws == None:
|
if ws == None:
|
||||||
ws = white
|
ws = white
|
||||||
@ -597,6 +608,7 @@ def StripCommon(text):
|
|||||||
t = ''
|
t = ''
|
||||||
for line in text.split('\n'):
|
for line in text.split('\n'):
|
||||||
line = line.rstrip()
|
line = line.rstrip()
|
||||||
|
line = TabsToSpaces(line)
|
||||||
if line.startswith(ws):
|
if line.startswith(ws):
|
||||||
line = line[len(ws):]
|
line = line[len(ws):]
|
||||||
if line == '' and t == '':
|
if line == '' and t == '':
|
||||||
@ -638,28 +650,53 @@ def effective_revpair(repo):
|
|||||||
return cmdutil.revpair(repo, None)
|
return cmdutil.revpair(repo, None)
|
||||||
|
|
||||||
# Return list of changed files in repository that match pats.
|
# Return list of changed files in repository that match pats.
|
||||||
def ChangedFiles(ui, repo, pats, opts):
|
# Warn about patterns that did not match.
|
||||||
# Find list of files being operated on.
|
def matchpats(ui, repo, pats, opts):
|
||||||
matcher = cmdutil.match(repo, pats, opts)
|
matcher = cmdutil.match(repo, pats, opts)
|
||||||
node1, node2 = effective_revpair(repo)
|
node1, node2 = effective_revpair(repo)
|
||||||
modified, added, removed = repo.status(node1, node2, matcher)[:3]
|
modified, added, removed, deleted, unknown, ignored, clean = repo.status(node1, node2, matcher, ignored=True, clean=True, unknown=True)
|
||||||
|
return (modified, added, removed, deleted, unknown, ignored, clean)
|
||||||
|
|
||||||
|
# Return list of changed files in repository that match pats.
|
||||||
|
# The patterns came from the command line, so we warn
|
||||||
|
# if they have no effect or cannot be understood.
|
||||||
|
def ChangedFiles(ui, repo, pats, opts, taken=None):
|
||||||
|
taken = taken or {}
|
||||||
|
# Run each pattern separately so that we can warn about
|
||||||
|
# patterns that didn't do anything useful.
|
||||||
|
for p in pats:
|
||||||
|
modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, [p], opts)
|
||||||
|
redo = False
|
||||||
|
for f in unknown:
|
||||||
|
promptadd(ui, repo, f)
|
||||||
|
redo = True
|
||||||
|
for f in deleted:
|
||||||
|
promptremove(ui, repo, f)
|
||||||
|
redo = True
|
||||||
|
if redo:
|
||||||
|
modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, [p], opts)
|
||||||
|
for f in modified + added + removed:
|
||||||
|
if f in taken:
|
||||||
|
ui.warn("warning: %s already in CL %s\n" % (f, taken[f].name))
|
||||||
|
if not modified and not added and not removed:
|
||||||
|
ui.warn("warning: %s did not match any modified files\n" % (p,))
|
||||||
|
|
||||||
|
# Again, all at once (eliminates duplicates)
|
||||||
|
modified, added, removed = matchpats(ui, repo, pats, opts)[:3]
|
||||||
l = modified + added + removed
|
l = modified + added + removed
|
||||||
l.sort()
|
l.sort()
|
||||||
|
if taken:
|
||||||
|
l = Sub(l, taken.keys())
|
||||||
return l
|
return l
|
||||||
|
|
||||||
# Return list of changed files in repository that match pats and still exist.
|
# Return list of changed files in repository that match pats and still exist.
|
||||||
def ChangedExistingFiles(ui, repo, pats, opts):
|
def ChangedExistingFiles(ui, repo, pats, opts):
|
||||||
matcher = cmdutil.match(repo, pats, opts)
|
modified, added = matchpats(ui, repo, pats, opts)[:2]
|
||||||
node1, node2 = effective_revpair(repo)
|
|
||||||
modified, added, _ = repo.status(node1, node2, matcher)[:3]
|
|
||||||
l = modified + added
|
l = modified + added
|
||||||
l.sort()
|
l.sort()
|
||||||
return l
|
return l
|
||||||
|
|
||||||
# Return list of files claimed by existing CLs
|
# Return list of files claimed by existing CLs
|
||||||
def TakenFiles(ui, repo):
|
|
||||||
return Taken(ui, repo).keys()
|
|
||||||
|
|
||||||
def Taken(ui, repo):
|
def Taken(ui, repo):
|
||||||
all = LoadAllCL(ui, repo, web=False)
|
all = LoadAllCL(ui, repo, web=False)
|
||||||
taken = {}
|
taken = {}
|
||||||
@ -670,7 +707,7 @@ def Taken(ui, repo):
|
|||||||
|
|
||||||
# Return list of changed files that are not claimed by other CLs
|
# Return list of changed files that are not claimed by other CLs
|
||||||
def DefaultFiles(ui, repo, pats, opts):
|
def DefaultFiles(ui, repo, pats, opts):
|
||||||
return Sub(ChangedFiles(ui, repo, pats, opts), TakenFiles(ui, repo))
|
return ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
|
||||||
|
|
||||||
def Sub(l1, l2):
|
def Sub(l1, l2):
|
||||||
return [l for l in l1 if l not in l2]
|
return [l for l in l1 if l not in l2]
|
||||||
@ -701,6 +738,39 @@ def Incoming(ui, repo, opts):
|
|||||||
_, incoming, _ = findcommonincoming(repo, getremote(ui, repo, opts))
|
_, incoming, _ = findcommonincoming(repo, getremote(ui, repo, opts))
|
||||||
return incoming
|
return incoming
|
||||||
|
|
||||||
|
desc_re = '^(.+: |tag release\.|release\.|fix build)'
|
||||||
|
|
||||||
|
desc_msg = '''Your CL description appears not to use the standard form.
|
||||||
|
|
||||||
|
The first line of your change description is conventionally a
|
||||||
|
one-line summary of the change, prefixed by the primary affected package,
|
||||||
|
and is used as the subject for code review mail; the rest of the description
|
||||||
|
elaborates.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
encoding/rot13: new package
|
||||||
|
|
||||||
|
math: add IsInf, IsNaN
|
||||||
|
|
||||||
|
net: fix cname in LookupHost
|
||||||
|
|
||||||
|
unicode: update to Unicode 5.0.2
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def promptremove(ui, repo, f):
|
||||||
|
if promptyesno(ui, "hg remove %s (y/n)?" % (f,)):
|
||||||
|
if commands.remove(ui, repo, 'path:'+f) != 0:
|
||||||
|
ui.warn("error removing %s" % (f,))
|
||||||
|
|
||||||
|
def promptadd(ui, repo, f):
|
||||||
|
if promptyesno(ui, "hg add %s (y/n)?" % (f,)):
|
||||||
|
if commands.add(ui, repo, 'path:'+f) != 0:
|
||||||
|
ui.warn("error adding %s" % (f,))
|
||||||
|
|
||||||
def EditCL(ui, repo, cl):
|
def EditCL(ui, repo, cl):
|
||||||
set_status(None) # do not show status
|
set_status(None) # do not show status
|
||||||
s = cl.EditorText()
|
s = cl.EditorText()
|
||||||
@ -711,13 +781,54 @@ def EditCL(ui, repo, cl):
|
|||||||
if not promptyesno(ui, "error parsing change list: line %d: %s\nre-edit (y/n)?" % (line, err)):
|
if not promptyesno(ui, "error parsing change list: line %d: %s\nre-edit (y/n)?" % (line, err)):
|
||||||
return "change list not modified"
|
return "change list not modified"
|
||||||
continue
|
continue
|
||||||
cl.desc = clx.desc;
|
|
||||||
|
# Check description.
|
||||||
|
if clx.desc == '':
|
||||||
|
if promptyesno(ui, "change list should have a description\nre-edit (y/n)?"):
|
||||||
|
continue
|
||||||
|
elif not re.match(desc_re, clx.desc.split('\n')[0]):
|
||||||
|
if promptyesno(ui, desc_msg + "re-edit (y/n)?"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check file list for files that need to be hg added or hg removed
|
||||||
|
# or simply aren't understood.
|
||||||
|
pats = ['path:'+f for f in clx.files]
|
||||||
|
modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, pats, {})
|
||||||
|
files = []
|
||||||
|
for f in clx.files:
|
||||||
|
if f in modified or f in added or f in removed:
|
||||||
|
files.append(f)
|
||||||
|
continue
|
||||||
|
if f in deleted:
|
||||||
|
promptremove(ui, repo, f)
|
||||||
|
files.append(f)
|
||||||
|
continue
|
||||||
|
if f in unknown:
|
||||||
|
promptadd(ui, repo, f)
|
||||||
|
files.append(f)
|
||||||
|
continue
|
||||||
|
if f in ignored:
|
||||||
|
ui.warn("error: %s is excluded by .hgignore; omitting\n" % (f,))
|
||||||
|
continue
|
||||||
|
if f in clean:
|
||||||
|
ui.warn("warning: %s is listed in the CL but unchanged\n" % (f,))
|
||||||
|
files.append(f)
|
||||||
|
continue
|
||||||
|
p = repo.root + '/' + f
|
||||||
|
if os.path.isfile(p):
|
||||||
|
ui.warn("warning: %s is a file but not known to hg\n" % (f,))
|
||||||
|
files.append(f)
|
||||||
|
continue
|
||||||
|
if os.path.isdir(p):
|
||||||
|
ui.warn("error: %s is a directory, not a file; omitting\n" % (f,))
|
||||||
|
continue
|
||||||
|
ui.warn("error: %s does not exist; omitting\n" % (f,))
|
||||||
|
clx.files = files
|
||||||
|
|
||||||
|
cl.desc = clx.desc
|
||||||
cl.reviewer = clx.reviewer
|
cl.reviewer = clx.reviewer
|
||||||
cl.cc = clx.cc
|
cl.cc = clx.cc
|
||||||
cl.files = clx.files
|
cl.files = clx.files
|
||||||
if cl.desc == '':
|
|
||||||
if promptyesno(ui, "change list should have description\nre-edit (y/n)?"):
|
|
||||||
continue
|
|
||||||
break
|
break
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@ -736,7 +847,7 @@ def CommandLineCL(ui, repo, pats, opts, defaultcc=None):
|
|||||||
else:
|
else:
|
||||||
cl = CL("new")
|
cl = CL("new")
|
||||||
cl.local = True
|
cl.local = True
|
||||||
cl.files = Sub(ChangedFiles(ui, repo, pats, opts), TakenFiles(ui, repo))
|
cl.files = ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
|
||||||
if not cl.files:
|
if not cl.files:
|
||||||
return None, "no files changed"
|
return None, "no files changed"
|
||||||
if opts.get('reviewer'):
|
if opts.get('reviewer'):
|
||||||
@ -758,10 +869,11 @@ def CommandLineCL(ui, repo, pats, opts, defaultcc=None):
|
|||||||
# which expands the syntax @clnumber to mean the files
|
# which expands the syntax @clnumber to mean the files
|
||||||
# in that CL.
|
# in that CL.
|
||||||
original_match = None
|
original_match = None
|
||||||
def ReplacementForCmdutilMatch(repo, pats=[], opts={}, globbed=False, default='relpath'):
|
def ReplacementForCmdutilMatch(repo, pats=None, opts=None, globbed=False, default='relpath'):
|
||||||
taken = []
|
taken = []
|
||||||
files = []
|
files = []
|
||||||
pats = pats or []
|
pats = pats or []
|
||||||
|
opts = opts or {}
|
||||||
for p in pats:
|
for p in pats:
|
||||||
if p.startswith('@'):
|
if p.startswith('@'):
|
||||||
taken.append(p)
|
taken.append(p)
|
||||||
@ -895,9 +1007,7 @@ def change(ui, repo, *pats, **opts):
|
|||||||
name = "new"
|
name = "new"
|
||||||
cl = CL("new")
|
cl = CL("new")
|
||||||
dirty[cl] = True
|
dirty[cl] = True
|
||||||
files = ChangedFiles(ui, repo, pats, opts)
|
files = ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
|
||||||
taken = TakenFiles(ui, repo)
|
|
||||||
files = Sub(files, taken)
|
|
||||||
|
|
||||||
if opts["delete"] or opts["deletelocal"]:
|
if opts["delete"] or opts["deletelocal"]:
|
||||||
if opts["delete"] and opts["deletelocal"]:
|
if opts["delete"] and opts["deletelocal"]:
|
||||||
@ -2284,10 +2394,18 @@ def GetRpcServer(options):
|
|||||||
|
|
||||||
def GetUserCredentials():
|
def GetUserCredentials():
|
||||||
"""Prompts the user for a username and password."""
|
"""Prompts the user for a username and password."""
|
||||||
|
# Disable status prints so they don't obscure the password prompt.
|
||||||
|
global global_status
|
||||||
|
st = global_status
|
||||||
|
global_status = None
|
||||||
|
|
||||||
email = options.email
|
email = options.email
|
||||||
if email is None:
|
if email is None:
|
||||||
email = GetEmail("Email (login for uploading to %s)" % options.server)
|
email = GetEmail("Email (login for uploading to %s)" % options.server)
|
||||||
password = getpass.getpass("Password for %s: " % email)
|
password = getpass.getpass("Password for %s: " % email)
|
||||||
|
|
||||||
|
# Put status back.
|
||||||
|
global_status = st
|
||||||
return (email, password)
|
return (email, password)
|
||||||
|
|
||||||
# If this is the dev_appserver, use fake authentication.
|
# If this is the dev_appserver, use fake authentication.
|
||||||
|
Loading…
Reference in New Issue
Block a user