##// END OF EJS Templates
bookmarks: hoist getbkfile out of bmstore class...
Augie Fackler -
r27186:34d26e22 default
parent child Browse files
Show More
@@ -1,176 +1,176
1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 '''share a common history between several working directories
6 '''share a common history between several working directories
7
7
8 Automatic Pooled Storage for Clones
8 Automatic Pooled Storage for Clones
9 -----------------------------------
9 -----------------------------------
10
10
11 When this extension is active, :hg:`clone` can be configured to
11 When this extension is active, :hg:`clone` can be configured to
12 automatically share/pool storage across multiple clones. This
12 automatically share/pool storage across multiple clones. This
13 mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`.
13 mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`.
14 The benefit of using this mode is the automatic management of
14 The benefit of using this mode is the automatic management of
15 store paths and intelligent pooling of related repositories.
15 store paths and intelligent pooling of related repositories.
16
16
17 The following ``share.`` config options influence this feature:
17 The following ``share.`` config options influence this feature:
18
18
19 ``share.pool``
19 ``share.pool``
20 Filesystem path where shared repository data will be stored. When
20 Filesystem path where shared repository data will be stored. When
21 defined, :hg:`clone` will automatically use shared repository
21 defined, :hg:`clone` will automatically use shared repository
22 storage instead of creating a store inside each clone.
22 storage instead of creating a store inside each clone.
23
23
24 ``share.poolnaming``
24 ``share.poolnaming``
25 How directory names in ``share.pool`` are constructed.
25 How directory names in ``share.pool`` are constructed.
26
26
27 "identity" means the name is derived from the first changeset in the
27 "identity" means the name is derived from the first changeset in the
28 repository. In this mode, different remotes share storage if their
28 repository. In this mode, different remotes share storage if their
29 root/initial changeset is identical. In this mode, the local shared
29 root/initial changeset is identical. In this mode, the local shared
30 repository is an aggregate of all encountered remote repositories.
30 repository is an aggregate of all encountered remote repositories.
31
31
32 "remote" means the name is derived from the source repository's
32 "remote" means the name is derived from the source repository's
33 path or URL. In this mode, storage is only shared if the path or URL
33 path or URL. In this mode, storage is only shared if the path or URL
34 requested in the :hg:`clone` command matches exactly to a repository
34 requested in the :hg:`clone` command matches exactly to a repository
35 that was cloned before.
35 that was cloned before.
36
36
37 The default naming mode is "identity."
37 The default naming mode is "identity."
38 '''
38 '''
39
39
40 from mercurial.i18n import _
40 from mercurial.i18n import _
41 from mercurial import cmdutil, commands, hg, util, extensions, bookmarks, error
41 from mercurial import cmdutil, commands, hg, util, extensions, bookmarks, error
42 from mercurial.hg import repository, parseurl
42 from mercurial.hg import repository, parseurl
43 import errno
43 import errno
44
44
45 cmdtable = {}
45 cmdtable = {}
46 command = cmdutil.command(cmdtable)
46 command = cmdutil.command(cmdtable)
47 # Note for extension authors: ONLY specify testedwith = 'internal' for
47 # Note for extension authors: ONLY specify testedwith = 'internal' for
48 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
48 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
49 # be specifying the version(s) of Mercurial they are tested with, or
49 # be specifying the version(s) of Mercurial they are tested with, or
50 # leave the attribute unspecified.
50 # leave the attribute unspecified.
51 testedwith = 'internal'
51 testedwith = 'internal'
52
52
53 @command('share',
53 @command('share',
54 [('U', 'noupdate', None, _('do not create a working directory')),
54 [('U', 'noupdate', None, _('do not create a working directory')),
55 ('B', 'bookmarks', None, _('also share bookmarks'))],
55 ('B', 'bookmarks', None, _('also share bookmarks'))],
56 _('[-U] [-B] SOURCE [DEST]'),
56 _('[-U] [-B] SOURCE [DEST]'),
57 norepo=True)
57 norepo=True)
58 def share(ui, source, dest=None, noupdate=False, bookmarks=False):
58 def share(ui, source, dest=None, noupdate=False, bookmarks=False):
59 """create a new shared repository
59 """create a new shared repository
60
60
61 Initialize a new repository and working directory that shares its
61 Initialize a new repository and working directory that shares its
62 history (and optionally bookmarks) with another repository.
62 history (and optionally bookmarks) with another repository.
63
63
64 .. note::
64 .. note::
65
65
66 using rollback or extensions that destroy/modify history (mq,
66 using rollback or extensions that destroy/modify history (mq,
67 rebase, etc.) can cause considerable confusion with shared
67 rebase, etc.) can cause considerable confusion with shared
68 clones. In particular, if two shared clones are both updated to
68 clones. In particular, if two shared clones are both updated to
69 the same changeset, and one of them destroys that changeset
69 the same changeset, and one of them destroys that changeset
70 with rollback, the other clone will suddenly stop working: all
70 with rollback, the other clone will suddenly stop working: all
71 operations will fail with "abort: working directory has unknown
71 operations will fail with "abort: working directory has unknown
72 parent". The only known workaround is to use debugsetparents on
72 parent". The only known workaround is to use debugsetparents on
73 the broken clone to reset it to a changeset that still exists.
73 the broken clone to reset it to a changeset that still exists.
74 """
74 """
75
75
76 return hg.share(ui, source, dest, not noupdate, bookmarks)
76 return hg.share(ui, source, dest, not noupdate, bookmarks)
77
77
78 @command('unshare', [], '')
78 @command('unshare', [], '')
79 def unshare(ui, repo):
79 def unshare(ui, repo):
80 """convert a shared repository to a normal one
80 """convert a shared repository to a normal one
81
81
82 Copy the store data to the repo and remove the sharedpath data.
82 Copy the store data to the repo and remove the sharedpath data.
83 """
83 """
84
84
85 if not repo.shared():
85 if not repo.shared():
86 raise error.Abort(_("this is not a shared repo"))
86 raise error.Abort(_("this is not a shared repo"))
87
87
88 destlock = lock = None
88 destlock = lock = None
89 lock = repo.lock()
89 lock = repo.lock()
90 try:
90 try:
91 # we use locks here because if we race with commit, we
91 # we use locks here because if we race with commit, we
92 # can end up with extra data in the cloned revlogs that's
92 # can end up with extra data in the cloned revlogs that's
93 # not pointed to by changesets, thus causing verify to
93 # not pointed to by changesets, thus causing verify to
94 # fail
94 # fail
95
95
96 destlock = hg.copystore(ui, repo, repo.path)
96 destlock = hg.copystore(ui, repo, repo.path)
97
97
98 sharefile = repo.join('sharedpath')
98 sharefile = repo.join('sharedpath')
99 util.rename(sharefile, sharefile + '.old')
99 util.rename(sharefile, sharefile + '.old')
100
100
101 repo.requirements.discard('sharedpath')
101 repo.requirements.discard('sharedpath')
102 repo._writerequirements()
102 repo._writerequirements()
103 finally:
103 finally:
104 destlock and destlock.release()
104 destlock and destlock.release()
105 lock and lock.release()
105 lock and lock.release()
106
106
107 # update store, spath, svfs and sjoin of repo
107 # update store, spath, svfs and sjoin of repo
108 repo.unfiltered().__init__(repo.baseui, repo.root)
108 repo.unfiltered().__init__(repo.baseui, repo.root)
109
109
110 # Wrap clone command to pass auto share options.
110 # Wrap clone command to pass auto share options.
111 def clone(orig, ui, source, *args, **opts):
111 def clone(orig, ui, source, *args, **opts):
112 pool = ui.config('share', 'pool', None)
112 pool = ui.config('share', 'pool', None)
113 if pool:
113 if pool:
114 pool = util.expandpath(pool)
114 pool = util.expandpath(pool)
115
115
116 opts['shareopts'] = dict(
116 opts['shareopts'] = dict(
117 pool=pool,
117 pool=pool,
118 mode=ui.config('share', 'poolnaming', 'identity'),
118 mode=ui.config('share', 'poolnaming', 'identity'),
119 )
119 )
120
120
121 return orig(ui, source, *args, **opts)
121 return orig(ui, source, *args, **opts)
122
122
123 def extsetup(ui):
123 def extsetup(ui):
124 extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile)
124 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
125 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
125 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
126 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
126 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
127 extensions.wrapcommand(commands.table, 'clone', clone)
127 extensions.wrapcommand(commands.table, 'clone', clone)
128
128
129 def _hassharedbookmarks(repo):
129 def _hassharedbookmarks(repo):
130 """Returns whether this repo has shared bookmarks"""
130 """Returns whether this repo has shared bookmarks"""
131 try:
131 try:
132 shared = repo.vfs.read('shared').splitlines()
132 shared = repo.vfs.read('shared').splitlines()
133 except IOError as inst:
133 except IOError as inst:
134 if inst.errno != errno.ENOENT:
134 if inst.errno != errno.ENOENT:
135 raise
135 raise
136 return False
136 return False
137 return 'bookmarks' in shared
137 return 'bookmarks' in shared
138
138
139 def _getsrcrepo(repo):
139 def _getsrcrepo(repo):
140 """
140 """
141 Returns the source repository object for a given shared repository.
141 Returns the source repository object for a given shared repository.
142 If repo is not a shared repository, return None.
142 If repo is not a shared repository, return None.
143 """
143 """
144 if repo.sharedpath == repo.path:
144 if repo.sharedpath == repo.path:
145 return None
145 return None
146
146
147 # the sharedpath always ends in the .hg; we want the path to the repo
147 # the sharedpath always ends in the .hg; we want the path to the repo
148 source = repo.vfs.split(repo.sharedpath)[0]
148 source = repo.vfs.split(repo.sharedpath)[0]
149 srcurl, branches = parseurl(source)
149 srcurl, branches = parseurl(source)
150 return repository(repo.ui, srcurl)
150 return repository(repo.ui, srcurl)
151
151
152 def getbkfile(orig, self, repo):
152 def getbkfile(orig, repo):
153 if _hassharedbookmarks(repo):
153 if _hassharedbookmarks(repo):
154 srcrepo = _getsrcrepo(repo)
154 srcrepo = _getsrcrepo(repo)
155 if srcrepo is not None:
155 if srcrepo is not None:
156 repo = srcrepo
156 repo = srcrepo
157 return orig(self, repo)
157 return orig(repo)
158
158
159 def recordchange(orig, self, tr):
159 def recordchange(orig, self, tr):
160 # Continue with write to local bookmarks file as usual
160 # Continue with write to local bookmarks file as usual
161 orig(self, tr)
161 orig(self, tr)
162
162
163 if _hassharedbookmarks(self._repo):
163 if _hassharedbookmarks(self._repo):
164 srcrepo = _getsrcrepo(self._repo)
164 srcrepo = _getsrcrepo(self._repo)
165 if srcrepo is not None:
165 if srcrepo is not None:
166 category = 'share-bookmarks'
166 category = 'share-bookmarks'
167 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
167 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
168
168
169 def writerepo(orig, self, repo):
169 def writerepo(orig, self, repo):
170 # First write local bookmarks file in case we ever unshare
170 # First write local bookmarks file in case we ever unshare
171 orig(self, repo)
171 orig(self, repo)
172
172
173 if _hassharedbookmarks(self._repo):
173 if _hassharedbookmarks(self._repo):
174 srcrepo = _getsrcrepo(self._repo)
174 srcrepo = _getsrcrepo(self._repo)
175 if srcrepo is not None:
175 if srcrepo is not None:
176 orig(self, srcrepo)
176 orig(self, srcrepo)
@@ -1,591 +1,592
1 # Mercurial bookmark support code
1 # Mercurial bookmark support code
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 bin,
15 bin,
16 hex,
16 hex,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 lock as lockmod,
20 lock as lockmod,
21 obsolete,
21 obsolete,
22 util,
22 util,
23 )
23 )
24
24
25 def _getbkfile(repo):
26 """Hook so that extensions that mess with the store can hook bm storage.
27
28 For core, this just handles wether we should see pending
29 bookmarks or the committed ones. Other extensions (like share)
30 may need to tweak this behavior further.
31 """
32 bkfile = None
33 if 'HG_PENDING' in os.environ:
34 try:
35 bkfile = repo.vfs('bookmarks.pending')
36 except IOError as inst:
37 if inst.errno != errno.ENOENT:
38 raise
39 if bkfile is None:
40 bkfile = repo.vfs('bookmarks')
41 return bkfile
42
43
25 class bmstore(dict):
44 class bmstore(dict):
26 """Storage for bookmarks.
45 """Storage for bookmarks.
27
46
28 This object should do all bookmark reads and writes, so that it's
47 This object should do all bookmark reads and writes, so that it's
29 fairly simple to replace the storage underlying bookmarks without
48 fairly simple to replace the storage underlying bookmarks without
30 having to clone the logic surrounding bookmarks.
49 having to clone the logic surrounding bookmarks.
31
50
32 This particular bmstore implementation stores bookmarks as
51 This particular bmstore implementation stores bookmarks as
33 {hash}\s{name}\n (the same format as localtags) in
52 {hash}\s{name}\n (the same format as localtags) in
34 .hg/bookmarks. The mapping is stored as {name: nodeid}.
53 .hg/bookmarks. The mapping is stored as {name: nodeid}.
35
54
36 This class does NOT handle the "active" bookmark state at this
55 This class does NOT handle the "active" bookmark state at this
37 time.
56 time.
38 """
57 """
39
58
40 def __init__(self, repo):
59 def __init__(self, repo):
41 dict.__init__(self)
60 dict.__init__(self)
42 self._repo = repo
61 self._repo = repo
43 try:
62 try:
44 bkfile = self.getbkfile(repo)
63 bkfile = _getbkfile(repo)
45 for line in bkfile:
64 for line in bkfile:
46 line = line.strip()
65 line = line.strip()
47 if not line:
66 if not line:
48 continue
67 continue
49 if ' ' not in line:
68 if ' ' not in line:
50 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
69 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
51 % line)
70 % line)
52 continue
71 continue
53 sha, refspec = line.split(' ', 1)
72 sha, refspec = line.split(' ', 1)
54 refspec = encoding.tolocal(refspec)
73 refspec = encoding.tolocal(refspec)
55 try:
74 try:
56 self[refspec] = repo.changelog.lookup(sha)
75 self[refspec] = repo.changelog.lookup(sha)
57 except LookupError:
76 except LookupError:
58 pass
77 pass
59 except IOError as inst:
78 except IOError as inst:
60 if inst.errno != errno.ENOENT:
79 if inst.errno != errno.ENOENT:
61 raise
80 raise
62
81
63 def getbkfile(self, repo):
64 """Hook so that extensions that mess with the store can hook bm storage.
65
66 For core, this just handles wether we should see pending
67 bookmarks or the committed ones. Other extensions (like share)
68 may need to tweak this behavior further.
69 """
70 bkfile = None
71 if 'HG_PENDING' in os.environ:
72 try:
73 bkfile = repo.vfs('bookmarks.pending')
74 except IOError as inst:
75 if inst.errno != errno.ENOENT:
76 raise
77 if bkfile is None:
78 bkfile = repo.vfs('bookmarks')
79 return bkfile
80
81 def recordchange(self, tr):
82 def recordchange(self, tr):
82 """record that bookmarks have been changed in a transaction
83 """record that bookmarks have been changed in a transaction
83
84
84 The transaction is then responsible for updating the file content."""
85 The transaction is then responsible for updating the file content."""
85 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
86 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
86 location='plain')
87 location='plain')
87 tr.hookargs['bookmark_moved'] = '1'
88 tr.hookargs['bookmark_moved'] = '1'
88
89
89 def write(self):
90 def write(self):
90 '''Write bookmarks
91 '''Write bookmarks
91
92
92 Write the given bookmark => hash dictionary to the .hg/bookmarks file
93 Write the given bookmark => hash dictionary to the .hg/bookmarks file
93 in a format equal to those of localtags.
94 in a format equal to those of localtags.
94
95
95 We also store a backup of the previous state in undo.bookmarks that
96 We also store a backup of the previous state in undo.bookmarks that
96 can be copied back on rollback.
97 can be copied back on rollback.
97 '''
98 '''
98 repo = self._repo
99 repo = self._repo
99 if (repo.ui.configbool('devel', 'all-warnings')
100 if (repo.ui.configbool('devel', 'all-warnings')
100 or repo.ui.configbool('devel', 'check-locks')):
101 or repo.ui.configbool('devel', 'check-locks')):
101 l = repo._wlockref and repo._wlockref()
102 l = repo._wlockref and repo._wlockref()
102 if l is None or not l.held:
103 if l is None or not l.held:
103 repo.ui.develwarn('bookmarks write with no wlock')
104 repo.ui.develwarn('bookmarks write with no wlock')
104
105
105 tr = repo.currenttransaction()
106 tr = repo.currenttransaction()
106 if tr:
107 if tr:
107 self.recordchange(tr)
108 self.recordchange(tr)
108 # invalidatevolatilesets() is omitted because this doesn't
109 # invalidatevolatilesets() is omitted because this doesn't
109 # write changes out actually
110 # write changes out actually
110 return
111 return
111
112
112 self._writerepo(repo)
113 self._writerepo(repo)
113 repo.invalidatevolatilesets()
114 repo.invalidatevolatilesets()
114
115
115 def _writerepo(self, repo):
116 def _writerepo(self, repo):
116 """Factored out for extensibility"""
117 """Factored out for extensibility"""
117 if repo._activebookmark not in self:
118 if repo._activebookmark not in self:
118 deactivate(repo)
119 deactivate(repo)
119
120
120 wlock = repo.wlock()
121 wlock = repo.wlock()
121 try:
122 try:
122
123
123 file = repo.vfs('bookmarks', 'w', atomictemp=True)
124 file = repo.vfs('bookmarks', 'w', atomictemp=True)
124 self._write(file)
125 self._write(file)
125 file.close()
126 file.close()
126
127
127 finally:
128 finally:
128 wlock.release()
129 wlock.release()
129
130
130 def _write(self, fp):
131 def _write(self, fp):
131 for name, node in self.iteritems():
132 for name, node in self.iteritems():
132 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
133 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
133
134
134 def readactive(repo):
135 def readactive(repo):
135 """
136 """
136 Get the active bookmark. We can have an active bookmark that updates
137 Get the active bookmark. We can have an active bookmark that updates
137 itself as we commit. This function returns the name of that bookmark.
138 itself as we commit. This function returns the name of that bookmark.
138 It is stored in .hg/bookmarks.current
139 It is stored in .hg/bookmarks.current
139 """
140 """
140 mark = None
141 mark = None
141 try:
142 try:
142 file = repo.vfs('bookmarks.current')
143 file = repo.vfs('bookmarks.current')
143 except IOError as inst:
144 except IOError as inst:
144 if inst.errno != errno.ENOENT:
145 if inst.errno != errno.ENOENT:
145 raise
146 raise
146 return None
147 return None
147 try:
148 try:
148 # No readline() in osutil.posixfile, reading everything is cheap
149 # No readline() in osutil.posixfile, reading everything is cheap
149 mark = encoding.tolocal((file.readlines() or [''])[0])
150 mark = encoding.tolocal((file.readlines() or [''])[0])
150 if mark == '' or mark not in repo._bookmarks:
151 if mark == '' or mark not in repo._bookmarks:
151 mark = None
152 mark = None
152 finally:
153 finally:
153 file.close()
154 file.close()
154 return mark
155 return mark
155
156
156 def activate(repo, mark):
157 def activate(repo, mark):
157 """
158 """
158 Set the given bookmark to be 'active', meaning that this bookmark will
159 Set the given bookmark to be 'active', meaning that this bookmark will
159 follow new commits that are made.
160 follow new commits that are made.
160 The name is recorded in .hg/bookmarks.current
161 The name is recorded in .hg/bookmarks.current
161 """
162 """
162 if mark not in repo._bookmarks:
163 if mark not in repo._bookmarks:
163 raise AssertionError('bookmark %s does not exist!' % mark)
164 raise AssertionError('bookmark %s does not exist!' % mark)
164
165
165 active = repo._activebookmark
166 active = repo._activebookmark
166 if active == mark:
167 if active == mark:
167 return
168 return
168
169
169 wlock = repo.wlock()
170 wlock = repo.wlock()
170 try:
171 try:
171 file = repo.vfs('bookmarks.current', 'w', atomictemp=True)
172 file = repo.vfs('bookmarks.current', 'w', atomictemp=True)
172 file.write(encoding.fromlocal(mark))
173 file.write(encoding.fromlocal(mark))
173 file.close()
174 file.close()
174 finally:
175 finally:
175 wlock.release()
176 wlock.release()
176 repo._activebookmark = mark
177 repo._activebookmark = mark
177
178
178 def deactivate(repo):
179 def deactivate(repo):
179 """
180 """
180 Unset the active bookmark in this repository.
181 Unset the active bookmark in this repository.
181 """
182 """
182 wlock = repo.wlock()
183 wlock = repo.wlock()
183 try:
184 try:
184 repo.vfs.unlink('bookmarks.current')
185 repo.vfs.unlink('bookmarks.current')
185 repo._activebookmark = None
186 repo._activebookmark = None
186 except OSError as inst:
187 except OSError as inst:
187 if inst.errno != errno.ENOENT:
188 if inst.errno != errno.ENOENT:
188 raise
189 raise
189 finally:
190 finally:
190 wlock.release()
191 wlock.release()
191
192
192 def isactivewdirparent(repo):
193 def isactivewdirparent(repo):
193 """
194 """
194 Tell whether the 'active' bookmark (the one that follows new commits)
195 Tell whether the 'active' bookmark (the one that follows new commits)
195 points to one of the parents of the current working directory (wdir).
196 points to one of the parents of the current working directory (wdir).
196
197
197 While this is normally the case, it can on occasion be false; for example,
198 While this is normally the case, it can on occasion be false; for example,
198 immediately after a pull, the active bookmark can be moved to point
199 immediately after a pull, the active bookmark can be moved to point
199 to a place different than the wdir. This is solved by running `hg update`.
200 to a place different than the wdir. This is solved by running `hg update`.
200 """
201 """
201 mark = repo._activebookmark
202 mark = repo._activebookmark
202 marks = repo._bookmarks
203 marks = repo._bookmarks
203 parents = [p.node() for p in repo[None].parents()]
204 parents = [p.node() for p in repo[None].parents()]
204 return (mark in marks and marks[mark] in parents)
205 return (mark in marks and marks[mark] in parents)
205
206
206 def deletedivergent(repo, deletefrom, bm):
207 def deletedivergent(repo, deletefrom, bm):
207 '''Delete divergent versions of bm on nodes in deletefrom.
208 '''Delete divergent versions of bm on nodes in deletefrom.
208
209
209 Return True if at least one bookmark was deleted, False otherwise.'''
210 Return True if at least one bookmark was deleted, False otherwise.'''
210 deleted = False
211 deleted = False
211 marks = repo._bookmarks
212 marks = repo._bookmarks
212 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
213 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
213 for mark in divergent:
214 for mark in divergent:
214 if mark == '@' or '@' not in mark:
215 if mark == '@' or '@' not in mark:
215 # can't be divergent by definition
216 # can't be divergent by definition
216 continue
217 continue
217 if mark and marks[mark] in deletefrom:
218 if mark and marks[mark] in deletefrom:
218 if mark != bm:
219 if mark != bm:
219 del marks[mark]
220 del marks[mark]
220 deleted = True
221 deleted = True
221 return deleted
222 return deleted
222
223
223 def calculateupdate(ui, repo, checkout):
224 def calculateupdate(ui, repo, checkout):
224 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
225 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
225 check out and where to move the active bookmark from, if needed.'''
226 check out and where to move the active bookmark from, if needed.'''
226 movemarkfrom = None
227 movemarkfrom = None
227 if checkout is None:
228 if checkout is None:
228 activemark = repo._activebookmark
229 activemark = repo._activebookmark
229 if isactivewdirparent(repo):
230 if isactivewdirparent(repo):
230 movemarkfrom = repo['.'].node()
231 movemarkfrom = repo['.'].node()
231 elif activemark:
232 elif activemark:
232 ui.status(_("updating to active bookmark %s\n") % activemark)
233 ui.status(_("updating to active bookmark %s\n") % activemark)
233 checkout = activemark
234 checkout = activemark
234 return (checkout, movemarkfrom)
235 return (checkout, movemarkfrom)
235
236
236 def update(repo, parents, node):
237 def update(repo, parents, node):
237 deletefrom = parents
238 deletefrom = parents
238 marks = repo._bookmarks
239 marks = repo._bookmarks
239 update = False
240 update = False
240 active = repo._activebookmark
241 active = repo._activebookmark
241 if not active:
242 if not active:
242 return False
243 return False
243
244
244 if marks[active] in parents:
245 if marks[active] in parents:
245 new = repo[node]
246 new = repo[node]
246 divs = [repo[b] for b in marks
247 divs = [repo[b] for b in marks
247 if b.split('@', 1)[0] == active.split('@', 1)[0]]
248 if b.split('@', 1)[0] == active.split('@', 1)[0]]
248 anc = repo.changelog.ancestors([new.rev()])
249 anc = repo.changelog.ancestors([new.rev()])
249 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
250 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
250 if validdest(repo, repo[marks[active]], new):
251 if validdest(repo, repo[marks[active]], new):
251 marks[active] = new.node()
252 marks[active] = new.node()
252 update = True
253 update = True
253
254
254 if deletedivergent(repo, deletefrom, active):
255 if deletedivergent(repo, deletefrom, active):
255 update = True
256 update = True
256
257
257 if update:
258 if update:
258 lock = tr = None
259 lock = tr = None
259 try:
260 try:
260 lock = repo.lock()
261 lock = repo.lock()
261 tr = repo.transaction('bookmark')
262 tr = repo.transaction('bookmark')
262 marks.recordchange(tr)
263 marks.recordchange(tr)
263 tr.close()
264 tr.close()
264 finally:
265 finally:
265 lockmod.release(tr, lock)
266 lockmod.release(tr, lock)
266 return update
267 return update
267
268
268 def listbookmarks(repo):
269 def listbookmarks(repo):
269 # We may try to list bookmarks on a repo type that does not
270 # We may try to list bookmarks on a repo type that does not
270 # support it (e.g., statichttprepository).
271 # support it (e.g., statichttprepository).
271 marks = getattr(repo, '_bookmarks', {})
272 marks = getattr(repo, '_bookmarks', {})
272
273
273 d = {}
274 d = {}
274 hasnode = repo.changelog.hasnode
275 hasnode = repo.changelog.hasnode
275 for k, v in marks.iteritems():
276 for k, v in marks.iteritems():
276 # don't expose local divergent bookmarks
277 # don't expose local divergent bookmarks
277 if hasnode(v) and ('@' not in k or k.endswith('@')):
278 if hasnode(v) and ('@' not in k or k.endswith('@')):
278 d[k] = hex(v)
279 d[k] = hex(v)
279 return d
280 return d
280
281
281 def pushbookmark(repo, key, old, new):
282 def pushbookmark(repo, key, old, new):
282 w = l = tr = None
283 w = l = tr = None
283 try:
284 try:
284 w = repo.wlock()
285 w = repo.wlock()
285 l = repo.lock()
286 l = repo.lock()
286 tr = repo.transaction('bookmarks')
287 tr = repo.transaction('bookmarks')
287 marks = repo._bookmarks
288 marks = repo._bookmarks
288 existing = hex(marks.get(key, ''))
289 existing = hex(marks.get(key, ''))
289 if existing != old and existing != new:
290 if existing != old and existing != new:
290 return False
291 return False
291 if new == '':
292 if new == '':
292 del marks[key]
293 del marks[key]
293 else:
294 else:
294 if new not in repo:
295 if new not in repo:
295 return False
296 return False
296 marks[key] = repo[new].node()
297 marks[key] = repo[new].node()
297 marks.recordchange(tr)
298 marks.recordchange(tr)
298 tr.close()
299 tr.close()
299 return True
300 return True
300 finally:
301 finally:
301 lockmod.release(tr, l, w)
302 lockmod.release(tr, l, w)
302
303
303 def compare(repo, srcmarks, dstmarks,
304 def compare(repo, srcmarks, dstmarks,
304 srchex=None, dsthex=None, targets=None):
305 srchex=None, dsthex=None, targets=None):
305 '''Compare bookmarks between srcmarks and dstmarks
306 '''Compare bookmarks between srcmarks and dstmarks
306
307
307 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
308 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
308 differ, invalid)", each are list of bookmarks below:
309 differ, invalid)", each are list of bookmarks below:
309
310
310 :addsrc: added on src side (removed on dst side, perhaps)
311 :addsrc: added on src side (removed on dst side, perhaps)
311 :adddst: added on dst side (removed on src side, perhaps)
312 :adddst: added on dst side (removed on src side, perhaps)
312 :advsrc: advanced on src side
313 :advsrc: advanced on src side
313 :advdst: advanced on dst side
314 :advdst: advanced on dst side
314 :diverge: diverge
315 :diverge: diverge
315 :differ: changed, but changeset referred on src is unknown on dst
316 :differ: changed, but changeset referred on src is unknown on dst
316 :invalid: unknown on both side
317 :invalid: unknown on both side
317 :same: same on both side
318 :same: same on both side
318
319
319 Each elements of lists in result tuple is tuple "(bookmark name,
320 Each elements of lists in result tuple is tuple "(bookmark name,
320 changeset ID on source side, changeset ID on destination
321 changeset ID on source side, changeset ID on destination
321 side)". Each changeset IDs are 40 hexadecimal digit string or
322 side)". Each changeset IDs are 40 hexadecimal digit string or
322 None.
323 None.
323
324
324 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
325 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
325 "invalid" list may be unknown for repo.
326 "invalid" list may be unknown for repo.
326
327
327 This function expects that "srcmarks" and "dstmarks" return
328 This function expects that "srcmarks" and "dstmarks" return
328 changeset ID in 40 hexadecimal digit string for specified
329 changeset ID in 40 hexadecimal digit string for specified
329 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
330 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
330 binary value), "srchex" or "dsthex" should be specified to convert
331 binary value), "srchex" or "dsthex" should be specified to convert
331 into such form.
332 into such form.
332
333
333 If "targets" is specified, only bookmarks listed in it are
334 If "targets" is specified, only bookmarks listed in it are
334 examined.
335 examined.
335 '''
336 '''
336 if not srchex:
337 if not srchex:
337 srchex = lambda x: x
338 srchex = lambda x: x
338 if not dsthex:
339 if not dsthex:
339 dsthex = lambda x: x
340 dsthex = lambda x: x
340
341
341 if targets:
342 if targets:
342 bset = set(targets)
343 bset = set(targets)
343 else:
344 else:
344 srcmarkset = set(srcmarks)
345 srcmarkset = set(srcmarks)
345 dstmarkset = set(dstmarks)
346 dstmarkset = set(dstmarks)
346 bset = srcmarkset | dstmarkset
347 bset = srcmarkset | dstmarkset
347
348
348 results = ([], [], [], [], [], [], [], [])
349 results = ([], [], [], [], [], [], [], [])
349 addsrc = results[0].append
350 addsrc = results[0].append
350 adddst = results[1].append
351 adddst = results[1].append
351 advsrc = results[2].append
352 advsrc = results[2].append
352 advdst = results[3].append
353 advdst = results[3].append
353 diverge = results[4].append
354 diverge = results[4].append
354 differ = results[5].append
355 differ = results[5].append
355 invalid = results[6].append
356 invalid = results[6].append
356 same = results[7].append
357 same = results[7].append
357
358
358 for b in sorted(bset):
359 for b in sorted(bset):
359 if b not in srcmarks:
360 if b not in srcmarks:
360 if b in dstmarks:
361 if b in dstmarks:
361 adddst((b, None, dsthex(dstmarks[b])))
362 adddst((b, None, dsthex(dstmarks[b])))
362 else:
363 else:
363 invalid((b, None, None))
364 invalid((b, None, None))
364 elif b not in dstmarks:
365 elif b not in dstmarks:
365 addsrc((b, srchex(srcmarks[b]), None))
366 addsrc((b, srchex(srcmarks[b]), None))
366 else:
367 else:
367 scid = srchex(srcmarks[b])
368 scid = srchex(srcmarks[b])
368 dcid = dsthex(dstmarks[b])
369 dcid = dsthex(dstmarks[b])
369 if scid == dcid:
370 if scid == dcid:
370 same((b, scid, dcid))
371 same((b, scid, dcid))
371 elif scid in repo and dcid in repo:
372 elif scid in repo and dcid in repo:
372 sctx = repo[scid]
373 sctx = repo[scid]
373 dctx = repo[dcid]
374 dctx = repo[dcid]
374 if sctx.rev() < dctx.rev():
375 if sctx.rev() < dctx.rev():
375 if validdest(repo, sctx, dctx):
376 if validdest(repo, sctx, dctx):
376 advdst((b, scid, dcid))
377 advdst((b, scid, dcid))
377 else:
378 else:
378 diverge((b, scid, dcid))
379 diverge((b, scid, dcid))
379 else:
380 else:
380 if validdest(repo, dctx, sctx):
381 if validdest(repo, dctx, sctx):
381 advsrc((b, scid, dcid))
382 advsrc((b, scid, dcid))
382 else:
383 else:
383 diverge((b, scid, dcid))
384 diverge((b, scid, dcid))
384 else:
385 else:
385 # it is too expensive to examine in detail, in this case
386 # it is too expensive to examine in detail, in this case
386 differ((b, scid, dcid))
387 differ((b, scid, dcid))
387
388
388 return results
389 return results
389
390
390 def _diverge(ui, b, path, localmarks, remotenode):
391 def _diverge(ui, b, path, localmarks, remotenode):
391 '''Return appropriate diverged bookmark for specified ``path``
392 '''Return appropriate diverged bookmark for specified ``path``
392
393
393 This returns None, if it is failed to assign any divergent
394 This returns None, if it is failed to assign any divergent
394 bookmark name.
395 bookmark name.
395
396
396 This reuses already existing one with "@number" suffix, if it
397 This reuses already existing one with "@number" suffix, if it
397 refers ``remotenode``.
398 refers ``remotenode``.
398 '''
399 '''
399 if b == '@':
400 if b == '@':
400 b = ''
401 b = ''
401 # try to use an @pathalias suffix
402 # try to use an @pathalias suffix
402 # if an @pathalias already exists, we overwrite (update) it
403 # if an @pathalias already exists, we overwrite (update) it
403 if path.startswith("file:"):
404 if path.startswith("file:"):
404 path = util.url(path).path
405 path = util.url(path).path
405 for p, u in ui.configitems("paths"):
406 for p, u in ui.configitems("paths"):
406 if u.startswith("file:"):
407 if u.startswith("file:"):
407 u = util.url(u).path
408 u = util.url(u).path
408 if path == u:
409 if path == u:
409 return '%s@%s' % (b, p)
410 return '%s@%s' % (b, p)
410
411
411 # assign a unique "@number" suffix newly
412 # assign a unique "@number" suffix newly
412 for x in range(1, 100):
413 for x in range(1, 100):
413 n = '%s@%d' % (b, x)
414 n = '%s@%d' % (b, x)
414 if n not in localmarks or localmarks[n] == remotenode:
415 if n not in localmarks or localmarks[n] == remotenode:
415 return n
416 return n
416
417
417 return None
418 return None
418
419
419 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
420 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
420 ui.debug("checking for updated bookmarks\n")
421 ui.debug("checking for updated bookmarks\n")
421 localmarks = repo._bookmarks
422 localmarks = repo._bookmarks
422 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
423 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
423 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
424 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
424
425
425 status = ui.status
426 status = ui.status
426 warn = ui.warn
427 warn = ui.warn
427 if ui.configbool('ui', 'quietbookmarkmove', False):
428 if ui.configbool('ui', 'quietbookmarkmove', False):
428 status = warn = ui.debug
429 status = warn = ui.debug
429
430
430 explicit = set(explicit)
431 explicit = set(explicit)
431 changed = []
432 changed = []
432 for b, scid, dcid in addsrc:
433 for b, scid, dcid in addsrc:
433 if scid in repo: # add remote bookmarks for changes we already have
434 if scid in repo: # add remote bookmarks for changes we already have
434 changed.append((b, bin(scid), status,
435 changed.append((b, bin(scid), status,
435 _("adding remote bookmark %s\n") % (b)))
436 _("adding remote bookmark %s\n") % (b)))
436 elif b in explicit:
437 elif b in explicit:
437 explicit.remove(b)
438 explicit.remove(b)
438 ui.warn(_("remote bookmark %s points to locally missing %s\n")
439 ui.warn(_("remote bookmark %s points to locally missing %s\n")
439 % (b, scid[:12]))
440 % (b, scid[:12]))
440
441
441 for b, scid, dcid in advsrc:
442 for b, scid, dcid in advsrc:
442 changed.append((b, bin(scid), status,
443 changed.append((b, bin(scid), status,
443 _("updating bookmark %s\n") % (b)))
444 _("updating bookmark %s\n") % (b)))
444 # remove normal movement from explicit set
445 # remove normal movement from explicit set
445 explicit.difference_update(d[0] for d in changed)
446 explicit.difference_update(d[0] for d in changed)
446
447
447 for b, scid, dcid in diverge:
448 for b, scid, dcid in diverge:
448 if b in explicit:
449 if b in explicit:
449 explicit.discard(b)
450 explicit.discard(b)
450 changed.append((b, bin(scid), status,
451 changed.append((b, bin(scid), status,
451 _("importing bookmark %s\n") % (b)))
452 _("importing bookmark %s\n") % (b)))
452 else:
453 else:
453 snode = bin(scid)
454 snode = bin(scid)
454 db = _diverge(ui, b, path, localmarks, snode)
455 db = _diverge(ui, b, path, localmarks, snode)
455 if db:
456 if db:
456 changed.append((db, snode, warn,
457 changed.append((db, snode, warn,
457 _("divergent bookmark %s stored as %s\n") %
458 _("divergent bookmark %s stored as %s\n") %
458 (b, db)))
459 (b, db)))
459 else:
460 else:
460 warn(_("warning: failed to assign numbered name "
461 warn(_("warning: failed to assign numbered name "
461 "to divergent bookmark %s\n") % (b))
462 "to divergent bookmark %s\n") % (b))
462 for b, scid, dcid in adddst + advdst:
463 for b, scid, dcid in adddst + advdst:
463 if b in explicit:
464 if b in explicit:
464 explicit.discard(b)
465 explicit.discard(b)
465 changed.append((b, bin(scid), status,
466 changed.append((b, bin(scid), status,
466 _("importing bookmark %s\n") % (b)))
467 _("importing bookmark %s\n") % (b)))
467 for b, scid, dcid in differ:
468 for b, scid, dcid in differ:
468 if b in explicit:
469 if b in explicit:
469 explicit.remove(b)
470 explicit.remove(b)
470 ui.warn(_("remote bookmark %s points to locally missing %s\n")
471 ui.warn(_("remote bookmark %s points to locally missing %s\n")
471 % (b, scid[:12]))
472 % (b, scid[:12]))
472
473
473 if changed:
474 if changed:
474 tr = trfunc()
475 tr = trfunc()
475 for b, node, writer, msg in sorted(changed):
476 for b, node, writer, msg in sorted(changed):
476 localmarks[b] = node
477 localmarks[b] = node
477 writer(msg)
478 writer(msg)
478 localmarks.recordchange(tr)
479 localmarks.recordchange(tr)
479
480
480 def incoming(ui, repo, other):
481 def incoming(ui, repo, other):
481 '''Show bookmarks incoming from other to repo
482 '''Show bookmarks incoming from other to repo
482 '''
483 '''
483 ui.status(_("searching for changed bookmarks\n"))
484 ui.status(_("searching for changed bookmarks\n"))
484
485
485 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
486 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
486 dsthex=hex)
487 dsthex=hex)
487 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
488 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
488
489
489 incomings = []
490 incomings = []
490 if ui.debugflag:
491 if ui.debugflag:
491 getid = lambda id: id
492 getid = lambda id: id
492 else:
493 else:
493 getid = lambda id: id[:12]
494 getid = lambda id: id[:12]
494 if ui.verbose:
495 if ui.verbose:
495 def add(b, id, st):
496 def add(b, id, st):
496 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
497 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
497 else:
498 else:
498 def add(b, id, st):
499 def add(b, id, st):
499 incomings.append(" %-25s %s\n" % (b, getid(id)))
500 incomings.append(" %-25s %s\n" % (b, getid(id)))
500 for b, scid, dcid in addsrc:
501 for b, scid, dcid in addsrc:
501 # i18n: "added" refers to a bookmark
502 # i18n: "added" refers to a bookmark
502 add(b, scid, _('added'))
503 add(b, scid, _('added'))
503 for b, scid, dcid in advsrc:
504 for b, scid, dcid in advsrc:
504 # i18n: "advanced" refers to a bookmark
505 # i18n: "advanced" refers to a bookmark
505 add(b, scid, _('advanced'))
506 add(b, scid, _('advanced'))
506 for b, scid, dcid in diverge:
507 for b, scid, dcid in diverge:
507 # i18n: "diverged" refers to a bookmark
508 # i18n: "diverged" refers to a bookmark
508 add(b, scid, _('diverged'))
509 add(b, scid, _('diverged'))
509 for b, scid, dcid in differ:
510 for b, scid, dcid in differ:
510 # i18n: "changed" refers to a bookmark
511 # i18n: "changed" refers to a bookmark
511 add(b, scid, _('changed'))
512 add(b, scid, _('changed'))
512
513
513 if not incomings:
514 if not incomings:
514 ui.status(_("no changed bookmarks found\n"))
515 ui.status(_("no changed bookmarks found\n"))
515 return 1
516 return 1
516
517
517 for s in sorted(incomings):
518 for s in sorted(incomings):
518 ui.write(s)
519 ui.write(s)
519
520
520 return 0
521 return 0
521
522
522 def outgoing(ui, repo, other):
523 def outgoing(ui, repo, other):
523 '''Show bookmarks outgoing from repo to other
524 '''Show bookmarks outgoing from repo to other
524 '''
525 '''
525 ui.status(_("searching for changed bookmarks\n"))
526 ui.status(_("searching for changed bookmarks\n"))
526
527
527 r = compare(repo, repo._bookmarks, other.listkeys('bookmarks'),
528 r = compare(repo, repo._bookmarks, other.listkeys('bookmarks'),
528 srchex=hex)
529 srchex=hex)
529 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
530 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
530
531
531 outgoings = []
532 outgoings = []
532 if ui.debugflag:
533 if ui.debugflag:
533 getid = lambda id: id
534 getid = lambda id: id
534 else:
535 else:
535 getid = lambda id: id[:12]
536 getid = lambda id: id[:12]
536 if ui.verbose:
537 if ui.verbose:
537 def add(b, id, st):
538 def add(b, id, st):
538 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
539 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
539 else:
540 else:
540 def add(b, id, st):
541 def add(b, id, st):
541 outgoings.append(" %-25s %s\n" % (b, getid(id)))
542 outgoings.append(" %-25s %s\n" % (b, getid(id)))
542 for b, scid, dcid in addsrc:
543 for b, scid, dcid in addsrc:
543 # i18n: "added refers to a bookmark
544 # i18n: "added refers to a bookmark
544 add(b, scid, _('added'))
545 add(b, scid, _('added'))
545 for b, scid, dcid in adddst:
546 for b, scid, dcid in adddst:
546 # i18n: "deleted" refers to a bookmark
547 # i18n: "deleted" refers to a bookmark
547 add(b, ' ' * 40, _('deleted'))
548 add(b, ' ' * 40, _('deleted'))
548 for b, scid, dcid in advsrc:
549 for b, scid, dcid in advsrc:
549 # i18n: "advanced" refers to a bookmark
550 # i18n: "advanced" refers to a bookmark
550 add(b, scid, _('advanced'))
551 add(b, scid, _('advanced'))
551 for b, scid, dcid in diverge:
552 for b, scid, dcid in diverge:
552 # i18n: "diverged" refers to a bookmark
553 # i18n: "diverged" refers to a bookmark
553 add(b, scid, _('diverged'))
554 add(b, scid, _('diverged'))
554 for b, scid, dcid in differ:
555 for b, scid, dcid in differ:
555 # i18n: "changed" refers to a bookmark
556 # i18n: "changed" refers to a bookmark
556 add(b, scid, _('changed'))
557 add(b, scid, _('changed'))
557
558
558 if not outgoings:
559 if not outgoings:
559 ui.status(_("no changed bookmarks found\n"))
560 ui.status(_("no changed bookmarks found\n"))
560 return 1
561 return 1
561
562
562 for s in sorted(outgoings):
563 for s in sorted(outgoings):
563 ui.write(s)
564 ui.write(s)
564
565
565 return 0
566 return 0
566
567
567 def summary(repo, other):
568 def summary(repo, other):
568 '''Compare bookmarks between repo and other for "hg summary" output
569 '''Compare bookmarks between repo and other for "hg summary" output
569
570
570 This returns "(# of incoming, # of outgoing)" tuple.
571 This returns "(# of incoming, # of outgoing)" tuple.
571 '''
572 '''
572 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
573 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
573 dsthex=hex)
574 dsthex=hex)
574 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
575 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
575 return (len(addsrc), len(adddst))
576 return (len(addsrc), len(adddst))
576
577
577 def validdest(repo, old, new):
578 def validdest(repo, old, new):
578 """Is the new bookmark destination a valid update from the old one"""
579 """Is the new bookmark destination a valid update from the old one"""
579 repo = repo.unfiltered()
580 repo = repo.unfiltered()
580 if old == new:
581 if old == new:
581 # Old == new -> nothing to update.
582 # Old == new -> nothing to update.
582 return False
583 return False
583 elif not old:
584 elif not old:
584 # old is nullrev, anything is valid.
585 # old is nullrev, anything is valid.
585 # (new != nullrev has been excluded by the previous check)
586 # (new != nullrev has been excluded by the previous check)
586 return True
587 return True
587 elif repo.obsstore:
588 elif repo.obsstore:
588 return new.node() in obsolete.foreground(repo, [old.node()])
589 return new.node() in obsolete.foreground(repo, [old.node()])
589 else:
590 else:
590 # still an independent clause as it is lazier (and therefore faster)
591 # still an independent clause as it is lazier (and therefore faster)
591 return old.descendant(new)
592 return old.descendant(new)
General Comments 0
You need to be logged in to leave comments. Login now