Commits

Matt Mackall  committed 6d99ff7 Merge

Merge with stable

  • Participants
  • Parent commits dd970a3, e562756

Comments (0)

Files changed (220)

 .DS_Store
 tags
 cscope.*
+i18n/hg.pot
+locale/*/LC_MESSAGES/hg.mo
+
+# files installed with a local --pure build
+mercurial/base85.py
+mercurial/bdiff.py
+mercurial/diffhelpers.py
+mercurial/mpatch.py
+mercurial/osutil.py
+mercurial/parsers.py
 
 syntax: regexp
 ^\.pc/
 PREFIX=/usr/local
 export PREFIX
 PYTHON=python
+PURE=
 
 help:
 	@echo 'Commonly used make targets:'
 	@echo '  dist         - run all tests and create a source tarball in dist/'
 	@echo '  clean        - remove files created by other targets'
 	@echo '                 (except installed files or dist source tarball)'
+	@echo '  update-pot   - update i18n/hg.pot'
 	@echo
 	@echo 'Example for a system-wide installation under /usr/local:'
 	@echo '  make all && su -c "make install" && hg version'
 all: build doc
 
 local:
-	$(PYTHON) setup.py build_ext -i
-	$(PYTHON) setup.py build_py -c -d .
+	$(PYTHON) setup.py $(PURE) build_py -c -d . build_ext -i build_mo
 	$(PYTHON) hg version
 
 build:
-	$(PYTHON) setup.py build
+	$(PYTHON) setup.py $(PURE) build
 
 doc:
 	$(MAKE) -C doc
 	-$(PYTHON) setup.py clean --all # ignore errors of this command
 	find . -name '*.py[cdo]' -exec rm -f '{}' ';'
 	rm -f MANIFEST mercurial/__version__.py mercurial/*.so tests/*.err
+	rm -rf locale
 	$(MAKE) -C doc clean
 
 install: install-bin install-doc
 
 install-bin: build
-	$(PYTHON) setup.py install --prefix="$(PREFIX)" --force
+	$(PYTHON) setup.py $(PURE) install --prefix="$(PREFIX)" --force
 
 install-doc: doc
 	cd doc && $(MAKE) $(MFLAGS) install
 install-home: install-home-bin install-home-doc
 
 install-home-bin: build
-	$(PYTHON) setup.py install --home="$(HOME)" --force
+	$(PYTHON) setup.py $(PURE) install --home="$(HOME)" --force
 
 install-home-doc: doc
 	cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
 test-%:
 	cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
 
+update-pot:
+	mkdir -p i18n
+	pygettext -d hg -p i18n --docstrings \
+	  mercurial/commands.py hgext/*.py hgext/*/__init__.py
+        # All strings marked for translation in Mercurial contain
+        # ASCII characters only. But some files contain string
+        # literals like this '\037\213'. xgettext thinks it has to
+        # parse these them even though they are not marked for
+        # translation. Extracting with an explicit encoding of
+        # ISO-8859-1 will make xgettext "parse" and ignore them.
+	find mercurial hgext doc -name '*.py' | xargs \
+	  xgettext --from-code ISO-8859-1 --join --sort-by-file \
+	  -d hg -p i18n -o hg.pot
 
 .PHONY: help all local build doc clean install install-bin install-doc \
-	install-home install-home-bin install-home-doc dist dist-notests tests
+	install-home install-home-bin install-home-doc dist dist-notests tests \
+	update-pot
 
 proc parsecommit {id contents listed olds} {
     global commitinfo children nchildren parents nparents cdate ncleft
+    global firstparents
 
     set inhdr 1
     set comment {}
     }
     set commitinfo($id) [list $headline $auname $audate \
 			     $comname $comdate $comment $rev $branch]
