Print this page
    
5025 import and use mandoc
Reviewed by: Hans Rosenfeld <hans.rosenfeld@nexenta.com>
Reviewed by: Igor Kozhukhov <ikozhukhov@gmail.com>
Reviewed by: Robert Mustacchi <rm@joyent.com>
Reviewed by: Albert Lee <trisk@nexenta.com>
Approved by: TBD
    
      
        | Split | 
	Close | 
      
      | Expand all | 
      | Collapse all | 
    
    
          --- old/usr/src/tools/onbld/hgext/cdm.py
          +++ new/usr/src/tools/onbld/hgext/cdm.py
   1    1  #
   2    2  #  This program is free software; you can redistribute it and/or modify
   3    3  #  it under the terms of the GNU General Public License version 2
   4    4  #  as published by the Free Software Foundation.
   5    5  #
   6    6  #  This program is distributed in the hope that it will be useful,
   7    7  #  but WITHOUT ANY WARRANTY; without even the implied warranty of
   8    8  #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  
    | 
      ↓ open down ↓ | 
    8 lines elided | 
    
      ↑ open up ↑ | 
  
   9    9  #  GNU General Public License for more details.
  10   10  #
  11   11  #  You should have received a copy of the GNU General Public License
  12   12  #  along with this program; if not, write to the Free Software
  13   13  #  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  14   14  #
  15   15  
  16   16  #
  17   17  # Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
  18   18  # Copyright 2008, 2011 Richard Lowe
       19 +# Copyright 2014 Garrett D'Amore <garrett@damore.org>
  19   20  #
  20   21  
  21   22  '''OpenSolaris extensions to Mercurial
  22   23  
  23   24      This extension contains a number of commands to help you work with
  24   25  the OpenSolaris consolidations.  It provides commands to check your
  25   26  changes against the various style rules used for OpenSolaris, to
  26   27  backup and restore your changes, to generate code reviews, and to
  27   28  prepare your changes for integration.
  28   29  
  29   30  
  30   31  The Parent
  31   32  
  32   33      To provide a uniform notion of parent workspace regardless of
  33   34  filesystem-based access, Cadmium uses the highest numbered changeset
  34   35  on the current branch that is also in the parent workspace to
  35   36  represent the parent workspace.
  36   37  
  37   38  
  38   39  The Active List
  39   40  
  40   41      Many Cadmium commands operate on the active list, the set of
  41   42  files ('active files') you have changed in this workspace in relation
  42   43  to its parent workspace, and the metadata (commentary, primarily)
  43   44  associated with those changes.
  44   45  
  45   46  
  46   47  NOT Files
  47   48  
  48   49      Many of Cadmium's commands to check that your work obeys the
  49   50  various stylistic rules of the OpenSolaris consolidations (such as
  50   51  those run by 'hg nits') allow files to be excluded from this checking
  51   52  by means of NOT files kept in the .hg/cdm/ directory of the Mercurial
  52   53  repository for one-time exceptions, and in the exception_lists
  53   54  directory at the repository root for permanent exceptions.  (For ON,
  54   55  these would mean one in $CODEMGR_WS and one in
  55   56  $CODEMGR_WS/usr/closed).
  56   57  
  57   58      These files are in the same format as the Mercurial hgignore
  58   59  file, a description of which is available in the hgignore(5) manual
  59   60  page.
  60   61  
  61   62  
  62   63  Common Tasks
  63   64  
  64   65    - Show diffs relative to parent workspace               - pdiffs
  65   66    - Check source style rules                              - nits
  66   67    - Run pre-integration checks                            - pbchk
  67   68    - Collapse all your changes into a single changeset     - recommit
  68   69  '''
  69   70  
  70   71  import atexit, os, re, sys, stat, termios
  71   72  
  72   73  
  73   74  #
  74   75  # Adjust the load path based on the location of cdm.py and the version
  75   76  # of python into which it is being loaded.  This assumes the normal
  76   77  # onbld directory structure, where cdm.py is in
  77   78  # lib/python(version)?/onbld/hgext/.  If that changes so too must
  78   79  # this.
  79   80  #
  80   81  # This and the case below are not equivalent.  In this case we may be
  81   82  # loading a cdm.py in python2.X/ via the lib/python/ symlink but need
  82   83  # python2.Y in sys.path.
  83   84  #
  84   85  sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..", "..", "..",
  85   86                                  "python%d.%d" % sys.version_info[:2]))
  86   87  
  87   88  #
  88   89  # Add the relative path from cdm.py to usr/src/tools to the load path,
  89   90  # such that a cdm.py loaded from the source tree uses the modules also
  90   91  # within the source tree.
  91   92  #
  92   93  sys.path.insert(2, os.path.join(os.path.dirname(__file__), "..", ".."))
  93   94  
  94   95  from onbld.Scm import Version
  95   96  from mercurial import util
  96   97  
  
    | 
      ↓ open down ↓ | 
    68 lines elided | 
    
      ↑ open up ↑ | 
  
  97   98  try:
  98   99      Version.check_version()
  99  100  except Version.VersionMismatch, badversion:
 100  101      raise util.Abort("Version Mismatch:\n %s\n" % badversion)
 101  102  
 102  103  from mercurial import cmdutil, ignore, node, patch
 103  104  
 104  105  from onbld.Scm.WorkSpace import WorkSpace, WorkList
 105  106  from onbld.Scm.Backup import CdmBackup
 106  107  from onbld.Checks import Cddl, Comments, Copyright, CStyle, HdrChk
 107      -from onbld.Checks import JStyle, Keywords, Mapfile
      108 +from onbld.Checks import JStyle, Keywords, ManLint, Mapfile
 108  109  
 109  110  
 110  111  def yes_no(ui, msg, default):
 111  112      if default:
 112  113          prompt = ' [Y/n]:'
 113  114          defanswer = 'y'
 114  115      else:
 115  116          prompt = ' [y/N]:'
 116  117          defanswer = 'n'
 117  118  
 118  119      if Version.at_least("1.4"):
 119  120          index = ui.promptchoice(msg + prompt, ['&yes', '&no'],
 120  121                                  default=['y', 'n'].index(defanswer))
 121  122          resp = ('y', 'n')[index]
 122  123      else:
 123  124          resp = ui.prompt(msg + prompt, ['&yes', '&no'], default=defanswer)
 124  125  
 125  126      return resp[0] in ('Y', 'y')
 126  127  
 127  128  
 128  129  def buildfilelist(ws, parent, files):
 129  130      '''Build a list of files in which we're interested.
 130  131  
 131  132      If no files are specified take files from the active list relative
 132  133      to 'parent'.
 133  134  
 134  135      Return a list of 2-tuples the first element being a path relative
 135  136      to the current directory and the second an entry from the active
 136  137      list, or None if an explicit file list was given.'''
 137  138  
 138  139      if files:
 139  140          return [(path, None) for path in sorted(files)]
 140  141      else:
 141  142          active = ws.active(parent=parent)
 142  143          return [(ws.filepath(e.name), e) for e in sorted(active)]
 143  144  buildfilelist = util.cachefunc(buildfilelist)
 144  145  
 145  146  
 146  147  def not_check(repo, cmd):
 147  148      '''return a function which returns boolean indicating whether a file
 148  149      should be skipped for CMD.'''
 149  150  
 150  151      #
 151  152      # The ignore routines need a canonical path to the file (relative to the
 152  153      # repo root), whereas the check commands get paths relative to the cwd.
 153  154      #
 154  155      # Wrap our argument such that the path is canonified before it is checked.
 155  156      #
 156  157      def canonified_check(ignfunc):
 157  158          def f(path):
 158  159              cpath = util.canonpath(repo.root, repo.getcwd(), path)
 159  160              return ignfunc(cpath)
 160  161          return f
 161  162  
 162  163      ignorefiles = []
 163  164  
 164  165      for f in [repo.join('cdm/%s.NOT' % cmd),
 165  166                 repo.wjoin('exception_lists/%s' % cmd)]:
 166  167          if os.path.exists(f):
 167  168              ignorefiles.append(f)
 168  169  
 169  170      if ignorefiles:
 170  171          ign = ignore.ignore(repo.root, ignorefiles, repo.ui.warn)
 171  172          return canonified_check(ign)
 172  173      else:
 173  174          return util.never
 174  175  
 175  176  
 176  177  def abort_if_dirty(ws):
 177  178      '''Abort if the workspace has uncommitted changes, merges,
 178  179      branches, or has Mq patches applied'''
 179  180  
 180  181      if ws.modified():
 181  182          raise util.Abort('workspace has uncommitted changes')
 182  183      if ws.merged():
 183  184          raise util.Abort('workspace contains uncommitted merge')
 184  185      if ws.branched():
 185  186          raise util.Abort('workspace contains uncommitted branch')
 186  187      if ws.mq_applied():
 187  188          raise util.Abort('workspace has Mq patches applied')
 188  189  
 189  190  
 190  191  #
 191  192  # Adding a reference to WorkSpace from a repo causes a circular reference
 192  193  # repo <-> WorkSpace.
 193  194  #
 194  195  # This prevents repo, WorkSpace and members thereof from being garbage
 195  196  # collected.  Since transactions are aborted when the transaction object
 196  197  # is collected, and localrepo holds a reference to the most recently created
 197  198  # transaction, this prevents transactions from cleanly aborting.
 198  199  #
 199  200  # Instead, we hold the repo->WorkSpace association in a dictionary, breaking
 200  201  # that dependence.
 201  202  #
 202  203  wslist = {}
 203  204  
 204  205  
 205  206  def reposetup(ui, repo):
 206  207      if repo.local() and repo not in wslist:
 207  208          wslist[repo] = WorkSpace(repo)
 208  209  
 209  210          if ui.interactive() and sys.stdin.isatty():
 210  211              ui.setconfig('hooks', 'preoutgoing.cdm_pbconfirm',
 211  212                           'python:hgext_cdm.pbconfirm')
 212  213  
 213  214  
 214  215  def pbconfirm(ui, repo, hooktype, source):
 215  216      def wrapper(settings=None):
 216  217          termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings)
 217  218  
 218  219      if source == 'push':
 219  220          if not yes_no(ui, "Are you sure you wish to push?", False):
 220  221              return 1
 221  222          else:
 222  223              settings = termios.tcgetattr(sys.stdin.fileno())
 223  224              orig = list(settings)
 224  225              atexit.register(wrapper, orig)
 225  226              settings[3] = settings[3] & (~termios.ISIG) # c_lflag
 226  227              termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, settings)
 227  228  
 228  229  
 229  230  def cdm_pdiffs(ui, repo, *pats, **opts):
 230  231      '''diff workspace against its parent
 231  232  
 232  233      Show differences between this workspace and its parent workspace
 233  234      in the same manner as 'hg diff'.
 234  235  
 235  236      For a description of the changeset used to represent the parent
 236  237      workspace, see The Parent in the extension documentation ('hg help
 237  238      cdm').
 238  239      '''
 239  240  
 240  241      act = wslist[repo].active(opts.get('parent'))
 241  242      if not act.revs:
 242  243          return
 243  244  
 244  245      #
 245  246      # If no patterns were specified, either explicitly or via -I or -X
 246  247      # use the active list files to avoid a workspace walk.
 247  248      #
 248  249      if pats or opts.get('include') or opts.get('exclude'):
 249  250          matchfunc = wslist[repo].matcher(pats=pats, opts=opts)
 250  251      else:
 251  252          matchfunc = wslist[repo].matcher(files=act.files())
 252  253  
 253  254      opts = patch.diffopts(ui, opts)
 254  255      diffs = wslist[repo].diff(act.parenttip.node(), act.localtip.node(),
 255  256                                match=matchfunc, opts=opts)
 256  257      if diffs:
 257  258          ui.write(diffs)
 258  259  
 259  260  
 260  261  def cdm_list(ui, repo, **opts):
 261  262      '''list active files (those changed in this workspace)
 262  263  
 263  264      Display a list of files changed in this workspace as compared to
 264  265      its parent workspace.
 265  266  
 266  267      File names are displayed one-per line, grouped by manner in which
 267  268      they changed (added, modified, removed).  Information about
 268  269      renames or copies is output in parentheses following the file
 269  270      name.
 270  271  
 271  272      For a description of the changeset used to represent the parent
 272  273      workspace, see The Parent in the extension documentation ('hg help
 273  274      cdm').
 274  275  
 275  276      Output can be filtered by change type with --added, --modified,
 276  277      and --removed.  By default, all files are shown.
 277  278      '''
 278  279  
 279  280      act = wslist[repo].active(opts['parent'])
 280  281      wanted = set(x for x in ('added', 'modified', 'removed') if opts[x])
 281  282      changes = {}
 282  283  
 283  284      for entry in act:
 284  285          if wanted and (entry.change not in wanted):
 285  286              continue
 286  287  
 287  288          if entry.change not in changes:
 288  289              changes[entry.change] = []
 289  290          changes[entry.change].append(entry)
 290  291  
 291  292      for change in sorted(changes.keys()):
 292  293          ui.write(change + ':\n')
 293  294  
 294  295          for entry in sorted(changes[change]):
 295  296              if entry.is_renamed():
 296  297                  ui.write('\t%s (renamed from %s)\n' % (entry.name,
 297  298                                                        entry.parentname))
 298  299              elif entry.is_copied():
 299  300                  ui.write('\t%s (copied from %s)\n' % (entry.name,
 300  301                                                        entry.parentname))
 301  302              else:
 302  303                  ui.write('\t%s\n' % entry.name)
 303  304  
 304  305  
 305  306  def cdm_bugs(ui, repo, parent=None):
 306  307      '''show all bug IDs referenced in changeset comments'''
 307  308  
 308  309      act = wslist[repo].active(parent)
 309  310  
 310  311      for elt in set(filter(Comments.isBug, act.comments())):
 311  312          ui.write(elt + '\n')
 312  313  
 313  314  
 314  315  def cdm_comments(ui, repo, parent=None):
 315  316      '''show changeset commentary for all active changesets'''
 316  317      act = wslist[repo].active(parent)
 317  318  
 318  319      for elt in act.comments():
 319  320          ui.write(elt + '\n')
 320  321  
 321  322  
 322  323  def cdm_renamed(ui, repo, parent=None):
 323  324      '''show renamed active files
 324  325  
 325  326      Renamed files are shown in the format::
 326  327  
 327  328         new-name old-name
 328  329  
 329  330      One pair per-line.
 330  331      '''
 331  332  
 332  333      act = wslist[repo].active(parent)
 333  334  
 334  335      for entry in sorted(filter(lambda x: x.is_renamed(), act)):
 335  336          ui.write('%s %s\n' % (entry.name, entry.parentname))
 336  337  
 337  338  
 338  339  def cdm_comchk(ui, repo, **opts):
 339  340      '''check active changeset comment formatting
 340  341  
 341  342      Check that active changeset comments conform to O/N rules.
 342  343  
 343  344      Each comment line must contain either one bug or ARC case ID
 344  345      followed by its synopsis, or credit an external contributor.
 345  346      '''
 346  347  
 347  348      active = wslist[repo].active(opts.get('parent'))
 348  349  
 349  350      ui.write('Comments check:\n')
 350  351  
 351  352      check_db = not opts.get('nocheck')
 352  353      return Comments.comchk(active.comments(), check_db=check_db, output=ui)
 353  354  
 354  355  
 355  356  def cdm_cddlchk(ui, repo, *args, **opts):
 356  357      '''check for a valid CDDL header comment in all active files.
 357  358  
 358  359      Check active files for a valid Common Development and Distribution
 359  360      License (CDDL) block comment.
 360  361  
 361  362      Newly added files are checked for a copy of the CDDL header
 362  363      comment.  Modified files are only checked if they contain what
 363  364      appears to be an existing CDDL header comment.
 364  365  
 365  366      Files can be excluded from this check using the cddlchk.NOT file.
 366  367      See NOT Files in the extension documentation ('hg help cdm').
 367  368      '''
 368  369  
 369  370      filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
 370  371      exclude = not_check(repo, 'cddlchk')
 371  372      lenient = True
 372  373      ret = 0
 373  374  
 374  375      ui.write('CDDL block check:\n')
 375  376  
 376  377      for f, e in filelist:
 377  378          if e and e.is_removed():
 378  379              continue
 379  380          elif (e or opts.get('honour_nots')) and exclude(f):
 380  381              ui.status('Skipping %s...\n' % f)
 381  382              continue
 382  383          elif e and e.is_added():
  
    | 
      ↓ open down ↓ | 
    265 lines elided | 
    
      ↑ open up ↑ | 
  
 383  384              lenient = False
 384  385          else:
 385  386              lenient = True
 386  387  
 387  388          fh = open(f, 'r')
 388  389          ret |= Cddl.cddlchk(fh, lenient=lenient, output=ui)
 389  390          fh.close()
 390  391      return ret
 391  392  
 392  393  
      394 +def cdm_manlintchk(ui, repo, *args, **opts):
      395 +    '''check for mandoc lint
      396 +
      397 +    Check for man page formatting errors.
      398 +
      399 +    Files can be excluded from this check using the manlint.NOT
      400 +    file.  See NOT Files in the extension documentation ('hg help
      401 +    cdm').
      402 +    '''
      403 +
      404 +    filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
      405 +    exclude = not_check(repo, 'manlint')
      406 +    ret = 0
      407 +
      408 +    # Man pages are identified as having a suffix starting with a digit.
      409 +    ManfileRE = re.compile(r'.*\.[0-9][a-z]*$', re.IGNORECASE)
      410 +
      411 +    ui.write('Man format check:\n')
      412 +
      413 +    for f, e in filelist:
      414 +        if e and e.is_removed():
      415 +            continue
      416 +        elif (not ManfileRE.match(f)):
      417 +            continue
      418 +        elif (e or opts.get('honour_nots')) and exclude(f):
      419 +            ui.status('Skipping %s...\n' % f)
      420 +            continue
      421 +
      422 +        fh = open(f, 'r')
      423 +        ret |= ManLint.manlint(fh, output=ui, picky=True)
      424 +        fh.close()
      425 +    return ret
      426 +
      427 +
 393  428  def cdm_mapfilechk(ui, repo, *args, **opts):
 394  429      '''check for a valid mapfile header block in active files
 395  430  
 396  431      Check that all link-editor mapfiles contain the standard mapfile
 397  432      header comment directing the reader to the document containing
 398  433      Solaris object versioning rules (README.mapfile).
 399  434  
 400  435      Files can be excluded from this check using the mapfilechk.NOT
 401  436      file.  See NOT Files in the extension documentation ('hg help
 402  437      cdm').
 403  438      '''
 404  439  
 405  440      filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
 406  441      exclude = not_check(repo, 'mapfilechk')
 407  442      ret = 0
 408  443  
 409  444      # We are interested in examining any file that has the following
 410  445      # in its final path segment:
 411  446      #    - Contains the word 'mapfile'
 412  447      #    - Begins with 'map.'
 413  448      #    - Ends with '.map'
 414  449      # We don't want to match unless these things occur in final path segment
 415  450      # because directory names with these strings don't indicate a mapfile.
 416  451      # We also ignore files with suffixes that tell us that the files
 417  452      # are not mapfiles.
 418  453      MapfileRE = re.compile(r'.*((mapfile[^/]*)|(/map\.+[^/]*)|(\.map))$',
 419  454          re.IGNORECASE)
 420  455      NotMapSuffixRE = re.compile(r'.*\.[ch]$', re.IGNORECASE)
 421  456  
 422  457      ui.write('Mapfile comment check:\n')
 423  458  
 424  459      for f, e in filelist:
 425  460          if e and e.is_removed():
 426  461              continue
 427  462          elif (not MapfileRE.match(f)) or NotMapSuffixRE.match(f):
 428  463              continue
 429  464          elif (e or opts.get('honour_nots')) and exclude(f):
 430  465              ui.status('Skipping %s...\n' % f)
 431  466              continue
 432  467  
 433  468          fh = open(f, 'r')
 434  469          ret |= Mapfile.mapfilechk(fh, output=ui)
 435  470          fh.close()
 436  471      return ret
 437  472  
 438  473  
 439  474  def cdm_copyright(ui, repo, *args, **opts):
 440  475      '''check each active file for a current and correct copyright notice
 441  476  
 442  477      Check that all active files have a correctly formed copyright
 443  478      notice containing the current year.
 444  479  
 445  480      See the Non-Formatting Considerations section of the OpenSolaris
 446  481      Developer's Reference for more info on the correct form of
 447  482      copyright notice.
 448  483      (http://hub.opensolaris.org/bin/view/Community+Group+on/devref_7#H723NonFormattingConsiderations)
 449  484  
 450  485      Files can be excluded from this check using the copyright.NOT file.
 451  486      See NOT Files in the extension documentation ('hg help cdm').
 452  487      '''
 453  488  
 454  489      filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
 455  490      exclude = not_check(repo, 'copyright')
 456  491      ret = 0
 457  492  
 458  493      ui.write('Copyright check:\n')
 459  494  
 460  495      for f, e in filelist:
 461  496          if e and e.is_removed():
 462  497              continue
 463  498          elif (e or opts.get('honour_nots')) and exclude(f):
 464  499              ui.status('Skipping %s...\n' % f)
 465  500              continue
 466  501  
 467  502          fh = open(f, 'r')
 468  503          ret |= Copyright.copyright(fh, output=ui)
 469  504          fh.close()
 470  505      return ret
 471  506  
 472  507  
 473  508  def cdm_hdrchk(ui, repo, *args, **opts):
 474  509      '''check active C header files conform to the O/N header rules
 475  510  
 476  511      Check that any added or modified C header files conform to the O/N
 477  512      header rules.
 478  513  
 479  514      See the section 'HEADER STANDARDS' in the hdrchk(1) manual page
 480  515      for more information on the rules for O/N header file formatting.
 481  516  
 482  517      Files can be excluded from this check using the hdrchk.NOT file.
 483  518      See NOT Files in the extension documentation ('hg help cdm').
 484  519      '''
 485  520  
 486  521      filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
 487  522      exclude = not_check(repo, 'hdrchk')
 488  523      ret = 0
 489  524  
 490  525      ui.write('Header format check:\n')
 491  526  
 492  527      for f, e in filelist:
 493  528          if e and e.is_removed():
 494  529              continue
 495  530          elif not f.endswith('.h'):
 496  531              continue
 497  532          elif (e or opts.get('honour_nots')) and exclude(f):
 498  533              ui.status('Skipping %s...\n' % f)
 499  534              continue
 500  535  
 501  536          fh = open(f, 'r')
 502  537          ret |= HdrChk.hdrchk(fh, lenient=True, output=ui)
 503  538          fh.close()
 504  539      return ret
 505  540  
 506  541  
 507  542  def cdm_cstyle(ui, repo, *args, **opts):
 508  543      '''check active C source files conform to the C Style Guide
 509  544  
 510  545      Check that any added or modified C source file conform to the C
 511  546      Style Guide.
 512  547  
 513  548      See the C Style Guide for more information about correct C source
 514  549      formatting.
 515  550      (http://hub.opensolaris.org/bin/download/Community+Group+on/WebHome/cstyle.ms.pdf)
 516  551  
 517  552      Files can be excluded from this check using the cstyle.NOT file.
 518  553      See NOT Files in the extension documentation ('hg help cdm').
 519  554      '''
 520  555  
 521  556      filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
 522  557      exclude = not_check(repo, 'cstyle')
 523  558      ret = 0
 524  559  
 525  560      ui.write('C style check:\n')
 526  561  
 527  562      for f, e in filelist:
 528  563          if e and e.is_removed():
 529  564              continue
 530  565          elif not (f.endswith('.c') or f.endswith('.h')):
 531  566              continue
 532  567          elif (e or opts.get('honour_nots')) and exclude(f):
 533  568              ui.status('Skipping %s...\n' % f)
 534  569              continue
 535  570  
 536  571          fh = open(f, 'r')
 537  572          ret |= CStyle.cstyle(fh, output=ui,
 538  573                               picky=True, check_posix_types=True,
 539  574                               check_continuation=True)
 540  575          fh.close()
 541  576      return ret
 542  577  
 543  578  
 544  579  def cdm_jstyle(ui, repo, *args, **opts):
 545  580      '''check active Java source files for common stylistic errors
 546  581  
 547  582      Files can be excluded from this check using the jstyle.NOT file.
 548  583      See NOT Files in the extension documentation ('hg help cdm').
 549  584      '''
 550  585  
 551  586      filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
 552  587      exclude = not_check(repo, 'jstyle')
 553  588      ret = 0
 554  589  
 555  590      ui.write('Java style check:\n')
 556  591  
 557  592      for f, e in filelist:
 558  593          if e and e.is_removed():
 559  594              continue
 560  595          elif not f.endswith('.java'):
 561  596              continue
 562  597          elif (e or opts.get('honour_nots')) and exclude(f):
 563  598              ui.status('Skipping %s...\n' % f)
 564  599              continue
 565  600  
 566  601          fh = open(f, 'r')
 567  602          ret |= JStyle.jstyle(fh, output=ui, picky=True)
 568  603          fh.close()
 569  604      return ret
 570  605  
 571  606  
 572  607  def cdm_permchk(ui, repo, *args, **opts):
 573  608      '''check the permissions of each active file
 574  609  
 575  610      Check that the file permissions of each added or modified file do not
 576  611      contain the executable bit.
 577  612  
 578  613      Files can be excluded from this check using the permchk.NOT file.
 579  614      See NOT Files in the extension documentation ('hg help cdm').
 580  615      '''
 581  616  
 582  617      filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
 583  618      exclude = not_check(repo, 'permchk')
 584  619      exeFiles = []
 585  620  
 586  621      ui.write('File permission check:\n')
 587  622  
 588  623      for f, e in filelist:
 589  624          if e and e.is_removed():
 590  625              continue
 591  626          elif (e or opts.get('honour_nots')) and exclude(f):
 592  627              ui.status('Skipping %s...\n' % f)
 593  628              continue
 594  629  
 595  630          mode = stat.S_IMODE(os.stat(f)[stat.ST_MODE])
 596  631          if mode & stat.S_IEXEC:
 597  632              exeFiles.append(f)
 598  633  
 599  634      if len(exeFiles) > 0:
 600  635          ui.write('Warning: the following active file(s) have executable mode '
 601  636              '(+x) permission set,\nremove unless intentional:\n')
 602  637          for fname in exeFiles:
 603  638              ui.write("  %s\n" % fname)
 604  639  
 605  640      return len(exeFiles) > 0
 606  641  
 607  642  
 608  643  def cdm_tagchk(ui, repo, **opts):
 609  644      '''check modification of workspace tags
 610  645  
 611  646      Check for any modification of the repository's .hgtags file.
 612  647  
 613  648      With the exception of the gatekeepers, nobody should introduce or
 614  649      modify a repository's tags.
 615  650      '''
 616  651  
 617  652      active = wslist[repo].active(opts.get('parent'))
 618  653  
 619  654      ui.write('Checking for new tags:\n')
 620  655  
 621  656      if ".hgtags" in active:
 622  657          tfile = wslist[repo].filepath('.hgtags')
 623  658          ptip = active.parenttip.rev()
 624  659  
 625  660          ui.write('Warning: Workspace contains new non-local tags.\n'
 626  661                   'Only gatekeepers should add or modify such tags.\n'
 627  662                   'Use the following commands to revert these changes:\n'
 628  663                   '  hg revert -r%d %s\n'
 629  664                   '  hg commit %s\n'
 630  665                   'You should also recommit before integration\n' %
 631  666                   (ptip, tfile, tfile))
 632  667  
 633  668          return 1
 634  669  
 635  670      return 0
 636  671  
 637  672  
 638  673  def cdm_branchchk(ui, repo, **opts):
 639  674      '''check for changes in number or name of branches
 640  675  
 641  676      Check that the workspace contains only a single head, that it is
 642  677      on the branch 'default', and that no new branches have been
 643  678      introduced.
 644  679      '''
 645  680  
 646  681      ui.write('Checking for multiple heads (or branches):\n')
 647  682  
 648  683      heads = set(repo.heads())
 649  684      parents = set([x.node() for x in wslist[repo].workingctx().parents()])
 650  685  
 651  686      #
 652  687      # We care if there's more than one head, and those heads aren't
 653  688      # identical to the dirstate parents (if they are identical, it's
 654  689      # an uncommitted merge which mergechk will catch, no need to
 655  690      # complain twice).
 656  691      #
 657  692      if len(heads) > 1 and heads != parents:
 658  693          ui.write('Workspace has multiple heads (or branches):\n')
 659  694          for head in [repo.changectx(head) for head in heads]:
 660  695              ui.write("  %d:%s\t%s\n" %
 661  696                  (head.rev(), str(head), head.description().splitlines()[0]))
 662  697          ui.write('You must merge and recommit.\n')
 663  698          return 1
 664  699  
 665  700      ui.write('\nChecking for branch changes:\n')
 666  701  
 667  702      if repo.dirstate.branch() != 'default':
 668  703          ui.write("Warning: Workspace tip has named branch: '%s'\n"
 669  704                   "Only gatekeepers should push new branches.\n"
 670  705                   "Use the following commands to restore the branch name:\n"
 671  706                   "  hg branch [-f] default\n"
 672  707                   "  hg commit\n"
 673  708                   "You should also recommit before integration\n" %
 674  709                   (repo.dirstate.branch()))
 675  710          return 1
 676  711  
 677  712      branches = repo.branchtags().keys()
 678  713      if len(branches) > 1:
 679  714          ui.write('Warning: Workspace has named branches:\n')
 680  715          for t in branches:
 681  716              if t == 'default':
 682  717                  continue
 683  718              ui.write("\t%s\n" % t)
 684  719  
 685  720          ui.write("Only gatekeepers should push new branches.\n"
 686  721                   "Use the following commands to remove extraneous branches.\n"
 687  722                   "  hg branch [-f] default\n"
 688  723                   "  hg commit"
 689  724                   "You should also recommit before integration\n")
 690  725          return 1
 691  726  
 692  727      return 0
 693  728  
 694  729  
 695  730  def cdm_keywords(ui, repo, *args, **opts):
 696  731      '''check active files for SCCS keywords
 697  732  
 698  733      Check that any added or modified files do not contain SCCS keywords
 699  734      (#ident lines, etc.).
 700  735  
 701  736      Files can be excluded from this check using the keywords.NOT file.
 702  737      See NOT Files in the extension documentation ('hg help cdm').
 703  738      '''
 704  739  
 705  740      filelist = buildfilelist(wslist[repo], opts.get('parent'), args)
 706  741      exclude = not_check(repo, 'keywords')
 707  742      ret = 0
 708  743  
 709  744      ui.write('Keywords check:\n')
 710  745  
 711  746      for f, e in filelist:
 712  747          if e and e.is_removed():
 713  748              continue
 714  749          elif (e or opts.get('honour_nots')) and exclude(f):
 715  750              ui.status('Skipping %s...\n' % f)
 716  751              continue
 717  752  
 718  753          fh = open(f, 'r')
 719  754          ret |= Keywords.keywords(fh, output=ui)
 720  755          fh.close()
 721  756      return ret
 722  757  
 723  758  
 724  759  #
 725  760  # NB:
 726  761  #    There's no reason to hook this up as an invokable command, since
 727  762  #    we have 'hg status', but it must accept the same arguments.
 728  763  #
 729  764  def cdm_outchk(ui, repo, **opts):
 730  765      '''Warn the user if they have uncommitted changes'''
 731  766  
 732  767      ui.write('Checking for uncommitted changes:\n')
 733  768  
 734  769      st = wslist[repo].modified()
 735  770      if st:
 736  771          ui.write('Warning: the following files have uncommitted changes:\n')
 737  772          for elt in st:
 738  773              ui.write('   %s\n' % elt)
 739  774          return 1
 740  775      return 0
 741  776  
 742  777  
 743  778  def cdm_mergechk(ui, repo, **opts):
 744  779      '''Warn the user if their workspace contains merges'''
 745  780  
 746  781      active = wslist[repo].active(opts.get('parent'))
 747  782  
 748  783      ui.write('Checking for merges:\n')
 749  784  
 750  785      merges = filter(lambda x: len(x.parents()) == 2 and x.parents()[1],
 751  786                     active.revs)
 752  787  
 753  788      if merges:
 754  789          ui.write('Workspace contains the following merges:\n')
 755  790          for rev in merges:
 756  791              desc = rev.description().splitlines()
 757  792              ui.write('  %s:%s\t%s\n' %
 758  793                       (rev.rev() or "working", str(rev),
 759  794                        desc and desc[0] or "*** uncommitted change ***"))
 760  795          return 1
 761  796      return 0
 762  797  
 763  798  
 764  799  def run_checks(ws, cmds, *args, **opts):
 765  800      '''Run CMDS (with OPTS) over active files in WS'''
 766  801  
 767  802      ret = 0
 768  803  
 769  804      for cmd in cmds:
 770  805          name = cmd.func_name.split('_')[1]
 771  806          if not ws.ui.configbool('cdm', name, True):
 772  807              ws.ui.status('Skipping %s check...\n' % name)
 773  808          else:
 774  809              ws.ui.pushbuffer()
 775  810              result = cmd(ws.ui, ws.repo, honour_nots=True, *args, **opts)
 776  811              output = ws.ui.popbuffer()
 777  812  
 778  813              ret |= result
 779  814  
 780  815              if not ws.ui.quiet or result != 0:
 781  816                  ws.ui.write(output, '\n')
 782  817      return ret
 783  818  
 784  819  
 785  820  def cdm_nits(ui, repo, *args, **opts):
 786  821      '''check for stylistic nits in active files
  
    | 
      ↓ open down ↓ | 
    384 lines elided | 
    
      ↑ open up ↑ | 
  
 787  822  
 788  823      Check each active file for basic stylistic errors.
 789  824  
 790  825      The following checks are run over each active file (see 'hg help
 791  826      <check>' for more information about each):
 792  827  
 793  828        - copyright  (copyright statements)
 794  829        - cstyle     (C source style)
 795  830        - hdrchk     (C header style)
 796  831        - jstyle     (java source style)
      832 +      - manlint    (man page formatting)
 797  833        - mapfilechk (link-editor mapfiles)
 798  834        - permchk    (file permissions)
 799  835        - keywords   (SCCS keywords)
 800  836  
 801  837      With the global -q/--quiet option, only provide output for those
 802  838      checks which fail.
 803  839      '''
 804  840  
 805  841      cmds = [cdm_copyright,
 806  842          cdm_cstyle,
 807  843          cdm_hdrchk,
 808  844          cdm_jstyle,
      845 +        cmd_manlintchk,
 809  846          cdm_mapfilechk,
 810  847          cdm_permchk,
 811  848          cdm_keywords]
 812  849  
 813  850      return run_checks(wslist[repo], cmds, *args, **opts)
 814  851  
 815  852  
 816  853  def cdm_pbchk(ui, repo, **opts):
 817  854      '''run pre-integration checks on this workspace
 818  855  
 819  856      Check this workspace for common errors prior to integration.
 820  857  
  
    | 
      ↓ open down ↓ | 
    2 lines elided | 
    
      ↑ open up ↑ | 
  
 821  858      The following checks are run over the active list (see 'hg help
 822  859      <check>' for more information about each):
 823  860  
 824  861        - branchchk  (addition/modification of branches)
 825  862        - comchk     (changeset descriptions)
 826  863        - copyright  (copyright statements)
 827  864        - cstyle     (C source style)
 828  865        - hdrchk     (C header style)
 829  866        - jstyle     (java source style)
 830  867        - keywords   (SCCS keywords)
      868 +      - manlint    (man page formatting)
 831  869        - mapfilechk (link-editor mapfiles)
 832  870        - permchk    (file permissions)
 833  871        - tagchk     (addition/modification of tags)
 834  872  
 835  873      Additionally, the workspace is checked for outgoing merges (which
 836  874      should be removed with 'hg recommit'), and uncommitted changes.
 837  875  
 838  876      With the global -q/--quiet option, only provide output for those
 839  877      checks which fail.
 840  878      '''
 841  879  
 842  880      #
 843  881      # The current ordering of these is that the commands from cdm_nits
 844  882      # run first in the same order as they would in cdm_nits, then the
 845  883      # pbchk specifics are run.
 846  884      #
 847  885      cmds = [cdm_copyright,
 848  886          cdm_cstyle,
 849  887          cdm_hdrchk,
 850  888          cdm_jstyle,
      889 +        cdm_manlintchk,
 851  890          cdm_mapfilechk,
 852  891          cdm_permchk,
 853  892          cdm_keywords,
 854  893          cdm_comchk,
 855  894          cdm_tagchk,
 856  895          cdm_branchchk,
 857  896          cdm_outchk,
 858  897          cdm_mergechk]
 859  898  
 860  899      return run_checks(wslist[repo], cmds, **opts)
 861  900  
 862  901  
 863  902  def cdm_recommit(ui, repo, **opts):
 864  903      '''replace outgoing changesets with a single equivalent changeset
 865  904  
 866  905      Replace all outgoing changesets with a single changeset containing
 867  906      equivalent changes.  This removes uninteresting changesets created
 868  907      during development that would only serve as noise in the gate.
 869  908  
 870  909      Any changed file that is now identical in content to that in the
 871  910      parent workspace (whether identical in history or otherwise) will
 872  911      not be included in the new changeset.  Any merges information will
 873  912      also be removed.
 874  913  
 875  914      If no files are changed in comparison to the parent workspace, the
 876  915      outgoing changesets will be removed, but no new changeset created.
 877  916  
 878  917      recommit will refuse to run if the workspace contains more than
 879  918      one outgoing head, even if those heads are on the same branch.  To
 880  919      recommit with only one branch containing outgoing changesets, your
 881  920      workspace must be on that branch and at that branch head.
 882  921  
 883  922      recommit will prompt you to take a backup if your workspace has
 884  923      been changed since the last backup was taken.  In almost all
 885  924      cases, you should allow it to take one (the default).
 886  925  
 887  926      recommit cannot be run if the workspace contains any uncommitted
 888  927      changes, applied Mq patches, or has multiple outgoing heads (or
 889  928      branches).
 890  929      '''
 891  930  
 892  931      ws = wslist[repo]
 893  932  
 894  933      if not os.getcwd().startswith(repo.root):
 895  934          raise util.Abort('recommit is not safe to run with -R')
 896  935  
 897  936      abort_if_dirty(ws)
 898  937  
 899  938      wlock = repo.wlock()
 900  939      lock = repo.lock()
 901  940  
 902  941      try:
 903  942          parent = ws.parent(opts['parent'])
 904  943          between = repo.changelog.nodesbetween(ws.findoutgoing(parent))[2]
 905  944          heads = set(between) & set(repo.heads())
 906  945  
 907  946          if len(heads) > 1:
 908  947              ui.warn('Workspace has multiple outgoing heads (or branches):\n')
 909  948              for head in sorted(map(repo.changelog.rev, heads), reverse=True):
 910  949                  ui.warn('\t%d\n' % head)
 911  950              raise util.Abort('you must merge before recommitting')
 912  951  
 913  952          #
 914  953          # We can safely use the worklist here, as we know (from the
 915  954          # abort_if_dirty() check above) that the working copy has not been
 916  955          # modified.
 917  956          #
 918  957          active = ws.active(parent)
 919  958  
 920  959          if filter(lambda b: len(b.parents()) > 1, active.bases()):
 921  960              raise util.Abort('Cannot recommit a merge of two non-outgoing '
 922  961                               'changesets')
 923  962  
 924  963          if len(active.revs) <= 0:
 925  964              raise util.Abort("no changes to recommit")
 926  965  
 927  966          if len(active.files()) <= 0:
 928  967              ui.warn("Recommitting %d active changesets, but no active files\n" %
 929  968                      len(active.revs))
 930  969  
 931  970          #
 932  971          # During the course of a recommit, any file bearing a name
 933  972          # matching the source name of any renamed file will be
 934  973          # clobbered by the operation.
 935  974          #
 936  975          # As such, we ask the user before proceeding.
 937  976          #
 938  977          bogosity = [f.parentname for f in active if f.is_renamed() and
 939  978                      os.path.exists(repo.wjoin(f.parentname))]
 940  979          if bogosity:
 941  980              ui.warn("The following file names are the original name of a "
 942  981                      "rename and also present\n"
 943  982                      "in the working directory:\n")
 944  983  
 945  984              for fname in bogosity:
 946  985                  ui.warn("  %s\n" % fname)
 947  986  
 948  987              if not yes_no(ui, "These files will be removed by recommit."
 949  988                            "  Continue?",
 950  989                            False):
 951  990                  raise util.Abort("recommit would clobber files")
 952  991  
 953  992          user = opts['user'] or ui.username()
 954  993          comments = '\n'.join(active.comments())
 955  994  
 956  995          message = cmdutil.logmessage(opts) or ui.edit(comments, user)
 957  996          if not message:
 958  997              raise util.Abort('empty commit message')
 959  998  
 960  999          bk = CdmBackup(ui, ws, backup_name(repo.root))
 961 1000          if bk.need_backup():
 962 1001              if yes_no(ui, 'Do you want to backup files first?', True):
 963 1002                  bk.backup()
 964 1003  
 965 1004          oldtags = repo.tags()
 966 1005          clearedtags = [(name, nd, repo.changelog.rev(nd), local)
 967 1006                  for name, nd, local in active.tags()]
 968 1007  
 969 1008          ws.squishdeltas(active, message, user=user)
 970 1009      finally:
 971 1010          lock.release()
 972 1011          wlock.release()
 973 1012  
 974 1013      if clearedtags:
 975 1014          ui.write("Removed tags:\n")
 976 1015          for name, nd, rev, local in sorted(clearedtags,
 977 1016                                             key=lambda x: x[0].lower()):
 978 1017              ui.write("  %5s:%s:\t%s%s\n" % (rev, node.short(nd),
 979 1018                                              name, (local and ' (local)' or '')))
 980 1019  
 981 1020          for ntag, nnode in sorted(repo.tags().items(),
 982 1021                                    key=lambda x: x[0].lower()):
 983 1022              if ntag in oldtags and ntag != "tip":
 984 1023                  if oldtags[ntag] != nnode:
 985 1024                      ui.write("tag '%s' now refers to revision %d:%s\n" %
 986 1025                               (ntag, repo.changelog.rev(nnode),
 987 1026                                node.short(nnode)))
 988 1027  
 989 1028  
 990 1029  def do_eval(cmd, files, root, changedir=True):
 991 1030      if not changedir:
 992 1031          os.chdir(root)
 993 1032  
 994 1033      for path in sorted(files):
 995 1034          dirn, base = os.path.split(path)
 996 1035  
 997 1036          if changedir:
 998 1037              os.chdir(os.path.join(root, dirn))
 999 1038  
