# HG changeset patch
# User Thomas Arendsen Hein
# Date 1202468117 -3600
# Node ID 012ad48a07fafb7460b194b169031f5968801bc8
# Parent 9360a58a09e6aa11d76a572304eafc6f5c765551# Parent 6605a03cbf876e3c47fb892bb916f40930269b0d
merge with crew-stable
diff -r 6605a03cbf87 -r 012ad48a07fa .hgignore
--- a/.hgignore Fri Feb 08 11:50:37 2008 +0100
+++ b/.hgignore Fri Feb 08 11:55:17 2008 +0100
@@ -22,8 +22,10 @@
MANIFEST
patches
mercurial/__version__.py
+Output/Mercurial-*.exe
.DS_Store
+tags
+cscope.*
syntax: regexp
^\.pc/
-Output/Mercurial-[0-9.]*.exe
diff -r 6605a03cbf87 -r 012ad48a07fa CONTRIBUTORS
--- a/CONTRIBUTORS Fri Feb 08 11:50:37 2008 +0100
+++ b/CONTRIBUTORS Fri Feb 08 11:55:17 2008 +0100
@@ -1,4 +1,7 @@
-Andrea Arcangeli
+[This file is here for historical purposes, all recent contributors
+should appear in the changelog directly]
+
+Andrea Arcangeli
Thomas Arendsen Hein
Goffredo Baroncelli
Muli Ben-Yehuda
@@ -36,5 +39,3 @@
Rafael Villar Burke
Tristan Wibberley
Mark Williamson
-
-If you are a contributor and don't see your name here, please let me know.
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/bash_completion
--- a/contrib/bash_completion Fri Feb 08 11:50:37 2008 +0100
+++ b/contrib/bash_completion Fri Feb 08 11:55:17 2008 +0100
@@ -305,6 +305,15 @@
_hg_ext_mq_patchlist qunapplied
}
+_hg_cmd_qgoto()
+{
+ if [[ "$prev" = @(-n|--name) ]]; then
+ _hg_ext_mq_queues
+ return
+ fi
+ _hg_ext_mq_patchlist qseries
+}
+
_hg_cmd_qdelete()
{
local qcmd=qunapplied
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/churn.py
--- a/contrib/churn.py Fri Feb 08 11:50:37 2008 +0100
+++ b/contrib/churn.py Fri Feb 08 11:55:17 2008 +0100
@@ -12,7 +12,7 @@
#
from mercurial.i18n import gettext as _
-from mercurial import hg, mdiff, cmdutil, ui, util, templater, node
+from mercurial import hg, mdiff, cmdutil, ui, util, templatefilters, node
import os, sys
def get_tty_width():
@@ -69,7 +69,7 @@
modified, added, removed, deleted, unknown = changes
who = repo.changelog.read(node2)[1]
- who = templater.email(who) # get the email of the person
+ who = util.email(who) # get the email of the person
mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
@@ -114,23 +114,24 @@
who, lines = __gather(ui, repo, node1, node2)
# remap the owner if possible
- if amap.has_key(who):
+ if who in amap:
ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
who = amap[who]
- if not stats.has_key(who):
+ if not who in stats:
stats[who] = 0
stats[who] += lines
ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
if progress:
+ nr_revs = max(nr_revs, 1)
if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
- ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),))
+ ui.write("\rGenerating stats: %d%%" % (int(100.0*cur_rev/nr_revs),))
sys.stdout.flush()
if progress:
- ui.write("done\n")
+ ui.write("\r")
sys.stdout.flush()
return stats
@@ -144,6 +145,7 @@
return s[0:l]
def graph(n, maximum, width, char):
+ maximum = max(1, maximum)
n = int(n * width / float(maximum))
return char * (n)
@@ -178,6 +180,8 @@
ordered = stats.items()
ordered.sort(lambda x, y: cmp(y[1], x[1]))
+ if not ordered:
+ return
maximum = ordered[0][1]
width = get_tty_width()
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/hgk
--- a/contrib/hgk Fri Feb 08 11:50:37 2008 +0100
+++ b/contrib/hgk Fri Feb 08 11:55:17 2008 +0100
@@ -274,6 +274,7 @@
set comname {}
set comdate {}
set rev {}
+ set branch {}
if {![info exists nchildren($id)]} {
set children($id) {}
set nchildren($id) 0
@@ -310,6 +311,8 @@
set comname [join [lrange $line 1 [expr {$x - 1}]]]
} elseif {$tag == "revision"} {
set rev [lindex $line 1]
+ } elseif {$tag == "branch"} {
+ set branch [join [lrange $line 1 end]]
}
}
} else {
@@ -334,7 +337,7 @@
set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
}
set commitinfo($id) [list $headline $auname $audate \
- $comname $comdate $comment $rev]
+ $comname $comdate $comment $rev $branch]
}
proc readrefs {} {
@@ -649,7 +652,7 @@
if {$stuffsaved} return
if {![winfo viewable .]} return
catch {
- set f [open "~/.gitk-new" w]
+ set f [open "~/.hgk-new" w]
puts $f [list set mainfont $mainfont]
puts $f [list set curidfont $curidfont]
puts $f [list set textfont $textfont]
@@ -687,7 +690,7 @@
puts $f "#"
puts $f "set authorcolors {$authorcolors}"
close $f
- file rename -force "~/.gitk-new" "~/.gitk"
+ file rename -force "~/.hgk-new" "~/.hgk"
}
set stuffsaved 1
}
@@ -2286,6 +2289,9 @@
$ctext mark gravity fmark.0 left
set info $commitinfo($id)
$ctext insert end "Revision: [lindex $info 6]\n"
+ if {[llength [lindex $info 7]] > 0} {
+ $ctext insert end "Branch: [lindex $info 7]\n"
+ }
$ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
$ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
if {[info exists idtags($id)]} {
@@ -3844,10 +3850,10 @@
set colors {green red blue magenta darkgrey brown orange}
set authorcolors {
- deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey
+ black blue deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey
}
-catch {source ~/.gitk}
+catch {source ~/.hgk}
if {$curidfont == ""} { # initialize late based on current mainfont
set curidfont "$mainfont bold italic underline"
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/hgwebdir.fcgi
--- a/contrib/hgwebdir.fcgi Fri Feb 08 11:50:37 2008 +0100
+++ b/contrib/hgwebdir.fcgi Fri Feb 08 11:55:17 2008 +0100
@@ -23,6 +23,7 @@
from mercurial.hgweb.hgwebdir_mod import hgwebdir
from mercurial.hgweb.request import wsgiapplication
+from mercurial import dispatch, ui
from flup.server.fcgi import WSGIServer
# The config file looks like this. You can have paths to individual
@@ -44,7 +45,8 @@
# 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")
+def web_app(ui):
+ return lambda: hgwebdir("hgweb.config", ui)
-WSGIServer(wsgiapplication(make_web_app)).run()
+u = ui.ui(report_untrusted=False, interactive=False)
+dispatch.profiled(u, lambda: WSGIServer(wsgiapplication(web_app(u))).run())
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/mergetools.hgrc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/mergetools.hgrc Fri Feb 08 11:55:17 2008 +0100
@@ -0,0 +1,49 @@
+# Some default global settings for common merge tools
+
+[merge-tools]
+kdiff3.args=--auto -L1 base --L2 local --L3 other $base $local $other -o $output
+kdiff3.regkey=Software\KDiff3
+kdiff3.regappend=\kdiff3.exe
+kdiff3.fixeol=True
+kdiff3.gui=True
+
+gvimdiff.args=--nofork -d -g -O $local $other $base
+gvimdiff.regkey=Software\Vim\GVim
+gvimdiff.regname=path
+gvimdiff.priority=-9
+
+merge.checkconflicts=True
+merge.priority=-10
+
+gpyfm.gui=True
+
+meld.gui=True
+
+tkdiff.args=$local $other -a $base -o $output
+tkdiff.gui=True
+tkdiff.priority=-8
+
+xxdiff.args=--show-merged-pane --exit-with-merge-status --title1 local --title2 base --title3 other --merged-filename $output --merge $local $base $other
+xxdiff.gui=True
+xxdiff.priority=-8
+
+diffmerge.args=--nosplash --merge --title1=base --title2=local --title3=other $base $local $other
+diffmerge.gui=True
+
+p4merge.args=$base $local $other $output
+p4merge.regkey=Software\Perforce\Environment
+p4merge.regname=P4INSTROOT
+p4merge.regappend=\p4merge.exe
+p4merge.gui=True
+p4merge.priority=-8
+
+tortoisemerge.args=/base: $output /mine:$local /theirs:$other /merged:$output
+tortoisemerge.regkey=Software\TortoiseSVN
+tortoisemerge.gui=True
+
+ecmerge.args=$base $local $other --mode=merge3 --title0=base --title1=local --title2=other --to=$output
+ecmerge.regkey=Software\Elli\xc3\xa9 Computing\Merge
+ecmerge.gui=True
+
+filemerge.args=-left $other -right $local -ancestor $base -merge $output
+filemerge.gui=True
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/simplemerge
--- a/contrib/simplemerge Fri Feb 08 11:50:37 2008 +0100
+++ b/contrib/simplemerge Fri Feb 08 11:55:17 2008 +0100
@@ -1,503 +1,11 @@
#!/usr/bin/env python
-# Copyright (C) 2004, 2005 Canonical Ltd
-#
-# This program 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.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-
-# mbp: "you know that thing where cvs gives you conflict markers?"
-# s: "i hate that."
from mercurial import demandimport
demandimport.enable()
-from mercurial import util, mdiff, fancyopts
+import os, sys
from mercurial.i18n import _
-
-
-class CantReprocessAndShowBase(Exception):
- pass
-
-
-def warn(message):
- sys.stdout.flush()
- sys.stderr.write(message)
- sys.stderr.flush()
-
-
-def intersect(ra, rb):
- """Given two ranges return the range where they intersect or None.
-
- >>> intersect((0, 10), (0, 6))
- (0, 6)
- >>> intersect((0, 10), (5, 15))
- (5, 10)
- >>> intersect((0, 10), (10, 15))
- >>> intersect((0, 9), (10, 15))
- >>> intersect((0, 9), (7, 15))
- (7, 9)
- """
- assert ra[0] <= ra[1]
- assert rb[0] <= rb[1]
-
- sa = max(ra[0], rb[0])
- sb = min(ra[1], rb[1])
- if sa < sb:
- return sa, sb
- else:
- return None
-
-
-def compare_range(a, astart, aend, b, bstart, bend):
- """Compare a[astart:aend] == b[bstart:bend], without slicing.
- """
- if (aend-astart) != (bend-bstart):
- return False
- for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
- if a[ia] != b[ib]:
- return False
- else:
- return True
-
-
-
-
-class Merge3Text(object):
- """3-way merge of texts.
-
- Given strings BASE, OTHER, THIS, tries to produce a combined text
- incorporating the changes from both BASE->OTHER and BASE->THIS."""
- def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
- self.basetext = basetext
- self.atext = atext
- self.btext = btext
- if base is None:
- base = mdiff.splitnewlines(basetext)
- if a is None:
- a = mdiff.splitnewlines(atext)
- if b is None:
- b = mdiff.splitnewlines(btext)
- self.base = base
- self.a = a
- self.b = b
-
-
-
- def merge_lines(self,
- name_a=None,
- name_b=None,
- name_base=None,
- start_marker='<<<<<<<',
- mid_marker='=======',
- end_marker='>>>>>>>',
- base_marker=None,
- reprocess=False):
- """Return merge in cvs-like form.
- """
- self.conflicts = False
- newline = '\n'
- if len(self.a) > 0:
- if self.a[0].endswith('\r\n'):
- newline = '\r\n'
- elif self.a[0].endswith('\r'):
- newline = '\r'
- if base_marker and reprocess:
- raise CantReprocessAndShowBase()
- if name_a:
- start_marker = start_marker + ' ' + name_a
- if name_b:
- end_marker = end_marker + ' ' + name_b
- if name_base and base_marker:
- base_marker = base_marker + ' ' + name_base
- merge_regions = self.merge_regions()
- if reprocess is True:
- merge_regions = self.reprocess_merge_regions(merge_regions)
- for t in merge_regions:
- what = t[0]
- if what == 'unchanged':
- for i in range(t[1], t[2]):
- yield self.base[i]
- elif what == 'a' or what == 'same':
- for i in range(t[1], t[2]):
- yield self.a[i]
- elif what == 'b':
- for i in range(t[1], t[2]):
- yield self.b[i]
- elif what == 'conflict':
- self.conflicts = True
- yield start_marker + newline
- for i in range(t[3], t[4]):
- yield self.a[i]
- if base_marker is not None:
- yield base_marker + newline
- for i in range(t[1], t[2]):
- yield self.base[i]
- yield mid_marker + newline
- for i in range(t[5], t[6]):
- yield self.b[i]
- yield end_marker + newline
- else:
- raise ValueError(what)
-
-
-
-
-
- def merge_annotated(self):
- """Return merge with conflicts, showing origin of lines.
-
- Most useful for debugging merge.
- """
- for t in self.merge_regions():
- what = t[0]
- if what == 'unchanged':
- for i in range(t[1], t[2]):
- yield 'u | ' + self.base[i]
- elif what == 'a' or what == 'same':
- for i in range(t[1], t[2]):
- yield what[0] + ' | ' + self.a[i]
- elif what == 'b':
- for i in range(t[1], t[2]):
- yield 'b | ' + self.b[i]
- elif what == 'conflict':
- yield '<<<<\n'
- for i in range(t[3], t[4]):
- yield 'A | ' + self.a[i]
- yield '----\n'
- for i in range(t[5], t[6]):
- yield 'B | ' + self.b[i]
- yield '>>>>\n'
- else:
- raise ValueError(what)
-
-
-
-
-
- def merge_groups(self):
- """Yield sequence of line groups. Each one is a tuple:
-
- 'unchanged', lines
- Lines unchanged from base
-
- 'a', lines
- Lines taken from a
-
- 'same', lines
- Lines taken from a (and equal to b)
-
- 'b', lines
- Lines taken from b
-
- 'conflict', base_lines, a_lines, b_lines
- Lines from base were changed to either a or b and conflict.
- """
- for t in self.merge_regions():
- what = t[0]
- if what == 'unchanged':
- yield what, self.base[t[1]:t[2]]
- elif what == 'a' or what == 'same':
- yield what, self.a[t[1]:t[2]]
- elif what == 'b':
- yield what, self.b[t[1]:t[2]]
- elif what == 'conflict':
- yield (what,
- self.base[t[1]:t[2]],
- self.a[t[3]:t[4]],
- self.b[t[5]:t[6]])
- else:
- raise ValueError(what)
-
-
- def merge_regions(self):
- """Return sequences of matching and conflicting regions.
-
- This returns tuples, where the first value says what kind we
- have:
-
- 'unchanged', start, end
- Take a region of base[start:end]
-
- 'same', astart, aend
- b and a are different from base but give the same result
-
- 'a', start, end
- Non-clashing insertion from a[start:end]
-
- Method is as follows:
-
- The two sequences align only on regions which match the base
- and both descendents. These are found by doing a two-way diff
- of each one against the base, and then finding the
- intersections between those regions. These "sync regions"
- are by definition unchanged in both and easily dealt with.
-
- The regions in between can be in any of three cases:
- conflicted, or changed on only one side.
- """
-
- # section a[0:ia] has been disposed of, etc
- iz = ia = ib = 0
-
- for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
- #print 'match base [%d:%d]' % (zmatch, zend)
-
- matchlen = zend - zmatch
- assert matchlen >= 0
- assert matchlen == (aend - amatch)
- assert matchlen == (bend - bmatch)
-
- len_a = amatch - ia
- len_b = bmatch - ib
- len_base = zmatch - iz
- assert len_a >= 0
- assert len_b >= 0
- assert len_base >= 0
-
- #print 'unmatched a=%d, b=%d' % (len_a, len_b)
-
- if len_a or len_b:
- # try to avoid actually slicing the lists
- equal_a = compare_range(self.a, ia, amatch,
- self.base, iz, zmatch)
- equal_b = compare_range(self.b, ib, bmatch,
- self.base, iz, zmatch)
- same = compare_range(self.a, ia, amatch,
- self.b, ib, bmatch)
-
- if same:
- yield 'same', ia, amatch
- elif equal_a and not equal_b:
- yield 'b', ib, bmatch
- elif equal_b and not equal_a:
- yield 'a', ia, amatch
- elif not equal_a and not equal_b:
- yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
- else:
- raise AssertionError("can't handle a=b=base but unmatched")
-
- ia = amatch
- ib = bmatch
- iz = zmatch
-
- # if the same part of the base was deleted on both sides
- # that's OK, we can just skip it.
-
-
- if matchlen > 0:
- assert ia == amatch
- assert ib == bmatch
- assert iz == zmatch
-
- yield 'unchanged', zmatch, zend
- iz = zend
- ia = aend
- ib = bend
-
-
- def reprocess_merge_regions(self, merge_regions):
- """Where there are conflict regions, remove the agreed lines.
-
- Lines where both A and B have made the same changes are
- eliminated.
- """
- for region in merge_regions:
- if region[0] != "conflict":
- yield region
- continue
- type, iz, zmatch, ia, amatch, ib, bmatch = region
- a_region = self.a[ia:amatch]
- b_region = self.b[ib:bmatch]
- matches = mdiff.get_matching_blocks(''.join(a_region),
- ''.join(b_region))
- next_a = ia
- next_b = ib
- for region_ia, region_ib, region_len in matches[:-1]:
- region_ia += ia
- region_ib += ib
- reg = self.mismatch_region(next_a, region_ia, next_b,
- region_ib)
- if reg is not None:
- yield reg
- yield 'same', region_ia, region_len+region_ia
- next_a = region_ia + region_len
- next_b = region_ib + region_len
- reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
- if reg is not None:
- yield reg
-
-
- def mismatch_region(next_a, region_ia, next_b, region_ib):
- if next_a < region_ia or next_b < region_ib:
- return 'conflict', None, None, next_a, region_ia, next_b, region_ib
- mismatch_region = staticmethod(mismatch_region)
-
-
- def find_sync_regions(self):
- """Return a list of sync regions, where both descendents match the base.
-
- Generates a list of (base1, base2, a1, a2, b1, b2). There is
- always a zero-length sync region at the end of all the files.
- """
-
- ia = ib = 0
- amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
- bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
- len_a = len(amatches)
- len_b = len(bmatches)
-
- sl = []
-
- while ia < len_a and ib < len_b:
- abase, amatch, alen = amatches[ia]
- bbase, bmatch, blen = bmatches[ib]
-
- # there is an unconflicted block at i; how long does it
- # extend? until whichever one ends earlier.
- i = intersect((abase, abase+alen), (bbase, bbase+blen))
- if i:
- intbase = i[0]
- intend = i[1]
- intlen = intend - intbase
-
- # found a match of base[i[0], i[1]]; this may be less than
- # the region that matches in either one
- assert intlen <= alen
- assert intlen <= blen
- assert abase <= intbase
- assert bbase <= intbase
-
- asub = amatch + (intbase - abase)
- bsub = bmatch + (intbase - bbase)
- aend = asub + intlen
- bend = bsub + intlen
-
- assert self.base[intbase:intend] == self.a[asub:aend], \
- (self.base[intbase:intend], self.a[asub:aend])
-
- assert self.base[intbase:intend] == self.b[bsub:bend]
-
- sl.append((intbase, intend,
- asub, aend,
- bsub, bend))
-
- # advance whichever one ends first in the base text
- if (abase + alen) < (bbase + blen):
- ia += 1
- else:
- ib += 1
-
- intbase = len(self.base)
- abase = len(self.a)
- bbase = len(self.b)
- sl.append((intbase, intbase, abase, abase, bbase, bbase))
-
- return sl
-
-
-
- def find_unconflicted(self):
- """Return a list of ranges in base that are not conflicted."""
- am = mdiff.get_matching_blocks(self.basetext, self.atext)
- bm = mdiff.get_matching_blocks(self.basetext, self.btext)
-
- unc = []
-
- while am and bm:
- # there is an unconflicted block at i; how long does it
- # extend? until whichever one ends earlier.
- a1 = am[0][0]
- a2 = a1 + am[0][2]
- b1 = bm[0][0]
- b2 = b1 + bm[0][2]
- i = intersect((a1, a2), (b1, b2))
- if i:
- unc.append(i)
-
- if a2 < b2:
- del am[0]
- else:
- del bm[0]
-
- return unc
-
-
-# bzr compatible interface, for the tests
-class Merge3(Merge3Text):
- """3-way merge of texts.
-
- Given BASE, OTHER, THIS, tries to produce a combined text
- incorporating the changes from both BASE->OTHER and BASE->THIS.
- All three will typically be sequences of lines."""
- def __init__(self, base, a, b):
- basetext = '\n'.join([i.strip('\n') for i in base] + [''])
- atext = '\n'.join([i.strip('\n') for i in a] + [''])
- btext = '\n'.join([i.strip('\n') for i in b] + [''])
- if util.binary(basetext) or util.binary(atext) or util.binary(btext):
- raise util.Abort(_("don't know how to merge binary files"))
- Merge3Text.__init__(self, basetext, atext, btext, base, a, b)
-
-
-def simplemerge(local, base, other, **opts):
- def readfile(filename):
- f = open(filename, "rb")
- text = f.read()
- f.close()
- if util.binary(text):
- msg = _("%s looks like a binary file.") % filename
- if not opts.get('text'):
- raise util.Abort(msg)
- elif not opts.get('quiet'):
- warn(_('warning: %s\n') % msg)
- return text
-
- name_a = local
- name_b = other
- labels = opts.get('label', [])
- if labels:
- name_a = labels.pop(0)
- if labels:
- name_b = labels.pop(0)
- if labels:
- raise util.Abort(_("can only specify two labels."))
-
- localtext = readfile(local)
- basetext = readfile(base)
- othertext = readfile(other)
-
- orig = local
- local = os.path.realpath(local)
- if not opts.get('print'):
- opener = util.opener(os.path.dirname(local))
- out = opener(os.path.basename(local), "w", atomictemp=True)
- else:
- out = sys.stdout
-
- reprocess = not opts.get('no_minimal')
-
- m3 = Merge3Text(basetext, localtext, othertext)
- for line in m3.merge_lines(name_a=name_a, name_b=name_b,
- reprocess=reprocess):
- out.write(line)
-
- if not opts.get('print'):
- out.rename()
-
- if m3.conflicts:
- if not opts.get('quiet'):
- warn(_("warning: conflicts during merge.\n"))
- return 1
+from mercurial import simplemerge, fancyopts, util
options = [('L', 'label', [], _('labels to use on conflict markers')),
('a', 'text', None, _('treat all files as text')),
@@ -517,6 +25,9 @@
By default, LOCAL is overwritten with the results of this operation.
''')
+class ParseError(Exception):
+ """Exception raised on errors in parsing the command line."""
+
def showhelp():
sys.stdout.write(usage)
sys.stdout.write('\noptions:\n')
@@ -530,33 +41,24 @@
for first, second in out_opts:
sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
-class ParseError(Exception):
- """Exception raised on errors in parsing the command line."""
-
-def main(argv):
+try:
+ opts = {}
try:
- opts = {}
- try:
- args = fancyopts.fancyopts(argv[1:], options, opts)
- except fancyopts.getopt.GetoptError, e:
- raise ParseError(e)
- if opts['help']:
- showhelp()
- return 0
- if len(args) != 3:
- raise ParseError(_('wrong number of arguments'))
- return simplemerge(*args, **opts)
- except ParseError, e:
- sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
+ args = fancyopts.fancyopts(sys.argv[1:], options, opts)
+ except fancyopts.getopt.GetoptError, e:
+ raise ParseError(e)
+ if opts['help']:
showhelp()
- return 1
- except util.Abort, e:
- sys.stderr.write("abort: %s\n" % e)
- return 255
- except KeyboardInterrupt:
- return 255
-
-if __name__ == '__main__':
- import sys
- import os
- sys.exit(main(sys.argv))
+ sys.exit(0)
+ if len(args) != 3:
+ raise ParseError(_('wrong number of arguments'))
+ sys.exit(simplemerge.simplemerge(*args, **opts))
+except ParseError, e:
+ sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
+ showhelp()
+ sys.exit(1)
+except util.Abort, e:
+ sys.stderr.write("abort: %s\n" % e)
+ sys.exit(255)
+except KeyboardInterrupt:
+ sys.exit(255)
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/win32/ReadMe.html
--- a/contrib/win32/ReadMe.html Fri Feb 08 11:50:37 2008 +0100
+++ b/contrib/win32/ReadMe.html Fri Feb 08 11:55:17 2008 +0100
@@ -33,7 +33,7 @@
href="http://hgbook.red-bean.com/">Distributed revision control
with Mercurial.
- By default, Mercurial installs to C:\Mercurial. The
+
By default, Mercurial installs to C:\Program Files\Mercurial. The
Mercurial command is called hg.exe.
Testing Mercurial after you've installed it
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/win32/mercurial.iss
--- a/contrib/win32/mercurial.iss Fri Feb 08 11:50:37 2008 +0100
+++ b/contrib/win32/mercurial.iss Fri Feb 08 11:55:17 2008 +0100
@@ -15,8 +15,8 @@
AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}
AppContact=mercurial@selenic.com
OutputBaseFilename=Mercurial-snapshot
-DefaultDirName={sd}\Mercurial
-SourceDir=C:\hg\hg-release
+DefaultDirName={pf}\Mercurial
+SourceDir=..\..
VersionInfoDescription=Mercurial distributed SCM
VersionInfoCopyright=Copyright 2005-2007 Matt Mackall and others
VersionInfoCompany=Matt Mackall and others
@@ -29,17 +29,17 @@
[Files]
Source: contrib\mercurial.el; DestDir: {app}/Contrib
+Source: contrib\vim\*.*; DestDir: {app}/Contrib/Vim
+Source: contrib\zsh_completion; DestDir: {app}/Contrib
Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite
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}
-Source: dist\patch.exe; DestDir: {app}
Source: dist\mfc71.dll; DestDir: {app}
Source: dist\msvcr71.dll; DestDir: {app}
Source: dist\w9xpopen.exe; DestDir: {app}
Source: dist\add_path.exe; DestDir: {app}
-Source: doc\*.txt; DestDir: {app}\Docs
Source: doc\*.html; DestDir: {app}\Docs
Source: templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt
diff -r 6605a03cbf87 -r 012ad48a07fa contrib/zsh_completion
--- a/contrib/zsh_completion Fri Feb 08 11:50:37 2008 +0100
+++ b/contrib/zsh_completion Fri Feb 08 11:55:17 2008 +0100
@@ -13,6 +13,9 @@
# option) any later version.
#
+emulate -LR zsh
+setopt extendedglob
+
local curcontext="$curcontext" state line
typeset -A _hg_cmd_globals
@@ -153,9 +156,9 @@
typeset -a tags
local tag rev
- _hg_cmd tags 2> /dev/null | while read tag rev
+ _hg_cmd tags 2> /dev/null | while read tag
do
- tags+=($tag)
+ tags+=(${tag/ # [0-9]#:*})
done
(( $#tags )) && _describe -t tags 'tags' tags
}
@@ -674,13 +677,13 @@
# MQ
_hg_qseries() {
typeset -a patches
- patches=($(_hg_cmd qseries 2>/dev/null))
+ patches=(${(f)"$(_hg_cmd qseries 2>/dev/null)"})
(( $#patches )) && _describe -t hg-patches 'patches' patches
}
_hg_qapplied() {
typeset -a patches
- patches=($(_hg_cmd qapplied 2>/dev/null))
+ patches=(${(f)"$(_hg_cmd qapplied 2>/dev/null)"})
if (( $#patches ))
then
patches+=(qbase qtip)
@@ -690,7 +693,7 @@
_hg_qunapplied() {
typeset -a patches
- patches=($(_hg_cmd qunapplied 2>/dev/null))
+ patches=(${(f)"$(_hg_cmd qunapplied 2>/dev/null)"})
(( $#patches )) && _describe -t hg-unapplied-patches 'unapplied patches' patches
}
@@ -730,6 +733,12 @@
'*:unapplied patch:_hg_qunapplied'
}
+_hg_cmd_qgoto() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--force -f)'{-f,--force}'[overwrite any local changes]' \
+ ':patch:_hg_qseries'
+}
+
_hg_cmd_qguard() {
_arguments -s -w : $_hg_global_opts \
'(--list -l)'{-l,--list}'[list all patches and guards]' \
diff -r 6605a03cbf87 -r 012ad48a07fa doc/hg.1.txt
--- a/doc/hg.1.txt Fri Feb 08 11:50:37 2008 +0100
+++ b/doc/hg.1.txt Fri Feb 08 11:55:17 2008 +0100
@@ -91,11 +91,11 @@
FILES
-----
- .hgignore::
+ repo/.hgignore::
This file contains regular expressions (one per line) that describe file
names that should be ignored by hg. For details, see hgignore(5).
- .hgtags::
+ repo/.hgtags::
This file contains changeset hash values and text tag names (one of each
separated by spaces) that correspond to tagged versions of the repository
contents.
diff -r 6605a03cbf87 -r 012ad48a07fa doc/hgmerge.1.txt
--- a/doc/hgmerge.1.txt Fri Feb 08 11:50:37 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-HGMERGE(1)
-==========
-Matt Mackall
-v0.1, 27 May 2005
-
-NAME
-----
-hgmerge - default wrapper to merge files in Mercurial SCM system
-
-SYNOPSIS
---------
-'hgmerge' local ancestor remote
-
-DESCRIPTION
------------
-The hgmerge(1) command provides a graphical interface to merge files in the
-Mercurial system. It is a simple wrapper around kdiff3, merge(1) and tkdiff(1),
-or simply diff(1) and patch(1) depending on what is present on the system.
-
-hgmerge(1) is used by the Mercurial SCM if the environment variable HGMERGE is
-not set.
-
-AUTHOR
-------
-Written by Vincent Danjean
-
-SEE ALSO
---------
-hg(1) - the command line interface to Mercurial SCM
-
-COPYING
--------
-Copyright \(C) 2005-2007 Matt Mackall.
-Free use of this software is granted under the terms of the GNU General
-Public License (GPL).
diff -r 6605a03cbf87 -r 012ad48a07fa doc/hgrc.5.txt
--- a/doc/hgrc.5.txt Fri Feb 08 11:50:37 2008 +0100
+++ b/doc/hgrc.5.txt Fri Feb 08 11:55:17 2008 +0100
@@ -17,7 +17,9 @@
Mercurial reads configuration data from several files, if they exist.
The names of these files depend on the system on which Mercurial is
-installed.
+installed. Windows registry keys contain PATH-like strings, every
+part must reference a Mercurial.ini file or be a directory where *.rc
+files will be read.
(Unix) /etc/mercurial/hgrc.d/*.rc::
(Unix) /etc/mercurial/hgrc::
@@ -29,6 +31,8 @@
(Unix) /etc/mercurial/hgrc.d/*.rc::
(Unix) /etc/mercurial/hgrc::
+(Windows) HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial::
+ or::
(Windows) C:\Mercurial\Mercurial.ini::
Per-system configuration files, for the system on which Mercurial
is running. Options in these files apply to all Mercurial
@@ -120,21 +124,26 @@
NOTE: the tempfile mechanism is recommended for Windows systems,
where the standard shell I/O redirection operators often have
- strange effects. In particular, if you are doing line ending
- conversion on Windows using the popular dos2unix and unix2dos
- programs, you *must* use the tempfile mechanism, as using pipes will
- corrupt the contents of your files.
+ strange effects and may corrupt the contents of your files.
- Tempfile example:
+ The most common usage is for LF <-> CRLF translation on Windows.
+ For this, use the "smart" convertors which check for binary files:
+ [extensions]
+ hgext.win32text =
[encode]
- # convert files to unix line ending conventions on checkin
- **.txt = tempfile: dos2unix -n INFILE OUTFILE
-
+ ** = cleverencode:
[decode]
- # convert files to windows line ending conventions when writing
- # them to the working dir
- **.txt = tempfile: unix2dos -n INFILE OUTFILE
+ ** = cleverdecode:
+
+ or if you only want to translate certain files:
+
+ [extensions]
+ hgext.win32text =
+ [encode]
+ **.txt = dumbencode:
+ [decode]
+ **.txt = dumbdecode:
defaults::
Use the [defaults] section to define command defaults, i.e. the
@@ -224,6 +233,78 @@
you to store longer filenames in some situations at the expense of
compatibility.
+merge-patterns::
+ This section specifies merge tools to associate with particular file
+ patterns. Tools matched here will take precedence over the default
+ merge tool. Patterns are globs by default, rooted at the repository root.
+
+ Example:
+
+ [merge-patterns]
+ **.c = kdiff3
+ **.jpg = myimgmerge
+
+merge-tools::
+ This section configures external merge tools to use for file-level
+ merges.
+
+ Example ~/.hgrc:
+
+ [merge-tools]
+ # Override stock tool location
+ kdiff3.executable = ~/bin/kdiff3
+ # Specify command line
+ kdiff3.args = $base $local $other -o $output
+ # Give higher priority
+ kdiff3.priority = 1
+
+ # Define new tool
+ myHtmlTool.args = -m $local $other $base $output
+ myHtmlTool.regkey = Software\FooSoftware\HtmlMerge
+ myHtmlTool.priority = 1
+
+ Supported arguments:
+ priority;;
+ The priority in which to evaluate this tool.
+ Default: 0.
+ executable;;
+ Either just the name of the executable or its pathname.
+ Default: the tool name.
+ args;;
+ The arguments to pass to the tool executable. You can refer to the files
+ being merged as well as the output file through these variables: $base,
+ $local, $other, $output.
+ Default: $local $base $other
+ premerge;;
+ Attempt to run internal non-interactive 3-way merge tool before
+ launching external tool.
+ Default: True
+ binary;;
+ This tool can merge binary files. Defaults to False, unless tool
+ was selected by file pattern match.
+ symlink;;
+ This tool can merge symlinks. Defaults to False, even if tool was
+ selected by file pattern match.
+ checkconflicts;;
+ Check whether there are conflicts even though the tool reported
+ success.
+ Default: False
+ fixeol;;
+ Attempt to fix up EOL changes caused by the merge tool.
+ Default: False
+ gui:;
+ This tool requires a graphical interface to run. Default: False
+ regkey;;
+ Windows registry key which describes install location of this tool.
+ Mercurial will search for this key first under HKEY_CURRENT_USER and
+ then under HKEY_LOCAL_MACHINE. Default: None
+ regname;;
+ Name of value to read from specified registry key. Defaults to the
+ unnamed (default) value.
+ regappend;;
+ String to append to the value read from the registry, typically the
+ executable name of the tool. Default: None
+
hooks::
Commands or Python functions that get automatically executed by
various actions such as starting or finishing a commit. Multiple
@@ -277,7 +358,7 @@
commit to proceed. Non-zero status will cause the commit to fail.
Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
preoutgoing;;
- Run before computing changes to send from the local repository to
+ Run before collecting changes to send from the local repository to
another. Non-zero status will cause failure. This lets you
prevent pull over http or ssh. Also prevents against local pull,
push (outbound) or bundle commands, but not effective, since you
@@ -394,6 +475,20 @@
Optional. Directory or URL to use when pushing if no destination
is specified.
+profile::
+ Configuration of profiling options, for in-depth performance
+ analysis. Mostly useful to developers.
+ enable;;
+ Enable a particular profiling mode. Useful for profiling
+ server-side processes. "lsprof" enables modern profiling.
+ "hotshot" is deprecated, and produces less reliable results.
+ Default is no profiling.
+ output;;
+ The name of a file to write profiling data to. Each occurrence of
+ "%%p" will be replaced with the current process ID (the repeated
+ "%" protects against the config parser's string interpolator).
+ Default output is to stderr.
+
server::
Controls generic server settings.
uncompressed;;
@@ -444,7 +539,18 @@
Template string for commands that print changesets.
merge;;
The conflict resolution program to use during a manual merge.
- Default is "hgmerge".
+ There are some internal tools available:
+
+ internal:local;;
+ keep the local version
+ internal:other;;
+ use the other version
+ internal:merge;;
+ use the internal non-interactive merge tool
+ internal:fail;;
+ fail to merge
+
+ See the merge-tools section for more information on configuring tools.
patch;;
command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
unset.
@@ -515,7 +621,7 @@
Example: "http://hgserver/repos/"
contact;;
Name or email address of the person in charge of the repository.
- Default is "unknown".
+ Defaults to ui.username or $EMAIL or "unknown" if unset or empty.
deny_push;;
Whether to deny pushing to the repository. If empty or not set,
push is not denied. If the special value "*", all remote users
@@ -544,6 +650,8 @@
Maximum number of files to list per changeset. Default is 10.
port;;
Port to listen on. Default is 8000.
+ prefix;;
+ Prefix path to serve from. Default is '' (server root).
push_ssl;;
Whether to require that inbound pushes be transported over SSL to
prevent password sniffing. Default is true.
diff -r 6605a03cbf87 -r 012ad48a07fa doc/ja/hgmerge.1.ja.txt
--- a/doc/ja/hgmerge.1.ja.txt Fri Feb 08 11:50:37 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-HGMERGE(1)
-==========
-Matt Mackall
-v0.1, 27 May 2005
-
-名前
---
-hgmerge - Mercurial ソースコード管理システムでファイルをマージする
-のに使われるデフォルトのラッパー
-
-書式
---
-'hgmerge' local ancestor remote
-
-説明
---
-hgmerge(1) コマンドは Mercurial システムでファイルをマージするため
-のグラフィカルなインターフェイスを提供します。これは kdiff3,
-merge(1), tkdiff(1), または単純に diff(1) と patch(1) のラッパーで、
-どれがシステム上にあるかに依存します。
-
-hgmerge(1) は Mercurial ソースコード管理システムで環境変数
-HGMERGE が設定されていない場合に使われます。
-
-著者
---
-Vincent Danjean によって書かれました。
-
-関連情報
---
-hg(1) - Mercurial システムへのコマンドラインインターフェイス
-
-著作権情報
-----
-Copyright (C) 2005-2007 Matt Mackall.
-このソフトウェアの自由な使用は GNU 一般公有使用許諾 (GPL) のもとで
-認められます。
diff -r 6605a03cbf87 -r 012ad48a07fa hg
--- a/hg Fri Feb 08 11:50:37 2008 +0100
+++ b/hg Fri Feb 08 11:55:17 2008 +0100
@@ -10,5 +10,11 @@
# enable importing on demand to reduce startup time
from mercurial import demandimport; demandimport.enable()
+import sys
+import mercurial.util
import mercurial.dispatch
+
+for fp in (sys.stdin, sys.stdout, sys.stderr):
+ mercurial.util.set_binary(fp)
+
mercurial.dispatch.run()
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/bugzilla.py
--- a/hgext/bugzilla.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/bugzilla.py Fri Feb 08 11:55:17 2008 +0100
@@ -282,7 +282,7 @@
root=self.repo.root,
webroot=webroot(self.repo.root))
data = self.ui.popbuffer()
- self.add_comment(bugid, data, templater.email(ctx.user()))
+ self.add_comment(bugid, data, util.email(ctx.user()))
def hook(ui, repo, hooktype, node=None, **kwargs):
'''add comment to bugzilla for each changeset that refers to a
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/color.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/color.py Fri Feb 08 11:55:17 2008 +0100
@@ -0,0 +1,219 @@
+# color.py color output for the status and qseries commands
+#
+# Copyright (C) 2007 Kevin Christen
+#
+# This program 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.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+'''add color output to the status and qseries commands
+
+This extension modifies the status command to add color to its output to
+reflect file status, and the qseries command to add color to reflect patch
+status (applied, unapplied, missing). Other effects in addition to color,
+like bold and underlined text, are also available. Effects are rendered
+with the ECMA-48 SGR control function (aka ANSI escape codes). This module
+also provides the render_text function, which can be used to add effects to
+any text.
+
+To enable this extension, add this to your .hgrc file:
+[extensions]
+color =
+
+Default effects my be overriden from the .hgrc file:
+
+[color]
+status.modified = blue bold underline red_background
+status.added = green bold
+status.removed = red bold blue_background
+status.deleted = cyan bold underline
+status.unknown = magenta bold underline
+status.ignored = black bold
+
+ 'none' turns off all effects
+status.clean = none
+status.copied = none
+
+qseries.applied = blue bold underline
+qseries.unapplied = black bold
+qseries.missing = red bold
+'''
+
+import re, sys
+
+from mercurial import commands, cmdutil, ui
+from mercurial.i18n import _
+
+# start and stop parameters for effects
+_effect_params = { 'none': (0, 0),
+ 'black': (30, 39),
+ 'red': (31, 39),
+ 'green': (32, 39),
+ 'yellow': (33, 39),
+ 'blue': (34, 39),
+ 'magenta': (35, 39),
+ 'cyan': (36, 39),
+ 'white': (37, 39),
+ 'bold': (1, 22),
+ 'italic': (3, 23),
+ 'underline': (4, 24),
+ 'inverse': (7, 27),
+ 'black_background': (40, 49),
+ 'red_background': (41, 49),
+ 'green_background': (42, 49),
+ 'yellow_background': (43, 49),
+ 'blue_background': (44, 49),
+ 'purple_background': (45, 49),
+ 'cyan_background': (46, 49),
+ 'white_background': (47, 49), }
+
+def render_effects(text, *effects):
+ 'Wrap text in commands to turn on each effect.'
+ start = []
+ stop = []
+ for effect in effects:
+ start.append(str(_effect_params[effect][0]))
+ stop.append(str(_effect_params[effect][1]))
+ start = '\033[' + ';'.join(start) + 'm'
+ stop = '\033[' + ';'.join(stop) + 'm'
+ return start + text + stop
+
+def colorstatus(statusfunc, ui, repo, *pats, **opts):
+ '''run the status command with colored output'''
+
+ delimiter = opts['print0'] and '\0' or '\n'
+
+ # run status and capture it's output
+ ui.pushbuffer()
+ retval = statusfunc(ui, repo, *pats, **opts)
+ # filter out empty strings
+ lines = [ line for line in ui.popbuffer().split(delimiter) if line ]
+
+ if opts['no_status']:
+ # if --no-status, run the command again without that option to get
+ # output with status abbreviations
+ opts['no_status'] = False
+ ui.pushbuffer()
+ statusfunc(ui, repo, *pats, **opts)
+ # filter out empty strings
+ lines_with_status = [ line for
+ line in ui.popbuffer().split(delimiter) if line ]
+ else:
+ lines_with_status = lines
+
+ # apply color to output and display it
+ for i in xrange(0, len(lines)):
+ status = _status_abbreviations[lines_with_status[i][0]]
+ effects = _status_effects[status]
+ if effects:
+ lines[i] = render_effects(lines[i], *effects)
+ sys.stdout.write(lines[i] + delimiter)
+ return retval
+
+_status_abbreviations = { 'M': 'modified',
+ 'A': 'added',
+ 'R': 'removed',
+ '!': 'deleted',
+ '?': 'unknown',
+ 'I': 'ignored',
+ 'C': 'clean',
+ ' ': 'copied', }
+
+_status_effects = { 'modified': ('blue', 'bold'),
+ 'added': ('green', 'bold'),
+ 'removed': ('red', 'bold'),
+ 'deleted': ('cyan', 'bold', 'underline'),
+ 'unknown': ('magenta', 'bold', 'underline'),
+ 'ignored': ('black', 'bold'),
+ 'clean': ('none', ),
+ 'copied': ('none', ), }
+
+def colorqseries(qseriesfunc, ui, repo, *dummy, **opts):
+ '''run the qseries command with colored output'''
+ ui.pushbuffer()
+ retval = qseriesfunc(ui, repo, **opts)
+ patches = ui.popbuffer().splitlines()
+ for patch in patches:
+ if opts['missing']:
+ effects = _patch_effects['missing']
+ # Determine if patch is applied. Search for beginning of output
+ # line in the applied patch list, in case --summary has been used
+ # and output line isn't just the patch name.
+ elif [ applied for applied in repo.mq.applied
+ if patch.startswith(applied.name) ]:
+ effects = _patch_effects['applied']
+ else:
+ effects = _patch_effects['unapplied']
+ sys.stdout.write(render_effects(patch, *effects) + '\n')
+ return retval
+
+_patch_effects = { 'applied': ('blue', 'bold', 'underline'),
+ 'missing': ('red', 'bold'),
+ 'unapplied': ('black', 'bold'), }
+
+def uisetup(ui):
+ '''Initialize the extension.'''
+ nocoloropt = ('', 'no-color', None, _("don't colorize output"))
+ _decoratecmd(ui, 'status', commands.table, colorstatus, nocoloropt)
+ _configcmdeffects(ui, 'status', _status_effects);
+ if ui.config('extensions', 'hgext.mq', default=None) is not None:
+ from hgext import mq
+ _decoratecmd(ui, 'qseries', mq.cmdtable, colorqseries, nocoloropt)
+ _configcmdeffects(ui, 'qseries', _patch_effects);
+
+def _decoratecmd(ui, cmd, table, delegate, *delegateoptions):
+ '''Replace the function that implements cmd in table with a decorator.
+
+ The decorator that becomes the new implementation of cmd calls
+ delegate. The delegate's first argument is the replaced function,
+ followed by the normal Mercurial command arguments (ui, repo, ...). If
+ the delegate adds command options, supply them as delegateoptions.
+ '''
+ cmdkey, cmdentry = _cmdtableitem(ui, cmd, table)
+ decorator = lambda ui, repo, *args, **opts: \
+ _colordecorator(delegate, cmdentry[0],
+ ui, repo, *args, **opts)
+ # make sure 'hg help cmd' still works
+ decorator.__doc__ = cmdentry[0].__doc__
+ decoratorentry = (decorator,) + cmdentry[1:]
+ for option in delegateoptions:
+ decoratorentry[1].append(option)
+ table[cmdkey] = decoratorentry
+
+def _cmdtableitem(ui, cmd, table):
+ '''Return key, value from table for cmd, or None if not found.'''
+ aliases, entry = cmdutil.findcmd(ui, cmd, table)
+ for candidatekey, candidateentry in table.iteritems():
+ if candidateentry is entry:
+ return candidatekey, entry
+
+def _colordecorator(colorfunc, nocolorfunc, ui, repo, *args, **opts):
+ '''Delegate to colorfunc or nocolorfunc, depending on conditions.
+
+ Delegate to colorfunc unless --no-color option is set or output is not
+ to a tty.
+ '''
+ if opts['no_color'] or not sys.stdout.isatty():
+ return nocolorfunc(ui, repo, *args, **opts)
+ return colorfunc(nocolorfunc, ui, repo, *args, **opts)
+
+def _configcmdeffects(ui, cmdname, effectsmap):
+ '''Override default effects for cmdname with those from .hgrc file.
+
+ Entries in the .hgrc file are in the [color] section, and look like
+ 'cmdname'.'status' (for instance, 'status.modified = blue bold inverse').
+ '''
+ for status in effectsmap:
+ effects = ui.config('color', cmdname + '.' + status)
+ if effects:
+ effectsmap[status] = re.split('\W+', effects)
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/__init__.py
--- a/hgext/convert/__init__.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/__init__.py Fri Feb 08 11:55:17 2008 +0100
@@ -19,9 +19,11 @@
- Darcs
- git
- Subversion
+ - GNU Arch
Accepted destination formats:
- Mercurial
+ - Subversion (history on branches is not preserved)
If no revision is given, all revisions will be converted. Otherwise,
convert will only import up to the named revision (given in a format
@@ -56,7 +58,7 @@
exclude path/to/file
rename from/file to/file
-
+
The 'include' directive causes a file, or all files under a
directory, to be included in the destination repository, and the
exclusion of all other files and dirs not explicitely included.
@@ -64,6 +66,24 @@
The 'rename' directive renames a file or directory. To rename from a
subdirectory into the root of the repository, use '.' as the path to
rename to.
+
+ Back end options:
+
+ --config convert.hg.clonebranches=False (boolean)
+ hg target: XXX not documented
+ --config convert.hg.saverev=True (boolean)
+ hg source: allow target to preserve source revision ID
+ --config convert.hg.tagsbranch=default (branch name)
+ hg target: XXX not documented
+ --config convert.hg.usebranchnames=True (boolean)
+ hg target: preserve branch names
+
+ --config convert.svn.branches=branches (directory name)
+ svn source: specify the directory containing branches
+ --config convert.svn.tags=tags (directory name)
+ svn source: specify the directory containing tags
+ --config convert.svn.trunk=trunk (directory name)
+ svn source: specify the name of the trunk branch
"""
return convcmd.convert(ui, src, dest, revmapfile, **opts)
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/common.py
--- a/hgext/convert/common.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/common.py Fri Feb 08 11:55:17 2008 +0100
@@ -1,6 +1,9 @@
# common code for the convert extension
-import base64
+import base64, errno
+import os
import cPickle as pickle
+from mercurial import util
+from mercurial.i18n import _
def encodeargs(args):
def encodearg(s):
@@ -15,6 +18,11 @@
s = base64.decodestring(s)
return pickle.loads(s)
+def checktool(exe, name=None):
+ name = name or exe
+ if not util.find_exe(exe):
+ raise util.Abort('cannot find required "%s" tool' % name)
+
class NoRepo(Exception): pass
SKIPREV = 'SKIP'
@@ -33,7 +41,7 @@
class converter_source(object):
"""Conversion source interface"""
- def __init__(self, ui, path, rev=None):
+ def __init__(self, ui, path=None, rev=None):
"""Initialize conversion source (or raise NoRepo("message")
exception if path is not a valid repository)"""
self.ui = ui
@@ -48,11 +56,8 @@
def after(self):
pass
- def setrevmap(self, revmap, order):
- """set the map of already-converted revisions
-
- order is a list with the keys from revmap in the order they
- appear in the revision map file."""
+ def setrevmap(self, revmap):
+ """set the map of already-converted revisions"""
pass
def getheads(self):
@@ -100,17 +105,22 @@
def getchangedfiles(self, rev, i):
"""Return the files changed by rev compared to parent[i].
-
+
i is an index selecting one of the parents of rev. The return
value should be the list of files that are different in rev and
this parent.
If rev has no parents, i is None.
-
+
This function is only needed to support --filemap
"""
raise NotImplementedError()
+ def converted(self, rev, sinkrev):
+ '''Notify the source that a revision has been converted.'''
+ pass
+
+
class converter_sink(object):
"""Conversion sink (target) interface"""
@@ -183,3 +193,162 @@
filter empty revisions.
"""
pass
+
+ def before(self):
+ pass
+
+ def after(self):
+ pass
+
+
+class commandline(object):
+ def __init__(self, ui, command):
+ self.ui = ui
+ self.command = command
+
+ def prerun(self):
+ pass
+
+ def postrun(self):
+ pass
+
+ def _cmdline(self, cmd, *args, **kwargs):
+ cmdline = [self.command, cmd] + list(args)
+ for k, v in kwargs.iteritems():
+ if len(k) == 1:
+ cmdline.append('-' + k)
+ else:
+ cmdline.append('--' + k.replace('_', '-'))
+ try:
+ if len(k) == 1:
+ cmdline.append('' + v)
+ else:
+ cmdline[-1] += '=' + v
+ except TypeError:
+ pass
+ cmdline = [util.shellquote(arg) for arg in cmdline]
+ cmdline += ['2>', util.nulldev, '<', util.nulldev]
+ cmdline = ' '.join(cmdline)
+ self.ui.debug(cmdline, '\n')
+ return cmdline
+
+ def _run(self, cmd, *args, **kwargs):
+ cmdline = self._cmdline(cmd, *args, **kwargs)
+ self.prerun()
+ try:
+ return util.popen(cmdline)
+ finally:
+ self.postrun()
+
+ def run(self, cmd, *args, **kwargs):
+ fp = self._run(cmd, *args, **kwargs)
+ output = fp.read()
+ self.ui.debug(output)
+ return output, fp.close()
+
+ def runlines(self, cmd, *args, **kwargs):
+ fp = self._run(cmd, *args, **kwargs)
+ output = fp.readlines()
+ self.ui.debug(output)
+ return output, fp.close()
+
+ def checkexit(self, status, output=''):
+ if status:
+ if output:
+ self.ui.warn(_('%s error:\n') % self.command)
+ self.ui.warn(output)
+ msg = util.explain_exit(status)[0]
+ raise util.Abort(_('%s %s') % (self.command, msg))
+
+ def run0(self, cmd, *args, **kwargs):
+ output, status = self.run(cmd, *args, **kwargs)
+ self.checkexit(status, output)
+ return output
+
+ def runlines0(self, cmd, *args, **kwargs):
+ output, status = self.runlines(cmd, *args, **kwargs)
+ self.checkexit(status, output)
+ return output
+
+ def getargmax(self):
+ if '_argmax' in self.__dict__:
+ return self._argmax
+
+ # POSIX requires at least 4096 bytes for ARG_MAX
+ self._argmax = 4096
+ try:
+ self._argmax = os.sysconf("SC_ARG_MAX")
+ except:
+ pass
+
+ # Windows shells impose their own limits on command line length,
+ # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
+ # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
+ # details about cmd.exe limitations.
+
+ # Since ARG_MAX is for command line _and_ environment, lower our limit
+ # (and make happy Windows shells while doing this).
+
+ self._argmax = self._argmax/2 - 1
+ return self._argmax
+
+ def limit_arglist(self, arglist, cmd, *args, **kwargs):
+ limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
+ bytes = 0
+ fl = []
+ for fn in arglist:
+ b = len(fn) + 3
+ if bytes + b < limit or len(fl) == 0:
+ fl.append(fn)
+ bytes += b
+ else:
+ yield fl
+ fl = [fn]
+ bytes = b
+ if fl:
+ yield fl
+
+ def xargs(self, arglist, cmd, *args, **kwargs):
+ for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
+ self.run0(cmd, *(list(args) + l), **kwargs)
+
+class mapfile(dict):
+ def __init__(self, ui, path):
+ super(mapfile, self).__init__()
+ self.ui = ui
+ self.path = path
+ self.fp = None
+ self.order = []
+ self._read()
+
+ def _read(self):
+ if self.path is None:
+ return
+ try:
+ fp = open(self.path, 'r')
+ except IOError, err:
+ if err.errno != errno.ENOENT:
+ raise
+ return
+ for line in fp:
+ key, value = line[:-1].split(' ', 1)
+ if key not in self:
+ self.order.append(key)
+ super(mapfile, self).__setitem__(key, value)
+ fp.close()
+
+ def __setitem__(self, key, value):
+ if self.fp is None:
+ try:
+ self.fp = open(self.path, 'a')
+ except IOError, err:
+ raise util.Abort(_('could not open map file %r: %s') %
+ (self.path, err.strerror))
+ self.fp.write('%s %s\n' % (key, value))
+ self.fp.flush()
+ super(mapfile, self).__setitem__(key, value)
+
+ def close(self):
+ if self.fp:
+ self.fp.close()
+ self.fp = None
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/convcmd.py
--- a/hgext/convert/convcmd.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/convcmd.py Fri Feb 08 11:55:17 2008 +0100
@@ -5,12 +5,13 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-from common import NoRepo, SKIPREV, converter_source, converter_sink
+from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
from cvs import convert_cvs
from darcs import darcs_source
from git import convert_git
from hg import mercurial_source, mercurial_sink
-from subversion import svn_source, debugsvnlog
+from subversion import debugsvnlog, svn_source, svn_sink
+from gnuarch import gnuarch_source
import filemap
import os, shutil
@@ -23,10 +24,12 @@
('svn', svn_source),
('hg', mercurial_source),
('darcs', darcs_source),
+ ('gnuarch', gnuarch_source),
]
sink_converters = [
('hg', mercurial_sink),
+ ('svn', svn_sink),
]
def convertsource(ui, path, type, rev):
@@ -59,23 +62,10 @@
self.ui = ui
self.opts = opts
self.commitcache = {}
- self.revmapfile = revmapfile
- self.revmapfilefd = None
self.authors = {}
self.authorfile = None
- self.maporder = []
- self.map = {}
- try:
- origrevmapfile = open(self.revmapfile, 'r')
- for l in origrevmapfile:
- sv, dv = l[:-1].split()
- if sv not in self.map:
- self.maporder.append(sv)
- self.map[sv] = dv
- origrevmapfile.close()
- except IOError:
- pass
+ self.map = mapfile(ui, revmapfile)
# Read first the dst author map if any
authorfile = self.dest.authorfile()
@@ -86,6 +76,8 @@
self.readauthormap(opts.get('authors'))
self.authorfile = self.dest.authorfile()
+ self.splicemap = mapfile(ui, ui.config('convert', 'splicemap'))
+
def walktree(self, heads):
'''Return a mapping that identifies the uncommitted parents of every
uncommitted changeset.'''
@@ -157,22 +149,13 @@
if pl:
depth[n] = max([depth[p] for p in pl]) + 1
- s = [(depth[n], self.commitcache[n].date, n) for n in s]
+ s = [(depth[n], util.parsedate(self.commitcache[n].date), n)
+ for n in s]
s.sort()
s = [e[2] for e in s]
return s
- def mapentry(self, src, dst):
- if self.revmapfilefd is None:
- try:
- self.revmapfilefd = open(self.revmapfile, "a")
- except IOError, (errno, strerror):
- raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
- self.map[src] = dst
- self.revmapfilefd.write("%s %s\n" % (src, dst))
- self.revmapfilefd.flush()
-
def writeauthormap(self):
authorfile = self.authorfile
if authorfile:
@@ -219,7 +202,7 @@
dest = SKIPREV
else:
dest = self.map[changes]
- self.mapentry(rev, dest)
+ self.map[rev] = dest
return
files, copies = changes
pbranches = []
@@ -245,15 +228,25 @@
# Merely marks that a copy happened.
self.dest.copyfile(copyf, f)
- parents = [b[0] for b in pbranches]
+ try:
+ parents = [self.splicemap[rev]]
+ self.ui.debug('spliced in %s as parents of %s\n' %
+ (parents, rev))
+ except KeyError:
+ parents = [b[0] for b in pbranches]
newnode = self.dest.putcommit(filenames, parents, commit)
- self.mapentry(rev, newnode)
+ self.source.converted(rev, newnode)
+ self.map[rev] = newnode
def convert(self):
+
+ def recode(s):
+ return s.decode('utf-8').encode(orig_encoding, 'replace')
+
try:
self.source.before()
self.dest.before()
- self.source.setrevmap(self.map, self.maporder)
+ self.source.setrevmap(self.map)
self.ui.status("scanning source...\n")
heads = self.source.getheads()
parents = self.walktree(heads)
@@ -268,7 +261,11 @@
desc = self.commitcache[c].desc
if "\n" in desc:
desc = desc.splitlines()[0]
- self.ui.status("%d %s\n" % (num, desc))
+ # convert log message to local encoding without using
+ # tolocal() because util._encoding conver() use it as
+ # 'utf-8'
+ self.ui.status("%d %s\n" % (num, recode(desc)))
+ self.ui.note(_("source: %s\n" % recode(c)))
self.copy(c)
tags = self.source.gettags()
@@ -283,7 +280,7 @@
# write another hash correspondence to override the previous
# one so we don't end up with extra tag heads
if nrev:
- self.mapentry(c, nrev)
+ self.map[c] = nrev
self.writeauthormap()
finally:
@@ -294,10 +291,13 @@
self.dest.after()
finally:
self.source.after()
- if self.revmapfilefd:
- self.revmapfilefd.close()
+ self.map.close()
+
+orig_encoding = 'ascii'
def convert(ui, src, dest=None, revmapfile=None, **opts):
+ global orig_encoding
+ orig_encoding = util._encoding
util._encoding = 'UTF-8'
if not dest:
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/cvs.py
--- a/hgext/convert/cvs.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/cvs.py Fri Feb 08 11:55:17 2008 +0100
@@ -1,9 +1,10 @@
# CVS conversion code inspired by hg-cvs-import and git-cvsimport
import os, locale, re, socket
+from cStringIO import StringIO
from mercurial import util
-from common import NoRepo, commit, converter_source
+from common import NoRepo, commit, converter_source, checktool
class convert_cvs(converter_source):
def __init__(self, ui, path, rev=None):
@@ -13,6 +14,9 @@
if not os.path.exists(cvs):
raise NoRepo("%s does not look like a CVS checkout" % path)
+ for tool in ('cvsps', 'cvs'):
+ checktool(tool)
+
self.changeset = {}
self.files = {}
self.tags = {}
@@ -196,7 +200,7 @@
if conntype != "pserver":
if conntype == "rsh":
- rsh = os.environ.get("CVS_RSH" or "rsh")
+ rsh = os.environ.get("CVS_RSH") or "ssh"
if user:
cmd = [rsh, '-l', user, host] + cmd
else:
@@ -227,6 +231,20 @@
return self.heads
def _getfile(self, name, rev):
+
+ def chunkedread(fp, count):
+ # file-objects returned by socked.makefile() do not handle
+ # large read() requests very well.
+ chunksize = 65536
+ output = StringIO()
+ while count > 0:
+ data = fp.read(min(count, chunksize))
+ if not data:
+ raise util.Abort("%d bytes missing from remote file" % count)
+ count -= len(data)
+ output.write(data)
+ return output.getvalue()
+
if rev.endswith("(DEAD)"):
raise IOError
@@ -245,14 +263,14 @@
self.readp.readline() # entries
mode = self.readp.readline()[:-1]
count = int(self.readp.readline()[:-1])
- data = self.readp.read(count)
+ data = chunkedread(self.readp, count)
elif line.startswith(" "):
data += line[1:]
elif line.startswith("M "):
pass
elif line.startswith("Mbinary "):
count = int(self.readp.readline()[:-1])
- data = self.readp.read(count)
+ data = chunkedread(self.readp, count)
else:
if line == "ok\n":
return (data, "x" in mode and "x" or "")
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/darcs.py
--- a/hgext/convert/darcs.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/darcs.py Fri Feb 08 11:55:17 2008 +0100
@@ -1,6 +1,6 @@
# darcs support for the convert extension
-from common import NoRepo, commit, converter_source
+from common import NoRepo, checktool, commandline, commit, converter_source
from mercurial.i18n import _
from mercurial import util
import os, shutil, tempfile
@@ -17,15 +17,18 @@
except ImportError: ElementTree = None
-class darcs_source(converter_source):
+class darcs_source(converter_source, commandline):
def __init__(self, ui, path, rev=None):
- super(darcs_source, self).__init__(ui, path, rev=rev)
+ converter_source.__init__(self, ui, path, rev=rev)
+ commandline.__init__(self, ui, 'darcs')
# check for _darcs, ElementTree, _darcs/inventory so that we can
# easily skip test-convert-darcs if ElementTree is not around
if not os.path.exists(os.path.join(path, '_darcs')):
raise NoRepo("%s does not look like a darcs repo" % path)
+ checktool('darcs')
+
if ElementTree is None:
raise util.Abort(_("Python ElementTree module is not available"))
@@ -45,7 +48,8 @@
output, status = self.run('init', repodir=self.tmppath)
self.checkexit(status)
- tree = self.xml('changes', '--xml-output', '--summary')
+ tree = self.xml('changes', xml_output=True, summary=True,
+ repodir=self.path)
tagname = None
child = None
for elt in tree.findall('patch'):
@@ -65,31 +69,9 @@
self.ui.debug('cleaning up %s\n' % self.tmppath)
shutil.rmtree(self.tmppath, ignore_errors=True)
- def _run(self, cmd, *args, **kwargs):
- cmdline = ['darcs', cmd, '--repodir', kwargs.get('repodir', self.path)]
- cmdline += args
- cmdline = [util.shellquote(arg) for arg in cmdline]
- cmdline += ['<', util.nulldev]
- cmdline = ' '.join(cmdline)
- self.ui.debug(cmdline, '\n')
- return util.popen(cmdline)
-
- def run(self, cmd, *args, **kwargs):
- fp = self._run(cmd, *args, **kwargs)
- output = fp.read()
- return output, fp.close()
-
- def checkexit(self, status, output=''):
- if status:
- if output:
- self.ui.warn(_('darcs error:\n'))
- self.ui.warn(output)
- msg = util.explain_exit(status)[0]
- raise util.Abort(_('darcs %s') % msg)
-
- def xml(self, cmd, *opts):
+ def xml(self, cmd, **kwargs):
etree = ElementTree()
- fp = self._run(cmd, *opts)
+ fp = self._run(cmd, **kwargs)
etree.parse(fp)
self.checkexit(fp.close())
return etree.getroot()
@@ -105,15 +87,15 @@
desc=desc.strip(), parents=self.parents[rev])
def pull(self, rev):
- output, status = self.run('pull', self.path, '--all',
- '--match', 'hash %s' % rev,
- '--no-test', '--no-posthook',
- '--external-merge', '/bin/false',
+ output, status = self.run('pull', self.path, all=True,
+ match='hash %s' % rev,
+ no_test=True, no_posthook=True,
+ external_merge='/bin/false',
repodir=self.tmppath)
if status:
if output.find('We have conflicts in') == -1:
self.checkexit(status, output)
- output, status = self.run('revert', '--all', repodir=self.tmppath)
+ output, status = self.run('revert', all=True, repodir=self.tmppath)
self.checkexit(status, output)
def getchanges(self, rev):
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/filemap.py
--- a/hgext/convert/filemap.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/filemap.py Fri Feb 08 11:55:17 2008 +0100
@@ -7,9 +7,10 @@
import shlex
from mercurial.i18n import _
from mercurial import util
-from common import SKIPREV
+from common import SKIPREV, converter_source
def rpairs(name):
+ yield '.', name
e = len(name)
while e != -1:
yield name[:e], name[e+1:]
@@ -110,9 +111,9 @@
# touch files we're interested in, but also merges that merge two
# or more interesting revisions.
-class filemap_source(object):
+class filemap_source(converter_source):
def __init__(self, ui, baseconverter, filemap):
- self.ui = ui
+ super(filemap_source, self).__init__(ui)
self.base = baseconverter
self.filemapper = filemapper(ui, filemap)
self.commits = {}
@@ -134,7 +135,7 @@
def after(self):
self.base.after()
- def setrevmap(self, revmap, order):
+ def setrevmap(self, revmap):
# rebuild our state to make things restartable
#
# To avoid calling getcommit for every revision that has already
@@ -149,7 +150,7 @@
seen = {SKIPREV: SKIPREV}
dummyset = util.set()
converted = []
- for rev in order:
+ for rev in revmap.order:
mapped = revmap[rev]
wanted = mapped not in seen
if wanted:
@@ -163,7 +164,7 @@
arg = None
converted.append((rev, wanted, arg))
self.convertedorder = converted
- return self.base.setrevmap(revmap, order)
+ return self.base.setrevmap(revmap)
def rebuild(self):
if self._rebuilt:
@@ -350,9 +351,3 @@
def gettags(self):
return self.base.gettags()
-
- def before(self):
- pass
-
- def after(self):
- pass
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/git.py
--- a/hgext/convert/git.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/git.py Fri Feb 08 11:55:17 2008 +0100
@@ -3,7 +3,7 @@
import os
from mercurial import util
-from common import NoRepo, commit, converter_source
+from common import NoRepo, commit, converter_source, checktool
class convert_git(converter_source):
# Windows does not support GIT_DIR= construct while other systems
@@ -31,6 +31,9 @@
path += "/.git"
if not os.path.exists(path + "/objects"):
raise NoRepo("%s does not look like a Git repo" % path)
+
+ checktool('git-rev-parse', 'git')
+
self.path = path
def getheads(self):
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/gnuarch.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/convert/gnuarch.py Fri Feb 08 11:55:17 2008 +0100
@@ -0,0 +1,267 @@
+# GNU Arch support for the convert extension
+
+from common import NoRepo, checktool, commandline, commit, converter_source
+from mercurial.i18n import _
+from mercurial import util
+import os, shutil, tempfile, stat
+
+class gnuarch_source(converter_source, commandline):
+
+ class gnuarch_rev:
+ def __init__(self, rev):
+ self.rev = rev
+ self.summary = ''
+ self.date = None
+ self.author = ''
+ self.add_files = []
+ self.mod_files = []
+ self.del_files = []
+ self.ren_files = {}
+ self.ren_dirs = {}
+
+ def __init__(self, ui, path, rev=None):
+ super(gnuarch_source, self).__init__(ui, path, rev=rev)
+
+ if not os.path.exists(os.path.join(path, '{arch}')):
+ raise NoRepo(_("couldn't open GNU Arch repo %s" % path))
+
+ # Could use checktool, but we want to check for baz or tla.
+ self.execmd = None
+ if util.find_exe('tla'):
+ self.execmd = 'tla'
+ else:
+ if util.find_exe('baz'):
+ self.execmd = 'baz'
+ else:
+ raise util.Abort(_('cannot find a GNU Arch tool'))
+
+ commandline.__init__(self, ui, self.execmd)
+
+ self.path = os.path.realpath(path)
+ self.tmppath = None
+
+ self.treeversion = None
+ self.lastrev = None
+ self.changes = {}
+ self.parents = {}
+ self.tags = {}
+ self.modecache = {}
+
+ def before(self):
+ 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)
+
+ # Read author, date and summary
+ catlog = self.runlines0('cat-log', '-d', self.path, rev)
+ self._parsecatlog(catlog, rev)
+
+ self.parents[rev] = child
+ child = [rev]
+ if rev == self.rev:
+ break
+ self.parents[None] = child
+
+ def after(self):
+ self.ui.debug(_('cleaning up %s\n' % self.tmppath))
+ shutil.rmtree(self.tmppath, ignore_errors=True)
+
+ def getheads(self):
+ return self.parents[None]
+
+ def getfile(self, name, rev):
+ if rev != self.lastrev:
+ raise util.Abort(_('internal calling inconsistency'))
+
+ # Raise IOError if necessary (i.e. deleted files).
+ if not os.path.exists(os.path.join(self.tmppath, name)):
+ raise IOError
+
+ data, mode = self._getfile(name, rev)
+ self.modecache[(name, rev)] = mode
+
+ return data
+
+ def getmode(self, name, rev):
+ return self.modecache[(name, rev)]
+
+ def getchanges(self, rev):
+ self.modecache = {}
+ self._update(rev)
+ changes = []
+ copies = {}
+
+ for f in self.changes[rev].add_files:
+ changes.append((f, rev))
+
+ for f in self.changes[rev].mod_files:
+ changes.append((f, rev))
+
+ for f in self.changes[rev].del_files:
+ changes.append((f, rev))
+
+ for src in self.changes[rev].ren_files:
+ to = self.changes[rev].ren_files[src]
+ changes.append((src, rev))
+ changes.append((to, rev))
+ copies[src] = to
+
+ for src in self.changes[rev].ren_dirs:
+ to = self.changes[rev].ren_dirs[src]
+ chgs, cps = self._rendirchanges(src, to);
+ changes += [(f, rev) for f in chgs]
+ for c in cps:
+ copies[c] = cps[c]
+
+ changes.sort()
+ self.lastrev = rev
+
+ return changes, copies
+
+ def getcommit(self, rev):
+ changes = self.changes[rev]
+ return commit(author = changes.author, date = changes.date,
+ desc = changes.summary, parents = self.parents[rev])
+
+ def gettags(self):
+ return self.tags
+
+ def _execute(self, cmd, *args, **kwargs):
+ cmdline = [self.execmd, cmd]
+ cmdline += args
+ cmdline = [util.shellquote(arg) for arg in cmdline]
+ cmdline += ['>', util.nulldev, '2>', util.nulldev]
+ cmdline = util.quotecommand(' '.join(cmdline))
+ self.ui.debug(cmdline, '\n')
+ return os.system(cmdline)
+
+ def _update(self, rev):
+ if rev == 'base-0':
+ # Initialise 'base-0' revision
+ self.ui.debug(_('obtaining revision %s...\n' % rev))
+ revision = '%s--%s' % (self.treeversion, rev)
+ output = self._execute('get', revision, self.tmppath)
+ self.ui.debug(_('analysing revision %s...\n' % rev))
+ files = self._readcontents(self.tmppath)
+ self.changes[rev].add_files += files
+ else:
+ self.ui.debug(_('applying revision %s...\n' % rev))
+ revision = '%s--%s' % (self.treeversion, rev)
+ output = self._execute('replay', '-d', self.tmppath, revision)
+
+ 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)
+ delta = self.runlines0('delta', '-n', rev_a, rev_b)
+ self._parsedelta(delta, rev)
+
+ def _getfile(self, name, rev):
+ mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
+ if stat.S_ISLNK(mode):
+ data = os.readlink(os.path.join(self.tmppath, name))
+ mode = mode and 'l' or ''
+ else:
+ data = open(os.path.join(self.tmppath, name), 'rb').read()
+ mode = (mode & 0111) and 'x' or ''
+ return data, mode
+
+ def _exclude(self, name):
+ exclude = [ '{arch}', '.arch-ids', '.arch-inventory' ]
+ for exc in exclude:
+ if name.find(exc) != -1:
+ return True
+ return False
+
+ def _readcontents(self, path):
+ files = []
+ contents = os.listdir(path)
+ while len(contents) > 0:
+ c = contents.pop()
+ p = os.path.join(path, c)
+ # os.walk could be used, but here we avoid internal GNU
+ # Arch files and directories, thus saving a lot time.
+ if not self._exclude(p):
+ if os.path.isdir(p):
+ contents += [os.path.join(c, f) for f in os.listdir(p)]
+ else:
+ files.append(c)
+ return files
+
+ def _rendirchanges(self, src, dest):
+ changes = []
+ copies = {}
+ files = self._readcontents(os.path.join(self.tmppath, dest))
+ for f in files:
+ s = os.path.join(src, f)
+ d = os.path.join(dest, f)
+ changes.append(s)
+ changes.append(d)
+ copies[s] = d
+ return changes, copies
+
+ 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)
+
+ def _parsedelta(self, data, rev):
+ for l in data:
+ l = l.strip()
+ if l.startswith('A') and not l.startswith('A/'):
+ file = l[1:].strip()
+ if not self._exclude(file):
+ self.changes[rev].add_files.append(file)
+ elif l.startswith('/>'):
+ dirs = l[2:].strip().split(' ')
+ if len(dirs) == 1:
+ dirs = l[2:].strip().split('\t')
+ if not self._exclude(dirs[0]) and not self._exclude(dirs[1]):
+ self.changes[rev].ren_dirs[dirs[0]] = dirs[1]
+ elif l.startswith('M'):
+ file = l[1:].strip()
+ if not self._exclude(file):
+ self.changes[rev].mod_files.append(file)
+ elif l.startswith('->'):
+ file = l[2:].strip()
+ if not self._exclude(file):
+ self.changes[rev].mod_files.append(file)
+ elif l.startswith('D') and not l.startswith('D/'):
+ file = l[1:].strip()
+ if not self._exclude(file):
+ self.changes[rev].del_files.append(file)
+ elif l.startswith('=>'):
+ files = l[2:].strip().split(' ')
+ if len(files) == 1:
+ files = l[2:].strip().split('\t')
+ if not self._exclude(files[0]) and not self._exclude(files[1]):
+ self.changes[rev].ren_files[files[0]] = files[1]
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/hg.py
--- a/hgext/convert/hg.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/hg.py Fri Feb 08 11:55:17 2008 +0100
@@ -1,10 +1,16 @@
# hg backend for convert extension
-# Note for hg->hg conversion: Old versions of Mercurial didn't trim
-# the whitespace from the ends of commit messages, but new versions
-# do. Changesets created by those older versions, then converted, may
-# thus have different hashes for changesets that are otherwise
-# identical.
+# Notes for hg->hg conversion:
+#
+# * Old versions of Mercurial didn't trim the whitespace from the ends
+# of commit messages, but new versions do. Changesets created by
+# those older versions, then converted, may thus have different
+# hashes for changesets that are otherwise identical.
+#
+# * By default, the source revision is stored in the converted
+# revision. This will cause the converted revision to have a
+# different identity than the source. To avoid this, use the
+# following option: "--config convert.hg.saverev=false"
import os, time
@@ -26,8 +32,6 @@
self.repo = hg.repository(self.ui, path)
if not self.repo.local():
raise NoRepo(_('%s is not a local Mercurial repo') % path)
- ui.status(_('destination %s is a Mercurial repository\n') %
- path)
except hg.RepoError, err:
ui.print_exc()
raise NoRepo(err.args[0])
@@ -46,11 +50,13 @@
self.filemapmode = False
def before(self):
+ self.ui.debug(_('run hg sink pre-conversion action\n'))
self.wlock = self.repo.wlock()
self.lock = self.repo.lock()
self.repo.dirstate.clear()
def after(self):
+ self.ui.debug(_('run hg sink post-conversion action\n'))
self.repo.dirstate.invalidate()
self.lock = None
self.wlock = None
@@ -191,7 +197,7 @@
except hg.RepoError, inst:
tagparent = nullid
self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
- date, tagparent, nullid)
+ date, tagparent, nullid, extra=extra)
return hex(self.repo.changelog.tip())
def setfilemapmode(self, active):
@@ -200,6 +206,7 @@
class mercurial_source(converter_source):
def __init__(self, ui, path, rev=None):
converter_source.__init__(self, ui, path, rev)
+ self.saverev = ui.configbool('convert', 'hg.saverev', True)
try:
self.repo = hg.repository(self.ui, path)
# try to provoke an exception if this isn't really a hg
@@ -212,6 +219,7 @@
self.lastrev = None
self.lastctx = None
self._changescache = None
+ self.convertfp = None
def changectx(self, rev):
if self.lastrev != rev:
@@ -257,8 +265,12 @@
def getcommit(self, rev):
ctx = self.changectx(rev)
parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
+ if self.saverev:
+ crev = rev
+ else:
+ crev = None
return commit(author=ctx.user(), date=util.datestr(ctx.date()),
- desc=ctx.description(), parents=parents,
+ desc=ctx.description(), rev=crev, parents=parents,
branch=ctx.branch(), extra=ctx.extra())
def gettags(self):
@@ -275,3 +287,15 @@
return changes[0] + changes[1] + changes[2]
+ def converted(self, rev, destrev):
+ if self.convertfp is None:
+ self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
+ 'a')
+ self.convertfp.write('%s %s\n' % (destrev, rev))
+ self.convertfp.flush()
+
+ def before(self):
+ self.ui.debug(_('run hg source pre-conversion action\n'))
+
+ def after(self):
+ self.ui.debug(_('run hg source post-conversion action\n'))
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/convert/subversion.py
--- a/hgext/convert/subversion.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/convert/subversion.py Fri Feb 08 11:55:17 2008 +0100
@@ -17,9 +17,13 @@
import locale
import os
+import re
import sys
import cPickle as pickle
-from mercurial import util
+import tempfile
+
+from mercurial import strutil, util
+from mercurial.i18n import _
# Subversion stuff. Works best with very recent Python SVN bindings
# e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
@@ -28,6 +32,7 @@
from cStringIO import StringIO
from common import NoRepo, commit, converter_source, encodeargs, decodeargs
+from common import commandline, converter_sink, mapfile
try:
from svn.core import SubversionException, Pool
@@ -46,7 +51,10 @@
except SubversionException:
pass
if os.path.isdir(path):
- return 'file://%s' % os.path.normpath(os.path.abspath(path))
+ path = os.path.normpath(os.path.abspath(path))
+ if os.name == 'nt':
+ path = '/' + util.normpath(path)
+ return 'file://%s' % path
return path
def optrev(number):
@@ -81,6 +89,9 @@
receiver)
except SubversionException, (inst, num):
pickle.dump(num, fp, protocol)
+ except IOError:
+ # Caller may interrupt the iteration
+ pickle.dump(None, fp, protocol)
else:
pickle.dump(None, fp, protocol)
fp.close()
@@ -94,7 +105,53 @@
args = decodeargs(sys.stdin.read())
get_log_child(sys.stdout, *args)
+class logstream:
+ """Interruptible revision log iterator."""
+ def __init__(self, stdout):
+ self._stdout = stdout
+
+ def __iter__(self):
+ while True:
+ entry = pickle.load(self._stdout)
+ try:
+ orig_paths, revnum, author, date, message = entry
+ except:
+ if entry is None:
+ break
+ raise SubversionException("child raised exception", entry)
+ yield entry
+
+ def close(self):
+ if self._stdout:
+ self._stdout.close()
+ self._stdout = None
+
+def get_log(url, paths, start, end, limit=0, discover_changed_paths=True,
+ strict_node_history=False):
+ args = [url, paths, start, end, limit, discover_changed_paths,
+ strict_node_history]
+ arg = encodeargs(args)
+ hgexe = util.hgexecutable()
+ cmd = '%s debugsvnlog' % util.shellquote(hgexe)
+ stdin, stdout = os.popen2(cmd, 'b')
+ stdin.write(arg)
+ stdin.close()
+ return logstream(stdout)
+
# SVN conversion code stolen from bzr-svn and tailor
+#
+# Subversion looks like a versioned filesystem, branches structures
+# are defined by conventions and not enforced by the tool. First,
+# we define the potential branches (modules) as "trunk" and "branches"
+# children directories. Revisions are then identified by their
+# module and revision number (and a repository identifier).
+#
+# The revision graph is really a tree (or a forest). By default, a
+# revision parent is the previous revision in the same module. If the
+# module directory is copied/moved from another module then the
+# revision is the module root and its parent the source revision in
+# the parent module. A revision has at most one parent.
+#
class svn_source(converter_source):
def __init__(self, ui, url, rev=None):
super(svn_source, self).__init__(ui, url, rev=rev)
@@ -125,7 +182,7 @@
self.ctx = self.transport.client
self.base = svn.ra.get_repos_root(self.ra)
self.module = self.url[len(self.base):]
- self.modulemap = {} # revision, module
+ self.rootmodule = self.module
self.commits = {}
self.paths = {}
self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
@@ -144,14 +201,23 @@
except IOError, e:
pass
- self.last_changed = self.latest(self.module, latest)
-
- self.head = self.revid(self.last_changed)
+ self.head = self.latest(self.module, latest)
+ if not self.head:
+ raise util.Abort(_('no revision found in module %s') %
+ self.module.encode(self.encoding))
+ self.last_changed = self.revnum(self.head)
+
self._changescache = None
- def setrevmap(self, revmap, order):
+ if os.path.exists(os.path.join(url, '.svn/entries')):
+ self.wc = url
+ else:
+ self.wc = None
+ self.convertfp = None
+
+ def setrevmap(self, revmap):
lastrevs = {}
- for revid in revmap.keys():
+ for revid in revmap.iterkeys():
uuid, module, revnum = self.revsplit(revid)
lastrevnum = lastrevs.setdefault(module, revnum)
if revnum > lastrevnum:
@@ -167,46 +233,54 @@
return False
def getheads(self):
- # detect standard /branches, /tags, /trunk layout
+
+ def getcfgpath(name, rev):
+ cfgpath = self.ui.config('convert', 'svn.' + name)
+ path = (cfgpath or name).strip('/')
+ if not self.exists(path, rev):
+ if cfgpath:
+ raise util.Abort(_('expected %s to be at %r, but not found')
+ % (name, path))
+ return None
+ self.ui.note(_('found %s at %r\n') % (name, path))
+ return path
+
rev = optrev(self.last_changed)
- rpath = self.url.strip('/')
- cfgtrunk = self.ui.config('convert', 'svn.trunk')
- cfgbranches = self.ui.config('convert', 'svn.branches')
- cfgtags = self.ui.config('convert', 'svn.tags')
- trunk = (cfgtrunk or 'trunk').strip('/')
- branches = (cfgbranches or 'branches').strip('/')
- tags = (cfgtags or 'tags').strip('/')
- if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
- self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
- (trunk, branches, tags))
- oldmodule = self.module
+ oldmodule = ''
+ trunk = getcfgpath('trunk', rev)
+ tags = getcfgpath('tags', rev)
+ branches = getcfgpath('branches', rev)
+
+ # If the project has a trunk or branches, we will extract heads
+ # from them. We keep the project root otherwise.
+ if trunk:
+ oldmodule = self.module or ''
self.module += '/' + trunk
- lt = self.latest(self.module, self.last_changed)
- self.head = self.revid(lt)
- self.heads = [self.head]
+ self.head = self.latest(self.module, self.last_changed)
+ if not self.head:
+ raise util.Abort(_('no revision found in module %s') %
+ self.module.encode(self.encoding))
+
+ # First head in the list is the module's head
+ self.heads = [self.head]
+ self.tags = '%s/%s' % (oldmodule , (tags or 'tags'))
+
+ # Check if branches bring a few more heads to the list
+ if branches:
+ rpath = self.url.strip('/')
branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
self.ctx)
for branch in branchnames.keys():
- if oldmodule:
- module = oldmodule + '/' + branches + '/' + branch
- else:
- module = '/' + branches + '/' + branch
- brevnum = self.latest(module, self.last_changed)
- brev = self.revid(brevnum, module)
- self.ui.note('found branch %s at %d\n' % (branch, brevnum))
- self.heads.append(brev)
+ module = '%s/%s/%s' % (oldmodule, branches, branch)
+ brevid = self.latest(module, self.last_changed)
+ if not brevid:
+ self.ui.note(_('ignoring empty branch %s\n') %
+ branch.encode(self.encoding))
+ continue
+ self.ui.note('found branch %s at %d\n' %
+ (branch, self.revnum(brevid)))
+ self.heads.append(brevid)
- if oldmodule:
- self.tags = '%s/%s' % (oldmodule, tags)
- else:
- self.tags = '/%s' % tags
-
- elif cfgtrunk or cfgbranches or cfgtags:
- raise util.Abort('trunk/branch/tags layout expected, but not found')
- else:
- self.ui.note('working with one branch\n')
- self.heads = [self.head]
- self.tags = tags
return self.heads
def getfile(self, file, rev):
@@ -223,7 +297,17 @@
self._changescache = None
self.modecache = {}
(paths, parents) = self.paths[rev]
- files, copies = self.expandpaths(rev, paths, parents)
+ if parents:
+ files, copies = self.expandpaths(rev, paths, parents)
+ else:
+ # Perform a full checkout on roots
+ uuid, module, revnum = self.revsplit(rev)
+ entries = svn.client.ls(self.base + module, optrev(revnum),
+ True, self.ctx)
+ files = [n for n,e in entries.iteritems()
+ if e.kind == svn.core.svn_node_file]
+ copies = {}
+
files.sort()
files = zip(files, [rev] * len(files))
@@ -241,45 +325,26 @@
uuid, module, revnum = self.revsplit(rev)
self.module = module
self.reparent(module)
+ # We assume that:
+ # - requests for revisions after "stop" come from the
+ # revision graph backward traversal. Cache all of them
+ # down to stop, they will be used eventually.
+ # - requests for revisions before "stop" come to get
+ # isolated branches parents. Just fetch what is needed.
stop = self.lastrevs.get(module, 0)
- self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
+ if revnum < stop:
+ stop = revnum + 1
+ self._fetch_revisions(revnum, stop)
commit = self.commits[rev]
# caller caches the result, so free it here to release memory
del self.commits[rev]
return commit
- def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
- strict_node_history=False):
-
- def parent(fp):
- while True:
- entry = pickle.load(fp)
- try:
- orig_paths, revnum, author, date, message = entry
- except:
- if entry is None:
- break
- raise SubversionException("child raised exception", entry)
- yield entry
-
- args = [self.url, paths, start, end, limit, discover_changed_paths,
- strict_node_history]
- arg = encodeargs(args)
- hgexe = util.hgexecutable()
- cmd = '%s debugsvnlog' % util.shellquote(hgexe)
- stdin, stdout = os.popen2(cmd, 'b')
-
- stdin.write(arg)
- stdin.close()
-
- for p in parent(stdout):
- yield p
-
def gettags(self):
tags = {}
start = self.revnum(self.head)
try:
- for entry in self.get_log([self.tags], 0, start):
+ for entry in get_log(self.url, [self.tags], 0, start):
orig_paths, revnum, author, date, message = entry
for path in orig_paths:
if not path.startswith(self.tags+'/'):
@@ -293,6 +358,15 @@
self.ui.note('no tags found at revision %d\n' % start)
return tags
+ def converted(self, rev, destrev):
+ if not self.wc:
+ return
+ if self.convertfp is None:
+ self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
+ 'a')
+ self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
+ self.convertfp.flush()
+
# -- helper functions --
def revid(self, revnum, module=None):
@@ -315,7 +389,11 @@
return uuid, mod, revnum
def latest(self, path, stop=0):
- 'find the latest revision affecting path, up to stop'
+ """Find the latest revid affecting path, up to stop. It may return
+ a revision in a different module, since a branch may be moved without
+ a change being reported. Return None if computed module does not
+ belong to rootmodule subtree.
+ """
if not stop:
stop = svn.ra.get_latest_revnum(self.ra)
try:
@@ -327,7 +405,31 @@
if not dirent:
raise util.Abort('%s not found up to revision %d' % (path, stop))
- return dirent.created_rev
+ # stat() gives us the previous revision on this line of development, but
+ # it might be in *another module*. Fetch the log and detect renames down
+ # to the latest revision.
+ stream = get_log(self.url, [path], stop, dirent.created_rev)
+ try:
+ for entry in stream:
+ paths, revnum, author, date, message = entry
+ if revnum <= dirent.created_rev:
+ break
+
+ for p in paths:
+ if not path.startswith(p) or not paths[p].copyfrom_path:
+ continue
+ newpath = paths[p].copyfrom_path + path[len(p):]
+ self.ui.debug("branch renamed from %s to %s at %d\n" %
+ (path, newpath, revnum))
+ path = newpath
+ break
+ finally:
+ stream.close()
+
+ if not path.startswith(self.rootmodule):
+ self.ui.debug(_('ignoring foreign branch %r\n') % path)
+ return None
+ return self.revid(dirent.created_rev, path)
def get_blacklist(self):
"""Avoid certain revision numbers.
@@ -375,13 +477,11 @@
entries = []
copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
copies = {}
- revnum = self.revnum(rev)
- if revnum in self.modulemap:
- new_module = self.modulemap[revnum]
- if new_module != self.module:
- self.module = new_module
- self.reparent(self.module)
+ new_module, revnum = self.revsplit(rev)[1:]
+ if new_module != self.module:
+ self.module = new_module
+ self.reparent(self.module)
for path, ent in paths:
entrypath = get_entry_from_path(path, module=self.module)
@@ -392,7 +492,9 @@
if ent.copyfrom_path:
copyfrom_path = get_entry_from_path(ent.copyfrom_path)
if copyfrom_path:
- self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
+ self.ui.debug("Copied to %s from %s@%s\n" %
+ (entrypath, copyfrom_path,
+ ent.copyfrom_rev))
# It's probably important for hg that the source
# exists in the revision's parent, not just the
# ent.copyfrom_rev
@@ -405,12 +507,9 @@
# if a branch is created but entries are removed in the same
# changeset, get the right fromrev
- if parents:
- uuid, old_module, fromrev = self.revsplit(parents[0])
- else:
- fromrev = revnum - 1
- # might always need to be revnum - 1 in these 3 lines?
- old_module = self.modulemap.get(fromrev, self.module)
+ # parents cannot be empty here, you cannot remove things from
+ # a root revision.
+ uuid, old_module, fromrev = self.revsplit(parents[0])
basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
@@ -486,6 +585,9 @@
# If the directory just had a prop change,
# then we shouldn't need to look for its children.
+ if ent.action == 'M':
+ continue
+
# Also this could create duplicate entries. Not sure
# whether this will matter. Maybe should make entries a set.
# print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
@@ -546,42 +648,46 @@
return (util.unique(entries), copies)
- def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
+ def _fetch_revisions(self, from_revnum, to_revnum):
+ if from_revnum < to_revnum:
+ from_revnum, to_revnum = to_revnum, from_revnum
+
self.child_cset = None
def parselogentry(orig_paths, revnum, author, date, message):
+ """Return the parsed commit object or None, and True if
+ the revision is a branch root.
+ """
self.ui.debug("parsing revision %d (%d changes)\n" %
(revnum, len(orig_paths)))
- if revnum in self.modulemap:
- new_module = self.modulemap[revnum]
- if new_module != self.module:
- self.module = new_module
- self.reparent(self.module)
-
+ branched = False
rev = self.revid(revnum)
# branch log might return entries for a parent we already have
- if (rev in self.commits or
- (revnum < self.lastrevs.get(self.module, 0))):
- return
+
+ if (rev in self.commits or revnum < to_revnum):
+ return None, branched
parents = []
- # check whether this revision is the start of a branch
- if self.module in orig_paths:
- ent = orig_paths[self.module]
+ # check whether this revision is the start of a branch or part
+ # of a branch renaming
+ orig_paths = orig_paths.items()
+ orig_paths.sort()
+ root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
+ if root_paths:
+ path, ent = root_paths[-1]
if ent.copyfrom_path:
+ branched = True
+ newpath = ent.copyfrom_path + self.module[len(path):]
# ent.copyfrom_rev may not be the actual last revision
- prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
- self.modulemap[prev] = ent.copyfrom_path
- parents = [self.revid(prev, ent.copyfrom_path)]
- self.ui.note('found parent of branch %s at %d: %s\n' % \
- (self.module, prev, ent.copyfrom_path))
+ previd = self.latest(newpath, ent.copyfrom_rev)
+ if previd is not None:
+ parents = [previd]
+ prevmodule, prevnum = self.revsplit(previd)[1:]
+ self.ui.note('found parent of branch %s at %d: %s\n' %
+ (self.module, prevnum, prevmodule))
else:
self.ui.debug("No copyfrom path, don't know what to do.\n")
- self.modulemap[revnum] = self.module # track backwards in time
-
- orig_paths = orig_paths.items()
- orig_paths.sort()
paths = []
# filter out unrelated paths
for path, ent in orig_paths:
@@ -590,8 +696,6 @@
continue
paths.append((path, ent))
- self.paths[rev] = (paths, parents)
-
# Example SVN datetime. Includes microseconds.
# ISO-8601 conformant
# '2007-01-04T17:35:00.902377Z'
@@ -614,23 +718,52 @@
rev=rev.encode('utf-8'))
self.commits[rev] = cset
+ # The parents list is *shared* among self.paths and the
+ # commit object. Both will be updated below.
+ self.paths[rev] = (paths, cset.parents)
if self.child_cset and not self.child_cset.parents:
- self.child_cset.parents = [rev]
+ self.child_cset.parents[:] = [rev]
self.child_cset = cset
+ return cset, branched
self.ui.note('fetching revision log for "%s" from %d to %d\n' %
(self.module, from_revnum, to_revnum))
try:
- for entry in self.get_log([self.module], from_revnum, to_revnum):
- orig_paths, revnum, author, date, message = entry
- if self.is_blacklisted(revnum):
- self.ui.note('skipping blacklisted revision %d\n' % revnum)
- continue
- if orig_paths is None:
- self.ui.debug('revision %d has no entries\n' % revnum)
- continue
- parselogentry(orig_paths, revnum, author, date, message)
+ firstcset = None
+ branched = False
+ stream = get_log(self.url, [self.module], from_revnum, to_revnum)
+ try:
+ for entry in stream:
+ paths, revnum, author, date, message = entry
+ if self.is_blacklisted(revnum):
+ self.ui.note('skipping blacklisted revision %d\n'
+ % revnum)
+ continue
+ if paths is None:
+ self.ui.debug('revision %d has no entries\n' % revnum)
+ continue
+ cset, branched = parselogentry(paths, revnum, author,
+ date, message)
+ if cset:
+ firstcset = cset
+ if branched:
+ break
+ finally:
+ stream.close()
+
+ if not branched and firstcset and not firstcset.parents:
+ # The first revision of the sequence (the last fetched one)
+ # has invalid parents if not a branch root. Find the parent
+ # revision now, if any.
+ try:
+ firstrevnum = self.revnum(firstcset.rev)
+ if firstrevnum > 1:
+ latest = self.latest(self.module, firstrevnum - 1)
+ if latest:
+ firstcset.parents.append(latest)
+ except util.Abort:
+ pass
except SubversionException, (inst, num):
if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
raise NoSuchRevision(branch=self,
@@ -642,9 +775,9 @@
# TODO: ra.get_file transmits the whole file instead of diffs.
mode = ''
try:
- revnum = self.revnum(rev)
- if self.module != self.modulemap[revnum]:
- self.module = self.modulemap[revnum]
+ new_module, revnum = self.revsplit(rev)[1:]
+ if self.module != new_module:
+ self.module = new_module
self.reparent(self.module)
info = svn.ra.get_file(self.ra, file, revnum, io)
if isinstance(info, list):
@@ -669,3 +802,240 @@
pool = Pool()
rpath = '/'.join([self.base, path]).strip('/')
return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
+
+pre_revprop_change = '''#!/bin/sh
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+ACTION="$5"
+
+if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
+if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
+if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
+
+echo "Changing prohibited revision property" >&2
+exit 1
+'''
+
+class svn_sink(converter_sink, commandline):
+ commit_re = re.compile(r'Committed revision (\d+).', re.M)
+
+ def prerun(self):
+ if self.wc:
+ os.chdir(self.wc)
+
+ def postrun(self):
+ if self.wc:
+ os.chdir(self.cwd)
+
+ def join(self, name):
+ return os.path.join(self.wc, '.svn', name)
+
+ def revmapfile(self):
+ return self.join('hg-shamap')
+
+ def authorfile(self):
+ return self.join('hg-authormap')
+
+ def __init__(self, ui, path):
+ converter_sink.__init__(self, ui, path)
+ commandline.__init__(self, ui, 'svn')
+ self.delete = []
+ self.setexec = []
+ self.delexec = []
+ self.copies = []
+ self.wc = None
+ self.cwd = os.getcwd()
+
+ path = os.path.realpath(path)
+
+ created = False
+ if os.path.isfile(os.path.join(path, '.svn', 'entries')):
+ self.wc = path
+ self.run0('update')
+ else:
+ wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
+
+ if os.path.isdir(os.path.dirname(path)):
+ if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
+ ui.status(_('initializing svn repo %r\n') %
+ os.path.basename(path))
+ commandline(ui, 'svnadmin').run0('create', path)
+ created = path
+ path = util.normpath(path)
+ if not path.startswith('/'):
+ path = '/' + path
+ path = 'file://' + path
+
+ ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
+ self.run0('checkout', path, wcpath)
+
+ self.wc = wcpath
+ self.opener = util.opener(self.wc)
+ self.wopener = util.opener(self.wc)
+ self.childmap = mapfile(ui, self.join('hg-childmap'))
+ self.is_exec = util.checkexec(self.wc) and util.is_exec or None
+
+ if created:
+ hook = os.path.join(created, 'hooks', 'pre-revprop-change')
+ fp = open(hook, 'w')
+ fp.write(pre_revprop_change)
+ fp.close()
+ util.set_flags(hook, "x")
+
+ xport = transport.SvnRaTransport(url=geturl(path))
+ self.uuid = svn.ra.get_uuid(xport.ra)
+
+ def wjoin(self, *names):
+ return os.path.join(self.wc, *names)
+
+ def putfile(self, filename, flags, data):
+ if 'l' in flags:
+ self.wopener.symlink(data, filename)
+ else:
+ try:
+ if os.path.islink(self.wjoin(filename)):
+ os.unlink(filename)
+ except OSError:
+ pass
+ self.wopener(filename, 'w').write(data)
+
+ if self.is_exec:
+ was_exec = self.is_exec(self.wjoin(filename))
+ else:
+ # On filesystems not supporting execute-bit, there is no way
+ # to know if it is set but asking subversion. Setting it
+ # systematically is just as expensive and much simpler.
+ was_exec = 'x' not in flags
+
+ util.set_flags(self.wjoin(filename), flags)
+ if was_exec:
+ if 'x' not in flags:
+ self.delexec.append(filename)
+ else:
+ if 'x' in flags:
+ self.setexec.append(filename)
+
+ def delfile(self, name):
+ self.delete.append(name)
+
+ def copyfile(self, source, dest):
+ self.copies.append([source, dest])
+
+ def _copyfile(self, source, dest):
+ # SVN's copy command pukes if the destination file exists, but
+ # our copyfile method expects to record a copy that has
+ # already occurred. Cross the semantic gap.
+ wdest = self.wjoin(dest)
+ exists = os.path.exists(wdest)
+ if exists:
+ fd, tempname = tempfile.mkstemp(
+ prefix='hg-copy-', dir=os.path.dirname(wdest))
+ os.close(fd)
+ os.unlink(tempname)
+ os.rename(wdest, tempname)
+ try:
+ self.run0('copy', source, dest)
+ finally:
+ if exists:
+ try:
+ os.unlink(wdest)
+ except OSError:
+ pass
+ os.rename(tempname, wdest)
+
+ def dirs_of(self, files):
+ dirs = set()
+ for f in files:
+ if os.path.isdir(self.wjoin(f)):
+ dirs.add(f)
+ for i in strutil.rfindall(f, '/'):
+ dirs.add(f[:i])
+ return dirs
+
+ def add_dirs(self, files):
+ add_dirs = [d for d in self.dirs_of(files)
+ if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
+ if add_dirs:
+ add_dirs.sort()
+ self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
+ return add_dirs
+
+ def add_files(self, files):
+ if files:
+ self.xargs(files, 'add', quiet=True)
+ return files
+
+ def tidy_dirs(self, names):
+ dirs = list(self.dirs_of(names))
+ dirs.sort(reverse=True)
+ deleted = []
+ for d in dirs:
+ wd = self.wjoin(d)
+ if os.listdir(wd) == '.svn':
+ self.run0('delete', d)
+ deleted.append(d)
+ return deleted
+
+ def addchild(self, parent, child):
+ self.childmap[parent] = child
+
+ def revid(self, rev):
+ return u"svn:%s@%s" % (self.uuid, rev)
+
+ def putcommit(self, files, parents, commit):
+ for parent in parents:
+ try:
+ return self.revid(self.childmap[parent])
+ except KeyError:
+ pass
+ entries = set(self.delete)
+ files = util.frozenset(files)
+ entries.update(self.add_dirs(files.difference(entries)))
+ if self.copies:
+ for s, d in self.copies:
+ self._copyfile(s, d)
+ self.copies = []
+ if self.delete:
+ self.xargs(self.delete, 'delete')
+ self.delete = []
+ entries.update(self.add_files(files.difference(entries)))
+ entries.update(self.tidy_dirs(entries))
+ if self.delexec:
+ self.xargs(self.delexec, 'propdel', 'svn:executable')
+ self.delexec = []
+ if self.setexec:
+ self.xargs(self.setexec, 'propset', 'svn:executable', '*')
+ self.setexec = []
+
+ fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
+ fp = os.fdopen(fd, 'w')
+ fp.write(commit.desc)
+ fp.close()
+ try:
+ output = self.run0('commit',
+ username=util.shortuser(commit.author),
+ file=messagefile,
+ encoding='utf-8')
+ try:
+ rev = self.commit_re.search(output).group(1)
+ except AttributeError:
+ self.ui.warn(_('unexpected svn output:\n'))
+ self.ui.warn(output)
+ raise util.Abort(_('unable to cope with svn output'))
+ if commit.rev:
+ self.run('propset', 'hg:convert-rev', commit.rev,
+ revprop=True, revision=rev)
+ if commit.branch and commit.branch != 'default':
+ self.run('propset', 'hg:convert-branch', commit.branch,
+ revprop=True, revision=rev)
+ for parent in parents:
+ self.addchild(parent, rev)
+ return self.revid(rev)
+ finally:
+ os.unlink(messagefile)
+
+ def puttags(self, tags):
+ self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/fetch.py
--- a/hgext/fetch.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/fetch.py Fri Feb 08 11:55:17 2008 +0100
@@ -43,7 +43,8 @@
if not err:
mod, add, rem = repo.status()[:3]
message = (cmdutil.logmessage(opts) or
- (_('Automated merge with %s') % other.url()))
+ (_('Automated merge with %s') %
+ util.removeauth(other.url())))
n = repo.commit(mod + add + rem, message,
opts['user'], opts['date'],
force_editor=opts.get('force_editor'))
@@ -54,7 +55,8 @@
cmdutil.setremoteconfig(ui, opts)
other = hg.repository(ui, ui.expandpath(source))
- ui.status(_('pulling from %s\n') % ui.expandpath(source))
+ ui.status(_('pulling from %s\n') %
+ util.hidepassword(ui.expandpath(source)))
revs = None
if opts['rev'] and not other.local():
raise util.Abort(_("fetch -r doesn't work for remote repositories yet"))
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/gpg.py
--- a/hgext/gpg.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/gpg.py Fri Feb 08 11:55:17 2008 +0100
@@ -249,7 +249,7 @@
message = opts['message']
if not message:
message = "\n".join([_("Added signature for changeset %s")
- % hgnode.hex(n)
+ % hgnode.short(n)
for n in nodes])
try:
repo.commit([".hgsigs"], message, opts['user'], opts['date'])
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/graphlog.py
--- a/hgext/graphlog.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/graphlog.py Fri Feb 08 11:55:17 2008 +0100
@@ -5,11 +5,12 @@
# This software may be used and distributed according to the terms of
# the GNU General Public License, incorporated herein by reference.
+import os
import sys
from mercurial.cmdutil import revrange, show_changeset
from mercurial.i18n import _
from mercurial.node import nullid, nullrev
-from mercurial.util import Abort
+from mercurial.util import Abort, canonpath
def revision_grapher(repo, start_rev, stop_rev):
"""incremental revision grapher
@@ -63,6 +64,62 @@
revs = next_revs
curr_rev -= 1
+def filelog_grapher(repo, path, start_rev, stop_rev):
+ """incremental file log grapher
+
+ This generator function walks through the revision history of a
+ single file from revision start_rev to revision stop_rev (which must
+ be less than or equal to start_rev) and for each revision emits
+ tuples with the following elements:
+
+ - Current revision.
+ - Current node.
+ - Column of the current node in the set of ongoing edges.
+ - Edges; a list of (col, next_col) indicating the edges between
+ the current node and its parents.
+ - Number of columns (ongoing edges) in the current revision.
+ - The difference between the number of columns (ongoing edges)
+ in the next revision and the number of columns (ongoing edges)
+ in the current revision. That is: -1 means one column removed;
+ 0 means no columns added or removed; 1 means one column added.
+ """
+
+ assert start_rev >= stop_rev
+ curr_rev = start_rev
+ revs = []
+ filerev = repo.file(path).count() - 1
+ while filerev >= 0:
+ fctx = repo.filectx(path, fileid=filerev)
+
+ # Compute revs and next_revs.
+ if filerev not in revs:
+ revs.append(filerev)
+ rev_index = revs.index(filerev)
+ next_revs = revs[:]
+
+ # Add parents to next_revs.
+ parents = [f.filerev() for f in fctx.parents() if f.path() == path]
+ parents_to_add = []
+ for parent in parents:
+ if parent not in next_revs:
+ parents_to_add.append(parent)
+ parents_to_add.sort()
+ next_revs[rev_index:rev_index + 1] = parents_to_add
+
+ edges = []
+ for parent in parents:
+ edges.append((rev_index, next_revs.index(parent)))
+
+ changerev = fctx.linkrev()
+ if changerev <= start_rev:
+ node = repo.changelog.node(changerev)
+ n_columns_diff = len(next_revs) - len(revs)
+ yield (changerev, node, rev_index, edges, len(revs), n_columns_diff)
+ if changerev <= stop_rev:
+ break
+ revs = next_revs
+ filerev -= 1
+
def get_rev_parents(repo, rev):
return [x for x in repo.changelog.parentrevs(rev) if x != nullrev]
@@ -141,7 +198,7 @@
else:
return (repo.changelog.count() - 1, 0)
-def graphlog(ui, repo, **opts):
+def graphlog(ui, repo, path=None, **opts):
"""show revision history alongside an ASCII revision graph
Print a revision history alongside a revision graph drawn with
@@ -157,7 +214,11 @@
if start_rev == nullrev:
return
cs_printer = show_changeset(ui, repo, opts)
- grapher = revision_grapher(repo, start_rev, stop_rev)
+ if path:
+ cpath = canonpath(repo.root, os.getcwd(), path)
+ grapher = filelog_grapher(repo, cpath, start_rev, stop_rev)
+ else:
+ grapher = revision_grapher(repo, start_rev, stop_rev)
repo_parents = repo.dirstate.parents()
prev_n_columns_diff = 0
prev_node_index = 0
@@ -261,5 +322,5 @@
('r', 'rev', [], _('show the specified revision or range')),
('', 'style', '', _('display using template map file')),
('', 'template', '', _('display with template'))],
- _('hg glog [OPTION]...')),
+ _('hg glog [OPTION]... [FILE]')),
}
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/hbisect.py
--- a/hgext/hbisect.py Fri Feb 08 11:50:37 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,313 +0,0 @@
-# bisect extension for mercurial
-#
-# Copyright 2005, 2006 Benoit Boissinot
-# Inspired by git bisect, extension skeleton taken from mq.py.
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-from mercurial.i18n import _
-from mercurial import hg, util, commands, cmdutil
-import os, sys, sets
-
-versionstr = "0.0.3"
-
-def lookup_rev(ui, repo, rev=None):
- """returns rev or the checked-out revision if rev is None"""
- if not rev is None:
- return repo.lookup(rev)
- parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
- if len(parents) != 1:
- raise util.Abort(_("unexpected number of parents, "
- "please commit or revert"))
- return parents.pop()
-
-def check_clean(ui, repo):
- modified, added, removed, deleted, unknown = repo.status()[:5]
- if modified or added or removed:
- ui.warn("Repository is not clean, please commit or revert\n")
- sys.exit(1)
-
-class bisect(object):
- """dichotomic search in the DAG of changesets"""
- def __init__(self, ui, repo):
- self.repo = repo
- self.path = repo.join("bisect")
- self.opener = util.opener(self.path)
- self.ui = ui
- self.goodrevs = []
- self.badrev = None
- self.good_path = "good"
- self.bad_path = "bad"
- self.is_reset = False
-
- if os.path.exists(os.path.join(self.path, self.good_path)):
- self.goodrevs = self.opener(self.good_path).read().splitlines()
- self.goodrevs = [hg.bin(x) for x in self.goodrevs]
- if os.path.exists(os.path.join(self.path, self.bad_path)):
- r = self.opener(self.bad_path).read().splitlines()
- if r:
- self.badrev = hg.bin(r.pop(0))
-
- def write(self):
- if self.is_reset:
- return
- if not os.path.isdir(self.path):
- os.mkdir(self.path)
- f = self.opener(self.good_path, "w")
- f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
- if len(self.goodrevs) > 0:
- f.write("\n")
- f = self.opener(self.bad_path, "w")
- if self.badrev:
- f.write(hg.hex(self.badrev) + "\n")
-
- def init(self):
- """start a new bisection"""
- if os.path.isdir(self.path):
- raise util.Abort(_("bisect directory already exists\n"))
- os.mkdir(self.path)
- check_clean(self.ui, self.repo)
- return 0
-
- def reset(self):
- """finish a bisection"""
- if os.path.isdir(self.path):
- sl = [os.path.join(self.path, p)
- for p in [self.bad_path, self.good_path]]
- for s in sl:
- if os.path.exists(s):
- os.unlink(s)
- os.rmdir(self.path)
- # Not sure about this
- #self.ui.write("Going back to tip\n")
- #self.repo.update(self.repo.changelog.tip())
- self.is_reset = True
- return 0
-
- def num_ancestors(self, head=None, stop=None):
- """
- returns a dict with the mapping:
- node -> number of ancestors (self included)
- for all nodes who are ancestor of head and
- not in stop.
- """
- if head is None:
- head = self.badrev
- return self.__ancestors_and_nb_ancestors(head, stop)[1]
-
- def ancestors(self, head=None, stop=None):
- """
- returns the set of the ancestors of head (self included)
- who are not in stop.
- """
- if head is None:
- head = self.badrev
- return self.__ancestors_and_nb_ancestors(head, stop)[0]
-
- def __ancestors_and_nb_ancestors(self, head, stop=None):
- """
- if stop is None then ancestors of goodrevs are used as
- lower limit.
-
- returns (anc, n_child) where anc is the set of the ancestors of head
- and n_child is a dictionary with the following mapping:
- node -> number of ancestors (self included)
- """
- cl = self.repo.changelog
- if not stop:
- stop = sets.Set([])
- for i in xrange(len(self.goodrevs)-1, -1, -1):
- g = self.goodrevs[i]
- if g in stop:
- continue
- stop.update(cl.reachable(g))
- def num_children(a):
- """
- returns a dictionnary with the following mapping
- node -> [number of children, empty set]
- """
- d = {a: [0, sets.Set([])]}
- for i in xrange(cl.rev(a)+1):
- n = cl.node(i)
- if not d.has_key(n):
- d[n] = [0, sets.Set([])]
- parents = [p for p in cl.parents(n) if p != hg.nullid]
- for p in parents:
- d[p][0] += 1
- return d
-
- if head in stop:
- raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
- % (cl.rev(head), hg.short(head)))
- n_child = num_children(head)
- for i in xrange(cl.rev(head)+1):
- n = cl.node(i)
- parents = [p for p in cl.parents(n) if p != hg.nullid]
- for p in parents:
- n_child[p][0] -= 1
- if not n in stop:
- n_child[n][1].union_update(n_child[p][1])
- if n_child[p][0] == 0:
- n_child[p] = len(n_child[p][1])
- if not n in stop:
- n_child[n][1].add(n)
- if n_child[n][0] == 0:
- if n == head:
- anc = n_child[n][1]
- n_child[n] = len(n_child[n][1])
- return anc, n_child
-
- def next(self):
- if not self.badrev:
- raise util.Abort(_("You should give at least one bad revision"))
- if not self.goodrevs:
- self.ui.warn(_("No good revision given\n"))
- self.ui.warn(_("Marking the first revision as good\n"))
- ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
- self.badrev)
- tot = len(ancestors)
- if tot == 1:
- if ancestors.pop() != self.badrev:
- raise util.Abort(_("Could not find the first bad revision"))
- self.ui.write(_("The first bad revision is:\n"))
- displayer = cmdutil.show_changeset(self.ui, self.repo, {})
- displayer.show(changenode=self.badrev)
- return None
- best_rev = None
- best_len = -1
- for n in ancestors:
- l = num_ancestors[n]
- l = min(l, tot - l)
- if l > best_len:
- best_len = l
- best_rev = n
- assert best_rev is not None
- nb_tests = 0
- q, r = divmod(tot, 2)
- while q:
- nb_tests += 1
- q, r = divmod(q, 2)
- msg = _("Testing changeset %s:%s (%s changesets remaining, "
- "~%s tests)\n") % (self.repo.changelog.rev(best_rev),
- hg.short(best_rev), tot, nb_tests)
- self.ui.write(msg)
- return best_rev
-
- def autonext(self):
- """find and update to the next revision to test"""
- check_clean(self.ui, self.repo)
- rev = self.next()
- if rev is not None:
- return hg.clean(self.repo, rev)
-
- def good(self, rev):
- self.goodrevs.append(rev)
-
- def autogood(self, rev=None):
- """mark revision as good and update to the next revision to test"""
- check_clean(self.ui, self.repo)
- rev = lookup_rev(self.ui, self.repo, rev)
- self.good(rev)
- if self.badrev:
- return self.autonext()
-
- def bad(self, rev):
- self.badrev = rev
-
- def autobad(self, rev=None):
- """mark revision as bad and update to the next revision to test"""
- check_clean(self.ui, self.repo)
- rev = lookup_rev(self.ui, self.repo, rev)
- self.bad(rev)
- if self.goodrevs:
- self.autonext()
-
-# should we put it in the class ?
-def test(ui, repo, rev):
- """test the bisection code"""
- b = bisect(ui, repo)
- rev = repo.lookup(rev)
- ui.write("testing with rev %s\n" % hg.hex(rev))
- anc = b.ancestors()
- while len(anc) > 1:
- if not rev in anc:
- ui.warn("failure while bisecting\n")
- sys.exit(1)
- ui.write("it worked :)\n")
- new_rev = b.next()
- ui.write("choosing if good or bad\n")
- if rev in b.ancestors(head=new_rev):
- b.bad(new_rev)
- ui.write("it is bad\n")
- else:
- b.good(new_rev)
- ui.write("it is good\n")
- anc = b.ancestors()
- #repo.update(new_rev, force=True)
- for v in anc:
- if v != rev:
- ui.warn("fail to found cset! :(\n")
- return 1
- ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
- ui.write("Everything is ok :)\n")
- return 0
-
-def bisect_run(ui, repo, cmd=None, *args):
- """Dichotomic search in the DAG of changesets
-
-This extension helps to find changesets which cause problems.
-To use, mark the earliest changeset you know introduces the problem
-as bad, then mark the latest changeset which is free from the problem
-as good. Bisect will update your working directory to a revision for
-testing. Once you have performed tests, mark the working directory
-as bad or good and bisect will either update to another candidate
-changeset or announce that it has found the bad revision.
-
-Note: bisect expects bad revisions to be descendants of good revisions.
-If you are looking for the point at which a problem was fixed, then make
-the problem-free state "bad" and the problematic state "good."
-
-For subcommands see "hg bisect help\"
- """
- def help_(cmd=None, *args):
- """show help for a given bisect subcommand or all subcommands"""
- cmdtable = bisectcmdtable
- if cmd:
- doc = cmdtable[cmd][0].__doc__
- synopsis = cmdtable[cmd][2]
- ui.write(synopsis + "\n")
- ui.write("\n" + doc + "\n")
- return
- ui.write(_("list of subcommands for the bisect extension\n\n"))
- cmds = cmdtable.keys()
- cmds.sort()
- m = max([len(c) for c in cmds])
- for cmd in cmds:
- doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
- ui.write(" %-*s %s\n" % (m, cmd, doc))
-
- b = bisect(ui, repo)
- bisectcmdtable = {
- "init": (b.init, 0, _("hg bisect init")),
- "bad": (b.autobad, 1, _("hg bisect bad []")),
- "good": (b.autogood, 1, _("hg bisect good []")),
- "next": (b.autonext, 0, _("hg bisect next")),
- "reset": (b.reset, 0, _("hg bisect reset")),
- "help": (help_, 1, _("hg bisect help []")),
- }
-
- if not bisectcmdtable.has_key(cmd):
- ui.warn(_("bisect: Unknown sub-command\n"))
- return help_()
- if len(args) > bisectcmdtable[cmd][1]:
- ui.warn(_("bisect: Too many arguments\n"))
- return help_()
- ret = bisectcmdtable[cmd][0](*args)
- b.write()
- return ret
-
-cmdtable = {
- "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")),
- #"bisect-test": (test, [], "hg bisect-test rev"),
-}
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/hgk.py
--- a/hgext/hgk.py Fri Feb 08 11:50:37 2008 +0100
+++ b/hgext/hgk.py Fri Feb 08 11:55:17 2008 +0100
@@ -13,7 +13,7 @@
# querying of information, and an extension to mercurial named hgk.py,
# which provides hooks for hgk to get information. hgk can be found in
# the contrib directory, and hgk.py can be found in the hgext
-# directory.
+# directory.
#
# To load the hgext.py extension, add it to your .hgrc file (you have
# to use your global $HOME/.hgrc file, not one in a repository). You
@@ -45,7 +45,7 @@
# Revisions context menu will now display additional entries to fire
# vdiff on hovered and selected revisions.
-import sys, os
+import os
from mercurial import hg, fancyopts, commands, ui, util, patch, revlog
def difftree(ui, repo, node1=None, node2=None, *files, **opts):
@@ -61,17 +61,14 @@
for f in modified:
# TODO get file permissions
- print ":100664 100664 %s %s M\t%s\t%s" % (hg.short(mmap[f]),
- hg.short(mmap2[f]),
- f, f)
+ ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
+ (hg.short(mmap[f]), hg.short(mmap2[f]), f, f))
for f in added:
- print ":000000 100664 %s %s N\t%s\t%s" % (empty,
- hg.short(mmap2[f]),
- f, f)
+ ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
+ (empty, hg.short(mmap2[f]), f, f))
for f in removed:
- print ":100664 000000 %s %s D\t%s\t%s" % (hg.short(mmap[f]),
- empty,
- f, f)
+ ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
+ (hg.short(mmap[f]), empty, f, f))
##
while True:
@@ -93,7 +90,7 @@
node1 = repo.changelog.parents(node1)[0]
if opts['patch']:
if opts['pretty']:
- catcommit(repo, node2, "")
+ catcommit(ui, repo, node2, "")
patch.diff(repo, node1, node2,
files=files,
opts=patch.diffopts(ui, {'git': True}))
@@ -102,14 +99,14 @@
if not opts['stdin']:
break
-def catcommit(repo, n, prefix, ctx=None):
+def catcommit(ui, repo, n, prefix, ctx=None):
nlprefix = '\n' + prefix;
if ctx is None:
ctx = repo.changectx(n)
(p1, p2) = ctx.parents()
- print "tree %s" % (hg.short(ctx.changeset()[0])) # use ctx.node() instead ??
- if p1: print "parent %s" % (hg.short(p1.node()))
- if p2: print "parent %s" % (hg.short(p2.node()))
+ ui.write("tree %s\n" % hg.short(ctx.changeset()[0])) # use ctx.node() instead ??
+ if p1: ui.write("parent %s\n" % hg.short(p1.node()))
+ if p2: ui.write("parent %s\n" % hg.short(p2.node()))
date = ctx.date()
description = ctx.description().replace("\0", "")
lines = description.splitlines()
@@ -118,23 +115,24 @@
else:
committer = ctx.user()
- print "author %s %s %s" % (ctx.user(), int(date[0]), date[1])
- print "committer %s %s %s" % (committer, int(date[0]), date[1])
- print "revision %d" % ctx.rev()
- print ""
+ ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
+ ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
+ ui.write("revision %d\n" % ctx.rev())
+ ui.write("branch %s\n\n" % ctx.branch())
+
if prefix != "":
- print "%s%s" % (prefix, description.replace('\n', nlprefix).strip())
+ ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
else:
- print description
+ ui.write(description + "\n")
if prefix:
- sys.stdout.write('\0')
+ ui.write('\0')
def base(ui, repo, node1, node2):
"""Output common ancestor information"""
node1 = repo.lookup(node1)
node2 = repo.lookup(node2)
n = repo.changelog.ancestor(node1, node2)
- print hg.short(n)
+ ui.write(hg.short(n) + "\n")
def catfile(ui, repo, type=None, r=None, **opts):
"""cat a specific revision"""
@@ -157,10 +155,10 @@
while r:
if type != "commit":
- sys.stderr.write("aborting hg cat-file only understands commits\n")
- sys.exit(1);
+ ui.warn("aborting hg cat-file only understands commits\n")
+ return 1;
n = repo.lookup(r)
- catcommit(repo, n, prefix)
+ catcommit(ui, repo, n, prefix)
if opts['stdin']:
try:
(type, r) = raw_input().split(' ');
@@ -174,7 +172,7 @@
# telling you which commits are reachable from the supplied ones via
# a bitmask based on arg position.
# you can specify a commit to stop at by starting the sha1 with ^
-def revtree(args, repo, full="tree", maxnr=0, parents=False):
+def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
def chlogwalk():
count = repo.changelog.count()
i = count
@@ -259,24 +257,24 @@
if pp[1] != hg.nullid:
parentstr += " " + hg.short(pp[1])
if not full:
- print hg.short(n) + parentstr
+ ui.write("%s%s\n" % (hg.short(n), parentstr))
elif full == "commit":
- print hg.short(n) + parentstr
- catcommit(repo, n, ' ', ctx)
+ ui.write("%s%s\n" % (hg.short(n), parentstr))
+ catcommit(ui, repo, n, ' ', ctx)
else:
(p1, p2) = repo.changelog.parents(n)
(h, h1, h2) = map(hg.short, (n, p1, p2))
(i1, i2) = map(repo.changelog.rev, (p1, p2))
date = ctx.date()[0]
- print "%s %s:%s" % (date, h, mask),
+ ui.write("%s %s:%s" % (date, h, mask))
mask = is_reachable(want_sha1, reachable, p1)
if i1 != hg.nullrev and mask > 0:
- print "%s:%s " % (h1, mask),
+ ui.write("%s:%s " % (h1, mask)),
mask = is_reachable(want_sha1, reachable, p2)
if i2 != hg.nullrev and mask > 0:
- print "%s:%s " % (h2, mask),
- print ""
+ ui.write("%s:%s " % (h2, mask))
+ ui.write("\n")
if maxnr and count >= maxnr:
break
count += 1
@@ -304,15 +302,15 @@
else:
full = None
copy = [x for x in revs]
- revtree(copy, repo, full, opts['max_count'], opts['parents'])
+ revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
def config(ui, repo, **opts):
"""print extension options"""
def writeopt(name, value):
- ui.write('k=%s\nv=%s\n' % (name, value))
+ ui.write('k=%s\nv=%s\n' % (name, value))
writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
-
+
def view(ui, repo, *etc, **opts):
"start interactive history viewer"
diff -r 6605a03cbf87 -r 012ad48a07fa hgext/highlight.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/highlight.py Fri Feb 08 11:55:17 2008 +0100
@@ -0,0 +1,139 @@
+"""
+This is Mercurial extension for syntax highlighting in the file
+revision view of hgweb.
+
+It depends on the pygments syntax highlighting library:
+http://pygments.org/
+
+To enable the extension add this to hgrc:
+
+[extensions]
+hgext.highlight =
+
+There is a single configuration option:
+
+[web]
+pygments_style =