+
+    if {[info exists firstparents]} {
+        set i [lsearch $firstparents $id]
+        if {$i != -1} {
+            # remove the parent from firstparents, possible building
+            # an empty list
+            set firstparents [concat \
+                                  [lrange $firstparents 0 [expr $i - 1]] \
+                                  [lrange $firstparents [expr $i + 1] end]]
+            if {$firstparents eq {}} {
+                # we have found all parents of the first changeset
+                # which means that we can safely select the first line
+                after idle {
+                    selectline 0 0
+                }
+            }
+        }
+    } else {
+        # this is the first changeset, save the parents
+        set firstparents $olds
+        if {$firstparents eq {}} {
+            # a repository with a single changeset
+            after idle {
+                selectline 0 0
+            }
+        }
+    }
 }
 
 proc readrefs {} {
     global entries sha1entry sha1string sha1but
     global maincursor textcursor curtextcursor
     global rowctxmenu gaudydiff mergemax
-    global hgvdiff
+    global hgvdiff bgcolor fgcolor diffremcolor diffaddcolor diffmerge1color
+    global diffmerge2color hunksepcolor
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
     .ctop add .ctop.top
     set canv .ctop.top.clist.canv
     canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
-	-bg white -bd 0 \
+	-bg $bgcolor -bd 0 \
 	-yscrollincr $linespc -yscrollcommand "$cscroll set" -selectbackground grey
     .ctop.top.clist add $canv
     set canv2 .ctop.top.clist.canv2
     canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
-	-bg white -bd 0 -yscrollincr $linespc -selectbackground grey
+	-bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground grey
     .ctop.top.clist add $canv2
     set canv3 .ctop.top.clist.canv3
     canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
-	-bg white -bd 0 -yscrollincr $linespc -selectbackground grey
+	-bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground grey
     .ctop.top.clist add $canv3
     bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
 
     .ctop add .ctop.cdet
     frame .ctop.cdet.left
     set ctext .ctop.cdet.left.ctext
-    text $ctext -bg white -state disabled -font $textfont \
+    text $ctext -fg $fgcolor -bg $bgcolor -state disabled -font $textfont \
 	-width $geometry(ctextw) -height $geometry(ctexth) \
 	-yscrollcommand ".ctop.cdet.left.sb set" \
 	-xscrollcommand ".ctop.cdet.left.hb set" -wrap none
 	$ctext tag conf d0 -back "#ff8080"
 	$ctext tag conf d1 -back green
     } else {
-	$ctext tag conf hunksep -fore blue
-	$ctext tag conf d0 -fore red
-	$ctext tag conf d1 -fore "#00a000"
-	$ctext tag conf m0 -fore red
-	$ctext tag conf m1 -fore blue
+	$ctext tag conf hunksep -fore $hunksepcolor
+	$ctext tag conf d0 -fore $diffremcolor
+	$ctext tag conf d1 -fore $diffaddcolor
+
+	# The mX colours seem to be used in merge changesets, where m0
+	# is first parent, m1 is second parent and so on. Git can have
+	# several parents, Hg cannot, so I think the m2..mmax would be
+	# unused.
+	$ctext tag conf m0 -fore $diffmerge1color
+	$ctext tag conf m1 -fore $diffmerge2color
 	$ctext tag conf m2 -fore green
 	$ctext tag conf m3 -fore purple
 	$ctext tag conf m4 -fore brown
 
     frame .ctop.cdet.right
     set cflist .ctop.cdet.right.cfiles
-    listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
+    listbox $cflist -fg $fgcolor -bg $bgcolor \
+        -selectmode extended -width $geometry(cflistw) \
 	-yscrollcommand ".ctop.cdet.right.sb set"
     scrollbar .ctop.cdet.right.sb -command "$cflist yview"
     pack .ctop.cdet.right.sb -side right -fill y
 proc savestuff {w} {
     global canv canv2 canv3 ctext cflist mainfont textfont
     global stuffsaved findmergefiles gaudydiff maxgraphpct
-    global maxwidth authorcolors curidfont
+    global maxwidth authorcolors curidfont bgcolor fgcolor
+    global diffremcolor diffaddcolor hunksepcolor
+    global diffmerge1color diffmerge2color
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
 	puts $f "# the last entry will be reused."
 	puts $f "#"
 	puts $f "set authorcolors {$authorcolors}"
+	puts $f "#"
+	puts $f "# The background color in the text windows"
+	puts $f "set bgcolor $bgcolor"
+	puts $f "#"
+	puts $f "# The text color used in the diff and file list view"
+	puts $f "set fgcolor $fgcolor"
+	puts $f "#"
+	puts $f "# Color to display + lines in diffs"
+	puts $f "set diffaddcolor $diffaddcolor"
+	puts $f "#"
+	puts $f "# Color to display - lines in diffs"
+	puts $f "set diffremcolor $diffremcolor"
+	puts $f "#"
+	puts $f "# Merge diffs: Color to signal lines from first parent"
+	puts $f "set diffmerge1color $diffmerge1color"
+	puts $f "#"
+	puts $f "# Merge diffs: Color to signal lines from second parent"
+	puts $f "set diffmerge2color $diffmerge2color"
+	puts $f "#"
+	puts $f "# Hunkseparator (@@ -lineno,lines +lineno,lines @@) color"
+	puts $f "set hunksepcolor $hunksepcolor"
 	close $f
 	file rename -force "~/.hgk-new" "~/.hgk"
     }
 set authorcolors {
     black blue deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey
 }
+set bgcolor white
+
+# This color should probably be some system color (provided by tk),
+# but as the bgcolor has always been set to white, I choose to ignore
+set fgcolor black
+set diffaddcolor "#00a000"
+set diffremcolor red
+set diffmerge1color red
+set diffmerge2color blue
+set hunksepcolor blue
 
 catch {source ~/.hgk}
 

File contrib/hgwebdir.fcgi

 #os.environ["HGENCODING"] = "UTF-8"
 
 from mercurial.hgweb.hgwebdir_mod import hgwebdir
-from mercurial.hgweb.request import wsgiapplication
 from flup.server.fcgi import WSGIServer
 
 # The config file looks like this.  You can have paths to individual
 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
 # or use a dictionary with entries like 'virtual/path': '/real/path'
 
-def make_web_app():
-    return hgwebdir("hgweb.config")
-
-WSGIServer(wsgiapplication(make_web_app)).run()
+WSGIServer(hgwebdir('hgweb.config')).run()

File contrib/hgwebdir.wsgi

+# An example WSGI (use with mod_wsgi) script to export multiple hgweb repos
+
+# adjust python path if not a system-wide install:
+#import sys
+#sys.path.insert(0, "/path/to/python/lib")
+
+# enable demandloading to reduce startup time
+from mercurial import demandimport; demandimport.enable()
+from mercurial.hgweb.hgwebdir_mod import hgwebdir
+
+# If you'd like to serve pages with UTF-8 instead of your default
+# locale charset, you can do so by uncommenting the following lines.
+# Note that this will cause your .hgrc files to be interpreted in
+# UTF-8 and all your repo files to be displayed using UTF-8.
+#
+#import os
+#os.environ["HGENCODING"] = "UTF-8"
+
+# The config file looks like this.  You can have paths to individual
+# repos, collections of repos in a directory tree, or both.
+#
+# [paths]
+# virtual/path1 = /real/path1
+# virtual/path2 = /real/path2
+# virtual/root = /real/root/*
+# / = /real/root2/*
+#
+# paths example:
+#
+# * First two lines mount one repository into one virtual path, like
+# '/real/path1' into 'virtual/path1'.
+#
+# * The third entry tells every mercurial repository found in
+# '/real/root', recursively, should be mounted in 'virtual/root'. This
+# format is preferred over the [collections] one, using absolute paths
+# as configuration keys is not supported on every platform (including
+# Windows).
+#
+# * The last entry is a special case mounting all repositories in
+# '/real/root2' in the root of the virtual directory.
+#
+# collections example: say directory tree /foo contains repos /foo/bar,
+# /foo/quux/baz.  Give this config section:
+#   [collections]
+#   /foo = /foo
+# Then repos will list as bar and quux/baz.
+#
+# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
+# or use a dictionary with entries like 'virtual/path': '/real/path'
+
+application = hgwebdir('hgweb.config')

File contrib/mergetools.hgrc

 p4merge.gui=True
 p4merge.priority=-8
 
-tortoisemerge.args=/base: $output /mine:$local /theirs:$other /merged:$output
+tortoisemerge.args=/base:$base /mine:$local /theirs:$other /merged:$output
 tortoisemerge.regkey=Software\TortoiseSVN
+tortoisemerge.checkchanged=True
 tortoisemerge.gui=True
 
 ecmerge.args=$base $local $other --mode=merge3 --title0=base --title1=local --title2=other --to=$output
 filemerge.executable=/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge
 filemerge.args=-left $other -right $local -ancestor $base -merge $output
 filemerge.gui=True
+
+beyondcompare3.args=$local $other $base $output /ro /lefttitle=local /centerfile=base /righttitle=other /automerge /reviewconflicts /solo
+beyondcompare3.regkey=Software\Scooter Software\Beyond Compare 3
+beyondcompare3.regname=ExePath
+beyondcompare3.gui=True
+
+winmerge.args=/e /u /dl local /dr other /wr $local $other $output
+winmerge.regkey=Software\Thingamahoochie\WinMerge
+winmerge.regname=Executable
+winmerge.checkchanged=True
+winmerge.gui=True

File contrib/tcsh_completion

 # tcsh completion for Mercurial
 #
 # This file has been auto-generated by tcsh_completion_build.sh for
-# Mercurial Distributed SCM (version 325c07fd2ebd)
+# Mercurial Distributed SCM (version 1.1.2)
 #
 # Copyright (C) 2005 TK Soh.
 #
     -y --noninteractive \
     -q --quiet \
     -v --verbose \
+    --config \
     --debug \
     --debugger \
+    --encoding \
+    --encodingmode \
+    --lsprof \
     --traceback \
     --time \
     --profile \
     --version \
     -h --help)/' \
-  'p/1/(add addremove annotate bundle cat \
-    clone commit ci copy cp \
-    debugancestor debugcheckstate debugconfig debugdata debugindex \
-    debugindexdot debugrename debugstate debugwalk diff \
-    export forget grep heads help \
-    identify id import patch incoming \
-    in init locate log history \
-    manifest outgoing out parents paths \
-    pull push rawcommit recover remove \
-    rm rename mv revert root \
-    serve status tag tags tip \
-    unbundle undo update up checkout \
-    co verify version)/'
+  'p/1/(add addremove annotate blame archive \
+    backout bisect branch branches bundle \
+    cat clone commit ci copy \
+    cp debugancestor debugcheckstate debugcomplete debugdata \
+    debugdate debugfsinfo debugindex debugindexdot debuginstall \
+    debugrawcommit rawcommit debugrebuildstate debugrename debugsetparents \
+    debugstate debugwalk diff export grep \
+    heads help identify id import \
+    patch incoming in init locate \
+    log history manifest merge outgoing \
+    out parents paths pull push \
+    recover remove rm rename mv \
+    resolve revert rollback root serve \
+    showconfig debugconfig status st tag \
+    tags tip unbundle update up \
+    checkout co verify version)/'
 

File contrib/tcsh_completion_build.sh

 
 hg_commands=`hg --debug help | \
         sed -e '1,/^list of commands:/d' \
-            -e '/^global options:/,$d' \
+            -e '/^enabled extensions:/,$d' \
             -e '/^ [^ ]/!d; s/[,:]//g;' | \
         xargs -n5 | \
         sed -e '$!s/$/ \\\\/g; 2,$s/^ */    /g'`

