1N/A#
1N/A# This program is free software; you can redistribute it and/or modify
1N/A# it under the terms of the GNU General Public License version 2
1N/A# as published by the Free Software Foundation.
1N/A#
1N/A# This program is distributed in the hope that it will be useful,
1N/A# but WITHOUT ANY WARRANTY; without even the implied warranty of
1N/A# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1N/A# GNU General Public License for more details.
1N/A#
1N/A# You should have received a copy of the GNU General Public License
1N/A# along with this program; if not, write to the Free Software
1N/A# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
1N/A#
1N/A
1N/A#
2N/A# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
1N/A# Copyright 2008, 2010 Richard Lowe
1N/A#
1N/A
1N/A'''OpenSolaris extensions to Mercurial
1N/A
1N/A This extension contains a number of commands to help you work with
1N/Athe OpenSolaris consolidations. It provides commands to check your
1N/Achanges against the various style rules used for OpenSolaris, to
1N/Abackup and restore your changes, to generate code reviews, and to
1N/Aprepare your changes for integration.
1N/A
1N/A
1N/AThe Parent
1N/A
1N/A To provide a uniform notion of parent workspace regardless of
1N/Afilesystem-based access, Cadmium uses the highest numbered changeset
1N/Aon the current branch that is also in the parent workspace to
1N/Arepresent the parent workspace.
1N/A
1N/A
1N/AThe Active List
1N/A
1N/A Many Cadmium commands operate on the active list, the set of
1N/Afiles ('active files') you have changed in this workspace in relation
1N/Ato its parent workspace, and the metadata (commentary, primarily)
1N/Aassociated with those changes.
1N/A
1N/A
1N/ANOT Files
1N/A
1N/A Many of Cadmium's commands to check that your work obeys the
1N/Avarious stylistic rules of the OpenSolaris consolidations (such as
1N/Athose run by 'hg nits') allow files to be excluded from this checking
1N/Aby means of NOT files kept in the .hg/cdm/ directory of the Mercurial
1N/Arepository for one-time exceptions, and in the exception_lists
1N/Adirectory at the repository root for permanent exceptions. (For ON,
1N/Athese would mean one in $CODEMGR_WS and one in
1N/A$CODEMGR_WS/usr/closed).
1N/A
1N/A These files are in the same format as the Mercurial hgignore
1N/Afile, a description of which is available in the hgignore(5) manual
1N/Apage.
1N/A
1N/A
1N/ACommon Tasks
1N/A
1N/A - Show diffs relative to parent workspace - pdiffs
1N/A - Check source style rules - nits
1N/A - Run pre-integration checks - pbchk
1N/A - Collapse all your changes into a single changeset - recommit
1N/A'''
1N/A
1N/Aimport atexit, os, re, sys, stat, termios
1N/A
1N/A
1N/A#
1N/A# Adjust the load path based on the location of cdm.py and the version
1N/A# of python into which it is being loaded. This assumes the normal
1N/A# onbld directory structure, where cdm.py is in
1N/A# lib/python(version)?/onbld/hgext/. If that changes so too must
1N/A# this.
1N/A#
1N/A# This and the case below are not equivalent. In this case we may be
1N/A# loading a cdm.py in python2.X/ via the lib/python/ symlink but need
1N/A# python2.Y in sys.path.
1N/A#
1N/Asys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "..", "..",
1N/A "python%d.%d" % sys.version_info[:2]))
1N/A
1N/A#
1N/A# Add the relative path from cdm.py to usr/src/tools to the load path,
1N/A# such that a cdm.py loaded from the source tree uses the modules also
1N/A# within the source tree.
1N/A#
1N/Asys.path.insert(2, os.path.join(os.path.dirname(__file__), "..", ".."))
1N/A
1N/Afrom onbld.Scm import Version
1N/Afrom mercurial import util
1N/A
1N/Atry:
1N/A Version.check_version()
1N/Aexcept Version.VersionMismatch, badversion:
1N/A raise util.Abort("Version Mismatch:\n %s\n" % badversion)
1N/A
1N/Aimport ConfigParser
2N/Afrom mercurial import cmdutil, ignore, node, patch
2N/Aif Version.at_least("1.9"):
2N/A from mercurial import scmutil
1N/A
1N/Afrom onbld.Scm.WorkSpace import ActiveEntry, WorkSpace
1N/Afrom onbld.Scm.Backup import CdmBackup
1N/Afrom onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk
1N/Afrom onbld.Checks import JStyle, Keywords, Mapfile, Rti, onSWAN
1N/A
1N/A
1N/Adef yes_no(ui, msg, default):
1N/A if default:
1N/A prompt = ' [Y/n]:'
1N/A defanswer = 'y'
1N/A else:
1N/A prompt = ' [y/N]:'
1N/A defanswer = 'n'
1N/A
1N/A if Version.at_least("1.4"):
1N/A index = ui.promptchoice(msg + prompt, ['&yes', '&no'],
1N/A default=['y', 'n'].index(defanswer))
1N/A resp = ('y', 'n')[index]
1N/A elif Version.at_least("1.3.0"):
1N/A resp = ui.prompt(msg + prompt, ['&yes', '&no'], default=defanswer)
1N/A else:
1N/A resp = ui.prompt(msg + prompt, r'([Yy(es)?|[Nn]o?)?',
1N/A default=defanswer)
1N/A
1N/A return resp[0] in ('Y', 'y')
1N/A
1N/A
1N/Adef buildfilelist(ws, parent, files):
1N/A '''Build a list of files in which we're interested.
1N/A
1N/A If no files are specified take files from the active list relative
1N/A to 'parent'.
1N/A
1N/A Return a list of 2-tuples the first element being a path relative
1N/A to the current directory and the second an entry from the active
1N/A list, or None if an explicit file list was given.'''
1N/A
1N/A if files:
1N/A return [(path, None) for path in sorted(files)]
1N/A else:
1N/A active = ws.active(parent=parent)
1N/A return [(ws.filepath(e.name), e) for e in sorted(active)]
1N/Abuildfilelist = util.cachefunc(buildfilelist)
1N/A
1N/A
1N/Adef not_check(repo, cmd):
1N/A '''return a function which returns boolean indicating whether a file
1N/A should be skipped for CMD.'''
1N/A
1N/A #
1N/A # The ignore routines need a canonical path to the file (relative to the
1N/A # repo root), whereas the check commands get paths relative to the cwd.
1N/A #
1N/A # Wrap our argument such that the path is canonified before it is checked.
1N/A #
1N/A def canonified_check(ignfunc):
1N/A def f(path):
2N/A if Version.at_least("1.9"):
2N/A cpath = scmutil.canonpath(repo.root, repo.getcwd(), path)
2N/A else:
2N/A cpath = util.canonpath(repo.root, repo.getcwd(), path)
1N/A return ignfunc(cpath)
1N/A return f
1N/A
1N/A ignorefiles = []
1N/A
1N/A for f in [repo.join('cdm/%s.NOT' % cmd),
1N/A repo.wjoin('exception_lists/%s' % cmd)]:
1N/A if os.path.exists(f):
1N/A ignorefiles.append(f)
1N/A
1N/A if ignorefiles:
1N/A ign = ignore.ignore(repo.root, ignorefiles, repo.ui.warn)
1N/A return canonified_check(ign)
1N/A else:
1N/A return util.never
1N/A
1N/A
1N/Adef abort_if_dirty(ws):
1N/A '''Abort if the workspace has uncommitted changes, merges,
1N/A branches, or has Mq patches applied'''
1N/A
1N/A if ws.modified():
1N/A raise util.Abort('workspace has uncommitted changes')
1N/A if ws.merged():
1N/A raise util.Abort('workspace contains uncommitted merge')
1N/A if ws.branched():
1N/A raise util.Abort('workspace contains uncommitted branch')
1N/A if ws.mq_applied():
1N/A raise util.Abort('workspace has Mq patches applied')
1N/A
1N/A
1N/A#
1N/A# Adding a reference to WorkSpace from a repo causes a circular reference
1N/A# repo <-> WorkSpace.
1N/A#
1N/A# This prevents repo, WorkSpace and members thereof from being garbage
1N/A# collected. Since transactions are aborted when the transaction object
1N/A# is collected, and localrepo holds a reference to the most recently created
1N/A# transaction, this prevents transactions from cleanly aborting.
1N/A#
1N/A# Instead, we hold the repo->WorkSpace association in a dictionary, breaking
1N/A# that dependence.
1N/A#
1N/Awslist = {}
1N/A
1N/A
1N/Adef reposetup(ui, repo):
1N/A if repo.local() and repo not in wslist:
1N/A wslist[repo] = WorkSpace(repo)
1N/A
1N/A if Version.at_least("1.3"):
1N/A interactive = ui.interactive()
1N/A else:
1N/A interactive = ui.interactive
1N/A
1N/A if interactive and sys.stdin.isatty():
1N/A ui.setconfig('hooks', 'preoutgoing.cdm_pbconfirm',
1N/A 'python:hgext_cdm.pbconfirm')
1N/A
1N/A
1N/Adef pbconfirm(ui, repo, hooktype, source):
1N/A def wrapper(settings=None):
1N/A termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings)
1N/A
1N/A if source == 'push':
1N/A if not yes_no(ui, "Are you sure you wish to push?", False):
1N/A return 1
1N/A else:
1N/A settings = termios.tcgetattr(sys.stdin.fileno())
1N/A orig = list(settings)
1N/A atexit.register(wrapper, orig)
1N/A settings[3] = settings[3] & (~termios.ISIG) # c_lflag
1N/A termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings)
1N/A
1N/A
1N/Adef cdm_pdiffs(ui, repo, *pats, **opts):
1N/A '''diff workspace against its parent
1N/A
1N/A Show differences between this workspace and its parent workspace
1N/A in the same manner as 'hg diff'.
1N/A
1N/A For a description of the changeset used to represent the parent
1N/A workspace, see The Parent in the extension documentation ('hg help
1N/A cdm').
1N/A '''
1N/A
1N/A parent = opts['parent']
1N/A
1N/A diffs = wslist[repo].pdiff(pats, opts, parent=parent)
1N/A if diffs:
2N/A if Version.at_least("1.6"):
2N/A for chunk, label in patch.difflabel(diffs.splitlines, True):
2N/A ui.write(chunk, label=label)
2N/A else:
2N/A ui.write(diffs)
1N/A
1N/A
1N/Adef cdm_list(ui, repo, **opts):
1N/A '''list active files (those changed in this workspace)
1N/A
1N/A Display a list of files changed in this workspace as compared to
1N/A its parent workspace.
1N/A
1N/A File names are displayed one-per line, grouped by manner in which
1N/A they changed (added, modified, removed). Information about
1N/A renames or copies is output in parentheses following the file
1N/A name.
1N/A
1N/A For a description of the changeset used to represent the parent
1N/A workspace, see The Parent in the extension documentation ('hg help
1N/A cdm').
1N/A
1N/A Output can be filtered by change type with --added, --modified,
1N/A and --removed. By default, all files are shown.
1N/A '''
1N/A
1N/A wanted = []
1N/A
1N/A if opts['added']:
1N/A wanted.append(ActiveEntry.ADDED)
1N/A if opts['modified']:
1N/A wanted.append(ActiveEntry.MODIFIED)
1N/A if opts['removed']:
1N/A wanted.append(ActiveEntry.REMOVED)
1N/A
1N/A act = wslist[repo].active(opts['parent'])
1N/A chngmap = {ActiveEntry.MODIFIED: 'modified',
1N/A ActiveEntry.ADDED: 'added',
1N/A ActiveEntry.REMOVED: 'removed'}
1N/A
1N/A lst = {}
1N/A for entry in act:
1N/A if wanted and (entry.change not in wanted):
1N/A continue
1N/A
1N/A chngstr = chngmap[entry.change]
1N/A if chngstr not in lst:
1N/A lst[chngstr] = []
1N/A lst[chngstr].append(entry)
1N/A
1N/A for chng in sorted(lst.keys()):
1N/A ui.write(chng + ':\n')
1N/A for elt in sorted(lst[chng]):
1N/A if elt.is_renamed():
1N/A ui.write('\t%s (renamed from %s)\n' % (elt.name,
1N/A elt.parentname))
1N/A elif elt.is_copied():
1N/A ui.write('\t%s (copied from %s)\n' % (elt.name,
1N/A elt.parentname))
1N/A else:
1N/A ui.write('\t%s\n' % elt.name)
1N/A
1N/A
1N/Adef cdm_arcs(ui, repo, parent=None):
1N/A '''show all ARC cases referenced in changeset comments'''
1N/A
1N/A act = wslist[repo].active(parent)
1N/A
1N/A # We take a set of the appropriate comments to eliminate duplicates.
1N/A for elt in set(filter(Comments.isARC, act.comments())):
1N/A ui.write(elt + '\n')
1N/A
1N/A
1N/Adef cdm_bugs(ui, repo, parent=None):
1N/A '''show all bug IDs referenced in changeset comments'''
1N/A
1N/A act = wslist[repo].active(parent)
1N/A
1N/A for elt in set(filter(Comments.isBug, act.comments())):
1N/A ui.write(elt + '\n')
1N/A
1N/A
1N/Adef cdm_comments(ui, repo, parent=None):
1N/A '''show changeset commentary for all active changesets'''
1N/A act = wslist[repo].active(parent)
1N/A
1N/A for elt in act.comments():
1N/A ui.write(elt + '\n')
1N/A
1N/A
1N/Adef cdm_renamed(ui, repo, parent=None):
1N/A '''show renamed active files
1N/A
1N/A Renamed files are shown in the format::
1N/A
1N/A new-name old-name
1N/A
1N/A One pair per-line.
1N/A '''
1N/A
1N/A act = wslist[repo].active(parent)
1N/A
1N/A for entry in sorted(filter(lambda x: x.is_renamed(), act)):
1N/A ui.write('%s %s\n' % (entry.name, entry.parentname))
1N/A
1N/A
1N/Adef cdm_comchk(ui, repo, **opts):
1N/A '''check active changeset comment formatting
1N/A
1N/A Check that active changeset comments conform to O/N rules.
1N/A
1N/A Each comment line must contain either one bug or ARC case ID
1N/A followed by its synopsis, or credit an external contributor.
1N/A '''
1N/A
1N/A active = wslist[repo].active(opts.get('parent'))
1N/A
1N/A ui.write('Comments check:\n')
1N/A
1N/A check_db = not opts.get('nocheck')
1N/A return Comments.comchk(active.comments(), check_db=check_db, output=ui,
1N/A arcPath=ui.config('cdm', 'arcpath', None))
1N/A
1N/A
1N/Adef cdm_cddlchk(ui, repo, *args, **opts):
1N/A '''check for a valid CDDL header comment in all active files.
1N/A
1N/A Check active files for a valid Common Development and Distribution
1N/A License (CDDL) block comment.
1N/A
1N/A Newly added files are checked for a copy of the CDDL header
1N/A comment. Modified files are only checked if they contain what
1N/A appears to be an existing CDDL header comment.
1N/A
1N/A Files can be excluded from this check using the cddlchk.NOT file.
1N/A See NOT Files in the extension documentation ('hg help cdm').
1N/A '''
1N/A
1N/A filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
1N/A exclude = not_check(repo, 'cddlchk')
1N/A lenient = True
1N/A ret = 0
1N/A
1N/A ui.write('CDDL block check:\n')
1N/A
1N/A for f, e in filelist:
1N/A if e and e.is_removed():
1N/A continue
1N/A elif (e or opts.get('honour_nots')) and exclude(f):
1N/A ui.status('Skipping %s...\n' % f)
1N/A continue
1N/A elif e and e.is_added():
1N/A lenient = False
1N/A else:
1N/A lenient = True
1N/A
1N/A fh = open(f, 'r')
1N/A ret |= Cddl.cddlchk(fh, lenient=lenient, output=ui)
1N/A fh.close()
1N/A return ret
1N/A
1N/A
1N/Adef cdm_mapfilechk(ui, repo, *args, **opts):
1N/A '''check for a valid mapfile header block in active files
1N/A
1N/A Check that all link-editor mapfiles contain the standard mapfile
1N/A header comment directing the reader to the document containing
1N/A Solaris object versioning rules (README.mapfile).
1N/A
1N/A Files can be excluded from this check using the mapfilechk.NOT
1N/A file. See NOT Files in the extension documentation ('hg help
1N/A cdm').
1N/A '''
1N/A
1N/A filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
1N/A exclude = not_check(repo, 'mapfilechk')
1N/A ret = 0
1N/A
1N/A # We are interested in examining any file that has the following
1N/A # in its final path segment:
1N/A # - Contains the word 'mapfile'
1N/A # - Begins with 'map.'
1N/A # - Ends with '.map'
1N/A # We don't want to match unless these things occur in final path segment
1N/A # because directory names with these strings don't indicate a mapfile.
1N/A # We also ignore files with suffixes that tell us that the files
1N/A # are not mapfiles.
1N/A MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
1N/A re.IGNORECASE)
1N/A NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
1N/A
1N/A ui.write('Mapfile comment check:\n')
1N/A
1N/A for f, e in filelist:
1N/A if e and e.is_removed():
1N/A continue
1N/A elif (not MapfileRE.match(f)) or NotMapSuffixRE.match(f):
1N/A continue
1N/A elif (e or opts.get('honour_nots')) and exclude(f):
1N/A ui.status('Skipping %s...\n' % f)
1N/A continue
1N/A
1N/A fh = open(f, 'r')
1N/A ret |= Mapfile.mapfilechk(fh, output=ui)
1N/A fh.close()
1N/A return ret
1N/A
1N/A
1N/Adef cdm_copyright(ui, repo, *args, **opts):
1N/A '''check each active file for a current and correct copyright notice
1N/A
1N/A Check that all active files have a correctly formed copyright
1N/A notice containing the current year.
1N/A
1N/A See the Non-Formatting Considerations section of the OpenSolaris
1N/A Developer's Reference for more info on the correct form of
1N/A copyright notice.
1N/A (http://hub.opensolaris.org/bin/view/Community+Group+on/devref_7#H723NonFormattingConsiderations)
1N/A
1N/A Files can be excluded from this check using the copyright.NOT file.
1N/A See NOT Files in the extension documentation ('hg help cdm').
1N/A '''
1N/A
1N/A filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
1N/A exclude = not_check(repo, 'copyright')
1N/A ret = 0
1N/A
1N/A ui.write('Copyright check:\n')
1N/A
1N/A for f, e in filelist:
1N/A if e and e.is_removed():
1N/A continue
1N/A elif (e or opts.get('honour_nots')) and exclude(f):
1N/A ui.status('Skipping %s...\n' % f)
1N/A continue
1N/A
1N/A fh = open(f, 'r')
1N/A ret |= Copyright.copyright(fh, output=ui)
1N/A fh.close()
1N/A return ret
1N/A
1N/A
1N/Adef cdm_hdrchk(ui, repo, *args, **opts):
1N/A '''check active C header files conform to the O/N header rules
1N/A
1N/A Check that any added or modified C header files conform to the O/N
1N/A header rules.
1N/A
1N/A See the section 'HEADER STANDARDS' in the hdrchk(1) manual page
1N/A for more information on the rules for O/N header file formatting.
1N/A
1N/A Files can be excluded from this check using the hdrchk.NOT file.
1N/A See NOT Files in the extension documentation ('hg help cdm').
1N/A '''
1N/A
1N/A filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
1N/A exclude = not_check(repo, 'hdrchk')
1N/A ret = 0
1N/A
1N/A ui.write('Header format check:\n')
1N/A
1N/A for f, e in filelist:
1N/A if e and e.is_removed():
1N/A continue
1N/A elif not f.endswith('.h'):
1N/A continue
1N/A elif (e or opts.get('honour_nots')) and exclude(f):
1N/A ui.status('Skipping %s...\n' % f)
1N/A continue
1N/A
1N/A fh = open(f, 'r')
1N/A ret |= HdrChk.hdrchk(fh, lenient=True, output=ui)
1N/A fh.close()
1N/A return ret
1N/A
1N/A
1N/Adef cdm_cstyle(ui, repo, *args, **opts):
1N/A '''check active C source files conform to the C Style Guide
1N/A
1N/A Check that any added or modified C source file conform to the C
1N/A Style Guide.
1N/A
1N/A See the C Style Guide for more information about correct C source
1N/A formatting.
1N/A (http://hub.opensolaris.org/bin/download/Community+Group+on/WebHome/cstyle.ms.pdf)
1N/A
1N/A Files can be excluded from this check using the cstyle.NOT file.
1N/A See NOT Files in the extension documentation ('hg help cdm').
1N/A '''
1N/A
1N/A filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
1N/A exclude = not_check(repo, 'cstyle')
1N/A ret = 0
1N/A
1N/A ui.write('C style check:\n')
1N/A
1N/A for f, e in filelist:
1N/A if e and e.is_removed():
1N/A continue
1N/A elif not (f.endswith('.c') or f.endswith('.h')):
1N/A continue
1N/A elif (e or opts.get('honour_nots')) and exclude(f):
1N/A ui.status('Skipping %s...\n' % f)
1N/A continue
1N/A
1N/A fh = open(f, 'r')
1N/A ret |= CStyle.cstyle(fh, output=ui,
1N/A picky=True, check_posix_types=True,
1N/A check_continuation=True)
1N/A fh.close()
1N/A return ret
1N/A
1N/A
1N/Adef cdm_jstyle(ui, repo, *args, **opts):
1N/A '''check active Java source files for common stylistic errors
1N/A
1N/A Files can be excluded from this check using the jstyle.NOT file.
1N/A See NOT Files in the extension documentation ('hg help cdm').
1N/A '''
1N/A
1N/A filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
1N/A exclude = not_check(repo, 'jstyle')
1N/A ret = 0
1N/A
1N/A ui.write('Java style check:\n')
1N/A
1N/A for f, e in filelist:
1N/A if e and e.is_removed():
1N/A continue
1N/A elif not f.endswith('.java'):
1N/A continue
1N/A elif (e or opts.get('honour_nots')) and exclude(f):
1N/A ui.status('Skipping %s...\n' % f)
1N/A continue
1N/A
1N/A fh = open(f, 'r')
1N/A ret |= JStyle.jstyle(fh, output=ui, picky=True)
1N/A fh.close()
1N/A return ret
1N/A
1N/A
1N/Adef cdm_permchk(ui, repo, *args, **opts):
1N/A '''check the permissions of each active file
1N/A
1N/A Check that the file permissions of each added or modified file do not
1N/A contain the executable bit.
1N/A
1N/A Files can be excluded from this check using the permchk.NOT file.
1N/A See NOT Files in the extension documentation ('hg help cdm').
1N/A '''
1N/A
1N/A filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
1N/A exclude = not_check(repo, 'permchk')
1N/A exeFiles = []
1N/A
1N/A ui.write('File permission check:\n')
1N/A
1N/A for f, e in filelist:
1N/A if e and e.is_removed():
1N/A continue
1N/A elif (e or opts.get('honour_nots')) and exclude(f):
1N/A ui.status('Skipping %s...\n' % f)
1N/A continue
1N/A
1N/A mode = stat.S_IMODE(os.stat(f)[stat.ST_MODE])
1N/A if mode & stat.S_IEXEC:
1N/A exeFiles.append(f)
1N/A
1N/A if len(exeFiles) > 0:
1N/A ui.write('Warning: the following active file(s) have executable mode '
1N/A '(+x) permission set,\nremove unless intentional:\n')
1N/A for fname in exeFiles:
1N/A ui.write(" %s\n" % fname)
1N/A
1N/A return len(exeFiles) > 0
1N/A
1N/A
1N/Adef cdm_tagchk(ui, repo, **opts):
1N/A '''check modification of workspace tags
1N/A
1N/A Check for any modification of the repository's .hgtags file.
1N/A
1N/A With the exception of the gatekeepers, nobody should introduce or
1N/A modify a repository's tags.
1N/A '''
1N/A
1N/A active = wslist[repo].active(opts.get('parent'))
1N/A
1N/A ui.write('Checking for new tags:\n')
1N/A
1N/A if ".hgtags" in active:
1N/A tfile = wslist[repo].filepath('.hgtags')
1N/A ptip = active.parenttip.rev()
1N/A
1N/A ui.write('Warning: Workspace contains new non-local tags.\n'
1N/A 'Only gatekeepers should add or modify such tags.\n'
1N/A 'Use the following commands to revert these changes:\n'
1N/A ' hg revert -r%d %s\n'
1N/A ' hg commit %s\n'
1N/A 'You should also recommit before integration\n' %
1N/A (ptip, tfile, tfile))
1N/A
1N/A return 1
1N/A
1N/A return 0
1N/A
1N/A
1N/Adef cdm_branchchk(ui, repo, **opts):
1N/A '''check for changes in number or name of branches
1N/A
1N/A Check that the workspace contains only a single head, that it is
1N/A on the branch 'default', and that no new branches have been
1N/A introduced.
1N/A '''
1N/A
1N/A ui.write('Checking for multiple heads (or branches):\n')
1N/A
1N/A heads = set(repo.heads())
1N/A parents = set([x.node() for x in wslist[repo].workingctx().parents()])
1N/A
1N/A #
1N/A # We care if there's more than one head, and those heads aren't
1N/A # identical to the dirstate parents (if they are identical, it's
1N/A # an uncommitted merge which mergechk will catch, no need to
1N/A # complain twice).
1N/A #
1N/A if len(heads) > 1 and heads != parents:
1N/A ui.write('Workspace has multiple heads (or branches):\n')
1N/A for head in [repo.changectx(head) for head in heads]:
1N/A ui.write(" %d:%s\t%s\n" %
1N/A (head.rev(), str(head), head.description().splitlines()[0]))
1N/A ui.write('You must merge and recommit.\n')
1N/A return 1
1N/A
1N/A ui.write('\nChecking for branch changes:\n')
1N/A
1N/A if repo.dirstate.branch() != 'default':
1N/A ui.write("Warning: Workspace tip has named branch: '%s'\n"
1N/A "Only gatekeepers should push new branches.\n"
1N/A "Use the following commands to restore the branch name:\n"
1N/A " hg branch [-f] default\n"
1N/A " hg commit\n"
1N/A "You should also recommit before integration\n" %
1N/A (repo.dirstate.branch()))
1N/A return 1
1N/A
1N/A branches = repo.branchtags().keys()
1N/A if len(branches) > 1:
1N/A ui.write('Warning: Workspace has named branches:\n')
1N/A for t in branches:
1N/A if t == 'default':
1N/A continue
1N/A ui.write("\t%s\n" % t)
1N/A
1N/A ui.write("Only gatekeepers should push new branches.\n"
1N/A "Use the following commands to remove extraneous branches.\n"
1N/A " hg branch [-f] default\n"
1N/A " hg commit"
1N/A "You should also recommit before integration\n")
1N/A return 1
1N/A
1N/A return 0
1N/A
1N/A
1N/Adef cdm_rtichk(ui, repo, **opts):
1N/A '''check active bug/RFEs have approved RTIs
1N/A
1N/A Check that any CR IDs mentioned in active changeset comments have
1N/A an associated, approved Request to Integrate (RTI).
1N/A
1N/A This check only works on systems with access to webrti.us.oracle.com.
1N/A
1N/A This check maybe disabled by creating an empty file rtichk.NOT.
1N/A See NOT Files in the extension documentation ('hg help cdm').
1N/A '''
1N/A
1N/A if opts.get('nocheck') or os.path.exists(repo.join('cdm/rtichk.NOT')):
1N/A ui.status('Skipping RTI checks...\n')
1N/A return 0
1N/A
1N/A if not onSWAN():
1N/A ui.write('RTI checks only work on SWAN, skipping...\n')
1N/A return 0
1N/A
1N/A parent = wslist[repo].parent(opts.get('parent'))
1N/A active = wslist[repo].active(parent)
1N/A
1N/A ui.write('RTI check:\n')
1N/A
1N/A bugs = []
1N/A
1N/A for com in active.comments():
1N/A match = Comments.isBug(com)
1N/A if match and match.group(1) not in bugs:
1N/A bugs.append(match.group(1))
1N/A
1N/A # RTI normalizes the gate path for us
1N/A return int(not Rti.rti(bugs, gatePath=parent, output=ui))
1N/A
1N/A
1N/Adef cdm_keywords(ui, repo, *args, **opts):
1N/A '''check active files for SCCS keywords
1N/A
1N/A Check that any added or modified files do not contain SCCS keywords
1N/A (#ident lines, etc.).
1N/A
1N/A Files can be excluded from this check using the keywords.NOT file.
1N/A See NOT Files in the extension documentation ('hg help cdm').
1N/A '''
1N/A
1N/A filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
1N/A exclude = not_check(repo, 'keywords')
1N/A ret = 0
1N/A
1N/A ui.write('Keywords check:\n')
1N/A
1N/A for f, e in filelist:
1N/A if e and e.is_removed():
1N/A continue
1N/A elif (e or opts.get('honour_nots')) and exclude(f):
1N/A ui.status('Skipping %s...\n' % f)
1N/A continue
1N/A
1N/A fh = open(f, 'r')
1N/A ret |= Keywords.keywords(fh, output=ui)
1N/A fh.close()
1N/A return ret
1N/A
1N/A
1N/A#
1N/A# NB:
1N/A# There's no reason to hook this up as an invokable command, since
1N/A# we have 'hg status', but it must accept the same arguments.
1N/A#
1N/Adef cdm_outchk(ui, repo, **opts):
1N/A '''Warn the user if they have uncommitted changes'''
1N/A
1N/A ui.write('Checking for uncommitted changes:\n')
1N/A
1N/A st = wslist[repo].modified()
1N/A if st:
1N/A ui.write('Warning: the following files have uncommitted changes:\n')
1N/A for elt in st:
1N/A ui.write(' %s\n' % elt)
1N/A return 1
1N/A return 0
1N/A
1N/A
1N/Adef cdm_mergechk(ui, repo, **opts):
1N/A '''Warn the user if their workspace contains merges'''
1N/A
1N/A active = wslist[repo].active(opts.get('parent'))
1N/A
1N/A ui.write('Checking for merges:\n')
1N/A
1N/A merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1],
1N/A active.revs)
1N/A
1N/A if merges:
1N/A ui.write('Workspace contains the following merges:\n')
1N/A for rev in merges:
1N/A desc = rev.description().splitlines()
1N/A ui.write(' %s:%s\t%s\n' %
1N/A (rev.rev() or "working", str(rev),
1N/A desc and desc[0] or "*** uncommitted change ***"))
1N/A return 1
1N/A return 0
1N/A
1N/A
1N/Adef run_checks(ws, cmds, *args, **opts):
1N/A '''Run CMDS (with OPTS) over active files in WS'''
1N/A
1N/A ret = 0
1N/A
1N/A for cmd in cmds:
1N/A name = cmd.func_name.split('_')[1]
1N/A if not ws.ui.configbool('cdm', name, True):
1N/A ws.ui.status('Skipping %s check...\n' % name)
1N/A else:
1N/A ws.ui.pushbuffer()
1N/A result = cmd(ws.ui, ws.repo, honour_nots=True, *args, **opts)
1N/A output = ws.ui.popbuffer()
1N/A
1N/A ret |= result
1N/A
1N/A if not ws.ui.quiet or result != 0:
1N/A ws.ui.write(output, '\n')
1N/A return ret
1N/A
1N/A
1N/Adef cdm_nits(ui, repo, *args, **opts):
1N/A '''check for stylistic nits in active files
1N/A
1N/A Check each active file for basic stylistic errors.
1N/A
1N/A The following checks are run over each active file (see 'hg help
1N/A <check>' for more information about each):
1N/A
1N/A - cddlchk (CDDL block comments)
1N/A - copyright (copyright statements)
1N/A - cstyle (C source style)
1N/A - hdrchk (C header style)
1N/A - jstyle (java source style)
1N/A - mapfilechk (link-editor mapfiles)
1N/A - permchk (file permissions)
1N/A - keywords (SCCS keywords)
1N/A
1N/A With the global -q/--quiet option, only provide output for those
1N/A checks which fail.
1N/A '''
1N/A
1N/A cmds = [cdm_cddlchk,
1N/A cdm_copyright,
1N/A cdm_cstyle,
1N/A cdm_hdrchk,
1N/A cdm_jstyle,
1N/A cdm_mapfilechk,
1N/A cdm_permchk,
1N/A cdm_keywords]
1N/A
1N/A return run_checks(wslist[repo], cmds, *args, **opts)
1N/A
1N/A
1N/Adef cdm_pbchk(ui, repo, **opts):
1N/A '''run pre-integration checks on this workspace
1N/A
1N/A Check this workspace for common errors prior to integration.
1N/A
1N/A The following checks are run over the active list (see 'hg help
1N/A <check>' for more information about each):
1N/A
1N/A - branchchk (addition/modification of branches)
1N/A - cddlchk (CDDL block comments)
1N/A - comchk (changeset descriptions)
1N/A - copyright (copyright statements)
1N/A - cstyle (C source style)
1N/A - hdrchk (C header style)
1N/A - jstyle (java source style)
1N/A - keywords (SCCS keywords)
1N/A - mapfilechk (link-editor mapfiles)
1N/A - permchk (file permissions)
1N/A - rtichk (requests to integrate)
1N/A - tagchk (addition/modification of tags)
1N/A
1N/A Additionally, the workspace is checked for outgoing merges (which
1N/A should be removed with 'hg recommit'), and uncommitted changes.
1N/A
1N/A With the global -q/--quiet option, only provide output for those
1N/A checks which fail.
1N/A '''
1N/A
1N/A #
1N/A # The current ordering of these is that the commands from cdm_nits
1N/A # run first in the same order as they would in cdm_nits, then the
1N/A # pbchk specifics are run.
1N/A #
1N/A cmds = [cdm_cddlchk,
1N/A cdm_copyright,
1N/A cdm_cstyle,
1N/A cdm_hdrchk,
1N/A cdm_jstyle,
1N/A cdm_mapfilechk,
1N/A cdm_permchk,
1N/A cdm_keywords,
1N/A cdm_comchk,
1N/A cdm_tagchk,
1N/A cdm_branchchk,
1N/A cdm_rtichk,
1N/A cdm_outchk,
1N/A cdm_mergechk]
1N/A
1N/A return run_checks(wslist[repo], cmds, **opts)
1N/A
1N/A
1N/Adef cdm_recommit(ui, repo, **opts):
1N/A '''replace outgoing changesets with a single equivalent changeset
1N/A
1N/A Replace all outgoing changesets with a single changeset containing
1N/A equivalent changes. This removes uninteresting changesets created
1N/A during development that would only serve as noise in the gate.
1N/A
1N/A Any changed file that is now identical in content to that in the
1N/A parent workspace (whether identical in history or otherwise) will
1N/A not be included in the new changeset. Any merges information will
1N/A also be removed.
1N/A
1N/A If no files are changed in comparison to the parent workspace, the
1N/A outgoing changesets will be removed, but no new changeset created.
1N/A
1N/A recommit will refuse to run if the workspace contains more than
1N/A one outgoing head, even if those heads are on the same branch. To
1N/A recommit with only one branch containing outgoing changesets, your
1N/A workspace must be on that branch and at that branch head.
1N/A
1N/A recommit will prompt you to take a backup if your workspace has
1N/A been changed since the last backup was taken. In almost all
1N/A cases, you should allow it to take one (the default).
1N/A
1N/A recommit cannot be run if the workspace contains any uncommitted
1N/A changes, applied Mq patches, or has multiple outgoing heads (or
1N/A branches).
1N/A '''
1N/A
1N/A ws = wslist[repo]
1N/A
1N/A if not os.getcwd().startswith(repo.root):
1N/A raise util.Abort('recommit is not safe to run with -R')
1N/A
1N/A abort_if_dirty(ws)
1N/A
1N/A wlock = repo.wlock()
1N/A lock = repo.lock()
1N/A
1N/A try:
1N/A parent = ws.parent(opts['parent'])
1N/A between = repo.changelog.nodesbetween(ws.findoutgoing(parent))[2]
1N/A heads = set(between) & set(repo.heads())
1N/A
1N/A if len(heads) > 1:
1N/A ui.warn('Workspace has multiple outgoing heads (or branches):\n')
1N/A for head in sorted(map(repo.changelog.rev, heads), reverse=True):
1N/A ui.warn('\t%d\n' % head)
1N/A raise util.Abort('you must merge before recommitting')
1N/A
1N/A active = ws.active(parent)
1N/A
1N/A if filter(lambda b: len(b.parents()) > 1, active.bases()):
1N/A raise util.Abort('Cannot recommit a merge of two non-outgoing '
1N/A 'changesets')
1N/A
1N/A if len(active.revs) <= 0:
1N/A raise util.Abort("no changes to recommit")
1N/A
1N/A if len(active.files()) <= 0:
1N/A ui.warn("Recommitting %d active changesets, but no active files\n" %
1N/A len(active.revs))
1N/A
1N/A #
1N/A # During the course of a recommit, any file bearing a name
1N/A # matching the source name of any renamed file will be
1N/A # clobbered by the operation.
1N/A #
1N/A # As such, we ask the user before proceeding.
1N/A #
1N/A bogosity = [f.parentname for f in active if f.is_renamed() and
1N/A os.path.exists(repo.wjoin(f.parentname))]
1N/A if bogosity:
1N/A ui.warn("The following file names are the original name of a "
1N/A "rename and also present\n"
1N/A "in the working directory:\n")
1N/A
1N/A for fname in bogosity:
1N/A ui.warn(" %s\n" % fname)
1N/A
1N/A if not yes_no(ui, "These files will be removed by recommit."
1N/A " Continue?",
1N/A False):
1N/A raise util.Abort("recommit would clobber files")
1N/A
1N/A user = opts['user'] or ui.username()
1N/A comments = '\n'.join(active.comments())
1N/A
2N/A if Version.at_least("1.9"):
2N/A message = cmdutil.logmessage(ui, opts) or ui.edit(comments, user)
2N/A else:
2N/A message = cmdutil.logmessage(opts) or ui.edit(comments, user)
1N/A if not message:
1N/A raise util.Abort('empty commit message')
1N/A
1N/A bk = CdmBackup(ui, ws, backup_name(repo.root))
1N/A if bk.need_backup():
1N/A if yes_no(ui, 'Do you want to backup files first?', True):
1N/A bk.backup()
1N/A
1N/A oldtags = repo.tags()
1N/A clearedtags = [(name, nd, repo.changelog.rev(nd), local)
1N/A for name, nd, local in active.tags()]
1N/A
1N/A ws.squishdeltas(active, message, user=user)
1N/A finally:
1N/A lock.release()
1N/A wlock.release()
1N/A
1N/A if clearedtags:
1N/A ui.write("Removed tags:\n")
1N/A for name, nd, rev, local in sorted(clearedtags,
1N/A key=lambda x: x[0].lower()):
1N/A ui.write(" %5s:%s:\t%s%s\n" % (rev, node.short(nd),
1N/A name, (local and ' (local)' or '')))
1N/A
1N/A for ntag, nnode in sorted(repo.tags().items(),
1N/A key=lambda x: x[0].lower()):
1N/A if ntag in oldtags and ntag != "tip":
1N/A if oldtags[ntag] != nnode:
1N/A ui.write("tag '%s' now refers to revision %d:%s\n" %
1N/A (ntag, repo.changelog.rev(nnode),
1N/A node.short(nnode)))
1N/A
1N/A
1N/Adef do_eval(cmd, files, root, changedir=True):
1N/A if not changedir:
1N/A os.chdir(root)
1N/A
1N/A for path in sorted(files):
1N/A dirn, base = os.path.split(path)
1N/A
1N/A if changedir:
1N/A os.chdir(os.path.join(root, dirn))
1N/A
1N/A os.putenv('workspace', root)
1N/A os.putenv('filepath', path)
1N/A os.putenv('dir', dirn)
1N/A os.putenv('file', base)
1N/A os.system(cmd)
1N/A
1N/A
1N/Adef cdm_eval(ui, repo, *command, **opts):
1N/A '''run specified command for each active file
1N/A
1N/A Run the command specified on the command line for each active
1N/A file, with the following variables present in the environment:
1N/A
1N/A :$file: - active file basename.
1N/A :$dir: - active file dirname.
1N/A :$filepath: - path from workspace root to active file.
1N/A :$workspace: - full path to workspace root.
1N/A
1N/A For example:
1N/A
1N/A hg eval 'echo $dir; hg log -l3 $file'
1N/A
1N/A will show the last the 3 log entries for each active file,
1N/A preceded by its directory.
1N/A '''
1N/A
1N/A act = wslist[repo].active(opts['parent'])
1N/A cmd = ' '.join(command)
1N/A files = [x.name for x in act if not x.is_removed()]
1N/A
1N/A do_eval(cmd, files, repo.root, not opts['remain'])
1N/A
1N/A
1N/Adef cdm_apply(ui, repo, *command, **opts):
1N/A '''apply specified command to all active files
1N/A
1N/A Run the command specified on the command line over each active
1N/A file.
1N/A
1N/A For example 'hg apply "wc -l"' will output a count of the lines in
1N/A each active file.
1N/A '''
1N/A
1N/A act = wslist[repo].active(opts['parent'])
1N/A
1N/A if opts['remain']:
1N/A appnd = ' $filepath'
1N/A else:
1N/A appnd = ' $file'
1N/A
1N/A cmd = ' '.join(command) + appnd
1N/A files = [x.name for x in act if not x.is_removed()]
1N/A
1N/A do_eval(cmd, files, repo.root, not opts['remain'])
1N/A
1N/Adef cdm_reparent_11(ui, repo, parent):
1N/A filename = repo.join('hgrc')
1N/A
1N/A p = ui.expandpath(parent)
1N/A cp = util.configparser()
1N/A
1N/A try:
1N/A cp.read(filename)
1N/A except ConfigParser.ParsingError, inst:
1N/A raise util.Abort('failed to parse %s\n%s' % (filename, inst))
1N/A
1N/A try:
1N/A fh = open(filename, 'w')
1N/A except IOError, e:
1N/A raise util.Abort('Failed to open workspace configuration: %s' % e)
1N/A
1N/A if not cp.has_section('paths'):
1N/A cp.add_section('paths')
1N/A cp.set('paths', 'default', p)
1N/A cp.write(fh)
1N/A fh.close()
1N/A
1N/Adef cdm_reparent_13(ui, repo, parent):
2N/A
2N/A # In 1.9 (changeset d764463b433e in the mercurial repo), atomictempfile()
2N/A # stopped delegating all undefined methods to file() using __getattr__(), so
2N/A # use of "closed" and "tell" now raise an exception. Then in 2.0 (changeset
2N/A # 774da7121fc9), rename() got renamed to close() and close() got renamed to
2N/A # discard(). Hence the ugliness.
2N/A
2N/A def append_new_parent_13(parent):
1N/A fp = None
1N/A try:
1N/A fp = repo.opener('hgrc', 'a', atomictemp=True)
1N/A if fp.tell() != 0:
1N/A fp.write('\n')
1N/A fp.write('[paths]\n'
1N/A 'default = %s\n\n' % parent)
1N/A fp.rename()
1N/A finally:
1N/A if fp and not fp.closed:
1N/A fp.close()
1N/A
2N/A def append_new_parent_19(parent):
2N/A fp = repo.opener('hgrc', 'a', atomictemp=True)
2N/A if fp._fp.tell() != 0:
2N/A fp.write('\n')
2N/A fp.write('[paths]\n'
2N/A 'default = %s\n\n' % parent)
2N/A if Version.at_least("2.0"):
2N/A fp.close()
2N/A else:
2N/A fp.rename()
2N/A
1N/A def update_parent(path, line, parent):
1N/A line = line - 1 # The line number we're passed will be 1-based
1N/A fp = None
1N/A
1N/A try:
1N/A fp = open(path)
1N/A data = fp.readlines()
1N/A finally:
1N/A if fp and not fp.closed:
1N/A fp.close()
1N/A
1N/A #
1N/A # line will be the last line of any continued block, go back
1N/A # to the first removing the continuation as we go.
1N/A #
1N/A while data[line][0].isspace():
1N/A data.pop(line)
1N/A line -= 1
1N/A
1N/A assert data[line].startswith('default')
1N/A
1N/A data[line] = "default = %s\n" % parent
1N/A if data[-1] != '\n':
1N/A data.append('\n')
1N/A
2N/A if Version.at_least("1.9"):
1N/A fp = util.atomictempfile(path, 'w', 0644)
2N/A fp.write("".join(data))
2N/A if Version.at_least("2.0"):
1N/A fp.close()
2N/A else:
2N/A fp.rename()
2N/A else:
2N/A try:
2N/A fp = util.atomictempfile(path, 'w', 0644)
2N/A fp.writelines(data)
2N/A fp.rename()
2N/A finally:
2N/A if fp and not fp.closed:
2N/A fp.close()
1N/A
1N/A from mercurial import config
1N/A parent = ui.expandpath(parent)
1N/A
2N/A if Version.at_least("1.9"):
2N/A append_new_parent = append_new_parent_19
2N/A else:
2N/A append_new_parent = append_new_parent_13
2N/A
1N/A if not os.path.exists(repo.join('hgrc')):
1N/A append_new_parent(parent)
1N/A return
1N/A
1N/A cfg = config.config()
1N/A cfg.read(repo.join('hgrc'))
1N/A source = cfg.source('paths', 'default')
1N/A
1N/A if not source:
1N/A append_new_parent(parent)
1N/A return
1N/A else:
1N/A path, target = source.rsplit(':', 1)
1N/A
1N/A if path != repo.join('hgrc'):
1N/A raise util.Abort("Cannot edit path specification not in repo hgrc\n"
1N/A "default path is from: %s" % source)
1N/A
1N/A update_parent(path, int(target), parent)
1N/A
1N/A
1N/Aif Version.at_least("1.3"):
1N/A cdm_reparent = cdm_reparent_13
1N/Aelse:
1N/A cdm_reparent = cdm_reparent_11
1N/A
1N/Acdm_reparent.__doc__ = '''reparent your workspace
1N/A Update the 'default' path alias that is used as the default source
1N/A for 'hg pull' and the default destination for 'hg push' (unless
1N/A there is a 'default-push' alias). This is also the path all
1N/A Cadmium commands treat as your parent workspace.
1N/A '''
1N/A
1N/A
1N/Adef backup_name(fullpath):
1N/A '''Create a backup directory name based on the specified path.
1N/A
1N/A In most cases this is the basename of the path specified, but
1N/A certain cases are handled specially to create meaningful names'''
1N/A
1N/A special = ['usr/closed']
1N/A
1N/A fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep)
1N/A
1N/A #
1N/A # If a path is 'special', we append the basename of the path to
1N/A # the path element preceding the constant, special, part.
1N/A #
1N/A # Such that for instance:
1N/A # /foo/bar/onnv-fixes/usr/closed
1N/A # has a backup name of:
1N/A # onnv-fixes-closed
1N/A #
1N/A for elt in special:
1N/A elt = elt.split(os.path.sep)
1N/A pathpos = len(elt)
1N/A
1N/A if fullpath[-pathpos:] == elt:
1N/A return "%s-%s" % (fullpath[-pathpos - 1], elt[-1])
1N/A else:
1N/A return fullpath[-1]
1N/A
1N/A
1N/Adef cdm_backup(ui, repo, if_newer=False):
1N/A '''backup workspace changes and metadata
1N/A
1N/A Create a backup copy of changes made in this workspace as compared
1N/A to its parent workspace, as well as important metadata of this
1N/A workspace.
1N/A
1N/A NOTE: Only changes as compared to the parent workspace are backed
1N/A up. If you lose this workspace and its parent, you will not be
1N/A able to restore a backup into a clone of the grandparent
1N/A workspace.
1N/A
1N/A By default, backups are stored in the cdm.backup/ directory in
1N/A your home directory. This is configurable using the cdm.backupdir
1N/A configuration variable, for example:
1N/A
1N/A hg backup --config cdm.backupdir=/net/foo/backups
1N/A
1N/A or place the following in an appropriate hgrc file::
1N/A
1N/A [cdm]
1N/A backupdir = /net/foo/backups
1N/A
1N/A Backups have the same name as the workspace in which they were
1N/A taken, with '-closed' appended in the case of O/N's usr/closed.
1N/A '''
1N/A
1N/A name = backup_name(repo.root)
1N/A bk = CdmBackup(ui, wslist[repo], name)
1N/A
1N/A wlock = repo.wlock()
1N/A lock = repo.lock()
1N/A
1N/A try:
1N/A if if_newer and not bk.need_backup():
1N/A ui.status('backup is up-to-date\n')
1N/A else:
1N/A bk.backup()
1N/A finally:
1N/A lock.release()
1N/A wlock.release()
1N/A
1N/A
1N/Adef cdm_restore(ui, repo, backup, **opts):
1N/A '''restore workspace from backup
1N/A
1N/A Restore this workspace from a backup (taken by 'hg backup').
1N/A
1N/A If the specified backup directory does not exist, it is assumed to
1N/A be relative to the cadmium backup directory (~/cdm.backup/ by
1N/A default).
1N/A
1N/A For example::
1N/A
1N/A % hg restore on-rfe - Restore the latest backup of ~/cdm.backup/on-rfe
1N/A % hg restore -g3 on-rfe - Restore the 3rd backup of ~/cdm.backup/on-rfe
1N/A % hg restore /net/foo/backup/on-rfe - Restore from an explicit path
1N/A '''
1N/A
1N/A if not os.getcwd().startswith(repo.root):
1N/A raise util.Abort('restore is not safe to run with -R')
1N/A
1N/A abort_if_dirty(wslist[repo])
1N/A
1N/A if opts['generation']:
1N/A gen = int(opts['generation'])
1N/A else:
1N/A gen = None
1N/A
1N/A if os.path.exists(backup):
1N/A backup = os.path.abspath(backup)
1N/A
1N/A wlock = repo.wlock()
1N/A lock = repo.lock()
1N/A
1N/A try:
1N/A bk = CdmBackup(ui, wslist[repo], backup)
1N/A bk.restore(gen)
1N/A finally:
1N/A lock.release()
1N/A wlock.release()
1N/A
1N/A
1N/Adef cdm_webrev(ui, repo, **opts):
1N/A '''generate web-based code review and optionally upload it
1N/A
1N/A Generate a web-based code review using webrev(1) and optionally
1N/A upload it. All known arguments are passed through to webrev(1).
1N/A '''
1N/A
1N/A webrev_args = ""
1N/A for key in opts.keys():
1N/A if opts[key]:
1N/A if type(opts[key]) == type(True):
1N/A webrev_args += '-' + key + ' '
1N/A else:
1N/A webrev_args += '-' + key + ' ' + opts[key] + ' '
1N/A
1N/A retval = os.system('webrev ' + webrev_args)
1N/A if retval != 0:
1N/A return retval - 255
1N/A
1N/A return 0
1N/A
1N/A
1N/Acmdtable = {
1N/A 'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'),
1N/A ('r', 'remain', None, 'do not change directory')],
1N/A 'hg apply [-p PARENT] [-r] command...'),
1N/A 'arcs': (cdm_arcs, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg arcs [-p PARENT]'),
1N/A '^backup|bu': (cdm_backup, [('t', 'if-newer', None,
1N/A 'only backup if workspace files are newer')],
1N/A 'hg backup [-t]'),
1N/A 'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg branchchk [-p PARENT]'),
1N/A 'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg bugs [-p PARENT]'),
1N/A 'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg cddlchk [-p PARENT]'),
1N/A 'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'),
1N/A ('N', 'nocheck', None,
1N/A 'do not compare comments with databases')],
1N/A 'hg comchk [-p PARENT]'),
1N/A 'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg comments [-p PARENT]'),
1N/A 'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg copyright [-p PARENT]'),
1N/A 'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg cstyle [-p PARENT]'),
1N/A 'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'),
1N/A ('r', 'remain', None, 'do not change directory')],
1N/A 'hg eval [-p PARENT] [-r] command...'),
1N/A 'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg hdrchk [-p PARENT]'),
1N/A 'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg jstyle [-p PARENT]'),
1N/A 'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg keywords [-p PARENT]'),
1N/A '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'),
1N/A ('a', 'added', None, 'show added files'),
1N/A ('m', 'modified', None, 'show modified files'),
1N/A ('r', 'removed', None, 'show removed files')],
1N/A 'hg list [-amrRu] [-p PARENT]'),
1N/A 'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg mapfilechk [-p PARENT]'),
1N/A '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg nits [-p PARENT]'),
1N/A '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'),
1N/A ('N', 'nocheck', None,
1N/A 'skip RTI and bug/ARC database checks')],
1N/A 'hg pbchk [-N] [-p PARENT]'),
1N/A 'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg permchk [-p PARENT]'),
1N/A '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace'),
1N/A ('a', 'text', None, 'treat all files as text'),
1N/A ('g', 'git', None, 'use extended git diff format'),
1N/A ('w', 'ignore-all-space', None,
1N/A 'ignore white space when comparing lines'),
1N/A ('b', 'ignore-space-change', None,
1N/A 'ignore changes in the amount of white space'),
1N/A ('B', 'ignore-blank-lines', None,
1N/A 'ignore changes whose lines are all blank'),
1N/A ('U', 'unified', 3,
1N/A 'number of lines of context to show'),
1N/A ('I', 'include', [],
1N/A 'include names matching the given patterns'),
1N/A ('X', 'exclude', [],
1N/A 'exclude names matching the given patterns')],
1N/A 'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'),
1N/A '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'),
1N/A ('m', 'message', '',
1N/A 'use <text> as commit message'),
1N/A ('l', 'logfile', '',
1N/A 'read commit message from file'),
1N/A ('u', 'user', '',
1N/A 'record user as committer')],
1N/A 'hg recommit [-m TEXT] [-l FILE] [-u USER] [-p PARENT]'),
1N/A 'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg renamed [-p PARENT]'),
1N/A 'reparent': (cdm_reparent, [], 'hg reparent PARENT'),
1N/A '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')],
1N/A 'hg restore [-g GENERATION] BACKUP'),
1N/A 'rtichk': (cdm_rtichk, [('p', 'parent', '', 'parent workspace'),
1N/A ('N', 'nocheck', None, 'skip RTI check')],
1N/A 'hg rtichk [-N] [-p PARENT]'),
1N/A 'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')],
1N/A 'hg tagchk [-p PARENT]'),
1N/A 'webrev': (cdm_webrev, [('C', 'C', '', 'ITS priority file'),
1N/A ('D', 'D', None, 'delete remote webrev'),
1N/A ('I', 'I', '', 'ITS configuration file'),
1N/A ('i', 'i', '', 'include file'),
1N/A ('N', 'N', None, 'suppress comments'),
1N/A ('n', 'n', None, 'do not generate webrev'),
1N/A ('O', 'O', None, 'OpenSolaris mode'),
1N/A ('o', 'o', '', 'output directory'),
1N/A ('p', 'p', '', 'use specified parent'),
1N/A ('t', 't', '', 'upload target'),
1N/A ('U', 'U', None, 'upload the webrev'),
1N/A ('w', 'w', '', 'use wx active file'),
1N/A ('z', 'z', None, 'compress the webrev in ZIP format')],
1N/A 'hg webrev [WEBREV_OPTIONS]'),
1N/A}