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