File contrib/win32/mercurial.ini

 
 [ui]
 editor = notepad
+; show changed files and be a bit more verbose if True
+; verbose = True
+
+; username data to appear in commits
+; it usually takes the form: Joe User <joe.user@host.com>
+; username = Joe User <j.user@example.com>
+
 
 ; By default, we try to encode and decode all files that do not
 ; contain ASCII NUL characters.  What this means is that we try to set
 
 ; Alternatively, you can explicitly specify each file extension that
 ; you want decoded (any you omit will be left untouched), like this:
+; **.txt = dumbdecode:
+[hgk]
+; Replace the following with your path to hgk, uncomment it and 
+; install ActiveTcl (or another win32 port)
+; path="C:\Program Files\Mercurial\Contrib\hgk.tcl"
 
-; **.txt = dumbdecode:
+

File contrib/win32/mercurial.iss

 ; Script generated by the Inno Setup Script Wizard.
 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
-
 [Setup]
-AppCopyright=Copyright 2005-2008 Matt Mackall and others
+AppCopyright=Copyright 2005-2009 Matt Mackall and others
 AppName=Mercurial
 AppVerName=Mercurial snapshot
 InfoAfterFile=contrib/win32/postinstall.txt
 DefaultDirName={pf}\Mercurial
 SourceDir=..\..
 VersionInfoDescription=Mercurial distributed SCM
-VersionInfoCopyright=Copyright 2005-2008 Matt Mackall and others
+VersionInfoCopyright=Copyright 2005-2009 Matt Mackall and others
 VersionInfoCompany=Matt Mackall and others
 InternalCompressLevel=max
 SolidCompression=true
 Source: contrib\mercurial.el; DestDir: {app}/Contrib
 Source: contrib\vim\*.*; DestDir: {app}/Contrib/Vim
 Source: contrib\zsh_completion; DestDir: {app}/Contrib
+Source: contrib\hgk; DestDir: {app}/Contrib; DestName: hgk.tcl
 Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
-Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite
+Source: contrib\mergetools.hgrc; DestDir: {tmp};
+Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Check: CheckFile; AfterInstall: ConcatenateFiles;
 Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt
 Source: dist\hg.exe; DestDir: {app}; AfterInstall: Touch('{app}\hg.exe.local')
 Source: dist\library.zip; DestDir: {app}
 
 [UninstallDelete]
 Type: files; Name: "{app}\hg.exe.local"
+[Code]
+var
+  WriteFile: Boolean;
+  CheckDone: Boolean;
 
-[Code]
+function CheckFile(): Boolean;
+begin
+  if not CheckDone then begin
+    WriteFile := True;
+    if FileExists(ExpandConstant(CurrentFileName)) then begin
+        WriteFile := MsgBox('' + ExpandConstant(CurrentFileName) + '' #13#13 'The file already exists.' #13#13 'Would you like Setup to overwrite it?', mbConfirmation, MB_YESNO) = idYes;
+    end;
+    CheckDone := True;
+  end;
+  Result := WriteFile;
+end;
+
+procedure ConcatenateFiles();
+var
+  MergeConfigs: TArrayOfString;
+begin
+  if LoadStringsFromFile(ExpandConstant('{tmp}\mergetools.hgrc'),MergeConfigs) then begin
+    SaveStringsToFile(ExpandConstant(CurrentFileName),MergeConfigs,True);
+  end;
+end;
+
 procedure Touch(fn: String);
 begin
   SaveStringToFile(ExpandConstant(fn), '', False);

File contrib/zsh_completion

 # it into your zsh function path (/usr/share/zsh/site-functions for
 # instance)
 #
-# Copyright (C) 2005 Steve Borho
+# Copyright (C) 2005-6 Steve Borho
 # Copyright (C) 2006-8 Brendan Cully <brendan@kublai.com>
 #
 # This is free software; you can redistribute it and/or modify it under
 # the terms of the GNU General Public License as published by the Free
 # Software Foundation; either version 2 of the License, or (at your
 # option) any later version.
-#
 
 emulate -LR zsh
 setopt extendedglob
   typeset -ga _hg_cmd_list
   typeset -gA _hg_alias_list
   local hline cmd cmdalias
-  _call_program help hg --verbose help | while read -A hline
+
+  _call_program hg hg debugcomplete -v 2>/dev/null | while read -A hline
   do
-    cmd="$hline[1]"
-    case $cmd in
-      *:)
-        cmd=${cmd%:}
-        _hg_cmd_list+=($cmd)
-      ;;
-      *,)
-        cmd=${cmd%,}
-        _hg_cmd_list+=($cmd)
-        integer i=2
-        while (( i <= $#hline ))
-        do
-          cmdalias=${hline[$i]%(:|,)}
-          _hg_cmd_list+=($cmdalias)
-          _hg_alias_list+=($cmdalias $cmd)
-          (( i++ ))
-        done
-      ;;
-    esac
+    cmd=$hline[1]
+    _hg_cmd_list+=($cmd)
+
+    for cmdalias in $hline[2,-1]
+    do
+      _hg_cmd_list+=($cmdalias)
+      _hg_alias_list+=($cmdalias $cmd)
+    done
   done
 }
 

File doc/Makefile

 HTML=$(SOURCES:%.txt=%.html)
 PREFIX=/usr/local
 MANDIR=$(PREFIX)/share/man
-INSTALL=install -c
+INSTALL=install -c -m 644
 PYTHON=python
 ASCIIDOC=asciidoc
 
 # of the GNU General Public License, incorporated herein by reference.
 
 # enable importing on demand to reduce startup time
-from mercurial import demandimport; demandimport.enable()
+try:
+    from mercurial import demandimport; demandimport.enable()
+except ImportError:
+    import sys
+    sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
+                     ' '.join(sys.path))
+    sys.stderr.write("(check your install and PYTHONPATH)\n")
+    sys.exit(-1)
 
 import sys
 import mercurial.util

File hgext/alias.py

 mycmd = cmd --args
 '''
 
-from mercurial.cmdutil import findcmd, UnknownCommand, AmbiguousCommand
-from mercurial import commands
 from mercurial.i18n import _
+from mercurial import commands, cmdutil, error
 
 cmdtable = {}
 
             return
 
         try:
-            self._cmd = findcmd(self._target, commands.table, False)[1]
+            self._cmd = cmdutil.findcmd(self._target, commands.table, False)[1]
             if self._cmd == self:
                 raise RecursiveCommand()
             if self._target in commands.norepo.split(' '):
                 commands.norepo += ' %s' % self._name
             return
-        except UnknownCommand:
+        except error.UnknownCommand:
             msg = _('*** [alias] %s: command %s is unknown') % \
                   (self._name, self._target)
-        except AmbiguousCommand:
+        except error.AmbiguousCommand:
             msg = _('*** [alias] %s: command %s is ambiguous') % \
                   (self._name, self._target)
         except RecursiveCommand:

File hgext/bookmarks.py

 
 It is possible to use bookmark names in every revision lookup (e.g. hg
 merge, hg update).
+
+The bookmark extension offers the possiblity to have a more git-like experience
+by adding the following configuration option to your .hgrc:
+
+[bookmarks]
+track.current = True
+
+This will cause bookmarks to track the bookmark that you are currently on, and
+just updates it. This is similar to git's approach of branching.
 '''
 
-from mercurial.commands import templateopts, hex, short
 from mercurial.i18n import _
-from mercurial import cmdutil, util, commands, changelog
-from mercurial.node import nullid, nullrev
-from mercurial.repo import RepoError
-import mercurial, mercurial.localrepo, mercurial.repair, os
+from mercurial.node import nullid, nullrev, hex, short
+from mercurial import util, commands, localrepo, repair, extensions
+import os
 
 def parse(repo):
     '''Parse .hg/bookmarks file and return a dictionary
 
-    Bookmarks are stored as {HASH}\s{NAME}\n (localtags format) values
+    Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
     in the .hg/bookmarks file. They are read by the parse() method and
     returned as a dictionary with name => hash values.
 
     '''
     if os.path.exists(repo.join('bookmarks')):
         util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
