--- a/hgext/mq.py Sat Jun 13 23:02:51 2009 +0200
+++ b/hgext/mq.py Sat Jun 13 18:08:51 2009 -0500
@@ -902,14 +902,14 @@
def push(self, repo, patch=None, force=False, list=False,
mergeq=None, all=False):
wlock = repo.wlock()
- if repo.dirstate.parents()[0] not in repo.heads():
- self.ui.status(_("(working directory not at a head)\n"))
+ try:
+ if repo.dirstate.parents()[0] not in repo.heads():
+ self.ui.status(_("(working directory not at a head)\n"))
- if not self.series:
- self.ui.warn(_('no patches in series\n'))
- return 0
+ if not self.series:
+ self.ui.warn(_('no patches in series\n'))
+ return 0
- try:
patch = self.lookup(patch)
# Suppose our series file is: A B C and the current 'top'
# patch is B. qpush C should be performed (moving forward)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/share.py Sat Jun 13 18:08:51 2009 -0500
@@ -0,0 +1,31 @@
+# Mercurial extension to provide the 'hg share' command
+#
+# Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2, incorporated herein by reference.
+
+import os
+from mercurial.i18n import _
+from mercurial import hg, commands
+
+def share(ui, source, dest, noupdate=False):
+ """create a new shared repository (experimental)
+
+ Initialize a new repository and working directory that shares its
+ history with another repository.
+
+ NOTE: actions that change history such as rollback or moving the
+ source may confuse sharers.
+ """
+
+ return hg.share(ui, source, dest, not noupdate)
+
+cmdtable = {
+ "share":
+ (share,
+ [('U', 'noupdate', None, _('do not create a working copy'))],
+ _('[-U] SOURCE DEST')),
+}
+
+commands.norepo += " share"
--- a/mercurial/cmdutil.py Sat Jun 13 23:02:51 2009 +0200
+++ b/mercurial/cmdutil.py Sat Jun 13 18:08:51 2009 -0500
@@ -101,10 +101,10 @@
def remoteui(src, opts):
'build a remote ui from ui or repo and opts'
if hasattr(src, 'baseui'): # looks like a repository
- dst = src.baseui # drop repo-specific config
+ dst = src.baseui.copy() # drop repo-specific config
src = src.ui # copy target options from repo
else: # assume it's a global ui object
- dst = src # keep all global options
+ dst = src.copy() # keep all global options
# copy ssh-specific options
for o in 'ssh', 'remotecmd':
--- a/mercurial/commands.py Sat Jun 13 23:02:51 2009 +0200
+++ b/mercurial/commands.py Sat Jun 13 18:08:51 2009 -0500
@@ -448,8 +448,12 @@
"""
hexfunc = ui.debugflag and hex or short
activebranches = [encoding.tolocal(repo[n].branch())
- for n in repo.heads(closed=False)]
- branches = sorted([(tag in activebranches, repo.changelog.rev(node), tag)
+ for n in repo.heads()]
+ def testactive(tag, node):
+ realhead = tag in activebranches
+ open = node in repo.branchheads(tag, closed=False)
+ return realhead and open
+ branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
for tag, node in repo.branchtags().items()],
reverse=True)
@@ -1304,17 +1308,22 @@
With no arguments, show all repository head changesets.
- If branch names or revisions are given this will show the heads of
- the specified branches or the branches those revisions are tagged
- with.
-
Repository "heads" are changesets that don't have child
changesets. They are where development generally takes place and
are the usual targets for update and merge operations.
- Branch heads are changesets that have a given branch tag, but have
- no child changesets with that tag. They are usually where
- development on a given branch takes place.
+ If one or more REV is given, the "branch heads" will be shown for
+ the named branch associated with that revision. The name of the
+ branch is called the revision's branch tag.
+
+ Branch heads are revisions on a given named branch that do not have
+ any children on the same branch. A branch head could be a true head
+ or it could be the last changeset on a branch before a new branch
+ was created. If none of the branch heads are true heads, the branch
+ is considered inactive.
+
+ If STARTREV is specified only those heads (or branch heads) that
+ are descendants of STARTREV will be displayed.
"""
if opts.get('rev'):
start = repo.lookup(opts['rev'])
@@ -1324,10 +1333,10 @@
hideinactive, _heads = opts.get('active'), None
if not branchrevs:
# Assume we're looking repo-wide heads if no revs were specified.
- heads = repo.heads(start, closed=closed)
+ heads = repo.heads(start)
else:
if hideinactive:
- _heads = repo.heads(start, closed=closed)
+ _heads = repo.heads(start)
heads = []
visitedset = set()
for branchrev in branchrevs:
@@ -1337,7 +1346,9 @@
visitedset.add(branch)
bheads = repo.branchheads(branch, start, closed=closed)
if not bheads:
- if branch != branchrev:
+ if not opts.get('rev'):
+ ui.warn(_("no open branch heads on branch %s\n") % branch)
+ elif branch != branchrev:
ui.warn(_("no changes on branch %s containing %s are "
"reachable from %s\n")
% (branch, branchrev, opts.get('rev')))
@@ -3253,7 +3264,7 @@
('c', 'closed', False,
_('show normal and closed heads')),
] + templateopts,
- _('[-r REV] [REV]...')),
+ _('[-r STARTREV] [REV]...')),
"help": (help_, [], _('[TOPIC]')),
"identify|id":
(identify,
--- a/mercurial/hg.py Sat Jun 13 23:02:51 2009 +0200
+++ b/mercurial/hg.py Sat Jun 13 18:08:51 2009 -0500
@@ -81,6 +81,64 @@
return path[5:]
return path
+def share(ui, source, dest, update=True):
+ '''create a shared repository'''
+
+ if not islocal(source):
+ raise util.Abort(_('can only share local repositories'))
+
+ if isinstance(source, str):
+ origsource = ui.expandpath(source)
+ source, rev, checkout = parseurl(origsource, '')
+ srcrepo = repository(ui, source)
+ else:
+ srcrepo = source
+ origsource = source = srcrepo.url()
+ checkout = None
+
+ sharedpath = srcrepo.sharedpath # if our source is already sharing
+
+ root = os.path.realpath(dest)
+ roothg = os.path.join(root, '.hg')
+
+ if os.path.exists(roothg):
+ raise util.Abort(_('destination already exists'))
+
+ if not os.path.isdir(root):
+ os.mkdir(root)
+ os.mkdir(roothg)
+
+ requirements = ''
+ try:
+ requirements = srcrepo.opener('requires').read()
+ except IOError, inst:
+ if inst.errno != errno.ENOENT:
+ raise
+
+ requirements += 'shared\n'
+ file(os.path.join(roothg, 'requires'), 'w').write(requirements)
+ file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
+
+ default = srcrepo.ui.config('paths', 'default')
+ if default:
+ f = file(os.path.join(roothg, 'hgrc'), 'w')
+ f.write('[paths]\ndefault = %s\n' % default)
+ f.close()
+
+ r = repository(ui, root)
+
+ if update:
+ r.ui.status(_("updating working directory\n"))
+ if update is not True:
+ checkout = update
+ for test in (checkout, 'default', 'tip'):
+ try:
+ uprev = r.lookup(test)
+ break
+ except:
+ continue
+ _update(r, uprev)
+
def clone(ui, source, dest=None, pull=False, rev=None, update=True,
stream=False):
"""Make a copy of an existing repository.
--- a/mercurial/hgweb/webcommands.py Sat Jun 13 23:02:51 2009 +0200
+++ b/mercurial/hgweb/webcommands.py Sat Jun 13 18:08:51 2009 -0500
@@ -361,7 +361,7 @@
def branches(web, req, tmpl):
b = web.repo.branchtags()
tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
- open = set(web.repo[n].branch() for n in web.repo.heads(closed=False))
+ heads = web.repo.heads()
parity = paritygen(web.stripecount)
sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
@@ -371,7 +371,12 @@
if limit > 0 and count >= limit:
return
count += 1
- status = ctx.branch() in open and 'open' or 'closed'
+ if ctx.node() not in heads:
+ status = 'inactive'
+ elif not web.repo.branchheads(ctx.branch()):
+ status = 'closed'
+ else:
+ status = 'open'
yield {'parity': parity.next(),
'branch': ctx.branch(),
'status': status,
--- a/mercurial/localrepo.py Sat Jun 13 23:02:51 2009 +0200
+++ b/mercurial/localrepo.py Sat Jun 13 18:08:51 2009 -0500
@@ -19,7 +19,7 @@
class localrepository(repo.repository):
capabilities = set(('lookup', 'changegroupsubset', 'branchmap'))
- supported = set('revlogv1 store fncache'.split())
+ supported = set('revlogv1 store fncache shared'.split())
def __init__(self, baseui, path=None, create=0):
repo.repository.__init__(self)
@@ -28,6 +28,14 @@
self.origroot = path
self.opener = util.opener(self.path)
self.wopener = util.opener(self.root)
+ self.baseui = baseui
+ self.ui = baseui.copy()
+
+ try:
+ self.ui.readconfig(self.join("hgrc"), self.root)
+ extensions.loadall(self.ui)
+ except IOError:
+ pass
if not os.path.isdir(self.path):
if create:
@@ -35,10 +43,10 @@
os.mkdir(path)
os.mkdir(self.path)
requirements = ["revlogv1"]
- if baseui.configbool('format', 'usestore', True):
+ if self.ui.configbool('format', 'usestore', True):
os.mkdir(os.path.join(self.path, "store"))
requirements.append("store")
- if baseui.configbool('format', 'usefncache', True):
+ if self.ui.configbool('format', 'usefncache', True):
requirements.append("fncache")
# create an invalid changelog
self.opener("00changelog.i", "a").write(
@@ -64,20 +72,23 @@
for r in requirements - self.supported:
raise error.RepoError(_("requirement '%s' not supported") % r)
- self.store = store.store(requirements, self.path, util.opener)
+ self.sharedpath = self.path
+ try:
+ s = os.path.realpath(self.opener("sharedpath").read())
+ if not os.path.exists(s):
+ raise error.RepoError(
+ _('.hg/sharedpath points to nonexistent directory %s' % s))
+ self.sharedpath = s
+ except IOError, inst:
+ if inst.errno != errno.ENOENT:
+ raise
+
+ self.store = store.store(requirements, self.sharedpath, util.opener)
self.spath = self.store.path
self.sopener = self.store.opener
self.sjoin = self.store.join
self.opener.createmode = self.store.createmode
- self.baseui = baseui
- self.ui = baseui.copy()
- try:
- self.ui.readconfig(self.join("hgrc"), self.root)
- extensions.loadall(self.ui)
- except IOError:
- pass
-
self.tagscache = None
self._tagstypecache = None
self.branchcache = None
@@ -1134,15 +1145,10 @@
finally:
wlock.release()
- def heads(self, start=None, closed=False):
+ def heads(self, start=None):
heads = self.changelog.heads(start)
- def display(head):
- if closed:
- return True
- extras = self.changelog.read(head)[5]
- return ('close' not in extras)
# sort the output in rev descending order
- heads = [(-self.changelog.rev(h), h) for h in heads if display(h)]
+ heads = [(-self.changelog.rev(h), h) for h in heads]
return [n for (r, n) in sorted(heads)]
def branchheads(self, branch=None, start=None, closed=False):
--- a/tests/test-branches Sat Jun 13 23:02:51 2009 +0200
+++ b/tests/test-branches Sat Jun 13 18:08:51 2009 -0500
@@ -71,9 +71,19 @@
echo '--- b branch should be inactive'
hg branches
hg branches -a
+hg heads b
+hg heads --closed b
echo 'xxx4' >> b
hg commit -d '9 0' -m 'reopen branch with a change'
echo '--- branch b is back in action'
hg branches -a
-hg heads -c
+echo '---- test heads listings'
hg heads
+echo '% branch default'
+hg heads default
+echo '% branch a'
+hg heads a
+hg heads --active a
+echo '% branch b'
+hg heads b
+hg heads --closed b
--- a/tests/test-branches.out Sat Jun 13 23:02:51 2009 +0200
+++ b/tests/test-branches.out Sat Jun 13 18:08:51 2009 -0500
@@ -113,9 +113,25 @@
a 5:d8cbc61dbaa6 (inactive)
default 0:19709c5a4e75 (inactive)
a branch name much longer than the default justification used by branches 7:10ff5895aa57
+no open branch heads on branch b
+changeset: 12:2da6583810df
+branch: b
+tag: tip
+parent: 8:eebb944467c9
+user: test
+date: Thu Jan 01 00:00:09 1970 +0000
+summary: close this part branch too
+
+changeset: 11:c84627f3c15d
+branch: b
+user: test
+date: Thu Jan 01 00:00:09 1970 +0000
+summary: prune bad branch
+
--- branch b is back in action
b 13:6ac12926b8c3
a branch name much longer than the default justification used by branches 7:10ff5895aa57
+---- test heads listings
changeset: 13:6ac12926b8c3
branch: b
tag: tip
@@ -135,6 +151,21 @@
date: Thu Jan 01 00:00:06 1970 +0000
summary: Adding d branch
+% branch default
+changeset: 0:19709c5a4e75
+user: test
+date: Thu Jan 01 00:00:00 1970 +0000
+summary: Adding root node
+
+% branch a
+changeset: 5:d8cbc61dbaa6
+branch: a
+parent: 2:881fe2b92ad0
+user: test
+date: Thu Jan 01 00:00:04 1970 +0000
+summary: Adding b branch head 2
+
+% branch b
changeset: 13:6ac12926b8c3
branch: b
tag: tip
@@ -142,9 +173,16 @@
date: Thu Jan 01 00:00:09 1970 +0000
summary: reopen branch with a change
-changeset: 7:10ff5895aa57
-branch: a branch name much longer than the default justification used by branches
+changeset: 13:6ac12926b8c3
+branch: b
+tag: tip
user: test
-date: Thu Jan 01 00:00:06 1970 +0000
-summary: Adding d branch
+date: Thu Jan 01 00:00:09 1970 +0000
+summary: reopen branch with a change
+changeset: 11:c84627f3c15d
+branch: b
+user: test
+date: Thu Jan 01 00:00:09 1970 +0000
+summary: prune bad branch
+
--- a/tests/test-hgweb-commands.out Sat Jun 13 23:02:51 2009 +0200
+++ b/tests/test-hgweb-commands.out Sat Jun 13 18:08:51 2009 -0500
@@ -532,7 +532,7 @@
<tr class="parity1">
<td class="age"><i>many years ago</i></td>
<td><a class="list" href="/shortlog/a4f92ed23982?style=gitweb"><b>a4f92ed23982</b></a></td>
-<td class="closed">default</td>
+<td class="inactive">default</td>
<td class="link">
<a href="/changeset/a4f92ed23982?style=gitweb">changeset</a> |
<a href="/log/a4f92ed23982?style=gitweb">changelog</a> |
--- a/tests/test-mq-qpush-fail Sat Jun 13 23:02:51 2009 +0200
+++ b/tests/test-mq-qpush-fail Sat Jun 13 18:08:51 2009 -0500
@@ -16,6 +16,10 @@
echo '.hgignore' >> .hgignore
hg qinit
+
+echo '% test qpush on empty series'
+hg qpush
+
hg qnew patch1
echo >> foo
hg qrefresh -m 'patch 1'
--- a/tests/test-mq-qpush-fail.out Sat Jun 13 23:02:51 2009 +0200
+++ b/tests/test-mq-qpush-fail.out Sat Jun 13 18:08:51 2009 -0500
@@ -1,4 +1,6 @@
adding foo
+% test qpush on empty series
+no patches in series
patch queue now empty
applying patch1
applying patch2