1000 1039          os.putenv('workspace', root)
1001 1040          os.putenv('filepath', path)
1002 1041          os.putenv('dir', dirn)
1003 1042          os.putenv('file', base)
1004 1043          os.system(cmd)
1005 1044  
1006 1045  
1007 1046  def cdm_eval(ui, repo, *command, **opts):
1008 1047      '''run specified command for each active file
1009 1048  
1010 1049      Run the command specified on the command line for each active
1011 1050      file, with the following variables present in the environment:
1012 1051  
1013 1052        :$file:      -  active file basename.
1014 1053        :$dir:       -  active file dirname.
1015 1054        :$filepath:  -  path from workspace root to active file.
1016 1055        :$workspace: -  full path to workspace root.
1017 1056  
1018 1057      For example:
1019 1058  
1020 1059        hg eval 'echo $dir; hg log -l3 $file'
1021 1060  
1022 1061      will show the last the 3 log entries for each active file,
1023 1062      preceded by its directory.
1024 1063      '''
1025 1064  
1026 1065      act = wslist[repo].active(opts['parent'])
1027 1066      cmd = ' '.join(command)
1028 1067      files = [x.name for x in act if not x.is_removed()]
1029 1068  
1030 1069      do_eval(cmd, files, repo.root, not opts['remain'])
1031 1070  
1032 1071  
1033 1072  def cdm_apply(ui, repo, *command, **opts):
1034 1073      '''apply specified command to all active files
1035 1074  
1036 1075      Run the command specified on the command line over each active
1037 1076      file.
1038 1077  
1039 1078      For example 'hg apply "wc -l"' will output a count of the lines in
1040 1079      each active file.
1041 1080      '''
1042 1081  
1043 1082      act = wslist[repo].active(opts['parent'])
1044 1083  
1045 1084      if opts['remain']:
1046 1085          appnd = ' $filepath'
1047 1086      else:
1048 1087          appnd = ' $file'
1049 1088  
1050 1089      cmd = ' '.join(command) + appnd
1051 1090      files = [x.name for x in act if not x.is_removed()]
1052 1091  
1053 1092      do_eval(cmd, files, repo.root, not opts['remain'])
1054 1093  
1055 1094  
1056 1095  def cdm_reparent(ui, repo, parent):
1057 1096      '''reparent your workspace
1058 1097  
1059 1098      Update the 'default' path alias that is used as the default source
1060 1099      for 'hg pull' and the default destination for 'hg push' (unless
1061 1100      there is a 'default-push' alias).  This is also the path all
1062 1101      Cadmium commands treat as your parent workspace.
1063 1102      '''
1064 1103  
1065 1104      def append_new_parent(parent):
1066 1105          fp = None
1067 1106          try:
1068 1107              fp = repo.opener('hgrc', 'a', atomictemp=True)
1069 1108              if fp.tell() != 0:
1070 1109                  fp.write('\n')
1071 1110              fp.write('[paths]\n'
1072 1111                       'default = %s\n\n' % parent)
1073 1112              fp.rename()
1074 1113          finally:
1075 1114              if fp and not fp.closed:
1076 1115                  fp.close()
1077 1116  
1078 1117      def update_parent(path, line, parent):
1079 1118          line = line - 1 # The line number we're passed will be 1-based
1080 1119          fp = None
1081 1120  
1082 1121          try:
1083 1122              fp = open(path)
1084 1123              data = fp.readlines()
1085 1124          finally:
1086 1125              if fp and not fp.closed:
1087 1126                  fp.close()
1088 1127  
1089 1128          #
1090 1129          # line will be the last line of any continued block, go back
1091 1130          # to the first removing the continuation as we go.
1092 1131          #
1093 1132          while data[line][0].isspace():
1094 1133              data.pop(line)
1095 1134              line -= 1
1096 1135  
1097 1136          assert data[line].startswith('default')
1098 1137  
1099 1138          data[line] = "default = %s\n" % parent
1100 1139          if data[-1] != '\n':
1101 1140              data.append('\n')
1102 1141  
1103 1142          try:
1104 1143              fp = util.atomictempfile(path, 'w', 0644)
1105 1144              fp.writelines(data)
1106 1145              fp.rename()
1107 1146          finally:
1108 1147              if fp and not fp.closed:
1109 1148                  fp.close()
1110 1149  
1111 1150      from mercurial import config
1112 1151      parent = ui.expandpath(parent)
1113 1152  
1114 1153      if not os.path.exists(repo.join('hgrc')):
1115 1154          append_new_parent(parent)
1116 1155          return
1117 1156  
1118 1157      cfg = config.config()
1119 1158      cfg.read(repo.join('hgrc'))
1120 1159      source = cfg.source('paths', 'default')
1121 1160  
1122 1161      if not source:
1123 1162          append_new_parent(parent)
1124 1163          return
1125 1164      else:
1126 1165          path, target = source.rsplit(':', 1)
1127 1166  
1128 1167          if path != repo.join('hgrc'):
1129 1168              raise util.Abort("Cannot edit path specification not in repo hgrc\n"
1130 1169                               "default path is from: %s" % source)
1131 1170  
1132 1171          update_parent(path, int(target), parent)
1133 1172  
1134 1173  
1135 1174  def backup_name(fullpath):
1136 1175      '''Create a backup directory name based on the specified path.
1137 1176  
1138 1177      In most cases this is the basename of the path specified, but
1139 1178      certain cases are handled specially to create meaningful names'''
1140 1179  
1141 1180      special = ['usr/closed']
1142 1181  
1143 1182      fullpath = fullpath.rstrip(os.path.sep).split(os.path.sep)
1144 1183  
1145 1184      #
1146 1185      # If a path is 'special', we append the basename of the path to
1147 1186      # the path element preceding the constant, special, part.
1148 1187      #
1149 1188      # Such that for instance:
1150 1189      #     /foo/bar/onnv-fixes/usr/closed
1151 1190      #  has a backup name of:
1152 1191      #     onnv-fixes-closed
1153 1192      #
1154 1193      for elt in special:
1155 1194          elt = elt.split(os.path.sep)
1156 1195          pathpos = len(elt)
1157 1196  
1158 1197          if fullpath[-pathpos:] == elt:
1159 1198              return "%s-%s" % (fullpath[-pathpos - 1], elt[-1])
1160 1199      else:
1161 1200          return fullpath[-1]
1162 1201  
1163 1202  
1164 1203  def cdm_backup(ui, repo, if_newer=False):
1165 1204      '''backup workspace changes and metadata
1166 1205  
1167 1206      Create a backup copy of changes made in this workspace as compared
1168 1207      to its parent workspace, as well as important metadata of this
1169 1208      workspace.
1170 1209  
1171 1210      NOTE: Only changes as compared to the parent workspace are backed
1172 1211      up.  If you lose this workspace and its parent, you will not be
1173 1212      able to restore a backup into a clone of the grandparent
1174 1213      workspace.
1175 1214  
1176 1215      By default, backups are stored in the cdm.backup/ directory in
1177 1216      your home directory.  This is configurable using the cdm.backupdir
1178 1217      configuration variable, for example:
1179 1218  
1180 1219        hg backup --config cdm.backupdir=/net/foo/backups
1181 1220  
1182 1221      or place the following in an appropriate hgrc file::
1183 1222  
1184 1223        [cdm]
1185 1224        backupdir = /net/foo/backups
1186 1225  
1187 1226      Backups have the same name as the workspace in which they were
1188 1227      taken, with '-closed' appended in the case of O/N's usr/closed.
1189 1228      '''
1190 1229  
1191 1230      name = backup_name(repo.root)
1192 1231      bk = CdmBackup(ui, wslist[repo], name)
1193 1232  
1194 1233      wlock = repo.wlock()
1195 1234      lock = repo.lock()
1196 1235  
1197 1236      try:
1198 1237          if if_newer and not bk.need_backup():
1199 1238              ui.status('backup is up-to-date\n')
1200 1239          else:
1201 1240              bk.backup()
1202 1241      finally:
1203 1242          lock.release()
1204 1243          wlock.release()
1205 1244  
1206 1245  
1207 1246  def cdm_restore(ui, repo, backup, **opts):
1208 1247      '''restore workspace from backup
1209 1248  
1210 1249      Restore this workspace from a backup (taken by 'hg backup').
1211 1250  
1212 1251      If the specified backup directory does not exist, it is assumed to
1213 1252      be relative to the cadmium backup directory (~/cdm.backup/ by
1214 1253      default).
1215 1254  
1216 1255      For example::
1217 1256  
1218 1257        % hg restore on-rfe - Restore the latest backup of ~/cdm.backup/on-rfe
1219 1258        % hg restore -g3 on-rfe - Restore the 3rd backup of ~/cdm.backup/on-rfe
1220 1259        % hg restore /net/foo/backup/on-rfe - Restore from an explicit path
1221 1260      '''
1222 1261  
1223 1262      if not os.getcwd().startswith(repo.root):
1224 1263          raise util.Abort('restore is not safe to run with -R')
1225 1264  
1226 1265      abort_if_dirty(wslist[repo])
1227 1266  
1228 1267      if opts['generation']:
1229 1268          gen = int(opts['generation'])
1230 1269      else:
1231 1270          gen = None
1232 1271  
1233 1272      if os.path.exists(backup):
1234 1273          backup = os.path.abspath(backup)
1235 1274  
1236 1275      wlock = repo.wlock()
1237 1276      lock = repo.lock()
1238 1277  
1239 1278      try:
1240 1279          bk = CdmBackup(ui, wslist[repo], backup)
1241 1280          bk.restore(gen)
1242 1281      finally:
1243 1282          lock.release()
1244 1283          wlock.release()
1245 1284  
1246 1285  
1247 1286  def cdm_webrev(ui, repo, **opts):
1248 1287      '''generate web-based code review and optionally upload it
1249 1288  
1250 1289      Generate a web-based code review using webrev(1) and optionally
1251 1290      upload it.  All known arguments are passed through to webrev(1).
1252 1291      '''
1253 1292  
1254 1293      webrev_args = ""
1255 1294      for key in opts.keys():
1256 1295          if opts[key]:
1257 1296              if type(opts[key]) == type(True):
1258 1297                  webrev_args += '-' + key + ' '
1259 1298              else:
1260 1299                  webrev_args += '-' + key + ' ' + opts[key] + ' '
1261 1300  
1262 1301      retval = os.system('webrev ' + webrev_args)
1263 1302      if retval != 0:
1264 1303          return retval - 255
1265 1304  
1266 1305      return 0
1267 1306  
1268 1307  
1269 1308  def cdm_debugcdmal(ui, repo, *pats, **opts):
1270 1309      '''dump the active list for the sake of debugging/testing'''
1271 1310  
1272 1311      ui.write(wslist[repo].active(opts['parent']).as_text(pats))
1273 1312  
1274 1313  
1275 1314  def cdm_changed(ui, repo, *pats, **opts):
1276 1315      '''mark a file as changed in the working copy
1277 1316  
1278 1317      Maintain a list of files checked for modification in the working
1279 1318      copy.  If the list exists, most cadmium commands will only check
1280 1319      the working copy for changes to those files, rather than checking
1281 1320      the whole workspace (this does not apply to committed changes,
1282 1321      which are always seen).
1283 1322  
1284 1323      Since this list functions only as a hint as to where in the
1285 1324      working copy to look for changes, entries that have not actually
1286 1325      been modified (in the working copy, or in general) are not
1287 1326      problematic.
1288 1327  
1289 1328  
1290 1329      Note: If such a list exists, it must be kept up-to-date.
1291 1330  
1292 1331  
1293 1332      Renamed files can be added with reference only to their new name:
1294 1333        $ hg mv foo bar
1295 1334        $ hg changed bar
1296 1335  
1297 1336      Without arguments, 'hg changed' will list all files recorded as
1298 1337      altered, such that, for instance:
1299 1338        $ hg status $(hg changed)
1300 1339        $ hg diff $(hg changed)
1301 1340      Become useful (generally faster than their unadorned counterparts)
1302 1341  
1303 1342      To create an initially empty list:
1304 1343        $ hg changed -i
1305 1344      Until files are added to the list it is equivalent to saying
1306 1345      "Nothing has been changed"
1307 1346  
1308 1347      Update the list based on the current active list:
1309 1348        $ hg changed -u
1310 1349      The old list is emptied, and replaced with paths from the
1311 1350      current active list.
1312 1351  
1313 1352      Remove the list entirely:
1314 1353        $ hg changed -d
1315 1354      '''
1316 1355  
1317 1356      def modded_files(repo, parent):
1318 1357          out = wslist[repo].findoutgoing(wslist[repo].parent(parent))
1319 1358          outnodes = repo.changelog.nodesbetween(out)[0]
1320 1359  
1321 1360          files = set()
1322 1361          for n in outnodes:
1323 1362              files.update(repo.changectx(n).files())
1324 1363  
1325 1364          files.update(wslist[repo].status().keys())
1326 1365          return files
1327 1366  
1328 1367      #
1329 1368      # specced_pats is convenient to treat as a boolean indicating
1330 1369      # whether any file patterns or paths were specified.
1331 1370      #
1332 1371      specced_pats = pats or opts['include'] or opts['exclude']
1333 1372      if len(filter(None, [opts['delete'], opts['update'], opts['init'],
1334 1373                           specced_pats])) > 1:
1335 1374          raise util.Abort("-d, -u, -i and patterns are mutually exclusive")
1336 1375  
1337 1376      wl = WorkList(wslist[repo])
1338 1377  
1339 1378      if (not wl and specced_pats) or opts['init']:
1340 1379          wl.delete()
1341 1380          if yes_no(ui, "Create a list based on your changes thus far?", True):
1342 1381              map(wl.add, modded_files(repo, opts.get('parent')))
1343 1382  
1344 1383      if opts['delete']:
1345 1384          wl.delete()
1346 1385      elif opts['update']:
1347 1386          wl.delete()
1348 1387          map(wl.add, modded_files(repo, opts.get('parent')))
1349 1388          wl.write()
1350 1389      elif opts['init']:       # Any possible old list was deleted above
1351 1390          wl.write()
1352 1391      elif specced_pats:
1353 1392          sources = []
1354 1393  
1355 1394          match = wslist[repo].matcher(pats=pats, opts=opts)
1356 1395          for abso in repo.walk(match):
1357 1396              if abso in repo.dirstate:
1358 1397                  wl.add(abso)
1359 1398                  #
1360 1399                  # Store the source name of any copy.  We use this so
1361 1400                  # both the add and delete of a rename can be entered
1362 1401                  # into the WorkList with only the destination name
1363 1402                  # explicitly being mentioned.
1364 1403                  #
1365 1404                  fctx = wslist[repo].workingctx().filectx(abso)
1366 1405                  rn = fctx.renamed()
1367 1406                  if rn:
1368 1407                      sources.append(rn[0])
1369 1408              else:
1370 1409                  ui.warn("%s is not version controlled -- skipping\n" %
1371 1410                          match.rel(abso))
1372 1411  
1373 1412          if sources:
1374 1413              for fname, chng in wslist[repo].status(files=sources).iteritems():
1375 1414                  if chng == 'removed':
1376 1415                      wl.add(fname)
1377 1416          wl.write()
1378 1417      else:
1379 1418          for elt in sorted(wl.list()):
1380 1419              ui.write("%s\n" % wslist[repo].filepath(elt))
1381 1420  
1382 1421  
1383 1422  cmdtable = {
1384 1423      'apply': (cdm_apply, [('p', 'parent', '', 'parent workspace'),
1385 1424                            ('r', 'remain', None, 'do not change directory')],
1386 1425                'hg apply [-p PARENT] [-r] command...'),
1387 1426      '^backup|bu': (cdm_backup, [('t', 'if-newer', None,
1388 1427                               'only backup if workspace files are newer')],
1389 1428                 'hg backup [-t]'),
1390 1429      'branchchk': (cdm_branchchk, [('p', 'parent', '', 'parent workspace')],
1391 1430                    'hg branchchk [-p PARENT]'),
1392 1431      'bugs': (cdm_bugs, [('p', 'parent', '', 'parent workspace')],
1393 1432               'hg bugs [-p PARENT]'),
1394 1433      'cddlchk': (cdm_cddlchk, [('p', 'parent', '', 'parent workspace')],
1395 1434                  'hg cddlchk [-p PARENT]'),
1396 1435      'changed': (cdm_changed, [('d', 'delete', None, 'delete the file list'),
1397 1436                                ('u', 'update', None, 'mark all changed files'),
1398 1437                                ('i', 'init', None, 'create an empty file list'),
1399 1438                                ('p', 'parent', '', 'parent workspace'),
1400 1439                                ('I', 'include', [],
1401 1440                                 'include names matching the given patterns'),
1402 1441                                ('X', 'exclude', [],
1403 1442                                 'exclude names matching the given patterns')],
1404 1443                  'hg changed -d\n'
1405 1444                  'hg changed -u\n'
1406 1445                  'hg changed -i\n'
1407 1446                  'hg changed [-I PATTERN...] [-X PATTERN...] [FILE...]'),
1408 1447      'comchk': (cdm_comchk, [('p', 'parent', '', 'parent workspace'),
1409 1448                              ('N', 'nocheck', None,
1410 1449                               'do not compare comments with databases')],
1411 1450                 'hg comchk [-p PARENT]'),
1412 1451      'comments': (cdm_comments, [('p', 'parent', '', 'parent workspace')],
1413 1452                   'hg comments [-p PARENT]'),
1414 1453      'copyright': (cdm_copyright, [('p', 'parent', '', 'parent workspace')],
1415 1454                    'hg copyright [-p PARENT]'),
1416 1455      'cstyle': (cdm_cstyle, [('p', 'parent', '', 'parent workspace')],
1417 1456                 'hg cstyle [-p PARENT]'),
1418 1457      'debugcdmal': (cdm_debugcdmal, [('p', 'parent', '', 'parent workspace')],
1419 1458                     'hg debugcdmal [-p PARENT] [FILE...]'),
1420 1459      'eval': (cdm_eval, [('p', 'parent', '', 'parent workspace'),
1421 1460                          ('r', 'remain', None, 'do not change directory')],
1422 1461               'hg eval [-p PARENT] [-r] command...'),
1423 1462      'hdrchk': (cdm_hdrchk, [('p', 'parent', '', 'parent workspace')],
  
    | 
      ↓ open down ↓ | 
    563 lines elided | 
    
      ↑ open up ↑ | 
  
1424 1463                 'hg hdrchk [-p PARENT]'),
1425 1464      'jstyle': (cdm_jstyle, [('p', 'parent', '', 'parent workspace')],
1426 1465                 'hg jstyle [-p PARENT]'),
1427 1466      'keywords': (cdm_keywords, [('p', 'parent', '', 'parent workspace')],
1428 1467                   'hg keywords [-p PARENT]'),
1429 1468      '^list|active': (cdm_list, [('p', 'parent', '', 'parent workspace'),
1430 1469                                  ('a', 'added', None, 'show added files'),
1431 1470                                  ('m', 'modified', None, 'show modified files'),
1432 1471                                  ('r', 'removed', None, 'show removed files')],
1433 1472                      'hg list [-amrRu] [-p PARENT]'),
     1473 +    'manlint': (cdm_manlintchk, [('p', 'parent', '', 'parent workspace')],
     1474 +                'hg manlint [-p PARENT]'),
1434 1475      'mapfilechk': (cdm_mapfilechk, [('p', 'parent', '', 'parent workspace')],
1435 1476                  'hg mapfilechk [-p PARENT]'),
1436 1477      '^nits': (cdm_nits, [('p', 'parent', '', 'parent workspace')],
1437 1478               'hg nits [-p PARENT]'),
1438 1479      '^pbchk': (cdm_pbchk, [('p', 'parent', '', 'parent workspace'),
1439 1480                             ('N', 'nocheck', None, 'skip database checks')],
1440 1481                'hg pbchk [-N] [-p PARENT]'),
1441 1482      'permchk': (cdm_permchk, [('p', 'parent', '', 'parent workspace')],
1442 1483                  'hg permchk [-p PARENT]'),
1443 1484      '^pdiffs': (cdm_pdiffs, [('p', 'parent', '', 'parent workspace'),
1444 1485                               ('a', 'text', None, 'treat all files as text'),
1445 1486                               ('g', 'git', None, 'use extended git diff format'),
1446 1487                               ('w', 'ignore-all-space', None,
1447 1488                                'ignore white space when comparing lines'),
1448 1489                               ('b', 'ignore-space-change', None,
1449 1490                                'ignore changes in the amount of white space'),
1450 1491                               ('B', 'ignore-blank-lines', None,
1451 1492                                'ignore changes whose lines are all blank'),
1452 1493                               ('U', 'unified', 3,
1453 1494                                'number of lines of context to show'),
1454 1495                               ('I', 'include', [],
1455 1496                                'include names matching the given patterns'),
1456 1497                               ('X', 'exclude', [],
1457 1498                                'exclude names matching the given patterns')],
1458 1499                 'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'),
1459 1500      '^recommit|reci': (cdm_recommit, [('p', 'parent', '', 'parent workspace'),
1460 1501                                        ('m', 'message', '',
1461 1502                                         'use <text> as commit message'),
1462 1503                                        ('l', 'logfile', '',
1463 1504                                         'read commit message from file'),
1464 1505                                        ('u', 'user', '',
1465 1506                                         'record user as committer')],
1466 1507                         'hg recommit [-m TEXT] [-l FILE] [-u USER] [-p PARENT]'),
1467 1508      'renamed': (cdm_renamed, [('p', 'parent', '', 'parent workspace')],
1468 1509                  'hg renamed [-p PARENT]'),
1469 1510      'reparent': (cdm_reparent, [], 'hg reparent PARENT'),
1470 1511      '^restore': (cdm_restore, [('g', 'generation', '', 'generation number')],
1471 1512                   'hg restore [-g GENERATION] BACKUP'),
1472 1513      'tagchk': (cdm_tagchk, [('p', 'parent', '', 'parent workspace')],
1473 1514                 'hg tagchk [-p PARENT]'),
1474 1515      'webrev': (cdm_webrev, [('C', 'C', '', 'ITS priority file'),
1475 1516                              ('D', 'D', '', 'delete remote webrev'),
1476 1517                              ('I', 'I', '', 'ITS configuration file'),
1477 1518                              ('i', 'i', '', 'include file'),
1478 1519                              ('N', 'N', None, 'suppress comments'),
1479 1520                              ('n', 'n', None, 'do not generate webrev'),
1480 1521                              ('O', 'O', None, 'OpenSolaris mode'),
1481 1522                              ('o', 'o', '', 'output directory'),
1482 1523                              ('p', 'p', '', 'use specified parent'),
1483 1524                              ('t', 't', '', 'upload target'),
1484 1525                              ('U', 'U', None, 'upload the webrev'),
1485 1526                              ('w', 'w', '', 'use wx active file')],
1486 1527                 'hg webrev [WEBREV_OPTIONS]'),
1487 1528  }
  
    | 
      ↓ open down ↓ | 
    44 lines elided | 
    
      ↑ open up ↑ | 
  
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX