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