+    if current(repo) not in refs:
+        setcurrent(repo, None)
     file = repo.opener('bookmarks', 'w+')
-    for refspec, node in refs.items():
+    for refspec, node in refs.iteritems():
         file.write("%s %s\n" % (hex(node), refspec))
     file.close()
 
+def current(repo):
+    '''Get the current bookmark
+
+    If we use gittishsh branches we have a current bookmark that
+    we are on. This function returns the name of the bookmark. It
+    is stored in .hg/bookmarks.current
+    '''
+    if repo._bookmarkcurrent:
+        return repo._bookmarkcurrent
+    mark = None
+    if os.path.exists(repo.join('bookmarks.current')):
+        file = repo.opener('bookmarks.current')
+        # No readline() in posixfile_nt, reading everything is cheap
+        mark = (file.readlines() or [''])[0]
+        if mark == '':
+            mark = None
+        file.close()
+    repo._bookmarkcurrent = mark
+    return mark
+
+def setcurrent(repo, mark):
+    '''Set the name of the bookmark that we are currently on
+
+    Set the name of the bookmark that we are on (hg update <bookmark>).
+    The name is recoreded in .hg/bookmarks.current
+    '''
+    if current(repo) == mark:
+        return
+
+    refs = parse(repo)
+
+    # do not update if we do update to a rev equal to the current bookmark
+    if (mark not in refs and
+        current(repo) and refs[current(repo)] == repo.changectx('.').node()):
+        return
+    if mark not in refs:
+        mark = ''
+    file = repo.opener('bookmarks.current', 'w+')
+    file.write(mark)
+    file.close()
+    repo._bookmarkcurrent = mark
+
 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
     '''mercurial bookmarks
 
             raise util.Abort(_("new bookmark name required"))
         marks[mark] = marks[rename]
         del marks[rename]
+        if current(repo) == rename:
+            setcurrent(repo, mark)
         write(repo, marks)
         return
 
             ui.status("no bookmarks set\n")
         else:
             for bmark, n in marks.iteritems():
-                prefix = (n == cur) and '*' or ' '
+                if ui.configbool('bookmarks', 'track.current'):
+                    prefix = (bmark == current(repo) and n == cur) and '*' or ' '
+                else:
+                    prefix = (n == cur) and '*' or ' '
+
                 ui.write(" %s %-25s %d:%s\n" % (
                     prefix, bmark, repo.changelog.rev(n), hexfn(n)))
         return
                         saveheads.append(p)
     return [r for r in tostrip if r not in saveheads]
 
-def strip(ui, repo, node, backup="all"):
+def strip(oldstrip, ui, repo, node, backup="all"):
     """Strip bookmarks if revisions are stripped using
     the mercurial.strip method. This usually happens during
     qpush and qpop"""
     revisions = _revstostrip(repo.changelog, node)
     marks = parse(repo)
     update = []
-    for mark, n in marks.items():
+    for mark, n in marks.iteritems():
         if repo.changelog.rev(n) in revisions:
             update.append(mark)
     oldstrip(ui, repo, node, backup)
             marks[m] = repo.changectx('.').node()
         write(repo, marks)
 
-oldstrip = mercurial.repair.strip
-mercurial.repair.strip = strip
-
 def reposetup(ui, repo):
-    if not isinstance(repo, mercurial.localrepo.localrepository):
+    if not isinstance(repo, localrepo.localrepository):
         return
 
     # init a bookmark cache as otherwise we would get a infinite reading
     # in lookup()
     repo._bookmarks = None
+    repo._bookmarkcurrent = None
 
     class bookmark_repo(repo.__class__):
         def rollback(self):
             marks = parse(repo)
             update = False
             for mark, n in marks.items():
-                if n in parents:
-                    marks[mark] = node
-                    update = True
+                if ui.configbool('bookmarks', 'track.current'):
+                    if mark == current(repo) and n in parents:
+                        marks[mark] = node
+                        update = True
+                else:
+                    if n in parents:
+                        marks[mark] = node
+                        update = True
             if update:
                 write(repo, marks)
             return node
                 write(repo, marks)
             return result
 
+        def tags(self):
+            """Merge bookmarks with normal tags"""
+            if self.tagscache:
+                return self.tagscache
+
+            tagscache = super(bookmark_repo, self).tags()
+            tagscache.update(parse(repo))
+            return tagscache
+
     repo.__class__ = bookmark_repo
 
+def uisetup(ui):
+    extensions.wrapfunction(repair, "strip", strip)
+    if ui.configbool('bookmarks', 'track.current'):
+        extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
+
+def updatecurbookmark(orig, ui, repo, *args, **opts):
+    '''Set the current bookmark
+
+    If the user updates to a bookmark we update the .hg/bookmarks.current
+    file.
+    '''
+    res = orig(ui, repo, *args, **opts)
+    rev = opts['rev']
+    if not rev and len(args) > 0:
+        rev = args[0]
+    setcurrent(repo, rev)
+    return res
+
 cmdtable = {
     "bookmarks":
         (bookmark,
           ('r', 'rev', '', _('revision')),
           ('d', 'delete', False, _('delete a given bookmark')),
           ('m', 'rename', '', _('rename a given bookmark'))],
-         _('hg bookmarks [-d] [-m NAME] [-r NAME] [NAME]')),
+         _('hg bookmarks [-f] [-d] [-m NAME] [-r NAME] [NAME]')),
 }

File hgext/bugzilla.py

 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
-#
-# hook extension to update comments of bugzilla bugs when changesets
-# that refer to bugs by id are seen.  this hook does not change bug
-# status, only comments.
-#
-# to configure, add items to '[bugzilla]' section of hgrc.
-#
-# to use, configure bugzilla extension and enable like this:
-#
-#   [extensions]
-#   hgext.bugzilla =
-#
-#   [hooks]
-#   # run bugzilla hook on every change pulled or pushed in here
-#   incoming.bugzilla = python:hgext.bugzilla.hook
-#
-# config items:
-#
-# section name is 'bugzilla'.
-#  [bugzilla]
-#
-# REQUIRED:
-#   host = bugzilla # mysql server where bugzilla database lives
-#   password = **   # user's password
-#   version = 2.16  # version of bugzilla installed
-#
-# OPTIONAL:
-#   bzuser = ...    # fallback bugzilla user name to record comments with
-#   db = bugs       # database to connect to
-#   notify = ...    # command to run to get bugzilla to send mail
-#   regexp = ...    # regexp to match bug ids (must contain one "()" group)
-#   strip = 0       # number of slashes to strip for url paths
-#   style = ...     # style file to use when formatting comments
-#   template = ...  # template to use when formatting comments
-#   timeout = 5     # database connection timeout (seconds)
-#   user = bugs     # user to connect to database as
-#   [web]
-#   baseurl = http://hgserver/... # root of hg web site for browsing commits
-#
-# if hg committer names are not same as bugzilla user names, use
-# "usermap" feature to map from committer email to bugzilla user name.
-# usermap can be in hgrc or separate config file.
-#
-#   [bugzilla]
-#   usermap = filename # cfg file with "committer"="bugzilla user" info
-#   [usermap]
-#   committer_email = bugzilla_user_name
+
+'''Bugzilla integration
+
+This hook extension adds comments on bugs in Bugzilla when changesets
+that refer to bugs by Bugzilla ID are seen. The hook does not change bug
+status.
+
+The hook updates the Bugzilla database directly. Only Bugzilla installations
+using MySQL are supported.
+
+The hook relies on a Bugzilla script to send bug change notification emails.
+That script changes between Bugzilla versions; the 'processmail' script used
+prior to 2.18 is replaced in 2.18 and subsequent versions by
+'config/sendbugmail.pl'. Note that these will be run by Mercurial as the user
+pushing the change; you will need to ensure the Bugzilla install file
+permissions are set appropriately.
+
+Configuring the extension:
+
+    [bugzilla]
+    host       Hostname of the MySQL server holding the Bugzilla database.
+    db         Name of the Bugzilla database in MySQL. Default 'bugs'.
+    user       Username to use to access MySQL server. Default 'bugs'.
+    password   Password to use to access MySQL server.
+    timeout    Database connection timeout (seconds). Default 5.
+    version    Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and
+               later, '2.18' for Bugzilla versions from 2.18 and '2.16' for
+               versions prior to 2.18.
+    bzuser     Fallback Bugzilla user name to record comments with, if
+               changeset committer cannot be found as a Bugzilla user.
+    bzdir      Bugzilla install directory. Used by default notify.
+               Default '/var/www/html/bugzilla'.
+    notify     The command to run to get Bugzilla to send bug change
+               notification emails. Substitutes from a map with 3 keys,
+               'bzdir', 'id' (bug id) and 'user' (committer bugzilla email).
+               Default depends on version; from 2.18 it is
+               "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s".
+    regexp     Regular expression to match bug IDs in changeset commit message.
+               Must contain one "()" group. The default expression matches
+               'Bug 1234', 'Bug no. 1234', 'Bug number 1234',
+               'Bugs 1234,5678', 'Bug 1234 and 5678' and variations thereof.
+               Matching is case insensitive.
+    style      The style file to use when formatting comments.
+    template   Template to use when formatting comments. Overrides
+               style if specified. In addition to the usual Mercurial
+               keywords, the extension specifies:
+                   {bug}       The Bugzilla bug ID.
+                   {root}      The full pathname of the Mercurial repository.
+                   {webroot}   Stripped pathname of the Mercurial repository.
+                   {hgweb}     Base URL for browsing Mercurial repositories.
+               Default 'changeset {node|short} in repo {root} refers '
+                       'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'
+    strip      The number of slashes to strip from the front of {root}
+               to produce {webroot}. Default 0.
+    usermap    Path of file containing Mercurial committer ID to Bugzilla user
+               ID mappings. If specified, the file should contain one mapping
+               per line, "committer"="Bugzilla user". See also the
+               [usermap] section.
+
+    [usermap]
+    Any entries in this section specify mappings of Mercurial committer ID
+    to Bugzilla user ID. See also [bugzilla].usermap.
+    "committer"="Bugzilla user"
+
+    [web]
+    baseurl    Base URL for browsing Mercurial repositories. Reference from
+               templates as {hgweb}.
+
+Activating the extension:
+
+    [extensions]
+    hgext.bugzilla =
+
+    [hooks]
+    # run bugzilla hook on every change pulled or pushed in here
+    incoming.bugzilla = python:hgext.bugzilla.hook
+
+Example configuration:
+
+This example configuration is for a collection of Mercurial repositories
+in /var/local/hg/repos/ used with a local Bugzilla 3.2 installation in
+/opt/bugzilla-3.2.
+
+    [bugzilla]
+    host=localhost
+    password=XYZZY
+    version=3.0
+    bzuser=unknown@domain.com
+    bzdir=/opt/bugzilla-3.2
+    template=Changeset {node|short} in {root|basename}.\\n{hgweb}/{webroot}/rev/{node|short}\\n\\n{desc}\\n
+    strip=5
+
+    [web]
+    baseurl=http://dev.domain.com/hg
+
+    [usermap]
+    user@emaildomain.com=user.name@bugzilladomain.com
+
+Commits add a comment to the Bugzilla bug record of the form:
+
+    Changeset 3b16791d6642 in repository-name.
+    http://dev.domain.com/hg/repository-name/rev/3b16791d6642
+
+    Changeset commit comment. Bug 1234.
+'''
 
 from mercurial.i18n import _
 from mercurial.node import short
         self.cursor = self.conn.cursor()
         self.longdesc_id = self.get_longdesc_id()
         self.user_ids = {}
+        self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
 
     def run(self, *args, **kwargs):
         '''run a query.'''
             unknown.pop(id, None)
         return util.sort(unknown.keys())
 
-    def notify(self, ids):
+    def notify(self, ids, committer):
         '''tell bugzilla to send mail.'''
 
         self.ui.status(_('telling bugzilla to send mail:\n'))
+        (user, userid) = self.get_bugzilla_user(committer)
         for id in ids:
             self.ui.status(_('  bug %s\n') % id)
-            cmd = self.ui.config('bugzilla', 'notify',
-                               'cd /var/www/html/bugzilla && '
-                               './processmail %s nobody@nowhere.com') % id
+            cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
+            bzdir = self.ui.config('bugzilla', 'bzdir', '/var/www/html/bugzilla')
+            try:
+                # Backwards-compatible with old notify string, which
+                # took one string. This will throw with a new format
+                # string.
+                cmd = cmdfmt % id
+            except TypeError:
+                cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
+            self.ui.note(_('running notify command %s\n') % cmd)
             fp = util.popen('(%s) 2>&1' % cmd)
             out = fp.read()
             ret = fp.close()
                 return bzuser
         return user
 
-    def add_comment(self, bugid, text, committer):
-        '''add comment to bug. try adding comment as committer of
-        changeset, otherwise as default bugzilla user.'''
+    def get_bugzilla_user(self, committer):
+        '''see if committer is a registered bugzilla user. Return
+        bugzilla username and userid if so. If not, return default
+        bugzilla username and userid.'''
         user = self.map_committer(committer)
         try:
             userid = self.get_user_id(user)
                     raise util.Abort(_('cannot find bugzilla user id for %s') %
                                      user)
                 userid = self.get_user_id(defaultuser)
+                user = defaultuser
             except KeyError:
                 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
                                  (user, defaultuser))
+        return (user, userid)
+
+    def add_comment(self, bugid, text, committer):
+        '''add comment to bug. try adding comment as committer of
+        changeset, otherwise as default bugzilla user.'''
+        (user, userid) = self.get_bugzilla_user(committer)
         now = time.strftime('%Y-%m-%d %H:%M:%S')
         self.run('''insert into longdescs
                     (bug_id, who, bug_when, thetext)
         self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
                     values (%s, %s, %s, %s)''',
                  (bugid, userid, now, self.longdesc_id))
+        self.conn.commit()
 
-class bugzilla_3_0(bugzilla_2_16):
+class bugzilla_2_18(bugzilla_2_16):
+    '''support for bugzilla 2.18 series.'''
+
+    def __init__(self, ui):
+        bugzilla_2_16.__init__(self, ui)
+        self.default_notify = "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
+
+class bugzilla_3_0(bugzilla_2_18):
     '''support for bugzilla 3.0 series.'''
 
     def __init__(self, ui):
-        bugzilla_2_16.__init__(self, ui)
+        bugzilla_2_18.__init__(self, ui)
 
     def get_longdesc_id(self):
         '''get identity of longdesc field'''
     # different schemas.
     _versions = {
         '2.16': bugzilla_2_16,
+        '2.18': bugzilla_2_18,
         '3.0':  bugzilla_3_0
         }
 
         mapfile = self.ui.config('bugzilla', 'style')
         tmpl = self.ui.config('bugzilla', 'template')
         t = cmdutil.changeset_templater(self.ui, self.repo,
-                                        False, mapfile, False)
+                                        False, None, mapfile, False)
         if not mapfile and not tmpl:
             tmpl = _('changeset {node|short} in repo {root} refers '
                      'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
         if ids:
             for id in ids:
                 bz.update(id, ctx)
-            bz.notify(ids)
+            bz.notify(ids, util.email(ctx.user()))
     except MySQLdb.MySQLError, err:
         raise util.Abort(_('database error: %s') % err[1])
 

File hgext/churn.py

 import os, sys
 import time, datetime
 
-def get_tty_width():
-    if 'COLUMNS' in os.environ:
-        try:
-            return int(os.environ['COLUMNS'])
-        except ValueError:
-            pass
-    try:
-        import termios, array, fcntl
-        for dev in (sys.stdout, sys.stdin):
-            try:
-                fd = dev.fileno()
-                if not os.isatty(fd):
-                    continue
-                arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
-                return array.array('h', arri)[1]
-            except ValueError:
-                pass
-    except ImportError:
-        pass
-    return 80
-
 def maketemplater(ui, repo, tmpl):
     tmpl = templater.parsestring(tmpl, quoted=False)
     try:
-        t = cmdutil.changeset_templater(ui, repo, False, None, False)
+        t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
     except SyntaxError, inst:
         raise util.Abort(inst.args[0])
     t.use_template(tmpl)
             newpct = int(100.0 * count / max(len(repo), 1))
             if pct < newpct:
                 pct = newpct
-                ui.write(_("\rGenerating stats: %d%%") % pct)
+                ui.write(_("\rgenerating stats: %d%%") % pct)
                 sys.stdout.flush()
 
     if opts.get('progress'):
 
 
 def churn(ui, repo, *pats, **opts):
-    '''Graph count of revisions grouped by template
+    '''graph count of revisions grouped by template
 
     Will graph count of changed lines or revisions grouped by template or
     alternatively by date, if dateformat is used. In this case it will override
     maxcount = float(max([v for k, v in rate]))
     maxname = max([len(k) for k, v in rate])
 
-    ttywidth = get_tty_width()
+    ttywidth = util.termwidth()
     ui.debug(_("assuming %i character terminal\n") % ttywidth)
     width = ttywidth - maxname - 2 - 6 - 2 - 2
 

File hgext/color.py

 _diff_prefixes = [('diff', 'diffline'),
                   ('copy', 'extended'),
                   ('rename', 'extended'),
+                  ('old', 'extended'),
                   ('new', 'extended'),
                   ('deleted', 'extended'),
                   ('---', 'file_a'),

File hgext/convert/__init__.py

 '''converting foreign VCS repositories to Mercurial'''
 
 import convcmd
+import cvsps
 from mercurial import commands
 from mercurial.i18n import _
 
 # Commands definition was moved elsewhere to ease demandload job.
 
 def convert(ui, src, dest=None, revmapfile=None, **opts):
-    """Convert a foreign SCM repository to a Mercurial one.
+    """convert a foreign SCM repository to a Mercurial one.
 
     Accepted source formats [identifiers]:
     - Mercurial [hg]
 def debugsvnlog(ui, **opts):
     return convcmd.debugsvnlog(ui, **opts)
 
-commands.norepo += " convert debugsvnlog"
+def debugcvsps(ui, *args, **opts):
+    '''create changeset information from CVS
+
+    This command is intended as a debugging tool for the CVS to Mercurial
+    converter, and can be used as a direct replacement for cvsps.
+
+    Hg debugcvsps reads the CVS rlog for current directory (or any named
+    directory) in the CVS repository, and converts the log to a series of
+    changesets based on matching commit log entries and dates.'''
+    return cvsps.debugcvsps(ui, *args, **opts)
+
+commands.norepo += " convert debugsvnlog debugcvsps"
 
 cmdtable = {
     "convert":
         (debugsvnlog,
          [],
          'hg debugsvnlog'),
+    "debugcvsps":
+        (debugcvsps,
+         [
+          # Main options shared with cvsps-2.1
+          ('b', 'branches', [], _('only return changes on specified branches')),
+          ('p', 'prefix', '', _('prefix to remove from file names')),
+          ('r', 'revisions', [], _('only return changes after or between specified tags')),
+          ('u', 'update-cache', None, _("update cvs log cache")),
+          ('x', 'new-cache', None, _("create new cvs log cache")),
+          ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
+          ('', 'root', '', _('specify cvsroot')),
+          # Options specific to builtin cvsps
+          ('', 'parents', '', _('show parent changesets')),
+          ('', 'ancestors', '', _('show current changeset in ancestor branches')),
+          # Options that are ignored for compatibility with cvsps-2.1
+          ('A', 'cvs-direct', None, _('ignored for compatibility')),
+         ],
+         _('hg debugcvsps [OPTION]... [PATH]...')),
 }

File hgext/convert/common.py

             except TypeError:
                 pass
         cmdline = [util.shellquote(arg) for arg in cmdline]
-        cmdline += ['2>', util.nulldev, '<', util.nulldev]
+        if not self.ui.debugflag:
+            cmdline += ['2>', util.nulldev]
+        cmdline += ['<', util.nulldev]
         cmdline = ' '.join(cmdline)
         return cmdline
 
         self._read()
 
     def _read(self):
-        if self.path is None:
+        if not self.path:
             return
         try:
             fp = open(self.path, 'r')

File hgext/convert/convcmd.py

                         _('Overriding mapping for author %s, was %s, will be %s\n')
                         % (srcauthor, self.authors[srcauthor], dstauthor))
                 else:
-                    self.ui.debug(_('Mapping author %s to %s\n')
+                    self.ui.debug(_('mapping author %s to %s\n')
                                   % (srcauthor, dstauthor))
                     self.authors[srcauthor] = dstauthor
             except IndexError:

File hgext/convert/cvs.py

                             if branch == "HEAD":
                                 branch = ""
                             if branch:
-                                latest = None
+                                latest = 0
                                 # the last changeset that contains a base
                                 # file is our parent
                                 for r in oldrevs:
-                                    latest = max(filerevids.get(r, None), latest)
+                                    latest = max(filerevids.get(r, 0), latest)
                                 if latest:
                                     p = [latest]
 

File hgext/convert/cvsps

-#!/usr/bin/env python
-#
-# Commandline front-end for cvsps.py
-#
-# Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-import sys
-from mercurial import util
-from mercurial.i18n import _
-from optparse import OptionParser, SUPPRESS_HELP
-from hgext.convert.cvsps import createlog, createchangeset, logerror
-
-def main():
-    '''Main program to mimic cvsps.'''
-
-    op = OptionParser(usage='%prog [-bpruvxz] path',
-                      description='Read CVS rlog for current directory or named '
-                                  'path in repository, and convert the log to changesets '
-                                  'based on matching commit log entries and dates.')
-
-    # Options that are ignored for compatibility with cvsps-2.1
-    op.add_option('-A', dest='Ignore', action='store_true', help=SUPPRESS_HELP)
-    op.add_option('--cvs-direct', dest='Ignore', action='store_true', help=SUPPRESS_HELP)
-    op.add_option('-q', dest='Ignore', action='store_true', help=SUPPRESS_HELP)
-
-    # Main options shared with cvsps-2.1
-    op.add_option('-b', dest='Branches', action='append', default=[],
-                  help='Only return changes on specified branches')
-    op.add_option('-p', dest='Prefix', action='store', default='',
-                  help='Prefix to remove from file names')
-    op.add_option('-r', dest='Revisions', action='append', default=[],
-                  help='Only return changes after or between specified tags')
-    op.add_option('-u', dest='Cache', action='store_const', const='update',
-                  help="Update cvs log cache")
-    op.add_option('-v', dest='Verbose', action='count', default=0,
-                  help='Be verbose')
-    op.add_option('-x', dest='Cache', action='store_const', const='write',
-                  help="Create new cvs log cache")
-    op.add_option('-z', dest='Fuzz', action='store', type='int', default=60,
-                  help='Set commit time fuzz', metavar='seconds')
-    op.add_option('--root', dest='Root', action='store', default='',
-                  help='Specify cvsroot', metavar='cvsroot')
-
-    # Options specific to this version
-    op.add_option('--parents', dest='Parents', action='store_true',
-                  help='Show parent changesets')
-    op.add_option('--ancestors', dest='Ancestors', action='store_true',
-                  help='Show current changeset in ancestor branches')
-
-    options, args = op.parse_args()
-
-    # Create a ui object for printing progress messages
-    class UI:
-        def __init__(self, verbose):
-            if verbose:
-                self.status = self.message
-            if verbose>1:
-                self.note = self.message
-            if verbose>2:
-                self.debug = self.message
-        def message(self, msg):
-            sys.stderr.write(msg)
-        def nomessage(self, msg):
-            pass
-        status = nomessage
-        note = nomessage
-        debug = nomessage
-    ui = UI(options.Verbose)
-
-    try:
-        if args:
-            log = []
-            for d in args:
-                log += createlog(ui, d, root=options.Root, cache=options.Cache)
-        else:
-            log = createlog(ui, root=options.Root, cache=options.Cache)
-    except logerror, e:
-        print e
-        return
-
-    changesets = createchangeset(ui, log, options.Fuzz)
-    del log
-
-    # Print changesets (optionally filtered)
-
-    off = len(options.Revisions)
-    branches = {}    # latest version number in each branch
-    ancestors = {}   # parent branch
-    for cs in changesets:
-
-        if options.Ancestors:
-            if cs.branch not in branches and cs.parents and cs.parents[0].id:
-                ancestors[cs.branch] = changesets[cs.parents[0].id-1].branch, cs.parents[0].id
-            branches[cs.branch] = cs.id
-
-        # limit by branches
-        if options.Branches and (cs.branch or 'HEAD') not in options.Branches:
-            continue
-
-        if not off:
-            # Note: trailing spaces on several lines here are needed to have
-            #       bug-for-bug compatibility with cvsps.
-            print '---------------------'
-            print 'PatchSet %d ' % cs.id
-            print 'Date: %s' % util.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2')
-            print 'Author: %s' % cs.author
-            print 'Branch: %s' % (cs.branch or 'HEAD')
-            print 'Tag%s: %s ' % (['', 's'][len(cs.tags)>1],
-                                  ','.join(cs.tags) or '(none)')
-            if options.Parents and cs.parents:
-                if len(cs.parents)>1:
-                    print 'Parents: %s' % (','.join([str(p.id) for p in cs.parents]))
-                else:
-                    print 'Parent: %d' % cs.parents[0].id
-
-            if options.Ancestors:
-                b = cs.branch
-                r = []
-                while b:
-                    b, c = ancestors[b]
-                    r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
-                if r:
-                    print 'Ancestors: %s' % (','.join(r))
-
-            print 'Log:'
-            print cs.comment
-            print
-            print 'Members: '
-            for f in cs.entries:
-                fn = f.file
-                if fn.startswith(options.Prefix):
-                    fn = fn[len(options.Prefix):]
-                print '\t%s:%s->%s%s ' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
-                                          '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead])
-            print
-
-        # have we seen the start tag?
-        if options.Revisions and off:
-            if options.Revisions[0] == str(cs.id) or \
-                options.Revisions[0] in cs.tags:
-                off = False
-
-        # see if we reached the end tag
-        if len(options.Revisions)>1 and not off:
-            if options.Revisions[1] == str(cs.id) or \
-                options.Revisions[1] in cs.tags:
-                break
-
-
-if __name__ == '__main__':
-    main()

File hgext/convert/cvsps.py

     ui.note(_("running %s\n") % (' '.join(cmd)))
     ui.debug(_("prefix=%r directory=%r root=%r\n") % (prefix, directory, root))
 
-    for line in util.popen(' '.join(cmd)):
+    pfp = util.popen(' '.join(cmd))
+    peek = pfp.readline()
+    while True:
+        line = peek
+        if line == '':
+            break
+        peek = pfp.readline()
         if line.endswith('\n'):
             line = line[:-1]
         #ui.debug('state=%d line=%r\n' % (state, line))
             if re_31.match(line):
                 state = 5
             else:
-                assert not re_32.match(line), _('Must have at least some revisions')
+                assert not re_32.match(line), _('must have at least some revisions')
 
         elif state == 5:
             # expecting revision number and possibly (ignored) lock indication
                 e.branches = [tuple([int(y) for y in x.strip().split('.')])
                                 for x in m.group(1).split(';')]
                 state = 8
-            elif re_31.match(line):
+            elif re_31.match(line) and re_50.match(peek):
                 state = 5
                 store = True
             elif re_32.match(line):
     ui.status(_('%d changeset entries\n') % len(changesets))
 
     return changesets
+
+
+def debugcvsps(ui, *args, **opts):
+    '''Read CVS rlog for current directory or named path in repository, and
+    convert the log to changesets based on matching commit log entries and dates.'''
+
+    if opts["new_cache"]:
+        cache = "write"
+    elif opts["update_cache"]:
+        cache = "update"
+    else:
+        cache = None
+
+    revisions = opts["revisions"]
+
+    try:
+        if args:
+            log = []
+            for d in args:
+                log += createlog(ui, d, root=opts["root"], cache=cache)
+        else:
+            log = createlog(ui, root=opts["root"], cache=cache)
+    except logerror, e:
+        ui.write("%r\n"%e)
+        return
+
+    changesets = createchangeset(ui, log, opts["fuzz"])
+    del log
+
+    # Print changesets (optionally filtered)
+
+    off = len(revisions)
+    branches = {}    # latest version number in each branch
+    ancestors = {}   # parent branch
+    for cs in changesets:
+
+        if opts["ancestors"]:
+            if cs.branch not in branches and cs.parents and cs.parents[0].id:
+                ancestors[cs.branch] = changesets[cs.parents[0].id-1].branch, cs.parents[0].id
+            branches[cs.branch] = cs.id
+
+        # limit by branches
+        if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
+            continue
+
+        if not off:
+            # Note: trailing spaces on several lines here are needed to have
+            #       bug-for-bug compatibility with cvsps.
+            ui.write('---------------------\n')
+            ui.write('PatchSet %d \n' % cs.id)
+            ui.write('Date: %s\n' % util.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2'))
+            ui.write('Author: %s\n' % cs.author)
+            ui.write('Branch: %s\n' % (cs.branch or 'HEAD'))
+            ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags)>1],
+                                  ','.join(cs.tags) or '(none)'))
+            if opts["parents"] and cs.parents:
+                if len(cs.parents)>1:
+                    ui.write('Parents: %s\n' % (','.join([str(p.id) for p in cs.parents])))
+                else:
+                    ui.write('Parent: %d\n' % cs.parents[0].id)
+
+            if opts["ancestors"]:
+                b = cs.branch
+                r = []
+                while b:
+                    b, c = ancestors[b]
+                    r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
+                if r:
+                    ui.write('Ancestors: %s\n' % (','.join(r)))
+
+            ui.write('Log:\n')
+            ui.write('%s\n\n' % cs.comment)
+            ui.write('Members: \n')
+            for f in cs.entries:
+                fn = f.file
+                if fn.startswith(opts["prefix"]):
+                    fn = fn[len(opts["prefix"]):]
+                ui.write('\t%s:%s->%s%s \n' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
+                                          '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead]))
+            ui.write('\n')
+
+        # have we seen the start tag?
+        if revisions and off:
+            if revisions[0] == str(cs.id) or \
+                revisions[0] in cs.tags:
+                off = False
+
+        # see if we reached the end tag
+        if len(revisions)>1 and not off:
+            if revisions[1] == str(cs.id) or \
+                revisions[1] in cs.tags:
+                break

File hgext/convert/darcs.py

         if ElementTree is None:
             raise util.Abort(_("Python ElementTree module is not available"))
 
-        if not os.path.exists(os.path.join(path, '_darcs', 'inventory')):
+        if not os.path.exists(os.path.join(path, '_darcs', 'inventories')):
             raise NoRepo("%s does not look like a darcs repo" % path)
 
         self.path = os.path.realpath(path)

File hgext/convert/gnuarch.py

 from common import NoRepo, commandline, commit, converter_source
 from mercurial.i18n import _
 from mercurial import util
-import os, shutil, tempfile, stat
+import os, shutil, tempfile, stat, locale
+from email.Parser import Parser
 
 class gnuarch_source(converter_source, commandline):
 
             self.summary = ''
             self.date = None
             self.author = ''
+            self.continuationof = None
             self.add_files = []
             self.mod_files = []
             self.del_files = []
         self.parents = {}
         self.tags = {}
         self.modecache = {}
+        self.catlogparser = Parser()
+        self.locale = locale.getpreferredencoding()
+        self.archives = []
 
     def before(self):
+        # Get registered archives
+        self.archives = [i.rstrip('\n')
+                         for i in self.runlines0('archives', '-n')]
+
         if self.execmd == 'tla':
             output = self.run0('tree-version', self.path)
         else:
             output = self.run0('tree-version', '-d', self.path)
         self.treeversion = output.strip()
 
-        self.ui.status(_('analyzing tree version %s...\n') % self.treeversion)
-
         # Get name of temporary directory
         version = self.treeversion.split('/')
         self.tmppath = os.path.join(tempfile.gettempdir(),
                                     'hg-%s' % version[1])
 
         # Generate parents dictionary
-        child = []
-        output, status = self.runlines('revisions', self.treeversion)
-        self.checkexit(status, 'archive registered?')
-        for l in output:
-            rev = l.strip()
-            self.changes[rev] = self.gnuarch_rev(rev)
+        self.parents[None] = []
+        treeversion = self.treeversion
+        child = None
+        while treeversion:
+            self.ui.status(_('analyzing tree version %s...\n') % treeversion)
 
-            # Read author, date and summary
-            catlog = self.runlines0('cat-log', '-d', self.path, rev)
-            self._parsecatlog(catlog, rev)
+            archive = treeversion.split('/')[0]
+            if archive not in self.archives:
+                self.ui.status(_('tree analysis stopped because it points to an unregistered archive %s...\n') % archive)
+                break
 
-            self.parents[rev] = child
-            child = [rev]
-            if rev == self.rev:
-                break
-        self.parents[None] = child
+            # Get the complete list of revisions for that tree version
+            output, status = self.runlines('revisions', '-r', '-f', treeversion)
+            self.checkexit(status, 'failed retrieveing revisions for %s' % treeversion)
+
+            # No new iteration unless a revision has a continuation-of header
+            treeversion = None
+
+            for l in output:
+                rev = l.strip()
+                self.changes[rev] = self.gnuarch_rev(rev)
+                self.parents[rev] = []
+
+                # Read author, date and summary
+                catlog, status = self.run('cat-log', '-d', self.path, rev)
+                if status:
+                    catlog  = self.run0('cat-archive-log', rev)
+                self._parsecatlog(catlog, rev)
+
+                # Populate the parents map
+                self.parents[child].append(rev)
+
+                # Keep track of the current revision as the child of the next
+                # revision scanned
+                child = rev
+
+                # Check if we have to follow the usual incremental history
+                # or if we have to 'jump' to a different treeversion given
+                # by the continuation-of header.
+                if self.changes[rev].continuationof:
+                    treeversion = '--'.join(self.changes[rev].continuationof.split('--')[:-1])
+                    break
+
+                # If we reached a base-0 revision w/o any continuation-of
+                # header, it means the tree history ends here.
+                if rev[-6:] == 'base-0':
+                    break
 
     def after(self):
         self.ui.debug(_('cleaning up %s\n') % self.tmppath)
     def getcommit(self, rev):
         changes = self.changes[rev]
         return commit(author = changes.author, date = changes.date,
-                      desc = changes.summary, parents = self.parents[rev])
+                      desc = changes.summary, parents = self.parents[rev], rev=rev)
 
     def gettags(self):
         return self.tags
         return os.system(cmdline)
 
     def _update(self, rev):
-        if rev == 'base-0':
-            # Initialise 'base-0' revision
+        self.ui.debug(_('applying revision %s...\n') % rev)
+        changeset, status = self.runlines('replay', '-d', self.tmppath,
+                                              rev)
+        if status:
+            # Something went wrong while merging (baz or tla
+            # issue?), get latest revision and try from there
+            shutil.rmtree(self.tmppath, ignore_errors=True)
             self._obtainrevision(rev)
         else:
-            self.ui.debug(_('applying revision %s...\n') % rev)
-            revision = '%s--%s' % (self.treeversion, rev)
-            changeset, status = self.runlines('replay', '-d', self.tmppath,
-                                              revision)
-            if status:
-                # Something went wrong while merging (baz or tla
-                # issue?), get latest revision and try from there
-                shutil.rmtree(self.tmppath, ignore_errors=True)
-                self._obtainrevision(rev)
-            else:
-                old_rev = self.parents[rev][0]
-                self.ui.debug(_('computing changeset between %s and %s...\n')
-                              % (old_rev, rev))
-                rev_a = '%s--%s' % (self.treeversion, old_rev)
-                rev_b = '%s--%s' % (self.treeversion, rev)
-                self._parsechangeset(changeset, rev)
+            old_rev = self.parents[rev][0]
+            self.ui.debug(_('computing changeset between %s and %s...\n')
+                          % (old_rev, rev))
+            self._parsechangeset(changeset, rev)
 
     def _getfile(self, name, rev):
         mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
 
     def _obtainrevision(self, rev):
         self.ui.debug(_('obtaining revision %s...\n') % rev)
-        revision = '%s--%s' % (self.treeversion, rev)
-        output = self._execute('get', revision, self.tmppath)
+        output = self._execute('get', rev, self.tmppath)
         self.checkexit(output)
         self.ui.debug(_('analysing revision %s...\n') % rev)
         files = self._readcontents(self.tmppath)
         return path
 
     def _parsecatlog(self, data, rev):
-        summary = []
-        for l in data:
-            l = l.strip()
-            if summary:
-                summary.append(l)
-            elif l.startswith('Summary:'):
-                summary.append(l[len('Summary: '):])
-            elif l.startswith('Standard-date:'):
-                date = l[len('Standard-date: '):]
-                strdate = util.strdate(date, '%Y-%m-%d %H:%M:%S')
-                self.changes[rev].date = util.datestr(strdate)
-            elif l.startswith('Creator:'):
-                self.changes[rev].author = l[len('Creator: '):]
-        self.changes[rev].summary = '\n'.join(summary)
+        try:
+            catlog = self.catlogparser.parsestr(data)
+
+            # Commit date
+            self.changes[rev].date = util.datestr(
+                util.strdate(catlog['Standard-date'],
+                             '%Y-%m-%d %H:%M:%S'))
+
+            # Commit author
+            self.changes[rev].author = self.recode(catlog['Creator'])
+
+            # Commit description
+            self.changes[rev].summary = '\n\n'.join((catlog['Summary'],
+                                                    catlog.get_payload()))
+            self.changes[rev].summary = self.recode(self.changes[rev].summary)
+
+            # Commit revision origin when dealing with a branch or tag
+            if catlog.has_key('Continuation-of'):