2 # This program is free software; you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License version 2
4 # as published by the Free Software Foundation.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
18 # Use is subject to license terms.
21 '''workspace extensions for mercurial
23 This extension contains a number of commands to help you work within
24 the OpenSolaris consolidations.
28 Show diffs relative to parent workspace - pdiffs
29 Check source style rules - nits
30 Run pre-putback checks - pbchk
31 Collapse all your changes into a single changeset - recommit'''
35 # NB: This assumes the normal directory structure, with this
36 # extension 2 levels below .../lib/python.
38 # If you change that, change this
40 import sys
, os
, stat
, termios
, atexit
41 sys
.path
.insert(1, "%s/../../" % os
.path
.dirname(__file__
))
43 from onbld
.Scm
import Version
44 from mercurial
import util
47 Version
.check_version()
48 except Version
.VersionMismatch
, badversion
:
49 raise util
.Abort("Version Mismatch:\n %s\n" % badversion
)
52 from mercurial
import cmdutil
, node
, ignore
54 from onbld
.Scm
.WorkSpace
import WorkSpace
, ActiveEntry
55 from onbld
.Scm
.Backup
import CdmBackup
56 from onbld
.Checks
import Cddl
, Comments
, Copyright
, CStyle
, HdrChk
57 from onbld
.Checks
import JStyle
, Keywords
, Mapfile
, Rti
, onSWAN
60 def yes_no(ui
, msg
, default
):
68 resp
= ui
.prompt(msg
+ prompt
, r
'([Yy(es)?|[Nn]o?)?',
72 elif resp
[0] in ['Y', 'y']:
78 def _buildfilelist(repo
, args
):
79 '''build a list of files in which we're interested
81 If no files are specified, then we'll default to using
82 the entire active list.
84 Returns a dictionary, wherein the keys are cwd-relative file paths,
85 and the values (when present) are entries from the active list.
86 Instead of warning the user explicitly about files not in the active
87 list, we'll attempt to do checks on them.'''
92 # If the user specified files on the command line, we'll only check
93 # those files. We won't pull the active list at all. That means we
94 # won't be smart about skipping deleted files and such, so the user
95 # needs to be smart enough to not explicitly specify a nonexistent
96 # file path. Which seems reasonable.
103 # Otherwise, if no files were listed explicitly, we assume that the
104 # checks should be run on all files in the active list. So we determine
107 # Tracking the file paths is a slight optimization, in that multiple
108 # check functions won't need to derive it themselves. This also dovetails
109 # nicely with the expectation that explicitly specified files will be
110 # ${CWD}-relative paths, so the fdict keyspace will be consistent either
114 active
= wslist
[repo
].active()
115 for e
in sorted(active
):
116 fdict
[wslist
[repo
].filepath(e
.name
)] = e
121 def not_check(repo
, cmd
):
122 '''return a function which returns boolean indicating whether a file
123 should be skipped for CMD.'''
126 # The ignore routines need a canonical path to the file (relative to the
127 # repo root), whereas the check commands get paths relative to the cwd.
129 # Wrap our argument such that the path is canonified before it is checked.
131 def canonified_check(ignfunc
):
133 cpath
= util
.canonpath(repo
.root
, repo
.getcwd(), path
)
134 return ignfunc(cpath
)
139 for f
in [repo
.join('cdm/%s.NOT' % cmd
),
140 repo
.wjoin('exception_lists/%s' % cmd
)]:
141 if os
.path
.exists(f
):
142 ignorefiles
.append(f
)
145 ign
= ignore
.ignore(repo
.root
, ignorefiles
, repo
.ui
.warn
)
146 return canonified_check(ign
)
152 # Adding a reference to WorkSpace from a repo causes a circular reference
153 # repo <-> WorkSpace.
155 # This prevents repo, WorkSpace and members thereof from being garbage
156 # collected. Since transactions are aborted when the transaction object
157 # is collected, and localrepo holds a reference to the most recently created
158 # transaction, this prevents transactions from cleanly aborting.
160 # Instead, we hold the repo->WorkSpace association in a dictionary, breaking
166 def reposetup(ui
, repo
):
167 if repo
.local() and repo
not in wslist
:
168 wslist
[repo
] = WorkSpace(repo
)
170 if ui
.interactive
and sys
.stdin
.isatty():
171 ui
.setconfig('hooks', 'preoutgoing.cdm_pbconfirm',
172 'python:hgext_cdm.pbconfirm')
175 def pbconfirm(ui
, repo
, hooktype
, source
):
176 def wrapper(settings
=None):
177 termios
.tcsetattr(sys
.stdin
.fileno(), termios
.TCSANOW
, settings
)
180 if not yes_no(ui
, "Are you sure you wish to push?", False):
183 settings
= termios
.tcgetattr(sys
.stdin
.fileno())
184 orig
= list(settings
)
185 atexit
.register(wrapper
, orig
)
186 settings
[3] = settings
[3] & (~termios
.ISIG
) # c_lflag
187 termios
.tcsetattr(sys
.stdin
.fileno(), termios
.TCSANOW
, settings
)
190 def cdm_pdiffs(ui
, repo
, *pats
, **opts
):
191 '''list workspace diffs relative to parent workspace
193 The parent tip is taken to be the latest revision shared between
194 us and the parent workspace.'''
196 parent
= opts
['parent']
198 diffs
= wslist
[repo
].pdiff(pats
, opts
, parent
=parent
)
203 def cdm_list(ui
, repo
, **opts
):
204 '''list files changed relative to parent workspace
206 The parent tip is taken to be the latest revision shared between
207 us and the parent workspace.'''
212 wanted
.append(ActiveEntry
.ADDED
)
214 wanted
.append(ActiveEntry
.MODIFIED
)
216 wanted
.append(ActiveEntry
.REMOVED
)
218 act
= wslist
[repo
].active(opts
['parent'])
219 chngmap
= {ActiveEntry
.MODIFIED
: 'modified',
220 ActiveEntry
.ADDED
: 'added',
221 ActiveEntry
.REMOVED
: 'removed'}
225 if wanted
and (entry
.change
not in wanted
):
228 chngstr
= chngmap
[entry
.change
]
229 if chngstr
not in lst
:
231 lst
[chngstr
].append(entry
)
233 for chng
in sorted(lst
.keys()):
234 ui
.write(chng
+ ':\n')
235 for elt
in sorted(lst
[chng
]):
237 ui
.write('\t%s (renamed from %s)\n' % (elt
.name
,
239 elif elt
.is_copied():
240 ui
.write('\t%s (copied from %s)\n' % (elt
.name
,
243 ui
.write('\t%s\n' % elt
.name
)
246 def cdm_arcs(ui
, repo
, parent
=None):
247 'show all ARC cases in checkin comments'
248 act
= wslist
[repo
].active(parent
)
250 # We take a set of the appropriate comments to eliminate duplicates.
251 for elt
in set(filter(Comments
.isARC
, act
.comments())):
255 def cdm_bugs(ui
, repo
, parent
=None):
256 'show all bug IDs in checkin comments'
257 act
= wslist
[repo
].active(parent
)
259 for elt
in set(filter(Comments
.isBug
, act
.comments())):
263 def cdm_comments(ui
, repo
, parent
=None):
264 'show checkin comments for active files'
265 act
= wslist
[repo
].active(parent
)
267 for elt
in act
.comments():
271 def cdm_renamed(ui
, repo
, parent
=None):
272 '''show renamed active files
274 Renamed files are shown in the format
278 One pair per-line.'''
280 act
= wslist
[repo
].active(parent
)
282 for entry
in sorted(filter(lambda x
: x
.is_renamed(), act
)):
283 ui
.write('%s %s\n' % (entry
.name
, entry
.parentname
))
286 def cdm_comchk(ui
, repo
, **opts
):
287 '''check checkin comments for active files
289 Check that checkin comments conform to O/N rules.'''
291 active
= wslist
[repo
].active(opts
.get('parent'))
293 ui
.write('Comments check:\n')
295 check_db
= not opts
.get('nocheck')
296 return Comments
.comchk(active
.comments(), check_db
=check_db
, output
=ui
)
299 def cdm_cddlchk(ui
, repo
, *args
, **opts
):
300 '''check for a valid CDDL block in active files
302 See http://www.opensolaris.org/os/community/on/devref_toc/devref_7/#7_2_3_nonformatting_considerations
305 filelist
= opts
.get('filelist') or _buildfilelist(repo
, args
)
307 ui
.write('CDDL block check:\n')
312 exclude
= not_check(repo
, 'cddlchk')
314 for f
, e
in filelist
.iteritems():
315 if e
and e
.is_removed():
317 elif (e
or opts
.get('honour_nots')) and exclude(f
):
318 ui
.status('Skipping %s...\n' % f
)
320 elif e
and e
.is_added():
326 ret |
= Cddl
.cddlchk(fh
, lenient
=lenient
, output
=ui
)
331 def cdm_mapfilechk(ui
, repo
, *args
, **opts
):
332 '''check for a valid MAPFILE header block in active files
334 Check that all link-editor mapfiles contain the standard mapfile
335 header comment directing the reader to the document containing
336 Solaris object versioning rules (README.mapfile).'''
338 filelist
= opts
.get('filelist') or _buildfilelist(repo
, args
)
340 ui
.write('Mapfile comment check:\n')
343 exclude
= not_check(repo
, 'mapfilechk')
345 for f
, e
in filelist
.iteritems():
346 if e
and e
.is_removed():
348 elif f
.find('mapfile') == -1:
350 elif (e
or opts
.get('honour_nots')) and exclude(f
):
351 ui
.status('Skipping %s...\n' % f
)
355 ret |
= Mapfile
.mapfilechk(fh
, output
=ui
)
360 def cdm_copyright(ui
, repo
, *args
, **opts
):
361 '''check active files for valid copyrights
363 Check that all active files have a valid copyright containing the
364 current year (and *only* the current year).
365 See http://www.opensolaris.org/os/project/muskoka/on_dev/golden_rules.txt
368 filelist
= opts
.get('filelist') or _buildfilelist(repo
, args
)
370 ui
.write('Copyright check:\n')
373 exclude
= not_check(repo
, 'copyright')
375 for f
, e
in filelist
.iteritems():
376 if e
and e
.is_removed():
378 elif (e
or opts
.get('honour_nots')) and exclude(f
):
379 ui
.status('Skipping %s...\n' % f
)
383 ret |
= Copyright
.copyright(fh
, output
=ui
)
388 def cdm_hdrchk(ui
, repo
, *args
, **opts
):
389 '''check active header files conform to O/N rules'''
391 filelist
= opts
.get('filelist') or _buildfilelist(repo
, args
)
393 ui
.write('Header format check:\n')
396 exclude
= not_check(repo
, 'hdrchk')
398 for f
, e
in filelist
.iteritems():
399 if e
and e
.is_removed():
401 elif not f
.endswith('.h'):
403 elif (e
or opts
.get('honour_nots')) and exclude(f
):
404 ui
.status('Skipping %s...\n' % f
)
408 ret |
= HdrChk
.hdrchk(fh
, lenient
=True, output
=ui
)
413 def cdm_cstyle(ui
, repo
, *args
, **opts
):
414 '''check active C source files conform to the C Style Guide
416 See http://opensolaris.org/os/community/documentation/getting_started_docs/cstyle.ms.pdf'''
418 filelist
= opts
.get('filelist') or _buildfilelist(repo
, args
)
420 ui
.write('C style check:\n')
423 exclude
= not_check(repo
, 'cstyle')
425 for f
, e
in filelist
.iteritems():
426 if e
and e
.is_removed():
428 elif not (f
.endswith('.c') or f
.endswith('.h')):
430 elif (e
or opts
.get('honour_nots')) and exclude(f
):
431 ui
.status('Skipping %s...\n' % f
)
435 ret |
= CStyle
.cstyle(fh
, output
=ui
,
436 picky
=True, check_posix_types
=True,
437 check_continuation
=True)
442 def cdm_jstyle(ui
, repo
, *args
, **opts
):
443 'check active Java source files for common stylistic errors'
445 filelist
= opts
.get('filelist') or _buildfilelist(repo
, args
)
447 ui
.write('Java style check:\n')
450 exclude
= not_check(repo
, 'jstyle')
452 for f
, e
in filelist
.iteritems():
453 if e
and e
.is_removed():
455 elif not f
.endswith('.java'):
457 elif (e
or opts
.get('honour_nots')) and exclude(f
):
458 ui
.status('Skipping %s...\n' % f
)
462 ret |
= JStyle
.jstyle(fh
, output
=ui
, picky
=True)
467 def cdm_permchk(ui
, repo
, *args
, **opts
):
468 '''check active files permission - warn +x (execute) mode'''
470 filelist
= opts
.get('filelist') or _buildfilelist(repo
, args
)
472 ui
.write('File permission check:\n')
475 exclude
= not_check(repo
, 'permchk')
477 for f
, e
in filelist
.iteritems():
478 if e
and e
.is_removed():
480 elif (e
or opts
.get('honour_nots')) and exclude(f
):
481 ui
.status('Skipping %s...\n' % f
)
484 mode
= stat
.S_IMODE(os
.stat(f
)[stat
.ST_MODE
])
485 if mode
& stat
.S_IEXEC
:
488 if len(exeFiles
) > 0:
489 ui
.write('Warning: the following active file(s) have executable mode '
490 '(+x) permission set,\nremove unless intentional:\n')
491 for fname
in exeFiles
:
492 ui
.write(" %s\n" % fname
)
494 return len(exeFiles
) > 0
497 def cdm_tagchk(ui
, repo
, **opts
):
498 '''check if .hgtags is active and issue warning
500 Tag sharing among repositories is restricted to gatekeepers'''
502 active
= wslist
[repo
].active(opts
.get('parent'))
504 ui
.write('Checking for new tags:\n')
506 if ".hgtags" in active
:
507 tfile
= wslist
[repo
].filepath('.hgtags')
508 ptip
= active
.parenttip
.rev()
510 ui
.write('Warning: Workspace contains new non-local tags.\n'
511 'Only gatekeepers should add or modify such tags.\n'
512 'Use the following commands to revert these changes:\n'
513 ' hg revert -r%d %s\n'
515 'You should also recommit before integration\n' %
516 (ptip
, tfile
, tfile
))
523 def cdm_branchchk(ui
, repo
, **opts
):
524 '''check if multiple heads (or branches) are present, or if
525 branch changes are made'''
527 ui
.write('Checking for multiple heads (or branches):\n')
529 heads
= set(repo
.heads())
530 parents
= set([x
.node() for x
in wslist
[repo
].workingctx().parents()])
533 # We care if there's more than one head, and those heads aren't
534 # identical to the dirstate parents (if they are identical, it's
535 # an uncommitted merge which mergechk will catch, no need to
538 if len(heads
) > 1 and heads
!= parents
:
539 ui
.write('Workspace has multiple heads (or branches):\n')
540 for head
in [repo
.changectx(head
) for head
in heads
]:
541 ui
.write(" %d:%s\t%s\n" %
542 (head
.rev(), str(head
), head
.description().splitlines()[0]))
543 ui
.write('You must merge and recommit.\n')
546 ui
.write('\nChecking for branch changes:\n')
548 if repo
.dirstate
.branch() != 'default':
549 ui
.write("Warning: Workspace tip has named branch: '%s'\n"
550 "Only gatekeepers should push new branches.\n"
551 "Use the following commands to restore the branch name:\n"
552 " hg branch [-f] default\n"
554 "You should also recommit before integration\n" %
555 (repo
.dirstate
.branch()))
558 branches
= repo
.branchtags().keys()
559 if len(branches
) > 1:
560 ui
.write('Warning: Workspace has named branches:\n')
564 ui
.write("\t%s\n" % t
)
566 ui
.write("Only gatekeepers should push new branches.\n"
567 "Use the following commands to remove extraneous branches.\n"
568 " hg branch [-f] default\n"
570 "You should also recommit before integration\n")
576 def cdm_rtichk(ui
, repo
, **opts
):
577 '''check active bug/RFEs for approved RTIs
579 Only works on SWAN.'''
581 if opts
.get('nocheck') or os
.path
.exists(repo
.join('cdm/rtichk.NOT')):
582 ui
.status('Skipping RTI checks...\n')
586 ui
.write('RTI checks only work on SWAN, skipping...\n')
589 parent
= wslist
[repo
].parent(opts
.get('parent'))
590 active
= wslist
[repo
].active(parent
)
592 ui
.write('RTI check:\n')
596 for com
in active
.comments():
597 match
= Comments
.isBug(com
)
598 if match
and match
.group(1) not in bugs
:
599 bugs
.append(match
.group(1))
601 # RTI normalizes the gate path for us
602 return int(not Rti
.rti(bugs
, gatePath
=parent
, output
=ui
))
605 def cdm_keywords(ui
, repo
, *args
, **opts
):
606 '''check source files do not contain SCCS keywords'''
608 filelist
= opts
.get('filelist') or _buildfilelist(repo
, args
)
610 ui
.write('Keywords check:\n')
613 exclude
= not_check(repo
, 'keywords')
615 for f
, e
in filelist
.iteritems():
616 if e
and e
.is_removed():
618 elif (e
or opts
.get('honour_nots')) and exclude(f
):
619 ui
.status('Skipping %s...\n' % f
)
623 ret |
= Keywords
.keywords(fh
, output
=ui
)
630 # There's no reason to hook this up as an invokable command, since
631 # we have 'hg status', but it must accept the same arguments.
633 def cdm_outchk(ui
, repo
, **opts
):
634 '''Warn the user if they have uncommitted changes'''
636 ui
.write('Checking for uncommitted changes:\n')
638 st
= wslist
[repo
].modified()
640 ui
.write('Warning: the following files have uncommitted changes:\n')
642 ui
.write(' %s\n' % elt
)
647 def cdm_mergechk(ui
, repo
, **opts
):
648 '''Warn the user if their workspace contains merges'''
650 active
= wslist
[repo
].active(opts
.get('parent'))
652 ui
.write('Checking for merges:\n')
654 merges
= filter(lambda x
: len(x
.parents()) == 2 and x
.parents()[1],
658 ui
.write('Workspace contains the following merges:\n')
660 desc
= rev
.description().splitlines()
661 ui
.write(' %s:%s\t%s\n' %
662 (rev
.rev() or "working", str(rev
),
663 desc
and desc
[0] or "*** uncommitted change ***"))
668 def run_checks(ws
, cmds
, *args
, **opts
):
669 '''Run CMDS (with OPTS) over active files in WS'''
673 flist
= _buildfilelist(ws
.repo
, args
)
676 name
= cmd
.func_name
.split('_')[1]
677 if not ws
.ui
.configbool('cdm', name
, True):
678 ws
.ui
.status('Skipping %s check...\n' % name
)
682 result
= cmd(ws
.ui
, ws
.repo
, filelist
=flist
,
683 honour_nots
=True, *args
, **opts
)
686 output
= ws
.ui
.popbuffer()
687 if not ws
.ui
.quiet
or result
!= 0:
688 ws
.ui
.write(output
, '\n')
692 def cdm_nits(ui
, repo
, *args
, **opts
):
693 '''check for stylistic nits in active files
695 Run cddlchk, copyright, cstyle, hdrchk, jstyle, mapfilechk,
696 permchk, and keywords checks.'''
707 return run_checks(wslist
[repo
], cmds
, *args
, **opts
)
710 def cdm_pbchk(ui
, repo
, *args
, **opts
):
711 '''pre-putback check all active files
713 Run cddlchk, comchk, copyright, cstyle, hdrchk, jstyle, mapfilechk,
714 permchk, tagchk, branchchk, keywords and rtichk checks. Additionally,
715 warn about uncommitted changes.'''
718 # The current ordering of these is that the commands from cdm_nits
719 # run first in the same order as they would in cdm_nits. Then the
720 # pbchk specifics run
737 return run_checks(wslist
[repo
], cmds
, *args
, **opts
)
740 def cdm_recommit(ui
, repo
, **opts
):
741 '''compact outgoing deltas into a single, conglomerate delta'''
743 if not os
.getcwd().startswith(repo
.root
):
744 raise util
.Abort('recommit is not safe to run with -R')
746 if wslist
[repo
].modified():
747 raise util
.Abort('workspace has uncommitted changes')
749 if wslist
[repo
].merged():
750 raise util
.Abort('workspace contains uncommitted merge')
752 if wslist
[repo
].branched():
753 raise util
.Abort('workspace contains uncommitted branch')
755 if wslist
[repo
].mq_applied():
756 raise util
.Abort("workspace has Mq patches applied")
763 ui
.warn('Workspace has multiple heads (or branches):\n')
765 ui
.warn('\t%d\n' % repo
.changelog
.rev(head
))
766 raise util
.Abort('you must merge before recommitting')
768 active
= wslist
[repo
].active(opts
['parent'])
770 if len(active
.revs
) <= 0:
771 raise util
.Abort("no changes to recommit")
773 if len(active
.files()) <= 0:
774 ui
.warn("Recommitting %d active changesets, but no active files\n" %
778 # During the course of a recommit, any file bearing a name matching the
779 # source name of any renamed file will be clobbered by the operation.
781 # As such, we ask the user before proceeding.
783 bogosity
= [f
.parentname
for f
in active
if f
.is_renamed() and
784 os
.path
.exists(repo
.wjoin(f
.parentname
))]
787 ui
.warn("The following file names are the original name of a rename "
789 "in the working directory:\n")
790 for fname
in bogosity
:
791 ui
.warn(" %s\n" % fname
)
792 if not yes_no(ui
, "These files will be removed by recommit. Continue?",
794 raise util
.Abort("recommit would clobber files")
796 user
= opts
['user'] or ui
.username()
798 message
= cmdutil
.logmessage(opts
) or ui
.edit('\n'.join(active
.comments()),
801 raise util
.Abort('empty commit message')
803 name
= backup_name(repo
.root
)
804 bk
= CdmBackup(ui
, wslist
[repo
], name
)
806 if yes_no(ui
, 'Do you want to backup files first?', True):
809 oldtags
= repo
.tags()
810 clearedtags
= [(name
, nd
, repo
.changelog
.rev(nd
), local
)
811 for name
, nd
, local
in active
.tags()]
813 wslist
[repo
].squishdeltas(active
, message
, user
=user
)
816 ui
.write("Removed tags:\n")
817 for name
, nd
, rev
, local
in clearedtags
:
818 ui
.write(" %s %s:%s%s\n" % (name
, rev
, node
.short(nd
),
819 (local
and ' (local)') or ''))
821 for ntag
, nnode
in repo
.tags().items():
822 if ntag
in oldtags
and ntag
!= "tip":
823 if oldtags
[ntag
] != nnode
:
824 ui
.write("tag %s now refers to revision %d:%s\n" %
825 (ntag
, repo
.changelog
.rev(nnode
), node
.short(nnode
)))
828 def do_eval(cmd
, files
, root
, changedir
=True):
832 for path
in sorted(files
):
833 dirn
, base
= os
.path
.split(path
)
836 os
.chdir(os
.path
.join(root
, dirn
))
838 os
.putenv('workspace', root
)
839 os
.putenv('filepath', path
)
840 os
.putenv('dir', dirn
)
841 os
.putenv('file', base
)
845 def cdm_eval(ui
, repo
, *command
, **opts
):
846 '''run cmd for each active file
849 $file - active file basename.
850 $dir - active file dirname.
851 $filepath - path from workspace root to active file.
852 $workspace - full path to workspace root.
854 For example "hg eval 'echo $dir; hg log -l3 $file'" will show the last
855 the 3 log entries for each active file, preceded by its directory.'''
857 act
= wslist
[repo
].active(opts
['parent'])
858 cmd
= ' '.join(command
)
859 files
= [x
.name
for x
in act
if not x
.is_removed()]
861 do_eval(cmd
, files
, repo
.root
, not opts
['remain'])
864 def cdm_apply(ui
, repo
, *command
, **opts
):
865 '''apply cmd to all active files
867 For example 'hg apply wc -l' outputs a line count of active files.'''
869 act
= wslist
[repo
].active(opts
['parent'])
876 cmd
= ' '.join(command
) + appnd
877 files
= [x
.name
for x
in act
if not x
.is_removed()]
879 do_eval(cmd
, files
, repo
.root
, not opts
['remain'])
882 def cdm_reparent(ui
, repo
, parent
):
883 '''reparent your workspace
885 Updates the 'default' path.'''
887 filename
= repo
.join('hgrc')
889 p
= ui
.expandpath(parent
)
891 raise util
.Abort("could not find parent: %s" % parent
)
893 cp
= util
.configparser()
896 except ConfigParser
.ParsingError
, inst
:
897 raise util
.Abort('failed to parse %s\n%s' % (filename
, inst
))
900 fh
= open(filename
, 'w')
902 raise util
.Abort('Failed to open workspace configuration: %s' % e
)
904 if not cp
.has_section('paths'):
905 cp
.add_section('paths')
906 cp
.set('paths', 'default', p
)
911 def backup_name(fullpath
):
912 '''Create a backup directory name based on the specified path.
914 In most cases this is the basename of the path specified, but
915 certain cases are handled specially to create meaningful names'''
917 special
= ['usr/closed']
919 fullpath
= fullpath
.rstrip(os
.path
.sep
).split(os
.path
.sep
)
922 # If a path is 'special', we append the basename of the path to
923 # the path element preceding the constant, special, part.
925 # Such that for instance:
926 # /foo/bar/onnv-fixes/usr/closed
927 # has a backup name of:
931 elt
= elt
.split(os
.path
.sep
)
934 if fullpath
[-pathpos
:] == elt
:
935 return "%s-%s" % (fullpath
[-pathpos
- 1], elt
[-1])
940 def cdm_backup(ui
, repo
, if_newer
=False):
941 '''make backup copies of all workspace changes
943 Backups will be stored in ~/cdm.backup/<basename of workspace>.'''
945 name
= backup_name(repo
.root
)
946 bk
= CdmBackup(ui
, wslist
[repo
], name
)
948 if if_newer
and not bk
.need_backup():
949 ui
.status('backup is up-to-date\n')
954 def cdm_restore(ui
, repo
, backup
, **opts
):
955 '''restore workspace from backup
957 Restores a workspace from the specified backup directory and generation
958 (which defaults to the latest).'''
960 if not os
.getcwd().startswith(repo
.root
):
961 raise util
.Abort('restore is not safe to run with -R')
962 if wslist
[repo
].modified():
963 raise util
.Abort('Workspace has uncommitted changes')
964 if wslist
[repo
].merged():
965 raise util
.Abort('Workspace has an uncommitted merge')
966 if wslist
[repo
].branched():
967 raise util
.Abort('Workspace has an uncommitted branch')
969 if opts
['generation']:
970 gen
= int(opts
['generation'])
974 if os
.path
.exists(backup
):
975 backup
= os
.path
.abspath(backup
)
977 bk
= CdmBackup(ui
, wslist
[repo
], backup
)
981 def cdm_webrev(ui
, repo
, **opts
):
982 '''generate webrev and optionally upload it
984 This command passes all arguments to webrev script'''
987 for key
in opts
.keys():
989 if type(opts
[key
]) == type(True):
990 webrev_args
+= '-' + key
+ ' '
992 webrev_args
+= '-' + key
+ ' ' + opts
[key
] + ' '
994 retval
= os
.system('webrev ' + webrev_args
)
1002 'apply': (cdm_apply
, [('p', 'parent', '', 'parent workspace'),
1003 ('r', 'remain', None, 'do not change directories')],
1004 'hg apply [-p PARENT] [-r] command...'),
1005 'arcs': (cdm_arcs
, [('p', 'parent', '', 'parent workspace')],
1006 'hg arcs [-p PARENT]'),
1007 '^backup|bu': (cdm_backup
, [('t', 'if-newer', None,
1008 'only backup if workspace files are newer')],
1010 'branchchk': (cdm_branchchk
, [('p', 'parent', '', 'parent workspace')],
1011 'hg branchchk [-p PARENT]'),
1012 'bugs': (cdm_bugs
, [('p', 'parent', '', 'parent workspace')],
1013 'hg bugs [-p PARENT]'),
1014 'cddlchk': (cdm_cddlchk
, [('p', 'parent', '', 'parent workspace')],
1015 'hg cddlchk [-p PARENT]'),
1016 'comchk': (cdm_comchk
, [('p', 'parent', '', 'parent workspace'),
1017 ('N', 'nocheck', None,
1018 'do not compare comments with databases')],
1019 'hg comchk [-p PARENT]'),
1020 'comments': (cdm_comments
, [('p', 'parent', '', 'parent workspace')],
1021 'hg comments [-p PARENT]'),
1022 'copyright': (cdm_copyright
, [('p', 'parent', '', 'parent workspace')],
1023 'hg copyright [-p PARENT]'),
1024 'cstyle': (cdm_cstyle
, [('p', 'parent', '', 'parent workspace')],
1025 'hg cstyle [-p PARENT]'),
1026 'eval': (cdm_eval
, [('p', 'parent', '', 'parent workspace'),
1027 ('r', 'remain', None, 'do not change directories')],
1028 'hg eval [-p PARENT] [-r] command...'),
1029 'hdrchk': (cdm_hdrchk
, [('p', 'parent', '', 'parent workspace')],
1030 'hg hdrchk [-p PARENT]'),
1031 'jstyle': (cdm_jstyle
, [('p', 'parent', '', 'parent workspace')],
1032 'hg jstyle [-p PARENT]'),
1033 'keywords': (cdm_keywords
, [('p', 'parent', '', 'parent workspace')],
1034 'hg keywords [-p PARENT]'),
1035 '^list|active': (cdm_list
, [('p', 'parent', '', 'parent workspace'),
1036 ('r', 'removed', None, 'show removed files'),
1037 ('a', 'added', None, 'show added files'),
1038 ('m', 'modified', None, 'show modified files')],
1039 'hg list [-amrRu] [-p PARENT]'),
1040 'mapfilechk': (cdm_mapfilechk
, [('p', 'parent', '', 'parent workspace')],
1041 'hg mapfilechk [-p PARENT]'),
1042 '^nits': (cdm_nits
, [('p', 'parent', '', 'parent workspace')],
1043 'hg nits [-p PARENT]'),
1044 '^pbchk': (cdm_pbchk
, [('p', 'parent', '', 'parent workspace'),
1045 ('N', 'nocheck', None, 'skip RTI check')],
1046 'hg pbchk [-N] [-p PARENT]'),
1047 'permchk': (cdm_permchk
, [('p', 'parent', '', 'parent workspace')],
1048 'hg permchk [-p PARENT]'),
1049 '^pdiffs': (cdm_pdiffs
, [('p', 'parent', '', 'parent workspace'),
1050 ('a', 'text', None, 'treat all files as text'),
1051 ('g', 'git', None, 'use extended git diff format'),
1052 ('w', 'ignore-all-space', None,
1053 'ignore white space when comparing lines'),
1054 ('b', 'ignore-space-change', None,
1055 'ignore changes in the amount of white space'),
1056 ('B', 'ignore-blank-lines', None,
1057 'ignore changes whos lines are all blank'),
1059 'number of lines of context to show'),
1060 ('I', 'include', [],
1061 'include names matching the given patterns'),
1062 ('X', 'exclude', [],
1063 'exclude names matching the given patterns')],
1064 'hg pdiffs [OPTION...] [-p PARENT] [FILE...]'),
1065 '^recommit|reci': (cdm_recommit
, [('p', 'parent', '', 'parent workspace'),
1066 ('f', 'force', None, 'force operation'),
1067 ('m', 'message', '',
1068 'use <text> as commit message'),
1069 ('l', 'logfile', '',
1070 'read commit message from file'),
1072 'record user as committer')],
1073 'hg recommit [-f] [-p PARENT]'),
1074 'renamed': (cdm_renamed
, [('p', 'parent', '', 'parent workspace')],
1075 'hg renamed [-p PARENT]'),
1076 'reparent': (cdm_reparent
, [], 'hg reparent PARENT'),
1077 '^restore': (cdm_restore
, [('g', 'generation', '', 'generation number')],
1078 'hg restore [-g GENERATION] BACKUP'),
1079 'rtichk': (cdm_rtichk
, [('p', 'parent', '', 'parent workspace'),
1080 ('N', 'nocheck', None, 'skip RTI check')],
1081 'hg rtichk [-N] [-p PARENT]'),
1082 'tagchk': (cdm_tagchk
, [('p', 'parent', '', 'parent workspace')],
1083 'hg tagchk [-p PARENT]'),
1084 'webrev': (cdm_webrev
, [('D', 'D', '', 'delete remote webrev'),
1085 ('i', 'i', '', 'include file'),
1086 ('l', 'l', '', 'extract file list from putback -n'),
1087 ('N', 'N', None, 'supress comments'),
1088 ('n', 'n', None, 'do not generate webrev'),
1089 ('O', 'O', None, 'OpenSolaris mode'),
1090 ('o', 'o', '', 'output directory'),
1091 ('p', 'p', '', 'use specified parent'),
1092 ('t', 't', '', 'upload target'),
1093 ('U', 'U', None, 'upload the webrev'),
1094 ('w', 'w', '', 'use wx active file')],
1095 'hg webrev [WEBREV_OPTIONS]'),