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