##// 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 1 #!/usr/bin/env python
2 2 #
3 3 # This software may be used and distributed according to the terms
4 4 # of the GNU General Public License, incorporated herein by reference.
5 5
6 6 from mercurial.demandload import demandload
7 7 demandload(globals(), "os sys sets")
8 8 from mercurial import hg
9 9
10 10 versionstr = "0.0.3"
11 11
12 12 def lookup_rev(ui, repo, rev=None):
13 13 """returns rev or the checked-out revision if rev is None"""
14 14 if not rev is None:
15 15 return repo.lookup(rev)
16 16 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
17 17 if len(parents) != 1:
18 18 ui.warn("unexpected number of parents\n")
19 19 ui.warn("please commit or revert\n")
20 20 sys.exit(1)
21 21 return parents.pop()
22 22
23 23 def check_clean(ui, repo):
24 c, a, d, u = repo.changes()
25 if c or a or d:
24 modified, added, removed, unknown = repo.changes()
25 if modified or added or removed:
26 26 ui.warn("Repository is not clean, please commit or revert\n")
27 27 sys.exit(1)
28 28
29 29 class bisect(object):
30 30 """dichotomic search in the DAG of changesets"""
31 31 def __init__(self, ui, repo):
32 32 self.repo = repo
33 33 self.path = os.path.join(repo.join(""), "bisect")
34 34 self.ui = ui
35 35 self.goodrevs = []
36 36 self.badrev = None
37 37 self.good_dirty = 0
38 38 self.bad_dirty = 0
39 39 self.good_path = os.path.join(self.path, "good")
40 40 self.bad_path = os.path.join(self.path, "bad")
41 41
42 42 s = self.good_path
43 43 if os.path.exists(s):
44 44 self.goodrevs = self.repo.opener(s).read().splitlines()
45 45 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
46 46 s = self.bad_path
47 47 if os.path.exists(s):
48 48 r = self.repo.opener(s).read().splitlines()
49 49 if r:
50 50 self.badrev = hg.bin(r.pop(0))
51 51
52 52 def __del__(self):
53 53 if not os.path.isdir(self.path):
54 54 return
55 55 f = self.repo.opener(self.good_path, "w")
56 56 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
57 57 if len(self.goodrevs) > 0:
58 58 f.write("\n")
59 59 f = self.repo.opener(self.bad_path, "w")
60 60 if self.badrev:
61 61 f.write(hg.hex(self.badrev) + "\n")
62 62
63 63 def init(self):
64 64 """start a new bisection"""
65 65 if os.path.isdir(self.path):
66 66 self.ui.warn("bisect directory already exists\n")
67 67 return 1
68 68 os.mkdir(self.path)
69 69 check_clean(self.ui, self.repo)
70 70 return 0
71 71
72 72 def reset(self):
73 73 """finish a bisection"""
74 74 if os.path.isdir(self.path):
75 75 sl = [self.bad_path, self.good_path]
76 76 for s in sl:
77 77 if os.path.exists(s):
78 78 os.unlink(s)
79 79 os.rmdir(self.path)
80 80 # Not sure about this
81 81 #self.ui.write("Going back to tip\n")
82 82 #self.repo.update(self.repo.changelog.tip())
83 83 return 1
84 84
85 85 def num_ancestors(self, head=None, stop=None):
86 86 """
87 87 returns a dict with the mapping:
88 88 node -> number of ancestors (self included)
89 89 for all nodes who are ancestor of head and
90 90 not in stop.
91 91 """
92 92 if head is None:
93 93 head = self.badrev
94 94 return self.__ancestors_and_nb_ancestors(head, stop)[1]
95 95
96 96 def ancestors(self, head=None, stop=None):
97 97 """
98 98 returns the set of the ancestors of head (self included)
99 99 who are not in stop.
100 100 """
101 101 if head is None:
102 102 head = self.badrev
103 103 return self.__ancestors_and_nb_ancestors(head, stop)[0]
104 104
105 105 def __ancestors_and_nb_ancestors(self, head, stop=None):
106 106 """
107 107 if stop is None then ancestors of goodrevs are used as
108 108 lower limit.
109 109
110 110 returns (anc, n_child) where anc is the set of the ancestors of head
111 111 and n_child is a dictionary with the following mapping:
112 112 node -> number of ancestors (self included)
113 113 """
114 114 cl = self.repo.changelog
115 115 if not stop:
116 116 stop = sets.Set([])
117 117 for g in reversed(self.goodrevs):
118 118 if g in stop:
119 119 continue
120 120 stop.update(cl.reachable(g))
121 121 def num_children(a):
122 122 """
123 123 returns a dictionnary with the following mapping
124 124 node -> [number of children, empty set]
125 125 """
126 126 d = {a: [0, sets.Set([])]}
127 127 for i in xrange(cl.rev(a)+1):
128 128 n = cl.node(i)
129 129 if not d.has_key(n):
130 130 d[n] = [0, sets.Set([])]
131 131 parents = [p for p in cl.parents(n) if p != hg.nullid]
132 132 for p in parents:
133 133 d[p][0] += 1
134 134 return d
135 135
136 136 if head in stop:
137 137 self.ui.warn("Unconsistent state, %s is good and bad\n"
138 138 % hg.hex(head))
139 139 sys.exit(1)
140 140 n_child = num_children(head)
141 141 for i in xrange(cl.rev(head)+1):
142 142 n = cl.node(i)
143 143 parents = [p for p in cl.parents(n) if p != hg.nullid]
144 144 for p in parents:
145 145 n_child[p][0] -= 1
146 146 if not n in stop:
147 147 n_child[n][1].union_update(n_child[p][1])
148 148 if n_child[p][0] == 0:
149 149 n_child[p] = len(n_child[p][1])
150 150 if not n in stop:
151 151 n_child[n][1].add(n)
152 152 if n_child[n][0] == 0:
153 153 if n == head:
154 154 anc = n_child[n][1]
155 155 n_child[n] = len(n_child[n][1])
156 156 return anc, n_child
157 157
158 158 def next(self):
159 159 if not self.badrev:
160 160 self.ui.warn("You should give at least one bad\n")
161 161 sys.exit(1)
162 162 if not self.goodrevs:
163 163 self.ui.warn("No good revision given\n")
164 164 self.ui.warn("Assuming the first revision is good\n")
165 165 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(self.badrev)
166 166 tot = len(ancestors)
167 167 if tot == 1:
168 168 if ancestors.pop() != self.badrev:
169 169 self.ui.warn("Could not find the first bad revision\n")
170 170 sys.exit(1)
171 171 self.ui.write(
172 172 "The first bad revision is : %s\n" % hg.hex(self.badrev))
173 173 sys.exit(0)
174 174 self.ui.write("%d revisions left\n" % tot)
175 175 best_rev = None
176 176 best_len = -1
177 177 for n in ancestors:
178 178 l = num_ancestors[n]
179 179 l = min(l, tot - l)
180 180 if l > best_len:
181 181 best_len = l
182 182 best_rev = n
183 183 return best_rev
184 184
185 185 def autonext(self):
186 186 """find and update to the next revision to test"""
187 187 check_clean(self.ui, self.repo)
188 188 rev = self.next()
189 189 self.ui.write("Now testing %s\n" % hg.hex(rev))
190 190 return self.repo.update(rev, allow=True, force=True)
191 191
192 192 def good(self, rev):
193 193 self.goodrevs.append(rev)
194 194
195 195 def autogood(self, rev=None):
196 196 """mark revision as good and update to the next revision to test"""
197 197 check_clean(self.ui, self.repo)
198 198 rev = lookup_rev(self.ui, self.repo, rev)
199 199 self.good(rev)
200 200 if self.badrev:
201 201 self.autonext()
202 202
203 203 def bad(self, rev):
204 204 self.badrev = rev
205 205
206 206 def autobad(self, rev=None):
207 207 """mark revision as bad and update to the next revision to test"""
208 208 check_clean(self.ui, self.repo)
209 209 rev = lookup_rev(self.ui, self.repo, rev)
210 210 self.bad(rev)
211 211 if self.goodrevs:
212 212 self.autonext()
213 213
214 214 # should we put it in the class ?
215 215 def test(ui, repo, rev):
216 216 """test the bisection code"""
217 217 b = bisect(ui, repo)
218 218 rev = repo.lookup(rev)
219 219 ui.write("testing with rev %s\n" % hg.hex(rev))
220 220 anc = b.ancestors()
221 221 while len(anc) > 1:
222 222 if not rev in anc:
223 223 ui.warn("failure while bisecting\n")
224 224 sys.exit(1)
225 225 ui.write("it worked :)\n")
226 226 new_rev = b.next()
227 227 ui.write("choosing if good or bad\n")
228 228 if rev in b.ancestors(head=new_rev):
229 229 b.bad(new_rev)
230 230 ui.write("it is bad\n")
231 231 else:
232 232 b.good(new_rev)
233 233 ui.write("it is good\n")
234 234 anc = b.ancestors()
235 235 repo.update(new_rev, allow=True, force=True)
236 236 for v in anc:
237 237 if v != rev:
238 238 ui.warn("fail to found cset! :(\n")
239 239 return 1
240 240 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
241 241 ui.write("Everything is ok :)\n")
242 242 return 0
243 243
244 244 def bisect_run(ui, repo, cmd=None, *args):
245 245 """bisect extension: dichotomic search in the DAG of changesets
246 246 for subcommands see "hg bisect help\"
247 247 """
248 248 def help_(cmd=None, *args):
249 249 """show help for a given bisect subcommand or all subcommands"""
250 250 cmdtable = bisectcmdtable
251 251 if cmd:
252 252 doc = cmdtable[cmd][0].__doc__
253 253 synopsis = cmdtable[cmd][2]
254 254 ui.write(synopsis + "\n")
255 255 ui.write("\n" + doc + "\n")
256 256 return
257 257 ui.write("list of subcommands for the bisect extension\n\n")
258 258 cmds = cmdtable.keys()
259 259 cmds.sort()
260 260 m = max([len(c) for c in cmds])
261 261 for cmd in cmds:
262 262 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
263 263 ui.write(" %-*s %s\n" % (m, cmd, doc))
264 264
265 265 b = bisect(ui, repo)
266 266 bisectcmdtable = {
267 267 "init": (b.init, 0, "hg bisect init"),
268 268 "bad": (b.autobad, 1, "hg bisect bad [<rev>]"),
269 269 "good": (b.autogood, 1, "hg bisect good [<rev>]"),
270 270 "next": (b.autonext, 0, "hg bisect next"),
271 271 "reset": (b.reset, 0, "hg bisect reset"),
272 272 "help": (help_, 1, "hg bisect help [<subcommand>]"),
273 273 }
274 274
275 275 if not bisectcmdtable.has_key(cmd):
276 276 ui.warn("bisect: Unknown sub-command\n")
277 277 return help_()
278 278 if len(args) > bisectcmdtable[cmd][1]:
279 279 ui.warn("bisect: Too many arguments\n")
280 280 return help_()
281 281 return bisectcmdtable[cmd][0](*args)
282 282
283 283 cmdtable = {
284 284 "bisect": (bisect_run, [],
285 285 "hg bisect [help|init|reset|next|good|bad]"),
286 286 #"bisect-test": (test, [], "hg bisect-test rev"),
287 287 }
@@ -1,339 +1,339
1 1 # Minimal support for git commands on an hg repository
2 2 #
3 3 # Copyright 2005 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import time, sys, signal, os
9 9 from mercurial import hg, mdiff, fancyopts, commands, ui, util
10 10
11 11 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
12 12 changes=None, text=False):
13 13 def date(c):
14 14 return time.asctime(time.gmtime(c[2][0]))
15 15
16 16 if not changes:
17 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
18 else:
19 (c, a, d, u) = changes
17 changes = repo.changes(node1, node2, files, match=match)
18 modified, added, removed, unknown = changes
20 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 24 return
25 25
26 26 if node2:
27 27 change = repo.changelog.read(node2)
28 28 mmap2 = repo.manifest.read(change[0])
29 29 date2 = date(change)
30 30 def read(f):
31 31 return repo.file(f).read(mmap2[f])
32 32 else:
33 33 date2 = time.asctime()
34 34 if not node1:
35 35 node1 = repo.dirstate.parents()[0]
36 36 def read(f):
37 37 return repo.wfile(f).read()
38 38
39 39 change = repo.changelog.read(node1)
40 40 mmap = repo.manifest.read(change[0])
41 41 date1 = date(change)
42 42
43 for f in c:
43 for f in modified:
44 44 to = None
45 45 if f in mmap:
46 46 to = repo.file(f).read(mmap[f])
47 47 tn = read(f)
48 48 fp.write("diff --git a/%s b/%s\n" % (f, f))
49 49 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
50 for f in a:
50 for f in added:
51 51 to = None
52 52 tn = read(f)
53 53 fp.write("diff --git /dev/null b/%s\n" % (f))
54 54 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
55 for f in d:
55 for f in removed:
56 56 to = repo.file(f).read(mmap[f])
57 57 tn = None
58 58 fp.write("diff --git a/%s /dev/null\n" % (f))
59 59 fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
60 60
61 61 def difftree(ui, repo, node1=None, node2=None, **opts):
62 62 """diff trees from two commits"""
63 63 def __difftree(repo, node1, node2):
64 64 def date(c):
65 65 return time.asctime(time.gmtime(c[2][0]))
66 66
67 67 if node2:
68 68 change = repo.changelog.read(node2)
69 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 71 def read(f): return repo.file(f).read(mmap2[f])
72 72 date2 = date(change)
73 73 else:
74 74 date2 = time.asctime()
75 (c, a, d, u) = repo.changes(node1, None)
75 modified, added, removed, unknown = repo.changes(node1)
76 76 if not node1:
77 77 node1 = repo.dirstate.parents()[0]
78 78 def read(f): return file(os.path.join(repo.root, f)).read()
79 79
80 80 change = repo.changelog.read(node1)
81 81 mmap = repo.manifest.read(change[0])
82 82 date1 = date(change)
83 83 empty = "0" * 40;
84 84
85 for f in c:
85 for f in modified:
86 86 # TODO get file permissions
87 87 print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
88 88 hg.hex(mmap2[f]), f, f)
89 for f in a:
89 for f in added:
90 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 92 print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
93 93 ##
94 94
95 95 while True:
96 96 if opts['stdin']:
97 97 try:
98 98 line = raw_input().split(' ')
99 99 node1 = line[0]
100 100 if len(line) > 1:
101 101 node2 = line[1]
102 102 else:
103 103 node2 = None
104 104 except EOFError:
105 105 break
106 106 node1 = repo.lookup(node1)
107 107 if node2:
108 108 node2 = repo.lookup(node2)
109 109 else:
110 110 node2 = node1
111 111 node1 = repo.changelog.parents(node1)[0]
112 112 if opts['patch']:
113 113 if opts['pretty']:
114 114 catcommit(repo, node2, "")
115 115 dodiff(sys.stdout, ui, repo, node1, node2)
116 116 else:
117 117 __difftree(repo, node1, node2)
118 118 if not opts['stdin']:
119 119 break
120 120
121 121 def catcommit(repo, n, prefix, changes=None):
122 122 nlprefix = '\n' + prefix;
123 123 (p1, p2) = repo.changelog.parents(n)
124 124 (h, h1, h2) = map(hg.hex, (n, p1, p2))
125 125 (i1, i2) = map(repo.changelog.rev, (p1, p2))
126 126 if not changes:
127 127 changes = repo.changelog.read(n)
128 128 print "tree %s" % (hg.hex(changes[0]))
129 129 if i1 != -1: print "parent %s" % (h1)
130 130 if i2 != -1: print "parent %s" % (h2)
131 131 date_ar = changes[2]
132 132 date = int(float(date_ar[0]))
133 133 lines = changes[4].splitlines()
134 134 if lines[-1].startswith('committer:'):
135 135 committer = lines[-1].split(': ')[1].rstrip()
136 136 else:
137 137 committer = "%s %s %s" % (changes[1], date, date_ar[1])
138 138
139 139 print "author %s %s %s" % (changes[1], date, date_ar[1])
140 140 print "committer %s" % (committer)
141 141 print ""
142 142 if prefix != "":
143 143 print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
144 144 else:
145 145 print changes[4]
146 146 if prefix:
147 147 sys.stdout.write('\0')
148 148
149 149 def base(ui, repo, node1, node2):
150 150 """Output common ancestor information"""
151 151 node1 = repo.lookup(node1)
152 152 node2 = repo.lookup(node2)
153 153 n = repo.changelog.ancestor(node1, node2)
154 154 print hg.hex(n)
155 155
156 156 def catfile(ui, repo, type=None, r=None, **opts):
157 157 """cat a specific revision"""
158 158 # in stdin mode, every line except the commit is prefixed with two
159 159 # spaces. This way the our caller can find the commit without magic
160 160 # strings
161 161 #
162 162 prefix = ""
163 163 if opts['stdin']:
164 164 try:
165 165 (type, r) = raw_input().split(' ');
166 166 prefix = " "
167 167 except EOFError:
168 168 return
169 169
170 170 else:
171 171 if not type or not r:
172 172 ui.warn("cat-file: type or revision not supplied\n")
173 173 commands.help_(ui, 'cat-file')
174 174
175 175 while r:
176 176 if type != "commit":
177 177 sys.stderr.write("aborting hg cat-file only understands commits\n")
178 178 sys.exit(1);
179 179 n = repo.lookup(r)
180 180 catcommit(repo, n, prefix)
181 181 if opts['stdin']:
182 182 try:
183 183 (type, r) = raw_input().split(' ');
184 184 except EOFError:
185 185 break
186 186 else:
187 187 break
188 188
189 189 # git rev-tree is a confusing thing. You can supply a number of
190 190 # commit sha1s on the command line, and it walks the commit history
191 191 # telling you which commits are reachable from the supplied ones via
192 192 # a bitmask based on arg position.
193 193 # you can specify a commit to stop at by starting the sha1 with ^
194 194 def revtree(args, repo, full="tree", maxnr=0, parents=False):
195 195 def chlogwalk():
196 196 ch = repo.changelog
197 197 count = ch.count()
198 198 i = count
199 199 l = [0] * 100
200 200 chunk = 100
201 201 while True:
202 202 if chunk > i:
203 203 chunk = i
204 204 i = 0
205 205 else:
206 206 i -= chunk
207 207
208 208 for x in xrange(0, chunk):
209 209 if i + x >= count:
210 210 l[chunk - x:] = [0] * (chunk - x)
211 211 break
212 212 if full != None:
213 213 l[x] = ch.read(ch.node(i + x))
214 214 else:
215 215 l[x] = 1
216 216 for x in xrange(chunk-1, -1, -1):
217 217 if l[x] != 0:
218 218 yield (i + x, full != None and l[x] or None)
219 219 if i == 0:
220 220 break
221 221
222 222 # calculate and return the reachability bitmask for sha
223 223 def is_reachable(ar, reachable, sha):
224 224 if len(ar) == 0:
225 225 return 1
226 226 mask = 0
227 227 for i in range(len(ar)):
228 228 if sha in reachable[i]:
229 229 mask |= 1 << i
230 230
231 231 return mask
232 232
233 233 reachable = []
234 234 stop_sha1 = []
235 235 want_sha1 = []
236 236 count = 0
237 237
238 238 # figure out which commits they are asking for and which ones they
239 239 # want us to stop on
240 240 for i in range(len(args)):
241 241 if args[i].startswith('^'):
242 242 s = repo.lookup(args[i][1:])
243 243 stop_sha1.append(s)
244 244 want_sha1.append(s)
245 245 elif args[i] != 'HEAD':
246 246 want_sha1.append(repo.lookup(args[i]))
247 247
248 248 # calculate the graph for the supplied commits
249 249 for i in range(len(want_sha1)):
250 250 reachable.append({});
251 251 n = want_sha1[i];
252 252 visit = [n];
253 253 reachable[i][n] = 1
254 254 while visit:
255 255 n = visit.pop(0)
256 256 if n in stop_sha1:
257 257 continue
258 258 for p in repo.changelog.parents(n):
259 259 if p not in reachable[i]:
260 260 reachable[i][p] = 1
261 261 visit.append(p)
262 262 if p in stop_sha1:
263 263 continue
264 264
265 265 # walk the repository looking for commits that are in our
266 266 # reachability graph
267 267 #for i in range(repo.changelog.count()-1, -1, -1):
268 268 for i, changes in chlogwalk():
269 269 n = repo.changelog.node(i)
270 270 mask = is_reachable(want_sha1, reachable, n)
271 271 if mask:
272 272 parentstr = ""
273 273 if parents:
274 274 pp = repo.changelog.parents(n)
275 275 if pp[0] != hg.nullid:
276 276 parentstr += " " + hg.hex(pp[0])
277 277 if pp[1] != hg.nullid:
278 278 parentstr += " " + hg.hex(pp[1])
279 279 if not full:
280 280 print hg.hex(n) + parentstr
281 281 elif full is "commit":
282 282 print hg.hex(n) + parentstr
283 283 catcommit(repo, n, ' ', changes)
284 284 else:
285 285 (p1, p2) = repo.changelog.parents(n)
286 286 (h, h1, h2) = map(hg.hex, (n, p1, p2))
287 287 (i1, i2) = map(repo.changelog.rev, (p1, p2))
288 288
289 289 date = changes[2][0]
290 290 print "%s %s:%s" % (date, h, mask),
291 291 mask = is_reachable(want_sha1, reachable, p1)
292 292 if i1 != -1 and mask > 0:
293 293 print "%s:%s " % (h1, mask),
294 294 mask = is_reachable(want_sha1, reachable, p2)
295 295 if i2 != -1 and mask > 0:
296 296 print "%s:%s " % (h2, mask),
297 297 print ""
298 298 if maxnr and count >= maxnr:
299 299 break
300 300 count += 1
301 301
302 302 # git rev-list tries to order things by date, and has the ability to stop
303 303 # at a given commit without walking the whole repo. TODO add the stop
304 304 # parameter
305 305 def revlist(ui, repo, *revs, **opts):
306 306 """print revisions"""
307 307 if opts['header']:
308 308 full = "commit"
309 309 else:
310 310 full = None
311 311 copy = [x for x in revs]
312 312 revtree(copy, repo, full, opts['max_count'], opts['parents'])
313 313
314 314 def view(ui, repo, *etc):
315 315 "start interactive history viewer"
316 316 os.chdir(repo.root)
317 317 os.system(ui.config("hgk", "path", "hgk") + " " + " ".join(etc))
318 318
319 319 cmdtable = {
320 320 "view": (view, [], 'hg view'),
321 321 "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
322 322 ('r', 'recursive', None, 'recursive'),
323 323 ('P', 'pretty', None, 'pretty'),
324 324 ('s', 'stdin', None, 'stdin'),
325 325 ('C', 'copy', None, 'detect copies'),
326 326 ('S', 'search', "", 'search')],
327 327 "hg git-diff-tree [options] node1 node2"),
328 328 "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
329 329 "hg debug-cat-file [options] type file"),
330 330 "debug-merge-base": (base, [], "hg debug-merge-base node node"),
331 331 "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
332 332 ('t', 'topo-order', None, 'topo-order'),
333 333 ('p', 'parents', None, 'parents'),
334 334 ('n', 'max-count', 0, 'max-count')],
335 335 "hg debug-rev-list [options] revs"),
336 336 }
337 337
338 338 def reposetup(ui, repo):
339 339 pass
@@ -1,207 +1,206
1 1 import os, tempfile, binascii, errno
2 2 from mercurial import util
3 3 from mercurial import node as hgnode
4 4
5 5 class gpg:
6 6 def __init__(self, path, key=None):
7 7 self.path = path
8 8 self.key = (key and " --local-user \"%s\"" % key) or ""
9 9
10 10 def sign(self, data):
11 11 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
12 12 return util.filter(data, gpgcmd)
13 13
14 14 def verify(self, data, sig):
15 15 """ returns of the good and bad signatures"""
16 16 try:
17 17 fd, sigfile = tempfile.mkstemp(prefix="hggpgsig")
18 18 fp = os.fdopen(fd, 'wb')
19 19 fp.write(sig)
20 20 fp.close()
21 21 fd, datafile = tempfile.mkstemp(prefix="hggpgdata")
22 22 fp = os.fdopen(fd, 'wb')
23 23 fp.write(data)
24 24 fp.close()
25 25 gpgcmd = "%s --logger-fd 1 --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
26 26 #gpgcmd = "%s --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
27 27 ret = util.filter("", gpgcmd)
28 28 except:
29 29 for f in (sigfile, datafile):
30 30 try:
31 31 if f: os.unlink(f)
32 32 except: pass
33 33 raise
34 34 keys = []
35 35 key, fingerprint = None, None
36 36 err = ""
37 37 for l in ret.splitlines():
38 38 # see DETAILS in the gnupg documentation
39 39 # filter the logger output
40 40 if not l.startswith("[GNUPG:]"):
41 41 continue
42 42 l = l[9:]
43 43 if l.startswith("ERRSIG"):
44 44 err = "error while verifying signature"
45 45 break
46 46 elif l.startswith("VALIDSIG"):
47 47 # fingerprint of the primary key
48 48 fingerprint = l.split()[10]
49 49 elif (l.startswith("GOODSIG") or
50 50 l.startswith("EXPSIG") or
51 51 l.startswith("EXPKEYSIG") or
52 52 l.startswith("BADSIG")):
53 53 if key is not None:
54 54 keys.append(key + [fingerprint])
55 55 key = l.split(" ", 2)
56 56 fingerprint = None
57 57 if err:
58 58 return err, []
59 59 if key is not None:
60 60 keys.append(key + [fingerprint])
61 61 return err, keys
62 62
63 63 def newgpg(ui, **opts):
64 64 gpgpath = ui.config("gpg", "cmd", "gpg")
65 65 gpgkey = opts.get('key')
66 66 if not gpgkey:
67 67 gpgkey = ui.config("gpg", "key", None)
68 68 return gpg(gpgpath, gpgkey)
69 69
70 70 def check(ui, repo, rev):
71 71 """verify all the signatures there may be for a particular revision"""
72 72 mygpg = newgpg(ui)
73 73 rev = repo.lookup(rev)
74 74 hexrev = hgnode.hex(rev)
75 75 keys = []
76 76
77 77 def addsig(fn, ln, l):
78 78 if not l: return
79 79 n, v, sig = l.split(" ", 2)
80 80 if n == hexrev:
81 81 data = node2txt(repo, rev, v)
82 82 sig = binascii.a2b_base64(sig)
83 83 err, k = mygpg.verify(data, sig)
84 84 if not err:
85 85 keys.append((k, fn, ln))
86 86 else:
87 87 ui.warn("%s:%d %s\n" % (fn, ln , err))
88 88
89 89 fl = repo.file(".hgsigs")
90 90 h = fl.heads()
91 91 h.reverse()
92 92 # read the heads
93 93 for r in h:
94 94 ln = 1
95 95 for l in fl.read(r).splitlines():
96 96 addsig(".hgsigs|%s" % hgnode.short(r), ln, l)
97 97 ln +=1
98 98 try:
99 99 # read local signatures
100 100 ln = 1
101 101 f = repo.opener("localsigs")
102 102 for l in f:
103 103 addsig("localsigs", ln, l)
104 104 ln +=1
105 105 except IOError:
106 106 pass
107 107
108 108 if not keys:
109 109 ui.write("%s not signed\n" % hgnode.short(rev))
110 110 return
111 111 valid = []
112 112 # warn for expired key and/or sigs
113 113 for k, fn, ln in keys:
114 114 prefix = "%s:%d" % (fn, ln)
115 115 for key in k:
116 116 if key[0] == "BADSIG":
117 117 ui.write("%s Bad signature from \"%s\"\n" % (prefix, key[2]))
118 118 continue
119 119 if key[0] == "EXPSIG":
120 120 ui.write("%s Note: Signature has expired"
121 121 " (signed by: \"%s\")\n" % (prefix, key[2]))
122 122 elif key[0] == "EXPKEYSIG":
123 123 ui.write("%s Note: This key has expired"
124 124 " (signed by: \"%s\")\n" % (prefix, key[2]))
125 125 valid.append((key[1], key[2], key[3]))
126 126 # print summary
127 127 ui.write("%s is signed by:\n" % hgnode.short(rev))
128 128 for keyid, user, fingerprint in valid:
129 129 role = getrole(ui, fingerprint)
130 130 ui.write(" %s (%s)\n" % (user, role))
131 131
132 132 def getrole(ui, fingerprint):
133 133 return ui.config("gpg", fingerprint, "no role defined")
134 134
135 135 def sign(ui, repo, *revs, **opts):
136 136 """add a signature for the current tip or a given revision"""
137 137 mygpg = newgpg(ui, **opts)
138 138 sigver = "0"
139 139 sigmessage = ""
140 140 if revs:
141 141 nodes = [repo.lookup(n) for n in revs]
142 142 else:
143 143 nodes = [repo.changelog.tip()]
144 144
145 145 for n in nodes:
146 146 hexnode = hgnode.hex(n)
147 147 ui.write("Signing %d:%s\n" % (repo.changelog.rev(n),
148 148 hgnode.short(n)))
149 149 # build data
150 150 data = node2txt(repo, n, sigver)
151 151 sig = mygpg.sign(data)
152 152 if not sig:
153 153 raise util.Abort("Error while signing")
154 154 sig = binascii.b2a_base64(sig)
155 155 sig = sig.replace("\n", "")
156 156 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
157 157
158 158 # write it
159 159 if opts['local']:
160 160 repo.opener("localsigs", "ab").write(sigmessage)
161 161 return
162 162
163 (c, a, d, u) = repo.changes()
164 for x in (c, a, d, u):
163 for x in repo.changes():
165 164 if ".hgsigs" in x and not opts["force"]:
166 165 raise util.Abort("working copy of .hgsigs is changed "
167 166 "(please commit .hgsigs manually"
168 167 "or use --force)")
169 168
170 169 repo.wfile(".hgsigs", "ab").write(sigmessage)
171 170
172 171 if repo.dirstate.state(".hgsigs") == '?':
173 172 repo.add([".hgsigs"])
174 173
175 174 if opts["no_commit"]:
176 175 return
177 176
178 177 message = opts['message']
179 178 if not message:
180 179 message = "\n".join(["Added signature for changeset %s" % hgnode.hex(n)
181 180 for n in nodes])
182 181 try:
183 182 repo.commit([".hgsigs"], message, opts['user'], opts['date'])
184 183 except ValueError, inst:
185 184 raise util.Abort(str(inst))
186 185
187 186 def node2txt(repo, node, ver):
188 187 """map a manifest into some text"""
189 188 if ver == "0":
190 189 return "%s\n" % hgnode.hex(node)
191 190 else:
192 191 util.Abort("unknown signature version")
193 192
194 193 cmdtable = {
195 194 "sign":
196 195 (sign,
197 196 [('l', 'local', None, "make the signature local"),
198 197 ('f', 'force', None, "sign even if the sigfile is modified"),
199 198 ('', 'no-commit', None, "do not commit the sigfile after signing"),
200 199 ('m', 'message', "", "commit message"),
201 200 ('d', 'date', "", "date code"),
202 201 ('u', 'user', "", "user"),
203 202 ('k', 'key', "", "the key id to sign with")],
204 203 "hg sign [OPTION]... REVISIONS"),
205 204 "sigcheck": (check, [], 'hg sigcheck REVISION')
206 205 }
207 206
@@ -1,2755 +1,2757
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 12 demandload(globals(), "fancyopts ui hg util lock revlog")
13 13 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
14 14 demandload(globals(), "errno socket version struct atexit sets bz2")
15 15
16 16 class UnknownCommand(Exception):
17 17 """Exception raised if command is not in the command table."""
18 18 class AmbiguousCommand(Exception):
19 19 """Exception raised if command shortcut matches more than one command."""
20 20
21 21 def filterfiles(filters, files):
22 22 l = [x for x in files if x in filters]
23 23
24 24 for t in filters:
25 25 if t and t[-1] != "/":
26 26 t += "/"
27 27 l += [x for x in files if x.startswith(t)]
28 28 return l
29 29
30 30 def relpath(repo, args):
31 31 cwd = repo.getcwd()
32 32 if cwd:
33 33 return [util.normpath(os.path.join(cwd, x)) for x in args]
34 34 return args
35 35
36 36 def matchpats(repo, pats=[], opts={}, head=''):
37 37 cwd = repo.getcwd()
38 38 if not pats and cwd:
39 39 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
40 40 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
41 41 cwd = ''
42 42 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
43 43 opts.get('exclude'), head) + (cwd,)
44 44
45 45 def makewalk(repo, pats, opts, node=None, head=''):
46 46 files, matchfn, anypats, cwd = matchpats(repo, pats, opts, head)
47 47 exact = dict(zip(files, files))
48 48 def walk():
49 49 for src, fn in repo.walk(node=node, files=files, match=matchfn):
50 50 yield src, fn, util.pathto(cwd, fn), fn in exact
51 51 return files, matchfn, walk()
52 52
53 53 def walk(repo, pats, opts, node=None, head=''):
54 54 files, matchfn, results = makewalk(repo, pats, opts, node, head)
55 55 for r in results:
56 56 yield r
57 57
58 58 def walkchangerevs(ui, repo, pats, opts):
59 59 '''Iterate over files and the revs they changed in.
60 60
61 61 Callers most commonly need to iterate backwards over the history
62 62 it is interested in. Doing so has awful (quadratic-looking)
63 63 performance, so we use iterators in a "windowed" way.
64 64
65 65 We walk a window of revisions in the desired order. Within the
66 66 window, we first walk forwards to gather data, then in the desired
67 67 order (usually backwards) to display it.
68 68
69 69 This function returns an (iterator, getchange, matchfn) tuple. The
70 70 getchange function returns the changelog entry for a numeric
71 71 revision. The iterator yields 3-tuples. They will be of one of
72 72 the following forms:
73 73
74 74 "window", incrementing, lastrev: stepping through a window,
75 75 positive if walking forwards through revs, last rev in the
76 76 sequence iterated over - use to reset state for the current window
77 77
78 78 "add", rev, fns: out-of-order traversal of the given file names
79 79 fns, which changed during revision rev - use to gather data for
80 80 possible display
81 81
82 82 "iter", rev, None: in-order traversal of the revs earlier iterated
83 83 over with "add" - use to display data'''
84 84
85 85 files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
86 86
87 87 if repo.changelog.count() == 0:
88 88 return [], False, matchfn
89 89
90 90 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
91 91 wanted = {}
92 92 slowpath = anypats
93 93 window = 300
94 94 fncache = {}
95 95
96 96 chcache = {}
97 97 def getchange(rev):
98 98 ch = chcache.get(rev)
99 99 if ch is None:
100 100 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
101 101 return ch
102 102
103 103 if not slowpath and not files:
104 104 # No files, no patterns. Display all revs.
105 105 wanted = dict(zip(revs, revs))
106 106 if not slowpath:
107 107 # Only files, no patterns. Check the history of each file.
108 108 def filerevgen(filelog):
109 109 for i in xrange(filelog.count() - 1, -1, -window):
110 110 revs = []
111 111 for j in xrange(max(0, i - window), i + 1):
112 112 revs.append(filelog.linkrev(filelog.node(j)))
113 113 revs.reverse()
114 114 for rev in revs:
115 115 yield rev
116 116
117 117 minrev, maxrev = min(revs), max(revs)
118 118 for file in files:
119 119 filelog = repo.file(file)
120 120 # A zero count may be a directory or deleted file, so
121 121 # try to find matching entries on the slow path.
122 122 if filelog.count() == 0:
123 123 slowpath = True
124 124 break
125 125 for rev in filerevgen(filelog):
126 126 if rev <= maxrev:
127 127 if rev < minrev:
128 128 break
129 129 fncache.setdefault(rev, [])
130 130 fncache[rev].append(file)
131 131 wanted[rev] = 1
132 132 if slowpath:
133 133 # The slow path checks files modified in every changeset.
134 134 def changerevgen():
135 135 for i in xrange(repo.changelog.count() - 1, -1, -window):
136 136 for j in xrange(max(0, i - window), i + 1):
137 137 yield j, getchange(j)[3]
138 138
139 139 for rev, changefiles in changerevgen():
140 140 matches = filter(matchfn, changefiles)
141 141 if matches:
142 142 fncache[rev] = matches
143 143 wanted[rev] = 1
144 144
145 145 def iterate():
146 146 for i in xrange(0, len(revs), window):
147 147 yield 'window', revs[0] < revs[-1], revs[-1]
148 148 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
149 149 if rev in wanted]
150 150 srevs = list(nrevs)
151 151 srevs.sort()
152 152 for rev in srevs:
153 153 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
154 154 yield 'add', rev, fns
155 155 for rev in nrevs:
156 156 yield 'iter', rev, None
157 157 return iterate(), getchange, matchfn
158 158
159 159 revrangesep = ':'
160 160
161 161 def revrange(ui, repo, revs, revlog=None):
162 162 """Yield revision as strings from a list of revision specifications."""
163 163 if revlog is None:
164 164 revlog = repo.changelog
165 165 revcount = revlog.count()
166 166 def fix(val, defval):
167 167 if not val:
168 168 return defval
169 169 try:
170 170 num = int(val)
171 171 if str(num) != val:
172 172 raise ValueError
173 173 if num < 0:
174 174 num += revcount
175 175 if num < 0:
176 176 num = 0
177 177 elif num >= revcount:
178 178 raise ValueError
179 179 except ValueError:
180 180 try:
181 181 num = repo.changelog.rev(repo.lookup(val))
182 182 except KeyError:
183 183 try:
184 184 num = revlog.rev(revlog.lookup(val))
185 185 except KeyError:
186 186 raise util.Abort(_('invalid revision identifier %s'), val)
187 187 return num
188 188 seen = {}
189 189 for spec in revs:
190 190 if spec.find(revrangesep) >= 0:
191 191 start, end = spec.split(revrangesep, 1)
192 192 start = fix(start, 0)
193 193 end = fix(end, revcount - 1)
194 194 step = start > end and -1 or 1
195 195 for rev in xrange(start, end+step, step):
196 196 if rev in seen:
197 197 continue
198 198 seen[rev] = 1
199 199 yield str(rev)
200 200 else:
201 201 rev = fix(spec, None)
202 202 if rev in seen:
203 203 continue
204 204 seen[rev] = 1
205 205 yield str(rev)
206 206
207 207 def make_filename(repo, r, pat, node=None,
208 208 total=None, seqno=None, revwidth=None, pathname=None):
209 209 node_expander = {
210 210 'H': lambda: hex(node),
211 211 'R': lambda: str(r.rev(node)),
212 212 'h': lambda: short(node),
213 213 }
214 214 expander = {
215 215 '%': lambda: '%',
216 216 'b': lambda: os.path.basename(repo.root),
217 217 }
218 218
219 219 try:
220 220 if node:
221 221 expander.update(node_expander)
222 222 if node and revwidth is not None:
223 223 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
224 224 if total is not None:
225 225 expander['N'] = lambda: str(total)
226 226 if seqno is not None:
227 227 expander['n'] = lambda: str(seqno)
228 228 if total is not None and seqno is not None:
229 229 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
230 230 if pathname is not None:
231 231 expander['s'] = lambda: os.path.basename(pathname)
232 232 expander['d'] = lambda: os.path.dirname(pathname) or '.'
233 233 expander['p'] = lambda: pathname
234 234
235 235 newname = []
236 236 patlen = len(pat)
237 237 i = 0
238 238 while i < patlen:
239 239 c = pat[i]
240 240 if c == '%':
241 241 i += 1
242 242 c = pat[i]
243 243 c = expander[c]()
244 244 newname.append(c)
245 245 i += 1
246 246 return ''.join(newname)
247 247 except KeyError, inst:
248 248 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
249 249 inst.args[0])
250 250
251 251 def make_file(repo, r, pat, node=None,
252 252 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
253 253 if not pat or pat == '-':
254 254 return 'w' in mode and sys.stdout or sys.stdin
255 255 if hasattr(pat, 'write') and 'w' in mode:
256 256 return pat
257 257 if hasattr(pat, 'read') and 'r' in mode:
258 258 return pat
259 259 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
260 260 pathname),
261 261 mode)
262 262
263 263 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
264 264 changes=None, text=False):
265 265 if not changes:
266 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
267 else:
268 (c, a, d, u) = changes
266 changes = repo.changes(node1, node2, files, match=match)
267 modified, added, removed, unknown = changes
269 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 273 return
274 274
275 275 if node2:
276 276 change = repo.changelog.read(node2)
277 277 mmap2 = repo.manifest.read(change[0])
278 278 date2 = util.datestr(change[2])
279 279 def read(f):
280 280 return repo.file(f).read(mmap2[f])
281 281 else:
282 282 date2 = util.datestr()
283 283 if not node1:
284 284 node1 = repo.dirstate.parents()[0]
285 285 def read(f):
286 286 return repo.wfile(f).read()
287 287
288 288 if ui.quiet:
289 289 r = None
290 290 else:
291 291 hexfunc = ui.verbose and hex or short
292 292 r = [hexfunc(node) for node in [node1, node2] if node]
293 293
294 294 change = repo.changelog.read(node1)
295 295 mmap = repo.manifest.read(change[0])
296 296 date1 = util.datestr(change[2])
297 297
298 for f in c:
298 for f in modified:
299 299 to = None
300 300 if f in mmap:
301 301 to = repo.file(f).read(mmap[f])
302 302 tn = read(f)
303 303 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
304 for f in a:
304 for f in added:
305 305 to = None
306 306 tn = read(f)
307 307 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
308 for f in d:
308 for f in removed:
309 309 to = repo.file(f).read(mmap[f])
310 310 tn = None
311 311 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
312 312
313 313 def trimuser(ui, name, rev, revcache):
314 314 """trim the name of the user who committed a change"""
315 315 user = revcache.get(rev)
316 316 if user is None:
317 317 user = revcache[rev] = ui.shortuser(name)
318 318 return user
319 319
320 320 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
321 321 """show a single changeset or file revision"""
322 322 log = repo.changelog
323 323 if changenode is None:
324 324 changenode = log.node(rev)
325 325 elif not rev:
326 326 rev = log.rev(changenode)
327 327
328 328 if ui.quiet:
329 329 ui.write("%d:%s\n" % (rev, short(changenode)))
330 330 return
331 331
332 332 changes = log.read(changenode)
333 333 date = util.datestr(changes[2])
334 334
335 335 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
336 336 for p in log.parents(changenode)
337 337 if ui.debugflag or p != nullid]
338 338 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
339 339 parents = []
340 340
341 341 if ui.verbose:
342 342 ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
343 343 else:
344 344 ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
345 345
346 346 for tag in repo.nodetags(changenode):
347 347 ui.status(_("tag: %s\n") % tag)
348 348 for parent in parents:
349 349 ui.write(_("parent: %d:%s\n") % parent)
350 350
351 351 if brinfo and changenode in brinfo:
352 352 br = brinfo[changenode]
353 353 ui.write(_("branch: %s\n") % " ".join(br))
354 354
355 355 ui.debug(_("manifest: %d:%s\n") % (repo.manifest.rev(changes[0]),
356 356 hex(changes[0])))
357 357 ui.status(_("user: %s\n") % changes[1])
358 358 ui.status(_("date: %s\n") % date)
359 359
360 360 if ui.debugflag:
361 361 files = repo.changes(log.parents(changenode)[0], changenode)
362 362 for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
363 363 if value:
364 364 ui.note("%-12s %s\n" % (key, " ".join(value)))
365 365 else:
366 366 ui.note(_("files: %s\n") % " ".join(changes[3]))
367 367
368 368 description = changes[4].strip()
369 369 if description:
370 370 if ui.verbose:
371 371 ui.status(_("description:\n"))
372 372 ui.status(description)
373 373 ui.status("\n\n")
374 374 else:
375 375 ui.status(_("summary: %s\n") % description.splitlines()[0])
376 376 ui.status("\n")
377 377
378 378 def show_version(ui):
379 379 """output version and copyright information"""
380 380 ui.write(_("Mercurial Distributed SCM (version %s)\n")
381 381 % version.get_version())
382 382 ui.status(_(
383 383 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
384 384 "This is free software; see the source for copying conditions. "
385 385 "There is NO\nwarranty; "
386 386 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
387 387 ))
388 388
389 389 def help_(ui, cmd=None, with_version=False):
390 390 """show help for a given command or all commands"""
391 391 option_lists = []
392 392 if cmd and cmd != 'shortlist':
393 393 if with_version:
394 394 show_version(ui)
395 395 ui.write('\n')
396 396 aliases, i = find(cmd)
397 397 # synopsis
398 398 ui.write("%s\n\n" % i[2])
399 399
400 400 # description
401 401 doc = i[0].__doc__
402 402 if ui.quiet:
403 403 doc = doc.splitlines(0)[0]
404 404 ui.write("%s\n" % doc.rstrip())
405 405
406 406 if not ui.quiet:
407 407 # aliases
408 408 if len(aliases) > 1:
409 409 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
410 410
411 411 # options
412 412 if i[1]:
413 413 option_lists.append(("options", i[1]))
414 414
415 415 else:
416 416 # program name
417 417 if ui.verbose or with_version:
418 418 show_version(ui)
419 419 else:
420 420 ui.status(_("Mercurial Distributed SCM\n"))
421 421 ui.status('\n')
422 422
423 423 # list of commands
424 424 if cmd == "shortlist":
425 425 ui.status(_('basic commands (use "hg help" '
426 426 'for the full list or option "-v" for details):\n\n'))
427 427 elif ui.verbose:
428 428 ui.status(_('list of commands:\n\n'))
429 429 else:
430 430 ui.status(_('list of commands (use "hg help -v" '
431 431 'to show aliases and global options):\n\n'))
432 432
433 433 h = {}
434 434 cmds = {}
435 435 for c, e in table.items():
436 436 f = c.split("|")[0]
437 437 if cmd == "shortlist" and not f.startswith("^"):
438 438 continue
439 439 f = f.lstrip("^")
440 440 if not ui.debugflag and f.startswith("debug"):
441 441 continue
442 442 d = ""
443 443 if e[0].__doc__:
444 444 d = e[0].__doc__.splitlines(0)[0].rstrip()
445 445 h[f] = d
446 446 cmds[f] = c.lstrip("^")
447 447
448 448 fns = h.keys()
449 449 fns.sort()
450 450 m = max(map(len, fns))
451 451 for f in fns:
452 452 if ui.verbose:
453 453 commands = cmds[f].replace("|",", ")
454 454 ui.write(" %s:\n %s\n"%(commands, h[f]))
455 455 else:
456 456 ui.write(' %-*s %s\n' % (m, f, h[f]))
457 457
458 458 # global options
459 459 if ui.verbose:
460 460 option_lists.append(("global options", globalopts))
461 461
462 462 # list all option lists
463 463 opt_output = []
464 464 for title, options in option_lists:
465 465 opt_output.append(("\n%s:\n" % title, None))
466 466 for shortopt, longopt, default, desc in options:
467 467 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
468 468 longopt and " --%s" % longopt),
469 469 "%s%s" % (desc,
470 470 default
471 471 and _(" (default: %s)") % default
472 472 or "")))
473 473
474 474 if opt_output:
475 475 opts_len = max([len(line[0]) for line in opt_output if line[1]])
476 476 for first, second in opt_output:
477 477 if second:
478 478 ui.write(" %-*s %s\n" % (opts_len, first, second))
479 479 else:
480 480 ui.write("%s\n" % first)
481 481
482 482 # Commands start here, listed alphabetically
483 483
484 484 def add(ui, repo, *pats, **opts):
485 485 """add the specified files on the next commit
486 486
487 487 Schedule files to be version controlled and added to the repository.
488 488
489 489 The files will be added to the repository at the next commit.
490 490
491 491 If no names are given, add all files in the repository.
492 492 """
493 493
494 494 names = []
495 495 for src, abs, rel, exact in walk(repo, pats, opts):
496 496 if exact:
497 497 if ui.verbose:
498 498 ui.status(_('adding %s\n') % rel)
499 499 names.append(abs)
500 500 elif repo.dirstate.state(abs) == '?':
501 501 ui.status(_('adding %s\n') % rel)
502 502 names.append(abs)
503 503 repo.add(names)
504 504
505 505 def addremove(ui, repo, *pats, **opts):
506 506 """add all new files, delete all missing files
507 507
508 508 Add all new files and remove all missing files from the repository.
509 509
510 510 New files are ignored if they match any of the patterns in .hgignore. As
511 511 with add, these changes take effect at the next commit.
512 512 """
513 513 add, remove = [], []
514 514 for src, abs, rel, exact in walk(repo, pats, opts):
515 515 if src == 'f' and repo.dirstate.state(abs) == '?':
516 516 add.append(abs)
517 517 if ui.verbose or not exact:
518 518 ui.status(_('adding %s\n') % rel)
519 519 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
520 520 remove.append(abs)
521 521 if ui.verbose or not exact:
522 522 ui.status(_('removing %s\n') % rel)
523 523 repo.add(add)
524 524 repo.remove(remove)
525 525
526 526 def annotate(ui, repo, *pats, **opts):
527 527 """show changeset information per file line
528 528
529 529 List changes in files, showing the revision id responsible for each line
530 530
531 531 This command is useful to discover who did a change or when a change took
532 532 place.
533 533
534 534 Without the -a option, annotate will avoid processing files it
535 535 detects as binary. With -a, annotate will generate an annotation
536 536 anyway, probably with undesirable results.
537 537 """
538 538 def getnode(rev):
539 539 return short(repo.changelog.node(rev))
540 540
541 541 ucache = {}
542 542 def getname(rev):
543 543 cl = repo.changelog.read(repo.changelog.node(rev))
544 544 return trimuser(ui, cl[1], rev, ucache)
545 545
546 546 dcache = {}
547 547 def getdate(rev):
548 548 datestr = dcache.get(rev)
549 549 if datestr is None:
550 550 cl = repo.changelog.read(repo.changelog.node(rev))
551 551 datestr = dcache[rev] = util.datestr(cl[2])
552 552 return datestr
553 553
554 554 if not pats:
555 555 raise util.Abort(_('at least one file name or pattern required'))
556 556
557 557 opmap = [['user', getname], ['number', str], ['changeset', getnode],
558 558 ['date', getdate]]
559 559 if not opts['user'] and not opts['changeset'] and not opts['date']:
560 560 opts['number'] = 1
561 561
562 562 if opts['rev']:
563 563 node = repo.changelog.lookup(opts['rev'])
564 564 else:
565 565 node = repo.dirstate.parents()[0]
566 566 change = repo.changelog.read(node)
567 567 mmap = repo.manifest.read(change[0])
568 568
569 569 for src, abs, rel, exact in walk(repo, pats, opts):
570 570 if abs not in mmap:
571 571 ui.warn(_("warning: %s is not in the repository!\n") % rel)
572 572 continue
573 573
574 574 f = repo.file(abs)
575 575 if not opts['text'] and util.binary(f.read(mmap[abs])):
576 576 ui.write(_("%s: binary file\n") % rel)
577 577 continue
578 578
579 579 lines = f.annotate(mmap[abs])
580 580 pieces = []
581 581
582 582 for o, f in opmap:
583 583 if opts[o]:
584 584 l = [f(n) for n, dummy in lines]
585 585 if l:
586 586 m = max(map(len, l))
587 587 pieces.append(["%*s" % (m, x) for x in l])
588 588
589 589 if pieces:
590 590 for p, l in zip(zip(*pieces), lines):
591 591 ui.write("%s: %s" % (" ".join(p), l[1]))
592 592
593 593 def bundle(ui, repo, fname, dest="default-push", **opts):
594 594 """create a changegroup file
595 595
596 596 Generate a compressed changegroup file collecting all changesets
597 597 not found in the other repository.
598 598
599 599 This file can then be transferred using conventional means and
600 600 applied to another repository with the unbundle command. This is
601 601 useful when native push and pull are not available or when
602 602 exporting an entire repository is undesirable. The standard file
603 603 extension is ".hg".
604 604
605 605 Unlike import/export, this exactly preserves all changeset
606 606 contents including permissions, rename data, and revision history.
607 607 """
608 608 f = open(fname, "wb")
609 609 dest = ui.expandpath(dest, repo.root)
610 610 other = hg.repository(ui, dest)
611 611 o = repo.findoutgoing(other)
612 612 cg = repo.changegroup(o)
613 613
614 614 try:
615 615 f.write("HG10")
616 616 z = bz2.BZ2Compressor(9)
617 617 while 1:
618 618 chunk = cg.read(4096)
619 619 if not chunk:
620 620 break
621 621 f.write(z.compress(chunk))
622 622 f.write(z.flush())
623 623 except:
624 624 os.unlink(fname)
625 625 raise
626 626
627 627 def cat(ui, repo, file1, *pats, **opts):
628 628 """output the latest or given revisions of files
629 629
630 630 Print the specified files as they were at the given revision.
631 631 If no revision is given then the tip is used.
632 632
633 633 Output may be to a file, in which case the name of the file is
634 634 given using a format string. The formatting rules are the same as
635 635 for the export command, with the following additions:
636 636
637 637 %s basename of file being printed
638 638 %d dirname of file being printed, or '.' if in repo root
639 639 %p root-relative path name of file being printed
640 640 """
641 641 mf = {}
642 642 rev = opts['rev']
643 643 if rev:
644 644 node = repo.lookup(rev)
645 645 else:
646 646 node = repo.changelog.tip()
647 647 change = repo.changelog.read(node)
648 648 mf = repo.manifest.read(change[0])
649 649 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
650 650 r = repo.file(abs)
651 651 n = mf[abs]
652 652 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
653 653 fp.write(r.read(n))
654 654
655 655 def clone(ui, source, dest=None, **opts):
656 656 """make a copy of an existing repository
657 657
658 658 Create a copy of an existing repository in a new directory.
659 659
660 660 If no destination directory name is specified, it defaults to the
661 661 basename of the source.
662 662
663 663 The location of the source is added to the new repository's
664 664 .hg/hgrc file, as the default to be used for future pulls.
665 665
666 666 For efficiency, hardlinks are used for cloning whenever the source
667 667 and destination are on the same filesystem. Some filesystems,
668 668 such as AFS, implement hardlinking incorrectly, but do not report
669 669 errors. In these cases, use the --pull option to avoid
670 670 hardlinking.
671 671 """
672 672 if dest is None:
673 673 dest = os.path.basename(os.path.normpath(source))
674 674
675 675 if os.path.exists(dest):
676 676 raise util.Abort(_("destination '%s' already exists"), dest)
677 677
678 678 dest = os.path.realpath(dest)
679 679
680 680 class Dircleanup(object):
681 681 def __init__(self, dir_):
682 682 self.rmtree = shutil.rmtree
683 683 self.dir_ = dir_
684 684 os.mkdir(dir_)
685 685 def close(self):
686 686 self.dir_ = None
687 687 def __del__(self):
688 688 if self.dir_:
689 689 self.rmtree(self.dir_, True)
690 690
691 691 if opts['ssh']:
692 692 ui.setconfig("ui", "ssh", opts['ssh'])
693 693 if opts['remotecmd']:
694 694 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
695 695
696 696 if not os.path.exists(source):
697 697 source = ui.expandpath(source)
698 698
699 699 d = Dircleanup(dest)
700 700 abspath = source
701 701 other = hg.repository(ui, source)
702 702
703 703 copy = False
704 704 if other.dev() != -1:
705 705 abspath = os.path.abspath(source)
706 706 if not opts['pull'] and not opts['rev']:
707 707 copy = True
708 708
709 709 if copy:
710 710 try:
711 711 # we use a lock here because if we race with commit, we
712 712 # can end up with extra data in the cloned revlogs that's
713 713 # not pointed to by changesets, thus causing verify to
714 714 # fail
715 715 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
716 716 except OSError:
717 717 copy = False
718 718
719 719 if copy:
720 720 # we lock here to avoid premature writing to the target
721 721 os.mkdir(os.path.join(dest, ".hg"))
722 722 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
723 723
724 724 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
725 725 for f in files.split():
726 726 src = os.path.join(source, ".hg", f)
727 727 dst = os.path.join(dest, ".hg", f)
728 728 try:
729 729 util.copyfiles(src, dst)
730 730 except OSError, inst:
731 731 if inst.errno != errno.ENOENT:
732 732 raise
733 733
734 734 repo = hg.repository(ui, dest)
735 735
736 736 else:
737 737 revs = None
738 738 if opts['rev']:
739 739 if not other.local():
740 740 error = _("clone -r not supported yet for remote repositories.")
741 741 raise util.Abort(error)
742 742 else:
743 743 revs = [other.lookup(rev) for rev in opts['rev']]
744 744 repo = hg.repository(ui, dest, create=1)
745 745 repo.pull(other, heads = revs)
746 746
747 747 f = repo.opener("hgrc", "w", text=True)
748 748 f.write("[paths]\n")
749 749 f.write("default = %s\n" % abspath)
750 750 f.close()
751 751
752 752 if not opts['noupdate']:
753 753 update(ui, repo)
754 754
755 755 d.close()
756 756
757 757 def commit(ui, repo, *pats, **opts):
758 758 """commit the specified files or all outstanding changes
759 759
760 760 Commit changes to the given files into the repository.
761 761
762 762 If a list of files is omitted, all changes reported by "hg status"
763 763 will be commited.
764 764
765 765 The HGEDITOR or EDITOR environment variables are used to start an
766 766 editor to add a commit comment.
767 767 """
768 768 message = opts['message']
769 769 logfile = opts['logfile']
770 770
771 771 if message and logfile:
772 772 raise util.Abort(_('options --message and --logfile are mutually '
773 773 'exclusive'))
774 774 if not message and logfile:
775 775 try:
776 776 if logfile == '-':
777 777 message = sys.stdin.read()
778 778 else:
779 779 message = open(logfile).read()
780 780 except IOError, inst:
781 781 raise util.Abort(_("can't read commit message '%s': %s") %
782 782 (logfile, inst.strerror))
783 783
784 784 if opts['addremove']:
785 785 addremove(ui, repo, *pats, **opts)
786 786 fns, match, anypats, cwd = matchpats(repo, pats, opts)
787 787 if pats:
788 c, a, d, u = repo.changes(files=fns, match=match)
789 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
788 modified, added, removed, unknown = (
789 repo.changes(files=fns, match=match))
790 files = (modified + added +
791 [fn for fn in removed if repo.dirstate.state(fn) == 'r'])
790 792 else:
791 793 files = []
792 794 try:
793 795 repo.commit(files, message, opts['user'], opts['date'], match)
794 796 except ValueError, inst:
795 797 raise util.Abort(str(inst))
796 798
797 799 def docopy(ui, repo, pats, opts):
798 800 cwd = repo.getcwd()
799 801 errors = 0
800 802 copied = []
801 803 targets = {}
802 804
803 805 def okaytocopy(abs, rel, exact):
804 806 reasons = {'?': _('is not managed'),
805 807 'a': _('has been marked for add')}
806 808 reason = reasons.get(repo.dirstate.state(abs))
807 809 if reason:
808 810 if exact:
809 811 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
810 812 else:
811 813 return True
812 814
813 815 def copy(abssrc, relsrc, target, exact):
814 816 abstarget = util.canonpath(repo.root, cwd, target)
815 817 reltarget = util.pathto(cwd, abstarget)
816 818 prevsrc = targets.get(abstarget)
817 819 if prevsrc is not None:
818 820 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
819 821 (reltarget, abssrc, prevsrc))
820 822 return
821 823 if (not opts['after'] and os.path.exists(reltarget) or
822 824 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
823 825 if not opts['force']:
824 826 ui.warn(_('%s: not overwriting - file exists\n') %
825 827 reltarget)
826 828 return
827 829 if not opts['after']:
828 830 os.unlink(reltarget)
829 831 if opts['after']:
830 832 if not os.path.exists(reltarget):
831 833 return
832 834 else:
833 835 targetdir = os.path.dirname(reltarget) or '.'
834 836 if not os.path.isdir(targetdir):
835 837 os.makedirs(targetdir)
836 838 try:
837 839 shutil.copyfile(relsrc, reltarget)
838 840 shutil.copymode(relsrc, reltarget)
839 841 except shutil.Error, inst:
840 842 raise util.Abort(str(inst))
841 843 except IOError, inst:
842 844 if inst.errno == errno.ENOENT:
843 845 ui.warn(_('%s: deleted in working copy\n') % relsrc)
844 846 else:
845 847 ui.warn(_('%s: cannot copy - %s\n') %
846 848 (relsrc, inst.strerror))
847 849 errors += 1
848 850 return
849 851 if ui.verbose or not exact:
850 852 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
851 853 targets[abstarget] = abssrc
852 854 repo.copy(abssrc, abstarget)
853 855 copied.append((abssrc, relsrc, exact))
854 856
855 857 def targetpathfn(pat, dest, srcs):
856 858 if os.path.isdir(pat):
857 859 if pat.endswith(os.sep):
858 860 pat = pat[:-len(os.sep)]
859 861 if destdirexists:
860 862 striplen = len(os.path.split(pat)[0])
861 863 else:
862 864 striplen = len(pat)
863 865 if striplen:
864 866 striplen += len(os.sep)
865 867 res = lambda p: os.path.join(dest, p[striplen:])
866 868 elif destdirexists:
867 869 res = lambda p: os.path.join(dest, os.path.basename(p))
868 870 else:
869 871 res = lambda p: dest
870 872 return res
871 873
872 874 def targetpathafterfn(pat, dest, srcs):
873 875 if util.patkind(pat, None)[0]:
874 876 # a mercurial pattern
875 877 res = lambda p: os.path.join(dest, os.path.basename(p))
876 878 elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]):
877 879 # A directory. Either the target path contains the last
878 880 # component of the source path or it does not.
879 881 def evalpath(striplen):
880 882 score = 0
881 883 for s in srcs:
882 884 t = os.path.join(dest, s[1][striplen:])
883 885 if os.path.exists(t):
884 886 score += 1
885 887 return score
886 888
887 889 if pat.endswith(os.sep):
888 890 pat = pat[:-len(os.sep)]
889 891 striplen = len(pat) + len(os.sep)
890 892 if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])):
891 893 score = evalpath(striplen)
892 894 striplen1 = len(os.path.split(pat)[0])
893 895 if striplen1:
894 896 striplen1 += len(os.sep)
895 897 if evalpath(striplen1) > score:
896 898 striplen = striplen1
897 899 res = lambda p: os.path.join(dest, p[striplen:])
898 900 else:
899 901 # a file
900 902 if destdirexists:
901 903 res = lambda p: os.path.join(dest, os.path.basename(p))
902 904 else:
903 905 res = lambda p: dest
904 906 return res
905 907
906 908
907 909 pats = list(pats)
908 910 if not pats:
909 911 raise util.Abort(_('no source or destination specified'))
910 912 if len(pats) == 1:
911 913 raise util.Abort(_('no destination specified'))
912 914 dest = pats.pop()
913 915 destdirexists = os.path.isdir(dest)
914 916 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
915 917 raise util.Abort(_('with multiple sources, destination must be an '
916 918 'existing directory'))
917 919 if opts['after']:
918 920 tfn = targetpathafterfn
919 921 else:
920 922 tfn = targetpathfn
921 923 copylist = []
922 924 for pat in pats:
923 925 srcs = []
924 926 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
925 927 if okaytocopy(abssrc, relsrc, exact):
926 928 srcs.append((abssrc, relsrc, exact))
927 929 if not srcs:
928 930 continue
929 931 copylist.append((tfn(pat, dest, srcs), srcs))
930 932 if not copylist:
931 933 raise util.Abort(_('no files to copy'))
932 934
933 935 for targetpath, srcs in copylist:
934 936 for abssrc, relsrc, exact in srcs:
935 937 copy(abssrc, relsrc, targetpath(relsrc), exact)
936 938
937 939 if errors:
938 940 ui.warn(_('(consider using --after)\n'))
939 941 return errors, copied
940 942
941 943 def copy(ui, repo, *pats, **opts):
942 944 """mark files as copied for the next commit
943 945
944 946 Mark dest as having copies of source files. If dest is a
945 947 directory, copies are put in that directory. If dest is a file,
946 948 there can only be one source.
947 949
948 950 By default, this command copies the contents of files as they
949 951 stand in the working directory. If invoked with --after, the
950 952 operation is recorded, but no copying is performed.
951 953
952 954 This command takes effect in the next commit.
953 955
954 956 NOTE: This command should be treated as experimental. While it
955 957 should properly record copied files, this information is not yet
956 958 fully used by merge, nor fully reported by log.
957 959 """
958 960 errs, copied = docopy(ui, repo, pats, opts)
959 961 return errs
960 962
961 963 def debugancestor(ui, index, rev1, rev2):
962 964 """find the ancestor revision of two revisions in a given index"""
963 965 r = revlog.revlog(util.opener(os.getcwd()), index, "")
964 966 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
965 967 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
966 968
967 969 def debugcheckstate(ui, repo):
968 970 """validate the correctness of the current dirstate"""
969 971 parent1, parent2 = repo.dirstate.parents()
970 972 repo.dirstate.read()
971 973 dc = repo.dirstate.map
972 974 keys = dc.keys()
973 975 keys.sort()
974 976 m1n = repo.changelog.read(parent1)[0]
975 977 m2n = repo.changelog.read(parent2)[0]
976 978 m1 = repo.manifest.read(m1n)
977 979 m2 = repo.manifest.read(m2n)
978 980 errors = 0
979 981 for f in dc:
980 982 state = repo.dirstate.state(f)
981 983 if state in "nr" and f not in m1:
982 984 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
983 985 errors += 1
984 986 if state in "a" and f in m1:
985 987 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
986 988 errors += 1
987 989 if state in "m" and f not in m1 and f not in m2:
988 990 ui.warn(_("%s in state %s, but not in either manifest\n") %
989 991 (f, state))
990 992 errors += 1
991 993 for f in m1:
992 994 state = repo.dirstate.state(f)
993 995 if state not in "nrm":
994 996 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
995 997 errors += 1
996 998 if errors:
997 999 error = _(".hg/dirstate inconsistent with current parent's manifest")
998 1000 raise util.Abort(error)
999 1001
1000 1002 def debugconfig(ui):
1001 1003 """show combined config settings from all hgrc files"""
1002 1004 try:
1003 1005 repo = hg.repository(ui)
1004 1006 except hg.RepoError:
1005 1007 pass
1006 1008 for section, name, value in ui.walkconfig():
1007 1009 ui.write('%s.%s=%s\n' % (section, name, value))
1008 1010
1009 1011 def debugsetparents(ui, repo, rev1, rev2=None):
1010 1012 """manually set the parents of the current working directory
1011 1013
1012 1014 This is useful for writing repository conversion tools, but should
1013 1015 be used with care.
1014 1016 """
1015 1017
1016 1018 if not rev2:
1017 1019 rev2 = hex(nullid)
1018 1020
1019 1021 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1020 1022
1021 1023 def debugstate(ui, repo):
1022 1024 """show the contents of the current dirstate"""
1023 1025 repo.dirstate.read()
1024 1026 dc = repo.dirstate.map
1025 1027 keys = dc.keys()
1026 1028 keys.sort()
1027 1029 for file_ in keys:
1028 1030 ui.write("%c %3o %10d %s %s\n"
1029 1031 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1030 1032 time.strftime("%x %X",
1031 1033 time.localtime(dc[file_][3])), file_))
1032 1034 for f in repo.dirstate.copies:
1033 1035 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1034 1036
1035 1037 def debugdata(ui, file_, rev):
1036 1038 """dump the contents of an data file revision"""
1037 1039 r = revlog.revlog(util.opener(os.getcwd()), file_[:-2] + ".i", file_)
1038 1040 try:
1039 1041 ui.write(r.revision(r.lookup(rev)))
1040 1042 except KeyError:
1041 1043 raise util.Abort(_('invalid revision identifier %s'), rev)
1042 1044
1043 1045 def debugindex(ui, file_):
1044 1046 """dump the contents of an index file"""
1045 1047 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1046 1048 ui.write(" rev offset length base linkrev" +
1047 1049 " nodeid p1 p2\n")
1048 1050 for i in range(r.count()):
1049 1051 e = r.index[i]
1050 1052 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1051 1053 i, e[0], e[1], e[2], e[3],
1052 1054 short(e[6]), short(e[4]), short(e[5])))
1053 1055
1054 1056 def debugindexdot(ui, file_):
1055 1057 """dump an index DAG as a .dot file"""
1056 1058 r = revlog.revlog(util.opener(os.getcwd()), file_, "")
1057 1059 ui.write("digraph G {\n")
1058 1060 for i in range(r.count()):
1059 1061 e = r.index[i]
1060 1062 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1061 1063 if e[5] != nullid:
1062 1064 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1063 1065 ui.write("}\n")
1064 1066
1065 1067 def debugrename(ui, repo, file, rev=None):
1066 1068 """dump rename information"""
1067 1069 r = repo.file(relpath(repo, [file])[0])
1068 1070 if rev:
1069 1071 try:
1070 1072 # assume all revision numbers are for changesets
1071 1073 n = repo.lookup(rev)
1072 1074 change = repo.changelog.read(n)
1073 1075 m = repo.manifest.read(change[0])
1074 1076 n = m[relpath(repo, [file])[0]]
1075 1077 except (hg.RepoError, KeyError):
1076 1078 n = r.lookup(rev)
1077 1079 else:
1078 1080 n = r.tip()
1079 1081 m = r.renamed(n)
1080 1082 if m:
1081 1083 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1082 1084 else:
1083 1085 ui.write(_("not renamed\n"))
1084 1086
1085 1087 def debugwalk(ui, repo, *pats, **opts):
1086 1088 """show how files match on given patterns"""
1087 1089 items = list(walk(repo, pats, opts))
1088 1090 if not items:
1089 1091 return
1090 1092 fmt = '%%s %%-%ds %%-%ds %%s' % (
1091 1093 max([len(abs) for (src, abs, rel, exact) in items]),
1092 1094 max([len(rel) for (src, abs, rel, exact) in items]))
1093 1095 for src, abs, rel, exact in items:
1094 1096 line = fmt % (src, abs, rel, exact and 'exact' or '')
1095 1097 ui.write("%s\n" % line.rstrip())
1096 1098
1097 1099 def diff(ui, repo, *pats, **opts):
1098 1100 """diff repository (or selected files)
1099 1101
1100 1102 Show differences between revisions for the specified files.
1101 1103
1102 1104 Differences between files are shown using the unified diff format.
1103 1105
1104 1106 When two revision arguments are given, then changes are shown
1105 1107 between those revisions. If only one revision is specified then
1106 1108 that revision is compared to the working directory, and, when no
1107 1109 revisions are specified, the working directory files are compared
1108 1110 to its parent.
1109 1111
1110 1112 Without the -a option, diff will avoid generating diffs of files
1111 1113 it detects as binary. With -a, diff will generate a diff anyway,
1112 1114 probably with undesirable results.
1113 1115 """
1114 1116 node1, node2 = None, None
1115 1117 revs = [repo.lookup(x) for x in opts['rev']]
1116 1118
1117 1119 if len(revs) > 0:
1118 1120 node1 = revs[0]
1119 1121 if len(revs) > 1:
1120 1122 node2 = revs[1]
1121 1123 if len(revs) > 2:
1122 1124 raise util.Abort(_("too many revisions to diff"))
1123 1125
1124 1126 fns, matchfn, anypats, cwd = matchpats(repo, pats, opts)
1125 1127
1126 1128 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1127 1129 text=opts['text'])
1128 1130
1129 1131 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1130 1132 node = repo.lookup(changeset)
1131 1133 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1132 1134 if opts['switch_parent']:
1133 1135 parents.reverse()
1134 1136 prev = (parents and parents[0]) or nullid
1135 1137 change = repo.changelog.read(node)
1136 1138
1137 1139 fp = make_file(repo, repo.changelog, opts['output'],
1138 1140 node=node, total=total, seqno=seqno,
1139 1141 revwidth=revwidth)
1140 1142 if fp != sys.stdout:
1141 1143 ui.note("%s\n" % fp.name)
1142 1144
1143 1145 fp.write("# HG changeset patch\n")
1144 1146 fp.write("# User %s\n" % change[1])
1145 1147 fp.write("# Node ID %s\n" % hex(node))
1146 1148 fp.write("# Parent %s\n" % hex(prev))
1147 1149 if len(parents) > 1:
1148 1150 fp.write("# Parent %s\n" % hex(parents[1]))
1149 1151 fp.write(change[4].rstrip())
1150 1152 fp.write("\n\n")
1151 1153
1152 1154 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1153 1155 if fp != sys.stdout:
1154 1156 fp.close()
1155 1157
1156 1158 def export(ui, repo, *changesets, **opts):
1157 1159 """dump the header and diffs for one or more changesets
1158 1160
1159 1161 Print the changeset header and diffs for one or more revisions.
1160 1162
1161 1163 The information shown in the changeset header is: author,
1162 1164 changeset hash, parent and commit comment.
1163 1165
1164 1166 Output may be to a file, in which case the name of the file is
1165 1167 given using a format string. The formatting rules are as follows:
1166 1168
1167 1169 %% literal "%" character
1168 1170 %H changeset hash (40 bytes of hexadecimal)
1169 1171 %N number of patches being generated
1170 1172 %R changeset revision number
1171 1173 %b basename of the exporting repository
1172 1174 %h short-form changeset hash (12 bytes of hexadecimal)
1173 1175 %n zero-padded sequence number, starting at 1
1174 1176 %r zero-padded changeset revision number
1175 1177
1176 1178 Without the -a option, export will avoid generating diffs of files
1177 1179 it detects as binary. With -a, export will generate a diff anyway,
1178 1180 probably with undesirable results.
1179 1181
1180 1182 With the --switch-parent option, the diff will be against the second
1181 1183 parent. It can be useful to review a merge.
1182 1184 """
1183 1185 if not changesets:
1184 1186 raise util.Abort(_("export requires at least one changeset"))
1185 1187 seqno = 0
1186 1188 revs = list(revrange(ui, repo, changesets))
1187 1189 total = len(revs)
1188 1190 revwidth = max(map(len, revs))
1189 1191 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1190 1192 ui.note(msg)
1191 1193 for cset in revs:
1192 1194 seqno += 1
1193 1195 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1194 1196
1195 1197 def forget(ui, repo, *pats, **opts):
1196 1198 """don't add the specified files on the next commit
1197 1199
1198 1200 Undo an 'hg add' scheduled for the next commit.
1199 1201 """
1200 1202 forget = []
1201 1203 for src, abs, rel, exact in walk(repo, pats, opts):
1202 1204 if repo.dirstate.state(abs) == 'a':
1203 1205 forget.append(abs)
1204 1206 if ui.verbose or not exact:
1205 1207 ui.status(_('forgetting %s\n') % rel)
1206 1208 repo.forget(forget)
1207 1209
1208 1210 def grep(ui, repo, pattern, *pats, **opts):
1209 1211 """search for a pattern in specified files and revisions
1210 1212
1211 1213 Search revisions of files for a regular expression.
1212 1214
1213 1215 This command behaves differently than Unix grep. It only accepts
1214 1216 Python/Perl regexps. It searches repository history, not the
1215 1217 working directory. It always prints the revision number in which
1216 1218 a match appears.
1217 1219
1218 1220 By default, grep only prints output for the first revision of a
1219 1221 file in which it finds a match. To get it to print every revision
1220 1222 that contains a change in match status ("-" for a match that
1221 1223 becomes a non-match, or "+" for a non-match that becomes a match),
1222 1224 use the --all flag.
1223 1225 """
1224 1226 reflags = 0
1225 1227 if opts['ignore_case']:
1226 1228 reflags |= re.I
1227 1229 regexp = re.compile(pattern, reflags)
1228 1230 sep, eol = ':', '\n'
1229 1231 if opts['print0']:
1230 1232 sep = eol = '\0'
1231 1233
1232 1234 fcache = {}
1233 1235 def getfile(fn):
1234 1236 if fn not in fcache:
1235 1237 fcache[fn] = repo.file(fn)
1236 1238 return fcache[fn]
1237 1239
1238 1240 def matchlines(body):
1239 1241 begin = 0
1240 1242 linenum = 0
1241 1243 while True:
1242 1244 match = regexp.search(body, begin)
1243 1245 if not match:
1244 1246 break
1245 1247 mstart, mend = match.span()
1246 1248 linenum += body.count('\n', begin, mstart) + 1
1247 1249 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1248 1250 lend = body.find('\n', mend)
1249 1251 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1250 1252 begin = lend + 1
1251 1253
1252 1254 class linestate(object):
1253 1255 def __init__(self, line, linenum, colstart, colend):
1254 1256 self.line = line
1255 1257 self.linenum = linenum
1256 1258 self.colstart = colstart
1257 1259 self.colend = colend
1258 1260 def __eq__(self, other):
1259 1261 return self.line == other.line
1260 1262 def __hash__(self):
1261 1263 return hash(self.line)
1262 1264
1263 1265 matches = {}
1264 1266 def grepbody(fn, rev, body):
1265 1267 matches[rev].setdefault(fn, {})
1266 1268 m = matches[rev][fn]
1267 1269 for lnum, cstart, cend, line in matchlines(body):
1268 1270 s = linestate(line, lnum, cstart, cend)
1269 1271 m[s] = s
1270 1272
1271 1273 prev = {}
1272 1274 ucache = {}
1273 1275 def display(fn, rev, states, prevstates):
1274 1276 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1275 1277 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1276 1278 counts = {'-': 0, '+': 0}
1277 1279 filerevmatches = {}
1278 1280 for l in diff:
1279 1281 if incrementing or not opts['all']:
1280 1282 change = ((l in prevstates) and '-') or '+'
1281 1283 r = rev
1282 1284 else:
1283 1285 change = ((l in states) and '-') or '+'
1284 1286 r = prev[fn]
1285 1287 cols = [fn, str(rev)]
1286 1288 if opts['line_number']:
1287 1289 cols.append(str(l.linenum))
1288 1290 if opts['all']:
1289 1291 cols.append(change)
1290 1292 if opts['user']:
1291 1293 cols.append(trimuser(ui, getchange(rev)[1], rev,
1292 1294 ucache))
1293 1295 if opts['files_with_matches']:
1294 1296 c = (fn, rev)
1295 1297 if c in filerevmatches:
1296 1298 continue
1297 1299 filerevmatches[c] = 1
1298 1300 else:
1299 1301 cols.append(l.line)
1300 1302 ui.write(sep.join(cols), eol)
1301 1303 counts[change] += 1
1302 1304 return counts['+'], counts['-']
1303 1305
1304 1306 fstate = {}
1305 1307 skip = {}
1306 1308 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1307 1309 count = 0
1308 1310 incrementing = False
1309 1311 for st, rev, fns in changeiter:
1310 1312 if st == 'window':
1311 1313 incrementing = rev
1312 1314 matches.clear()
1313 1315 elif st == 'add':
1314 1316 change = repo.changelog.read(repo.lookup(str(rev)))
1315 1317 mf = repo.manifest.read(change[0])
1316 1318 matches[rev] = {}
1317 1319 for fn in fns:
1318 1320 if fn in skip:
1319 1321 continue
1320 1322 fstate.setdefault(fn, {})
1321 1323 try:
1322 1324 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1323 1325 except KeyError:
1324 1326 pass
1325 1327 elif st == 'iter':
1326 1328 states = matches[rev].items()
1327 1329 states.sort()
1328 1330 for fn, m in states:
1329 1331 if fn in skip:
1330 1332 continue
1331 1333 if incrementing or not opts['all'] or fstate[fn]:
1332 1334 pos, neg = display(fn, rev, m, fstate[fn])
1333 1335 count += pos + neg
1334 1336 if pos and not opts['all']:
1335 1337 skip[fn] = True
1336 1338 fstate[fn] = m
1337 1339 prev[fn] = rev
1338 1340
1339 1341 if not incrementing:
1340 1342 fstate = fstate.items()
1341 1343 fstate.sort()
1342 1344 for fn, state in fstate:
1343 1345 if fn in skip:
1344 1346 continue
1345 1347 display(fn, rev, {}, state)
1346 1348 return (count == 0 and 1) or 0
1347 1349
1348 1350 def heads(ui, repo, **opts):
1349 1351 """show current repository heads
1350 1352
1351 1353 Show all repository head changesets.
1352 1354
1353 1355 Repository "heads" are changesets that don't have children
1354 1356 changesets. They are where development generally takes place and
1355 1357 are the usual targets for update and merge operations.
1356 1358 """
1357 1359 if opts['rev']:
1358 1360 heads = repo.heads(repo.lookup(opts['rev']))
1359 1361 else:
1360 1362 heads = repo.heads()
1361 1363 br = None
1362 1364 if opts['branches']:
1363 1365 br = repo.branchlookup(heads)
1364 1366 for n in heads:
1365 1367 show_changeset(ui, repo, changenode=n, brinfo=br)
1366 1368
1367 1369 def identify(ui, repo):
1368 1370 """print information about the working copy
1369 1371
1370 1372 Print a short summary of the current state of the repo.
1371 1373
1372 1374 This summary identifies the repository state using one or two parent
1373 1375 hash identifiers, followed by a "+" if there are uncommitted changes
1374 1376 in the working directory, followed by a list of tags for this revision.
1375 1377 """
1376 1378 parents = [p for p in repo.dirstate.parents() if p != nullid]
1377 1379 if not parents:
1378 1380 ui.write(_("unknown\n"))
1379 1381 return
1380 1382
1381 1383 hexfunc = ui.verbose and hex or short
1382 (c, a, d, u) = repo.changes()
1384 modified, added, removed, unknown = repo.changes()
1383 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 1388 if not ui.quiet:
1387 1389 # multiple tags for a single parent separated by '/'
1388 1390 parenttags = ['/'.join(tags)
1389 1391 for tags in map(repo.nodetags, parents) if tags]
1390 1392 # tags for multiple parents separated by ' + '
1391 1393 if parenttags:
1392 1394 output.append(' + '.join(parenttags))
1393 1395
1394 1396 ui.write("%s\n" % ' '.join(output))
1395 1397
1396 1398 def import_(ui, repo, patch1, *patches, **opts):
1397 1399 """import an ordered set of patches
1398 1400
1399 1401 Import a list of patches and commit them individually.
1400 1402
1401 1403 If there are outstanding changes in the working directory, import
1402 1404 will abort unless given the -f flag.
1403 1405
1404 1406 If a patch looks like a mail message (its first line starts with
1405 1407 "From " or looks like an RFC822 header), it will not be applied
1406 1408 unless the -f option is used. The importer neither parses nor
1407 1409 discards mail headers, so use -f only to override the "mailness"
1408 1410 safety check, not to import a real mail message.
1409 1411 """
1410 1412 patches = (patch1,) + patches
1411 1413
1412 1414 if not opts['force']:
1413 (c, a, d, u) = repo.changes()
1414 if c or a or d:
1415 modified, added, removed, unknown = repo.changes()
1416 if modified or added or removed:
1415 1417 raise util.Abort(_("outstanding uncommitted changes"))
1416 1418
1417 1419 d = opts["base"]
1418 1420 strip = opts["strip"]
1419 1421
1420 1422 mailre = re.compile(r'(?:From |[\w-]+:)')
1421 1423
1422 1424 # attempt to detect the start of a patch
1423 1425 # (this heuristic is borrowed from quilt)
1424 1426 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1425 1427 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1426 1428 '(---|\*\*\*)[ \t])')
1427 1429
1428 1430 for patch in patches:
1429 1431 ui.status(_("applying %s\n") % patch)
1430 1432 pf = os.path.join(d, patch)
1431 1433
1432 1434 message = []
1433 1435 user = None
1434 1436 hgpatch = False
1435 1437 for line in file(pf):
1436 1438 line = line.rstrip()
1437 1439 if (not message and not hgpatch and
1438 1440 mailre.match(line) and not opts['force']):
1439 1441 if len(line) > 35:
1440 1442 line = line[:32] + '...'
1441 1443 raise util.Abort(_('first line looks like a '
1442 1444 'mail header: ') + line)
1443 1445 if diffre.match(line):
1444 1446 break
1445 1447 elif hgpatch:
1446 1448 # parse values when importing the result of an hg export
1447 1449 if line.startswith("# User "):
1448 1450 user = line[7:]
1449 1451 ui.debug(_('User: %s\n') % user)
1450 1452 elif not line.startswith("# ") and line:
1451 1453 message.append(line)
1452 1454 hgpatch = False
1453 1455 elif line == '# HG changeset patch':
1454 1456 hgpatch = True
1455 1457 message = [] # We may have collected garbage
1456 1458 else:
1457 1459 message.append(line)
1458 1460
1459 1461 # make sure message isn't empty
1460 1462 if not message:
1461 1463 message = _("imported patch %s\n") % patch
1462 1464 else:
1463 1465 message = "%s\n" % '\n'.join(message)
1464 1466 ui.debug(_('message:\n%s\n') % message)
1465 1467
1466 1468 files = util.patch(strip, pf, ui)
1467 1469
1468 1470 if len(files) > 0:
1469 1471 addremove(ui, repo, *files)
1470 1472 repo.commit(files, message, user)
1471 1473
1472 1474 def incoming(ui, repo, source="default", **opts):
1473 1475 """show new changesets found in source
1474 1476
1475 1477 Show new changesets found in the specified repo or the default
1476 1478 pull repo. These are the changesets that would be pulled if a pull
1477 1479 was requested.
1478 1480
1479 1481 Currently only local repositories are supported.
1480 1482 """
1481 1483 source = ui.expandpath(source, repo.root)
1482 1484 other = hg.repository(ui, source)
1483 1485 if not other.local():
1484 1486 raise util.Abort(_("incoming doesn't work for remote repositories yet"))
1485 1487 o = repo.findincoming(other)
1486 1488 if not o:
1487 1489 return
1488 1490 o = other.changelog.nodesbetween(o)[0]
1489 1491 if opts['newest_first']:
1490 1492 o.reverse()
1491 1493 for n in o:
1492 1494 parents = [p for p in other.changelog.parents(n) if p != nullid]
1493 1495 if opts['no_merges'] and len(parents) == 2:
1494 1496 continue
1495 1497 show_changeset(ui, other, changenode=n)
1496 1498 if opts['patch']:
1497 1499 prev = (parents and parents[0]) or nullid
1498 1500 dodiff(ui, ui, other, prev, n)
1499 1501 ui.write("\n")
1500 1502
1501 1503 def init(ui, dest="."):
1502 1504 """create a new repository in the given directory
1503 1505
1504 1506 Initialize a new repository in the given directory. If the given
1505 1507 directory does not exist, it is created.
1506 1508
1507 1509 If no directory is given, the current directory is used.
1508 1510 """
1509 1511 if not os.path.exists(dest):
1510 1512 os.mkdir(dest)
1511 1513 hg.repository(ui, dest, create=1)
1512 1514
1513 1515 def locate(ui, repo, *pats, **opts):
1514 1516 """locate files matching specific patterns
1515 1517
1516 1518 Print all files under Mercurial control whose names match the
1517 1519 given patterns.
1518 1520
1519 1521 This command searches the current directory and its
1520 1522 subdirectories. To search an entire repository, move to the root
1521 1523 of the repository.
1522 1524
1523 1525 If no patterns are given to match, this command prints all file
1524 1526 names.
1525 1527
1526 1528 If you want to feed the output of this command into the "xargs"
1527 1529 command, use the "-0" option to both this command and "xargs".
1528 1530 This will avoid the problem of "xargs" treating single filenames
1529 1531 that contain white space as multiple filenames.
1530 1532 """
1531 1533 end = opts['print0'] and '\0' or '\n'
1532 1534
1533 1535 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1534 1536 if repo.dirstate.state(abs) == '?':
1535 1537 continue
1536 1538 if opts['fullpath']:
1537 1539 ui.write(os.path.join(repo.root, abs), end)
1538 1540 else:
1539 1541 ui.write(rel, end)
1540 1542
1541 1543 def log(ui, repo, *pats, **opts):
1542 1544 """show revision history of entire repository or files
1543 1545
1544 1546 Print the revision history of the specified files or the entire project.
1545 1547
1546 1548 By default this command outputs: changeset id and hash, tags,
1547 1549 non-trivial parents, user, date and time, and a summary for each
1548 1550 commit. When the -v/--verbose switch is used, the list of changed
1549 1551 files and full commit message is shown.
1550 1552 """
1551 1553 class dui(object):
1552 1554 # Implement and delegate some ui protocol. Save hunks of
1553 1555 # output for later display in the desired order.
1554 1556 def __init__(self, ui):
1555 1557 self.ui = ui
1556 1558 self.hunk = {}
1557 1559 def bump(self, rev):
1558 1560 self.rev = rev
1559 1561 self.hunk[rev] = []
1560 1562 def note(self, *args):
1561 1563 if self.verbose:
1562 1564 self.write(*args)
1563 1565 def status(self, *args):
1564 1566 if not self.quiet:
1565 1567 self.write(*args)
1566 1568 def write(self, *args):
1567 1569 self.hunk[self.rev].append(args)
1568 1570 def debug(self, *args):
1569 1571 if self.debugflag:
1570 1572 self.write(*args)
1571 1573 def __getattr__(self, key):
1572 1574 return getattr(self.ui, key)
1573 1575 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1574 1576 for st, rev, fns in changeiter:
1575 1577 if st == 'window':
1576 1578 du = dui(ui)
1577 1579 elif st == 'add':
1578 1580 du.bump(rev)
1579 1581 changenode = repo.changelog.node(rev)
1580 1582 parents = [p for p in repo.changelog.parents(changenode)
1581 1583 if p != nullid]
1582 1584 if opts['no_merges'] and len(parents) == 2:
1583 1585 continue
1584 1586 if opts['only_merges'] and len(parents) != 2:
1585 1587 continue
1586 1588
1587 1589 br = None
1588 1590 if opts['keyword']:
1589 1591 changes = getchange(rev)
1590 1592 miss = 0
1591 1593 for k in [kw.lower() for kw in opts['keyword']]:
1592 1594 if not (k in changes[1].lower() or
1593 1595 k in changes[4].lower() or
1594 1596 k in " ".join(changes[3][:20]).lower()):
1595 1597 miss = 1
1596 1598 break
1597 1599 if miss:
1598 1600 continue
1599 1601
1600 1602 if opts['branch']:
1601 1603 br = repo.branchlookup([repo.changelog.node(rev)])
1602 1604
1603 1605 show_changeset(du, repo, rev, brinfo=br)
1604 1606 if opts['patch']:
1605 1607 prev = (parents and parents[0]) or nullid
1606 1608 dodiff(du, du, repo, prev, changenode, match=matchfn)
1607 1609 du.write("\n\n")
1608 1610 elif st == 'iter':
1609 1611 for args in du.hunk[rev]:
1610 1612 ui.write(*args)
1611 1613
1612 1614 def manifest(ui, repo, rev=None):
1613 1615 """output the latest or given revision of the project manifest
1614 1616
1615 1617 Print a list of version controlled files for the given revision.
1616 1618
1617 1619 The manifest is the list of files being version controlled. If no revision
1618 1620 is given then the tip is used.
1619 1621 """
1620 1622 if rev:
1621 1623 try:
1622 1624 # assume all revision numbers are for changesets
1623 1625 n = repo.lookup(rev)
1624 1626 change = repo.changelog.read(n)
1625 1627 n = change[0]
1626 1628 except hg.RepoError:
1627 1629 n = repo.manifest.lookup(rev)
1628 1630 else:
1629 1631 n = repo.manifest.tip()
1630 1632 m = repo.manifest.read(n)
1631 1633 mf = repo.manifest.readflags(n)
1632 1634 files = m.keys()
1633 1635 files.sort()
1634 1636
1635 1637 for f in files:
1636 1638 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1637 1639
1638 1640 def outgoing(ui, repo, dest="default-push", **opts):
1639 1641 """show changesets not found in destination
1640 1642
1641 1643 Show changesets not found in the specified destination repo or the
1642 1644 default push repo. These are the changesets that would be pushed
1643 1645 if a push was requested.
1644 1646 """
1645 1647 dest = ui.expandpath(dest, repo.root)
1646 1648 other = hg.repository(ui, dest)
1647 1649 o = repo.findoutgoing(other)
1648 1650 o = repo.changelog.nodesbetween(o)[0]
1649 1651 if opts['newest_first']:
1650 1652 o.reverse()
1651 1653 for n in o:
1652 1654 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1653 1655 if opts['no_merges'] and len(parents) == 2:
1654 1656 continue
1655 1657 show_changeset(ui, repo, changenode=n)
1656 1658 if opts['patch']:
1657 1659 prev = (parents and parents[0]) or nullid
1658 1660 dodiff(ui, ui, repo, prev, n)
1659 1661 ui.write("\n")
1660 1662
1661 1663 def parents(ui, repo, rev=None):
1662 1664 """show the parents of the working dir or revision
1663 1665
1664 1666 Print the working directory's parent revisions.
1665 1667 """
1666 1668 if rev:
1667 1669 p = repo.changelog.parents(repo.lookup(rev))
1668 1670 else:
1669 1671 p = repo.dirstate.parents()
1670 1672
1671 1673 for n in p:
1672 1674 if n != nullid:
1673 1675 show_changeset(ui, repo, changenode=n)
1674 1676
1675 1677 def paths(ui, search=None):
1676 1678 """show definition of symbolic path names
1677 1679
1678 1680 Show definition of symbolic path name NAME. If no name is given, show
1679 1681 definition of available names.
1680 1682
1681 1683 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1682 1684 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1683 1685 """
1684 1686 try:
1685 1687 repo = hg.repository(ui=ui)
1686 1688 except hg.RepoError:
1687 1689 pass
1688 1690
1689 1691 if search:
1690 1692 for name, path in ui.configitems("paths"):
1691 1693 if name == search:
1692 1694 ui.write("%s\n" % path)
1693 1695 return
1694 1696 ui.warn(_("not found!\n"))
1695 1697 return 1
1696 1698 else:
1697 1699 for name, path in ui.configitems("paths"):
1698 1700 ui.write("%s = %s\n" % (name, path))
1699 1701
1700 1702 def pull(ui, repo, source="default", **opts):
1701 1703 """pull changes from the specified source
1702 1704
1703 1705 Pull changes from a remote repository to a local one.
1704 1706
1705 1707 This finds all changes from the repository at the specified path
1706 1708 or URL and adds them to the local repository. By default, this
1707 1709 does not update the copy of the project in the working directory.
1708 1710
1709 1711 Valid URLs are of the form:
1710 1712
1711 1713 local/filesystem/path
1712 1714 http://[user@]host[:port][/path]
1713 1715 https://[user@]host[:port][/path]
1714 1716 ssh://[user@]host[:port][/path]
1715 1717
1716 1718 SSH requires an accessible shell account on the destination machine
1717 1719 and a copy of hg in the remote path. With SSH, paths are relative
1718 1720 to the remote user's home directory by default; use two slashes at
1719 1721 the start of a path to specify it as relative to the filesystem root.
1720 1722 """
1721 1723 source = ui.expandpath(source, repo.root)
1722 1724 ui.status(_('pulling from %s\n') % (source))
1723 1725
1724 1726 if opts['ssh']:
1725 1727 ui.setconfig("ui", "ssh", opts['ssh'])
1726 1728 if opts['remotecmd']:
1727 1729 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1728 1730
1729 1731 other = hg.repository(ui, source)
1730 1732 revs = None
1731 1733 if opts['rev'] and not other.local():
1732 1734 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
1733 1735 elif opts['rev']:
1734 1736 revs = [other.lookup(rev) for rev in opts['rev']]
1735 1737 r = repo.pull(other, heads=revs)
1736 1738 if not r:
1737 1739 if opts['update']:
1738 1740 return update(ui, repo)
1739 1741 else:
1740 1742 ui.status(_("(run 'hg update' to get a working copy)\n"))
1741 1743
1742 1744 return r
1743 1745
1744 1746 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1745 1747 """push changes to the specified destination
1746 1748
1747 1749 Push changes from the local repository to the given destination.
1748 1750
1749 1751 This is the symmetrical operation for pull. It helps to move
1750 1752 changes from the current repository to a different one. If the
1751 1753 destination is local this is identical to a pull in that directory
1752 1754 from the current one.
1753 1755
1754 1756 By default, push will refuse to run if it detects the result would
1755 1757 increase the number of remote heads. This generally indicates the
1756 1758 the client has forgotten to sync and merge before pushing.
1757 1759
1758 1760 Valid URLs are of the form:
1759 1761
1760 1762 local/filesystem/path
1761 1763 ssh://[user@]host[:port][/path]
1762 1764
1763 1765 SSH requires an accessible shell account on the destination
1764 1766 machine and a copy of hg in the remote path.
1765 1767 """
1766 1768 dest = ui.expandpath(dest, repo.root)
1767 1769 ui.status('pushing to %s\n' % (dest))
1768 1770
1769 1771 if ssh:
1770 1772 ui.setconfig("ui", "ssh", ssh)
1771 1773 if remotecmd:
1772 1774 ui.setconfig("ui", "remotecmd", remotecmd)
1773 1775
1774 1776 other = hg.repository(ui, dest)
1775 1777 r = repo.push(other, force)
1776 1778 return r
1777 1779
1778 1780 def rawcommit(ui, repo, *flist, **rc):
1779 1781 """raw commit interface
1780 1782
1781 1783 Lowlevel commit, for use in helper scripts.
1782 1784
1783 1785 This command is not intended to be used by normal users, as it is
1784 1786 primarily useful for importing from other SCMs.
1785 1787 """
1786 1788 message = rc['message']
1787 1789 if not message and rc['logfile']:
1788 1790 try:
1789 1791 message = open(rc['logfile']).read()
1790 1792 except IOError:
1791 1793 pass
1792 1794 if not message and not rc['logfile']:
1793 1795 raise util.Abort(_("missing commit message"))
1794 1796
1795 1797 files = relpath(repo, list(flist))
1796 1798 if rc['files']:
1797 1799 files += open(rc['files']).read().splitlines()
1798 1800
1799 1801 rc['parent'] = map(repo.lookup, rc['parent'])
1800 1802
1801 1803 try:
1802 1804 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1803 1805 except ValueError, inst:
1804 1806 raise util.Abort(str(inst))
1805 1807
1806 1808 def recover(ui, repo):
1807 1809 """roll back an interrupted transaction
1808 1810
1809 1811 Recover from an interrupted commit or pull.
1810 1812
1811 1813 This command tries to fix the repository status after an interrupted
1812 1814 operation. It should only be necessary when Mercurial suggests it.
1813 1815 """
1814 1816 if repo.recover():
1815 1817 return repo.verify()
1816 1818 return False
1817 1819
1818 1820 def remove(ui, repo, pat, *pats, **opts):
1819 1821 """remove the specified files on the next commit
1820 1822
1821 1823 Schedule the indicated files for removal from the repository.
1822 1824
1823 1825 This command schedules the files to be removed at the next commit.
1824 1826 This only removes files from the current branch, not from the
1825 1827 entire project history. If the files still exist in the working
1826 1828 directory, they will be deleted from it.
1827 1829 """
1828 1830 names = []
1829 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 1833 reason = None
1832 if c:
1834 if modified:
1833 1835 reason = _('is modified')
1834 elif a:
1836 elif added:
1835 1837 reason = _('has been marked for add')
1836 elif u:
1838 elif unknown:
1837 1839 reason = _('is not managed')
1838 1840 if reason:
1839 1841 if exact:
1840 1842 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
1841 1843 else:
1842 1844 return True
1843 1845 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1844 1846 if okaytoremove(abs, rel, exact):
1845 1847 if ui.verbose or not exact:
1846 1848 ui.status(_('removing %s\n') % rel)
1847 1849 names.append(abs)
1848 1850 repo.remove(names, unlink=True)
1849 1851
1850 1852 def rename(ui, repo, *pats, **opts):
1851 1853 """rename files; equivalent of copy + remove
1852 1854
1853 1855 Mark dest as copies of sources; mark sources for deletion. If
1854 1856 dest is a directory, copies are put in that directory. If dest is
1855 1857 a file, there can only be one source.
1856 1858
1857 1859 By default, this command copies the contents of files as they
1858 1860 stand in the working directory. If invoked with --after, the
1859 1861 operation is recorded, but no copying is performed.
1860 1862
1861 1863 This command takes effect in the next commit.
1862 1864
1863 1865 NOTE: This command should be treated as experimental. While it
1864 1866 should properly record rename files, this information is not yet
1865 1867 fully used by merge, nor fully reported by log.
1866 1868 """
1867 1869 errs, copied = docopy(ui, repo, pats, opts)
1868 1870 names = []
1869 1871 for abs, rel, exact in copied:
1870 1872 if ui.verbose or not exact:
1871 1873 ui.status(_('removing %s\n') % rel)
1872 1874 names.append(abs)
1873 1875 repo.remove(names, unlink=True)
1874 1876 return errs
1875 1877
1876 1878 def revert(ui, repo, *pats, **opts):
1877 1879 """revert modified files or dirs back to their unmodified states
1878 1880
1879 1881 Revert any uncommitted modifications made to the named files or
1880 1882 directories. This restores the contents of the affected files to
1881 1883 an unmodified state.
1882 1884
1883 1885 If a file has been deleted, it is recreated. If the executable
1884 1886 mode of a file was changed, it is reset.
1885 1887
1886 1888 If names are given, all files matching the names are reverted.
1887 1889
1888 1890 If no arguments are given, all files in the repository are reverted.
1889 1891 """
1890 1892 node = opts['rev'] and repo.lookup(opts['rev']) or \
1891 1893 repo.dirstate.parents()[0]
1892 1894
1893 1895 files, choose, anypats, cwd = matchpats(repo, pats, opts)
1894 (c, a, d, u) = repo.changes(match=choose)
1895 repo.forget(a)
1896 repo.undelete(d)
1896 modified, added, removed, unknown = repo.changes(match=choose)
1897 repo.forget(added)
1898 repo.undelete(removed)
1897 1899
1898 1900 return repo.update(node, False, True, choose, False)
1899 1901
1900 1902 def root(ui, repo):
1901 1903 """print the root (top) of the current working dir
1902 1904
1903 1905 Print the root directory of the current repository.
1904 1906 """
1905 1907 ui.write(repo.root + "\n")
1906 1908
1907 1909 def serve(ui, repo, **opts):
1908 1910 """export the repository via HTTP
1909 1911
1910 1912 Start a local HTTP repository browser and pull server.
1911 1913
1912 1914 By default, the server logs accesses to stdout and errors to
1913 1915 stderr. Use the "-A" and "-E" options to log to files.
1914 1916 """
1915 1917
1916 1918 if opts["stdio"]:
1917 1919 fin, fout = sys.stdin, sys.stdout
1918 1920 sys.stdout = sys.stderr
1919 1921
1920 1922 # Prevent insertion/deletion of CRs
1921 1923 util.set_binary(fin)
1922 1924 util.set_binary(fout)
1923 1925
1924 1926 def getarg():
1925 1927 argline = fin.readline()[:-1]
1926 1928 arg, l = argline.split()
1927 1929 val = fin.read(int(l))
1928 1930 return arg, val
1929 1931 def respond(v):
1930 1932 fout.write("%d\n" % len(v))
1931 1933 fout.write(v)
1932 1934 fout.flush()
1933 1935
1934 1936 lock = None
1935 1937
1936 1938 while 1:
1937 1939 cmd = fin.readline()[:-1]
1938 1940 if cmd == '':
1939 1941 return
1940 1942 if cmd == "heads":
1941 1943 h = repo.heads()
1942 1944 respond(" ".join(map(hex, h)) + "\n")
1943 1945 if cmd == "lock":
1944 1946 lock = repo.lock()
1945 1947 respond("")
1946 1948 if cmd == "unlock":
1947 1949 if lock:
1948 1950 lock.release()
1949 1951 lock = None
1950 1952 respond("")
1951 1953 elif cmd == "branches":
1952 1954 arg, nodes = getarg()
1953 1955 nodes = map(bin, nodes.split(" "))
1954 1956 r = []
1955 1957 for b in repo.branches(nodes):
1956 1958 r.append(" ".join(map(hex, b)) + "\n")
1957 1959 respond("".join(r))
1958 1960 elif cmd == "between":
1959 1961 arg, pairs = getarg()
1960 1962 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1961 1963 r = []
1962 1964 for b in repo.between(pairs):
1963 1965 r.append(" ".join(map(hex, b)) + "\n")
1964 1966 respond("".join(r))
1965 1967 elif cmd == "changegroup":
1966 1968 nodes = []
1967 1969 arg, roots = getarg()
1968 1970 nodes = map(bin, roots.split(" "))
1969 1971
1970 1972 cg = repo.changegroup(nodes)
1971 1973 while 1:
1972 1974 d = cg.read(4096)
1973 1975 if not d:
1974 1976 break
1975 1977 fout.write(d)
1976 1978
1977 1979 fout.flush()
1978 1980
1979 1981 elif cmd == "addchangegroup":
1980 1982 if not lock:
1981 1983 respond("not locked")
1982 1984 continue
1983 1985 respond("")
1984 1986
1985 1987 r = repo.addchangegroup(fin)
1986 1988 respond("")
1987 1989
1988 1990 optlist = "name templates style address port ipv6 accesslog errorlog"
1989 1991 for o in optlist.split():
1990 1992 if opts[o]:
1991 1993 ui.setconfig("web", o, opts[o])
1992 1994
1993 1995 try:
1994 1996 httpd = hgweb.create_server(repo)
1995 1997 except socket.error, inst:
1996 1998 raise util.Abort(_('cannot start server: ') + inst.args[1])
1997 1999
1998 2000 if ui.verbose:
1999 2001 addr, port = httpd.socket.getsockname()
2000 2002 if addr == '0.0.0.0':
2001 2003 addr = socket.gethostname()
2002 2004 else:
2003 2005 try:
2004 2006 addr = socket.gethostbyaddr(addr)[0]
2005 2007 except socket.error:
2006 2008 pass
2007 2009 if port != 80:
2008 2010 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2009 2011 else:
2010 2012 ui.status(_('listening at http://%s/\n') % addr)
2011 2013 httpd.serve_forever()
2012 2014
2013 2015 def status(ui, repo, *pats, **opts):
2014 2016 """show changed files in the working directory
2015 2017
2016 2018 Show changed files in the repository. If names are
2017 2019 given, only files that match are shown.
2018 2020
2019 2021 The codes used to show the status of files are:
2020 2022 M = modified
2021 2023 A = added
2022 2024 R = removed
2023 2025 ? = not tracked
2024 2026 """
2025 2027
2026 2028 files, matchfn, anypats, cwd = matchpats(repo, pats, opts)
2027 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
2028 for n in repo.changes(files=files, match=matchfn)]
2029 modified, added, removed, unknown = [
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),
2031 (_('added'), 'A', a),
2032 (_('removed'), 'R', d),
2033 (_('unknown'), '?', u)]
2033 changetypes = [(_('modified'), 'M', modified),
2034 (_('added'), 'A', added),
2035 (_('removed'), 'R', removed),
2036 (_('unknown'), '?', unknown)]
2034 2037
2035 2038 end = opts['print0'] and '\0' or '\n'
2036 2039
2037 2040 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2038 2041 or changetypes):
2039 2042 if opts['no_status']:
2040 2043 format = "%%s%s" % end
2041 2044 else:
2042 2045 format = "%s %%s%s" % (char, end);
2043 2046
2044 2047 for f in changes:
2045 2048 ui.write(format % f)
2046 2049
2047 2050 def tag(ui, repo, name, rev_=None, **opts):
2048 2051 """add a tag for the current tip or a given revision
2049 2052
2050 2053 Name a particular revision using <name>.
2051 2054
2052 2055 Tags are used to name particular revisions of the repository and are
2053 2056 very useful to compare different revision, to go back to significant
2054 2057 earlier versions or to mark branch points as releases, etc.
2055 2058
2056 2059 If no revision is given, the tip is used.
2057 2060
2058 2061 To facilitate version control, distribution, and merging of tags,
2059 2062 they are stored as a file named ".hgtags" which is managed
2060 2063 similarly to other project files and can be hand-edited if
2061 2064 necessary.
2062 2065 """
2063 2066 if name == "tip":
2064 2067 raise util.Abort(_("the name 'tip' is reserved"))
2065 2068 if opts['rev']:
2066 2069 rev_ = opts['rev']
2067 2070 if rev_:
2068 2071 r = hex(repo.lookup(rev_))
2069 2072 else:
2070 2073 r = hex(repo.changelog.tip())
2071 2074
2072 2075 disallowed = (revrangesep, '\r', '\n')
2073 2076 for c in disallowed:
2074 2077 if name.find(c) >= 0:
2075 2078 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2076 2079
2077 2080 if opts['local']:
2078 2081 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2079 2082 return
2080 2083
2081 (c, a, d, u) = repo.changes()
2082 for x in (c, a, d, u):
2084 for x in repo.changes():
2083 2085 if ".hgtags" in x:
2084 2086 raise util.Abort(_("working copy of .hgtags is changed "
2085 2087 "(please commit .hgtags manually)"))
2086 2088
2087 2089 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2088 2090 if repo.dirstate.state(".hgtags") == '?':
2089 2091 repo.add([".hgtags"])
2090 2092
2091 2093 message = (opts['message'] or
2092 2094 _("Added tag %s for changeset %s") % (name, r))
2093 2095 try:
2094 2096 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2095 2097 except ValueError, inst:
2096 2098 raise util.Abort(str(inst))
2097 2099
2098 2100 def tags(ui, repo):
2099 2101 """list repository tags
2100 2102
2101 2103 List the repository tags.
2102 2104
2103 2105 This lists both regular and local tags.
2104 2106 """
2105 2107
2106 2108 l = repo.tagslist()
2107 2109 l.reverse()
2108 2110 for t, n in l:
2109 2111 try:
2110 2112 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2111 2113 except KeyError:
2112 2114 r = " ?:?"
2113 2115 ui.write("%-30s %s\n" % (t, r))
2114 2116
2115 2117 def tip(ui, repo):
2116 2118 """show the tip revision
2117 2119
2118 2120 Show the tip revision.
2119 2121 """
2120 2122 n = repo.changelog.tip()
2121 2123 show_changeset(ui, repo, changenode=n)
2122 2124
2123 2125 def unbundle(ui, repo, fname, **opts):
2124 2126 """apply a changegroup file
2125 2127
2126 2128 Apply a compressed changegroup file generated by the bundle
2127 2129 command.
2128 2130 """
2129 2131 f = urllib.urlopen(fname)
2130 2132
2131 2133 if f.read(4) != "HG10":
2132 2134 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2133 2135
2134 2136 def bzgenerator(f):
2135 2137 zd = bz2.BZ2Decompressor()
2136 2138 for chunk in f:
2137 2139 yield zd.decompress(chunk)
2138 2140
2139 2141 bzgen = bzgenerator(util.filechunkiter(f, 4096))
2140 2142 if repo.addchangegroup(util.chunkbuffer(bzgen)):
2141 2143 return 1
2142 2144
2143 2145 if opts['update']:
2144 2146 return update(ui, repo)
2145 2147 else:
2146 2148 ui.status(_("(run 'hg update' to get a working copy)\n"))
2147 2149
2148 2150 def undo(ui, repo):
2149 2151 """undo the last commit or pull
2150 2152
2151 2153 Roll back the last pull or commit transaction on the
2152 2154 repository, restoring the project to its earlier state.
2153 2155
2154 2156 This command should be used with care. There is only one level of
2155 2157 undo and there is no redo.
2156 2158
2157 2159 This command is not intended for use on public repositories. Once
2158 2160 a change is visible for pull by other users, undoing it locally is
2159 2161 ineffective.
2160 2162 """
2161 2163 repo.undo()
2162 2164
2163 2165 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2164 2166 branch=None):
2165 2167 """update or merge working directory
2166 2168
2167 2169 Update the working directory to the specified revision.
2168 2170
2169 2171 If there are no outstanding changes in the working directory and
2170 2172 there is a linear relationship between the current version and the
2171 2173 requested version, the result is the requested version.
2172 2174
2173 2175 Otherwise the result is a merge between the contents of the
2174 2176 current working directory and the requested version. Files that
2175 2177 changed between either parent are marked as changed for the next
2176 2178 commit and a commit must be performed before any further updates
2177 2179 are allowed.
2178 2180
2179 2181 By default, update will refuse to run if doing so would require
2180 2182 merging or discarding local changes.
2181 2183 """
2182 2184 if branch:
2183 2185 br = repo.branchlookup(branch=branch)
2184 2186 found = []
2185 2187 for x in br:
2186 2188 if branch in br[x]:
2187 2189 found.append(x)
2188 2190 if len(found) > 1:
2189 2191 ui.warn(_("Found multiple heads for %s\n") % branch)
2190 2192 for x in found:
2191 2193 show_changeset(ui, repo, changenode=x, brinfo=br)
2192 2194 return 1
2193 2195 if len(found) == 1:
2194 2196 node = found[0]
2195 2197 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2196 2198 else:
2197 2199 ui.warn(_("branch %s not found\n") % (branch))
2198 2200 return 1
2199 2201 else:
2200 2202 node = node and repo.lookup(node) or repo.changelog.tip()
2201 2203 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2202 2204
2203 2205 def verify(ui, repo):
2204 2206 """verify the integrity of the repository
2205 2207
2206 2208 Verify the integrity of the current repository.
2207 2209
2208 2210 This will perform an extensive check of the repository's
2209 2211 integrity, validating the hashes and checksums of each entry in
2210 2212 the changelog, manifest, and tracked files, as well as the
2211 2213 integrity of their crosslinks and indices.
2212 2214 """
2213 2215 return repo.verify()
2214 2216
2215 2217 # Command options and aliases are listed here, alphabetically
2216 2218
2217 2219 table = {
2218 2220 "^add":
2219 2221 (add,
2220 2222 [('I', 'include', [], _('include names matching the given patterns')),
2221 2223 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2222 2224 _('hg add [OPTION]... [FILE]...')),
2223 2225 "addremove":
2224 2226 (addremove,
2225 2227 [('I', 'include', [], _('include names matching the given patterns')),
2226 2228 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2227 2229 _('hg addremove [OPTION]... [FILE]...')),
2228 2230 "^annotate":
2229 2231 (annotate,
2230 2232 [('r', 'rev', '', _('annotate the specified revision')),
2231 2233 ('a', 'text', None, _('treat all files as text')),
2232 2234 ('u', 'user', None, _('list the author')),
2233 2235 ('d', 'date', None, _('list the date')),
2234 2236 ('n', 'number', None, _('list the revision number (default)')),
2235 2237 ('c', 'changeset', None, _('list the changeset')),
2236 2238 ('I', 'include', [], _('include names matching the given patterns')),
2237 2239 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2238 2240 _('hg annotate [OPTION]... FILE...')),
2239 2241 "bundle":
2240 2242 (bundle,
2241 2243 [],
2242 2244 _('hg bundle FILE DEST')),
2243 2245 "cat":
2244 2246 (cat,
2245 2247 [('I', 'include', [], _('include names matching the given patterns')),
2246 2248 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2247 2249 ('o', 'output', '', _('print output to file with formatted name')),
2248 2250 ('r', 'rev', '', _('print the given revision'))],
2249 2251 _('hg cat [OPTION]... FILE...')),
2250 2252 "^clone":
2251 2253 (clone,
2252 2254 [('U', 'noupdate', None, _('do not update the new working directory')),
2253 2255 ('e', 'ssh', '', _('specify ssh command to use')),
2254 2256 ('', 'pull', None, _('use pull protocol to copy metadata')),
2255 2257 ('r', 'rev', [],
2256 2258 _('a changeset you would like to have after cloning')),
2257 2259 ('', 'remotecmd', '',
2258 2260 _('specify hg command to run on the remote side'))],
2259 2261 _('hg clone [OPTION]... SOURCE [DEST]')),
2260 2262 "^commit|ci":
2261 2263 (commit,
2262 2264 [('A', 'addremove', None, _('run addremove during commit')),
2263 2265 ('I', 'include', [], _('include names matching the given patterns')),
2264 2266 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2265 2267 ('m', 'message', '', _('use <text> as commit message')),
2266 2268 ('l', 'logfile', '', _('read the commit message from <file>')),
2267 2269 ('d', 'date', '', _('record datecode as commit date')),
2268 2270 ('u', 'user', '', _('record user as commiter'))],
2269 2271 _('hg commit [OPTION]... [FILE]...')),
2270 2272 "copy|cp":
2271 2273 (copy,
2272 2274 [('I', 'include', [], _('include names matching the given patterns')),
2273 2275 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2274 2276 ('A', 'after', None, _('record a copy that has already occurred')),
2275 2277 ('f', 'force', None,
2276 2278 _('forcibly copy over an existing managed file'))],
2277 2279 _('hg copy [OPTION]... [SOURCE]... DEST')),
2278 2280 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2279 2281 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2280 2282 "debugconfig": (debugconfig, [], _('debugconfig')),
2281 2283 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2282 2284 "debugstate": (debugstate, [], _('debugstate')),
2283 2285 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2284 2286 "debugindex": (debugindex, [], _('debugindex FILE')),
2285 2287 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2286 2288 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2287 2289 "debugwalk":
2288 2290 (debugwalk,
2289 2291 [('I', 'include', [], _('include names matching the given patterns')),
2290 2292 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2291 2293 _('debugwalk [OPTION]... [FILE]...')),
2292 2294 "^diff":
2293 2295 (diff,
2294 2296 [('r', 'rev', [], _('revision')),
2295 2297 ('a', 'text', None, _('treat all files as text')),
2296 2298 ('I', 'include', [], _('include names matching the given patterns')),
2297 2299 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2298 2300 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2299 2301 "^export":
2300 2302 (export,
2301 2303 [('o', 'output', '', _('print output to file with formatted name')),
2302 2304 ('a', 'text', None, _('treat all files as text')),
2303 2305 ('', 'switch-parent', None, _('diff against the second parent'))],
2304 2306 _('hg export [-a] [-o OUTFILE] REV...')),
2305 2307 "forget":
2306 2308 (forget,
2307 2309 [('I', 'include', [], _('include names matching the given patterns')),
2308 2310 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2309 2311 _('hg forget [OPTION]... FILE...')),
2310 2312 "grep":
2311 2313 (grep,
2312 2314 [('0', 'print0', None, _('end fields with NUL')),
2313 2315 ('I', 'include', [], _('include names matching the given patterns')),
2314 2316 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2315 2317 ('', 'all', None, _('print all revisions that match')),
2316 2318 ('i', 'ignore-case', None, _('ignore case when matching')),
2317 2319 ('l', 'files-with-matches', None,
2318 2320 _('print only filenames and revs that match')),
2319 2321 ('n', 'line-number', None, _('print matching line numbers')),
2320 2322 ('r', 'rev', [], _('search in given revision range')),
2321 2323 ('u', 'user', None, _('print user who committed change'))],
2322 2324 _('hg grep [OPTION]... PATTERN [FILE]...')),
2323 2325 "heads":
2324 2326 (heads,
2325 2327 [('b', 'branches', None, _('find branch info')),
2326 2328 ('r', 'rev', '', _('show only heads which are descendants of rev'))],
2327 2329 _('hg heads [-b] [-r <rev>]')),
2328 2330 "help": (help_, [], _('hg help [COMMAND]')),
2329 2331 "identify|id": (identify, [], _('hg identify')),
2330 2332 "import|patch":
2331 2333 (import_,
2332 2334 [('p', 'strip', 1,
2333 2335 _('directory strip option for patch. This has the same\n') +
2334 2336 _('meaning as the corresponding patch option')),
2335 2337 ('f', 'force', None,
2336 2338 _('skip check for outstanding uncommitted changes')),
2337 2339 ('b', 'base', '', _('base path'))],
2338 2340 _('hg import [-f] [-p NUM] [-b BASE] PATCH...')),
2339 2341 "incoming|in": (incoming,
2340 2342 [('M', 'no-merges', None, _('do not show merges')),
2341 2343 ('p', 'patch', None, _('show patch')),
2342 2344 ('n', 'newest-first', None, _('show newest record first'))],
2343 2345 _('hg incoming [-p] [-n] [-M] [SOURCE]')),
2344 2346 "^init": (init, [], _('hg init [DEST]')),
2345 2347 "locate":
2346 2348 (locate,
2347 2349 [('r', 'rev', '', _('search the repository as it stood at rev')),
2348 2350 ('0', 'print0', None,
2349 2351 _('end filenames with NUL, for use with xargs')),
2350 2352 ('f', 'fullpath', None,
2351 2353 _('print complete paths from the filesystem root')),
2352 2354 ('I', 'include', [], _('include names matching the given patterns')),
2353 2355 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2354 2356 _('hg locate [OPTION]... [PATTERN]...')),
2355 2357 "^log|history":
2356 2358 (log,
2357 2359 [('I', 'include', [], _('include names matching the given patterns')),
2358 2360 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2359 2361 ('b', 'branch', None, _('show branches')),
2360 2362 ('k', 'keyword', [], _('search for a keyword')),
2361 2363 ('r', 'rev', [], _('show the specified revision or range')),
2362 2364 ('M', 'no-merges', None, _('do not show merges')),
2363 2365 ('m', 'only-merges', None, _('show only merges')),
2364 2366 ('p', 'patch', None, _('show patch'))],
2365 2367 _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
2366 2368 "manifest": (manifest, [], _('hg manifest [REV]')),
2367 2369 "outgoing|out": (outgoing,
2368 2370 [('M', 'no-merges', None, _('do not show merges')),
2369 2371 ('p', 'patch', None, _('show patch')),
2370 2372 ('n', 'newest-first', None, _('show newest record first'))],
2371 2373 _('hg outgoing [-p] [-n] [-M] [DEST]')),
2372 2374 "^parents": (parents, [], _('hg parents [REV]')),
2373 2375 "paths": (paths, [], _('hg paths [NAME]')),
2374 2376 "^pull":
2375 2377 (pull,
2376 2378 [('u', 'update', None,
2377 2379 _('update the working directory to tip after pull')),
2378 2380 ('e', 'ssh', '', _('specify ssh command to use')),
2379 2381 ('r', 'rev', [], _('a specific revision you would like to pull')),
2380 2382 ('', 'remotecmd', '',
2381 2383 _('specify hg command to run on the remote side'))],
2382 2384 _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')),
2383 2385 "^push":
2384 2386 (push,
2385 2387 [('f', 'force', None, _('force push')),
2386 2388 ('e', 'ssh', '', _('specify ssh command to use')),
2387 2389 ('', 'remotecmd', '',
2388 2390 _('specify hg command to run on the remote side'))],
2389 2391 _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')),
2390 2392 "rawcommit":
2391 2393 (rawcommit,
2392 2394 [('p', 'parent', [], _('parent')),
2393 2395 ('d', 'date', '', _('date code')),
2394 2396 ('u', 'user', '', _('user')),
2395 2397 ('F', 'files', '', _('file list')),
2396 2398 ('m', 'message', '', _('commit message')),
2397 2399 ('l', 'logfile', '', _('commit message file'))],
2398 2400 _('hg rawcommit [OPTION]... [FILE]...')),
2399 2401 "recover": (recover, [], _('hg recover')),
2400 2402 "^remove|rm":
2401 2403 (remove,
2402 2404 [('I', 'include', [], _('include names matching the given patterns')),
2403 2405 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2404 2406 _('hg remove [OPTION]... FILE...')),
2405 2407 "rename|mv":
2406 2408 (rename,
2407 2409 [('I', 'include', [], _('include names matching the given patterns')),
2408 2410 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2409 2411 ('A', 'after', None, _('record a rename that has already occurred')),
2410 2412 ('f', 'force', None,
2411 2413 _('forcibly copy over an existing managed file'))],
2412 2414 _('hg rename [OPTION]... [SOURCE]... DEST')),
2413 2415 "^revert":
2414 2416 (revert,
2415 2417 [('I', 'include', [], _('include names matching the given patterns')),
2416 2418 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2417 2419 ('r', 'rev', '', _('revision to revert to'))],
2418 2420 _('hg revert [-n] [-r REV] [NAME]...')),
2419 2421 "root": (root, [], _('hg root')),
2420 2422 "^serve":
2421 2423 (serve,
2422 2424 [('A', 'accesslog', '', _('name of access log file to write to')),
2423 2425 ('E', 'errorlog', '', _('name of error log file to write to')),
2424 2426 ('p', 'port', 0, _('port to use (default: 8000)')),
2425 2427 ('a', 'address', '', _('address to use')),
2426 2428 ('n', 'name', '',
2427 2429 _('name to show in web pages (default: working dir)')),
2428 2430 ('', 'stdio', None, _('for remote clients')),
2429 2431 ('t', 'templates', '', _('web templates to use')),
2430 2432 ('', 'style', '', _('template style to use')),
2431 2433 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2432 2434 _('hg serve [OPTION]...')),
2433 2435 "^status|st":
2434 2436 (status,
2435 2437 [('m', 'modified', None, _('show only modified files')),
2436 2438 ('a', 'added', None, _('show only added files')),
2437 2439 ('r', 'removed', None, _('show only removed files')),
2438 2440 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2439 2441 ('n', 'no-status', None, _('hide status prefix')),
2440 2442 ('0', 'print0', None,
2441 2443 _('end filenames with NUL, for use with xargs')),
2442 2444 ('I', 'include', [], _('include names matching the given patterns')),
2443 2445 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2444 2446 _('hg status [OPTION]... [FILE]...')),
2445 2447 "tag":
2446 2448 (tag,
2447 2449 [('l', 'local', None, _('make the tag local')),
2448 2450 ('m', 'message', '', _('message for tag commit log entry')),
2449 2451 ('d', 'date', '', _('record datecode as commit date')),
2450 2452 ('u', 'user', '', _('record user as commiter')),
2451 2453 ('r', 'rev', '', _('revision to tag'))],
2452 2454 _('hg tag [OPTION]... NAME [REV]')),
2453 2455 "tags": (tags, [], _('hg tags')),
2454 2456 "tip": (tip, [], _('hg tip')),
2455 2457 "unbundle":
2456 2458 (unbundle,
2457 2459 [('u', 'update', None,
2458 2460 _('update the working directory to tip after unbundle'))],
2459 2461 _('hg unbundle [-u] FILE')),
2460 2462 "undo": (undo, [], _('hg undo')),
2461 2463 "^update|up|checkout|co":
2462 2464 (update,
2463 2465 [('b', 'branch', '', _('checkout the head of a specific branch')),
2464 2466 ('m', 'merge', None, _('allow merging of branches')),
2465 2467 ('C', 'clean', None, _('overwrite locally modified files')),
2466 2468 ('f', 'force', None, _('force a merge with outstanding changes'))],
2467 2469 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
2468 2470 "verify": (verify, [], _('hg verify')),
2469 2471 "version": (show_version, [], _('hg version')),
2470 2472 }
2471 2473
2472 2474 globalopts = [
2473 2475 ('R', 'repository', '', _('repository root directory')),
2474 2476 ('', 'cwd', '', _('change working directory')),
2475 2477 ('y', 'noninteractive', None,
2476 2478 _('do not prompt, assume \'yes\' for any required answers')),
2477 2479 ('q', 'quiet', None, _('suppress output')),
2478 2480 ('v', 'verbose', None, _('enable additional output')),
2479 2481 ('', 'debug', None, _('enable debugging output')),
2480 2482 ('', 'debugger', None, _('start debugger')),
2481 2483 ('', 'traceback', None, _('print traceback on exception')),
2482 2484 ('', 'time', None, _('time how long the command takes')),
2483 2485 ('', 'profile', None, _('print command execution profile')),
2484 2486 ('', 'version', None, _('output version information and exit')),
2485 2487 ('h', 'help', None, _('display help and exit')),
2486 2488 ]
2487 2489
2488 2490 norepo = ("clone init version help debugancestor debugconfig debugdata"
2489 2491 " debugindex debugindexdot paths")
2490 2492
2491 2493 def find(cmd):
2492 2494 """Return (aliases, command table entry) for command string."""
2493 2495 choice = None
2494 2496 for e in table.keys():
2495 2497 aliases = e.lstrip("^").split("|")
2496 2498 if cmd in aliases:
2497 2499 return aliases, table[e]
2498 2500 for a in aliases:
2499 2501 if a.startswith(cmd):
2500 2502 if choice:
2501 2503 raise AmbiguousCommand(cmd)
2502 2504 else:
2503 2505 choice = aliases, table[e]
2504 2506 break
2505 2507 if choice:
2506 2508 return choice
2507 2509
2508 2510 raise UnknownCommand(cmd)
2509 2511
2510 2512 class SignalInterrupt(Exception):
2511 2513 """Exception raised on SIGTERM and SIGHUP."""
2512 2514
2513 2515 def catchterm(*args):
2514 2516 raise SignalInterrupt
2515 2517
2516 2518 def run():
2517 2519 sys.exit(dispatch(sys.argv[1:]))
2518 2520
2519 2521 class ParseError(Exception):
2520 2522 """Exception raised on errors in parsing the command line."""
2521 2523
2522 2524 def parse(ui, args):
2523 2525 options = {}
2524 2526 cmdoptions = {}
2525 2527
2526 2528 try:
2527 2529 args = fancyopts.fancyopts(args, globalopts, options)
2528 2530 except fancyopts.getopt.GetoptError, inst:
2529 2531 raise ParseError(None, inst)
2530 2532
2531 2533 if args:
2532 2534 cmd, args = args[0], args[1:]
2533 2535 aliases, i = find(cmd)
2534 2536 cmd = aliases[0]
2535 2537 defaults = ui.config("defaults", cmd)
2536 2538 if defaults:
2537 2539 args = defaults.split() + args
2538 2540 c = list(i[1])
2539 2541 else:
2540 2542 cmd = None
2541 2543 c = []
2542 2544
2543 2545 # combine global options into local
2544 2546 for o in globalopts:
2545 2547 c.append((o[0], o[1], options[o[1]], o[3]))
2546 2548
2547 2549 try:
2548 2550 args = fancyopts.fancyopts(args, c, cmdoptions)
2549 2551 except fancyopts.getopt.GetoptError, inst:
2550 2552 raise ParseError(cmd, inst)
2551 2553
2552 2554 # separate global options back out
2553 2555 for o in globalopts:
2554 2556 n = o[1]
2555 2557 options[n] = cmdoptions[n]
2556 2558 del cmdoptions[n]
2557 2559
2558 2560 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2559 2561
2560 2562 def dispatch(args):
2561 2563 signal.signal(signal.SIGTERM, catchterm)
2562 2564 try:
2563 2565 signal.signal(signal.SIGHUP, catchterm)
2564 2566 except AttributeError:
2565 2567 pass
2566 2568
2567 2569 try:
2568 2570 u = ui.ui()
2569 2571 except util.Abort, inst:
2570 2572 sys.stderr.write(_("abort: %s\n") % inst)
2571 2573 sys.exit(1)
2572 2574
2573 2575 external = []
2574 2576 for x in u.extensions():
2575 2577 def on_exception(exc, inst):
2576 2578 u.warn(_("*** failed to import extension %s\n") % x[1])
2577 2579 u.warn("%s\n" % inst)
2578 2580 if "--traceback" in sys.argv[1:]:
2579 2581 traceback.print_exc()
2580 2582 if x[1]:
2581 2583 try:
2582 2584 mod = imp.load_source(x[0], x[1])
2583 2585 except Exception, inst:
2584 2586 on_exception(Exception, inst)
2585 2587 continue
2586 2588 else:
2587 2589 def importh(name):
2588 2590 mod = __import__(name)
2589 2591 components = name.split('.')
2590 2592 for comp in components[1:]:
2591 2593 mod = getattr(mod, comp)
2592 2594 return mod
2593 2595 try:
2594 2596 mod = importh(x[0])
2595 2597 except Exception, inst:
2596 2598 on_exception(Exception, inst)
2597 2599 continue
2598 2600
2599 2601 external.append(mod)
2600 2602 for x in external:
2601 2603 cmdtable = getattr(x, 'cmdtable', {})
2602 2604 for t in cmdtable:
2603 2605 if t in table:
2604 2606 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
2605 2607 table.update(cmdtable)
2606 2608
2607 2609 try:
2608 2610 cmd, func, args, options, cmdoptions = parse(u, args)
2609 2611 except ParseError, inst:
2610 2612 if inst.args[0]:
2611 2613 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
2612 2614 help_(u, inst.args[0])
2613 2615 else:
2614 2616 u.warn(_("hg: %s\n") % inst.args[1])
2615 2617 help_(u, 'shortlist')
2616 2618 sys.exit(-1)
2617 2619 except AmbiguousCommand, inst:
2618 2620 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2619 2621 sys.exit(1)
2620 2622 except UnknownCommand, inst:
2621 2623 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2622 2624 help_(u, 'shortlist')
2623 2625 sys.exit(1)
2624 2626
2625 2627 if options["time"]:
2626 2628 def get_times():
2627 2629 t = os.times()
2628 2630 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2629 2631 t = (t[0], t[1], t[2], t[3], time.clock())
2630 2632 return t
2631 2633 s = get_times()
2632 2634 def print_time():
2633 2635 t = get_times()
2634 2636 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
2635 2637 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2636 2638 atexit.register(print_time)
2637 2639
2638 2640 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2639 2641 not options["noninteractive"])
2640 2642
2641 2643 # enter the debugger before command execution
2642 2644 if options['debugger']:
2643 2645 pdb.set_trace()
2644 2646
2645 2647 try:
2646 2648 try:
2647 2649 if options['help']:
2648 2650 help_(u, cmd, options['version'])
2649 2651 sys.exit(0)
2650 2652 elif options['version']:
2651 2653 show_version(u)
2652 2654 sys.exit(0)
2653 2655 elif not cmd:
2654 2656 help_(u, 'shortlist')
2655 2657 sys.exit(0)
2656 2658
2657 2659 if options['cwd']:
2658 2660 try:
2659 2661 os.chdir(options['cwd'])
2660 2662 except OSError, inst:
2661 2663 raise util.Abort('%s: %s' %
2662 2664 (options['cwd'], inst.strerror))
2663 2665
2664 2666 if cmd not in norepo.split():
2665 2667 path = options["repository"] or ""
2666 2668 repo = hg.repository(ui=u, path=path)
2667 2669 for x in external:
2668 2670 if hasattr(x, 'reposetup'):
2669 2671 x.reposetup(u, repo)
2670 2672 d = lambda: func(u, repo, *args, **cmdoptions)
2671 2673 else:
2672 2674 d = lambda: func(u, *args, **cmdoptions)
2673 2675
2674 2676 if options['profile']:
2675 2677 import hotshot, hotshot.stats
2676 2678 prof = hotshot.Profile("hg.prof")
2677 2679 r = prof.runcall(d)
2678 2680 prof.close()
2679 2681 stats = hotshot.stats.load("hg.prof")
2680 2682 stats.strip_dirs()
2681 2683 stats.sort_stats('time', 'calls')
2682 2684 stats.print_stats(40)
2683 2685 return r
2684 2686 else:
2685 2687 return d()
2686 2688 except:
2687 2689 # enter the debugger when we hit an exception
2688 2690 if options['debugger']:
2689 2691 pdb.post_mortem(sys.exc_info()[2])
2690 2692 if options['traceback']:
2691 2693 traceback.print_exc()
2692 2694 raise
2693 2695 except hg.RepoError, inst:
2694 2696 u.warn(_("abort: "), inst, "!\n")
2695 2697 except revlog.RevlogError, inst:
2696 2698 u.warn(_("abort: "), inst, "!\n")
2697 2699 except SignalInterrupt:
2698 2700 u.warn(_("killed!\n"))
2699 2701 except KeyboardInterrupt:
2700 2702 try:
2701 2703 u.warn(_("interrupted!\n"))
2702 2704 except IOError, inst:
2703 2705 if inst.errno == errno.EPIPE:
2704 2706 if u.debugflag:
2705 2707 u.warn(_("\nbroken pipe\n"))
2706 2708 else:
2707 2709 raise
2708 2710 except IOError, inst:
2709 2711 if hasattr(inst, "code"):
2710 2712 u.warn(_("abort: %s\n") % inst)
2711 2713 elif hasattr(inst, "reason"):
2712 2714 u.warn(_("abort: error: %s\n") % inst.reason[1])
2713 2715 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2714 2716 if u.debugflag:
2715 2717 u.warn(_("broken pipe\n"))
2716 2718 elif getattr(inst, "strerror", None):
2717 2719 if getattr(inst, "filename", None):
2718 2720 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
2719 2721 else:
2720 2722 u.warn(_("abort: %s\n") % inst.strerror)
2721 2723 else:
2722 2724 raise
2723 2725 except OSError, inst:
2724 2726 if hasattr(inst, "filename"):
2725 2727 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
2726 2728 else:
2727 2729 u.warn(_("abort: %s\n") % inst.strerror)
2728 2730 except util.Abort, inst:
2729 2731 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
2730 2732 sys.exit(1)
2731 2733 except TypeError, inst:
2732 2734 # was this an argument error?
2733 2735 tb = traceback.extract_tb(sys.exc_info()[2])
2734 2736 if len(tb) > 2: # no
2735 2737 raise
2736 2738 u.debug(inst, "\n")
2737 2739 u.warn(_("%s: invalid arguments\n") % cmd)
2738 2740 help_(u, cmd)
2739 2741 except AmbiguousCommand, inst:
2740 2742 u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
2741 2743 help_(u, 'shortlist')
2742 2744 except UnknownCommand, inst:
2743 2745 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
2744 2746 help_(u, 'shortlist')
2745 2747 except SystemExit:
2746 2748 # don't catch this in the catch-all below
2747 2749 raise
2748 2750 except:
2749 2751 u.warn(_("** unknown exception encountered, details follow\n"))
2750 2752 u.warn(_("** report bug details to mercurial@selenic.com\n"))
2751 2753 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
2752 2754 % version.get_version())
2753 2755 raise
2754 2756
2755 2757 sys.exit(-1)
@@ -1,1109 +1,1110
1 1 # hgweb.py - web interface to a mercurial repository
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, cgi, sys
10 10 from demandload import demandload
11 11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 13 demandload(globals(), "mimetypes")
14 14 from node import *
15 15 from i18n import gettext as _
16 16
17 17 def templatepath():
18 18 for f in "templates", "../templates":
19 19 p = os.path.join(os.path.dirname(__file__), f)
20 20 if os.path.isdir(p):
21 21 return p
22 22
23 23 def age(x):
24 24 def plural(t, c):
25 25 if c == 1:
26 26 return t
27 27 return t + "s"
28 28 def fmt(t, c):
29 29 return "%d %s" % (c, plural(t, c))
30 30
31 31 now = time.time()
32 32 then = x[0]
33 33 delta = max(1, int(now - then))
34 34
35 35 scales = [["second", 1],
36 36 ["minute", 60],
37 37 ["hour", 3600],
38 38 ["day", 3600 * 24],
39 39 ["week", 3600 * 24 * 7],
40 40 ["month", 3600 * 24 * 30],
41 41 ["year", 3600 * 24 * 365]]
42 42
43 43 scales.reverse()
44 44
45 45 for t, s in scales:
46 46 n = delta / s
47 47 if n >= 2 or s == 1:
48 48 return fmt(t, n)
49 49
50 50 def nl2br(text):
51 51 return text.replace('\n', '<br/>\n')
52 52
53 53 def obfuscate(text):
54 54 return ''.join(['&#%d;' % ord(c) for c in text])
55 55
56 56 def up(p):
57 57 if p[0] != "/":
58 58 p = "/" + p
59 59 if p[-1] == "/":
60 60 p = p[:-1]
61 61 up = os.path.dirname(p)
62 62 if up == "/":
63 63 return "/"
64 64 return up + "/"
65 65
66 66 def get_mtime(repo_path):
67 67 hg_path = os.path.join(repo_path, ".hg")
68 68 cl_path = os.path.join(hg_path, "00changelog.i")
69 69 if os.path.exists(os.path.join(cl_path)):
70 70 return os.stat(cl_path).st_mtime
71 71 else:
72 72 return os.stat(hg_path).st_mtime
73 73
74 74 class hgrequest(object):
75 75 def __init__(self, inp=None, out=None, env=None):
76 76 self.inp = inp or sys.stdin
77 77 self.out = out or sys.stdout
78 78 self.env = env or os.environ
79 79 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
80 80
81 81 def write(self, *things):
82 82 for thing in things:
83 83 if hasattr(thing, "__iter__"):
84 84 for part in thing:
85 85 self.write(part)
86 86 else:
87 87 try:
88 88 self.out.write(str(thing))
89 89 except socket.error, inst:
90 90 if inst[0] != errno.ECONNRESET:
91 91 raise
92 92
93 93 def header(self, headers=[('Content-type','text/html')]):
94 94 for header in headers:
95 95 self.out.write("%s: %s\r\n" % header)
96 96 self.out.write("\r\n")
97 97
98 98 def httphdr(self, type, file="", size=0):
99 99
100 100 headers = [('Content-type', type)]
101 101 if file:
102 102 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
103 103 if size > 0:
104 104 headers.append(('Content-length', str(size)))
105 105 self.header(headers)
106 106
107 107 class templater(object):
108 108 def __init__(self, mapfile, filters={}, defaults={}):
109 109 self.cache = {}
110 110 self.map = {}
111 111 self.base = os.path.dirname(mapfile)
112 112 self.filters = filters
113 113 self.defaults = defaults
114 114
115 115 for l in file(mapfile):
116 116 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
117 117 if m:
118 118 self.cache[m.group(1)] = m.group(2)
119 119 else:
120 120 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
121 121 if m:
122 122 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
123 123 else:
124 124 raise LookupError(_("unknown map entry '%s'") % l)
125 125
126 126 def __call__(self, t, **map):
127 127 m = self.defaults.copy()
128 128 m.update(map)
129 129 try:
130 130 tmpl = self.cache[t]
131 131 except KeyError:
132 132 tmpl = self.cache[t] = file(self.map[t]).read()
133 133 return self.template(tmpl, self.filters, **m)
134 134
135 135 def template(self, tmpl, filters={}, **map):
136 136 while tmpl:
137 137 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
138 138 if m:
139 139 yield tmpl[:m.start(0)]
140 140 v = map.get(m.group(1), "")
141 141 v = callable(v) and v(**map) or v
142 142
143 143 format = m.group(2)
144 144 fl = m.group(4)
145 145
146 146 if format:
147 147 q = v.__iter__
148 148 for i in q():
149 149 lm = map.copy()
150 150 lm.update(i)
151 151 yield self(format[1:], **lm)
152 152
153 153 v = ""
154 154
155 155 elif fl:
156 156 for f in fl.split("|")[1:]:
157 157 v = filters[f](v)
158 158
159 159 yield v
160 160 tmpl = tmpl[m.end(0):]
161 161 else:
162 162 yield tmpl
163 163 return
164 164
165 165 common_filters = {
166 166 "escape": cgi.escape,
167 167 "strip": lambda x: x.strip(),
168 168 "age": age,
169 169 "date": lambda x: util.datestr(x),
170 170 "addbreaks": nl2br,
171 171 "obfuscate": obfuscate,
172 172 "short": (lambda x: x[:12]),
173 173 "firstline": (lambda x: x.splitlines(1)[0]),
174 174 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
175 175 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
176 176 }
177 177
178 178 class hgweb(object):
179 179 def __init__(self, repo, name=None):
180 180 if type(repo) == type(""):
181 181 self.repo = hg.repository(ui.ui(), repo)
182 182 else:
183 183 self.repo = repo
184 184
185 185 self.mtime = -1
186 186 self.reponame = name
187 187 self.archives = 'zip', 'gz', 'bz2'
188 188
189 189 def refresh(self):
190 190 mtime = get_mtime(self.repo.root)
191 191 if mtime != self.mtime:
192 192 self.mtime = mtime
193 193 self.repo = hg.repository(self.repo.ui, self.repo.root)
194 194 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
195 195 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
196 196 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
197 197
198 198 def archivelist(self, nodeid):
199 199 for i in self.archives:
200 200 if self.repo.ui.configbool("web", "allow" + i, False):
201 201 yield {"type" : i, "node" : nodeid}
202 202
203 203 def listfiles(self, files, mf):
204 204 for f in files[:self.maxfiles]:
205 205 yield self.t("filenodelink", node=hex(mf[f]), file=f)
206 206 if len(files) > self.maxfiles:
207 207 yield self.t("fileellipses")
208 208
209 209 def listfilediffs(self, files, changeset):
210 210 for f in files[:self.maxfiles]:
211 211 yield self.t("filedifflink", node=hex(changeset), file=f)
212 212 if len(files) > self.maxfiles:
213 213 yield self.t("fileellipses")
214 214
215 215 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
216 216 if not rev:
217 217 rev = lambda x: ""
218 218 siblings = [s for s in siblings if s != nullid]
219 219 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
220 220 return
221 221 for s in siblings:
222 222 yield dict(node=hex(s), rev=rev(s), **args)
223 223
224 224 def showtag(self, t1, node=nullid, **args):
225 225 for t in self.repo.nodetags(node):
226 226 yield self.t(t1, tag=t, **args)
227 227
228 228 def diff(self, node1, node2, files):
229 229 def filterfiles(list, files):
230 230 l = [x for x in list if x in files]
231 231
232 232 for f in files:
233 233 if f[-1] != os.sep:
234 234 f += os.sep
235 235 l += [x for x in list if x.startswith(f)]
236 236 return l
237 237
238 238 parity = [0]
239 239 def diffblock(diff, f, fn):
240 240 yield self.t("diffblock",
241 241 lines=prettyprintlines(diff),
242 242 parity=parity[0],
243 243 file=f,
244 244 filenode=hex(fn or nullid))
245 245 parity[0] = 1 - parity[0]
246 246
247 247 def prettyprintlines(diff):
248 248 for l in diff.splitlines(1):
249 249 if l.startswith('+'):
250 250 yield self.t("difflineplus", line=l)
251 251 elif l.startswith('-'):
252 252 yield self.t("difflineminus", line=l)
253 253 elif l.startswith('@'):
254 254 yield self.t("difflineat", line=l)
255 255 else:
256 256 yield self.t("diffline", line=l)
257 257
258 258 r = self.repo
259 259 cl = r.changelog
260 260 mf = r.manifest
261 261 change1 = cl.read(node1)
262 262 change2 = cl.read(node2)
263 263 mmap1 = mf.read(change1[0])
264 264 mmap2 = mf.read(change2[0])
265 265 date1 = util.datestr(change1[2])
266 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 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 274 to = r.file(f).read(mmap1[f])
274 275 tn = r.file(f).read(mmap2[f])
275 276 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
276 for f in a:
277 for f in added:
277 278 to = None
278 279 tn = r.file(f).read(mmap2[f])
279 280 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
280 for f in d:
281 for f in removed:
281 282 to = r.file(f).read(mmap1[f])
282 283 tn = None
283 284 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
284 285
285 286 def changelog(self, pos):
286 287 def changenav(**map):
287 288 def seq(factor=1):
288 289 yield 1 * factor
289 290 yield 3 * factor
290 291 #yield 5 * factor
291 292 for f in seq(factor * 10):
292 293 yield f
293 294
294 295 l = []
295 296 for f in seq():
296 297 if f < self.maxchanges / 2:
297 298 continue
298 299 if f > count:
299 300 break
300 301 r = "%d" % f
301 302 if pos + f < count:
302 303 l.append(("+" + r, pos + f))
303 304 if pos - f >= 0:
304 305 l.insert(0, ("-" + r, pos - f))
305 306
306 307 yield {"rev": 0, "label": "(0)"}
307 308
308 309 for label, rev in l:
309 310 yield {"label": label, "rev": rev}
310 311
311 312 yield {"label": "tip", "rev": "tip"}
312 313
313 314 def changelist(**map):
314 315 parity = (start - end) & 1
315 316 cl = self.repo.changelog
316 317 l = [] # build a list in forward order for efficiency
317 318 for i in range(start, end):
318 319 n = cl.node(i)
319 320 changes = cl.read(n)
320 321 hn = hex(n)
321 322
322 323 l.insert(0, {"parity": parity,
323 324 "author": changes[1],
324 325 "parent": self.siblings(cl.parents(n), cl.rev,
325 326 cl.rev(n) - 1),
326 327 "child": self.siblings(cl.children(n), cl.rev,
327 328 cl.rev(n) + 1),
328 329 "changelogtag": self.showtag("changelogtag",n),
329 330 "manifest": hex(changes[0]),
330 331 "desc": changes[4],
331 332 "date": changes[2],
332 333 "files": self.listfilediffs(changes[3], n),
333 334 "rev": i,
334 335 "node": hn})
335 336 parity = 1 - parity
336 337
337 338 for e in l:
338 339 yield e
339 340
340 341 cl = self.repo.changelog
341 342 mf = cl.read(cl.tip())[0]
342 343 count = cl.count()
343 344 start = max(0, pos - self.maxchanges + 1)
344 345 end = min(count, start + self.maxchanges)
345 346 pos = end - 1
346 347
347 348 yield self.t('changelog',
348 349 changenav=changenav,
349 350 manifest=hex(mf),
350 351 rev=pos, changesets=count, entries=changelist)
351 352
352 353 def search(self, query):
353 354
354 355 def changelist(**map):
355 356 cl = self.repo.changelog
356 357 count = 0
357 358 qw = query.lower().split()
358 359
359 360 def revgen():
360 361 for i in range(cl.count() - 1, 0, -100):
361 362 l = []
362 363 for j in range(max(0, i - 100), i):
363 364 n = cl.node(j)
364 365 changes = cl.read(n)
365 366 l.append((n, j, changes))
366 367 l.reverse()
367 368 for e in l:
368 369 yield e
369 370
370 371 for n, i, changes in revgen():
371 372 miss = 0
372 373 for q in qw:
373 374 if not (q in changes[1].lower() or
374 375 q in changes[4].lower() or
375 376 q in " ".join(changes[3][:20]).lower()):
376 377 miss = 1
377 378 break
378 379 if miss:
379 380 continue
380 381
381 382 count += 1
382 383 hn = hex(n)
383 384
384 385 yield self.t('searchentry',
385 386 parity=count & 1,
386 387 author=changes[1],
387 388 parent=self.siblings(cl.parents(n), cl.rev),
388 389 child=self.siblings(cl.children(n), cl.rev),
389 390 changelogtag=self.showtag("changelogtag",n),
390 391 manifest=hex(changes[0]),
391 392 desc=changes[4],
392 393 date=changes[2],
393 394 files=self.listfilediffs(changes[3], n),
394 395 rev=i,
395 396 node=hn)
396 397
397 398 if count >= self.maxchanges:
398 399 break
399 400
400 401 cl = self.repo.changelog
401 402 mf = cl.read(cl.tip())[0]
402 403
403 404 yield self.t('search',
404 405 query=query,
405 406 manifest=hex(mf),
406 407 entries=changelist)
407 408
408 409 def changeset(self, nodeid):
409 410 cl = self.repo.changelog
410 411 n = self.repo.lookup(nodeid)
411 412 nodeid = hex(n)
412 413 changes = cl.read(n)
413 414 p1 = cl.parents(n)[0]
414 415
415 416 files = []
416 417 mf = self.repo.manifest.read(changes[0])
417 418 for f in changes[3]:
418 419 files.append(self.t("filenodelink",
419 420 filenode=hex(mf.get(f, nullid)), file=f))
420 421
421 422 def diff(**map):
422 423 yield self.diff(p1, n, None)
423 424
424 425 yield self.t('changeset',
425 426 diff=diff,
426 427 rev=cl.rev(n),
427 428 node=nodeid,
428 429 parent=self.siblings(cl.parents(n), cl.rev),
429 430 child=self.siblings(cl.children(n), cl.rev),
430 431 changesettag=self.showtag("changesettag",n),
431 432 manifest=hex(changes[0]),
432 433 author=changes[1],
433 434 desc=changes[4],
434 435 date=changes[2],
435 436 files=files,
436 437 archives=self.archivelist(nodeid))
437 438
438 439 def filelog(self, f, filenode):
439 440 cl = self.repo.changelog
440 441 fl = self.repo.file(f)
441 442 filenode = hex(fl.lookup(filenode))
442 443 count = fl.count()
443 444
444 445 def entries(**map):
445 446 l = []
446 447 parity = (count - 1) & 1
447 448
448 449 for i in range(count):
449 450 n = fl.node(i)
450 451 lr = fl.linkrev(n)
451 452 cn = cl.node(lr)
452 453 cs = cl.read(cl.node(lr))
453 454
454 455 l.insert(0, {"parity": parity,
455 456 "filenode": hex(n),
456 457 "filerev": i,
457 458 "file": f,
458 459 "node": hex(cn),
459 460 "author": cs[1],
460 461 "date": cs[2],
461 462 "parent": self.siblings(fl.parents(n),
462 463 fl.rev, file=f),
463 464 "child": self.siblings(fl.children(n),
464 465 fl.rev, file=f),
465 466 "desc": cs[4]})
466 467 parity = 1 - parity
467 468
468 469 for e in l:
469 470 yield e
470 471
471 472 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
472 473
473 474 def filerevision(self, f, node):
474 475 fl = self.repo.file(f)
475 476 n = fl.lookup(node)
476 477 node = hex(n)
477 478 text = fl.read(n)
478 479 changerev = fl.linkrev(n)
479 480 cl = self.repo.changelog
480 481 cn = cl.node(changerev)
481 482 cs = cl.read(cn)
482 483 mfn = cs[0]
483 484
484 485 mt = mimetypes.guess_type(f)[0]
485 486 rawtext = text
486 487 if util.binary(text):
487 488 text = "(binary:%s)" % mt
488 489
489 490 def lines():
490 491 for l, t in enumerate(text.splitlines(1)):
491 492 yield {"line": t,
492 493 "linenumber": "% 6d" % (l + 1),
493 494 "parity": l & 1}
494 495
495 496 yield self.t("filerevision",
496 497 file=f,
497 498 filenode=node,
498 499 path=up(f),
499 500 text=lines(),
500 501 raw=rawtext,
501 502 mimetype=mt,
502 503 rev=changerev,
503 504 node=hex(cn),
504 505 manifest=hex(mfn),
505 506 author=cs[1],
506 507 date=cs[2],
507 508 parent=self.siblings(fl.parents(n), fl.rev, file=f),
508 509 child=self.siblings(fl.children(n), fl.rev, file=f),
509 510 permissions=self.repo.manifest.readflags(mfn)[f])
510 511
511 512 def fileannotate(self, f, node):
512 513 bcache = {}
513 514 ncache = {}
514 515 fl = self.repo.file(f)
515 516 n = fl.lookup(node)
516 517 node = hex(n)
517 518 changerev = fl.linkrev(n)
518 519
519 520 cl = self.repo.changelog
520 521 cn = cl.node(changerev)
521 522 cs = cl.read(cn)
522 523 mfn = cs[0]
523 524
524 525 def annotate(**map):
525 526 parity = 1
526 527 last = None
527 528 for r, l in fl.annotate(n):
528 529 try:
529 530 cnode = ncache[r]
530 531 except KeyError:
531 532 cnode = ncache[r] = self.repo.changelog.node(r)
532 533
533 534 try:
534 535 name = bcache[r]
535 536 except KeyError:
536 537 cl = self.repo.changelog.read(cnode)
537 538 bcache[r] = name = self.repo.ui.shortuser(cl[1])
538 539
539 540 if last != cnode:
540 541 parity = 1 - parity
541 542 last = cnode
542 543
543 544 yield {"parity": parity,
544 545 "node": hex(cnode),
545 546 "rev": r,
546 547 "author": name,
547 548 "file": f,
548 549 "line": l}
549 550
550 551 yield self.t("fileannotate",
551 552 file=f,
552 553 filenode=node,
553 554 annotate=annotate,
554 555 path=up(f),
555 556 rev=changerev,
556 557 node=hex(cn),
557 558 manifest=hex(mfn),
558 559 author=cs[1],
559 560 date=cs[2],
560 561 parent=self.siblings(fl.parents(n), fl.rev, file=f),
561 562 child=self.siblings(fl.children(n), fl.rev, file=f),
562 563 permissions=self.repo.manifest.readflags(mfn)[f])
563 564
564 565 def manifest(self, mnode, path):
565 566 man = self.repo.manifest
566 567 mn = man.lookup(mnode)
567 568 mnode = hex(mn)
568 569 mf = man.read(mn)
569 570 rev = man.rev(mn)
570 571 node = self.repo.changelog.node(rev)
571 572 mff = man.readflags(mn)
572 573
573 574 files = {}
574 575
575 576 p = path[1:]
576 577 l = len(p)
577 578
578 579 for f,n in mf.items():
579 580 if f[:l] != p:
580 581 continue
581 582 remain = f[l:]
582 583 if "/" in remain:
583 584 short = remain[:remain.find("/") + 1] # bleah
584 585 files[short] = (f, None)
585 586 else:
586 587 short = os.path.basename(remain)
587 588 files[short] = (f, n)
588 589
589 590 def filelist(**map):
590 591 parity = 0
591 592 fl = files.keys()
592 593 fl.sort()
593 594 for f in fl:
594 595 full, fnode = files[f]
595 596 if not fnode:
596 597 continue
597 598
598 599 yield {"file": full,
599 600 "manifest": mnode,
600 601 "filenode": hex(fnode),
601 602 "parity": parity,
602 603 "basename": f,
603 604 "permissions": mff[full]}
604 605 parity = 1 - parity
605 606
606 607 def dirlist(**map):
607 608 parity = 0
608 609 fl = files.keys()
609 610 fl.sort()
610 611 for f in fl:
611 612 full, fnode = files[f]
612 613 if fnode:
613 614 continue
614 615
615 616 yield {"parity": parity,
616 617 "path": os.path.join(path, f),
617 618 "manifest": mnode,
618 619 "basename": f[:-1]}
619 620 parity = 1 - parity
620 621
621 622 yield self.t("manifest",
622 623 manifest=mnode,
623 624 rev=rev,
624 625 node=hex(node),
625 626 path=path,
626 627 up=up(path),
627 628 fentries=filelist,
628 629 dentries=dirlist,
629 630 archives=self.archivelist(hex(node)))
630 631
631 632 def tags(self):
632 633 cl = self.repo.changelog
633 634 mf = cl.read(cl.tip())[0]
634 635
635 636 i = self.repo.tagslist()
636 637 i.reverse()
637 638
638 639 def entries(**map):
639 640 parity = 0
640 641 for k,n in i:
641 642 yield {"parity": parity,
642 643 "tag": k,
643 644 "tagmanifest": hex(cl.read(n)[0]),
644 645 "date": cl.read(n)[2],
645 646 "node": hex(n)}
646 647 parity = 1 - parity
647 648
648 649 yield self.t("tags",
649 650 manifest=hex(mf),
650 651 entries=entries)
651 652
652 653 def summary(self):
653 654 cl = self.repo.changelog
654 655 mf = cl.read(cl.tip())[0]
655 656
656 657 i = self.repo.tagslist()
657 658 i.reverse()
658 659
659 660 def tagentries(**map):
660 661 parity = 0
661 662 count = 0
662 663 for k,n in i:
663 664 if k == "tip": # skip tip
664 665 continue;
665 666
666 667 count += 1
667 668 if count > 10: # limit to 10 tags
668 669 break;
669 670
670 671 c = cl.read(n)
671 672 m = c[0]
672 673 t = c[2]
673 674
674 675 yield self.t("tagentry",
675 676 parity = parity,
676 677 tag = k,
677 678 node = hex(n),
678 679 date = t,
679 680 tagmanifest = hex(m))
680 681 parity = 1 - parity
681 682
682 683 def changelist(**map):
683 684 parity = 0
684 685 cl = self.repo.changelog
685 686 l = [] # build a list in forward order for efficiency
686 687 for i in range(start, end):
687 688 n = cl.node(i)
688 689 changes = cl.read(n)
689 690 hn = hex(n)
690 691 t = changes[2]
691 692
692 693 l.insert(0, self.t(
693 694 'shortlogentry',
694 695 parity = parity,
695 696 author = changes[1],
696 697 manifest = hex(changes[0]),
697 698 desc = changes[4],
698 699 date = t,
699 700 rev = i,
700 701 node = hn))
701 702 parity = 1 - parity
702 703
703 704 yield l
704 705
705 706 cl = self.repo.changelog
706 707 mf = cl.read(cl.tip())[0]
707 708 count = cl.count()
708 709 start = max(0, count - self.maxchanges)
709 710 end = min(count, start + self.maxchanges)
710 711 pos = end - 1
711 712
712 713 yield self.t("summary",
713 714 desc = self.repo.ui.config("web", "description", "unknown"),
714 715 owner = (self.repo.ui.config("ui", "username") or # preferred
715 716 self.repo.ui.config("web", "contact") or # deprecated
716 717 self.repo.ui.config("web", "author", "unknown")), # also
717 718 lastchange = (0, 0), # FIXME
718 719 manifest = hex(mf),
719 720 tags = tagentries,
720 721 shortlog = changelist)
721 722
722 723 def filediff(self, file, changeset):
723 724 cl = self.repo.changelog
724 725 n = self.repo.lookup(changeset)
725 726 changeset = hex(n)
726 727 p1 = cl.parents(n)[0]
727 728 cs = cl.read(n)
728 729 mf = self.repo.manifest.read(cs[0])
729 730
730 731 def diff(**map):
731 732 yield self.diff(p1, n, file)
732 733
733 734 yield self.t("filediff",
734 735 file=file,
735 736 filenode=hex(mf.get(file, nullid)),
736 737 node=changeset,
737 738 rev=self.repo.changelog.rev(n),
738 739 parent=self.siblings(cl.parents(n), cl.rev),
739 740 child=self.siblings(cl.children(n), cl.rev),
740 741 diff=diff)
741 742
742 743 def archive(self, req, cnode, type):
743 744 cs = self.repo.changelog.read(cnode)
744 745 mnode = cs[0]
745 746 mf = self.repo.manifest.read(mnode)
746 747 rev = self.repo.manifest.rev(mnode)
747 748 reponame = re.sub(r"\W+", "-", self.reponame)
748 749 name = "%s-%s/" % (reponame, short(cnode))
749 750
750 751 files = mf.keys()
751 752 files.sort()
752 753
753 754 if type == 'zip':
754 755 tmp = tempfile.mkstemp()[1]
755 756 try:
756 757 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
757 758
758 759 for f in files:
759 760 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
760 761 zf.close()
761 762
762 763 f = open(tmp, 'r')
763 764 req.httphdr('application/zip', name[:-1] + '.zip',
764 765 os.path.getsize(tmp))
765 766 req.write(f.read())
766 767 f.close()
767 768 finally:
768 769 os.unlink(tmp)
769 770
770 771 else:
771 772 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
772 773 mff = self.repo.manifest.readflags(mnode)
773 774 mtime = int(time.time())
774 775
775 776 if type == "gz":
776 777 encoding = "gzip"
777 778 else:
778 779 encoding = "x-bzip2"
779 780 req.header([('Content-type', 'application/x-tar'),
780 781 ('Content-disposition', 'attachment; filename=%s%s%s' %
781 782 (name[:-1], '.tar.', type)),
782 783 ('Content-encoding', encoding)])
783 784 for fname in files:
784 785 rcont = self.repo.file(fname).read(mf[fname])
785 786 finfo = tarfile.TarInfo(name + fname)
786 787 finfo.mtime = mtime
787 788 finfo.size = len(rcont)
788 789 finfo.mode = mff[fname] and 0755 or 0644
789 790 tf.addfile(finfo, StringIO.StringIO(rcont))
790 791 tf.close()
791 792
792 793 # add tags to things
793 794 # tags -> list of changesets corresponding to tags
794 795 # find tag, changeset, file
795 796
796 797 def run(self, req=hgrequest()):
797 798 def header(**map):
798 799 yield self.t("header", **map)
799 800
800 801 def footer(**map):
801 802 yield self.t("footer", **map)
802 803
803 804 def expand_form(form):
804 805 shortcuts = {
805 806 'cl': [('cmd', ['changelog']), ('rev', None)],
806 807 'cs': [('cmd', ['changeset']), ('node', None)],
807 808 'f': [('cmd', ['file']), ('filenode', None)],
808 809 'fl': [('cmd', ['filelog']), ('filenode', None)],
809 810 'fd': [('cmd', ['filediff']), ('node', None)],
810 811 'fa': [('cmd', ['annotate']), ('filenode', None)],
811 812 'mf': [('cmd', ['manifest']), ('manifest', None)],
812 813 'ca': [('cmd', ['archive']), ('node', None)],
813 814 'tags': [('cmd', ['tags'])],
814 815 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
815 816 }
816 817
817 818 for k in shortcuts.iterkeys():
818 819 if form.has_key(k):
819 820 for name, value in shortcuts[k]:
820 821 if value is None:
821 822 value = form[k]
822 823 form[name] = value
823 824 del form[k]
824 825
825 826 self.refresh()
826 827
827 828 expand_form(req.form)
828 829
829 830 t = self.repo.ui.config("web", "templates", templatepath())
830 831 m = os.path.join(t, "map")
831 832 style = self.repo.ui.config("web", "style", "")
832 833 if req.form.has_key('style'):
833 834 style = req.form['style'][0]
834 835 if style:
835 836 b = os.path.basename("map-" + style)
836 837 p = os.path.join(t, b)
837 838 if os.path.isfile(p):
838 839 m = p
839 840
840 841 port = req.env["SERVER_PORT"]
841 842 port = port != "80" and (":" + port) or ""
842 843 uri = req.env["REQUEST_URI"]
843 844 if "?" in uri:
844 845 uri = uri.split("?")[0]
845 846 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
846 847 if not self.reponame:
847 848 self.reponame = (self.repo.ui.config("web", "name")
848 849 or uri.strip('/') or self.repo.root)
849 850
850 851 self.t = templater(m, common_filters,
851 852 {"url": url,
852 853 "repo": self.reponame,
853 854 "header": header,
854 855 "footer": footer,
855 856 })
856 857
857 858 if not req.form.has_key('cmd'):
858 859 req.form['cmd'] = [self.t.cache['default'],]
859 860
860 861 if req.form['cmd'][0] == 'changelog':
861 862 c = self.repo.changelog.count() - 1
862 863 hi = c
863 864 if req.form.has_key('rev'):
864 865 hi = req.form['rev'][0]
865 866 try:
866 867 hi = self.repo.changelog.rev(self.repo.lookup(hi))
867 868 except hg.RepoError:
868 869 req.write(self.search(hi))
869 870 return
870 871
871 872 req.write(self.changelog(hi))
872 873
873 874 elif req.form['cmd'][0] == 'changeset':
874 875 req.write(self.changeset(req.form['node'][0]))
875 876
876 877 elif req.form['cmd'][0] == 'manifest':
877 878 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
878 879
879 880 elif req.form['cmd'][0] == 'tags':
880 881 req.write(self.tags())
881 882
882 883 elif req.form['cmd'][0] == 'summary':
883 884 req.write(self.summary())
884 885
885 886 elif req.form['cmd'][0] == 'filediff':
886 887 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
887 888
888 889 elif req.form['cmd'][0] == 'file':
889 890 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
890 891
891 892 elif req.form['cmd'][0] == 'annotate':
892 893 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
893 894
894 895 elif req.form['cmd'][0] == 'filelog':
895 896 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
896 897
897 898 elif req.form['cmd'][0] == 'heads':
898 899 req.httphdr("application/mercurial-0.1")
899 900 h = self.repo.heads()
900 901 req.write(" ".join(map(hex, h)) + "\n")
901 902
902 903 elif req.form['cmd'][0] == 'branches':
903 904 req.httphdr("application/mercurial-0.1")
904 905 nodes = []
905 906 if req.form.has_key('nodes'):
906 907 nodes = map(bin, req.form['nodes'][0].split(" "))
907 908 for b in self.repo.branches(nodes):
908 909 req.write(" ".join(map(hex, b)) + "\n")
909 910
910 911 elif req.form['cmd'][0] == 'between':
911 912 req.httphdr("application/mercurial-0.1")
912 913 nodes = []
913 914 if req.form.has_key('pairs'):
914 915 pairs = [map(bin, p.split("-"))
915 916 for p in req.form['pairs'][0].split(" ")]
916 917 for b in self.repo.between(pairs):
917 918 req.write(" ".join(map(hex, b)) + "\n")
918 919
919 920 elif req.form['cmd'][0] == 'changegroup':
920 921 req.httphdr("application/mercurial-0.1")
921 922 nodes = []
922 923 if not self.allowpull:
923 924 return
924 925
925 926 if req.form.has_key('roots'):
926 927 nodes = map(bin, req.form['roots'][0].split(" "))
927 928
928 929 z = zlib.compressobj()
929 930 f = self.repo.changegroup(nodes)
930 931 while 1:
931 932 chunk = f.read(4096)
932 933 if not chunk:
933 934 break
934 935 req.write(z.compress(chunk))
935 936
936 937 req.write(z.flush())
937 938
938 939 elif req.form['cmd'][0] == 'archive':
939 940 changeset = self.repo.lookup(req.form['node'][0])
940 941 type = req.form['type'][0]
941 942 if (type in self.archives and
942 943 self.repo.ui.configbool("web", "allow" + type, False)):
943 944 self.archive(req, changeset, type)
944 945 return
945 946
946 947 req.write(self.t("error"))
947 948
948 949 else:
949 950 req.write(self.t("error"))
950 951
951 952 def create_server(repo):
952 953
953 954 def openlog(opt, default):
954 955 if opt and opt != '-':
955 956 return open(opt, 'w')
956 957 return default
957 958
958 959 address = repo.ui.config("web", "address", "")
959 960 port = int(repo.ui.config("web", "port", 8000))
960 961 use_ipv6 = repo.ui.configbool("web", "ipv6")
961 962 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
962 963 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
963 964
964 965 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
965 966 address_family = getattr(socket, 'AF_INET6', None)
966 967
967 968 def __init__(self, *args, **kwargs):
968 969 if self.address_family is None:
969 970 raise hg.RepoError(_('IPv6 not available on this system'))
970 971 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
971 972
972 973 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
973 974 def log_error(self, format, *args):
974 975 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
975 976 self.log_date_time_string(),
976 977 format % args))
977 978
978 979 def log_message(self, format, *args):
979 980 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
980 981 self.log_date_time_string(),
981 982 format % args))
982 983
983 984 def do_POST(self):
984 985 try:
985 986 self.do_hgweb()
986 987 except socket.error, inst:
987 988 if inst[0] != errno.EPIPE:
988 989 raise
989 990
990 991 def do_GET(self):
991 992 self.do_POST()
992 993
993 994 def do_hgweb(self):
994 995 query = ""
995 996 p = self.path.find("?")
996 997 if p:
997 998 query = self.path[p + 1:]
998 999 query = query.replace('+', ' ')
999 1000
1000 1001 env = {}
1001 1002 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
1002 1003 env['REQUEST_METHOD'] = self.command
1003 1004 env['SERVER_NAME'] = self.server.server_name
1004 1005 env['SERVER_PORT'] = str(self.server.server_port)
1005 1006 env['REQUEST_URI'] = "/"
1006 1007 if query:
1007 1008 env['QUERY_STRING'] = query
1008 1009 host = self.address_string()
1009 1010 if host != self.client_address[0]:
1010 1011 env['REMOTE_HOST'] = host
1011 1012 env['REMOTE_ADDR'] = self.client_address[0]
1012 1013
1013 1014 if self.headers.typeheader is None:
1014 1015 env['CONTENT_TYPE'] = self.headers.type
1015 1016 else:
1016 1017 env['CONTENT_TYPE'] = self.headers.typeheader
1017 1018 length = self.headers.getheader('content-length')
1018 1019 if length:
1019 1020 env['CONTENT_LENGTH'] = length
1020 1021 accept = []
1021 1022 for line in self.headers.getallmatchingheaders('accept'):
1022 1023 if line[:1] in "\t\n\r ":
1023 1024 accept.append(line.strip())
1024 1025 else:
1025 1026 accept = accept + line[7:].split(',')
1026 1027 env['HTTP_ACCEPT'] = ','.join(accept)
1027 1028
1028 1029 req = hgrequest(self.rfile, self.wfile, env)
1029 1030 self.send_response(200, "Script output follows")
1030 1031 hg.run(req)
1031 1032
1032 1033 hg = hgweb(repo)
1033 1034 if use_ipv6:
1034 1035 return IPv6HTTPServer((address, port), hgwebhandler)
1035 1036 else:
1036 1037 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
1037 1038
1038 1039 # This is a stopgap
1039 1040 class hgwebdir(object):
1040 1041 def __init__(self, config):
1041 1042 def cleannames(items):
1042 1043 return [(name.strip('/'), path) for name, path in items]
1043 1044
1044 1045 if type(config) == type([]):
1045 1046 self.repos = cleannames(config)
1046 1047 elif type(config) == type({}):
1047 1048 self.repos = cleannames(config.items())
1048 1049 self.repos.sort()
1049 1050 else:
1050 1051 cp = ConfigParser.SafeConfigParser()
1051 1052 cp.read(config)
1052 1053 self.repos = cleannames(cp.items("paths"))
1053 1054 self.repos.sort()
1054 1055
1055 1056 def run(self, req=hgrequest()):
1056 1057 def header(**map):
1057 1058 yield tmpl("header", **map)
1058 1059
1059 1060 def footer(**map):
1060 1061 yield tmpl("footer", **map)
1061 1062
1062 1063 m = os.path.join(templatepath(), "map")
1063 1064 tmpl = templater(m, common_filters,
1064 1065 {"header": header, "footer": footer})
1065 1066
1066 1067 def entries(**map):
1067 1068 parity = 0
1068 1069 for name, path in self.repos:
1069 1070 u = ui.ui()
1070 1071 try:
1071 1072 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
1072 1073 except IOError:
1073 1074 pass
1074 1075 get = u.config
1075 1076
1076 1077 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
1077 1078 .replace("//", "/"))
1078 1079
1079 1080 # update time with local timezone
1080 1081 try:
1081 1082 d = (get_mtime(path), util.makedate()[1])
1082 1083 except OSError:
1083 1084 continue
1084 1085
1085 1086 yield dict(contact=(get("ui", "username") or # preferred
1086 1087 get("web", "contact") or # deprecated
1087 1088 get("web", "author", "unknown")), # also
1088 1089 name=get("web", "name", name),
1089 1090 url=url,
1090 1091 parity=parity,
1091 1092 shortdesc=get("web", "description", "unknown"),
1092 1093 lastupdate=d)
1093 1094
1094 1095 parity = 1 - parity
1095 1096
1096 1097 virtual = req.env.get("PATH_INFO", "").strip('/')
1097 1098 if virtual:
1098 1099 real = dict(self.repos).get(virtual)
1099 1100 if real:
1100 1101 try:
1101 1102 hgweb(real).run(req)
1102 1103 except IOError, inst:
1103 1104 req.write(tmpl("error", error=inst.strerror))
1104 1105 except hg.RepoError, inst:
1105 1106 req.write(tmpl("error", error=str(inst)))
1106 1107 else:
1107 1108 req.write(tmpl("notfound", repo=virtual))
1108 1109 else:
1109 1110 req.write(tmpl("index", entries=entries))
@@ -1,1817 +1,1817
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import struct, os, util
9 9 import filelog, manifest, changelog, dirstate, repo
10 10 from node import *
11 11 from i18n import gettext as _
12 12 from demandload import *
13 13 demandload(globals(), "re lock transaction tempfile stat mdiff errno")
14 14
15 15 class localrepository(object):
16 16 def __init__(self, ui, path=None, create=0):
17 17 if not path:
18 18 p = os.getcwd()
19 19 while not os.path.isdir(os.path.join(p, ".hg")):
20 20 oldp = p
21 21 p = os.path.dirname(p)
22 22 if p == oldp:
23 23 raise repo.RepoError(_("no repo found"))
24 24 path = p
25 25 self.path = os.path.join(path, ".hg")
26 26
27 27 if not create and not os.path.isdir(self.path):
28 28 raise repo.RepoError(_("repository %s not found") % path)
29 29
30 30 self.root = os.path.abspath(path)
31 31 self.ui = ui
32 32 self.opener = util.opener(self.path)
33 33 self.wopener = util.opener(self.root)
34 34 self.manifest = manifest.manifest(self.opener)
35 35 self.changelog = changelog.changelog(self.opener)
36 36 self.tagscache = None
37 37 self.nodetagscache = None
38 38 self.encodepats = None
39 39 self.decodepats = None
40 40
41 41 if create:
42 42 os.mkdir(self.path)
43 43 os.mkdir(self.join("data"))
44 44
45 45 self.dirstate = dirstate.dirstate(self.opener, ui, self.root)
46 46 try:
47 47 self.ui.readconfig(self.join("hgrc"))
48 48 except IOError:
49 49 pass
50 50
51 51 def hook(self, name, **args):
52 52 def runhook(name, cmd):
53 53 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
54 54 old = {}
55 55 for k, v in args.items():
56 56 k = k.upper()
57 57 old[k] = os.environ.get(k, None)
58 58 os.environ[k] = v
59 59
60 60 # Hooks run in the repository root
61 61 olddir = os.getcwd()
62 62 os.chdir(self.root)
63 63 r = os.system(cmd)
64 64 os.chdir(olddir)
65 65
66 66 for k, v in old.items():
67 67 if v != None:
68 68 os.environ[k] = v
69 69 else:
70 70 del os.environ[k]
71 71
72 72 if r:
73 73 self.ui.warn(_("abort: %s hook failed with status %d!\n") %
74 74 (name, r))
75 75 return False
76 76 return True
77 77
78 78 r = True
79 79 for hname, cmd in self.ui.configitems("hooks"):
80 80 s = hname.split(".")
81 81 if s[0] == name and cmd:
82 82 r = runhook(hname, cmd) and r
83 83 return r
84 84
85 85 def tags(self):
86 86 '''return a mapping of tag to node'''
87 87 if not self.tagscache:
88 88 self.tagscache = {}
89 89 def addtag(self, k, n):
90 90 try:
91 91 bin_n = bin(n)
92 92 except TypeError:
93 93 bin_n = ''
94 94 self.tagscache[k.strip()] = bin_n
95 95
96 96 try:
97 97 # read each head of the tags file, ending with the tip
98 98 # and add each tag found to the map, with "newer" ones
99 99 # taking precedence
100 100 fl = self.file(".hgtags")
101 101 h = fl.heads()
102 102 h.reverse()
103 103 for r in h:
104 104 for l in fl.read(r).splitlines():
105 105 if l:
106 106 n, k = l.split(" ", 1)
107 107 addtag(self, k, n)
108 108 except KeyError:
109 109 pass
110 110
111 111 try:
112 112 f = self.opener("localtags")
113 113 for l in f:
114 114 n, k = l.split(" ", 1)
115 115 addtag(self, k, n)
116 116 except IOError:
117 117 pass
118 118
119 119 self.tagscache['tip'] = self.changelog.tip()
120 120
121 121 return self.tagscache
122 122
123 123 def tagslist(self):
124 124 '''return a list of tags ordered by revision'''
125 125 l = []
126 126 for t, n in self.tags().items():
127 127 try:
128 128 r = self.changelog.rev(n)
129 129 except:
130 130 r = -2 # sort to the beginning of the list if unknown
131 131 l.append((r, t, n))
132 132 l.sort()
133 133 return [(t, n) for r, t, n in l]
134 134
135 135 def nodetags(self, node):
136 136 '''return the tags associated with a node'''
137 137 if not self.nodetagscache:
138 138 self.nodetagscache = {}
139 139 for t, n in self.tags().items():
140 140 self.nodetagscache.setdefault(n, []).append(t)
141 141 return self.nodetagscache.get(node, [])
142 142
143 143 def lookup(self, key):
144 144 try:
145 145 return self.tags()[key]
146 146 except KeyError:
147 147 try:
148 148 return self.changelog.lookup(key)
149 149 except:
150 150 raise repo.RepoError(_("unknown revision '%s'") % key)
151 151
152 152 def dev(self):
153 153 return os.stat(self.path).st_dev
154 154
155 155 def local(self):
156 156 return True
157 157
158 158 def join(self, f):
159 159 return os.path.join(self.path, f)
160 160
161 161 def wjoin(self, f):
162 162 return os.path.join(self.root, f)
163 163
164 164 def file(self, f):
165 165 if f[0] == '/':
166 166 f = f[1:]
167 167 return filelog.filelog(self.opener, f)
168 168
169 169 def getcwd(self):
170 170 return self.dirstate.getcwd()
171 171
172 172 def wfile(self, f, mode='r'):
173 173 return self.wopener(f, mode)
174 174
175 175 def wread(self, filename):
176 176 if self.encodepats == None:
177 177 l = []
178 178 for pat, cmd in self.ui.configitems("encode"):
179 179 mf = util.matcher("", "/", [pat], [], [])[1]
180 180 l.append((mf, cmd))
181 181 self.encodepats = l
182 182
183 183 data = self.wopener(filename, 'r').read()
184 184
185 185 for mf, cmd in self.encodepats:
186 186 if mf(filename):
187 187 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
188 188 data = util.filter(data, cmd)
189 189 break
190 190
191 191 return data
192 192
193 193 def wwrite(self, filename, data, fd=None):
194 194 if self.decodepats == None:
195 195 l = []
196 196 for pat, cmd in self.ui.configitems("decode"):
197 197 mf = util.matcher("", "/", [pat], [], [])[1]
198 198 l.append((mf, cmd))
199 199 self.decodepats = l
200 200
201 201 for mf, cmd in self.decodepats:
202 202 if mf(filename):
203 203 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
204 204 data = util.filter(data, cmd)
205 205 break
206 206
207 207 if fd:
208 208 return fd.write(data)
209 209 return self.wopener(filename, 'w').write(data)
210 210
211 211 def transaction(self):
212 212 # save dirstate for undo
213 213 try:
214 214 ds = self.opener("dirstate").read()
215 215 except IOError:
216 216 ds = ""
217 217 self.opener("journal.dirstate", "w").write(ds)
218 218
219 219 def after():
220 220 util.rename(self.join("journal"), self.join("undo"))
221 221 util.rename(self.join("journal.dirstate"),
222 222 self.join("undo.dirstate"))
223 223
224 224 return transaction.transaction(self.ui.warn, self.opener,
225 225 self.join("journal"), after)
226 226
227 227 def recover(self):
228 228 lock = self.lock()
229 229 if os.path.exists(self.join("journal")):
230 230 self.ui.status(_("rolling back interrupted transaction\n"))
231 231 transaction.rollback(self.opener, self.join("journal"))
232 232 self.manifest = manifest.manifest(self.opener)
233 233 self.changelog = changelog.changelog(self.opener)
234 234 return True
235 235 else:
236 236 self.ui.warn(_("no interrupted transaction available\n"))
237 237 return False
238 238
239 239 def undo(self):
240 240 wlock = self.wlock()
241 241 lock = self.lock()
242 242 if os.path.exists(self.join("undo")):
243 243 self.ui.status(_("rolling back last transaction\n"))
244 244 transaction.rollback(self.opener, self.join("undo"))
245 245 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
246 246 self.dirstate.read()
247 247 else:
248 248 self.ui.warn(_("no undo information available\n"))
249 249
250 250 def lock(self, wait=1):
251 251 try:
252 252 return lock.lock(self.join("lock"), 0)
253 253 except lock.LockHeld, inst:
254 254 if wait:
255 255 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
256 256 return lock.lock(self.join("lock"), wait)
257 257 raise inst
258 258
259 259 def wlock(self, wait=1):
260 260 try:
261 261 wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
262 262 except lock.LockHeld, inst:
263 263 if not wait:
264 264 raise inst
265 265 self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
266 266 wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
267 267 self.dirstate.read()
268 268 return wlock
269 269
270 270 def rawcommit(self, files, text, user, date, p1=None, p2=None):
271 271 orig_parent = self.dirstate.parents()[0] or nullid
272 272 p1 = p1 or self.dirstate.parents()[0] or nullid
273 273 p2 = p2 or self.dirstate.parents()[1] or nullid
274 274 c1 = self.changelog.read(p1)
275 275 c2 = self.changelog.read(p2)
276 276 m1 = self.manifest.read(c1[0])
277 277 mf1 = self.manifest.readflags(c1[0])
278 278 m2 = self.manifest.read(c2[0])
279 279 changed = []
280 280
281 281 if orig_parent == p1:
282 282 update_dirstate = 1
283 283 else:
284 284 update_dirstate = 0
285 285
286 286 wlock = self.wlock()
287 287 lock = self.lock()
288 288 tr = self.transaction()
289 289 mm = m1.copy()
290 290 mfm = mf1.copy()
291 291 linkrev = self.changelog.count()
292 292 for f in files:
293 293 try:
294 294 t = self.wread(f)
295 295 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
296 296 r = self.file(f)
297 297 mfm[f] = tm
298 298
299 299 fp1 = m1.get(f, nullid)
300 300 fp2 = m2.get(f, nullid)
301 301
302 302 # is the same revision on two branches of a merge?
303 303 if fp2 == fp1:
304 304 fp2 = nullid
305 305
306 306 if fp2 != nullid:
307 307 # is one parent an ancestor of the other?
308 308 fpa = r.ancestor(fp1, fp2)
309 309 if fpa == fp1:
310 310 fp1, fp2 = fp2, nullid
311 311 elif fpa == fp2:
312 312 fp2 = nullid
313 313
314 314 # is the file unmodified from the parent?
315 315 if t == r.read(fp1):
316 316 # record the proper existing parent in manifest
317 317 # no need to add a revision
318 318 mm[f] = fp1
319 319 continue
320 320
321 321 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
322 322 changed.append(f)
323 323 if update_dirstate:
324 324 self.dirstate.update([f], "n")
325 325 except IOError:
326 326 try:
327 327 del mm[f]
328 328 del mfm[f]
329 329 if update_dirstate:
330 330 self.dirstate.forget([f])
331 331 except:
332 332 # deleted from p2?
333 333 pass
334 334
335 335 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
336 336 user = user or self.ui.username()
337 337 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
338 338 tr.close()
339 339 if update_dirstate:
340 340 self.dirstate.setparents(n, nullid)
341 341
342 342 def commit(self, files=None, text="", user=None, date=None,
343 343 match=util.always, force=False):
344 344 commit = []
345 345 remove = []
346 346 changed = []
347 347
348 348 if files:
349 349 for f in files:
350 350 s = self.dirstate.state(f)
351 351 if s in 'nmai':
352 352 commit.append(f)
353 353 elif s == 'r':
354 354 remove.append(f)
355 355 else:
356 356 self.ui.warn(_("%s not tracked!\n") % f)
357 357 else:
358 (c, a, d, u) = self.changes(match=match)
359 commit = c + a
360 remove = d
358 modified, added, removed, unknown = self.changes(match=match)
359 commit = modified + added
360 remove = removed
361 361
362 362 p1, p2 = self.dirstate.parents()
363 363 c1 = self.changelog.read(p1)
364 364 c2 = self.changelog.read(p2)
365 365 m1 = self.manifest.read(c1[0])
366 366 mf1 = self.manifest.readflags(c1[0])
367 367 m2 = self.manifest.read(c2[0])
368 368
369 369 if not commit and not remove and not force and p2 == nullid:
370 370 self.ui.status(_("nothing changed\n"))
371 371 return None
372 372
373 373 if not self.hook("precommit"):
374 374 return None
375 375
376 376 wlock = self.wlock()
377 377 lock = self.lock()
378 378 tr = self.transaction()
379 379
380 380 # check in files
381 381 new = {}
382 382 linkrev = self.changelog.count()
383 383 commit.sort()
384 384 for f in commit:
385 385 self.ui.note(f + "\n")
386 386 try:
387 387 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
388 388 t = self.wread(f)
389 389 except IOError:
390 390 self.ui.warn(_("trouble committing %s!\n") % f)
391 391 raise
392 392
393 393 r = self.file(f)
394 394
395 395 meta = {}
396 396 cp = self.dirstate.copied(f)
397 397 if cp:
398 398 meta["copy"] = cp
399 399 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
400 400 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
401 401 fp1, fp2 = nullid, nullid
402 402 else:
403 403 fp1 = m1.get(f, nullid)
404 404 fp2 = m2.get(f, nullid)
405 405
406 406 if fp2 != nullid:
407 407 # is one parent an ancestor of the other?
408 408 fpa = r.ancestor(fp1, fp2)
409 409 if fpa == fp1:
410 410 fp1, fp2 = fp2, nullid
411 411 elif fpa == fp2:
412 412 fp2 = nullid
413 413
414 414 # is the file unmodified from the parent?
415 415 if not meta and t == r.read(fp1) and fp2 == nullid:
416 416 # record the proper existing parent in manifest
417 417 # no need to add a revision
418 418 new[f] = fp1
419 419 continue
420 420
421 421 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
422 422 # remember what we've added so that we can later calculate
423 423 # the files to pull from a set of changesets
424 424 changed.append(f)
425 425
426 426 # update manifest
427 427 m1.update(new)
428 428 for f in remove:
429 429 if f in m1:
430 430 del m1[f]
431 431 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
432 432 (new, remove))
433 433
434 434 # add changeset
435 435 new = new.keys()
436 436 new.sort()
437 437
438 438 if not text:
439 439 edittext = ""
440 440 if p2 != nullid:
441 441 edittext += "HG: branch merge\n"
442 442 edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
443 443 edittext += "".join(["HG: changed %s\n" % f for f in changed])
444 444 edittext += "".join(["HG: removed %s\n" % f for f in remove])
445 445 if not changed and not remove:
446 446 edittext += "HG: no files changed\n"
447 447 edittext = self.ui.edit(edittext)
448 448 if not edittext.rstrip():
449 449 return None
450 450 text = edittext
451 451
452 452 user = user or self.ui.username()
453 453 n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date)
454 454 tr.close()
455 455
456 456 self.dirstate.setparents(n)
457 457 self.dirstate.update(new, "n")
458 458 self.dirstate.forget(remove)
459 459
460 460 if not self.hook("commit", node=hex(n)):
461 461 return None
462 462 return n
463 463
464 464 def walk(self, node=None, files=[], match=util.always):
465 465 if node:
466 466 fdict = dict.fromkeys(files)
467 467 for fn in self.manifest.read(self.changelog.read(node)[0]):
468 468 fdict.pop(fn, None)
469 469 if match(fn):
470 470 yield 'm', fn
471 471 for fn in fdict:
472 472 self.ui.warn(_('%s: No such file in rev %s\n') % (
473 473 util.pathto(self.getcwd(), fn), short(node)))
474 474 else:
475 475 for src, fn in self.dirstate.walk(files, match):
476 476 yield src, fn
477 477
478 478 def changes(self, node1=None, node2=None, files=[], match=util.always):
479 479 """return changes between two nodes or node and working directory
480 480
481 481 If node1 is None, use the first dirstate parent instead.
482 482 If node2 is None, compare node1 with working directory.
483 483 """
484 484
485 485 def fcmp(fn, mf):
486 486 t1 = self.wread(fn)
487 487 t2 = self.file(fn).read(mf.get(fn, nullid))
488 488 return cmp(t1, t2)
489 489
490 490 def mfmatches(node):
491 491 change = self.changelog.read(node)
492 492 mf = dict(self.manifest.read(change[0]))
493 493 for fn in mf.keys():
494 494 if not match(fn):
495 495 del mf[fn]
496 496 return mf
497 497
498 498 # are we comparing the working directory?
499 499 if not node2:
500 500 try:
501 501 wlock = self.wlock(wait=0)
502 502 except lock.LockHeld:
503 503 wlock = None
504 504 lookup, modified, added, removed, deleted, unknown = (
505 505 self.dirstate.changes(files, match))
506 506
507 507 # are we comparing working dir against its parent?
508 508 if not node1:
509 509 if lookup:
510 510 # do a full compare of any files that might have changed
511 511 mf2 = mfmatches(self.dirstate.parents()[0])
512 512 for f in lookup:
513 513 if fcmp(f, mf2):
514 514 modified.append(f)
515 515 elif wlock is not None:
516 516 self.dirstate.update([f], "n")
517 517 else:
518 518 # we are comparing working dir against non-parent
519 519 # generate a pseudo-manifest for the working dir
520 520 mf2 = mfmatches(self.dirstate.parents()[0])
521 521 for f in lookup + modified + added:
522 522 mf2[f] = ""
523 523 for f in removed:
524 524 if f in mf2:
525 525 del mf2[f]
526 526 else:
527 527 # we are comparing two revisions
528 528 deleted, unknown = [], []
529 529 mf2 = mfmatches(node2)
530 530
531 531 if node1:
532 532 # flush lists from dirstate before comparing manifests
533 533 modified, added = [], []
534 534
535 535 mf1 = mfmatches(node1)
536 536
537 537 for fn in mf2:
538 538 if mf1.has_key(fn):
539 539 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
540 540 modified.append(fn)
541 541 del mf1[fn]
542 542 else:
543 543 added.append(fn)
544 544
545 545 removed = mf1.keys()
546 546
547 547 removed.extend(deleted) #XXX get rid of this when returning deleted
548 548
549 549 # sort and return results:
550 550 for l in modified, added, removed, unknown:
551 551 l.sort()
552 552 return (modified, added, removed, unknown)
553 553
554 554 def add(self, list):
555 555 wlock = self.wlock()
556 556 for f in list:
557 557 p = self.wjoin(f)
558 558 if not os.path.exists(p):
559 559 self.ui.warn(_("%s does not exist!\n") % f)
560 560 elif not os.path.isfile(p):
561 561 self.ui.warn(_("%s not added: only files supported currently\n")
562 562 % f)
563 563 elif self.dirstate.state(f) in 'an':
564 564 self.ui.warn(_("%s already tracked!\n") % f)
565 565 else:
566 566 self.dirstate.update([f], "a")
567 567
568 568 def forget(self, list):
569 569 wlock = self.wlock()
570 570 for f in list:
571 571 if self.dirstate.state(f) not in 'ai':
572 572 self.ui.warn(_("%s not added!\n") % f)
573 573 else:
574 574 self.dirstate.forget([f])
575 575
576 576 def remove(self, list, unlink=False):
577 577 if unlink:
578 578 for f in list:
579 579 try:
580 580 util.unlink(self.wjoin(f))
581 581 except OSError, inst:
582 582 if inst.errno != errno.ENOENT:
583 583 raise
584 584 wlock = self.wlock()
585 585 for f in list:
586 586 p = self.wjoin(f)
587 587 if os.path.exists(p):
588 588 self.ui.warn(_("%s still exists!\n") % f)
589 589 elif self.dirstate.state(f) == 'a':
590 590 self.ui.warn(_("%s never committed!\n") % f)
591 591 self.dirstate.forget([f])
592 592 elif f not in self.dirstate:
593 593 self.ui.warn(_("%s not tracked!\n") % f)
594 594 else:
595 595 self.dirstate.update([f], "r")
596 596
597 597 def undelete(self, list):
598 598 p = self.dirstate.parents()[0]
599 599 mn = self.changelog.read(p)[0]
600 600 mf = self.manifest.readflags(mn)
601 601 m = self.manifest.read(mn)
602 602 wlock = self.wlock()
603 603 for f in list:
604 604 if self.dirstate.state(f) not in "r":
605 605 self.ui.warn("%s not removed!\n" % f)
606 606 else:
607 607 t = self.file(f).read(m[f])
608 608 self.wwrite(f, t)
609 609 util.set_exec(self.wjoin(f), mf[f])
610 610 self.dirstate.update([f], "n")
611 611
612 612 def copy(self, source, dest):
613 613 p = self.wjoin(dest)
614 614 if not os.path.exists(p):
615 615 self.ui.warn(_("%s does not exist!\n") % dest)
616 616 elif not os.path.isfile(p):
617 617 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
618 618 else:
619 619 wlock = self.wlock()
620 620 if self.dirstate.state(dest) == '?':
621 621 self.dirstate.update([dest], "a")
622 622 self.dirstate.copy(source, dest)
623 623
624 624 def heads(self, start=None):
625 625 heads = self.changelog.heads(start)
626 626 # sort the output in rev descending order
627 627 heads = [(-self.changelog.rev(h), h) for h in heads]
628 628 heads.sort()
629 629 return [n for (r, n) in heads]
630 630
631 631 # branchlookup returns a dict giving a list of branches for
632 632 # each head. A branch is defined as the tag of a node or
633 633 # the branch of the node's parents. If a node has multiple
634 634 # branch tags, tags are eliminated if they are visible from other
635 635 # branch tags.
636 636 #
637 637 # So, for this graph: a->b->c->d->e
638 638 # \ /
639 639 # aa -----/
640 640 # a has tag 2.6.12
641 641 # d has tag 2.6.13
642 642 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
643 643 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
644 644 # from the list.
645 645 #
646 646 # It is possible that more than one head will have the same branch tag.
647 647 # callers need to check the result for multiple heads under the same
648 648 # branch tag if that is a problem for them (ie checkout of a specific
649 649 # branch).
650 650 #
651 651 # passing in a specific branch will limit the depth of the search
652 652 # through the parents. It won't limit the branches returned in the
653 653 # result though.
654 654 def branchlookup(self, heads=None, branch=None):
655 655 if not heads:
656 656 heads = self.heads()
657 657 headt = [ h for h in heads ]
658 658 chlog = self.changelog
659 659 branches = {}
660 660 merges = []
661 661 seenmerge = {}
662 662
663 663 # traverse the tree once for each head, recording in the branches
664 664 # dict which tags are visible from this head. The branches
665 665 # dict also records which tags are visible from each tag
666 666 # while we traverse.
667 667 while headt or merges:
668 668 if merges:
669 669 n, found = merges.pop()
670 670 visit = [n]
671 671 else:
672 672 h = headt.pop()
673 673 visit = [h]
674 674 found = [h]
675 675 seen = {}
676 676 while visit:
677 677 n = visit.pop()
678 678 if n in seen:
679 679 continue
680 680 pp = chlog.parents(n)
681 681 tags = self.nodetags(n)
682 682 if tags:
683 683 for x in tags:
684 684 if x == 'tip':
685 685 continue
686 686 for f in found:
687 687 branches.setdefault(f, {})[n] = 1
688 688 branches.setdefault(n, {})[n] = 1
689 689 break
690 690 if n not in found:
691 691 found.append(n)
692 692 if branch in tags:
693 693 continue
694 694 seen[n] = 1
695 695 if pp[1] != nullid and n not in seenmerge:
696 696 merges.append((pp[1], [x for x in found]))
697 697 seenmerge[n] = 1
698 698 if pp[0] != nullid:
699 699 visit.append(pp[0])
700 700 # traverse the branches dict, eliminating branch tags from each
701 701 # head that are visible from another branch tag for that head.
702 702 out = {}
703 703 viscache = {}
704 704 for h in heads:
705 705 def visible(node):
706 706 if node in viscache:
707 707 return viscache[node]
708 708 ret = {}
709 709 visit = [node]
710 710 while visit:
711 711 x = visit.pop()
712 712 if x in viscache:
713 713 ret.update(viscache[x])
714 714 elif x not in ret:
715 715 ret[x] = 1
716 716 if x in branches:
717 717 visit[len(visit):] = branches[x].keys()
718 718 viscache[node] = ret
719 719 return ret
720 720 if h not in branches:
721 721 continue
722 722 # O(n^2), but somewhat limited. This only searches the
723 723 # tags visible from a specific head, not all the tags in the
724 724 # whole repo.
725 725 for b in branches[h]:
726 726 vis = False
727 727 for bb in branches[h].keys():
728 728 if b != bb:
729 729 if b in visible(bb):
730 730 vis = True
731 731 break
732 732 if not vis:
733 733 l = out.setdefault(h, [])
734 734 l[len(l):] = self.nodetags(b)
735 735 return out
736 736
737 737 def branches(self, nodes):
738 738 if not nodes:
739 739 nodes = [self.changelog.tip()]
740 740 b = []
741 741 for n in nodes:
742 742 t = n
743 743 while n:
744 744 p = self.changelog.parents(n)
745 745 if p[1] != nullid or p[0] == nullid:
746 746 b.append((t, n, p[0], p[1]))
747 747 break
748 748 n = p[0]
749 749 return b
750 750
751 751 def between(self, pairs):
752 752 r = []
753 753
754 754 for top, bottom in pairs:
755 755 n, l, i = top, [], 0
756 756 f = 1
757 757
758 758 while n != bottom:
759 759 p = self.changelog.parents(n)[0]
760 760 if i == f:
761 761 l.append(n)
762 762 f = f * 2
763 763 n = p
764 764 i += 1
765 765
766 766 r.append(l)
767 767
768 768 return r
769 769
770 770 def findincoming(self, remote, base=None, heads=None):
771 771 m = self.changelog.nodemap
772 772 search = []
773 773 fetch = {}
774 774 seen = {}
775 775 seenbranch = {}
776 776 if base == None:
777 777 base = {}
778 778
779 779 # assume we're closer to the tip than the root
780 780 # and start by examining the heads
781 781 self.ui.status(_("searching for changes\n"))
782 782
783 783 if not heads:
784 784 heads = remote.heads()
785 785
786 786 unknown = []
787 787 for h in heads:
788 788 if h not in m:
789 789 unknown.append(h)
790 790 else:
791 791 base[h] = 1
792 792
793 793 if not unknown:
794 794 return None
795 795
796 796 rep = {}
797 797 reqcnt = 0
798 798
799 799 # search through remote branches
800 800 # a 'branch' here is a linear segment of history, with four parts:
801 801 # head, root, first parent, second parent
802 802 # (a branch always has two parents (or none) by definition)
803 803 unknown = remote.branches(unknown)
804 804 while unknown:
805 805 r = []
806 806 while unknown:
807 807 n = unknown.pop(0)
808 808 if n[0] in seen:
809 809 continue
810 810
811 811 self.ui.debug(_("examining %s:%s\n")
812 812 % (short(n[0]), short(n[1])))
813 813 if n[0] == nullid:
814 814 break
815 815 if n in seenbranch:
816 816 self.ui.debug(_("branch already found\n"))
817 817 continue
818 818 if n[1] and n[1] in m: # do we know the base?
819 819 self.ui.debug(_("found incomplete branch %s:%s\n")
820 820 % (short(n[0]), short(n[1])))
821 821 search.append(n) # schedule branch range for scanning
822 822 seenbranch[n] = 1
823 823 else:
824 824 if n[1] not in seen and n[1] not in fetch:
825 825 if n[2] in m and n[3] in m:
826 826 self.ui.debug(_("found new changeset %s\n") %
827 827 short(n[1]))
828 828 fetch[n[1]] = 1 # earliest unknown
829 829 base[n[2]] = 1 # latest known
830 830 continue
831 831
832 832 for a in n[2:4]:
833 833 if a not in rep:
834 834 r.append(a)
835 835 rep[a] = 1
836 836
837 837 seen[n[0]] = 1
838 838
839 839 if r:
840 840 reqcnt += 1
841 841 self.ui.debug(_("request %d: %s\n") %
842 842 (reqcnt, " ".join(map(short, r))))
843 843 for p in range(0, len(r), 10):
844 844 for b in remote.branches(r[p:p+10]):
845 845 self.ui.debug(_("received %s:%s\n") %
846 846 (short(b[0]), short(b[1])))
847 847 if b[0] in m:
848 848 self.ui.debug(_("found base node %s\n")
849 849 % short(b[0]))
850 850 base[b[0]] = 1
851 851 elif b[0] not in seen:
852 852 unknown.append(b)
853 853
854 854 # do binary search on the branches we found
855 855 while search:
856 856 n = search.pop(0)
857 857 reqcnt += 1
858 858 l = remote.between([(n[0], n[1])])[0]
859 859 l.append(n[1])
860 860 p = n[0]
861 861 f = 1
862 862 for i in l:
863 863 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
864 864 if i in m:
865 865 if f <= 2:
866 866 self.ui.debug(_("found new branch changeset %s\n") %
867 867 short(p))
868 868 fetch[p] = 1
869 869 base[i] = 1
870 870 else:
871 871 self.ui.debug(_("narrowed branch search to %s:%s\n")
872 872 % (short(p), short(i)))
873 873 search.append((p, i))
874 874 break
875 875 p, f = i, f * 2
876 876
877 877 # sanity check our fetch list
878 878 for f in fetch.keys():
879 879 if f in m:
880 880 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
881 881
882 882 if base.keys() == [nullid]:
883 883 self.ui.warn(_("warning: pulling from an unrelated repository!\n"))
884 884
885 885 self.ui.note(_("found new changesets starting at ") +
886 886 " ".join([short(f) for f in fetch]) + "\n")
887 887
888 888 self.ui.debug(_("%d total queries\n") % reqcnt)
889 889
890 890 return fetch.keys()
891 891
892 892 def findoutgoing(self, remote, base=None, heads=None):
893 893 if base == None:
894 894 base = {}
895 895 self.findincoming(remote, base, heads)
896 896
897 897 self.ui.debug(_("common changesets up to ")
898 898 + " ".join(map(short, base.keys())) + "\n")
899 899
900 900 remain = dict.fromkeys(self.changelog.nodemap)
901 901
902 902 # prune everything remote has from the tree
903 903 del remain[nullid]
904 904 remove = base.keys()
905 905 while remove:
906 906 n = remove.pop(0)
907 907 if n in remain:
908 908 del remain[n]
909 909 for p in self.changelog.parents(n):
910 910 remove.append(p)
911 911
912 912 # find every node whose parents have been pruned
913 913 subset = []
914 914 for n in remain:
915 915 p1, p2 = self.changelog.parents(n)
916 916 if p1 not in remain and p2 not in remain:
917 917 subset.append(n)
918 918
919 919 # this is the set of all roots we have to push
920 920 return subset
921 921
922 922 def pull(self, remote, heads=None):
923 923 lock = self.lock()
924 924
925 925 # if we have an empty repo, fetch everything
926 926 if self.changelog.tip() == nullid:
927 927 self.ui.status(_("requesting all changes\n"))
928 928 fetch = [nullid]
929 929 else:
930 930 fetch = self.findincoming(remote)
931 931
932 932 if not fetch:
933 933 self.ui.status(_("no changes found\n"))
934 934 return 1
935 935
936 936 if heads is None:
937 937 cg = remote.changegroup(fetch)
938 938 else:
939 939 cg = remote.changegroupsubset(fetch, heads)
940 940 return self.addchangegroup(cg)
941 941
942 942 def push(self, remote, force=False):
943 943 lock = remote.lock()
944 944
945 945 base = {}
946 946 heads = remote.heads()
947 947 inc = self.findincoming(remote, base, heads)
948 948 if not force and inc:
949 949 self.ui.warn(_("abort: unsynced remote changes!\n"))
950 950 self.ui.status(_("(did you forget to sync? use push -f to force)\n"))
951 951 return 1
952 952
953 953 update = self.findoutgoing(remote, base)
954 954 if not update:
955 955 self.ui.status(_("no changes found\n"))
956 956 return 1
957 957 elif not force:
958 958 if len(heads) < len(self.changelog.heads()):
959 959 self.ui.warn(_("abort: push creates new remote branches!\n"))
960 960 self.ui.status(_("(did you forget to merge?"
961 961 " use push -f to force)\n"))
962 962 return 1
963 963
964 964 cg = self.changegroup(update)
965 965 return remote.addchangegroup(cg)
966 966
967 967 def changegroupsubset(self, bases, heads):
968 968 """This function generates a changegroup consisting of all the nodes
969 969 that are descendents of any of the bases, and ancestors of any of
970 970 the heads.
971 971
972 972 It is fairly complex as determining which filenodes and which
973 973 manifest nodes need to be included for the changeset to be complete
974 974 is non-trivial.
975 975
976 976 Another wrinkle is doing the reverse, figuring out which changeset in
977 977 the changegroup a particular filenode or manifestnode belongs to."""
978 978
979 979 # Set up some initial variables
980 980 # Make it easy to refer to self.changelog
981 981 cl = self.changelog
982 982 # msng is short for missing - compute the list of changesets in this
983 983 # changegroup.
984 984 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
985 985 # Some bases may turn out to be superfluous, and some heads may be
986 986 # too. nodesbetween will return the minimal set of bases and heads
987 987 # necessary to re-create the changegroup.
988 988
989 989 # Known heads are the list of heads that it is assumed the recipient
990 990 # of this changegroup will know about.
991 991 knownheads = {}
992 992 # We assume that all parents of bases are known heads.
993 993 for n in bases:
994 994 for p in cl.parents(n):
995 995 if p != nullid:
996 996 knownheads[p] = 1
997 997 knownheads = knownheads.keys()
998 998 if knownheads:
999 999 # Now that we know what heads are known, we can compute which
1000 1000 # changesets are known. The recipient must know about all
1001 1001 # changesets required to reach the known heads from the null
1002 1002 # changeset.
1003 1003 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1004 1004 junk = None
1005 1005 # Transform the list into an ersatz set.
1006 1006 has_cl_set = dict.fromkeys(has_cl_set)
1007 1007 else:
1008 1008 # If there were no known heads, the recipient cannot be assumed to
1009 1009 # know about any changesets.
1010 1010 has_cl_set = {}
1011 1011
1012 1012 # Make it easy to refer to self.manifest
1013 1013 mnfst = self.manifest
1014 1014 # We don't know which manifests are missing yet
1015 1015 msng_mnfst_set = {}
1016 1016 # Nor do we know which filenodes are missing.
1017 1017 msng_filenode_set = {}
1018 1018
1019 1019 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1020 1020 junk = None
1021 1021
1022 1022 # A changeset always belongs to itself, so the changenode lookup
1023 1023 # function for a changenode is identity.
1024 1024 def identity(x):
1025 1025 return x
1026 1026
1027 1027 # A function generating function. Sets up an environment for the
1028 1028 # inner function.
1029 1029 def cmp_by_rev_func(revlog):
1030 1030 # Compare two nodes by their revision number in the environment's
1031 1031 # revision history. Since the revision number both represents the
1032 1032 # most efficient order to read the nodes in, and represents a
1033 1033 # topological sorting of the nodes, this function is often useful.
1034 1034 def cmp_by_rev(a, b):
1035 1035 return cmp(revlog.rev(a), revlog.rev(b))
1036 1036 return cmp_by_rev
1037 1037
1038 1038 # If we determine that a particular file or manifest node must be a
1039 1039 # node that the recipient of the changegroup will already have, we can
1040 1040 # also assume the recipient will have all the parents. This function
1041 1041 # prunes them from the set of missing nodes.
1042 1042 def prune_parents(revlog, hasset, msngset):
1043 1043 haslst = hasset.keys()
1044 1044 haslst.sort(cmp_by_rev_func(revlog))
1045 1045 for node in haslst:
1046 1046 parentlst = [p for p in revlog.parents(node) if p != nullid]
1047 1047 while parentlst:
1048 1048 n = parentlst.pop()
1049 1049 if n not in hasset:
1050 1050 hasset[n] = 1
1051 1051 p = [p for p in revlog.parents(n) if p != nullid]
1052 1052 parentlst.extend(p)
1053 1053 for n in hasset:
1054 1054 msngset.pop(n, None)
1055 1055
1056 1056 # This is a function generating function used to set up an environment
1057 1057 # for the inner function to execute in.
1058 1058 def manifest_and_file_collector(changedfileset):
1059 1059 # This is an information gathering function that gathers
1060 1060 # information from each changeset node that goes out as part of
1061 1061 # the changegroup. The information gathered is a list of which
1062 1062 # manifest nodes are potentially required (the recipient may
1063 1063 # already have them) and total list of all files which were
1064 1064 # changed in any changeset in the changegroup.
1065 1065 #
1066 1066 # We also remember the first changenode we saw any manifest
1067 1067 # referenced by so we can later determine which changenode 'owns'
1068 1068 # the manifest.
1069 1069 def collect_manifests_and_files(clnode):
1070 1070 c = cl.read(clnode)
1071 1071 for f in c[3]:
1072 1072 # This is to make sure we only have one instance of each
1073 1073 # filename string for each filename.
1074 1074 changedfileset.setdefault(f, f)
1075 1075 msng_mnfst_set.setdefault(c[0], clnode)
1076 1076 return collect_manifests_and_files
1077 1077
1078 1078 # Figure out which manifest nodes (of the ones we think might be part
1079 1079 # of the changegroup) the recipient must know about and remove them
1080 1080 # from the changegroup.
1081 1081 def prune_manifests():
1082 1082 has_mnfst_set = {}
1083 1083 for n in msng_mnfst_set:
1084 1084 # If a 'missing' manifest thinks it belongs to a changenode
1085 1085 # the recipient is assumed to have, obviously the recipient
1086 1086 # must have that manifest.
1087 1087 linknode = cl.node(mnfst.linkrev(n))
1088 1088 if linknode in has_cl_set:
1089 1089 has_mnfst_set[n] = 1
1090 1090 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1091 1091
1092 1092 # Use the information collected in collect_manifests_and_files to say
1093 1093 # which changenode any manifestnode belongs to.
1094 1094 def lookup_manifest_link(mnfstnode):
1095 1095 return msng_mnfst_set[mnfstnode]
1096 1096
1097 1097 # A function generating function that sets up the initial environment
1098 1098 # the inner function.
1099 1099 def filenode_collector(changedfiles):
1100 1100 next_rev = [0]
1101 1101 # This gathers information from each manifestnode included in the
1102 1102 # changegroup about which filenodes the manifest node references
1103 1103 # so we can include those in the changegroup too.
1104 1104 #
1105 1105 # It also remembers which changenode each filenode belongs to. It
1106 1106 # does this by assuming the a filenode belongs to the changenode
1107 1107 # the first manifest that references it belongs to.
1108 1108 def collect_msng_filenodes(mnfstnode):
1109 1109 r = mnfst.rev(mnfstnode)
1110 1110 if r == next_rev[0]:
1111 1111 # If the last rev we looked at was the one just previous,
1112 1112 # we only need to see a diff.
1113 1113 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1114 1114 # For each line in the delta
1115 1115 for dline in delta.splitlines():
1116 1116 # get the filename and filenode for that line
1117 1117 f, fnode = dline.split('\0')
1118 1118 fnode = bin(fnode[:40])
1119 1119 f = changedfiles.get(f, None)
1120 1120 # And if the file is in the list of files we care
1121 1121 # about.
1122 1122 if f is not None:
1123 1123 # Get the changenode this manifest belongs to
1124 1124 clnode = msng_mnfst_set[mnfstnode]
1125 1125 # Create the set of filenodes for the file if
1126 1126 # there isn't one already.
1127 1127 ndset = msng_filenode_set.setdefault(f, {})
1128 1128 # And set the filenode's changelog node to the
1129 1129 # manifest's if it hasn't been set already.
1130 1130 ndset.setdefault(fnode, clnode)
1131 1131 else:
1132 1132 # Otherwise we need a full manifest.
1133 1133 m = mnfst.read(mnfstnode)
1134 1134 # For every file in we care about.
1135 1135 for f in changedfiles:
1136 1136 fnode = m.get(f, None)
1137 1137 # If it's in the manifest
1138 1138 if fnode is not None:
1139 1139 # See comments above.
1140 1140 clnode = msng_mnfst_set[mnfstnode]
1141 1141 ndset = msng_filenode_set.setdefault(f, {})
1142 1142 ndset.setdefault(fnode, clnode)
1143 1143 # Remember the revision we hope to see next.
1144 1144 next_rev[0] = r + 1
1145 1145 return collect_msng_filenodes
1146 1146
1147 1147 # We have a list of filenodes we think we need for a file, lets remove
1148 1148 # all those we now the recipient must have.
1149 1149 def prune_filenodes(f, filerevlog):
1150 1150 msngset = msng_filenode_set[f]
1151 1151 hasset = {}
1152 1152 # If a 'missing' filenode thinks it belongs to a changenode we
1153 1153 # assume the recipient must have, then the recipient must have
1154 1154 # that filenode.
1155 1155 for n in msngset:
1156 1156 clnode = cl.node(filerevlog.linkrev(n))
1157 1157 if clnode in has_cl_set:
1158 1158 hasset[n] = 1
1159 1159 prune_parents(filerevlog, hasset, msngset)
1160 1160
1161 1161 # A function generator function that sets up the a context for the
1162 1162 # inner function.
1163 1163 def lookup_filenode_link_func(fname):
1164 1164 msngset = msng_filenode_set[fname]
1165 1165 # Lookup the changenode the filenode belongs to.
1166 1166 def lookup_filenode_link(fnode):
1167 1167 return msngset[fnode]
1168 1168 return lookup_filenode_link
1169 1169
1170 1170 # Now that we have all theses utility functions to help out and
1171 1171 # logically divide up the task, generate the group.
1172 1172 def gengroup():
1173 1173 # The set of changed files starts empty.
1174 1174 changedfiles = {}
1175 1175 # Create a changenode group generator that will call our functions
1176 1176 # back to lookup the owning changenode and collect information.
1177 1177 group = cl.group(msng_cl_lst, identity,
1178 1178 manifest_and_file_collector(changedfiles))
1179 1179 for chnk in group:
1180 1180 yield chnk
1181 1181
1182 1182 # The list of manifests has been collected by the generator
1183 1183 # calling our functions back.
1184 1184 prune_manifests()
1185 1185 msng_mnfst_lst = msng_mnfst_set.keys()
1186 1186 # Sort the manifestnodes by revision number.
1187 1187 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1188 1188 # Create a generator for the manifestnodes that calls our lookup
1189 1189 # and data collection functions back.
1190 1190 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1191 1191 filenode_collector(changedfiles))
1192 1192 for chnk in group:
1193 1193 yield chnk
1194 1194
1195 1195 # These are no longer needed, dereference and toss the memory for
1196 1196 # them.
1197 1197 msng_mnfst_lst = None
1198 1198 msng_mnfst_set.clear()
1199 1199
1200 1200 changedfiles = changedfiles.keys()
1201 1201 changedfiles.sort()
1202 1202 # Go through all our files in order sorted by name.
1203 1203 for fname in changedfiles:
1204 1204 filerevlog = self.file(fname)
1205 1205 # Toss out the filenodes that the recipient isn't really
1206 1206 # missing.
1207 1207 prune_filenodes(fname, filerevlog)
1208 1208 msng_filenode_lst = msng_filenode_set[fname].keys()
1209 1209 # If any filenodes are left, generate the group for them,
1210 1210 # otherwise don't bother.
1211 1211 if len(msng_filenode_lst) > 0:
1212 1212 yield struct.pack(">l", len(fname) + 4) + fname
1213 1213 # Sort the filenodes by their revision #
1214 1214 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1215 1215 # Create a group generator and only pass in a changenode
1216 1216 # lookup function as we need to collect no information
1217 1217 # from filenodes.
1218 1218 group = filerevlog.group(msng_filenode_lst,
1219 1219 lookup_filenode_link_func(fname))
1220 1220 for chnk in group:
1221 1221 yield chnk
1222 1222 # Don't need this anymore, toss it to free memory.
1223 1223 del msng_filenode_set[fname]
1224 1224 # Signal that no more groups are left.
1225 1225 yield struct.pack(">l", 0)
1226 1226
1227 1227 return util.chunkbuffer(gengroup())
1228 1228
1229 1229 def changegroup(self, basenodes):
1230 1230 """Generate a changegroup of all nodes that we have that a recipient
1231 1231 doesn't.
1232 1232
1233 1233 This is much easier than the previous function as we can assume that
1234 1234 the recipient has any changenode we aren't sending them."""
1235 1235 cl = self.changelog
1236 1236 nodes = cl.nodesbetween(basenodes, None)[0]
1237 1237 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1238 1238
1239 1239 def identity(x):
1240 1240 return x
1241 1241
1242 1242 def gennodelst(revlog):
1243 1243 for r in xrange(0, revlog.count()):
1244 1244 n = revlog.node(r)
1245 1245 if revlog.linkrev(n) in revset:
1246 1246 yield n
1247 1247
1248 1248 def changed_file_collector(changedfileset):
1249 1249 def collect_changed_files(clnode):
1250 1250 c = cl.read(clnode)
1251 1251 for fname in c[3]:
1252 1252 changedfileset[fname] = 1
1253 1253 return collect_changed_files
1254 1254
1255 1255 def lookuprevlink_func(revlog):
1256 1256 def lookuprevlink(n):
1257 1257 return cl.node(revlog.linkrev(n))
1258 1258 return lookuprevlink
1259 1259
1260 1260 def gengroup():
1261 1261 # construct a list of all changed files
1262 1262 changedfiles = {}
1263 1263
1264 1264 for chnk in cl.group(nodes, identity,
1265 1265 changed_file_collector(changedfiles)):
1266 1266 yield chnk
1267 1267 changedfiles = changedfiles.keys()
1268 1268 changedfiles.sort()
1269 1269
1270 1270 mnfst = self.manifest
1271 1271 nodeiter = gennodelst(mnfst)
1272 1272 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1273 1273 yield chnk
1274 1274
1275 1275 for fname in changedfiles:
1276 1276 filerevlog = self.file(fname)
1277 1277 nodeiter = gennodelst(filerevlog)
1278 1278 nodeiter = list(nodeiter)
1279 1279 if nodeiter:
1280 1280 yield struct.pack(">l", len(fname) + 4) + fname
1281 1281 lookup = lookuprevlink_func(filerevlog)
1282 1282 for chnk in filerevlog.group(nodeiter, lookup):
1283 1283 yield chnk
1284 1284
1285 1285 yield struct.pack(">l", 0)
1286 1286
1287 1287 return util.chunkbuffer(gengroup())
1288 1288
1289 1289 def addchangegroup(self, source):
1290 1290
1291 1291 def getchunk():
1292 1292 d = source.read(4)
1293 1293 if not d:
1294 1294 return ""
1295 1295 l = struct.unpack(">l", d)[0]
1296 1296 if l <= 4:
1297 1297 return ""
1298 1298 d = source.read(l - 4)
1299 1299 if len(d) < l - 4:
1300 1300 raise repo.RepoError(_("premature EOF reading chunk"
1301 1301 " (got %d bytes, expected %d)")
1302 1302 % (len(d), l - 4))
1303 1303 return d
1304 1304
1305 1305 def getgroup():
1306 1306 while 1:
1307 1307 c = getchunk()
1308 1308 if not c:
1309 1309 break
1310 1310 yield c
1311 1311
1312 1312 def csmap(x):
1313 1313 self.ui.debug(_("add changeset %s\n") % short(x))
1314 1314 return self.changelog.count()
1315 1315
1316 1316 def revmap(x):
1317 1317 return self.changelog.rev(x)
1318 1318
1319 1319 if not source:
1320 1320 return
1321 1321 changesets = files = revisions = 0
1322 1322
1323 1323 tr = self.transaction()
1324 1324
1325 1325 oldheads = len(self.changelog.heads())
1326 1326
1327 1327 # pull off the changeset group
1328 1328 self.ui.status(_("adding changesets\n"))
1329 1329 co = self.changelog.tip()
1330 1330 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1331 1331 cnr, cor = map(self.changelog.rev, (cn, co))
1332 1332 if cn == nullid:
1333 1333 cnr = cor
1334 1334 changesets = cnr - cor
1335 1335
1336 1336 # pull off the manifest group
1337 1337 self.ui.status(_("adding manifests\n"))
1338 1338 mm = self.manifest.tip()
1339 1339 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1340 1340
1341 1341 # process the files
1342 1342 self.ui.status(_("adding file changes\n"))
1343 1343 while 1:
1344 1344 f = getchunk()
1345 1345 if not f:
1346 1346 break
1347 1347 self.ui.debug(_("adding %s revisions\n") % f)
1348 1348 fl = self.file(f)
1349 1349 o = fl.count()
1350 1350 n = fl.addgroup(getgroup(), revmap, tr)
1351 1351 revisions += fl.count() - o
1352 1352 files += 1
1353 1353
1354 1354 newheads = len(self.changelog.heads())
1355 1355 heads = ""
1356 1356 if oldheads and newheads > oldheads:
1357 1357 heads = _(" (+%d heads)") % (newheads - oldheads)
1358 1358
1359 1359 self.ui.status(_("added %d changesets"
1360 1360 " with %d changes to %d files%s\n")
1361 1361 % (changesets, revisions, files, heads))
1362 1362
1363 1363 tr.close()
1364 1364
1365 1365 if changesets > 0:
1366 1366 if not self.hook("changegroup",
1367 1367 node=hex(self.changelog.node(cor+1))):
1368 1368 self.ui.warn(_("abort: changegroup hook returned failure!\n"))
1369 1369 return 1
1370 1370
1371 1371 for i in range(cor + 1, cnr + 1):
1372 1372 self.hook("commit", node=hex(self.changelog.node(i)))
1373 1373
1374 1374 return
1375 1375
1376 1376 def update(self, node, allow=False, force=False, choose=None,
1377 1377 moddirstate=True, forcemerge=False):
1378 1378 pl = self.dirstate.parents()
1379 1379 if not force and pl[1] != nullid:
1380 1380 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1381 1381 return 1
1382 1382
1383 1383 p1, p2 = pl[0], node
1384 1384 pa = self.changelog.ancestor(p1, p2)
1385 1385 m1n = self.changelog.read(p1)[0]
1386 1386 m2n = self.changelog.read(p2)[0]
1387 1387 man = self.manifest.ancestor(m1n, m2n)
1388 1388 m1 = self.manifest.read(m1n)
1389 1389 mf1 = self.manifest.readflags(m1n)
1390 1390 m2 = self.manifest.read(m2n)
1391 1391 mf2 = self.manifest.readflags(m2n)
1392 1392 ma = self.manifest.read(man)
1393 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 1397 if allow and not forcemerge:
1398 if c or a or d:
1398 if modified or added or removed:
1399 1399 raise util.Abort(_("outstanding uncommited changes"))
1400 1400 if not forcemerge and not force:
1401 for f in u:
1401 for f in unknown:
1402 1402 if f in m2:
1403 1403 t1 = self.wread(f)
1404 1404 t2 = self.file(f).read(m2[f])
1405 1405 if cmp(t1, t2) != 0:
1406 1406 raise util.Abort(_("'%s' already exists in the working"
1407 1407 " dir and differs from remote") % f)
1408 1408
1409 1409 # is this a jump, or a merge? i.e. is there a linear path
1410 1410 # from p1 to p2?
1411 1411 linear_path = (pa == p1 or pa == p2)
1412 1412
1413 1413 # resolve the manifest to determine which files
1414 1414 # we care about merging
1415 1415 self.ui.note(_("resolving manifests\n"))
1416 1416 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1417 1417 (force, allow, moddirstate, linear_path))
1418 1418 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1419 1419 (short(man), short(m1n), short(m2n)))
1420 1420
1421 1421 merge = {}
1422 1422 get = {}
1423 1423 remove = []
1424 1424
1425 1425 # construct a working dir manifest
1426 1426 mw = m1.copy()
1427 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 1431 mw[f] = ""
1432 1432 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1433 1433
1434 1434 if moddirstate:
1435 1435 wlock = self.wlock()
1436 1436
1437 for f in d:
1437 for f in removed:
1438 1438 if f in mw:
1439 1439 del mw[f]
1440 1440
1441 1441 # If we're jumping between revisions (as opposed to merging),
1442 1442 # and if neither the working directory nor the target rev has
1443 1443 # the file, then we need to remove it from the dirstate, to
1444 1444 # prevent the dirstate from listing the file when it is no
1445 1445 # longer in the manifest.
1446 1446 if moddirstate and linear_path and f not in m2:
1447 1447 self.dirstate.forget((f,))
1448 1448
1449 1449 # Compare manifests
1450 1450 for f, n in mw.iteritems():
1451 1451 if choose and not choose(f):
1452 1452 continue
1453 1453 if f in m2:
1454 1454 s = 0
1455 1455
1456 1456 # is the wfile new since m1, and match m2?
1457 1457 if f not in m1:
1458 1458 t1 = self.wread(f)
1459 1459 t2 = self.file(f).read(m2[f])
1460 1460 if cmp(t1, t2) == 0:
1461 1461 n = m2[f]
1462 1462 del t1, t2
1463 1463
1464 1464 # are files different?
1465 1465 if n != m2[f]:
1466 1466 a = ma.get(f, nullid)
1467 1467 # are both different from the ancestor?
1468 1468 if n != a and m2[f] != a:
1469 1469 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1470 1470 # merge executable bits
1471 1471 # "if we changed or they changed, change in merge"
1472 1472 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1473 1473 mode = ((a^b) | (a^c)) ^ a
1474 1474 merge[f] = (m1.get(f, nullid), m2[f], mode)
1475 1475 s = 1
1476 1476 # are we clobbering?
1477 1477 # is remote's version newer?
1478 1478 # or are we going back in time?
1479 1479 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1480 1480 self.ui.debug(_(" remote %s is newer, get\n") % f)
1481 1481 get[f] = m2[f]
1482 1482 s = 1
1483 1483 elif f in umap:
1484 1484 # this unknown file is the same as the checkout
1485 1485 get[f] = m2[f]
1486 1486
1487 1487 if not s and mfw[f] != mf2[f]:
1488 1488 if force:
1489 1489 self.ui.debug(_(" updating permissions for %s\n") % f)
1490 1490 util.set_exec(self.wjoin(f), mf2[f])
1491 1491 else:
1492 1492 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1493 1493 mode = ((a^b) | (a^c)) ^ a
1494 1494 if mode != b:
1495 1495 self.ui.debug(_(" updating permissions for %s\n")
1496 1496 % f)
1497 1497 util.set_exec(self.wjoin(f), mode)
1498 1498 del m2[f]
1499 1499 elif f in ma:
1500 1500 if n != ma[f]:
1501 1501 r = _("d")
1502 1502 if not force and (linear_path or allow):
1503 1503 r = self.ui.prompt(
1504 1504 (_(" local changed %s which remote deleted\n") % f) +
1505 1505 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1506 1506 if r == _("d"):
1507 1507 remove.append(f)
1508 1508 else:
1509 1509 self.ui.debug(_("other deleted %s\n") % f)
1510 1510 remove.append(f) # other deleted it
1511 1511 else:
1512 1512 # file is created on branch or in working directory
1513 1513 if force and f not in umap:
1514 1514 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1515 1515 remove.append(f)
1516 1516 elif n == m1.get(f, nullid): # same as parent
1517 1517 if p2 == pa: # going backwards?
1518 1518 self.ui.debug(_("remote deleted %s\n") % f)
1519 1519 remove.append(f)
1520 1520 else:
1521 1521 self.ui.debug(_("local modified %s, keeping\n") % f)
1522 1522 else:
1523 1523 self.ui.debug(_("working dir created %s, keeping\n") % f)
1524 1524
1525 1525 for f, n in m2.iteritems():
1526 1526 if choose and not choose(f):
1527 1527 continue
1528 1528 if f[0] == "/":
1529 1529 continue
1530 1530 if f in ma and n != ma[f]:
1531 1531 r = _("k")
1532 1532 if not force and (linear_path or allow):
1533 1533 r = self.ui.prompt(
1534 1534 (_("remote changed %s which local deleted\n") % f) +
1535 1535 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1536 1536 if r == _("k"):
1537 1537 get[f] = n
1538 1538 elif f not in ma:
1539 1539 self.ui.debug(_("remote created %s\n") % f)
1540 1540 get[f] = n
1541 1541 else:
1542 1542 if force or p2 == pa: # going backwards?
1543 1543 self.ui.debug(_("local deleted %s, recreating\n") % f)
1544 1544 get[f] = n
1545 1545 else:
1546 1546 self.ui.debug(_("local deleted %s\n") % f)
1547 1547
1548 1548 del mw, m1, m2, ma
1549 1549
1550 1550 if force:
1551 1551 for f in merge:
1552 1552 get[f] = merge[f][1]
1553 1553 merge = {}
1554 1554
1555 1555 if linear_path or force:
1556 1556 # we don't need to do any magic, just jump to the new rev
1557 1557 branch_merge = False
1558 1558 p1, p2 = p2, nullid
1559 1559 else:
1560 1560 if not allow:
1561 1561 self.ui.status(_("this update spans a branch"
1562 1562 " affecting the following files:\n"))
1563 1563 fl = merge.keys() + get.keys()
1564 1564 fl.sort()
1565 1565 for f in fl:
1566 1566 cf = ""
1567 1567 if f in merge:
1568 1568 cf = _(" (resolve)")
1569 1569 self.ui.status(" %s%s\n" % (f, cf))
1570 1570 self.ui.warn(_("aborting update spanning branches!\n"))
1571 1571 self.ui.status(_("(use update -m to merge across branches"
1572 1572 " or -C to lose changes)\n"))
1573 1573 return 1
1574 1574 branch_merge = True
1575 1575
1576 1576 # get the files we don't need to change
1577 1577 files = get.keys()
1578 1578 files.sort()
1579 1579 for f in files:
1580 1580 if f[0] == "/":
1581 1581 continue
1582 1582 self.ui.note(_("getting %s\n") % f)
1583 1583 t = self.file(f).read(get[f])
1584 1584 self.wwrite(f, t)
1585 1585 util.set_exec(self.wjoin(f), mf2[f])
1586 1586 if moddirstate:
1587 1587 if branch_merge:
1588 1588 self.dirstate.update([f], 'n', st_mtime=-1)
1589 1589 else:
1590 1590 self.dirstate.update([f], 'n')
1591 1591
1592 1592 # merge the tricky bits
1593 1593 files = merge.keys()
1594 1594 files.sort()
1595 1595 for f in files:
1596 1596 self.ui.status(_("merging %s\n") % f)
1597 1597 my, other, flag = merge[f]
1598 1598 self.merge3(f, my, other)
1599 1599 util.set_exec(self.wjoin(f), flag)
1600 1600 if moddirstate:
1601 1601 if branch_merge:
1602 1602 # We've done a branch merge, mark this file as merged
1603 1603 # so that we properly record the merger later
1604 1604 self.dirstate.update([f], 'm')
1605 1605 else:
1606 1606 # We've update-merged a locally modified file, so
1607 1607 # we set the dirstate to emulate a normal checkout
1608 1608 # of that file some time in the past. Thus our
1609 1609 # merge will appear as a normal local file
1610 1610 # modification.
1611 1611 f_len = len(self.file(f).read(other))
1612 1612 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1613 1613
1614 1614 remove.sort()
1615 1615 for f in remove:
1616 1616 self.ui.note(_("removing %s\n") % f)
1617 1617 try:
1618 1618 util.unlink(self.wjoin(f))
1619 1619 except OSError, inst:
1620 1620 if inst.errno != errno.ENOENT:
1621 1621 self.ui.warn(_("update failed to remove %s: %s!\n") %
1622 1622 (f, inst.strerror))
1623 1623 if moddirstate:
1624 1624 if branch_merge:
1625 1625 self.dirstate.update(remove, 'r')
1626 1626 else:
1627 1627 self.dirstate.forget(remove)
1628 1628
1629 1629 if moddirstate:
1630 1630 self.dirstate.setparents(p1, p2)
1631 1631
1632 1632 def merge3(self, fn, my, other):
1633 1633 """perform a 3-way merge in the working directory"""
1634 1634
1635 1635 def temp(prefix, node):
1636 1636 pre = "%s~%s." % (os.path.basename(fn), prefix)
1637 1637 (fd, name) = tempfile.mkstemp("", pre)
1638 1638 f = os.fdopen(fd, "wb")
1639 1639 self.wwrite(fn, fl.read(node), f)
1640 1640 f.close()
1641 1641 return name
1642 1642
1643 1643 fl = self.file(fn)
1644 1644 base = fl.ancestor(my, other)
1645 1645 a = self.wjoin(fn)
1646 1646 b = temp("base", base)
1647 1647 c = temp("other", other)
1648 1648
1649 1649 self.ui.note(_("resolving %s\n") % fn)
1650 1650 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1651 1651 (fn, short(my), short(other), short(base)))
1652 1652
1653 1653 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1654 1654 or "hgmerge")
1655 1655 r = os.system('%s "%s" "%s" "%s"' % (cmd, a, b, c))
1656 1656 if r:
1657 1657 self.ui.warn(_("merging %s failed!\n") % fn)
1658 1658
1659 1659 os.unlink(b)
1660 1660 os.unlink(c)
1661 1661
1662 1662 def verify(self):
1663 1663 filelinkrevs = {}
1664 1664 filenodes = {}
1665 1665 changesets = revisions = files = 0
1666 1666 errors = [0]
1667 1667 neededmanifests = {}
1668 1668
1669 1669 def err(msg):
1670 1670 self.ui.warn(msg + "\n")
1671 1671 errors[0] += 1
1672 1672
1673 1673 seen = {}
1674 1674 self.ui.status(_("checking changesets\n"))
1675 1675 d = self.changelog.checksize()
1676 1676 if d:
1677 1677 err(_("changeset data short %d bytes") % d)
1678 1678 for i in range(self.changelog.count()):
1679 1679 changesets += 1
1680 1680 n = self.changelog.node(i)
1681 1681 l = self.changelog.linkrev(n)
1682 1682 if l != i:
1683 1683 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1684 1684 if n in seen:
1685 1685 err(_("duplicate changeset at revision %d") % i)
1686 1686 seen[n] = 1
1687 1687
1688 1688 for p in self.changelog.parents(n):
1689 1689 if p not in self.changelog.nodemap:
1690 1690 err(_("changeset %s has unknown parent %s") %
1691 1691 (short(n), short(p)))
1692 1692 try:
1693 1693 changes = self.changelog.read(n)
1694 1694 except KeyboardInterrupt:
1695 1695 self.ui.warn(_("interrupted"))
1696 1696 raise
1697 1697 except Exception, inst:
1698 1698 err(_("unpacking changeset %s: %s") % (short(n), inst))
1699 1699
1700 1700 neededmanifests[changes[0]] = n
1701 1701
1702 1702 for f in changes[3]:
1703 1703 filelinkrevs.setdefault(f, []).append(i)
1704 1704
1705 1705 seen = {}
1706 1706 self.ui.status(_("checking manifests\n"))
1707 1707 d = self.manifest.checksize()
1708 1708 if d:
1709 1709 err(_("manifest data short %d bytes") % d)
1710 1710 for i in range(self.manifest.count()):
1711 1711 n = self.manifest.node(i)
1712 1712 l = self.manifest.linkrev(n)
1713 1713
1714 1714 if l < 0 or l >= self.changelog.count():
1715 1715 err(_("bad manifest link (%d) at revision %d") % (l, i))
1716 1716
1717 1717 if n in neededmanifests:
1718 1718 del neededmanifests[n]
1719 1719
1720 1720 if n in seen:
1721 1721 err(_("duplicate manifest at revision %d") % i)
1722 1722
1723 1723 seen[n] = 1
1724 1724
1725 1725 for p in self.manifest.parents(n):
1726 1726 if p not in self.manifest.nodemap:
1727 1727 err(_("manifest %s has unknown parent %s") %
1728 1728 (short(n), short(p)))
1729 1729
1730 1730 try:
1731 1731 delta = mdiff.patchtext(self.manifest.delta(n))
1732 1732 except KeyboardInterrupt:
1733 1733 self.ui.warn(_("interrupted"))
1734 1734 raise
1735 1735 except Exception, inst:
1736 1736 err(_("unpacking manifest %s: %s") % (short(n), inst))
1737 1737
1738 1738 ff = [ l.split('\0') for l in delta.splitlines() ]
1739 1739 for f, fn in ff:
1740 1740 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1741 1741
1742 1742 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1743 1743
1744 1744 for m, c in neededmanifests.items():
1745 1745 err(_("Changeset %s refers to unknown manifest %s") %
1746 1746 (short(m), short(c)))
1747 1747 del neededmanifests
1748 1748
1749 1749 for f in filenodes:
1750 1750 if f not in filelinkrevs:
1751 1751 err(_("file %s in manifest but not in changesets") % f)
1752 1752
1753 1753 for f in filelinkrevs:
1754 1754 if f not in filenodes:
1755 1755 err(_("file %s in changeset but not in manifest") % f)
1756 1756
1757 1757 self.ui.status(_("checking files\n"))
1758 1758 ff = filenodes.keys()
1759 1759 ff.sort()
1760 1760 for f in ff:
1761 1761 if f == "/dev/null":
1762 1762 continue
1763 1763 files += 1
1764 1764 fl = self.file(f)
1765 1765 d = fl.checksize()
1766 1766 if d:
1767 1767 err(_("%s file data short %d bytes") % (f, d))
1768 1768
1769 1769 nodes = {nullid: 1}
1770 1770 seen = {}
1771 1771 for i in range(fl.count()):
1772 1772 revisions += 1
1773 1773 n = fl.node(i)
1774 1774
1775 1775 if n in seen:
1776 1776 err(_("%s: duplicate revision %d") % (f, i))
1777 1777 if n not in filenodes[f]:
1778 1778 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1779 1779 else:
1780 1780 del filenodes[f][n]
1781 1781
1782 1782 flr = fl.linkrev(n)
1783 1783 if flr not in filelinkrevs[f]:
1784 1784 err(_("%s:%s points to unexpected changeset %d")
1785 1785 % (f, short(n), flr))
1786 1786 else:
1787 1787 filelinkrevs[f].remove(flr)
1788 1788
1789 1789 # verify contents
1790 1790 try:
1791 1791 t = fl.read(n)
1792 1792 except KeyboardInterrupt:
1793 1793 self.ui.warn(_("interrupted"))
1794 1794 raise
1795 1795 except Exception, inst:
1796 1796 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1797 1797
1798 1798 # verify parents
1799 1799 (p1, p2) = fl.parents(n)
1800 1800 if p1 not in nodes:
1801 1801 err(_("file %s:%s unknown parent 1 %s") %
1802 1802 (f, short(n), short(p1)))
1803 1803 if p2 not in nodes:
1804 1804 err(_("file %s:%s unknown parent 2 %s") %
1805 1805 (f, short(n), short(p1)))
1806 1806 nodes[n] = 1
1807 1807
1808 1808 # cross-check
1809 1809 for node in filenodes[f]:
1810 1810 err(_("node %s in manifests not in %s") % (hex(node), f))
1811 1811
1812 1812 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1813 1813 (files, changesets, revisions))
1814 1814
1815 1815 if errors[0]:
1816 1816 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1817 1817 return 1
General Comments 0
You need to be logged in to leave comments. Login now