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