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