##// END OF EJS Templates
Add hg copy...
mpm@selenic.com -
r363:ae96b7e1 default
parent child Browse files
Show More
@@ -0,0 +1,18 b''
1 #!/bin/bash
2
3 set -x
4 hg init
5 echo a > a
6 hg add a
7 hg commit -t "1" -u test -d "0 0"
8 hg status
9 cp a b
10 hg copy a b
11 hg status
12 hg -d commit -t "2" -u test -d "0 0"
13 hg history
14 hg log a
15 hexdump -C .hg/data/b.d
16 hg cat b | md5sum
17 hg cat a | md5sum
18 hg verify
@@ -0,0 +1,51 b''
1 + hg init
2 + echo a
3 + hg add a
4 + hg commit -t 1 -u test -d '0 0'
5 + hg status
6 ? .out
7 + cp a b
8 + hg copy a b
9 + hg status
10 A b
11 ? .out
12 + hg -d commit -t 2 -u test -d '0 0'
13 b
14 b: copy a:b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
15 + hg history
16 changeset: 1:3b5b84850bbed12e8ff8c1b87b32dc93c59ae6d8
17 user: test
18 date: Wed Dec 31 16:00:00 1969
19 summary: 2
20
21 changeset: 0:c19d34741b0a4ced8e4ba74bb834597d5193851e
22 user: test
23 date: Wed Dec 31 16:00:00 1969
24 summary: 1
25
26 + hg log a
27 revision: 0:b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
28 changeset: 0:c19d34741b0a4ced8e4ba74bb834597d5193851e
29 user: test
30 date: Wed Dec 31 16:00:00 1969
31 summary: 1
32
33 + hexdump -C .hg/data/b.d
34 00000000 75 01 0a 63 6f 70 79 72 65 76 3a 20 62 37 38 39 |u..copyrev: b789|
35 00000010 66 64 64 39 36 64 63 32 66 33 62 64 32 32 39 63 |fdd96dc2f3bd229c|
36 00000020 31 64 64 38 65 65 64 66 30 66 63 36 30 65 32 62 |1dd8eedf0fc60e2b|
37 00000030 36 38 65 33 0a 63 6f 70 79 3a 20 61 0a 01 0a 61 |68e3.copy: a...a|
38 00000040 0a |.|
39 00000041
40 + hg cat b
41 + md5sum
42 60b725f10c9c85c70d97880dfe8191b3 -
43 + hg cat a
44 + md5sum
45 60b725f10c9c85c70d97880dfe8191b3 -
46 + hg verify
47 checking changesets
48 checking manifests
49 crosschecking files in changesets and manifests
50 checking files
51 2 files, 2 changesets, 2 total revisions
@@ -1,754 +1,759 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, re, sys, signal
8 import os, re, sys, signal
9 import fancyopts, ui, hg
9 import fancyopts, ui, hg
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "mdiff time hgweb traceback random signal")
11 demandload(globals(), "mdiff time hgweb traceback random signal")
12
12
13 class UnknownCommand(Exception): pass
13 class UnknownCommand(Exception): pass
14
14
15 def filterfiles(filters, files):
15 def filterfiles(filters, files):
16 l = [ x for x in files if x in filters ]
16 l = [ x for x in files if x in filters ]
17
17
18 for t in filters:
18 for t in filters:
19 if t and t[-1] != os.sep: t += os.sep
19 if t and t[-1] != os.sep: t += os.sep
20 l += [ x for x in files if x.startswith(t) ]
20 l += [ x for x in files if x.startswith(t) ]
21 return l
21 return l
22
22
23 def relfilter(repo, files):
23 def relfilter(repo, files):
24 if os.getcwd() != repo.root:
24 if os.getcwd() != repo.root:
25 p = os.getcwd()[len(repo.root) + 1: ]
25 p = os.getcwd()[len(repo.root) + 1: ]
26 return filterfiles([p], files)
26 return filterfiles([p], files)
27 return files
27 return files
28
28
29 def relpath(repo, args):
29 def relpath(repo, args):
30 if os.getcwd() != repo.root:
30 if os.getcwd() != repo.root:
31 p = os.getcwd()[len(repo.root) + 1: ]
31 p = os.getcwd()[len(repo.root) + 1: ]
32 return [ os.path.normpath(os.path.join(p, x)) for x in args ]
32 return [ os.path.normpath(os.path.join(p, x)) for x in args ]
33 return args
33 return args
34
34
35 def dodiff(repo, path, files = None, node1 = None, node2 = None):
35 def dodiff(repo, path, files = None, node1 = None, node2 = None):
36 def date(c):
36 def date(c):
37 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
37 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
38
38
39 if node2:
39 if node2:
40 change = repo.changelog.read(node2)
40 change = repo.changelog.read(node2)
41 mmap2 = repo.manifest.read(change[0])
41 mmap2 = repo.manifest.read(change[0])
42 (c, a, d) = repo.diffrevs(node1, node2)
42 (c, a, d) = repo.diffrevs(node1, node2)
43 def read(f): return repo.file(f).read(mmap2[f])
43 def read(f): return repo.file(f).read(mmap2[f])
44 date2 = date(change)
44 date2 = date(change)
45 else:
45 else:
46 date2 = time.asctime()
46 date2 = time.asctime()
47 (c, a, d, u) = repo.diffdir(path, node1)
47 (c, a, d, u) = repo.diffdir(path, node1)
48 if not node1:
48 if not node1:
49 node1 = repo.dirstate.parents()[0]
49 node1 = repo.dirstate.parents()[0]
50 def read(f): return file(os.path.join(repo.root, f)).read()
50 def read(f): return file(os.path.join(repo.root, f)).read()
51
51
52 change = repo.changelog.read(node1)
52 change = repo.changelog.read(node1)
53 mmap = repo.manifest.read(change[0])
53 mmap = repo.manifest.read(change[0])
54 date1 = date(change)
54 date1 = date(change)
55
55
56 if files:
56 if files:
57 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
57 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
58
58
59 for f in c:
59 for f in c:
60 to = None
60 to = None
61 if f in mmap:
61 if f in mmap:
62 to = repo.file(f).read(mmap[f])
62 to = repo.file(f).read(mmap[f])
63 tn = read(f)
63 tn = read(f)
64 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
64 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
65 for f in a:
65 for f in a:
66 to = None
66 to = None
67 tn = read(f)
67 tn = read(f)
68 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
68 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
69 for f in d:
69 for f in d:
70 to = repo.file(f).read(mmap[f])
70 to = repo.file(f).read(mmap[f])
71 tn = None
71 tn = None
72 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
72 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
73
73
74 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
74 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
75 """show a single changeset or file revision"""
75 """show a single changeset or file revision"""
76 changelog = repo.changelog
76 changelog = repo.changelog
77 if filelog:
77 if filelog:
78 log = filelog
78 log = filelog
79 filerev = rev
79 filerev = rev
80 node = filenode = filelog.node(filerev)
80 node = filenode = filelog.node(filerev)
81 changerev = filelog.linkrev(filenode)
81 changerev = filelog.linkrev(filenode)
82 changenode = changenode or changelog.node(changerev)
82 changenode = changenode or changelog.node(changerev)
83 else:
83 else:
84 log = changelog
84 log = changelog
85 changerev = rev
85 changerev = rev
86 if changenode is None:
86 if changenode is None:
87 changenode = changelog.node(changerev)
87 changenode = changelog.node(changerev)
88 elif not changerev:
88 elif not changerev:
89 rev = changerev = changelog.rev(changenode)
89 rev = changerev = changelog.rev(changenode)
90 node = changenode
90 node = changenode
91
91
92 if ui.quiet:
92 if ui.quiet:
93 ui.write("%d:%s\n" % (rev, hg.hex(node)))
93 ui.write("%d:%s\n" % (rev, hg.hex(node)))
94 return
94 return
95
95
96 changes = changelog.read(changenode)
96 changes = changelog.read(changenode)
97
97
98 parents = [(log.rev(parent), hg.hex(parent))
98 parents = [(log.rev(parent), hg.hex(parent))
99 for parent in log.parents(node)
99 for parent in log.parents(node)
100 if ui.debugflag or parent != hg.nullid]
100 if ui.debugflag or parent != hg.nullid]
101 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
101 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
102 parents = []
102 parents = []
103
103
104 if filelog:
104 if filelog:
105 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
105 ui.write("revision: %d:%s\n" % (filerev, hg.hex(filenode)))
106 for parent in parents:
106 for parent in parents:
107 ui.write("parent: %d:%s\n" % parent)
107 ui.write("parent: %d:%s\n" % parent)
108 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
108 ui.status("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
109 else:
109 else:
110 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
110 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
111 for parent in parents:
111 for parent in parents:
112 ui.write("parent: %d:%s\n" % parent)
112 ui.write("parent: %d:%s\n" % parent)
113 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
113 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
114 hg.hex(changes[0])))
114 hg.hex(changes[0])))
115 ui.status("user: %s\n" % changes[1])
115 ui.status("user: %s\n" % changes[1])
116 ui.status("date: %s\n" % time.asctime(
116 ui.status("date: %s\n" % time.asctime(
117 time.localtime(float(changes[2].split(' ')[0]))))
117 time.localtime(float(changes[2].split(' ')[0]))))
118 ui.note("files: %s\n" % " ".join(changes[3]))
118 ui.note("files: %s\n" % " ".join(changes[3]))
119 description = changes[4].strip()
119 description = changes[4].strip()
120 if description:
120 if description:
121 if ui.verbose:
121 if ui.verbose:
122 ui.status("description:\n")
122 ui.status("description:\n")
123 ui.status(description)
123 ui.status(description)
124 ui.status("\n\n")
124 ui.status("\n\n")
125 else:
125 else:
126 ui.status("summary: %s\n" % description.splitlines()[0])
126 ui.status("summary: %s\n" % description.splitlines()[0])
127 ui.status("\n")
127 ui.status("\n")
128
128
129 def help(ui, cmd=None):
129 def help(ui, cmd=None):
130 '''show help for a given command or all commands'''
130 '''show help for a given command or all commands'''
131 if cmd:
131 if cmd:
132 try:
132 try:
133 i = find(cmd)
133 i = find(cmd)
134 ui.write("%s\n\n" % i[2])
134 ui.write("%s\n\n" % i[2])
135
135
136 if i[1]:
136 if i[1]:
137 for s, l, d, c in i[1]:
137 for s, l, d, c in i[1]:
138 opt=' '
138 opt=' '
139 if s: opt = opt + '-' + s + ' '
139 if s: opt = opt + '-' + s + ' '
140 if l: opt = opt + '--' + l + ' '
140 if l: opt = opt + '--' + l + ' '
141 if d: opt = opt + '(' + str(d) + ')'
141 if d: opt = opt + '(' + str(d) + ')'
142 ui.write(opt, "\n")
142 ui.write(opt, "\n")
143 if c: ui.write(' %s\n' % c)
143 if c: ui.write(' %s\n' % c)
144 ui.write("\n")
144 ui.write("\n")
145
145
146 ui.write(i[0].__doc__, "\n")
146 ui.write(i[0].__doc__, "\n")
147 except UnknownCommand:
147 except UnknownCommand:
148 ui.warn("hg: unknown command %s\n" % cmd)
148 ui.warn("hg: unknown command %s\n" % cmd)
149 sys.exit(0)
149 sys.exit(0)
150 else:
150 else:
151 ui.status('hg commands:\n\n')
151 ui.status('hg commands:\n\n')
152
152
153 h = {}
153 h = {}
154 for e in table.values():
154 for e in table.values():
155 f = e[0]
155 f = e[0]
156 if f.__name__.startswith("debug"): continue
156 if f.__name__.startswith("debug"): continue
157 d = ""
157 d = ""
158 if f.__doc__:
158 if f.__doc__:
159 d = f.__doc__.splitlines(0)[0].rstrip()
159 d = f.__doc__.splitlines(0)[0].rstrip()
160 h[f.__name__] = d
160 h[f.__name__] = d
161
161
162 fns = h.keys()
162 fns = h.keys()
163 fns.sort()
163 fns.sort()
164 m = max(map(len, fns))
164 m = max(map(len, fns))
165 for f in fns:
165 for f in fns:
166 ui.status(' %-*s %s\n' % (m, f, h[f]))
166 ui.status(' %-*s %s\n' % (m, f, h[f]))
167
167
168 # Commands start here, listed alphabetically
168 # Commands start here, listed alphabetically
169
169
170 def add(ui, repo, file, *files):
170 def add(ui, repo, file, *files):
171 '''add the specified files on the next commit'''
171 '''add the specified files on the next commit'''
172 repo.add(relpath(repo, (file,) + files))
172 repo.add(relpath(repo, (file,) + files))
173
173
174 def addremove(ui, repo, *files):
174 def addremove(ui, repo, *files):
175 """add all new files, delete all missing files"""
175 """add all new files, delete all missing files"""
176 if files:
176 if files:
177 files = relpath(repo, files)
177 files = relpath(repo, files)
178 d = []
178 d = []
179 u = []
179 u = []
180 for f in files:
180 for f in files:
181 p = repo.wjoin(f)
181 p = repo.wjoin(f)
182 s = repo.dirstate.state(f)
182 s = repo.dirstate.state(f)
183 isfile = os.path.isfile(p)
183 isfile = os.path.isfile(p)
184 if s != 'r' and not isfile:
184 if s != 'r' and not isfile:
185 d.append(f)
185 d.append(f)
186 elif s not in 'nmai' and isfile:
186 elif s not in 'nmai' and isfile:
187 u.append(f)
187 u.append(f)
188 else:
188 else:
189 (c, a, d, u) = repo.diffdir(repo.root)
189 (c, a, d, u) = repo.diffdir(repo.root)
190 repo.add(u)
190 repo.add(u)
191 repo.remove(d)
191 repo.remove(d)
192
192
193 def annotate(u, repo, file, *files, **ops):
193 def annotate(u, repo, file, *files, **ops):
194 """show changeset information per file line"""
194 """show changeset information per file line"""
195 def getnode(rev):
195 def getnode(rev):
196 return hg.short(repo.changelog.node(rev))
196 return hg.short(repo.changelog.node(rev))
197
197
198 def getname(rev):
198 def getname(rev):
199 try:
199 try:
200 return bcache[rev]
200 return bcache[rev]
201 except KeyError:
201 except KeyError:
202 cl = repo.changelog.read(repo.changelog.node(rev))
202 cl = repo.changelog.read(repo.changelog.node(rev))
203 name = cl[1]
203 name = cl[1]
204 f = name.find('@')
204 f = name.find('@')
205 if f >= 0:
205 if f >= 0:
206 name = name[:f]
206 name = name[:f]
207 bcache[rev] = name
207 bcache[rev] = name
208 return name
208 return name
209
209
210 bcache = {}
210 bcache = {}
211 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
211 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
212 if not ops['user'] and not ops['changeset']:
212 if not ops['user'] and not ops['changeset']:
213 ops['number'] = 1
213 ops['number'] = 1
214
214
215 node = repo.dirstate.parents()[0]
215 node = repo.dirstate.parents()[0]
216 if ops['revision']:
216 if ops['revision']:
217 node = repo.changelog.lookup(ops['revision'])
217 node = repo.changelog.lookup(ops['revision'])
218 change = repo.changelog.read(node)
218 change = repo.changelog.read(node)
219 mmap = repo.manifest.read(change[0])
219 mmap = repo.manifest.read(change[0])
220 maxuserlen = 0
220 maxuserlen = 0
221 maxchangelen = 0
221 maxchangelen = 0
222 for f in relpath(repo, (file,) + files):
222 for f in relpath(repo, (file,) + files):
223 lines = repo.file(f).annotate(mmap[f])
223 lines = repo.file(f).annotate(mmap[f])
224 pieces = []
224 pieces = []
225
225
226 for o, f in opmap:
226 for o, f in opmap:
227 if ops[o]:
227 if ops[o]:
228 l = [ f(n) for n,t in lines ]
228 l = [ f(n) for n,t in lines ]
229 m = max(map(len, l))
229 m = max(map(len, l))
230 pieces.append([ "%*s" % (m, x) for x in l])
230 pieces.append([ "%*s" % (m, x) for x in l])
231
231
232 for p,l in zip(zip(*pieces), lines):
232 for p,l in zip(zip(*pieces), lines):
233 u.write(" ".join(p) + ": " + l[1])
233 u.write(" ".join(p) + ": " + l[1])
234
234
235 def cat(ui, repo, file, rev = []):
235 def cat(ui, repo, file, rev = []):
236 """output the latest or given revision of a file"""
236 """output the latest or given revision of a file"""
237 r = repo.file(relpath(repo, [file])[0])
237 r = repo.file(relpath(repo, [file])[0])
238 n = r.tip()
238 n = r.tip()
239 if rev: n = r.lookup(rev)
239 if rev: n = r.lookup(rev)
240 sys.stdout.write(r.read(n))
240 sys.stdout.write(r.read(n))
241
241
242 def commit(ui, repo, *files, **opts):
242 def commit(ui, repo, *files, **opts):
243 """commit the specified files or all outstanding changes"""
243 """commit the specified files or all outstanding changes"""
244 text = opts['text']
244 text = opts['text']
245 if not text and opts['logfile']:
245 if not text and opts['logfile']:
246 try: text = open(opts['logfile']).read()
246 try: text = open(opts['logfile']).read()
247 except IOError: pass
247 except IOError: pass
248
248
249 if opts['addremove']:
249 if opts['addremove']:
250 addremove(ui, repo, *files)
250 addremove(ui, repo, *files)
251 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
251 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
252
252
253 def copy(ui, repo, source, dest):
254 """mark a file as copied or renamed for the next commit"""
255 return repo.copy(*relpath(repo, (source, dest)))
256
253 def debugaddchangegroup(ui, repo):
257 def debugaddchangegroup(ui, repo):
254 data = sys.stdin.read()
258 data = sys.stdin.read()
255 repo.addchangegroup(data)
259 repo.addchangegroup(data)
256
260
257 def debugchangegroup(ui, repo, roots):
261 def debugchangegroup(ui, repo, roots):
258 newer = repo.newer(map(repo.lookup, roots))
262 newer = repo.newer(map(repo.lookup, roots))
259 for chunk in repo.changegroup(newer):
263 for chunk in repo.changegroup(newer):
260 sys.stdout.write(chunk)
264 sys.stdout.write(chunk)
261
265
262 def debugindex(ui, file):
266 def debugindex(ui, file):
263 r = hg.revlog(open, file, "")
267 r = hg.revlog(open, file, "")
264 print " rev offset length base linkrev"+\
268 print " rev offset length base linkrev"+\
265 " p1 p2 nodeid"
269 " p1 p2 nodeid"
266 for i in range(r.count()):
270 for i in range(r.count()):
267 e = r.index[i]
271 e = r.index[i]
268 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
272 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
269 i, e[0], e[1], e[2], e[3],
273 i, e[0], e[1], e[2], e[3],
270 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
274 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
271
275
272 def debugindexdot(ui, file):
276 def debugindexdot(ui, file):
273 r = hg.revlog(open, file, "")
277 r = hg.revlog(open, file, "")
274 print "digraph G {"
278 print "digraph G {"
275 for i in range(r.count()):
279 for i in range(r.count()):
276 e = r.index[i]
280 e = r.index[i]
277 print "\t%d -> %d" % (r.rev(e[4]), i)
281 print "\t%d -> %d" % (r.rev(e[4]), i)
278 if e[5] != hg.nullid:
282 if e[5] != hg.nullid:
279 print "\t%d -> %d" % (r.rev(e[5]), i)
283 print "\t%d -> %d" % (r.rev(e[5]), i)
280 print "}"
284 print "}"
281
285
282 def diff(ui, repo, *files, **opts):
286 def diff(ui, repo, *files, **opts):
283 """diff working directory (or selected files)"""
287 """diff working directory (or selected files)"""
284 revs = []
288 revs = []
285 if opts['rev']:
289 if opts['rev']:
286 revs = map(lambda x: repo.lookup(x), opts['rev'])
290 revs = map(lambda x: repo.lookup(x), opts['rev'])
287
291
288 if len(revs) > 2:
292 if len(revs) > 2:
289 self.ui.warn("too many revisions to diff\n")
293 self.ui.warn("too many revisions to diff\n")
290 sys.exit(1)
294 sys.exit(1)
291
295
292 if files:
296 if files:
293 files = relpath(repo, files)
297 files = relpath(repo, files)
294 else:
298 else:
295 files = relpath(repo, [""])
299 files = relpath(repo, [""])
296
300
297 dodiff(repo, os.getcwd(), files, *revs)
301 dodiff(repo, os.getcwd(), files, *revs)
298
302
299 def export(ui, repo, changeset):
303 def export(ui, repo, changeset):
300 """dump the changeset header and diffs for a revision"""
304 """dump the changeset header and diffs for a revision"""
301 node = repo.lookup(changeset)
305 node = repo.lookup(changeset)
302 prev, other = repo.changelog.parents(node)
306 prev, other = repo.changelog.parents(node)
303 change = repo.changelog.read(node)
307 change = repo.changelog.read(node)
304 print "# HG changeset patch"
308 print "# HG changeset patch"
305 print "# User %s" % change[1]
309 print "# User %s" % change[1]
306 print "# Node ID %s" % hg.hex(node)
310 print "# Node ID %s" % hg.hex(node)
307 print "# Parent %s" % hg.hex(prev)
311 print "# Parent %s" % hg.hex(prev)
308 print
312 print
309 if other != hg.nullid:
313 if other != hg.nullid:
310 print "# Parent %s" % hg.hex(other)
314 print "# Parent %s" % hg.hex(other)
311 print change[4].rstrip()
315 print change[4].rstrip()
312 print
316 print
313
317
314 dodiff(repo, "", None, prev, node)
318 dodiff(repo, "", None, prev, node)
315
319
316 def forget(ui, repo, file, *files):
320 def forget(ui, repo, file, *files):
317 """don't add the specified files on the next commit"""
321 """don't add the specified files on the next commit"""
318 repo.forget(relpath(repo, (file,) + files))
322 repo.forget(relpath(repo, (file,) + files))
319
323
320 def heads(ui, repo):
324 def heads(ui, repo):
321 """show current repository heads"""
325 """show current repository heads"""
322 for n in repo.changelog.heads():
326 for n in repo.changelog.heads():
323 show_changeset(ui, repo, changenode=n)
327 show_changeset(ui, repo, changenode=n)
324
328
325 def history(ui, repo):
329 def history(ui, repo):
326 """show the changelog history"""
330 """show the changelog history"""
327 for i in range(repo.changelog.count() - 1, -1, -1):
331 for i in range(repo.changelog.count() - 1, -1, -1):
328 show_changeset(ui, repo, rev=i)
332 show_changeset(ui, repo, rev=i)
329
333
330 def identify(ui, repo):
334 def identify(ui, repo):
331 """print information about the working copy"""
335 """print information about the working copy"""
332 (c, a, d, u) = repo.diffdir(repo.root)
336 (c, a, d, u) = repo.diffdir(repo.root)
333 mflag = (c or a or d or u) and "+" or ""
337 mflag = (c or a or d or u) and "+" or ""
334 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
338 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
335 if not parents:
339 if not parents:
336 ui.write("unknown\n")
340 ui.write("unknown\n")
337 return
341 return
338
342
339 tstring = ''
343 tstring = ''
340 if not ui.quiet:
344 if not ui.quiet:
341 tags = sum(map(repo.nodetags, parents), [])
345 tags = sum(map(repo.nodetags, parents), [])
342 tstring = " " + ' + '.join(tags)
346 tstring = " " + ' + '.join(tags)
343
347
344 hexfunc = ui.verbose and hg.hex or hg.short
348 hexfunc = ui.verbose and hg.hex or hg.short
345 pstring = '+'.join([hexfunc(parent) for parent in parents])
349 pstring = '+'.join([hexfunc(parent) for parent in parents])
346 ui.write("%s%s%s\n" % (pstring, mflag, tstring))
350 ui.write("%s%s%s\n" % (pstring, mflag, tstring))
347
351
348 def init(ui, source=None):
352 def init(ui, source=None):
349 """create a new repository or copy an existing one"""
353 """create a new repository or copy an existing one"""
350
354
351 if source:
355 if source:
352 paths = {}
356 paths = {}
353 for name, path in ui.configitems("paths"):
357 for name, path in ui.configitems("paths"):
354 paths[name] = path
358 paths[name] = path
355
359
356 if source in paths: source = paths[source]
360 if source in paths: source = paths[source]
357
361
358 link = 0
362 link = 0
359 if not source.startswith("http://"):
363 if not source.startswith("http://"):
360 d1 = os.stat(os.getcwd()).st_dev
364 d1 = os.stat(os.getcwd()).st_dev
361 d2 = os.stat(source).st_dev
365 d2 = os.stat(source).st_dev
362 if d1 == d2: link = 1
366 if d1 == d2: link = 1
363
367
364 if link:
368 if link:
365 ui.debug("copying by hardlink\n")
369 ui.debug("copying by hardlink\n")
366 os.system("cp -al %s/.hg .hg" % source)
370 os.system("cp -al %s/.hg .hg" % source)
367 try:
371 try:
368 os.remove(".hg/dirstate")
372 os.remove(".hg/dirstate")
369 except: pass
373 except: pass
370
374
371 repo = hg.repository(ui, ".")
375 repo = hg.repository(ui, ".")
372
376
373 else:
377 else:
374 repo = hg.repository(ui, ".", create=1)
378 repo = hg.repository(ui, ".", create=1)
375 other = hg.repository(ui, source)
379 other = hg.repository(ui, source)
376 cg = repo.getchangegroup(other)
380 cg = repo.getchangegroup(other)
377 repo.addchangegroup(cg)
381 repo.addchangegroup(cg)
378 else:
382 else:
379 repo = hg.repository(ui, ".", create=1)
383 repo = hg.repository(ui, ".", create=1)
380
384
381 f = repo.opener("hgrc", "w")
385 f = repo.opener("hgrc", "w")
382 f.write("[paths]\n")
386 f.write("[paths]\n")
383 f.write("default = %s\n" % source)
387 f.write("default = %s\n" % source)
384
388
385 def log(ui, repo, f):
389 def log(ui, repo, f):
386 """show the revision history of a single file"""
390 """show the revision history of a single file"""
387 f = relpath(repo, [f])[0]
391 f = relpath(repo, [f])[0]
388
392
389 r = repo.file(f)
393 r = repo.file(f)
390 for i in range(r.count() - 1, -1, -1):
394 for i in range(r.count() - 1, -1, -1):
391 show_changeset(ui, repo, filelog=r, rev=i)
395 show_changeset(ui, repo, filelog=r, rev=i)
392
396
393 def manifest(ui, repo, rev = []):
397 def manifest(ui, repo, rev = []):
394 """output the latest or given revision of the project manifest"""
398 """output the latest or given revision of the project manifest"""
395 n = repo.manifest.tip()
399 n = repo.manifest.tip()
396 if rev:
400 if rev:
397 n = repo.manifest.lookup(rev)
401 n = repo.manifest.lookup(rev)
398 m = repo.manifest.read(n)
402 m = repo.manifest.read(n)
399 mf = repo.manifest.readflags(n)
403 mf = repo.manifest.readflags(n)
400 files = m.keys()
404 files = m.keys()
401 files.sort()
405 files.sort()
402
406
403 for f in files:
407 for f in files:
404 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
408 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
405
409
406 def parents(ui, repo, node = None):
410 def parents(ui, repo, node = None):
407 '''show the parents of the current working dir'''
411 '''show the parents of the current working dir'''
408 if node:
412 if node:
409 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
413 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
410 else:
414 else:
411 p = repo.dirstate.parents()
415 p = repo.dirstate.parents()
412
416
413 for n in p:
417 for n in p:
414 if n != hg.nullid:
418 if n != hg.nullid:
415 show_changeset(ui, repo, changenode=n)
419 show_changeset(ui, repo, changenode=n)
416
420
417 def patch(ui, repo, patch1, *patches, **opts):
421 def patch(ui, repo, patch1, *patches, **opts):
418 """import an ordered set of patches"""
422 """import an ordered set of patches"""
419 try:
423 try:
420 import psyco
424 import psyco
421 psyco.full()
425 psyco.full()
422 except:
426 except:
423 pass
427 pass
424
428
425 patches = (patch1,) + patches
429 patches = (patch1,) + patches
426
430
427 d = opts["base"]
431 d = opts["base"]
428 strip = opts["strip"]
432 strip = opts["strip"]
429 quiet = opts["quiet"] and "> /dev/null" or ""
433 quiet = opts["quiet"] and "> /dev/null" or ""
430
434
431 for patch in patches:
435 for patch in patches:
432 ui.status("applying %s\n" % patch)
436 ui.status("applying %s\n" % patch)
433 pf = os.path.join(d, patch)
437 pf = os.path.join(d, patch)
434
438
435 text = ""
439 text = ""
436 for l in file(pf):
440 for l in file(pf):
437 if l[:4] == "--- ": break
441 if l[:4] == "--- ": break
438 text += l
442 text += l
439
443
440 # make sure text isn't empty
444 # make sure text isn't empty
441 if not text: text = "imported patch %s\n" % patch
445 if not text: text = "imported patch %s\n" % patch
442
446
443 f = os.popen("patch -p%d < %s" % (strip, pf))
447 f = os.popen("patch -p%d < %s" % (strip, pf))
444 files = []
448 files = []
445 for l in f.read().splitlines():
449 for l in f.read().splitlines():
446 l.rstrip('\r\n');
450 l.rstrip('\r\n');
447 if not quiet:
451 if not quiet:
448 print l
452 print l
449 if l[:14] == 'patching file ':
453 if l[:14] == 'patching file ':
450 files.append(l[14:])
454 files.append(l[14:])
451 f.close()
455 f.close()
452
456
453 if len(files) > 0:
457 if len(files) > 0:
454 addremove(ui, repo, *files)
458 addremove(ui, repo, *files)
455 repo.commit(files, text)
459 repo.commit(files, text)
456
460
457 def pull(ui, repo, source="default"):
461 def pull(ui, repo, source="default"):
458 """pull changes from the specified source"""
462 """pull changes from the specified source"""
459 paths = {}
463 paths = {}
460 for name, path in ui.configitems("paths"):
464 for name, path in ui.configitems("paths"):
461 paths[name] = path
465 paths[name] = path
462
466
463 if source in paths: source = paths[source]
467 if source in paths: source = paths[source]
464
468
465 other = hg.repository(ui, source)
469 other = hg.repository(ui, source)
466 cg = repo.getchangegroup(other)
470 cg = repo.getchangegroup(other)
467 repo.addchangegroup(cg)
471 repo.addchangegroup(cg)
468
472
469 def push(ui, repo, dest):
473 def push(ui, repo, dest):
470 """push changes to the specified destination"""
474 """push changes to the specified destination"""
471 paths = {}
475 paths = {}
472 for name, path in ui.configitems("paths"):
476 for name, path in ui.configitems("paths"):
473 paths[name] = path
477 paths[name] = path
474
478
475 if dest in paths: dest = paths[dest]
479 if dest in paths: dest = paths[dest]
476
480
477 if not dest.startswith("ssh://"):
481 if not dest.startswith("ssh://"):
478 ui.warn("abort: can only push to ssh:// destinations currently\n")
482 ui.warn("abort: can only push to ssh:// destinations currently\n")
479 return 1
483 return 1
480
484
481 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
485 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', dest)
482 if not m:
486 if not m:
483 ui.warn("abort: couldn't parse destination %s\n" % dest)
487 ui.warn("abort: couldn't parse destination %s\n" % dest)
484 return 1
488 return 1
485
489
486 user, host, port, path = map(m.group, (2, 3, 5, 7))
490 user, host, port, path = map(m.group, (2, 3, 5, 7))
487 host = user and ("%s@%s" % (user, host)) or host
491 host = user and ("%s@%s" % (user, host)) or host
488 port = port and (" -p %s") % port or ""
492 port = port and (" -p %s") % port or ""
489 path = path or ""
493 path = path or ""
490
494
491 sport = random.randrange(30000, 60000)
495 sport = random.randrange(30000, 60000)
492 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
496 cmd = "ssh %s%s -R %d:localhost:%d 'cd %s; hg pull http://localhost:%d/'"
493 cmd = cmd % (host, port, sport+1, sport, path, sport+1)
497 cmd = cmd % (host, port, sport+1, sport, path, sport+1)
494
498
495 child = os.fork()
499 child = os.fork()
496 if not child:
500 if not child:
497 sys.stdout = file("/dev/null", "w")
501 sys.stdout = file("/dev/null", "w")
498 sys.stderr = sys.stdout
502 sys.stderr = sys.stdout
499 hgweb.server(repo.root, "pull", "", "localhost", sport)
503 hgweb.server(repo.root, "pull", "", "localhost", sport)
500 else:
504 else:
501 r = os.system(cmd)
505 r = os.system(cmd)
502 os.kill(child, signal.SIGTERM)
506 os.kill(child, signal.SIGTERM)
503 return r
507 return r
504
508
505 def rawcommit(ui, repo, flist, **rc):
509 def rawcommit(ui, repo, flist, **rc):
506 "raw commit interface"
510 "raw commit interface"
507
511
508 text = rc['text']
512 text = rc['text']
509 if not text and rc['logfile']:
513 if not text and rc['logfile']:
510 try: text = open(rc['logfile']).read()
514 try: text = open(rc['logfile']).read()
511 except IOError: pass
515 except IOError: pass
512 if not text and not rc['logfile']:
516 if not text and not rc['logfile']:
513 print "missing commit text"
517 print "missing commit text"
514 return 1
518 return 1
515
519
516 files = relpath(repo, flist)
520 files = relpath(repo, flist)
517 if rc['files']:
521 if rc['files']:
518 files += open(rc['files']).read().splitlines()
522 files += open(rc['files']).read().splitlines()
519
523
520 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
524 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
521
525
522 def recover(ui, repo):
526 def recover(ui, repo):
523 """roll back an interrupted transaction"""
527 """roll back an interrupted transaction"""
524 repo.recover()
528 repo.recover()
525
529
526 def remove(ui, repo, file, *files):
530 def remove(ui, repo, file, *files):
527 """remove the specified files on the next commit"""
531 """remove the specified files on the next commit"""
528 repo.remove(relpath(repo, (file,) + files))
532 repo.remove(relpath(repo, (file,) + files))
529
533
530 def serve(ui, repo, **opts):
534 def serve(ui, repo, **opts):
531 """export the repository via HTTP"""
535 """export the repository via HTTP"""
532 hgweb.server(repo.root, opts["name"], opts["templates"],
536 hgweb.server(repo.root, opts["name"], opts["templates"],
533 opts["address"], opts["port"])
537 opts["address"], opts["port"])
534
538
535 def status(ui, repo):
539 def status(ui, repo):
536 '''show changed files in the working directory
540 '''show changed files in the working directory
537
541
538 C = changed
542 C = changed
539 A = added
543 A = added
540 R = removed
544 R = removed
541 ? = not tracked'''
545 ? = not tracked'''
542
546
543 (c, a, d, u) = repo.diffdir(os.getcwd())
547 (c, a, d, u) = repo.diffdir(os.getcwd())
544 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
548 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
545
549
546 for f in c: print "C", f
550 for f in c: print "C", f
547 for f in a: print "A", f
551 for f in a: print "A", f
548 for f in d: print "R", f
552 for f in d: print "R", f
549 for f in u: print "?", f
553 for f in u: print "?", f
550
554
551 def tags(ui, repo):
555 def tags(ui, repo):
552 """list repository tags"""
556 """list repository tags"""
553
557
554 l = repo.tagslist()
558 l = repo.tagslist()
555 l.reverse()
559 l.reverse()
556 for t,n in l:
560 for t,n in l:
557 try:
561 try:
558 r = repo.changelog.rev(n)
562 r = repo.changelog.rev(n)
559 except KeyError:
563 except KeyError:
560 r = "?"
564 r = "?"
561 print "%-30s %5d:%s" % (t, repo.changelog.rev(n), hg.hex(n))
565 print "%-30s %5d:%s" % (t, repo.changelog.rev(n), hg.hex(n))
562
566
563 def tip(ui, repo):
567 def tip(ui, repo):
564 """show the tip revision"""
568 """show the tip revision"""
565 n = repo.changelog.tip()
569 n = repo.changelog.tip()
566 show_changeset(ui, repo, changenode=n)
570 show_changeset(ui, repo, changenode=n)
567
571
568 def undo(ui, repo):
572 def undo(ui, repo):
569 """undo the last transaction"""
573 """undo the last transaction"""
570 repo.undo()
574 repo.undo()
571
575
572 def update(ui, repo, node=None, merge=False, clean=False):
576 def update(ui, repo, node=None, merge=False, clean=False):
573 '''update or merge working directory
577 '''update or merge working directory
574
578
575 If there are no outstanding changes in the working directory and
579 If there are no outstanding changes in the working directory and
576 there is a linear relationship between the current version and the
580 there is a linear relationship between the current version and the
577 requested version, the result is the requested version.
581 requested version, the result is the requested version.
578
582
579 Otherwise the result is a merge between the contents of the
583 Otherwise the result is a merge between the contents of the
580 current working directory and the requested version. Files that
584 current working directory and the requested version. Files that
581 changed between either parent are marked as changed for the next
585 changed between either parent are marked as changed for the next
582 commit and a commit must be performed before any further updates
586 commit and a commit must be performed before any further updates
583 are allowed.
587 are allowed.
584 '''
588 '''
585 node = node and repo.lookup(node) or repo.changelog.tip()
589 node = node and repo.lookup(node) or repo.changelog.tip()
586 return repo.update(node, allow=merge, force=clean)
590 return repo.update(node, allow=merge, force=clean)
587
591
588 def verify(ui, repo):
592 def verify(ui, repo):
589 """verify the integrity of the repository"""
593 """verify the integrity of the repository"""
590 return repo.verify()
594 return repo.verify()
591
595
592 # Command options and aliases are listed here, alphabetically
596 # Command options and aliases are listed here, alphabetically
593
597
594 table = {
598 table = {
595 "add": (add, [], "hg add [files]"),
599 "add": (add, [], "hg add [files]"),
596 "addremove": (addremove, [], "hg addremove [files]"),
600 "addremove": (addremove, [], "hg addremove [files]"),
597 "ann|annotate": (annotate,
601 "ann|annotate": (annotate,
598 [('r', 'revision', '', 'revision'),
602 [('r', 'revision', '', 'revision'),
599 ('u', 'user', None, 'show user'),
603 ('u', 'user', None, 'show user'),
600 ('n', 'number', None, 'show revision number'),
604 ('n', 'number', None, 'show revision number'),
601 ('c', 'changeset', None, 'show changeset')],
605 ('c', 'changeset', None, 'show changeset')],
602 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
606 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
603 "cat|dump": (cat, [], 'hg cat <file> [rev]'),
607 "cat|dump": (cat, [], 'hg cat <file> [rev]'),
604 "commit|ci": (commit,
608 "commit|ci": (commit,
605 [('t', 'text', "", 'commit text'),
609 [('t', 'text', "", 'commit text'),
606 ('A', 'addremove', None, 'run add/remove during commit'),
610 ('A', 'addremove', None, 'run add/remove during commit'),
607 ('l', 'logfile', "", 'commit text file'),
611 ('l', 'logfile', "", 'commit text file'),
608 ('d', 'date', "", 'data'),
612 ('d', 'date', "", 'data'),
609 ('u', 'user', "", 'user')],
613 ('u', 'user', "", 'user')],
610 'hg commit [files]'),
614 'hg commit [files]'),
615 "copy": (copy, [], 'hg copy <source> <dest>'),
611 "debugaddchangegroup": (debugaddchangegroup, [], 'debugaddchangegroup'),
616 "debugaddchangegroup": (debugaddchangegroup, [], 'debugaddchangegroup'),
612 "debugchangegroup": (debugchangegroup, [], 'debugchangegroup [roots]'),
617 "debugchangegroup": (debugchangegroup, [], 'debugchangegroup [roots]'),
613 "debugindex": (debugindex, [], 'debugindex <file>'),
618 "debugindex": (debugindex, [], 'debugindex <file>'),
614 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
619 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
615 "diff": (diff, [('r', 'rev', [], 'revision')],
620 "diff": (diff, [('r', 'rev', [], 'revision')],
616 'hg diff [-r A] [-r B] [files]'),
621 'hg diff [-r A] [-r B] [files]'),
617 "export": (export, [], "hg export <changeset>"),
622 "export": (export, [], "hg export <changeset>"),
618 "forget": (forget, [], "hg forget [files]"),
623 "forget": (forget, [], "hg forget [files]"),
619 "heads": (heads, [], 'hg heads'),
624 "heads": (heads, [], 'hg heads'),
620 "history": (history, [], 'hg history'),
625 "history": (history, [], 'hg history'),
621 "help": (help, [], 'hg help [command]'),
626 "help": (help, [], 'hg help [command]'),
622 "identify|id": (identify, [], 'hg identify'),
627 "identify|id": (identify, [], 'hg identify'),
623 "init": (init, [], 'hg init [url]'),
628 "init": (init, [], 'hg init [url]'),
624 "log": (log, [], 'hg log <file>'),
629 "log": (log, [], 'hg log <file>'),
625 "manifest|dumpmanifest": (manifest, [], 'hg manifest [rev]'),
630 "manifest|dumpmanifest": (manifest, [], 'hg manifest [rev]'),
626 "parents": (parents, [], 'hg parents [node]'),
631 "parents": (parents, [], 'hg parents [node]'),
627 "patch|import": (patch,
632 "patch|import": (patch,
628 [('p', 'strip', 1, 'path strip'),
633 [('p', 'strip', 1, 'path strip'),
629 ('b', 'base', "", 'base path'),
634 ('b', 'base', "", 'base path'),
630 ('q', 'quiet', "", 'silence diff')],
635 ('q', 'quiet', "", 'silence diff')],
631 "hg import [options] patches"),
636 "hg import [options] patches"),
632 "pull|merge": (pull, [], 'hg pull [source]'),
637 "pull|merge": (pull, [], 'hg pull [source]'),
633 "push": (push, [], 'hg push <destination>'),
638 "push": (push, [], 'hg push <destination>'),
634 "rawcommit": (rawcommit,
639 "rawcommit": (rawcommit,
635 [('p', 'parent', [], 'parent'),
640 [('p', 'parent', [], 'parent'),
636 ('d', 'date', "", 'data'),
641 ('d', 'date', "", 'data'),
637 ('u', 'user', "", 'user'),
642 ('u', 'user', "", 'user'),
638 ('F', 'files', "", 'file list'),
643 ('F', 'files', "", 'file list'),
639 ('t', 'text', "", 'commit text'),
644 ('t', 'text', "", 'commit text'),
640 ('l', 'logfile', "", 'commit text file')],
645 ('l', 'logfile', "", 'commit text file')],
641 'hg rawcommit [options] [files]'),
646 'hg rawcommit [options] [files]'),
642 "recover": (recover, [], "hg recover"),
647 "recover": (recover, [], "hg recover"),
643 "remove": (remove, [], "hg remove [files]"),
648 "remove": (remove, [], "hg remove [files]"),
644 "serve": (serve, [('p', 'port', 8000, 'listen port'),
649 "serve": (serve, [('p', 'port', 8000, 'listen port'),
645 ('a', 'address', '', 'interface address'),
650 ('a', 'address', '', 'interface address'),
646 ('n', 'name', os.getcwd(), 'repository name'),
651 ('n', 'name', os.getcwd(), 'repository name'),
647 ('t', 'templates', "", 'template map')],
652 ('t', 'templates', "", 'template map')],
648 "hg serve [options]"),
653 "hg serve [options]"),
649 "status": (status, [], 'hg status'),
654 "status": (status, [], 'hg status'),
650 "tags": (tags, [], 'hg tags'),
655 "tags": (tags, [], 'hg tags'),
651 "tip": (tip, [], 'hg tip'),
656 "tip": (tip, [], 'hg tip'),
652 "undo": (undo, [], 'hg undo'),
657 "undo": (undo, [], 'hg undo'),
653 "update|up|checkout|co|resolve": (update,
658 "update|up|checkout|co|resolve": (update,
654 [('m', 'merge', None,
659 [('m', 'merge', None,
655 'allow merging of conflicts'),
660 'allow merging of conflicts'),
656 ('C', 'clean', None,
661 ('C', 'clean', None,
657 'overwrite locally modified files')],
662 'overwrite locally modified files')],
658 'hg update [options] [node]'),
663 'hg update [options] [node]'),
659 "verify": (verify, [], 'hg verify'),
664 "verify": (verify, [], 'hg verify'),
660 }
665 }
661
666
662 norepo = "init branch help debugindex debugindexdot"
667 norepo = "init branch help debugindex debugindexdot"
663
668
664 def find(cmd):
669 def find(cmd):
665 i = None
670 i = None
666 for e in table.keys():
671 for e in table.keys():
667 if re.match("(%s)$" % e, cmd):
672 if re.match("(%s)$" % e, cmd):
668 return table[e]
673 return table[e]
669
674
670 raise UnknownCommand(cmd)
675 raise UnknownCommand(cmd)
671
676
672 class SignalInterrupt(Exception): pass
677 class SignalInterrupt(Exception): pass
673
678
674 def catchterm(*args):
679 def catchterm(*args):
675 raise SignalInterrupt
680 raise SignalInterrupt
676
681
677 def run():
682 def run():
678 sys.exit(dispatch(sys.argv[1:]))
683 sys.exit(dispatch(sys.argv[1:]))
679
684
680 def dispatch(args):
685 def dispatch(args):
681 options = {}
686 options = {}
682 opts = [('v', 'verbose', None, 'verbose'),
687 opts = [('v', 'verbose', None, 'verbose'),
683 ('d', 'debug', None, 'debug'),
688 ('d', 'debug', None, 'debug'),
684 ('q', 'quiet', None, 'quiet'),
689 ('q', 'quiet', None, 'quiet'),
685 ('p', 'profile', None, 'profile'),
690 ('p', 'profile', None, 'profile'),
686 ('y', 'noninteractive', None, 'run non-interactively'),
691 ('y', 'noninteractive', None, 'run non-interactively'),
687 ]
692 ]
688
693
689 args = fancyopts.fancyopts(args, opts, options,
694 args = fancyopts.fancyopts(args, opts, options,
690 'hg [options] <command> [options] [files]')
695 'hg [options] <command> [options] [files]')
691
696
692 if not args:
697 if not args:
693 cmd = "help"
698 cmd = "help"
694 else:
699 else:
695 cmd, args = args[0], args[1:]
700 cmd, args = args[0], args[1:]
696
701
697 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
702 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
698 not options["noninteractive"])
703 not options["noninteractive"])
699
704
700 try:
705 try:
701 i = find(cmd)
706 i = find(cmd)
702 except UnknownCommand:
707 except UnknownCommand:
703 u.warn("hg: unknown command '%s'\n" % cmd)
708 u.warn("hg: unknown command '%s'\n" % cmd)
704 help(u)
709 help(u)
705 sys.exit(1)
710 sys.exit(1)
706
711
707 signal.signal(signal.SIGTERM, catchterm)
712 signal.signal(signal.SIGTERM, catchterm)
708
713
709 cmdoptions = {}
714 cmdoptions = {}
710 try:
715 try:
711 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
716 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
712 except fancyopts.getopt.GetoptError, inst:
717 except fancyopts.getopt.GetoptError, inst:
713 u.warn("hg %s: %s\n" % (cmd, inst))
718 u.warn("hg %s: %s\n" % (cmd, inst))
714 help(u, cmd)
719 help(u, cmd)
715 sys.exit(-1)
720 sys.exit(-1)
716
721
717 if cmd not in norepo.split():
722 if cmd not in norepo.split():
718 repo = hg.repository(ui = u)
723 repo = hg.repository(ui = u)
719 d = lambda: i[0](u, repo, *args, **cmdoptions)
724 d = lambda: i[0](u, repo, *args, **cmdoptions)
720 else:
725 else:
721 d = lambda: i[0](u, *args, **cmdoptions)
726 d = lambda: i[0](u, *args, **cmdoptions)
722
727
723 try:
728 try:
724 if options['profile']:
729 if options['profile']:
725 import hotshot, hotshot.stats
730 import hotshot, hotshot.stats
726 prof = hotshot.Profile("hg.prof")
731 prof = hotshot.Profile("hg.prof")
727 r = prof.runcall(d)
732 r = prof.runcall(d)
728 prof.close()
733 prof.close()
729 stats = hotshot.stats.load("hg.prof")
734 stats = hotshot.stats.load("hg.prof")
730 stats.strip_dirs()
735 stats.strip_dirs()
731 stats.sort_stats('time', 'calls')
736 stats.sort_stats('time', 'calls')
732 stats.print_stats(40)
737 stats.print_stats(40)
733 return r
738 return r
734 else:
739 else:
735 return d()
740 return d()
736 except SignalInterrupt:
741 except SignalInterrupt:
737 u.warn("killed!\n")
742 u.warn("killed!\n")
738 except KeyboardInterrupt:
743 except KeyboardInterrupt:
739 u.warn("interrupted!\n")
744 u.warn("interrupted!\n")
740 except IOError, inst:
745 except IOError, inst:
741 if inst.errno == 32:
746 if inst.errno == 32:
742 u.warn("broken pipe\n")
747 u.warn("broken pipe\n")
743 else:
748 else:
744 raise
749 raise
745 except TypeError, inst:
750 except TypeError, inst:
746 # was this an argument error?
751 # was this an argument error?
747 tb = traceback.extract_tb(sys.exc_info()[2])
752 tb = traceback.extract_tb(sys.exc_info()[2])
748 if len(tb) > 2: # no
753 if len(tb) > 2: # no
749 raise
754 raise
750 u.debug(inst, "\n")
755 u.debug(inst, "\n")
751 u.warn("%s: invalid arguments\n" % i[0].__name__)
756 u.warn("%s: invalid arguments\n" % i[0].__name__)
752 help(u, cmd)
757 help(u, cmd)
753 sys.exit(-1)
758 sys.exit(-1)
754
759
@@ -1,1402 +1,1433 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import sys, struct, os
8 import sys, struct, os
9 from revlog import *
9 from revlog import *
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 demandload(globals(), "tempfile byterange difflib")
12 demandload(globals(), "tempfile byterange difflib")
13
13
14 def is_exec(f):
14 def is_exec(f):
15 return (os.stat(f).st_mode & 0100 != 0)
15 return (os.stat(f).st_mode & 0100 != 0)
16
16
17 def set_exec(f, mode):
17 def set_exec(f, mode):
18 s = os.stat(f).st_mode
18 s = os.stat(f).st_mode
19 if (s & 0100 != 0) == mode:
19 if (s & 0100 != 0) == mode:
20 return
20 return
21 if mode:
21 if mode:
22 # Turn on +x for every +r bit when making a file executable
22 # Turn on +x for every +r bit when making a file executable
23 # and obey umask.
23 # and obey umask.
24 umask = os.umask(0)
24 umask = os.umask(0)
25 os.umask(umask)
25 os.umask(umask)
26 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
26 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
27 else:
27 else:
28 os.chmod(f, s & 0666)
28 os.chmod(f, s & 0666)
29
29
30 class filelog(revlog):
30 class filelog(revlog):
31 def __init__(self, opener, path):
31 def __init__(self, opener, path):
32 revlog.__init__(self, opener,
32 revlog.__init__(self, opener,
33 os.path.join("data", path + ".i"),
33 os.path.join("data", path + ".i"),
34 os.path.join("data", path + ".d"))
34 os.path.join("data", path + ".d"))
35
35
36 def read(self, node):
36 def read(self, node):
37 t = self.revision(node)
37 t = self.revision(node)
38 if t[:2] != '\1\n':
38 if t[:2] != '\1\n':
39 return t
39 return t
40 s = t.find('\1\n', 2)
40 s = t.find('\1\n', 2)
41 return t[s+2:]
41 return t[s+2:]
42
42
43 def readmeta(self, node):
43 def readmeta(self, node):
44 t = self.revision(node)
44 t = self.revision(node)
45 if t[:2] != '\1\n':
45 if t[:2] != '\1\n':
46 return t
46 return t
47 s = t.find('\1\n', 2)
47 s = t.find('\1\n', 2)
48 mt = t[2:s]
48 mt = t[2:s]
49 for l in mt.splitlines():
49 for l in mt.splitlines():
50 k, v = l.split(": ", 1)
50 k, v = l.split(": ", 1)
51 m[k] = v
51 m[k] = v
52 return m
52 return m
53
53
54 def add(self, text, meta, transaction, link, p1=None, p2=None):
54 def add(self, text, meta, transaction, link, p1=None, p2=None):
55 if meta or text[:2] == '\1\n':
55 if meta or text[:2] == '\1\n':
56 mt = ""
56 mt = ""
57 if meta:
57 if meta:
58 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
58 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
59 text = "\1\n" + "".join(mt) + "\1\n" + text
59 text = "\1\n" + "".join(mt) + "\1\n" + text
60 return self.addrevision(text, transaction, link, p1, p2)
60 return self.addrevision(text, transaction, link, p1, p2)
61
61
62 def annotate(self, node):
62 def annotate(self, node):
63
63
64 def decorate(text, rev):
64 def decorate(text, rev):
65 return [(rev, l) for l in text.splitlines(1)]
65 return [(rev, l) for l in text.splitlines(1)]
66
66
67 def strip(annotation):
67 def strip(annotation):
68 return [e[1] for e in annotation]
68 return [e[1] for e in annotation]
69
69
70 def pair(parent, child):
70 def pair(parent, child):
71 new = []
71 new = []
72 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
72 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
73 for o, m, n, s, t in sm.get_opcodes():
73 for o, m, n, s, t in sm.get_opcodes():
74 if o == 'equal':
74 if o == 'equal':
75 new += parent[m:n]
75 new += parent[m:n]
76 else:
76 else:
77 new += child[s:t]
77 new += child[s:t]
78 return new
78 return new
79
79
80 # find all ancestors
80 # find all ancestors
81 needed = {node:1}
81 needed = {node:1}
82 visit = [node]
82 visit = [node]
83 while visit:
83 while visit:
84 n = visit.pop(0)
84 n = visit.pop(0)
85 for p in self.parents(n):
85 for p in self.parents(n):
86 if p not in needed:
86 if p not in needed:
87 needed[p] = 1
87 needed[p] = 1
88 visit.append(p)
88 visit.append(p)
89 else:
89 else:
90 # count how many times we'll use this
90 # count how many times we'll use this
91 needed[p] += 1
91 needed[p] += 1
92
92
93 # sort by revision which is a topological order
93 # sort by revision which is a topological order
94 visit = needed.keys()
94 visit = needed.keys()
95 visit = [ (self.rev(n), n) for n in visit ]
95 visit = [ (self.rev(n), n) for n in visit ]
96 visit.sort()
96 visit.sort()
97 visit = [ p[1] for p in visit ]
97 visit = [ p[1] for p in visit ]
98 hist = {}
98 hist = {}
99
99
100 for n in visit:
100 for n in visit:
101 curr = decorate(self.read(n), self.linkrev(n))
101 curr = decorate(self.read(n), self.linkrev(n))
102 for p in self.parents(n):
102 for p in self.parents(n):
103 if p != nullid:
103 if p != nullid:
104 curr = pair(hist[p], curr)
104 curr = pair(hist[p], curr)
105 # trim the history of unneeded revs
105 # trim the history of unneeded revs
106 needed[p] -= 1
106 needed[p] -= 1
107 if not needed[p]:
107 if not needed[p]:
108 del hist[p]
108 del hist[p]
109 hist[n] = curr
109 hist[n] = curr
110
110
111 return hist[n]
111 return hist[n]
112
112
113 class manifest(revlog):
113 class manifest(revlog):
114 def __init__(self, opener):
114 def __init__(self, opener):
115 self.mapcache = None
115 self.mapcache = None
116 self.listcache = None
116 self.listcache = None
117 self.addlist = None
117 self.addlist = None
118 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
118 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
119
119
120 def read(self, node):
120 def read(self, node):
121 if node == nullid: return {} # don't upset local cache
121 if node == nullid: return {} # don't upset local cache
122 if self.mapcache and self.mapcache[0] == node:
122 if self.mapcache and self.mapcache[0] == node:
123 return self.mapcache[1].copy()
123 return self.mapcache[1].copy()
124 text = self.revision(node)
124 text = self.revision(node)
125 map = {}
125 map = {}
126 flag = {}
126 flag = {}
127 self.listcache = (text, text.splitlines(1))
127 self.listcache = (text, text.splitlines(1))
128 for l in self.listcache[1]:
128 for l in self.listcache[1]:
129 (f, n) = l.split('\0')
129 (f, n) = l.split('\0')
130 map[f] = bin(n[:40])
130 map[f] = bin(n[:40])
131 flag[f] = (n[40:-1] == "x")
131 flag[f] = (n[40:-1] == "x")
132 self.mapcache = (node, map, flag)
132 self.mapcache = (node, map, flag)
133 return map
133 return map
134
134
135 def readflags(self, node):
135 def readflags(self, node):
136 if node == nullid: return {} # don't upset local cache
136 if node == nullid: return {} # don't upset local cache
137 if not self.mapcache or self.mapcache[0] != node:
137 if not self.mapcache or self.mapcache[0] != node:
138 self.read(node)
138 self.read(node)
139 return self.mapcache[2]
139 return self.mapcache[2]
140
140
141 def diff(self, a, b):
141 def diff(self, a, b):
142 # this is sneaky, as we're not actually using a and b
142 # this is sneaky, as we're not actually using a and b
143 if self.listcache and self.addlist and self.listcache[0] == a:
143 if self.listcache and self.addlist and self.listcache[0] == a:
144 d = mdiff.diff(self.listcache[1], self.addlist, 1)
144 d = mdiff.diff(self.listcache[1], self.addlist, 1)
145 if mdiff.patch(a, d) != b:
145 if mdiff.patch(a, d) != b:
146 sys.stderr.write("*** sortdiff failed, falling back ***\n")
146 sys.stderr.write("*** sortdiff failed, falling back ***\n")
147 return mdiff.textdiff(a, b)
147 return mdiff.textdiff(a, b)
148 return d
148 return d
149 else:
149 else:
150 return mdiff.textdiff(a, b)
150 return mdiff.textdiff(a, b)
151
151
152 def add(self, map, flags, transaction, link, p1=None, p2=None):
152 def add(self, map, flags, transaction, link, p1=None, p2=None):
153 files = map.keys()
153 files = map.keys()
154 files.sort()
154 files.sort()
155
155
156 self.addlist = ["%s\000%s%s\n" %
156 self.addlist = ["%s\000%s%s\n" %
157 (f, hex(map[f]), flags[f] and "x" or '')
157 (f, hex(map[f]), flags[f] and "x" or '')
158 for f in files]
158 for f in files]
159 text = "".join(self.addlist)
159 text = "".join(self.addlist)
160
160
161 n = self.addrevision(text, transaction, link, p1, p2)
161 n = self.addrevision(text, transaction, link, p1, p2)
162 self.mapcache = (n, map, flags)
162 self.mapcache = (n, map, flags)
163 self.listcache = (text, self.addlist)
163 self.listcache = (text, self.addlist)
164 self.addlist = None
164 self.addlist = None
165
165
166 return n
166 return n
167
167
168 class changelog(revlog):
168 class changelog(revlog):
169 def __init__(self, opener):
169 def __init__(self, opener):
170 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
170 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
171
171
172 def extract(self, text):
172 def extract(self, text):
173 if not text:
173 if not text:
174 return (nullid, "", "0", [], "")
174 return (nullid, "", "0", [], "")
175 last = text.index("\n\n")
175 last = text.index("\n\n")
176 desc = text[last + 2:]
176 desc = text[last + 2:]
177 l = text[:last].splitlines()
177 l = text[:last].splitlines()
178 manifest = bin(l[0])
178 manifest = bin(l[0])
179 user = l[1]
179 user = l[1]
180 date = l[2]
180 date = l[2]
181 files = l[3:]
181 files = l[3:]
182 return (manifest, user, date, files, desc)
182 return (manifest, user, date, files, desc)
183
183
184 def read(self, node):
184 def read(self, node):
185 return self.extract(self.revision(node))
185 return self.extract(self.revision(node))
186
186
187 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
187 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
188 user=None, date=None):
188 user=None, date=None):
189 user = (user or
189 user = (user or
190 os.environ.get("HGUSER") or
190 os.environ.get("HGUSER") or
191 os.environ.get("EMAIL") or
191 os.environ.get("EMAIL") or
192 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
192 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
193 date = date or "%d %d" % (time.time(), time.timezone)
193 date = date or "%d %d" % (time.time(), time.timezone)
194 list.sort()
194 list.sort()
195 l = [hex(manifest), user, date] + list + ["", desc]
195 l = [hex(manifest), user, date] + list + ["", desc]
196 text = "\n".join(l)
196 text = "\n".join(l)
197 return self.addrevision(text, transaction, self.count(), p1, p2)
197 return self.addrevision(text, transaction, self.count(), p1, p2)
198
198
199 class dirstate:
199 class dirstate:
200 def __init__(self, opener, ui, root):
200 def __init__(self, opener, ui, root):
201 self.opener = opener
201 self.opener = opener
202 self.root = root
202 self.root = root
203 self.dirty = 0
203 self.dirty = 0
204 self.ui = ui
204 self.ui = ui
205 self.map = None
205 self.map = None
206 self.pl = None
206 self.pl = None
207 self.copies = {}
207
208
208 def __del__(self):
209 def __del__(self):
209 if self.dirty:
210 if self.dirty:
210 self.write()
211 self.write()
211
212
212 def __getitem__(self, key):
213 def __getitem__(self, key):
213 try:
214 try:
214 return self.map[key]
215 return self.map[key]
215 except TypeError:
216 except TypeError:
216 self.read()
217 self.read()
217 return self[key]
218 return self[key]
218
219
219 def __contains__(self, key):
220 def __contains__(self, key):
220 if not self.map: self.read()
221 if not self.map: self.read()
221 return key in self.map
222 return key in self.map
222
223
223 def parents(self):
224 def parents(self):
224 if not self.pl:
225 if not self.pl:
225 self.read()
226 self.read()
226 return self.pl
227 return self.pl
227
228
228 def setparents(self, p1, p2 = nullid):
229 def setparents(self, p1, p2 = nullid):
229 self.dirty = 1
230 self.dirty = 1
230 self.pl = p1, p2
231 self.pl = p1, p2
231
232
232 def state(self, key):
233 def state(self, key):
233 try:
234 try:
234 return self[key][0]
235 return self[key][0]
235 except KeyError:
236 except KeyError:
236 return "?"
237 return "?"
237
238
238 def read(self):
239 def read(self):
239 if self.map is not None: return self.map
240 if self.map is not None: return self.map
240
241
241 self.map = {}
242 self.map = {}
242 self.pl = [nullid, nullid]
243 self.pl = [nullid, nullid]
243 try:
244 try:
244 st = self.opener("dirstate").read()
245 st = self.opener("dirstate").read()
245 if not st: return
246 if not st: return
246 except: return
247 except: return
247
248
248 self.pl = [st[:20], st[20: 40]]
249 self.pl = [st[:20], st[20: 40]]
249
250
250 pos = 40
251 pos = 40
251 while pos < len(st):
252 while pos < len(st):
252 e = struct.unpack(">cllll", st[pos:pos+17])
253 e = struct.unpack(">cllll", st[pos:pos+17])
253 l = e[4]
254 l = e[4]
254 pos += 17
255 pos += 17
255 f = st[pos:pos + l]
256 f = st[pos:pos + l]
257 if '\0' in f:
258 f, c = f.split('\0')
259 self.copies[f] = c
256 self.map[f] = e[:4]
260 self.map[f] = e[:4]
257 pos += l
261 pos += l
258
262
263 def copy(self, source, dest):
264 self.read()
265 self.dirty = 1
266 self.copies[dest] = source
267
268 def copied(self, file):
269 return self.copies.get(file, None)
270
259 def update(self, files, state):
271 def update(self, files, state):
260 ''' current states:
272 ''' current states:
261 n normal
273 n normal
262 m needs merging
274 m needs merging
263 r marked for removal
275 r marked for removal
264 a marked for addition'''
276 a marked for addition'''
265
277
266 if not files: return
278 if not files: return
267 self.read()
279 self.read()
268 self.dirty = 1
280 self.dirty = 1
269 for f in files:
281 for f in files:
270 if state == "r":
282 if state == "r":
271 self.map[f] = ('r', 0, 0, 0)
283 self.map[f] = ('r', 0, 0, 0)
272 else:
284 else:
273 s = os.stat(os.path.join(self.root, f))
285 s = os.stat(os.path.join(self.root, f))
274 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
286 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
275
287
276 def forget(self, files):
288 def forget(self, files):
277 if not files: return
289 if not files: return
278 self.read()
290 self.read()
279 self.dirty = 1
291 self.dirty = 1
280 for f in files:
292 for f in files:
281 try:
293 try:
282 del self.map[f]
294 del self.map[f]
283 except KeyError:
295 except KeyError:
284 self.ui.warn("not in dirstate: %s!\n" % f)
296 self.ui.warn("not in dirstate: %s!\n" % f)
285 pass
297 pass
286
298
287 def clear(self):
299 def clear(self):
288 self.map = {}
300 self.map = {}
289 self.dirty = 1
301 self.dirty = 1
290
302
291 def write(self):
303 def write(self):
292 st = self.opener("dirstate", "w")
304 st = self.opener("dirstate", "w")
293 st.write("".join(self.pl))
305 st.write("".join(self.pl))
294 for f, e in self.map.items():
306 for f, e in self.map.items():
307 c = self.copied(f)
308 if c:
309 f = f + "\0" + c
295 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
310 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
296 st.write(e + f)
311 st.write(e + f)
297 self.dirty = 0
312 self.dirty = 0
298
313
299 def copy(self):
314 def dup(self):
300 self.read()
315 self.read()
301 return self.map.copy()
316 return self.map.copy()
302
317
303 # used to avoid circular references so destructors work
318 # used to avoid circular references so destructors work
304 def opener(base):
319 def opener(base):
305 p = base
320 p = base
306 def o(path, mode="r"):
321 def o(path, mode="r"):
307 if p[:7] == "http://":
322 if p[:7] == "http://":
308 f = os.path.join(p, urllib.quote(path))
323 f = os.path.join(p, urllib.quote(path))
309 return httprangereader(f)
324 return httprangereader(f)
310
325
311 f = os.path.join(p, path)
326 f = os.path.join(p, path)
312
327
313 mode += "b" # for that other OS
328 mode += "b" # for that other OS
314
329
315 if mode[0] != "r":
330 if mode[0] != "r":
316 try:
331 try:
317 s = os.stat(f)
332 s = os.stat(f)
318 except OSError:
333 except OSError:
319 d = os.path.dirname(f)
334 d = os.path.dirname(f)
320 if not os.path.isdir(d):
335 if not os.path.isdir(d):
321 os.makedirs(d)
336 os.makedirs(d)
322 else:
337 else:
323 if s.st_nlink > 1:
338 if s.st_nlink > 1:
324 file(f + ".tmp", "w").write(file(f).read())
339 file(f + ".tmp", "w").write(file(f).read())
325 os.rename(f+".tmp", f)
340 os.rename(f+".tmp", f)
326
341
327 return file(f, mode)
342 return file(f, mode)
328
343
329 return o
344 return o
330
345
331 class localrepository:
346 class localrepository:
332 def __init__(self, ui, path=None, create=0):
347 def __init__(self, ui, path=None, create=0):
333 self.remote = 0
348 self.remote = 0
334 if path and path[:7] == "http://":
349 if path and path[:7] == "http://":
335 self.remote = 1
350 self.remote = 1
336 self.path = path
351 self.path = path
337 else:
352 else:
338 if not path:
353 if not path:
339 p = os.getcwd()
354 p = os.getcwd()
340 while not os.path.isdir(os.path.join(p, ".hg")):
355 while not os.path.isdir(os.path.join(p, ".hg")):
341 p = os.path.dirname(p)
356 p = os.path.dirname(p)
342 if p == "/": raise "No repo found"
357 if p == "/": raise "No repo found"
343 path = p
358 path = p
344 self.path = os.path.join(path, ".hg")
359 self.path = os.path.join(path, ".hg")
345
360
346 self.root = path
361 self.root = path
347 self.ui = ui
362 self.ui = ui
348
363
349 if create:
364 if create:
350 os.mkdir(self.path)
365 os.mkdir(self.path)
351 os.mkdir(self.join("data"))
366 os.mkdir(self.join("data"))
352
367
353 self.opener = opener(self.path)
368 self.opener = opener(self.path)
354 self.wopener = opener(self.root)
369 self.wopener = opener(self.root)
355 self.manifest = manifest(self.opener)
370 self.manifest = manifest(self.opener)
356 self.changelog = changelog(self.opener)
371 self.changelog = changelog(self.opener)
357 self.ignorelist = None
372 self.ignorelist = None
358 self.tagscache = None
373 self.tagscache = None
359 self.nodetagscache = None
374 self.nodetagscache = None
360
375
361 if not self.remote:
376 if not self.remote:
362 self.dirstate = dirstate(self.opener, ui, self.root)
377 self.dirstate = dirstate(self.opener, ui, self.root)
363 try:
378 try:
364 self.ui.readconfig(self.opener("hgrc"))
379 self.ui.readconfig(self.opener("hgrc"))
365 except IOError: pass
380 except IOError: pass
366
381
367 def ignore(self, f):
382 def ignore(self, f):
368 if self.ignorelist is None:
383 if self.ignorelist is None:
369 self.ignorelist = []
384 self.ignorelist = []
370 try:
385 try:
371 l = self.wfile(".hgignore")
386 l = self.wfile(".hgignore")
372 for pat in l:
387 for pat in l:
373 if pat != "\n":
388 if pat != "\n":
374 self.ignorelist.append(re.compile(pat[:-1]))
389 self.ignorelist.append(re.compile(pat[:-1]))
375 except IOError: pass
390 except IOError: pass
376 for pat in self.ignorelist:
391 for pat in self.ignorelist:
377 if pat.search(f): return True
392 if pat.search(f): return True
378 return False
393 return False
379
394
380 def tags(self):
395 def tags(self):
381 '''return a mapping of tag to node'''
396 '''return a mapping of tag to node'''
382 if not self.tagscache:
397 if not self.tagscache:
383 self.tagscache = {}
398 self.tagscache = {}
384 try:
399 try:
385 # read each head of the tags file, ending with the tip
400 # read each head of the tags file, ending with the tip
386 # and add each tag found to the map, with "newer" ones
401 # and add each tag found to the map, with "newer" ones
387 # taking precedence
402 # taking precedence
388 fl = self.file(".hgtags")
403 fl = self.file(".hgtags")
389 h = fl.heads()
404 h = fl.heads()
390 h.reverse()
405 h.reverse()
391 for r in h:
406 for r in h:
392 for l in fl.revision(r).splitlines():
407 for l in fl.revision(r).splitlines():
393 if l:
408 if l:
394 n, k = l.split(" ")
409 n, k = l.split(" ")
395 self.tagscache[k] = bin(n)
410 self.tagscache[k] = bin(n)
396 except KeyError: pass
411 except KeyError: pass
397 self.tagscache['tip'] = self.changelog.tip()
412 self.tagscache['tip'] = self.changelog.tip()
398
413
399 return self.tagscache
414 return self.tagscache
400
415
401 def tagslist(self):
416 def tagslist(self):
402 '''return a list of tags ordered by revision'''
417 '''return a list of tags ordered by revision'''
403 l = []
418 l = []
404 for t,n in self.tags().items():
419 for t,n in self.tags().items():
405 try:
420 try:
406 r = self.changelog.rev(n)
421 r = self.changelog.rev(n)
407 except:
422 except:
408 r = -2 # sort to the beginning of the list if unknown
423 r = -2 # sort to the beginning of the list if unknown
409 l.append((r,t,n))
424 l.append((r,t,n))
410 l.sort()
425 l.sort()
411 return [(t,n) for r,t,n in l]
426 return [(t,n) for r,t,n in l]
412
427
413 def nodetags(self, node):
428 def nodetags(self, node):
414 '''return the tags associated with a node'''
429 '''return the tags associated with a node'''
415 if not self.nodetagscache:
430 if not self.nodetagscache:
416 self.nodetagscache = {}
431 self.nodetagscache = {}
417 for t,n in self.tags().items():
432 for t,n in self.tags().items():
418 self.nodetagscache.setdefault(n,[]).append(t)
433 self.nodetagscache.setdefault(n,[]).append(t)
419 return self.nodetagscache.get(node, [])
434 return self.nodetagscache.get(node, [])
420
435
421 def lookup(self, key):
436 def lookup(self, key):
422 try:
437 try:
423 return self.tags()[key]
438 return self.tags()[key]
424 except KeyError:
439 except KeyError:
425 return self.changelog.lookup(key)
440 return self.changelog.lookup(key)
426
441
427 def join(self, f):
442 def join(self, f):
428 return os.path.join(self.path, f)
443 return os.path.join(self.path, f)
429
444
430 def wjoin(self, f):
445 def wjoin(self, f):
431 return os.path.join(self.root, f)
446 return os.path.join(self.root, f)
432
447
433 def file(self, f):
448 def file(self, f):
434 if f[0] == '/': f = f[1:]
449 if f[0] == '/': f = f[1:]
435 return filelog(self.opener, f)
450 return filelog(self.opener, f)
436
451
437 def wfile(self, f, mode='r'):
452 def wfile(self, f, mode='r'):
438 return self.wopener(f, mode)
453 return self.wopener(f, mode)
439
454
440 def transaction(self):
455 def transaction(self):
441 # save dirstate for undo
456 # save dirstate for undo
442 try:
457 try:
443 ds = self.opener("dirstate").read()
458 ds = self.opener("dirstate").read()
444 except IOError:
459 except IOError:
445 ds = ""
460 ds = ""
446 self.opener("undo.dirstate", "w").write(ds)
461 self.opener("undo.dirstate", "w").write(ds)
447
462
448 return transaction.transaction(self.opener, self.join("journal"),
463 return transaction.transaction(self.opener, self.join("journal"),
449 self.join("undo"))
464 self.join("undo"))
450
465
451 def recover(self):
466 def recover(self):
452 lock = self.lock()
467 lock = self.lock()
453 if os.path.exists(self.join("recover")):
468 if os.path.exists(self.join("recover")):
454 self.ui.status("attempting to rollback interrupted transaction\n")
469 self.ui.status("attempting to rollback interrupted transaction\n")
455 return transaction.rollback(self.opener, self.join("recover"))
470 return transaction.rollback(self.opener, self.join("recover"))
456 else:
471 else:
457 self.ui.warn("no interrupted transaction available\n")
472 self.ui.warn("no interrupted transaction available\n")
458
473
459 def undo(self):
474 def undo(self):
460 lock = self.lock()
475 lock = self.lock()
461 if os.path.exists(self.join("undo")):
476 if os.path.exists(self.join("undo")):
462 self.ui.status("attempting to rollback last transaction\n")
477 self.ui.status("attempting to rollback last transaction\n")
463 transaction.rollback(self.opener, self.join("undo"))
478 transaction.rollback(self.opener, self.join("undo"))
464 self.dirstate = None
479 self.dirstate = None
465 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
480 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
466 self.dirstate = dirstate(self.opener, self.ui, self.root)
481 self.dirstate = dirstate(self.opener, self.ui, self.root)
467 else:
482 else:
468 self.ui.warn("no undo information available\n")
483 self.ui.warn("no undo information available\n")
469
484
470 def lock(self, wait = 1):
485 def lock(self, wait = 1):
471 try:
486 try:
472 return lock.lock(self.join("lock"), 0)
487 return lock.lock(self.join("lock"), 0)
473 except lock.LockHeld, inst:
488 except lock.LockHeld, inst:
474 if wait:
489 if wait:
475 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
490 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
476 return lock.lock(self.join("lock"), wait)
491 return lock.lock(self.join("lock"), wait)
477 raise inst
492 raise inst
478
493
479 def rawcommit(self, files, text, user, date, p1=None, p2=None):
494 def rawcommit(self, files, text, user, date, p1=None, p2=None):
480 p1 = p1 or self.dirstate.parents()[0] or nullid
495 p1 = p1 or self.dirstate.parents()[0] or nullid
481 p2 = p2 or self.dirstate.parents()[1] or nullid
496 p2 = p2 or self.dirstate.parents()[1] or nullid
482 c1 = self.changelog.read(p1)
497 c1 = self.changelog.read(p1)
483 c2 = self.changelog.read(p2)
498 c2 = self.changelog.read(p2)
484 m1 = self.manifest.read(c1[0])
499 m1 = self.manifest.read(c1[0])
485 mf1 = self.manifest.readflags(c1[0])
500 mf1 = self.manifest.readflags(c1[0])
486 m2 = self.manifest.read(c2[0])
501 m2 = self.manifest.read(c2[0])
487
502
488 tr = self.transaction()
503 tr = self.transaction()
489 mm = m1.copy()
504 mm = m1.copy()
490 mfm = mf1.copy()
505 mfm = mf1.copy()
491 linkrev = self.changelog.count()
506 linkrev = self.changelog.count()
492 self.dirstate.setparents(p1, p2)
507 self.dirstate.setparents(p1, p2)
493 for f in files:
508 for f in files:
494 try:
509 try:
495 t = self.wfile(f).read()
510 t = self.wfile(f).read()
496 tm = is_exec(self.wjoin(f))
511 tm = is_exec(self.wjoin(f))
497 r = self.file(f)
512 r = self.file(f)
498 mfm[f] = tm
513 mfm[f] = tm
499 mm[f] = r.add(t, {}, tr, linkrev,
514 mm[f] = r.add(t, {}, tr, linkrev,
500 m1.get(f, nullid), m2.get(f, nullid))
515 m1.get(f, nullid), m2.get(f, nullid))
501 self.dirstate.update([f], "n")
516 self.dirstate.update([f], "n")
502 except IOError:
517 except IOError:
503 try:
518 try:
504 del mm[f]
519 del mm[f]
505 del mfm[f]
520 del mfm[f]
506 self.dirstate.forget([f])
521 self.dirstate.forget([f])
507 except:
522 except:
508 # deleted from p2?
523 # deleted from p2?
509 pass
524 pass
510
525
511 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
526 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
512 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
527 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
513 tr.close()
528 tr.close()
514
529
515 def commit(self, files = None, text = "", user = None, date = None):
530 def commit(self, files = None, text = "", user = None, date = None):
516 commit = []
531 commit = []
517 remove = []
532 remove = []
518 if files:
533 if files:
519 for f in files:
534 for f in files:
520 s = self.dirstate.state(f)
535 s = self.dirstate.state(f)
521 if s in 'nmai':
536 if s in 'nmai':
522 commit.append(f)
537 commit.append(f)
523 elif s == 'r':
538 elif s == 'r':
524 remove.append(f)
539 remove.append(f)
525 else:
540 else:
526 self.ui.warn("%s not tracked!\n" % f)
541 self.ui.warn("%s not tracked!\n" % f)
527 else:
542 else:
528 (c, a, d, u) = self.diffdir(self.root)
543 (c, a, d, u) = self.diffdir(self.root)
529 commit = c + a
544 commit = c + a
530 remove = d
545 remove = d
531
546
532 if not commit and not remove:
547 if not commit and not remove:
533 self.ui.status("nothing changed\n")
548 self.ui.status("nothing changed\n")
534 return
549 return
535
550
536 p1, p2 = self.dirstate.parents()
551 p1, p2 = self.dirstate.parents()
537 c1 = self.changelog.read(p1)
552 c1 = self.changelog.read(p1)
538 c2 = self.changelog.read(p2)
553 c2 = self.changelog.read(p2)
539 m1 = self.manifest.read(c1[0])
554 m1 = self.manifest.read(c1[0])
540 mf1 = self.manifest.readflags(c1[0])
555 mf1 = self.manifest.readflags(c1[0])
541 m2 = self.manifest.read(c2[0])
556 m2 = self.manifest.read(c2[0])
542 lock = self.lock()
557 lock = self.lock()
543 tr = self.transaction()
558 tr = self.transaction()
544
559
545 # check in files
560 # check in files
546 new = {}
561 new = {}
547 linkrev = self.changelog.count()
562 linkrev = self.changelog.count()
548 commit.sort()
563 commit.sort()
549 for f in commit:
564 for f in commit:
550 self.ui.note(f + "\n")
565 self.ui.note(f + "\n")
551 try:
566 try:
552 fp = self.wjoin(f)
567 fp = self.wjoin(f)
553 mf1[f] = is_exec(fp)
568 mf1[f] = is_exec(fp)
554 t = file(fp).read()
569 t = file(fp).read()
555 except IOError:
570 except IOError:
556 self.warn("trouble committing %s!\n" % f)
571 self.warn("trouble committing %s!\n" % f)
557 raise
572 raise
558
573
574 meta = {}
575 cp = self.dirstate.copied(f)
576 if cp:
577 meta["copy"] = cp
578 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
579 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
580
559 r = self.file(f)
581 r = self.file(f)
560 fp1 = m1.get(f, nullid)
582 fp1 = m1.get(f, nullid)
561 fp2 = m2.get(f, nullid)
583 fp2 = m2.get(f, nullid)
562 new[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
584 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
563
585
564 # update manifest
586 # update manifest
565 m1.update(new)
587 m1.update(new)
566 for f in remove: del m1[f]
588 for f in remove: del m1[f]
567 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
589 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
568
590
569 # add changeset
591 # add changeset
570 new = new.keys()
592 new = new.keys()
571 new.sort()
593 new.sort()
572
594
573 if not text:
595 if not text:
574 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
596 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
575 edittext += "".join(["HG: changed %s\n" % f for f in new])
597 edittext += "".join(["HG: changed %s\n" % f for f in new])
576 edittext += "".join(["HG: removed %s\n" % f for f in remove])
598 edittext += "".join(["HG: removed %s\n" % f for f in remove])
577 edittext = self.ui.edit(edittext)
599 edittext = self.ui.edit(edittext)
578 if not edittext.rstrip():
600 if not edittext.rstrip():
579 return 1
601 return 1
580 text = edittext
602 text = edittext
581
603
582 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
604 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
583 tr.close()
605 tr.close()
584
606
585 self.dirstate.setparents(n)
607 self.dirstate.setparents(n)
586 self.dirstate.update(new, "n")
608 self.dirstate.update(new, "n")
587 self.dirstate.forget(remove)
609 self.dirstate.forget(remove)
588
610
589 def diffdir(self, path, changeset = None):
611 def diffdir(self, path, changeset = None):
590 changed = []
612 changed = []
591 added = []
613 added = []
592 unknown = []
614 unknown = []
593 mf = {}
615 mf = {}
594
616
595 if changeset:
617 if changeset:
596 change = self.changelog.read(changeset)
618 change = self.changelog.read(changeset)
597 mf = self.manifest.read(change[0])
619 mf = self.manifest.read(change[0])
598 dc = dict.fromkeys(mf)
620 dc = dict.fromkeys(mf)
599 else:
621 else:
600 changeset = self.dirstate.parents()[0]
622 changeset = self.dirstate.parents()[0]
601 change = self.changelog.read(changeset)
623 change = self.changelog.read(changeset)
602 mf = self.manifest.read(change[0])
624 mf = self.manifest.read(change[0])
603 dc = self.dirstate.copy()
625 dc = self.dirstate.dup()
604
626
605 def fcmp(fn):
627 def fcmp(fn):
606 t1 = self.wfile(fn).read()
628 t1 = self.wfile(fn).read()
607 t2 = self.file(fn).revision(mf[fn])
629 t2 = self.file(fn).revision(mf[fn])
608 return cmp(t1, t2)
630 return cmp(t1, t2)
609
631
610 for dir, subdirs, files in os.walk(path):
632 for dir, subdirs, files in os.walk(path):
611 d = dir[len(self.root)+1:]
633 d = dir[len(self.root)+1:]
612 if ".hg" in subdirs: subdirs.remove(".hg")
634 if ".hg" in subdirs: subdirs.remove(".hg")
613
635
614 for f in files:
636 for f in files:
615 fn = os.path.join(d, f)
637 fn = os.path.join(d, f)
616 try: s = os.stat(os.path.join(self.root, fn))
638 try: s = os.stat(os.path.join(self.root, fn))
617 except: continue
639 except: continue
618 if fn in dc:
640 if fn in dc:
619 c = dc[fn]
641 c = dc[fn]
620 del dc[fn]
642 del dc[fn]
621 if not c:
643 if not c:
622 if fcmp(fn):
644 if fcmp(fn):
623 changed.append(fn)
645 changed.append(fn)
624 elif c[0] == 'm':
646 elif c[0] == 'm':
625 changed.append(fn)
647 changed.append(fn)
626 elif c[0] == 'a':
648 elif c[0] == 'a':
627 added.append(fn)
649 added.append(fn)
628 elif c[0] == 'r':
650 elif c[0] == 'r':
629 unknown.append(fn)
651 unknown.append(fn)
630 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
652 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
631 changed.append(fn)
653 changed.append(fn)
632 elif c[1] != s.st_mode or c[3] != s.st_mtime:
654 elif c[1] != s.st_mode or c[3] != s.st_mtime:
633 if fcmp(fn):
655 if fcmp(fn):
634 changed.append(fn)
656 changed.append(fn)
635 else:
657 else:
636 if self.ignore(fn): continue
658 if self.ignore(fn): continue
637 unknown.append(fn)
659 unknown.append(fn)
638
660
639 deleted = dc.keys()
661 deleted = dc.keys()
640 deleted.sort()
662 deleted.sort()
641
663
642 return (changed, added, deleted, unknown)
664 return (changed, added, deleted, unknown)
643
665
644 def diffrevs(self, node1, node2):
666 def diffrevs(self, node1, node2):
645 changed, added = [], []
667 changed, added = [], []
646
668
647 change = self.changelog.read(node1)
669 change = self.changelog.read(node1)
648 mf1 = self.manifest.read(change[0])
670 mf1 = self.manifest.read(change[0])
649 change = self.changelog.read(node2)
671 change = self.changelog.read(node2)
650 mf2 = self.manifest.read(change[0])
672 mf2 = self.manifest.read(change[0])
651
673
652 for fn in mf2:
674 for fn in mf2:
653 if mf1.has_key(fn):
675 if mf1.has_key(fn):
654 if mf1[fn] != mf2[fn]:
676 if mf1[fn] != mf2[fn]:
655 changed.append(fn)
677 changed.append(fn)
656 del mf1[fn]
678 del mf1[fn]
657 else:
679 else:
658 added.append(fn)
680 added.append(fn)
659
681
660 deleted = mf1.keys()
682 deleted = mf1.keys()
661 deleted.sort()
683 deleted.sort()
662
684
663 return (changed, added, deleted)
685 return (changed, added, deleted)
664
686
665 def add(self, list):
687 def add(self, list):
666 for f in list:
688 for f in list:
667 p = self.wjoin(f)
689 p = self.wjoin(f)
668 if not os.path.isfile(p):
690 if not os.path.isfile(p):
669 self.ui.warn("%s does not exist!\n" % f)
691 self.ui.warn("%s does not exist!\n" % f)
670 elif self.dirstate.state(f) == 'n':
692 elif self.dirstate.state(f) == 'n':
671 self.ui.warn("%s already tracked!\n" % f)
693 self.ui.warn("%s already tracked!\n" % f)
672 else:
694 else:
673 self.dirstate.update([f], "a")
695 self.dirstate.update([f], "a")
674
696
675 def forget(self, list):
697 def forget(self, list):
676 for f in list:
698 for f in list:
677 if self.dirstate.state(f) not in 'ai':
699 if self.dirstate.state(f) not in 'ai':
678 self.ui.warn("%s not added!\n" % f)
700 self.ui.warn("%s not added!\n" % f)
679 else:
701 else:
680 self.dirstate.forget([f])
702 self.dirstate.forget([f])
681
703
682 def remove(self, list):
704 def remove(self, list):
683 for f in list:
705 for f in list:
684 p = self.wjoin(f)
706 p = self.wjoin(f)
685 if os.path.isfile(p):
707 if os.path.isfile(p):
686 self.ui.warn("%s still exists!\n" % f)
708 self.ui.warn("%s still exists!\n" % f)
687 elif f not in self.dirstate:
709 elif f not in self.dirstate:
688 self.ui.warn("%s not tracked!\n" % f)
710 self.ui.warn("%s not tracked!\n" % f)
689 else:
711 else:
690 self.dirstate.update([f], "r")
712 self.dirstate.update([f], "r")
691
713
714 def copy(self, source, dest):
715 p = self.wjoin(dest)
716 if not os.path.isfile(dest):
717 self.ui.warn("%s does not exist!\n" % dest)
718 else:
719 if self.dirstate.state(dest) == '?':
720 self.dirstate.update([dest], "a")
721 self.dirstate.copy(source, dest)
722
692 def heads(self):
723 def heads(self):
693 return self.changelog.heads()
724 return self.changelog.heads()
694
725
695 def branches(self, nodes):
726 def branches(self, nodes):
696 if not nodes: nodes = [self.changelog.tip()]
727 if not nodes: nodes = [self.changelog.tip()]
697 b = []
728 b = []
698 for n in nodes:
729 for n in nodes:
699 t = n
730 t = n
700 while n:
731 while n:
701 p = self.changelog.parents(n)
732 p = self.changelog.parents(n)
702 if p[1] != nullid or p[0] == nullid:
733 if p[1] != nullid or p[0] == nullid:
703 b.append((t, n, p[0], p[1]))
734 b.append((t, n, p[0], p[1]))
704 break
735 break
705 n = p[0]
736 n = p[0]
706 return b
737 return b
707
738
708 def between(self, pairs):
739 def between(self, pairs):
709 r = []
740 r = []
710
741
711 for top, bottom in pairs:
742 for top, bottom in pairs:
712 n, l, i = top, [], 0
743 n, l, i = top, [], 0
713 f = 1
744 f = 1
714
745
715 while n != bottom:
746 while n != bottom:
716 p = self.changelog.parents(n)[0]
747 p = self.changelog.parents(n)[0]
717 if i == f:
748 if i == f:
718 l.append(n)
749 l.append(n)
719 f = f * 2
750 f = f * 2
720 n = p
751 n = p
721 i += 1
752 i += 1
722
753
723 r.append(l)
754 r.append(l)
724
755
725 return r
756 return r
726
757
727 def newer(self, nodes):
758 def newer(self, nodes):
728 m = {}
759 m = {}
729 nl = []
760 nl = []
730 pm = {}
761 pm = {}
731 cl = self.changelog
762 cl = self.changelog
732 t = l = cl.count()
763 t = l = cl.count()
733
764
734 # find the lowest numbered node
765 # find the lowest numbered node
735 for n in nodes:
766 for n in nodes:
736 l = min(l, cl.rev(n))
767 l = min(l, cl.rev(n))
737 m[n] = 1
768 m[n] = 1
738
769
739 for i in xrange(l, t):
770 for i in xrange(l, t):
740 n = cl.node(i)
771 n = cl.node(i)
741 if n in m: # explicitly listed
772 if n in m: # explicitly listed
742 pm[n] = 1
773 pm[n] = 1
743 nl.append(n)
774 nl.append(n)
744 continue
775 continue
745 for p in cl.parents(n):
776 for p in cl.parents(n):
746 if p in pm: # parent listed
777 if p in pm: # parent listed
747 pm[n] = 1
778 pm[n] = 1
748 nl.append(n)
779 nl.append(n)
749 break
780 break
750
781
751 return nl
782 return nl
752
783
753 def getchangegroup(self, remote):
784 def getchangegroup(self, remote):
754 m = self.changelog.nodemap
785 m = self.changelog.nodemap
755 search = []
786 search = []
756 fetch = []
787 fetch = []
757 seen = {}
788 seen = {}
758 seenbranch = {}
789 seenbranch = {}
759
790
760 # if we have an empty repo, fetch everything
791 # if we have an empty repo, fetch everything
761 if self.changelog.tip() == nullid:
792 if self.changelog.tip() == nullid:
762 self.ui.status("requesting all changes\n")
793 self.ui.status("requesting all changes\n")
763 return remote.changegroup([nullid])
794 return remote.changegroup([nullid])
764
795
765 # otherwise, assume we're closer to the tip than the root
796 # otherwise, assume we're closer to the tip than the root
766 self.ui.status("searching for changes\n")
797 self.ui.status("searching for changes\n")
767 heads = remote.heads()
798 heads = remote.heads()
768 unknown = []
799 unknown = []
769 for h in heads:
800 for h in heads:
770 if h not in m:
801 if h not in m:
771 unknown.append(h)
802 unknown.append(h)
772
803
773 if not unknown:
804 if not unknown:
774 self.ui.status("nothing to do!\n")
805 self.ui.status("nothing to do!\n")
775 return None
806 return None
776
807
777 rep = {}
808 rep = {}
778 reqcnt = 0
809 reqcnt = 0
779
810
780 unknown = remote.branches(unknown)
811 unknown = remote.branches(unknown)
781 while unknown:
812 while unknown:
782 r = []
813 r = []
783 while unknown:
814 while unknown:
784 n = unknown.pop(0)
815 n = unknown.pop(0)
785 if n[0] in seen:
816 if n[0] in seen:
786 continue
817 continue
787
818
788 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
819 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
789 if n[0] == nullid:
820 if n[0] == nullid:
790 break
821 break
791 if n in seenbranch:
822 if n in seenbranch:
792 self.ui.debug("branch already found\n")
823 self.ui.debug("branch already found\n")
793 continue
824 continue
794 if n[1] and n[1] in m: # do we know the base?
825 if n[1] and n[1] in m: # do we know the base?
795 self.ui.debug("found incomplete branch %s:%s\n"
826 self.ui.debug("found incomplete branch %s:%s\n"
796 % (short(n[0]), short(n[1])))
827 % (short(n[0]), short(n[1])))
797 search.append(n) # schedule branch range for scanning
828 search.append(n) # schedule branch range for scanning
798 seenbranch[n] = 1
829 seenbranch[n] = 1
799 else:
830 else:
800 if n[1] not in seen and n[1] not in fetch:
831 if n[1] not in seen and n[1] not in fetch:
801 if n[2] in m and n[3] in m:
832 if n[2] in m and n[3] in m:
802 self.ui.debug("found new changeset %s\n" %
833 self.ui.debug("found new changeset %s\n" %
803 short(n[1]))
834 short(n[1]))
804 fetch.append(n[1]) # earliest unknown
835 fetch.append(n[1]) # earliest unknown
805 continue
836 continue
806
837
807 for a in n[2:4]:
838 for a in n[2:4]:
808 if a not in rep:
839 if a not in rep:
809 r.append(a)
840 r.append(a)
810 rep[a] = 1
841 rep[a] = 1
811
842
812 seen[n[0]] = 1
843 seen[n[0]] = 1
813
844
814 if r:
845 if r:
815 reqcnt += 1
846 reqcnt += 1
816 self.ui.debug("request %d: %s\n" %
847 self.ui.debug("request %d: %s\n" %
817 (reqcnt, " ".join(map(short, r))))
848 (reqcnt, " ".join(map(short, r))))
818 for p in range(0, len(r), 10):
849 for p in range(0, len(r), 10):
819 for b in remote.branches(r[p:p+10]):
850 for b in remote.branches(r[p:p+10]):
820 self.ui.debug("received %s:%s\n" %
851 self.ui.debug("received %s:%s\n" %
821 (short(b[0]), short(b[1])))
852 (short(b[0]), short(b[1])))
822 if b[0] not in m and b[0] not in seen:
853 if b[0] not in m and b[0] not in seen:
823 unknown.append(b)
854 unknown.append(b)
824
855
825 while search:
856 while search:
826 n = search.pop(0)
857 n = search.pop(0)
827 reqcnt += 1
858 reqcnt += 1
828 l = remote.between([(n[0], n[1])])[0]
859 l = remote.between([(n[0], n[1])])[0]
829 l.append(n[1])
860 l.append(n[1])
830 p = n[0]
861 p = n[0]
831 f = 1
862 f = 1
832 for i in l:
863 for i in l:
833 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
864 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
834 if i in m:
865 if i in m:
835 if f <= 2:
866 if f <= 2:
836 self.ui.debug("found new branch changeset %s\n" %
867 self.ui.debug("found new branch changeset %s\n" %
837 short(p))
868 short(p))
838 fetch.append(p)
869 fetch.append(p)
839 else:
870 else:
840 self.ui.debug("narrowed branch search to %s:%s\n"
871 self.ui.debug("narrowed branch search to %s:%s\n"
841 % (short(p), short(i)))
872 % (short(p), short(i)))
842 search.append((p, i))
873 search.append((p, i))
843 break
874 break
844 p, f = i, f * 2
875 p, f = i, f * 2
845
876
846 for f in fetch:
877 for f in fetch:
847 if f in m:
878 if f in m:
848 raise "already have", short(f[:4])
879 raise "already have", short(f[:4])
849
880
850 self.ui.note("adding new changesets starting at " +
881 self.ui.note("adding new changesets starting at " +
851 " ".join([short(f) for f in fetch]) + "\n")
882 " ".join([short(f) for f in fetch]) + "\n")
852
883
853 self.ui.debug("%d total queries\n" % reqcnt)
884 self.ui.debug("%d total queries\n" % reqcnt)
854
885
855 return remote.changegroup(fetch)
886 return remote.changegroup(fetch)
856
887
857 def changegroup(self, basenodes):
888 def changegroup(self, basenodes):
858 nodes = self.newer(basenodes)
889 nodes = self.newer(basenodes)
859
890
860 # construct the link map
891 # construct the link map
861 linkmap = {}
892 linkmap = {}
862 for n in nodes:
893 for n in nodes:
863 linkmap[self.changelog.rev(n)] = n
894 linkmap[self.changelog.rev(n)] = n
864
895
865 # construct a list of all changed files
896 # construct a list of all changed files
866 changed = {}
897 changed = {}
867 for n in nodes:
898 for n in nodes:
868 c = self.changelog.read(n)
899 c = self.changelog.read(n)
869 for f in c[3]:
900 for f in c[3]:
870 changed[f] = 1
901 changed[f] = 1
871 changed = changed.keys()
902 changed = changed.keys()
872 changed.sort()
903 changed.sort()
873
904
874 # the changegroup is changesets + manifests + all file revs
905 # the changegroup is changesets + manifests + all file revs
875 revs = [ self.changelog.rev(n) for n in nodes ]
906 revs = [ self.changelog.rev(n) for n in nodes ]
876
907
877 for y in self.changelog.group(linkmap): yield y
908 for y in self.changelog.group(linkmap): yield y
878 for y in self.manifest.group(linkmap): yield y
909 for y in self.manifest.group(linkmap): yield y
879 for f in changed:
910 for f in changed:
880 yield struct.pack(">l", len(f) + 4) + f
911 yield struct.pack(">l", len(f) + 4) + f
881 g = self.file(f).group(linkmap)
912 g = self.file(f).group(linkmap)
882 for y in g:
913 for y in g:
883 yield y
914 yield y
884
915
885 def addchangegroup(self, generator):
916 def addchangegroup(self, generator):
886
917
887 class genread:
918 class genread:
888 def __init__(self, generator):
919 def __init__(self, generator):
889 self.g = generator
920 self.g = generator
890 self.buf = ""
921 self.buf = ""
891 def read(self, l):
922 def read(self, l):
892 while l > len(self.buf):
923 while l > len(self.buf):
893 try:
924 try:
894 self.buf += self.g.next()
925 self.buf += self.g.next()
895 except StopIteration:
926 except StopIteration:
896 break
927 break
897 d, self.buf = self.buf[:l], self.buf[l:]
928 d, self.buf = self.buf[:l], self.buf[l:]
898 return d
929 return d
899
930
900 def getchunk():
931 def getchunk():
901 d = source.read(4)
932 d = source.read(4)
902 if not d: return ""
933 if not d: return ""
903 l = struct.unpack(">l", d)[0]
934 l = struct.unpack(">l", d)[0]
904 if l <= 4: return ""
935 if l <= 4: return ""
905 return source.read(l - 4)
936 return source.read(l - 4)
906
937
907 def getgroup():
938 def getgroup():
908 while 1:
939 while 1:
909 c = getchunk()
940 c = getchunk()
910 if not c: break
941 if not c: break
911 yield c
942 yield c
912
943
913 def csmap(x):
944 def csmap(x):
914 self.ui.debug("add changeset %s\n" % short(x))
945 self.ui.debug("add changeset %s\n" % short(x))
915 return self.changelog.count()
946 return self.changelog.count()
916
947
917 def revmap(x):
948 def revmap(x):
918 return self.changelog.rev(x)
949 return self.changelog.rev(x)
919
950
920 if not generator: return
951 if not generator: return
921 changesets = files = revisions = 0
952 changesets = files = revisions = 0
922
953
923 source = genread(generator)
954 source = genread(generator)
924 lock = self.lock()
955 lock = self.lock()
925 tr = self.transaction()
956 tr = self.transaction()
926
957
927 # pull off the changeset group
958 # pull off the changeset group
928 self.ui.status("adding changesets\n")
959 self.ui.status("adding changesets\n")
929 co = self.changelog.tip()
960 co = self.changelog.tip()
930 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
961 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
931 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
962 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
932
963
933 # pull off the manifest group
964 # pull off the manifest group
934 self.ui.status("adding manifests\n")
965 self.ui.status("adding manifests\n")
935 mm = self.manifest.tip()
966 mm = self.manifest.tip()
936 mo = self.manifest.addgroup(getgroup(), revmap, tr)
967 mo = self.manifest.addgroup(getgroup(), revmap, tr)
937
968
938 # process the files
969 # process the files
939 self.ui.status("adding file revisions\n")
970 self.ui.status("adding file revisions\n")
940 while 1:
971 while 1:
941 f = getchunk()
972 f = getchunk()
942 if not f: break
973 if not f: break
943 self.ui.debug("adding %s revisions\n" % f)
974 self.ui.debug("adding %s revisions\n" % f)
944 fl = self.file(f)
975 fl = self.file(f)
945 o = fl.tip()
976 o = fl.tip()
946 n = fl.addgroup(getgroup(), revmap, tr)
977 n = fl.addgroup(getgroup(), revmap, tr)
947 revisions += fl.rev(n) - fl.rev(o)
978 revisions += fl.rev(n) - fl.rev(o)
948 files += 1
979 files += 1
949
980
950 self.ui.status(("modified %d files, added %d changesets" +
981 self.ui.status(("modified %d files, added %d changesets" +
951 " and %d new revisions\n")
982 " and %d new revisions\n")
952 % (files, changesets, revisions))
983 % (files, changesets, revisions))
953
984
954 tr.close()
985 tr.close()
955 return
986 return
956
987
957 def update(self, node, allow=False, force=False):
988 def update(self, node, allow=False, force=False):
958 pl = self.dirstate.parents()
989 pl = self.dirstate.parents()
959 if not force and pl[1] != nullid:
990 if not force and pl[1] != nullid:
960 self.ui.warn("aborting: outstanding uncommitted merges\n")
991 self.ui.warn("aborting: outstanding uncommitted merges\n")
961 return
992 return
962
993
963 p1, p2 = pl[0], node
994 p1, p2 = pl[0], node
964 pa = self.changelog.ancestor(p1, p2)
995 pa = self.changelog.ancestor(p1, p2)
965 m1n = self.changelog.read(p1)[0]
996 m1n = self.changelog.read(p1)[0]
966 m2n = self.changelog.read(p2)[0]
997 m2n = self.changelog.read(p2)[0]
967 man = self.manifest.ancestor(m1n, m2n)
998 man = self.manifest.ancestor(m1n, m2n)
968 m1 = self.manifest.read(m1n)
999 m1 = self.manifest.read(m1n)
969 mf1 = self.manifest.readflags(m1n)
1000 mf1 = self.manifest.readflags(m1n)
970 m2 = self.manifest.read(m2n)
1001 m2 = self.manifest.read(m2n)
971 mf2 = self.manifest.readflags(m2n)
1002 mf2 = self.manifest.readflags(m2n)
972 ma = self.manifest.read(man)
1003 ma = self.manifest.read(man)
973 mfa = self.manifest.readflags(m2n)
1004 mfa = self.manifest.readflags(m2n)
974
1005
975 (c, a, d, u) = self.diffdir(self.root)
1006 (c, a, d, u) = self.diffdir(self.root)
976
1007
977 # resolve the manifest to determine which files
1008 # resolve the manifest to determine which files
978 # we care about merging
1009 # we care about merging
979 self.ui.note("resolving manifests\n")
1010 self.ui.note("resolving manifests\n")
980 self.ui.debug(" ancestor %s local %s remote %s\n" %
1011 self.ui.debug(" ancestor %s local %s remote %s\n" %
981 (short(man), short(m1n), short(m2n)))
1012 (short(man), short(m1n), short(m2n)))
982
1013
983 merge = {}
1014 merge = {}
984 get = {}
1015 get = {}
985 remove = []
1016 remove = []
986 mark = {}
1017 mark = {}
987
1018
988 # construct a working dir manifest
1019 # construct a working dir manifest
989 mw = m1.copy()
1020 mw = m1.copy()
990 mfw = mf1.copy()
1021 mfw = mf1.copy()
991 for f in a + c + u:
1022 for f in a + c + u:
992 mw[f] = ""
1023 mw[f] = ""
993 mfw[f] = is_exec(self.wjoin(f))
1024 mfw[f] = is_exec(self.wjoin(f))
994 for f in d:
1025 for f in d:
995 if f in mw: del mw[f]
1026 if f in mw: del mw[f]
996
1027
997 for f, n in mw.iteritems():
1028 for f, n in mw.iteritems():
998 if f in m2:
1029 if f in m2:
999 s = 0
1030 s = 0
1000
1031
1001 # are files different?
1032 # are files different?
1002 if n != m2[f]:
1033 if n != m2[f]:
1003 a = ma.get(f, nullid)
1034 a = ma.get(f, nullid)
1004 # are both different from the ancestor?
1035 # are both different from the ancestor?
1005 if n != a and m2[f] != a:
1036 if n != a and m2[f] != a:
1006 self.ui.debug(" %s versions differ, resolve\n" % f)
1037 self.ui.debug(" %s versions differ, resolve\n" % f)
1007 merge[f] = (m1.get(f, nullid), m2[f])
1038 merge[f] = (m1.get(f, nullid), m2[f])
1008 # merge executable bits
1039 # merge executable bits
1009 # "if we changed or they changed, change in merge"
1040 # "if we changed or they changed, change in merge"
1010 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1041 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1011 mode = ((a^b) | (a^c)) ^ a
1042 mode = ((a^b) | (a^c)) ^ a
1012 merge[f] = (m1.get(f, nullid), m2[f], mode)
1043 merge[f] = (m1.get(f, nullid), m2[f], mode)
1013 s = 1
1044 s = 1
1014 # are we clobbering?
1045 # are we clobbering?
1015 # is remote's version newer?
1046 # is remote's version newer?
1016 # or are we going back in time?
1047 # or are we going back in time?
1017 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1048 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1018 self.ui.debug(" remote %s is newer, get\n" % f)
1049 self.ui.debug(" remote %s is newer, get\n" % f)
1019 get[f] = m2[f]
1050 get[f] = m2[f]
1020 s = 1
1051 s = 1
1021 else:
1052 else:
1022 mark[f] = 1
1053 mark[f] = 1
1023
1054
1024 if not s and mfw[f] != mf2[f]:
1055 if not s and mfw[f] != mf2[f]:
1025 if force:
1056 if force:
1026 self.ui.debug(" updating permissions for %s\n" % f)
1057 self.ui.debug(" updating permissions for %s\n" % f)
1027 set_exec(self.wjoin(f), mf2[f])
1058 set_exec(self.wjoin(f), mf2[f])
1028 else:
1059 else:
1029 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1060 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1030 mode = ((a^b) | (a^c)) ^ a
1061 mode = ((a^b) | (a^c)) ^ a
1031 if mode != b:
1062 if mode != b:
1032 self.ui.debug(" updating permissions for %s\n" % f)
1063 self.ui.debug(" updating permissions for %s\n" % f)
1033 set_exec(self.wjoin(f), mode)
1064 set_exec(self.wjoin(f), mode)
1034 mark[f] = 1
1065 mark[f] = 1
1035 del m2[f]
1066 del m2[f]
1036 elif f in ma:
1067 elif f in ma:
1037 if not force and n != ma[f]:
1068 if not force and n != ma[f]:
1038 r = self.ui.prompt(
1069 r = self.ui.prompt(
1039 (" local changed %s which remote deleted\n" % f) +
1070 (" local changed %s which remote deleted\n" % f) +
1040 "(k)eep or (d)elete?", "[kd]", "k")
1071 "(k)eep or (d)elete?", "[kd]", "k")
1041 if r == "d":
1072 if r == "d":
1042 remove.append(f)
1073 remove.append(f)
1043 else:
1074 else:
1044 self.ui.debug("other deleted %s\n" % f)
1075 self.ui.debug("other deleted %s\n" % f)
1045 remove.append(f) # other deleted it
1076 remove.append(f) # other deleted it
1046 else:
1077 else:
1047 if n == m1.get(f, nullid): # same as parent
1078 if n == m1.get(f, nullid): # same as parent
1048 self.ui.debug("local created %s, keeping\n" % f)
1079 self.ui.debug("local created %s, keeping\n" % f)
1049 else:
1080 else:
1050 self.ui.debug("working dir created %s, keeping\n" % f)
1081 self.ui.debug("working dir created %s, keeping\n" % f)
1051
1082
1052 for f, n in m2.iteritems():
1083 for f, n in m2.iteritems():
1053 if f[0] == "/": continue
1084 if f[0] == "/": continue
1054 if not force and f in ma and n != ma[f]:
1085 if not force and f in ma and n != ma[f]:
1055 r = self.ui.prompt(
1086 r = self.ui.prompt(
1056 ("remote changed %s which local deleted\n" % f) +
1087 ("remote changed %s which local deleted\n" % f) +
1057 "(k)eep or (d)elete?", "[kd]", "k")
1088 "(k)eep or (d)elete?", "[kd]", "k")
1058 if r == "d": remove.append(f)
1089 if r == "d": remove.append(f)
1059 else:
1090 else:
1060 self.ui.debug("remote created %s\n" % f)
1091 self.ui.debug("remote created %s\n" % f)
1061 get[f] = n
1092 get[f] = n
1062
1093
1063 del mw, m1, m2, ma
1094 del mw, m1, m2, ma
1064
1095
1065 if force:
1096 if force:
1066 for f in merge:
1097 for f in merge:
1067 get[f] = merge[f][1]
1098 get[f] = merge[f][1]
1068 merge = {}
1099 merge = {}
1069
1100
1070 if pa == p1 or pa == p2:
1101 if pa == p1 or pa == p2:
1071 # we don't need to do any magic, just jump to the new rev
1102 # we don't need to do any magic, just jump to the new rev
1072 mode = 'n'
1103 mode = 'n'
1073 p1, p2 = p2, nullid
1104 p1, p2 = p2, nullid
1074 else:
1105 else:
1075 if not allow:
1106 if not allow:
1076 self.ui.status("this update spans a branch" +
1107 self.ui.status("this update spans a branch" +
1077 " affecting the following files:\n")
1108 " affecting the following files:\n")
1078 fl = merge.keys() + get.keys()
1109 fl = merge.keys() + get.keys()
1079 fl.sort()
1110 fl.sort()
1080 for f in fl:
1111 for f in fl:
1081 cf = ""
1112 cf = ""
1082 if f in merge: cf = " (resolve)"
1113 if f in merge: cf = " (resolve)"
1083 self.ui.status(" %s%s\n" % (f, cf))
1114 self.ui.status(" %s%s\n" % (f, cf))
1084 self.ui.warn("aborting update spanning branches!\n")
1115 self.ui.warn("aborting update spanning branches!\n")
1085 self.ui.status("(use update -m to perform a branch merge)\n")
1116 self.ui.status("(use update -m to perform a branch merge)\n")
1086 return 1
1117 return 1
1087 # we have to remember what files we needed to get/change
1118 # we have to remember what files we needed to get/change
1088 # because any file that's different from either one of its
1119 # because any file that's different from either one of its
1089 # parents must be in the changeset
1120 # parents must be in the changeset
1090 mode = 'm'
1121 mode = 'm'
1091 self.dirstate.update(mark.keys(), "m")
1122 self.dirstate.update(mark.keys(), "m")
1092
1123
1093 self.dirstate.setparents(p1, p2)
1124 self.dirstate.setparents(p1, p2)
1094
1125
1095 # get the files we don't need to change
1126 # get the files we don't need to change
1096 files = get.keys()
1127 files = get.keys()
1097 files.sort()
1128 files.sort()
1098 for f in files:
1129 for f in files:
1099 if f[0] == "/": continue
1130 if f[0] == "/": continue
1100 self.ui.note("getting %s\n" % f)
1131 self.ui.note("getting %s\n" % f)
1101 t = self.file(f).read(get[f])
1132 t = self.file(f).read(get[f])
1102 try:
1133 try:
1103 self.wfile(f, "w").write(t)
1134 self.wfile(f, "w").write(t)
1104 except IOError:
1135 except IOError:
1105 os.makedirs(os.path.dirname(self.wjoin(f)))
1136 os.makedirs(os.path.dirname(self.wjoin(f)))
1106 self.wfile(f, "w").write(t)
1137 self.wfile(f, "w").write(t)
1107 set_exec(self.wjoin(f), mf2[f])
1138 set_exec(self.wjoin(f), mf2[f])
1108 self.dirstate.update([f], mode)
1139 self.dirstate.update([f], mode)
1109
1140
1110 # merge the tricky bits
1141 # merge the tricky bits
1111 files = merge.keys()
1142 files = merge.keys()
1112 files.sort()
1143 files.sort()
1113 for f in files:
1144 for f in files:
1114 self.ui.status("merging %s\n" % f)
1145 self.ui.status("merging %s\n" % f)
1115 m, o, flag = merge[f]
1146 m, o, flag = merge[f]
1116 self.merge3(f, m, o)
1147 self.merge3(f, m, o)
1117 set_exec(self.wjoin(f), flag)
1148 set_exec(self.wjoin(f), flag)
1118 self.dirstate.update([f], 'm')
1149 self.dirstate.update([f], 'm')
1119
1150
1120 for f in remove:
1151 for f in remove:
1121 self.ui.note("removing %s\n" % f)
1152 self.ui.note("removing %s\n" % f)
1122 os.unlink(f)
1153 os.unlink(f)
1123 if mode == 'n':
1154 if mode == 'n':
1124 self.dirstate.forget(remove)
1155 self.dirstate.forget(remove)
1125 else:
1156 else:
1126 self.dirstate.update(remove, 'r')
1157 self.dirstate.update(remove, 'r')
1127
1158
1128 def merge3(self, fn, my, other):
1159 def merge3(self, fn, my, other):
1129 """perform a 3-way merge in the working directory"""
1160 """perform a 3-way merge in the working directory"""
1130
1161
1131 def temp(prefix, node):
1162 def temp(prefix, node):
1132 pre = "%s~%s." % (os.path.basename(fn), prefix)
1163 pre = "%s~%s." % (os.path.basename(fn), prefix)
1133 (fd, name) = tempfile.mkstemp("", pre)
1164 (fd, name) = tempfile.mkstemp("", pre)
1134 f = os.fdopen(fd, "w")
1165 f = os.fdopen(fd, "w")
1135 f.write(fl.revision(node))
1166 f.write(fl.revision(node))
1136 f.close()
1167 f.close()
1137 return name
1168 return name
1138
1169
1139 fl = self.file(fn)
1170 fl = self.file(fn)
1140 base = fl.ancestor(my, other)
1171 base = fl.ancestor(my, other)
1141 a = self.wjoin(fn)
1172 a = self.wjoin(fn)
1142 b = temp("base", base)
1173 b = temp("base", base)
1143 c = temp("other", other)
1174 c = temp("other", other)
1144
1175
1145 self.ui.note("resolving %s\n" % fn)
1176 self.ui.note("resolving %s\n" % fn)
1146 self.ui.debug("file %s: other %s ancestor %s\n" %
1177 self.ui.debug("file %s: other %s ancestor %s\n" %
1147 (fn, short(other), short(base)))
1178 (fn, short(other), short(base)))
1148
1179
1149 cmd = os.environ.get("HGMERGE", "hgmerge")
1180 cmd = os.environ.get("HGMERGE", "hgmerge")
1150 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1181 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1151 if r:
1182 if r:
1152 self.ui.warn("merging %s failed!\n" % fn)
1183 self.ui.warn("merging %s failed!\n" % fn)
1153
1184
1154 os.unlink(b)
1185 os.unlink(b)
1155 os.unlink(c)
1186 os.unlink(c)
1156
1187
1157 def verify(self):
1188 def verify(self):
1158 filelinkrevs = {}
1189 filelinkrevs = {}
1159 filenodes = {}
1190 filenodes = {}
1160 changesets = revisions = files = 0
1191 changesets = revisions = files = 0
1161 errors = 0
1192 errors = 0
1162
1193
1163 seen = {}
1194 seen = {}
1164 self.ui.status("checking changesets\n")
1195 self.ui.status("checking changesets\n")
1165 for i in range(self.changelog.count()):
1196 for i in range(self.changelog.count()):
1166 changesets += 1
1197 changesets += 1
1167 n = self.changelog.node(i)
1198 n = self.changelog.node(i)
1168 if n in seen:
1199 if n in seen:
1169 self.ui.warn("duplicate changeset at revision %d\n" % i)
1200 self.ui.warn("duplicate changeset at revision %d\n" % i)
1170 errors += 1
1201 errors += 1
1171 seen[n] = 1
1202 seen[n] = 1
1172
1203
1173 for p in self.changelog.parents(n):
1204 for p in self.changelog.parents(n):
1174 if p not in self.changelog.nodemap:
1205 if p not in self.changelog.nodemap:
1175 self.ui.warn("changeset %s has unknown parent %s\n" %
1206 self.ui.warn("changeset %s has unknown parent %s\n" %
1176 (short(n), short(p)))
1207 (short(n), short(p)))
1177 errors += 1
1208 errors += 1
1178 try:
1209 try:
1179 changes = self.changelog.read(n)
1210 changes = self.changelog.read(n)
1180 except Exception, inst:
1211 except Exception, inst:
1181 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1212 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1182 errors += 1
1213 errors += 1
1183
1214
1184 for f in changes[3]:
1215 for f in changes[3]:
1185 filelinkrevs.setdefault(f, []).append(i)
1216 filelinkrevs.setdefault(f, []).append(i)
1186
1217
1187 seen = {}
1218 seen = {}
1188 self.ui.status("checking manifests\n")
1219 self.ui.status("checking manifests\n")
1189 for i in range(self.manifest.count()):
1220 for i in range(self.manifest.count()):
1190 n = self.manifest.node(i)
1221 n = self.manifest.node(i)
1191 if n in seen:
1222 if n in seen:
1192 self.ui.warn("duplicate manifest at revision %d\n" % i)
1223 self.ui.warn("duplicate manifest at revision %d\n" % i)
1193 errors += 1
1224 errors += 1
1194 seen[n] = 1
1225 seen[n] = 1
1195
1226
1196 for p in self.manifest.parents(n):
1227 for p in self.manifest.parents(n):
1197 if p not in self.manifest.nodemap:
1228 if p not in self.manifest.nodemap:
1198 self.ui.warn("manifest %s has unknown parent %s\n" %
1229 self.ui.warn("manifest %s has unknown parent %s\n" %
1199 (short(n), short(p)))
1230 (short(n), short(p)))
1200 errors += 1
1231 errors += 1
1201
1232
1202 try:
1233 try:
1203 delta = mdiff.patchtext(self.manifest.delta(n))
1234 delta = mdiff.patchtext(self.manifest.delta(n))
1204 except KeyboardInterrupt:
1235 except KeyboardInterrupt:
1205 print "aborted"
1236 print "aborted"
1206 sys.exit(0)
1237 sys.exit(0)
1207 except Exception, inst:
1238 except Exception, inst:
1208 self.ui.warn("unpacking manifest %s: %s\n"
1239 self.ui.warn("unpacking manifest %s: %s\n"
1209 % (short(n), inst))
1240 % (short(n), inst))
1210 errors += 1
1241 errors += 1
1211
1242
1212 ff = [ l.split('\0') for l in delta.splitlines() ]
1243 ff = [ l.split('\0') for l in delta.splitlines() ]
1213 for f, fn in ff:
1244 for f, fn in ff:
1214 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1245 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1215
1246
1216 self.ui.status("crosschecking files in changesets and manifests\n")
1247 self.ui.status("crosschecking files in changesets and manifests\n")
1217 for f in filenodes:
1248 for f in filenodes:
1218 if f not in filelinkrevs:
1249 if f not in filelinkrevs:
1219 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1250 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1220 errors += 1
1251 errors += 1
1221
1252
1222 for f in filelinkrevs:
1253 for f in filelinkrevs:
1223 if f not in filenodes:
1254 if f not in filenodes:
1224 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1255 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1225 errors += 1
1256 errors += 1
1226
1257
1227 self.ui.status("checking files\n")
1258 self.ui.status("checking files\n")
1228 ff = filenodes.keys()
1259 ff = filenodes.keys()
1229 ff.sort()
1260 ff.sort()
1230 for f in ff:
1261 for f in ff:
1231 if f == "/dev/null": continue
1262 if f == "/dev/null": continue
1232 files += 1
1263 files += 1
1233 fl = self.file(f)
1264 fl = self.file(f)
1234 nodes = { nullid: 1 }
1265 nodes = { nullid: 1 }
1235 seen = {}
1266 seen = {}
1236 for i in range(fl.count()):
1267 for i in range(fl.count()):
1237 revisions += 1
1268 revisions += 1
1238 n = fl.node(i)
1269 n = fl.node(i)
1239
1270
1240 if n in seen:
1271 if n in seen:
1241 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1272 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1242 errors += 1
1273 errors += 1
1243
1274
1244 if n not in filenodes[f]:
1275 if n not in filenodes[f]:
1245 self.ui.warn("%s: %d:%s not in manifests\n"
1276 self.ui.warn("%s: %d:%s not in manifests\n"
1246 % (f, i, short(n)))
1277 % (f, i, short(n)))
1247 print len(filenodes[f].keys()), fl.count(), f
1278 print len(filenodes[f].keys()), fl.count(), f
1248 errors += 1
1279 errors += 1
1249 else:
1280 else:
1250 del filenodes[f][n]
1281 del filenodes[f][n]
1251
1282
1252 flr = fl.linkrev(n)
1283 flr = fl.linkrev(n)
1253 if flr not in filelinkrevs[f]:
1284 if flr not in filelinkrevs[f]:
1254 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1285 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1255 % (f, short(n), fl.linkrev(n)))
1286 % (f, short(n), fl.linkrev(n)))
1256 errors += 1
1287 errors += 1
1257 else:
1288 else:
1258 filelinkrevs[f].remove(flr)
1289 filelinkrevs[f].remove(flr)
1259
1290
1260 # verify contents
1291 # verify contents
1261 try:
1292 try:
1262 t = fl.read(n)
1293 t = fl.read(n)
1263 except Exception, inst:
1294 except Exception, inst:
1264 self.ui.warn("unpacking file %s %s: %s\n"
1295 self.ui.warn("unpacking file %s %s: %s\n"
1265 % (f, short(n), inst))
1296 % (f, short(n), inst))
1266 errors += 1
1297 errors += 1
1267
1298
1268 # verify parents
1299 # verify parents
1269 (p1, p2) = fl.parents(n)
1300 (p1, p2) = fl.parents(n)
1270 if p1 not in nodes:
1301 if p1 not in nodes:
1271 self.ui.warn("file %s:%s unknown parent 1 %s" %
1302 self.ui.warn("file %s:%s unknown parent 1 %s" %
1272 (f, short(n), short(p1)))
1303 (f, short(n), short(p1)))
1273 errors += 1
1304 errors += 1
1274 if p2 not in nodes:
1305 if p2 not in nodes:
1275 self.ui.warn("file %s:%s unknown parent 2 %s" %
1306 self.ui.warn("file %s:%s unknown parent 2 %s" %
1276 (f, short(n), short(p1)))
1307 (f, short(n), short(p1)))
1277 errors += 1
1308 errors += 1
1278 nodes[n] = 1
1309 nodes[n] = 1
1279
1310
1280 # cross-check
1311 # cross-check
1281 for node in filenodes[f]:
1312 for node in filenodes[f]:
1282 self.ui.warn("node %s in manifests not in %s\n"
1313 self.ui.warn("node %s in manifests not in %s\n"
1283 % (hex(n), f))
1314 % (hex(n), f))
1284 errors += 1
1315 errors += 1
1285
1316
1286 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1317 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1287 (files, changesets, revisions))
1318 (files, changesets, revisions))
1288
1319
1289 if errors:
1320 if errors:
1290 self.ui.warn("%d integrity errors encountered!\n" % errors)
1321 self.ui.warn("%d integrity errors encountered!\n" % errors)
1291 return 1
1322 return 1
1292
1323
1293 class remoterepository:
1324 class remoterepository:
1294 def __init__(self, ui, path):
1325 def __init__(self, ui, path):
1295 self.url = path
1326 self.url = path
1296 self.ui = ui
1327 self.ui = ui
1297 no_list = [ "localhost", "127.0.0.1" ]
1328 no_list = [ "localhost", "127.0.0.1" ]
1298 host = ui.config("http_proxy", "host")
1329 host = ui.config("http_proxy", "host")
1299 user = ui.config("http_proxy", "user")
1330 user = ui.config("http_proxy", "user")
1300 passwd = ui.config("http_proxy", "passwd")
1331 passwd = ui.config("http_proxy", "passwd")
1301 no = ui.config("http_proxy", "no")
1332 no = ui.config("http_proxy", "no")
1302 if no:
1333 if no:
1303 no_list = no_list + no.split(",")
1334 no_list = no_list + no.split(",")
1304
1335
1305 no_proxy = 0
1336 no_proxy = 0
1306 for h in no_list:
1337 for h in no_list:
1307 if (path.startswith("http://" + h + "/") or
1338 if (path.startswith("http://" + h + "/") or
1308 path.startswith("http://" + h + ":") or
1339 path.startswith("http://" + h + ":") or
1309 path == "http://" + h):
1340 path == "http://" + h):
1310 no_proxy = 1
1341 no_proxy = 1
1311
1342
1312 # Note: urllib2 takes proxy values from the environment and those will
1343 # Note: urllib2 takes proxy values from the environment and those will
1313 # take precedence
1344 # take precedence
1314
1345
1315 proxy_handler = urllib2.BaseHandler()
1346 proxy_handler = urllib2.BaseHandler()
1316 if host and not no_proxy:
1347 if host and not no_proxy:
1317 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1348 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1318
1349
1319 authinfo = None
1350 authinfo = None
1320 if user and passwd:
1351 if user and passwd:
1321 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1352 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1322 passmgr.add_password(None, host, user, passwd)
1353 passmgr.add_password(None, host, user, passwd)
1323 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1354 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1324
1355
1325 opener = urllib2.build_opener(proxy_handler, authinfo)
1356 opener = urllib2.build_opener(proxy_handler, authinfo)
1326 urllib2.install_opener(opener)
1357 urllib2.install_opener(opener)
1327
1358
1328 def do_cmd(self, cmd, **args):
1359 def do_cmd(self, cmd, **args):
1329 self.ui.debug("sending %s command\n" % cmd)
1360 self.ui.debug("sending %s command\n" % cmd)
1330 q = {"cmd": cmd}
1361 q = {"cmd": cmd}
1331 q.update(args)
1362 q.update(args)
1332 qs = urllib.urlencode(q)
1363 qs = urllib.urlencode(q)
1333 cu = "%s?%s" % (self.url, qs)
1364 cu = "%s?%s" % (self.url, qs)
1334 return urllib2.urlopen(cu)
1365 return urllib2.urlopen(cu)
1335
1366
1336 def heads(self):
1367 def heads(self):
1337 d = self.do_cmd("heads").read()
1368 d = self.do_cmd("heads").read()
1338 try:
1369 try:
1339 return map(bin, d[:-1].split(" "))
1370 return map(bin, d[:-1].split(" "))
1340 except:
1371 except:
1341 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1372 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1342 raise
1373 raise
1343
1374
1344 def branches(self, nodes):
1375 def branches(self, nodes):
1345 n = " ".join(map(hex, nodes))
1376 n = " ".join(map(hex, nodes))
1346 d = self.do_cmd("branches", nodes=n).read()
1377 d = self.do_cmd("branches", nodes=n).read()
1347 try:
1378 try:
1348 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1379 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1349 return br
1380 return br
1350 except:
1381 except:
1351 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1382 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1352 raise
1383 raise
1353
1384
1354 def between(self, pairs):
1385 def between(self, pairs):
1355 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1386 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1356 d = self.do_cmd("between", pairs=n).read()
1387 d = self.do_cmd("between", pairs=n).read()
1357 try:
1388 try:
1358 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1389 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1359 return p
1390 return p
1360 except:
1391 except:
1361 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1392 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1362 raise
1393 raise
1363
1394
1364 def changegroup(self, nodes):
1395 def changegroup(self, nodes):
1365 n = " ".join(map(hex, nodes))
1396 n = " ".join(map(hex, nodes))
1366 zd = zlib.decompressobj()
1397 zd = zlib.decompressobj()
1367 f = self.do_cmd("changegroup", roots=n)
1398 f = self.do_cmd("changegroup", roots=n)
1368 bytes = 0
1399 bytes = 0
1369 while 1:
1400 while 1:
1370 d = f.read(4096)
1401 d = f.read(4096)
1371 bytes += len(d)
1402 bytes += len(d)
1372 if not d:
1403 if not d:
1373 yield zd.flush()
1404 yield zd.flush()
1374 break
1405 break
1375 yield zd.decompress(d)
1406 yield zd.decompress(d)
1376 self.ui.note("%d bytes of data transfered\n" % bytes)
1407 self.ui.note("%d bytes of data transfered\n" % bytes)
1377
1408
1378 def repository(ui, path=None, create=0):
1409 def repository(ui, path=None, create=0):
1379 if path and path[:7] == "http://":
1410 if path and path[:7] == "http://":
1380 return remoterepository(ui, path)
1411 return remoterepository(ui, path)
1381 if path and path[:5] == "hg://":
1412 if path and path[:5] == "hg://":
1382 return remoterepository(ui, path.replace("hg://", "http://"))
1413 return remoterepository(ui, path.replace("hg://", "http://"))
1383 if path and path[:11] == "old-http://":
1414 if path and path[:11] == "old-http://":
1384 return localrepository(ui, path.replace("old-http://", "http://"))
1415 return localrepository(ui, path.replace("old-http://", "http://"))
1385 else:
1416 else:
1386 return localrepository(ui, path, create)
1417 return localrepository(ui, path, create)
1387
1418
1388 class httprangereader:
1419 class httprangereader:
1389 def __init__(self, url):
1420 def __init__(self, url):
1390 self.url = url
1421 self.url = url
1391 self.pos = 0
1422 self.pos = 0
1392 def seek(self, pos):
1423 def seek(self, pos):
1393 self.pos = pos
1424 self.pos = pos
1394 def read(self, bytes=None):
1425 def read(self, bytes=None):
1395 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1426 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1396 urllib2.install_opener(opener)
1427 urllib2.install_opener(opener)
1397 req = urllib2.Request(self.url)
1428 req = urllib2.Request(self.url)
1398 end = ''
1429 end = ''
1399 if bytes: end = self.pos + bytes
1430 if bytes: end = self.pos + bytes
1400 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1431 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1401 f = urllib2.urlopen(req)
1432 f = urllib2.urlopen(req)
1402 return f.read()
1433 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now