##// END OF EJS Templates
bookmarks: Set current bookmark if we create a new one on the tip...
David Soria Parra -
r7816:f420eafe default
parent child Browse files
Show More
@@ -1,318 +1,319 b''
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 The bookmark extension offers the possiblity to have a more git-like experience
19 19 by adding the following configuration option to your .hgrc:
20 20
21 21 [bookmarks]
22 22 track.current = True
23 23
24 24 This will cause bookmarks to track the bookmark that you are currently on, and
25 25 just updates it. This is similar to git's approach of branching.
26 26 '''
27 27
28 28 from mercurial.i18n import _
29 29 from mercurial.node import nullid, nullrev, hex, short
30 30 from mercurial import util, commands, localrepo, repair, extensions
31 31 import os
32 32
33 33 def parse(repo):
34 34 '''Parse .hg/bookmarks file and return a dictionary
35 35
36 36 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
37 37 in the .hg/bookmarks file. They are read by the parse() method and
38 38 returned as a dictionary with name => hash values.
39 39
40 40 The parsed dictionary is cached until a write() operation is done.
41 41 '''
42 42 try:
43 43 if repo._bookmarks:
44 44 return repo._bookmarks
45 45 repo._bookmarks = {}
46 46 for line in repo.opener('bookmarks'):
47 47 sha, refspec = line.strip().split(' ', 1)
48 48 repo._bookmarks[refspec] = repo.lookup(sha)
49 49 except:
50 50 pass
51 51 return repo._bookmarks
52 52
53 53 def write(repo, refs):
54 54 '''Write bookmarks
55 55
56 56 Write the given bookmark => hash dictionary to the .hg/bookmarks file
57 57 in a format equal to those of localtags.
58 58
59 59 We also store a backup of the previous state in undo.bookmarks that
60 60 can be copied back on rollback.
61 61 '''
62 62 if os.path.exists(repo.join('bookmarks')):
63 63 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
64 64 if current(repo) not in refs:
65 65 setcurrent(repo, None)
66 66 file = repo.opener('bookmarks', 'w+')
67 67 for refspec, node in refs.iteritems():
68 68 file.write("%s %s\n" % (hex(node), refspec))
69 69 file.close()
70 70
71 71 def current(repo):
72 72 '''Get the current bookmark
73 73
74 74 If we use gittishsh branches we have a current bookmark that
75 75 we are on. This function returns the name of the bookmark. It
76 76 is stored in .hg/bookmarks.current
77 77 '''
78 78 if repo._bookmarkcurrent:
79 79 return repo._bookmarkcurrent
80 80 mark = None
81 81 if os.path.exists(repo.join('bookmarks.current')):
82 82 file = repo.opener('bookmarks.current')
83 83 # No readline() in posixfile_nt, reading everything is cheap
84 84 mark = (file.readlines() or [''])[0]
85 85 if mark == '':
86 86 mark = None
87 87 file.close()
88 88 repo._bookmarkcurrent = mark
89 89 return mark
90 90
91 91 def setcurrent(repo, mark):
92 92 '''Set the name of the bookmark that we are currently on
93 93
94 94 Set the name of the bookmark that we are on (hg update <bookmark>).
95 95 The name is recoreded in .hg/bookmarks.current
96 96 '''
97 97 if current(repo) == mark:
98 98 return
99 99
100 100 refs = parse(repo)
101 101
102 102 # do not update if we do update to a rev equal to the current bookmark
103 103 if (mark not in refs and
104 104 current(repo) and refs[current(repo)] == repo.changectx('.').node()):
105 105 return
106 106 if mark not in refs:
107 107 mark = ''
108 108 file = repo.opener('bookmarks.current', 'w+')
109 109 file.write(mark)
110 110 file.close()
111 111 repo._bookmarkcurrent = mark
112 112
113 113 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
114 114 '''mercurial bookmarks
115 115
116 116 Bookmarks are pointers to certain commits that move when
117 117 commiting. Bookmarks are local. They can be renamed, copied and
118 118 deleted. It is possible to use bookmark names in 'hg merge' and 'hg
119 119 update' to update to a given bookmark.
120 120
121 121 You can use 'hg bookmark NAME' to set a bookmark on the current
122 122 tip with the given name. If you specify a revision using -r REV
123 123 (where REV may be an existing bookmark), the bookmark is set to
124 124 that revision.
125 125 '''
126 126 hexfn = ui.debugflag and hex or short
127 127 marks = parse(repo)
128 128 cur = repo.changectx('.').node()
129 129
130 130 if rename:
131 131 if rename not in marks:
132 132 raise util.Abort(_("a bookmark of this name does not exist"))
133 133 if mark in marks and not force:
134 134 raise util.Abort(_("a bookmark of the same name already exists"))
135 135 if mark is None:
136 136 raise util.Abort(_("new bookmark name required"))
137 137 marks[mark] = marks[rename]
138 138 del marks[rename]
139 139 if current(repo) == rename:
140 140 setcurrent(repo, mark)
141 141 write(repo, marks)
142 142 return
143 143
144 144 if delete:
145 145 if mark == None:
146 146 raise util.Abort(_("bookmark name required"))
147 147 if mark not in marks:
148 148 raise util.Abort(_("a bookmark of this name does not exist"))
149 149 del marks[mark]
150 150 write(repo, marks)
151 151 return
152 152
153 153 if mark != None:
154 154 if "\n" in mark:
155 155 raise util.Abort(_("bookmark name cannot contain newlines"))
156 156 mark = mark.strip()
157 157 if mark in marks and not force:
158 158 raise util.Abort(_("a bookmark of the same name already exists"))
159 159 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
160 160 and not force):
161 161 raise util.Abort(
162 162 _("a bookmark cannot have the name of an existing branch"))
163 163 if rev:
164 164 marks[mark] = repo.lookup(rev)
165 165 else:
166 166 marks[mark] = repo.changectx('.').node()
167 setcurrent(repo, mark)
167 168 write(repo, marks)
168 169 return
169 170
170 171 if mark == None:
171 172 if rev:
172 173 raise util.Abort(_("bookmark name required"))
173 174 if len(marks) == 0:
174 175 ui.status("no bookmarks set\n")
175 176 else:
176 177 for bmark, n in marks.iteritems():
177 178 if ui.configbool('bookmarks', 'track.current'):
178 179 prefix = (bmark == current(repo) and n == cur) and '*' or ' '
179 180 else:
180 181 prefix = (n == cur) and '*' or ' '
181 182
182 183 ui.write(" %s %-25s %d:%s\n" % (
183 184 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
184 185 return
185 186
186 187 def _revstostrip(changelog, node):
187 188 srev = changelog.rev(node)
188 189 tostrip = [srev]
189 190 saveheads = []
190 191 for r in xrange(srev, len(changelog)):
191 192 parents = changelog.parentrevs(r)
192 193 if parents[0] in tostrip or parents[1] in tostrip:
193 194 tostrip.append(r)
194 195 if parents[1] != nullrev:
195 196 for p in parents:
196 197 if p not in tostrip and p > srev:
197 198 saveheads.append(p)
198 199 return [r for r in tostrip if r not in saveheads]
199 200
200 201 def strip(oldstrip, ui, repo, node, backup="all"):
201 202 """Strip bookmarks if revisions are stripped using
202 203 the mercurial.strip method. This usually happens during
203 204 qpush and qpop"""
204 205 revisions = _revstostrip(repo.changelog, node)
205 206 marks = parse(repo)
206 207 update = []
207 208 for mark, n in marks.iteritems():
208 209 if repo.changelog.rev(n) in revisions:
209 210 update.append(mark)
210 211 oldstrip(ui, repo, node, backup)
211 212 if len(update) > 0:
212 213 for m in update:
213 214 marks[m] = repo.changectx('.').node()
214 215 write(repo, marks)
215 216
216 217 def reposetup(ui, repo):
217 218 if not isinstance(repo, localrepo.localrepository):
218 219 return
219 220
220 221 # init a bookmark cache as otherwise we would get a infinite reading
221 222 # in lookup()
222 223 repo._bookmarks = None
223 224 repo._bookmarkcurrent = None
224 225
225 226 class bookmark_repo(repo.__class__):
226 227 def rollback(self):
227 228 if os.path.exists(self.join('undo.bookmarks')):
228 229 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
229 230 return super(bookmark_repo, self).rollback()
230 231
231 232 def lookup(self, key):
232 233 if self._bookmarks is None:
233 234 self._bookmarks = parse(self)
234 235 if key in self._bookmarks:
235 236 key = self._bookmarks[key]
236 237 return super(bookmark_repo, self).lookup(key)
237 238
238 239 def commit(self, *k, **kw):
239 240 """Add a revision to the repository and
240 241 move the bookmark"""
241 242 node = super(bookmark_repo, self).commit(*k, **kw)
242 243 if node == None:
243 244 return None
244 245 parents = repo.changelog.parents(node)
245 246 if parents[1] == nullid:
246 247 parents = (parents[0],)
247 248 marks = parse(repo)
248 249 update = False
249 250 for mark, n in marks.items():
250 251 if ui.configbool('bookmarks', 'track.current'):
251 252 if mark == current(repo) and n in parents:
252 253 marks[mark] = node
253 254 update = True
254 255 else:
255 256 if n in parents:
256 257 marks[mark] = node
257 258 update = True
258 259 if update:
259 260 write(repo, marks)
260 261 return node
261 262
262 263 def addchangegroup(self, source, srctype, url, emptyok=False):
263 264 parents = repo.dirstate.parents()
264 265
265 266 result = super(bookmark_repo, self).addchangegroup(
266 267 source, srctype, url, emptyok)
267 268 if result > 1:
268 269 # We have more heads than before
269 270 return result
270 271 node = repo.changelog.tip()
271 272 marks = parse(repo)
272 273 update = False
273 274 for mark, n in marks.items():
274 275 if n in parents:
275 276 marks[mark] = node
276 277 update = True
277 278 if update:
278 279 write(repo, marks)
279 280 return result
280 281
281 282 def tags(self):
282 283 """Merge bookmarks with normal tags"""
283 284 if self.tagscache:
284 285 return self.tagscache
285 286
286 287 tagscache = super(bookmark_repo, self).tags()
287 288 tagscache.update(parse(repo))
288 289 return tagscache
289 290
290 291 repo.__class__ = bookmark_repo
291 292
292 293 def uisetup(ui):
293 294 extensions.wrapfunction(repair, "strip", strip)
294 295 if ui.configbool('bookmarks', 'track.current'):
295 296 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
296 297
297 298 def updatecurbookmark(orig, ui, repo, *args, **opts):
298 299 '''Set the current bookmark
299 300
300 301 If the user updates to a bookmark we update the .hg/bookmarks.current
301 302 file.
302 303 '''
303 304 res = orig(ui, repo, *args, **opts)
304 305 rev = opts['rev']
305 306 if not rev and len(args) > 0:
306 307 rev = args[0]
307 308 setcurrent(repo, rev)
308 309 return res
309 310
310 311 cmdtable = {
311 312 "bookmarks":
312 313 (bookmark,
313 314 [('f', 'force', False, _('force')),
314 315 ('r', 'rev', '', _('revision')),
315 316 ('d', 'delete', False, _('delete a given bookmark')),
316 317 ('m', 'rename', '', _('rename a given bookmark'))],
317 318 _('hg bookmarks [-f] [-d] [-m NAME] [-r NAME] [NAME]')),
318 319 }
@@ -1,41 +1,44 b''
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "bookmarks=" >> $HGRCPATH
5 5
6 6 echo "[bookmarks]" >> $HGRCPATH
7 7 echo "track.current = True" >> $HGRCPATH
8 8
9 9 hg init
10 10
11 11 echo % no bookmarks
12 12 hg bookmarks
13 13
14 14 echo % set bookmark X
15 15 hg bookmark X
16 16
17 echo % list bookmarks
18 hg bookmark
19
17 20 echo % update to bookmark X
18 21 hg update X
19 22
20 23 echo % list bookmarks
21 24 hg bookmarks
22 25
23 26 echo % rename
24 27 hg bookmark -m X Z
25 28
26 29 echo % list bookmarks
27 30 hg bookmarks
28 31
29 32 echo % new bookmark Y
30 33 hg bookmark Y
31 34
32 35 echo % list bookmarks
33 36 hg bookmark
34 37
35 38 echo % commit
36 39 echo 'b' > b
37 40 hg add b
38 41 hg commit -m'test'
39 42
40 43 echo % list bookmarks
41 44 hg bookmark
@@ -1,18 +1,20 b''
1 1 % no bookmarks
2 2 no bookmarks set
3 3 % set bookmark X
4 % list bookmarks
5 * X -1:000000000000
4 6 % update to bookmark X
5 7 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
6 8 % list bookmarks
7 9 * X -1:000000000000
8 10 % rename
9 11 % list bookmarks
10 12 * Z -1:000000000000
11 13 % new bookmark Y
12 14 % list bookmarks
13 Y -1:000000000000
14 * Z -1:000000000000
15 * Y -1:000000000000
16 Z -1:000000000000
15 17 % commit
16 18 % list bookmarks
17 Y -1:000000000000
18 * Z 0:719295282060
19 * Y 0:719295282060
20 Z -1:000000000000
General Comments 0
You need to be logged in to leave comments. Login now