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