##// END OF EJS Templates
bookmarks: Avoid unconditional forwarding of bookmarks for the null revision...
Joel Rosdahl -
r7256:df800e00 default
parent child Browse files
Show More
@@ -1,227 +1,229 b''
1 # Mercurial extension to provide the 'hg bookmark' command
1 # Mercurial extension to provide the 'hg bookmark' command
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''mercurial bookmarks
8 '''mercurial bookmarks
9
9
10 Mercurial bookmarks are local moveable pointers to changesets. Every
10 Mercurial bookmarks are local moveable pointers to changesets. Every
11 bookmark points to a changeset identified by its hash. If you commit a
11 bookmark points to a changeset identified by its hash. If you commit a
12 changeset that is based on a changeset that has a bookmark on it, the
12 changeset that is based on a changeset that has a bookmark on it, the
13 bookmark is forwarded to the new changeset.
13 bookmark is forwarded to the new changeset.
14
14
15 It is possible to use bookmark names in every revision lookup (e.g. hg
15 It is possible to use bookmark names in every revision lookup (e.g. hg
16 merge, hg update).
16 merge, hg update).
17 '''
17 '''
18
18
19 from mercurial.commands import templateopts, hex, short
19 from mercurial.commands import templateopts, hex, short
20 from mercurial.i18n import _
20 from mercurial.i18n import _
21 from mercurial import cmdutil, util, commands, changelog
21 from mercurial import cmdutil, util, commands, changelog
22 from mercurial.node import nullrev
22 from mercurial.node import nullid, nullrev
23 from mercurial.repo import RepoError
23 from mercurial.repo import RepoError
24 import mercurial, mercurial.localrepo, mercurial.repair, os
24 import mercurial, mercurial.localrepo, mercurial.repair, os
25
25
26 def parse(repo):
26 def parse(repo):
27 '''Parse .hg/bookmarks file and return a dictionary
27 '''Parse .hg/bookmarks file and return a dictionary
28
28
29 Bookmarks are stored as {HASH}\s{NAME}\n (localtags format) values
29 Bookmarks are stored as {HASH}\s{NAME}\n (localtags format) values
30 in the .hg/bookmarks file. They are read by the parse() method and
30 in the .hg/bookmarks file. They are read by the parse() method and
31 returned as a dictionary with name => hash values.
31 returned as a dictionary with name => hash values.
32
32
33 The parsed dictionary is cached until a write() operation is done.
33 The parsed dictionary is cached until a write() operation is done.
34 '''
34 '''
35 try:
35 try:
36 if repo._bookmarks:
36 if repo._bookmarks:
37 return repo._bookmarks
37 return repo._bookmarks
38 repo._bookmarks = {}
38 repo._bookmarks = {}
39 for line in repo.opener('bookmarks'):
39 for line in repo.opener('bookmarks'):
40 sha, refspec = line.strip().split(' ', 1)
40 sha, refspec = line.strip().split(' ', 1)
41 repo._bookmarks[refspec] = repo.lookup(sha)
41 repo._bookmarks[refspec] = repo.lookup(sha)
42 except:
42 except:
43 pass
43 pass
44 return repo._bookmarks
44 return repo._bookmarks
45
45
46 def write(repo, refs):
46 def write(repo, refs):
47 '''Write bookmarks
47 '''Write bookmarks
48
48
49 Write the given bookmark => hash dictionary to the .hg/bookmarks file
49 Write the given bookmark => hash dictionary to the .hg/bookmarks file
50 in a format equal to those of localtags.
50 in a format equal to those of localtags.
51
51
52 We also store a backup of the previous state in undo.bookmarks that
52 We also store a backup of the previous state in undo.bookmarks that
53 can be copied back on rollback.
53 can be copied back on rollback.
54 '''
54 '''
55 if os.path.exists(repo.join('bookmarks')):
55 if os.path.exists(repo.join('bookmarks')):
56 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
56 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
57 file = repo.opener('bookmarks', 'w+')
57 file = repo.opener('bookmarks', 'w+')
58 for refspec, node in refs.items():
58 for refspec, node in refs.items():
59 file.write("%s %s\n" % (hex(node), refspec))
59 file.write("%s %s\n" % (hex(node), refspec))
60 file.close()
60 file.close()
61
61
62 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
62 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
63 '''mercurial bookmarks
63 '''mercurial bookmarks
64
64
65 Bookmarks are pointers to certain commits that move when
65 Bookmarks are pointers to certain commits that move when
66 commiting. Bookmarks are local. They can be renamed, copied and
66 commiting. Bookmarks are local. They can be renamed, copied and
67 deleted. It is possible to use bookmark names in 'hg merge' and 'hg
67 deleted. It is possible to use bookmark names in 'hg merge' and 'hg
68 update' to update to a given bookmark.
68 update' to update to a given bookmark.
69
69
70 You can use 'hg bookmark [NAME]' to set a bookmark on the current tip
70 You can use 'hg bookmark [NAME]' to set a bookmark on the current tip
71 with the given name. If you specify a second [NAME] the bookmark is
71 with the given name. If you specify a second [NAME] the bookmark is
72 set to the bookmark that has that name. You can also pass revision
72 set to the bookmark that has that name. You can also pass revision
73 identifiers to set bookmarks too.
73 identifiers to set bookmarks too.
74 '''
74 '''
75 hexfn = ui.debugflag and hex or short
75 hexfn = ui.debugflag and hex or short
76 marks = parse(repo)
76 marks = parse(repo)
77 cur = repo.changectx('.').node()
77 cur = repo.changectx('.').node()
78
78
79 if rename:
79 if rename:
80 if rename not in marks:
80 if rename not in marks:
81 raise util.Abort(_("a bookmark of this name does not exist"))
81 raise util.Abort(_("a bookmark of this name does not exist"))
82 if mark in marks and not force:
82 if mark in marks and not force:
83 raise util.Abort(_("a bookmark of the same name already exists"))
83 raise util.Abort(_("a bookmark of the same name already exists"))
84 if mark is None:
84 if mark is None:
85 raise util.Abort(_("new bookmark name required"))
85 raise util.Abort(_("new bookmark name required"))
86 marks[mark] = marks[rename]
86 marks[mark] = marks[rename]
87 del marks[rename]
87 del marks[rename]
88 write(repo, marks)
88 write(repo, marks)
89 return
89 return
90
90
91 if delete:
91 if delete:
92 if mark == None:
92 if mark == None:
93 raise util.Abort(_("bookmark name required"))
93 raise util.Abort(_("bookmark name required"))
94 if mark not in marks:
94 if mark not in marks:
95 raise util.Abort(_("a bookmark of this name does not exist"))
95 raise util.Abort(_("a bookmark of this name does not exist"))
96 del marks[mark]
96 del marks[mark]
97 write(repo, marks)
97 write(repo, marks)
98 return
98 return
99
99
100 if mark != None:
100 if mark != None:
101 if mark.strip().count("\n") > 0:
101 if mark.strip().count("\n") > 0:
102 raise Exception("bookmark cannot contain newlines")
102 raise Exception("bookmark cannot contain newlines")
103 if mark in marks and not force:
103 if mark in marks and not force:
104 raise util.Abort(_("a bookmark of the same name already exists"))
104 raise util.Abort(_("a bookmark of the same name already exists"))
105 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
105 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
106 and not force):
106 and not force):
107 raise util.Abort(
107 raise util.Abort(
108 _("a bookmark cannot have the name of an existing branch"))
108 _("a bookmark cannot have the name of an existing branch"))
109 if rev:
109 if rev:
110 marks[mark] = repo.lookup(rev)
110 marks[mark] = repo.lookup(rev)
111 else:
111 else:
112 marks[mark] = repo.changectx('.').node()
112 marks[mark] = repo.changectx('.').node()
113 write(repo, marks)
113 write(repo, marks)
114 return
114 return
115
115
116 if mark == None:
116 if mark == None:
117 if len(marks) == 0:
117 if len(marks) == 0:
118 ui.status("no bookmarks set\n")
118 ui.status("no bookmarks set\n")
119 else:
119 else:
120 for bmark, n in marks.iteritems():
120 for bmark, n in marks.iteritems():
121 prefix = (n == cur) and '*' or ' '
121 prefix = (n == cur) and '*' or ' '
122 ui.write(" %s %-25s %d:%s\n" % (
122 ui.write(" %s %-25s %d:%s\n" % (
123 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
123 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
124 return
124 return
125
125
126 def _revstostrip(changelog, node):
126 def _revstostrip(changelog, node):
127 srev = changelog.rev(node)
127 srev = changelog.rev(node)
128 tostrip = [srev]
128 tostrip = [srev]
129 saveheads = []
129 saveheads = []
130 for r in xrange(srev, changelog.rev(changelog.tip()) + 1):
130 for r in xrange(srev, changelog.rev(changelog.tip()) + 1):
131 parents = changelog.parentrevs(r)
131 parents = changelog.parentrevs(r)
132 if parents[0] in tostrip or parents[1] in tostrip:
132 if parents[0] in tostrip or parents[1] in tostrip:
133 tostrip.append(r)
133 tostrip.append(r)
134 if parents[1] != nullrev:
134 if parents[1] != nullrev:
135 for p in parents:
135 for p in parents:
136 if p not in tostrip and p > striprev:
136 if p not in tostrip and p > striprev:
137 saveheads.append(p)
137 saveheads.append(p)
138 return [r for r in tostrip if r not in saveheads]
138 return [r for r in tostrip if r not in saveheads]
139
139
140 def strip(ui, repo, node, backup="all"):
140 def strip(ui, repo, node, backup="all"):
141 """Strip bookmarks if revisions are stripped using
141 """Strip bookmarks if revisions are stripped using
142 the mercurial.strip method. This usually happens during
142 the mercurial.strip method. This usually happens during
143 qpush and qpop"""
143 qpush and qpop"""
144 revisions = _revstostrip(repo.changelog, node)
144 revisions = _revstostrip(repo.changelog, node)
145 marks = parse(repo)
145 marks = parse(repo)
146 update = []
146 update = []
147 for mark, n in marks.items():
147 for mark, n in marks.items():
148 if repo.changelog.rev(n) in revisions:
148 if repo.changelog.rev(n) in revisions:
149 update.append(mark)
149 update.append(mark)
150 result = oldstrip(ui, repo, node, backup)
150 result = oldstrip(ui, repo, node, backup)
151 if len(update) > 0:
151 if len(update) > 0:
152 for m in update:
152 for m in update:
153 marks[m] = repo.changectx('.').node()
153 marks[m] = repo.changectx('.').node()
154 write(repo, marks)
154 write(repo, marks)
155
155
156 oldstrip = mercurial.repair.strip
156 oldstrip = mercurial.repair.strip
157 mercurial.repair.strip = strip
157 mercurial.repair.strip = strip
158
158
159 def reposetup(ui, repo):
159 def reposetup(ui, repo):
160 if not isinstance(repo, mercurial.localrepo.localrepository):
160 if not isinstance(repo, mercurial.localrepo.localrepository):
161 return
161 return
162
162
163 # init a bookmark cache as otherwise we would get a infinite reading
163 # init a bookmark cache as otherwise we would get a infinite reading
164 # in lookup()
164 # in lookup()
165 repo._bookmarks = None
165 repo._bookmarks = None
166
166
167 class bookmark_repo(repo.__class__):
167 class bookmark_repo(repo.__class__):
168 def rollback(self):
168 def rollback(self):
169 if os.path.exists(self.join('undo.bookmarks')):
169 if os.path.exists(self.join('undo.bookmarks')):
170 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
170 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
171 return super(bookmark_repo, self).rollback()
171 return super(bookmark_repo, self).rollback()
172
172
173 def lookup(self, key):
173 def lookup(self, key):
174 if self._bookmarks is None:
174 if self._bookmarks is None:
175 self._bookmarks = parse(self)
175 self._bookmarks = parse(self)
176 if key in self._bookmarks:
176 if key in self._bookmarks:
177 key = self._bookmarks[key]
177 key = self._bookmarks[key]
178 return super(bookmark_repo, self).lookup(key)
178 return super(bookmark_repo, self).lookup(key)
179
179
180 def commit(self, *k, **kw):
180 def commit(self, *k, **kw):
181 """Add a revision to the repository and
181 """Add a revision to the repository and
182 move the bookmark"""
182 move the bookmark"""
183 node = super(bookmark_repo, self).commit(*k, **kw)
183 node = super(bookmark_repo, self).commit(*k, **kw)
184 parents = repo.changelog.parents(node)
184 parents = repo.changelog.parents(node)
185 if parents[1] == nullid:
186 parents = (parents[0],)
185 marks = parse(repo)
187 marks = parse(repo)
186 update = False
188 update = False
187 for mark, n in marks.items():
189 for mark, n in marks.items():
188 if n in parents:
190 if n in parents:
189 marks[mark] = node
191 marks[mark] = node
190 update = True
192 update = True
191 if update:
193 if update:
192 write(repo, marks)
194 write(repo, marks)
193 return node
195 return node
194
196
195 def addchangegroup(self, source, srctype, url, emptyok=False):
197 def addchangegroup(self, source, srctype, url, emptyok=False):
196 try:
198 try:
197 onode = repo.changectx('.').node()
199 onode = repo.changectx('.').node()
198 except RepoError, inst:
200 except RepoError, inst:
199 pass
201 pass
200
202
201 result = super(bookmark_repo, self).addchangegroup(
203 result = super(bookmark_repo, self).addchangegroup(
202 source, srctype, url, emptyok)
204 source, srctype, url, emptyok)
203 if result > 1:
205 if result > 1:
204 # We have more heads than before
206 # We have more heads than before
205 return result
207 return result
206 node = repo.changelog.tip()
208 node = repo.changelog.tip()
207 marks = parse(repo)
209 marks = parse(repo)
208 update = False
210 update = False
209 for mark, n in marks.items():
211 for mark, n in marks.items():
210 if n == onode:
212 if n == onode:
211 marks[mark] = node
213 marks[mark] = node
212 update = True
214 update = True
213 if update:
215 if update:
214 write(repo, marks)
216 write(repo, marks)
215 return result
217 return result
216
218
217 repo.__class__ = bookmark_repo
219 repo.__class__ = bookmark_repo
218
220
219 cmdtable = {
221 cmdtable = {
220 "bookmarks":
222 "bookmarks":
221 (bookmark,
223 (bookmark,
222 [('f', 'force', False, _('force')),
224 [('f', 'force', False, _('force')),
223 ('r', 'rev', '', _('revision')),
225 ('r', 'rev', '', _('revision')),
224 ('d', 'delete', False, _('delete a given bookmark')),
226 ('d', 'delete', False, _('delete a given bookmark')),
225 ('m', 'rename', '', _('rename a given bookmark'))],
227 ('m', 'rename', '', _('rename a given bookmark'))],
226 _('hg bookmarks [-d] [-m NAME] [-r NAME] [NAME]')),
228 _('hg bookmarks [-d] [-m NAME] [-r NAME] [NAME]')),
227 }
229 }
General Comments 0
You need to be logged in to leave comments. Login now