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