##// END OF EJS Templates
Renamed c, a, d, u to modified, added, removed, unknown for users of changes()
Thomas Arendsen Hein -
r1618:ff339dd2 default
parent child Browse files
Show More
@@ -1,287 +1,287
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # This software may be used and distributed according to the terms
3 # This software may be used and distributed according to the terms
4 # of the GNU General Public License, incorporated herein by reference.
4 # of the GNU General Public License, incorporated herein by reference.
5
5
6 from mercurial.demandload import demandload
6 from mercurial.demandload import demandload
7 demandload(globals(), "os sys sets")
7 demandload(globals(), "os sys sets")
8 from mercurial import hg
8 from mercurial import hg
9
9
10 versionstr = "0.0.3"
10 versionstr = "0.0.3"
11
11
12 def lookup_rev(ui, repo, rev=None):
12 def lookup_rev(ui, repo, rev=None):
13 """returns rev or the checked-out revision if rev is None"""
13 """returns rev or the checked-out revision if rev is None"""
14 if not rev is None:
14 if not rev is None:
15 return repo.lookup(rev)
15 return repo.lookup(rev)
16 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
16 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
17 if len(parents) != 1:
17 if len(parents) != 1:
18 ui.warn("unexpected number of parents\n")
18 ui.warn("unexpected number of parents\n")
19 ui.warn("please commit or revert\n")
19 ui.warn("please commit or revert\n")
20 sys.exit(1)
20 sys.exit(1)
21 return parents.pop()
21 return parents.pop()
22
22
23 def check_clean(ui, repo):
23 def check_clean(ui, repo):
24 c, a, d, u = repo.changes()
24 modified, added, removed, unknown = repo.changes()
25 if c or a or d:
25 if modified or added or removed:
26 ui.warn("Repository is not clean, please commit or revert\n")
26 ui.warn("Repository is not clean, please commit or revert\n")
27 sys.exit(1)
27 sys.exit(1)
28
28
29 class bisect(object):
29 class bisect(object):
30 """dichotomic search in the DAG of changesets"""
30 """dichotomic search in the DAG of changesets"""
31 def __init__(self, ui, repo):
31 def __init__(self, ui, repo):
32 self.repo = repo
32 self.repo = repo
33 self.path = os.path.join(repo.join(""), "bisect")
33 self.path = os.path.join(repo.join(""), "bisect")
34 self.ui = ui
34 self.ui = ui
35 self.goodrevs = []
35 self.goodrevs = []
36 self.badrev = None
36 self.badrev = None
37 self.good_dirty = 0
37 self.good_dirty = 0
38 self.bad_dirty = 0
38 self.bad_dirty = 0
39 self.good_path = os.path.join(self.path, "good")
39 self.good_path = os.path.join(self.path, "good")
40 self.bad_path = os.path.join(self.path, "bad")
40 self.bad_path = os.path.join(self.path, "bad")
41
41
42 s = self.good_path
42 s = self.good_path
43 if os.path.exists(s):
43 if os.path.exists(s):
44 self.goodrevs = self.repo.opener(s).read().splitlines()
44 self.goodrevs = self.repo.opener(s).read().splitlines()
45 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
45 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
46 s = self.bad_path
46 s = self.bad_path
47 if os.path.exists(s):
47 if os.path.exists(s):
48 r = self.repo.opener(s).read().splitlines()
48 r = self.repo.opener(s).read().splitlines()
49 if r:
49 if r:
50 self.badrev = hg.bin(r.pop(0))
50 self.badrev = hg.bin(r.pop(0))
51
51
52 def __del__(self):
52 def __del__(self):
53 if not os.path.isdir(self.path):
53 if not os.path.isdir(self.path):
54 return
54 return
55 f = self.repo.opener(self.good_path, "w")
55 f = self.repo.opener(self.good_path, "w")
56 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
56 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
57 if len(self.goodrevs) > 0:
57 if len(self.goodrevs) > 0:
58 f.write("\n")
58 f.write("\n")
59 f = self.repo.opener(self.bad_path, "w")
59 f = self.repo.opener(self.bad_path, "w")
60 if self.badrev:
60 if self.badrev:
61 f.write(hg.hex(self.badrev) + "\n")
61 f.write(hg.hex(self.badrev) + "\n")
62
62
63 def init(self):
63 def init(self):
64 """start a new bisection"""
64 """start a new bisection"""
65 if os.path.isdir(self.path):
65 if os.path.isdir(self.path):
66 self.ui.warn("bisect directory already exists\n")
66 self.ui.warn("bisect directory already exists\n")
67 return 1
67 return 1
68 os.mkdir(self.path)
68 os.mkdir(self.path)
69 check_clean(self.ui, self.repo)
69 check_clean(self.ui, self.repo)
70 return 0
70 return 0
71
71
72 def reset(self):
72 def reset(self):
73 """finish a bisection"""
73 """finish a bisection"""
74 if os.path.isdir(self.path):
74 if os.path.isdir(self.path):
75 sl = [self.bad_path, self.good_path]
75 sl = [self.bad_path, self.good_path]
76 for s in sl:
76 for s in sl:
77 if os.path.exists(s):
77 if os.path.exists(s):
78 os.unlink(s)
78 os.unlink(s)
79 os.rmdir(self.path)
79 os.rmdir(self.path)
80 # Not sure about this
80 # Not sure about this
81 #self.ui.write("Going back to tip\n")
81 #self.ui.write("Going back to tip\n")
82 #self.repo.update(self.repo.changelog.tip())
82 #self.repo.update(self.repo.changelog.tip())
83 return 1
83 return 1
84
84
85 def num_ancestors(self, head=None, stop=None):
85 def num_ancestors(self, head=None, stop=None):
86 """
86 """
87 returns a dict with the mapping:
87 returns a dict with the mapping:
88 node -> number of ancestors (self included)
88 node -> number of ancestors (self included)
89 for all nodes who are ancestor of head and
89 for all nodes who are ancestor of head and
90 not in stop.
90 not in stop.
91 """
91 """
92 if head is None:
92 if head is None:
93 head = self.badrev
93 head = self.badrev
94 return self.__ancestors_and_nb_ancestors(head, stop)[1]
94 return self.__ancestors_and_nb_ancestors(head, stop)[1]
95
95
96 def ancestors(self, head=None, stop=None):
96 def ancestors(self, head=None, stop=None):
97 """
97 """
98 returns the set of the ancestors of head (self included)
98 returns the set of the ancestors of head (self included)
99 who are not in stop.
99 who are not in stop.
100 """
100 """
101 if head is None:
101 if head is None:
102 head = self.badrev
102 head = self.badrev
103 return self.__ancestors_and_nb_ancestors(head, stop)[0]
103 return self.__ancestors_and_nb_ancestors(head, stop)[0]
104
104
105 def __ancestors_and_nb_ancestors(self, head, stop=None):
105 def __ancestors_and_nb_ancestors(self, head, stop=None):
106 """
106 """
107 if stop is None then ancestors of goodrevs are used as
107 if stop is None then ancestors of goodrevs are used as
108 lower limit.
108 lower limit.
109
109
110 returns (anc, n_child) where anc is the set of the ancestors of head
110 returns (anc, n_child) where anc is the set of the ancestors of head
111 and n_child is a dictionary with the following mapping:
111 and n_child is a dictionary with the following mapping:
112 node -> number of ancestors (self included)
112 node -> number of ancestors (self included)
113 """
113 """
114 cl = self.repo.changelog
114 cl = self.repo.changelog
115 if not stop:
115 if not stop:
116 stop = sets.Set([])
116 stop = sets.Set([])
117 for g in reversed(self.goodrevs):
117 for g in reversed(self.goodrevs):
118 if g in stop:
118 if g in stop:
119 continue
119 continue
120 stop.update(cl.reachable(g))
120 stop.update(cl.reachable(g))
121 def num_children(a):
121 def num_children(a):
122 """
122 """
123 returns a dictionnary with the following mapping
123 returns a dictionnary with the following mapping
124 node -> [number of children, empty set]
124 node -> [number of children, empty set]
125 """
125 """
126 d = {a: [0, sets.Set([])]}
126 d = {a: [0, sets.Set([])]}
127 for i in xrange(cl.rev(a)+1):
127 for i in xrange(cl.rev(a)+1):
128 n = cl.node(i)
128 n = cl.node(i)
129 if not d.has_key(n):
129 if not d.has_key(n):
130 d[n] = [0, sets.Set([])]
130 d[n] = [0, sets.Set([])]
131 parents = [p for p in cl.parents(n) if p != hg.nullid]
131 parents = [p for p in cl.parents(n) if p != hg.nullid]
132 for p in parents:
132 for p in parents:
133 d[p][0] += 1
133 d[p][0] += 1
134 return d
134 return d
135
135
136 if head in stop:
136 if head in stop:
137 self.ui.warn("Unconsistent state, %s is good and bad\n"
137 self.ui.warn("Unconsistent state, %s is good and bad\n"
138 % hg.hex(head))
138 % hg.hex(head))
139 sys.exit(1)
139 sys.exit(1)
140 n_child = num_children(head)
140 n_child = num_children(head)
141 for i in xrange(cl.rev(head)+1):
141 for i in xrange(cl.rev(head)+1):
142 n = cl.node(i)
142 n = cl.node(i)
143 parents = [p for p in cl.parents(n) if p != hg.nullid]
143 parents = [p for p in cl.parents(n) if p != hg.nullid]
144 for p in parents:
144 for p in parents:
145 n_child[p][0] -= 1
145 n_child[p][0] -= 1
146 if not n in stop:
146 if not n in stop:
147 n_child[n][1].union_update(n_child[p][1])
147 n_child[n][1].union_update(n_child[p][1])
148 if n_child[p][0] == 0:
148 if n_child[p][0] == 0:
149 n_child[p] = len(n_child[p][1])
149 n_child[p] = len(n_child[p][1])
150 if not n in stop:
150 if not n in stop:
151 n_child[n][1].add(n)
151 n_child[n][1].add(n)
152 if n_child[n][0] == 0:
152 if n_child[n][0] == 0:
153 if n == head:
153 if n == head:
154 anc = n_child[n][1]
154 anc = n_child[n][1]
155 n_child[n] = len(n_child[n][1])
155 n_child[n] = len(n_child[n][1])
156 return anc, n_child
156 return anc, n_child
157
157
158 def next(self):
158 def next(self):
159 if not self.badrev:
159 if not self.badrev:
160 self.ui.warn("You should give at least one bad\n")
160 self.ui.warn("You should give at least one bad\n")
161 sys.exit(1)
161 sys.exit(1)
162 if not self.goodrevs:
162 if not self.goodrevs:
163 self.ui.warn("No good revision given\n")
163 self.ui.warn("No good revision given\n")
164 self.ui.warn("Assuming the first revision is good\n")
164 self.ui.warn("Assuming the first revision is good\n")
165 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(self.badrev)
165 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(self.badrev)
166 tot = len(ancestors)
166 tot = len(ancestors)
167 if tot == 1:
167 if tot == 1:
168 if ancestors.pop() != self.badrev:
168 if ancestors.pop() != self.badrev:
169 self.ui.warn("Could not find the first bad revision\n")
169 self.ui.warn("Could not find the first bad revision\n")
170 sys.exit(1)
170 sys.exit(1)
171 self.ui.write(
171 self.ui.write(
172 "The first bad revision is : %s\n" % hg.hex(self.badrev))
172 "The first bad revision is : %s\n" % hg.hex(self.badrev))
173 sys.exit(0)
173 sys.exit(0)
174 self.ui.write("%d revisions left\n" % tot)
174 self.ui.write("%d revisions left\n" % tot)
175 best_rev = None
175 best_rev = None
176 best_len = -1
176 best_len = -1
177 for n in ancestors:
177 for n in ancestors:
178 l = num_ancestors[n]
178 l = num_ancestors[n]
179 l = min(l, tot - l)
179 l = min(l, tot - l)
180 if l > best_len:
180 if l > best_len:
181 best_len = l
181 best_len = l
182 best_rev = n
182 best_rev = n
183 return best_rev
183 return best_rev
184
184
185 def autonext(self):
185 def autonext(self):
186 """find and update to the next revision to test"""
186 """find and update to the next revision to test"""
187 check_clean(self.ui, self.repo)
187 check_clean(self.ui, self.repo)
188 rev = self.next()
188 rev = self.next()
189 self.ui.write("Now testing %s\n" % hg.hex(rev))
189 self.ui.write("Now testing %s\n" % hg.hex(rev))
190 return self.repo.update(rev, allow=True, force=True)
190 return self.repo.update(rev, allow=True, force=True)
191
191
192 def good(self, rev):
192 def good(self, rev):
193 self.goodrevs.append(rev)
193 self.goodrevs.append(rev)
194
194
195 def autogood(self, rev=None):
195 def autogood(self, rev=None):
196 """mark revision as good and update to the next revision to test"""
196 """mark revision as good and update to the next revision to test"""
197 check_clean(self.ui, self.repo)
197 check_clean(self.ui, self.repo)
198 rev = lookup_rev(self.ui, self.repo, rev)
198 rev = lookup_rev(self.ui, self.repo, rev)
199 self.good(rev)
199 self.good(rev)
200 if self.badrev:
200 if self.badrev:
201 self.autonext()
201 self.autonext()
202
202
203 def bad(self, rev):
203 def bad(self, rev):
204 self.badrev = rev
204 self.badrev = rev
205
205
206 def autobad(self, rev=None):
206 def autobad(self, rev=None):
207 """mark revision as bad and update to the next revision to test"""
207 """mark revision as bad and update to the next revision to test"""
208 check_clean(self.ui, self.repo)
208 check_clean(self.ui, self.repo)
209 rev = lookup_rev(self.ui, self.repo, rev)
209 rev = lookup_rev(self.ui, self.repo, rev)
210 self.bad(rev)
210 self.bad(rev)
211 if self.goodrevs:
211 if self.goodrevs:
212 self.autonext()
212 self.autonext()
213
213
214 # should we put it in the class ?
214 # should we put it in the class ?
215 def test(ui, repo, rev):
215 def test(ui, repo, rev):
216 """test the bisection code"""
216 """test the bisection code"""
217 b = bisect(ui, repo)
217 b = bisect(ui, repo)
218 rev = repo.lookup(rev)
218 rev = repo.lookup(rev)
219 ui.write("testing with rev %s\n" % hg.hex(rev))
219 ui.write("testing with rev %s\n" % hg.hex(rev))
220 anc = b.ancestors()
220 anc = b.ancestors()
221 while len(anc) > 1:
221 while len(anc) > 1:
222 if not rev in anc:
222 if not rev in anc:
223 ui.warn("failure while bisecting\n")
223 ui.warn("failure while bisecting\n")
224 sys.exit(1)
224 sys.exit(1)
225 ui.write("it worked :)\n")
225 ui.write("it worked :)\n")
226 new_rev = b.next()
226 new_rev = b.next()
227 ui.write("choosing if good or bad\n")
227 ui.write("choosing if good or bad\n")
228 if rev in b.ancestors(head=new_rev):
228 if rev in b.ancestors(head=new_rev):
229 b.bad(new_rev)
229 b.bad(new_rev)
230 ui.write("it is bad\n")
230 ui.write("it is bad\n")
231 else:
231 else:
232 b.good(new_rev)
232 b.good(new_rev)
233 ui.write("it is good\n")
233 ui.write("it is good\n")
234 anc = b.ancestors()
234 anc = b.ancestors()
235 repo.update(new_rev, allow=True, force=True)
235 repo.update(new_rev, allow=True, force=True)
236 for v in anc:
236 for v in anc:
237 if v != rev:
237 if v != rev:
238 ui.warn("fail to found cset! :(\n")
238 ui.warn("fail to found cset! :(\n")
239 return 1
239 return 1
240 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
240 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
241 ui.write("Everything is ok :)\n")
241 ui.write("Everything is ok :)\n")
242 return 0
242 return 0
243
243
244 def bisect_run(ui, repo, cmd=None, *args):
244 def bisect_run(ui, repo, cmd=None, *args):
245 """bisect extension: dichotomic search in the DAG of changesets
245 """bisect extension: dichotomic search in the DAG of changesets
246 for subcommands see "hg bisect help\"
246 for subcommands see "hg bisect help\"
247 """
247 """
248 def help_(cmd=None, *args):
248 def help_(cmd=None, *args):
249 """show help for a given bisect subcommand or all subcommands"""
249 """show help for a given bisect subcommand or all subcommands"""
250 cmdtable = bisectcmdtable
250 cmdtable = bisectcmdtable
251 if cmd:
251 if cmd:
252 doc = cmdtable[cmd][0].__doc__
252 doc = cmdtable[cmd][0].__doc__
253 synopsis = cmdtable[cmd][2]
253 synopsis = cmdtable[cmd][2]
254 ui.write(synopsis + "\n")
254 ui.write(synopsis + "\n")
255 ui.write("\n" + doc + "\n")
255 ui.write("\n" + doc + "\n")
256 return
256 return
257 ui.write("list of subcommands for the bisect extension\n\n")
257 ui.write("list of subcommands for the bisect extension\n\n")
258 cmds = cmdtable.keys()
258 cmds = cmdtable.keys()
259 cmds.sort()
259 cmds.sort()
260 m = max([len(c) for c in cmds])
260 m = max([len(c) for c in cmds])
261 for cmd in cmds:
261 for cmd in cmds:
262 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
262 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
263 ui.write(" %-*s %s\n" % (m, cmd, doc))
263 ui.write(" %-*s %s\n" % (m, cmd, doc))
264
264
265 b = bisect(ui, repo)
265 b = bisect(ui, repo)
266 bisectcmdtable = {
266 bisectcmdtable = {
267 "init": (b.init, 0, "hg bisect init"),
267 "init": (b.init, 0, "hg bisect init"),
268 "bad": (b.autobad, 1, "hg bisect bad [<rev>]"),
268 "bad": (b.autobad, 1, "hg bisect bad [<rev>]"),
269 "good": (b.autogood, 1, "hg bisect good [<rev>]"),
269 "good": (b.autogood, 1, "hg bisect good [<rev>]"),
270 "next": (b.autonext, 0, "hg bisect next"),
270 "next": (b.autonext, 0, "hg bisect next"),
271 "reset": (b.reset, 0, "hg bisect reset"),
271 "reset": (b.reset, 0, "hg bisect reset"),
272 "help": (help_, 1, "hg bisect help [<subcommand>]"),
272 "help": (help_, 1, "hg bisect help [<subcommand>]"),
273 }
273 }
274
274
275 if not bisectcmdtable.has_key(cmd):
275 if not bisectcmdtable.has_key(cmd):
276 ui.warn("bisect: Unknown sub-command\n")
276 ui.warn("bisect: Unknown sub-command\n")
277 return help_()
277 return help_()
278 if len(args) > bisectcmdtable[cmd][1]:
278 if len(args) > bisectcmdtable[cmd][1]:
279 ui.warn("bisect: Too many arguments\n")
279 ui.warn("bisect: Too many arguments\n")
280 return help_()
280 return help_()
281 return bisectcmdtable[cmd][0](*args)
281 return bisectcmdtable[cmd][0](*args)
282
282
283 cmdtable = {
283 cmdtable = {
284 "bisect": (bisect_run, [],
284 "bisect": (bisect_run, [],
285 "hg bisect [help|init|reset|next|good|bad]"),
285 "hg bisect [help|init|reset|next|good|bad]"),
286 #"bisect-test": (test, [], "hg bisect-test rev"),
286 #"bisect-test": (test, [], "hg bisect-test rev"),
287 }
287 }
@@ -1,339 +1,339
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005 Chris Mason <mason@suse.com>
3 # Copyright 2005 Chris Mason <mason@suse.com>
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 import time, sys, signal, os
8 import time, sys, signal, os
9 from mercurial import hg, mdiff, fancyopts, commands, ui, util
9 from mercurial import hg, mdiff, fancyopts, commands, ui, util
10
10
11 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
11 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
12 changes=None, text=False):
12 changes=None, text=False):
13 def date(c):
13 def date(c):
14 return time.asctime(time.gmtime(c[2][0]))
14 return time.asctime(time.gmtime(c[2][0]))
15
15
16 if not changes:
16 if not changes:
17 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
17 changes = repo.changes(node1, node2, files, match=match)
18 else:
18 modified, added, removed, unknown = changes
19 (c, a, d, u) = changes
20 if files:
19 if files:
21 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
20 modified, added, removed = map(lambda x: filterfiles(x, files),
21 (modified, added, removed))
22
22
23 if not c and not a and not d:
23 if not modified and not added and not removed:
24 return
24 return
25
25
26 if node2:
26 if node2:
27 change = repo.changelog.read(node2)
27 change = repo.changelog.read(node2)
28 mmap2 = repo.manifest.read(change[0])
28 mmap2 = repo.manifest.read(change[0])
29 date2 = date(change)
29 date2 = date(change)
30 def read(f):
30 def read(f):
31 return repo.file(f).read(mmap2[f])
31 return repo.file(f).read(mmap2[f])
32 else:
32 else:
33 date2 = time.asctime()
33 date2 = time.asctime()
34 if not node1:
34 if not node1:
35 node1 = repo.dirstate.parents()[0]
35 node1 = repo.dirstate.parents()[0]
36 def read(f):
36 def read(f):
37 return repo.wfile(f).read()
37 return repo.wfile(f).read()
38
38
39 change = repo.changelog.read(node1)
39 change = repo.changelog.read(node1)
40 mmap = repo.manifest.read(change[0])
40 mmap = repo.manifest.read(change[0])
41 date1 = date(change)
41 date1 = date(change)
42
42
43 for f in c:
43 for f in modified:
44 to = None
44 to = None
45 if f in mmap:
45 if f in mmap:
46 to = repo.file(f).read(mmap[f])
46 to = repo.file(f).read(mmap[f])
47 tn = read(f)
47 tn = read(f)
48 fp.write("diff --git a/%s b/%s\n" % (f, f))
48 fp.write("diff --git a/%s b/%s\n" % (f, f))
49 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
49 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
50 for f in a:
50 for f in added:
51 to = None
51 to = None
52 tn = read(f)
52 tn = read(f)
53 fp.write("diff --git /dev/null b/%s\n" % (f))
53 fp.write("diff --git /dev/null b/%s\n" % (f))
54 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
54 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
55 for f in d:
55 for f in removed:
56 to = repo.file(f).read(mmap[f])
56 to = repo.file(f).read(mmap[f])
57 tn = None
57 tn = None
58 fp.write("diff --git a/%s /dev/null\n" % (f))
58 fp.write("diff --git a/%s /dev/null\n" % (f))
59 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
59 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
60
60
61 def difftree(ui, repo, node1=None, node2=None, **opts):
61 def difftree(ui, repo, node1=None, node2=None, **opts):
62 """diff trees from two commits"""
62 """diff trees from two commits"""
63 def __difftree(repo, node1, node2):
63 def __difftree(repo, node1, node2):
64 def date(c):
64 def date(c):
65 return time.asctime(time.gmtime(c[2][0]))
65 return time.asctime(time.gmtime(c[2][0]))
66
66
67 if node2:
67 if node2:
68 change = repo.changelog.read(node2)
68 change = repo.changelog.read(node2)
69 mmap2 = repo.manifest.read(change[0])
69 mmap2 = repo.manifest.read(change[0])
70 (c, a, d, u) = repo.changes(node1, node2)
70 modified, added, removed, unknown = repo.changes(node1, node2)
71 def read(f): return repo.file(f).read(mmap2[f])
71 def read(f): return repo.file(f).read(mmap2[f])
72 date2 = date(change)
72 date2 = date(change)
73 else:
73 else:
74 date2 = time.asctime()
74 date2 = time.asctime()
75 (c, a, d, u) = repo.changes(node1, None)
75 modified, added, removed, unknown = repo.changes(node1)
76 if not node1:
76 if not node1:
77 node1 = repo.dirstate.parents()[0]
77 node1 = repo.dirstate.parents()[0]
78 def read(f): return file(os.path.join(repo.root, f)).read()
78 def read(f): return file(os.path.join(repo.root, f)).read()
79
79
80 change = repo.changelog.read(node1)
80 change = repo.changelog.read(node1)
81 mmap = repo.manifest.read(change[0])
81 mmap = repo.manifest.read(change[0])
82 date1 = date(change)
82 date1 = date(change)
83 empty = "0" * 40;
83 empty = "0" * 40;
84
84
85 for f in c:
85 for f in modified:
86 # TODO get file permissions
86 # TODO get file permissions
87 print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
87 print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
88 hg.hex(mmap2[f]), f, f)
88 hg.hex(mmap2[f]), f, f)
89 for f in a:
89 for f in added:
90 print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
90 print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
91 for f in d:
91 for f in removed:
92 print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
92 print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
93 ##
93 ##
94
94
95 while True:
95 while True:
96 if opts['stdin']:
96 if opts['stdin']:
97 try:
97 try:
98 line = raw_input().split(' ')
98 line = raw_input().split(' ')
99 node1 = line[0]
99 node1 = line[0]
100 if len(line) > 1:
100 if len(line) > 1:
101 node2 = line[1]
101 node2 = line[1]
102 else:
102 else:
103 node2 = None
103 node2 = None
104 except EOFError:
104 except EOFError:
105 break
105 break
106 node1 = repo.lookup(node1)
106 node1 = repo.lookup(node1)
107 if node2:
107 if node2:
108 node2 = repo.lookup(node2)
108 node2 = repo.lookup(node2)
109 else:
109 else:
110 node2 = node1
110 node2 = node1
111 node1 = repo.changelog.parents(node1)[0]
111 node1 = repo.changelog.parents(node1)[0]
112 if opts['patch']:
112 if opts['patch']:
113 if opts['pretty']:
113 if opts['pretty']:
114 catcommit(repo, node2, "")
114 catcommit(repo, node2, "")
115 dodiff(sys.stdout, ui, repo, node1, node2)
115 dodiff(sys.stdout, ui, repo, node1, node2)
116 else:
116 else:
117 __difftree(repo, node1, node2)
117 __difftree(repo, node1, node2)
118 if not opts['stdin']:
118 if not opts['stdin']:
119 break
119 break
120
120
121 def catcommit(repo, n, prefix, changes=None):
121 def catcommit(repo, n, prefix, changes=None):
122 nlprefix = '\n' + prefix;
122 nlprefix = '\n' + prefix;
123 (p1, p2) = repo.changelog.parents(n)
123 (p1, p2) = repo.changelog.parents(n)
124 (h, h1, h2) = map(hg.hex, (n, p1, p2))
124 (h, h1, h2) = map(hg.hex, (n, p1, p2))
125 (i1, i2) = map(repo.changelog.rev, (p1, p2))
125 (i1, i2) = map(repo.changelog.rev, (p1, p2))
126 if not changes:
126 if not changes:
127 changes = repo.changelog.read(n)
127 changes = repo.changelog.read(n)
128 print "tree %s" % (hg.hex(changes[0]))
128 print "tree %s" % (hg.hex(changes[0]))
129 if i1 != -1: print "parent %s" % (h1)
129 if i1 != -1: print "parent %s" % (h1)
130 if i2 != -1: print "parent %s" % (h2)
130 if i2 != -1: print "parent %s" % (h2)
131 date_ar = changes[2]
131 date_ar = changes[2]
132 date = int(float(date_ar[0]))
132 date = int(float(date_ar[0]))
133 lines = changes[4].splitlines()
133 lines = changes[4].splitlines()
134 if lines[-1].startswith('committer:'):
134 if lines[-1].startswith('committer:'):
135 committer = lines[-1].split(': ')[1].rstrip()
135 committer = lines[-1].split(': ')[1].rstrip()
136 else:
136 else:
137 committer = "%s %s %s" % (changes[1], date, date_ar[1])
137 committer = "%s %s %s" % (changes[1], date, date_ar[1])
138
138
139 print "author %s %s %s" % (changes[1], date, date_ar[1])
139 print "author %s %s %s" % (changes[1], date, date_ar[1])
140 print "committer %s" % (committer)
140 print "committer %s" % (committer)
141 print ""
141 print ""
142 if prefix != "":
142 if prefix != "":
143 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
143 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
144 else:
144 else:
145 print changes[4]
145 print changes[4]
146 if prefix:
146 if prefix:
147 sys.stdout.write('\0')
147 sys.stdout.write('\0')
148
148
149 def base(ui, repo, node1, node2):
149 def base(ui, repo, node1, node2):
150 """Output common ancestor information"""
150 """Output common ancestor information"""
151 node1 = repo.lookup(node1)
151 node1 = repo.lookup(node1)
152 node2 = repo.lookup(node2)
152 node2 = repo.lookup(node2)
153 n = repo.changelog.ancestor(node1, node2)
153 n = repo.changelog.ancestor(node1, node2)
154 print hg.hex(n)
154 print hg.hex(n)
155
155
156 def catfile(ui, repo, type=None, r=None, **opts):
156 def catfile(ui, repo, type=None, r=None, **opts):
157 """cat a specific revision"""
157 """cat a specific revision"""
158 # in stdin mode, every line except the commit is prefixed with two
158 # in stdin mode, every line except the commit is prefixed with two
159 # spaces. This way the our caller can find the commit without magic
159 # spaces. This way the our caller can find the commit without magic
160 # strings
160 # strings
161 #
161 #
162 prefix = ""
162 prefix = ""
163 if opts['stdin']:
163 if opts['stdin']:
164 try:
164 try:
165 (type, r) = raw_input().split(' ');
165 (type, r) = raw_input().split(' ');
166 prefix = " "
166 prefix = " "
167 except EOFError:
167 except EOFError:
168 return
168 return
169
169
170 else:
170 else:
171 if not type or not r:
171 if not type or not r:
172 ui.warn("cat-file: type or revision not supplied\n")
172 ui.warn("cat-file: type or revision not supplied\n")
173 commands.help_(ui, 'cat-file')
173 commands.help_(ui, 'cat-file')
174
174
175 while r:
175 while r:
176 if type != "commit":
176 if type != "commit":
177 sys.stderr.write("aborting hg cat-file only understands commits\n")
177 sys.stderr.write("aborting hg cat-file only understands commits\n")
178 sys.exit(1);
178 sys.exit(1);
179 n = repo.lookup(r)
179 n = repo.lookup(r)
180 catcommit(repo, n, prefix)
180 catcommit(repo, n, prefix)
181 if opts['stdin']:
181 if opts['stdin']:
182 try:
182 try:
183 (type, r) = raw_input().split(' ');
183 (type, r) = raw_input().split(' ');
184 except EOFError:
184 except EOFError:
185 break
185 break
186 else:
186 else:
187 break
187 break
188
188
189 # git rev-tree is a confusing thing. You can supply a number of
189 # git rev-tree is a confusing thing. You can supply a number of
190 # commit sha1s on the command line, and it walks the commit history
190 # commit sha1s on the command line, and it walks the commit history
191 # telling you which commits are reachable from the supplied ones via
191 # telling you which commits are reachable from the supplied ones via
192 # a bitmask based on arg position.
192 # a bitmask based on arg position.
193 # you can specify a commit to stop at by starting the sha1 with ^
193 # you can specify a commit to stop at by starting the sha1 with ^
194 def revtree(args, repo, full="tree", maxnr=0, parents=False):
194 def revtree(args, repo, full="tree", maxnr=0, parents=False):
195 def chlogwalk():
195 def chlogwalk():
196 ch = repo.changelog
196 ch = repo.changelog
197 count = ch.count()
197 count = ch.count()
198 i = count
198 i = count
199 l = [0] * 100
199 l = [0] * 100
200 chunk = 100
200 chunk = 100
201 while True:
201 while True:
202 if chunk > i:
202 if chunk > i:
203 chunk = i
203 chunk = i
204 i = 0
204 i = 0
205 else:
205 else:
206 i -= chunk
206 i -= chunk
207
207
208 for x in xrange(0, chunk):
208 for x in xrange(0, chunk):
209 if i + x >= count:
209 if i + x >= count:
210 l[chunk - x:] = [0] * (chunk - x)
210 l[chunk - x:] = [0] * (chunk - x)
211 break
211 break
212 if full != None:
212 if full != None:
213 l[x] = ch.read(ch.node(i + x))
213 l[x] = ch.read(ch.node(i + x))
214 else:
214 else:
215 l[x] = 1
215 l[x] = 1
216 for x in xrange(chunk-1, -1, -1):
216 for x in xrange(chunk-1, -1, -1):
217 if l[x] != 0:
217 if l[x] != 0:
218 yield (i + x, full != None and l[x] or None)
218 yield (i + x, full != None and l[x] or None)
219 if i == 0:
219 if i == 0:
220 break
220 break
221
221
222 # calculate and return the reachability bitmask for sha
222 # calculate and return the reachability bitmask for sha
223 def is_reachable(ar, reachable, sha):
223 def is_reachable(ar, reachable, sha):
224 if len(ar) == 0:
224 if len(ar) == 0:
225 return 1
225 return 1
226 mask = 0
226 mask = 0
227 for i in range(len(ar)):
227 for i in range(len(ar)):
228 if sha in reachable[i]:
228 if sha in reachable[i]:
229 mask |= 1 << i
229 mask |= 1 << i
230
230
231 return mask
231 return mask
232
232
233 reachable = []
233 reachable = []
234 stop_sha1 = []
234 stop_sha1 = []
235 want_sha1 = []
235 want_sha1 = []
236 count = 0
236 count = 0
237
237
238 # figure out which commits they are asking for and which ones they
238 # figure out which commits they are asking for and which ones they
239 # want us to stop on
239 # want us to stop on
240 for i in range(len(args)):
240 for i in range(len(args)):
241 if args[i].startswith('^'):
241 if args[i].startswith('^'):
242 s = repo.lookup(args[i][1:])
242 s = repo.lookup(args[i][1:])
243 stop_sha1.append(s)
243 stop_sha1.append(s)
244 want_sha1.append(s)
244 want_sha1.append(s)
245 elif args[i] != 'HEAD':
245 elif args[i] != 'HEAD':
246 want_sha1.append(repo.lookup(args[i]))
246 want_sha1.append(repo.lookup(args[i]))
247
247
248 # calculate the graph for the supplied commits
248 # calculate the graph for the supplied commits
249 for i in range(len(want_sha1)):
249 for i in range(len(want_sha1)):
250 reachable.append({});
250 reachable.append({});
251 n = want_sha1[i];
251 n = want_sha1[i];
252 visit = [n];
252 visit = [n];
253 reachable[i][n] = 1
253 reachable[i][n] = 1
254 while visit:
254 while visit:
255 n = visit.pop(0)
255 n = visit.pop(0)
256 if n in stop_sha1:
256 if n in stop_sha1:
257 continue
257 continue
258 for p in repo.changelog.parents(n):
258 for p in repo.changelog.parents(n):
259 if p not in reachable[i]:
259 if p not in reachable[i]:
260 reachable[i][p] = 1
260 reachable[i][p] = 1
261 visit.append(p)
261 visit.append(p)
262 if p in stop_sha1:
262 if p in stop_sha1:
263 continue
263 continue
264
264
265 # walk the repository looking for commits that are in our
265 # walk the repository looking for commits that are in our
266 # reachability graph
266 # reachability graph
267 #for i in range(repo.changelog.count()-1, -1, -1):
267 #for i in range(repo.changelog.count()-1, -1, -1):
268 for i, changes in chlogwalk():
268 for i, changes in chlogwalk():
269 n = repo.changelog.node(i)
269 n = repo.changelog.node(i)
270 mask = is_reachable(want_sha1, reachable, n)
270 mask = is_reachable(want_sha1, reachable, n)
271 if mask:
271 if mask:
272 parentstr = ""
272 parentstr = ""
273 if parents:
273 if parents:
274 pp = repo.changelog.parents(n)
274 pp = repo.changelog.parents(n)
275 if pp[0] != hg.nullid:
275 if pp[0] != hg.nullid:
276 parentstr += " " + hg.hex(pp[0])
276 parentstr += " " + hg.hex(pp[0])
277 if pp[1] != hg.nullid:
277 if pp[1] != hg.nullid:
278 parentstr += " " + hg.hex(pp[1])
278 parentstr += " " + hg.hex(pp[1])
279 if not full:
279 if not full:
280 print hg.hex(n) + parentstr
280 print hg.hex(n) + parentstr
281 elif full is "commit":
281 elif full is "commit":
282 print hg.hex(n) + parentstr
282 print hg.hex(n) + parentstr
283 catcommit(repo, n, ' ', changes)
283 catcommit(repo, n, ' ', changes)
284 else:
284 else:
285 (p1, p2) = repo.changelog.parents(n)
285 (p1, p2) = repo.changelog.parents(n)
286 (h, h1, h2) = map(hg.hex, (n, p1, p2))
286 (h, h1, h2) = map(hg.hex, (n, p1, p2))
287 (i1, i2) = map(repo.changelog.rev, (p1, p2))
287 (i1, i2) = map(repo.changelog.rev, (p1, p2))
288
288
289 date = changes[2][0]
289 date = changes[2][0]
290 print "%s %s:%s" % (date, h, mask),
290 print "%s %s:%s" % (date, h, mask),
291 mask = is_reachable(want_sha1, reachable, p1)
291 mask = is_reachable(want_sha1, reachable, p1)
292 if i1 != -1 and mask > 0:
292 if i1 != -1 and mask > 0:
293 print "%s:%s " % (h1, mask),
293 print "%s:%s " % (h1, mask),
294 mask = is_reachable(want_sha1, reachable, p2)
294 mask = is_reachable(want_sha1, reachable, p2)
295 if i2 != -1 and mask > 0:
295 if i2 != -1 and mask > 0:
296 print "%s:%s " % (h2, mask),
296 print "%s:%s " % (h2, mask),
297 print ""
297 print ""
298 if maxnr and count >= maxnr:
298 if maxnr and count >= maxnr:
299 break
299 break
300 count += 1
300 count += 1
301
301
302 # git rev-list tries to order things by date, and has the ability to stop
302 # git rev-list tries to order things by date, and has the ability to stop
303 # at a given commit without walking the whole repo. TODO add the stop
303 # at a given commit without walking the whole repo. TODO add the stop
304 # parameter
304 # parameter
305 def revlist(ui, repo, *revs, **opts):
305 def revlist(ui, repo, *revs, **opts):
306 """print revisions"""
306 """print revisions"""
307 if opts['header']:
307 if opts['header']:
308 full = "commit"
308 full = "commit"
309 else:
309 else:
310 full = None
310 full = None
311 copy = [x for x in revs]
311 copy = [x for x in revs]
312 revtree(copy, repo, full, opts['max_count'], opts['parents'])
312 revtree(copy, repo, full, opts['max_count'], opts['parents'])
313
313
314 def view(ui, repo, *etc):
314 def view(ui, repo, *etc):
315 "start interactive history viewer"
315 "start interactive history viewer"
316 os.chdir(repo.root)
316 os.chdir(repo.root)
317 os.system(ui.config("hgk", "path", "hgk") + " " + " ".join(etc))
317 os.system(ui.config("hgk", "path", "hgk") + " " + " ".join(etc))
318
318
319 cmdtable = {
319 cmdtable = {
320 "view": (view, [], 'hg view'),
320 "view": (view, [], 'hg view'),
321 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
321 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
322 ('r', 'recursive', None, 'recursive'),
322 ('r', 'recursive', None, 'recursive'),
323 ('P', 'pretty', None, 'pretty'),
323 ('P', 'pretty', None, 'pretty'),
324 ('s', 'stdin', None, 'stdin'),
324 ('s', 'stdin', None, 'stdin'),
325 ('C', 'copy', None, 'detect copies'),
325 ('C', 'copy', None, 'detect copies'),
326 ('S', 'search', "", 'search')],
326 ('S', 'search', "", 'search')],
327 "hg git-diff-tree [options] node1 node2"),
327 "hg git-diff-tree [options] node1 node2"),
328 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
328 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
329 "hg debug-cat-file [options] type file"),
329 "hg debug-cat-file [options] type file"),
330 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
330 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
331 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
331 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
332 ('t', 'topo-order', None, 'topo-order'),
332 ('t', 'topo-order', None, 'topo-order'),
333 ('p', 'parents', None, 'parents'),
333 ('p', 'parents', None, 'parents'),
334 ('n', 'max-count', 0, 'max-count')],
334 ('n', 'max-count', 0, 'max-count')],
335 "hg debug-rev-list [options] revs"),
335 "hg debug-rev-list [options] revs"),
336 }
336 }
337
337
338 def reposetup(ui, repo):
338 def reposetup(ui, repo):
339 pass
339 pass
@@ -1,207 +1,206
1 import os, tempfile, binascii, errno
1 import os, tempfile, binascii, errno
2 from mercurial import util
2 from mercurial import util
3 from mercurial import node as hgnode
3 from mercurial import node as hgnode
4
4
5 class gpg:
5 class gpg:
6 def __init__(self, path, key=None):
6 def __init__(self, path, key=None):
7 self.path = path
7 self.path = path
8 self.key = (key and " --local-user \"%s\"" % key) or ""
8 self.key = (key and " --local-user \"%s\"" % key) or ""
9
9
10 def sign(self, data):
10 def sign(self, data):
11 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
11 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
12 return util.filter(data, gpgcmd)
12 return util.filter(data, gpgcmd)
13
13
14 def verify(self, data, sig):
14 def verify(self, data, sig):
15 """ returns of the good and bad signatures"""
15 """ returns of the good and bad signatures"""
16 try:
16 try:
17 fd, sigfile = tempfile.mkstemp(prefix="hggpgsig")
17 fd, sigfile = tempfile.mkstemp(prefix="hggpgsig")
18 fp = os.fdopen(fd, 'wb')
18 fp = os.fdopen(fd, 'wb')
19 fp.write(sig)
19 fp.write(sig)
20 fp.close()
20 fp.close()
21 fd, datafile = tempfile.mkstemp(prefix="hggpgdata")
21 fd, datafile = tempfile.mkstemp(prefix="hggpgdata")
22 fp = os.fdopen(fd, 'wb')
22 fp = os.fdopen(fd, 'wb')
23 fp.write(data)
23 fp.write(data)
24 fp.close()
24 fp.close()
25 gpgcmd = "%s --logger-fd 1 --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
25 gpgcmd = "%s --logger-fd 1 --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
26 #gpgcmd = "%s --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
26 #gpgcmd = "%s --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
27 ret = util.filter("", gpgcmd)
27 ret = util.filter("", gpgcmd)
28 except:
28 except:
29 for f in (sigfile, datafile):
29 for f in (sigfile, datafile):
30 try:
30 try:
31 if f: os.unlink(f)
31 if f: os.unlink(f)
32 except: pass
32 except: pass
33 raise
33 raise
34 keys = []
34 keys = []
35 key, fingerprint = None, None
35 key, fingerprint = None, None
36 err = ""
36 err = ""
37 for l in ret.splitlines():
37 for l in ret.splitlines():
38 # see DETAILS in the gnupg documentation
38 # see DETAILS in the gnupg documentation
39 # filter the logger output
39 # filter the logger output
40 if not l.startswith("[GNUPG:]"):
40 if not l.startswith("[GNUPG:]"):
41 continue
41 continue
42 l = l[9:]
42 l = l[9:]
43 if l.startswith("ERRSIG"):
43 if l.startswith("ERRSIG"):
44 err = "error while verifying signature"
44 err = "error while verifying signature"
45 break
45 break
46 elif l.startswith("VALIDSIG"):
46 elif l.startswith("VALIDSIG"):
47 # fingerprint of the primary key
47 # fingerprint of the primary key
48 fingerprint = l.split()[10]
48 fingerprint = l.split()[10]
49 elif (l.startswith("GOODSIG") or
49 elif (l.startswith("GOODSIG") or
50 l.startswith("EXPSIG") or
50 l.startswith("EXPSIG") or
51 l.startswith("EXPKEYSIG") or
51 l.startswith("EXPKEYSIG") or
52 l.startswith("BADSIG")):
52 l.startswith("BADSIG")):
53 if key is not None:
53 if key is not None:
54 keys.append(key + [fingerprint])
54 keys.append(key + [fingerprint])
55 key = l.split(" ", 2)
55 key = l.split(" ", 2)
56 fingerprint = None
56 fingerprint = None
57 if err:
57 if err:
58 return err, []
58 return err, []
59 if key is not None:
59 if key is not None:
60 keys.append(key + [fingerprint])
60 keys.append(key + [fingerprint])
61 return err, keys
61 return err, keys
62
62
63 def newgpg(ui, **opts):
63 def newgpg(ui, **opts):
64 gpgpath = ui.config("gpg", "cmd", "gpg")
64 gpgpath = ui.config("gpg", "cmd", "gpg")
65 gpgkey = opts.get('key')
65 gpgkey = opts.get('key')
66 if not gpgkey:
66 if not gpgkey:
67 gpgkey = ui.config("gpg", "key", None)
67 gpgkey = ui.config("gpg", "key", None)
68 return gpg(gpgpath, gpgkey)
68 return gpg(gpgpath, gpgkey)
69
69
70 def check(ui, repo, rev):
70 def check(ui, repo, rev):
71 """verify all the signatures there may be for a particular revision"""
71 """verify all the signatures there may be for a particular revision"""
72 mygpg = newgpg(ui)
72 mygpg = newgpg(ui)
73 rev = repo.lookup(rev)
73 rev = repo.lookup(rev)
74 hexrev = hgnode.hex(rev)
74 hexrev = hgnode.hex(rev)
75 keys = []
75 keys = []
76
76
77 def addsig(fn, ln, l):
77 def addsig(fn, ln, l):
78 if not l: return
78 if not l: return
79 n, v, sig = l.split(" ", 2)
79 n, v, sig = l.split(" ", 2)
80 if n == hexrev:
80 if n == hexrev:
81 data = node2txt(repo, rev, v)
81 data = node2txt(repo, rev, v)
82 sig = binascii.a2b_base64(sig)
82 sig = binascii.a2b_base64(sig)
83 err, k = mygpg.verify(data, sig)
83 err, k = mygpg.verify(data, sig)
84 if not err:
84 if not err:
85 keys.append((k, fn, ln))
85 keys.append((k, fn, ln))
86 else:
86 else:
87 ui.warn("%s:%d %s\n" % (fn, ln , err))
87 ui.warn("%s:%d %s\n" % (fn, ln , err))
88
88
89 fl = repo.file(".hgsigs")
89 fl = repo.file(".hgsigs")
90 h = fl.heads()
90 h = fl.heads()
91 h.reverse()
91 h.reverse()
92 # read the heads
92 # read the heads
93 for r in h:
93 for r in h:
94 ln = 1
94 ln = 1
95 for l in fl.read(r).splitlines():
95 for l in fl.read(r).splitlines():
96 addsig(".hgsigs|%s" % hgnode.short(r), ln, l)
96 addsig(".hgsigs|%s" % hgnode.short(r), ln, l)
97 ln +=1
97 ln +=1
98 try:
98 try:
99 # read local signatures
99 # read local signatures
100 ln = 1
100 ln = 1
101 f = repo.opener("localsigs")
101 f = repo.opener("localsigs")
102 for l in f:
102 for l in f:
103 addsig("localsigs", ln, l)
103 addsig("localsigs", ln, l)
104 ln +=1
104 ln +=1
105 except IOError:
105 except IOError:
106 pass
106 pass
107
107
108 if not keys:
108 if not keys:
109 ui.write("%s not signed\n" % hgnode.short(rev))
109 ui.write("%s not signed\n" % hgnode.short(rev))
110 return
110 return
111 valid = []
111 valid = []
112 # warn for expired key and/or sigs
112 # warn for expired key and/or sigs
113 for k, fn, ln in keys:
113 for k, fn, ln in keys:
114 prefix = "%s:%d" % (fn, ln)
114 prefix = "%s:%d" % (fn, ln)
115 for key in k:
115 for key in k:
116 if key[0] == "BADSIG":
116 if key[0] == "BADSIG":
117 ui.write("%s Bad signature from \"%s\"\n" % (prefix, key[2]))
117 ui.write("%s Bad signature from \"%s\"\n" % (prefix, key[2]))
118 continue
118 continue
119 if key[0] == "EXPSIG":
119 if key[0] == "EXPSIG":
120 ui.write("%s Note: Signature has expired"
120 ui.write("%s Note: Signature has expired"
121 " (signed by: \"%s\")\n" % (prefix, key[2]))
121 " (signed by: \"%s\")\n" % (prefix, key[2]))
122 elif key[0] == "EXPKEYSIG":
122 elif key[0] == "EXPKEYSIG":
123 ui.write("%s Note: This key has expired"
123 ui.write("%s Note: This key has expired"
124 " (signed by: \"%s\")\n" % (prefix, key[2]))
124 " (signed by: \"%s\")\n" % (prefix, key[2]))
125 valid.append((key[1], key[2], key[3]))
125 valid.append((key[1], key[2], key[3]))
126 # print summary
126 # print summary
127 ui.write("%s is signed by:\n" % hgnode.short(rev))
127 ui.write("%s is signed by:\n" % hgnode.short(rev))
128 for keyid, user, fingerprint in valid:
128 for keyid, user, fingerprint in valid:
129 role = getrole(ui, fingerprint)
129 role = getrole(ui, fingerprint)
130 ui.write(" %s (%s)\n" % (user, role))
130 ui.write(" %s (%s)\n" % (user, role))
131
131
132 def getrole(ui, fingerprint):
132 def getrole(ui, fingerprint):
133 return ui.config("gpg", fingerprint, "no role defined")
133 return ui.config("gpg", fingerprint, "no role defined")
134
134
135 def sign(ui, repo, *revs, **opts):
135 def sign(ui, repo, *revs, **opts):
136 """add a signature for the current tip or a given revision"""
136 """add a signature for the current tip or a given revision"""
137 mygpg = newgpg(ui, **opts)
137 mygpg = newgpg(ui, **opts)
138 sigver = "0"
138 sigver = "0"
139 sigmessage = ""
139 sigmessage = ""
140 if revs:
140 if revs:
141 nodes = [repo.lookup(n) for n in revs]
141 nodes = [repo.lookup(n) for n in revs]
142 else:
142 else:
143 nodes = [repo.changelog.tip()]
143 nodes = [repo.changelog.tip()]
144
144
145 for n in nodes:
145 for n in nodes:
146 hexnode = hgnode.hex(n)
146 hexnode = hgnode.hex(n)
147 ui.write("Signing %d:%s\n" % (repo.changelog.rev(n),
147 ui.write("Signing %d:%s\n" % (repo.changelog.rev(n),
148 hgnode.short(n)))
148 hgnode.short(n)))
149 # build data
149 # build data
150 data = node2txt(repo, n, sigver)
150 data = node2txt(repo, n, sigver)
151 sig = mygpg.sign(data)
151 sig = mygpg.sign(data)
152 if not sig:
152 if not sig:
153 raise util.Abort("Error while signing")
153 raise util.Abort("Error while signing")
154 sig = binascii.b2a_base64(sig)
154 sig = binascii.b2a_base64(sig)
155 sig = sig.replace("\n", "")
155 sig = sig.replace("\n", "")
156 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
156 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
157
157
158 # write it
158 # write it
159 if opts['local']:
159 if opts['local']:
160 repo.opener("localsigs", "ab").write(sigmessage)
160 repo.opener("localsigs", "ab").write(sigmessage)
161 return
161 return
162
162
163 (c, a, d, u) = repo.changes()
163 for x in repo.changes():
164 for x in (c, a, d, u):
165 if ".hgsigs" in x and not opts["force"]:
164 if ".hgsigs" in x and not opts["force"]:
166 raise util.Abort("working copy of .hgsigs is changed "
165 raise util.Abort("working copy of .hgsigs is changed "
167 "(please commit .hgsigs manually"
166 "(please commit .hgsigs manually"
168 "or use --force)")
167 "or use --force)")
169
168
170 repo.wfile(".hgsigs", "ab").write(sigmessage)
169 repo.wfile(".hgsigs", "ab").write(sigmessage)
171
170
172 if repo.dirstate.state(".hgsigs") == '?':
171 if repo.dirstate.state(".hgsigs") == '?':
173 repo.add([".hgsigs"])
172 repo.add([".hgsigs"])
174
173
175 if opts["no_commit"]:
174 if opts["no_commit"]:
176 return
175 return
177
176
178 message = opts['message']
177 message = opts['message']
179 if not message:
178 if not message:
180 message = "\n".join(["Added signature for changeset %s" % hgnode.hex(n)
179 message = "\n".join(["Added signature for changeset %s" % hgnode.hex(n)
181 for n in nodes])
180 for n in nodes])
182 try:
181 try:
183 repo.commit([".hgsigs"], message, opts['user'], opts['date'])
182 repo.commit([".hgsigs"], message, opts['user'], opts['date'])
184 except ValueError, inst:
183 except ValueError, inst:
185 raise util.Abort(str(inst))
184 raise util.Abort(str(inst))
186
185
187 def node2txt(repo, node, ver):
186 def node2txt(repo, node, ver):
188 """map a manifest into some text"""
187 """map a manifest into some text"""
189 if ver == "0":
188 if ver == "0":
190 return "%s\n" % hgnode.hex(node)
189 return "%s\n" % hgnode.hex(node)
191 else:
190 else:
192 util.Abort("unknown signature version")
191 util.Abort("unknown signature version")
193
192
194 cmdtable = {
193 cmdtable = {
195 "sign":
194 "sign":
196 (sign,
195 (sign,
197 [('l', 'local', None, "make the signature local"),
196 [('l', 'local', None, "make the signature local"),
198 ('f', 'force', None, "sign even if the sigfile is modified"),
197 ('f', 'force', None, "sign even if the sigfile is modified"),
199 ('', 'no-commit', None, "do not commit the sigfile after signing"),
198 ('', 'no-commit', None, "do not commit the sigfile after signing"),
200 ('m', 'message', "", "commit message"),
199 ('m', 'message', "", "commit message"),
201 ('d', 'date', "", "date code"),
200 ('d', 'date', "", "date code"),
202 ('u', 'user', "", "user"),
201 ('u', 'user', "", "user"),
203 ('k', 'key', "", "the key id to sign with")],
202 ('k', 'key', "", "the key id to sign with")],
204 "hg sign [OPTION]... REVISIONS"),
203 "hg sign [OPTION]... REVISIONS"),
205 "sigcheck": (check, [], 'hg sigcheck REVISION')
204 "sigcheck": (check, [], 'hg sigcheck REVISION')
206 }
205 }
207
206
@@ -1,2755 +1,2757
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
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 from demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 demandload(globals(), "fancyopts ui hg util lock revlog")
12 demandload(globals(), "fancyopts ui hg util lock revlog")
13 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
13 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
14 demandload(globals(), "errno socket version struct atexit sets bz2")
14 demandload(globals(), "errno socket version struct atexit sets bz2")
15
15
16 class UnknownCommand(Exception):
16 class UnknownCommand(Exception):
17 """Exception raised if command is not in the command table."""
17 """Exception raised if command is not in the command table."""
18 class AmbiguousCommand(Exception):
18 class AmbiguousCommand(Exception):
19 """Exception raised if command shortcut matches more than one command."""
19 """Exception raised if command shortcut matches more than one command."""
20
20
21 def filterfiles(filters, files):
21 def filterfiles(filters, files):
22 l = [x for x in files if x in filters]
22 l = [x for x in files if x in filters]
23
23
24 for t in filters:
24 for t in filters:
25 if t and t[-1] != "/":
25 if t and t[-1] != "/":
26 t += "/"
26 t += "/"
27 l += [x for x in files if x.startswith(t)]
27 l += [x for x in files if x.startswith(t)]
28 return l
28 return l
29
29
30 def relpath(repo, args):
30 def relpath(repo, args):
31 cwd = repo.getcwd()
31 cwd = repo.getcwd()
32 if cwd:
32 if cwd:
33 return [util.normpath(os.path.join(cwd, x)) for x in args]
33 return [util.normpath(os.path.join(cwd, x)) for x in args]
34 return args
34 return args
35
35
36 def matchpats(repo, pats=[], opts={}, head=''):
36 def matchpats(repo, pats=[], opts={}, head=''):
37 cwd = repo.getcwd()
37 cwd = repo.getcwd()
38 if not pats and cwd:
38 if not pats and cwd:
39 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
39 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
40 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
40 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
41 cwd = ''
41 cwd = ''
42 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
42 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
43 opts.get('exclude'), head) + (cwd,)
43 opts.get('exclude'), head) + (cwd,)
44
44
45 def makewalk(repo, pats, opts, node=None, head=''):
45 def makewalk(repo, pats, opts, node=None, head=''):
46 files, matchfn, anypats, cwd = matchpats(repo, pats, opts, head)
46 files, matchfn, anypats, cwd = matchpats(repo, pats, opts, head)
47 exact = dict(zip(files, files))
47 exact = dict(zip(files, files))
48 def walk():
48 def walk():
49 for src, fn in repo.walk(node=node, files=files, match=matchfn):
49 for src, fn in repo.walk(node=node, files=files, match=matchfn):
50 yield src, fn, util.pathto(cwd, fn), fn in exact
50 yield src, fn, util.pathto(cwd, fn), fn in exact
51 return files, matchfn, walk()
51 return files, matchfn, walk()
52
52
53 def walk(repo, pats, opts, node=None, head=''):
53 def walk(repo, pats, opts, node=None, head=''):
54 files, matchfn, results = makewalk(repo, pats, opts, node, head)
54 files, matchfn, results = makewalk(repo, pats, opts, node, head)
55 for r in results:
55 for r in results:
56 yield r
56 yield r
57
57
58 def walkchangerevs(ui, repo, pats, opts):
58 def walkchangerevs(ui, repo, pats, opts):
59 '''Iterate over files and the revs they changed in.
59 '''Iterate over files and the revs they changed in.
60
60
61 Callers most commonly need to iterate backwards over the history
61 Callers most commonly need to iterate backwards over the history
62 it is interested in. Doing so has awful (quadratic-looking)
62 it is interested in. Doing so has awful (quadratic-looking)
63 performance, so we use iterators in a "windowed" way.
63 performance, so we use iterators in a "windowed" way.
64
64
65 We walk a window of revisions in the desired order. Within the
65 We walk a window of revisions in the desired order. Within the
66 window, we first walk forwards to gather data, then in the desired
66 window, we first walk forwards to gather data, then in the desired
67 order (usually backwards) to display it.
67 order (usually backwards) to display it.
68
68
69 This function returns an (iterator, getchange, matchfn) tuple. The
69 This function returns an (iterator, getchange, matchfn) tuple. The
70 getchange function returns the changelog entry for a numeric
70 getchange function returns the changelog entry for a numeric
71 revision. The iterator yields 3-tuples. They will be of one of
71 revision. The iterator yields 3-tuples. They will be of one of
72 the following forms:
72 the following forms:
73
73
74 "window", incrementing, lastrev: stepping through a window,
74 "window", incrementing, lastrev: stepping through a window,
75 positive if walking forwards through revs, last rev in the
75 positive if walking forwards through revs, last rev in the
76 sequence iterated over - use to reset state for the current window
76 sequence iterated over - use to reset state for the current window
77
77
78 "add", rev, fns: out-of-order traversal of the given file names
78 "add", rev, fns: out-of-order traversal of the given file names
79 fns, which changed during revision rev - use to gather data for
79 fns, which changed during revision rev - use to gather data for
80 possible display
80 possible display
81
81
82 "iter", rev, None: in-order traversal of the revs earlier iterated
82 "iter", rev, None: in-order traversal of the revs earlier iterated
83 over with "add" - use to display data'''
83 over with "add" - use to display data'''
84
84
85 files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
85 files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
86
86
87 if repo.changelog.count() == 0:
87 if repo.changelog.count() == 0:
88 return [], False, matchfn
88 return [], False, matchfn
89
89
90 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
90 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
91 wanted = {}
91 wanted = {}
92 slowpath = anypats
92 slowpath = anypats
93 window = 300
93 window = 300
94 fncache = {}
94 fncache = {}
95
95
96 chcache = {}
96 chcache = {}
97 def getchange(rev):
97 def getchange(rev):
98 ch = chcache.get(rev)
98 ch = chcache.get(rev)
99 if ch is None:
99 if ch is None:
100 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
100 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
101 return ch
101 return ch
102
102
103 if not slowpath and not files:
103 if not slowpath and not files:
104 # No files, no patterns. Display all revs.
104 # No files, no patterns. Display all revs.
105 wanted = dict(zip(revs, revs))
105 wanted = dict(zip(revs, revs))
106 if not slowpath:
106 if not slowpath:
107 # Only files, no patterns. Check the history of each file.
107 # Only files, no patterns. Check the history of each file.
108 def filerevgen(filelog):
108 def filerevgen(filelog):
109 for i in xrange(filelog.count() - 1, -1, -window):
109 for i in xrange(filelog.count() - 1, -1, -window):
110 revs = []
110 revs = []
111 for j in xrange(max(0, i - window), i + 1):
111 for j in xrange(max(0, i - window), i + 1):
112 revs.append(filelog.linkrev(filelog.node(j)))
112 revs.append(filelog.linkrev(filelog.node(j)))
113 revs.reverse()
113 revs.reverse()
114 for rev in revs:
114 for rev in revs:
115 yield rev
115 yield rev
116
116
117 minrev, maxrev = min(revs), max(revs)
117 minrev, maxrev = min(revs), max(revs)
118 for file in files:
118 for file in files:
119 filelog = repo.file(file)
119 filelog = repo.file(file)
120 # A zero count may be a directory or deleted file, so
120 # A zero count may be a directory or deleted file, so
121 # try to find matching entries on the slow path.
121 # try to find matching entries on the slow path.
122 if filelog.count() == 0:
122 if filelog.count() == 0:
123 slowpath = True
123 slowpath = True
124 break
124 break
125 for rev in filerevgen(filelog):
125 for rev in filerevgen(filelog):
126 if rev <= maxrev:
126 if rev <= maxrev:
127 if rev < minrev:
127 if rev < minrev:
128 break
128 break
129 fncache.setdefault(rev, [])
129 fncache.setdefault(rev, [])
130 fncache[rev].append(file)
130 fncache[rev].append(file)
131 wanted[rev] = 1
131 wanted[rev] = 1
132 if slowpath:
132 if slowpath:
133 # The slow path checks files modified in every changeset.
133 # The slow path checks files modified in every changeset.
134 def changerevgen():
134 def changerevgen():
135 for i in xrange(repo.changelog.count() - 1, -1, -window):
135 for i in xrange(repo.changelog.count() - 1, -1, -window):
136 for j in xrange(max(0, i - window), i + 1):
136 for j in xrange(max(0, i - window), i + 1):
137 yield j, getchange(j)[3]
137 yield j, getchange(j)[3]
138
138
139 for rev, changefiles in changerevgen():
139 for rev, changefiles in changerevgen():
140 matches = filter(matchfn, changefiles)
140 matches = filter(matchfn, changefiles)
141 if matches:
141 if matches:
142 fncache[rev] = matches
142 fncache[rev] = matches
143 wanted[rev] = 1
143 wanted[rev] = 1
144
144
145 def iterate():
145 def iterate():
146 for i in xrange(0, len(revs), window):
146 for i in xrange(0, len(revs), window):
147 yield 'window', revs[0] < revs[-1], revs[-1]
147 yield 'window', revs[0] < revs[-1], revs[-1]
148 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
148 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
149 if rev in wanted]
149 if rev in wanted]
150 srevs = list(nrevs)
150 srevs = list(nrevs)
151 srevs.sort()
151 srevs.sort()
152 for rev in srevs:
152 for rev in srevs:
153 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
153 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
154 yield 'add', rev, fns
154 yield 'add', rev, fns
155 for rev in nrevs:
155 for rev in nrevs:
156 yield 'iter', rev, None
156 yield 'iter', rev, None
157 return iterate(), getchange, matchfn
157 return iterate(), getchange, matchfn
158
158
159 revrangesep = ':'
159 revrangesep = ':'
160
160
161 def revrange(ui, repo, revs, revlog=None):
161 def revrange(ui, repo, revs, revlog=None):
162 """Yield revision as strings from a list of revision specifications."""
162 """Yield revision as strings from a list of revision specifications."""
163 if revlog is None:
163 if revlog is None:
164 revlog = repo.changelog
164 revlog = repo.changelog
165 revcount = revlog.count()
165 revcount = revlog.count()
166 def fix(val, defval):
166 def fix(val, defval):
167 if not val:
167 if not val:
168 return defval
168 return defval
169 try:
169 try:
170 num = int(val)
170 num = int(val)
171 if str(num) != val:
171 if str(num) != val:
172 raise ValueError
172 raise ValueError
173 if num < 0:
173 if num < 0:
174 num += revcount
174 num += revcount
175 if num < 0:
175 if num < 0:
176 num = 0
176 num = 0
177 elif num >= revcount:
177 elif num >= revcount:
178 raise ValueError
178 raise ValueError
179 except ValueError:
179 except ValueError:
180 try:
180 try:
181 num = repo.changelog.rev(repo.lookup(val))
181 num = repo.changelog.rev(repo.lookup(val))
182 except KeyError:
182 except KeyError:
183 try:
183 try:
184 num = revlog.rev(revlog.lookup(val))
184 num = revlog.rev(revlog.lookup(val))
185 except KeyError:
185 except KeyError:
186 raise util.Abort(_('invalid revision identifier %s'), val)
186 raise util.Abort(_('invalid revision identifier %s'), val)
187 return num
187 return num
188 seen = {}
188 seen = {}
189 for spec in revs:
189 for spec in revs:
190 if spec.find(revrangesep) >= 0:
190 if spec.find(revrangesep) >= 0:
191 start, end = spec.split(revrangesep, 1)
191 start, end = spec.split(revrangesep, 1)
192 start = fix(start, 0)
192 start = fix(start, 0)
193 end = fix(end, revcount - 1)
193 end = fix(end, revcount - 1)
194 step = start > end and -1 or 1
194 step = start > end and -1 or 1
195 for rev in xrange(start, end+step, step):
195 for rev in xrange(start, end+step, step):
196 if rev in seen:
196 if rev in seen:
197 continue
197 continue
198 seen[rev] = 1
198 seen[rev] = 1
199 yield str(rev)
199 yield str(rev)
200 else:
200 else:
201 rev = fix(spec, None)
201 rev = fix(spec, None)
202 if rev in seen:
202 if rev in seen:
203 continue
203 continue
204 seen[rev] = 1
204 seen[rev] = 1
205 yield str(rev)
205 yield str(rev)
206
206
207 def make_filename(repo, r, pat, node=None,
207 def make_filename(repo, r, pat, node=None,
208 total=None, seqno=None, revwidth=None, pathname=None):
208 total=None, seqno=None, revwidth=None, pathname=None):
209 node_expander = {
209 node_expander = {
210 'H': lambda: hex(node),
210 'H': lambda: hex(node),
211 'R': lambda: str(r.rev(node)),
211 'R': lambda: str(r.rev(node)),
212 'h': lambda: short(node),
212 'h': lambda: short(node),
213 }
213 }
214 expander = {
214 expander = {
215 '%': lambda: '%',
215 '%': lambda: '%',
216 'b': lambda: os.path.basename(repo.root),
216 'b': lambda: os.path.basename(repo.root),
217 }
217 }
218
218
219 try:
219 try:
220 if node:
220 if node:
221 expander.update(node_expander)
221 expander.update(node_expander)
222 if node and revwidth is not None:
222 if node and revwidth is not None:
223 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
223 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
224 if total is not None:
224 if total is not None:
225 expander['N'] = lambda: str(total)
225 expander['N'] = lambda: str(total)
226 if seqno is not None:
226 if seqno is not None:
227 expander['n'] = lambda: str(seqno)
227 expander['n'] = lambda: str(seqno)
228 if total is not None and seqno is not None:
228 if total is not None and seqno is not None:
229 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
229 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
230 if pathname is not None:
230 if pathname is not None:
231 expander['s'] = lambda: os.path.basename(pathname)
231 expander['s'] = lambda: os.path.basename(pathname)
232 expander['d'] = lambda: os.path.dirname(pathname) or '.'
232 expander['d'] = lambda: os.path.dirname(pathname) or '.'
233 expander['p'] = lambda: pathname
233 expander['p'] = lambda: pathname
234
234
235 newname = []
235 newname = []
236 patlen = len(pat)
236 patlen = len(pat)
237 i = 0
237 i = 0
238 while i < patlen:
238 while i < patlen:
239 c = pat[i]
239 c = pat[i]
240 if c == '%':
240 if c == '%':
241 i += 1
241 i += 1
242 c = pat[i]
242 c = pat[i]
243 c = expander[c]()
243 c = expander[c]()
244 newname.append(c)
244 newname.append(c)
245 i += 1
245 i += 1
246 return ''.join(newname)
246 return ''.join(newname)
247 except KeyError, inst:
247 except KeyError, inst:
248 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
248 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
249 inst.args[0])
249 inst.args[0])
250
250
251 def make_file(repo, r, pat, node=None,
251 def make_file(repo, r, pat, node=None,
252 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
252 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
253 if not pat or pat == '-':
253 if not pat or pat == '-':
254 return 'w' in mode and sys.stdout or sys.stdin
254 return 'w' in mode and sys.stdout or sys.stdin
255 if hasattr(pat, 'write') and 'w' in mode:
255 if hasattr(pat, 'write') and 'w' in mode:
256 return pat
256 return pat
257 if hasattr(pat, 'read') and 'r' in mode:
257 if hasattr(pat, 'read') and 'r' in mode:
258 return pat
258 return pat
259 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
259 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
260 pathname),
260 pathname),
261 mode)
261 mode)
262
262
263 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
263 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
264 changes=None, text=False):
264 changes=None, text=False):
265 if not changes:
265 if not changes:
266 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
266 changes = repo.changes(node1, node2, files, match=match)
267 else:
267 modified, added, removed, unknown = changes
268 (c, a, d, u) = changes
269 if files:
268 if files:
270 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
269 modified, added, removed = map(lambda x: filterfiles(x, files),
270 (modified, added, removed))
271
271
272 if not c and not a and not d:
272 if not modified and not added and not removed:
273 return
273 return
274
274
275 if node2:
275 if node2:
276 change = repo.changelog.read(node2)
276 change = repo.changelog.read(node2)
277 mmap2 = repo.manifest.read(change[0])
277 mmap2 = repo.manifest.read(change[0])
278 date2 = util.datestr(change[2])
278 date2 = util.datestr(change[2])
279 def read(f):
279 def read(f):
280 return repo.file(f).read(mmap2[f])
280 return repo.file(f).read(mmap2[f])
281 else:
281 else:
282 date2 = util.datestr()
282 date2 = util.datestr()
283 if not node1:
283 if not node1:
284 node1 = repo.dirstate.parents()[0]
284 node1 = repo.dirstate.parents()[0]
285 def read(f):
285 def read(f):
286 return repo.wfile(f).read()
286 return repo.wfile(f).read()
287
287
288 if ui.quiet:
288 if ui.quiet:
289 r = None
289 r = None
290 else:
290 else:
291 hexfunc = ui.verbose and hex or short
291 hexfunc = ui.verbose and hex or short
292 r = [hexfunc(node) for node in [node1, node2] if node]
292 r = [hexfunc(node) for node in [node1, node2] if node]
293
293
294 change = repo.changelog.read(node1)
294 change = repo.changelog.read(node1)
295 mmap = repo.manifest.read(change[0])
295 mmap = repo.manifest.read(change[0])
296 date1 = util.datestr(change[2])
296 date1 = util.datestr(change[2])
297
297
298 for f in c:
298 for f in modified:
299 to = None
299 to = None
300 if f in mmap:
300 if f in mmap:
301 to = repo.file(f).read(mmap[f])
301 to = repo.file(f).read(mmap[f])
302 tn = read(f)
302 tn = read(f)
303 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
303 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
304 for f in a:
304 for f in added:
305 to = None
305 to = None
306 tn = read(f)
306 tn = read(f)
307 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
307 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
308 for f in d:
308 for f in removed:
309 to = repo.file(f).read(mmap[f])
309 to = repo.file(f).read(mmap[f])
310 tn = None
310 tn = None
311 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
311 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
312
312
313 def trimuser(ui, name, rev, revcache):
313 def trimuser(ui, name, rev, revcache):
314 """trim the name of the user who committed a change"""
314 """trim the name of the user who committed a change"""
315 user = revcache.get(rev)
315 user = revcache.get(rev)
316 if user is None:
316 if user is None:
317 user = revcache[rev] = ui.shortuser(name)
317 user = revcache[rev] = ui.shortuser(name)
318 return user
318 return user
319
319
320 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
320 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
321 """show a single changeset or file revision"""
321 """show a single changeset or file revision"""
322 log = repo.changelog
322 log = repo.changelog
323 if changenode is None:
323 if changenode is None:
324 changenode = log.node(rev)
324 changenode = log.node(rev)
325 elif not rev:
325 elif not rev:
326 rev = log.rev(changenode)
326 rev = log.rev(changenode)
327
327
328 if ui.quiet:
328 if ui.quiet:
329 ui.write("%d:%s\n" % (rev, short(changenode)))
329 ui.write("%d:%s\n" % (rev, short(changenode)))
330 return
330 return
331
331
332 changes = log.read(changenode)
332 changes = log.read(changenode)
333 date = util.datestr(changes[2])
333 date = util.datestr(changes[2])
334
334
335 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
335 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
336 for p in log.parents(changenode)
336 for p in log.parents(changenode)
337 if ui.debugflag or p != nullid]
337 if ui.debugflag or p != nullid]
338 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
338 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
339 parents = []
339 parents = []
340
340
341 if ui.verbose:
341 if ui.verbose:
342 ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
342 ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
343 else:
343 else:
344 ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
344 ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
345
345
346 for tag in repo.nodetags(changenode):
346 for tag in repo.nodetags(changenode):
347 ui.status(_("tag: %s\n") % tag)
347 ui.status(_("tag: %s\n") % tag)
348 for parent in parents:
348 for parent in parents:
349 ui.write(_("parent: %d:%s\n") % parent)
349 ui.write(_("parent: %d:%s\n") % parent)
350
350
351 if brinfo and changenode in brinfo:
351 if brinfo and changenode in brinfo:
352 br = brinfo[changenode]
352 br = brinfo[changenode]
353 ui.write(_("branch: %s\n") % " ".join(br))
353 ui.write(_("branch: %s\n") % " ".join(br))
354
354
355 ui.debug(_("manifest: %d:%s\n") % (repo.manifest.rev(changes[0]),
355 ui.debug(_("manifest: %d:%s\n") % (repo.manifest.rev(changes[0]),
356 hex(changes[0])))
356 hex(changes[0])))
357 ui.status(_("user: %s\n") % changes[1])
357 ui.status(_("user: %s\n") % changes[1])
358 ui.status(_("date: %s\n") % date)
358 ui.status(_("date: %s\n") % date)
359
359
360 if ui.debugflag:
360 if ui.debugflag:
361 files = repo.changes(log.parents(changenode)[0], changenode)
361 files = repo.changes(log.parents(changenode)[0], changenode)
362 for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
362 for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
363 if value:
363 if value:
364 ui.note("%-12s %s\n" % (key, " ".join(value)))
364 ui.note("%-12s %s\n" % (key, " ".join(value)))
365 else:
365 else:
366 ui.note(_("files: %s\n") % " ".join(changes[3]))
366 ui.note(_("files: %s\n") % " ".join(changes[3]))
367
367
368 description = changes[4].strip()
368 description = changes[4].strip()
369 if description:
369 if description:
370 if ui.verbose:
370 if ui.verbose:
371 ui.status(_("description:\n"))
371 ui.status(_("description:\n"))
372 ui.status(description)
372 ui.status(description)
373 ui.status("\n\n")
373 ui.status("\n\n")
374 else:
374 else:
375 ui.status(_("summary: %s\n") % description.splitlines()[0])
375 ui.status(_("summary: %s\n") % description.splitlines()[0])
376 ui.status("\n")
376 ui.status("\n")
377
377
378 def show_version(ui):
378 def show_version(ui):
379 """output version and copyright information"""
379 """output version and copyright information"""
380 ui.write(_("Mercurial Distributed SCM (version %s)\n")
380 ui.write(_("Mercurial Distributed SCM (version %s)\n")
381 % version.get_version())
381 % version.get_version())
382 ui.status(_(
382 ui.status(_(
383 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
383 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
384 "This is free software; see the source for copying conditions. "
384 "This is free software; see the source for copying conditions. "
385 "There is NO\nwarranty; "
385 "There is NO\nwarranty; "
386 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
386 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
387 ))
387 ))
388
388
389 def help_(ui, cmd=None, with_version=False):
389 def help_(ui, cmd=None, with_version=False):
390 """show help for a given command or all commands"""
390 """show help for a given command or all commands"""
391 option_lists = []
391 option_lists = []
392 if cmd and cmd != 'shortlist':
392 if cmd and cmd != 'shortlist':
393 if with_version:
393 if with_version:
394 show_version(ui)
394 show_version(ui)
395 ui.write('\n')
395 ui.write('\n')
396 aliases, i = find(cmd)
396 aliases, i = find(cmd)
397 # synopsis
397 # synopsis
398 ui.write("%s\n\n" % i[2])
398 ui.write("%s\n\n" % i[2])
399
399
400 # description
400 # description
401 doc = i[0].__doc__
401 doc = i[0].__doc__
402 if ui.quiet:
402 if ui.quiet:
403 doc = doc.splitlines(0)[0]
403 doc = doc.splitlines(0)[0]
404 ui.write("%s\n" % doc.rstrip())
404 ui.write("%s\n" % doc.rstrip())
405
405
406 if not ui.quiet:
406 if not ui.quiet:
407 # aliases
407 # aliases
408 if len(aliases) > 1:
408 if len(aliases) > 1:
409 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
409 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
410
410
411 # options
411 # options
412 if i[1]:
412 if i[1]:
413 option_lists.append(("options", i[1]))
413 option_lists.append(("options", i[1]))
414
414
415 else:
415 else:
416 # program name
416 # program name
417 if ui.verbose or with_version:
417 if ui.verbose or with_version:
418 show_version(ui)
418 show_version(ui)
419 else:
419 else:
420 ui.status(_("Mercurial Distributed SCM\n"))
420 ui.status(_("Mercurial Distributed SCM\n"))
421 ui.status('\n')
421 ui.status('\n')
422
422
423 # list of commands
423 # list of commands
424 if cmd == "shortlist":
424 if cmd == "shortlist":
425 ui.status(_('basic commands (use "hg help" '
425 ui.status(_('basic commands (use "hg help" '
426 'for the full list or option "-v" for details):\n\n'))
426 'for the full list or option "-v" for details):\n\n'))
427 elif ui.verbose:
427 elif ui.verbose:
428 ui.status(_('list of commands:\n\n'))
428 ui.status(_('list of commands:\n\n'))
429 else:
429 else:
430 ui.status(_('list of commands (use "hg help -v" '
430 ui.status(_('list of commands (use "hg help -v" '
431 'to show aliases and global options):\n\n'))
431 'to show aliases and global options):\n\n'))
432
432
433 h = {}
433 h = {}
434 cmds = {}
434 cmds = {}
435 for c, e in table.items():
435 for c, e in table.items():
436 f = c.split("|")[0]
436 f = c.split("|")[0]
437 if cmd == "shortlist" and not f.startswith("^"):
437 if cmd == "shortlist" and not f.startswith("^"):
438 continue
438 continue
439 f = f.lstrip("^")
439 f = f.lstrip("^")
440 if not ui.debugflag and f.startswith("debug"):
440 if not ui.debugflag and f.startswith("debug"):
441 continue
441 continue
442 d = ""
442 d = ""
443 if e[0].__doc__:
443 if e[0].__doc__:
444 d = e[0].__doc__.splitlines(0)[0].rstrip()
444 d = e[0].__doc__.splitlines(0)[0].rstrip()
445 h[f] = d
445 h[f] = d
446 cmds[f] = c.lstrip("^")
446 cmds[f] = c.lstrip("^")
447
447
448 fns = h.keys()
448 fns = h.keys()
449 fns.sort()
449 fns.sort()
450 m = max(map(len, fns))
450 m = max(map(len, fns))
451 for f in fns:
451 for f in fns:
452 if ui.verbose:
452 if ui.verbose:
453 commands = cmds[f].replace("|",", ")
453 commands = cmds[f].replace("|",", ")
454 ui.write(" %s:\n %s\n"%(commands, h[f]))
454 ui.write(" %s:\n %s\n"%(commands, h[f]))
455 else:
455 else:
456 ui.write(' %-*s %s\n' % (m, f, h[f]))
456 ui.write(' %-*s %s\n' % (m, f, h[f]))
457
457
458 # global options
458 # global options
459 if ui.verbose:
459 if ui.verbose:
460 option_lists.append(("global options", globalopts))
460 option_lists.append(("global options", globalopts))
461
461
462 # list all option lists
462 # list all option lists
463 opt_output = []
463 opt_output = []
464 for title, options in option_lists:
464 for title, options in option_lists:
465 opt_output.append(("\n%s:\n" % title, None))
465 opt_output.append(("\n%s:\n" % title, None))
466 for shortopt, longopt, default, desc in options:
466 for shortopt, longopt, default, desc in options:
467 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
467 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
468 longopt and " --%s" % longopt),
468 longopt and " --%s" % longopt),
469 "%s%s" % (desc,
469 "%s%s" % (desc,
470 default
470 default
471 and _(" (default: %s)") % default
471 and _(" (default: %s)") % default
472 or "")))
472 or "")))
473
473
474 if opt_output:
474 if opt_output:
475 opts_len = max([len(line[0]) for line in opt_output if line[1]])
475 opts_len = max([len(line[0]) for line in opt_output if line[1]])
476 for first, second in opt_output:
476 for first, second in opt_output:
477 if second:
477 if second:
478 ui.write(" %-*s %s\n" % (opts_len, first, second))
478 ui.write(" %-*s %s\n" % (opts_len, first, second))
479 else:
479 else:
480 ui.write("%s\n" % first)
480 ui.write("%s\n" % first)
481
481
482 # Commands start here, listed alphabetically
482 # Commands start here, listed alphabetically
483
483
484 def add(ui, repo, *pats, **opts):
484 def add(ui, repo, *pats, **opts):
485 """add the specified files on the next commit
485 """add the specified files on the next commit
486
486
487 Schedule files to be version controlled and added to the repository.
487 Schedule files to be version controlled and added to the repository.
488
488
489 The files will be added to the repository at the next commit.
489 The files will be added to the repository at the next commit.
490
490
491 If no names are given, add all files in the repository.
491 If no names are given, add all files in the repository.
492 """
492 """
493
493
494 names = []
494 names = []
495 for src, abs, rel, exact in walk(repo, pats, opts):
495 for src, abs, rel, exact in walk(repo, pats, opts):
496 if exact:
496 if exact:
497 if ui.verbose:
497 if ui.verbose:
498 ui.status(_('adding %s\n') % rel)
498 ui.status(_('adding %s\n') % rel)
499 names.append(abs)
499 names.append(abs)
500 elif repo.dirstate.state(abs) == '?':
500 elif repo.dirstate.state(abs) == '?':
501 ui.status(_('adding %s\n') % rel)
501 ui.status(_('adding %s\n') % rel)
502 names.append(abs)
502 names.append(abs)
503 repo.add(names)
503 repo.add(names)
504
504
505 def addremove(ui, repo, *pats, **opts):
505 def addremove(ui, repo, *pats, **opts):
506 """add all new files, delete all missing files
506 """add all new files, delete all missing files
507
507
508 Add all new files and remove all missing files from the repository.
508 Add all new files and remove all missing files from the repository.
509
509
510 New files are ignored if they match any of the patterns in .hgignore. As
510 New files are ignored if they match any of the patterns in .hgignore. As
511 with add, these changes take effect at the next commit.
511 with add, these changes take effect at the next commit.
512 """
512 """
513 add, remove = [], []
513 add, remove = [], []
514 for src, abs, rel, exact in walk(repo, pats, opts):
514 for src, abs, rel, exact in walk(repo, pats, opts):
515 if src == 'f' and repo.dirstate.state(abs) == '?':
515 if src == 'f' and repo.dirstate.state(abs) == '?':
516 add.append(abs)
516 add.append(abs)
517 if ui.verbose or not exact:
517 if ui.verbose or not exact:
518 ui.status(_('adding %s\n') % rel)
518 ui.status(_('adding %s\n') % rel)
519 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
519 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
520 remove.append(abs)
520 remove.append(abs)
521 if ui.verbose or not exact:
521 if ui.verbose or not exact:
522 ui.status(_('removing %s\n') % rel)
522 ui.status(_('removing %s\n') % rel)
523 repo.add(add)
523 repo.add(add)
524 repo.remove(remove)
524 repo.remove(remove)
525
525
526 def annotate(ui, repo, *pats, **opts):
526 def annotate(ui, repo, *pats, **opts):
527 """show changeset information per file line
527 """show changeset information per file line
528
528
529 List changes in files, showing the revision id responsible for each line
529 List changes in files, showing the revision id responsible for each line
530
530
531 This command is useful to discover who did a change or when a change took
531 This command is useful to discover who did a change or when a change took
532 place.
532 place.
533
533
534 Without the -a option, annotate will avoid processing files it
534 Without the -a option, annotate will avoid processing files it
535 detects as binary. With -a, annotate will generate an annotation
535 detects as binary. With -a, annotate will generate an annotation
536 anyway, probably with undesirable results.
536 anyway, probably with undesirable results.
537 """
537 """
538 def getnode(rev):
538 def getnode(rev):
539 return short(repo.changelog.node(rev))
539 return short(repo.changelog.node(rev))
540
540
541 ucache = {}
541 ucache = {}
542 def getname(rev):
542 def getname(rev):
543 cl = repo.changelog.read(repo.changelog.node(rev))
543 cl = repo.changelog.read(repo.changelog.node(rev))
544 return trimuser(ui, cl[1], rev, ucache)
544 return trimuser(ui, cl[1], rev, ucache)
545
545
546 dcache = {}
546 dcache = {}
547 def getdate(rev):
547 def getdate(rev):
548 datestr = dcache.get(rev)
548 datestr = dcache.get(rev)
549 if datestr is None:
549 if datestr is None:
550 cl = repo.changelog.read(repo.changelog.node(rev))
550 cl = repo.changelog.read(repo.changelog.node(rev))
551 datestr = dcache[rev] = util.datestr(cl[2])
551 datestr = dcache[rev] = util.datestr(cl[2])
552 return datestr
552 return datestr
553
553
554 if not pats:
554 if not pats:
555 raise util.Abort(_('at least one file name or pattern required'))
555 raise util.Abort(_('at least one file name or pattern required'))
556
556
557 opmap = [['user', getname], ['number', str], ['changeset', getnode],
557 opmap = [['user', getname], ['number', str], ['changeset', getnode],
558 ['date', getdate]]
558 ['date', getdate]]
559 if not opts['user'] and not opts['changeset'] and not opts['date']:
559 if not opts['user'] and not opts['changeset'] and not opts['date']:
560 opts['number'] = 1
560 opts['number'] = 1
561
561
562 if opts['rev']:
562 if opts['rev']:
563 node = repo.changelog.lookup(opts['rev'])
563 node = repo.changelog.lookup(opts['rev'])
564 else:
564 else:
565 node = repo.dirstate.parents()[0]
565 node = repo.dirstate.parents()[0]
566 change = repo.changelog.read(node)
566 change = repo.changelog.read(node)
567 mmap = repo.manifest.read(change[0])
567 mmap = repo.manifest.read(change[0])
568
568
569 for src, abs, rel, exact in walk(repo, pats, opts):
569 for src, abs, rel, exact in walk(repo, pats, opts):
570 if abs not in mmap:
570 if abs not in mmap:
571 ui.warn(_("warning: %s is not in the repository!\n") % rel)
571 ui.warn(_("warning: %s is not in the repository!\n") % rel)
572 continue
572 continue
573
573
574 f = repo.file(abs)
574 f = repo.file(abs)
575 if not opts['text'] and util.binary(f.read(mmap[abs])):
575 if not opts['text'] and util.binary(f.read(mmap[abs])):
576 ui.write(_("%s: binary file\n") % rel)
576 ui.write(_("%s: binary file\n") % rel)
577 continue
577 continue
578
578
579 lines = f.annotate(mmap[abs])
579 lines = f.annotate(mmap[abs])
580 pieces = []
580 pieces = []
581
581
582 for o, f in opmap:
582 for o, f in opmap:
583 if opts[o]:
583 if opts[o]:
584 l = [f(n) for n, dummy in lines]
584 l = [f(n) for n, dummy in lines]
585 if l:
585 if l:
586 m = max(map(len, l))
586 m = max(map(len, l))
587 pieces.append(["%*s" % (m, x) for x in l])
587 pieces.append(["%*s" % (m, x) for x in l])
588
588
589 if pieces:
589 if pieces:
590 for p, l in zip(zip(*pieces), lines):
590 for p, l in zip(zip(*pieces), lines):
591 ui.write("%s: %s" % (" ".join(p), l[1]))
591 ui.write("%s: %s" % (" ".join(p), l[1]))
592
592
593 def bundle(ui, repo, fname, dest="default-push", **opts):
593 def bundle(ui, repo, fname, dest="default-push", **opts):
594 """create a changegroup file
594 """create a changegroup file
595
595
596 Generate a compressed changegroup file collecting all changesets
596 Generate a compressed changegroup file collecting all changesets
597 not found in the other repository.
597 not found in the other repository.
598
598
599 This file can then be transferred using conventional means and
599 This file can then be transferred using conventional means and
600 applied to another repository with the unbundle command. This is
600 applied to another repository with the unbundle command. This is
601 useful when native push and pull are not available or when
601 useful when native push and pull are not available or when
602 exporting an entire repository is undesirable. The standard file
602 exporting an entire repository is undesirable. The standard file
603 extension is ".hg".
603 extension is ".hg".
604
604
605 Unlike import/export, this exactly preserves all changeset
605 Unlike import/export, this exactly preserves all changeset
606 contents including permissions, rename data, and revision history.
606 contents including permissions, rename data, and revision history.
607 """
607 """
608 f = open(fname, "wb")
608 f = open(fname, "wb")
609 dest = ui.expandpath(dest, repo.root)
609 dest = ui.expandpath(dest, repo.root)
610 other = hg.repository(ui, dest)
610 other = hg.repository(ui, dest)
611 o = repo.findoutgoing(other)
611 o = repo.findoutgoing(other)
612 cg = repo.changegroup(o)
612 cg = repo.changegroup(o)
613
613
614 try:
614 try:
615 f.write("HG10")
615 f.write("HG10")
616 z = bz2.BZ2Compressor(9)
616 z = bz2.BZ2Compressor(9)
617 while 1:
617 while 1:
618 chunk = cg.read(4096)
618 chunk = cg.read(4096)
619 if not chunk:
619 if not chunk:
620 break
620 break
621 f.write(z.compress(chunk))
621 f.write(z.compress(chunk))
622 f.write(z.flush())
622 f.write(z.flush())
623 except:
623 except:
624 os.unlink(fname)
624 os.unlink(fname)
625 raise
625 raise
626
626
627 def cat(ui, repo, file1, *pats, **opts):
627 def cat(ui, repo, file1, *pats, **opts):
628 """output the latest or given revisions of files
628 """output the latest or given revisions of files
629
629
630 Print the specified files as they were at the given revision.
630 Print the specified files as they were at the given revision.
631 If no revision is given then the tip is used.
631 If no revision is given then the tip is used.
632
632
633 Output may be to a file, in which case the name of the file is
633 Output may be to a file, in which case the name of the file is
634 given using a format string. The formatting rules are the same as
634 given using a format string. The formatting rules are the same as
635 for the export command, with the following additions:
635 for the export command, with the following additions:
636
636
637 %s basename of file being printed
637 %s basename of file being printed
638 %d dirname of file being printed, or '.' if in repo root
638 %d dirname of file being printed, or '.' if in repo root
639 %p root-relative path name of file being printed
639 %p root-relative path name of file being printed
640 """
640 """
641 mf = {}
641 mf = {}
642 rev = opts['rev']
642 rev = opts['rev']
643 if rev:
643 if rev:
644 node = repo.lookup(rev)
644 node = repo.lookup(rev)
645 else:
645 else:
646 node = repo.changelog.tip()
646 node = repo.changelog.tip()
647 change = repo.changelog.read(node)
647 change = repo.changelog.read(node)
648 mf = repo.manifest.read(change[0])
648 mf = repo.manifest.read(change[0])
649 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
649 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
650 r = repo.file(abs)
650 r = repo.file(abs)
651 n = mf[abs]
651 n = mf[abs]
652 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
652 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
653 fp.write(r.read(n))
653 fp.write(r.read(n))
654
654
655 def clone(ui, source, dest=None, **opts):
655 def clone(ui, source, dest=None, **opts):
656 """make a copy of an existing repository
656 """make a copy of an existing repository
657
657
658 Create a copy of an existing repository in a new directory.
658 Create a copy of an existing repository in a new directory.
659
659
660 If no destination directory name is specified, it defaults to the
660 If no destination directory name is specified, it defaults to the
661 basename of the source.
661 basename of the source.
662
662
663 The location of the source is added to the new repository's
663 The location of the source is added to the new repository's
664 .hg/hgrc file, as the default to be used for future pulls.
664 .hg/hgrc file, as the default to be used for future pulls.
665
665
666 For efficiency, hardlinks are used for cloning whenever the source
666 For efficiency, hardlinks are used for cloning whenever the source
667 and destination are on the same filesystem. Some filesystems,
667 and destination are on the same filesystem. Some filesystems,
668 such as AFS, implement hardlinking incorrectly, but do not report
668 such as AFS, implement hardlinking incorrectly, but do not report
669 errors. In these cases, use the --pull option to avoid
669 errors. In these cases, use the --pull option to avoid
670 hardlinking.
670 hardlinking.
671 """
671 """
672 if dest is None:
672 if dest is None:
673 dest = os.path.basename(os.path.normpath(source))
673 dest = os.path.basename(os.path.normpath(source))
674
674
675 if os.path.exists(dest):
675 if os.path.exists(dest):
676 raise util.Abort(_("destination '%s' already exists"), dest)
676 raise util.Abort(_("destination '%s' already exists"), dest)
677
677
678 dest = os.path.realpath(dest)
678 dest = os.path.realpath(dest)
679
679
680 class Dircleanup(object):
680 class Dircleanup(object):
681 def __init__(self, dir_):
681 def __init__(self, dir_):
682 self.rmtree = shutil.rmtree
682 self.rmtree = shutil.rmtree
683 self.dir_ = dir_
683 self.dir_ = dir_
684 os.mkdir(dir_)
684 os.mkdir(dir_)
685 def close(self):
685 def close(self):
686 self.dir_ = None
686 self.dir_ = None
687 def __del__(self):
687 def __del__(self):
688 if self.dir_:
688 if self.dir_:
689 self.rmtree(self.dir_, True)
689 self.rmtree(self.dir_, True)
690
690
691 if opts['ssh']:
691 if opts['ssh']:
692 ui.setconfig("ui", "ssh", opts['ssh'])
692 ui.setconfig("ui", "ssh", opts['ssh'])
693 if opts['remotecmd']:
693 if opts['remotecmd']:
694 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
694 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
695
695
696 if not os.path.exists(source):
696 if not os.path.exists(source):
697 source = ui.expandpath(source)
697 source = ui.expandpath(source)
698
698
699 d = Dircleanup(dest)
699 d = Dircleanup(dest)
700 abspath = source
700 abspath = source
701 other = hg.repository(ui, source)
701 other = hg.repository(ui, source)
702
702
703 copy = False
703 copy = False
704 if other.dev() != -1:
704 if other.dev() != -1:
705 abspath = os.path.abspath(source)
705 abspath = os.path.abspath(source)
706 if not opts['pull'] and not opts['rev']:
706 if not opts['pull'] and not opts['rev']:
707 copy = True
707 copy = True
708
708
709 if copy:
709 if copy:
710 try:
710 try:
711 # we use a lock here because if we race with commit, we
711 # we use a lock here because if we race with commit, we
712 # can end up with extra data in the cloned revlogs that's
712 # can end up with extra data in the cloned revlogs that's
713 # not pointed to by changesets, thus causing verify to
713 # not pointed to by changesets, thus causing verify to
714 # fail
714 # fail
715 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
715 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
716 except OSError:
716 except OSError:
717 copy = False
717 copy = False
718
718
719 if copy:
719 if copy:
720 # we lock here to avoid premature writing to the target
720 # we lock here to avoid premature writing to the target
721 os.mkdir(os.path.join(dest, ".hg"))
721 os.mkdir(os.path.join(dest, ".hg"))
722 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
722 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
723
723
724 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
724 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
725 for f in files.split():
725 for f in files.split():
726 src = os.path.join(source, ".hg", f)
726 src = os.path.join(source, ".hg", f)
727 dst = os.path.join(dest, ".hg", f)
727 dst = os.path.join(dest, ".hg", f)
728 try:
728 try:
729 util.copyfiles(src, dst)
729 util.copyfiles(src, dst)
730 except OSError, inst:
730 except OSError, inst:
731 if inst.errno != errno.ENOENT:
731 if inst.errno != errno.ENOENT:
732 raise
732 raise
733
733
734 repo = hg.repository(ui, dest)
734 repo = hg.repository(ui, dest)
735
735
736 else:
736 else:
737 revs = None
737 revs = None
738 if opts['rev']:
738 if opts['rev']:
739 if not other.local():
739 if not other.local():
740 error = _("clone -r not supported yet for remote repositories.")
740 error = _("clone -r not supported yet for remote repositories.")
741 raise util.Abort(error)
741 raise util.Abort(error)
742 else:
742 else:
743 revs = [other.lookup(rev) for rev in opts['rev']]
743 revs = [other.lookup(rev) for rev in opts['rev']]
744 repo = hg.repository(ui, dest, create=1)
744 repo = hg.repository(ui, dest, create=1)
745 repo.pull(other, heads = revs)
745 repo.pull(other, heads = revs)
746
746
747 f = repo.opener("hgrc", "w", text=True)
747 f = repo.opener("hgrc", "w", text=True)
748 f.write("[paths]\n")
748 f.write("[paths]\n")
749 f.write("default = %s\n" % abspath)
749 f.write("default = %s\n" % abspath)
750 f.close()
750 f.close()
751
751
752 if not opts['noupdate']:
752 if not opts['noupdate']:
753 update(ui, repo)
753 update(ui, repo)
754
754
755 d.close()
755 d.close()
756
756
757 def commit(ui, repo, *pats, **opts):
757 def commit(ui, repo, *pats, **opts):
758 """commit the specified files or all outstanding changes
758 """commit the specified files or all outstanding changes
759
759
760 Commit changes to the given files into the repository.
760 Commit changes to the given files into the repository.
761
761
762 If a list of files is omitted, all changes reported by "hg status"
762 If a list of files is omitted, all changes reported by "hg status"
763 will be commited.
763 will be commited.
764
764
765 The HGEDITOR or EDITOR environment variables are used to start an
765 The HGEDITOR or EDITOR environment variables are used to start an
766 editor to add a commit comment.
766 editor to add a commit comment.
767 """
767 """
768 message = opts['message']
768 message = opts['message']
769 logfile = opts['logfile']
769 logfile = opts['logfile']
770
770
771 if message and logfile:
771 if message and logfile:
772 raise util.Abort(_('options --message and --logfile are mutually '
772 raise util.Abort(_('options --message and --logfile are mutually '
773 'exclusive'))
773 'exclusive'))
774 if not message and logfile:
774 if not message and logfile:
775 try:
775 try:
776 if logfile == '-':
776 if logfile == '-':
777 message = sys.stdin.read()
777 message = sys.stdin.read()
778 else:
778 else:
779 message = open(logfile).read()
779 message = open(logfile).read()
780 except IOError, inst:
780 except IOError, inst:
781 raise util.Abort(_("can't read commit message '%s': %s") %
781 raise util.Abort(_("can't read commit message '%s': %s") %
782 (logfile, inst.strerror))
782 (logfile, inst.strerror))
783
783
784 if opts['addremove']:
784 if opts['addremove']:
785 addremove(ui, repo, *pats, **opts)
785 addremove(ui, repo, *pats, **opts)
786 fns, match, anypats, cwd = matchpats(repo, pats, opts)
786 fns, match, anypats, cwd = matchpats(repo, pats, opts)
787 if pats:
787 if pats:
788 c, a, d, u = repo.changes(files=fns, match=match)
788 modified, added, removed, unknown = (
789 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
789 repo.changes(files=fns, match=match))
790 files = (modified + added +
791 [fn for fn in removed if repo.dirstate.state(fn) == 'r'])
790 else:
792 else:
791 files = []
793 files = []
792 try:
794 try:
793 repo.commit(files, message, opts['user'], opts['date'], match)
795 repo.commit(files, message, opts['user'], opts['date'], match)
794 except ValueError, inst:
796 except ValueError, inst:
795 raise util.Abort(str(inst))
797 raise util.Abort(str(inst))
796
798
797 def docopy(ui, repo, pats, opts):
799 def docopy(ui, repo, pats, opts):
798 cwd = repo.getcwd()
800 cwd = repo.getcwd()
799 errors = 0
801 errors = 0
800 copied = []
802 copied = []
801 targets = {}
803 targets = {}
802
804
803 def okaytocopy(abs, rel, exact):
805 def okaytocopy(abs, rel, exact):
804 reasons = {'?': _('is not managed'),
806 reasons = {'?': _('is not managed'),
805 'a': _('has been marked for add')}
807 'a': _('has been marked for add')}
806 reason = reasons.get(repo.dirstate.state(abs))
808 reason = reasons.get(repo.dirstate.state(abs))
807 if reason:
809 if reason:
808 if exact:
810 if exact:
809 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
811 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
810 else:
812 else:
811 return True
813 return True
812
814
813 def copy(abssrc, relsrc, target, exact):
815 def copy(abssrc, relsrc, target, exact):
814 abstarget = util.canonpath(repo.root, cwd, target)
816 abstarget = util.canonpath(repo.root, cwd, target)
815 reltarget = util.pathto(cwd, abstarget)
817 reltarget = util.pathto(cwd, abstarget)
816 prevsrc = targets.get(abstarget)
818 prevsrc = targets.get(abstarget)
817 if prevsrc is not None:
819 if prevsrc is not None:
818 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
820 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
819 (reltarget, abssrc, prevsrc))
821 (reltarget, abssrc, prevsrc))
820 return
822 return
821 if (not opts['after'] and os.path.exists(reltarget) or
823 if (not opts['after'] and os.path.exists(reltarget) or
822 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
824 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
823 if not opts['force']:
825 if not opts['force']:
824 ui.warn(_('%s: not overwriting - file exists\n') %
826 ui.warn(_('%s: not overwriting - file exists\n') %
825 reltarget)
827 reltarget)
826 return
828 return
827 if not opts['after']:
829 if not opts['after']:
828 os.unlink(reltarget)
830 os.unlink(reltarget)
829 if opts['after']:
831 if opts['after']:
830 if not os.path.exists(reltarget):
832 if not os.path.exists(reltarget):
831 return
833 return
832 else:
834 else:
833 targetdir = os.path.dirname(reltarget) or '.'
835 targetdir = os.path.dirname(reltarget) or '.'
834 if not os.path.isdir(targetdir):
836 if not os.path.isdir(targetdir):
835 os.makedirs(targetdir)
837 os.makedirs(targetdir)
836 try:
838 try:
837 shutil.copyfile(relsrc, reltarget)
839 shutil.copyfile(relsrc, reltarget)
838 shutil.copymode(relsrc, reltarget)
840 shutil.copymode(relsrc, reltarget)
839 except shutil.Error, inst:
841 except shutil.Error, inst:
840 raise util.Abort(str(inst))
842 raise util.Abort(str(inst))
841 except IOError, inst:
843 except IOError, inst:
842 if inst.errno == errno.ENOENT:
844 if inst.errno == errno.ENOENT:
843 ui.warn(_('%s: deleted in working copy\n') % relsrc)
845 ui.warn(_('%s: deleted in working copy\n') % relsrc)
844 else:
846 else:
845 ui.warn(_('%s: cannot copy - %s\n') %
847 ui.warn(_('%s: cannot copy - %s\n') %
846 (relsrc, inst.strerror))
848 (relsrc, inst.strerror))
847 errors += 1
849 errors += 1
848 return
850 return
849 if ui.verbose or not exact:
851 if ui.verbose or not exact:
850 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
852 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
851 targets[abstarget] = abssrc
853 targets[abstarget] = abssrc
852 repo.copy(abssrc, abstarget)
854 repo.copy(abssrc, abstarget)
853 copied.append((abssrc, relsrc, exact))
855 copied.append((abssrc, relsrc, exact))
854
856
855 def targetpathfn(pat, dest, srcs):
857 def targetpathfn(pat, dest, srcs):
856 if os.path.isdir(pat):
858 if os.path.isdir(pat):
857 if pat.endswith(os.sep):
859 if pat.endswith(os.sep):
858 pat = pat[:-len(os.sep)]
860 pat = pat[:-len(os.sep)]
859 if destdirexists:
861 if destdirexists:
860 striplen = len(os.path.split(pat)[0])
862 striplen = len(os.path.split(pat)[0])
861 else:
863 else:
862 striplen = len(pat)
864 striplen = len(pat)
863 if striplen:
865 if striplen:
864 striplen += len(os.sep)
866 striplen += len(os.sep)
865 res = lambda p: os.path.join(dest, p[striplen:])
867 res = lambda p: os.path.join(dest, p[striplen:])
866 elif destdirexists:
868 elif destdirexists:
867 res = lambda p: os.path.join(dest, os.path.basename(p))
869 res = lambda p: os.path.join(dest, os.path.basename(p))
868 else:
870 else:
869 res = lambda p: dest
871 res = lambda p: dest
870 return res
872 return res
871
873
872 def targetpathafterfn(pat, dest, srcs):
874 def targetpathafterfn(pat, dest, srcs):
873 if util.patkind(pat, None)[0]:
875 if util.patkind(pat, None)[0]:
874 # a mercurial pattern
876 # a mercurial pattern
875 res = lambda p: os.path.join(dest, os.path.basename(p))
877 res = lambda p: os.path.join(dest, os.path.basename(p))
876 elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]):
878 elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]):
877 # A directory. Either the target path contains the last
879 # A directory. Either the target path contains the last
878 # component of the source path or it does not.
880 # component of the source path or it does not.
879 def evalpath(striplen):
881 def evalpath(striplen):
880 score = 0
882 score = 0
881 for s in srcs:
883 for s in srcs:
882 t = os.path.join(dest, s[1][striplen:])
884 t = os.path.join(dest, s[1][striplen:])
883 if os.path.exists(t):
885 if os.path.exists(t):
884 score += 1
886 score += 1
885 return score
887 return score
886
888
887 if pat.endswith(os.sep):
889 if pat.endswith(os.sep):
888 pat = pat[:-len(os.sep)]
890 pat = pat[:-len(os.sep)]
889 striplen = len(pat) + len(os.sep)
891 striplen = len(pat) + len(os.sep)
890 if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])):
892 if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])):
891 score = evalpath(striplen)
893 score = evalpath(striplen)
892 striplen1 = len(os.path.split(pat)[0])
894 striplen1 = len(os.path.split(pat)[0])
893 if striplen1:
895 if striplen1:
894 striplen1 += len(os.sep)
896 striplen1 += len(os.sep)
895 if evalpath(striplen1) > score:
897 if evalpath(striplen1) > score:
896 striplen = striplen1
898 striplen = striplen1
897 res = lambda p: os.path.join(dest, p[striplen:])
899 res = lambda p: os.path.join(dest, p[striplen:])
898 else:
900 else:
899 # a file
901 # a file
900 if destdirexists:
902 if destdirexists:
901 res = lambda p: os.path.join(dest, os.path.basename(p))
903 res = lambda p: os.path.join(dest, os.path.basename(p))
902 else:
904 else:
903 res = lambda p: dest
905 res = lambda p: dest
904 return res
906 return res
905
907
906
908
907 pats = list(pats)
909 pats = list(pats)
908 if not pats:
910 if not pats:
909 raise util.Abort(_('no source or destination specified'))
911 raise util.Abort(_('no source or destination specified'))
910 if len(pats) == 1:
912 if len(pats) == 1:
911 raise util.Abort(_('no destination specified'))
913 raise util.Abort(_('no destination specified'))
912 dest = pats.pop()
914 dest = pats.pop()
913 destdirexists = os.path.isdir(dest)
915 destdirexists = os.path.isdir(dest)
914 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
916 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
915 raise util.Abort(_('with multiple sources, destination must be an '
917 raise util.Abort(_('with multiple sources, destination must be an '
916 'existing directory'))
918 'existing directory'))
917 if opts['after']:
919 if opts['after']:
918 tfn = targetpathafterfn
920 tfn = targetpathafterfn
919 else:
921 else:
920 tfn = targetpathfn
922 tfn = targetpathfn
921 copylist = []
923 copylist = []
922 for pat in pats:
924 for pat in pats:
923 srcs = []
925 srcs = []
924 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
926 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
925 if okaytocopy(abssrc, relsrc, exact):
927 if okaytocopy(abssrc, relsrc, exact):
926 srcs.append((abssrc, relsrc, exact))
928 srcs.append((abssrc, relsrc, exact))
927 if not srcs:
929 if not srcs:
928 continue
930 continue
929 copylist.append((tfn(pat, dest, srcs), srcs))
931 copylist.append((tfn(pat, dest, srcs), srcs))
930 if not copylist:
932 if not copylist:
931 raise util.Abort(_('no files to copy'))
933 raise util.Abort(_('no files to copy'))
932
934
933 for targetpath, srcs in copylist:
935 for targetpath, srcs in copylist:
934 for abssrc, relsrc, exact in srcs:
936 for abssrc, relsrc, exact in srcs:
935 copy(abssrc, relsrc, targetpath(relsrc), exact)
937 copy(abssrc, relsrc, targetpath(relsrc), exact)
936
938
937 if errors:
939 if errors:
938 ui.warn(_('(consider using --after)\n'))
940 ui.warn(_('(consider using --after)\n'))
939 return errors, copied
941 return errors, copied
940
942
941 def copy(ui, repo, *pats, **opts):
943 def copy(ui, repo, *pats, **opts):
942 """mark files as copied for the next commit
944 """mark files as copied for the next commit
943
945
944 Mark dest as having copies of source files. If dest is a
946 Mark dest as having copies of source files. If dest is a
945 directory, copies are put in that directory. If dest is a file,
947 directory, copies are put in that directory. If dest is a file,
946 there can only be one source.
948 there can only be one source.
947
949
948 By default, this command copies the contents of files as they
950 By default, this command copies the contents of files as they
949 stand in the working directory. If invoked with --after, the
951 stand in the working directory. If invoked with --after, the
950 operation is recorded, but no copying is performed.
952 operation is recorded, but no copying is performed.
951
953
952 This command takes effect in the next commit.
954 This command takes effect in the next commit.
953
955
954 NOTE: This command should be treated as experimental. While it
956 NOTE: This command should be treated as experimental. While it
955 should properly record copied files, this information is not yet
957 should properly record copied files, this information is not yet
956 fully used by merge, nor fully reported by log.
958 fully used by merge, nor fully reported by log.
957 """
959 """
958 errs, copied = docopy(ui, repo, pats, opts)
960 errs, copied = docopy(ui, repo, pats, opts)
959 return errs
961 return errs
960
962
961 def debugancestor(ui, index, rev1, rev2):
963 def debugancestor(ui, index, rev1, rev2):
962 """find the ancestor revision of two revisions in a given index"""
964 """find the ancestor revision of two revisions in a given index"""
963 r = revlog.revlog(util.opener(os.getcwd()), index, "")
965 r = revlog.revlog(util.opener(os.getcwd()), index, "")
964 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
966 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
965 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
967 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
966
968
967 def debugcheckstate(ui, repo):
969 def debugcheckstate(ui, repo):
968 """validate the correctness of the current dirstate"""
970 """validate the correctness of the current dirstate"""
969 parent1, parent2 = repo.dirstate.parents()
971 parent1, parent2 = repo.dirstate.parents()
970 repo.dirstate.read()
972 repo.dirstate.read()
971 dc = repo.dirstate.map
973 dc = repo.dirstate.map
972 keys = dc.keys()
974 keys = dc.keys()
973 keys.sort()
975 keys.sort()
974 m1n = repo.changelog.read(parent1)[0]
976 m1n = repo.changelog.read(parent1)[0]
975 m2n = repo.changelog.read(parent2)[0]
977 m2n = repo.changelog.read(parent2)[0]
976 m1 = repo.manifest.read(m1n)
978 m1 = repo.manifest.read(m1n)
977 m2 = repo.manifest.read(m2n)
979 m2 = repo.manifest.read(m2n)
978 errors = 0
980 errors = 0
979 for f in dc:
981 for f in dc:
980 state = repo.dirstate.state(f)
982 state = repo.dirstate.state(f)
981 if state in "nr" and f not in m1:
983 if state in "nr" and f not in m1:
982 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
984 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
983 errors += 1
985 errors += 1
984 if state in "a" and f in m1:
986 if state in "a" and f in m1:
985 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
987 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
986 errors += 1
988 errors += 1
987 if state in "m" and f not in m1 and f not in m2:
989 if state in "m" and f not in m1 and f not in m2:
988 ui.warn(_("%s in state %s, but not in either manifest\n") %
990 ui.warn(_("%s in state %s, but not in either manifest\n") %
989 (f, state))
991 (f, state))
990 errors += 1
992 errors += 1
991 for f in m1:
993 for f in m1:
992 state = repo.dirstate.state(f)
994 state = repo.dirstate.state(f)
993 if state not in "nrm":
995 if state not in "nrm":
994 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
996 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
995 errors += 1
997 errors += 1
996 if errors:
998 if errors:
997 error = _(".hg/dirstate inconsistent with current parent's manifest")
999 error = _(".hg/dirstate inconsistent with current parent's manifest")
998 raise util.Abort(error)
1000 raise util.Abort(error)
999
1001
1000 def debugconfig(ui):
1002 def debugconfig(ui):
1001 """show combined config settings from all hgrc files"""
1003 """show combined config settings from all hgrc files"""
1002 try:
1004 try:
1003 repo = hg.repository(ui)
1005 repo = hg.repository(ui)
1004 except hg.RepoError:
1006 except hg.RepoError:
1005 pass
1007 pass
1006 for section, name, value in ui.walkconfig():
1008 for section, name, value in ui.walkconfig():
1007 ui.write('%s.%s=%s\n' % (section, name, value))
1009 ui.write('%s.%s=%s\n' % (section, name, value))
1008
1010
1009 def debugsetparents(ui, repo, rev1, rev2=None):
1011 def debugsetparents(ui, repo, rev1, rev2=None):
1010 """manually set the parents of the current working directory
1012 """manually set the parents of the current working directory
1011
1013
1012 This is useful for writing repository conversion tools, but should
1014 This is useful for writing repository conversion tools, but should
1013 be used with care.
1015 be used with care.
1014 """
1016 """
1015
1017
1016 if not rev2:
1018 if not rev2:
1017 rev2 = hex(nullid)
1019 rev2 = hex(nullid)
1018
1020
1019 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1021 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1020
1022
1021 def debugstate(ui, repo):
1023 def debugstate(ui, repo):
1022 """show the contents of the current dirstate"""
1024 """show the contents of the current dirstate"""
1023 repo.dirstate.read()
1025 repo.dirstate.read()
1024 dc = repo.dirstate.map
1026 dc = repo.dirstate.map
1025 keys = dc.keys()
1027 keys = dc.keys()
1026 keys.sort()
1028 keys.sort()
1027 for file_ in keys:
1029 for file_ in keys:
1028 ui.write("%c %3o %10d %s %s\n"
1030 ui.write("%c %3o %10d %s %s\n"
1029 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1031 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1030 time.strftime("%x %X",
1032 time.strftime("%x %X",
1031 time.localtime(dc[file_][3])), file_))
1033 time.localtime(dc[file_][3])), file_))
1032 for f in repo.dirstate.copies:
1034 for f in repo.dirstate.copies:
1033 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1035 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1034
1036
1035 def debugdata(ui, file_, rev):
1037 def debugdata(ui, file_, rev):
1036 """dump the contents of an data file revision"""
1038 """dump the contents of an data file revision"""
1037 r = revlog.revlog(util.opener(os.getcwd()), file_[:-2] + ".i", file_)
1039 r = revlog.revlog(util.opener(os.getcwd()), file_[:-2] + ".i", file_)
1038 try:
1040 try:
1039 ui.write(r.revision(r.lookup(rev)))
1041 ui.write(r.revision(r.lookup(rev)))
1040 except KeyError:
1042 except KeyError:
1041 raise util.Abort(_('invalid revision identifier %s'), rev)
1043 raise util.Abort(_('invalid revision identifier %s'), rev)
1042
1044
1043 def debugindex(ui, file_):
1045 def debugindex(ui, file_):
1044 """dump the contents of an index file"""
1046 """dump the contents of an index file"""
1045 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1047 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1046 ui.write(" rev offset length base linkrev" +
1048 ui.write(" rev offset length base linkrev" +
1047 " nodeid p1 p2\n")
1049 " nodeid p1 p2\n")
1048 for i in range(r.count()):
1050 for i in range(r.count()):
1049 e = r.index[i]
1051 e = r.index[i]
1050 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1052 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1051 i, e[0], e[1], e[2], e[3],
1053 i, e[0], e[1], e[2], e[3],
1052 short(e[6]), short(e[4]), short(e[5])))
1054 short(e[6]), short(e[4]), short(e[5])))
1053
1055
1054 def debugindexdot(ui, file_):
1056 def debugindexdot(ui, file_):
1055 """dump an index DAG as a .dot file"""
1057 """dump an index DAG as a .dot file"""
1056 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1058 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1057 ui.write("digraph G {\n")
1059 ui.write("digraph G {\n")
1058 for i in range(r.count()):
1060 for i in range(r.count()):
1059 e = r.index[i]
1061 e = r.index[i]
1060 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1062 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1061 if e[5] != nullid:
1063 if e[5] != nullid:
1062 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1064 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1063 ui.write("}\n")
1065 ui.write("}\n")
1064
1066
1065 def debugrename(ui, repo, file, rev=None):
1067 def debugrename(ui, repo, file, rev=None):
1066 """dump rename information"""
1068 """dump rename information"""
1067 r = repo.file(relpath(repo, [file])[0])
1069 r = repo.file(relpath(repo, [file])[0])
1068 if rev:
1070 if rev:
1069 try:
1071 try:
1070 # assume all revision numbers are for changesets
1072 # assume all revision numbers are for changesets
1071 n = repo.lookup(rev)
1073 n = repo.lookup(rev)
1072 change = repo.changelog.read(n)
1074 change = repo.changelog.read(n)
1073 m = repo.manifest.read(change[0])
1075 m = repo.manifest.read(change[0])
1074 n = m[relpath(repo, [file])[0]]
1076 n = m[relpath(repo, [file])[0]]
1075 except (hg.RepoError, KeyError):
1077 except (hg.RepoError, KeyError):
1076 n = r.lookup(rev)
1078 n = r.lookup(rev)
1077 else:
1079 else:
1078 n = r.tip()
1080 n = r.tip()
1079 m = r.renamed(n)
1081 m = r.renamed(n)
1080 if m:
1082 if m:
1081 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1083 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1082 else:
1084 else:
1083 ui.write(_("not renamed\n"))
1085 ui.write(_("not renamed\n"))
1084
1086
1085 def debugwalk(ui, repo, *pats, **opts):
1087 def debugwalk(ui, repo, *pats, **opts):
1086 """show how files match on given patterns"""
1088 """show how files match on given patterns"""
1087 items = list(walk(repo, pats, opts))
1089 items = list(walk(repo, pats, opts))
1088 if not items:
1090 if not items:
1089 return
1091 return
1090 fmt = '%%s %%-%ds %%-%ds %%s' % (
1092 fmt = '%%s %%-%ds %%-%ds %%s' % (
1091 max([len(abs) for (src, abs, rel, exact) in items]),
1093 max([len(abs) for (src, abs, rel, exact) in items]),
1092 max([len(rel) for (src, abs, rel, exact) in items]))
1094 max([len(rel) for (src, abs, rel, exact) in items]))
1093 for src, abs, rel, exact in items:
1095 for src, abs, rel, exact in items:
1094 line = fmt % (src, abs, rel, exact and 'exact' or '')
1096 line = fmt % (src, abs, rel, exact and 'exact' or '')
1095 ui.write("%s\n" % line.rstrip())
1097 ui.write("%s\n" % line.rstrip())
1096
1098
1097 def diff(ui, repo, *pats, **opts):
1099 def diff(ui, repo, *pats, **opts):
1098 """diff repository (or selected files)
1100 """diff repository (or selected files)
1099
1101
1100 Show differences between revisions for the specified files.
1102 Show differences between revisions for the specified files.
1101
1103
1102 Differences between files are shown using the unified diff format.
1104 Differences between files are shown using the unified diff format.
1103
1105
1104 When two revision arguments are given, then changes are shown
1106 When two revision arguments are given, then changes are shown
1105 between those revisions. If only one revision is specified then
1107 between those revisions. If only one revision is specified then
1106 that revision is compared to the working directory, and, when no
1108 that revision is compared to the working directory, and, when no
1107 revisions are specified, the working directory files are compared
1109 revisions are specified, the working directory files are compared
1108 to its parent.
1110 to its parent.
1109
1111
1110 Without the -a option, diff will avoid generating diffs of files
1112 Without the -a option, diff will avoid generating diffs of files
1111 it detects as binary. With -a, diff will generate a diff anyway,
1113 it detects as binary. With -a, diff will generate a diff anyway,
1112 probably with undesirable results.
1114 probably with undesirable results.
1113 """
1115 """
1114 node1, node2 = None, None
1116 node1, node2 = None, None
1115 revs = [repo.lookup(x) for x in opts['rev']]
1117 revs = [repo.lookup(x) for x in opts['rev']]
1116
1118
1117 if len(revs) > 0:
1119 if len(revs) > 0:
1118 node1 = revs[0]
1120 node1 = revs[0]
1119 if len(revs) > 1:
1121 if len(revs) > 1:
1120 node2 = revs[1]
1122 node2 = revs[1]
1121 if len(revs) > 2:
1123 if len(revs) > 2:
1122 raise util.Abort(_("too many revisions to diff"))
1124 raise util.Abort(_("too many revisions to diff"))
1123
1125
1124 fns, matchfn, anypats, cwd = matchpats(repo, pats, opts)
1126 fns, matchfn, anypats, cwd = matchpats(repo, pats, opts)
1125
1127
1126 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1128 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1127 text=opts['text'])
1129 text=opts['text'])
1128
1130
1129 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1131 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1130 node = repo.lookup(changeset)
1132 node = repo.lookup(changeset)
1131 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1133 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1132 if opts['switch_parent']:
1134 if opts['switch_parent']:
1133 parents.reverse()
1135 parents.reverse()
1134 prev = (parents and parents[0]) or nullid
1136 prev = (parents and parents[0]) or nullid
1135 change = repo.changelog.read(node)
1137 change = repo.changelog.read(node)
1136
1138
1137 fp = make_file(repo, repo.changelog, opts['output'],
1139 fp = make_file(repo, repo.changelog, opts['output'],
1138 node=node, total=total, seqno=seqno,
1140 node=node, total=total, seqno=seqno,
1139 revwidth=revwidth)
1141 revwidth=revwidth)
1140 if fp != sys.stdout:
1142 if fp != sys.stdout:
1141 ui.note("%s\n" % fp.name)
1143 ui.note("%s\n" % fp.name)
1142
1144
1143 fp.write("# HG changeset patch\n")
1145 fp.write("# HG changeset patch\n")
1144 fp.write("# User %s\n" % change[1])
1146 fp.write("# User %s\n" % change[1])
1145 fp.write("# Node ID %s\n" % hex(node))
1147 fp.write("# Node ID %s\n" % hex(node))
1146 fp.write("# Parent %s\n" % hex(prev))
1148 fp.write("# Parent %s\n" % hex(prev))
1147 if len(parents) > 1:
1149 if len(parents) > 1:
1148 fp.write("# Parent %s\n" % hex(parents[1]))
1150 fp.write("# Parent %s\n" % hex(parents[1]))
1149 fp.write(change[4].rstrip())
1151 fp.write(change[4].rstrip())
1150 fp.write("\n\n")
1152 fp.write("\n\n")
1151
1153
1152 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1154 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1153 if fp != sys.stdout:
1155 if fp != sys.stdout:
1154 fp.close()
1156 fp.close()
1155
1157
1156 def export(ui, repo, *changesets, **opts):
1158 def export(ui, repo, *changesets, **opts):
1157 """dump the header and diffs for one or more changesets
1159 """dump the header and diffs for one or more changesets
1158
1160
1159 Print the changeset header and diffs for one or more revisions.
1161 Print the changeset header and diffs for one or more revisions.
1160
1162
1161 The information shown in the changeset header is: author,
1163 The information shown in the changeset header is: author,
1162 changeset hash, parent and commit comment.
1164 changeset hash, parent and commit comment.
1163
1165
1164 Output may be to a file, in which case the name of the file is
1166 Output may be to a file, in which case the name of the file is
1165 given using a format string. The formatting rules are as follows:
1167 given using a format string. The formatting rules are as follows:
1166
1168
1167 %% literal "%" character
1169 %% literal "%" character
1168 %H changeset hash (40 bytes of hexadecimal)
1170 %H changeset hash (40 bytes of hexadecimal)
1169 %N number of patches being generated
1171 %N number of patches being generated
1170 %R changeset revision number
1172 %R changeset revision number
1171 %b basename of the exporting repository
1173 %b basename of the exporting repository
1172 %h short-form changeset hash (12 bytes of hexadecimal)
1174 %h short-form changeset hash (12 bytes of hexadecimal)
1173 %n zero-padded sequence number, starting at 1
1175 %n zero-padded sequence number, starting at 1
1174 %r zero-padded changeset revision number
1176 %r zero-padded changeset revision number
1175
1177
1176 Without the -a option, export will avoid generating diffs of files
1178 Without the -a option, export will avoid generating diffs of files
1177 it detects as binary. With -a, export will generate a diff anyway,
1179 it detects as binary. With -a, export will generate a diff anyway,
1178 probably with undesirable results.
1180 probably with undesirable results.
1179
1181
1180 With the --switch-parent option, the diff will be against the second
1182 With the --switch-parent option, the diff will be against the second
1181 parent. It can be useful to review a merge.
1183 parent. It can be useful to review a merge.
1182 """
1184 """
1183 if not changesets:
1185 if not changesets:
1184 raise util.Abort(_("export requires at least one changeset"))
1186 raise util.Abort(_("export requires at least one changeset"))
1185 seqno = 0
1187 seqno = 0
1186 revs = list(revrange(ui, repo, changesets))
1188 revs = list(revrange(ui, repo, changesets))
1187 total = len(revs)
1189 total = len(revs)
1188 revwidth = max(map(len, revs))
1190 revwidth = max(map(len, revs))
1189 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1191 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1190 ui.note(msg)
1192 ui.note(msg)
1191 for cset in revs:
1193 for cset in revs:
1192 seqno += 1
1194 seqno += 1
1193 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1195 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1194
1196
1195 def forget(ui, repo, *pats, **opts):
1197 def forget(ui, repo, *pats, **opts):
1196 """don't add the specified files on the next commit
1198 """don't add the specified files on the next commit
1197
1199
1198 Undo an 'hg add' scheduled for the next commit.
1200 Undo an 'hg add' scheduled for the next commit.
1199 """
1201 """
1200 forget = []
1202 forget = []
1201 for src, abs, rel, exact in walk(repo, pats, opts):
1203 for src, abs, rel, exact in walk(repo, pats, opts):
1202 if repo.dirstate.state(abs) == 'a':
1204 if repo.dirstate.state(abs) == 'a':
1203 forget.append(abs)
1205 forget.append(abs)
1204 if ui.verbose or not exact:
1206 if ui.verbose or not exact:
1205 ui.status(_('forgetting %s\n') % rel)
1207 ui.status(_('forgetting %s\n') % rel)
1206 repo.forget(forget)
1208 repo.forget(forget)
1207
1209
1208 def grep(ui, repo, pattern, *pats, **opts):
1210 def grep(ui, repo, pattern, *pats, **opts):
1209 """search for a pattern in specified files and revisions
1211 """search for a pattern in specified files and revisions
1210
1212
1211 Search revisions of files for a regular expression.
1213 Search revisions of files for a regular expression.
1212
1214
1213 This command behaves differently than Unix grep. It only accepts
1215 This command behaves differently than Unix grep. It only accepts
1214 Python/Perl regexps. It searches repository history, not the
1216 Python/Perl regexps. It searches repository history, not the
1215 working directory. It always prints the revision number in which
1217 working directory. It always prints the revision number in which
1216 a match appears.
1218 a match appears.
1217
1219
1218 By default, grep only prints output for the first revision of a
1220 By default, grep only prints output for the first revision of a
1219 file in which it finds a match. To get it to print every revision
1221 file in which it finds a match. To get it to print every revision
1220 that contains a change in match status ("-" for a match that
1222 that contains a change in match status ("-" for a match that
1221 becomes a non-match, or "+" for a non-match that becomes a match),
1223 becomes a non-match, or "+" for a non-match that becomes a match),
1222 use the --all flag.
1224 use the --all flag.
1223 """
1225 """
1224 reflags = 0
1226 reflags = 0
1225 if opts['ignore_case']:
1227 if opts['ignore_case']:
1226 reflags |= re.I
1228 reflags |= re.I
1227 regexp = re.compile(pattern, reflags)
1229 regexp = re.compile(pattern, reflags)
1228 sep, eol = ':', '\n'
1230 sep, eol = ':', '\n'
1229 if opts['print0']:
1231 if opts['print0']:
1230 sep = eol = '\0'
1232 sep = eol = '\0'
1231
1233
1232 fcache = {}
1234 fcache = {}
1233 def getfile(fn):
1235 def getfile(fn):
1234 if fn not in fcache:
1236 if fn not in fcache:
1235 fcache[fn] = repo.file(fn)
1237 fcache[fn] = repo.file(fn)
1236 return fcache[fn]
1238 return fcache[fn]
1237
1239
1238 def matchlines(body):
1240 def matchlines(body):
1239 begin = 0
1241 begin = 0
1240 linenum = 0
1242 linenum = 0
1241 while True:
1243 while True:
1242 match = regexp.search(body, begin)
1244 match = regexp.search(body, begin)
1243 if not match:
1245 if not match:
1244 break
1246 break
1245 mstart, mend = match.span()
1247 mstart, mend = match.span()
1246 linenum += body.count('\n', begin, mstart) + 1
1248 linenum += body.count('\n', begin, mstart) + 1
1247 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1249 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1248 lend = body.find('\n', mend)
1250 lend = body.find('\n', mend)
1249 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1251 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1250 begin = lend + 1
1252 begin = lend + 1
1251
1253
1252 class linestate(object):
1254 class linestate(object):
1253 def __init__(self, line, linenum, colstart, colend):
1255 def __init__(self, line, linenum, colstart, colend):
1254 self.line = line
1256 self.line = line
1255 self.linenum = linenum
1257 self.linenum = linenum
1256 self.colstart = colstart
1258 self.colstart = colstart
1257 self.colend = colend
1259 self.colend = colend
1258 def __eq__(self, other):
1260 def __eq__(self, other):
1259 return self.line == other.line
1261 return self.line == other.line
1260 def __hash__(self):
1262 def __hash__(self):
1261 return hash(self.line)
1263 return hash(self.line)
1262
1264
1263 matches = {}
1265 matches = {}
1264 def grepbody(fn, rev, body):
1266 def grepbody(fn, rev, body):
1265 matches[rev].setdefault(fn, {})
1267 matches[rev].setdefault(fn, {})
1266 m = matches[rev][fn]
1268 m = matches[rev][fn]
1267 for lnum, cstart, cend, line in matchlines(body):
1269 for lnum, cstart, cend, line in matchlines(body):
1268 s = linestate(line, lnum, cstart, cend)
1270 s = linestate(line, lnum, cstart, cend)
1269 m[s] = s
1271 m[s] = s
1270
1272
1271 prev = {}
1273 prev = {}
1272 ucache = {}
1274 ucache = {}
1273 def display(fn, rev, states, prevstates):
1275 def display(fn, rev, states, prevstates):
1274 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1276 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1275 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1277 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1276 counts = {'-': 0, '+': 0}
1278 counts = {'-': 0, '+': 0}
1277 filerevmatches = {}
1279 filerevmatches = {}
1278 for l in diff:
1280 for l in diff:
1279 if incrementing or not opts['all']:
1281 if incrementing or not opts['all']:
1280 change = ((l in prevstates) and '-') or '+'
1282 change = ((l in prevstates) and '-') or '+'
1281 r = rev
1283 r = rev
1282 else:
1284 else:
1283 change = ((l in states) and '-') or '+'
1285 change = ((l in states) and '-') or '+'
1284 r = prev[fn]
1286 r = prev[fn]
1285 cols = [fn, str(rev)]
1287 cols = [fn, str(rev)]
1286 if opts['line_number']:
1288 if opts['line_number']:
1287 cols.append(str(l.linenum))
1289 cols.append(str(l.linenum))
1288 if opts['all']:
1290 if opts['all']:
1289 cols.append(change)
1291 cols.append(change)
1290 if opts['user']:
1292 if opts['user']:
1291 cols.append(trimuser(ui, getchange(rev)[1], rev,
1293 cols.append(trimuser(ui, getchange(rev)[1], rev,
1292 ucache))
1294 ucache))
1293 if opts['files_with_matches']:
1295 if opts['files_with_matches']:
1294 c = (fn, rev)
1296 c = (fn, rev)
1295 if c in filerevmatches:
1297 if c in filerevmatches:
1296 continue
1298 continue
1297 filerevmatches[c] = 1
1299 filerevmatches[c] = 1
1298 else:
1300 else:
1299 cols.append(l.line)
1301 cols.append(l.line)
1300 ui.write(sep.join(cols), eol)
1302 ui.write(sep.join(cols), eol)
1301 counts[change] += 1
1303 counts[change] += 1
1302 return counts['+'], counts['-']
1304 return counts['+'], counts['-']
1303
1305
1304 fstate = {}
1306 fstate = {}
1305 skip = {}
1307 skip = {}
1306 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1308 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1307 count = 0
1309 count = 0
1308 incrementing = False
1310 incrementing = False
1309 for st, rev, fns in changeiter:
1311 for st, rev, fns in changeiter:
1310 if st == 'window':
1312 if st == 'window':
1311 incrementing = rev
1313 incrementing = rev
1312 matches.clear()
1314 matches.clear()
1313 elif st == 'add':
1315 elif st == 'add':
1314 change = repo.changelog.read(repo.lookup(str(rev)))
1316 change = repo.changelog.read(repo.lookup(str(rev)))
1315 mf = repo.manifest.read(change[0])
1317 mf = repo.manifest.read(change[0])
1316 matches[rev] = {}
1318 matches[rev] = {}
1317 for fn in fns:
1319 for fn in fns:
1318 if fn in skip:
1320 if fn in skip:
1319 continue
1321 continue
1320 fstate.setdefault(fn, {})
1322 fstate.setdefault(fn, {})
1321 try:
1323 try:
1322 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1324 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1323 except KeyError:
1325 except KeyError:
1324 pass
1326 pass
1325 elif st == 'iter':
1327 elif st == 'iter':
1326 states = matches[rev].items()
1328 states = matches[rev].items()
1327 states.sort()
1329 states.sort()
1328 for fn, m in states:
1330 for fn, m in states:
1329 if fn in skip:
1331 if fn in skip:
1330 continue
1332 continue
1331 if incrementing or not opts['all'] or fstate[fn]:
1333 if incrementing or not opts['all'] or fstate[fn]:
1332 pos, neg = display(fn, rev, m, fstate[fn])
1334 pos, neg = display(fn, rev, m, fstate[fn])
1333 count += pos + neg
1335 count += pos + neg
1334 if pos and not opts['all']:
1336 if pos and not opts['all']:
1335 skip[fn] = True
1337 skip[fn] = True
1336 fstate[fn] = m
1338 fstate[fn] = m
1337 prev[fn] = rev
1339 prev[fn] = rev
1338
1340
1339 if not incrementing:
1341 if not incrementing:
1340 fstate = fstate.items()
1342 fstate = fstate.items()
1341 fstate.sort()
1343 fstate.sort()
1342 for fn, state in fstate:
1344 for fn, state in fstate:
1343 if fn in skip:
1345 if fn in skip:
1344 continue
1346 continue
1345 display(fn, rev, {}, state)
1347 display(fn, rev, {}, state)
1346 return (count == 0 and 1) or 0
1348 return (count == 0 and 1) or 0
1347
1349
1348 def heads(ui, repo, **opts):
1350 def heads(ui, repo, **opts):
1349 """show current repository heads
1351 """show current repository heads
1350
1352
1351 Show all repository head changesets.
1353 Show all repository head changesets.
1352
1354
1353 Repository "heads" are changesets that don't have children
1355 Repository "heads" are changesets that don't have children
1354 changesets. They are where development generally takes place and
1356 changesets. They are where development generally takes place and
1355 are the usual targets for update and merge operations.
1357 are the usual targets for update and merge operations.
1356 """
1358 """
1357 if opts['rev']:
1359 if opts['rev']:
1358 heads = repo.heads(repo.lookup(opts['rev']))
1360 heads = repo.heads(repo.lookup(opts['rev']))
1359 else:
1361 else:
1360 heads = repo.heads()
1362 heads = repo.heads()
1361 br = None
1363 br = None
1362 if opts['branches']:
1364 if opts['branches']:
1363 br = repo.branchlookup(heads)
1365 br = repo.branchlookup(heads)
1364 for n in heads:
1366 for n in heads:
1365 show_changeset(ui, repo, changenode=n, brinfo=br)
1367 show_changeset(ui, repo, changenode=n, brinfo=br)
1366
1368
1367 def identify(ui, repo):
1369 def identify(ui, repo):
1368 """print information about the working copy
1370 """print information about the working copy
1369
1371
1370 Print a short summary of the current state of the repo.
1372 Print a short summary of the current state of the repo.
1371
1373
1372 This summary identifies the repository state using one or two parent
1374 This summary identifies the repository state using one or two parent
1373 hash identifiers, followed by a "+" if there are uncommitted changes
1375 hash identifiers, followed by a "+" if there are uncommitted changes
1374 in the working directory, followed by a list of tags for this revision.
1376 in the working directory, followed by a list of tags for this revision.
1375 """
1377 """
1376 parents = [p for p in repo.dirstate.parents() if p != nullid]
1378 parents = [p for p in repo.dirstate.parents() if p != nullid]
1377 if not parents:
1379 if not parents:
1378 ui.write(_("unknown\n"))
1380 ui.write(_("unknown\n"))
1379 return
1381 return
1380
1382
1381 hexfunc = ui.verbose and hex or short
1383 hexfunc = ui.verbose and hex or short
1382 (c, a, d, u) = repo.changes()
1384 modified, added, removed, unknown = repo.changes()
1383 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1385 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1384 (c or a or d) and "+" or "")]
1386 (modified or added or removed) and "+" or "")]
1385
1387
1386 if not ui.quiet:
1388 if not ui.quiet:
1387 # multiple tags for a single parent separated by '/'
1389 # multiple tags for a single parent separated by '/'
1388 parenttags = ['/'.join(tags)
1390 parenttags = ['/'.join(tags)
1389 for tags in map(repo.nodetags, parents) if tags]
1391 for tags in map(repo.nodetags, parents) if tags]
1390 # tags for multiple parents separated by ' + '
1392 # tags for multiple parents separated by ' + '
1391 if parenttags:
1393 if parenttags:
1392 output.append(' + '.join(parenttags))
1394 output.append(' + '.join(parenttags))
1393
1395
1394 ui.write("%s\n" % ' '.join(output))
1396 ui.write("%s\n" % ' '.join(output))
1395
1397
1396 def import_(ui, repo, patch1, *patches, **opts):
1398 def import_(ui, repo, patch1, *patches, **opts):
1397 """import an ordered set of patches
1399 """import an ordered set of patches
1398
1400
1399 Import a list of patches and commit them individually.
1401 Import a list of patches and commit them individually.
1400
1402
1401 If there are outstanding changes in the working directory, import
1403 If there are outstanding changes in the working directory, import
1402 will abort unless given the -f flag.
1404 will abort unless given the -f flag.
1403
1405
1404 If a patch looks like a mail message (its first line starts with
1406 If a patch looks like a mail message (its first line starts with
1405 "From " or looks like an RFC822 header), it will not be applied
1407 "From " or looks like an RFC822 header), it will not be applied
1406 unless the -f option is used. The importer neither parses nor
1408 unless the -f option is used. The importer neither parses nor
1407 discards mail headers, so use -f only to override the "mailness"
1409 discards mail headers, so use -f only to override the "mailness"
1408 safety check, not to import a real mail message.
1410 safety check, not to import a real mail message.
1409 """
1411 """
1410 patches = (patch1,) + patches
1412 patches = (patch1,) + patches
1411
1413
1412 if not opts['force']:
1414 if not opts['force']:
1413 (c, a, d, u) = repo.changes()
1415 modified, added, removed, unknown = repo.changes()
1414 if c or a or d:
1416 if modified or added or removed:
1415 raise util.Abort(_("outstanding uncommitted changes"))
1417 raise util.Abort(_("outstanding uncommitted changes"))
1416
1418
1417 d = opts["base"]
1419 d = opts["base"]
1418 strip = opts["strip"]
1420 strip = opts["strip"]
1419
1421
1420 mailre = re.compile(r'(?:From |[\w-]+:)')
1422 mailre = re.compile(r'(?:From |[\w-]+:)')
1421
1423
1422 # attempt to detect the start of a patch
1424 # attempt to detect the start of a patch
1423 # (this heuristic is borrowed from quilt)
1425 # (this heuristic is borrowed from quilt)
1424 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1426 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1425 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1427 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1426 '(---|\*\*\*)[ \t])')
1428 '(---|\*\*\*)[ \t])')
1427
1429
1428 for patch in patches:
1430 for patch in patches:
1429 ui.status(_("applying %s\n") % patch)
1431 ui.status(_("applying %s\n") % patch)
1430 pf = os.path.join(d, patch)
1432 pf = os.path.join(d, patch)
1431
1433
1432 message = []
1434 message = []
1433 user = None
1435 user = None
1434 hgpatch = False
1436 hgpatch = False
1435 for line in file(pf):
1437 for line in file(pf):
1436 line = line.rstrip()
1438 line = line.rstrip()
1437 if (not message and not hgpatch and
1439 if (not message and not hgpatch and
1438 mailre.match(line) and not opts['force']):
1440 mailre.match(line) and not opts['force']):
1439 if len(line) > 35:
1441 if len(line) > 35:
1440 line = line[:32] + '...'
1442 line = line[:32] + '...'
1441 raise util.Abort(_('first line looks like a '
1443 raise util.Abort(_('first line looks like a '
1442 'mail header: ') + line)
1444 'mail header: ') + line)
1443 if diffre.match(line):
1445 if diffre.match(line):
1444 break
1446 break
1445 elif hgpatch:
1447 elif hgpatch:
1446 # parse values when importing the result of an hg export
1448 # parse values when importing the result of an hg export
1447 if line.startswith("# User "):
1449 if line.startswith("# User "):
1448 user = line[7:]
1450 user = line[7:]
1449 ui.debug(_('User: %s\n') % user)
1451 ui.debug(_('User: %s\n') % user)
1450 elif not line.startswith("# ") and line:
1452 elif not line.startswith("# ") and line:
1451 message.append(line)
1453 message.append(line)
1452 hgpatch = False
1454 hgpatch = False
1453 elif line == '# HG changeset patch':
1455 elif line == '# HG changeset patch':
1454 hgpatch = True
1456 hgpatch = True
1455 message = [] # We may have collected garbage
1457 message = [] # We may have collected garbage
1456 else:
1458 else:
1457 message.append(line)
1459 message.append(line)
1458
1460
1459 # make sure message isn't empty
1461 # make sure message isn't empty
1460 if not message:
1462 if not message:
1461 message = _("imported patch %s\n") % patch
1463 message = _("imported patch %s\n") % patch
1462 else:
1464 else:
1463 message = "%s\n" % '\n'.join(message)
1465 message = "%s\n" % '\n'.join(message)
1464 ui.debug(_('message:\n%s\n') % message)
1466 ui.debug(_('message:\n%s\n') % message)
1465
1467
1466 files = util.patch(strip, pf, ui)
1468 files = util.patch(strip, pf, ui)
1467
1469
1468 if len(files) > 0:
1470 if len(files) > 0:
1469 addremove(ui, repo, *files)
1471 addremove(ui, repo, *files)
1470 repo.commit(files, message, user)
1472 repo.commit(files, message, user)
1471
1473
1472 def incoming(ui, repo, source="default", **opts):
1474 def incoming(ui, repo, source="default", **opts):
1473 """show new changesets found in source
1475 """show new changesets found in source
1474
1476
1475 Show new changesets found in the specified repo or the default
1477 Show new changesets found in the specified repo or the default
1476 pull repo. These are the changesets that would be pulled if a pull
1478 pull repo. These are the changesets that would be pulled if a pull
1477 was requested.
1479 was requested.
1478
1480
1479 Currently only local repositories are supported.
1481 Currently only local repositories are supported.
1480 """
1482 """
1481 source = ui.expandpath(source, repo.root)
1483 source = ui.expandpath(source, repo.root)
1482 other = hg.repository(ui, source)
1484 other = hg.repository(ui, source)
1483 if not other.local():
1485 if not other.local():
1484 raise util.Abort(_("incoming doesn't work for remote repositories yet"))
1486 raise util.Abort(_("incoming doesn't work for remote repositories yet"))
1485 o = repo.findincoming(other)
1487 o = repo.findincoming(other)
1486 if not o:
1488 if not o:
1487 return
1489 return
1488 o = other.changelog.nodesbetween(o)[0]
1490 o = other.changelog.nodesbetween(o)[0]
1489 if opts['newest_first']:
1491 if opts['newest_first']:
1490 o.reverse()
1492 o.reverse()
1491 for n in o:
1493 for n in o:
1492 parents = [p for p in other.changelog.parents(n) if p != nullid]
1494 parents = [p for p in other.changelog.parents(n) if p != nullid]
1493 if opts['no_merges'] and len(parents) == 2:
1495 if opts['no_merges'] and len(parents) == 2:
1494 continue
1496 continue
1495 show_changeset(ui, other, changenode=n)
1497 show_changeset(ui, other, changenode=n)
1496 if opts['patch']:
1498 if opts['patch']:
1497 prev = (parents and parents[0]) or nullid
1499 prev = (parents and parents[0]) or nullid
1498 dodiff(ui, ui, other, prev, n)
1500 dodiff(ui, ui, other, prev, n)
1499 ui.write("\n")
1501 ui.write("\n")
1500
1502
1501 def init(ui, dest="."):
1503 def init(ui, dest="."):
1502 """create a new repository in the given directory
1504 """create a new repository in the given directory
1503
1505
1504 Initialize a new repository in the given directory. If the given
1506 Initialize a new repository in the given directory. If the given
1505 directory does not exist, it is created.
1507 directory does not exist, it is created.
1506
1508
1507 If no directory is given, the current directory is used.
1509 If no directory is given, the current directory is used.
1508 """
1510 """
1509 if not os.path.exists(dest):
1511 if not os.path.exists(dest):
1510 os.mkdir(dest)
1512 os.mkdir(dest)
1511 hg.repository(ui, dest, create=1)
1513 hg.repository(ui, dest, create=1)
1512
1514
1513 def locate(ui, repo, *pats, **opts):
1515 def locate(ui, repo, *pats, **opts):
1514 """locate files matching specific patterns
1516 """locate files matching specific patterns
1515
1517
1516 Print all files under Mercurial control whose names match the
1518 Print all files under Mercurial control whose names match the
1517 given patterns.
1519 given patterns.
1518
1520
1519 This command searches the current directory and its
1521 This command searches the current directory and its
1520 subdirectories. To search an entire repository, move to the root
1522 subdirectories. To search an entire repository, move to the root
1521 of the repository.
1523 of the repository.
1522
1524
1523 If no patterns are given to match, this command prints all file
1525 If no patterns are given to match, this command prints all file
1524 names.
1526 names.
1525
1527
1526 If you want to feed the output of this command into the "xargs"
1528 If you want to feed the output of this command into the "xargs"
1527 command, use the "-0" option to both this command and "xargs".
1529 command, use the "-0" option to both this command and "xargs".
1528 This will avoid the problem of "xargs" treating single filenames
1530 This will avoid the problem of "xargs" treating single filenames
1529 that contain white space as multiple filenames.
1531 that contain white space as multiple filenames.
1530 """
1532 """
1531 end = opts['print0'] and '\0' or '\n'
1533 end = opts['print0'] and '\0' or '\n'
1532
1534
1533 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1535 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1534 if repo.dirstate.state(abs) == '?':
1536 if repo.dirstate.state(abs) == '?':
1535 continue
1537 continue
1536 if opts['fullpath']:
1538 if opts['fullpath']:
1537 ui.write(os.path.join(repo.root, abs), end)
1539 ui.write(os.path.join(repo.root, abs), end)
1538 else:
1540 else:
1539 ui.write(rel, end)
1541 ui.write(rel, end)
1540
1542
1541 def log(ui, repo, *pats, **opts):
1543 def log(ui, repo, *pats, **opts):
1542 """show revision history of entire repository or files
1544 """show revision history of entire repository or files
1543
1545
1544 Print the revision history of the specified files or the entire project.
1546 Print the revision history of the specified files or the entire project.
1545
1547
1546 By default this command outputs: changeset id and hash, tags,
1548 By default this command outputs: changeset id and hash, tags,
1547 non-trivial parents, user, date and time, and a summary for each
1549 non-trivial parents, user, date and time, and a summary for each
1548 commit. When the -v/--verbose switch is used, the list of changed
1550 commit. When the -v/--verbose switch is used, the list of changed
1549 files and full commit message is shown.
1551 files and full commit message is shown.
1550 """
1552 """
1551 class dui(object):
1553 class dui(object):
1552 # Implement and delegate some ui protocol. Save hunks of
1554 # Implement and delegate some ui protocol. Save hunks of
1553 # output for later display in the desired order.
1555 # output for later display in the desired order.
1554 def __init__(self, ui):
1556 def __init__(self, ui):
1555 self.ui = ui
1557 self.ui = ui
1556 self.hunk = {}
1558 self.hunk = {}
1557 def bump(self, rev):
1559 def bump(self, rev):
1558 self.rev = rev
1560 self.rev = rev
1559 self.hunk[rev] = []
1561 self.hunk[rev] = []
1560 def note(self, *args):
1562 def note(self, *args):
1561 if self.verbose:
1563 if self.verbose:
1562 self.write(*args)
1564 self.write(*args)
1563 def status(self, *args):
1565 def status(self, *args):
1564 if not self.quiet:
1566 if not self.quiet:
1565 self.write(*args)
1567 self.write(*args)
1566 def write(self, *args):
1568 def write(self, *args):
1567 self.hunk[self.rev].append(args)
1569 self.hunk[self.rev].append(args)
1568 def debug(self, *args):
1570 def debug(self, *args):
1569 if self.debugflag:
1571 if self.debugflag:
1570 self.write(*args)
1572 self.write(*args)
1571 def __getattr__(self, key):
1573 def __getattr__(self, key):
1572 return getattr(self.ui, key)
1574 return getattr(self.ui, key)
1573 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1575 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1574 for st, rev, fns in changeiter:
1576 for st, rev, fns in changeiter:
1575 if st == 'window':
1577 if st == 'window':
1576 du = dui(ui)
1578 du = dui(ui)
1577 elif st == 'add':
1579 elif st == 'add':
1578 du.bump(rev)
1580 du.bump(rev)
1579 changenode = repo.changelog.node(rev)
1581 changenode = repo.changelog.node(rev)
1580 parents = [p for p in repo.changelog.parents(changenode)
1582 parents = [p for p in repo.changelog.parents(changenode)
1581 if p != nullid]
1583 if p != nullid]
1582 if opts['no_merges'] and len(parents) == 2:
1584 if opts['no_merges'] and len(parents) == 2:
1583 continue
1585 continue
1584 if opts['only_merges'] and len(parents) != 2:
1586 if opts['only_merges'] and len(parents) != 2:
1585 continue
1587 continue
1586
1588
1587 br = None
1589 br = None
1588 if opts['keyword']:
1590 if opts['keyword']:
1589 changes = getchange(rev)
1591 changes = getchange(rev)
1590 miss = 0
1592 miss = 0
1591 for k in [kw.lower() for kw in opts['keyword']]:
1593 for k in [kw.lower() for kw in opts['keyword']]:
1592 if not (k in changes[1].lower() or
1594 if not (k in changes[1].lower() or
1593 k in changes[4].lower() or
1595 k in changes[4].lower() or
1594 k in " ".join(changes[3][:20]).lower()):
1596 k in " ".join(changes[3][:20]).lower()):
1595 miss = 1
1597 miss = 1
1596 break
1598 break
1597 if miss:
1599 if miss:
1598 continue
1600 continue
1599
1601
1600 if opts['branch']:
1602 if opts['branch']:
1601 br = repo.branchlookup([repo.changelog.node(rev)])
1603 br = repo.branchlookup([repo.changelog.node(rev)])
1602
1604
1603 show_changeset(du, repo, rev, brinfo=br)
1605 show_changeset(du, repo, rev, brinfo=br)
1604 if opts['patch']:
1606 if opts['patch']:
1605 prev = (parents and parents[0]) or nullid
1607 prev = (parents and parents[0]) or nullid
1606 dodiff(du, du, repo, prev, changenode, match=matchfn)
1608 dodiff(du, du, repo, prev, changenode, match=matchfn)
1607 du.write("\n\n")
1609 du.write("\n\n")
1608 elif st == 'iter':
1610 elif st == 'iter':
1609 for args in du.hunk[rev]:
1611 for args in du.hunk[rev]:
1610 ui.write(*args)
1612 ui.write(*args)
1611
1613
1612 def manifest(ui, repo, rev=None):
1614 def manifest(ui, repo, rev=None):
1613 """output the latest or given revision of the project manifest
1615 """output the latest or given revision of the project manifest
1614
1616
1615 Print a list of version controlled files for the given revision.
1617 Print a list of version controlled files for the given revision.
1616
1618
1617 The manifest is the list of files being version controlled. If no revision
1619 The manifest is the list of files being version controlled. If no revision
1618 is given then the tip is used.
1620 is given then the tip is used.
1619 """
1621 """
1620 if rev:
1622 if rev:
1621 try:
1623 try:
1622 # assume all revision numbers are for changesets
1624 # assume all revision numbers are for changesets
1623 n = repo.lookup(rev)
1625 n = repo.lookup(rev)
1624 change = repo.changelog.read(n)
1626 change = repo.changelog.read(n)
1625 n = change[0]
1627 n = change[0]
1626 except hg.RepoError:
1628 except hg.RepoError:
1627 n = repo.manifest.lookup(rev)
1629 n = repo.manifest.lookup(rev)
1628 else:
1630 else:
1629 n = repo.manifest.tip()
1631 n = repo.manifest.tip()
1630 m = repo.manifest.read(n)
1632 m = repo.manifest.read(n)
1631 mf = repo.manifest.readflags(n)
1633 mf = repo.manifest.readflags(n)
1632 files = m.keys()
1634 files = m.keys()
1633 files.sort()
1635 files.sort()
1634
1636
1635 for f in files:
1637 for f in files:
1636 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1638 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1637
1639
1638 def outgoing(ui, repo, dest="default-push", **opts):
1640 def outgoing(ui, repo, dest="default-push", **opts):
1639 """show changesets not found in destination
1641 """show changesets not found in destination
1640
1642
1641 Show changesets not found in the specified destination repo or the
1643 Show changesets not found in the specified destination repo or the
1642 default push repo. These are the changesets that would be pushed
1644 default push repo. These are the changesets that would be pushed
1643 if a push was requested.
1645 if a push was requested.
1644 """
1646 """
1645 dest = ui.expandpath(dest, repo.root)
1647 dest = ui.expandpath(dest, repo.root)
1646 other = hg.repository(ui, dest)
1648 other = hg.repository(ui, dest)
1647 o = repo.findoutgoing(other)
1649 o = repo.findoutgoing(other)
1648 o = repo.changelog.nodesbetween(o)[0]
1650 o = repo.changelog.nodesbetween(o)[0]
1649 if opts['newest_first']:
1651 if opts['newest_first']:
1650 o.reverse()
1652 o.reverse()
1651 for n in o:
1653 for n in o:
1652 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1654 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1653 if opts['no_merges'] and len(parents) == 2:
1655 if opts['no_merges'] and len(parents) == 2:
1654 continue
1656 continue
1655 show_changeset(ui, repo, changenode=n)
1657 show_changeset(ui, repo, changenode=n)
1656 if opts['patch']:
1658 if opts['patch']:
1657 prev = (parents and parents[0]) or nullid
1659 prev = (parents and parents[0]) or nullid
1658 dodiff(ui, ui, repo, prev, n)
1660 dodiff(ui, ui, repo, prev, n)
1659 ui.write("\n")
1661 ui.write("\n")
1660
1662
1661 def parents(ui, repo, rev=None):
1663 def parents(ui, repo, rev=None):
1662 """show the parents of the working dir or revision
1664 """show the parents of the working dir or revision
1663
1665
1664 Print the working directory's parent revisions.
1666 Print the working directory's parent revisions.
1665 """
1667 """
1666 if rev:
1668 if rev:
1667 p = repo.changelog.parents(repo.lookup(rev))
1669 p = repo.changelog.parents(repo.lookup(rev))
1668 else:
1670 else:
1669 p = repo.dirstate.parents()
1671 p = repo.dirstate.parents()
1670
1672
1671 for n in p:
1673 for n in p:
1672 if n != nullid:
1674 if n != nullid:
1673 show_changeset(ui, repo, changenode=n)
1675 show_changeset(ui, repo, changenode=n)
1674
1676
1675 def paths(ui, search=None):
1677 def paths(ui, search=None):
1676 """show definition of symbolic path names
1678 """show definition of symbolic path names
1677
1679
1678 Show definition of symbolic path name NAME. If no name is given, show
1680 Show definition of symbolic path name NAME. If no name is given, show
1679 definition of available names.
1681 definition of available names.
1680
1682
1681 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1683 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1682 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1684 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1683 """
1685 """
1684 try:
1686 try:
1685 repo = hg.repository(ui=ui)
1687 repo = hg.repository(ui=ui)
1686 except hg.RepoError:
1688 except hg.RepoError:
1687 pass
1689 pass
1688
1690
1689 if search:
1691 if search:
1690 for name, path in ui.configitems("paths"):
1692 for name, path in ui.configitems("paths"):
1691 if name == search:
1693 if name == search:
1692 ui.write("%s\n" % path)
1694 ui.write("%s\n" % path)
1693 return
1695 return
1694 ui.warn(_("not found!\n"))
1696 ui.warn(_("not found!\n"))
1695 return 1
1697 return 1
1696 else:
1698 else:
1697 for name, path in ui.configitems("paths"):
1699 for name, path in ui.configitems("paths"):
1698 ui.write("%s = %s\n" % (name, path))
1700 ui.write("%s = %s\n" % (name, path))
1699
1701
1700 def pull(ui, repo, source="default", **opts):
1702 def pull(ui, repo, source="default", **opts):
1701 """pull changes from the specified source
1703 """pull changes from the specified source
1702
1704
1703 Pull changes from a remote repository to a local one.
1705 Pull changes from a remote repository to a local one.
1704
1706
1705 This finds all changes from the repository at the specified path
1707 This finds all changes from the repository at the specified path
1706 or URL and adds them to the local repository. By default, this
1708 or URL and adds them to the local repository. By default, this
1707 does not update the copy of the project in the working directory.
1709 does not update the copy of the project in the working directory.
1708
1710
1709 Valid URLs are of the form:
1711 Valid URLs are of the form:
1710
1712
1711 local/filesystem/path
1713 local/filesystem/path
1712 http://[user@]host[:port][/path]
1714 http://[user@]host[:port][/path]
1713 https://[user@]host[:port][/path]
1715 https://[user@]host[:port][/path]
1714 ssh://[user@]host[:port][/path]
1716 ssh://[user@]host[:port][/path]
1715
1717
1716 SSH requires an accessible shell account on the destination machine
1718 SSH requires an accessible shell account on the destination machine
1717 and a copy of hg in the remote path. With SSH, paths are relative
1719 and a copy of hg in the remote path. With SSH, paths are relative
1718 to the remote user's home directory by default; use two slashes at
1720 to the remote user's home directory by default; use two slashes at
1719 the start of a path to specify it as relative to the filesystem root.
1721 the start of a path to specify it as relative to the filesystem root.
1720 """
1722 """
1721 source = ui.expandpath(source, repo.root)
1723 source = ui.expandpath(source, repo.root)
1722 ui.status(_('pulling from %s\n') % (source))
1724 ui.status(_('pulling from %s\n') % (source))
1723
1725
1724 if opts['ssh']:
1726 if opts['ssh']:
1725 ui.setconfig("ui", "ssh", opts['ssh'])
1727 ui.setconfig("ui", "ssh", opts['ssh'])
1726 if opts['remotecmd']:
1728 if opts['remotecmd']:
1727 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1729 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1728
1730
1729 other = hg.repository(ui, source)
1731 other = hg.repository(ui, source)
1730 revs = None
1732 revs = None
1731 if opts['rev'] and not other.local():
1733 if opts['rev'] and not other.local():
1732 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
1734 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
1733 elif opts['rev']:
1735 elif opts['rev']:
1734 revs = [other.lookup(rev) for rev in opts['rev']]
1736 revs = [other.lookup(rev) for rev in opts['rev']]
1735 r = repo.pull(other, heads=revs)
1737 r = repo.pull(other, heads=revs)
1736 if not r:
1738 if not r:
1737 if opts['update']:
1739 if opts['update']:
1738 return update(ui, repo)
1740 return update(ui, repo)
1739 else:
1741 else:
1740 ui.status(_("(run 'hg update' to get a working copy)\n"))
1742 ui.status(_("(run 'hg update' to get a working copy)\n"))
1741
1743
1742 return r
1744 return r
1743
1745
1744 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1746 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1745 """push changes to the specified destination
1747 """push changes to the specified destination
1746
1748
1747 Push changes from the local repository to the given destination.
1749 Push changes from the local repository to the given destination.
1748
1750
1749 This is the symmetrical operation for pull. It helps to move
1751 This is the symmetrical operation for pull. It helps to move
1750 changes from the current repository to a different one. If the
1752 changes from the current repository to a different one. If the
1751 destination is local this is identical to a pull in that directory
1753 destination is local this is identical to a pull in that directory
1752 from the current one.
1754 from the current one.
1753
1755
1754 By default, push will refuse to run if it detects the result would
1756 By default, push will refuse to run if it detects the result would
1755 increase the number of remote heads. This generally indicates the
1757 increase the number of remote heads. This generally indicates the
1756 the client has forgotten to sync and merge before pushing.
1758 the client has forgotten to sync and merge before pushing.
1757
1759
1758 Valid URLs are of the form:
1760 Valid URLs are of the form:
1759
1761
1760 local/filesystem/path
1762 local/filesystem/path
1761 ssh://[user@]host[:port][/path]
1763 ssh://[user@]host[:port][/path]
1762
1764
1763 SSH requires an accessible shell account on the destination
1765 SSH requires an accessible shell account on the destination
1764 machine and a copy of hg in the remote path.
1766 machine and a copy of hg in the remote path.
1765 """
1767 """
1766 dest = ui.expandpath(dest, repo.root)
1768 dest = ui.expandpath(dest, repo.root)
1767 ui.status('pushing to %s\n' % (dest))
1769 ui.status('pushing to %s\n' % (dest))
1768
1770
1769 if ssh:
1771 if ssh:
1770 ui.setconfig("ui", "ssh", ssh)
1772 ui.setconfig("ui", "ssh", ssh)
1771 if remotecmd:
1773 if remotecmd:
1772 ui.setconfig("ui", "remotecmd", remotecmd)
1774 ui.setconfig("ui", "remotecmd", remotecmd)
1773
1775
1774 other = hg.repository(ui, dest)
1776 other = hg.repository(ui, dest)
1775 r = repo.push(other, force)
1777 r = repo.push(other, force)
1776 return r
1778 return r
1777
1779
1778 def rawcommit(ui, repo, *flist, **rc):
1780 def rawcommit(ui, repo, *flist, **rc):
1779 """raw commit interface
1781 """raw commit interface
1780
1782
1781 Lowlevel commit, for use in helper scripts.
1783 Lowlevel commit, for use in helper scripts.
1782
1784
1783 This command is not intended to be used by normal users, as it is
1785 This command is not intended to be used by normal users, as it is
1784 primarily useful for importing from other SCMs.
1786 primarily useful for importing from other SCMs.
1785 """
1787 """
1786 message = rc['message']
1788 message = rc['message']
1787 if not message and rc['logfile']:
1789 if not message and rc['logfile']:
1788 try:
1790 try:
1789 message = open(rc['logfile']).read()
1791 message = open(rc['logfile']).read()
1790 except IOError:
1792 except IOError:
1791 pass
1793 pass
1792 if not message and not rc['logfile']:
1794 if not message and not rc['logfile']:
1793 raise util.Abort(_("missing commit message"))
1795 raise util.Abort(_("missing commit message"))
1794
1796
1795 files = relpath(repo, list(flist))
1797 files = relpath(repo, list(flist))
1796 if rc['files']:
1798 if rc['files']:
1797 files += open(rc['files']).read().splitlines()
1799 files += open(rc['files']).read().splitlines()
1798
1800
1799 rc['parent'] = map(repo.lookup, rc['parent'])
1801 rc['parent'] = map(repo.lookup, rc['parent'])
1800
1802
1801 try:
1803 try:
1802 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1804 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1803 except ValueError, inst:
1805 except ValueError, inst:
1804 raise util.Abort(str(inst))
1806 raise util.Abort(str(inst))
1805
1807
1806 def recover(ui, repo):
1808 def recover(ui, repo):
1807 """roll back an interrupted transaction
1809 """roll back an interrupted transaction
1808
1810
1809 Recover from an interrupted commit or pull.
1811 Recover from an interrupted commit or pull.
1810
1812
1811 This command tries to fix the repository status after an interrupted
1813 This command tries to fix the repository status after an interrupted
1812 operation. It should only be necessary when Mercurial suggests it.
1814 operation. It should only be necessary when Mercurial suggests it.
1813 """
1815 """
1814 if repo.recover():
1816 if repo.recover():
1815 return repo.verify()
1817 return repo.verify()
1816 return False
1818 return False
1817
1819
1818 def remove(ui, repo, pat, *pats, **opts):
1820 def remove(ui, repo, pat, *pats, **opts):
1819 """remove the specified files on the next commit
1821 """remove the specified files on the next commit
1820
1822
1821 Schedule the indicated files for removal from the repository.
1823 Schedule the indicated files for removal from the repository.
1822
1824
1823 This command schedules the files to be removed at the next commit.
1825 This command schedules the files to be removed at the next commit.
1824 This only removes files from the current branch, not from the
1826 This only removes files from the current branch, not from the
1825 entire project history. If the files still exist in the working
1827 entire project history. If the files still exist in the working
1826 directory, they will be deleted from it.
1828 directory, they will be deleted from it.
1827 """
1829 """
1828 names = []
1830 names = []
1829 def okaytoremove(abs, rel, exact):
1831 def okaytoremove(abs, rel, exact):
1830 c, a, d, u = repo.changes(files = [abs])
1832 modified, added, removed, unknown = repo.changes(files=[abs])
1831 reason = None
1833 reason = None
1832 if c:
1834 if modified:
1833 reason = _('is modified')
1835 reason = _('is modified')
1834 elif a:
1836 elif added:
1835 reason = _('has been marked for add')
1837 reason = _('has been marked for add')
1836 elif u:
1838 elif unknown:
1837 reason = _('is not managed')
1839 reason = _('is not managed')
1838 if reason:
1840 if reason:
1839 if exact:
1841 if exact:
1840 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
1842 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
1841 else:
1843 else:
1842 return True
1844 return True
1843 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1845 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1844 if okaytoremove(abs, rel, exact):
1846 if okaytoremove(abs, rel, exact):
1845 if ui.verbose or not exact:
1847 if ui.verbose or not exact:
1846 ui.status(_('removing %s\n') % rel)
1848 ui.status(_('removing %s\n') % rel)
1847 names.append(abs)
1849 names.append(abs)
1848 repo.remove(names, unlink=True)
1850 repo.remove(names, unlink=True)
1849
1851
1850 def rename(ui, repo, *pats, **opts):
1852 def rename(ui, repo, *pats, **opts):
1851 """rename files; equivalent of copy + remove
1853 """rename files; equivalent of copy + remove
1852
1854
1853 Mark dest as copies of sources; mark sources for deletion. If
1855 Mark dest as copies of sources; mark sources for deletion. If
1854 dest is a directory, copies are put in that directory. If dest is
1856 dest is a directory, copies are put in that directory. If dest is
1855 a file, there can only be one source.
1857 a file, there can only be one source.
1856
1858
1857 By default, this command copies the contents of files as they
1859 By default, this command copies the contents of files as they
1858 stand in the working directory. If invoked with --after, the
1860 stand in the working directory. If invoked with --after, the
1859 operation is recorded, but no copying is performed.
1861 operation is recorded, but no copying is performed.
1860
1862
1861 This command takes effect in the next commit.
1863 This command takes effect in the next commit.
1862
1864
1863 NOTE: This command should be treated as experimental. While it
1865 NOTE: This command should be treated as experimental. While it
1864 should properly record rename files, this information is not yet
1866 should properly record rename files, this information is not yet
1865 fully used by merge, nor fully reported by log.
1867 fully used by merge, nor fully reported by log.
1866 """
1868 """
1867 errs, copied = docopy(ui, repo, pats, opts)
1869 errs, copied = docopy(ui, repo, pats, opts)
1868 names = []
1870 names = []
1869 for abs, rel, exact in copied:
1871 for abs, rel, exact in copied:
1870 if ui.verbose or not exact:
1872 if ui.verbose or not exact:
1871 ui.status(_('removing %s\n') % rel)
1873 ui.status(_('removing %s\n') % rel)
1872 names.append(abs)
1874 names.append(abs)
1873 repo.remove(names, unlink=True)
1875 repo.remove(names, unlink=True)
1874 return errs
1876 return errs
1875
1877
1876 def revert(ui, repo, *pats, **opts):
1878 def revert(ui, repo, *pats, **opts):
1877 """revert modified files or dirs back to their unmodified states
1879 """revert modified files or dirs back to their unmodified states
1878
1880
1879 Revert any uncommitted modifications made to the named files or
1881 Revert any uncommitted modifications made to the named files or
1880 directories. This restores the contents of the affected files to
1882 directories. This restores the contents of the affected files to
1881 an unmodified state.
1883 an unmodified state.
1882
1884
1883 If a file has been deleted, it is recreated. If the executable
1885 If a file has been deleted, it is recreated. If the executable
1884 mode of a file was changed, it is reset.
1886 mode of a file was changed, it is reset.
1885
1887
1886 If names are given, all files matching the names are reverted.
1888 If names are given, all files matching the names are reverted.
1887
1889
1888 If no arguments are given, all files in the repository are reverted.
1890 If no arguments are given, all files in the repository are reverted.
1889 """
1891 """
1890 node = opts['rev'] and repo.lookup(opts['rev']) or \
1892 node = opts['rev'] and repo.lookup(opts['rev']) or \
1891 repo.dirstate.parents()[0]
1893 repo.dirstate.parents()[0]
1892
1894
1893 files, choose, anypats, cwd = matchpats(repo, pats, opts)
1895 files, choose, anypats, cwd = matchpats(repo, pats, opts)
1894 (c, a, d, u) = repo.changes(match=choose)
1896 modified, added, removed, unknown = repo.changes(match=choose)
1895 repo.forget(a)
1897 repo.forget(added)
1896 repo.undelete(d)
1898 repo.undelete(removed)
1897
1899
1898 return repo.update(node, False, True, choose, False)
1900 return repo.update(node, False, True, choose, False)
1899
1901
1900 def root(ui, repo):
1902 def root(ui, repo):
1901 """print the root (top) of the current working dir
1903 """print the root (top) of the current working dir
1902
1904
1903 Print the root directory of the current repository.
1905 Print the root directory of the current repository.
1904 """
1906 """
1905 ui.write(repo.root + "\n")
1907 ui.write(repo.root + "\n")
1906
1908
1907 def serve(ui, repo, **opts):
1909 def serve(ui, repo, **opts):
1908 """export the repository via HTTP
1910 """export the repository via HTTP
1909
1911
1910 Start a local HTTP repository browser and pull server.
1912 Start a local HTTP repository browser and pull server.
1911
1913
1912 By default, the server logs accesses to stdout and errors to
1914 By default, the server logs accesses to stdout and errors to
1913 stderr. Use the "-A" and "-E" options to log to files.
1915 stderr. Use the "-A" and "-E" options to log to files.
1914 """
1916 """
1915
1917
1916 if opts["stdio"]:
1918 if opts["stdio"]:
1917 fin, fout = sys.stdin, sys.stdout
1919 fin, fout = sys.stdin, sys.stdout
1918 sys.stdout = sys.stderr
1920 sys.stdout = sys.stderr
1919
1921
1920 # Prevent insertion/deletion of CRs
1922 # Prevent insertion/deletion of CRs
1921 util.set_binary(fin)
1923 util.set_binary(fin)
1922 util.set_binary(fout)
1924 util.set_binary(fout)
1923
1925
1924 def getarg():
1926 def getarg():
1925 argline = fin.readline()[:-1]
1927 argline = fin.readline()[:-1]
1926 arg, l = argline.split()
1928 arg, l = argline.split()
1927 val = fin.read(int(l))
1929 val = fin.read(int(l))
1928 return arg, val
1930 return arg, val
1929 def respond(v):
1931 def respond(v):
1930 fout.write("%d\n" % len(v))
1932 fout.write("%d\n" % len(v))
1931 fout.write(v)
1933 fout.write(v)
1932 fout.flush()
1934 fout.flush()
1933
1935
1934 lock = None
1936 lock = None
1935
1937
1936 while 1:
1938 while 1:
1937 cmd = fin.readline()[:-1]
1939 cmd = fin.readline()[:-1]
1938 if cmd == '':
1940 if cmd == '':
1939 return
1941 return
1940 if cmd == "heads":
1942 if cmd == "heads":
1941 h = repo.heads()
1943 h = repo.heads()
1942 respond(" ".join(map(hex, h)) + "\n")
1944 respond(" ".join(map(hex, h)) + "\n")
1943 if cmd == "lock":
1945 if cmd == "lock":
1944 lock = repo.lock()
1946 lock = repo.lock()
1945 respond("")
1947 respond("")
1946 if cmd == "unlock":
1948 if cmd == "unlock":
1947 if lock:
1949 if lock:
1948 lock.release()
1950 lock.release()
1949 lock = None
1951 lock = None
1950 respond("")
1952 respond("")
1951 elif cmd == "branches":
1953 elif cmd == "branches":
1952 arg, nodes = getarg()
1954 arg, nodes = getarg()
1953 nodes = map(bin, nodes.split(" "))
1955 nodes = map(bin, nodes.split(" "))
1954 r = []
1956 r = []
1955 for b in repo.branches(nodes):
1957 for b in repo.branches(nodes):
1956 r.append(" ".join(map(hex, b)) + "\n")
1958 r.append(" ".join(map(hex, b)) + "\n")
1957 respond("".join(r))
1959 respond("".join(r))
1958 elif cmd == "between":
1960 elif cmd == "between":
1959 arg, pairs = getarg()
1961 arg, pairs = getarg()
1960 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1962 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1961 r = []
1963 r = []
1962 for b in repo.between(pairs):
1964 for b in repo.between(pairs):
1963 r.append(" ".join(map(hex, b)) + "\n")
1965 r.append(" ".join(map(hex, b)) + "\n")
1964 respond("".join(r))
1966 respond("".join(r))
1965 elif cmd == "changegroup":
1967 elif cmd == "changegroup":
1966 nodes = []
1968 nodes = []
1967 arg, roots = getarg()
1969 arg, roots = getarg()
1968 nodes = map(bin, roots.split(" "))
1970 nodes = map(bin, roots.split(" "))
1969
1971
1970 cg = repo.changegroup(nodes)
1972 cg = repo.changegroup(nodes)
1971 while 1:
1973 while 1:
1972 d = cg.read(4096)
1974 d = cg.read(4096)
1973 if not d:
1975 if not d:
1974 break
1976 break
1975 fout.write(d)
1977 fout.write(d)
1976
1978
1977 fout.flush()
1979 fout.flush()
1978
1980
1979 elif cmd == "addchangegroup":
1981 elif cmd == "addchangegroup":
1980 if not lock:
1982 if not lock:
1981 respond("not locked")
1983 respond("not locked")
1982 continue
1984 continue
1983 respond("")
1985 respond("")
1984
1986
1985 r = repo.addchangegroup(fin)
1987 r = repo.addchangegroup(fin)
1986 respond("")
1988 respond("")
1987
1989
1988 optlist = "name templates style address port ipv6 accesslog errorlog"
1990 optlist = "name templates style address port ipv6 accesslog errorlog"
1989 for o in optlist.split():
1991 for o in optlist.split():
1990 if opts[o]:
1992 if opts[o]:
1991 ui.setconfig("web", o, opts[o])
1993 ui.setconfig("web", o, opts[o])
1992
1994
1993 try:
1995 try:
1994 httpd = hgweb.create_server(repo)
1996 httpd = hgweb.create_server(repo)
1995 except socket.error, inst:
1997 except socket.error, inst:
1996 raise util.Abort(_('cannot start server: ') + inst.args[1])
1998 raise util.Abort(_('cannot start server: ') + inst.args[1])
1997
1999
1998 if ui.verbose:
2000 if ui.verbose:
1999 addr, port = httpd.socket.getsockname()
2001 addr, port = httpd.socket.getsockname()
2000 if addr == '0.0.0.0':
2002 if addr == '0.0.0.0':
2001 addr = socket.gethostname()
2003 addr = socket.gethostname()
2002 else:
2004 else:
2003 try:
2005 try:
2004 addr = socket.gethostbyaddr(addr)[0]
2006 addr = socket.gethostbyaddr(addr)[0]
2005 except socket.error:
2007 except socket.error:
2006 pass
2008 pass
2007 if port != 80:
2009 if port != 80:
2008 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2010 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2009 else:
2011 else:
2010 ui.status(_('listening at http://%s/\n') % addr)
2012 ui.status(_('listening at http://%s/\n') % addr)
2011 httpd.serve_forever()
2013 httpd.serve_forever()
2012
2014
2013 def status(ui, repo, *pats, **opts):
2015 def status(ui, repo, *pats, **opts):
2014 """show changed files in the working directory
2016 """show changed files in the working directory
2015
2017
2016 Show changed files in the repository. If names are
2018 Show changed files in the repository. If names are
2017 given, only files that match are shown.
2019 given, only files that match are shown.
2018
2020
2019 The codes used to show the status of files are:
2021 The codes used to show the status of files are:
2020 M = modified
2022 M = modified
2021 A = added
2023 A = added
2022 R = removed
2024 R = removed
2023 ? = not tracked
2025 ? = not tracked
2024 """
2026 """
2025
2027
2026 files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
2028 files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
2027 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
2029 modified, added, removed, unknown = [
2028 for n in repo.changes(files=files, match=matchfn)]
2030 [util.pathto(cwd, x) for x in n]
2031 for n in repo.changes(files=files, match=matchfn)]
2029
2032
2030 changetypes = [(_('modified'), 'M', c),
2033 changetypes = [(_('modified'), 'M', modified),
2031 (_('added'), 'A', a),
2034 (_('added'), 'A', added),
2032 (_('removed'), 'R', d),
2035 (_('removed'), 'R', removed),
2033 (_('unknown'), '?', u)]
2036 (_('unknown'), '?', unknown)]
2034
2037
2035 end = opts['print0'] and '\0' or '\n'
2038 end = opts['print0'] and '\0' or '\n'
2036
2039
2037 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2040 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2038 or changetypes):
2041 or changetypes):
2039 if opts['no_status']:
2042 if opts['no_status']:
2040 format = "%%s%s" % end
2043 format = "%%s%s" % end
2041 else:
2044 else:
2042 format = "%s %%s%s" % (char, end);
2045 format = "%s %%s%s" % (char, end);
2043
2046
2044 for f in changes:
2047 for f in changes:
2045 ui.write(format % f)
2048 ui.write(format % f)
2046
2049
2047 def tag(ui, repo, name, rev_=None, **opts):
2050 def tag(ui, repo, name, rev_=None, **opts):
2048 """add a tag for the current tip or a given revision
2051 """add a tag for the current tip or a given revision
2049
2052
2050 Name a particular revision using <name>.
2053 Name a particular revision using <name>.
2051
2054
2052 Tags are used to name particular revisions of the repository and are
2055 Tags are used to name particular revisions of the repository and are
2053 very useful to compare different revision, to go back to significant
2056 very useful to compare different revision, to go back to significant
2054 earlier versions or to mark branch points as releases, etc.
2057 earlier versions or to mark branch points as releases, etc.
2055
2058
2056 If no revision is given, the tip is used.
2059 If no revision is given, the tip is used.
2057
2060
2058 To facilitate version control, distribution, and merging of tags,
2061 To facilitate version control, distribution, and merging of tags,
2059 they are stored as a file named ".hgtags" which is managed
2062 they are stored as a file named ".hgtags" which is managed
2060 similarly to other project files and can be hand-edited if
2063 similarly to other project files and can be hand-edited if
2061 necessary.
2064 necessary.
2062 """
2065 """
2063 if name == "tip":
2066 if name == "tip":
2064 raise util.Abort(_("the name 'tip' is reserved"))
2067 raise util.Abort(_("the name 'tip' is reserved"))
2065 if opts['rev']:
2068 if opts['rev']:
2066 rev_ = opts['rev']
2069 rev_ = opts['rev']
2067 if rev_:
2070 if rev_:
2068 r = hex(repo.lookup(rev_))
2071 r = hex(repo.lookup(rev_))
2069 else:
2072 else:
2070 r = hex(repo.changelog.tip())
2073 r = hex(repo.changelog.tip())
2071
2074
2072 disallowed = (revrangesep, '\r', '\n')
2075 disallowed = (revrangesep, '\r', '\n')
2073 for c in disallowed:
2076 for c in disallowed:
2074 if name.find(c) >= 0:
2077 if name.find(c) >= 0:
2075 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2078 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2076
2079
2077 if opts['local']:
2080 if opts['local']:
2078 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2081 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2079 return
2082 return
2080
2083
2081 (c, a, d, u) = repo.changes()
2084 for x in repo.changes():
2082 for x in (c, a, d, u):
2083 if ".hgtags" in x:
2085 if ".hgtags" in x:
2084 raise util.Abort(_("working copy of .hgtags is changed "
2086 raise util.Abort(_("working copy of .hgtags is changed "
2085 "(please commit .hgtags manually)"))
2087 "(please commit .hgtags manually)"))
2086
2088
2087 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2089 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2088 if repo.dirstate.state(".hgtags") == '?':
2090 if repo.dirstate.state(".hgtags") == '?':
2089 repo.add([".hgtags"])
2091 repo.add([".hgtags"])
2090
2092
2091 message = (opts['message'] or
2093 message = (opts['message'] or
2092 _("Added tag %s for changeset %s") % (name, r))
2094 _("Added tag %s for changeset %s") % (name, r))
2093 try:
2095 try:
2094 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2096 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2095 except ValueError, inst:
2097 except ValueError, inst:
2096 raise util.Abort(str(inst))
2098 raise util.Abort(str(inst))
2097
2099
2098 def tags(ui, repo):
2100 def tags(ui, repo):
2099 """list repository tags
2101 """list repository tags
2100
2102
2101 List the repository tags.
2103 List the repository tags.
2102
2104
2103 This lists both regular and local tags.
2105 This lists both regular and local tags.
2104 """
2106 """
2105
2107
2106 l = repo.tagslist()
2108 l = repo.tagslist()
2107 l.reverse()
2109 l.reverse()
2108 for t, n in l:
2110 for t, n in l:
2109 try:
2111 try:
2110 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2112 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2111 except KeyError:
2113 except KeyError:
2112 r = " ?:?"
2114 r = " ?:?"
2113 ui.write("%-30s %s\n" % (t, r))
2115 ui.write("%-30s %s\n" % (t, r))
2114
2116
2115 def tip(ui, repo):
2117 def tip(ui, repo):
2116 """show the tip revision
2118 """show the tip revision
2117
2119
2118 Show the tip revision.
2120 Show the tip revision.
2119 """
2121 """
2120 n = repo.changelog.tip()
2122 n = repo.changelog.tip()
2121 show_changeset(ui, repo, changenode=n)
2123 show_changeset(ui, repo, changenode=n)
2122
2124
2123 def unbundle(ui, repo, fname, **opts):
2125 def unbundle(ui, repo, fname, **opts):
2124 """apply a changegroup file
2126 """apply a changegroup file
2125
2127
2126 Apply a compressed changegroup file generated by the bundle
2128 Apply a compressed changegroup file generated by the bundle
2127 command.
2129 command.
2128 """
2130 """
2129 f = urllib.urlopen(fname)
2131 f = urllib.urlopen(fname)
2130
2132
2131 if f.read(4) != "HG10":
2133 if f.read(4) != "HG10":
2132 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2134 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2133
2135
2134 def bzgenerator(f):
2136 def bzgenerator(f):
2135 zd = bz2.BZ2Decompressor()
2137 zd = bz2.BZ2Decompressor()
2136 for chunk in f:
2138 for chunk in f:
2137 yield zd.decompress(chunk)
2139 yield zd.decompress(chunk)
2138
2140
2139 bzgen = bzgenerator(util.filechunkiter(f, 4096))
2141 bzgen = bzgenerator(util.filechunkiter(f, 4096))
2140 if repo.addchangegroup(util.chunkbuffer(bzgen)):
2142 if repo.addchangegroup(util.chunkbuffer(bzgen)):
2141 return 1
2143 return 1
2142
2144
2143 if opts['update']:
2145 if opts['update']:
2144 return update(ui, repo)
2146 return update(ui, repo)
2145 else:
2147 else:
2146 ui.status(_("(run 'hg update' to get a working copy)\n"))
2148 ui.status(_("(run 'hg update' to get a working copy)\n"))
2147
2149
2148 def undo(ui, repo):
2150 def undo(ui, repo):
2149 """undo the last commit or pull
2151 """undo the last commit or pull
2150
2152
2151 Roll back the last pull or commit transaction on the
2153 Roll back the last pull or commit transaction on the
2152 repository, restoring the project to its earlier state.
2154 repository, restoring the project to its earlier state.
2153
2155
2154 This command should be used with care. There is only one level of
2156 This command should be used with care. There is only one level of
2155 undo and there is no redo.
2157 undo and there is no redo.
2156
2158
2157 This command is not intended for use on public repositories. Once
2159 This command is not intended for use on public repositories. Once
2158 a change is visible for pull by other users, undoing it locally is
2160 a change is visible for pull by other users, undoing it locally is
2159 ineffective.
2161 ineffective.
2160 """
2162 """
2161 repo.undo()
2163 repo.undo()
2162
2164
2163 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2165 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2164 branch=None):
2166 branch=None):
2165 """update or merge working directory
2167 """update or merge working directory
2166
2168
2167 Update the working directory to the specified revision.
2169 Update the working directory to the specified revision.
2168
2170
2169 If there are no outstanding changes in the working directory and
2171 If there are no outstanding changes in the working directory and
2170 there is a linear relationship between the current version and the
2172 there is a linear relationship between the current version and the
2171 requested version, the result is the requested version.
2173 requested version, the result is the requested version.
2172
2174
2173 Otherwise the result is a merge between the contents of the
2175 Otherwise the result is a merge between the contents of the
2174 current working directory and the requested version. Files that
2176 current working directory and the requested version. Files that
2175 changed between either parent are marked as changed for the next
2177 changed between either parent are marked as changed for the next
2176 commit and a commit must be performed before any further updates
2178 commit and a commit must be performed before any further updates
2177 are allowed.
2179 are allowed.
2178
2180
2179 By default, update will refuse to run if doing so would require
2181 By default, update will refuse to run if doing so would require
2180 merging or discarding local changes.
2182 merging or discarding local changes.
2181 """
2183 """
2182 if branch:
2184 if branch:
2183 br = repo.branchlookup(branch=branch)
2185 br = repo.branchlookup(branch=branch)
2184 found = []
2186 found = []
2185 for x in br:
2187 for x in br:
2186 if branch in br[x]:
2188 if branch in br[x]:
2187 found.append(x)
2189 found.append(x)
2188 if len(found) > 1:
2190 if len(found) > 1:
2189 ui.warn(_("Found multiple heads for %s\n") % branch)
2191 ui.warn(_("Found multiple heads for %s\n") % branch)
2190 for x in found:
2192 for x in found:
2191 show_changeset(ui, repo, changenode=x, brinfo=br)
2193 show_changeset(ui, repo, changenode=x, brinfo=br)
2192 return 1
2194 return 1
2193 if len(found) == 1:
2195 if len(found) == 1:
2194 node = found[0]
2196 node = found[0]
2195 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2197 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2196 else:
2198 else:
2197 ui.warn(_("branch %s not found\n") % (branch))
2199 ui.warn(_("branch %s not found\n") % (branch))
2198 return 1
2200 return 1
2199 else:
2201 else:
2200 node = node and repo.lookup(node) or repo.changelog.tip()
2202 node = node and repo.lookup(node) or repo.changelog.tip()
2201 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2203 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2202
2204
2203 def verify(ui, repo):
2205 def verify(ui, repo):
2204 """verify the integrity of the repository
2206 """verify the integrity of the repository
2205
2207
2206 Verify the integrity of the current repository.
2208 Verify the integrity of the current repository.
2207
2209
2208 This will perform an extensive check of the repository's
2210 This will perform an extensive check of the repository's
2209 integrity, validating the hashes and checksums of each entry in
2211 integrity, validating the hashes and checksums of each entry in
2210 the changelog, manifest, and tracked files, as well as the
2212 the changelog, manifest, and tracked files, as well as the
2211 integrity of their crosslinks and indices.
2213 integrity of their crosslinks and indices.
2212 """
2214 """
2213 return repo.verify()
2215 return repo.verify()
2214
2216
2215 # Command options and aliases are listed here, alphabetically
2217 # Command options and aliases are listed here, alphabetically
2216
2218
2217 table = {
2219 table = {
2218 "^add":
2220 "^add":
2219 (add,
2221 (add,
2220 [('I', 'include', [], _('include names matching the given patterns')),
2222 [('I', 'include', [], _('include names matching the given patterns')),
2221 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2223 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2222 _('hg add [OPTION]... [FILE]...')),
2224 _('hg add [OPTION]... [FILE]...')),
2223 "addremove":
2225 "addremove":
2224 (addremove,
2226 (addremove,
2225 [('I', 'include', [], _('include names matching the given patterns')),
2227 [('I', 'include', [], _('include names matching the given patterns')),
2226 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2228 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2227 _('hg addremove [OPTION]... [FILE]...')),
2229 _('hg addremove [OPTION]... [FILE]...')),
2228 "^annotate":
2230 "^annotate":
2229 (annotate,
2231 (annotate,
2230 [('r', 'rev', '', _('annotate the specified revision')),
2232 [('r', 'rev', '', _('annotate the specified revision')),
2231 ('a', 'text', None, _('treat all files as text')),
2233 ('a', 'text', None, _('treat all files as text')),
2232 ('u', 'user', None, _('list the author')),
2234 ('u', 'user', None, _('list the author')),
2233 ('d', 'date', None, _('list the date')),
2235 ('d', 'date', None, _('list the date')),
2234 ('n', 'number', None, _('list the revision number (default)')),
2236 ('n', 'number', None, _('list the revision number (default)')),
2235 ('c', 'changeset', None, _('list the changeset')),
2237 ('c', 'changeset', None, _('list the changeset')),
2236 ('I', 'include', [], _('include names matching the given patterns')),
2238 ('I', 'include', [], _('include names matching the given patterns')),
2237 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2239 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2238 _('hg annotate [OPTION]... FILE...')),
2240 _('hg annotate [OPTION]... FILE...')),
2239 "bundle":
2241 "bundle":
2240 (bundle,
2242 (bundle,
2241 [],
2243 [],
2242 _('hg bundle FILE DEST')),
2244 _('hg bundle FILE DEST')),
2243 "cat":
2245 "cat":
2244 (cat,
2246 (cat,
2245 [('I', 'include', [], _('include names matching the given patterns')),
2247 [('I', 'include', [], _('include names matching the given patterns')),
2246 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2248 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2247 ('o', 'output', '', _('print output to file with formatted name')),
2249 ('o', 'output', '', _('print output to file with formatted name')),
2248 ('r', 'rev', '', _('print the given revision'))],
2250 ('r', 'rev', '', _('print the given revision'))],
2249 _('hg cat [OPTION]... FILE...')),
2251 _('hg cat [OPTION]... FILE...')),
2250 "^clone":
2252 "^clone":
2251 (clone,
2253 (clone,
2252 [('U', 'noupdate', None, _('do not update the new working directory')),
2254 [('U', 'noupdate', None, _('do not update the new working directory')),
2253 ('e', 'ssh', '', _('specify ssh command to use')),
2255 ('e', 'ssh', '', _('specify ssh command to use')),
2254 ('', 'pull', None, _('use pull protocol to copy metadata')),
2256 ('', 'pull', None, _('use pull protocol to copy metadata')),
2255 ('r', 'rev', [],
2257 ('r', 'rev', [],
2256 _('a changeset you would like to have after cloning')),
2258 _('a changeset you would like to have after cloning')),
2257 ('', 'remotecmd', '',
2259 ('', 'remotecmd', '',
2258 _('specify hg command to run on the remote side'))],
2260 _('specify hg command to run on the remote side'))],
2259 _('hg clone [OPTION]... SOURCE [DEST]')),
2261 _('hg clone [OPTION]... SOURCE [DEST]')),
2260 "^commit|ci":
2262 "^commit|ci":
2261 (commit,
2263 (commit,
2262 [('A', 'addremove', None, _('run addremove during commit')),
2264 [('A', 'addremove', None, _('run addremove during commit')),
2263 ('I', 'include', [], _('include names matching the given patterns')),
2265 ('I', 'include', [], _('include names matching the given patterns')),
2264 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2266 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2265 ('m', 'message', '', _('use <text> as commit message')),
2267 ('m', 'message', '', _('use <text> as commit message')),
2266 ('l', 'logfile', '', _('read the commit message from <file>')),
2268 ('l', 'logfile', '', _('read the commit message from <file>')),
2267 ('d', 'date', '', _('record datecode as commit date')),
2269 ('d', 'date', '', _('record datecode as commit date')),
2268 ('u', 'user', '', _('record user as commiter'))],
2270 ('u', 'user', '', _('record user as commiter'))],
2269 _('hg commit [OPTION]... [FILE]...')),
2271 _('hg commit [OPTION]... [FILE]...')),
2270 "copy|cp":
2272 "copy|cp":
2271 (copy,
2273 (copy,
2272 [('I', 'include', [], _('include names matching the given patterns')),
2274 [('I', 'include', [], _('include names matching the given patterns')),
2273 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2275 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2274 ('A', 'after', None, _('record a copy that has already occurred')),
2276 ('A', 'after', None, _('record a copy that has already occurred')),
2275 ('f', 'force', None,
2277 ('f', 'force', None,
2276 _('forcibly copy over an existing managed file'))],
2278 _('forcibly copy over an existing managed file'))],
2277 _('hg copy [OPTION]... [SOURCE]... DEST')),
2279 _('hg copy [OPTION]... [SOURCE]... DEST')),
2278 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2280 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2279 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2281 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2280 "debugconfig": (debugconfig, [], _('debugconfig')),
2282 "debugconfig": (debugconfig, [], _('debugconfig')),
2281 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2283 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2282 "debugstate": (debugstate, [], _('debugstate')),
2284 "debugstate": (debugstate, [], _('debugstate')),
2283 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2285 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2284 "debugindex": (debugindex, [], _('debugindex FILE')),
2286 "debugindex": (debugindex, [], _('debugindex FILE')),
2285 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2287 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2286 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2288 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2287 "debugwalk":
2289 "debugwalk":
2288 (debugwalk,
2290 (debugwalk,
2289 [('I', 'include', [], _('include names matching the given patterns')),
2291 [('I', 'include', [], _('include names matching the given patterns')),
2290 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2292 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2291 _('debugwalk [OPTION]... [FILE]...')),
2293 _('debugwalk [OPTION]... [FILE]...')),
2292 "^diff":
2294 "^diff":
2293 (diff,
2295 (diff,
2294 [('r', 'rev', [], _('revision')),
2296 [('r', 'rev', [], _('revision')),
2295 ('a', 'text', None, _('treat all files as text')),
2297 ('a', 'text', None, _('treat all files as text')),
2296 ('I', 'include', [], _('include names matching the given patterns')),
2298 ('I', 'include', [], _('include names matching the given patterns')),
2297 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2299 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2298 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2300 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2299 "^export":
2301 "^export":
2300 (export,
2302 (export,
2301 [('o', 'output', '', _('print output to file with formatted name')),
2303 [('o', 'output', '', _('print output to file with formatted name')),
2302 ('a', 'text', None, _('treat all files as text')),
2304 ('a', 'text', None, _('treat all files as text')),
2303 ('', 'switch-parent', None, _('diff against the second parent'))],
2305 ('', 'switch-parent', None, _('diff against the second parent'))],
2304 _('hg export [-a] [-o OUTFILE] REV...')),
2306 _('hg export [-a] [-o OUTFILE] REV...')),
2305 "forget":
2307 "forget":
2306 (forget,
2308 (forget,
2307 [('I', 'include', [], _('include names matching the given patterns')),
2309 [('I', 'include', [], _('include names matching the given patterns')),
2308 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2310 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2309 _('hg forget [OPTION]... FILE...')),
2311 _('hg forget [OPTION]... FILE...')),
2310 "grep":
2312 "grep":
2311 (grep,
2313 (grep,
2312 [('0', 'print0', None, _('end fields with NUL')),
2314 [('0', 'print0', None, _('end fields with NUL')),
2313 ('I', 'include', [], _('include names matching the given patterns')),
2315 ('I', 'include', [], _('include names matching the given patterns')),
2314 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2316 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2315 ('', 'all', None, _('print all revisions that match')),
2317 ('', 'all', None, _('print all revisions that match')),
2316 ('i', 'ignore-case', None, _('ignore case when matching')),
2318 ('i', 'ignore-case', None, _('ignore case when matching')),
2317 ('l', 'files-with-matches', None,
2319 ('l', 'files-with-matches', None,
2318 _('print only filenames and revs that match')),
2320 _('print only filenames and revs that match')),
2319 ('n', 'line-number', None, _('print matching line numbers')),
2321 ('n', 'line-number', None, _('print matching line numbers')),
2320 ('r', 'rev', [], _('search in given revision range')),
2322 ('r', 'rev', [], _('search in given revision range')),
2321 ('u', 'user', None, _('print user who committed change'))],
2323 ('u', 'user', None, _('print user who committed change'))],
2322 _('hg grep [OPTION]... PATTERN [FILE]...')),
2324 _('hg grep [OPTION]... PATTERN [FILE]...')),
2323 "heads":
2325 "heads":
2324 (heads,
2326 (heads,
2325 [('b', 'branches', None, _('find branch info')),
2327 [('b', 'branches', None, _('find branch info')),
2326 ('r', 'rev', '', _('show only heads which are descendants of rev'))],
2328 ('r', 'rev', '', _('show only heads which are descendants of rev'))],
2327 _('hg heads [-b] [-r <rev>]')),
2329 _('hg heads [-b] [-r <rev>]')),
2328 "help": (help_, [], _('hg help [COMMAND]')),
2330 "help": (help_, [], _('hg help [COMMAND]')),
2329 "identify|id": (identify, [], _('hg identify')),
2331 "identify|id": (identify, [], _('hg identify')),
2330 "import|patch":
2332 "import|patch":
2331 (import_,
2333 (import_,
2332 [('p', 'strip', 1,
2334 [('p', 'strip', 1,
2333 _('directory strip option for patch. This has the same\n') +
2335 _('directory strip option for patch. This has the same\n') +
2334 _('meaning as the corresponding patch option')),
2336 _('meaning as the corresponding patch option')),
2335 ('f', 'force', None,
2337 ('f', 'force', None,
2336 _('skip check for outstanding uncommitted changes')),
2338 _('skip check for outstanding uncommitted changes')),
2337 ('b', 'base', '', _('base path'))],
2339 ('b', 'base', '', _('base path'))],
2338 _('hg import [-f] [-p NUM] [-b BASE] PATCH...')),
2340 _('hg import [-f] [-p NUM] [-b BASE] PATCH...')),
2339 "incoming|in": (incoming,
2341 "incoming|in": (incoming,
2340 [('M', 'no-merges', None, _('do not show merges')),
2342 [('M', 'no-merges', None, _('do not show merges')),
2341 ('p', 'patch', None, _('show patch')),
2343 ('p', 'patch', None, _('show patch')),
2342 ('n', 'newest-first', None, _('show newest record first'))],
2344 ('n', 'newest-first', None, _('show newest record first'))],
2343 _('hg incoming [-p] [-n] [-M] [SOURCE]')),
2345 _('hg incoming [-p] [-n] [-M] [SOURCE]')),
2344 "^init": (init, [], _('hg init [DEST]')),
2346 "^init": (init, [], _('hg init [DEST]')),
2345 "locate":
2347 "locate":
2346 (locate,
2348 (locate,
2347 [('r', 'rev', '', _('search the repository as it stood at rev')),
2349 [('r', 'rev', '', _('search the repository as it stood at rev')),
2348 ('0', 'print0', None,
2350 ('0', 'print0', None,
2349 _('end filenames with NUL, for use with xargs')),
2351 _('end filenames with NUL, for use with xargs')),
2350 ('f', 'fullpath', None,
2352 ('f', 'fullpath', None,
2351 _('print complete paths from the filesystem root')),
2353 _('print complete paths from the filesystem root')),
2352 ('I', 'include', [], _('include names matching the given patterns')),
2354 ('I', 'include', [], _('include names matching the given patterns')),
2353 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2355 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2354 _('hg locate [OPTION]... [PATTERN]...')),
2356 _('hg locate [OPTION]... [PATTERN]...')),
2355 "^log|history":
2357 "^log|history":
2356 (log,
2358 (log,
2357 [('I', 'include', [], _('include names matching the given patterns')),
2359 [('I', 'include', [], _('include names matching the given patterns')),
2358 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2360 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2359 ('b', 'branch', None, _('show branches')),
2361 ('b', 'branch', None, _('show branches')),
2360 ('k', 'keyword', [], _('search for a keyword')),
2362 ('k', 'keyword', [], _('search for a keyword')),
2361 ('r', 'rev', [], _('show the specified revision or range')),
2363 ('r', 'rev', [], _('show the specified revision or range')),
2362 ('M', 'no-merges', None, _('do not show merges')),
2364 ('M', 'no-merges', None, _('do not show merges')),
2363 ('m', 'only-merges', None, _('show only merges')),
2365 ('m', 'only-merges', None, _('show only merges')),
2364 ('p', 'patch', None, _('show patch'))],
2366 ('p', 'patch', None, _('show patch'))],
2365 _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
2367 _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
2366 "manifest": (manifest, [], _('hg manifest [REV]')),
2368 "manifest": (manifest, [], _('hg manifest [REV]')),
2367 "outgoing|out": (outgoing,
2369 "outgoing|out": (outgoing,
2368 [('M', 'no-merges', None, _('do not show merges')),
2370 [('M', 'no-merges', None, _('do not show merges')),
2369 ('p', 'patch', None, _('show patch')),
2371 ('p', 'patch', None, _('show patch')),
2370 ('n', 'newest-first', None, _('show newest record first'))],
2372 ('n', 'newest-first', None, _('show newest record first'))],
2371 _('hg outgoing [-p] [-n] [-M] [DEST]')),
2373 _('hg outgoing [-p] [-n] [-M] [DEST]')),
2372 "^parents": (parents, [], _('hg parents [REV]')),
2374 "^parents": (parents, [], _('hg parents [REV]')),
2373 "paths": (paths, [], _('hg paths [NAME]')),
2375 "paths": (paths, [], _('hg paths [NAME]')),
2374 "^pull":
2376 "^pull":
2375 (pull,
2377 (pull,
2376 [('u', 'update', None,
2378 [('u', 'update', None,
2377 _('update the working directory to tip after pull')),
2379 _('update the working directory to tip after pull')),
2378 ('e', 'ssh', '', _('specify ssh command to use')),
2380 ('e', 'ssh', '', _('specify ssh command to use')),
2379 ('r', 'rev', [], _('a specific revision you would like to pull')),
2381 ('r', 'rev', [], _('a specific revision you would like to pull')),
2380 ('', 'remotecmd', '',
2382 ('', 'remotecmd', '',
2381 _('specify hg command to run on the remote side'))],
2383 _('specify hg command to run on the remote side'))],
2382 _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')),
2384 _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')),
2383 "^push":
2385 "^push":
2384 (push,
2386 (push,
2385 [('f', 'force', None, _('force push')),
2387 [('f', 'force', None, _('force push')),
2386 ('e', 'ssh', '', _('specify ssh command to use')),
2388 ('e', 'ssh', '', _('specify ssh command to use')),
2387 ('', 'remotecmd', '',
2389 ('', 'remotecmd', '',
2388 _('specify hg command to run on the remote side'))],
2390 _('specify hg command to run on the remote side'))],
2389 _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')),
2391 _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')),
2390 "rawcommit":
2392 "rawcommit":
2391 (rawcommit,
2393 (rawcommit,
2392 [('p', 'parent', [], _('parent')),
2394 [('p', 'parent', [], _('parent')),
2393 ('d', 'date', '', _('date code')),
2395 ('d', 'date', '', _('date code')),
2394 ('u', 'user', '', _('user')),
2396 ('u', 'user', '', _('user')),
2395 ('F', 'files', '', _('file list')),
2397 ('F', 'files', '', _('file list')),
2396 ('m', 'message', '', _('commit message')),
2398 ('m', 'message', '', _('commit message')),
2397 ('l', 'logfile', '', _('commit message file'))],
2399 ('l', 'logfile', '', _('commit message file'))],
2398 _('hg rawcommit [OPTION]... [FILE]...')),
2400 _('hg rawcommit [OPTION]... [FILE]...')),
2399 "recover": (recover, [], _('hg recover')),
2401 "recover": (recover, [], _('hg recover')),
2400 "^remove|rm":
2402 "^remove|rm":
2401 (remove,
2403 (remove,
2402 [('I', 'include', [], _('include names matching the given patterns')),
2404 [('I', 'include', [], _('include names matching the given patterns')),
2403 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2405 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2404 _('hg remove [OPTION]... FILE...')),
2406 _('hg remove [OPTION]... FILE...')),
2405 "rename|mv":
2407 "rename|mv":
2406 (rename,
2408 (rename,
2407 [('I', 'include', [], _('include names matching the given patterns')),
2409 [('I', 'include', [], _('include names matching the given patterns')),
2408 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2410 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2409 ('A', 'after', None, _('record a rename that has already occurred')),
2411 ('A', 'after', None, _('record a rename that has already occurred')),
2410 ('f', 'force', None,
2412 ('f', 'force', None,
2411 _('forcibly copy over an existing managed file'))],
2413 _('forcibly copy over an existing managed file'))],
2412 _('hg rename [OPTION]... [SOURCE]... DEST')),
2414 _('hg rename [OPTION]... [SOURCE]... DEST')),
2413 "^revert":
2415 "^revert":
2414 (revert,
2416 (revert,
2415 [('I', 'include', [], _('include names matching the given patterns')),
2417 [('I', 'include', [], _('include names matching the given patterns')),
2416 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2418 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2417 ('r', 'rev', '', _('revision to revert to'))],
2419 ('r', 'rev', '', _('revision to revert to'))],
2418 _('hg revert [-n] [-r REV] [NAME]...')),
2420 _('hg revert [-n] [-r REV] [NAME]...')),
2419 "root": (root, [], _('hg root')),
2421 "root": (root, [], _('hg root')),
2420 "^serve":
2422 "^serve":
2421 (serve,
2423 (serve,
2422 [('A', 'accesslog', '', _('name of access log file to write to')),
2424 [('A', 'accesslog', '', _('name of access log file to write to')),
2423 ('E', 'errorlog', '', _('name of error log file to write to')),
2425 ('E', 'errorlog', '', _('name of error log file to write to')),
2424 ('p', 'port', 0, _('port to use (default: 8000)')),
2426 ('p', 'port', 0, _('port to use (default: 8000)')),
2425 ('a', 'address', '', _('address to use')),
2427 ('a', 'address', '', _('address to use')),
2426 ('n', 'name', '',
2428 ('n', 'name', '',
2427 _('name to show in web pages (default: working dir)')),
2429 _('name to show in web pages (default: working dir)')),
2428 ('', 'stdio', None, _('for remote clients')),
2430 ('', 'stdio', None, _('for remote clients')),
2429 ('t', 'templates', '', _('web templates to use')),
2431 ('t', 'templates', '', _('web templates to use')),
2430 ('', 'style', '', _('template style to use')),
2432 ('', 'style', '', _('template style to use')),
2431 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2433 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2432 _('hg serve [OPTION]...')),
2434 _('hg serve [OPTION]...')),
2433 "^status|st":
2435 "^status|st":
2434 (status,
2436 (status,
2435 [('m', 'modified', None, _('show only modified files')),
2437 [('m', 'modified', None, _('show only modified files')),
2436 ('a', 'added', None, _('show only added files')),
2438 ('a', 'added', None, _('show only added files')),
2437 ('r', 'removed', None, _('show only removed files')),
2439 ('r', 'removed', None, _('show only removed files')),
2438 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2440 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2439 ('n', 'no-status', None, _('hide status prefix')),
2441 ('n', 'no-status', None, _('hide status prefix')),
2440 ('0', 'print0', None,
2442 ('0', 'print0', None,
2441 _('end filenames with NUL, for use with xargs')),
2443 _('end filenames with NUL, for use with xargs')),
2442 ('I', 'include', [], _('include names matching the given patterns')),
2444 ('I', 'include', [], _('include names matching the given patterns')),
2443 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2445 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2444 _('hg status [OPTION]... [FILE]...')),
2446 _('hg status [OPTION]... [FILE]...')),
2445 "tag":
2447 "tag":
2446 (tag,
2448 (tag,
2447 [('l', 'local', None, _('make the tag local')),
2449 [('l', 'local', None, _('make the tag local')),
2448 ('m', 'message', '', _('message for tag commit log entry')),
2450 ('m', 'message', '', _('message for tag commit log entry')),
2449 ('d', 'date', '', _('record datecode as commit date')),
2451 ('d', 'date', '', _('record datecode as commit date')),
2450 ('u', 'user', '', _('record user as commiter')),
2452 ('u', 'user', '', _('record user as commiter')),
2451 ('r', 'rev', '', _('revision to tag'))],
2453 ('r', 'rev', '', _('revision to tag'))],
2452 _('hg tag [OPTION]... NAME [REV]')),
2454 _('hg tag [OPTION]... NAME [REV]')),
2453 "tags": (tags, [], _('hg tags')),
2455 "tags": (tags, [], _('hg tags')),
2454 "tip": (tip, [], _('hg tip')),
2456 "tip": (tip, [], _('hg tip')),
2455 "unbundle":
2457 "unbundle":
2456 (unbundle,
2458 (unbundle,
2457 [('u', 'update', None,
2459 [('u', 'update', None,
2458 _('update the working directory to tip after unbundle'))],
2460 _('update the working directory to tip after unbundle'))],
2459 _('hg unbundle [-u] FILE')),
2461 _('hg unbundle [-u] FILE')),
2460 "undo": (undo, [], _('hg undo')),
2462 "undo": (undo, [], _('hg undo')),
2461 "^update|up|checkout|co":
2463 "^update|up|checkout|co":
2462 (update,
2464 (update,
2463 [('b', 'branch', '', _('checkout the head of a specific branch')),
2465 [('b', 'branch', '', _('checkout the head of a specific branch')),
2464 ('m', 'merge', None, _('allow merging of branches')),
2466 ('m', 'merge', None, _('allow merging of branches')),
2465 ('C', 'clean', None, _('overwrite locally modified files')),
2467 ('C', 'clean', None, _('overwrite locally modified files')),
2466 ('f', 'force', None, _('force a merge with outstanding changes'))],
2468 ('f', 'force', None, _('force a merge with outstanding changes'))],
2467 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
2469 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
2468 "verify": (verify, [], _('hg verify')),
2470 "verify": (verify, [], _('hg verify')),
2469 "version": (show_version, [], _('hg version')),
2471 "version": (show_version, [], _('hg version')),
2470 }
2472 }
2471
2473
2472 globalopts = [
2474 globalopts = [
2473 ('R', 'repository', '', _('repository root directory')),
2475 ('R', 'repository', '', _('repository root directory')),
2474 ('', 'cwd', '', _('change working directory')),
2476 ('', 'cwd', '', _('change working directory')),
2475 ('y', 'noninteractive', None,
2477 ('y', 'noninteractive', None,
2476 _('do not prompt, assume \'yes\' for any required answers')),
2478 _('do not prompt, assume \'yes\' for any required answers')),
2477 ('q', 'quiet', None, _('suppress output')),
2479 ('q', 'quiet', None, _('suppress output')),
2478 ('v', 'verbose', None, _('enable additional output')),
2480 ('v', 'verbose', None, _('enable additional output')),
2479 ('', 'debug', None, _('enable debugging output')),
2481 ('', 'debug', None, _('enable debugging output')),
2480 ('', 'debugger', None, _('start debugger')),
2482 ('', 'debugger', None, _('start debugger')),
2481 ('', 'traceback', None, _('print traceback on exception')),
2483 ('', 'traceback', None, _('print traceback on exception')),
2482 ('', 'time', None, _('time how long the command takes')),
2484 ('', 'time', None, _('time how long the command takes')),
2483 ('', 'profile', None, _('print command execution profile')),
2485 ('', 'profile', None, _('print command execution profile')),
2484 ('', 'version', None, _('output version information and exit')),
2486 ('', 'version', None, _('output version information and exit')),
2485 ('h', 'help', None, _('display help and exit')),
2487 ('h', 'help', None, _('display help and exit')),
2486 ]
2488 ]
2487
2489
2488 norepo = ("clone init version help debugancestor debugconfig debugdata"
2490 norepo = ("clone init version help debugancestor debugconfig debugdata"
2489 " debugindex debugindexdot paths")
2491 " debugindex debugindexdot paths")
2490
2492
2491 def find(cmd):
2493 def find(cmd):
2492 """Return (aliases, command table entry) for command string."""
2494 """Return (aliases, command table entry) for command string."""
2493 choice = None
2495 choice = None
2494 for e in table.keys():
2496 for e in table.keys():
2495 aliases = e.lstrip("^").split("|")
2497 aliases = e.lstrip("^").split("|")
2496 if cmd in aliases:
2498 if cmd in aliases:
2497 return aliases, table[e]
2499 return aliases, table[e]
2498 for a in aliases:
2500 for a in aliases:
2499 if a.startswith(cmd):
2501 if a.startswith(cmd):
2500 if choice:
2502 if choice:
2501 raise AmbiguousCommand(cmd)
2503 raise AmbiguousCommand(cmd)
2502 else:
2504 else:
2503 choice = aliases, table[e]
2505 choice = aliases, table[e]
2504 break
2506 break
2505 if choice:
2507 if choice:
2506 return choice
2508 return choice
2507
2509
2508 raise UnknownCommand(cmd)
2510 raise UnknownCommand(cmd)
2509
2511
2510 class SignalInterrupt(Exception):
2512 class SignalInterrupt(Exception):
2511 """Exception raised on SIGTERM and SIGHUP."""
2513 """Exception raised on SIGTERM and SIGHUP."""
2512
2514
2513 def catchterm(*args):
2515 def catchterm(*args):
2514 raise SignalInterrupt
2516 raise SignalInterrupt
2515
2517
2516 def run():
2518 def run():
2517 sys.exit(dispatch(sys.argv[1:]))
2519 sys.exit(dispatch(sys.argv[1:]))
2518
2520
2519 class ParseError(Exception):
2521 class ParseError(Exception):
2520 """Exception raised on errors in parsing the command line."""
2522 """Exception raised on errors in parsing the command line."""
2521
2523
2522 def parse(ui, args):
2524 def parse(ui, args):
2523 options = {}
2525 options = {}
2524 cmdoptions = {}
2526 cmdoptions = {}
2525
2527
2526 try:
2528 try:
2527 args = fancyopts.fancyopts(args, globalopts, options)
2529 args = fancyopts.fancyopts(args, globalopts, options)
2528 except fancyopts.getopt.GetoptError, inst:
2530 except fancyopts.getopt.GetoptError, inst:
2529 raise ParseError(None, inst)
2531 raise ParseError(None, inst)
2530
2532
2531 if args:
2533 if args:
2532 cmd, args = args[0], args[1:]
2534 cmd, args = args[0], args[1:]
2533 aliases, i = find(cmd)
2535 aliases, i = find(cmd)
2534 cmd = aliases[0]
2536 cmd = aliases[0]
2535 defaults = ui.config("defaults", cmd)
2537 defaults = ui.config("defaults", cmd)
2536 if defaults:
2538 if defaults:
2537 args = defaults.split() + args
2539 args = defaults.split() + args
2538 c = list(i[1])
2540 c = list(i[1])
2539 else:
2541 else:
2540 cmd = None
2542 cmd = None
2541 c = []
2543 c = []
2542
2544
2543 # combine global options into local
2545 # combine global options into local
2544 for o in globalopts:
2546 for o in globalopts:
2545 c.append((o[0], o[1], options[o[1]], o[3]))
2547 c.append((o[0], o[1], options[o[1]], o[3]))
2546
2548
2547 try:
2549 try:
2548 args = fancyopts.fancyopts(args, c, cmdoptions)
2550 args = fancyopts.fancyopts(args, c, cmdoptions)
2549 except fancyopts.getopt.GetoptError, inst:
2551 except fancyopts.getopt.GetoptError, inst:
2550 raise ParseError(cmd, inst)
2552 raise ParseError(cmd, inst)
2551
2553
2552 # separate global options back out
2554 # separate global options back out
2553 for o in globalopts:
2555 for o in globalopts:
2554 n = o[1]
2556 n = o[1]
2555 options[n] = cmdoptions[n]
2557 options[n] = cmdoptions[n]
2556 del cmdoptions[n]
2558 del cmdoptions[n]
2557
2559
2558 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2560 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2559
2561
2560 def dispatch(args):
2562 def dispatch(args):
2561 signal.signal(signal.SIGTERM, catchterm)
2563 signal.signal(signal.SIGTERM, catchterm)
2562 try:
2564 try:
2563 signal.signal(signal.SIGHUP, catchterm)
2565 signal.signal(signal.SIGHUP, catchterm)
2564 except AttributeError:
2566 except AttributeError:
2565 pass
2567 pass
2566
2568
2567 try:
2569 try:
2568 u = ui.ui()
2570 u = ui.ui()
2569 except util.Abort, inst:
2571 except util.Abort, inst:
2570 sys.stderr.write(_("abort: %s\n") % inst)
2572 sys.stderr.write(_("abort: %s\n") % inst)
2571 sys.exit(1)
2573 sys.exit(1)
2572
2574
2573 external = []
2575 external = []
2574 for x in u.extensions():
2576 for x in u.extensions():
2575 def on_exception(exc, inst):
2577 def on_exception(exc, inst):
2576 u.warn(_("*** failed to import extension %s\n") % x[1])
2578 u.warn(_("*** failed to import extension %s\n") % x[1])
2577 u.warn("%s\n" % inst)
2579 u.warn("%s\n" % inst)
2578 if "--traceback" in sys.argv[1:]:
2580 if "--traceback" in sys.argv[1:]:
2579 traceback.print_exc()
2581 traceback.print_exc()
2580 if x[1]:
2582 if x[1]:
2581 try:
2583 try:
2582 mod = imp.load_source(x[0], x[1])
2584 mod = imp.load_source(x[0], x[1])
2583 except Exception, inst:
2585 except Exception, inst:
2584 on_exception(Exception, inst)
2586 on_exception(Exception, inst)
2585 continue
2587 continue
2586 else:
2588 else:
2587 def importh(name):
2589 def importh(name):
2588 mod = __import__(name)
2590 mod = __import__(name)
2589 components = name.split('.')
2591 components = name.split('.')
2590 for comp in components[1:]:
2592 for comp in components[1:]:
2591 mod = getattr(mod, comp)
2593 mod = getattr(mod, comp)
2592 return mod
2594 return mod
2593 try:
2595 try:
2594 mod = importh(x[0])
2596 mod = importh(x[0])
2595 except Exception, inst:
2597 except Exception, inst:
2596 on_exception(Exception, inst)
2598 on_exception(Exception, inst)
2597 continue
2599 continue
2598
2600
2599 external.append(mod)
2601 external.append(mod)
2600 for x in external:
2602 for x in external:
2601 cmdtable = getattr(x, 'cmdtable', {})
2603 cmdtable = getattr(x, 'cmdtable', {})
2602 for t in cmdtable:
2604 for t in cmdtable:
2603 if t in table:
2605 if t in table:
2604 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
2606 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
2605 table.update(cmdtable)
2607 table.update(cmdtable)
2606
2608
2607 try:
2609 try:
2608 cmd, func, args, options, cmdoptions = parse(u, args)
2610 cmd, func, args, options, cmdoptions = parse(u, args)
2609 except ParseError, inst:
2611 except ParseError, inst:
2610 if inst.args[0]:
2612 if inst.args[0]:
2611 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
2613 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
2612 help_(u, inst.args[0])
2614 help_(u, inst.args[0])
2613 else:
2615 else:
2614 u.warn(_("hg: %s\n") % inst.args[1])
2616 u.warn(_("hg: %s\n") % inst.args[1])
2615 help_(u, 'shortlist')
2617 help_(u, 'shortlist')
2616 sys.exit(-1)
2618 sys.exit(-1)
2617 except AmbiguousCommand, inst:
2619 except AmbiguousCommand, inst:
2618 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2620 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2619 sys.exit(1)
2621 sys.exit(1)
2620 except UnknownCommand, inst:
2622 except UnknownCommand, inst:
2621 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2623 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2622 help_(u, 'shortlist')
2624 help_(u, 'shortlist')
2623 sys.exit(1)
2625 sys.exit(1)
2624
2626
2625 if options["time"]:
2627 if options["time"]:
2626 def get_times():
2628 def get_times():
2627 t = os.times()
2629 t = os.times()
2628 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2630 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2629 t = (t[0], t[1], t[2], t[3], time.clock())
2631 t = (t[0], t[1], t[2], t[3], time.clock())
2630 return t
2632 return t
2631 s = get_times()
2633 s = get_times()
2632 def print_time():
2634 def print_time():
2633 t = get_times()
2635 t = get_times()
2634 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
2636 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
2635 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2637 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2636 atexit.register(print_time)
2638 atexit.register(print_time)
2637
2639
2638 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2640 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2639 not options["noninteractive"])
2641 not options["noninteractive"])
2640
2642
2641 # enter the debugger before command execution
2643 # enter the debugger before command execution
2642 if options['debugger']:
2644 if options['debugger']:
2643 pdb.set_trace()
2645 pdb.set_trace()
2644
2646
2645 try:
2647 try:
2646 try:
2648 try:
2647 if options['help']:
2649 if options['help']:
2648 help_(u, cmd, options['version'])
2650 help_(u, cmd, options['version'])
2649 sys.exit(0)
2651 sys.exit(0)
2650 elif options['version']:
2652 elif options['version']:
2651 show_version(u)
2653 show_version(u)
2652 sys.exit(0)
2654 sys.exit(0)
2653 elif not cmd:
2655 elif not cmd:
2654 help_(u, 'shortlist')
2656 help_(u, 'shortlist')
2655 sys.exit(0)
2657 sys.exit(0)
2656
2658
2657 if options['cwd']:
2659 if options['cwd']:
2658 try:
2660 try:
2659 os.chdir(options['cwd'])
2661 os.chdir(options['cwd'])
2660 except OSError, inst:
2662 except OSError, inst:
2661 raise util.Abort('%s: %s' %
2663 raise util.Abort('%s: %s' %
2662 (options['cwd'], inst.strerror))
2664 (options['cwd'], inst.strerror))
2663
2665
2664 if cmd not in norepo.split():
2666 if cmd not in norepo.split():
2665 path = options["repository"] or ""
2667 path = options["repository"] or ""
2666 repo = hg.repository(ui=u, path=path)
2668 repo = hg.repository(ui=u, path=path)
2667 for x in external:
2669 for x in external:
2668 if hasattr(x, 'reposetup'):
2670 if hasattr(x, 'reposetup'):
2669 x.reposetup(u, repo)
2671 x.reposetup(u, repo)
2670 d = lambda: func(u, repo, *args, **cmdoptions)
2672 d = lambda: func(u, repo, *args, **cmdoptions)
2671 else:
2673 else:
2672 d = lambda: func(u, *args, **cmdoptions)
2674 d = lambda: func(u, *args, **cmdoptions)
2673
2675
2674 if options['profile']:
2676 if options['profile']:
2675 import hotshot, hotshot.stats
2677 import hotshot, hotshot.stats
2676 prof = hotshot.Profile("hg.prof")
2678 prof = hotshot.Profile("hg.prof")
2677 r = prof.runcall(d)
2679 r = prof.runcall(d)
2678 prof.close()
2680 prof.close()
2679 stats = hotshot.stats.load("hg.prof")
2681 stats = hotshot.stats.load("hg.prof")
2680 stats.strip_dirs()
2682 stats.strip_dirs()
2681 stats.sort_stats('time', 'calls')
2683 stats.sort_stats('time', 'calls')
2682 stats.print_stats(40)
2684 stats.print_stats(40)
2683 return r
2685 return r
2684 else:
2686 else:
2685 return d()
2687 return d()
2686 except:
2688 except:
2687 # enter the debugger when we hit an exception
2689 # enter the debugger when we hit an exception
2688 if options['debugger']:
2690 if options['debugger']:
2689 pdb.post_mortem(sys.exc_info()[2])
2691 pdb.post_mortem(sys.exc_info()[2])
2690 if options['traceback']:
2692 if options['traceback']:
2691 traceback.print_exc()
2693 traceback.print_exc()
2692 raise
2694 raise
2693 except hg.RepoError, inst:
2695 except hg.RepoError, inst:
2694 u.warn(_("abort: "), inst, "!\n")
2696 u.warn(_("abort: "), inst, "!\n")
2695 except revlog.RevlogError, inst:
2697 except revlog.RevlogError, inst:
2696 u.warn(_("abort: "), inst, "!\n")
2698 u.warn(_("abort: "), inst, "!\n")
2697 except SignalInterrupt:
2699 except SignalInterrupt:
2698 u.warn(_("killed!\n"))
2700 u.warn(_("killed!\n"))
2699 except KeyboardInterrupt:
2701 except KeyboardInterrupt:
2700 try:
2702 try:
2701 u.warn(_("interrupted!\n"))
2703 u.warn(_("interrupted!\n"))
2702 except IOError, inst:
2704 except IOError, inst:
2703 if inst.errno == errno.EPIPE:
2705 if inst.errno == errno.EPIPE:
2704 if u.debugflag:
2706 if u.debugflag:
2705 u.warn(_("\nbroken pipe\n"))
2707 u.warn(_("\nbroken pipe\n"))
2706 else:
2708 else:
2707 raise
2709 raise
2708 except IOError, inst:
2710 except IOError, inst:
2709 if hasattr(inst, "code"):
2711 if hasattr(inst, "code"):
2710 u.warn(_("abort: %s\n") % inst)
2712 u.warn(_("abort: %s\n") % inst)
2711 elif hasattr(inst, "reason"):
2713 elif hasattr(inst, "reason"):
2712 u.warn(_("abort: error: %s\n") % inst.reason[1])
2714 u.warn(_("abort: error: %s\n") % inst.reason[1])
2713 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2715 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2714 if u.debugflag:
2716 if u.debugflag:
2715 u.warn(_("broken pipe\n"))
2717 u.warn(_("broken pipe\n"))
2716 elif getattr(inst, "strerror", None):
2718 elif getattr(inst, "strerror", None):
2717 if getattr(inst, "filename", None):
2719 if getattr(inst, "filename", None):
2718 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
2720 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
2719 else:
2721 else:
2720 u.warn(_("abort: %s\n") % inst.strerror)
2722 u.warn(_("abort: %s\n") % inst.strerror)
2721 else:
2723 else:
2722 raise
2724 raise
2723 except OSError, inst:
2725 except OSError, inst:
2724 if hasattr(inst, "filename"):
2726 if hasattr(inst, "filename"):
2725 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
2727 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
2726 else:
2728 else:
2727 u.warn(_("abort: %s\n") % inst.strerror)
2729 u.warn(_("abort: %s\n") % inst.strerror)
2728 except util.Abort, inst:
2730 except util.Abort, inst:
2729 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
2731 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
2730 sys.exit(1)
2732 sys.exit(1)
2731 except TypeError, inst:
2733 except TypeError, inst:
2732 # was this an argument error?
2734 # was this an argument error?
2733 tb = traceback.extract_tb(sys.exc_info()[2])
2735 tb = traceback.extract_tb(sys.exc_info()[2])
2734 if len(tb) > 2: # no
2736 if len(tb) > 2: # no
2735 raise
2737 raise
2736 u.debug(inst, "\n")
2738 u.debug(inst, "\n")
2737 u.warn(_("%s: invalid arguments\n") % cmd)
2739 u.warn(_("%s: invalid arguments\n") % cmd)
2738 help_(u, cmd)
2740 help_(u, cmd)
2739 except AmbiguousCommand, inst:
2741 except AmbiguousCommand, inst:
2740 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2742 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2741 help_(u, 'shortlist')
2743 help_(u, 'shortlist')
2742 except UnknownCommand, inst:
2744 except UnknownCommand, inst:
2743 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2745 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2744 help_(u, 'shortlist')
2746 help_(u, 'shortlist')
2745 except SystemExit:
2747 except SystemExit:
2746 # don't catch this in the catch-all below
2748 # don't catch this in the catch-all below
2747 raise
2749 raise
2748 except:
2750 except:
2749 u.warn(_("** unknown exception encountered, details follow\n"))
2751 u.warn(_("** unknown exception encountered, details follow\n"))
2750 u.warn(_("** report bug details to mercurial@selenic.com\n"))
2752 u.warn(_("** report bug details to mercurial@selenic.com\n"))
2751 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
2753 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
2752 % version.get_version())
2754 % version.get_version())
2753 raise
2755 raise
2754
2756
2755 sys.exit(-1)
2757 sys.exit(-1)
@@ -1,1109 +1,1110
1 # hgweb.py - web interface to a mercurial repository
1 # hgweb.py - web interface to a mercurial repository
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, cgi, sys
9 import os, cgi, sys
10 from demandload import demandload
10 from demandload import demandload
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 demandload(globals(), "mimetypes")
13 demandload(globals(), "mimetypes")
14 from node import *
14 from node import *
15 from i18n import gettext as _
15 from i18n import gettext as _
16
16
17 def templatepath():
17 def templatepath():
18 for f in "templates", "../templates":
18 for f in "templates", "../templates":
19 p = os.path.join(os.path.dirname(__file__), f)
19 p = os.path.join(os.path.dirname(__file__), f)
20 if os.path.isdir(p):
20 if os.path.isdir(p):
21 return p
21 return p
22
22
23 def age(x):
23 def age(x):
24 def plural(t, c):
24 def plural(t, c):
25 if c == 1:
25 if c == 1:
26 return t
26 return t
27 return t + "s"
27 return t + "s"
28 def fmt(t, c):
28 def fmt(t, c):
29 return "%d %s" % (c, plural(t, c))
29 return "%d %s" % (c, plural(t, c))
30
30
31 now = time.time()
31 now = time.time()
32 then = x[0]
32 then = x[0]
33 delta = max(1, int(now - then))
33 delta = max(1, int(now - then))
34
34
35 scales = [["second", 1],
35 scales = [["second", 1],
36 ["minute", 60],
36 ["minute", 60],
37 ["hour", 3600],
37 ["hour", 3600],
38 ["day", 3600 * 24],
38 ["day", 3600 * 24],
39 ["week", 3600 * 24 * 7],
39 ["week", 3600 * 24 * 7],
40 ["month", 3600 * 24 * 30],
40 ["month", 3600 * 24 * 30],
41 ["year", 3600 * 24 * 365]]
41 ["year", 3600 * 24 * 365]]
42
42
43 scales.reverse()
43 scales.reverse()
44
44
45 for t, s in scales:
45 for t, s in scales:
46 n = delta / s
46 n = delta / s
47 if n >= 2 or s == 1:
47 if n >= 2 or s == 1:
48 return fmt(t, n)
48 return fmt(t, n)
49
49
50 def nl2br(text):
50 def nl2br(text):
51 return text.replace('\n', '<br/>\n')
51 return text.replace('\n', '<br/>\n')
52
52
53 def obfuscate(text):
53 def obfuscate(text):
54 return ''.join(['&#%d;' % ord(c) for c in text])
54 return ''.join(['&#%d;' % ord(c) for c in text])
55
55
56 def up(p):
56 def up(p):
57 if p[0] != "/":
57 if p[0] != "/":
58 p = "/" + p
58 p = "/" + p
59 if p[-1] == "/":
59 if p[-1] == "/":
60 p = p[:-1]
60 p = p[:-1]
61 up = os.path.dirname(p)
61 up = os.path.dirname(p)
62 if up == "/":
62 if up == "/":
63 return "/"
63 return "/"
64 return up + "/"
64 return up + "/"
65
65
66 def get_mtime(repo_path):
66 def get_mtime(repo_path):
67 hg_path = os.path.join(repo_path, ".hg")
67 hg_path = os.path.join(repo_path, ".hg")
68 cl_path = os.path.join(hg_path, "00changelog.i")
68 cl_path = os.path.join(hg_path, "00changelog.i")
69 if os.path.exists(os.path.join(cl_path)):
69 if os.path.exists(os.path.join(cl_path)):
70 return os.stat(cl_path).st_mtime
70 return os.stat(cl_path).st_mtime
71 else:
71 else:
72 return os.stat(hg_path).st_mtime
72 return os.stat(hg_path).st_mtime
73
73
74 class hgrequest(object):
74 class hgrequest(object):
75 def __init__(self, inp=None, out=None, env=None):
75 def __init__(self, inp=None, out=None, env=None):
76 self.inp = inp or sys.stdin
76 self.inp = inp or sys.stdin
77 self.out = out or sys.stdout
77 self.out = out or sys.stdout
78 self.env = env or os.environ
78 self.env = env or os.environ
79 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
79 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
80
80
81 def write(self, *things):
81 def write(self, *things):
82 for thing in things:
82 for thing in things:
83 if hasattr(thing, "__iter__"):
83 if hasattr(thing, "__iter__"):
84 for part in thing:
84 for part in thing:
85 self.write(part)
85 self.write(part)
86 else:
86 else:
87 try:
87 try:
88 self.out.write(str(thing))
88 self.out.write(str(thing))
89 except socket.error, inst:
89 except socket.error, inst:
90 if inst[0] != errno.ECONNRESET:
90 if inst[0] != errno.ECONNRESET:
91 raise
91 raise
92
92
93 def header(self, headers=[('Content-type','text/html')]):
93 def header(self, headers=[('Content-type','text/html')]):
94 for header in headers:
94 for header in headers:
95 self.out.write("%s: %s\r\n" % header)
95 self.out.write("%s: %s\r\n" % header)
96 self.out.write("\r\n")
96 self.out.write("\r\n")
97
97
98 def httphdr(self, type, file="", size=0):
98 def httphdr(self, type, file="", size=0):
99
99
100 headers = [('Content-type', type)]
100 headers = [('Content-type', type)]
101 if file:
101 if file:
102 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
102 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
103 if size > 0:
103 if size > 0:
104 headers.append(('Content-length', str(size)))
104 headers.append(('Content-length', str(size)))
105 self.header(headers)
105 self.header(headers)
106
106
107 class templater(object):
107 class templater(object):
108 def __init__(self, mapfile, filters={}, defaults={}):
108 def __init__(self, mapfile, filters={}, defaults={}):
109 self.cache = {}
109 self.cache = {}
110 self.map = {}
110 self.map = {}
111 self.base = os.path.dirname(mapfile)
111 self.base = os.path.dirname(mapfile)
112 self.filters = filters
112 self.filters = filters
113 self.defaults = defaults
113 self.defaults = defaults
114
114
115 for l in file(mapfile):
115 for l in file(mapfile):
116 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
116 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
117 if m:
117 if m:
118 self.cache[m.group(1)] = m.group(2)
118 self.cache[m.group(1)] = m.group(2)
119 else:
119 else:
120 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
120 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
121 if m:
121 if m:
122 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
122 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
123 else:
123 else:
124 raise LookupError(_("unknown map entry '%s'") % l)
124 raise LookupError(_("unknown map entry '%s'") % l)
125
125
126 def __call__(self, t, **map):
126 def __call__(self, t, **map):
127 m = self.defaults.copy()
127 m = self.defaults.copy()
128 m.update(map)
128 m.update(map)
129 try:
129 try:
130 tmpl = self.cache[t]
130 tmpl = self.cache[t]
131 except KeyError:
131 except KeyError:
132 tmpl = self.cache[t] = file(self.map[t]).read()
132 tmpl = self.cache[t] = file(self.map[t]).read()
133 return self.template(tmpl, self.filters, **m)
133 return self.template(tmpl, self.filters, **m)
134
134
135 def template(self, tmpl, filters={}, **map):
135 def template(self, tmpl, filters={}, **map):
136 while tmpl:
136 while tmpl:
137 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
137 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
138 if m:
138 if m:
139 yield tmpl[:m.start(0)]
139 yield tmpl[:m.start(0)]
140 v = map.get(m.group(1), "")
140 v = map.get(m.group(1), "")
141 v = callable(v) and v(**map) or v
141 v = callable(v) and v(**map) or v
142
142
143 format = m.group(2)
143 format = m.group(2)
144 fl = m.group(4)
144 fl = m.group(4)
145
145
146 if format:
146 if format:
147 q = v.__iter__
147 q = v.__iter__
148 for i in q():
148 for i in q():
149 lm = map.copy()
149 lm = map.copy()
150 lm.update(i)
150 lm.update(i)
151 yield self(format[1:], **lm)
151 yield self(format[1:], **lm)
152
152
153 v = ""
153 v = ""
154
154
155 elif fl:
155 elif fl:
156 for f in fl.split("|")[1:]:
156 for f in fl.split("|")[1:]:
157 v = filters[f](v)
157 v = filters[f](v)
158
158
159 yield v
159 yield v
160 tmpl = tmpl[m.end(0):]
160 tmpl = tmpl[m.end(0):]
161 else:
161 else:
162 yield tmpl
162 yield tmpl
163 return
163 return
164
164
165 common_filters = {
165 common_filters = {
166 "escape": cgi.escape,
166 "escape": cgi.escape,
167 "strip": lambda x: x.strip(),
167 "strip": lambda x: x.strip(),
168 "age": age,
168 "age": age,
169 "date": lambda x: util.datestr(x),
169 "date": lambda x: util.datestr(x),
170 "addbreaks": nl2br,
170 "addbreaks": nl2br,
171 "obfuscate": obfuscate,
171 "obfuscate": obfuscate,
172 "short": (lambda x: x[:12]),
172 "short": (lambda x: x[:12]),
173 "firstline": (lambda x: x.splitlines(1)[0]),
173 "firstline": (lambda x: x.splitlines(1)[0]),
174 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
174 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
175 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
175 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
176 }
176 }
177
177
178 class hgweb(object):
178 class hgweb(object):
179 def __init__(self, repo, name=None):
179 def __init__(self, repo, name=None):
180 if type(repo) == type(""):
180 if type(repo) == type(""):
181 self.repo = hg.repository(ui.ui(), repo)
181 self.repo = hg.repository(ui.ui(), repo)
182 else:
182 else:
183 self.repo = repo
183 self.repo = repo
184
184
185 self.mtime = -1
185 self.mtime = -1
186 self.reponame = name
186 self.reponame = name
187 self.archives = 'zip', 'gz', 'bz2'
187 self.archives = 'zip', 'gz', 'bz2'
188
188
189 def refresh(self):
189 def refresh(self):
190 mtime = get_mtime(self.repo.root)
190 mtime = get_mtime(self.repo.root)
191 if mtime != self.mtime:
191 if mtime != self.mtime:
192 self.mtime = mtime
192 self.mtime = mtime
193 self.repo = hg.repository(self.repo.ui, self.repo.root)
193 self.repo = hg.repository(self.repo.ui, self.repo.root)
194 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
194 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
195 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
195 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
196 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
196 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
197
197
198 def archivelist(self, nodeid):
198 def archivelist(self, nodeid):
199 for i in self.archives:
199 for i in self.archives:
200 if self.repo.ui.configbool("web", "allow" + i, False):
200 if self.repo.ui.configbool("web", "allow" + i, False):
201 yield {"type" : i, "node" : nodeid}
201 yield {"type" : i, "node" : nodeid}
202
202
203 def listfiles(self, files, mf):
203 def listfiles(self, files, mf):
204 for f in files[:self.maxfiles]:
204 for f in files[:self.maxfiles]:
205 yield self.t("filenodelink", node=hex(mf[f]), file=f)
205 yield self.t("filenodelink", node=hex(mf[f]), file=f)
206 if len(files) > self.maxfiles:
206 if len(files) > self.maxfiles:
207 yield self.t("fileellipses")
207 yield self.t("fileellipses")
208
208
209 def listfilediffs(self, files, changeset):
209 def listfilediffs(self, files, changeset):
210 for f in files[:self.maxfiles]:
210 for f in files[:self.maxfiles]:
211 yield self.t("filedifflink", node=hex(changeset), file=f)
211 yield self.t("filedifflink", node=hex(changeset), file=f)
212 if len(files) > self.maxfiles:
212 if len(files) > self.maxfiles:
213 yield self.t("fileellipses")
213 yield self.t("fileellipses")
214
214
215 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
215 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
216 if not rev:
216 if not rev:
217 rev = lambda x: ""
217 rev = lambda x: ""
218 siblings = [s for s in siblings if s != nullid]
218 siblings = [s for s in siblings if s != nullid]
219 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
219 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
220 return
220 return
221 for s in siblings:
221 for s in siblings:
222 yield dict(node=hex(s), rev=rev(s), **args)
222 yield dict(node=hex(s), rev=rev(s), **args)
223
223
224 def showtag(self, t1, node=nullid, **args):
224 def showtag(self, t1, node=nullid, **args):
225 for t in self.repo.nodetags(node):
225 for t in self.repo.nodetags(node):
226 yield self.t(t1, tag=t, **args)
226 yield self.t(t1, tag=t, **args)
227
227
228 def diff(self, node1, node2, files):
228 def diff(self, node1, node2, files):
229 def filterfiles(list, files):
229 def filterfiles(list, files):
230 l = [x for x in list if x in files]
230 l = [x for x in list if x in files]
231
231
232 for f in files:
232 for f in files:
233 if f[-1] != os.sep:
233 if f[-1] != os.sep:
234 f += os.sep
234 f += os.sep
235 l += [x for x in list if x.startswith(f)]
235 l += [x for x in list if x.startswith(f)]
236 return l
236 return l
237
237
238 parity = [0]
238 parity = [0]
239 def diffblock(diff, f, fn):
239 def diffblock(diff, f, fn):
240 yield self.t("diffblock",
240 yield self.t("diffblock",
241 lines=prettyprintlines(diff),
241 lines=prettyprintlines(diff),
242 parity=parity[0],
242 parity=parity[0],
243 file=f,
243 file=f,
244 filenode=hex(fn or nullid))
244 filenode=hex(fn or nullid))
245 parity[0] = 1 - parity[0]
245 parity[0] = 1 - parity[0]
246
246
247 def prettyprintlines(diff):
247 def prettyprintlines(diff):
248 for l in diff.splitlines(1):
248 for l in diff.splitlines(1):
249 if l.startswith('+'):
249 if l.startswith('+'):
250 yield self.t("difflineplus", line=l)
250 yield self.t("difflineplus", line=l)
251 elif l.startswith('-'):
251 elif l.startswith('-'):
252 yield self.t("difflineminus", line=l)
252 yield self.t("difflineminus", line=l)
253 elif l.startswith('@'):
253 elif l.startswith('@'):
254 yield self.t("difflineat", line=l)
254 yield self.t("difflineat", line=l)
255 else:
255 else:
256 yield self.t("diffline", line=l)
256 yield self.t("diffline", line=l)
257
257
258 r = self.repo
258 r = self.repo
259 cl = r.changelog
259 cl = r.changelog
260 mf = r.manifest
260 mf = r.manifest
261 change1 = cl.read(node1)
261 change1 = cl.read(node1)
262 change2 = cl.read(node2)
262 change2 = cl.read(node2)
263 mmap1 = mf.read(change1[0])
263 mmap1 = mf.read(change1[0])
264 mmap2 = mf.read(change2[0])
264 mmap2 = mf.read(change2[0])
265 date1 = util.datestr(change1[2])
265 date1 = util.datestr(change1[2])
266 date2 = util.datestr(change2[2])
266 date2 = util.datestr(change2[2])
267
267
268 c, a, d, u = r.changes(node1, node2)
268 modified, added, removed, unknown = r.changes(node1, node2)
269 if files:
269 if files:
270 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
270 modified, added, removed = map(lambda x: filterfiles(x, files),
271 (modified, added, removed))
271
272
272 for f in c:
273 for f in modified:
273 to = r.file(f).read(mmap1[f])
274 to = r.file(f).read(mmap1[f])
274 tn = r.file(f).read(mmap2[f])
275 tn = r.file(f).read(mmap2[f])
275 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
276 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
276 for f in a:
277 for f in added:
277 to = None
278 to = None
278 tn = r.file(f).read(mmap2[f])
279 tn = r.file(f).read(mmap2[f])
279 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
280 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
280 for f in d:
281 for f in removed:
281 to = r.file(f).read(mmap1[f])
282 to = r.file(f).read(mmap1[f])
282 tn = None
283 tn = None
283 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
284 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
284
285
285 def changelog(self, pos):
286 def changelog(self, pos):
286 def changenav(**map):
287 def changenav(**map):
287 def seq(factor=1):
288 def seq(factor=1):
288 yield 1 * factor
289 yield 1 * factor
289 yield 3 * factor
290 yield 3 * factor
290 #yield 5 * factor
291 #yield 5 * factor
291 for f in seq(factor * 10):
292 for f in seq(factor * 10):
292 yield f
293 yield f
293
294
294 l = []
295 l = []
295 for f in seq():
296 for f in seq():
296 if f < self.maxchanges / 2:
297 if f < self.maxchanges / 2:
297 continue
298 continue
298 if f > count:
299 if f > count:
299 break
300 break
300 r = "%d" % f
301 r = "%d" % f
301 if pos + f < count:
302 if pos + f < count:
302 l.append(("+" + r, pos + f))
303 l.append(("+" + r, pos + f))
303 if pos - f >= 0:
304 if pos - f >= 0:
304 l.insert(0, ("-" + r, pos - f))
305 l.insert(0, ("-" + r, pos - f))
305
306
306 yield {"rev": 0, "label": "(0)"}
307 yield {"rev": 0, "label": "(0)"}
307
308
308 for label, rev in l:
309 for label, rev in l:
309 yield {"label": label, "rev": rev}
310 yield {"label": label, "rev": rev}
310
311
311 yield {"label": "tip", "rev": "tip"}
312 yield {"label": "tip", "rev": "tip"}
312
313
313 def changelist(**map):
314 def changelist(**map):
314 parity = (start - end) & 1
315 parity = (start - end) & 1
315 cl = self.repo.changelog
316 cl = self.repo.changelog
316 l = [] # build a list in forward order for efficiency
317 l = [] # build a list in forward order for efficiency
317 for i in range(start, end):
318 for i in range(start, end):
318 n = cl.node(i)
319 n = cl.node(i)
319 changes = cl.read(n)
320 changes = cl.read(n)
320 hn = hex(n)
321 hn = hex(n)
321
322
322 l.insert(0, {"parity": parity,
323 l.insert(0, {"parity": parity,
323 "author": changes[1],
324 "author": changes[1],
324 "parent": self.siblings(cl.parents(n), cl.rev,
325 "parent": self.siblings(cl.parents(n), cl.rev,
325 cl.rev(n) - 1),
326 cl.rev(n) - 1),
326 "child": self.siblings(cl.children(n), cl.rev,
327 "child": self.siblings(cl.children(n), cl.rev,
327 cl.rev(n) + 1),
328 cl.rev(n) + 1),
328 "changelogtag": self.showtag("changelogtag",n),
329 "changelogtag": self.showtag("changelogtag",n),
329 "manifest": hex(changes[0]),
330 "manifest": hex(changes[0]),
330 "desc": changes[4],
331 "desc": changes[4],
331 "date": changes[2],
332 "date": changes[2],
332 "files": self.listfilediffs(changes[3], n),
333 "files": self.listfilediffs(changes[3], n),
333 "rev": i,
334 "rev": i,
334 "node": hn})
335 "node": hn})
335 parity = 1 - parity
336 parity = 1 - parity
336
337
337 for e in l:
338 for e in l:
338 yield e
339 yield e
339
340
340 cl = self.repo.changelog
341 cl = self.repo.changelog
341 mf = cl.read(cl.tip())[0]
342 mf = cl.read(cl.tip())[0]
342 count = cl.count()
343 count = cl.count()
343 start = max(0, pos - self.maxchanges + 1)
344 start = max(0, pos - self.maxchanges + 1)
344 end = min(count, start + self.maxchanges)
345 end = min(count, start + self.maxchanges)
345 pos = end - 1
346 pos = end - 1
346
347
347 yield self.t('changelog',
348 yield self.t('changelog',
348 changenav=changenav,
349 changenav=changenav,
349 manifest=hex(mf),
350 manifest=hex(mf),
350 rev=pos, changesets=count, entries=changelist)
351 rev=pos, changesets=count, entries=changelist)
351
352
352 def search(self, query):
353 def search(self, query):
353
354
354 def changelist(**map):
355 def changelist(**map):
355 cl = self.repo.changelog
356 cl = self.repo.changelog
356 count = 0
357 count = 0
357 qw = query.lower().split()
358 qw = query.lower().split()
358
359
359 def revgen():
360 def revgen():
360 for i in range(cl.count() - 1, 0, -100):
361 for i in range(cl.count() - 1, 0, -100):
361 l = []
362 l = []
362 for j in range(max(0, i - 100), i):
363 for j in range(max(0, i - 100), i):
363 n = cl.node(j)
364 n = cl.node(j)
364 changes = cl.read(n)
365 changes = cl.read(n)
365 l.append((n, j, changes))
366 l.append((n, j, changes))
366 l.reverse()
367 l.reverse()
367 for e in l:
368 for e in l:
368 yield e
369 yield e
369
370
370 for n, i, changes in revgen():
371 for n, i, changes in revgen():
371 miss = 0
372 miss = 0
372 for q in qw:
373 for q in qw:
373 if not (q in changes[1].lower() or
374 if not (q in changes[1].lower() or
374 q in changes[4].lower() or
375 q in changes[4].lower() or
375 q in " ".join(changes[3][:20]).lower()):
376 q in " ".join(changes[3][:20]).lower()):
376 miss = 1
377 miss = 1
377 break
378 break
378 if miss:
379 if miss:
379 continue
380 continue
380
381
381 count += 1
382 count += 1
382 hn = hex(n)
383 hn = hex(n)
383
384
384 yield self.t('searchentry',
385 yield self.t('searchentry',
385 parity=count & 1,
386 parity=count & 1,
386 author=changes[1],
387 author=changes[1],
387 parent=self.siblings(cl.parents(n), cl.rev),
388 parent=self.siblings(cl.parents(n), cl.rev),
388 child=self.siblings(cl.children(n), cl.rev),
389 child=self.siblings(cl.children(n), cl.rev),
389 changelogtag=self.showtag("changelogtag",n),
390 changelogtag=self.showtag("changelogtag",n),
390 manifest=hex(changes[0]),
391 manifest=hex(changes[0]),
391 desc=changes[4],
392 desc=changes[4],
392 date=changes[2],
393 date=changes[2],
393 files=self.listfilediffs(changes[3], n),
394 files=self.listfilediffs(changes[3], n),
394 rev=i,
395 rev=i,
395 node=hn)
396 node=hn)
396
397
397 if count >= self.maxchanges:
398 if count >= self.maxchanges:
398 break
399 break
399
400
400 cl = self.repo.changelog
401 cl = self.repo.changelog
401 mf = cl.read(cl.tip())[0]
402 mf = cl.read(cl.tip())[0]
402
403
403 yield self.t('search',
404 yield self.t('search',
404 query=query,
405 query=query,
405 manifest=hex(mf),
406 manifest=hex(mf),
406 entries=changelist)
407 entries=changelist)
407
408
408 def changeset(self, nodeid):
409 def changeset(self, nodeid):
409 cl = self.repo.changelog
410 cl = self.repo.changelog
410 n = self.repo.lookup(nodeid)
411 n = self.repo.lookup(nodeid)
411 nodeid = hex(n)
412 nodeid = hex(n)
412 changes = cl.read(n)
413 changes = cl.read(n)
413 p1 = cl.parents(n)[0]
414 p1 = cl.parents(n)[0]
414
415
415 files = []
416 files = []
416 mf = self.repo.manifest.read(changes[0])
417 mf = self.repo.manifest.read(changes[0])
417 for f in changes[3]:
418 for f in changes[3]:
418 files.append(self.t("filenodelink",
419 files.append(self.t("filenodelink",
419 filenode=hex(mf.get(f, nullid)), file=f))
420 filenode=hex(mf.get(f, nullid)), file=f))
420
421
421 def diff(**map):
422 def diff(**map):
422 yield self.diff(p1, n, None)
423 yield self.diff(p1, n, None)
423
424
424 yield self.t('changeset',
425 yield self.t('changeset',
425 diff=diff,
426 diff=diff,
426 rev=cl.rev(n),
427 rev=cl.rev(n),
427 node=nodeid,
428 node=nodeid,
428 parent=self.siblings(cl.parents(n), cl.rev),
429 parent=self.siblings(cl.parents(n), cl.rev),
429 child=self.siblings(cl.children(n), cl.rev),
430 child=self.siblings(cl.children(n), cl.rev),
430 changesettag=self.showtag("changesettag",n),
431 changesettag=self.showtag("changesettag",n),
431 manifest=hex(changes[0]),
432 manifest=hex(changes[0]),
432 author=changes[1],
433 author=changes[1],
433 desc=changes[4],
434 desc=changes[4],
434 date=changes[2],
435 date=changes[2],
435 files=files,
436 files=files,
436 archives=self.archivelist(nodeid))
437 archives=self.archivelist(nodeid))
437
438
438 def filelog(self, f, filenode):
439 def filelog(self, f, filenode):
439 cl = self.repo.changelog
440 cl = self.repo.changelog
440 fl = self.repo.file(f)
441 fl = self.repo.file(f)
441 filenode = hex(fl.lookup(filenode))
442 filenode = hex(fl.lookup(filenode))
442 count = fl.count()
443 count = fl.count()
443
444
444 def entries(**map):
445 def entries(**map):
445 l = []
446 l = []
446 parity = (count - 1) & 1
447 parity = (count - 1) & 1
447
448
448 for i in range(count):
449 for i in range(count):
449 n = fl.node(i)
450 n = fl.node(i)
450 lr = fl.linkrev(n)
451 lr = fl.linkrev(n)
451 cn = cl.node(lr)
452 cn = cl.node(lr)
452 cs = cl.read(cl.node(lr))
453 cs = cl.read(cl.node(lr))
453
454
454 l.insert(0, {"parity": parity,
455 l.insert(0, {"parity": parity,
455 "filenode": hex(n),
456 "filenode": hex(n),
456 "filerev": i,
457 "filerev": i,
457 "file": f,
458 "file": f,
458 "node": hex(cn),
459 "node": hex(cn),
459 "author": cs[1],
460 "author": cs[1],
460 "date": cs[2],
461 "date": cs[2],
461 "parent": self.siblings(fl.parents(n),
462 "parent": self.siblings(fl.parents(n),
462 fl.rev, file=f),
463 fl.rev, file=f),
463 "child": self.siblings(fl.children(n),
464 "child": self.siblings(fl.children(n),
464 fl.rev, file=f),
465 fl.rev, file=f),
465 "desc": cs[4]})
466 "desc": cs[4]})
466 parity = 1 - parity
467 parity = 1 - parity
467
468
468 for e in l:
469 for e in l:
469 yield e
470 yield e
470
471
471 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
472 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
472
473
473 def filerevision(self, f, node):
474 def filerevision(self, f, node):
474 fl = self.repo.file(f)
475 fl = self.repo.file(f)
475 n = fl.lookup(node)
476 n = fl.lookup(node)
476 node = hex(n)
477 node = hex(n)
477 text = fl.read(n)
478 text = fl.read(n)
478 changerev = fl.linkrev(n)
479 changerev = fl.linkrev(n)
479 cl = self.repo.changelog
480 cl = self.repo.changelog
480 cn = cl.node(changerev)
481 cn = cl.node(changerev)
481 cs = cl.read(cn)
482 cs = cl.read(cn)
482 mfn = cs[0]
483 mfn = cs[0]
483
484
484 mt = mimetypes.guess_type(f)[0]
485 mt = mimetypes.guess_type(f)[0]
485 rawtext = text
486 rawtext = text
486 if util.binary(text):
487 if util.binary(text):
487 text = "(binary:%s)" % mt
488 text = "(binary:%s)" % mt
488
489
489 def lines():
490 def lines():
490 for l, t in enumerate(text.splitlines(1)):
491 for l, t in enumerate(text.splitlines(1)):
491 yield {"line": t,
492 yield {"line": t,
492 "linenumber": "% 6d" % (l + 1),
493 "linenumber": "% 6d" % (l + 1),
493 "parity": l & 1}
494 "parity": l & 1}
494
495
495 yield self.t("filerevision",
496 yield self.t("filerevision",
496 file=f,
497 file=f,
497 filenode=node,
498 filenode=node,
498 path=up(f),
499 path=up(f),
499 text=lines(),
500 text=lines(),
500 raw=rawtext,
501 raw=rawtext,
501 mimetype=mt,
502 mimetype=mt,
502 rev=changerev,
503 rev=changerev,
503 node=hex(cn),
504 node=hex(cn),
504 manifest=hex(mfn),
505 manifest=hex(mfn),
505 author=cs[1],
506 author=cs[1],
506 date=cs[2],
507 date=cs[2],
507 parent=self.siblings(fl.parents(n), fl.rev, file=f),
508 parent=self.siblings(fl.parents(n), fl.rev, file=f),
508 child=self.siblings(fl.children(n), fl.rev, file=f),
509 child=self.siblings(fl.children(n), fl.rev, file=f),
509 permissions=self.repo.manifest.readflags(mfn)[f])
510 permissions=self.repo.manifest.readflags(mfn)[f])
510
511
511 def fileannotate(self, f, node):
512 def fileannotate(self, f, node):
512 bcache = {}
513 bcache = {}
513 ncache = {}
514 ncache = {}
514 fl = self.repo.file(f)
515 fl = self.repo.file(f)
515 n = fl.lookup(node)
516 n = fl.lookup(node)
516 node = hex(n)
517 node = hex(n)
517 changerev = fl.linkrev(n)
518 changerev = fl.linkrev(n)
518
519
519 cl = self.repo.changelog
520 cl = self.repo.changelog
520 cn = cl.node(changerev)
521 cn = cl.node(changerev)
521 cs = cl.read(cn)
522 cs = cl.read(cn)
522 mfn = cs[0]
523 mfn = cs[0]
523
524
524 def annotate(**map):
525 def annotate(**map):
525 parity = 1
526 parity = 1
526 last = None
527 last = None
527 for r, l in fl.annotate(n):
528 for r, l in fl.annotate(n):
528 try:
529 try:
529 cnode = ncache[r]
530 cnode = ncache[r]
530 except KeyError:
531 except KeyError:
531 cnode = ncache[r] = self.repo.changelog.node(r)
532 cnode = ncache[r] = self.repo.changelog.node(r)
532
533
533 try:
534 try:
534 name = bcache[r]
535 name = bcache[r]
535 except KeyError:
536 except KeyError:
536 cl = self.repo.changelog.read(cnode)
537 cl = self.repo.changelog.read(cnode)
537 bcache[r] = name = self.repo.ui.shortuser(cl[1])
538 bcache[r] = name = self.repo.ui.shortuser(cl[1])
538
539
539 if last != cnode:
540 if last != cnode:
540 parity = 1 - parity
541 parity = 1 - parity
541 last = cnode
542 last = cnode
542
543
543 yield {"parity": parity,
544 yield {"parity": parity,
544 "node": hex(cnode),
545 "node": hex(cnode),
545 "rev": r,
546 "rev": r,
546 "author": name,
547 "author": name,
547 "file": f,
548 "file": f,
548 "line": l}
549 "line": l}
549
550
550 yield self.t("fileannotate",
551 yield self.t("fileannotate",
551 file=f,
552 file=f,
552 filenode=node,
553 filenode=node,
553 annotate=annotate,
554 annotate=annotate,
554 path=up(f),
555 path=up(f),
555 rev=changerev,
556 rev=changerev,
556 node=hex(cn),
557 node=hex(cn),
557 manifest=hex(mfn),
558 manifest=hex(mfn),
558 author=cs[1],
559 author=cs[1],
559 date=cs[2],
560 date=cs[2],
560 parent=self.siblings(fl.parents(n), fl.rev, file=f),
561 parent=self.siblings(fl.parents(n), fl.rev, file=f),
561 child=self.siblings(fl.children(n), fl.rev, file=f),
562 child=self.siblings(fl.children(n), fl.rev, file=f),
562 permissions=self.repo.manifest.readflags(mfn)[f])
563 permissions=self.repo.manifest.readflags(mfn)[f])
563
564
564 def manifest(self, mnode, path):
565 def manifest(self, mnode, path):
565 man = self.repo.manifest
566 man = self.repo.manifest
566 mn = man.lookup(mnode)
567 mn = man.lookup(mnode)
567 mnode = hex(mn)
568 mnode = hex(mn)
568 mf = man.read(mn)
569 mf = man.read(mn)
569 rev = man.rev(mn)
570 rev = man.rev(mn)
570 node = self.repo.changelog.node(rev)
571 node = self.repo.changelog.node(rev)
571 mff = man.readflags(mn)
572 mff = man.readflags(mn)
572
573
573 files = {}
574 files = {}
574
575
575 p = path[1:]
576 p = path[1:]
576 l = len(p)
577 l = len(p)
577
578
578 for f,n in mf.items():
579 for f,n in mf.items():
579 if f[:l] != p:
580 if f[:l] != p:
580 continue
581 continue
581 remain = f[l:]
582 remain = f[l:]
582 if "/" in remain:
583 if "/" in remain:
583 short = remain[:remain.find("/") + 1] # bleah
584 short = remain[:remain.find("/") + 1] # bleah
584 files[short] = (f, None)
585 files[short] = (f, None)
585 else:
586 else:
586 short = os.path.basename(remain)
587 short = os.path.basename(remain)
587 files[short] = (f, n)
588 files[short] = (f, n)
588
589
589 def filelist(**map):
590 def filelist(**map):
590 parity = 0
591 parity = 0
591 fl = files.keys()
592 fl = files.keys()
592 fl.sort()
593 fl.sort()
593 for f in fl:
594 for f in fl:
594 full, fnode = files[f]
595 full, fnode = files[f]
595 if not fnode:
596 if not fnode:
596 continue
597 continue
597
598
598 yield {"file": full,
599 yield {"file": full,
599 "manifest": mnode,
600 "manifest": mnode,
600 "filenode": hex(fnode),
601 "filenode": hex(fnode),
601 "parity": parity,
602 "parity": parity,
602 "basename": f,
603 "basename": f,
603 "permissions": mff[full]}
604 "permissions": mff[full]}
604 parity = 1 - parity
605 parity = 1 - parity
605
606
606 def dirlist(**map):
607 def dirlist(**map):
607 parity = 0
608 parity = 0
608 fl = files.keys()
609 fl = files.keys()
609 fl.sort()
610 fl.sort()
610 for f in fl:
611 for f in fl:
611 full, fnode = files[f]
612 full, fnode = files[f]
612 if fnode:
613 if fnode:
613 continue
614 continue
614
615
615 yield {"parity": parity,
616 yield {"parity": parity,
616 "path": os.path.join(path, f),
617 "path": os.path.join(path, f),
617 "manifest": mnode,
618 "manifest": mnode,
618 "basename": f[:-1]}
619 "basename": f[:-1]}
619 parity = 1 - parity
620 parity = 1 - parity
620
621
621 yield self.t("manifest",
622 yield self.t("manifest",
622 manifest=mnode,
623 manifest=mnode,
623 rev=rev,
624 rev=rev,
624 node=hex(node),
625 node=hex(node),
625 path=path,
626 path=path,
626 up=up(path),
627 up=up(path),
627 fentries=filelist,
628 fentries=filelist,
628 dentries=dirlist,
629 dentries=dirlist,
629 archives=self.archivelist(hex(node)))
630 archives=self.archivelist(hex(node)))
630
631
631 def tags(self):
632 def tags(self):
632 cl = self.repo.changelog
633 cl = self.repo.changelog
633 mf = cl.read(cl.tip())[0]
634 mf = cl.read(cl.tip())[0]
634
635
635 i = self.repo.tagslist()
636 i = self.repo.tagslist()
636 i.reverse()
637 i.reverse()
637
638
638 def entries(**map):
639 def entries(**map):
639 parity = 0
640 parity = 0
640 for k,n in i:
641 for k,n in i:
641 yield {"parity": parity,
642 yield {"parity": parity,
642 "tag": k,
643 "tag": k,
643 "tagmanifest": hex(cl.read(n)[0]),
644 "tagmanifest": hex(cl.read(n)[0]),
644 "date": cl.read(n)[2],
645 "date": cl.read(n)[2],
645 "node": hex(n)}
646 "node": hex(n)}
646 parity = 1 - parity
647 parity = 1 - parity
647
648
648 yield self.t("tags",
649 yield self.t("tags",
649 manifest=hex(mf),
650 manifest=hex(mf),
650 entries=entries)
651 entries=entries)
651
652
652 def summary(self):
653 def summary(self):
653 cl = self.repo.changelog
654 cl = self.repo.changelog
654 mf = cl.read(cl.tip())[0]
655 mf = cl.read(cl.tip())[0]
655
656
656 i = self.repo.tagslist()
657 i = self.repo.tagslist()
657 i.reverse()
658 i.reverse()
658
659
659 def tagentries(**map):
660 def tagentries(**map):
660 parity = 0
661 parity = 0
661 count = 0
662 count = 0
662 for k,n in i:
663 for k,n in i:
663 if k == "tip": # skip tip
664 if k == "tip": # skip tip
664 continue;
665 continue;
665
666
666 count += 1
667 count += 1
667 if count > 10: # limit to 10 tags
668 if count > 10: # limit to 10 tags
668 break;
669 break;
669
670
670 c = cl.read(n)
671 c = cl.read(n)
671 m = c[0]
672 m = c[0]
672 t = c[2]
673 t = c[2]
673
674
674 yield self.t("tagentry",
675 yield self.t("tagentry",
675 parity = parity,
676 parity = parity,
676 tag = k,
677 tag = k,
677 node = hex(n),
678 node = hex(n),
678 date = t,
679 date = t,
679 tagmanifest = hex(m))
680 tagmanifest = hex(m))
680 parity = 1 - parity
681 parity = 1 - parity
681
682
682 def changelist(**map):
683 def changelist(**map):
683 parity = 0
684 parity = 0
684 cl = self.repo.changelog
685 cl = self.repo.changelog
685 l = [] # build a list in forward order for efficiency
686 l = [] # build a list in forward order for efficiency
686 for i in range(start, end):
687 for i in range(start, end):
687 n = cl.node(i)
688 n = cl.node(i)
688 changes = cl.read(n)
689 changes = cl.read(n)
689 hn = hex(n)
690 hn = hex(n)
690 t = changes[2]
691 t = changes[2]
691
692
692 l.insert(0, self.t(
693 l.insert(0, self.t(
693 'shortlogentry',
694 'shortlogentry',
694 parity = parity,
695 parity = parity,
695 author = changes[1],
696 author = changes[1],
696 manifest = hex(changes[0]),
697 manifest = hex(changes[0]),
697 desc = changes[4],
698 desc = changes[4],
698 date = t,
699 date = t,
699 rev = i,
700 rev = i,
700 node = hn))
701 node = hn))
701 parity = 1 - parity
702 parity = 1 - parity
702
703
703 yield l
704 yield l
704
705
705 cl = self.repo.changelog
706 cl = self.repo.changelog
706 mf = cl.read(cl.tip())[0]
707 mf = cl.read(cl.tip())[0]
707 count = cl.count()
708 count = cl.count()
708 start = max(0, count - self.maxchanges)
709 start = max(0, count - self.maxchanges)
709 end = min(count, start + self.maxchanges)
710 end = min(count, start + self.maxchanges)
710 pos = end - 1
711 pos = end - 1
711
712
712 yield self.t("summary",
713 yield self.t("summary",
713 desc = self.repo.ui.config("web", "description", "unknown"),
714 desc = self.repo.ui.config("web", "description", "unknown"),
714 owner = (self.repo.ui.config("ui", "username") or # preferred
715 owner = (self.repo.ui.config("ui", "username") or # preferred
715 self.repo.ui.config("web", "contact") or # deprecated
716 self.repo.ui.config("web", "contact") or # deprecated
716 self.repo.ui.config("web", "author", "unknown")), # also
717 self.repo.ui.config("web", "author", "unknown")), # also
717 lastchange = (0, 0), # FIXME
718 lastchange = (0, 0), # FIXME
718 manifest = hex(mf),
719 manifest = hex(mf),
719 tags = tagentries,
720 tags = tagentries,
720 shortlog = changelist)
721 shortlog = changelist)
721
722
722 def filediff(self, file, changeset):
723 def filediff(self, file, changeset):
723 cl = self.repo.changelog
724 cl = self.repo.changelog
724 n = self.repo.lookup(changeset)
725 n = self.repo.lookup(changeset)
725 changeset = hex(n)
726 changeset = hex(n)
726 p1 = cl.parents(n)[0]
727 p1 = cl.parents(n)[0]
727 cs = cl.read(n)
728 cs = cl.read(n)
728 mf = self.repo.manifest.read(cs[0])
729 mf = self.repo.manifest.read(cs[0])
729
730
730 def diff(**map):
731 def diff(**map):
731 yield self.diff(p1, n, file)
732 yield self.diff(p1, n, file)
732
733
733 yield self.t("filediff",
734 yield self.t("filediff",
734 file=file,
735 file=file,
735 filenode=hex(mf.get(file, nullid)),
736 filenode=hex(mf.get(file, nullid)),
736 node=changeset,
737 node=changeset,
737 rev=self.repo.changelog.rev(n),
738 rev=self.repo.changelog.rev(n),
738 parent=self.siblings(cl.parents(n), cl.rev),
739 parent=self.siblings(cl.parents(n), cl.rev),
739 child=self.siblings(cl.children(n), cl.rev),
740 child=self.siblings(cl.children(n), cl.rev),
740 diff=diff)
741 diff=diff)
741
742
742 def archive(self, req, cnode, type):
743 def archive(self, req, cnode, type):
743 cs = self.repo.changelog.read(cnode)
744 cs = self.repo.changelog.read(cnode)
744 mnode = cs[0]
745 mnode = cs[0]
745 mf = self.repo.manifest.read(mnode)
746 mf = self.repo.manifest.read(mnode)
746 rev = self.repo.manifest.rev(mnode)
747 rev = self.repo.manifest.rev(mnode)
747 reponame = re.sub(r"\W+", "-", self.reponame)
748 reponame = re.sub(r"\W+", "-", self.reponame)
748 name = "%s-%s/" % (reponame, short(cnode))
749 name = "%s-%s/" % (reponame, short(cnode))
749
750
750 files = mf.keys()
751 files = mf.keys()
751 files.sort()
752 files.sort()
752
753
753 if type == 'zip':
754 if type == 'zip':
754 tmp = tempfile.mkstemp()[1]
755 tmp = tempfile.mkstemp()[1]
755 try:
756 try:
756 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
757 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
757
758
758 for f in files:
759 for f in files:
759 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
760 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
760 zf.close()
761 zf.close()
761
762
762 f = open(tmp, 'r')
763 f = open(tmp, 'r')
763 req.httphdr('application/zip', name[:-1] + '.zip',
764 req.httphdr('application/zip', name[:-1] + '.zip',
764 os.path.getsize(tmp))
765 os.path.getsize(tmp))
765 req.write(f.read())
766 req.write(f.read())
766 f.close()
767 f.close()
767 finally:
768 finally:
768 os.unlink(tmp)
769 os.unlink(tmp)
769
770
770 else:
771 else:
771 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
772 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
772 mff = self.repo.manifest.readflags(mnode)
773 mff = self.repo.manifest.readflags(mnode)
773 mtime = int(time.time())
774 mtime = int(time.time())
774
775
775 if type == "gz":
776 if type == "gz":
776 encoding = "gzip"
777 encoding = "gzip"
777 else:
778 else:
778 encoding = "x-bzip2"
779 encoding = "x-bzip2"
779 req.header([('Content-type', 'application/x-tar'),
780 req.header([('Content-type', 'application/x-tar'),
780 ('Content-disposition', 'attachment; filename=%s%s%s' %
781 ('Content-disposition', 'attachment; filename=%s%s%s' %
781 (name[:-1], '.tar.', type)),
782 (name[:-1], '.tar.', type)),
782 ('Content-encoding', encoding)])
783 ('Content-encoding', encoding)])
783 for fname in files:
784 for fname in files:
784 rcont = self.repo.file(fname).read(mf[fname])
785 rcont = self.repo.file(fname).read(mf[fname])
785 finfo = tarfile.TarInfo(name + fname)
786 finfo = tarfile.TarInfo(name + fname)
786 finfo.mtime = mtime
787 finfo.mtime = mtime
787 finfo.size = len(rcont)
788 finfo.size = len(rcont)
788 finfo.mode = mff[fname] and 0755 or 0644
789 finfo.mode = mff[fname] and 0755 or 0644
789 tf.addfile(finfo, StringIO.StringIO(rcont))
790 tf.addfile(finfo, StringIO.StringIO(rcont))
790 tf.close()
791 tf.close()
791
792
792 # add tags to things
793 # add tags to things
793 # tags -> list of changesets corresponding to tags
794 # tags -> list of changesets corresponding to tags
794 # find tag, changeset, file
795 # find tag, changeset, file
795
796
796 def run(self, req=hgrequest()):
797 def run(self, req=hgrequest()):
797 def header(**map):
798 def header(**map):
798 yield self.t("header", **map)
799 yield self.t("header", **map)
799
800
800 def footer(**map):
801 def footer(**map):
801 yield self.t("footer", **map)
802 yield self.t("footer", **map)
802
803
803 def expand_form(form):
804 def expand_form(form):
804 shortcuts = {
805 shortcuts = {
805 'cl': [('cmd', ['changelog']), ('rev', None)],
806 'cl': [('cmd', ['changelog']), ('rev', None)],
806 'cs': [('cmd', ['changeset']), ('node', None)],
807 'cs': [('cmd', ['changeset']), ('node', None)],
807 'f': [('cmd', ['file']), ('filenode', None)],
808 'f': [('cmd', ['file']), ('filenode', None)],
808 'fl': [('cmd', ['filelog']), ('filenode', None)],
809 'fl': [('cmd', ['filelog']), ('filenode', None)],
809 'fd': [('cmd', ['filediff']), ('node', None)],
810 'fd': [('cmd', ['filediff']), ('node', None)],
810 'fa': [('cmd', ['annotate']), ('filenode', None)],
811 'fa': [('cmd', ['annotate']), ('filenode', None)],
811 'mf': [('cmd', ['manifest']), ('manifest', None)],
812 'mf': [('cmd', ['manifest']), ('manifest', None)],
812 'ca': [('cmd', ['archive']), ('node', None)],
813 'ca': [('cmd', ['archive']), ('node', None)],
813 'tags': [('cmd', ['tags'])],
814 'tags': [('cmd', ['tags'])],
814 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
815 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
815 }
816 }
816
817
817 for k in shortcuts.iterkeys():
818 for k in shortcuts.iterkeys():
818 if form.has_key(k):
819 if form.has_key(k):
819 for name, value in shortcuts[k]:
820 for name, value in shortcuts[k]:
820 if value is None:
821 if value is None:
821 value = form[k]
822 value = form[k]
822 form[name] = value
823 form[name] = value
823 del form[k]
824 del form[k]
824
825
825 self.refresh()
826 self.refresh()
826
827
827 expand_form(req.form)
828 expand_form(req.form)
828
829
829 t = self.repo.ui.config("web", "templates", templatepath())
830 t = self.repo.ui.config("web", "templates", templatepath())
830 m = os.path.join(t, "map")
831 m = os.path.join(t, "map")
831 style = self.repo.ui.config("web", "style", "")
832 style = self.repo.ui.config("web", "style", "")
832 if req.form.has_key('style'):
833 if req.form.has_key('style'):
833 style = req.form['style'][0]
834 style = req.form['style'][0]
834 if style:
835 if style:
835 b = os.path.basename("map-" + style)
836 b = os.path.basename("map-" + style)
836 p = os.path.join(t, b)
837 p = os.path.join(t, b)
837 if os.path.isfile(p):
838 if os.path.isfile(p):
838 m = p
839 m = p
839
840
840 port = req.env["SERVER_PORT"]
841 port = req.env["SERVER_PORT"]
841 port = port != "80" and (":" + port) or ""
842 port = port != "80" and (":" + port) or ""
842 uri = req.env["REQUEST_URI"]
843 uri = req.env["REQUEST_URI"]
843 if "?" in uri:
844 if "?" in uri:
844 uri = uri.split("?")[0]
845 uri = uri.split("?")[0]
845 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
846 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
846 if not self.reponame:
847 if not self.reponame:
847 self.reponame = (self.repo.ui.config("web", "name")
848 self.reponame = (self.repo.ui.config("web", "name")
848 or uri.strip('/') or self.repo.root)
849 or uri.strip('/') or self.repo.root)
849
850
850 self.t = templater(m, common_filters,
851 self.t = templater(m, common_filters,
851 {"url": url,
852 {"url": url,
852 "repo": self.reponame,
853 "repo": self.reponame,
853 "header": header,
854 "header": header,
854 "footer": footer,
855 "footer": footer,
855 })
856 })
856
857
857 if not req.form.has_key('cmd'):
858 if not req.form.has_key('cmd'):
858 req.form['cmd'] = [self.t.cache['default'],]
859 req.form['cmd'] = [self.t.cache['default'],]
859
860
860 if req.form['cmd'][0] == 'changelog':
861 if req.form['cmd'][0] == 'changelog':
861 c = self.repo.changelog.count() - 1
862 c = self.repo.changelog.count() - 1
862 hi = c
863 hi = c
863 if req.form.has_key('rev'):
864 if req.form.has_key('rev'):
864 hi = req.form['rev'][0]
865 hi = req.form['rev'][0]
865 try:
866 try:
866 hi = self.repo.changelog.rev(self.repo.lookup(hi))
867 hi = self.repo.changelog.rev(self.repo.lookup(hi))
867 except hg.RepoError:
868 except hg.RepoError:
868 req.write(self.search(hi))
869 req.write(self.search(hi))
869 return
870 return
870
871
871 req.write(self.changelog(hi))
872 req.write(self.changelog(hi))
872
873
873 elif req.form['cmd'][0] == 'changeset':
874 elif req.form['cmd'][0] == 'changeset':
874 req.write(self.changeset(req.form['node'][0]))
875 req.write(self.changeset(req.form['node'][0]))
875
876
876 elif req.form['cmd'][0] == 'manifest':
877 elif req.form['cmd'][0] == 'manifest':
877 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
878 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
878
879
879 elif req.form['cmd'][0] == 'tags':
880 elif req.form['cmd'][0] == 'tags':
880 req.write(self.tags())
881 req.write(self.tags())
881
882
882 elif req.form['cmd'][0] == 'summary':
883 elif req.form['cmd'][0] == 'summary':
883 req.write(self.summary())
884 req.write(self.summary())
884
885
885 elif req.form['cmd'][0] == 'filediff':
886 elif req.form['cmd'][0] == 'filediff':
886 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
887 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
887
888
888 elif req.form['cmd'][0] == 'file':
889 elif req.form['cmd'][0] == 'file':
889 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
890 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
890
891
891 elif req.form['cmd'][0] == 'annotate':
892 elif req.form['cmd'][0] == 'annotate':
892 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
893 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
893
894
894 elif req.form['cmd'][0] == 'filelog':
895 elif req.form['cmd'][0] == 'filelog':
895 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
896 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
896
897
897 elif req.form['cmd'][0] == 'heads':
898 elif req.form['cmd'][0] == 'heads':
898 req.httphdr("application/mercurial-0.1")
899 req.httphdr("application/mercurial-0.1")
899 h = self.repo.heads()
900 h = self.repo.heads()
900 req.write(" ".join(map(hex, h)) + "\n")
901 req.write(" ".join(map(hex, h)) + "\n")
901
902
902 elif req.form['cmd'][0] == 'branches':
903 elif req.form['cmd'][0] == 'branches':
903 req.httphdr("application/mercurial-0.1")
904 req.httphdr("application/mercurial-0.1")
904 nodes = []
905 nodes = []
905 if req.form.has_key('nodes'):
906 if req.form.has_key('nodes'):
906 nodes = map(bin, req.form['nodes'][0].split(" "))
907 nodes = map(bin, req.form['nodes'][0].split(" "))
907 for b in self.repo.branches(nodes):
908 for b in self.repo.branches(nodes):
908 req.write(" ".join(map(hex, b)) + "\n")
909 req.write(" ".join(map(hex, b)) + "\n")
909
910
910 elif req.form['cmd'][0] == 'between':
911 elif req.form['cmd'][0] == 'between':
911 req.httphdr("application/mercurial-0.1")
912 req.httphdr("application/mercurial-0.1")
912 nodes = []
913 nodes = []
913 if req.form.has_key('pairs'):
914 if req.form.has_key('pairs'):
914 pairs = [map(bin, p.split("-"))
915 pairs = [map(bin, p.split("-"))
915 for p in req.form['pairs'][0].split(" ")]
916 for p in req.form['pairs'][0].split(" ")]
916 for b in self.repo.between(pairs):
917 for b in self.repo.between(pairs):
917 req.write(" ".join(map(hex, b)) + "\n")
918 req.write(" ".join(map(hex, b)) + "\n")
918
919
919 elif req.form['cmd'][0] == 'changegroup':
920 elif req.form['cmd'][0] == 'changegroup':
920 req.httphdr("application/mercurial-0.1")
921 req.httphdr("application/mercurial-0.1")
921 nodes = []
922 nodes = []
922 if not self.allowpull:
923 if not self.allowpull:
923 return
924 return
924
925
925 if req.form.has_key('roots'):
926 if req.form.has_key('roots'):
926 nodes = map(bin, req.form['roots'][0].split(" "))
927 nodes = map(bin, req.form['roots'][0].split(" "))
927
928
928 z = zlib.compressobj()
929 z = zlib.compressobj()
929 f = self.repo.changegroup(nodes)
930 f = self.repo.changegroup(nodes)
930 while 1:
931 while 1:
931 chunk = f.read(4096)
932 chunk = f.read(4096)
932 if not chunk:
933 if not chunk:
933 break
934 break
934 req.write(z.compress(chunk))
935 req.write(z.compress(chunk))
935
936
936 req.write(z.flush())
937 req.write(z.flush())
937
938
938 elif req.form['cmd'][0] == 'archive':
939 elif req.form['cmd'][0] == 'archive':
939 changeset = self.repo.lookup(req.form['node'][0])
940 changeset = self.repo.lookup(req.form['node'][0])
940 type = req.form['type'][0]
941 type = req.form['type'][0]
941 if (type in self.archives and
942 if (type in self.archives and
942 self.repo.ui.configbool("web", "allow" + type, False)):
943 self.repo.ui.configbool("web", "allow" + type, False)):
943 self.archive(req, changeset, type)
944 self.archive(req, changeset, type)
944 return
945 return
945
946
946 req.write(self.t("error"))
947 req.write(self.t("error"))
947
948
948 else:
949 else:
949 req.write(self.t("error"))
950 req.write(self.t("error"))
950
951
951 def create_server(repo):
952 def create_server(repo):
952
953
953 def openlog(opt, default):
954 def openlog(opt, default):
954 if opt and opt != '-':
955 if opt and opt != '-':
955 return open(opt, 'w')
956 return open(opt, 'w')
956 return default
957 return default
957
958
958 address = repo.ui.config("web", "address", "")
959 address = repo.ui.config("web", "address", "")
959 port = int(repo.ui.config("web", "port", 8000))
960 port = int(repo.ui.config("web", "port", 8000))
960 use_ipv6 = repo.ui.configbool("web", "ipv6")
961 use_ipv6 = repo.ui.configbool("web", "ipv6")
961 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
962 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
962 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
963 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
963
964
964 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
965 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
965 address_family = getattr(socket, 'AF_INET6', None)
966 address_family = getattr(socket, 'AF_INET6', None)
966
967
967 def __init__(self, *args, **kwargs):
968 def __init__(self, *args, **kwargs):
968 if self.address_family is None:
969 if self.address_family is None:
969 raise hg.RepoError(_('IPv6 not available on this system'))
970 raise hg.RepoError(_('IPv6 not available on this system'))
970 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
971 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
971
972
972 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
973 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
973 def log_error(self, format, *args):
974 def log_error(self, format, *args):
974 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
975 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
975 self.log_date_time_string(),
976 self.log_date_time_string(),
976 format % args))
977 format % args))
977
978
978 def log_message(self, format, *args):
979 def log_message(self, format, *args):
979 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
980 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
980 self.log_date_time_string(),
981 self.log_date_time_string(),
981 format % args))
982 format % args))
982
983
983 def do_POST(self):
984 def do_POST(self):
984 try:
985 try:
985 self.do_hgweb()
986 self.do_hgweb()
986 except socket.error, inst:
987 except socket.error, inst:
987 if inst[0] != errno.EPIPE:
988 if inst[0] != errno.EPIPE:
988 raise
989 raise
989
990
990 def do_GET(self):
991 def do_GET(self):
991 self.do_POST()
992 self.do_POST()
992
993
993 def do_hgweb(self):
994 def do_hgweb(self):
994 query = ""
995 query = ""
995 p = self.path.find("?")
996 p = self.path.find("?")
996 if p:
997 if p:
997 query = self.path[p + 1:]
998 query = self.path[p + 1:]
998 query = query.replace('+', ' ')
999 query = query.replace('+', ' ')
999
1000
1000 env = {}
1001 env = {}
1001 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1002 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1002 env['REQUEST_METHOD'] = self.command
1003 env['REQUEST_METHOD'] = self.command
1003 env['SERVER_NAME'] = self.server.server_name
1004 env['SERVER_NAME'] = self.server.server_name
1004 env['SERVER_PORT'] = str(self.server.server_port)
1005 env['SERVER_PORT'] = str(self.server.server_port)
1005 env['REQUEST_URI'] = "/"
1006 env['REQUEST_URI'] = "/"
1006 if query:
1007 if query:
1007 env['QUERY_STRING'] = query
1008 env['QUERY_STRING'] = query
1008 host = self.address_string()
1009 host = self.address_string()
1009 if host != self.client_address[0]:
1010 if host != self.client_address[0]:
1010 env['REMOTE_HOST'] = host
1011 env['REMOTE_HOST'] = host
1011 env['REMOTE_ADDR'] = self.client_address[0]
1012 env['REMOTE_ADDR'] = self.client_address[0]
1012
1013
1013 if self.headers.typeheader is None:
1014 if self.headers.typeheader is None:
1014 env['CONTENT_TYPE'] = self.headers.type
1015 env['CONTENT_TYPE'] = self.headers.type
1015 else:
1016 else:
1016 env['CONTENT_TYPE'] = self.headers.typeheader
1017 env['CONTENT_TYPE'] = self.headers.typeheader
1017 length = self.headers.getheader('content-length')
1018 length = self.headers.getheader('content-length')
1018 if length:
1019 if length:
1019 env['CONTENT_LENGTH'] = length
1020 env['CONTENT_LENGTH'] = length
1020 accept = []
1021 accept = []
1021 for line in self.headers.getallmatchingheaders('accept'):
1022 for line in self.headers.getallmatchingheaders('accept'):
1022 if line[:1] in "\t\n\r ":
1023 if line[:1] in "\t\n\r ":
1023 accept.append(line.strip())
1024 accept.append(line.strip())
1024 else:
1025 else:
1025 accept = accept + line[7:].split(',')
1026 accept = accept + line[7:].split(',')
1026 env['HTTP_ACCEPT'] = ','.join(accept)
1027 env['HTTP_ACCEPT'] = ','.join(accept)
1027
1028
1028 req = hgrequest(self.rfile, self.wfile, env)
1029 req = hgrequest(self.rfile, self.wfile, env)
1029 self.send_response(200, "Script output follows")
1030 self.send_response(200, "Script output follows")
1030 hg.run(req)
1031 hg.run(req)
1031
1032
1032 hg = hgweb(repo)
1033 hg = hgweb(repo)
1033 if use_ipv6:
1034 if use_ipv6:
1034 return IPv6HTTPServer((address, port), hgwebhandler)
1035 return IPv6HTTPServer((address, port), hgwebhandler)
1035 else:
1036 else:
1036 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1037 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1037
1038
1038 # This is a stopgap
1039 # This is a stopgap
1039 class hgwebdir(object):
1040 class hgwebdir(object):
1040 def __init__(self, config):
1041 def __init__(self, config):
1041 def cleannames(items):
1042 def cleannames(items):
1042 return [(name.strip('/'), path) for name, path in items]
1043 return [(name.strip('/'), path) for name, path in items]
1043
1044
1044 if type(config) == type([]):
1045 if type(config) == type([]):
1045 self.repos = cleannames(config)
1046 self.repos = cleannames(config)
1046 elif type(config) == type({}):
1047 elif type(config) == type({}):
1047 self.repos = cleannames(config.items())
1048 self.repos = cleannames(config.items())
1048 self.repos.sort()
1049 self.repos.sort()
1049 else:
1050 else:
1050 cp = ConfigParser.SafeConfigParser()
1051 cp = ConfigParser.SafeConfigParser()
1051 cp.read(config)
1052 cp.read(config)
1052 self.repos = cleannames(cp.items("paths"))
1053 self.repos = cleannames(cp.items("paths"))
1053 self.repos.sort()
1054 self.repos.sort()
1054
1055
1055 def run(self, req=hgrequest()):
1056 def run(self, req=hgrequest()):
1056 def header(**map):
1057 def header(**map):
1057 yield tmpl("header", **map)
1058 yield tmpl("header", **map)
1058
1059
1059 def footer(**map):
1060 def footer(**map):
1060 yield tmpl("footer", **map)
1061 yield tmpl("footer", **map)
1061
1062
1062 m = os.path.join(templatepath(), "map")
1063 m = os.path.join(templatepath(), "map")
1063 tmpl = templater(m, common_filters,
1064 tmpl = templater(m, common_filters,
1064 {"header": header, "footer": footer})
1065 {"header": header, "footer": footer})
1065
1066
1066 def entries(**map):
1067 def entries(**map):
1067 parity = 0
1068 parity = 0
1068 for name, path in self.repos:
1069 for name, path in self.repos:
1069 u = ui.ui()
1070 u = ui.ui()
1070 try:
1071 try:
1071 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1072 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1072 except IOError:
1073 except IOError:
1073 pass
1074 pass
1074 get = u.config
1075 get = u.config
1075
1076
1076 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1077 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1077 .replace("//", "/"))
1078 .replace("//", "/"))
1078
1079
1079 # update time with local timezone
1080 # update time with local timezone
1080 try:
1081 try:
1081 d = (get_mtime(path), util.makedate()[1])
1082 d = (get_mtime(path), util.makedate()[1])
1082 except OSError:
1083 except OSError:
1083 continue
1084 continue
1084
1085
1085 yield dict(contact=(get("ui", "username") or # preferred
1086 yield dict(contact=(get("ui", "username") or # preferred
1086 get("web", "contact") or # deprecated
1087 get("web", "contact") or # deprecated
1087 get("web", "author", "unknown")), # also
1088 get("web", "author", "unknown")), # also
1088 name=get("web", "name", name),
1089 name=get("web", "name", name),
1089 url=url,
1090 url=url,
1090 parity=parity,
1091 parity=parity,
1091 shortdesc=get("web", "description", "unknown"),
1092 shortdesc=get("web", "description", "unknown"),
1092 lastupdate=d)
1093 lastupdate=d)
1093
1094
1094 parity = 1 - parity
1095 parity = 1 - parity
1095
1096
1096 virtual = req.env.get("PATH_INFO", "").strip('/')
1097 virtual = req.env.get("PATH_INFO", "").strip('/')
1097 if virtual:
1098 if virtual:
1098 real = dict(self.repos).get(virtual)
1099 real = dict(self.repos).get(virtual)
1099 if real:
1100 if real:
1100 try:
1101 try:
1101 hgweb(real).run(req)
1102 hgweb(real).run(req)
1102 except IOError, inst:
1103 except IOError, inst:
1103 req.write(tmpl("error", error=inst.strerror))
1104 req.write(tmpl("error", error=inst.strerror))
1104 except hg.RepoError, inst:
1105 except hg.RepoError, inst:
1105 req.write(tmpl("error", error=str(inst)))
1106 req.write(tmpl("error", error=str(inst)))
1106 else:
1107 else:
1107 req.write(tmpl("notfound", repo=virtual))
1108 req.write(tmpl("notfound", repo=virtual))
1108 else:
1109 else:
1109 req.write(tmpl("index", entries=entries))
1110 req.write(tmpl("index", entries=entries))
@@ -1,1817 +1,1817
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
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 import struct, os, util
8 import struct, os, util
9 import filelog, manifest, changelog, dirstate, repo
9 import filelog, manifest, changelog, dirstate, repo
10 from node import *
10 from node import *
11 from i18n import gettext as _
11 from i18n import gettext as _
12 from demandload import *
12 from demandload import *
13 demandload(globals(), "re lock transaction tempfile stat mdiff errno")
13 demandload(globals(), "re lock transaction tempfile stat mdiff errno")
14
14
15 class localrepository(object):
15 class localrepository(object):
16 def __init__(self, ui, path=None, create=0):
16 def __init__(self, ui, path=None, create=0):
17 if not path:
17 if not path:
18 p = os.getcwd()
18 p = os.getcwd()
19 while not os.path.isdir(os.path.join(p, ".hg")):
19 while not os.path.isdir(os.path.join(p, ".hg")):
20 oldp = p
20 oldp = p
21 p = os.path.dirname(p)
21 p = os.path.dirname(p)
22 if p == oldp:
22 if p == oldp:
23 raise repo.RepoError(_("no repo found"))
23 raise repo.RepoError(_("no repo found"))
24 path = p
24 path = p
25 self.path = os.path.join(path, ".hg")
25 self.path = os.path.join(path, ".hg")
26
26
27 if not create and not os.path.isdir(self.path):
27 if not create and not os.path.isdir(self.path):
28 raise repo.RepoError(_("repository %s not found") % path)
28 raise repo.RepoError(_("repository %s not found") % path)
29
29
30 self.root = os.path.abspath(path)
30 self.root = os.path.abspath(path)
31 self.ui = ui
31 self.ui = ui
32 self.opener = util.opener(self.path)
32 self.opener = util.opener(self.path)
33 self.wopener = util.opener(self.root)
33 self.wopener = util.opener(self.root)
34 self.manifest = manifest.manifest(self.opener)
34 self.manifest = manifest.manifest(self.opener)
35 self.changelog = changelog.changelog(self.opener)
35 self.changelog = changelog.changelog(self.opener)
36 self.tagscache = None
36 self.tagscache = None
37 self.nodetagscache = None
37 self.nodetagscache = None
38 self.encodepats = None
38 self.encodepats = None
39 self.decodepats = None
39 self.decodepats = None
40
40
41 if create:
41 if create:
42 os.mkdir(self.path)
42 os.mkdir(self.path)
43 os.mkdir(self.join("data"))
43 os.mkdir(self.join("data"))
44
44
45 self.dirstate = dirstate.dirstate(self.opener, ui, self.root)
45 self.dirstate = dirstate.dirstate(self.opener, ui, self.root)
46 try:
46 try:
47 self.ui.readconfig(self.join("hgrc"))
47 self.ui.readconfig(self.join("hgrc"))
48 except IOError:
48 except IOError:
49 pass
49 pass
50
50
51 def hook(self, name, **args):
51 def hook(self, name, **args):
52 def runhook(name, cmd):
52 def runhook(name, cmd):
53 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
53 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
54 old = {}
54 old = {}
55 for k, v in args.items():
55 for k, v in args.items():
56 k = k.upper()
56 k = k.upper()
57 old[k] = os.environ.get(k, None)
57 old[k] = os.environ.get(k, None)
58 os.environ[k] = v
58 os.environ[k] = v
59
59
60 # Hooks run in the repository root
60 # Hooks run in the repository root
61 olddir = os.getcwd()
61 olddir = os.getcwd()
62 os.chdir(self.root)
62 os.chdir(self.root)
63 r = os.system(cmd)
63 r = os.system(cmd)
64 os.chdir(olddir)
64 os.chdir(olddir)
65
65
66 for k, v in old.items():
66 for k, v in old.items():
67 if v != None:
67 if v != None:
68 os.environ[k] = v
68 os.environ[k] = v
69 else:
69 else:
70 del os.environ[k]
70 del os.environ[k]
71
71
72 if r:
72 if r:
73 self.ui.warn(_("abort: %s hook failed with status %d!\n") %
73 self.ui.warn(_("abort: %s hook failed with status %d!\n") %
74 (name, r))
74 (name, r))
75 return False
75 return False
76 return True
76 return True
77
77
78 r = True
78 r = True
79 for hname, cmd in self.ui.configitems("hooks"):
79 for hname, cmd in self.ui.configitems("hooks"):
80 s = hname.split(".")
80 s = hname.split(".")
81 if s[0] == name and cmd:
81 if s[0] == name and cmd:
82 r = runhook(hname, cmd) and r
82 r = runhook(hname, cmd) and r
83 return r
83 return r
84
84
85 def tags(self):
85 def tags(self):
86 '''return a mapping of tag to node'''
86 '''return a mapping of tag to node'''
87 if not self.tagscache:
87 if not self.tagscache:
88 self.tagscache = {}
88 self.tagscache = {}
89 def addtag(self, k, n):
89 def addtag(self, k, n):
90 try:
90 try:
91 bin_n = bin(n)
91 bin_n = bin(n)
92 except TypeError:
92 except TypeError:
93 bin_n = ''
93 bin_n = ''
94 self.tagscache[k.strip()] = bin_n
94 self.tagscache[k.strip()] = bin_n
95
95
96 try:
96 try:
97 # read each head of the tags file, ending with the tip
97 # read each head of the tags file, ending with the tip
98 # and add each tag found to the map, with "newer" ones
98 # and add each tag found to the map, with "newer" ones
99 # taking precedence
99 # taking precedence
100 fl = self.file(".hgtags")
100 fl = self.file(".hgtags")
101 h = fl.heads()
101 h = fl.heads()
102 h.reverse()
102 h.reverse()
103 for r in h:
103 for r in h:
104 for l in fl.read(r).splitlines():
104 for l in fl.read(r).splitlines():
105 if l:
105 if l:
106 n, k = l.split(" ", 1)
106 n, k = l.split(" ", 1)
107 addtag(self, k, n)
107 addtag(self, k, n)
108 except KeyError:
108 except KeyError:
109 pass
109 pass
110
110
111 try:
111 try:
112 f = self.opener("localtags")
112 f = self.opener("localtags")
113 for l in f:
113 for l in f:
114 n, k = l.split(" ", 1)
114 n, k = l.split(" ", 1)
115 addtag(self, k, n)
115 addtag(self, k, n)
116 except IOError:
116 except IOError:
117 pass
117 pass
118
118
119 self.tagscache['tip'] = self.changelog.tip()
119 self.tagscache['tip'] = self.changelog.tip()
120
120
121 return self.tagscache
121 return self.tagscache
122
122
123 def tagslist(self):
123 def tagslist(self):
124 '''return a list of tags ordered by revision'''
124 '''return a list of tags ordered by revision'''
125 l = []
125 l = []
126 for t, n in self.tags().items():
126 for t, n in self.tags().items():
127 try:
127 try:
128 r = self.changelog.rev(n)
128 r = self.changelog.rev(n)
129 except:
129 except:
130 r = -2 # sort to the beginning of the list if unknown
130 r = -2 # sort to the beginning of the list if unknown
131 l.append((r, t, n))
131 l.append((r, t, n))
132 l.sort()
132 l.sort()
133 return [(t, n) for r, t, n in l]
133 return [(t, n) for r, t, n in l]
134
134
135 def nodetags(self, node):
135 def nodetags(self, node):
136 '''return the tags associated with a node'''
136 '''return the tags associated with a node'''
137 if not self.nodetagscache:
137 if not self.nodetagscache:
138 self.nodetagscache = {}
138 self.nodetagscache = {}
139 for t, n in self.tags().items():
139 for t, n in self.tags().items():
140 self.nodetagscache.setdefault(n, []).append(t)
140 self.nodetagscache.setdefault(n, []).append(t)
141 return self.nodetagscache.get(node, [])
141 return self.nodetagscache.get(node, [])
142
142
143 def lookup(self, key):
143 def lookup(self, key):
144 try:
144 try:
145 return self.tags()[key]
145 return self.tags()[key]
146 except KeyError:
146 except KeyError:
147 try:
147 try:
148 return self.changelog.lookup(key)
148 return self.changelog.lookup(key)
149 except:
149 except:
150 raise repo.RepoError(_("unknown revision '%s'") % key)
150 raise repo.RepoError(_("unknown revision '%s'") % key)
151
151
152 def dev(self):
152 def dev(self):
153 return os.stat(self.path).st_dev
153 return os.stat(self.path).st_dev
154
154
155 def local(self):
155 def local(self):
156 return True
156 return True
157
157
158 def join(self, f):
158 def join(self, f):
159 return os.path.join(self.path, f)
159 return os.path.join(self.path, f)
160
160
161 def wjoin(self, f):
161 def wjoin(self, f):
162 return os.path.join(self.root, f)
162 return os.path.join(self.root, f)
163
163
164 def file(self, f):
164 def file(self, f):
165 if f[0] == '/':
165 if f[0] == '/':
166 f = f[1:]
166 f = f[1:]
167 return filelog.filelog(self.opener, f)
167 return filelog.filelog(self.opener, f)
168
168
169 def getcwd(self):
169 def getcwd(self):
170 return self.dirstate.getcwd()
170 return self.dirstate.getcwd()
171
171
172 def wfile(self, f, mode='r'):
172 def wfile(self, f, mode='r'):
173 return self.wopener(f, mode)
173 return self.wopener(f, mode)
174
174
175 def wread(self, filename):
175 def wread(self, filename):
176 if self.encodepats == None:
176 if self.encodepats == None:
177 l = []
177 l = []
178 for pat, cmd in self.ui.configitems("encode"):
178 for pat, cmd in self.ui.configitems("encode"):
179 mf = util.matcher("", "/", [pat], [], [])[1]
179 mf = util.matcher("", "/", [pat], [], [])[1]
180 l.append((mf, cmd))
180 l.append((mf, cmd))
181 self.encodepats = l
181 self.encodepats = l
182
182
183 data = self.wopener(filename, 'r').read()
183 data = self.wopener(filename, 'r').read()
184
184
185 for mf, cmd in self.encodepats:
185 for mf, cmd in self.encodepats:
186 if mf(filename):
186 if mf(filename):
187 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
187 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
188 data = util.filter(data, cmd)
188 data = util.filter(data, cmd)
189 break
189 break
190
190
191 return data
191 return data
192
192
193 def wwrite(self, filename, data, fd=None):
193 def wwrite(self, filename, data, fd=None):
194 if self.decodepats == None:
194 if self.decodepats == None:
195 l = []
195 l = []
196 for pat, cmd in self.ui.configitems("decode"):
196 for pat, cmd in self.ui.configitems("decode"):
197 mf = util.matcher("", "/", [pat], [], [])[1]
197 mf = util.matcher("", "/", [pat], [], [])[1]
198 l.append((mf, cmd))
198 l.append((mf, cmd))
199 self.decodepats = l
199 self.decodepats = l
200
200
201 for mf, cmd in self.decodepats:
201 for mf, cmd in self.decodepats:
202 if mf(filename):
202 if mf(filename):
203 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
203 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
204 data = util.filter(data, cmd)
204 data = util.filter(data, cmd)
205 break
205 break
206
206
207 if fd:
207 if fd:
208 return fd.write(data)
208 return fd.write(data)
209 return self.wopener(filename, 'w').write(data)
209 return self.wopener(filename, 'w').write(data)
210
210
211 def transaction(self):
211 def transaction(self):
212 # save dirstate for undo
212 # save dirstate for undo
213 try:
213 try:
214 ds = self.opener("dirstate").read()
214 ds = self.opener("dirstate").read()
215 except IOError:
215 except IOError:
216 ds = ""
216 ds = ""
217 self.opener("journal.dirstate", "w").write(ds)
217 self.opener("journal.dirstate", "w").write(ds)
218
218
219 def after():
219 def after():
220 util.rename(self.join("journal"), self.join("undo"))
220 util.rename(self.join("journal"), self.join("undo"))
221 util.rename(self.join("journal.dirstate"),
221 util.rename(self.join("journal.dirstate"),
222 self.join("undo.dirstate"))
222 self.join("undo.dirstate"))
223
223
224 return transaction.transaction(self.ui.warn, self.opener,
224 return transaction.transaction(self.ui.warn, self.opener,
225 self.join("journal"), after)
225 self.join("journal"), after)
226
226
227 def recover(self):
227 def recover(self):
228 lock = self.lock()
228 lock = self.lock()
229 if os.path.exists(self.join("journal")):
229 if os.path.exists(self.join("journal")):
230 self.ui.status(_("rolling back interrupted transaction\n"))
230 self.ui.status(_("rolling back interrupted transaction\n"))
231 transaction.rollback(self.opener, self.join("journal"))
231 transaction.rollback(self.opener, self.join("journal"))
232 self.manifest = manifest.manifest(self.opener)
232 self.manifest = manifest.manifest(self.opener)
233 self.changelog = changelog.changelog(self.opener)
233 self.changelog = changelog.changelog(self.opener)
234 return True
234 return True
235 else:
235 else:
236 self.ui.warn(_("no interrupted transaction available\n"))
236 self.ui.warn(_("no interrupted transaction available\n"))
237 return False
237 return False
238
238
239 def undo(self):
239 def undo(self):
240 wlock = self.wlock()
240 wlock = self.wlock()
241 lock = self.lock()
241 lock = self.lock()
242 if os.path.exists(self.join("undo")):
242 if os.path.exists(self.join("undo")):
243 self.ui.status(_("rolling back last transaction\n"))
243 self.ui.status(_("rolling back last transaction\n"))
244 transaction.rollback(self.opener, self.join("undo"))
244 transaction.rollback(self.opener, self.join("undo"))
245 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
245 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
246 self.dirstate.read()
246 self.dirstate.read()
247 else:
247 else:
248 self.ui.warn(_("no undo information available\n"))
248 self.ui.warn(_("no undo information available\n"))
249
249
250 def lock(self, wait=1):
250 def lock(self, wait=1):
251 try:
251 try:
252 return lock.lock(self.join("lock"), 0)
252 return lock.lock(self.join("lock"), 0)
253 except lock.LockHeld, inst:
253 except lock.LockHeld, inst:
254 if wait:
254 if wait:
255 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
255 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
256 return lock.lock(self.join("lock"), wait)
256 return lock.lock(self.join("lock"), wait)
257 raise inst
257 raise inst
258
258
259 def wlock(self, wait=1):
259 def wlock(self, wait=1):
260 try:
260 try:
261 wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
261 wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
262 except lock.LockHeld, inst:
262 except lock.LockHeld, inst:
263 if not wait:
263 if not wait:
264 raise inst
264 raise inst
265 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
265 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
266 wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
266 wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
267 self.dirstate.read()
267 self.dirstate.read()
268 return wlock
268 return wlock
269
269
270 def rawcommit(self, files, text, user, date, p1=None, p2=None):
270 def rawcommit(self, files, text, user, date, p1=None, p2=None):
271 orig_parent = self.dirstate.parents()[0] or nullid
271 orig_parent = self.dirstate.parents()[0] or nullid
272 p1 = p1 or self.dirstate.parents()[0] or nullid
272 p1 = p1 or self.dirstate.parents()[0] or nullid
273 p2 = p2 or self.dirstate.parents()[1] or nullid
273 p2 = p2 or self.dirstate.parents()[1] or nullid
274 c1 = self.changelog.read(p1)
274 c1 = self.changelog.read(p1)
275 c2 = self.changelog.read(p2)
275 c2 = self.changelog.read(p2)
276 m1 = self.manifest.read(c1[0])
276 m1 = self.manifest.read(c1[0])
277 mf1 = self.manifest.readflags(c1[0])
277 mf1 = self.manifest.readflags(c1[0])
278 m2 = self.manifest.read(c2[0])
278 m2 = self.manifest.read(c2[0])
279 changed = []
279 changed = []
280
280
281 if orig_parent == p1:
281 if orig_parent == p1:
282 update_dirstate = 1
282 update_dirstate = 1
283 else:
283 else:
284 update_dirstate = 0
284 update_dirstate = 0
285
285
286 wlock = self.wlock()
286 wlock = self.wlock()
287 lock = self.lock()
287 lock = self.lock()
288 tr = self.transaction()
288 tr = self.transaction()
289 mm = m1.copy()
289 mm = m1.copy()
290 mfm = mf1.copy()
290 mfm = mf1.copy()
291 linkrev = self.changelog.count()
291 linkrev = self.changelog.count()
292 for f in files:
292 for f in files:
293 try:
293 try:
294 t = self.wread(f)
294 t = self.wread(f)
295 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
295 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
296 r = self.file(f)
296 r = self.file(f)
297 mfm[f] = tm
297 mfm[f] = tm
298
298
299 fp1 = m1.get(f, nullid)
299 fp1 = m1.get(f, nullid)
300 fp2 = m2.get(f, nullid)
300 fp2 = m2.get(f, nullid)
301
301
302 # is the same revision on two branches of a merge?
302 # is the same revision on two branches of a merge?
303 if fp2 == fp1:
303 if fp2 == fp1:
304 fp2 = nullid
304 fp2 = nullid
305
305
306 if fp2 != nullid:
306 if fp2 != nullid:
307 # is one parent an ancestor of the other?
307 # is one parent an ancestor of the other?
308 fpa = r.ancestor(fp1, fp2)
308 fpa = r.ancestor(fp1, fp2)
309 if fpa == fp1:
309 if fpa == fp1:
310 fp1, fp2 = fp2, nullid
310 fp1, fp2 = fp2, nullid
311 elif fpa == fp2:
311 elif fpa == fp2:
312 fp2 = nullid
312 fp2 = nullid
313
313
314 # is the file unmodified from the parent?
314 # is the file unmodified from the parent?
315 if t == r.read(fp1):
315 if t == r.read(fp1):
316 # record the proper existing parent in manifest
316 # record the proper existing parent in manifest
317 # no need to add a revision
317 # no need to add a revision
318 mm[f] = fp1
318 mm[f] = fp1
319 continue
319 continue
320
320
321 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
321 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
322 changed.append(f)
322 changed.append(f)
323 if update_dirstate:
323 if update_dirstate:
324 self.dirstate.update([f], "n")
324 self.dirstate.update([f], "n")
325 except IOError:
325 except IOError:
326 try:
326 try:
327 del mm[f]
327 del mm[f]
328 del mfm[f]
328 del mfm[f]
329 if update_dirstate:
329 if update_dirstate:
330 self.dirstate.forget([f])
330 self.dirstate.forget([f])
331 except:
331 except:
332 # deleted from p2?
332 # deleted from p2?
333 pass
333 pass
334
334
335 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
335 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
336 user = user or self.ui.username()
336 user = user or self.ui.username()
337 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
337 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
338 tr.close()
338 tr.close()
339 if update_dirstate:
339 if update_dirstate:
340 self.dirstate.setparents(n, nullid)
340 self.dirstate.setparents(n, nullid)
341
341
342 def commit(self, files=None, text="", user=None, date=None,
342 def commit(self, files=None, text="", user=None, date=None,
343 match=util.always, force=False):
343 match=util.always, force=False):
344 commit = []
344 commit = []
345 remove = []
345 remove = []
346 changed = []
346 changed = []
347
347
348 if files:
348 if files:
349 for f in files:
349 for f in files:
350 s = self.dirstate.state(f)
350 s = self.dirstate.state(f)
351 if s in 'nmai':
351 if s in 'nmai':
352 commit.append(f)
352 commit.append(f)
353 elif s == 'r':
353 elif s == 'r':
354 remove.append(f)
354 remove.append(f)
355 else:
355 else:
356 self.ui.warn(_("%s not tracked!\n") % f)
356 self.ui.warn(_("%s not tracked!\n") % f)
357 else:
357 else:
358 (c, a, d, u) = self.changes(match=match)
358 modified, added, removed, unknown = self.changes(match=match)
359 commit = c + a
359 commit = modified + added
360 remove = d
360 remove = removed
361
361
362 p1, p2 = self.dirstate.parents()
362 p1, p2 = self.dirstate.parents()
363 c1 = self.changelog.read(p1)
363 c1 = self.changelog.read(p1)
364 c2 = self.changelog.read(p2)
364 c2 = self.changelog.read(p2)
365 m1 = self.manifest.read(c1[0])
365 m1 = self.manifest.read(c1[0])
366 mf1 = self.manifest.readflags(c1[0])
366 mf1 = self.manifest.readflags(c1[0])
367 m2 = self.manifest.read(c2[0])
367 m2 = self.manifest.read(c2[0])
368
368
369 if not commit and not remove and not force and p2 == nullid:
369 if not commit and not remove and not force and p2 == nullid:
370 self.ui.status(_("nothing changed\n"))
370 self.ui.status(_("nothing changed\n"))
371 return None
371 return None
372
372
373 if not self.hook("precommit"):
373 if not self.hook("precommit"):
374 return None
374 return None
375
375
376 wlock = self.wlock()
376 wlock = self.wlock()
377 lock = self.lock()
377 lock = self.lock()
378 tr = self.transaction()
378 tr = self.transaction()
379
379
380 # check in files
380 # check in files
381 new = {}
381 new = {}
382 linkrev = self.changelog.count()
382 linkrev = self.changelog.count()
383 commit.sort()
383 commit.sort()
384 for f in commit:
384 for f in commit:
385 self.ui.note(f + "\n")
385 self.ui.note(f + "\n")
386 try:
386 try:
387 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
387 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
388 t = self.wread(f)
388 t = self.wread(f)
389 except IOError:
389 except IOError:
390 self.ui.warn(_("trouble committing %s!\n") % f)
390 self.ui.warn(_("trouble committing %s!\n") % f)
391 raise
391 raise
392
392
393 r = self.file(f)
393 r = self.file(f)
394
394
395 meta = {}
395 meta = {}
396 cp = self.dirstate.copied(f)
396 cp = self.dirstate.copied(f)
397 if cp:
397 if cp:
398 meta["copy"] = cp
398 meta["copy"] = cp
399 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
399 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
400 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
400 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
401 fp1, fp2 = nullid, nullid
401 fp1, fp2 = nullid, nullid
402 else:
402 else:
403 fp1 = m1.get(f, nullid)
403 fp1 = m1.get(f, nullid)
404 fp2 = m2.get(f, nullid)
404 fp2 = m2.get(f, nullid)
405
405
406 if fp2 != nullid:
406 if fp2 != nullid:
407 # is one parent an ancestor of the other?
407 # is one parent an ancestor of the other?
408 fpa = r.ancestor(fp1, fp2)
408 fpa = r.ancestor(fp1, fp2)
409 if fpa == fp1:
409 if fpa == fp1:
410 fp1, fp2 = fp2, nullid
410 fp1, fp2 = fp2, nullid
411 elif fpa == fp2:
411 elif fpa == fp2:
412 fp2 = nullid
412 fp2 = nullid
413
413
414 # is the file unmodified from the parent?
414 # is the file unmodified from the parent?
415 if not meta and t == r.read(fp1) and fp2 == nullid:
415 if not meta and t == r.read(fp1) and fp2 == nullid:
416 # record the proper existing parent in manifest
416 # record the proper existing parent in manifest
417 # no need to add a revision
417 # no need to add a revision
418 new[f] = fp1
418 new[f] = fp1
419 continue
419 continue
420
420
421 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
421 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
422 # remember what we've added so that we can later calculate
422 # remember what we've added so that we can later calculate
423 # the files to pull from a set of changesets
423 # the files to pull from a set of changesets
424 changed.append(f)
424 changed.append(f)
425
425
426 # update manifest
426 # update manifest
427 m1.update(new)
427 m1.update(new)
428 for f in remove:
428 for f in remove:
429 if f in m1:
429 if f in m1:
430 del m1[f]
430 del m1[f]
431 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
431 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
432 (new, remove))
432 (new, remove))
433
433
434 # add changeset
434 # add changeset
435 new = new.keys()
435 new = new.keys()
436 new.sort()
436 new.sort()
437
437
438 if not text:
438 if not text:
439 edittext = ""
439 edittext = ""
440 if p2 != nullid:
440 if p2 != nullid:
441 edittext += "HG: branch merge\n"
441 edittext += "HG: branch merge\n"
442 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
442 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
443 edittext += "".join(["HG: changed %s\n" % f for f in changed])
443 edittext += "".join(["HG: changed %s\n" % f for f in changed])
444 edittext += "".join(["HG: removed %s\n" % f for f in remove])
444 edittext += "".join(["HG: removed %s\n" % f for f in remove])
445 if not changed and not remove:
445 if not changed and not remove:
446 edittext += "HG: no files changed\n"
446 edittext += "HG: no files changed\n"
447 edittext = self.ui.edit(edittext)
447 edittext = self.ui.edit(edittext)
448 if not edittext.rstrip():
448 if not edittext.rstrip():
449 return None
449 return None
450 text = edittext
450 text = edittext
451
451
452 user = user or self.ui.username()
452 user = user or self.ui.username()
453 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
453 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
454 tr.close()
454 tr.close()
455
455
456 self.dirstate.setparents(n)
456 self.dirstate.setparents(n)
457 self.dirstate.update(new, "n")
457 self.dirstate.update(new, "n")
458 self.dirstate.forget(remove)
458 self.dirstate.forget(remove)
459
459
460 if not self.hook("commit", node=hex(n)):
460 if not self.hook("commit", node=hex(n)):
461 return None
461 return None
462 return n
462 return n
463
463
464 def walk(self, node=None, files=[], match=util.always):
464 def walk(self, node=None, files=[], match=util.always):
465 if node:
465 if node:
466 fdict = dict.fromkeys(files)
466 fdict = dict.fromkeys(files)
467 for fn in self.manifest.read(self.changelog.read(node)[0]):
467 for fn in self.manifest.read(self.changelog.read(node)[0]):
468 fdict.pop(fn, None)
468 fdict.pop(fn, None)
469 if match(fn):
469 if match(fn):
470 yield 'm', fn
470 yield 'm', fn
471 for fn in fdict:
471 for fn in fdict:
472 self.ui.warn(_('%s: No such file in rev %s\n') % (
472 self.ui.warn(_('%s: No such file in rev %s\n') % (
473 util.pathto(self.getcwd(), fn), short(node)))
473 util.pathto(self.getcwd(), fn), short(node)))
474 else:
474 else:
475 for src, fn in self.dirstate.walk(files, match):
475 for src, fn in self.dirstate.walk(files, match):
476 yield src, fn
476 yield src, fn
477
477
478 def changes(self, node1=None, node2=None, files=[], match=util.always):
478 def changes(self, node1=None, node2=None, files=[], match=util.always):
479 """return changes between two nodes or node and working directory
479 """return changes between two nodes or node and working directory
480
480
481 If node1 is None, use the first dirstate parent instead.
481 If node1 is None, use the first dirstate parent instead.
482 If node2 is None, compare node1 with working directory.
482 If node2 is None, compare node1 with working directory.
483 """
483 """
484
484
485 def fcmp(fn, mf):
485 def fcmp(fn, mf):
486 t1 = self.wread(fn)
486 t1 = self.wread(fn)
487 t2 = self.file(fn).read(mf.get(fn, nullid))
487 t2 = self.file(fn).read(mf.get(fn, nullid))
488 return cmp(t1, t2)
488 return cmp(t1, t2)
489
489
490 def mfmatches(node):
490 def mfmatches(node):
491 change = self.changelog.read(node)
491 change = self.changelog.read(node)
492 mf = dict(self.manifest.read(change[0]))
492 mf = dict(self.manifest.read(change[0]))
493 for fn in mf.keys():
493 for fn in mf.keys():
494 if not match(fn):
494 if not match(fn):
495 del mf[fn]
495 del mf[fn]
496 return mf
496 return mf
497
497
498 # are we comparing the working directory?
498 # are we comparing the working directory?
499 if not node2:
499 if not node2:
500 try:
500 try:
501 wlock = self.wlock(wait=0)
501 wlock = self.wlock(wait=0)
502 except lock.LockHeld:
502 except lock.LockHeld:
503 wlock = None
503 wlock = None
504 lookup, modified, added, removed, deleted, unknown = (
504 lookup, modified, added, removed, deleted, unknown = (
505 self.dirstate.changes(files, match))
505 self.dirstate.changes(files, match))
506
506
507 # are we comparing working dir against its parent?
507 # are we comparing working dir against its parent?
508 if not node1:
508 if not node1:
509 if lookup:
509 if lookup:
510 # do a full compare of any files that might have changed
510 # do a full compare of any files that might have changed
511 mf2 = mfmatches(self.dirstate.parents()[0])
511 mf2 = mfmatches(self.dirstate.parents()[0])
512 for f in lookup:
512 for f in lookup:
513 if fcmp(f, mf2):
513 if fcmp(f, mf2):
514 modified.append(f)
514 modified.append(f)
515 elif wlock is not None:
515 elif wlock is not None:
516 self.dirstate.update([f], "n")
516 self.dirstate.update([f], "n")
517 else:
517 else:
518 # we are comparing working dir against non-parent
518 # we are comparing working dir against non-parent
519 # generate a pseudo-manifest for the working dir
519 # generate a pseudo-manifest for the working dir
520 mf2 = mfmatches(self.dirstate.parents()[0])
520 mf2 = mfmatches(self.dirstate.parents()[0])
521 for f in lookup + modified + added:
521 for f in lookup + modified + added:
522 mf2[f] = ""
522 mf2[f] = ""
523 for f in removed:
523 for f in removed:
524 if f in mf2:
524 if f in mf2:
525 del mf2[f]
525 del mf2[f]
526 else:
526 else:
527 # we are comparing two revisions
527 # we are comparing two revisions
528 deleted, unknown = [], []
528 deleted, unknown = [], []
529 mf2 = mfmatches(node2)
529 mf2 = mfmatches(node2)
530
530
531 if node1:
531 if node1:
532 # flush lists from dirstate before comparing manifests
532 # flush lists from dirstate before comparing manifests
533 modified, added = [], []
533 modified, added = [], []
534
534
535 mf1 = mfmatches(node1)
535 mf1 = mfmatches(node1)
536
536
537 for fn in mf2:
537 for fn in mf2:
538 if mf1.has_key(fn):
538 if mf1.has_key(fn):
539 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
539 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
540 modified.append(fn)
540 modified.append(fn)
541 del mf1[fn]
541 del mf1[fn]
542 else:
542 else:
543 added.append(fn)
543 added.append(fn)
544
544
545 removed = mf1.keys()
545 removed = mf1.keys()
546
546
547 removed.extend(deleted) #XXX get rid of this when returning deleted
547 removed.extend(deleted) #XXX get rid of this when returning deleted
548
548
549 # sort and return results:
549 # sort and return results:
550 for l in modified, added, removed, unknown:
550 for l in modified, added, removed, unknown:
551 l.sort()
551 l.sort()
552 return (modified, added, removed, unknown)
552 return (modified, added, removed, unknown)
553
553
554 def add(self, list):
554 def add(self, list):
555 wlock = self.wlock()
555 wlock = self.wlock()
556 for f in list:
556 for f in list:
557 p = self.wjoin(f)
557 p = self.wjoin(f)
558 if not os.path.exists(p):
558 if not os.path.exists(p):
559 self.ui.warn(_("%s does not exist!\n") % f)
559 self.ui.warn(_("%s does not exist!\n") % f)
560 elif not os.path.isfile(p):
560 elif not os.path.isfile(p):
561 self.ui.warn(_("%s not added: only files supported currently\n")
561 self.ui.warn(_("%s not added: only files supported currently\n")
562 % f)
562 % f)
563 elif self.dirstate.state(f) in 'an':
563 elif self.dirstate.state(f) in 'an':
564 self.ui.warn(_("%s already tracked!\n") % f)
564 self.ui.warn(_("%s already tracked!\n") % f)
565 else:
565 else:
566 self.dirstate.update([f], "a")
566 self.dirstate.update([f], "a")
567
567
568 def forget(self, list):
568 def forget(self, list):
569 wlock = self.wlock()
569 wlock = self.wlock()
570 for f in list:
570 for f in list:
571 if self.dirstate.state(f) not in 'ai':
571 if self.dirstate.state(f) not in 'ai':
572 self.ui.warn(_("%s not added!\n") % f)
572 self.ui.warn(_("%s not added!\n") % f)
573 else:
573 else:
574 self.dirstate.forget([f])
574 self.dirstate.forget([f])
575
575
576 def remove(self, list, unlink=False):
576 def remove(self, list, unlink=False):
577 if unlink:
577 if unlink:
578 for f in list:
578 for f in list:
579 try:
579 try:
580 util.unlink(self.wjoin(f))
580 util.unlink(self.wjoin(f))
581 except OSError, inst:
581 except OSError, inst:
582 if inst.errno != errno.ENOENT:
582 if inst.errno != errno.ENOENT:
583 raise
583 raise
584 wlock = self.wlock()
584 wlock = self.wlock()
585 for f in list:
585 for f in list:
586 p = self.wjoin(f)
586 p = self.wjoin(f)
587 if os.path.exists(p):
587 if os.path.exists(p):
588 self.ui.warn(_("%s still exists!\n") % f)
588 self.ui.warn(_("%s still exists!\n") % f)
589 elif self.dirstate.state(f) == 'a':
589 elif self.dirstate.state(f) == 'a':
590 self.ui.warn(_("%s never committed!\n") % f)
590 self.ui.warn(_("%s never committed!\n") % f)
591 self.dirstate.forget([f])
591 self.dirstate.forget([f])
592 elif f not in self.dirstate:
592 elif f not in self.dirstate:
593 self.ui.warn(_("%s not tracked!\n") % f)
593 self.ui.warn(_("%s not tracked!\n") % f)
594 else:
594 else:
595 self.dirstate.update([f], "r")
595 self.dirstate.update([f], "r")
596
596
597 def undelete(self, list):
597 def undelete(self, list):
598 p = self.dirstate.parents()[0]
598 p = self.dirstate.parents()[0]
599 mn = self.changelog.read(p)[0]
599 mn = self.changelog.read(p)[0]
600 mf = self.manifest.readflags(mn)
600 mf = self.manifest.readflags(mn)
601 m = self.manifest.read(mn)
601 m = self.manifest.read(mn)
602 wlock = self.wlock()
602 wlock = self.wlock()
603 for f in list:
603 for f in list:
604 if self.dirstate.state(f) not in "r":
604 if self.dirstate.state(f) not in "r":
605 self.ui.warn("%s not removed!\n" % f)
605 self.ui.warn("%s not removed!\n" % f)
606 else:
606 else:
607 t = self.file(f).read(m[f])
607 t = self.file(f).read(m[f])
608 self.wwrite(f, t)
608 self.wwrite(f, t)
609 util.set_exec(self.wjoin(f), mf[f])
609 util.set_exec(self.wjoin(f), mf[f])
610 self.dirstate.update([f], "n")
610 self.dirstate.update([f], "n")
611
611
612 def copy(self, source, dest):
612 def copy(self, source, dest):
613 p = self.wjoin(dest)
613 p = self.wjoin(dest)
614 if not os.path.exists(p):
614 if not os.path.exists(p):
615 self.ui.warn(_("%s does not exist!\n") % dest)
615 self.ui.warn(_("%s does not exist!\n") % dest)
616 elif not os.path.isfile(p):
616 elif not os.path.isfile(p):
617 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
617 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
618 else:
618 else:
619 wlock = self.wlock()
619 wlock = self.wlock()
620 if self.dirstate.state(dest) == '?':
620 if self.dirstate.state(dest) == '?':
621 self.dirstate.update([dest], "a")
621 self.dirstate.update([dest], "a")
622 self.dirstate.copy(source, dest)
622 self.dirstate.copy(source, dest)
623
623
624 def heads(self, start=None):
624 def heads(self, start=None):
625 heads = self.changelog.heads(start)
625 heads = self.changelog.heads(start)
626 # sort the output in rev descending order
626 # sort the output in rev descending order
627 heads = [(-self.changelog.rev(h), h) for h in heads]
627 heads = [(-self.changelog.rev(h), h) for h in heads]
628 heads.sort()
628 heads.sort()
629 return [n for (r, n) in heads]
629 return [n for (r, n) in heads]
630
630
631 # branchlookup returns a dict giving a list of branches for
631 # branchlookup returns a dict giving a list of branches for
632 # each head. A branch is defined as the tag of a node or
632 # each head. A branch is defined as the tag of a node or
633 # the branch of the node's parents. If a node has multiple
633 # the branch of the node's parents. If a node has multiple
634 # branch tags, tags are eliminated if they are visible from other
634 # branch tags, tags are eliminated if they are visible from other
635 # branch tags.
635 # branch tags.
636 #
636 #
637 # So, for this graph: a->b->c->d->e
637 # So, for this graph: a->b->c->d->e
638 # \ /
638 # \ /
639 # aa -----/
639 # aa -----/
640 # a has tag 2.6.12
640 # a has tag 2.6.12
641 # d has tag 2.6.13
641 # d has tag 2.6.13
642 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
642 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
643 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
643 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
644 # from the list.
644 # from the list.
645 #
645 #
646 # It is possible that more than one head will have the same branch tag.
646 # It is possible that more than one head will have the same branch tag.
647 # callers need to check the result for multiple heads under the same
647 # callers need to check the result for multiple heads under the same
648 # branch tag if that is a problem for them (ie checkout of a specific
648 # branch tag if that is a problem for them (ie checkout of a specific
649 # branch).
649 # branch).
650 #
650 #
651 # passing in a specific branch will limit the depth of the search
651 # passing in a specific branch will limit the depth of the search
652 # through the parents. It won't limit the branches returned in the
652 # through the parents. It won't limit the branches returned in the
653 # result though.
653 # result though.
654 def branchlookup(self, heads=None, branch=None):
654 def branchlookup(self, heads=None, branch=None):
655 if not heads:
655 if not heads:
656 heads = self.heads()
656 heads = self.heads()
657 headt = [ h for h in heads ]
657 headt = [ h for h in heads ]
658 chlog = self.changelog
658 chlog = self.changelog
659 branches = {}
659 branches = {}
660 merges = []
660 merges = []
661 seenmerge = {}
661 seenmerge = {}
662
662
663 # traverse the tree once for each head, recording in the branches
663 # traverse the tree once for each head, recording in the branches
664 # dict which tags are visible from this head. The branches
664 # dict which tags are visible from this head. The branches
665 # dict also records which tags are visible from each tag
665 # dict also records which tags are visible from each tag
666 # while we traverse.
666 # while we traverse.
667 while headt or merges:
667 while headt or merges:
668 if merges:
668 if merges:
669 n, found = merges.pop()
669 n, found = merges.pop()
670 visit = [n]
670 visit = [n]
671 else:
671 else:
672 h = headt.pop()
672 h = headt.pop()
673 visit = [h]
673 visit = [h]
674 found = [h]
674 found = [h]
675 seen = {}
675 seen = {}
676 while visit:
676 while visit:
677 n = visit.pop()
677 n = visit.pop()
678 if n in seen:
678 if n in seen:
679 continue
679 continue
680 pp = chlog.parents(n)
680 pp = chlog.parents(n)
681 tags = self.nodetags(n)
681 tags = self.nodetags(n)
682 if tags:
682 if tags:
683 for x in tags:
683 for x in tags:
684 if x == 'tip':
684 if x == 'tip':
685 continue
685 continue
686 for f in found:
686 for f in found:
687 branches.setdefault(f, {})[n] = 1
687 branches.setdefault(f, {})[n] = 1
688 branches.setdefault(n, {})[n] = 1
688 branches.setdefault(n, {})[n] = 1
689 break
689 break
690 if n not in found:
690 if n not in found:
691 found.append(n)
691 found.append(n)
692 if branch in tags:
692 if branch in tags:
693 continue
693 continue
694 seen[n] = 1
694 seen[n] = 1
695 if pp[1] != nullid and n not in seenmerge:
695 if pp[1] != nullid and n not in seenmerge:
696 merges.append((pp[1], [x for x in found]))
696 merges.append((pp[1], [x for x in found]))
697 seenmerge[n] = 1
697 seenmerge[n] = 1
698 if pp[0] != nullid:
698 if pp[0] != nullid:
699 visit.append(pp[0])
699 visit.append(pp[0])
700 # traverse the branches dict, eliminating branch tags from each
700 # traverse the branches dict, eliminating branch tags from each
701 # head that are visible from another branch tag for that head.
701 # head that are visible from another branch tag for that head.
702 out = {}
702 out = {}
703 viscache = {}
703 viscache = {}
704 for h in heads:
704 for h in heads:
705 def visible(node):
705 def visible(node):
706 if node in viscache:
706 if node in viscache:
707 return viscache[node]
707 return viscache[node]
708 ret = {}
708 ret = {}
709 visit = [node]
709 visit = [node]
710 while visit:
710 while visit:
711 x = visit.pop()
711 x = visit.pop()
712 if x in viscache:
712 if x in viscache:
713 ret.update(viscache[x])
713 ret.update(viscache[x])
714 elif x not in ret:
714 elif x not in ret:
715 ret[x] = 1
715 ret[x] = 1
716 if x in branches:
716 if x in branches:
717 visit[len(visit):] = branches[x].keys()
717 visit[len(visit):] = branches[x].keys()
718 viscache[node] = ret
718 viscache[node] = ret
719 return ret
719 return ret
720 if h not in branches:
720 if h not in branches:
721 continue
721 continue
722 # O(n^2), but somewhat limited. This only searches the
722 # O(n^2), but somewhat limited. This only searches the
723 # tags visible from a specific head, not all the tags in the
723 # tags visible from a specific head, not all the tags in the
724 # whole repo.
724 # whole repo.
725 for b in branches[h]:
725 for b in branches[h]:
726 vis = False
726 vis = False
727 for bb in branches[h].keys():
727 for bb in branches[h].keys():
728 if b != bb:
728 if b != bb:
729 if b in visible(bb):
729 if b in visible(bb):
730 vis = True
730 vis = True
731 break
731 break
732 if not vis:
732 if not vis:
733 l = out.setdefault(h, [])
733 l = out.setdefault(h, [])
734 l[len(l):] = self.nodetags(b)
734 l[len(l):] = self.nodetags(b)
735 return out
735 return out
736
736
737 def branches(self, nodes):
737 def branches(self, nodes):
738 if not nodes:
738 if not nodes:
739 nodes = [self.changelog.tip()]
739 nodes = [self.changelog.tip()]
740 b = []
740 b = []
741 for n in nodes:
741 for n in nodes:
742 t = n
742 t = n
743 while n:
743 while n:
744 p = self.changelog.parents(n)
744 p = self.changelog.parents(n)
745 if p[1] != nullid or p[0] == nullid:
745 if p[1] != nullid or p[0] == nullid:
746 b.append((t, n, p[0], p[1]))
746 b.append((t, n, p[0], p[1]))
747 break
747 break
748 n = p[0]
748 n = p[0]
749 return b
749 return b
750
750
751 def between(self, pairs):
751 def between(self, pairs):
752 r = []
752 r = []
753
753
754 for top, bottom in pairs:
754 for top, bottom in pairs:
755 n, l, i = top, [], 0
755 n, l, i = top, [], 0
756 f = 1
756 f = 1
757
757
758 while n != bottom:
758 while n != bottom:
759 p = self.changelog.parents(n)[0]
759 p = self.changelog.parents(n)[0]
760 if i == f:
760 if i == f:
761 l.append(n)
761 l.append(n)
762 f = f * 2
762 f = f * 2
763 n = p
763 n = p
764 i += 1
764 i += 1
765
765
766 r.append(l)
766 r.append(l)
767
767
768 return r
768 return r
769
769
770 def findincoming(self, remote, base=None, heads=None):
770 def findincoming(self, remote, base=None, heads=None):
771 m = self.changelog.nodemap
771 m = self.changelog.nodemap
772 search = []
772 search = []
773 fetch = {}
773 fetch = {}
774 seen = {}
774 seen = {}
775 seenbranch = {}
775 seenbranch = {}
776 if base == None:
776 if base == None:
777 base = {}
777 base = {}
778
778
779 # assume we're closer to the tip than the root
779 # assume we're closer to the tip than the root
780 # and start by examining the heads
780 # and start by examining the heads
781 self.ui.status(_("searching for changes\n"))
781 self.ui.status(_("searching for changes\n"))
782
782
783 if not heads:
783 if not heads:
784 heads = remote.heads()
784 heads = remote.heads()
785
785
786 unknown = []
786 unknown = []
787 for h in heads:
787 for h in heads:
788 if h not in m:
788 if h not in m:
789 unknown.append(h)
789 unknown.append(h)
790 else:
790 else:
791 base[h] = 1
791 base[h] = 1
792
792
793 if not unknown:
793 if not unknown:
794 return None
794 return None
795
795
796 rep = {}
796 rep = {}
797 reqcnt = 0
797 reqcnt = 0
798
798
799 # search through remote branches
799 # search through remote branches
800 # a 'branch' here is a linear segment of history, with four parts:
800 # a 'branch' here is a linear segment of history, with four parts:
801 # head, root, first parent, second parent
801 # head, root, first parent, second parent
802 # (a branch always has two parents (or none) by definition)
802 # (a branch always has two parents (or none) by definition)
803 unknown = remote.branches(unknown)
803 unknown = remote.branches(unknown)
804 while unknown:
804 while unknown:
805 r = []
805 r = []
806 while unknown:
806 while unknown:
807 n = unknown.pop(0)
807 n = unknown.pop(0)
808 if n[0] in seen:
808 if n[0] in seen:
809 continue
809 continue
810
810
811 self.ui.debug(_("examining %s:%s\n")
811 self.ui.debug(_("examining %s:%s\n")
812 % (short(n[0]), short(n[1])))
812 % (short(n[0]), short(n[1])))
813 if n[0] == nullid:
813 if n[0] == nullid:
814 break
814 break
815 if n in seenbranch:
815 if n in seenbranch:
816 self.ui.debug(_("branch already found\n"))
816 self.ui.debug(_("branch already found\n"))
817 continue
817 continue
818 if n[1] and n[1] in m: # do we know the base?
818 if n[1] and n[1] in m: # do we know the base?
819 self.ui.debug(_("found incomplete branch %s:%s\n")
819 self.ui.debug(_("found incomplete branch %s:%s\n")
820 % (short(n[0]), short(n[1])))
820 % (short(n[0]), short(n[1])))
821 search.append(n) # schedule branch range for scanning
821 search.append(n) # schedule branch range for scanning
822 seenbranch[n] = 1
822 seenbranch[n] = 1
823 else:
823 else:
824 if n[1] not in seen and n[1] not in fetch:
824 if n[1] not in seen and n[1] not in fetch:
825 if n[2] in m and n[3] in m:
825 if n[2] in m and n[3] in m:
826 self.ui.debug(_("found new changeset %s\n") %
826 self.ui.debug(_("found new changeset %s\n") %
827 short(n[1]))
827 short(n[1]))
828 fetch[n[1]] = 1 # earliest unknown
828 fetch[n[1]] = 1 # earliest unknown
829 base[n[2]] = 1 # latest known
829 base[n[2]] = 1 # latest known
830 continue
830 continue
831
831
832 for a in n[2:4]:
832 for a in n[2:4]:
833 if a not in rep:
833 if a not in rep:
834 r.append(a)
834 r.append(a)
835 rep[a] = 1
835 rep[a] = 1
836
836
837 seen[n[0]] = 1
837 seen[n[0]] = 1
838
838
839 if r:
839 if r:
840 reqcnt += 1
840 reqcnt += 1
841 self.ui.debug(_("request %d: %s\n") %
841 self.ui.debug(_("request %d: %s\n") %
842 (reqcnt, " ".join(map(short, r))))
842 (reqcnt, " ".join(map(short, r))))
843 for p in range(0, len(r), 10):
843 for p in range(0, len(r), 10):
844 for b in remote.branches(r[p:p+10]):
844 for b in remote.branches(r[p:p+10]):
845 self.ui.debug(_("received %s:%s\n") %
845 self.ui.debug(_("received %s:%s\n") %
846 (short(b[0]), short(b[1])))
846 (short(b[0]), short(b[1])))
847 if b[0] in m:
847 if b[0] in m:
848 self.ui.debug(_("found base node %s\n")
848 self.ui.debug(_("found base node %s\n")
849 % short(b[0]))
849 % short(b[0]))
850 base[b[0]] = 1
850 base[b[0]] = 1
851 elif b[0] not in seen:
851 elif b[0] not in seen:
852 unknown.append(b)
852 unknown.append(b)
853
853
854 # do binary search on the branches we found
854 # do binary search on the branches we found
855 while search:
855 while search:
856 n = search.pop(0)
856 n = search.pop(0)
857 reqcnt += 1
857 reqcnt += 1
858 l = remote.between([(n[0], n[1])])[0]
858 l = remote.between([(n[0], n[1])])[0]
859 l.append(n[1])
859 l.append(n[1])
860 p = n[0]
860 p = n[0]
861 f = 1
861 f = 1
862 for i in l:
862 for i in l:
863 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
863 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
864 if i in m:
864 if i in m:
865 if f <= 2:
865 if f <= 2:
866 self.ui.debug(_("found new branch changeset %s\n") %
866 self.ui.debug(_("found new branch changeset %s\n") %
867 short(p))
867 short(p))
868 fetch[p] = 1
868 fetch[p] = 1
869 base[i] = 1
869 base[i] = 1
870 else:
870 else:
871 self.ui.debug(_("narrowed branch search to %s:%s\n")
871 self.ui.debug(_("narrowed branch search to %s:%s\n")
872 % (short(p), short(i)))
872 % (short(p), short(i)))
873 search.append((p, i))
873 search.append((p, i))
874 break
874 break
875 p, f = i, f * 2
875 p, f = i, f * 2
876
876
877 # sanity check our fetch list
877 # sanity check our fetch list
878 for f in fetch.keys():
878 for f in fetch.keys():
879 if f in m:
879 if f in m:
880 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
880 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
881
881
882 if base.keys() == [nullid]:
882 if base.keys() == [nullid]:
883 self.ui.warn(_("warning: pulling from an unrelated repository!\n"))
883 self.ui.warn(_("warning: pulling from an unrelated repository!\n"))
884
884
885 self.ui.note(_("found new changesets starting at ") +
885 self.ui.note(_("found new changesets starting at ") +
886 " ".join([short(f) for f in fetch]) + "\n")
886 " ".join([short(f) for f in fetch]) + "\n")
887
887
888 self.ui.debug(_("%d total queries\n") % reqcnt)
888 self.ui.debug(_("%d total queries\n") % reqcnt)
889
889
890 return fetch.keys()
890 return fetch.keys()
891
891
892 def findoutgoing(self, remote, base=None, heads=None):
892 def findoutgoing(self, remote, base=None, heads=None):
893 if base == None:
893 if base == None:
894 base = {}
894 base = {}
895 self.findincoming(remote, base, heads)
895 self.findincoming(remote, base, heads)
896
896
897 self.ui.debug(_("common changesets up to ")
897 self.ui.debug(_("common changesets up to ")
898 + " ".join(map(short, base.keys())) + "\n")
898 + " ".join(map(short, base.keys())) + "\n")
899
899
900 remain = dict.fromkeys(self.changelog.nodemap)
900 remain = dict.fromkeys(self.changelog.nodemap)
901
901
902 # prune everything remote has from the tree
902 # prune everything remote has from the tree
903 del remain[nullid]
903 del remain[nullid]
904 remove = base.keys()
904 remove = base.keys()
905 while remove:
905 while remove:
906 n = remove.pop(0)
906 n = remove.pop(0)
907 if n in remain:
907 if n in remain:
908 del remain[n]
908 del remain[n]
909 for p in self.changelog.parents(n):
909 for p in self.changelog.parents(n):
910 remove.append(p)
910 remove.append(p)
911
911
912 # find every node whose parents have been pruned
912 # find every node whose parents have been pruned
913 subset = []
913 subset = []
914 for n in remain:
914 for n in remain:
915 p1, p2 = self.changelog.parents(n)
915 p1, p2 = self.changelog.parents(n)
916 if p1 not in remain and p2 not in remain:
916 if p1 not in remain and p2 not in remain:
917 subset.append(n)
917 subset.append(n)
918
918
919 # this is the set of all roots we have to push
919 # this is the set of all roots we have to push
920 return subset
920 return subset
921
921
922 def pull(self, remote, heads=None):
922 def pull(self, remote, heads=None):
923 lock = self.lock()
923 lock = self.lock()
924
924
925 # if we have an empty repo, fetch everything
925 # if we have an empty repo, fetch everything
926 if self.changelog.tip() == nullid:
926 if self.changelog.tip() == nullid:
927 self.ui.status(_("requesting all changes\n"))
927 self.ui.status(_("requesting all changes\n"))
928 fetch = [nullid]
928 fetch = [nullid]
929 else:
929 else:
930 fetch = self.findincoming(remote)
930 fetch = self.findincoming(remote)
931
931
932 if not fetch:
932 if not fetch:
933 self.ui.status(_("no changes found\n"))
933 self.ui.status(_("no changes found\n"))
934 return 1
934 return 1
935
935
936 if heads is None:
936 if heads is None:
937 cg = remote.changegroup(fetch)
937 cg = remote.changegroup(fetch)
938 else:
938 else:
939 cg = remote.changegroupsubset(fetch, heads)
939 cg = remote.changegroupsubset(fetch, heads)
940 return self.addchangegroup(cg)
940 return self.addchangegroup(cg)
941
941
942 def push(self, remote, force=False):
942 def push(self, remote, force=False):
943 lock = remote.lock()
943 lock = remote.lock()
944
944
945 base = {}
945 base = {}
946 heads = remote.heads()
946 heads = remote.heads()
947 inc = self.findincoming(remote, base, heads)
947 inc = self.findincoming(remote, base, heads)
948 if not force and inc:
948 if not force and inc:
949 self.ui.warn(_("abort: unsynced remote changes!\n"))
949 self.ui.warn(_("abort: unsynced remote changes!\n"))
950 self.ui.status(_("(did you forget to sync? use push -f to force)\n"))
950 self.ui.status(_("(did you forget to sync? use push -f to force)\n"))
951 return 1
951 return 1
952
952
953 update = self.findoutgoing(remote, base)
953 update = self.findoutgoing(remote, base)
954 if not update:
954 if not update:
955 self.ui.status(_("no changes found\n"))
955 self.ui.status(_("no changes found\n"))
956 return 1
956 return 1
957 elif not force:
957 elif not force:
958 if len(heads) < len(self.changelog.heads()):
958 if len(heads) < len(self.changelog.heads()):
959 self.ui.warn(_("abort: push creates new remote branches!\n"))
959 self.ui.warn(_("abort: push creates new remote branches!\n"))
960 self.ui.status(_("(did you forget to merge?"
960 self.ui.status(_("(did you forget to merge?"
961 " use push -f to force)\n"))
961 " use push -f to force)\n"))
962 return 1
962 return 1
963
963
964 cg = self.changegroup(update)
964 cg = self.changegroup(update)
965 return remote.addchangegroup(cg)
965 return remote.addchangegroup(cg)
966
966
967 def changegroupsubset(self, bases, heads):
967 def changegroupsubset(self, bases, heads):
968 """This function generates a changegroup consisting of all the nodes
968 """This function generates a changegroup consisting of all the nodes
969 that are descendents of any of the bases, and ancestors of any of
969 that are descendents of any of the bases, and ancestors of any of
970 the heads.
970 the heads.
971
971
972 It is fairly complex as determining which filenodes and which
972 It is fairly complex as determining which filenodes and which
973 manifest nodes need to be included for the changeset to be complete
973 manifest nodes need to be included for the changeset to be complete
974 is non-trivial.
974 is non-trivial.
975
975
976 Another wrinkle is doing the reverse, figuring out which changeset in
976 Another wrinkle is doing the reverse, figuring out which changeset in
977 the changegroup a particular filenode or manifestnode belongs to."""
977 the changegroup a particular filenode or manifestnode belongs to."""
978
978
979 # Set up some initial variables
979 # Set up some initial variables
980 # Make it easy to refer to self.changelog
980 # Make it easy to refer to self.changelog
981 cl = self.changelog
981 cl = self.changelog
982 # msng is short for missing - compute the list of changesets in this
982 # msng is short for missing - compute the list of changesets in this
983 # changegroup.
983 # changegroup.
984 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
984 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
985 # Some bases may turn out to be superfluous, and some heads may be
985 # Some bases may turn out to be superfluous, and some heads may be
986 # too. nodesbetween will return the minimal set of bases and heads
986 # too. nodesbetween will return the minimal set of bases and heads
987 # necessary to re-create the changegroup.
987 # necessary to re-create the changegroup.
988
988
989 # Known heads are the list of heads that it is assumed the recipient
989 # Known heads are the list of heads that it is assumed the recipient
990 # of this changegroup will know about.
990 # of this changegroup will know about.
991 knownheads = {}
991 knownheads = {}
992 # We assume that all parents of bases are known heads.
992 # We assume that all parents of bases are known heads.
993 for n in bases:
993 for n in bases:
994 for p in cl.parents(n):
994 for p in cl.parents(n):
995 if p != nullid:
995 if p != nullid:
996 knownheads[p] = 1
996 knownheads[p] = 1
997 knownheads = knownheads.keys()
997 knownheads = knownheads.keys()
998 if knownheads:
998 if knownheads:
999 # Now that we know what heads are known, we can compute which
999 # Now that we know what heads are known, we can compute which
1000 # changesets are known. The recipient must know about all
1000 # changesets are known. The recipient must know about all
1001 # changesets required to reach the known heads from the null
1001 # changesets required to reach the known heads from the null
1002 # changeset.
1002 # changeset.
1003 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1003 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1004 junk = None
1004 junk = None
1005 # Transform the list into an ersatz set.
1005 # Transform the list into an ersatz set.
1006 has_cl_set = dict.fromkeys(has_cl_set)
1006 has_cl_set = dict.fromkeys(has_cl_set)
1007 else:
1007 else:
1008 # If there were no known heads, the recipient cannot be assumed to
1008 # If there were no known heads, the recipient cannot be assumed to
1009 # know about any changesets.
1009 # know about any changesets.
1010 has_cl_set = {}
1010 has_cl_set = {}
1011
1011
1012 # Make it easy to refer to self.manifest
1012 # Make it easy to refer to self.manifest
1013 mnfst = self.manifest
1013 mnfst = self.manifest
1014 # We don't know which manifests are missing yet
1014 # We don't know which manifests are missing yet
1015 msng_mnfst_set = {}
1015 msng_mnfst_set = {}
1016 # Nor do we know which filenodes are missing.
1016 # Nor do we know which filenodes are missing.
1017 msng_filenode_set = {}
1017 msng_filenode_set = {}
1018
1018
1019 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1019 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1020 junk = None
1020 junk = None
1021
1021
1022 # A changeset always belongs to itself, so the changenode lookup
1022 # A changeset always belongs to itself, so the changenode lookup
1023 # function for a changenode is identity.
1023 # function for a changenode is identity.
1024 def identity(x):
1024 def identity(x):
1025 return x
1025 return x
1026
1026
1027 # A function generating function. Sets up an environment for the
1027 # A function generating function. Sets up an environment for the
1028 # inner function.
1028 # inner function.
1029 def cmp_by_rev_func(revlog):
1029 def cmp_by_rev_func(revlog):
1030 # Compare two nodes by their revision number in the environment's
1030 # Compare two nodes by their revision number in the environment's
1031 # revision history. Since the revision number both represents the
1031 # revision history. Since the revision number both represents the
1032 # most efficient order to read the nodes in, and represents a
1032 # most efficient order to read the nodes in, and represents a
1033 # topological sorting of the nodes, this function is often useful.
1033 # topological sorting of the nodes, this function is often useful.
1034 def cmp_by_rev(a, b):
1034 def cmp_by_rev(a, b):
1035 return cmp(revlog.rev(a), revlog.rev(b))
1035 return cmp(revlog.rev(a), revlog.rev(b))
1036 return cmp_by_rev
1036 return cmp_by_rev
1037
1037
1038 # If we determine that a particular file or manifest node must be a
1038 # If we determine that a particular file or manifest node must be a
1039 # node that the recipient of the changegroup will already have, we can
1039 # node that the recipient of the changegroup will already have, we can
1040 # also assume the recipient will have all the parents. This function
1040 # also assume the recipient will have all the parents. This function
1041 # prunes them from the set of missing nodes.
1041 # prunes them from the set of missing nodes.
1042 def prune_parents(revlog, hasset, msngset):
1042 def prune_parents(revlog, hasset, msngset):
1043 haslst = hasset.keys()
1043 haslst = hasset.keys()
1044 haslst.sort(cmp_by_rev_func(revlog))
1044 haslst.sort(cmp_by_rev_func(revlog))
1045 for node in haslst:
1045 for node in haslst:
1046 parentlst = [p for p in revlog.parents(node) if p != nullid]
1046 parentlst = [p for p in revlog.parents(node) if p != nullid]
1047 while parentlst:
1047 while parentlst:
1048 n = parentlst.pop()
1048 n = parentlst.pop()
1049 if n not in hasset:
1049 if n not in hasset:
1050 hasset[n] = 1
1050 hasset[n] = 1
1051 p = [p for p in revlog.parents(n) if p != nullid]
1051 p = [p for p in revlog.parents(n) if p != nullid]
1052 parentlst.extend(p)
1052 parentlst.extend(p)
1053 for n in hasset:
1053 for n in hasset:
1054 msngset.pop(n, None)
1054 msngset.pop(n, None)
1055
1055
1056 # This is a function generating function used to set up an environment
1056 # This is a function generating function used to set up an environment
1057 # for the inner function to execute in.
1057 # for the inner function to execute in.
1058 def manifest_and_file_collector(changedfileset):
1058 def manifest_and_file_collector(changedfileset):
1059 # This is an information gathering function that gathers
1059 # This is an information gathering function that gathers
1060 # information from each changeset node that goes out as part of
1060 # information from each changeset node that goes out as part of
1061 # the changegroup. The information gathered is a list of which
1061 # the changegroup. The information gathered is a list of which
1062 # manifest nodes are potentially required (the recipient may
1062 # manifest nodes are potentially required (the recipient may
1063 # already have them) and total list of all files which were
1063 # already have them) and total list of all files which were
1064 # changed in any changeset in the changegroup.
1064 # changed in any changeset in the changegroup.
1065 #
1065 #
1066 # We also remember the first changenode we saw any manifest
1066 # We also remember the first changenode we saw any manifest
1067 # referenced by so we can later determine which changenode 'owns'
1067 # referenced by so we can later determine which changenode 'owns'
1068 # the manifest.
1068 # the manifest.
1069 def collect_manifests_and_files(clnode):
1069 def collect_manifests_and_files(clnode):
1070 c = cl.read(clnode)
1070 c = cl.read(clnode)
1071 for f in c[3]:
1071 for f in c[3]:
1072 # This is to make sure we only have one instance of each
1072 # This is to make sure we only have one instance of each
1073 # filename string for each filename.
1073 # filename string for each filename.
1074 changedfileset.setdefault(f, f)
1074 changedfileset.setdefault(f, f)
1075 msng_mnfst_set.setdefault(c[0], clnode)
1075 msng_mnfst_set.setdefault(c[0], clnode)
1076 return collect_manifests_and_files
1076 return collect_manifests_and_files
1077
1077
1078 # Figure out which manifest nodes (of the ones we think might be part
1078 # Figure out which manifest nodes (of the ones we think might be part
1079 # of the changegroup) the recipient must know about and remove them
1079 # of the changegroup) the recipient must know about and remove them
1080 # from the changegroup.
1080 # from the changegroup.
1081 def prune_manifests():
1081 def prune_manifests():
1082 has_mnfst_set = {}
1082 has_mnfst_set = {}
1083 for n in msng_mnfst_set:
1083 for n in msng_mnfst_set:
1084 # If a 'missing' manifest thinks it belongs to a changenode
1084 # If a 'missing' manifest thinks it belongs to a changenode
1085 # the recipient is assumed to have, obviously the recipient
1085 # the recipient is assumed to have, obviously the recipient
1086 # must have that manifest.
1086 # must have that manifest.
1087 linknode = cl.node(mnfst.linkrev(n))
1087 linknode = cl.node(mnfst.linkrev(n))
1088 if linknode in has_cl_set:
1088 if linknode in has_cl_set:
1089 has_mnfst_set[n] = 1
1089 has_mnfst_set[n] = 1
1090 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1090 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1091
1091
1092 # Use the information collected in collect_manifests_and_files to say
1092 # Use the information collected in collect_manifests_and_files to say
1093 # which changenode any manifestnode belongs to.
1093 # which changenode any manifestnode belongs to.
1094 def lookup_manifest_link(mnfstnode):
1094 def lookup_manifest_link(mnfstnode):
1095 return msng_mnfst_set[mnfstnode]
1095 return msng_mnfst_set[mnfstnode]
1096
1096
1097 # A function generating function that sets up the initial environment
1097 # A function generating function that sets up the initial environment
1098 # the inner function.
1098 # the inner function.
1099 def filenode_collector(changedfiles):
1099 def filenode_collector(changedfiles):
1100 next_rev = [0]
1100 next_rev = [0]
1101 # This gathers information from each manifestnode included in the
1101 # This gathers information from each manifestnode included in the
1102 # changegroup about which filenodes the manifest node references
1102 # changegroup about which filenodes the manifest node references
1103 # so we can include those in the changegroup too.
1103 # so we can include those in the changegroup too.
1104 #
1104 #
1105 # It also remembers which changenode each filenode belongs to. It
1105 # It also remembers which changenode each filenode belongs to. It
1106 # does this by assuming the a filenode belongs to the changenode
1106 # does this by assuming the a filenode belongs to the changenode
1107 # the first manifest that references it belongs to.
1107 # the first manifest that references it belongs to.
1108 def collect_msng_filenodes(mnfstnode):
1108 def collect_msng_filenodes(mnfstnode):
1109 r = mnfst.rev(mnfstnode)
1109 r = mnfst.rev(mnfstnode)
1110 if r == next_rev[0]:
1110 if r == next_rev[0]:
1111 # If the last rev we looked at was the one just previous,
1111 # If the last rev we looked at was the one just previous,
1112 # we only need to see a diff.
1112 # we only need to see a diff.
1113 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1113 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1114 # For each line in the delta
1114 # For each line in the delta
1115 for dline in delta.splitlines():
1115 for dline in delta.splitlines():
1116 # get the filename and filenode for that line
1116 # get the filename and filenode for that line
1117 f, fnode = dline.split('\0')
1117 f, fnode = dline.split('\0')
1118 fnode = bin(fnode[:40])
1118 fnode = bin(fnode[:40])
1119 f = changedfiles.get(f, None)
1119 f = changedfiles.get(f, None)
1120 # And if the file is in the list of files we care
1120 # And if the file is in the list of files we care
1121 # about.
1121 # about.
1122 if f is not None:
1122 if f is not None:
1123 # Get the changenode this manifest belongs to
1123 # Get the changenode this manifest belongs to
1124 clnode = msng_mnfst_set[mnfstnode]
1124 clnode = msng_mnfst_set[mnfstnode]
1125 # Create the set of filenodes for the file if
1125 # Create the set of filenodes for the file if
1126 # there isn't one already.
1126 # there isn't one already.
1127 ndset = msng_filenode_set.setdefault(f, {})
1127 ndset = msng_filenode_set.setdefault(f, {})
1128 # And set the filenode's changelog node to the
1128 # And set the filenode's changelog node to the
1129 # manifest's if it hasn't been set already.
1129 # manifest's if it hasn't been set already.
1130 ndset.setdefault(fnode, clnode)
1130 ndset.setdefault(fnode, clnode)
1131 else:
1131 else:
1132 # Otherwise we need a full manifest.
1132 # Otherwise we need a full manifest.
1133 m = mnfst.read(mnfstnode)
1133 m = mnfst.read(mnfstnode)
1134 # For every file in we care about.
1134 # For every file in we care about.
1135 for f in changedfiles:
1135 for f in changedfiles:
1136 fnode = m.get(f, None)
1136 fnode = m.get(f, None)
1137 # If it's in the manifest
1137 # If it's in the manifest
1138 if fnode is not None:
1138 if fnode is not None:
1139 # See comments above.
1139 # See comments above.
1140 clnode = msng_mnfst_set[mnfstnode]
1140 clnode = msng_mnfst_set[mnfstnode]
1141 ndset = msng_filenode_set.setdefault(f, {})
1141 ndset = msng_filenode_set.setdefault(f, {})
1142 ndset.setdefault(fnode, clnode)
1142 ndset.setdefault(fnode, clnode)
1143 # Remember the revision we hope to see next.
1143 # Remember the revision we hope to see next.
1144 next_rev[0] = r + 1
1144 next_rev[0] = r + 1
1145 return collect_msng_filenodes
1145 return collect_msng_filenodes
1146
1146
1147 # We have a list of filenodes we think we need for a file, lets remove
1147 # We have a list of filenodes we think we need for a file, lets remove
1148 # all those we now the recipient must have.
1148 # all those we now the recipient must have.
1149 def prune_filenodes(f, filerevlog):
1149 def prune_filenodes(f, filerevlog):
1150 msngset = msng_filenode_set[f]
1150 msngset = msng_filenode_set[f]
1151 hasset = {}
1151 hasset = {}
1152 # If a 'missing' filenode thinks it belongs to a changenode we
1152 # If a 'missing' filenode thinks it belongs to a changenode we
1153 # assume the recipient must have, then the recipient must have
1153 # assume the recipient must have, then the recipient must have
1154 # that filenode.
1154 # that filenode.
1155 for n in msngset:
1155 for n in msngset:
1156 clnode = cl.node(filerevlog.linkrev(n))
1156 clnode = cl.node(filerevlog.linkrev(n))
1157 if clnode in has_cl_set:
1157 if clnode in has_cl_set:
1158 hasset[n] = 1
1158 hasset[n] = 1
1159 prune_parents(filerevlog, hasset, msngset)
1159 prune_parents(filerevlog, hasset, msngset)
1160
1160
1161 # A function generator function that sets up the a context for the
1161 # A function generator function that sets up the a context for the
1162 # inner function.
1162 # inner function.
1163 def lookup_filenode_link_func(fname):
1163 def lookup_filenode_link_func(fname):
1164 msngset = msng_filenode_set[fname]
1164 msngset = msng_filenode_set[fname]
1165 # Lookup the changenode the filenode belongs to.
1165 # Lookup the changenode the filenode belongs to.
1166 def lookup_filenode_link(fnode):
1166 def lookup_filenode_link(fnode):
1167 return msngset[fnode]
1167 return msngset[fnode]
1168 return lookup_filenode_link
1168 return lookup_filenode_link
1169
1169
1170 # Now that we have all theses utility functions to help out and
1170 # Now that we have all theses utility functions to help out and
1171 # logically divide up the task, generate the group.
1171 # logically divide up the task, generate the group.
1172 def gengroup():
1172 def gengroup():
1173 # The set of changed files starts empty.
1173 # The set of changed files starts empty.
1174 changedfiles = {}
1174 changedfiles = {}
1175 # Create a changenode group generator that will call our functions
1175 # Create a changenode group generator that will call our functions
1176 # back to lookup the owning changenode and collect information.
1176 # back to lookup the owning changenode and collect information.
1177 group = cl.group(msng_cl_lst, identity,
1177 group = cl.group(msng_cl_lst, identity,
1178 manifest_and_file_collector(changedfiles))
1178 manifest_and_file_collector(changedfiles))
1179 for chnk in group:
1179 for chnk in group:
1180 yield chnk
1180 yield chnk
1181
1181
1182 # The list of manifests has been collected by the generator
1182 # The list of manifests has been collected by the generator
1183 # calling our functions back.
1183 # calling our functions back.
1184 prune_manifests()
1184 prune_manifests()
1185 msng_mnfst_lst = msng_mnfst_set.keys()
1185 msng_mnfst_lst = msng_mnfst_set.keys()
1186 # Sort the manifestnodes by revision number.
1186 # Sort the manifestnodes by revision number.
1187 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1187 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1188 # Create a generator for the manifestnodes that calls our lookup
1188 # Create a generator for the manifestnodes that calls our lookup
1189 # and data collection functions back.
1189 # and data collection functions back.
1190 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1190 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1191 filenode_collector(changedfiles))
1191 filenode_collector(changedfiles))
1192 for chnk in group:
1192 for chnk in group:
1193 yield chnk
1193 yield chnk
1194
1194
1195 # These are no longer needed, dereference and toss the memory for
1195 # These are no longer needed, dereference and toss the memory for
1196 # them.
1196 # them.
1197 msng_mnfst_lst = None
1197 msng_mnfst_lst = None
1198 msng_mnfst_set.clear()
1198 msng_mnfst_set.clear()
1199
1199
1200 changedfiles = changedfiles.keys()
1200 changedfiles = changedfiles.keys()
1201 changedfiles.sort()
1201 changedfiles.sort()
1202 # Go through all our files in order sorted by name.
1202 # Go through all our files in order sorted by name.
1203 for fname in changedfiles:
1203 for fname in changedfiles:
1204 filerevlog = self.file(fname)
1204 filerevlog = self.file(fname)
1205 # Toss out the filenodes that the recipient isn't really
1205 # Toss out the filenodes that the recipient isn't really
1206 # missing.
1206 # missing.
1207 prune_filenodes(fname, filerevlog)
1207 prune_filenodes(fname, filerevlog)
1208 msng_filenode_lst = msng_filenode_set[fname].keys()
1208 msng_filenode_lst = msng_filenode_set[fname].keys()
1209 # If any filenodes are left, generate the group for them,
1209 # If any filenodes are left, generate the group for them,
1210 # otherwise don't bother.
1210 # otherwise don't bother.
1211 if len(msng_filenode_lst) > 0:
1211 if len(msng_filenode_lst) > 0:
1212 yield struct.pack(">l", len(fname) + 4) + fname
1212 yield struct.pack(">l", len(fname) + 4) + fname
1213 # Sort the filenodes by their revision #
1213 # Sort the filenodes by their revision #
1214 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1214 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1215 # Create a group generator and only pass in a changenode
1215 # Create a group generator and only pass in a changenode
1216 # lookup function as we need to collect no information
1216 # lookup function as we need to collect no information
1217 # from filenodes.
1217 # from filenodes.
1218 group = filerevlog.group(msng_filenode_lst,
1218 group = filerevlog.group(msng_filenode_lst,
1219 lookup_filenode_link_func(fname))
1219 lookup_filenode_link_func(fname))
1220 for chnk in group:
1220 for chnk in group:
1221 yield chnk
1221 yield chnk
1222 # Don't need this anymore, toss it to free memory.
1222 # Don't need this anymore, toss it to free memory.
1223 del msng_filenode_set[fname]
1223 del msng_filenode_set[fname]
1224 # Signal that no more groups are left.
1224 # Signal that no more groups are left.
1225 yield struct.pack(">l", 0)
1225 yield struct.pack(">l", 0)
1226
1226
1227 return util.chunkbuffer(gengroup())
1227 return util.chunkbuffer(gengroup())
1228
1228
1229 def changegroup(self, basenodes):
1229 def changegroup(self, basenodes):
1230 """Generate a changegroup of all nodes that we have that a recipient
1230 """Generate a changegroup of all nodes that we have that a recipient
1231 doesn't.
1231 doesn't.
1232
1232
1233 This is much easier than the previous function as we can assume that
1233 This is much easier than the previous function as we can assume that
1234 the recipient has any changenode we aren't sending them."""
1234 the recipient has any changenode we aren't sending them."""
1235 cl = self.changelog
1235 cl = self.changelog
1236 nodes = cl.nodesbetween(basenodes, None)[0]
1236 nodes = cl.nodesbetween(basenodes, None)[0]
1237 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1237 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1238
1238
1239 def identity(x):
1239 def identity(x):
1240 return x
1240 return x
1241
1241
1242 def gennodelst(revlog):
1242 def gennodelst(revlog):
1243 for r in xrange(0, revlog.count()):
1243 for r in xrange(0, revlog.count()):
1244 n = revlog.node(r)
1244 n = revlog.node(r)
1245 if revlog.linkrev(n) in revset:
1245 if revlog.linkrev(n) in revset:
1246 yield n
1246 yield n
1247
1247
1248 def changed_file_collector(changedfileset):
1248 def changed_file_collector(changedfileset):
1249 def collect_changed_files(clnode):
1249 def collect_changed_files(clnode):
1250 c = cl.read(clnode)
1250 c = cl.read(clnode)
1251 for fname in c[3]:
1251 for fname in c[3]:
1252 changedfileset[fname] = 1
1252 changedfileset[fname] = 1
1253 return collect_changed_files
1253 return collect_changed_files
1254
1254
1255 def lookuprevlink_func(revlog):
1255 def lookuprevlink_func(revlog):
1256 def lookuprevlink(n):
1256 def lookuprevlink(n):
1257 return cl.node(revlog.linkrev(n))
1257 return cl.node(revlog.linkrev(n))
1258 return lookuprevlink
1258 return lookuprevlink
1259
1259
1260 def gengroup():
1260 def gengroup():
1261 # construct a list of all changed files
1261 # construct a list of all changed files
1262 changedfiles = {}
1262 changedfiles = {}
1263
1263
1264 for chnk in cl.group(nodes, identity,
1264 for chnk in cl.group(nodes, identity,
1265 changed_file_collector(changedfiles)):
1265 changed_file_collector(changedfiles)):
1266 yield chnk
1266 yield chnk
1267 changedfiles = changedfiles.keys()
1267 changedfiles = changedfiles.keys()
1268 changedfiles.sort()
1268 changedfiles.sort()
1269
1269
1270 mnfst = self.manifest
1270 mnfst = self.manifest
1271 nodeiter = gennodelst(mnfst)
1271 nodeiter = gennodelst(mnfst)
1272 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1272 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1273 yield chnk
1273 yield chnk
1274
1274
1275 for fname in changedfiles:
1275 for fname in changedfiles:
1276 filerevlog = self.file(fname)
1276 filerevlog = self.file(fname)
1277 nodeiter = gennodelst(filerevlog)
1277 nodeiter = gennodelst(filerevlog)
1278 nodeiter = list(nodeiter)
1278 nodeiter = list(nodeiter)
1279 if nodeiter:
1279 if nodeiter:
1280 yield struct.pack(">l", len(fname) + 4) + fname
1280 yield struct.pack(">l", len(fname) + 4) + fname
1281 lookup = lookuprevlink_func(filerevlog)
1281 lookup = lookuprevlink_func(filerevlog)
1282 for chnk in filerevlog.group(nodeiter, lookup):
1282 for chnk in filerevlog.group(nodeiter, lookup):
1283 yield chnk
1283 yield chnk
1284
1284
1285 yield struct.pack(">l", 0)
1285 yield struct.pack(">l", 0)
1286
1286
1287 return util.chunkbuffer(gengroup())
1287 return util.chunkbuffer(gengroup())
1288
1288
1289 def addchangegroup(self, source):
1289 def addchangegroup(self, source):
1290
1290
1291 def getchunk():
1291 def getchunk():
1292 d = source.read(4)
1292 d = source.read(4)
1293 if not d:
1293 if not d:
1294 return ""
1294 return ""
1295 l = struct.unpack(">l", d)[0]
1295 l = struct.unpack(">l", d)[0]
1296 if l <= 4:
1296 if l <= 4:
1297 return ""
1297 return ""
1298 d = source.read(l - 4)
1298 d = source.read(l - 4)
1299 if len(d) < l - 4:
1299 if len(d) < l - 4:
1300 raise repo.RepoError(_("premature EOF reading chunk"
1300 raise repo.RepoError(_("premature EOF reading chunk"
1301 " (got %d bytes, expected %d)")
1301 " (got %d bytes, expected %d)")
1302 % (len(d), l - 4))
1302 % (len(d), l - 4))
1303 return d
1303 return d
1304
1304
1305 def getgroup():
1305 def getgroup():
1306 while 1:
1306 while 1:
1307 c = getchunk()
1307 c = getchunk()
1308 if not c:
1308 if not c:
1309 break
1309 break
1310 yield c
1310 yield c
1311
1311
1312 def csmap(x):
1312 def csmap(x):
1313 self.ui.debug(_("add changeset %s\n") % short(x))
1313 self.ui.debug(_("add changeset %s\n") % short(x))
1314 return self.changelog.count()
1314 return self.changelog.count()
1315
1315
1316 def revmap(x):
1316 def revmap(x):
1317 return self.changelog.rev(x)
1317 return self.changelog.rev(x)
1318
1318
1319 if not source:
1319 if not source:
1320 return
1320 return
1321 changesets = files = revisions = 0
1321 changesets = files = revisions = 0
1322
1322
1323 tr = self.transaction()
1323 tr = self.transaction()
1324
1324
1325 oldheads = len(self.changelog.heads())
1325 oldheads = len(self.changelog.heads())
1326
1326
1327 # pull off the changeset group
1327 # pull off the changeset group
1328 self.ui.status(_("adding changesets\n"))
1328 self.ui.status(_("adding changesets\n"))
1329 co = self.changelog.tip()
1329 co = self.changelog.tip()
1330 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1330 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1331 cnr, cor = map(self.changelog.rev, (cn, co))
1331 cnr, cor = map(self.changelog.rev, (cn, co))
1332 if cn == nullid:
1332 if cn == nullid:
1333 cnr = cor
1333 cnr = cor
1334 changesets = cnr - cor
1334 changesets = cnr - cor
1335
1335
1336 # pull off the manifest group
1336 # pull off the manifest group
1337 self.ui.status(_("adding manifests\n"))
1337 self.ui.status(_("adding manifests\n"))
1338 mm = self.manifest.tip()
1338 mm = self.manifest.tip()
1339 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1339 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1340
1340
1341 # process the files
1341 # process the files
1342 self.ui.status(_("adding file changes\n"))
1342 self.ui.status(_("adding file changes\n"))
1343 while 1:
1343 while 1:
1344 f = getchunk()
1344 f = getchunk()
1345 if not f:
1345 if not f:
1346 break
1346 break
1347 self.ui.debug(_("adding %s revisions\n") % f)
1347 self.ui.debug(_("adding %s revisions\n") % f)
1348 fl = self.file(f)
1348 fl = self.file(f)
1349 o = fl.count()
1349 o = fl.count()
1350 n = fl.addgroup(getgroup(), revmap, tr)
1350 n = fl.addgroup(getgroup(), revmap, tr)
1351 revisions += fl.count() - o
1351 revisions += fl.count() - o
1352 files += 1
1352 files += 1
1353
1353
1354 newheads = len(self.changelog.heads())
1354 newheads = len(self.changelog.heads())
1355 heads = ""
1355 heads = ""
1356 if oldheads and newheads > oldheads:
1356 if oldheads and newheads > oldheads:
1357 heads = _(" (+%d heads)") % (newheads - oldheads)
1357 heads = _(" (+%d heads)") % (newheads - oldheads)
1358
1358
1359 self.ui.status(_("added %d changesets"
1359 self.ui.status(_("added %d changesets"
1360 " with %d changes to %d files%s\n")
1360 " with %d changes to %d files%s\n")
1361 % (changesets, revisions, files, heads))
1361 % (changesets, revisions, files, heads))
1362
1362
1363 tr.close()
1363 tr.close()
1364
1364
1365 if changesets > 0:
1365 if changesets > 0:
1366 if not self.hook("changegroup",
1366 if not self.hook("changegroup",
1367 node=hex(self.changelog.node(cor+1))):
1367 node=hex(self.changelog.node(cor+1))):
1368 self.ui.warn(_("abort: changegroup hook returned failure!\n"))
1368 self.ui.warn(_("abort: changegroup hook returned failure!\n"))
1369 return 1
1369 return 1
1370
1370
1371 for i in range(cor + 1, cnr + 1):
1371 for i in range(cor + 1, cnr + 1):
1372 self.hook("commit", node=hex(self.changelog.node(i)))
1372 self.hook("commit", node=hex(self.changelog.node(i)))
1373
1373
1374 return
1374 return
1375
1375
1376 def update(self, node, allow=False, force=False, choose=None,
1376 def update(self, node, allow=False, force=False, choose=None,
1377 moddirstate=True, forcemerge=False):
1377 moddirstate=True, forcemerge=False):
1378 pl = self.dirstate.parents()
1378 pl = self.dirstate.parents()
1379 if not force and pl[1] != nullid:
1379 if not force and pl[1] != nullid:
1380 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1380 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1381 return 1
1381 return 1
1382
1382
1383 p1, p2 = pl[0], node
1383 p1, p2 = pl[0], node
1384 pa = self.changelog.ancestor(p1, p2)
1384 pa = self.changelog.ancestor(p1, p2)
1385 m1n = self.changelog.read(p1)[0]
1385 m1n = self.changelog.read(p1)[0]
1386 m2n = self.changelog.read(p2)[0]
1386 m2n = self.changelog.read(p2)[0]
1387 man = self.manifest.ancestor(m1n, m2n)
1387 man = self.manifest.ancestor(m1n, m2n)
1388 m1 = self.manifest.read(m1n)
1388 m1 = self.manifest.read(m1n)
1389 mf1 = self.manifest.readflags(m1n)
1389 mf1 = self.manifest.readflags(m1n)
1390 m2 = self.manifest.read(m2n)
1390 m2 = self.manifest.read(m2n)
1391 mf2 = self.manifest.readflags(m2n)
1391 mf2 = self.manifest.readflags(m2n)
1392 ma = self.manifest.read(man)
1392 ma = self.manifest.read(man)
1393 mfa = self.manifest.readflags(man)
1393 mfa = self.manifest.readflags(man)
1394
1394
1395 (c, a, d, u) = self.changes()
1395 modified, added, removed, unknown = self.changes()
1396
1396
1397 if allow and not forcemerge:
1397 if allow and not forcemerge:
1398 if c or a or d:
1398 if modified or added or removed:
1399 raise util.Abort(_("outstanding uncommited changes"))
1399 raise util.Abort(_("outstanding uncommited changes"))
1400 if not forcemerge and not force:
1400 if not forcemerge and not force:
1401 for f in u:
1401 for f in unknown:
1402 if f in m2:
1402 if f in m2:
1403 t1 = self.wread(f)
1403 t1 = self.wread(f)
1404 t2 = self.file(f).read(m2[f])
1404 t2 = self.file(f).read(m2[f])
1405 if cmp(t1, t2) != 0:
1405 if cmp(t1, t2) != 0:
1406 raise util.Abort(_("'%s' already exists in the working"
1406 raise util.Abort(_("'%s' already exists in the working"
1407 " dir and differs from remote") % f)
1407 " dir and differs from remote") % f)
1408
1408
1409 # is this a jump, or a merge? i.e. is there a linear path
1409 # is this a jump, or a merge? i.e. is there a linear path
1410 # from p1 to p2?
1410 # from p1 to p2?
1411 linear_path = (pa == p1 or pa == p2)
1411 linear_path = (pa == p1 or pa == p2)
1412
1412
1413 # resolve the manifest to determine which files
1413 # resolve the manifest to determine which files
1414 # we care about merging
1414 # we care about merging
1415 self.ui.note(_("resolving manifests\n"))
1415 self.ui.note(_("resolving manifests\n"))
1416 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1416 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1417 (force, allow, moddirstate, linear_path))
1417 (force, allow, moddirstate, linear_path))
1418 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1418 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1419 (short(man), short(m1n), short(m2n)))
1419 (short(man), short(m1n), short(m2n)))
1420
1420
1421 merge = {}
1421 merge = {}
1422 get = {}
1422 get = {}
1423 remove = []
1423 remove = []
1424
1424
1425 # construct a working dir manifest
1425 # construct a working dir manifest
1426 mw = m1.copy()
1426 mw = m1.copy()
1427 mfw = mf1.copy()
1427 mfw = mf1.copy()
1428 umap = dict.fromkeys(u)
1428 umap = dict.fromkeys(unknown)
1429
1429
1430 for f in a + c + u:
1430 for f in added + modified + unknown:
1431 mw[f] = ""
1431 mw[f] = ""
1432 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1432 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1433
1433
1434 if moddirstate:
1434 if moddirstate:
1435 wlock = self.wlock()
1435 wlock = self.wlock()
1436
1436
1437 for f in d:
1437 for f in removed:
1438 if f in mw:
1438 if f in mw:
1439 del mw[f]
1439 del mw[f]
1440
1440
1441 # If we're jumping between revisions (as opposed to merging),
1441 # If we're jumping between revisions (as opposed to merging),
1442 # and if neither the working directory nor the target rev has
1442 # and if neither the working directory nor the target rev has
1443 # the file, then we need to remove it from the dirstate, to
1443 # the file, then we need to remove it from the dirstate, to
1444 # prevent the dirstate from listing the file when it is no
1444 # prevent the dirstate from listing the file when it is no
1445 # longer in the manifest.
1445 # longer in the manifest.
1446 if moddirstate and linear_path and f not in m2:
1446 if moddirstate and linear_path and f not in m2:
1447 self.dirstate.forget((f,))
1447 self.dirstate.forget((f,))
1448
1448
1449 # Compare manifests
1449 # Compare manifests
1450 for f, n in mw.iteritems():
1450 for f, n in mw.iteritems():
1451 if choose and not choose(f):
1451 if choose and not choose(f):
1452 continue
1452 continue
1453 if f in m2:
1453 if f in m2:
1454 s = 0
1454 s = 0
1455
1455
1456 # is the wfile new since m1, and match m2?
1456 # is the wfile new since m1, and match m2?
1457 if f not in m1:
1457 if f not in m1:
1458 t1 = self.wread(f)
1458 t1 = self.wread(f)
1459 t2 = self.file(f).read(m2[f])
1459 t2 = self.file(f).read(m2[f])
1460 if cmp(t1, t2) == 0:
1460 if cmp(t1, t2) == 0:
1461 n = m2[f]
1461 n = m2[f]
1462 del t1, t2
1462 del t1, t2
1463
1463
1464 # are files different?
1464 # are files different?
1465 if n != m2[f]:
1465 if n != m2[f]:
1466 a = ma.get(f, nullid)
1466 a = ma.get(f, nullid)
1467 # are both different from the ancestor?
1467 # are both different from the ancestor?
1468 if n != a and m2[f] != a:
1468 if n != a and m2[f] != a:
1469 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1469 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1470 # merge executable bits
1470 # merge executable bits
1471 # "if we changed or they changed, change in merge"
1471 # "if we changed or they changed, change in merge"
1472 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1472 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1473 mode = ((a^b) | (a^c)) ^ a
1473 mode = ((a^b) | (a^c)) ^ a
1474 merge[f] = (m1.get(f, nullid), m2[f], mode)
1474 merge[f] = (m1.get(f, nullid), m2[f], mode)
1475 s = 1
1475 s = 1
1476 # are we clobbering?
1476 # are we clobbering?
1477 # is remote's version newer?
1477 # is remote's version newer?
1478 # or are we going back in time?
1478 # or are we going back in time?
1479 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1479 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1480 self.ui.debug(_(" remote %s is newer, get\n") % f)
1480 self.ui.debug(_(" remote %s is newer, get\n") % f)
1481 get[f] = m2[f]
1481 get[f] = m2[f]
1482 s = 1
1482 s = 1
1483 elif f in umap:
1483 elif f in umap:
1484 # this unknown file is the same as the checkout
1484 # this unknown file is the same as the checkout
1485 get[f] = m2[f]
1485 get[f] = m2[f]
1486
1486
1487 if not s and mfw[f] != mf2[f]:
1487 if not s and mfw[f] != mf2[f]:
1488 if force:
1488 if force:
1489 self.ui.debug(_(" updating permissions for %s\n") % f)
1489 self.ui.debug(_(" updating permissions for %s\n") % f)
1490 util.set_exec(self.wjoin(f), mf2[f])
1490 util.set_exec(self.wjoin(f), mf2[f])
1491 else:
1491 else:
1492 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1492 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1493 mode = ((a^b) | (a^c)) ^ a
1493 mode = ((a^b) | (a^c)) ^ a
1494 if mode != b:
1494 if mode != b:
1495 self.ui.debug(_(" updating permissions for %s\n")
1495 self.ui.debug(_(" updating permissions for %s\n")
1496 % f)
1496 % f)
1497 util.set_exec(self.wjoin(f), mode)
1497 util.set_exec(self.wjoin(f), mode)
1498 del m2[f]
1498 del m2[f]
1499 elif f in ma:
1499 elif f in ma:
1500 if n != ma[f]:
1500 if n != ma[f]:
1501 r = _("d")
1501 r = _("d")
1502 if not force and (linear_path or allow):
1502 if not force and (linear_path or allow):
1503 r = self.ui.prompt(
1503 r = self.ui.prompt(
1504 (_(" local changed %s which remote deleted\n") % f) +
1504 (_(" local changed %s which remote deleted\n") % f) +
1505 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1505 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1506 if r == _("d"):
1506 if r == _("d"):
1507 remove.append(f)
1507 remove.append(f)
1508 else:
1508 else:
1509 self.ui.debug(_("other deleted %s\n") % f)
1509 self.ui.debug(_("other deleted %s\n") % f)
1510 remove.append(f) # other deleted it
1510 remove.append(f) # other deleted it
1511 else:
1511 else:
1512 # file is created on branch or in working directory
1512 # file is created on branch or in working directory
1513 if force and f not in umap:
1513 if force and f not in umap:
1514 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1514 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1515 remove.append(f)
1515 remove.append(f)
1516 elif n == m1.get(f, nullid): # same as parent
1516 elif n == m1.get(f, nullid): # same as parent
1517 if p2 == pa: # going backwards?
1517 if p2 == pa: # going backwards?
1518 self.ui.debug(_("remote deleted %s\n") % f)
1518 self.ui.debug(_("remote deleted %s\n") % f)
1519 remove.append(f)
1519 remove.append(f)
1520 else:
1520 else:
1521 self.ui.debug(_("local modified %s, keeping\n") % f)
1521 self.ui.debug(_("local modified %s, keeping\n") % f)
1522 else:
1522 else:
1523 self.ui.debug(_("working dir created %s, keeping\n") % f)
1523 self.ui.debug(_("working dir created %s, keeping\n") % f)
1524
1524
1525 for f, n in m2.iteritems():
1525 for f, n in m2.iteritems():
1526 if choose and not choose(f):
1526 if choose and not choose(f):
1527 continue
1527 continue
1528 if f[0] == "/":
1528 if f[0] == "/":
1529 continue
1529 continue
1530 if f in ma and n != ma[f]:
1530 if f in ma and n != ma[f]:
1531 r = _("k")
1531 r = _("k")
1532 if not force and (linear_path or allow):
1532 if not force and (linear_path or allow):
1533 r = self.ui.prompt(
1533 r = self.ui.prompt(
1534 (_("remote changed %s which local deleted\n") % f) +
1534 (_("remote changed %s which local deleted\n") % f) +
1535 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1535 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1536 if r == _("k"):
1536 if r == _("k"):
1537 get[f] = n
1537 get[f] = n
1538 elif f not in ma:
1538 elif f not in ma:
1539 self.ui.debug(_("remote created %s\n") % f)
1539 self.ui.debug(_("remote created %s\n") % f)
1540 get[f] = n
1540 get[f] = n
1541 else:
1541 else:
1542 if force or p2 == pa: # going backwards?
1542 if force or p2 == pa: # going backwards?
1543 self.ui.debug(_("local deleted %s, recreating\n") % f)
1543 self.ui.debug(_("local deleted %s, recreating\n") % f)
1544 get[f] = n
1544 get[f] = n
1545 else:
1545 else:
1546 self.ui.debug(_("local deleted %s\n") % f)
1546 self.ui.debug(_("local deleted %s\n") % f)
1547
1547
1548 del mw, m1, m2, ma
1548 del mw, m1, m2, ma
1549
1549
1550 if force:
1550 if force:
1551 for f in merge:
1551 for f in merge:
1552 get[f] = merge[f][1]
1552 get[f] = merge[f][1]
1553 merge = {}
1553 merge = {}
1554
1554
1555 if linear_path or force:
1555 if linear_path or force:
1556 # we don't need to do any magic, just jump to the new rev
1556 # we don't need to do any magic, just jump to the new rev
1557 branch_merge = False
1557 branch_merge = False
1558 p1, p2 = p2, nullid
1558 p1, p2 = p2, nullid
1559 else:
1559 else:
1560 if not allow:
1560 if not allow:
1561 self.ui.status(_("this update spans a branch"
1561 self.ui.status(_("this update spans a branch"
1562 " affecting the following files:\n"))
1562 " affecting the following files:\n"))
1563 fl = merge.keys() + get.keys()
1563 fl = merge.keys() + get.keys()
1564 fl.sort()
1564 fl.sort()
1565 for f in fl:
1565 for f in fl:
1566 cf = ""
1566 cf = ""
1567 if f in merge:
1567 if f in merge:
1568 cf = _(" (resolve)")
1568 cf = _(" (resolve)")
1569 self.ui.status(" %s%s\n" % (f, cf))
1569 self.ui.status(" %s%s\n" % (f, cf))
1570 self.ui.warn(_("aborting update spanning branches!\n"))
1570 self.ui.warn(_("aborting update spanning branches!\n"))
1571 self.ui.status(_("(use update -m to merge across branches"
1571 self.ui.status(_("(use update -m to merge across branches"
1572 " or -C to lose changes)\n"))
1572 " or -C to lose changes)\n"))
1573 return 1
1573 return 1
1574 branch_merge = True
1574 branch_merge = True
1575
1575
1576 # get the files we don't need to change
1576 # get the files we don't need to change
1577 files = get.keys()
1577 files = get.keys()
1578 files.sort()
1578 files.sort()
1579 for f in files:
1579 for f in files:
1580 if f[0] == "/":
1580 if f[0] == "/":
1581 continue
1581 continue
1582 self.ui.note(_("getting %s\n") % f)
1582 self.ui.note(_("getting %s\n") % f)
1583 t = self.file(f).read(get[f])
1583 t = self.file(f).read(get[f])
1584 self.wwrite(f, t)
1584 self.wwrite(f, t)
1585 util.set_exec(self.wjoin(f), mf2[f])
1585 util.set_exec(self.wjoin(f), mf2[f])
1586 if moddirstate:
1586 if moddirstate:
1587 if branch_merge:
1587 if branch_merge:
1588 self.dirstate.update([f], 'n', st_mtime=-1)
1588 self.dirstate.update([f], 'n', st_mtime=-1)
1589 else:
1589 else:
1590 self.dirstate.update([f], 'n')
1590 self.dirstate.update([f], 'n')
1591
1591
1592 # merge the tricky bits
1592 # merge the tricky bits
1593 files = merge.keys()
1593 files = merge.keys()
1594 files.sort()
1594 files.sort()
1595 for f in files:
1595 for f in files:
1596 self.ui.status(_("merging %s\n") % f)
1596 self.ui.status(_("merging %s\n") % f)
1597 my, other, flag = merge[f]
1597 my, other, flag = merge[f]
1598 self.merge3(f, my, other)
1598 self.merge3(f, my, other)
1599 util.set_exec(self.wjoin(f), flag)
1599 util.set_exec(self.wjoin(f), flag)
1600 if moddirstate:
1600 if moddirstate:
1601 if branch_merge:
1601 if branch_merge:
1602 # We've done a branch merge, mark this file as merged
1602 # We've done a branch merge, mark this file as merged
1603 # so that we properly record the merger later
1603 # so that we properly record the merger later
1604 self.dirstate.update([f], 'm')
1604 self.dirstate.update([f], 'm')
1605 else:
1605 else:
1606 # We've update-merged a locally modified file, so
1606 # We've update-merged a locally modified file, so
1607 # we set the dirstate to emulate a normal checkout
1607 # we set the dirstate to emulate a normal checkout
1608 # of that file some time in the past. Thus our
1608 # of that file some time in the past. Thus our
1609 # merge will appear as a normal local file
1609 # merge will appear as a normal local file
1610 # modification.
1610 # modification.
1611 f_len = len(self.file(f).read(other))
1611 f_len = len(self.file(f).read(other))
1612 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1612 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1613
1613
1614 remove.sort()
1614 remove.sort()
1615 for f in remove:
1615 for f in remove:
1616 self.ui.note(_("removing %s\n") % f)
1616 self.ui.note(_("removing %s\n") % f)
1617 try:
1617 try:
1618 util.unlink(self.wjoin(f))
1618 util.unlink(self.wjoin(f))
1619 except OSError, inst:
1619 except OSError, inst:
1620 if inst.errno != errno.ENOENT:
1620 if inst.errno != errno.ENOENT:
1621 self.ui.warn(_("update failed to remove %s: %s!\n") %
1621 self.ui.warn(_("update failed to remove %s: %s!\n") %
1622 (f, inst.strerror))
1622 (f, inst.strerror))
1623 if moddirstate:
1623 if moddirstate:
1624 if branch_merge:
1624 if branch_merge:
1625 self.dirstate.update(remove, 'r')
1625 self.dirstate.update(remove, 'r')
1626 else:
1626 else:
1627 self.dirstate.forget(remove)
1627 self.dirstate.forget(remove)
1628
1628
1629 if moddirstate:
1629 if moddirstate:
1630 self.dirstate.setparents(p1, p2)
1630 self.dirstate.setparents(p1, p2)
1631
1631
1632 def merge3(self, fn, my, other):
1632 def merge3(self, fn, my, other):
1633 """perform a 3-way merge in the working directory"""
1633 """perform a 3-way merge in the working directory"""
1634
1634
1635 def temp(prefix, node):
1635 def temp(prefix, node):
1636 pre = "%s~%s." % (os.path.basename(fn), prefix)
1636 pre = "%s~%s." % (os.path.basename(fn), prefix)
1637 (fd, name) = tempfile.mkstemp("", pre)
1637 (fd, name) = tempfile.mkstemp("", pre)
1638 f = os.fdopen(fd, "wb")
1638 f = os.fdopen(fd, "wb")
1639 self.wwrite(fn, fl.read(node), f)
1639 self.wwrite(fn, fl.read(node), f)
1640 f.close()
1640 f.close()
1641 return name
1641 return name
1642
1642
1643 fl = self.file(fn)
1643 fl = self.file(fn)
1644 base = fl.ancestor(my, other)
1644 base = fl.ancestor(my, other)
1645 a = self.wjoin(fn)
1645 a = self.wjoin(fn)
1646 b = temp("base", base)
1646 b = temp("base", base)
1647 c = temp("other", other)
1647 c = temp("other", other)
1648
1648
1649 self.ui.note(_("resolving %s\n") % fn)
1649 self.ui.note(_("resolving %s\n") % fn)
1650 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1650 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1651 (fn, short(my), short(other), short(base)))
1651 (fn, short(my), short(other), short(base)))
1652
1652
1653 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1653 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1654 or "hgmerge")
1654 or "hgmerge")
1655 r = os.system('%s "%s" "%s" "%s"' % (cmd, a, b, c))
1655 r = os.system('%s "%s" "%s" "%s"' % (cmd, a, b, c))
1656 if r:
1656 if r:
1657 self.ui.warn(_("merging %s failed!\n") % fn)
1657 self.ui.warn(_("merging %s failed!\n") % fn)
1658
1658
1659 os.unlink(b)
1659 os.unlink(b)
1660 os.unlink(c)
1660 os.unlink(c)
1661
1661
1662 def verify(self):
1662 def verify(self):
1663 filelinkrevs = {}
1663 filelinkrevs = {}
1664 filenodes = {}
1664 filenodes = {}
1665 changesets = revisions = files = 0
1665 changesets = revisions = files = 0
1666 errors = [0]
1666 errors = [0]
1667 neededmanifests = {}
1667 neededmanifests = {}
1668
1668
1669 def err(msg):
1669 def err(msg):
1670 self.ui.warn(msg + "\n")
1670 self.ui.warn(msg + "\n")
1671 errors[0] += 1
1671 errors[0] += 1
1672
1672
1673 seen = {}
1673 seen = {}
1674 self.ui.status(_("checking changesets\n"))
1674 self.ui.status(_("checking changesets\n"))
1675 d = self.changelog.checksize()
1675 d = self.changelog.checksize()
1676 if d:
1676 if d:
1677 err(_("changeset data short %d bytes") % d)
1677 err(_("changeset data short %d bytes") % d)
1678 for i in range(self.changelog.count()):
1678 for i in range(self.changelog.count()):
1679 changesets += 1
1679 changesets += 1
1680 n = self.changelog.node(i)
1680 n = self.changelog.node(i)
1681 l = self.changelog.linkrev(n)
1681 l = self.changelog.linkrev(n)
1682 if l != i:
1682 if l != i:
1683 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1683 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1684 if n in seen:
1684 if n in seen:
1685 err(_("duplicate changeset at revision %d") % i)
1685 err(_("duplicate changeset at revision %d") % i)
1686 seen[n] = 1
1686 seen[n] = 1
1687
1687
1688 for p in self.changelog.parents(n):
1688 for p in self.changelog.parents(n):
1689 if p not in self.changelog.nodemap:
1689 if p not in self.changelog.nodemap:
1690 err(_("changeset %s has unknown parent %s") %
1690 err(_("changeset %s has unknown parent %s") %
1691 (short(n), short(p)))
1691 (short(n), short(p)))
1692 try:
1692 try:
1693 changes = self.changelog.read(n)
1693 changes = self.changelog.read(n)
1694 except KeyboardInterrupt:
1694 except KeyboardInterrupt:
1695 self.ui.warn(_("interrupted"))
1695 self.ui.warn(_("interrupted"))
1696 raise
1696 raise
1697 except Exception, inst:
1697 except Exception, inst:
1698 err(_("unpacking changeset %s: %s") % (short(n), inst))
1698 err(_("unpacking changeset %s: %s") % (short(n), inst))
1699
1699
1700 neededmanifests[changes[0]] = n
1700 neededmanifests[changes[0]] = n
1701
1701
1702 for f in changes[3]:
1702 for f in changes[3]:
1703 filelinkrevs.setdefault(f, []).append(i)
1703 filelinkrevs.setdefault(f, []).append(i)
1704
1704
1705 seen = {}
1705 seen = {}
1706 self.ui.status(_("checking manifests\n"))
1706 self.ui.status(_("checking manifests\n"))
1707 d = self.manifest.checksize()
1707 d = self.manifest.checksize()
1708 if d:
1708 if d:
1709 err(_("manifest data short %d bytes") % d)
1709 err(_("manifest data short %d bytes") % d)
1710 for i in range(self.manifest.count()):
1710 for i in range(self.manifest.count()):
1711 n = self.manifest.node(i)
1711 n = self.manifest.node(i)
1712 l = self.manifest.linkrev(n)
1712 l = self.manifest.linkrev(n)
1713
1713
1714 if l < 0 or l >= self.changelog.count():
1714 if l < 0 or l >= self.changelog.count():
1715 err(_("bad manifest link (%d) at revision %d") % (l, i))
1715 err(_("bad manifest link (%d) at revision %d") % (l, i))
1716
1716
1717 if n in neededmanifests:
1717 if n in neededmanifests:
1718 del neededmanifests[n]
1718 del neededmanifests[n]
1719
1719
1720 if n in seen:
1720 if n in seen:
1721 err(_("duplicate manifest at revision %d") % i)
1721 err(_("duplicate manifest at revision %d") % i)
1722
1722
1723 seen[n] = 1
1723 seen[n] = 1
1724
1724
1725 for p in self.manifest.parents(n):
1725 for p in self.manifest.parents(n):
1726 if p not in self.manifest.nodemap:
1726 if p not in self.manifest.nodemap:
1727 err(_("manifest %s has unknown parent %s") %
1727 err(_("manifest %s has unknown parent %s") %
1728 (short(n), short(p)))
1728 (short(n), short(p)))
1729
1729
1730 try:
1730 try:
1731 delta = mdiff.patchtext(self.manifest.delta(n))
1731 delta = mdiff.patchtext(self.manifest.delta(n))
1732 except KeyboardInterrupt:
1732 except KeyboardInterrupt:
1733 self.ui.warn(_("interrupted"))
1733 self.ui.warn(_("interrupted"))
1734 raise
1734 raise
1735 except Exception, inst:
1735 except Exception, inst:
1736 err(_("unpacking manifest %s: %s") % (short(n), inst))
1736 err(_("unpacking manifest %s: %s") % (short(n), inst))
1737
1737
1738 ff = [ l.split('\0') for l in delta.splitlines() ]
1738 ff = [ l.split('\0') for l in delta.splitlines() ]
1739 for f, fn in ff:
1739 for f, fn in ff:
1740 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1740 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1741
1741
1742 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1742 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1743
1743
1744 for m, c in neededmanifests.items():
1744 for m, c in neededmanifests.items():
1745 err(_("Changeset %s refers to unknown manifest %s") %
1745 err(_("Changeset %s refers to unknown manifest %s") %
1746 (short(m), short(c)))
1746 (short(m), short(c)))
1747 del neededmanifests
1747 del neededmanifests
1748
1748
1749 for f in filenodes:
1749 for f in filenodes:
1750 if f not in filelinkrevs:
1750 if f not in filelinkrevs:
1751 err(_("file %s in manifest but not in changesets") % f)
1751 err(_("file %s in manifest but not in changesets") % f)
1752
1752
1753 for f in filelinkrevs:
1753 for f in filelinkrevs:
1754 if f not in filenodes:
1754 if f not in filenodes:
1755 err(_("file %s in changeset but not in manifest") % f)
1755 err(_("file %s in changeset but not in manifest") % f)
1756
1756
1757 self.ui.status(_("checking files\n"))
1757 self.ui.status(_("checking files\n"))
1758 ff = filenodes.keys()
1758 ff = filenodes.keys()
1759 ff.sort()
1759 ff.sort()
1760 for f in ff:
1760 for f in ff:
1761 if f == "/dev/null":
1761 if f == "/dev/null":
1762 continue
1762 continue
1763 files += 1
1763 files += 1
1764 fl = self.file(f)
1764 fl = self.file(f)
1765 d = fl.checksize()
1765 d = fl.checksize()
1766 if d:
1766 if d:
1767 err(_("%s file data short %d bytes") % (f, d))
1767 err(_("%s file data short %d bytes") % (f, d))
1768
1768
1769 nodes = {nullid: 1}
1769 nodes = {nullid: 1}
1770 seen = {}
1770 seen = {}
1771 for i in range(fl.count()):
1771 for i in range(fl.count()):
1772 revisions += 1
1772 revisions += 1
1773 n = fl.node(i)
1773 n = fl.node(i)
1774
1774
1775 if n in seen:
1775 if n in seen:
1776 err(_("%s: duplicate revision %d") % (f, i))
1776 err(_("%s: duplicate revision %d") % (f, i))
1777 if n not in filenodes[f]:
1777 if n not in filenodes[f]:
1778 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1778 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1779 else:
1779 else:
1780 del filenodes[f][n]
1780 del filenodes[f][n]
1781
1781
1782 flr = fl.linkrev(n)
1782 flr = fl.linkrev(n)
1783 if flr not in filelinkrevs[f]:
1783 if flr not in filelinkrevs[f]:
1784 err(_("%s:%s points to unexpected changeset %d")
1784 err(_("%s:%s points to unexpected changeset %d")
1785 % (f, short(n), flr))
1785 % (f, short(n), flr))
1786 else:
1786 else:
1787 filelinkrevs[f].remove(flr)
1787 filelinkrevs[f].remove(flr)
1788
1788
1789 # verify contents
1789 # verify contents
1790 try:
1790 try:
1791 t = fl.read(n)
1791 t = fl.read(n)
1792 except KeyboardInterrupt:
1792 except KeyboardInterrupt:
1793 self.ui.warn(_("interrupted"))
1793 self.ui.warn(_("interrupted"))
1794 raise
1794 raise
1795 except Exception, inst:
1795 except Exception, inst:
1796 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1796 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1797
1797
1798 # verify parents
1798 # verify parents
1799 (p1, p2) = fl.parents(n)
1799 (p1, p2) = fl.parents(n)
1800 if p1 not in nodes:
1800 if p1 not in nodes:
1801 err(_("file %s:%s unknown parent 1 %s") %
1801 err(_("file %s:%s unknown parent 1 %s") %
1802 (f, short(n), short(p1)))
1802 (f, short(n), short(p1)))
1803 if p2 not in nodes:
1803 if p2 not in nodes:
1804 err(_("file %s:%s unknown parent 2 %s") %
1804 err(_("file %s:%s unknown parent 2 %s") %
1805 (f, short(n), short(p1)))
1805 (f, short(n), short(p1)))
1806 nodes[n] = 1
1806 nodes[n] = 1
1807
1807
1808 # cross-check
1808 # cross-check
1809 for node in filenodes[f]:
1809 for node in filenodes[f]:
1810 err(_("node %s in manifests not in %s") % (hex(node), f))
1810 err(_("node %s in manifests not in %s") % (hex(node), f))
1811
1811
1812 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1812 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1813 (files, changesets, revisions))
1813 (files, changesets, revisions))
1814
1814
1815 if errors[0]:
1815 if errors[0]:
1816 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1816 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1817 return 1
1817 return 1
General Comments 0
You need to be logged in to leave comments. Login now