##// END OF EJS Templates
localrepository.addchangegroup: add more source infos to hooks
Vadim Gelfer -
r2230:33295034 default
parent child Browse files
Show More
@@ -1,259 +1,267 b''
1 # notify.py - email notifications for mercurial
1 # notify.py - email notifications for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 # hook extension to email notifications to people when changesets are
8 # hook extension to email notifications to people when changesets are
9 # committed to a repo they subscribe to.
9 # committed to a repo they subscribe to.
10 #
10 #
11 # default mode is to print messages to stdout, for testing and
11 # default mode is to print messages to stdout, for testing and
12 # configuring.
12 # configuring.
13 #
13 #
14 # to use, configure notify extension and enable in hgrc like this:
14 # to use, configure notify extension and enable in hgrc like this:
15 #
15 #
16 # [extensions]
16 # [extensions]
17 # hgext.notify =
17 # hgext.notify =
18 #
18 #
19 # [hooks]
19 # [hooks]
20 # # one email for each incoming changeset
20 # # one email for each incoming changeset
21 # incoming.notify = python:hgext.notify.hook
21 # incoming.notify = python:hgext.notify.hook
22 # # batch emails when many changesets incoming at one time
22 # # batch emails when many changesets incoming at one time
23 # changegroup.notify = python:hgext.notify.hook
23 # changegroup.notify = python:hgext.notify.hook
24 #
24 #
25 # [notify]
25 # [notify]
26 # # config items go in here
26 # # config items go in here
27 #
27 #
28 # config items:
28 # config items:
29 #
29 #
30 # REQUIRED:
30 # REQUIRED:
31 # config = /path/to/file # file containing subscriptions
31 # config = /path/to/file # file containing subscriptions
32 #
32 #
33 # OPTIONAL:
33 # OPTIONAL:
34 # test = True # print messages to stdout for testing
34 # test = True # print messages to stdout for testing
35 # strip = 3 # number of slashes to strip for url paths
35 # strip = 3 # number of slashes to strip for url paths
36 # domain = example.com # domain to use if committer missing domain
36 # domain = example.com # domain to use if committer missing domain
37 # style = ... # style file to use when formatting email
37 # style = ... # style file to use when formatting email
38 # template = ... # template to use when formatting email
38 # template = ... # template to use when formatting email
39 # incoming = ... # template to use when run as incoming hook
39 # incoming = ... # template to use when run as incoming hook
40 # changegroup = ... # template when run as changegroup hook
40 # changegroup = ... # template when run as changegroup hook
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 # maxsubject = 67 # truncate subject line longer than this
42 # maxsubject = 67 # truncate subject line longer than this
43 # sources = serve # notify if source of incoming changes in this list
44 # # (serve == ssh or http, push, pull, bundle)
43 # [email]
45 # [email]
44 # from = user@host.com # email address to send as if none given
46 # from = user@host.com # email address to send as if none given
45 # [web]
47 # [web]
46 # baseurl = http://hgserver/... # root of hg web site for browsing commits
48 # baseurl = http://hgserver/... # root of hg web site for browsing commits
47 #
49 #
48 # notify config file has same format as regular hgrc. it has two
50 # notify config file has same format as regular hgrc. it has two
49 # sections so you can express subscriptions in whatever way is handier
51 # sections so you can express subscriptions in whatever way is handier
50 # for you.
52 # for you.
51 #
53 #
52 # [usersubs]
54 # [usersubs]
53 # # key is subscriber email, value is ","-separated list of glob patterns
55 # # key is subscriber email, value is ","-separated list of glob patterns
54 # user@host = pattern
56 # user@host = pattern
55 #
57 #
56 # [reposubs]
58 # [reposubs]
57 # # key is glob pattern, value is ","-separated list of subscriber emails
59 # # key is glob pattern, value is ","-separated list of subscriber emails
58 # pattern = user@host
60 # pattern = user@host
59 #
61 #
60 # glob patterns are matched against path to repo root.
62 # glob patterns are matched against path to repo root.
61 #
63 #
62 # if you like, you can put notify config file in repo that users can
64 # if you like, you can put notify config file in repo that users can
63 # push changes to, they can manage their own subscriptions.
65 # push changes to, they can manage their own subscriptions.
64
66
65 from mercurial.demandload import *
67 from mercurial.demandload import *
66 from mercurial.i18n import gettext as _
68 from mercurial.i18n import gettext as _
67 from mercurial.node import *
69 from mercurial.node import *
68 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
70 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
69 demandload(globals(), 'fnmatch socket time')
71 demandload(globals(), 'fnmatch socket time')
70
72
71 # template for single changeset can include email headers.
73 # template for single changeset can include email headers.
72 single_template = '''
74 single_template = '''
73 Subject: changeset in {webroot}: {desc|firstline|strip}
75 Subject: changeset in {webroot}: {desc|firstline|strip}
74 From: {author}
76 From: {author}
75
77
76 changeset {node|short} in {root}
78 changeset {node|short} in {root}
77 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
78 description:
80 description:
79 \t{desc|tabindent|strip}
81 \t{desc|tabindent|strip}
80 '''.lstrip()
82 '''.lstrip()
81
83
82 # template for multiple changesets should not contain email headers,
84 # template for multiple changesets should not contain email headers,
83 # because only first set of headers will be used and result will look
85 # because only first set of headers will be used and result will look
84 # strange.
86 # strange.
85 multiple_template = '''
87 multiple_template = '''
86 changeset {node|short} in {root}
88 changeset {node|short} in {root}
87 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
88 summary: {desc|firstline}
90 summary: {desc|firstline}
89 '''
91 '''
90
92
91 deftemplates = {
93 deftemplates = {
92 'changegroup': multiple_template,
94 'changegroup': multiple_template,
93 }
95 }
94
96
95 class notifier(object):
97 class notifier(object):
96 '''email notification class.'''
98 '''email notification class.'''
97
99
98 def __init__(self, ui, repo, hooktype):
100 def __init__(self, ui, repo, hooktype):
99 self.ui = ui
101 self.ui = ui
100 self.ui.readconfig(self.ui.config('notify', 'config'))
102 self.ui.readconfig(self.ui.config('notify', 'config'))
101 self.repo = repo
103 self.repo = repo
102 self.stripcount = int(self.ui.config('notify', 'strip', 0))
104 self.stripcount = int(self.ui.config('notify', 'strip', 0))
103 self.root = self.strip(self.repo.root)
105 self.root = self.strip(self.repo.root)
104 self.domain = self.ui.config('notify', 'domain')
106 self.domain = self.ui.config('notify', 'domain')
105 self.sio = templater.stringio()
107 self.sio = templater.stringio()
106 self.subs = self.subscribers()
108 self.subs = self.subscribers()
107
109
108 mapfile = self.ui.config('notify', 'style')
110 mapfile = self.ui.config('notify', 'style')
109 template = (self.ui.config('notify', hooktype) or
111 template = (self.ui.config('notify', hooktype) or
110 self.ui.config('notify', 'template'))
112 self.ui.config('notify', 'template'))
111 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
113 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
112 self.sio)
114 self.sio)
113 if not mapfile and not template:
115 if not mapfile and not template:
114 template = deftemplates.get(hooktype) or single_template
116 template = deftemplates.get(hooktype) or single_template
115 if template:
117 if template:
116 template = templater.parsestring(template, quoted=False)
118 template = templater.parsestring(template, quoted=False)
117 self.t.use_template(template)
119 self.t.use_template(template)
118
120
119 def strip(self, path):
121 def strip(self, path):
120 '''strip leading slashes from local path, turn into web-safe path.'''
122 '''strip leading slashes from local path, turn into web-safe path.'''
121
123
122 path = util.pconvert(path)
124 path = util.pconvert(path)
123 count = self.stripcount
125 count = self.stripcount
124 while path and count >= 0:
126 while path and count >= 0:
125 c = path.find('/')
127 c = path.find('/')
126 if c == -1:
128 if c == -1:
127 break
129 break
128 path = path[c+1:]
130 path = path[c+1:]
129 count -= 1
131 count -= 1
130 return path
132 return path
131
133
132 def fixmail(self, addr):
134 def fixmail(self, addr):
133 '''try to clean up email addresses.'''
135 '''try to clean up email addresses.'''
134
136
135 addr = templater.email(addr.strip())
137 addr = templater.email(addr.strip())
136 a = addr.find('@localhost')
138 a = addr.find('@localhost')
137 if a != -1:
139 if a != -1:
138 addr = addr[:a]
140 addr = addr[:a]
139 if '@' not in addr:
141 if '@' not in addr:
140 return addr + '@' + self.domain
142 return addr + '@' + self.domain
141 return addr
143 return addr
142
144
143 def subscribers(self):
145 def subscribers(self):
144 '''return list of email addresses of subscribers to this repo.'''
146 '''return list of email addresses of subscribers to this repo.'''
145
147
146 subs = {}
148 subs = {}
147 for user, pats in self.ui.configitems('usersubs'):
149 for user, pats in self.ui.configitems('usersubs'):
148 for pat in pats.split(','):
150 for pat in pats.split(','):
149 if fnmatch.fnmatch(self.repo.root, pat.strip()):
151 if fnmatch.fnmatch(self.repo.root, pat.strip()):
150 subs[self.fixmail(user)] = 1
152 subs[self.fixmail(user)] = 1
151 for pat, users in self.ui.configitems('reposubs'):
153 for pat, users in self.ui.configitems('reposubs'):
152 if fnmatch.fnmatch(self.repo.root, pat):
154 if fnmatch.fnmatch(self.repo.root, pat):
153 for user in users.split(','):
155 for user in users.split(','):
154 subs[self.fixmail(user)] = 1
156 subs[self.fixmail(user)] = 1
155 subs = subs.keys()
157 subs = subs.keys()
156 subs.sort()
158 subs.sort()
157 return subs
159 return subs
158
160
159 def url(self, path=None):
161 def url(self, path=None):
160 return self.ui.config('web', 'baseurl') + (path or self.root)
162 return self.ui.config('web', 'baseurl') + (path or self.root)
161
163
162 def node(self, node):
164 def node(self, node):
163 '''format one changeset.'''
165 '''format one changeset.'''
164
166
165 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
167 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
166 baseurl=self.ui.config('web', 'baseurl'),
168 baseurl=self.ui.config('web', 'baseurl'),
167 root=self.repo.root,
169 root=self.repo.root,
168 webroot=self.root)
170 webroot=self.root)
169
171
172 def skipsource(self, source):
173 '''true if incoming changes from this source should be skipped.'''
174 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
175 return source not in ok_sources
176
170 def send(self, node, count):
177 def send(self, node, count):
171 '''send message.'''
178 '''send message.'''
172
179
173 p = email.Parser.Parser()
180 p = email.Parser.Parser()
174 self.sio.seek(0)
181 self.sio.seek(0)
175 msg = p.parse(self.sio)
182 msg = p.parse(self.sio)
176
183
177 def fix_subject():
184 def fix_subject():
178 '''try to make subject line exist and be useful.'''
185 '''try to make subject line exist and be useful.'''
179
186
180 subject = msg['Subject']
187 subject = msg['Subject']
181 if not subject:
188 if not subject:
182 if count > 1:
189 if count > 1:
183 subject = _('%s: %d new changesets') % (self.root, count)
190 subject = _('%s: %d new changesets') % (self.root, count)
184 else:
191 else:
185 changes = self.repo.changelog.read(node)
192 changes = self.repo.changelog.read(node)
186 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
193 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
187 subject = '%s: %s' % (self.root, s)
194 subject = '%s: %s' % (self.root, s)
188 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
195 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
189 if maxsubject and len(subject) > maxsubject:
196 if maxsubject and len(subject) > maxsubject:
190 subject = subject[:maxsubject-3] + '...'
197 subject = subject[:maxsubject-3] + '...'
191 del msg['Subject']
198 del msg['Subject']
192 msg['Subject'] = subject
199 msg['Subject'] = subject
193
200
194 def fix_sender():
201 def fix_sender():
195 '''try to make message have proper sender.'''
202 '''try to make message have proper sender.'''
196
203
197 sender = msg['From']
204 sender = msg['From']
198 if not sender:
205 if not sender:
199 sender = self.ui.config('email', 'from') or self.ui.username()
206 sender = self.ui.config('email', 'from') or self.ui.username()
200 if '@' not in sender or '@localhost' in sender:
207 if '@' not in sender or '@localhost' in sender:
201 sender = self.fixmail(sender)
208 sender = self.fixmail(sender)
202 del msg['From']
209 del msg['From']
203 msg['From'] = sender
210 msg['From'] = sender
204
211
205 fix_subject()
212 fix_subject()
206 fix_sender()
213 fix_sender()
207
214
208 msg['X-Hg-Notification'] = 'changeset ' + short(node)
215 msg['X-Hg-Notification'] = 'changeset ' + short(node)
209 if not msg['Message-Id']:
216 if not msg['Message-Id']:
210 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
217 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
211 (short(node), int(time.time()),
218 (short(node), int(time.time()),
212 hash(self.repo.root), socket.getfqdn()))
219 hash(self.repo.root), socket.getfqdn()))
213 msg['To'] = self.subs
220 msg['To'] = ', '.join(self.subs)
214
221
215 msgtext = msg.as_string(0)
222 msgtext = msg.as_string(0)
216 if self.ui.configbool('notify', 'test', True):
223 if self.ui.configbool('notify', 'test', True):
217 self.ui.write(msgtext)
224 self.ui.write(msgtext)
218 if not msgtext.endswith('\n'):
225 if not msgtext.endswith('\n'):
219 self.ui.write('\n')
226 self.ui.write('\n')
220 else:
227 else:
221 mail = self.ui.sendmail()
228 mail = self.ui.sendmail()
222 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
229 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
223
230
224 def diff(self, node):
231 def diff(self, node):
225 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
232 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
226 if maxdiff == 0:
233 if maxdiff == 0:
227 return
234 return
228 fp = templater.stringio()
235 fp = templater.stringio()
229 prev = self.repo.changelog.parents(node)[0]
236 prev = self.repo.changelog.parents(node)[0]
230 commands.dodiff(fp, self.ui, self.repo, prev,
237 commands.dodiff(fp, self.ui, self.repo, prev,
231 self.repo.changelog.tip())
238 self.repo.changelog.tip())
232 difflines = fp.getvalue().splitlines(1)
239 difflines = fp.getvalue().splitlines(1)
233 if maxdiff > 0 and len(difflines) > maxdiff:
240 if maxdiff > 0 and len(difflines) > maxdiff:
234 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
241 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
235 (len(difflines), maxdiff))
242 (len(difflines), maxdiff))
236 difflines = difflines[:maxdiff]
243 difflines = difflines[:maxdiff]
237 elif difflines:
244 elif difflines:
238 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
245 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
239 self.sio.write(*difflines)
246 self.sio.write(*difflines)
240
247
241 def hook(ui, repo, hooktype, node=None, **kwargs):
248 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
242 '''send email notifications to interested subscribers.
249 '''send email notifications to interested subscribers.
243
250
244 if used as changegroup hook, send one email for all changesets in
251 if used as changegroup hook, send one email for all changesets in
245 changegroup. else send one email per changeset.'''
252 changegroup. else send one email per changeset.'''
246 n = notifier(ui, repo, hooktype)
253 n = notifier(ui, repo, hooktype)
247 if not n.subs: return True
254 if not n.subs or n.skipsource(source):
255 return
248 node = bin(node)
256 node = bin(node)
249 if hooktype == 'changegroup':
257 if hooktype == 'changegroup':
250 start = repo.changelog.rev(node)
258 start = repo.changelog.rev(node)
251 end = repo.changelog.count()
259 end = repo.changelog.count()
252 count = end - start
260 count = end - start
253 for rev in xrange(start, end):
261 for rev in xrange(start, end):
254 n.node(repo.changelog.node(rev))
262 n.node(repo.changelog.node(rev))
255 else:
263 else:
256 count = 1
264 count = 1
257 n.node(node)
265 n.node(node)
258 n.diff(node)
266 n.diff(node)
259 n.send(node, count)
267 n.send(node, count)
@@ -1,3430 +1,3430 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 from demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
13 demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 demandload(globals(), "archival changegroup")
15 demandload(globals(), "archival changegroup")
16
16
17 class UnknownCommand(Exception):
17 class UnknownCommand(Exception):
18 """Exception raised if command is not in the command table."""
18 """Exception raised if command is not in the command table."""
19 class AmbiguousCommand(Exception):
19 class AmbiguousCommand(Exception):
20 """Exception raised if command shortcut matches more than one command."""
20 """Exception raised if command shortcut matches more than one command."""
21
21
22 def bail_if_changed(repo):
22 def bail_if_changed(repo):
23 modified, added, removed, deleted, unknown = repo.changes()
23 modified, added, removed, deleted, unknown = repo.changes()
24 if modified or added or removed or deleted:
24 if modified or added or removed or deleted:
25 raise util.Abort(_("outstanding uncommitted changes"))
25 raise util.Abort(_("outstanding uncommitted changes"))
26
26
27 def filterfiles(filters, files):
27 def filterfiles(filters, files):
28 l = [x for x in files if x in filters]
28 l = [x for x in files if x in filters]
29
29
30 for t in filters:
30 for t in filters:
31 if t and t[-1] != "/":
31 if t and t[-1] != "/":
32 t += "/"
32 t += "/"
33 l += [x for x in files if x.startswith(t)]
33 l += [x for x in files if x.startswith(t)]
34 return l
34 return l
35
35
36 def relpath(repo, args):
36 def relpath(repo, args):
37 cwd = repo.getcwd()
37 cwd = repo.getcwd()
38 if cwd:
38 if cwd:
39 return [util.normpath(os.path.join(cwd, x)) for x in args]
39 return [util.normpath(os.path.join(cwd, x)) for x in args]
40 return args
40 return args
41
41
42 def matchpats(repo, pats=[], opts={}, head=''):
42 def matchpats(repo, pats=[], opts={}, head=''):
43 cwd = repo.getcwd()
43 cwd = repo.getcwd()
44 if not pats and cwd:
44 if not pats and cwd:
45 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
45 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
46 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
46 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
47 cwd = ''
47 cwd = ''
48 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
48 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
49 opts.get('exclude'), head)
49 opts.get('exclude'), head)
50
50
51 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
51 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
52 files, matchfn, anypats = matchpats(repo, pats, opts, head)
52 files, matchfn, anypats = matchpats(repo, pats, opts, head)
53 exact = dict(zip(files, files))
53 exact = dict(zip(files, files))
54 def walk():
54 def walk():
55 for src, fn in repo.walk(node=node, files=files, match=matchfn,
55 for src, fn in repo.walk(node=node, files=files, match=matchfn,
56 badmatch=badmatch):
56 badmatch=badmatch):
57 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
57 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
58 return files, matchfn, walk()
58 return files, matchfn, walk()
59
59
60 def walk(repo, pats, opts, node=None, head='', badmatch=None):
60 def walk(repo, pats, opts, node=None, head='', badmatch=None):
61 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
61 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
62 for r in results:
62 for r in results:
63 yield r
63 yield r
64
64
65 def walkchangerevs(ui, repo, pats, opts):
65 def walkchangerevs(ui, repo, pats, opts):
66 '''Iterate over files and the revs they changed in.
66 '''Iterate over files and the revs they changed in.
67
67
68 Callers most commonly need to iterate backwards over the history
68 Callers most commonly need to iterate backwards over the history
69 it is interested in. Doing so has awful (quadratic-looking)
69 it is interested in. Doing so has awful (quadratic-looking)
70 performance, so we use iterators in a "windowed" way.
70 performance, so we use iterators in a "windowed" way.
71
71
72 We walk a window of revisions in the desired order. Within the
72 We walk a window of revisions in the desired order. Within the
73 window, we first walk forwards to gather data, then in the desired
73 window, we first walk forwards to gather data, then in the desired
74 order (usually backwards) to display it.
74 order (usually backwards) to display it.
75
75
76 This function returns an (iterator, getchange, matchfn) tuple. The
76 This function returns an (iterator, getchange, matchfn) tuple. The
77 getchange function returns the changelog entry for a numeric
77 getchange function returns the changelog entry for a numeric
78 revision. The iterator yields 3-tuples. They will be of one of
78 revision. The iterator yields 3-tuples. They will be of one of
79 the following forms:
79 the following forms:
80
80
81 "window", incrementing, lastrev: stepping through a window,
81 "window", incrementing, lastrev: stepping through a window,
82 positive if walking forwards through revs, last rev in the
82 positive if walking forwards through revs, last rev in the
83 sequence iterated over - use to reset state for the current window
83 sequence iterated over - use to reset state for the current window
84
84
85 "add", rev, fns: out-of-order traversal of the given file names
85 "add", rev, fns: out-of-order traversal of the given file names
86 fns, which changed during revision rev - use to gather data for
86 fns, which changed during revision rev - use to gather data for
87 possible display
87 possible display
88
88
89 "iter", rev, None: in-order traversal of the revs earlier iterated
89 "iter", rev, None: in-order traversal of the revs earlier iterated
90 over with "add" - use to display data'''
90 over with "add" - use to display data'''
91
91
92 def increasing_windows(start, end, windowsize=8, sizelimit=512):
92 def increasing_windows(start, end, windowsize=8, sizelimit=512):
93 if start < end:
93 if start < end:
94 while start < end:
94 while start < end:
95 yield start, min(windowsize, end-start)
95 yield start, min(windowsize, end-start)
96 start += windowsize
96 start += windowsize
97 if windowsize < sizelimit:
97 if windowsize < sizelimit:
98 windowsize *= 2
98 windowsize *= 2
99 else:
99 else:
100 while start > end:
100 while start > end:
101 yield start, min(windowsize, start-end-1)
101 yield start, min(windowsize, start-end-1)
102 start -= windowsize
102 start -= windowsize
103 if windowsize < sizelimit:
103 if windowsize < sizelimit:
104 windowsize *= 2
104 windowsize *= 2
105
105
106
106
107 files, matchfn, anypats = matchpats(repo, pats, opts)
107 files, matchfn, anypats = matchpats(repo, pats, opts)
108
108
109 if repo.changelog.count() == 0:
109 if repo.changelog.count() == 0:
110 return [], False, matchfn
110 return [], False, matchfn
111
111
112 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
112 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
113 wanted = {}
113 wanted = {}
114 slowpath = anypats
114 slowpath = anypats
115 fncache = {}
115 fncache = {}
116
116
117 chcache = {}
117 chcache = {}
118 def getchange(rev):
118 def getchange(rev):
119 ch = chcache.get(rev)
119 ch = chcache.get(rev)
120 if ch is None:
120 if ch is None:
121 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
121 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
122 return ch
122 return ch
123
123
124 if not slowpath and not files:
124 if not slowpath and not files:
125 # No files, no patterns. Display all revs.
125 # No files, no patterns. Display all revs.
126 wanted = dict(zip(revs, revs))
126 wanted = dict(zip(revs, revs))
127 if not slowpath:
127 if not slowpath:
128 # Only files, no patterns. Check the history of each file.
128 # Only files, no patterns. Check the history of each file.
129 def filerevgen(filelog):
129 def filerevgen(filelog):
130 for i, window in increasing_windows(filelog.count()-1, -1):
130 for i, window in increasing_windows(filelog.count()-1, -1):
131 revs = []
131 revs = []
132 for j in xrange(i - window, i + 1):
132 for j in xrange(i - window, i + 1):
133 revs.append(filelog.linkrev(filelog.node(j)))
133 revs.append(filelog.linkrev(filelog.node(j)))
134 revs.reverse()
134 revs.reverse()
135 for rev in revs:
135 for rev in revs:
136 yield rev
136 yield rev
137
137
138 minrev, maxrev = min(revs), max(revs)
138 minrev, maxrev = min(revs), max(revs)
139 for file_ in files:
139 for file_ in files:
140 filelog = repo.file(file_)
140 filelog = repo.file(file_)
141 # A zero count may be a directory or deleted file, so
141 # A zero count may be a directory or deleted file, so
142 # try to find matching entries on the slow path.
142 # try to find matching entries on the slow path.
143 if filelog.count() == 0:
143 if filelog.count() == 0:
144 slowpath = True
144 slowpath = True
145 break
145 break
146 for rev in filerevgen(filelog):
146 for rev in filerevgen(filelog):
147 if rev <= maxrev:
147 if rev <= maxrev:
148 if rev < minrev:
148 if rev < minrev:
149 break
149 break
150 fncache.setdefault(rev, [])
150 fncache.setdefault(rev, [])
151 fncache[rev].append(file_)
151 fncache[rev].append(file_)
152 wanted[rev] = 1
152 wanted[rev] = 1
153 if slowpath:
153 if slowpath:
154 # The slow path checks files modified in every changeset.
154 # The slow path checks files modified in every changeset.
155 def changerevgen():
155 def changerevgen():
156 for i, window in increasing_windows(repo.changelog.count()-1, -1):
156 for i, window in increasing_windows(repo.changelog.count()-1, -1):
157 for j in xrange(i - window, i + 1):
157 for j in xrange(i - window, i + 1):
158 yield j, getchange(j)[3]
158 yield j, getchange(j)[3]
159
159
160 for rev, changefiles in changerevgen():
160 for rev, changefiles in changerevgen():
161 matches = filter(matchfn, changefiles)
161 matches = filter(matchfn, changefiles)
162 if matches:
162 if matches:
163 fncache[rev] = matches
163 fncache[rev] = matches
164 wanted[rev] = 1
164 wanted[rev] = 1
165
165
166 def iterate():
166 def iterate():
167 for i, window in increasing_windows(0, len(revs)):
167 for i, window in increasing_windows(0, len(revs)):
168 yield 'window', revs[0] < revs[-1], revs[-1]
168 yield 'window', revs[0] < revs[-1], revs[-1]
169 nrevs = [rev for rev in revs[i:i+window]
169 nrevs = [rev for rev in revs[i:i+window]
170 if rev in wanted]
170 if rev in wanted]
171 srevs = list(nrevs)
171 srevs = list(nrevs)
172 srevs.sort()
172 srevs.sort()
173 for rev in srevs:
173 for rev in srevs:
174 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
174 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
175 yield 'add', rev, fns
175 yield 'add', rev, fns
176 for rev in nrevs:
176 for rev in nrevs:
177 yield 'iter', rev, None
177 yield 'iter', rev, None
178 return iterate(), getchange, matchfn
178 return iterate(), getchange, matchfn
179
179
180 revrangesep = ':'
180 revrangesep = ':'
181
181
182 def revrange(ui, repo, revs, revlog=None):
182 def revrange(ui, repo, revs, revlog=None):
183 """Yield revision as strings from a list of revision specifications."""
183 """Yield revision as strings from a list of revision specifications."""
184 if revlog is None:
184 if revlog is None:
185 revlog = repo.changelog
185 revlog = repo.changelog
186 revcount = revlog.count()
186 revcount = revlog.count()
187 def fix(val, defval):
187 def fix(val, defval):
188 if not val:
188 if not val:
189 return defval
189 return defval
190 try:
190 try:
191 num = int(val)
191 num = int(val)
192 if str(num) != val:
192 if str(num) != val:
193 raise ValueError
193 raise ValueError
194 if num < 0:
194 if num < 0:
195 num += revcount
195 num += revcount
196 if num < 0:
196 if num < 0:
197 num = 0
197 num = 0
198 elif num >= revcount:
198 elif num >= revcount:
199 raise ValueError
199 raise ValueError
200 except ValueError:
200 except ValueError:
201 try:
201 try:
202 num = repo.changelog.rev(repo.lookup(val))
202 num = repo.changelog.rev(repo.lookup(val))
203 except KeyError:
203 except KeyError:
204 try:
204 try:
205 num = revlog.rev(revlog.lookup(val))
205 num = revlog.rev(revlog.lookup(val))
206 except KeyError:
206 except KeyError:
207 raise util.Abort(_('invalid revision identifier %s'), val)
207 raise util.Abort(_('invalid revision identifier %s'), val)
208 return num
208 return num
209 seen = {}
209 seen = {}
210 for spec in revs:
210 for spec in revs:
211 if spec.find(revrangesep) >= 0:
211 if spec.find(revrangesep) >= 0:
212 start, end = spec.split(revrangesep, 1)
212 start, end = spec.split(revrangesep, 1)
213 start = fix(start, 0)
213 start = fix(start, 0)
214 end = fix(end, revcount - 1)
214 end = fix(end, revcount - 1)
215 step = start > end and -1 or 1
215 step = start > end and -1 or 1
216 for rev in xrange(start, end+step, step):
216 for rev in xrange(start, end+step, step):
217 if rev in seen:
217 if rev in seen:
218 continue
218 continue
219 seen[rev] = 1
219 seen[rev] = 1
220 yield str(rev)
220 yield str(rev)
221 else:
221 else:
222 rev = fix(spec, None)
222 rev = fix(spec, None)
223 if rev in seen:
223 if rev in seen:
224 continue
224 continue
225 seen[rev] = 1
225 seen[rev] = 1
226 yield str(rev)
226 yield str(rev)
227
227
228 def make_filename(repo, r, pat, node=None,
228 def make_filename(repo, r, pat, node=None,
229 total=None, seqno=None, revwidth=None, pathname=None):
229 total=None, seqno=None, revwidth=None, pathname=None):
230 node_expander = {
230 node_expander = {
231 'H': lambda: hex(node),
231 'H': lambda: hex(node),
232 'R': lambda: str(r.rev(node)),
232 'R': lambda: str(r.rev(node)),
233 'h': lambda: short(node),
233 'h': lambda: short(node),
234 }
234 }
235 expander = {
235 expander = {
236 '%': lambda: '%',
236 '%': lambda: '%',
237 'b': lambda: os.path.basename(repo.root),
237 'b': lambda: os.path.basename(repo.root),
238 }
238 }
239
239
240 try:
240 try:
241 if node:
241 if node:
242 expander.update(node_expander)
242 expander.update(node_expander)
243 if node and revwidth is not None:
243 if node and revwidth is not None:
244 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
244 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
245 if total is not None:
245 if total is not None:
246 expander['N'] = lambda: str(total)
246 expander['N'] = lambda: str(total)
247 if seqno is not None:
247 if seqno is not None:
248 expander['n'] = lambda: str(seqno)
248 expander['n'] = lambda: str(seqno)
249 if total is not None and seqno is not None:
249 if total is not None and seqno is not None:
250 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
250 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
251 if pathname is not None:
251 if pathname is not None:
252 expander['s'] = lambda: os.path.basename(pathname)
252 expander['s'] = lambda: os.path.basename(pathname)
253 expander['d'] = lambda: os.path.dirname(pathname) or '.'
253 expander['d'] = lambda: os.path.dirname(pathname) or '.'
254 expander['p'] = lambda: pathname
254 expander['p'] = lambda: pathname
255
255
256 newname = []
256 newname = []
257 patlen = len(pat)
257 patlen = len(pat)
258 i = 0
258 i = 0
259 while i < patlen:
259 while i < patlen:
260 c = pat[i]
260 c = pat[i]
261 if c == '%':
261 if c == '%':
262 i += 1
262 i += 1
263 c = pat[i]
263 c = pat[i]
264 c = expander[c]()
264 c = expander[c]()
265 newname.append(c)
265 newname.append(c)
266 i += 1
266 i += 1
267 return ''.join(newname)
267 return ''.join(newname)
268 except KeyError, inst:
268 except KeyError, inst:
269 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
269 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
270 inst.args[0])
270 inst.args[0])
271
271
272 def make_file(repo, r, pat, node=None,
272 def make_file(repo, r, pat, node=None,
273 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
273 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
274 if not pat or pat == '-':
274 if not pat or pat == '-':
275 return 'w' in mode and sys.stdout or sys.stdin
275 return 'w' in mode and sys.stdout or sys.stdin
276 if hasattr(pat, 'write') and 'w' in mode:
276 if hasattr(pat, 'write') and 'w' in mode:
277 return pat
277 return pat
278 if hasattr(pat, 'read') and 'r' in mode:
278 if hasattr(pat, 'read') and 'r' in mode:
279 return pat
279 return pat
280 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
280 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
281 pathname),
281 pathname),
282 mode)
282 mode)
283
283
284 def write_bundle(cg, filename=None, compress=True):
284 def write_bundle(cg, filename=None, compress=True):
285 """Write a bundle file and return its filename.
285 """Write a bundle file and return its filename.
286
286
287 Existing files will not be overwritten.
287 Existing files will not be overwritten.
288 If no filename is specified, a temporary file is created.
288 If no filename is specified, a temporary file is created.
289 bz2 compression can be turned off.
289 bz2 compression can be turned off.
290 The bundle file will be deleted in case of errors.
290 The bundle file will be deleted in case of errors.
291 """
291 """
292 class nocompress(object):
292 class nocompress(object):
293 def compress(self, x):
293 def compress(self, x):
294 return x
294 return x
295 def flush(self):
295 def flush(self):
296 return ""
296 return ""
297
297
298 fh = None
298 fh = None
299 cleanup = None
299 cleanup = None
300 try:
300 try:
301 if filename:
301 if filename:
302 if os.path.exists(filename):
302 if os.path.exists(filename):
303 raise util.Abort(_("file '%s' already exists"), filename)
303 raise util.Abort(_("file '%s' already exists"), filename)
304 fh = open(filename, "wb")
304 fh = open(filename, "wb")
305 else:
305 else:
306 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
306 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
307 fh = os.fdopen(fd, "wb")
307 fh = os.fdopen(fd, "wb")
308 cleanup = filename
308 cleanup = filename
309
309
310 if compress:
310 if compress:
311 fh.write("HG10")
311 fh.write("HG10")
312 z = bz2.BZ2Compressor(9)
312 z = bz2.BZ2Compressor(9)
313 else:
313 else:
314 fh.write("HG10UN")
314 fh.write("HG10UN")
315 z = nocompress()
315 z = nocompress()
316 # parse the changegroup data, otherwise we will block
316 # parse the changegroup data, otherwise we will block
317 # in case of sshrepo because we don't know the end of the stream
317 # in case of sshrepo because we don't know the end of the stream
318
318
319 # an empty chunkiter is the end of the changegroup
319 # an empty chunkiter is the end of the changegroup
320 empty = False
320 empty = False
321 while not empty:
321 while not empty:
322 empty = True
322 empty = True
323 for chunk in changegroup.chunkiter(cg):
323 for chunk in changegroup.chunkiter(cg):
324 empty = False
324 empty = False
325 fh.write(z.compress(changegroup.genchunk(chunk)))
325 fh.write(z.compress(changegroup.genchunk(chunk)))
326 fh.write(z.compress(changegroup.closechunk()))
326 fh.write(z.compress(changegroup.closechunk()))
327 fh.write(z.flush())
327 fh.write(z.flush())
328 cleanup = None
328 cleanup = None
329 return filename
329 return filename
330 finally:
330 finally:
331 if fh is not None:
331 if fh is not None:
332 fh.close()
332 fh.close()
333 if cleanup is not None:
333 if cleanup is not None:
334 os.unlink(cleanup)
334 os.unlink(cleanup)
335
335
336 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
336 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
337 changes=None, text=False, opts={}):
337 changes=None, text=False, opts={}):
338 if not node1:
338 if not node1:
339 node1 = repo.dirstate.parents()[0]
339 node1 = repo.dirstate.parents()[0]
340 # reading the data for node1 early allows it to play nicely
340 # reading the data for node1 early allows it to play nicely
341 # with repo.changes and the revlog cache.
341 # with repo.changes and the revlog cache.
342 change = repo.changelog.read(node1)
342 change = repo.changelog.read(node1)
343 mmap = repo.manifest.read(change[0])
343 mmap = repo.manifest.read(change[0])
344 date1 = util.datestr(change[2])
344 date1 = util.datestr(change[2])
345
345
346 if not changes:
346 if not changes:
347 changes = repo.changes(node1, node2, files, match=match)
347 changes = repo.changes(node1, node2, files, match=match)
348 modified, added, removed, deleted, unknown = changes
348 modified, added, removed, deleted, unknown = changes
349 if files:
349 if files:
350 modified, added, removed = map(lambda x: filterfiles(files, x),
350 modified, added, removed = map(lambda x: filterfiles(files, x),
351 (modified, added, removed))
351 (modified, added, removed))
352
352
353 if not modified and not added and not removed:
353 if not modified and not added and not removed:
354 return
354 return
355
355
356 if node2:
356 if node2:
357 change = repo.changelog.read(node2)
357 change = repo.changelog.read(node2)
358 mmap2 = repo.manifest.read(change[0])
358 mmap2 = repo.manifest.read(change[0])
359 date2 = util.datestr(change[2])
359 date2 = util.datestr(change[2])
360 def read(f):
360 def read(f):
361 return repo.file(f).read(mmap2[f])
361 return repo.file(f).read(mmap2[f])
362 else:
362 else:
363 date2 = util.datestr()
363 date2 = util.datestr()
364 def read(f):
364 def read(f):
365 return repo.wread(f)
365 return repo.wread(f)
366
366
367 if ui.quiet:
367 if ui.quiet:
368 r = None
368 r = None
369 else:
369 else:
370 hexfunc = ui.verbose and hex or short
370 hexfunc = ui.verbose and hex or short
371 r = [hexfunc(node) for node in [node1, node2] if node]
371 r = [hexfunc(node) for node in [node1, node2] if node]
372
372
373 diffopts = ui.diffopts()
373 diffopts = ui.diffopts()
374 showfunc = opts.get('show_function') or diffopts['showfunc']
374 showfunc = opts.get('show_function') or diffopts['showfunc']
375 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
375 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
376 for f in modified:
376 for f in modified:
377 to = None
377 to = None
378 if f in mmap:
378 if f in mmap:
379 to = repo.file(f).read(mmap[f])
379 to = repo.file(f).read(mmap[f])
380 tn = read(f)
380 tn = read(f)
381 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
381 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
382 showfunc=showfunc, ignorews=ignorews))
382 showfunc=showfunc, ignorews=ignorews))
383 for f in added:
383 for f in added:
384 to = None
384 to = None
385 tn = read(f)
385 tn = read(f)
386 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
386 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
387 showfunc=showfunc, ignorews=ignorews))
387 showfunc=showfunc, ignorews=ignorews))
388 for f in removed:
388 for f in removed:
389 to = repo.file(f).read(mmap[f])
389 to = repo.file(f).read(mmap[f])
390 tn = None
390 tn = None
391 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
391 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
392 showfunc=showfunc, ignorews=ignorews))
392 showfunc=showfunc, ignorews=ignorews))
393
393
394 def trimuser(ui, name, rev, revcache):
394 def trimuser(ui, name, rev, revcache):
395 """trim the name of the user who committed a change"""
395 """trim the name of the user who committed a change"""
396 user = revcache.get(rev)
396 user = revcache.get(rev)
397 if user is None:
397 if user is None:
398 user = revcache[rev] = ui.shortuser(name)
398 user = revcache[rev] = ui.shortuser(name)
399 return user
399 return user
400
400
401 class changeset_printer(object):
401 class changeset_printer(object):
402 '''show changeset information when templating not requested.'''
402 '''show changeset information when templating not requested.'''
403
403
404 def __init__(self, ui, repo):
404 def __init__(self, ui, repo):
405 self.ui = ui
405 self.ui = ui
406 self.repo = repo
406 self.repo = repo
407
407
408 def show(self, rev=0, changenode=None, brinfo=None):
408 def show(self, rev=0, changenode=None, brinfo=None):
409 '''show a single changeset or file revision'''
409 '''show a single changeset or file revision'''
410 log = self.repo.changelog
410 log = self.repo.changelog
411 if changenode is None:
411 if changenode is None:
412 changenode = log.node(rev)
412 changenode = log.node(rev)
413 elif not rev:
413 elif not rev:
414 rev = log.rev(changenode)
414 rev = log.rev(changenode)
415
415
416 if self.ui.quiet:
416 if self.ui.quiet:
417 self.ui.write("%d:%s\n" % (rev, short(changenode)))
417 self.ui.write("%d:%s\n" % (rev, short(changenode)))
418 return
418 return
419
419
420 changes = log.read(changenode)
420 changes = log.read(changenode)
421 date = util.datestr(changes[2])
421 date = util.datestr(changes[2])
422
422
423 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
423 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
424 for p in log.parents(changenode)
424 for p in log.parents(changenode)
425 if self.ui.debugflag or p != nullid]
425 if self.ui.debugflag or p != nullid]
426 if (not self.ui.debugflag and len(parents) == 1 and
426 if (not self.ui.debugflag and len(parents) == 1 and
427 parents[0][0] == rev-1):
427 parents[0][0] == rev-1):
428 parents = []
428 parents = []
429
429
430 if self.ui.verbose:
430 if self.ui.verbose:
431 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
431 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
432 else:
432 else:
433 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
433 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
434
434
435 for tag in self.repo.nodetags(changenode):
435 for tag in self.repo.nodetags(changenode):
436 self.ui.status(_("tag: %s\n") % tag)
436 self.ui.status(_("tag: %s\n") % tag)
437 for parent in parents:
437 for parent in parents:
438 self.ui.write(_("parent: %d:%s\n") % parent)
438 self.ui.write(_("parent: %d:%s\n") % parent)
439
439
440 if brinfo and changenode in brinfo:
440 if brinfo and changenode in brinfo:
441 br = brinfo[changenode]
441 br = brinfo[changenode]
442 self.ui.write(_("branch: %s\n") % " ".join(br))
442 self.ui.write(_("branch: %s\n") % " ".join(br))
443
443
444 self.ui.debug(_("manifest: %d:%s\n") %
444 self.ui.debug(_("manifest: %d:%s\n") %
445 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
445 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
446 self.ui.status(_("user: %s\n") % changes[1])
446 self.ui.status(_("user: %s\n") % changes[1])
447 self.ui.status(_("date: %s\n") % date)
447 self.ui.status(_("date: %s\n") % date)
448
448
449 if self.ui.debugflag:
449 if self.ui.debugflag:
450 files = self.repo.changes(log.parents(changenode)[0], changenode)
450 files = self.repo.changes(log.parents(changenode)[0], changenode)
451 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
451 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
452 files):
452 files):
453 if value:
453 if value:
454 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
454 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
455 else:
455 else:
456 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
456 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
457
457
458 description = changes[4].strip()
458 description = changes[4].strip()
459 if description:
459 if description:
460 if self.ui.verbose:
460 if self.ui.verbose:
461 self.ui.status(_("description:\n"))
461 self.ui.status(_("description:\n"))
462 self.ui.status(description)
462 self.ui.status(description)
463 self.ui.status("\n\n")
463 self.ui.status("\n\n")
464 else:
464 else:
465 self.ui.status(_("summary: %s\n") %
465 self.ui.status(_("summary: %s\n") %
466 description.splitlines()[0])
466 description.splitlines()[0])
467 self.ui.status("\n")
467 self.ui.status("\n")
468
468
469 def show_changeset(ui, repo, opts):
469 def show_changeset(ui, repo, opts):
470 '''show one changeset. uses template or regular display. caller
470 '''show one changeset. uses template or regular display. caller
471 can pass in 'style' and 'template' options in opts.'''
471 can pass in 'style' and 'template' options in opts.'''
472
472
473 tmpl = opts.get('template')
473 tmpl = opts.get('template')
474 if tmpl:
474 if tmpl:
475 tmpl = templater.parsestring(tmpl, quoted=False)
475 tmpl = templater.parsestring(tmpl, quoted=False)
476 else:
476 else:
477 tmpl = ui.config('ui', 'logtemplate')
477 tmpl = ui.config('ui', 'logtemplate')
478 if tmpl: tmpl = templater.parsestring(tmpl)
478 if tmpl: tmpl = templater.parsestring(tmpl)
479 mapfile = opts.get('style') or ui.config('ui', 'style')
479 mapfile = opts.get('style') or ui.config('ui', 'style')
480 if tmpl or mapfile:
480 if tmpl or mapfile:
481 if mapfile:
481 if mapfile:
482 if not os.path.isfile(mapfile):
482 if not os.path.isfile(mapfile):
483 mapname = templater.templatepath('map-cmdline.' + mapfile)
483 mapname = templater.templatepath('map-cmdline.' + mapfile)
484 if not mapname: mapname = templater.templatepath(mapfile)
484 if not mapname: mapname = templater.templatepath(mapfile)
485 if mapname: mapfile = mapname
485 if mapname: mapfile = mapname
486 try:
486 try:
487 t = templater.changeset_templater(ui, repo, mapfile)
487 t = templater.changeset_templater(ui, repo, mapfile)
488 except SyntaxError, inst:
488 except SyntaxError, inst:
489 raise util.Abort(inst.args[0])
489 raise util.Abort(inst.args[0])
490 if tmpl: t.use_template(tmpl)
490 if tmpl: t.use_template(tmpl)
491 return t
491 return t
492 return changeset_printer(ui, repo)
492 return changeset_printer(ui, repo)
493
493
494 def show_version(ui):
494 def show_version(ui):
495 """output version and copyright information"""
495 """output version and copyright information"""
496 ui.write(_("Mercurial Distributed SCM (version %s)\n")
496 ui.write(_("Mercurial Distributed SCM (version %s)\n")
497 % version.get_version())
497 % version.get_version())
498 ui.status(_(
498 ui.status(_(
499 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
499 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
500 "This is free software; see the source for copying conditions. "
500 "This is free software; see the source for copying conditions. "
501 "There is NO\nwarranty; "
501 "There is NO\nwarranty; "
502 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
502 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
503 ))
503 ))
504
504
505 def help_(ui, cmd=None, with_version=False):
505 def help_(ui, cmd=None, with_version=False):
506 """show help for a given command or all commands"""
506 """show help for a given command or all commands"""
507 option_lists = []
507 option_lists = []
508 if cmd and cmd != 'shortlist':
508 if cmd and cmd != 'shortlist':
509 if with_version:
509 if with_version:
510 show_version(ui)
510 show_version(ui)
511 ui.write('\n')
511 ui.write('\n')
512 aliases, i = find(cmd)
512 aliases, i = find(cmd)
513 # synopsis
513 # synopsis
514 ui.write("%s\n\n" % i[2])
514 ui.write("%s\n\n" % i[2])
515
515
516 # description
516 # description
517 doc = i[0].__doc__
517 doc = i[0].__doc__
518 if not doc:
518 if not doc:
519 doc = _("(No help text available)")
519 doc = _("(No help text available)")
520 if ui.quiet:
520 if ui.quiet:
521 doc = doc.splitlines(0)[0]
521 doc = doc.splitlines(0)[0]
522 ui.write("%s\n" % doc.rstrip())
522 ui.write("%s\n" % doc.rstrip())
523
523
524 if not ui.quiet:
524 if not ui.quiet:
525 # aliases
525 # aliases
526 if len(aliases) > 1:
526 if len(aliases) > 1:
527 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
527 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
528
528
529 # options
529 # options
530 if i[1]:
530 if i[1]:
531 option_lists.append(("options", i[1]))
531 option_lists.append(("options", i[1]))
532
532
533 else:
533 else:
534 # program name
534 # program name
535 if ui.verbose or with_version:
535 if ui.verbose or with_version:
536 show_version(ui)
536 show_version(ui)
537 else:
537 else:
538 ui.status(_("Mercurial Distributed SCM\n"))
538 ui.status(_("Mercurial Distributed SCM\n"))
539 ui.status('\n')
539 ui.status('\n')
540
540
541 # list of commands
541 # list of commands
542 if cmd == "shortlist":
542 if cmd == "shortlist":
543 ui.status(_('basic commands (use "hg help" '
543 ui.status(_('basic commands (use "hg help" '
544 'for the full list or option "-v" for details):\n\n'))
544 'for the full list or option "-v" for details):\n\n'))
545 elif ui.verbose:
545 elif ui.verbose:
546 ui.status(_('list of commands:\n\n'))
546 ui.status(_('list of commands:\n\n'))
547 else:
547 else:
548 ui.status(_('list of commands (use "hg help -v" '
548 ui.status(_('list of commands (use "hg help -v" '
549 'to show aliases and global options):\n\n'))
549 'to show aliases and global options):\n\n'))
550
550
551 h = {}
551 h = {}
552 cmds = {}
552 cmds = {}
553 for c, e in table.items():
553 for c, e in table.items():
554 f = c.split("|")[0]
554 f = c.split("|")[0]
555 if cmd == "shortlist" and not f.startswith("^"):
555 if cmd == "shortlist" and not f.startswith("^"):
556 continue
556 continue
557 f = f.lstrip("^")
557 f = f.lstrip("^")
558 if not ui.debugflag and f.startswith("debug"):
558 if not ui.debugflag and f.startswith("debug"):
559 continue
559 continue
560 doc = e[0].__doc__
560 doc = e[0].__doc__
561 if not doc:
561 if not doc:
562 doc = _("(No help text available)")
562 doc = _("(No help text available)")
563 h[f] = doc.splitlines(0)[0].rstrip()
563 h[f] = doc.splitlines(0)[0].rstrip()
564 cmds[f] = c.lstrip("^")
564 cmds[f] = c.lstrip("^")
565
565
566 fns = h.keys()
566 fns = h.keys()
567 fns.sort()
567 fns.sort()
568 m = max(map(len, fns))
568 m = max(map(len, fns))
569 for f in fns:
569 for f in fns:
570 if ui.verbose:
570 if ui.verbose:
571 commands = cmds[f].replace("|",", ")
571 commands = cmds[f].replace("|",", ")
572 ui.write(" %s:\n %s\n"%(commands, h[f]))
572 ui.write(" %s:\n %s\n"%(commands, h[f]))
573 else:
573 else:
574 ui.write(' %-*s %s\n' % (m, f, h[f]))
574 ui.write(' %-*s %s\n' % (m, f, h[f]))
575
575
576 # global options
576 # global options
577 if ui.verbose:
577 if ui.verbose:
578 option_lists.append(("global options", globalopts))
578 option_lists.append(("global options", globalopts))
579
579
580 # list all option lists
580 # list all option lists
581 opt_output = []
581 opt_output = []
582 for title, options in option_lists:
582 for title, options in option_lists:
583 opt_output.append(("\n%s:\n" % title, None))
583 opt_output.append(("\n%s:\n" % title, None))
584 for shortopt, longopt, default, desc in options:
584 for shortopt, longopt, default, desc in options:
585 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
585 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
586 longopt and " --%s" % longopt),
586 longopt and " --%s" % longopt),
587 "%s%s" % (desc,
587 "%s%s" % (desc,
588 default
588 default
589 and _(" (default: %s)") % default
589 and _(" (default: %s)") % default
590 or "")))
590 or "")))
591
591
592 if opt_output:
592 if opt_output:
593 opts_len = max([len(line[0]) for line in opt_output if line[1]])
593 opts_len = max([len(line[0]) for line in opt_output if line[1]])
594 for first, second in opt_output:
594 for first, second in opt_output:
595 if second:
595 if second:
596 ui.write(" %-*s %s\n" % (opts_len, first, second))
596 ui.write(" %-*s %s\n" % (opts_len, first, second))
597 else:
597 else:
598 ui.write("%s\n" % first)
598 ui.write("%s\n" % first)
599
599
600 # Commands start here, listed alphabetically
600 # Commands start here, listed alphabetically
601
601
602 def add(ui, repo, *pats, **opts):
602 def add(ui, repo, *pats, **opts):
603 """add the specified files on the next commit
603 """add the specified files on the next commit
604
604
605 Schedule files to be version controlled and added to the repository.
605 Schedule files to be version controlled and added to the repository.
606
606
607 The files will be added to the repository at the next commit.
607 The files will be added to the repository at the next commit.
608
608
609 If no names are given, add all files in the repository.
609 If no names are given, add all files in the repository.
610 """
610 """
611
611
612 names = []
612 names = []
613 for src, abs, rel, exact in walk(repo, pats, opts):
613 for src, abs, rel, exact in walk(repo, pats, opts):
614 if exact:
614 if exact:
615 if ui.verbose:
615 if ui.verbose:
616 ui.status(_('adding %s\n') % rel)
616 ui.status(_('adding %s\n') % rel)
617 names.append(abs)
617 names.append(abs)
618 elif repo.dirstate.state(abs) == '?':
618 elif repo.dirstate.state(abs) == '?':
619 ui.status(_('adding %s\n') % rel)
619 ui.status(_('adding %s\n') % rel)
620 names.append(abs)
620 names.append(abs)
621 repo.add(names)
621 repo.add(names)
622
622
623 def addremove(ui, repo, *pats, **opts):
623 def addremove(ui, repo, *pats, **opts):
624 """add all new files, delete all missing files (DEPRECATED)
624 """add all new files, delete all missing files (DEPRECATED)
625
625
626 (DEPRECATED)
626 (DEPRECATED)
627 Add all new files and remove all missing files from the repository.
627 Add all new files and remove all missing files from the repository.
628
628
629 New files are ignored if they match any of the patterns in .hgignore. As
629 New files are ignored if they match any of the patterns in .hgignore. As
630 with add, these changes take effect at the next commit.
630 with add, these changes take effect at the next commit.
631
631
632 This command is now deprecated and will be removed in a future
632 This command is now deprecated and will be removed in a future
633 release. Please use add and remove --after instead.
633 release. Please use add and remove --after instead.
634 """
634 """
635 ui.warn(_('(the addremove command is deprecated; use add and remove '
635 ui.warn(_('(the addremove command is deprecated; use add and remove '
636 '--after instead)\n'))
636 '--after instead)\n'))
637 return addremove_lock(ui, repo, pats, opts)
637 return addremove_lock(ui, repo, pats, opts)
638
638
639 def addremove_lock(ui, repo, pats, opts, wlock=None):
639 def addremove_lock(ui, repo, pats, opts, wlock=None):
640 add, remove = [], []
640 add, remove = [], []
641 for src, abs, rel, exact in walk(repo, pats, opts):
641 for src, abs, rel, exact in walk(repo, pats, opts):
642 if src == 'f' and repo.dirstate.state(abs) == '?':
642 if src == 'f' and repo.dirstate.state(abs) == '?':
643 add.append(abs)
643 add.append(abs)
644 if ui.verbose or not exact:
644 if ui.verbose or not exact:
645 ui.status(_('adding %s\n') % ((pats and rel) or abs))
645 ui.status(_('adding %s\n') % ((pats and rel) or abs))
646 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
646 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
647 remove.append(abs)
647 remove.append(abs)
648 if ui.verbose or not exact:
648 if ui.verbose or not exact:
649 ui.status(_('removing %s\n') % ((pats and rel) or abs))
649 ui.status(_('removing %s\n') % ((pats and rel) or abs))
650 repo.add(add, wlock=wlock)
650 repo.add(add, wlock=wlock)
651 repo.remove(remove, wlock=wlock)
651 repo.remove(remove, wlock=wlock)
652
652
653 def annotate(ui, repo, *pats, **opts):
653 def annotate(ui, repo, *pats, **opts):
654 """show changeset information per file line
654 """show changeset information per file line
655
655
656 List changes in files, showing the revision id responsible for each line
656 List changes in files, showing the revision id responsible for each line
657
657
658 This command is useful to discover who did a change or when a change took
658 This command is useful to discover who did a change or when a change took
659 place.
659 place.
660
660
661 Without the -a option, annotate will avoid processing files it
661 Without the -a option, annotate will avoid processing files it
662 detects as binary. With -a, annotate will generate an annotation
662 detects as binary. With -a, annotate will generate an annotation
663 anyway, probably with undesirable results.
663 anyway, probably with undesirable results.
664 """
664 """
665 def getnode(rev):
665 def getnode(rev):
666 return short(repo.changelog.node(rev))
666 return short(repo.changelog.node(rev))
667
667
668 ucache = {}
668 ucache = {}
669 def getname(rev):
669 def getname(rev):
670 cl = repo.changelog.read(repo.changelog.node(rev))
670 cl = repo.changelog.read(repo.changelog.node(rev))
671 return trimuser(ui, cl[1], rev, ucache)
671 return trimuser(ui, cl[1], rev, ucache)
672
672
673 dcache = {}
673 dcache = {}
674 def getdate(rev):
674 def getdate(rev):
675 datestr = dcache.get(rev)
675 datestr = dcache.get(rev)
676 if datestr is None:
676 if datestr is None:
677 cl = repo.changelog.read(repo.changelog.node(rev))
677 cl = repo.changelog.read(repo.changelog.node(rev))
678 datestr = dcache[rev] = util.datestr(cl[2])
678 datestr = dcache[rev] = util.datestr(cl[2])
679 return datestr
679 return datestr
680
680
681 if not pats:
681 if not pats:
682 raise util.Abort(_('at least one file name or pattern required'))
682 raise util.Abort(_('at least one file name or pattern required'))
683
683
684 opmap = [['user', getname], ['number', str], ['changeset', getnode],
684 opmap = [['user', getname], ['number', str], ['changeset', getnode],
685 ['date', getdate]]
685 ['date', getdate]]
686 if not opts['user'] and not opts['changeset'] and not opts['date']:
686 if not opts['user'] and not opts['changeset'] and not opts['date']:
687 opts['number'] = 1
687 opts['number'] = 1
688
688
689 if opts['rev']:
689 if opts['rev']:
690 node = repo.changelog.lookup(opts['rev'])
690 node = repo.changelog.lookup(opts['rev'])
691 else:
691 else:
692 node = repo.dirstate.parents()[0]
692 node = repo.dirstate.parents()[0]
693 change = repo.changelog.read(node)
693 change = repo.changelog.read(node)
694 mmap = repo.manifest.read(change[0])
694 mmap = repo.manifest.read(change[0])
695
695
696 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
696 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
697 f = repo.file(abs)
697 f = repo.file(abs)
698 if not opts['text'] and util.binary(f.read(mmap[abs])):
698 if not opts['text'] and util.binary(f.read(mmap[abs])):
699 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
699 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
700 continue
700 continue
701
701
702 lines = f.annotate(mmap[abs])
702 lines = f.annotate(mmap[abs])
703 pieces = []
703 pieces = []
704
704
705 for o, f in opmap:
705 for o, f in opmap:
706 if opts[o]:
706 if opts[o]:
707 l = [f(n) for n, dummy in lines]
707 l = [f(n) for n, dummy in lines]
708 if l:
708 if l:
709 m = max(map(len, l))
709 m = max(map(len, l))
710 pieces.append(["%*s" % (m, x) for x in l])
710 pieces.append(["%*s" % (m, x) for x in l])
711
711
712 if pieces:
712 if pieces:
713 for p, l in zip(zip(*pieces), lines):
713 for p, l in zip(zip(*pieces), lines):
714 ui.write("%s: %s" % (" ".join(p), l[1]))
714 ui.write("%s: %s" % (" ".join(p), l[1]))
715
715
716 def archive(ui, repo, dest, **opts):
716 def archive(ui, repo, dest, **opts):
717 '''create unversioned archive of a repository revision
717 '''create unversioned archive of a repository revision
718
718
719 By default, the revision used is the parent of the working
719 By default, the revision used is the parent of the working
720 directory; use "-r" to specify a different revision.
720 directory; use "-r" to specify a different revision.
721
721
722 To specify the type of archive to create, use "-t". Valid
722 To specify the type of archive to create, use "-t". Valid
723 types are:
723 types are:
724
724
725 "files" (default): a directory full of files
725 "files" (default): a directory full of files
726 "tar": tar archive, uncompressed
726 "tar": tar archive, uncompressed
727 "tbz2": tar archive, compressed using bzip2
727 "tbz2": tar archive, compressed using bzip2
728 "tgz": tar archive, compressed using gzip
728 "tgz": tar archive, compressed using gzip
729 "uzip": zip archive, uncompressed
729 "uzip": zip archive, uncompressed
730 "zip": zip archive, compressed using deflate
730 "zip": zip archive, compressed using deflate
731
731
732 The exact name of the destination archive or directory is given
732 The exact name of the destination archive or directory is given
733 using a format string; see "hg help export" for details.
733 using a format string; see "hg help export" for details.
734
734
735 Each member added to an archive file has a directory prefix
735 Each member added to an archive file has a directory prefix
736 prepended. Use "-p" to specify a format string for the prefix.
736 prepended. Use "-p" to specify a format string for the prefix.
737 The default is the basename of the archive, with suffixes removed.
737 The default is the basename of the archive, with suffixes removed.
738 '''
738 '''
739
739
740 if opts['rev']:
740 if opts['rev']:
741 node = repo.lookup(opts['rev'])
741 node = repo.lookup(opts['rev'])
742 else:
742 else:
743 node, p2 = repo.dirstate.parents()
743 node, p2 = repo.dirstate.parents()
744 if p2 != nullid:
744 if p2 != nullid:
745 raise util.Abort(_('uncommitted merge - please provide a '
745 raise util.Abort(_('uncommitted merge - please provide a '
746 'specific revision'))
746 'specific revision'))
747
747
748 dest = make_filename(repo, repo.changelog, dest, node)
748 dest = make_filename(repo, repo.changelog, dest, node)
749 prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
749 prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
750 if os.path.realpath(dest) == repo.root:
750 if os.path.realpath(dest) == repo.root:
751 raise util.Abort(_('repository root cannot be destination'))
751 raise util.Abort(_('repository root cannot be destination'))
752 dummy, matchfn, dummy = matchpats(repo, [], opts)
752 dummy, matchfn, dummy = matchpats(repo, [], opts)
753 archival.archive(repo, dest, node, opts.get('type') or 'files',
753 archival.archive(repo, dest, node, opts.get('type') or 'files',
754 not opts['no_decode'], matchfn, prefix)
754 not opts['no_decode'], matchfn, prefix)
755
755
756 def backout(ui, repo, rev, **opts):
756 def backout(ui, repo, rev, **opts):
757 '''reverse effect of earlier changeset
757 '''reverse effect of earlier changeset
758
758
759 Commit the backed out changes as a new changeset.
759 Commit the backed out changes as a new changeset.
760
760
761 If you back out a changeset other than the tip, a new head is
761 If you back out a changeset other than the tip, a new head is
762 created. The --merge option remembers the parent of the working
762 created. The --merge option remembers the parent of the working
763 directory before starting the backout, then merges the new head
763 directory before starting the backout, then merges the new head
764 with it afterwards, to save you from doing this by hand. The
764 with it afterwards, to save you from doing this by hand. The
765 result of this merge is not committed, as for a normal merge.'''
765 result of this merge is not committed, as for a normal merge.'''
766
766
767 bail_if_changed(repo)
767 bail_if_changed(repo)
768 op1, op2 = repo.dirstate.parents()
768 op1, op2 = repo.dirstate.parents()
769 if op2 != nullid:
769 if op2 != nullid:
770 raise util.Abort(_('outstanding uncommitted merge'))
770 raise util.Abort(_('outstanding uncommitted merge'))
771 node = repo.lookup(rev)
771 node = repo.lookup(rev)
772 parent, p2 = repo.changelog.parents(node)
772 parent, p2 = repo.changelog.parents(node)
773 if parent == nullid:
773 if parent == nullid:
774 raise util.Abort(_('cannot back out a change with no parents'))
774 raise util.Abort(_('cannot back out a change with no parents'))
775 if p2 != nullid:
775 if p2 != nullid:
776 raise util.Abort(_('cannot back out a merge'))
776 raise util.Abort(_('cannot back out a merge'))
777 repo.update(node, force=True, show_stats=False)
777 repo.update(node, force=True, show_stats=False)
778 revert_opts = opts.copy()
778 revert_opts = opts.copy()
779 revert_opts['rev'] = hex(parent)
779 revert_opts['rev'] = hex(parent)
780 revert(ui, repo, **revert_opts)
780 revert(ui, repo, **revert_opts)
781 commit_opts = opts.copy()
781 commit_opts = opts.copy()
782 commit_opts['addremove'] = False
782 commit_opts['addremove'] = False
783 if not commit_opts['message'] and not commit_opts['logfile']:
783 if not commit_opts['message'] and not commit_opts['logfile']:
784 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
784 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
785 commit(ui, repo, **commit_opts)
785 commit(ui, repo, **commit_opts)
786 def nice(node):
786 def nice(node):
787 return '%d:%s' % (repo.changelog.rev(node), short(node))
787 return '%d:%s' % (repo.changelog.rev(node), short(node))
788 ui.status(_('changeset %s backs out changeset %s\n') %
788 ui.status(_('changeset %s backs out changeset %s\n') %
789 (nice(repo.changelog.tip()), nice(node)))
789 (nice(repo.changelog.tip()), nice(node)))
790 if opts['merge'] and op1 != node:
790 if opts['merge'] and op1 != node:
791 ui.status(_('merging with changeset %s\n') % nice(op1))
791 ui.status(_('merging with changeset %s\n') % nice(op1))
792 update(ui, repo, hex(op1), **opts)
792 update(ui, repo, hex(op1), **opts)
793
793
794 def bundle(ui, repo, fname, dest="default-push", **opts):
794 def bundle(ui, repo, fname, dest="default-push", **opts):
795 """create a changegroup file
795 """create a changegroup file
796
796
797 Generate a compressed changegroup file collecting all changesets
797 Generate a compressed changegroup file collecting all changesets
798 not found in the other repository.
798 not found in the other repository.
799
799
800 This file can then be transferred using conventional means and
800 This file can then be transferred using conventional means and
801 applied to another repository with the unbundle command. This is
801 applied to another repository with the unbundle command. This is
802 useful when native push and pull are not available or when
802 useful when native push and pull are not available or when
803 exporting an entire repository is undesirable. The standard file
803 exporting an entire repository is undesirable. The standard file
804 extension is ".hg".
804 extension is ".hg".
805
805
806 Unlike import/export, this exactly preserves all changeset
806 Unlike import/export, this exactly preserves all changeset
807 contents including permissions, rename data, and revision history.
807 contents including permissions, rename data, and revision history.
808 """
808 """
809 dest = ui.expandpath(dest)
809 dest = ui.expandpath(dest)
810 other = hg.repository(ui, dest)
810 other = hg.repository(ui, dest)
811 o = repo.findoutgoing(other, force=opts['force'])
811 o = repo.findoutgoing(other, force=opts['force'])
812 cg = repo.changegroup(o, 'bundle')
812 cg = repo.changegroup(o, 'bundle')
813 write_bundle(cg, fname)
813 write_bundle(cg, fname)
814
814
815 def cat(ui, repo, file1, *pats, **opts):
815 def cat(ui, repo, file1, *pats, **opts):
816 """output the latest or given revisions of files
816 """output the latest or given revisions of files
817
817
818 Print the specified files as they were at the given revision.
818 Print the specified files as they were at the given revision.
819 If no revision is given then the tip is used.
819 If no revision is given then the tip is used.
820
820
821 Output may be to a file, in which case the name of the file is
821 Output may be to a file, in which case the name of the file is
822 given using a format string. The formatting rules are the same as
822 given using a format string. The formatting rules are the same as
823 for the export command, with the following additions:
823 for the export command, with the following additions:
824
824
825 %s basename of file being printed
825 %s basename of file being printed
826 %d dirname of file being printed, or '.' if in repo root
826 %d dirname of file being printed, or '.' if in repo root
827 %p root-relative path name of file being printed
827 %p root-relative path name of file being printed
828 """
828 """
829 mf = {}
829 mf = {}
830 rev = opts['rev']
830 rev = opts['rev']
831 if rev:
831 if rev:
832 node = repo.lookup(rev)
832 node = repo.lookup(rev)
833 else:
833 else:
834 node = repo.changelog.tip()
834 node = repo.changelog.tip()
835 change = repo.changelog.read(node)
835 change = repo.changelog.read(node)
836 mf = repo.manifest.read(change[0])
836 mf = repo.manifest.read(change[0])
837 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
837 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
838 r = repo.file(abs)
838 r = repo.file(abs)
839 n = mf[abs]
839 n = mf[abs]
840 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
840 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
841 fp.write(r.read(n))
841 fp.write(r.read(n))
842
842
843 def clone(ui, source, dest=None, **opts):
843 def clone(ui, source, dest=None, **opts):
844 """make a copy of an existing repository
844 """make a copy of an existing repository
845
845
846 Create a copy of an existing repository in a new directory.
846 Create a copy of an existing repository in a new directory.
847
847
848 If no destination directory name is specified, it defaults to the
848 If no destination directory name is specified, it defaults to the
849 basename of the source.
849 basename of the source.
850
850
851 The location of the source is added to the new repository's
851 The location of the source is added to the new repository's
852 .hg/hgrc file, as the default to be used for future pulls.
852 .hg/hgrc file, as the default to be used for future pulls.
853
853
854 For efficiency, hardlinks are used for cloning whenever the source
854 For efficiency, hardlinks are used for cloning whenever the source
855 and destination are on the same filesystem. Some filesystems,
855 and destination are on the same filesystem. Some filesystems,
856 such as AFS, implement hardlinking incorrectly, but do not report
856 such as AFS, implement hardlinking incorrectly, but do not report
857 errors. In these cases, use the --pull option to avoid
857 errors. In these cases, use the --pull option to avoid
858 hardlinking.
858 hardlinking.
859
859
860 See pull for valid source format details.
860 See pull for valid source format details.
861 """
861 """
862 if dest is None:
862 if dest is None:
863 dest = os.path.basename(os.path.normpath(source))
863 dest = os.path.basename(os.path.normpath(source))
864
864
865 if os.path.exists(dest):
865 if os.path.exists(dest):
866 raise util.Abort(_("destination '%s' already exists"), dest)
866 raise util.Abort(_("destination '%s' already exists"), dest)
867
867
868 dest = os.path.realpath(dest)
868 dest = os.path.realpath(dest)
869
869
870 class Dircleanup(object):
870 class Dircleanup(object):
871 def __init__(self, dir_):
871 def __init__(self, dir_):
872 self.rmtree = shutil.rmtree
872 self.rmtree = shutil.rmtree
873 self.dir_ = dir_
873 self.dir_ = dir_
874 os.mkdir(dir_)
874 os.mkdir(dir_)
875 def close(self):
875 def close(self):
876 self.dir_ = None
876 self.dir_ = None
877 def __del__(self):
877 def __del__(self):
878 if self.dir_:
878 if self.dir_:
879 self.rmtree(self.dir_, True)
879 self.rmtree(self.dir_, True)
880
880
881 if opts['ssh']:
881 if opts['ssh']:
882 ui.setconfig("ui", "ssh", opts['ssh'])
882 ui.setconfig("ui", "ssh", opts['ssh'])
883 if opts['remotecmd']:
883 if opts['remotecmd']:
884 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
884 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
885
885
886 source = ui.expandpath(source)
886 source = ui.expandpath(source)
887
887
888 d = Dircleanup(dest)
888 d = Dircleanup(dest)
889 abspath = source
889 abspath = source
890 other = hg.repository(ui, source)
890 other = hg.repository(ui, source)
891
891
892 copy = False
892 copy = False
893 if other.dev() != -1:
893 if other.dev() != -1:
894 abspath = os.path.abspath(source)
894 abspath = os.path.abspath(source)
895 if not opts['pull'] and not opts['rev']:
895 if not opts['pull'] and not opts['rev']:
896 copy = True
896 copy = True
897
897
898 if copy:
898 if copy:
899 try:
899 try:
900 # we use a lock here because if we race with commit, we
900 # we use a lock here because if we race with commit, we
901 # can end up with extra data in the cloned revlogs that's
901 # can end up with extra data in the cloned revlogs that's
902 # not pointed to by changesets, thus causing verify to
902 # not pointed to by changesets, thus causing verify to
903 # fail
903 # fail
904 l1 = other.lock()
904 l1 = other.lock()
905 except lock.LockException:
905 except lock.LockException:
906 copy = False
906 copy = False
907
907
908 if copy:
908 if copy:
909 # we lock here to avoid premature writing to the target
909 # we lock here to avoid premature writing to the target
910 os.mkdir(os.path.join(dest, ".hg"))
910 os.mkdir(os.path.join(dest, ".hg"))
911 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
911 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
912
912
913 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
913 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
914 for f in files.split():
914 for f in files.split():
915 src = os.path.join(source, ".hg", f)
915 src = os.path.join(source, ".hg", f)
916 dst = os.path.join(dest, ".hg", f)
916 dst = os.path.join(dest, ".hg", f)
917 try:
917 try:
918 util.copyfiles(src, dst)
918 util.copyfiles(src, dst)
919 except OSError, inst:
919 except OSError, inst:
920 if inst.errno != errno.ENOENT:
920 if inst.errno != errno.ENOENT:
921 raise
921 raise
922
922
923 repo = hg.repository(ui, dest)
923 repo = hg.repository(ui, dest)
924
924
925 else:
925 else:
926 revs = None
926 revs = None
927 if opts['rev']:
927 if opts['rev']:
928 if not other.local():
928 if not other.local():
929 error = _("clone -r not supported yet for remote repositories.")
929 error = _("clone -r not supported yet for remote repositories.")
930 raise util.Abort(error)
930 raise util.Abort(error)
931 else:
931 else:
932 revs = [other.lookup(rev) for rev in opts['rev']]
932 revs = [other.lookup(rev) for rev in opts['rev']]
933 repo = hg.repository(ui, dest, create=1)
933 repo = hg.repository(ui, dest, create=1)
934 repo.pull(other, heads = revs)
934 repo.pull(other, heads = revs)
935
935
936 f = repo.opener("hgrc", "w", text=True)
936 f = repo.opener("hgrc", "w", text=True)
937 f.write("[paths]\n")
937 f.write("[paths]\n")
938 f.write("default = %s\n" % abspath)
938 f.write("default = %s\n" % abspath)
939 f.close()
939 f.close()
940
940
941 if not opts['noupdate']:
941 if not opts['noupdate']:
942 update(repo.ui, repo)
942 update(repo.ui, repo)
943
943
944 d.close()
944 d.close()
945
945
946 def commit(ui, repo, *pats, **opts):
946 def commit(ui, repo, *pats, **opts):
947 """commit the specified files or all outstanding changes
947 """commit the specified files or all outstanding changes
948
948
949 Commit changes to the given files into the repository.
949 Commit changes to the given files into the repository.
950
950
951 If a list of files is omitted, all changes reported by "hg status"
951 If a list of files is omitted, all changes reported by "hg status"
952 will be committed.
952 will be committed.
953
953
954 If no commit message is specified, the editor configured in your hgrc
954 If no commit message is specified, the editor configured in your hgrc
955 or in the EDITOR environment variable is started to enter a message.
955 or in the EDITOR environment variable is started to enter a message.
956 """
956 """
957 message = opts['message']
957 message = opts['message']
958 logfile = opts['logfile']
958 logfile = opts['logfile']
959
959
960 if message and logfile:
960 if message and logfile:
961 raise util.Abort(_('options --message and --logfile are mutually '
961 raise util.Abort(_('options --message and --logfile are mutually '
962 'exclusive'))
962 'exclusive'))
963 if not message and logfile:
963 if not message and logfile:
964 try:
964 try:
965 if logfile == '-':
965 if logfile == '-':
966 message = sys.stdin.read()
966 message = sys.stdin.read()
967 else:
967 else:
968 message = open(logfile).read()
968 message = open(logfile).read()
969 except IOError, inst:
969 except IOError, inst:
970 raise util.Abort(_("can't read commit message '%s': %s") %
970 raise util.Abort(_("can't read commit message '%s': %s") %
971 (logfile, inst.strerror))
971 (logfile, inst.strerror))
972
972
973 if opts['addremove']:
973 if opts['addremove']:
974 addremove_lock(ui, repo, pats, opts)
974 addremove_lock(ui, repo, pats, opts)
975 fns, match, anypats = matchpats(repo, pats, opts)
975 fns, match, anypats = matchpats(repo, pats, opts)
976 if pats:
976 if pats:
977 modified, added, removed, deleted, unknown = (
977 modified, added, removed, deleted, unknown = (
978 repo.changes(files=fns, match=match))
978 repo.changes(files=fns, match=match))
979 files = modified + added + removed
979 files = modified + added + removed
980 else:
980 else:
981 files = []
981 files = []
982 try:
982 try:
983 repo.commit(files, message, opts['user'], opts['date'], match)
983 repo.commit(files, message, opts['user'], opts['date'], match)
984 except ValueError, inst:
984 except ValueError, inst:
985 raise util.Abort(str(inst))
985 raise util.Abort(str(inst))
986
986
987 def docopy(ui, repo, pats, opts, wlock):
987 def docopy(ui, repo, pats, opts, wlock):
988 # called with the repo lock held
988 # called with the repo lock held
989 cwd = repo.getcwd()
989 cwd = repo.getcwd()
990 errors = 0
990 errors = 0
991 copied = []
991 copied = []
992 targets = {}
992 targets = {}
993
993
994 def okaytocopy(abs, rel, exact):
994 def okaytocopy(abs, rel, exact):
995 reasons = {'?': _('is not managed'),
995 reasons = {'?': _('is not managed'),
996 'a': _('has been marked for add'),
996 'a': _('has been marked for add'),
997 'r': _('has been marked for remove')}
997 'r': _('has been marked for remove')}
998 state = repo.dirstate.state(abs)
998 state = repo.dirstate.state(abs)
999 reason = reasons.get(state)
999 reason = reasons.get(state)
1000 if reason:
1000 if reason:
1001 if state == 'a':
1001 if state == 'a':
1002 origsrc = repo.dirstate.copied(abs)
1002 origsrc = repo.dirstate.copied(abs)
1003 if origsrc is not None:
1003 if origsrc is not None:
1004 return origsrc
1004 return origsrc
1005 if exact:
1005 if exact:
1006 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1006 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1007 else:
1007 else:
1008 return abs
1008 return abs
1009
1009
1010 def copy(origsrc, abssrc, relsrc, target, exact):
1010 def copy(origsrc, abssrc, relsrc, target, exact):
1011 abstarget = util.canonpath(repo.root, cwd, target)
1011 abstarget = util.canonpath(repo.root, cwd, target)
1012 reltarget = util.pathto(cwd, abstarget)
1012 reltarget = util.pathto(cwd, abstarget)
1013 prevsrc = targets.get(abstarget)
1013 prevsrc = targets.get(abstarget)
1014 if prevsrc is not None:
1014 if prevsrc is not None:
1015 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1015 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1016 (reltarget, abssrc, prevsrc))
1016 (reltarget, abssrc, prevsrc))
1017 return
1017 return
1018 if (not opts['after'] and os.path.exists(reltarget) or
1018 if (not opts['after'] and os.path.exists(reltarget) or
1019 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1019 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1020 if not opts['force']:
1020 if not opts['force']:
1021 ui.warn(_('%s: not overwriting - file exists\n') %
1021 ui.warn(_('%s: not overwriting - file exists\n') %
1022 reltarget)
1022 reltarget)
1023 return
1023 return
1024 if not opts['after']:
1024 if not opts['after']:
1025 os.unlink(reltarget)
1025 os.unlink(reltarget)
1026 if opts['after']:
1026 if opts['after']:
1027 if not os.path.exists(reltarget):
1027 if not os.path.exists(reltarget):
1028 return
1028 return
1029 else:
1029 else:
1030 targetdir = os.path.dirname(reltarget) or '.'
1030 targetdir = os.path.dirname(reltarget) or '.'
1031 if not os.path.isdir(targetdir):
1031 if not os.path.isdir(targetdir):
1032 os.makedirs(targetdir)
1032 os.makedirs(targetdir)
1033 try:
1033 try:
1034 restore = repo.dirstate.state(abstarget) == 'r'
1034 restore = repo.dirstate.state(abstarget) == 'r'
1035 if restore:
1035 if restore:
1036 repo.undelete([abstarget], wlock)
1036 repo.undelete([abstarget], wlock)
1037 try:
1037 try:
1038 shutil.copyfile(relsrc, reltarget)
1038 shutil.copyfile(relsrc, reltarget)
1039 shutil.copymode(relsrc, reltarget)
1039 shutil.copymode(relsrc, reltarget)
1040 restore = False
1040 restore = False
1041 finally:
1041 finally:
1042 if restore:
1042 if restore:
1043 repo.remove([abstarget], wlock)
1043 repo.remove([abstarget], wlock)
1044 except shutil.Error, inst:
1044 except shutil.Error, inst:
1045 raise util.Abort(str(inst))
1045 raise util.Abort(str(inst))
1046 except IOError, inst:
1046 except IOError, inst:
1047 if inst.errno == errno.ENOENT:
1047 if inst.errno == errno.ENOENT:
1048 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1048 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1049 else:
1049 else:
1050 ui.warn(_('%s: cannot copy - %s\n') %
1050 ui.warn(_('%s: cannot copy - %s\n') %
1051 (relsrc, inst.strerror))
1051 (relsrc, inst.strerror))
1052 errors += 1
1052 errors += 1
1053 return
1053 return
1054 if ui.verbose or not exact:
1054 if ui.verbose or not exact:
1055 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1055 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1056 targets[abstarget] = abssrc
1056 targets[abstarget] = abssrc
1057 if abstarget != origsrc:
1057 if abstarget != origsrc:
1058 repo.copy(origsrc, abstarget, wlock)
1058 repo.copy(origsrc, abstarget, wlock)
1059 copied.append((abssrc, relsrc, exact))
1059 copied.append((abssrc, relsrc, exact))
1060
1060
1061 def targetpathfn(pat, dest, srcs):
1061 def targetpathfn(pat, dest, srcs):
1062 if os.path.isdir(pat):
1062 if os.path.isdir(pat):
1063 abspfx = util.canonpath(repo.root, cwd, pat)
1063 abspfx = util.canonpath(repo.root, cwd, pat)
1064 if destdirexists:
1064 if destdirexists:
1065 striplen = len(os.path.split(abspfx)[0])
1065 striplen = len(os.path.split(abspfx)[0])
1066 else:
1066 else:
1067 striplen = len(abspfx)
1067 striplen = len(abspfx)
1068 if striplen:
1068 if striplen:
1069 striplen += len(os.sep)
1069 striplen += len(os.sep)
1070 res = lambda p: os.path.join(dest, p[striplen:])
1070 res = lambda p: os.path.join(dest, p[striplen:])
1071 elif destdirexists:
1071 elif destdirexists:
1072 res = lambda p: os.path.join(dest, os.path.basename(p))
1072 res = lambda p: os.path.join(dest, os.path.basename(p))
1073 else:
1073 else:
1074 res = lambda p: dest
1074 res = lambda p: dest
1075 return res
1075 return res
1076
1076
1077 def targetpathafterfn(pat, dest, srcs):
1077 def targetpathafterfn(pat, dest, srcs):
1078 if util.patkind(pat, None)[0]:
1078 if util.patkind(pat, None)[0]:
1079 # a mercurial pattern
1079 # a mercurial pattern
1080 res = lambda p: os.path.join(dest, os.path.basename(p))
1080 res = lambda p: os.path.join(dest, os.path.basename(p))
1081 else:
1081 else:
1082 abspfx = util.canonpath(repo.root, cwd, pat)
1082 abspfx = util.canonpath(repo.root, cwd, pat)
1083 if len(abspfx) < len(srcs[0][0]):
1083 if len(abspfx) < len(srcs[0][0]):
1084 # A directory. Either the target path contains the last
1084 # A directory. Either the target path contains the last
1085 # component of the source path or it does not.
1085 # component of the source path or it does not.
1086 def evalpath(striplen):
1086 def evalpath(striplen):
1087 score = 0
1087 score = 0
1088 for s in srcs:
1088 for s in srcs:
1089 t = os.path.join(dest, s[0][striplen:])
1089 t = os.path.join(dest, s[0][striplen:])
1090 if os.path.exists(t):
1090 if os.path.exists(t):
1091 score += 1
1091 score += 1
1092 return score
1092 return score
1093
1093
1094 striplen = len(abspfx)
1094 striplen = len(abspfx)
1095 if striplen:
1095 if striplen:
1096 striplen += len(os.sep)
1096 striplen += len(os.sep)
1097 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1097 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1098 score = evalpath(striplen)
1098 score = evalpath(striplen)
1099 striplen1 = len(os.path.split(abspfx)[0])
1099 striplen1 = len(os.path.split(abspfx)[0])
1100 if striplen1:
1100 if striplen1:
1101 striplen1 += len(os.sep)
1101 striplen1 += len(os.sep)
1102 if evalpath(striplen1) > score:
1102 if evalpath(striplen1) > score:
1103 striplen = striplen1
1103 striplen = striplen1
1104 res = lambda p: os.path.join(dest, p[striplen:])
1104 res = lambda p: os.path.join(dest, p[striplen:])
1105 else:
1105 else:
1106 # a file
1106 # a file
1107 if destdirexists:
1107 if destdirexists:
1108 res = lambda p: os.path.join(dest, os.path.basename(p))
1108 res = lambda p: os.path.join(dest, os.path.basename(p))
1109 else:
1109 else:
1110 res = lambda p: dest
1110 res = lambda p: dest
1111 return res
1111 return res
1112
1112
1113
1113
1114 pats = list(pats)
1114 pats = list(pats)
1115 if not pats:
1115 if not pats:
1116 raise util.Abort(_('no source or destination specified'))
1116 raise util.Abort(_('no source or destination specified'))
1117 if len(pats) == 1:
1117 if len(pats) == 1:
1118 raise util.Abort(_('no destination specified'))
1118 raise util.Abort(_('no destination specified'))
1119 dest = pats.pop()
1119 dest = pats.pop()
1120 destdirexists = os.path.isdir(dest)
1120 destdirexists = os.path.isdir(dest)
1121 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1121 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1122 raise util.Abort(_('with multiple sources, destination must be an '
1122 raise util.Abort(_('with multiple sources, destination must be an '
1123 'existing directory'))
1123 'existing directory'))
1124 if opts['after']:
1124 if opts['after']:
1125 tfn = targetpathafterfn
1125 tfn = targetpathafterfn
1126 else:
1126 else:
1127 tfn = targetpathfn
1127 tfn = targetpathfn
1128 copylist = []
1128 copylist = []
1129 for pat in pats:
1129 for pat in pats:
1130 srcs = []
1130 srcs = []
1131 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1131 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1132 origsrc = okaytocopy(abssrc, relsrc, exact)
1132 origsrc = okaytocopy(abssrc, relsrc, exact)
1133 if origsrc:
1133 if origsrc:
1134 srcs.append((origsrc, abssrc, relsrc, exact))
1134 srcs.append((origsrc, abssrc, relsrc, exact))
1135 if not srcs:
1135 if not srcs:
1136 continue
1136 continue
1137 copylist.append((tfn(pat, dest, srcs), srcs))
1137 copylist.append((tfn(pat, dest, srcs), srcs))
1138 if not copylist:
1138 if not copylist:
1139 raise util.Abort(_('no files to copy'))
1139 raise util.Abort(_('no files to copy'))
1140
1140
1141 for targetpath, srcs in copylist:
1141 for targetpath, srcs in copylist:
1142 for origsrc, abssrc, relsrc, exact in srcs:
1142 for origsrc, abssrc, relsrc, exact in srcs:
1143 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1143 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1144
1144
1145 if errors:
1145 if errors:
1146 ui.warn(_('(consider using --after)\n'))
1146 ui.warn(_('(consider using --after)\n'))
1147 return errors, copied
1147 return errors, copied
1148
1148
1149 def copy(ui, repo, *pats, **opts):
1149 def copy(ui, repo, *pats, **opts):
1150 """mark files as copied for the next commit
1150 """mark files as copied for the next commit
1151
1151
1152 Mark dest as having copies of source files. If dest is a
1152 Mark dest as having copies of source files. If dest is a
1153 directory, copies are put in that directory. If dest is a file,
1153 directory, copies are put in that directory. If dest is a file,
1154 there can only be one source.
1154 there can only be one source.
1155
1155
1156 By default, this command copies the contents of files as they
1156 By default, this command copies the contents of files as they
1157 stand in the working directory. If invoked with --after, the
1157 stand in the working directory. If invoked with --after, the
1158 operation is recorded, but no copying is performed.
1158 operation is recorded, but no copying is performed.
1159
1159
1160 This command takes effect in the next commit.
1160 This command takes effect in the next commit.
1161
1161
1162 NOTE: This command should be treated as experimental. While it
1162 NOTE: This command should be treated as experimental. While it
1163 should properly record copied files, this information is not yet
1163 should properly record copied files, this information is not yet
1164 fully used by merge, nor fully reported by log.
1164 fully used by merge, nor fully reported by log.
1165 """
1165 """
1166 wlock = repo.wlock(0)
1166 wlock = repo.wlock(0)
1167 errs, copied = docopy(ui, repo, pats, opts, wlock)
1167 errs, copied = docopy(ui, repo, pats, opts, wlock)
1168 return errs
1168 return errs
1169
1169
1170 def debugancestor(ui, index, rev1, rev2):
1170 def debugancestor(ui, index, rev1, rev2):
1171 """find the ancestor revision of two revisions in a given index"""
1171 """find the ancestor revision of two revisions in a given index"""
1172 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1172 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1173 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1173 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1174 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1174 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1175
1175
1176 def debugcomplete(ui, cmd='', **opts):
1176 def debugcomplete(ui, cmd='', **opts):
1177 """returns the completion list associated with the given command"""
1177 """returns the completion list associated with the given command"""
1178
1178
1179 if opts['options']:
1179 if opts['options']:
1180 options = []
1180 options = []
1181 otables = [globalopts]
1181 otables = [globalopts]
1182 if cmd:
1182 if cmd:
1183 aliases, entry = find(cmd)
1183 aliases, entry = find(cmd)
1184 otables.append(entry[1])
1184 otables.append(entry[1])
1185 for t in otables:
1185 for t in otables:
1186 for o in t:
1186 for o in t:
1187 if o[0]:
1187 if o[0]:
1188 options.append('-%s' % o[0])
1188 options.append('-%s' % o[0])
1189 options.append('--%s' % o[1])
1189 options.append('--%s' % o[1])
1190 ui.write("%s\n" % "\n".join(options))
1190 ui.write("%s\n" % "\n".join(options))
1191 return
1191 return
1192
1192
1193 clist = findpossible(cmd).keys()
1193 clist = findpossible(cmd).keys()
1194 clist.sort()
1194 clist.sort()
1195 ui.write("%s\n" % "\n".join(clist))
1195 ui.write("%s\n" % "\n".join(clist))
1196
1196
1197 def debugrebuildstate(ui, repo, rev=None):
1197 def debugrebuildstate(ui, repo, rev=None):
1198 """rebuild the dirstate as it would look like for the given revision"""
1198 """rebuild the dirstate as it would look like for the given revision"""
1199 if not rev:
1199 if not rev:
1200 rev = repo.changelog.tip()
1200 rev = repo.changelog.tip()
1201 else:
1201 else:
1202 rev = repo.lookup(rev)
1202 rev = repo.lookup(rev)
1203 change = repo.changelog.read(rev)
1203 change = repo.changelog.read(rev)
1204 n = change[0]
1204 n = change[0]
1205 files = repo.manifest.readflags(n)
1205 files = repo.manifest.readflags(n)
1206 wlock = repo.wlock()
1206 wlock = repo.wlock()
1207 repo.dirstate.rebuild(rev, files.iteritems())
1207 repo.dirstate.rebuild(rev, files.iteritems())
1208
1208
1209 def debugcheckstate(ui, repo):
1209 def debugcheckstate(ui, repo):
1210 """validate the correctness of the current dirstate"""
1210 """validate the correctness of the current dirstate"""
1211 parent1, parent2 = repo.dirstate.parents()
1211 parent1, parent2 = repo.dirstate.parents()
1212 repo.dirstate.read()
1212 repo.dirstate.read()
1213 dc = repo.dirstate.map
1213 dc = repo.dirstate.map
1214 keys = dc.keys()
1214 keys = dc.keys()
1215 keys.sort()
1215 keys.sort()
1216 m1n = repo.changelog.read(parent1)[0]
1216 m1n = repo.changelog.read(parent1)[0]
1217 m2n = repo.changelog.read(parent2)[0]
1217 m2n = repo.changelog.read(parent2)[0]
1218 m1 = repo.manifest.read(m1n)
1218 m1 = repo.manifest.read(m1n)
1219 m2 = repo.manifest.read(m2n)
1219 m2 = repo.manifest.read(m2n)
1220 errors = 0
1220 errors = 0
1221 for f in dc:
1221 for f in dc:
1222 state = repo.dirstate.state(f)
1222 state = repo.dirstate.state(f)
1223 if state in "nr" and f not in m1:
1223 if state in "nr" and f not in m1:
1224 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1224 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1225 errors += 1
1225 errors += 1
1226 if state in "a" and f in m1:
1226 if state in "a" and f in m1:
1227 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1227 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1228 errors += 1
1228 errors += 1
1229 if state in "m" and f not in m1 and f not in m2:
1229 if state in "m" and f not in m1 and f not in m2:
1230 ui.warn(_("%s in state %s, but not in either manifest\n") %
1230 ui.warn(_("%s in state %s, but not in either manifest\n") %
1231 (f, state))
1231 (f, state))
1232 errors += 1
1232 errors += 1
1233 for f in m1:
1233 for f in m1:
1234 state = repo.dirstate.state(f)
1234 state = repo.dirstate.state(f)
1235 if state not in "nrm":
1235 if state not in "nrm":
1236 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1236 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1237 errors += 1
1237 errors += 1
1238 if errors:
1238 if errors:
1239 error = _(".hg/dirstate inconsistent with current parent's manifest")
1239 error = _(".hg/dirstate inconsistent with current parent's manifest")
1240 raise util.Abort(error)
1240 raise util.Abort(error)
1241
1241
1242 def debugconfig(ui, repo):
1242 def debugconfig(ui, repo):
1243 """show combined config settings from all hgrc files"""
1243 """show combined config settings from all hgrc files"""
1244 for section, name, value in ui.walkconfig():
1244 for section, name, value in ui.walkconfig():
1245 ui.write('%s.%s=%s\n' % (section, name, value))
1245 ui.write('%s.%s=%s\n' % (section, name, value))
1246
1246
1247 def debugsetparents(ui, repo, rev1, rev2=None):
1247 def debugsetparents(ui, repo, rev1, rev2=None):
1248 """manually set the parents of the current working directory
1248 """manually set the parents of the current working directory
1249
1249
1250 This is useful for writing repository conversion tools, but should
1250 This is useful for writing repository conversion tools, but should
1251 be used with care.
1251 be used with care.
1252 """
1252 """
1253
1253
1254 if not rev2:
1254 if not rev2:
1255 rev2 = hex(nullid)
1255 rev2 = hex(nullid)
1256
1256
1257 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1257 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1258
1258
1259 def debugstate(ui, repo):
1259 def debugstate(ui, repo):
1260 """show the contents of the current dirstate"""
1260 """show the contents of the current dirstate"""
1261 repo.dirstate.read()
1261 repo.dirstate.read()
1262 dc = repo.dirstate.map
1262 dc = repo.dirstate.map
1263 keys = dc.keys()
1263 keys = dc.keys()
1264 keys.sort()
1264 keys.sort()
1265 for file_ in keys:
1265 for file_ in keys:
1266 ui.write("%c %3o %10d %s %s\n"
1266 ui.write("%c %3o %10d %s %s\n"
1267 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1267 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1268 time.strftime("%x %X",
1268 time.strftime("%x %X",
1269 time.localtime(dc[file_][3])), file_))
1269 time.localtime(dc[file_][3])), file_))
1270 for f in repo.dirstate.copies:
1270 for f in repo.dirstate.copies:
1271 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1271 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1272
1272
1273 def debugdata(ui, file_, rev):
1273 def debugdata(ui, file_, rev):
1274 """dump the contents of an data file revision"""
1274 """dump the contents of an data file revision"""
1275 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1275 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1276 file_[:-2] + ".i", file_, 0)
1276 file_[:-2] + ".i", file_, 0)
1277 try:
1277 try:
1278 ui.write(r.revision(r.lookup(rev)))
1278 ui.write(r.revision(r.lookup(rev)))
1279 except KeyError:
1279 except KeyError:
1280 raise util.Abort(_('invalid revision identifier %s'), rev)
1280 raise util.Abort(_('invalid revision identifier %s'), rev)
1281
1281
1282 def debugindex(ui, file_):
1282 def debugindex(ui, file_):
1283 """dump the contents of an index file"""
1283 """dump the contents of an index file"""
1284 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1284 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1285 ui.write(" rev offset length base linkrev" +
1285 ui.write(" rev offset length base linkrev" +
1286 " nodeid p1 p2\n")
1286 " nodeid p1 p2\n")
1287 for i in range(r.count()):
1287 for i in range(r.count()):
1288 node = r.node(i)
1288 node = r.node(i)
1289 pp = r.parents(node)
1289 pp = r.parents(node)
1290 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1290 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1291 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1291 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1292 short(node), short(pp[0]), short(pp[1])))
1292 short(node), short(pp[0]), short(pp[1])))
1293
1293
1294 def debugindexdot(ui, file_):
1294 def debugindexdot(ui, file_):
1295 """dump an index DAG as a .dot file"""
1295 """dump an index DAG as a .dot file"""
1296 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1296 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1297 ui.write("digraph G {\n")
1297 ui.write("digraph G {\n")
1298 for i in range(r.count()):
1298 for i in range(r.count()):
1299 e = r.index[i]
1299 e = r.index[i]
1300 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1300 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1301 if e[5] != nullid:
1301 if e[5] != nullid:
1302 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1302 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1303 ui.write("}\n")
1303 ui.write("}\n")
1304
1304
1305 def debugrename(ui, repo, file, rev=None):
1305 def debugrename(ui, repo, file, rev=None):
1306 """dump rename information"""
1306 """dump rename information"""
1307 r = repo.file(relpath(repo, [file])[0])
1307 r = repo.file(relpath(repo, [file])[0])
1308 if rev:
1308 if rev:
1309 try:
1309 try:
1310 # assume all revision numbers are for changesets
1310 # assume all revision numbers are for changesets
1311 n = repo.lookup(rev)
1311 n = repo.lookup(rev)
1312 change = repo.changelog.read(n)
1312 change = repo.changelog.read(n)
1313 m = repo.manifest.read(change[0])
1313 m = repo.manifest.read(change[0])
1314 n = m[relpath(repo, [file])[0]]
1314 n = m[relpath(repo, [file])[0]]
1315 except (hg.RepoError, KeyError):
1315 except (hg.RepoError, KeyError):
1316 n = r.lookup(rev)
1316 n = r.lookup(rev)
1317 else:
1317 else:
1318 n = r.tip()
1318 n = r.tip()
1319 m = r.renamed(n)
1319 m = r.renamed(n)
1320 if m:
1320 if m:
1321 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1321 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1322 else:
1322 else:
1323 ui.write(_("not renamed\n"))
1323 ui.write(_("not renamed\n"))
1324
1324
1325 def debugwalk(ui, repo, *pats, **opts):
1325 def debugwalk(ui, repo, *pats, **opts):
1326 """show how files match on given patterns"""
1326 """show how files match on given patterns"""
1327 items = list(walk(repo, pats, opts))
1327 items = list(walk(repo, pats, opts))
1328 if not items:
1328 if not items:
1329 return
1329 return
1330 fmt = '%%s %%-%ds %%-%ds %%s' % (
1330 fmt = '%%s %%-%ds %%-%ds %%s' % (
1331 max([len(abs) for (src, abs, rel, exact) in items]),
1331 max([len(abs) for (src, abs, rel, exact) in items]),
1332 max([len(rel) for (src, abs, rel, exact) in items]))
1332 max([len(rel) for (src, abs, rel, exact) in items]))
1333 for src, abs, rel, exact in items:
1333 for src, abs, rel, exact in items:
1334 line = fmt % (src, abs, rel, exact and 'exact' or '')
1334 line = fmt % (src, abs, rel, exact and 'exact' or '')
1335 ui.write("%s\n" % line.rstrip())
1335 ui.write("%s\n" % line.rstrip())
1336
1336
1337 def diff(ui, repo, *pats, **opts):
1337 def diff(ui, repo, *pats, **opts):
1338 """diff repository (or selected files)
1338 """diff repository (or selected files)
1339
1339
1340 Show differences between revisions for the specified files.
1340 Show differences between revisions for the specified files.
1341
1341
1342 Differences between files are shown using the unified diff format.
1342 Differences between files are shown using the unified diff format.
1343
1343
1344 When two revision arguments are given, then changes are shown
1344 When two revision arguments are given, then changes are shown
1345 between those revisions. If only one revision is specified then
1345 between those revisions. If only one revision is specified then
1346 that revision is compared to the working directory, and, when no
1346 that revision is compared to the working directory, and, when no
1347 revisions are specified, the working directory files are compared
1347 revisions are specified, the working directory files are compared
1348 to its parent.
1348 to its parent.
1349
1349
1350 Without the -a option, diff will avoid generating diffs of files
1350 Without the -a option, diff will avoid generating diffs of files
1351 it detects as binary. With -a, diff will generate a diff anyway,
1351 it detects as binary. With -a, diff will generate a diff anyway,
1352 probably with undesirable results.
1352 probably with undesirable results.
1353 """
1353 """
1354 node1, node2 = None, None
1354 node1, node2 = None, None
1355 revs = [repo.lookup(x) for x in opts['rev']]
1355 revs = [repo.lookup(x) for x in opts['rev']]
1356
1356
1357 if len(revs) > 0:
1357 if len(revs) > 0:
1358 node1 = revs[0]
1358 node1 = revs[0]
1359 if len(revs) > 1:
1359 if len(revs) > 1:
1360 node2 = revs[1]
1360 node2 = revs[1]
1361 if len(revs) > 2:
1361 if len(revs) > 2:
1362 raise util.Abort(_("too many revisions to diff"))
1362 raise util.Abort(_("too many revisions to diff"))
1363
1363
1364 fns, matchfn, anypats = matchpats(repo, pats, opts)
1364 fns, matchfn, anypats = matchpats(repo, pats, opts)
1365
1365
1366 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1366 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1367 text=opts['text'], opts=opts)
1367 text=opts['text'], opts=opts)
1368
1368
1369 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1369 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1370 node = repo.lookup(changeset)
1370 node = repo.lookup(changeset)
1371 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1371 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1372 if opts['switch_parent']:
1372 if opts['switch_parent']:
1373 parents.reverse()
1373 parents.reverse()
1374 prev = (parents and parents[0]) or nullid
1374 prev = (parents and parents[0]) or nullid
1375 change = repo.changelog.read(node)
1375 change = repo.changelog.read(node)
1376
1376
1377 fp = make_file(repo, repo.changelog, opts['output'],
1377 fp = make_file(repo, repo.changelog, opts['output'],
1378 node=node, total=total, seqno=seqno,
1378 node=node, total=total, seqno=seqno,
1379 revwidth=revwidth)
1379 revwidth=revwidth)
1380 if fp != sys.stdout:
1380 if fp != sys.stdout:
1381 ui.note("%s\n" % fp.name)
1381 ui.note("%s\n" % fp.name)
1382
1382
1383 fp.write("# HG changeset patch\n")
1383 fp.write("# HG changeset patch\n")
1384 fp.write("# User %s\n" % change[1])
1384 fp.write("# User %s\n" % change[1])
1385 fp.write("# Node ID %s\n" % hex(node))
1385 fp.write("# Node ID %s\n" % hex(node))
1386 fp.write("# Parent %s\n" % hex(prev))
1386 fp.write("# Parent %s\n" % hex(prev))
1387 if len(parents) > 1:
1387 if len(parents) > 1:
1388 fp.write("# Parent %s\n" % hex(parents[1]))
1388 fp.write("# Parent %s\n" % hex(parents[1]))
1389 fp.write(change[4].rstrip())
1389 fp.write(change[4].rstrip())
1390 fp.write("\n\n")
1390 fp.write("\n\n")
1391
1391
1392 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1392 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1393 if fp != sys.stdout:
1393 if fp != sys.stdout:
1394 fp.close()
1394 fp.close()
1395
1395
1396 def export(ui, repo, *changesets, **opts):
1396 def export(ui, repo, *changesets, **opts):
1397 """dump the header and diffs for one or more changesets
1397 """dump the header and diffs for one or more changesets
1398
1398
1399 Print the changeset header and diffs for one or more revisions.
1399 Print the changeset header and diffs for one or more revisions.
1400
1400
1401 The information shown in the changeset header is: author,
1401 The information shown in the changeset header is: author,
1402 changeset hash, parent and commit comment.
1402 changeset hash, parent and commit comment.
1403
1403
1404 Output may be to a file, in which case the name of the file is
1404 Output may be to a file, in which case the name of the file is
1405 given using a format string. The formatting rules are as follows:
1405 given using a format string. The formatting rules are as follows:
1406
1406
1407 %% literal "%" character
1407 %% literal "%" character
1408 %H changeset hash (40 bytes of hexadecimal)
1408 %H changeset hash (40 bytes of hexadecimal)
1409 %N number of patches being generated
1409 %N number of patches being generated
1410 %R changeset revision number
1410 %R changeset revision number
1411 %b basename of the exporting repository
1411 %b basename of the exporting repository
1412 %h short-form changeset hash (12 bytes of hexadecimal)
1412 %h short-form changeset hash (12 bytes of hexadecimal)
1413 %n zero-padded sequence number, starting at 1
1413 %n zero-padded sequence number, starting at 1
1414 %r zero-padded changeset revision number
1414 %r zero-padded changeset revision number
1415
1415
1416 Without the -a option, export will avoid generating diffs of files
1416 Without the -a option, export will avoid generating diffs of files
1417 it detects as binary. With -a, export will generate a diff anyway,
1417 it detects as binary. With -a, export will generate a diff anyway,
1418 probably with undesirable results.
1418 probably with undesirable results.
1419
1419
1420 With the --switch-parent option, the diff will be against the second
1420 With the --switch-parent option, the diff will be against the second
1421 parent. It can be useful to review a merge.
1421 parent. It can be useful to review a merge.
1422 """
1422 """
1423 if not changesets:
1423 if not changesets:
1424 raise util.Abort(_("export requires at least one changeset"))
1424 raise util.Abort(_("export requires at least one changeset"))
1425 seqno = 0
1425 seqno = 0
1426 revs = list(revrange(ui, repo, changesets))
1426 revs = list(revrange(ui, repo, changesets))
1427 total = len(revs)
1427 total = len(revs)
1428 revwidth = max(map(len, revs))
1428 revwidth = max(map(len, revs))
1429 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1429 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1430 ui.note(msg)
1430 ui.note(msg)
1431 for cset in revs:
1431 for cset in revs:
1432 seqno += 1
1432 seqno += 1
1433 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1433 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1434
1434
1435 def forget(ui, repo, *pats, **opts):
1435 def forget(ui, repo, *pats, **opts):
1436 """don't add the specified files on the next commit (DEPRECATED)
1436 """don't add the specified files on the next commit (DEPRECATED)
1437
1437
1438 (DEPRECATED)
1438 (DEPRECATED)
1439 Undo an 'hg add' scheduled for the next commit.
1439 Undo an 'hg add' scheduled for the next commit.
1440
1440
1441 This command is now deprecated and will be removed in a future
1441 This command is now deprecated and will be removed in a future
1442 release. Please use revert instead.
1442 release. Please use revert instead.
1443 """
1443 """
1444 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1444 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1445 forget = []
1445 forget = []
1446 for src, abs, rel, exact in walk(repo, pats, opts):
1446 for src, abs, rel, exact in walk(repo, pats, opts):
1447 if repo.dirstate.state(abs) == 'a':
1447 if repo.dirstate.state(abs) == 'a':
1448 forget.append(abs)
1448 forget.append(abs)
1449 if ui.verbose or not exact:
1449 if ui.verbose or not exact:
1450 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1450 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1451 repo.forget(forget)
1451 repo.forget(forget)
1452
1452
1453 def grep(ui, repo, pattern, *pats, **opts):
1453 def grep(ui, repo, pattern, *pats, **opts):
1454 """search for a pattern in specified files and revisions
1454 """search for a pattern in specified files and revisions
1455
1455
1456 Search revisions of files for a regular expression.
1456 Search revisions of files for a regular expression.
1457
1457
1458 This command behaves differently than Unix grep. It only accepts
1458 This command behaves differently than Unix grep. It only accepts
1459 Python/Perl regexps. It searches repository history, not the
1459 Python/Perl regexps. It searches repository history, not the
1460 working directory. It always prints the revision number in which
1460 working directory. It always prints the revision number in which
1461 a match appears.
1461 a match appears.
1462
1462
1463 By default, grep only prints output for the first revision of a
1463 By default, grep only prints output for the first revision of a
1464 file in which it finds a match. To get it to print every revision
1464 file in which it finds a match. To get it to print every revision
1465 that contains a change in match status ("-" for a match that
1465 that contains a change in match status ("-" for a match that
1466 becomes a non-match, or "+" for a non-match that becomes a match),
1466 becomes a non-match, or "+" for a non-match that becomes a match),
1467 use the --all flag.
1467 use the --all flag.
1468 """
1468 """
1469 reflags = 0
1469 reflags = 0
1470 if opts['ignore_case']:
1470 if opts['ignore_case']:
1471 reflags |= re.I
1471 reflags |= re.I
1472 regexp = re.compile(pattern, reflags)
1472 regexp = re.compile(pattern, reflags)
1473 sep, eol = ':', '\n'
1473 sep, eol = ':', '\n'
1474 if opts['print0']:
1474 if opts['print0']:
1475 sep = eol = '\0'
1475 sep = eol = '\0'
1476
1476
1477 fcache = {}
1477 fcache = {}
1478 def getfile(fn):
1478 def getfile(fn):
1479 if fn not in fcache:
1479 if fn not in fcache:
1480 fcache[fn] = repo.file(fn)
1480 fcache[fn] = repo.file(fn)
1481 return fcache[fn]
1481 return fcache[fn]
1482
1482
1483 def matchlines(body):
1483 def matchlines(body):
1484 begin = 0
1484 begin = 0
1485 linenum = 0
1485 linenum = 0
1486 while True:
1486 while True:
1487 match = regexp.search(body, begin)
1487 match = regexp.search(body, begin)
1488 if not match:
1488 if not match:
1489 break
1489 break
1490 mstart, mend = match.span()
1490 mstart, mend = match.span()
1491 linenum += body.count('\n', begin, mstart) + 1
1491 linenum += body.count('\n', begin, mstart) + 1
1492 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1492 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1493 lend = body.find('\n', mend)
1493 lend = body.find('\n', mend)
1494 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1494 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1495 begin = lend + 1
1495 begin = lend + 1
1496
1496
1497 class linestate(object):
1497 class linestate(object):
1498 def __init__(self, line, linenum, colstart, colend):
1498 def __init__(self, line, linenum, colstart, colend):
1499 self.line = line
1499 self.line = line
1500 self.linenum = linenum
1500 self.linenum = linenum
1501 self.colstart = colstart
1501 self.colstart = colstart
1502 self.colend = colend
1502 self.colend = colend
1503 def __eq__(self, other):
1503 def __eq__(self, other):
1504 return self.line == other.line
1504 return self.line == other.line
1505 def __hash__(self):
1505 def __hash__(self):
1506 return hash(self.line)
1506 return hash(self.line)
1507
1507
1508 matches = {}
1508 matches = {}
1509 def grepbody(fn, rev, body):
1509 def grepbody(fn, rev, body):
1510 matches[rev].setdefault(fn, {})
1510 matches[rev].setdefault(fn, {})
1511 m = matches[rev][fn]
1511 m = matches[rev][fn]
1512 for lnum, cstart, cend, line in matchlines(body):
1512 for lnum, cstart, cend, line in matchlines(body):
1513 s = linestate(line, lnum, cstart, cend)
1513 s = linestate(line, lnum, cstart, cend)
1514 m[s] = s
1514 m[s] = s
1515
1515
1516 # FIXME: prev isn't used, why ?
1516 # FIXME: prev isn't used, why ?
1517 prev = {}
1517 prev = {}
1518 ucache = {}
1518 ucache = {}
1519 def display(fn, rev, states, prevstates):
1519 def display(fn, rev, states, prevstates):
1520 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1520 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1521 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1521 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1522 counts = {'-': 0, '+': 0}
1522 counts = {'-': 0, '+': 0}
1523 filerevmatches = {}
1523 filerevmatches = {}
1524 for l in diff:
1524 for l in diff:
1525 if incrementing or not opts['all']:
1525 if incrementing or not opts['all']:
1526 change = ((l in prevstates) and '-') or '+'
1526 change = ((l in prevstates) and '-') or '+'
1527 r = rev
1527 r = rev
1528 else:
1528 else:
1529 change = ((l in states) and '-') or '+'
1529 change = ((l in states) and '-') or '+'
1530 r = prev[fn]
1530 r = prev[fn]
1531 cols = [fn, str(rev)]
1531 cols = [fn, str(rev)]
1532 if opts['line_number']:
1532 if opts['line_number']:
1533 cols.append(str(l.linenum))
1533 cols.append(str(l.linenum))
1534 if opts['all']:
1534 if opts['all']:
1535 cols.append(change)
1535 cols.append(change)
1536 if opts['user']:
1536 if opts['user']:
1537 cols.append(trimuser(ui, getchange(rev)[1], rev,
1537 cols.append(trimuser(ui, getchange(rev)[1], rev,
1538 ucache))
1538 ucache))
1539 if opts['files_with_matches']:
1539 if opts['files_with_matches']:
1540 c = (fn, rev)
1540 c = (fn, rev)
1541 if c in filerevmatches:
1541 if c in filerevmatches:
1542 continue
1542 continue
1543 filerevmatches[c] = 1
1543 filerevmatches[c] = 1
1544 else:
1544 else:
1545 cols.append(l.line)
1545 cols.append(l.line)
1546 ui.write(sep.join(cols), eol)
1546 ui.write(sep.join(cols), eol)
1547 counts[change] += 1
1547 counts[change] += 1
1548 return counts['+'], counts['-']
1548 return counts['+'], counts['-']
1549
1549
1550 fstate = {}
1550 fstate = {}
1551 skip = {}
1551 skip = {}
1552 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1552 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1553 count = 0
1553 count = 0
1554 incrementing = False
1554 incrementing = False
1555 for st, rev, fns in changeiter:
1555 for st, rev, fns in changeiter:
1556 if st == 'window':
1556 if st == 'window':
1557 incrementing = rev
1557 incrementing = rev
1558 matches.clear()
1558 matches.clear()
1559 elif st == 'add':
1559 elif st == 'add':
1560 change = repo.changelog.read(repo.lookup(str(rev)))
1560 change = repo.changelog.read(repo.lookup(str(rev)))
1561 mf = repo.manifest.read(change[0])
1561 mf = repo.manifest.read(change[0])
1562 matches[rev] = {}
1562 matches[rev] = {}
1563 for fn in fns:
1563 for fn in fns:
1564 if fn in skip:
1564 if fn in skip:
1565 continue
1565 continue
1566 fstate.setdefault(fn, {})
1566 fstate.setdefault(fn, {})
1567 try:
1567 try:
1568 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1568 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1569 except KeyError:
1569 except KeyError:
1570 pass
1570 pass
1571 elif st == 'iter':
1571 elif st == 'iter':
1572 states = matches[rev].items()
1572 states = matches[rev].items()
1573 states.sort()
1573 states.sort()
1574 for fn, m in states:
1574 for fn, m in states:
1575 if fn in skip:
1575 if fn in skip:
1576 continue
1576 continue
1577 if incrementing or not opts['all'] or fstate[fn]:
1577 if incrementing or not opts['all'] or fstate[fn]:
1578 pos, neg = display(fn, rev, m, fstate[fn])
1578 pos, neg = display(fn, rev, m, fstate[fn])
1579 count += pos + neg
1579 count += pos + neg
1580 if pos and not opts['all']:
1580 if pos and not opts['all']:
1581 skip[fn] = True
1581 skip[fn] = True
1582 fstate[fn] = m
1582 fstate[fn] = m
1583 prev[fn] = rev
1583 prev[fn] = rev
1584
1584
1585 if not incrementing:
1585 if not incrementing:
1586 fstate = fstate.items()
1586 fstate = fstate.items()
1587 fstate.sort()
1587 fstate.sort()
1588 for fn, state in fstate:
1588 for fn, state in fstate:
1589 if fn in skip:
1589 if fn in skip:
1590 continue
1590 continue
1591 display(fn, rev, {}, state)
1591 display(fn, rev, {}, state)
1592 return (count == 0 and 1) or 0
1592 return (count == 0 and 1) or 0
1593
1593
1594 def heads(ui, repo, **opts):
1594 def heads(ui, repo, **opts):
1595 """show current repository heads
1595 """show current repository heads
1596
1596
1597 Show all repository head changesets.
1597 Show all repository head changesets.
1598
1598
1599 Repository "heads" are changesets that don't have children
1599 Repository "heads" are changesets that don't have children
1600 changesets. They are where development generally takes place and
1600 changesets. They are where development generally takes place and
1601 are the usual targets for update and merge operations.
1601 are the usual targets for update and merge operations.
1602 """
1602 """
1603 if opts['rev']:
1603 if opts['rev']:
1604 heads = repo.heads(repo.lookup(opts['rev']))
1604 heads = repo.heads(repo.lookup(opts['rev']))
1605 else:
1605 else:
1606 heads = repo.heads()
1606 heads = repo.heads()
1607 br = None
1607 br = None
1608 if opts['branches']:
1608 if opts['branches']:
1609 br = repo.branchlookup(heads)
1609 br = repo.branchlookup(heads)
1610 displayer = show_changeset(ui, repo, opts)
1610 displayer = show_changeset(ui, repo, opts)
1611 for n in heads:
1611 for n in heads:
1612 displayer.show(changenode=n, brinfo=br)
1612 displayer.show(changenode=n, brinfo=br)
1613
1613
1614 def identify(ui, repo):
1614 def identify(ui, repo):
1615 """print information about the working copy
1615 """print information about the working copy
1616
1616
1617 Print a short summary of the current state of the repo.
1617 Print a short summary of the current state of the repo.
1618
1618
1619 This summary identifies the repository state using one or two parent
1619 This summary identifies the repository state using one or two parent
1620 hash identifiers, followed by a "+" if there are uncommitted changes
1620 hash identifiers, followed by a "+" if there are uncommitted changes
1621 in the working directory, followed by a list of tags for this revision.
1621 in the working directory, followed by a list of tags for this revision.
1622 """
1622 """
1623 parents = [p for p in repo.dirstate.parents() if p != nullid]
1623 parents = [p for p in repo.dirstate.parents() if p != nullid]
1624 if not parents:
1624 if not parents:
1625 ui.write(_("unknown\n"))
1625 ui.write(_("unknown\n"))
1626 return
1626 return
1627
1627
1628 hexfunc = ui.verbose and hex or short
1628 hexfunc = ui.verbose and hex or short
1629 modified, added, removed, deleted, unknown = repo.changes()
1629 modified, added, removed, deleted, unknown = repo.changes()
1630 output = ["%s%s" %
1630 output = ["%s%s" %
1631 ('+'.join([hexfunc(parent) for parent in parents]),
1631 ('+'.join([hexfunc(parent) for parent in parents]),
1632 (modified or added or removed or deleted) and "+" or "")]
1632 (modified or added or removed or deleted) and "+" or "")]
1633
1633
1634 if not ui.quiet:
1634 if not ui.quiet:
1635 # multiple tags for a single parent separated by '/'
1635 # multiple tags for a single parent separated by '/'
1636 parenttags = ['/'.join(tags)
1636 parenttags = ['/'.join(tags)
1637 for tags in map(repo.nodetags, parents) if tags]
1637 for tags in map(repo.nodetags, parents) if tags]
1638 # tags for multiple parents separated by ' + '
1638 # tags for multiple parents separated by ' + '
1639 if parenttags:
1639 if parenttags:
1640 output.append(' + '.join(parenttags))
1640 output.append(' + '.join(parenttags))
1641
1641
1642 ui.write("%s\n" % ' '.join(output))
1642 ui.write("%s\n" % ' '.join(output))
1643
1643
1644 def import_(ui, repo, patch1, *patches, **opts):
1644 def import_(ui, repo, patch1, *patches, **opts):
1645 """import an ordered set of patches
1645 """import an ordered set of patches
1646
1646
1647 Import a list of patches and commit them individually.
1647 Import a list of patches and commit them individually.
1648
1648
1649 If there are outstanding changes in the working directory, import
1649 If there are outstanding changes in the working directory, import
1650 will abort unless given the -f flag.
1650 will abort unless given the -f flag.
1651
1651
1652 If a patch looks like a mail message (its first line starts with
1652 If a patch looks like a mail message (its first line starts with
1653 "From " or looks like an RFC822 header), it will not be applied
1653 "From " or looks like an RFC822 header), it will not be applied
1654 unless the -f option is used. The importer neither parses nor
1654 unless the -f option is used. The importer neither parses nor
1655 discards mail headers, so use -f only to override the "mailness"
1655 discards mail headers, so use -f only to override the "mailness"
1656 safety check, not to import a real mail message.
1656 safety check, not to import a real mail message.
1657 """
1657 """
1658 patches = (patch1,) + patches
1658 patches = (patch1,) + patches
1659
1659
1660 if not opts['force']:
1660 if not opts['force']:
1661 bail_if_changed(repo)
1661 bail_if_changed(repo)
1662
1662
1663 d = opts["base"]
1663 d = opts["base"]
1664 strip = opts["strip"]
1664 strip = opts["strip"]
1665
1665
1666 mailre = re.compile(r'(?:From |[\w-]+:)')
1666 mailre = re.compile(r'(?:From |[\w-]+:)')
1667
1667
1668 # attempt to detect the start of a patch
1668 # attempt to detect the start of a patch
1669 # (this heuristic is borrowed from quilt)
1669 # (this heuristic is borrowed from quilt)
1670 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1670 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1671 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1671 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1672 '(---|\*\*\*)[ \t])')
1672 '(---|\*\*\*)[ \t])')
1673
1673
1674 for patch in patches:
1674 for patch in patches:
1675 ui.status(_("applying %s\n") % patch)
1675 ui.status(_("applying %s\n") % patch)
1676 pf = os.path.join(d, patch)
1676 pf = os.path.join(d, patch)
1677
1677
1678 message = []
1678 message = []
1679 user = None
1679 user = None
1680 hgpatch = False
1680 hgpatch = False
1681 for line in file(pf):
1681 for line in file(pf):
1682 line = line.rstrip()
1682 line = line.rstrip()
1683 if (not message and not hgpatch and
1683 if (not message and not hgpatch and
1684 mailre.match(line) and not opts['force']):
1684 mailre.match(line) and not opts['force']):
1685 if len(line) > 35:
1685 if len(line) > 35:
1686 line = line[:32] + '...'
1686 line = line[:32] + '...'
1687 raise util.Abort(_('first line looks like a '
1687 raise util.Abort(_('first line looks like a '
1688 'mail header: ') + line)
1688 'mail header: ') + line)
1689 if diffre.match(line):
1689 if diffre.match(line):
1690 break
1690 break
1691 elif hgpatch:
1691 elif hgpatch:
1692 # parse values when importing the result of an hg export
1692 # parse values when importing the result of an hg export
1693 if line.startswith("# User "):
1693 if line.startswith("# User "):
1694 user = line[7:]
1694 user = line[7:]
1695 ui.debug(_('User: %s\n') % user)
1695 ui.debug(_('User: %s\n') % user)
1696 elif not line.startswith("# ") and line:
1696 elif not line.startswith("# ") and line:
1697 message.append(line)
1697 message.append(line)
1698 hgpatch = False
1698 hgpatch = False
1699 elif line == '# HG changeset patch':
1699 elif line == '# HG changeset patch':
1700 hgpatch = True
1700 hgpatch = True
1701 message = [] # We may have collected garbage
1701 message = [] # We may have collected garbage
1702 else:
1702 else:
1703 message.append(line)
1703 message.append(line)
1704
1704
1705 # make sure message isn't empty
1705 # make sure message isn't empty
1706 if not message:
1706 if not message:
1707 message = _("imported patch %s\n") % patch
1707 message = _("imported patch %s\n") % patch
1708 else:
1708 else:
1709 message = "%s\n" % '\n'.join(message)
1709 message = "%s\n" % '\n'.join(message)
1710 ui.debug(_('message:\n%s\n') % message)
1710 ui.debug(_('message:\n%s\n') % message)
1711
1711
1712 files = util.patch(strip, pf, ui)
1712 files = util.patch(strip, pf, ui)
1713
1713
1714 if len(files) > 0:
1714 if len(files) > 0:
1715 addremove_lock(ui, repo, files, {})
1715 addremove_lock(ui, repo, files, {})
1716 repo.commit(files, message, user)
1716 repo.commit(files, message, user)
1717
1717
1718 def incoming(ui, repo, source="default", **opts):
1718 def incoming(ui, repo, source="default", **opts):
1719 """show new changesets found in source
1719 """show new changesets found in source
1720
1720
1721 Show new changesets found in the specified path/URL or the default
1721 Show new changesets found in the specified path/URL or the default
1722 pull location. These are the changesets that would be pulled if a pull
1722 pull location. These are the changesets that would be pulled if a pull
1723 was requested.
1723 was requested.
1724
1724
1725 For remote repository, using --bundle avoids downloading the changesets
1725 For remote repository, using --bundle avoids downloading the changesets
1726 twice if the incoming is followed by a pull.
1726 twice if the incoming is followed by a pull.
1727
1727
1728 See pull for valid source format details.
1728 See pull for valid source format details.
1729 """
1729 """
1730 source = ui.expandpath(source)
1730 source = ui.expandpath(source)
1731 if opts['ssh']:
1731 if opts['ssh']:
1732 ui.setconfig("ui", "ssh", opts['ssh'])
1732 ui.setconfig("ui", "ssh", opts['ssh'])
1733 if opts['remotecmd']:
1733 if opts['remotecmd']:
1734 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1734 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1735
1735
1736 other = hg.repository(ui, source)
1736 other = hg.repository(ui, source)
1737 incoming = repo.findincoming(other, force=opts["force"])
1737 incoming = repo.findincoming(other, force=opts["force"])
1738 if not incoming:
1738 if not incoming:
1739 ui.status(_("no changes found\n"))
1739 ui.status(_("no changes found\n"))
1740 return
1740 return
1741
1741
1742 cleanup = None
1742 cleanup = None
1743 try:
1743 try:
1744 fname = opts["bundle"]
1744 fname = opts["bundle"]
1745 if fname or not other.local():
1745 if fname or not other.local():
1746 # create a bundle (uncompressed if other repo is not local)
1746 # create a bundle (uncompressed if other repo is not local)
1747 cg = other.changegroup(incoming, "incoming")
1747 cg = other.changegroup(incoming, "incoming")
1748 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1748 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1749 # keep written bundle?
1749 # keep written bundle?
1750 if opts["bundle"]:
1750 if opts["bundle"]:
1751 cleanup = None
1751 cleanup = None
1752 if not other.local():
1752 if not other.local():
1753 # use the created uncompressed bundlerepo
1753 # use the created uncompressed bundlerepo
1754 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1754 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1755
1755
1756 o = other.changelog.nodesbetween(incoming)[0]
1756 o = other.changelog.nodesbetween(incoming)[0]
1757 if opts['newest_first']:
1757 if opts['newest_first']:
1758 o.reverse()
1758 o.reverse()
1759 displayer = show_changeset(ui, other, opts)
1759 displayer = show_changeset(ui, other, opts)
1760 for n in o:
1760 for n in o:
1761 parents = [p for p in other.changelog.parents(n) if p != nullid]
1761 parents = [p for p in other.changelog.parents(n) if p != nullid]
1762 if opts['no_merges'] and len(parents) == 2:
1762 if opts['no_merges'] and len(parents) == 2:
1763 continue
1763 continue
1764 displayer.show(changenode=n)
1764 displayer.show(changenode=n)
1765 if opts['patch']:
1765 if opts['patch']:
1766 prev = (parents and parents[0]) or nullid
1766 prev = (parents and parents[0]) or nullid
1767 dodiff(ui, ui, other, prev, n)
1767 dodiff(ui, ui, other, prev, n)
1768 ui.write("\n")
1768 ui.write("\n")
1769 finally:
1769 finally:
1770 if hasattr(other, 'close'):
1770 if hasattr(other, 'close'):
1771 other.close()
1771 other.close()
1772 if cleanup:
1772 if cleanup:
1773 os.unlink(cleanup)
1773 os.unlink(cleanup)
1774
1774
1775 def init(ui, dest="."):
1775 def init(ui, dest="."):
1776 """create a new repository in the given directory
1776 """create a new repository in the given directory
1777
1777
1778 Initialize a new repository in the given directory. If the given
1778 Initialize a new repository in the given directory. If the given
1779 directory does not exist, it is created.
1779 directory does not exist, it is created.
1780
1780
1781 If no directory is given, the current directory is used.
1781 If no directory is given, the current directory is used.
1782 """
1782 """
1783 if not os.path.exists(dest):
1783 if not os.path.exists(dest):
1784 os.mkdir(dest)
1784 os.mkdir(dest)
1785 hg.repository(ui, dest, create=1)
1785 hg.repository(ui, dest, create=1)
1786
1786
1787 def locate(ui, repo, *pats, **opts):
1787 def locate(ui, repo, *pats, **opts):
1788 """locate files matching specific patterns
1788 """locate files matching specific patterns
1789
1789
1790 Print all files under Mercurial control whose names match the
1790 Print all files under Mercurial control whose names match the
1791 given patterns.
1791 given patterns.
1792
1792
1793 This command searches the current directory and its
1793 This command searches the current directory and its
1794 subdirectories. To search an entire repository, move to the root
1794 subdirectories. To search an entire repository, move to the root
1795 of the repository.
1795 of the repository.
1796
1796
1797 If no patterns are given to match, this command prints all file
1797 If no patterns are given to match, this command prints all file
1798 names.
1798 names.
1799
1799
1800 If you want to feed the output of this command into the "xargs"
1800 If you want to feed the output of this command into the "xargs"
1801 command, use the "-0" option to both this command and "xargs".
1801 command, use the "-0" option to both this command and "xargs".
1802 This will avoid the problem of "xargs" treating single filenames
1802 This will avoid the problem of "xargs" treating single filenames
1803 that contain white space as multiple filenames.
1803 that contain white space as multiple filenames.
1804 """
1804 """
1805 end = opts['print0'] and '\0' or '\n'
1805 end = opts['print0'] and '\0' or '\n'
1806 rev = opts['rev']
1806 rev = opts['rev']
1807 if rev:
1807 if rev:
1808 node = repo.lookup(rev)
1808 node = repo.lookup(rev)
1809 else:
1809 else:
1810 node = None
1810 node = None
1811
1811
1812 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1812 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1813 head='(?:.*/|)'):
1813 head='(?:.*/|)'):
1814 if not node and repo.dirstate.state(abs) == '?':
1814 if not node and repo.dirstate.state(abs) == '?':
1815 continue
1815 continue
1816 if opts['fullpath']:
1816 if opts['fullpath']:
1817 ui.write(os.path.join(repo.root, abs), end)
1817 ui.write(os.path.join(repo.root, abs), end)
1818 else:
1818 else:
1819 ui.write(((pats and rel) or abs), end)
1819 ui.write(((pats and rel) or abs), end)
1820
1820
1821 def log(ui, repo, *pats, **opts):
1821 def log(ui, repo, *pats, **opts):
1822 """show revision history of entire repository or files
1822 """show revision history of entire repository or files
1823
1823
1824 Print the revision history of the specified files or the entire project.
1824 Print the revision history of the specified files or the entire project.
1825
1825
1826 By default this command outputs: changeset id and hash, tags,
1826 By default this command outputs: changeset id and hash, tags,
1827 non-trivial parents, user, date and time, and a summary for each
1827 non-trivial parents, user, date and time, and a summary for each
1828 commit. When the -v/--verbose switch is used, the list of changed
1828 commit. When the -v/--verbose switch is used, the list of changed
1829 files and full commit message is shown.
1829 files and full commit message is shown.
1830 """
1830 """
1831 class dui(object):
1831 class dui(object):
1832 # Implement and delegate some ui protocol. Save hunks of
1832 # Implement and delegate some ui protocol. Save hunks of
1833 # output for later display in the desired order.
1833 # output for later display in the desired order.
1834 def __init__(self, ui):
1834 def __init__(self, ui):
1835 self.ui = ui
1835 self.ui = ui
1836 self.hunk = {}
1836 self.hunk = {}
1837 self.header = {}
1837 self.header = {}
1838 def bump(self, rev):
1838 def bump(self, rev):
1839 self.rev = rev
1839 self.rev = rev
1840 self.hunk[rev] = []
1840 self.hunk[rev] = []
1841 self.header[rev] = []
1841 self.header[rev] = []
1842 def note(self, *args):
1842 def note(self, *args):
1843 if self.verbose:
1843 if self.verbose:
1844 self.write(*args)
1844 self.write(*args)
1845 def status(self, *args):
1845 def status(self, *args):
1846 if not self.quiet:
1846 if not self.quiet:
1847 self.write(*args)
1847 self.write(*args)
1848 def write(self, *args):
1848 def write(self, *args):
1849 self.hunk[self.rev].append(args)
1849 self.hunk[self.rev].append(args)
1850 def write_header(self, *args):
1850 def write_header(self, *args):
1851 self.header[self.rev].append(args)
1851 self.header[self.rev].append(args)
1852 def debug(self, *args):
1852 def debug(self, *args):
1853 if self.debugflag:
1853 if self.debugflag:
1854 self.write(*args)
1854 self.write(*args)
1855 def __getattr__(self, key):
1855 def __getattr__(self, key):
1856 return getattr(self.ui, key)
1856 return getattr(self.ui, key)
1857
1857
1858 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1858 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1859
1859
1860 if opts['limit']:
1860 if opts['limit']:
1861 try:
1861 try:
1862 limit = int(opts['limit'])
1862 limit = int(opts['limit'])
1863 except ValueError:
1863 except ValueError:
1864 raise util.Abort(_('limit must be a positive integer'))
1864 raise util.Abort(_('limit must be a positive integer'))
1865 if limit <= 0: raise util.Abort(_('limit must be positive'))
1865 if limit <= 0: raise util.Abort(_('limit must be positive'))
1866 else:
1866 else:
1867 limit = sys.maxint
1867 limit = sys.maxint
1868 count = 0
1868 count = 0
1869
1869
1870 displayer = show_changeset(ui, repo, opts)
1870 displayer = show_changeset(ui, repo, opts)
1871 for st, rev, fns in changeiter:
1871 for st, rev, fns in changeiter:
1872 if st == 'window':
1872 if st == 'window':
1873 du = dui(ui)
1873 du = dui(ui)
1874 displayer.ui = du
1874 displayer.ui = du
1875 elif st == 'add':
1875 elif st == 'add':
1876 du.bump(rev)
1876 du.bump(rev)
1877 changenode = repo.changelog.node(rev)
1877 changenode = repo.changelog.node(rev)
1878 parents = [p for p in repo.changelog.parents(changenode)
1878 parents = [p for p in repo.changelog.parents(changenode)
1879 if p != nullid]
1879 if p != nullid]
1880 if opts['no_merges'] and len(parents) == 2:
1880 if opts['no_merges'] and len(parents) == 2:
1881 continue
1881 continue
1882 if opts['only_merges'] and len(parents) != 2:
1882 if opts['only_merges'] and len(parents) != 2:
1883 continue
1883 continue
1884
1884
1885 if opts['keyword']:
1885 if opts['keyword']:
1886 changes = getchange(rev)
1886 changes = getchange(rev)
1887 miss = 0
1887 miss = 0
1888 for k in [kw.lower() for kw in opts['keyword']]:
1888 for k in [kw.lower() for kw in opts['keyword']]:
1889 if not (k in changes[1].lower() or
1889 if not (k in changes[1].lower() or
1890 k in changes[4].lower() or
1890 k in changes[4].lower() or
1891 k in " ".join(changes[3][:20]).lower()):
1891 k in " ".join(changes[3][:20]).lower()):
1892 miss = 1
1892 miss = 1
1893 break
1893 break
1894 if miss:
1894 if miss:
1895 continue
1895 continue
1896
1896
1897 br = None
1897 br = None
1898 if opts['branches']:
1898 if opts['branches']:
1899 br = repo.branchlookup([repo.changelog.node(rev)])
1899 br = repo.branchlookup([repo.changelog.node(rev)])
1900
1900
1901 displayer.show(rev, brinfo=br)
1901 displayer.show(rev, brinfo=br)
1902 if opts['patch']:
1902 if opts['patch']:
1903 prev = (parents and parents[0]) or nullid
1903 prev = (parents and parents[0]) or nullid
1904 dodiff(du, du, repo, prev, changenode, match=matchfn)
1904 dodiff(du, du, repo, prev, changenode, match=matchfn)
1905 du.write("\n\n")
1905 du.write("\n\n")
1906 elif st == 'iter':
1906 elif st == 'iter':
1907 if count == limit: break
1907 if count == limit: break
1908 if du.header[rev]:
1908 if du.header[rev]:
1909 for args in du.header[rev]:
1909 for args in du.header[rev]:
1910 ui.write_header(*args)
1910 ui.write_header(*args)
1911 if du.hunk[rev]:
1911 if du.hunk[rev]:
1912 count += 1
1912 count += 1
1913 for args in du.hunk[rev]:
1913 for args in du.hunk[rev]:
1914 ui.write(*args)
1914 ui.write(*args)
1915
1915
1916 def manifest(ui, repo, rev=None):
1916 def manifest(ui, repo, rev=None):
1917 """output the latest or given revision of the project manifest
1917 """output the latest or given revision of the project manifest
1918
1918
1919 Print a list of version controlled files for the given revision.
1919 Print a list of version controlled files for the given revision.
1920
1920
1921 The manifest is the list of files being version controlled. If no revision
1921 The manifest is the list of files being version controlled. If no revision
1922 is given then the tip is used.
1922 is given then the tip is used.
1923 """
1923 """
1924 if rev:
1924 if rev:
1925 try:
1925 try:
1926 # assume all revision numbers are for changesets
1926 # assume all revision numbers are for changesets
1927 n = repo.lookup(rev)
1927 n = repo.lookup(rev)
1928 change = repo.changelog.read(n)
1928 change = repo.changelog.read(n)
1929 n = change[0]
1929 n = change[0]
1930 except hg.RepoError:
1930 except hg.RepoError:
1931 n = repo.manifest.lookup(rev)
1931 n = repo.manifest.lookup(rev)
1932 else:
1932 else:
1933 n = repo.manifest.tip()
1933 n = repo.manifest.tip()
1934 m = repo.manifest.read(n)
1934 m = repo.manifest.read(n)
1935 mf = repo.manifest.readflags(n)
1935 mf = repo.manifest.readflags(n)
1936 files = m.keys()
1936 files = m.keys()
1937 files.sort()
1937 files.sort()
1938
1938
1939 for f in files:
1939 for f in files:
1940 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1940 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1941
1941
1942 def merge(ui, repo, node=None, **opts):
1942 def merge(ui, repo, node=None, **opts):
1943 """Merge working directory with another revision
1943 """Merge working directory with another revision
1944
1944
1945 Merge the contents of the current working directory and the
1945 Merge the contents of the current working directory and the
1946 requested revision. Files that changed between either parent are
1946 requested revision. Files that changed between either parent are
1947 marked as changed for the next commit and a commit must be
1947 marked as changed for the next commit and a commit must be
1948 performed before any further updates are allowed.
1948 performed before any further updates are allowed.
1949 """
1949 """
1950 return update(ui, repo, node=node, merge=True, **opts)
1950 return update(ui, repo, node=node, merge=True, **opts)
1951
1951
1952 def outgoing(ui, repo, dest="default-push", **opts):
1952 def outgoing(ui, repo, dest="default-push", **opts):
1953 """show changesets not found in destination
1953 """show changesets not found in destination
1954
1954
1955 Show changesets not found in the specified destination repository or
1955 Show changesets not found in the specified destination repository or
1956 the default push location. These are the changesets that would be pushed
1956 the default push location. These are the changesets that would be pushed
1957 if a push was requested.
1957 if a push was requested.
1958
1958
1959 See pull for valid destination format details.
1959 See pull for valid destination format details.
1960 """
1960 """
1961 dest = ui.expandpath(dest)
1961 dest = ui.expandpath(dest)
1962 if opts['ssh']:
1962 if opts['ssh']:
1963 ui.setconfig("ui", "ssh", opts['ssh'])
1963 ui.setconfig("ui", "ssh", opts['ssh'])
1964 if opts['remotecmd']:
1964 if opts['remotecmd']:
1965 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1965 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1966
1966
1967 other = hg.repository(ui, dest)
1967 other = hg.repository(ui, dest)
1968 o = repo.findoutgoing(other, force=opts['force'])
1968 o = repo.findoutgoing(other, force=opts['force'])
1969 if not o:
1969 if not o:
1970 ui.status(_("no changes found\n"))
1970 ui.status(_("no changes found\n"))
1971 return
1971 return
1972 o = repo.changelog.nodesbetween(o)[0]
1972 o = repo.changelog.nodesbetween(o)[0]
1973 if opts['newest_first']:
1973 if opts['newest_first']:
1974 o.reverse()
1974 o.reverse()
1975 displayer = show_changeset(ui, repo, opts)
1975 displayer = show_changeset(ui, repo, opts)
1976 for n in o:
1976 for n in o:
1977 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1977 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1978 if opts['no_merges'] and len(parents) == 2:
1978 if opts['no_merges'] and len(parents) == 2:
1979 continue
1979 continue
1980 displayer.show(changenode=n)
1980 displayer.show(changenode=n)
1981 if opts['patch']:
1981 if opts['patch']:
1982 prev = (parents and parents[0]) or nullid
1982 prev = (parents and parents[0]) or nullid
1983 dodiff(ui, ui, repo, prev, n)
1983 dodiff(ui, ui, repo, prev, n)
1984 ui.write("\n")
1984 ui.write("\n")
1985
1985
1986 def parents(ui, repo, rev=None, branches=None, **opts):
1986 def parents(ui, repo, rev=None, branches=None, **opts):
1987 """show the parents of the working dir or revision
1987 """show the parents of the working dir or revision
1988
1988
1989 Print the working directory's parent revisions.
1989 Print the working directory's parent revisions.
1990 """
1990 """
1991 if rev:
1991 if rev:
1992 p = repo.changelog.parents(repo.lookup(rev))
1992 p = repo.changelog.parents(repo.lookup(rev))
1993 else:
1993 else:
1994 p = repo.dirstate.parents()
1994 p = repo.dirstate.parents()
1995
1995
1996 br = None
1996 br = None
1997 if branches is not None:
1997 if branches is not None:
1998 br = repo.branchlookup(p)
1998 br = repo.branchlookup(p)
1999 displayer = show_changeset(ui, repo, opts)
1999 displayer = show_changeset(ui, repo, opts)
2000 for n in p:
2000 for n in p:
2001 if n != nullid:
2001 if n != nullid:
2002 displayer.show(changenode=n, brinfo=br)
2002 displayer.show(changenode=n, brinfo=br)
2003
2003
2004 def paths(ui, repo, search=None):
2004 def paths(ui, repo, search=None):
2005 """show definition of symbolic path names
2005 """show definition of symbolic path names
2006
2006
2007 Show definition of symbolic path name NAME. If no name is given, show
2007 Show definition of symbolic path name NAME. If no name is given, show
2008 definition of available names.
2008 definition of available names.
2009
2009
2010 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2010 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2011 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2011 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2012 """
2012 """
2013 if search:
2013 if search:
2014 for name, path in ui.configitems("paths"):
2014 for name, path in ui.configitems("paths"):
2015 if name == search:
2015 if name == search:
2016 ui.write("%s\n" % path)
2016 ui.write("%s\n" % path)
2017 return
2017 return
2018 ui.warn(_("not found!\n"))
2018 ui.warn(_("not found!\n"))
2019 return 1
2019 return 1
2020 else:
2020 else:
2021 for name, path in ui.configitems("paths"):
2021 for name, path in ui.configitems("paths"):
2022 ui.write("%s = %s\n" % (name, path))
2022 ui.write("%s = %s\n" % (name, path))
2023
2023
2024 def postincoming(ui, repo, modheads, optupdate):
2024 def postincoming(ui, repo, modheads, optupdate):
2025 if modheads == 0:
2025 if modheads == 0:
2026 return
2026 return
2027 if optupdate:
2027 if optupdate:
2028 if modheads == 1:
2028 if modheads == 1:
2029 return update(ui, repo)
2029 return update(ui, repo)
2030 else:
2030 else:
2031 ui.status(_("not updating, since new heads added\n"))
2031 ui.status(_("not updating, since new heads added\n"))
2032 if modheads > 1:
2032 if modheads > 1:
2033 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2033 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2034 else:
2034 else:
2035 ui.status(_("(run 'hg update' to get a working copy)\n"))
2035 ui.status(_("(run 'hg update' to get a working copy)\n"))
2036
2036
2037 def pull(ui, repo, source="default", **opts):
2037 def pull(ui, repo, source="default", **opts):
2038 """pull changes from the specified source
2038 """pull changes from the specified source
2039
2039
2040 Pull changes from a remote repository to a local one.
2040 Pull changes from a remote repository to a local one.
2041
2041
2042 This finds all changes from the repository at the specified path
2042 This finds all changes from the repository at the specified path
2043 or URL and adds them to the local repository. By default, this
2043 or URL and adds them to the local repository. By default, this
2044 does not update the copy of the project in the working directory.
2044 does not update the copy of the project in the working directory.
2045
2045
2046 Valid URLs are of the form:
2046 Valid URLs are of the form:
2047
2047
2048 local/filesystem/path
2048 local/filesystem/path
2049 http://[user@]host[:port][/path]
2049 http://[user@]host[:port][/path]
2050 https://[user@]host[:port][/path]
2050 https://[user@]host[:port][/path]
2051 ssh://[user@]host[:port][/path]
2051 ssh://[user@]host[:port][/path]
2052
2052
2053 Some notes about using SSH with Mercurial:
2053 Some notes about using SSH with Mercurial:
2054 - SSH requires an accessible shell account on the destination machine
2054 - SSH requires an accessible shell account on the destination machine
2055 and a copy of hg in the remote path or specified with as remotecmd.
2055 and a copy of hg in the remote path or specified with as remotecmd.
2056 - /path is relative to the remote user's home directory by default.
2056 - /path is relative to the remote user's home directory by default.
2057 Use two slashes at the start of a path to specify an absolute path.
2057 Use two slashes at the start of a path to specify an absolute path.
2058 - Mercurial doesn't use its own compression via SSH; the right thing
2058 - Mercurial doesn't use its own compression via SSH; the right thing
2059 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2059 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2060 Host *.mylocalnetwork.example.com
2060 Host *.mylocalnetwork.example.com
2061 Compression off
2061 Compression off
2062 Host *
2062 Host *
2063 Compression on
2063 Compression on
2064 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2064 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2065 with the --ssh command line option.
2065 with the --ssh command line option.
2066 """
2066 """
2067 source = ui.expandpath(source)
2067 source = ui.expandpath(source)
2068 ui.status(_('pulling from %s\n') % (source))
2068 ui.status(_('pulling from %s\n') % (source))
2069
2069
2070 if opts['ssh']:
2070 if opts['ssh']:
2071 ui.setconfig("ui", "ssh", opts['ssh'])
2071 ui.setconfig("ui", "ssh", opts['ssh'])
2072 if opts['remotecmd']:
2072 if opts['remotecmd']:
2073 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2073 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2074
2074
2075 other = hg.repository(ui, source)
2075 other = hg.repository(ui, source)
2076 revs = None
2076 revs = None
2077 if opts['rev'] and not other.local():
2077 if opts['rev'] and not other.local():
2078 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2078 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2079 elif opts['rev']:
2079 elif opts['rev']:
2080 revs = [other.lookup(rev) for rev in opts['rev']]
2080 revs = [other.lookup(rev) for rev in opts['rev']]
2081 modheads = repo.pull(other, heads=revs, force=opts['force'])
2081 modheads = repo.pull(other, heads=revs, force=opts['force'])
2082 return postincoming(ui, repo, modheads, opts['update'])
2082 return postincoming(ui, repo, modheads, opts['update'])
2083
2083
2084 def push(ui, repo, dest="default-push", **opts):
2084 def push(ui, repo, dest="default-push", **opts):
2085 """push changes to the specified destination
2085 """push changes to the specified destination
2086
2086
2087 Push changes from the local repository to the given destination.
2087 Push changes from the local repository to the given destination.
2088
2088
2089 This is the symmetrical operation for pull. It helps to move
2089 This is the symmetrical operation for pull. It helps to move
2090 changes from the current repository to a different one. If the
2090 changes from the current repository to a different one. If the
2091 destination is local this is identical to a pull in that directory
2091 destination is local this is identical to a pull in that directory
2092 from the current one.
2092 from the current one.
2093
2093
2094 By default, push will refuse to run if it detects the result would
2094 By default, push will refuse to run if it detects the result would
2095 increase the number of remote heads. This generally indicates the
2095 increase the number of remote heads. This generally indicates the
2096 the client has forgotten to sync and merge before pushing.
2096 the client has forgotten to sync and merge before pushing.
2097
2097
2098 Valid URLs are of the form:
2098 Valid URLs are of the form:
2099
2099
2100 local/filesystem/path
2100 local/filesystem/path
2101 ssh://[user@]host[:port][/path]
2101 ssh://[user@]host[:port][/path]
2102
2102
2103 Look at the help text for the pull command for important details
2103 Look at the help text for the pull command for important details
2104 about ssh:// URLs.
2104 about ssh:// URLs.
2105 """
2105 """
2106 dest = ui.expandpath(dest)
2106 dest = ui.expandpath(dest)
2107 ui.status('pushing to %s\n' % (dest))
2107 ui.status('pushing to %s\n' % (dest))
2108
2108
2109 if opts['ssh']:
2109 if opts['ssh']:
2110 ui.setconfig("ui", "ssh", opts['ssh'])
2110 ui.setconfig("ui", "ssh", opts['ssh'])
2111 if opts['remotecmd']:
2111 if opts['remotecmd']:
2112 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2112 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2113
2113
2114 other = hg.repository(ui, dest)
2114 other = hg.repository(ui, dest)
2115 revs = None
2115 revs = None
2116 if opts['rev']:
2116 if opts['rev']:
2117 revs = [repo.lookup(rev) for rev in opts['rev']]
2117 revs = [repo.lookup(rev) for rev in opts['rev']]
2118 r = repo.push(other, opts['force'], revs=revs)
2118 r = repo.push(other, opts['force'], revs=revs)
2119 return r == 0
2119 return r == 0
2120
2120
2121 def rawcommit(ui, repo, *flist, **rc):
2121 def rawcommit(ui, repo, *flist, **rc):
2122 """raw commit interface (DEPRECATED)
2122 """raw commit interface (DEPRECATED)
2123
2123
2124 (DEPRECATED)
2124 (DEPRECATED)
2125 Lowlevel commit, for use in helper scripts.
2125 Lowlevel commit, for use in helper scripts.
2126
2126
2127 This command is not intended to be used by normal users, as it is
2127 This command is not intended to be used by normal users, as it is
2128 primarily useful for importing from other SCMs.
2128 primarily useful for importing from other SCMs.
2129
2129
2130 This command is now deprecated and will be removed in a future
2130 This command is now deprecated and will be removed in a future
2131 release, please use debugsetparents and commit instead.
2131 release, please use debugsetparents and commit instead.
2132 """
2132 """
2133
2133
2134 ui.warn(_("(the rawcommit command is deprecated)\n"))
2134 ui.warn(_("(the rawcommit command is deprecated)\n"))
2135
2135
2136 message = rc['message']
2136 message = rc['message']
2137 if not message and rc['logfile']:
2137 if not message and rc['logfile']:
2138 try:
2138 try:
2139 message = open(rc['logfile']).read()
2139 message = open(rc['logfile']).read()
2140 except IOError:
2140 except IOError:
2141 pass
2141 pass
2142 if not message and not rc['logfile']:
2142 if not message and not rc['logfile']:
2143 raise util.Abort(_("missing commit message"))
2143 raise util.Abort(_("missing commit message"))
2144
2144
2145 files = relpath(repo, list(flist))
2145 files = relpath(repo, list(flist))
2146 if rc['files']:
2146 if rc['files']:
2147 files += open(rc['files']).read().splitlines()
2147 files += open(rc['files']).read().splitlines()
2148
2148
2149 rc['parent'] = map(repo.lookup, rc['parent'])
2149 rc['parent'] = map(repo.lookup, rc['parent'])
2150
2150
2151 try:
2151 try:
2152 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2152 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2153 except ValueError, inst:
2153 except ValueError, inst:
2154 raise util.Abort(str(inst))
2154 raise util.Abort(str(inst))
2155
2155
2156 def recover(ui, repo):
2156 def recover(ui, repo):
2157 """roll back an interrupted transaction
2157 """roll back an interrupted transaction
2158
2158
2159 Recover from an interrupted commit or pull.
2159 Recover from an interrupted commit or pull.
2160
2160
2161 This command tries to fix the repository status after an interrupted
2161 This command tries to fix the repository status after an interrupted
2162 operation. It should only be necessary when Mercurial suggests it.
2162 operation. It should only be necessary when Mercurial suggests it.
2163 """
2163 """
2164 if repo.recover():
2164 if repo.recover():
2165 return repo.verify()
2165 return repo.verify()
2166 return 1
2166 return 1
2167
2167
2168 def remove(ui, repo, *pats, **opts):
2168 def remove(ui, repo, *pats, **opts):
2169 """remove the specified files on the next commit
2169 """remove the specified files on the next commit
2170
2170
2171 Schedule the indicated files for removal from the repository.
2171 Schedule the indicated files for removal from the repository.
2172
2172
2173 This command schedules the files to be removed at the next commit.
2173 This command schedules the files to be removed at the next commit.
2174 This only removes files from the current branch, not from the
2174 This only removes files from the current branch, not from the
2175 entire project history. If the files still exist in the working
2175 entire project history. If the files still exist in the working
2176 directory, they will be deleted from it. If invoked with --after,
2176 directory, they will be deleted from it. If invoked with --after,
2177 files that have been manually deleted are marked as removed.
2177 files that have been manually deleted are marked as removed.
2178 """
2178 """
2179 names = []
2179 names = []
2180 if not opts['after'] and not pats:
2180 if not opts['after'] and not pats:
2181 raise util.Abort(_('no files specified'))
2181 raise util.Abort(_('no files specified'))
2182 def okaytoremove(abs, rel, exact):
2182 def okaytoremove(abs, rel, exact):
2183 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
2183 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
2184 reason = None
2184 reason = None
2185 if not deleted and opts['after']:
2185 if not deleted and opts['after']:
2186 reason = _('is still present')
2186 reason = _('is still present')
2187 elif modified and not opts['force']:
2187 elif modified and not opts['force']:
2188 reason = _('is modified')
2188 reason = _('is modified')
2189 elif added:
2189 elif added:
2190 reason = _('has been marked for add')
2190 reason = _('has been marked for add')
2191 elif unknown:
2191 elif unknown:
2192 reason = _('is not managed')
2192 reason = _('is not managed')
2193 elif removed:
2193 elif removed:
2194 return False
2194 return False
2195 if reason:
2195 if reason:
2196 if exact:
2196 if exact:
2197 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2197 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2198 else:
2198 else:
2199 return True
2199 return True
2200 for src, abs, rel, exact in walk(repo, pats, opts):
2200 for src, abs, rel, exact in walk(repo, pats, opts):
2201 if okaytoremove(abs, rel, exact):
2201 if okaytoremove(abs, rel, exact):
2202 if ui.verbose or not exact:
2202 if ui.verbose or not exact:
2203 ui.status(_('removing %s\n') % rel)
2203 ui.status(_('removing %s\n') % rel)
2204 names.append(abs)
2204 names.append(abs)
2205 repo.remove(names, unlink=not opts['after'])
2205 repo.remove(names, unlink=not opts['after'])
2206
2206
2207 def rename(ui, repo, *pats, **opts):
2207 def rename(ui, repo, *pats, **opts):
2208 """rename files; equivalent of copy + remove
2208 """rename files; equivalent of copy + remove
2209
2209
2210 Mark dest as copies of sources; mark sources for deletion. If
2210 Mark dest as copies of sources; mark sources for deletion. If
2211 dest is a directory, copies are put in that directory. If dest is
2211 dest is a directory, copies are put in that directory. If dest is
2212 a file, there can only be one source.
2212 a file, there can only be one source.
2213
2213
2214 By default, this command copies the contents of files as they
2214 By default, this command copies the contents of files as they
2215 stand in the working directory. If invoked with --after, the
2215 stand in the working directory. If invoked with --after, the
2216 operation is recorded, but no copying is performed.
2216 operation is recorded, but no copying is performed.
2217
2217
2218 This command takes effect in the next commit.
2218 This command takes effect in the next commit.
2219
2219
2220 NOTE: This command should be treated as experimental. While it
2220 NOTE: This command should be treated as experimental. While it
2221 should properly record rename files, this information is not yet
2221 should properly record rename files, this information is not yet
2222 fully used by merge, nor fully reported by log.
2222 fully used by merge, nor fully reported by log.
2223 """
2223 """
2224 wlock = repo.wlock(0)
2224 wlock = repo.wlock(0)
2225 errs, copied = docopy(ui, repo, pats, opts, wlock)
2225 errs, copied = docopy(ui, repo, pats, opts, wlock)
2226 names = []
2226 names = []
2227 for abs, rel, exact in copied:
2227 for abs, rel, exact in copied:
2228 if ui.verbose or not exact:
2228 if ui.verbose or not exact:
2229 ui.status(_('removing %s\n') % rel)
2229 ui.status(_('removing %s\n') % rel)
2230 names.append(abs)
2230 names.append(abs)
2231 repo.remove(names, True, wlock)
2231 repo.remove(names, True, wlock)
2232 return errs
2232 return errs
2233
2233
2234 def revert(ui, repo, *pats, **opts):
2234 def revert(ui, repo, *pats, **opts):
2235 """revert files or dirs to their states as of some revision
2235 """revert files or dirs to their states as of some revision
2236
2236
2237 With no revision specified, revert the named files or directories
2237 With no revision specified, revert the named files or directories
2238 to the contents they had in the parent of the working directory.
2238 to the contents they had in the parent of the working directory.
2239 This restores the contents of the affected files to an unmodified
2239 This restores the contents of the affected files to an unmodified
2240 state. If the working directory has two parents, you must
2240 state. If the working directory has two parents, you must
2241 explicitly specify the revision to revert to.
2241 explicitly specify the revision to revert to.
2242
2242
2243 Modified files are saved with a .orig suffix before reverting.
2243 Modified files are saved with a .orig suffix before reverting.
2244 To disable these backups, use --no-backup.
2244 To disable these backups, use --no-backup.
2245
2245
2246 Using the -r option, revert the given files or directories to
2246 Using the -r option, revert the given files or directories to
2247 their contents as of a specific revision. This can be helpful to"roll
2247 their contents as of a specific revision. This can be helpful to"roll
2248 back" some or all of a change that should not have been committed.
2248 back" some or all of a change that should not have been committed.
2249
2249
2250 Revert modifies the working directory. It does not commit any
2250 Revert modifies the working directory. It does not commit any
2251 changes, or change the parent of the working directory. If you
2251 changes, or change the parent of the working directory. If you
2252 revert to a revision other than the parent of the working
2252 revert to a revision other than the parent of the working
2253 directory, the reverted files will thus appear modified
2253 directory, the reverted files will thus appear modified
2254 afterwards.
2254 afterwards.
2255
2255
2256 If a file has been deleted, it is recreated. If the executable
2256 If a file has been deleted, it is recreated. If the executable
2257 mode of a file was changed, it is reset.
2257 mode of a file was changed, it is reset.
2258
2258
2259 If names are given, all files matching the names are reverted.
2259 If names are given, all files matching the names are reverted.
2260
2260
2261 If no arguments are given, all files in the repository are reverted.
2261 If no arguments are given, all files in the repository are reverted.
2262 """
2262 """
2263 parent, p2 = repo.dirstate.parents()
2263 parent, p2 = repo.dirstate.parents()
2264 if opts['rev']:
2264 if opts['rev']:
2265 node = repo.lookup(opts['rev'])
2265 node = repo.lookup(opts['rev'])
2266 elif p2 != nullid:
2266 elif p2 != nullid:
2267 raise util.Abort(_('working dir has two parents; '
2267 raise util.Abort(_('working dir has two parents; '
2268 'you must specify the revision to revert to'))
2268 'you must specify the revision to revert to'))
2269 else:
2269 else:
2270 node = parent
2270 node = parent
2271 mf = repo.manifest.read(repo.changelog.read(node)[0])
2271 mf = repo.manifest.read(repo.changelog.read(node)[0])
2272
2272
2273 wlock = repo.wlock()
2273 wlock = repo.wlock()
2274
2274
2275 # need all matching names in dirstate and manifest of target rev,
2275 # need all matching names in dirstate and manifest of target rev,
2276 # so have to walk both. do not print errors if files exist in one
2276 # so have to walk both. do not print errors if files exist in one
2277 # but not other.
2277 # but not other.
2278
2278
2279 names = {}
2279 names = {}
2280 target_only = {}
2280 target_only = {}
2281
2281
2282 # walk dirstate.
2282 # walk dirstate.
2283
2283
2284 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2284 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2285 names[abs] = (rel, exact)
2285 names[abs] = (rel, exact)
2286 if src == 'b':
2286 if src == 'b':
2287 target_only[abs] = True
2287 target_only[abs] = True
2288
2288
2289 # walk target manifest.
2289 # walk target manifest.
2290
2290
2291 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2291 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2292 badmatch=names.has_key):
2292 badmatch=names.has_key):
2293 if abs in names: continue
2293 if abs in names: continue
2294 names[abs] = (rel, exact)
2294 names[abs] = (rel, exact)
2295 target_only[abs] = True
2295 target_only[abs] = True
2296
2296
2297 changes = repo.changes(match=names.has_key, wlock=wlock)
2297 changes = repo.changes(match=names.has_key, wlock=wlock)
2298 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2298 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2299
2299
2300 revert = ([], _('reverting %s\n'))
2300 revert = ([], _('reverting %s\n'))
2301 add = ([], _('adding %s\n'))
2301 add = ([], _('adding %s\n'))
2302 remove = ([], _('removing %s\n'))
2302 remove = ([], _('removing %s\n'))
2303 forget = ([], _('forgetting %s\n'))
2303 forget = ([], _('forgetting %s\n'))
2304 undelete = ([], _('undeleting %s\n'))
2304 undelete = ([], _('undeleting %s\n'))
2305 update = {}
2305 update = {}
2306
2306
2307 disptable = (
2307 disptable = (
2308 # dispatch table:
2308 # dispatch table:
2309 # file state
2309 # file state
2310 # action if in target manifest
2310 # action if in target manifest
2311 # action if not in target manifest
2311 # action if not in target manifest
2312 # make backup if in target manifest
2312 # make backup if in target manifest
2313 # make backup if not in target manifest
2313 # make backup if not in target manifest
2314 (modified, revert, remove, True, True),
2314 (modified, revert, remove, True, True),
2315 (added, revert, forget, True, False),
2315 (added, revert, forget, True, False),
2316 (removed, undelete, None, False, False),
2316 (removed, undelete, None, False, False),
2317 (deleted, revert, remove, False, False),
2317 (deleted, revert, remove, False, False),
2318 (unknown, add, None, True, False),
2318 (unknown, add, None, True, False),
2319 (target_only, add, None, False, False),
2319 (target_only, add, None, False, False),
2320 )
2320 )
2321
2321
2322 entries = names.items()
2322 entries = names.items()
2323 entries.sort()
2323 entries.sort()
2324
2324
2325 for abs, (rel, exact) in entries:
2325 for abs, (rel, exact) in entries:
2326 in_mf = abs in mf
2326 in_mf = abs in mf
2327 def handle(xlist, dobackup):
2327 def handle(xlist, dobackup):
2328 xlist[0].append(abs)
2328 xlist[0].append(abs)
2329 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2329 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2330 bakname = "%s.orig" % rel
2330 bakname = "%s.orig" % rel
2331 ui.note(_('saving current version of %s as %s\n') %
2331 ui.note(_('saving current version of %s as %s\n') %
2332 (rel, bakname))
2332 (rel, bakname))
2333 shutil.copyfile(rel, bakname)
2333 shutil.copyfile(rel, bakname)
2334 shutil.copymode(rel, bakname)
2334 shutil.copymode(rel, bakname)
2335 if ui.verbose or not exact:
2335 if ui.verbose or not exact:
2336 ui.status(xlist[1] % rel)
2336 ui.status(xlist[1] % rel)
2337 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2337 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2338 if abs not in table: continue
2338 if abs not in table: continue
2339 # file has changed in dirstate
2339 # file has changed in dirstate
2340 if in_mf:
2340 if in_mf:
2341 handle(hitlist, backuphit)
2341 handle(hitlist, backuphit)
2342 elif misslist is not None:
2342 elif misslist is not None:
2343 handle(misslist, backupmiss)
2343 handle(misslist, backupmiss)
2344 else:
2344 else:
2345 if exact: ui.warn(_('file not managed: %s\n' % rel))
2345 if exact: ui.warn(_('file not managed: %s\n' % rel))
2346 break
2346 break
2347 else:
2347 else:
2348 # file has not changed in dirstate
2348 # file has not changed in dirstate
2349 if node == parent:
2349 if node == parent:
2350 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2350 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2351 continue
2351 continue
2352 if not in_mf:
2352 if not in_mf:
2353 handle(remove, False)
2353 handle(remove, False)
2354 update[abs] = True
2354 update[abs] = True
2355
2355
2356 repo.dirstate.forget(forget[0])
2356 repo.dirstate.forget(forget[0])
2357 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2357 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2358 show_stats=False)
2358 show_stats=False)
2359 repo.dirstate.update(add[0], 'a')
2359 repo.dirstate.update(add[0], 'a')
2360 repo.dirstate.update(undelete[0], 'n')
2360 repo.dirstate.update(undelete[0], 'n')
2361 repo.dirstate.update(remove[0], 'r')
2361 repo.dirstate.update(remove[0], 'r')
2362 return r
2362 return r
2363
2363
2364 def rollback(ui, repo):
2364 def rollback(ui, repo):
2365 """roll back the last transaction in this repository
2365 """roll back the last transaction in this repository
2366
2366
2367 Roll back the last transaction in this repository, restoring the
2367 Roll back the last transaction in this repository, restoring the
2368 project to its state prior to the transaction.
2368 project to its state prior to the transaction.
2369
2369
2370 Transactions are used to encapsulate the effects of all commands
2370 Transactions are used to encapsulate the effects of all commands
2371 that create new changesets or propagate existing changesets into a
2371 that create new changesets or propagate existing changesets into a
2372 repository. For example, the following commands are transactional,
2372 repository. For example, the following commands are transactional,
2373 and their effects can be rolled back:
2373 and their effects can be rolled back:
2374
2374
2375 commit
2375 commit
2376 import
2376 import
2377 pull
2377 pull
2378 push (with this repository as destination)
2378 push (with this repository as destination)
2379 unbundle
2379 unbundle
2380
2380
2381 This command should be used with care. There is only one level of
2381 This command should be used with care. There is only one level of
2382 rollback, and there is no way to undo a rollback.
2382 rollback, and there is no way to undo a rollback.
2383
2383
2384 This command is not intended for use on public repositories. Once
2384 This command is not intended for use on public repositories. Once
2385 changes are visible for pull by other users, rolling a transaction
2385 changes are visible for pull by other users, rolling a transaction
2386 back locally is ineffective (someone else may already have pulled
2386 back locally is ineffective (someone else may already have pulled
2387 the changes). Furthermore, a race is possible with readers of the
2387 the changes). Furthermore, a race is possible with readers of the
2388 repository; for example an in-progress pull from the repository
2388 repository; for example an in-progress pull from the repository
2389 may fail if a rollback is performed.
2389 may fail if a rollback is performed.
2390 """
2390 """
2391 repo.undo()
2391 repo.undo()
2392
2392
2393 def root(ui, repo):
2393 def root(ui, repo):
2394 """print the root (top) of the current working dir
2394 """print the root (top) of the current working dir
2395
2395
2396 Print the root directory of the current repository.
2396 Print the root directory of the current repository.
2397 """
2397 """
2398 ui.write(repo.root + "\n")
2398 ui.write(repo.root + "\n")
2399
2399
2400 def serve(ui, repo, **opts):
2400 def serve(ui, repo, **opts):
2401 """export the repository via HTTP
2401 """export the repository via HTTP
2402
2402
2403 Start a local HTTP repository browser and pull server.
2403 Start a local HTTP repository browser and pull server.
2404
2404
2405 By default, the server logs accesses to stdout and errors to
2405 By default, the server logs accesses to stdout and errors to
2406 stderr. Use the "-A" and "-E" options to log to files.
2406 stderr. Use the "-A" and "-E" options to log to files.
2407 """
2407 """
2408
2408
2409 if opts["stdio"]:
2409 if opts["stdio"]:
2410 if repo is None:
2410 if repo is None:
2411 raise hg.RepoError(_('no repo found'))
2411 raise hg.RepoError(_('no repo found'))
2412 fin, fout = sys.stdin, sys.stdout
2412 fin, fout = sys.stdin, sys.stdout
2413 sys.stdout = sys.stderr
2413 sys.stdout = sys.stderr
2414
2414
2415 # Prevent insertion/deletion of CRs
2415 # Prevent insertion/deletion of CRs
2416 util.set_binary(fin)
2416 util.set_binary(fin)
2417 util.set_binary(fout)
2417 util.set_binary(fout)
2418
2418
2419 def getarg():
2419 def getarg():
2420 argline = fin.readline()[:-1]
2420 argline = fin.readline()[:-1]
2421 arg, l = argline.split()
2421 arg, l = argline.split()
2422 val = fin.read(int(l))
2422 val = fin.read(int(l))
2423 return arg, val
2423 return arg, val
2424 def respond(v):
2424 def respond(v):
2425 fout.write("%d\n" % len(v))
2425 fout.write("%d\n" % len(v))
2426 fout.write(v)
2426 fout.write(v)
2427 fout.flush()
2427 fout.flush()
2428
2428
2429 lock = None
2429 lock = None
2430
2430
2431 while 1:
2431 while 1:
2432 cmd = fin.readline()[:-1]
2432 cmd = fin.readline()[:-1]
2433 if cmd == '':
2433 if cmd == '':
2434 return
2434 return
2435 if cmd == "heads":
2435 if cmd == "heads":
2436 h = repo.heads()
2436 h = repo.heads()
2437 respond(" ".join(map(hex, h)) + "\n")
2437 respond(" ".join(map(hex, h)) + "\n")
2438 if cmd == "lock":
2438 if cmd == "lock":
2439 lock = repo.lock()
2439 lock = repo.lock()
2440 respond("")
2440 respond("")
2441 if cmd == "unlock":
2441 if cmd == "unlock":
2442 if lock:
2442 if lock:
2443 lock.release()
2443 lock.release()
2444 lock = None
2444 lock = None
2445 respond("")
2445 respond("")
2446 elif cmd == "branches":
2446 elif cmd == "branches":
2447 arg, nodes = getarg()
2447 arg, nodes = getarg()
2448 nodes = map(bin, nodes.split(" "))
2448 nodes = map(bin, nodes.split(" "))
2449 r = []
2449 r = []
2450 for b in repo.branches(nodes):
2450 for b in repo.branches(nodes):
2451 r.append(" ".join(map(hex, b)) + "\n")
2451 r.append(" ".join(map(hex, b)) + "\n")
2452 respond("".join(r))
2452 respond("".join(r))
2453 elif cmd == "between":
2453 elif cmd == "between":
2454 arg, pairs = getarg()
2454 arg, pairs = getarg()
2455 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
2455 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
2456 r = []
2456 r = []
2457 for b in repo.between(pairs):
2457 for b in repo.between(pairs):
2458 r.append(" ".join(map(hex, b)) + "\n")
2458 r.append(" ".join(map(hex, b)) + "\n")
2459 respond("".join(r))
2459 respond("".join(r))
2460 elif cmd == "changegroup":
2460 elif cmd == "changegroup":
2461 nodes = []
2461 nodes = []
2462 arg, roots = getarg()
2462 arg, roots = getarg()
2463 nodes = map(bin, roots.split(" "))
2463 nodes = map(bin, roots.split(" "))
2464
2464
2465 cg = repo.changegroup(nodes, 'serve')
2465 cg = repo.changegroup(nodes, 'serve')
2466 while 1:
2466 while 1:
2467 d = cg.read(4096)
2467 d = cg.read(4096)
2468 if not d:
2468 if not d:
2469 break
2469 break
2470 fout.write(d)
2470 fout.write(d)
2471
2471
2472 fout.flush()
2472 fout.flush()
2473
2473
2474 elif cmd == "addchangegroup":
2474 elif cmd == "addchangegroup":
2475 if not lock:
2475 if not lock:
2476 respond("not locked")
2476 respond("not locked")
2477 continue
2477 continue
2478 respond("")
2478 respond("")
2479
2479
2480 r = repo.addchangegroup(fin)
2480 r = repo.addchangegroup(fin, 'serve')
2481 respond(str(r))
2481 respond(str(r))
2482
2482
2483 optlist = ("name templates style address port ipv6"
2483 optlist = ("name templates style address port ipv6"
2484 " accesslog errorlog webdir_conf")
2484 " accesslog errorlog webdir_conf")
2485 for o in optlist.split():
2485 for o in optlist.split():
2486 if opts[o]:
2486 if opts[o]:
2487 ui.setconfig("web", o, opts[o])
2487 ui.setconfig("web", o, opts[o])
2488
2488
2489 if repo is None and not ui.config("web", "webdir_conf"):
2489 if repo is None and not ui.config("web", "webdir_conf"):
2490 raise hg.RepoError(_('no repo found'))
2490 raise hg.RepoError(_('no repo found'))
2491
2491
2492 if opts['daemon'] and not opts['daemon_pipefds']:
2492 if opts['daemon'] and not opts['daemon_pipefds']:
2493 rfd, wfd = os.pipe()
2493 rfd, wfd = os.pipe()
2494 args = sys.argv[:]
2494 args = sys.argv[:]
2495 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2495 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2496 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2496 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2497 args[0], args)
2497 args[0], args)
2498 os.close(wfd)
2498 os.close(wfd)
2499 os.read(rfd, 1)
2499 os.read(rfd, 1)
2500 os._exit(0)
2500 os._exit(0)
2501
2501
2502 try:
2502 try:
2503 httpd = hgweb.create_server(ui, repo)
2503 httpd = hgweb.create_server(ui, repo)
2504 except socket.error, inst:
2504 except socket.error, inst:
2505 raise util.Abort(_('cannot start server: ') + inst.args[1])
2505 raise util.Abort(_('cannot start server: ') + inst.args[1])
2506
2506
2507 if ui.verbose:
2507 if ui.verbose:
2508 addr, port = httpd.socket.getsockname()
2508 addr, port = httpd.socket.getsockname()
2509 if addr == '0.0.0.0':
2509 if addr == '0.0.0.0':
2510 addr = socket.gethostname()
2510 addr = socket.gethostname()
2511 else:
2511 else:
2512 try:
2512 try:
2513 addr = socket.gethostbyaddr(addr)[0]
2513 addr = socket.gethostbyaddr(addr)[0]
2514 except socket.error:
2514 except socket.error:
2515 pass
2515 pass
2516 if port != 80:
2516 if port != 80:
2517 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2517 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2518 else:
2518 else:
2519 ui.status(_('listening at http://%s/\n') % addr)
2519 ui.status(_('listening at http://%s/\n') % addr)
2520
2520
2521 if opts['pid_file']:
2521 if opts['pid_file']:
2522 fp = open(opts['pid_file'], 'w')
2522 fp = open(opts['pid_file'], 'w')
2523 fp.write(str(os.getpid()))
2523 fp.write(str(os.getpid()))
2524 fp.close()
2524 fp.close()
2525
2525
2526 if opts['daemon_pipefds']:
2526 if opts['daemon_pipefds']:
2527 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2527 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2528 os.close(rfd)
2528 os.close(rfd)
2529 os.write(wfd, 'y')
2529 os.write(wfd, 'y')
2530 os.close(wfd)
2530 os.close(wfd)
2531 sys.stdout.flush()
2531 sys.stdout.flush()
2532 sys.stderr.flush()
2532 sys.stderr.flush()
2533 fd = os.open(util.nulldev, os.O_RDWR)
2533 fd = os.open(util.nulldev, os.O_RDWR)
2534 if fd != 0: os.dup2(fd, 0)
2534 if fd != 0: os.dup2(fd, 0)
2535 if fd != 1: os.dup2(fd, 1)
2535 if fd != 1: os.dup2(fd, 1)
2536 if fd != 2: os.dup2(fd, 2)
2536 if fd != 2: os.dup2(fd, 2)
2537 if fd not in (0, 1, 2): os.close(fd)
2537 if fd not in (0, 1, 2): os.close(fd)
2538
2538
2539 httpd.serve_forever()
2539 httpd.serve_forever()
2540
2540
2541 def status(ui, repo, *pats, **opts):
2541 def status(ui, repo, *pats, **opts):
2542 """show changed files in the working directory
2542 """show changed files in the working directory
2543
2543
2544 Show changed files in the repository. If names are
2544 Show changed files in the repository. If names are
2545 given, only files that match are shown.
2545 given, only files that match are shown.
2546
2546
2547 The codes used to show the status of files are:
2547 The codes used to show the status of files are:
2548 M = modified
2548 M = modified
2549 A = added
2549 A = added
2550 R = removed
2550 R = removed
2551 ! = deleted, but still tracked
2551 ! = deleted, but still tracked
2552 ? = not tracked
2552 ? = not tracked
2553 I = ignored (not shown by default)
2553 I = ignored (not shown by default)
2554 """
2554 """
2555
2555
2556 show_ignored = opts['ignored'] and True or False
2556 show_ignored = opts['ignored'] and True or False
2557 files, matchfn, anypats = matchpats(repo, pats, opts)
2557 files, matchfn, anypats = matchpats(repo, pats, opts)
2558 cwd = (pats and repo.getcwd()) or ''
2558 cwd = (pats and repo.getcwd()) or ''
2559 modified, added, removed, deleted, unknown, ignored = [
2559 modified, added, removed, deleted, unknown, ignored = [
2560 [util.pathto(cwd, x) for x in n]
2560 [util.pathto(cwd, x) for x in n]
2561 for n in repo.changes(files=files, match=matchfn,
2561 for n in repo.changes(files=files, match=matchfn,
2562 show_ignored=show_ignored)]
2562 show_ignored=show_ignored)]
2563
2563
2564 changetypes = [('modified', 'M', modified),
2564 changetypes = [('modified', 'M', modified),
2565 ('added', 'A', added),
2565 ('added', 'A', added),
2566 ('removed', 'R', removed),
2566 ('removed', 'R', removed),
2567 ('deleted', '!', deleted),
2567 ('deleted', '!', deleted),
2568 ('unknown', '?', unknown),
2568 ('unknown', '?', unknown),
2569 ('ignored', 'I', ignored)]
2569 ('ignored', 'I', ignored)]
2570
2570
2571 end = opts['print0'] and '\0' or '\n'
2571 end = opts['print0'] and '\0' or '\n'
2572
2572
2573 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2573 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2574 or changetypes):
2574 or changetypes):
2575 if opts['no_status']:
2575 if opts['no_status']:
2576 format = "%%s%s" % end
2576 format = "%%s%s" % end
2577 else:
2577 else:
2578 format = "%s %%s%s" % (char, end)
2578 format = "%s %%s%s" % (char, end)
2579
2579
2580 for f in changes:
2580 for f in changes:
2581 ui.write(format % f)
2581 ui.write(format % f)
2582
2582
2583 def tag(ui, repo, name, rev_=None, **opts):
2583 def tag(ui, repo, name, rev_=None, **opts):
2584 """add a tag for the current tip or a given revision
2584 """add a tag for the current tip or a given revision
2585
2585
2586 Name a particular revision using <name>.
2586 Name a particular revision using <name>.
2587
2587
2588 Tags are used to name particular revisions of the repository and are
2588 Tags are used to name particular revisions of the repository and are
2589 very useful to compare different revision, to go back to significant
2589 very useful to compare different revision, to go back to significant
2590 earlier versions or to mark branch points as releases, etc.
2590 earlier versions or to mark branch points as releases, etc.
2591
2591
2592 If no revision is given, the tip is used.
2592 If no revision is given, the tip is used.
2593
2593
2594 To facilitate version control, distribution, and merging of tags,
2594 To facilitate version control, distribution, and merging of tags,
2595 they are stored as a file named ".hgtags" which is managed
2595 they are stored as a file named ".hgtags" which is managed
2596 similarly to other project files and can be hand-edited if
2596 similarly to other project files and can be hand-edited if
2597 necessary. The file '.hg/localtags' is used for local tags (not
2597 necessary. The file '.hg/localtags' is used for local tags (not
2598 shared among repositories).
2598 shared among repositories).
2599 """
2599 """
2600 if name == "tip":
2600 if name == "tip":
2601 raise util.Abort(_("the name 'tip' is reserved"))
2601 raise util.Abort(_("the name 'tip' is reserved"))
2602 if rev_ is not None:
2602 if rev_ is not None:
2603 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2603 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2604 "please use 'hg tag [-r REV] NAME' instead\n"))
2604 "please use 'hg tag [-r REV] NAME' instead\n"))
2605 if opts['rev']:
2605 if opts['rev']:
2606 raise util.Abort(_("use only one form to specify the revision"))
2606 raise util.Abort(_("use only one form to specify the revision"))
2607 if opts['rev']:
2607 if opts['rev']:
2608 rev_ = opts['rev']
2608 rev_ = opts['rev']
2609 if rev_:
2609 if rev_:
2610 r = hex(repo.lookup(rev_))
2610 r = hex(repo.lookup(rev_))
2611 else:
2611 else:
2612 r = hex(repo.changelog.tip())
2612 r = hex(repo.changelog.tip())
2613
2613
2614 disallowed = (revrangesep, '\r', '\n')
2614 disallowed = (revrangesep, '\r', '\n')
2615 for c in disallowed:
2615 for c in disallowed:
2616 if name.find(c) >= 0:
2616 if name.find(c) >= 0:
2617 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2617 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2618
2618
2619 repo.hook('pretag', throw=True, node=r, tag=name,
2619 repo.hook('pretag', throw=True, node=r, tag=name,
2620 local=int(not not opts['local']))
2620 local=int(not not opts['local']))
2621
2621
2622 if opts['local']:
2622 if opts['local']:
2623 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2623 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2624 repo.hook('tag', node=r, tag=name, local=1)
2624 repo.hook('tag', node=r, tag=name, local=1)
2625 return
2625 return
2626
2626
2627 for x in repo.changes():
2627 for x in repo.changes():
2628 if ".hgtags" in x:
2628 if ".hgtags" in x:
2629 raise util.Abort(_("working copy of .hgtags is changed "
2629 raise util.Abort(_("working copy of .hgtags is changed "
2630 "(please commit .hgtags manually)"))
2630 "(please commit .hgtags manually)"))
2631
2631
2632 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2632 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2633 if repo.dirstate.state(".hgtags") == '?':
2633 if repo.dirstate.state(".hgtags") == '?':
2634 repo.add([".hgtags"])
2634 repo.add([".hgtags"])
2635
2635
2636 message = (opts['message'] or
2636 message = (opts['message'] or
2637 _("Added tag %s for changeset %s") % (name, r))
2637 _("Added tag %s for changeset %s") % (name, r))
2638 try:
2638 try:
2639 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2639 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2640 repo.hook('tag', node=r, tag=name, local=0)
2640 repo.hook('tag', node=r, tag=name, local=0)
2641 except ValueError, inst:
2641 except ValueError, inst:
2642 raise util.Abort(str(inst))
2642 raise util.Abort(str(inst))
2643
2643
2644 def tags(ui, repo):
2644 def tags(ui, repo):
2645 """list repository tags
2645 """list repository tags
2646
2646
2647 List the repository tags.
2647 List the repository tags.
2648
2648
2649 This lists both regular and local tags.
2649 This lists both regular and local tags.
2650 """
2650 """
2651
2651
2652 l = repo.tagslist()
2652 l = repo.tagslist()
2653 l.reverse()
2653 l.reverse()
2654 for t, n in l:
2654 for t, n in l:
2655 try:
2655 try:
2656 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2656 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2657 except KeyError:
2657 except KeyError:
2658 r = " ?:?"
2658 r = " ?:?"
2659 if ui.quiet:
2659 if ui.quiet:
2660 ui.write("%s\n" % t)
2660 ui.write("%s\n" % t)
2661 else:
2661 else:
2662 ui.write("%-30s %s\n" % (t, r))
2662 ui.write("%-30s %s\n" % (t, r))
2663
2663
2664 def tip(ui, repo, **opts):
2664 def tip(ui, repo, **opts):
2665 """show the tip revision
2665 """show the tip revision
2666
2666
2667 Show the tip revision.
2667 Show the tip revision.
2668 """
2668 """
2669 n = repo.changelog.tip()
2669 n = repo.changelog.tip()
2670 br = None
2670 br = None
2671 if opts['branches']:
2671 if opts['branches']:
2672 br = repo.branchlookup([n])
2672 br = repo.branchlookup([n])
2673 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2673 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2674 if opts['patch']:
2674 if opts['patch']:
2675 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2675 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2676
2676
2677 def unbundle(ui, repo, fname, **opts):
2677 def unbundle(ui, repo, fname, **opts):
2678 """apply a changegroup file
2678 """apply a changegroup file
2679
2679
2680 Apply a compressed changegroup file generated by the bundle
2680 Apply a compressed changegroup file generated by the bundle
2681 command.
2681 command.
2682 """
2682 """
2683 f = urllib.urlopen(fname)
2683 f = urllib.urlopen(fname)
2684
2684
2685 header = f.read(6)
2685 header = f.read(6)
2686 if not header.startswith("HG"):
2686 if not header.startswith("HG"):
2687 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2687 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2688 elif not header.startswith("HG10"):
2688 elif not header.startswith("HG10"):
2689 raise util.Abort(_("%s: unknown bundle version") % fname)
2689 raise util.Abort(_("%s: unknown bundle version") % fname)
2690 elif header == "HG10BZ":
2690 elif header == "HG10BZ":
2691 def generator(f):
2691 def generator(f):
2692 zd = bz2.BZ2Decompressor()
2692 zd = bz2.BZ2Decompressor()
2693 zd.decompress("BZ")
2693 zd.decompress("BZ")
2694 for chunk in f:
2694 for chunk in f:
2695 yield zd.decompress(chunk)
2695 yield zd.decompress(chunk)
2696 elif header == "HG10UN":
2696 elif header == "HG10UN":
2697 def generator(f):
2697 def generator(f):
2698 for chunk in f:
2698 for chunk in f:
2699 yield chunk
2699 yield chunk
2700 else:
2700 else:
2701 raise util.Abort(_("%s: unknown bundle compression type")
2701 raise util.Abort(_("%s: unknown bundle compression type")
2702 % fname)
2702 % fname)
2703 gen = generator(util.filechunkiter(f, 4096))
2703 gen = generator(util.filechunkiter(f, 4096))
2704 modheads = repo.addchangegroup(util.chunkbuffer(gen))
2704 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2705 return postincoming(ui, repo, modheads, opts['update'])
2705 return postincoming(ui, repo, modheads, opts['update'])
2706
2706
2707 def undo(ui, repo):
2707 def undo(ui, repo):
2708 """undo the last commit or pull (DEPRECATED)
2708 """undo the last commit or pull (DEPRECATED)
2709
2709
2710 (DEPRECATED)
2710 (DEPRECATED)
2711 This command is now deprecated and will be removed in a future
2711 This command is now deprecated and will be removed in a future
2712 release. Please use the rollback command instead. For usage
2712 release. Please use the rollback command instead. For usage
2713 instructions, see the rollback command.
2713 instructions, see the rollback command.
2714 """
2714 """
2715 repo.undo()
2715 repo.undo()
2716
2716
2717 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2717 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2718 branch=None, **opts):
2718 branch=None, **opts):
2719 """update or merge working directory
2719 """update or merge working directory
2720
2720
2721 Update the working directory to the specified revision.
2721 Update the working directory to the specified revision.
2722
2722
2723 If there are no outstanding changes in the working directory and
2723 If there are no outstanding changes in the working directory and
2724 there is a linear relationship between the current version and the
2724 there is a linear relationship between the current version and the
2725 requested version, the result is the requested version.
2725 requested version, the result is the requested version.
2726
2726
2727 Otherwise the result is a merge between the contents of the
2727 Otherwise the result is a merge between the contents of the
2728 current working directory and the requested version. Files that
2728 current working directory and the requested version. Files that
2729 changed between either parent are marked as changed for the next
2729 changed between either parent are marked as changed for the next
2730 commit and a commit must be performed before any further updates
2730 commit and a commit must be performed before any further updates
2731 are allowed.
2731 are allowed.
2732
2732
2733 By default, update will refuse to run if doing so would require
2733 By default, update will refuse to run if doing so would require
2734 merging or discarding local changes.
2734 merging or discarding local changes.
2735 """
2735 """
2736 if branch:
2736 if branch:
2737 br = repo.branchlookup(branch=branch)
2737 br = repo.branchlookup(branch=branch)
2738 found = []
2738 found = []
2739 for x in br:
2739 for x in br:
2740 if branch in br[x]:
2740 if branch in br[x]:
2741 found.append(x)
2741 found.append(x)
2742 if len(found) > 1:
2742 if len(found) > 1:
2743 ui.warn(_("Found multiple heads for %s\n") % branch)
2743 ui.warn(_("Found multiple heads for %s\n") % branch)
2744 for x in found:
2744 for x in found:
2745 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2745 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2746 return 1
2746 return 1
2747 if len(found) == 1:
2747 if len(found) == 1:
2748 node = found[0]
2748 node = found[0]
2749 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2749 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2750 else:
2750 else:
2751 ui.warn(_("branch %s not found\n") % (branch))
2751 ui.warn(_("branch %s not found\n") % (branch))
2752 return 1
2752 return 1
2753 else:
2753 else:
2754 node = node and repo.lookup(node) or repo.changelog.tip()
2754 node = node and repo.lookup(node) or repo.changelog.tip()
2755 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2755 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2756
2756
2757 def verify(ui, repo):
2757 def verify(ui, repo):
2758 """verify the integrity of the repository
2758 """verify the integrity of the repository
2759
2759
2760 Verify the integrity of the current repository.
2760 Verify the integrity of the current repository.
2761
2761
2762 This will perform an extensive check of the repository's
2762 This will perform an extensive check of the repository's
2763 integrity, validating the hashes and checksums of each entry in
2763 integrity, validating the hashes and checksums of each entry in
2764 the changelog, manifest, and tracked files, as well as the
2764 the changelog, manifest, and tracked files, as well as the
2765 integrity of their crosslinks and indices.
2765 integrity of their crosslinks and indices.
2766 """
2766 """
2767 return repo.verify()
2767 return repo.verify()
2768
2768
2769 # Command options and aliases are listed here, alphabetically
2769 # Command options and aliases are listed here, alphabetically
2770
2770
2771 table = {
2771 table = {
2772 "^add":
2772 "^add":
2773 (add,
2773 (add,
2774 [('I', 'include', [], _('include names matching the given patterns')),
2774 [('I', 'include', [], _('include names matching the given patterns')),
2775 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2775 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2776 _('hg add [OPTION]... [FILE]...')),
2776 _('hg add [OPTION]... [FILE]...')),
2777 "debugaddremove|addremove":
2777 "debugaddremove|addremove":
2778 (addremove,
2778 (addremove,
2779 [('I', 'include', [], _('include names matching the given patterns')),
2779 [('I', 'include', [], _('include names matching the given patterns')),
2780 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2780 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2781 _('hg addremove [OPTION]... [FILE]...')),
2781 _('hg addremove [OPTION]... [FILE]...')),
2782 "^annotate":
2782 "^annotate":
2783 (annotate,
2783 (annotate,
2784 [('r', 'rev', '', _('annotate the specified revision')),
2784 [('r', 'rev', '', _('annotate the specified revision')),
2785 ('a', 'text', None, _('treat all files as text')),
2785 ('a', 'text', None, _('treat all files as text')),
2786 ('u', 'user', None, _('list the author')),
2786 ('u', 'user', None, _('list the author')),
2787 ('d', 'date', None, _('list the date')),
2787 ('d', 'date', None, _('list the date')),
2788 ('n', 'number', None, _('list the revision number (default)')),
2788 ('n', 'number', None, _('list the revision number (default)')),
2789 ('c', 'changeset', None, _('list the changeset')),
2789 ('c', 'changeset', None, _('list the changeset')),
2790 ('I', 'include', [], _('include names matching the given patterns')),
2790 ('I', 'include', [], _('include names matching the given patterns')),
2791 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2791 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2792 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2792 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2793 "archive":
2793 "archive":
2794 (archive,
2794 (archive,
2795 [('', 'no-decode', None, _('do not pass files through decoders')),
2795 [('', 'no-decode', None, _('do not pass files through decoders')),
2796 ('p', 'prefix', '', _('directory prefix for files in archive')),
2796 ('p', 'prefix', '', _('directory prefix for files in archive')),
2797 ('r', 'rev', '', _('revision to distribute')),
2797 ('r', 'rev', '', _('revision to distribute')),
2798 ('t', 'type', '', _('type of distribution to create')),
2798 ('t', 'type', '', _('type of distribution to create')),
2799 ('I', 'include', [], _('include names matching the given patterns')),
2799 ('I', 'include', [], _('include names matching the given patterns')),
2800 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2800 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2801 _('hg archive [OPTION]... DEST')),
2801 _('hg archive [OPTION]... DEST')),
2802 "backout":
2802 "backout":
2803 (backout,
2803 (backout,
2804 [('', 'merge', None,
2804 [('', 'merge', None,
2805 _('merge with old dirstate parent after backout')),
2805 _('merge with old dirstate parent after backout')),
2806 ('m', 'message', '', _('use <text> as commit message')),
2806 ('m', 'message', '', _('use <text> as commit message')),
2807 ('l', 'logfile', '', _('read commit message from <file>')),
2807 ('l', 'logfile', '', _('read commit message from <file>')),
2808 ('d', 'date', '', _('record datecode as commit date')),
2808 ('d', 'date', '', _('record datecode as commit date')),
2809 ('u', 'user', '', _('record user as committer')),
2809 ('u', 'user', '', _('record user as committer')),
2810 ('I', 'include', [], _('include names matching the given patterns')),
2810 ('I', 'include', [], _('include names matching the given patterns')),
2811 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2811 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2812 _('hg backout [OPTION]... REV')),
2812 _('hg backout [OPTION]... REV')),
2813 "bundle":
2813 "bundle":
2814 (bundle,
2814 (bundle,
2815 [('f', 'force', None,
2815 [('f', 'force', None,
2816 _('run even when remote repository is unrelated'))],
2816 _('run even when remote repository is unrelated'))],
2817 _('hg bundle FILE DEST')),
2817 _('hg bundle FILE DEST')),
2818 "cat":
2818 "cat":
2819 (cat,
2819 (cat,
2820 [('o', 'output', '', _('print output to file with formatted name')),
2820 [('o', 'output', '', _('print output to file with formatted name')),
2821 ('r', 'rev', '', _('print the given revision')),
2821 ('r', 'rev', '', _('print the given revision')),
2822 ('I', 'include', [], _('include names matching the given patterns')),
2822 ('I', 'include', [], _('include names matching the given patterns')),
2823 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2823 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2824 _('hg cat [OPTION]... FILE...')),
2824 _('hg cat [OPTION]... FILE...')),
2825 "^clone":
2825 "^clone":
2826 (clone,
2826 (clone,
2827 [('U', 'noupdate', None, _('do not update the new working directory')),
2827 [('U', 'noupdate', None, _('do not update the new working directory')),
2828 ('r', 'rev', [],
2828 ('r', 'rev', [],
2829 _('a changeset you would like to have after cloning')),
2829 _('a changeset you would like to have after cloning')),
2830 ('', 'pull', None, _('use pull protocol to copy metadata')),
2830 ('', 'pull', None, _('use pull protocol to copy metadata')),
2831 ('e', 'ssh', '', _('specify ssh command to use')),
2831 ('e', 'ssh', '', _('specify ssh command to use')),
2832 ('', 'remotecmd', '',
2832 ('', 'remotecmd', '',
2833 _('specify hg command to run on the remote side'))],
2833 _('specify hg command to run on the remote side'))],
2834 _('hg clone [OPTION]... SOURCE [DEST]')),
2834 _('hg clone [OPTION]... SOURCE [DEST]')),
2835 "^commit|ci":
2835 "^commit|ci":
2836 (commit,
2836 (commit,
2837 [('A', 'addremove', None,
2837 [('A', 'addremove', None,
2838 _('mark new/missing files as added/removed before committing')),
2838 _('mark new/missing files as added/removed before committing')),
2839 ('m', 'message', '', _('use <text> as commit message')),
2839 ('m', 'message', '', _('use <text> as commit message')),
2840 ('l', 'logfile', '', _('read the commit message from <file>')),
2840 ('l', 'logfile', '', _('read the commit message from <file>')),
2841 ('d', 'date', '', _('record datecode as commit date')),
2841 ('d', 'date', '', _('record datecode as commit date')),
2842 ('u', 'user', '', _('record user as commiter')),
2842 ('u', 'user', '', _('record user as commiter')),
2843 ('I', 'include', [], _('include names matching the given patterns')),
2843 ('I', 'include', [], _('include names matching the given patterns')),
2844 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2844 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2845 _('hg commit [OPTION]... [FILE]...')),
2845 _('hg commit [OPTION]... [FILE]...')),
2846 "copy|cp":
2846 "copy|cp":
2847 (copy,
2847 (copy,
2848 [('A', 'after', None, _('record a copy that has already occurred')),
2848 [('A', 'after', None, _('record a copy that has already occurred')),
2849 ('f', 'force', None,
2849 ('f', 'force', None,
2850 _('forcibly copy over an existing managed file')),
2850 _('forcibly copy over an existing managed file')),
2851 ('I', 'include', [], _('include names matching the given patterns')),
2851 ('I', 'include', [], _('include names matching the given patterns')),
2852 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2852 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2853 _('hg copy [OPTION]... [SOURCE]... DEST')),
2853 _('hg copy [OPTION]... [SOURCE]... DEST')),
2854 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2854 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2855 "debugcomplete":
2855 "debugcomplete":
2856 (debugcomplete,
2856 (debugcomplete,
2857 [('o', 'options', None, _('show the command options'))],
2857 [('o', 'options', None, _('show the command options'))],
2858 _('debugcomplete [-o] CMD')),
2858 _('debugcomplete [-o] CMD')),
2859 "debugrebuildstate":
2859 "debugrebuildstate":
2860 (debugrebuildstate,
2860 (debugrebuildstate,
2861 [('r', 'rev', '', _('revision to rebuild to'))],
2861 [('r', 'rev', '', _('revision to rebuild to'))],
2862 _('debugrebuildstate [-r REV] [REV]')),
2862 _('debugrebuildstate [-r REV] [REV]')),
2863 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2863 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2864 "debugconfig": (debugconfig, [], _('debugconfig')),
2864 "debugconfig": (debugconfig, [], _('debugconfig')),
2865 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2865 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2866 "debugstate": (debugstate, [], _('debugstate')),
2866 "debugstate": (debugstate, [], _('debugstate')),
2867 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2867 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2868 "debugindex": (debugindex, [], _('debugindex FILE')),
2868 "debugindex": (debugindex, [], _('debugindex FILE')),
2869 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2869 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2870 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2870 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2871 "debugwalk":
2871 "debugwalk":
2872 (debugwalk,
2872 (debugwalk,
2873 [('I', 'include', [], _('include names matching the given patterns')),
2873 [('I', 'include', [], _('include names matching the given patterns')),
2874 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2874 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2875 _('debugwalk [OPTION]... [FILE]...')),
2875 _('debugwalk [OPTION]... [FILE]...')),
2876 "^diff":
2876 "^diff":
2877 (diff,
2877 (diff,
2878 [('r', 'rev', [], _('revision')),
2878 [('r', 'rev', [], _('revision')),
2879 ('a', 'text', None, _('treat all files as text')),
2879 ('a', 'text', None, _('treat all files as text')),
2880 ('p', 'show-function', None,
2880 ('p', 'show-function', None,
2881 _('show which function each change is in')),
2881 _('show which function each change is in')),
2882 ('w', 'ignore-all-space', None,
2882 ('w', 'ignore-all-space', None,
2883 _('ignore white space when comparing lines')),
2883 _('ignore white space when comparing lines')),
2884 ('I', 'include', [], _('include names matching the given patterns')),
2884 ('I', 'include', [], _('include names matching the given patterns')),
2885 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2885 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2886 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2886 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2887 "^export":
2887 "^export":
2888 (export,
2888 (export,
2889 [('o', 'output', '', _('print output to file with formatted name')),
2889 [('o', 'output', '', _('print output to file with formatted name')),
2890 ('a', 'text', None, _('treat all files as text')),
2890 ('a', 'text', None, _('treat all files as text')),
2891 ('', 'switch-parent', None, _('diff against the second parent'))],
2891 ('', 'switch-parent', None, _('diff against the second parent'))],
2892 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2892 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2893 "debugforget|forget":
2893 "debugforget|forget":
2894 (forget,
2894 (forget,
2895 [('I', 'include', [], _('include names matching the given patterns')),
2895 [('I', 'include', [], _('include names matching the given patterns')),
2896 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2896 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2897 _('hg forget [OPTION]... FILE...')),
2897 _('hg forget [OPTION]... FILE...')),
2898 "grep":
2898 "grep":
2899 (grep,
2899 (grep,
2900 [('0', 'print0', None, _('end fields with NUL')),
2900 [('0', 'print0', None, _('end fields with NUL')),
2901 ('', 'all', None, _('print all revisions that match')),
2901 ('', 'all', None, _('print all revisions that match')),
2902 ('i', 'ignore-case', None, _('ignore case when matching')),
2902 ('i', 'ignore-case', None, _('ignore case when matching')),
2903 ('l', 'files-with-matches', None,
2903 ('l', 'files-with-matches', None,
2904 _('print only filenames and revs that match')),
2904 _('print only filenames and revs that match')),
2905 ('n', 'line-number', None, _('print matching line numbers')),
2905 ('n', 'line-number', None, _('print matching line numbers')),
2906 ('r', 'rev', [], _('search in given revision range')),
2906 ('r', 'rev', [], _('search in given revision range')),
2907 ('u', 'user', None, _('print user who committed change')),
2907 ('u', 'user', None, _('print user who committed change')),
2908 ('I', 'include', [], _('include names matching the given patterns')),
2908 ('I', 'include', [], _('include names matching the given patterns')),
2909 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2909 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2910 _('hg grep [OPTION]... PATTERN [FILE]...')),
2910 _('hg grep [OPTION]... PATTERN [FILE]...')),
2911 "heads":
2911 "heads":
2912 (heads,
2912 (heads,
2913 [('b', 'branches', None, _('show branches')),
2913 [('b', 'branches', None, _('show branches')),
2914 ('', 'style', '', _('display using template map file')),
2914 ('', 'style', '', _('display using template map file')),
2915 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2915 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2916 ('', 'template', '', _('display with template'))],
2916 ('', 'template', '', _('display with template'))],
2917 _('hg heads [-b] [-r <rev>]')),
2917 _('hg heads [-b] [-r <rev>]')),
2918 "help": (help_, [], _('hg help [COMMAND]')),
2918 "help": (help_, [], _('hg help [COMMAND]')),
2919 "identify|id": (identify, [], _('hg identify')),
2919 "identify|id": (identify, [], _('hg identify')),
2920 "import|patch":
2920 "import|patch":
2921 (import_,
2921 (import_,
2922 [('p', 'strip', 1,
2922 [('p', 'strip', 1,
2923 _('directory strip option for patch. This has the same\n'
2923 _('directory strip option for patch. This has the same\n'
2924 'meaning as the corresponding patch option')),
2924 'meaning as the corresponding patch option')),
2925 ('b', 'base', '', _('base path')),
2925 ('b', 'base', '', _('base path')),
2926 ('f', 'force', None,
2926 ('f', 'force', None,
2927 _('skip check for outstanding uncommitted changes'))],
2927 _('skip check for outstanding uncommitted changes'))],
2928 _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
2928 _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
2929 "incoming|in": (incoming,
2929 "incoming|in": (incoming,
2930 [('M', 'no-merges', None, _('do not show merges')),
2930 [('M', 'no-merges', None, _('do not show merges')),
2931 ('f', 'force', None,
2931 ('f', 'force', None,
2932 _('run even when remote repository is unrelated')),
2932 _('run even when remote repository is unrelated')),
2933 ('', 'style', '', _('display using template map file')),
2933 ('', 'style', '', _('display using template map file')),
2934 ('n', 'newest-first', None, _('show newest record first')),
2934 ('n', 'newest-first', None, _('show newest record first')),
2935 ('', 'bundle', '', _('file to store the bundles into')),
2935 ('', 'bundle', '', _('file to store the bundles into')),
2936 ('p', 'patch', None, _('show patch')),
2936 ('p', 'patch', None, _('show patch')),
2937 ('', 'template', '', _('display with template')),
2937 ('', 'template', '', _('display with template')),
2938 ('e', 'ssh', '', _('specify ssh command to use')),
2938 ('e', 'ssh', '', _('specify ssh command to use')),
2939 ('', 'remotecmd', '',
2939 ('', 'remotecmd', '',
2940 _('specify hg command to run on the remote side'))],
2940 _('specify hg command to run on the remote side'))],
2941 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
2941 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
2942 "^init": (init, [], _('hg init [DEST]')),
2942 "^init": (init, [], _('hg init [DEST]')),
2943 "locate":
2943 "locate":
2944 (locate,
2944 (locate,
2945 [('r', 'rev', '', _('search the repository as it stood at rev')),
2945 [('r', 'rev', '', _('search the repository as it stood at rev')),
2946 ('0', 'print0', None,
2946 ('0', 'print0', None,
2947 _('end filenames with NUL, for use with xargs')),
2947 _('end filenames with NUL, for use with xargs')),
2948 ('f', 'fullpath', None,
2948 ('f', 'fullpath', None,
2949 _('print complete paths from the filesystem root')),
2949 _('print complete paths from the filesystem root')),
2950 ('I', 'include', [], _('include names matching the given patterns')),
2950 ('I', 'include', [], _('include names matching the given patterns')),
2951 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2951 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2952 _('hg locate [OPTION]... [PATTERN]...')),
2952 _('hg locate [OPTION]... [PATTERN]...')),
2953 "^log|history":
2953 "^log|history":
2954 (log,
2954 (log,
2955 [('b', 'branches', None, _('show branches')),
2955 [('b', 'branches', None, _('show branches')),
2956 ('k', 'keyword', [], _('search for a keyword')),
2956 ('k', 'keyword', [], _('search for a keyword')),
2957 ('l', 'limit', '', _('limit number of changes displayed')),
2957 ('l', 'limit', '', _('limit number of changes displayed')),
2958 ('r', 'rev', [], _('show the specified revision or range')),
2958 ('r', 'rev', [], _('show the specified revision or range')),
2959 ('M', 'no-merges', None, _('do not show merges')),
2959 ('M', 'no-merges', None, _('do not show merges')),
2960 ('', 'style', '', _('display using template map file')),
2960 ('', 'style', '', _('display using template map file')),
2961 ('m', 'only-merges', None, _('show only merges')),
2961 ('m', 'only-merges', None, _('show only merges')),
2962 ('p', 'patch', None, _('show patch')),
2962 ('p', 'patch', None, _('show patch')),
2963 ('', 'template', '', _('display with template')),
2963 ('', 'template', '', _('display with template')),
2964 ('I', 'include', [], _('include names matching the given patterns')),
2964 ('I', 'include', [], _('include names matching the given patterns')),
2965 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2965 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2966 _('hg log [OPTION]... [FILE]')),
2966 _('hg log [OPTION]... [FILE]')),
2967 "manifest": (manifest, [], _('hg manifest [REV]')),
2967 "manifest": (manifest, [], _('hg manifest [REV]')),
2968 "merge":
2968 "merge":
2969 (merge,
2969 (merge,
2970 [('b', 'branch', '', _('merge with head of a specific branch')),
2970 [('b', 'branch', '', _('merge with head of a specific branch')),
2971 ('f', 'force', None, _('force a merge with outstanding changes'))],
2971 ('f', 'force', None, _('force a merge with outstanding changes'))],
2972 _('hg merge [-b TAG] [-f] [REV]')),
2972 _('hg merge [-b TAG] [-f] [REV]')),
2973 "outgoing|out": (outgoing,
2973 "outgoing|out": (outgoing,
2974 [('M', 'no-merges', None, _('do not show merges')),
2974 [('M', 'no-merges', None, _('do not show merges')),
2975 ('f', 'force', None,
2975 ('f', 'force', None,
2976 _('run even when remote repository is unrelated')),
2976 _('run even when remote repository is unrelated')),
2977 ('p', 'patch', None, _('show patch')),
2977 ('p', 'patch', None, _('show patch')),
2978 ('', 'style', '', _('display using template map file')),
2978 ('', 'style', '', _('display using template map file')),
2979 ('n', 'newest-first', None, _('show newest record first')),
2979 ('n', 'newest-first', None, _('show newest record first')),
2980 ('', 'template', '', _('display with template')),
2980 ('', 'template', '', _('display with template')),
2981 ('e', 'ssh', '', _('specify ssh command to use')),
2981 ('e', 'ssh', '', _('specify ssh command to use')),
2982 ('', 'remotecmd', '',
2982 ('', 'remotecmd', '',
2983 _('specify hg command to run on the remote side'))],
2983 _('specify hg command to run on the remote side'))],
2984 _('hg outgoing [-M] [-p] [-n] [DEST]')),
2984 _('hg outgoing [-M] [-p] [-n] [DEST]')),
2985 "^parents":
2985 "^parents":
2986 (parents,
2986 (parents,
2987 [('b', 'branches', None, _('show branches')),
2987 [('b', 'branches', None, _('show branches')),
2988 ('', 'style', '', _('display using template map file')),
2988 ('', 'style', '', _('display using template map file')),
2989 ('', 'template', '', _('display with template'))],
2989 ('', 'template', '', _('display with template'))],
2990 _('hg parents [-b] [REV]')),
2990 _('hg parents [-b] [REV]')),
2991 "paths": (paths, [], _('hg paths [NAME]')),
2991 "paths": (paths, [], _('hg paths [NAME]')),
2992 "^pull":
2992 "^pull":
2993 (pull,
2993 (pull,
2994 [('u', 'update', None,
2994 [('u', 'update', None,
2995 _('update the working directory to tip after pull')),
2995 _('update the working directory to tip after pull')),
2996 ('e', 'ssh', '', _('specify ssh command to use')),
2996 ('e', 'ssh', '', _('specify ssh command to use')),
2997 ('f', 'force', None,
2997 ('f', 'force', None,
2998 _('run even when remote repository is unrelated')),
2998 _('run even when remote repository is unrelated')),
2999 ('r', 'rev', [], _('a specific revision you would like to pull')),
2999 ('r', 'rev', [], _('a specific revision you would like to pull')),
3000 ('', 'remotecmd', '',
3000 ('', 'remotecmd', '',
3001 _('specify hg command to run on the remote side'))],
3001 _('specify hg command to run on the remote side'))],
3002 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3002 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3003 "^push":
3003 "^push":
3004 (push,
3004 (push,
3005 [('f', 'force', None, _('force push')),
3005 [('f', 'force', None, _('force push')),
3006 ('e', 'ssh', '', _('specify ssh command to use')),
3006 ('e', 'ssh', '', _('specify ssh command to use')),
3007 ('r', 'rev', [], _('a specific revision you would like to push')),
3007 ('r', 'rev', [], _('a specific revision you would like to push')),
3008 ('', 'remotecmd', '',
3008 ('', 'remotecmd', '',
3009 _('specify hg command to run on the remote side'))],
3009 _('specify hg command to run on the remote side'))],
3010 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3010 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3011 "debugrawcommit|rawcommit":
3011 "debugrawcommit|rawcommit":
3012 (rawcommit,
3012 (rawcommit,
3013 [('p', 'parent', [], _('parent')),
3013 [('p', 'parent', [], _('parent')),
3014 ('d', 'date', '', _('date code')),
3014 ('d', 'date', '', _('date code')),
3015 ('u', 'user', '', _('user')),
3015 ('u', 'user', '', _('user')),
3016 ('F', 'files', '', _('file list')),
3016 ('F', 'files', '', _('file list')),
3017 ('m', 'message', '', _('commit message')),
3017 ('m', 'message', '', _('commit message')),
3018 ('l', 'logfile', '', _('commit message file'))],
3018 ('l', 'logfile', '', _('commit message file'))],
3019 _('hg debugrawcommit [OPTION]... [FILE]...')),
3019 _('hg debugrawcommit [OPTION]... [FILE]...')),
3020 "recover": (recover, [], _('hg recover')),
3020 "recover": (recover, [], _('hg recover')),
3021 "^remove|rm":
3021 "^remove|rm":
3022 (remove,
3022 (remove,
3023 [('', 'after', None, _('record remove that has already occurred')),
3023 [('', 'after', None, _('record remove that has already occurred')),
3024 ('f', 'force', None, _('remove file even if modified')),
3024 ('f', 'force', None, _('remove file even if modified')),
3025 ('I', 'include', [], _('include names matching the given patterns')),
3025 ('I', 'include', [], _('include names matching the given patterns')),
3026 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3026 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3027 _('hg remove [OPTION]... FILE...')),
3027 _('hg remove [OPTION]... FILE...')),
3028 "rename|mv":
3028 "rename|mv":
3029 (rename,
3029 (rename,
3030 [('A', 'after', None, _('record a rename that has already occurred')),
3030 [('A', 'after', None, _('record a rename that has already occurred')),
3031 ('f', 'force', None,
3031 ('f', 'force', None,
3032 _('forcibly copy over an existing managed file')),
3032 _('forcibly copy over an existing managed file')),
3033 ('I', 'include', [], _('include names matching the given patterns')),
3033 ('I', 'include', [], _('include names matching the given patterns')),
3034 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3034 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3035 _('hg rename [OPTION]... SOURCE... DEST')),
3035 _('hg rename [OPTION]... SOURCE... DEST')),
3036 "^revert":
3036 "^revert":
3037 (revert,
3037 (revert,
3038 [('r', 'rev', '', _('revision to revert to')),
3038 [('r', 'rev', '', _('revision to revert to')),
3039 ('', 'no-backup', None, _('do not save backup copies of files')),
3039 ('', 'no-backup', None, _('do not save backup copies of files')),
3040 ('I', 'include', [], _('include names matching given patterns')),
3040 ('I', 'include', [], _('include names matching given patterns')),
3041 ('X', 'exclude', [], _('exclude names matching given patterns'))],
3041 ('X', 'exclude', [], _('exclude names matching given patterns'))],
3042 _('hg revert [-r REV] [NAME]...')),
3042 _('hg revert [-r REV] [NAME]...')),
3043 "rollback": (rollback, [], _('hg rollback')),
3043 "rollback": (rollback, [], _('hg rollback')),
3044 "root": (root, [], _('hg root')),
3044 "root": (root, [], _('hg root')),
3045 "^serve":
3045 "^serve":
3046 (serve,
3046 (serve,
3047 [('A', 'accesslog', '', _('name of access log file to write to')),
3047 [('A', 'accesslog', '', _('name of access log file to write to')),
3048 ('d', 'daemon', None, _('run server in background')),
3048 ('d', 'daemon', None, _('run server in background')),
3049 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3049 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3050 ('E', 'errorlog', '', _('name of error log file to write to')),
3050 ('E', 'errorlog', '', _('name of error log file to write to')),
3051 ('p', 'port', 0, _('port to use (default: 8000)')),
3051 ('p', 'port', 0, _('port to use (default: 8000)')),
3052 ('a', 'address', '', _('address to use')),
3052 ('a', 'address', '', _('address to use')),
3053 ('n', 'name', '',
3053 ('n', 'name', '',
3054 _('name to show in web pages (default: working dir)')),
3054 _('name to show in web pages (default: working dir)')),
3055 ('', 'webdir-conf', '', _('name of the webdir config file'
3055 ('', 'webdir-conf', '', _('name of the webdir config file'
3056 ' (serve more than one repo)')),
3056 ' (serve more than one repo)')),
3057 ('', 'pid-file', '', _('name of file to write process ID to')),
3057 ('', 'pid-file', '', _('name of file to write process ID to')),
3058 ('', 'stdio', None, _('for remote clients')),
3058 ('', 'stdio', None, _('for remote clients')),
3059 ('t', 'templates', '', _('web templates to use')),
3059 ('t', 'templates', '', _('web templates to use')),
3060 ('', 'style', '', _('template style to use')),
3060 ('', 'style', '', _('template style to use')),
3061 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3061 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3062 _('hg serve [OPTION]...')),
3062 _('hg serve [OPTION]...')),
3063 "^status|st":
3063 "^status|st":
3064 (status,
3064 (status,
3065 [('m', 'modified', None, _('show only modified files')),
3065 [('m', 'modified', None, _('show only modified files')),
3066 ('a', 'added', None, _('show only added files')),
3066 ('a', 'added', None, _('show only added files')),
3067 ('r', 'removed', None, _('show only removed files')),
3067 ('r', 'removed', None, _('show only removed files')),
3068 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3068 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3069 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3069 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3070 ('i', 'ignored', None, _('show ignored files')),
3070 ('i', 'ignored', None, _('show ignored files')),
3071 ('n', 'no-status', None, _('hide status prefix')),
3071 ('n', 'no-status', None, _('hide status prefix')),
3072 ('0', 'print0', None,
3072 ('0', 'print0', None,
3073 _('end filenames with NUL, for use with xargs')),
3073 _('end filenames with NUL, for use with xargs')),
3074 ('I', 'include', [], _('include names matching the given patterns')),
3074 ('I', 'include', [], _('include names matching the given patterns')),
3075 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3075 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3076 _('hg status [OPTION]... [FILE]...')),
3076 _('hg status [OPTION]... [FILE]...')),
3077 "tag":
3077 "tag":
3078 (tag,
3078 (tag,
3079 [('l', 'local', None, _('make the tag local')),
3079 [('l', 'local', None, _('make the tag local')),
3080 ('m', 'message', '', _('message for tag commit log entry')),
3080 ('m', 'message', '', _('message for tag commit log entry')),
3081 ('d', 'date', '', _('record datecode as commit date')),
3081 ('d', 'date', '', _('record datecode as commit date')),
3082 ('u', 'user', '', _('record user as commiter')),
3082 ('u', 'user', '', _('record user as commiter')),
3083 ('r', 'rev', '', _('revision to tag'))],
3083 ('r', 'rev', '', _('revision to tag'))],
3084 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3084 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3085 "tags": (tags, [], _('hg tags')),
3085 "tags": (tags, [], _('hg tags')),
3086 "tip":
3086 "tip":
3087 (tip,
3087 (tip,
3088 [('b', 'branches', None, _('show branches')),
3088 [('b', 'branches', None, _('show branches')),
3089 ('', 'style', '', _('display using template map file')),
3089 ('', 'style', '', _('display using template map file')),
3090 ('p', 'patch', None, _('show patch')),
3090 ('p', 'patch', None, _('show patch')),
3091 ('', 'template', '', _('display with template'))],
3091 ('', 'template', '', _('display with template'))],
3092 _('hg tip [-b] [-p]')),
3092 _('hg tip [-b] [-p]')),
3093 "unbundle":
3093 "unbundle":
3094 (unbundle,
3094 (unbundle,
3095 [('u', 'update', None,
3095 [('u', 'update', None,
3096 _('update the working directory to tip after unbundle'))],
3096 _('update the working directory to tip after unbundle'))],
3097 _('hg unbundle [-u] FILE')),
3097 _('hg unbundle [-u] FILE')),
3098 "undo": (undo, [], _('hg undo')),
3098 "undo": (undo, [], _('hg undo')),
3099 "^update|up|checkout|co":
3099 "^update|up|checkout|co":
3100 (update,
3100 (update,
3101 [('b', 'branch', '', _('checkout the head of a specific branch')),
3101 [('b', 'branch', '', _('checkout the head of a specific branch')),
3102 ('m', 'merge', None, _('allow merging of branches')),
3102 ('m', 'merge', None, _('allow merging of branches')),
3103 ('C', 'clean', None, _('overwrite locally modified files')),
3103 ('C', 'clean', None, _('overwrite locally modified files')),
3104 ('f', 'force', None, _('force a merge with outstanding changes'))],
3104 ('f', 'force', None, _('force a merge with outstanding changes'))],
3105 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3105 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3106 "verify": (verify, [], _('hg verify')),
3106 "verify": (verify, [], _('hg verify')),
3107 "version": (show_version, [], _('hg version')),
3107 "version": (show_version, [], _('hg version')),
3108 }
3108 }
3109
3109
3110 globalopts = [
3110 globalopts = [
3111 ('R', 'repository', '',
3111 ('R', 'repository', '',
3112 _('repository root directory or symbolic path name')),
3112 _('repository root directory or symbolic path name')),
3113 ('', 'cwd', '', _('change working directory')),
3113 ('', 'cwd', '', _('change working directory')),
3114 ('y', 'noninteractive', None,
3114 ('y', 'noninteractive', None,
3115 _('do not prompt, assume \'yes\' for any required answers')),
3115 _('do not prompt, assume \'yes\' for any required answers')),
3116 ('q', 'quiet', None, _('suppress output')),
3116 ('q', 'quiet', None, _('suppress output')),
3117 ('v', 'verbose', None, _('enable additional output')),
3117 ('v', 'verbose', None, _('enable additional output')),
3118 ('', 'debug', None, _('enable debugging output')),
3118 ('', 'debug', None, _('enable debugging output')),
3119 ('', 'debugger', None, _('start debugger')),
3119 ('', 'debugger', None, _('start debugger')),
3120 ('', 'traceback', None, _('print traceback on exception')),
3120 ('', 'traceback', None, _('print traceback on exception')),
3121 ('', 'time', None, _('time how long the command takes')),
3121 ('', 'time', None, _('time how long the command takes')),
3122 ('', 'profile', None, _('print command execution profile')),
3122 ('', 'profile', None, _('print command execution profile')),
3123 ('', 'version', None, _('output version information and exit')),
3123 ('', 'version', None, _('output version information and exit')),
3124 ('h', 'help', None, _('display help and exit')),
3124 ('h', 'help', None, _('display help and exit')),
3125 ]
3125 ]
3126
3126
3127 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3127 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3128 " debugindex debugindexdot")
3128 " debugindex debugindexdot")
3129 optionalrepo = ("paths serve debugconfig")
3129 optionalrepo = ("paths serve debugconfig")
3130
3130
3131 def findpossible(cmd):
3131 def findpossible(cmd):
3132 """
3132 """
3133 Return cmd -> (aliases, command table entry)
3133 Return cmd -> (aliases, command table entry)
3134 for each matching command.
3134 for each matching command.
3135 Return debug commands (or their aliases) only if no normal command matches.
3135 Return debug commands (or their aliases) only if no normal command matches.
3136 """
3136 """
3137 choice = {}
3137 choice = {}
3138 debugchoice = {}
3138 debugchoice = {}
3139 for e in table.keys():
3139 for e in table.keys():
3140 aliases = e.lstrip("^").split("|")
3140 aliases = e.lstrip("^").split("|")
3141 found = None
3141 found = None
3142 if cmd in aliases:
3142 if cmd in aliases:
3143 found = cmd
3143 found = cmd
3144 else:
3144 else:
3145 for a in aliases:
3145 for a in aliases:
3146 if a.startswith(cmd):
3146 if a.startswith(cmd):
3147 found = a
3147 found = a
3148 break
3148 break
3149 if found is not None:
3149 if found is not None:
3150 if aliases[0].startswith("debug"):
3150 if aliases[0].startswith("debug"):
3151 debugchoice[found] = (aliases, table[e])
3151 debugchoice[found] = (aliases, table[e])
3152 else:
3152 else:
3153 choice[found] = (aliases, table[e])
3153 choice[found] = (aliases, table[e])
3154
3154
3155 if not choice and debugchoice:
3155 if not choice and debugchoice:
3156 choice = debugchoice
3156 choice = debugchoice
3157
3157
3158 return choice
3158 return choice
3159
3159
3160 def find(cmd):
3160 def find(cmd):
3161 """Return (aliases, command table entry) for command string."""
3161 """Return (aliases, command table entry) for command string."""
3162 choice = findpossible(cmd)
3162 choice = findpossible(cmd)
3163
3163
3164 if choice.has_key(cmd):
3164 if choice.has_key(cmd):
3165 return choice[cmd]
3165 return choice[cmd]
3166
3166
3167 if len(choice) > 1:
3167 if len(choice) > 1:
3168 clist = choice.keys()
3168 clist = choice.keys()
3169 clist.sort()
3169 clist.sort()
3170 raise AmbiguousCommand(cmd, clist)
3170 raise AmbiguousCommand(cmd, clist)
3171
3171
3172 if choice:
3172 if choice:
3173 return choice.values()[0]
3173 return choice.values()[0]
3174
3174
3175 raise UnknownCommand(cmd)
3175 raise UnknownCommand(cmd)
3176
3176
3177 def catchterm(*args):
3177 def catchterm(*args):
3178 raise util.SignalInterrupt
3178 raise util.SignalInterrupt
3179
3179
3180 def run():
3180 def run():
3181 sys.exit(dispatch(sys.argv[1:]))
3181 sys.exit(dispatch(sys.argv[1:]))
3182
3182
3183 class ParseError(Exception):
3183 class ParseError(Exception):
3184 """Exception raised on errors in parsing the command line."""
3184 """Exception raised on errors in parsing the command line."""
3185
3185
3186 def parse(ui, args):
3186 def parse(ui, args):
3187 options = {}
3187 options = {}
3188 cmdoptions = {}
3188 cmdoptions = {}
3189
3189
3190 try:
3190 try:
3191 args = fancyopts.fancyopts(args, globalopts, options)
3191 args = fancyopts.fancyopts(args, globalopts, options)
3192 except fancyopts.getopt.GetoptError, inst:
3192 except fancyopts.getopt.GetoptError, inst:
3193 raise ParseError(None, inst)
3193 raise ParseError(None, inst)
3194
3194
3195 if args:
3195 if args:
3196 cmd, args = args[0], args[1:]
3196 cmd, args = args[0], args[1:]
3197 aliases, i = find(cmd)
3197 aliases, i = find(cmd)
3198 cmd = aliases[0]
3198 cmd = aliases[0]
3199 defaults = ui.config("defaults", cmd)
3199 defaults = ui.config("defaults", cmd)
3200 if defaults:
3200 if defaults:
3201 args = defaults.split() + args
3201 args = defaults.split() + args
3202 c = list(i[1])
3202 c = list(i[1])
3203 else:
3203 else:
3204 cmd = None
3204 cmd = None
3205 c = []
3205 c = []
3206
3206
3207 # combine global options into local
3207 # combine global options into local
3208 for o in globalopts:
3208 for o in globalopts:
3209 c.append((o[0], o[1], options[o[1]], o[3]))
3209 c.append((o[0], o[1], options[o[1]], o[3]))
3210
3210
3211 try:
3211 try:
3212 args = fancyopts.fancyopts(args, c, cmdoptions)
3212 args = fancyopts.fancyopts(args, c, cmdoptions)
3213 except fancyopts.getopt.GetoptError, inst:
3213 except fancyopts.getopt.GetoptError, inst:
3214 raise ParseError(cmd, inst)
3214 raise ParseError(cmd, inst)
3215
3215
3216 # separate global options back out
3216 # separate global options back out
3217 for o in globalopts:
3217 for o in globalopts:
3218 n = o[1]
3218 n = o[1]
3219 options[n] = cmdoptions[n]
3219 options[n] = cmdoptions[n]
3220 del cmdoptions[n]
3220 del cmdoptions[n]
3221
3221
3222 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3222 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3223
3223
3224 def dispatch(args):
3224 def dispatch(args):
3225 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3225 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3226 num = getattr(signal, name, None)
3226 num = getattr(signal, name, None)
3227 if num: signal.signal(num, catchterm)
3227 if num: signal.signal(num, catchterm)
3228
3228
3229 try:
3229 try:
3230 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3230 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3231 except util.Abort, inst:
3231 except util.Abort, inst:
3232 sys.stderr.write(_("abort: %s\n") % inst)
3232 sys.stderr.write(_("abort: %s\n") % inst)
3233 return -1
3233 return -1
3234
3234
3235 external = []
3235 external = []
3236 for x in u.extensions():
3236 for x in u.extensions():
3237 try:
3237 try:
3238 if x[1]:
3238 if x[1]:
3239 mod = imp.load_source(x[0], x[1])
3239 mod = imp.load_source(x[0], x[1])
3240 else:
3240 else:
3241 def importh(name):
3241 def importh(name):
3242 mod = __import__(name)
3242 mod = __import__(name)
3243 components = name.split('.')
3243 components = name.split('.')
3244 for comp in components[1:]:
3244 for comp in components[1:]:
3245 mod = getattr(mod, comp)
3245 mod = getattr(mod, comp)
3246 return mod
3246 return mod
3247 try:
3247 try:
3248 mod = importh("hgext." + x[0])
3248 mod = importh("hgext." + x[0])
3249 except ImportError:
3249 except ImportError:
3250 mod = importh(x[0])
3250 mod = importh(x[0])
3251 external.append(mod)
3251 external.append(mod)
3252 except Exception, inst:
3252 except Exception, inst:
3253 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3253 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3254 if u.traceback:
3254 if u.traceback:
3255 traceback.print_exc()
3255 traceback.print_exc()
3256 return 1
3256 return 1
3257 continue
3257 continue
3258
3258
3259 for x in external:
3259 for x in external:
3260 cmdtable = getattr(x, 'cmdtable', {})
3260 cmdtable = getattr(x, 'cmdtable', {})
3261 for t in cmdtable:
3261 for t in cmdtable:
3262 if t in table:
3262 if t in table:
3263 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3263 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3264 table.update(cmdtable)
3264 table.update(cmdtable)
3265
3265
3266 try:
3266 try:
3267 cmd, func, args, options, cmdoptions = parse(u, args)
3267 cmd, func, args, options, cmdoptions = parse(u, args)
3268 if options["time"]:
3268 if options["time"]:
3269 def get_times():
3269 def get_times():
3270 t = os.times()
3270 t = os.times()
3271 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3271 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3272 t = (t[0], t[1], t[2], t[3], time.clock())
3272 t = (t[0], t[1], t[2], t[3], time.clock())
3273 return t
3273 return t
3274 s = get_times()
3274 s = get_times()
3275 def print_time():
3275 def print_time():
3276 t = get_times()
3276 t = get_times()
3277 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3277 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3278 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3278 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3279 atexit.register(print_time)
3279 atexit.register(print_time)
3280
3280
3281 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3281 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3282 not options["noninteractive"], options["traceback"])
3282 not options["noninteractive"], options["traceback"])
3283
3283
3284 # enter the debugger before command execution
3284 # enter the debugger before command execution
3285 if options['debugger']:
3285 if options['debugger']:
3286 pdb.set_trace()
3286 pdb.set_trace()
3287
3287
3288 try:
3288 try:
3289 if options['cwd']:
3289 if options['cwd']:
3290 try:
3290 try:
3291 os.chdir(options['cwd'])
3291 os.chdir(options['cwd'])
3292 except OSError, inst:
3292 except OSError, inst:
3293 raise util.Abort('%s: %s' %
3293 raise util.Abort('%s: %s' %
3294 (options['cwd'], inst.strerror))
3294 (options['cwd'], inst.strerror))
3295
3295
3296 path = u.expandpath(options["repository"]) or ""
3296 path = u.expandpath(options["repository"]) or ""
3297 repo = path and hg.repository(u, path=path) or None
3297 repo = path and hg.repository(u, path=path) or None
3298
3298
3299 if options['help']:
3299 if options['help']:
3300 return help_(u, cmd, options['version'])
3300 return help_(u, cmd, options['version'])
3301 elif options['version']:
3301 elif options['version']:
3302 return show_version(u)
3302 return show_version(u)
3303 elif not cmd:
3303 elif not cmd:
3304 return help_(u, 'shortlist')
3304 return help_(u, 'shortlist')
3305
3305
3306 if cmd not in norepo.split():
3306 if cmd not in norepo.split():
3307 try:
3307 try:
3308 if not repo:
3308 if not repo:
3309 repo = hg.repository(u, path=path)
3309 repo = hg.repository(u, path=path)
3310 u = repo.ui
3310 u = repo.ui
3311 for x in external:
3311 for x in external:
3312 if hasattr(x, 'reposetup'):
3312 if hasattr(x, 'reposetup'):
3313 x.reposetup(u, repo)
3313 x.reposetup(u, repo)
3314 except hg.RepoError:
3314 except hg.RepoError:
3315 if cmd not in optionalrepo.split():
3315 if cmd not in optionalrepo.split():
3316 raise
3316 raise
3317 d = lambda: func(u, repo, *args, **cmdoptions)
3317 d = lambda: func(u, repo, *args, **cmdoptions)
3318 else:
3318 else:
3319 d = lambda: func(u, *args, **cmdoptions)
3319 d = lambda: func(u, *args, **cmdoptions)
3320
3320
3321 try:
3321 try:
3322 if options['profile']:
3322 if options['profile']:
3323 import hotshot, hotshot.stats
3323 import hotshot, hotshot.stats
3324 prof = hotshot.Profile("hg.prof")
3324 prof = hotshot.Profile("hg.prof")
3325 try:
3325 try:
3326 try:
3326 try:
3327 return prof.runcall(d)
3327 return prof.runcall(d)
3328 except:
3328 except:
3329 try:
3329 try:
3330 u.warn(_('exception raised - generating '
3330 u.warn(_('exception raised - generating '
3331 'profile anyway\n'))
3331 'profile anyway\n'))
3332 except:
3332 except:
3333 pass
3333 pass
3334 raise
3334 raise
3335 finally:
3335 finally:
3336 prof.close()
3336 prof.close()
3337 stats = hotshot.stats.load("hg.prof")
3337 stats = hotshot.stats.load("hg.prof")
3338 stats.strip_dirs()
3338 stats.strip_dirs()
3339 stats.sort_stats('time', 'calls')
3339 stats.sort_stats('time', 'calls')
3340 stats.print_stats(40)
3340 stats.print_stats(40)
3341 else:
3341 else:
3342 return d()
3342 return d()
3343 finally:
3343 finally:
3344 u.flush()
3344 u.flush()
3345 except:
3345 except:
3346 # enter the debugger when we hit an exception
3346 # enter the debugger when we hit an exception
3347 if options['debugger']:
3347 if options['debugger']:
3348 pdb.post_mortem(sys.exc_info()[2])
3348 pdb.post_mortem(sys.exc_info()[2])
3349 if u.traceback:
3349 if u.traceback:
3350 traceback.print_exc()
3350 traceback.print_exc()
3351 raise
3351 raise
3352 except ParseError, inst:
3352 except ParseError, inst:
3353 if inst.args[0]:
3353 if inst.args[0]:
3354 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3354 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3355 help_(u, inst.args[0])
3355 help_(u, inst.args[0])
3356 else:
3356 else:
3357 u.warn(_("hg: %s\n") % inst.args[1])
3357 u.warn(_("hg: %s\n") % inst.args[1])
3358 help_(u, 'shortlist')
3358 help_(u, 'shortlist')
3359 except AmbiguousCommand, inst:
3359 except AmbiguousCommand, inst:
3360 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3360 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3361 (inst.args[0], " ".join(inst.args[1])))
3361 (inst.args[0], " ".join(inst.args[1])))
3362 except UnknownCommand, inst:
3362 except UnknownCommand, inst:
3363 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3363 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3364 help_(u, 'shortlist')
3364 help_(u, 'shortlist')
3365 except hg.RepoError, inst:
3365 except hg.RepoError, inst:
3366 u.warn(_("abort: %s!\n") % inst)
3366 u.warn(_("abort: %s!\n") % inst)
3367 except lock.LockHeld, inst:
3367 except lock.LockHeld, inst:
3368 if inst.errno == errno.ETIMEDOUT:
3368 if inst.errno == errno.ETIMEDOUT:
3369 reason = _('timed out waiting for lock held by %s') % inst.locker
3369 reason = _('timed out waiting for lock held by %s') % inst.locker
3370 else:
3370 else:
3371 reason = _('lock held by %s') % inst.locker
3371 reason = _('lock held by %s') % inst.locker
3372 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3372 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3373 except lock.LockUnavailable, inst:
3373 except lock.LockUnavailable, inst:
3374 u.warn(_("abort: could not lock %s: %s\n") %
3374 u.warn(_("abort: could not lock %s: %s\n") %
3375 (inst.desc or inst.filename, inst.strerror))
3375 (inst.desc or inst.filename, inst.strerror))
3376 except revlog.RevlogError, inst:
3376 except revlog.RevlogError, inst:
3377 u.warn(_("abort: "), inst, "!\n")
3377 u.warn(_("abort: "), inst, "!\n")
3378 except util.SignalInterrupt:
3378 except util.SignalInterrupt:
3379 u.warn(_("killed!\n"))
3379 u.warn(_("killed!\n"))
3380 except KeyboardInterrupt:
3380 except KeyboardInterrupt:
3381 try:
3381 try:
3382 u.warn(_("interrupted!\n"))
3382 u.warn(_("interrupted!\n"))
3383 except IOError, inst:
3383 except IOError, inst:
3384 if inst.errno == errno.EPIPE:
3384 if inst.errno == errno.EPIPE:
3385 if u.debugflag:
3385 if u.debugflag:
3386 u.warn(_("\nbroken pipe\n"))
3386 u.warn(_("\nbroken pipe\n"))
3387 else:
3387 else:
3388 raise
3388 raise
3389 except IOError, inst:
3389 except IOError, inst:
3390 if hasattr(inst, "code"):
3390 if hasattr(inst, "code"):
3391 u.warn(_("abort: %s\n") % inst)
3391 u.warn(_("abort: %s\n") % inst)
3392 elif hasattr(inst, "reason"):
3392 elif hasattr(inst, "reason"):
3393 u.warn(_("abort: error: %s\n") % inst.reason[1])
3393 u.warn(_("abort: error: %s\n") % inst.reason[1])
3394 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3394 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3395 if u.debugflag:
3395 if u.debugflag:
3396 u.warn(_("broken pipe\n"))
3396 u.warn(_("broken pipe\n"))
3397 elif getattr(inst, "strerror", None):
3397 elif getattr(inst, "strerror", None):
3398 if getattr(inst, "filename", None):
3398 if getattr(inst, "filename", None):
3399 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3399 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3400 else:
3400 else:
3401 u.warn(_("abort: %s\n") % inst.strerror)
3401 u.warn(_("abort: %s\n") % inst.strerror)
3402 else:
3402 else:
3403 raise
3403 raise
3404 except OSError, inst:
3404 except OSError, inst:
3405 if hasattr(inst, "filename"):
3405 if hasattr(inst, "filename"):
3406 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3406 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3407 else:
3407 else:
3408 u.warn(_("abort: %s\n") % inst.strerror)
3408 u.warn(_("abort: %s\n") % inst.strerror)
3409 except util.Abort, inst:
3409 except util.Abort, inst:
3410 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3410 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3411 except TypeError, inst:
3411 except TypeError, inst:
3412 # was this an argument error?
3412 # was this an argument error?
3413 tb = traceback.extract_tb(sys.exc_info()[2])
3413 tb = traceback.extract_tb(sys.exc_info()[2])
3414 if len(tb) > 2: # no
3414 if len(tb) > 2: # no
3415 raise
3415 raise
3416 u.debug(inst, "\n")
3416 u.debug(inst, "\n")
3417 u.warn(_("%s: invalid arguments\n") % cmd)
3417 u.warn(_("%s: invalid arguments\n") % cmd)
3418 help_(u, cmd)
3418 help_(u, cmd)
3419 except SystemExit, inst:
3419 except SystemExit, inst:
3420 # Commands shouldn't sys.exit directly, but give a return code.
3420 # Commands shouldn't sys.exit directly, but give a return code.
3421 # Just in case catch this and and pass exit code to caller.
3421 # Just in case catch this and and pass exit code to caller.
3422 return inst.code
3422 return inst.code
3423 except:
3423 except:
3424 u.warn(_("** unknown exception encountered, details follow\n"))
3424 u.warn(_("** unknown exception encountered, details follow\n"))
3425 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3425 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3426 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3426 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3427 % version.get_version())
3427 % version.get_version())
3428 raise
3428 raise
3429
3429
3430 return -1
3430 return -1
@@ -1,2078 +1,2078 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, util
8 import os, util
9 import filelog, manifest, changelog, dirstate, repo
9 import filelog, manifest, changelog, dirstate, repo
10 from node import *
10 from node import *
11 from i18n import gettext as _
11 from i18n import gettext as _
12 from demandload import *
12 from demandload import *
13 demandload(globals(), "appendfile changegroup")
13 demandload(globals(), "appendfile changegroup")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
15 demandload(globals(), "revlog traceback")
15 demandload(globals(), "revlog traceback")
16
16
17 class localrepository(object):
17 class localrepository(object):
18 def __del__(self):
18 def __del__(self):
19 self.transhandle = None
19 self.transhandle = None
20 def __init__(self, parentui, path=None, create=0):
20 def __init__(self, parentui, path=None, create=0):
21 if not path:
21 if not path:
22 p = os.getcwd()
22 p = os.getcwd()
23 while not os.path.isdir(os.path.join(p, ".hg")):
23 while not os.path.isdir(os.path.join(p, ".hg")):
24 oldp = p
24 oldp = p
25 p = os.path.dirname(p)
25 p = os.path.dirname(p)
26 if p == oldp:
26 if p == oldp:
27 raise repo.RepoError(_("no repo found"))
27 raise repo.RepoError(_("no repo found"))
28 path = p
28 path = p
29 self.path = os.path.join(path, ".hg")
29 self.path = os.path.join(path, ".hg")
30
30
31 if not create and not os.path.isdir(self.path):
31 if not create and not os.path.isdir(self.path):
32 raise repo.RepoError(_("repository %s not found") % path)
32 raise repo.RepoError(_("repository %s not found") % path)
33
33
34 self.root = os.path.abspath(path)
34 self.root = os.path.abspath(path)
35 self.origroot = path
35 self.origroot = path
36 self.ui = ui.ui(parentui=parentui)
36 self.ui = ui.ui(parentui=parentui)
37 self.opener = util.opener(self.path)
37 self.opener = util.opener(self.path)
38 self.wopener = util.opener(self.root)
38 self.wopener = util.opener(self.root)
39
39
40 try:
40 try:
41 self.ui.readconfig(self.join("hgrc"), self.root)
41 self.ui.readconfig(self.join("hgrc"), self.root)
42 except IOError:
42 except IOError:
43 pass
43 pass
44
44
45 v = self.ui.revlogopts
45 v = self.ui.revlogopts
46 self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
46 self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
47 self.revlogv1 = self.revlogversion != revlog.REVLOGV0
47 self.revlogv1 = self.revlogversion != revlog.REVLOGV0
48 fl = v.get('flags', None)
48 fl = v.get('flags', None)
49 flags = 0
49 flags = 0
50 if fl != None:
50 if fl != None:
51 for x in fl.split():
51 for x in fl.split():
52 flags |= revlog.flagstr(x)
52 flags |= revlog.flagstr(x)
53 elif self.revlogv1:
53 elif self.revlogv1:
54 flags = revlog.REVLOG_DEFAULT_FLAGS
54 flags = revlog.REVLOG_DEFAULT_FLAGS
55
55
56 v = self.revlogversion | flags
56 v = self.revlogversion | flags
57 self.manifest = manifest.manifest(self.opener, v)
57 self.manifest = manifest.manifest(self.opener, v)
58 self.changelog = changelog.changelog(self.opener, v)
58 self.changelog = changelog.changelog(self.opener, v)
59
59
60 # the changelog might not have the inline index flag
60 # the changelog might not have the inline index flag
61 # on. If the format of the changelog is the same as found in
61 # on. If the format of the changelog is the same as found in
62 # .hgrc, apply any flags found in the .hgrc as well.
62 # .hgrc, apply any flags found in the .hgrc as well.
63 # Otherwise, just version from the changelog
63 # Otherwise, just version from the changelog
64 v = self.changelog.version
64 v = self.changelog.version
65 if v == self.revlogversion:
65 if v == self.revlogversion:
66 v |= flags
66 v |= flags
67 self.revlogversion = v
67 self.revlogversion = v
68
68
69 self.tagscache = None
69 self.tagscache = None
70 self.nodetagscache = None
70 self.nodetagscache = None
71 self.encodepats = None
71 self.encodepats = None
72 self.decodepats = None
72 self.decodepats = None
73 self.transhandle = None
73 self.transhandle = None
74
74
75 if create:
75 if create:
76 os.mkdir(self.path)
76 os.mkdir(self.path)
77 os.mkdir(self.join("data"))
77 os.mkdir(self.join("data"))
78
78
79 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
79 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
80
80
81 def hook(self, name, throw=False, **args):
81 def hook(self, name, throw=False, **args):
82 def callhook(hname, funcname):
82 def callhook(hname, funcname):
83 '''call python hook. hook is callable object, looked up as
83 '''call python hook. hook is callable object, looked up as
84 name in python module. if callable returns "true", hook
84 name in python module. if callable returns "true", hook
85 fails, else passes. if hook raises exception, treated as
85 fails, else passes. if hook raises exception, treated as
86 hook failure. exception propagates if throw is "true".
86 hook failure. exception propagates if throw is "true".
87
87
88 reason for "true" meaning "hook failed" is so that
88 reason for "true" meaning "hook failed" is so that
89 unmodified commands (e.g. mercurial.commands.update) can
89 unmodified commands (e.g. mercurial.commands.update) can
90 be run as hooks without wrappers to convert return values.'''
90 be run as hooks without wrappers to convert return values.'''
91
91
92 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
92 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
93 d = funcname.rfind('.')
93 d = funcname.rfind('.')
94 if d == -1:
94 if d == -1:
95 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
95 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
96 % (hname, funcname))
96 % (hname, funcname))
97 modname = funcname[:d]
97 modname = funcname[:d]
98 try:
98 try:
99 obj = __import__(modname)
99 obj = __import__(modname)
100 except ImportError:
100 except ImportError:
101 raise util.Abort(_('%s hook is invalid '
101 raise util.Abort(_('%s hook is invalid '
102 '(import of "%s" failed)') %
102 '(import of "%s" failed)') %
103 (hname, modname))
103 (hname, modname))
104 try:
104 try:
105 for p in funcname.split('.')[1:]:
105 for p in funcname.split('.')[1:]:
106 obj = getattr(obj, p)
106 obj = getattr(obj, p)
107 except AttributeError, err:
107 except AttributeError, err:
108 raise util.Abort(_('%s hook is invalid '
108 raise util.Abort(_('%s hook is invalid '
109 '("%s" is not defined)') %
109 '("%s" is not defined)') %
110 (hname, funcname))
110 (hname, funcname))
111 if not callable(obj):
111 if not callable(obj):
112 raise util.Abort(_('%s hook is invalid '
112 raise util.Abort(_('%s hook is invalid '
113 '("%s" is not callable)') %
113 '("%s" is not callable)') %
114 (hname, funcname))
114 (hname, funcname))
115 try:
115 try:
116 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
116 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
117 except (KeyboardInterrupt, util.SignalInterrupt):
117 except (KeyboardInterrupt, util.SignalInterrupt):
118 raise
118 raise
119 except Exception, exc:
119 except Exception, exc:
120 if isinstance(exc, util.Abort):
120 if isinstance(exc, util.Abort):
121 self.ui.warn(_('error: %s hook failed: %s\n') %
121 self.ui.warn(_('error: %s hook failed: %s\n') %
122 (hname, exc.args[0] % exc.args[1:]))
122 (hname, exc.args[0] % exc.args[1:]))
123 else:
123 else:
124 self.ui.warn(_('error: %s hook raised an exception: '
124 self.ui.warn(_('error: %s hook raised an exception: '
125 '%s\n') % (hname, exc))
125 '%s\n') % (hname, exc))
126 if throw:
126 if throw:
127 raise
127 raise
128 if self.ui.traceback:
128 if self.ui.traceback:
129 traceback.print_exc()
129 traceback.print_exc()
130 return True
130 return True
131 if r:
131 if r:
132 if throw:
132 if throw:
133 raise util.Abort(_('%s hook failed') % hname)
133 raise util.Abort(_('%s hook failed') % hname)
134 self.ui.warn(_('warning: %s hook failed\n') % hname)
134 self.ui.warn(_('warning: %s hook failed\n') % hname)
135 return r
135 return r
136
136
137 def runhook(name, cmd):
137 def runhook(name, cmd):
138 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
138 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
139 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
139 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
140 [(k.upper(), v) for k, v in args.iteritems()])
140 [(k.upper(), v) for k, v in args.iteritems()])
141 r = util.system(cmd, environ=env, cwd=self.root)
141 r = util.system(cmd, environ=env, cwd=self.root)
142 if r:
142 if r:
143 desc, r = util.explain_exit(r)
143 desc, r = util.explain_exit(r)
144 if throw:
144 if throw:
145 raise util.Abort(_('%s hook %s') % (name, desc))
145 raise util.Abort(_('%s hook %s') % (name, desc))
146 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
146 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
147 return r
147 return r
148
148
149 r = False
149 r = False
150 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
150 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
151 if hname.split(".", 1)[0] == name and cmd]
151 if hname.split(".", 1)[0] == name and cmd]
152 hooks.sort()
152 hooks.sort()
153 for hname, cmd in hooks:
153 for hname, cmd in hooks:
154 if cmd.startswith('python:'):
154 if cmd.startswith('python:'):
155 r = callhook(hname, cmd[7:].strip()) or r
155 r = callhook(hname, cmd[7:].strip()) or r
156 else:
156 else:
157 r = runhook(hname, cmd) or r
157 r = runhook(hname, cmd) or r
158 return r
158 return r
159
159
160 def tags(self):
160 def tags(self):
161 '''return a mapping of tag to node'''
161 '''return a mapping of tag to node'''
162 if not self.tagscache:
162 if not self.tagscache:
163 self.tagscache = {}
163 self.tagscache = {}
164
164
165 def parsetag(line, context):
165 def parsetag(line, context):
166 if not line:
166 if not line:
167 return
167 return
168 s = l.split(" ", 1)
168 s = l.split(" ", 1)
169 if len(s) != 2:
169 if len(s) != 2:
170 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
170 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
171 return
171 return
172 node, key = s
172 node, key = s
173 try:
173 try:
174 bin_n = bin(node)
174 bin_n = bin(node)
175 except TypeError:
175 except TypeError:
176 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
176 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
177 return
177 return
178 if bin_n not in self.changelog.nodemap:
178 if bin_n not in self.changelog.nodemap:
179 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
179 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
180 return
180 return
181 self.tagscache[key.strip()] = bin_n
181 self.tagscache[key.strip()] = bin_n
182
182
183 # read each head of the tags file, ending with the tip
183 # read each head of the tags file, ending with the tip
184 # and add each tag found to the map, with "newer" ones
184 # and add each tag found to the map, with "newer" ones
185 # taking precedence
185 # taking precedence
186 fl = self.file(".hgtags")
186 fl = self.file(".hgtags")
187 h = fl.heads()
187 h = fl.heads()
188 h.reverse()
188 h.reverse()
189 for r in h:
189 for r in h:
190 count = 0
190 count = 0
191 for l in fl.read(r).splitlines():
191 for l in fl.read(r).splitlines():
192 count += 1
192 count += 1
193 parsetag(l, ".hgtags:%d" % count)
193 parsetag(l, ".hgtags:%d" % count)
194
194
195 try:
195 try:
196 f = self.opener("localtags")
196 f = self.opener("localtags")
197 count = 0
197 count = 0
198 for l in f:
198 for l in f:
199 count += 1
199 count += 1
200 parsetag(l, "localtags:%d" % count)
200 parsetag(l, "localtags:%d" % count)
201 except IOError:
201 except IOError:
202 pass
202 pass
203
203
204 self.tagscache['tip'] = self.changelog.tip()
204 self.tagscache['tip'] = self.changelog.tip()
205
205
206 return self.tagscache
206 return self.tagscache
207
207
208 def tagslist(self):
208 def tagslist(self):
209 '''return a list of tags ordered by revision'''
209 '''return a list of tags ordered by revision'''
210 l = []
210 l = []
211 for t, n in self.tags().items():
211 for t, n in self.tags().items():
212 try:
212 try:
213 r = self.changelog.rev(n)
213 r = self.changelog.rev(n)
214 except:
214 except:
215 r = -2 # sort to the beginning of the list if unknown
215 r = -2 # sort to the beginning of the list if unknown
216 l.append((r, t, n))
216 l.append((r, t, n))
217 l.sort()
217 l.sort()
218 return [(t, n) for r, t, n in l]
218 return [(t, n) for r, t, n in l]
219
219
220 def nodetags(self, node):
220 def nodetags(self, node):
221 '''return the tags associated with a node'''
221 '''return the tags associated with a node'''
222 if not self.nodetagscache:
222 if not self.nodetagscache:
223 self.nodetagscache = {}
223 self.nodetagscache = {}
224 for t, n in self.tags().items():
224 for t, n in self.tags().items():
225 self.nodetagscache.setdefault(n, []).append(t)
225 self.nodetagscache.setdefault(n, []).append(t)
226 return self.nodetagscache.get(node, [])
226 return self.nodetagscache.get(node, [])
227
227
228 def lookup(self, key):
228 def lookup(self, key):
229 try:
229 try:
230 return self.tags()[key]
230 return self.tags()[key]
231 except KeyError:
231 except KeyError:
232 try:
232 try:
233 return self.changelog.lookup(key)
233 return self.changelog.lookup(key)
234 except:
234 except:
235 raise repo.RepoError(_("unknown revision '%s'") % key)
235 raise repo.RepoError(_("unknown revision '%s'") % key)
236
236
237 def dev(self):
237 def dev(self):
238 return os.stat(self.path).st_dev
238 return os.stat(self.path).st_dev
239
239
240 def local(self):
240 def local(self):
241 return True
241 return True
242
242
243 def join(self, f):
243 def join(self, f):
244 return os.path.join(self.path, f)
244 return os.path.join(self.path, f)
245
245
246 def wjoin(self, f):
246 def wjoin(self, f):
247 return os.path.join(self.root, f)
247 return os.path.join(self.root, f)
248
248
249 def file(self, f):
249 def file(self, f):
250 if f[0] == '/':
250 if f[0] == '/':
251 f = f[1:]
251 f = f[1:]
252 return filelog.filelog(self.opener, f, self.revlogversion)
252 return filelog.filelog(self.opener, f, self.revlogversion)
253
253
254 def getcwd(self):
254 def getcwd(self):
255 return self.dirstate.getcwd()
255 return self.dirstate.getcwd()
256
256
257 def wfile(self, f, mode='r'):
257 def wfile(self, f, mode='r'):
258 return self.wopener(f, mode)
258 return self.wopener(f, mode)
259
259
260 def wread(self, filename):
260 def wread(self, filename):
261 if self.encodepats == None:
261 if self.encodepats == None:
262 l = []
262 l = []
263 for pat, cmd in self.ui.configitems("encode"):
263 for pat, cmd in self.ui.configitems("encode"):
264 mf = util.matcher(self.root, "", [pat], [], [])[1]
264 mf = util.matcher(self.root, "", [pat], [], [])[1]
265 l.append((mf, cmd))
265 l.append((mf, cmd))
266 self.encodepats = l
266 self.encodepats = l
267
267
268 data = self.wopener(filename, 'r').read()
268 data = self.wopener(filename, 'r').read()
269
269
270 for mf, cmd in self.encodepats:
270 for mf, cmd in self.encodepats:
271 if mf(filename):
271 if mf(filename):
272 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
272 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
273 data = util.filter(data, cmd)
273 data = util.filter(data, cmd)
274 break
274 break
275
275
276 return data
276 return data
277
277
278 def wwrite(self, filename, data, fd=None):
278 def wwrite(self, filename, data, fd=None):
279 if self.decodepats == None:
279 if self.decodepats == None:
280 l = []
280 l = []
281 for pat, cmd in self.ui.configitems("decode"):
281 for pat, cmd in self.ui.configitems("decode"):
282 mf = util.matcher(self.root, "", [pat], [], [])[1]
282 mf = util.matcher(self.root, "", [pat], [], [])[1]
283 l.append((mf, cmd))
283 l.append((mf, cmd))
284 self.decodepats = l
284 self.decodepats = l
285
285
286 for mf, cmd in self.decodepats:
286 for mf, cmd in self.decodepats:
287 if mf(filename):
287 if mf(filename):
288 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
288 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
289 data = util.filter(data, cmd)
289 data = util.filter(data, cmd)
290 break
290 break
291
291
292 if fd:
292 if fd:
293 return fd.write(data)
293 return fd.write(data)
294 return self.wopener(filename, 'w').write(data)
294 return self.wopener(filename, 'w').write(data)
295
295
296 def transaction(self):
296 def transaction(self):
297 tr = self.transhandle
297 tr = self.transhandle
298 if tr != None and tr.running():
298 if tr != None and tr.running():
299 return tr.nest()
299 return tr.nest()
300
300
301 # save dirstate for undo
301 # save dirstate for undo
302 try:
302 try:
303 ds = self.opener("dirstate").read()
303 ds = self.opener("dirstate").read()
304 except IOError:
304 except IOError:
305 ds = ""
305 ds = ""
306 self.opener("journal.dirstate", "w").write(ds)
306 self.opener("journal.dirstate", "w").write(ds)
307
307
308 tr = transaction.transaction(self.ui.warn, self.opener,
308 tr = transaction.transaction(self.ui.warn, self.opener,
309 self.join("journal"),
309 self.join("journal"),
310 aftertrans(self.path))
310 aftertrans(self.path))
311 self.transhandle = tr
311 self.transhandle = tr
312 return tr
312 return tr
313
313
314 def recover(self):
314 def recover(self):
315 l = self.lock()
315 l = self.lock()
316 if os.path.exists(self.join("journal")):
316 if os.path.exists(self.join("journal")):
317 self.ui.status(_("rolling back interrupted transaction\n"))
317 self.ui.status(_("rolling back interrupted transaction\n"))
318 transaction.rollback(self.opener, self.join("journal"))
318 transaction.rollback(self.opener, self.join("journal"))
319 self.reload()
319 self.reload()
320 return True
320 return True
321 else:
321 else:
322 self.ui.warn(_("no interrupted transaction available\n"))
322 self.ui.warn(_("no interrupted transaction available\n"))
323 return False
323 return False
324
324
325 def undo(self, wlock=None):
325 def undo(self, wlock=None):
326 if not wlock:
326 if not wlock:
327 wlock = self.wlock()
327 wlock = self.wlock()
328 l = self.lock()
328 l = self.lock()
329 if os.path.exists(self.join("undo")):
329 if os.path.exists(self.join("undo")):
330 self.ui.status(_("rolling back last transaction\n"))
330 self.ui.status(_("rolling back last transaction\n"))
331 transaction.rollback(self.opener, self.join("undo"))
331 transaction.rollback(self.opener, self.join("undo"))
332 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
332 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
333 self.reload()
333 self.reload()
334 self.wreload()
334 self.wreload()
335 else:
335 else:
336 self.ui.warn(_("no undo information available\n"))
336 self.ui.warn(_("no undo information available\n"))
337
337
338 def wreload(self):
338 def wreload(self):
339 self.dirstate.read()
339 self.dirstate.read()
340
340
341 def reload(self):
341 def reload(self):
342 self.changelog.load()
342 self.changelog.load()
343 self.manifest.load()
343 self.manifest.load()
344 self.tagscache = None
344 self.tagscache = None
345 self.nodetagscache = None
345 self.nodetagscache = None
346
346
347 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
347 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
348 desc=None):
348 desc=None):
349 try:
349 try:
350 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
350 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
351 except lock.LockHeld, inst:
351 except lock.LockHeld, inst:
352 if not wait:
352 if not wait:
353 raise
353 raise
354 self.ui.warn(_("waiting for lock on %s held by %s\n") %
354 self.ui.warn(_("waiting for lock on %s held by %s\n") %
355 (desc, inst.args[0]))
355 (desc, inst.args[0]))
356 # default to 600 seconds timeout
356 # default to 600 seconds timeout
357 l = lock.lock(self.join(lockname),
357 l = lock.lock(self.join(lockname),
358 int(self.ui.config("ui", "timeout") or 600),
358 int(self.ui.config("ui", "timeout") or 600),
359 releasefn, desc=desc)
359 releasefn, desc=desc)
360 if acquirefn:
360 if acquirefn:
361 acquirefn()
361 acquirefn()
362 return l
362 return l
363
363
364 def lock(self, wait=1):
364 def lock(self, wait=1):
365 return self.do_lock("lock", wait, acquirefn=self.reload,
365 return self.do_lock("lock", wait, acquirefn=self.reload,
366 desc=_('repository %s') % self.origroot)
366 desc=_('repository %s') % self.origroot)
367
367
368 def wlock(self, wait=1):
368 def wlock(self, wait=1):
369 return self.do_lock("wlock", wait, self.dirstate.write,
369 return self.do_lock("wlock", wait, self.dirstate.write,
370 self.wreload,
370 self.wreload,
371 desc=_('working directory of %s') % self.origroot)
371 desc=_('working directory of %s') % self.origroot)
372
372
373 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
373 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
374 "determine whether a new filenode is needed"
374 "determine whether a new filenode is needed"
375 fp1 = manifest1.get(filename, nullid)
375 fp1 = manifest1.get(filename, nullid)
376 fp2 = manifest2.get(filename, nullid)
376 fp2 = manifest2.get(filename, nullid)
377
377
378 if fp2 != nullid:
378 if fp2 != nullid:
379 # is one parent an ancestor of the other?
379 # is one parent an ancestor of the other?
380 fpa = filelog.ancestor(fp1, fp2)
380 fpa = filelog.ancestor(fp1, fp2)
381 if fpa == fp1:
381 if fpa == fp1:
382 fp1, fp2 = fp2, nullid
382 fp1, fp2 = fp2, nullid
383 elif fpa == fp2:
383 elif fpa == fp2:
384 fp2 = nullid
384 fp2 = nullid
385
385
386 # is the file unmodified from the parent? report existing entry
386 # is the file unmodified from the parent? report existing entry
387 if fp2 == nullid and text == filelog.read(fp1):
387 if fp2 == nullid and text == filelog.read(fp1):
388 return (fp1, None, None)
388 return (fp1, None, None)
389
389
390 return (None, fp1, fp2)
390 return (None, fp1, fp2)
391
391
392 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
392 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
393 orig_parent = self.dirstate.parents()[0] or nullid
393 orig_parent = self.dirstate.parents()[0] or nullid
394 p1 = p1 or self.dirstate.parents()[0] or nullid
394 p1 = p1 or self.dirstate.parents()[0] or nullid
395 p2 = p2 or self.dirstate.parents()[1] or nullid
395 p2 = p2 or self.dirstate.parents()[1] or nullid
396 c1 = self.changelog.read(p1)
396 c1 = self.changelog.read(p1)
397 c2 = self.changelog.read(p2)
397 c2 = self.changelog.read(p2)
398 m1 = self.manifest.read(c1[0])
398 m1 = self.manifest.read(c1[0])
399 mf1 = self.manifest.readflags(c1[0])
399 mf1 = self.manifest.readflags(c1[0])
400 m2 = self.manifest.read(c2[0])
400 m2 = self.manifest.read(c2[0])
401 changed = []
401 changed = []
402
402
403 if orig_parent == p1:
403 if orig_parent == p1:
404 update_dirstate = 1
404 update_dirstate = 1
405 else:
405 else:
406 update_dirstate = 0
406 update_dirstate = 0
407
407
408 if not wlock:
408 if not wlock:
409 wlock = self.wlock()
409 wlock = self.wlock()
410 l = self.lock()
410 l = self.lock()
411 tr = self.transaction()
411 tr = self.transaction()
412 mm = m1.copy()
412 mm = m1.copy()
413 mfm = mf1.copy()
413 mfm = mf1.copy()
414 linkrev = self.changelog.count()
414 linkrev = self.changelog.count()
415 for f in files:
415 for f in files:
416 try:
416 try:
417 t = self.wread(f)
417 t = self.wread(f)
418 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
418 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
419 r = self.file(f)
419 r = self.file(f)
420 mfm[f] = tm
420 mfm[f] = tm
421
421
422 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
422 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
423 if entry:
423 if entry:
424 mm[f] = entry
424 mm[f] = entry
425 continue
425 continue
426
426
427 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
427 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
428 changed.append(f)
428 changed.append(f)
429 if update_dirstate:
429 if update_dirstate:
430 self.dirstate.update([f], "n")
430 self.dirstate.update([f], "n")
431 except IOError:
431 except IOError:
432 try:
432 try:
433 del mm[f]
433 del mm[f]
434 del mfm[f]
434 del mfm[f]
435 if update_dirstate:
435 if update_dirstate:
436 self.dirstate.forget([f])
436 self.dirstate.forget([f])
437 except:
437 except:
438 # deleted from p2?
438 # deleted from p2?
439 pass
439 pass
440
440
441 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
441 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
442 user = user or self.ui.username()
442 user = user or self.ui.username()
443 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
443 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
444 tr.close()
444 tr.close()
445 if update_dirstate:
445 if update_dirstate:
446 self.dirstate.setparents(n, nullid)
446 self.dirstate.setparents(n, nullid)
447
447
448 def commit(self, files=None, text="", user=None, date=None,
448 def commit(self, files=None, text="", user=None, date=None,
449 match=util.always, force=False, lock=None, wlock=None):
449 match=util.always, force=False, lock=None, wlock=None):
450 commit = []
450 commit = []
451 remove = []
451 remove = []
452 changed = []
452 changed = []
453
453
454 if files:
454 if files:
455 for f in files:
455 for f in files:
456 s = self.dirstate.state(f)
456 s = self.dirstate.state(f)
457 if s in 'nmai':
457 if s in 'nmai':
458 commit.append(f)
458 commit.append(f)
459 elif s == 'r':
459 elif s == 'r':
460 remove.append(f)
460 remove.append(f)
461 else:
461 else:
462 self.ui.warn(_("%s not tracked!\n") % f)
462 self.ui.warn(_("%s not tracked!\n") % f)
463 else:
463 else:
464 modified, added, removed, deleted, unknown = self.changes(match=match)
464 modified, added, removed, deleted, unknown = self.changes(match=match)
465 commit = modified + added
465 commit = modified + added
466 remove = removed
466 remove = removed
467
467
468 p1, p2 = self.dirstate.parents()
468 p1, p2 = self.dirstate.parents()
469 c1 = self.changelog.read(p1)
469 c1 = self.changelog.read(p1)
470 c2 = self.changelog.read(p2)
470 c2 = self.changelog.read(p2)
471 m1 = self.manifest.read(c1[0])
471 m1 = self.manifest.read(c1[0])
472 mf1 = self.manifest.readflags(c1[0])
472 mf1 = self.manifest.readflags(c1[0])
473 m2 = self.manifest.read(c2[0])
473 m2 = self.manifest.read(c2[0])
474
474
475 if not commit and not remove and not force and p2 == nullid:
475 if not commit and not remove and not force and p2 == nullid:
476 self.ui.status(_("nothing changed\n"))
476 self.ui.status(_("nothing changed\n"))
477 return None
477 return None
478
478
479 xp1 = hex(p1)
479 xp1 = hex(p1)
480 if p2 == nullid: xp2 = ''
480 if p2 == nullid: xp2 = ''
481 else: xp2 = hex(p2)
481 else: xp2 = hex(p2)
482
482
483 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
483 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
484
484
485 if not wlock:
485 if not wlock:
486 wlock = self.wlock()
486 wlock = self.wlock()
487 if not lock:
487 if not lock:
488 lock = self.lock()
488 lock = self.lock()
489 tr = self.transaction()
489 tr = self.transaction()
490
490
491 # check in files
491 # check in files
492 new = {}
492 new = {}
493 linkrev = self.changelog.count()
493 linkrev = self.changelog.count()
494 commit.sort()
494 commit.sort()
495 for f in commit:
495 for f in commit:
496 self.ui.note(f + "\n")
496 self.ui.note(f + "\n")
497 try:
497 try:
498 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
498 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
499 t = self.wread(f)
499 t = self.wread(f)
500 except IOError:
500 except IOError:
501 self.ui.warn(_("trouble committing %s!\n") % f)
501 self.ui.warn(_("trouble committing %s!\n") % f)
502 raise
502 raise
503
503
504 r = self.file(f)
504 r = self.file(f)
505
505
506 meta = {}
506 meta = {}
507 cp = self.dirstate.copied(f)
507 cp = self.dirstate.copied(f)
508 if cp:
508 if cp:
509 meta["copy"] = cp
509 meta["copy"] = cp
510 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
510 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
511 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
511 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
512 fp1, fp2 = nullid, nullid
512 fp1, fp2 = nullid, nullid
513 else:
513 else:
514 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
514 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
515 if entry:
515 if entry:
516 new[f] = entry
516 new[f] = entry
517 continue
517 continue
518
518
519 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
519 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
520 # remember what we've added so that we can later calculate
520 # remember what we've added so that we can later calculate
521 # the files to pull from a set of changesets
521 # the files to pull from a set of changesets
522 changed.append(f)
522 changed.append(f)
523
523
524 # update manifest
524 # update manifest
525 m1 = m1.copy()
525 m1 = m1.copy()
526 m1.update(new)
526 m1.update(new)
527 for f in remove:
527 for f in remove:
528 if f in m1:
528 if f in m1:
529 del m1[f]
529 del m1[f]
530 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
530 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
531 (new, remove))
531 (new, remove))
532
532
533 # add changeset
533 # add changeset
534 new = new.keys()
534 new = new.keys()
535 new.sort()
535 new.sort()
536
536
537 user = user or self.ui.username()
537 user = user or self.ui.username()
538 if not text:
538 if not text:
539 edittext = [""]
539 edittext = [""]
540 if p2 != nullid:
540 if p2 != nullid:
541 edittext.append("HG: branch merge")
541 edittext.append("HG: branch merge")
542 edittext.extend(["HG: changed %s" % f for f in changed])
542 edittext.extend(["HG: changed %s" % f for f in changed])
543 edittext.extend(["HG: removed %s" % f for f in remove])
543 edittext.extend(["HG: removed %s" % f for f in remove])
544 if not changed and not remove:
544 if not changed and not remove:
545 edittext.append("HG: no files changed")
545 edittext.append("HG: no files changed")
546 edittext.append("")
546 edittext.append("")
547 # run editor in the repository root
547 # run editor in the repository root
548 olddir = os.getcwd()
548 olddir = os.getcwd()
549 os.chdir(self.root)
549 os.chdir(self.root)
550 edittext = self.ui.edit("\n".join(edittext), user)
550 edittext = self.ui.edit("\n".join(edittext), user)
551 os.chdir(olddir)
551 os.chdir(olddir)
552 if not edittext.rstrip():
552 if not edittext.rstrip():
553 return None
553 return None
554 text = edittext
554 text = edittext
555
555
556 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
556 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
557 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
557 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
558 parent2=xp2)
558 parent2=xp2)
559 tr.close()
559 tr.close()
560
560
561 self.dirstate.setparents(n)
561 self.dirstate.setparents(n)
562 self.dirstate.update(new, "n")
562 self.dirstate.update(new, "n")
563 self.dirstate.forget(remove)
563 self.dirstate.forget(remove)
564
564
565 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
565 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
566 return n
566 return n
567
567
568 def walk(self, node=None, files=[], match=util.always, badmatch=None):
568 def walk(self, node=None, files=[], match=util.always, badmatch=None):
569 if node:
569 if node:
570 fdict = dict.fromkeys(files)
570 fdict = dict.fromkeys(files)
571 for fn in self.manifest.read(self.changelog.read(node)[0]):
571 for fn in self.manifest.read(self.changelog.read(node)[0]):
572 fdict.pop(fn, None)
572 fdict.pop(fn, None)
573 if match(fn):
573 if match(fn):
574 yield 'm', fn
574 yield 'm', fn
575 for fn in fdict:
575 for fn in fdict:
576 if badmatch and badmatch(fn):
576 if badmatch and badmatch(fn):
577 if match(fn):
577 if match(fn):
578 yield 'b', fn
578 yield 'b', fn
579 else:
579 else:
580 self.ui.warn(_('%s: No such file in rev %s\n') % (
580 self.ui.warn(_('%s: No such file in rev %s\n') % (
581 util.pathto(self.getcwd(), fn), short(node)))
581 util.pathto(self.getcwd(), fn), short(node)))
582 else:
582 else:
583 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
583 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
584 yield src, fn
584 yield src, fn
585
585
586 def changes(self, node1=None, node2=None, files=[], match=util.always,
586 def changes(self, node1=None, node2=None, files=[], match=util.always,
587 wlock=None, show_ignored=None):
587 wlock=None, show_ignored=None):
588 """return changes between two nodes or node and working directory
588 """return changes between two nodes or node and working directory
589
589
590 If node1 is None, use the first dirstate parent instead.
590 If node1 is None, use the first dirstate parent instead.
591 If node2 is None, compare node1 with working directory.
591 If node2 is None, compare node1 with working directory.
592 """
592 """
593
593
594 def fcmp(fn, mf):
594 def fcmp(fn, mf):
595 t1 = self.wread(fn)
595 t1 = self.wread(fn)
596 t2 = self.file(fn).read(mf.get(fn, nullid))
596 t2 = self.file(fn).read(mf.get(fn, nullid))
597 return cmp(t1, t2)
597 return cmp(t1, t2)
598
598
599 def mfmatches(node):
599 def mfmatches(node):
600 change = self.changelog.read(node)
600 change = self.changelog.read(node)
601 mf = dict(self.manifest.read(change[0]))
601 mf = dict(self.manifest.read(change[0]))
602 for fn in mf.keys():
602 for fn in mf.keys():
603 if not match(fn):
603 if not match(fn):
604 del mf[fn]
604 del mf[fn]
605 return mf
605 return mf
606
606
607 if node1:
607 if node1:
608 # read the manifest from node1 before the manifest from node2,
608 # read the manifest from node1 before the manifest from node2,
609 # so that we'll hit the manifest cache if we're going through
609 # so that we'll hit the manifest cache if we're going through
610 # all the revisions in parent->child order.
610 # all the revisions in parent->child order.
611 mf1 = mfmatches(node1)
611 mf1 = mfmatches(node1)
612
612
613 # are we comparing the working directory?
613 # are we comparing the working directory?
614 if not node2:
614 if not node2:
615 if not wlock:
615 if not wlock:
616 try:
616 try:
617 wlock = self.wlock(wait=0)
617 wlock = self.wlock(wait=0)
618 except lock.LockException:
618 except lock.LockException:
619 wlock = None
619 wlock = None
620 lookup, modified, added, removed, deleted, unknown, ignored = (
620 lookup, modified, added, removed, deleted, unknown, ignored = (
621 self.dirstate.changes(files, match, show_ignored))
621 self.dirstate.changes(files, match, show_ignored))
622
622
623 # are we comparing working dir against its parent?
623 # are we comparing working dir against its parent?
624 if not node1:
624 if not node1:
625 if lookup:
625 if lookup:
626 # do a full compare of any files that might have changed
626 # do a full compare of any files that might have changed
627 mf2 = mfmatches(self.dirstate.parents()[0])
627 mf2 = mfmatches(self.dirstate.parents()[0])
628 for f in lookup:
628 for f in lookup:
629 if fcmp(f, mf2):
629 if fcmp(f, mf2):
630 modified.append(f)
630 modified.append(f)
631 elif wlock is not None:
631 elif wlock is not None:
632 self.dirstate.update([f], "n")
632 self.dirstate.update([f], "n")
633 else:
633 else:
634 # we are comparing working dir against non-parent
634 # we are comparing working dir against non-parent
635 # generate a pseudo-manifest for the working dir
635 # generate a pseudo-manifest for the working dir
636 mf2 = mfmatches(self.dirstate.parents()[0])
636 mf2 = mfmatches(self.dirstate.parents()[0])
637 for f in lookup + modified + added:
637 for f in lookup + modified + added:
638 mf2[f] = ""
638 mf2[f] = ""
639 for f in removed:
639 for f in removed:
640 if f in mf2:
640 if f in mf2:
641 del mf2[f]
641 del mf2[f]
642 else:
642 else:
643 # we are comparing two revisions
643 # we are comparing two revisions
644 deleted, unknown, ignored = [], [], []
644 deleted, unknown, ignored = [], [], []
645 mf2 = mfmatches(node2)
645 mf2 = mfmatches(node2)
646
646
647 if node1:
647 if node1:
648 # flush lists from dirstate before comparing manifests
648 # flush lists from dirstate before comparing manifests
649 modified, added = [], []
649 modified, added = [], []
650
650
651 for fn in mf2:
651 for fn in mf2:
652 if mf1.has_key(fn):
652 if mf1.has_key(fn):
653 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
653 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
654 modified.append(fn)
654 modified.append(fn)
655 del mf1[fn]
655 del mf1[fn]
656 else:
656 else:
657 added.append(fn)
657 added.append(fn)
658
658
659 removed = mf1.keys()
659 removed = mf1.keys()
660
660
661 # sort and return results:
661 # sort and return results:
662 for l in modified, added, removed, deleted, unknown, ignored:
662 for l in modified, added, removed, deleted, unknown, ignored:
663 l.sort()
663 l.sort()
664 if show_ignored is None:
664 if show_ignored is None:
665 return (modified, added, removed, deleted, unknown)
665 return (modified, added, removed, deleted, unknown)
666 else:
666 else:
667 return (modified, added, removed, deleted, unknown, ignored)
667 return (modified, added, removed, deleted, unknown, ignored)
668
668
669 def add(self, list, wlock=None):
669 def add(self, list, wlock=None):
670 if not wlock:
670 if not wlock:
671 wlock = self.wlock()
671 wlock = self.wlock()
672 for f in list:
672 for f in list:
673 p = self.wjoin(f)
673 p = self.wjoin(f)
674 if not os.path.exists(p):
674 if not os.path.exists(p):
675 self.ui.warn(_("%s does not exist!\n") % f)
675 self.ui.warn(_("%s does not exist!\n") % f)
676 elif not os.path.isfile(p):
676 elif not os.path.isfile(p):
677 self.ui.warn(_("%s not added: only files supported currently\n")
677 self.ui.warn(_("%s not added: only files supported currently\n")
678 % f)
678 % f)
679 elif self.dirstate.state(f) in 'an':
679 elif self.dirstate.state(f) in 'an':
680 self.ui.warn(_("%s already tracked!\n") % f)
680 self.ui.warn(_("%s already tracked!\n") % f)
681 else:
681 else:
682 self.dirstate.update([f], "a")
682 self.dirstate.update([f], "a")
683
683
684 def forget(self, list, wlock=None):
684 def forget(self, list, wlock=None):
685 if not wlock:
685 if not wlock:
686 wlock = self.wlock()
686 wlock = self.wlock()
687 for f in list:
687 for f in list:
688 if self.dirstate.state(f) not in 'ai':
688 if self.dirstate.state(f) not in 'ai':
689 self.ui.warn(_("%s not added!\n") % f)
689 self.ui.warn(_("%s not added!\n") % f)
690 else:
690 else:
691 self.dirstate.forget([f])
691 self.dirstate.forget([f])
692
692
693 def remove(self, list, unlink=False, wlock=None):
693 def remove(self, list, unlink=False, wlock=None):
694 if unlink:
694 if unlink:
695 for f in list:
695 for f in list:
696 try:
696 try:
697 util.unlink(self.wjoin(f))
697 util.unlink(self.wjoin(f))
698 except OSError, inst:
698 except OSError, inst:
699 if inst.errno != errno.ENOENT:
699 if inst.errno != errno.ENOENT:
700 raise
700 raise
701 if not wlock:
701 if not wlock:
702 wlock = self.wlock()
702 wlock = self.wlock()
703 for f in list:
703 for f in list:
704 p = self.wjoin(f)
704 p = self.wjoin(f)
705 if os.path.exists(p):
705 if os.path.exists(p):
706 self.ui.warn(_("%s still exists!\n") % f)
706 self.ui.warn(_("%s still exists!\n") % f)
707 elif self.dirstate.state(f) == 'a':
707 elif self.dirstate.state(f) == 'a':
708 self.dirstate.forget([f])
708 self.dirstate.forget([f])
709 elif f not in self.dirstate:
709 elif f not in self.dirstate:
710 self.ui.warn(_("%s not tracked!\n") % f)
710 self.ui.warn(_("%s not tracked!\n") % f)
711 else:
711 else:
712 self.dirstate.update([f], "r")
712 self.dirstate.update([f], "r")
713
713
714 def undelete(self, list, wlock=None):
714 def undelete(self, list, wlock=None):
715 p = self.dirstate.parents()[0]
715 p = self.dirstate.parents()[0]
716 mn = self.changelog.read(p)[0]
716 mn = self.changelog.read(p)[0]
717 mf = self.manifest.readflags(mn)
717 mf = self.manifest.readflags(mn)
718 m = self.manifest.read(mn)
718 m = self.manifest.read(mn)
719 if not wlock:
719 if not wlock:
720 wlock = self.wlock()
720 wlock = self.wlock()
721 for f in list:
721 for f in list:
722 if self.dirstate.state(f) not in "r":
722 if self.dirstate.state(f) not in "r":
723 self.ui.warn("%s not removed!\n" % f)
723 self.ui.warn("%s not removed!\n" % f)
724 else:
724 else:
725 t = self.file(f).read(m[f])
725 t = self.file(f).read(m[f])
726 self.wwrite(f, t)
726 self.wwrite(f, t)
727 util.set_exec(self.wjoin(f), mf[f])
727 util.set_exec(self.wjoin(f), mf[f])
728 self.dirstate.update([f], "n")
728 self.dirstate.update([f], "n")
729
729
730 def copy(self, source, dest, wlock=None):
730 def copy(self, source, dest, wlock=None):
731 p = self.wjoin(dest)
731 p = self.wjoin(dest)
732 if not os.path.exists(p):
732 if not os.path.exists(p):
733 self.ui.warn(_("%s does not exist!\n") % dest)
733 self.ui.warn(_("%s does not exist!\n") % dest)
734 elif not os.path.isfile(p):
734 elif not os.path.isfile(p):
735 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
735 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
736 else:
736 else:
737 if not wlock:
737 if not wlock:
738 wlock = self.wlock()
738 wlock = self.wlock()
739 if self.dirstate.state(dest) == '?':
739 if self.dirstate.state(dest) == '?':
740 self.dirstate.update([dest], "a")
740 self.dirstate.update([dest], "a")
741 self.dirstate.copy(source, dest)
741 self.dirstate.copy(source, dest)
742
742
743 def heads(self, start=None):
743 def heads(self, start=None):
744 heads = self.changelog.heads(start)
744 heads = self.changelog.heads(start)
745 # sort the output in rev descending order
745 # sort the output in rev descending order
746 heads = [(-self.changelog.rev(h), h) for h in heads]
746 heads = [(-self.changelog.rev(h), h) for h in heads]
747 heads.sort()
747 heads.sort()
748 return [n for (r, n) in heads]
748 return [n for (r, n) in heads]
749
749
750 # branchlookup returns a dict giving a list of branches for
750 # branchlookup returns a dict giving a list of branches for
751 # each head. A branch is defined as the tag of a node or
751 # each head. A branch is defined as the tag of a node or
752 # the branch of the node's parents. If a node has multiple
752 # the branch of the node's parents. If a node has multiple
753 # branch tags, tags are eliminated if they are visible from other
753 # branch tags, tags are eliminated if they are visible from other
754 # branch tags.
754 # branch tags.
755 #
755 #
756 # So, for this graph: a->b->c->d->e
756 # So, for this graph: a->b->c->d->e
757 # \ /
757 # \ /
758 # aa -----/
758 # aa -----/
759 # a has tag 2.6.12
759 # a has tag 2.6.12
760 # d has tag 2.6.13
760 # d has tag 2.6.13
761 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
761 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
762 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
762 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
763 # from the list.
763 # from the list.
764 #
764 #
765 # It is possible that more than one head will have the same branch tag.
765 # It is possible that more than one head will have the same branch tag.
766 # callers need to check the result for multiple heads under the same
766 # callers need to check the result for multiple heads under the same
767 # branch tag if that is a problem for them (ie checkout of a specific
767 # branch tag if that is a problem for them (ie checkout of a specific
768 # branch).
768 # branch).
769 #
769 #
770 # passing in a specific branch will limit the depth of the search
770 # passing in a specific branch will limit the depth of the search
771 # through the parents. It won't limit the branches returned in the
771 # through the parents. It won't limit the branches returned in the
772 # result though.
772 # result though.
773 def branchlookup(self, heads=None, branch=None):
773 def branchlookup(self, heads=None, branch=None):
774 if not heads:
774 if not heads:
775 heads = self.heads()
775 heads = self.heads()
776 headt = [ h for h in heads ]
776 headt = [ h for h in heads ]
777 chlog = self.changelog
777 chlog = self.changelog
778 branches = {}
778 branches = {}
779 merges = []
779 merges = []
780 seenmerge = {}
780 seenmerge = {}
781
781
782 # traverse the tree once for each head, recording in the branches
782 # traverse the tree once for each head, recording in the branches
783 # dict which tags are visible from this head. The branches
783 # dict which tags are visible from this head. The branches
784 # dict also records which tags are visible from each tag
784 # dict also records which tags are visible from each tag
785 # while we traverse.
785 # while we traverse.
786 while headt or merges:
786 while headt or merges:
787 if merges:
787 if merges:
788 n, found = merges.pop()
788 n, found = merges.pop()
789 visit = [n]
789 visit = [n]
790 else:
790 else:
791 h = headt.pop()
791 h = headt.pop()
792 visit = [h]
792 visit = [h]
793 found = [h]
793 found = [h]
794 seen = {}
794 seen = {}
795 while visit:
795 while visit:
796 n = visit.pop()
796 n = visit.pop()
797 if n in seen:
797 if n in seen:
798 continue
798 continue
799 pp = chlog.parents(n)
799 pp = chlog.parents(n)
800 tags = self.nodetags(n)
800 tags = self.nodetags(n)
801 if tags:
801 if tags:
802 for x in tags:
802 for x in tags:
803 if x == 'tip':
803 if x == 'tip':
804 continue
804 continue
805 for f in found:
805 for f in found:
806 branches.setdefault(f, {})[n] = 1
806 branches.setdefault(f, {})[n] = 1
807 branches.setdefault(n, {})[n] = 1
807 branches.setdefault(n, {})[n] = 1
808 break
808 break
809 if n not in found:
809 if n not in found:
810 found.append(n)
810 found.append(n)
811 if branch in tags:
811 if branch in tags:
812 continue
812 continue
813 seen[n] = 1
813 seen[n] = 1
814 if pp[1] != nullid and n not in seenmerge:
814 if pp[1] != nullid and n not in seenmerge:
815 merges.append((pp[1], [x for x in found]))
815 merges.append((pp[1], [x for x in found]))
816 seenmerge[n] = 1
816 seenmerge[n] = 1
817 if pp[0] != nullid:
817 if pp[0] != nullid:
818 visit.append(pp[0])
818 visit.append(pp[0])
819 # traverse the branches dict, eliminating branch tags from each
819 # traverse the branches dict, eliminating branch tags from each
820 # head that are visible from another branch tag for that head.
820 # head that are visible from another branch tag for that head.
821 out = {}
821 out = {}
822 viscache = {}
822 viscache = {}
823 for h in heads:
823 for h in heads:
824 def visible(node):
824 def visible(node):
825 if node in viscache:
825 if node in viscache:
826 return viscache[node]
826 return viscache[node]
827 ret = {}
827 ret = {}
828 visit = [node]
828 visit = [node]
829 while visit:
829 while visit:
830 x = visit.pop()
830 x = visit.pop()
831 if x in viscache:
831 if x in viscache:
832 ret.update(viscache[x])
832 ret.update(viscache[x])
833 elif x not in ret:
833 elif x not in ret:
834 ret[x] = 1
834 ret[x] = 1
835 if x in branches:
835 if x in branches:
836 visit[len(visit):] = branches[x].keys()
836 visit[len(visit):] = branches[x].keys()
837 viscache[node] = ret
837 viscache[node] = ret
838 return ret
838 return ret
839 if h not in branches:
839 if h not in branches:
840 continue
840 continue
841 # O(n^2), but somewhat limited. This only searches the
841 # O(n^2), but somewhat limited. This only searches the
842 # tags visible from a specific head, not all the tags in the
842 # tags visible from a specific head, not all the tags in the
843 # whole repo.
843 # whole repo.
844 for b in branches[h]:
844 for b in branches[h]:
845 vis = False
845 vis = False
846 for bb in branches[h].keys():
846 for bb in branches[h].keys():
847 if b != bb:
847 if b != bb:
848 if b in visible(bb):
848 if b in visible(bb):
849 vis = True
849 vis = True
850 break
850 break
851 if not vis:
851 if not vis:
852 l = out.setdefault(h, [])
852 l = out.setdefault(h, [])
853 l[len(l):] = self.nodetags(b)
853 l[len(l):] = self.nodetags(b)
854 return out
854 return out
855
855
856 def branches(self, nodes):
856 def branches(self, nodes):
857 if not nodes:
857 if not nodes:
858 nodes = [self.changelog.tip()]
858 nodes = [self.changelog.tip()]
859 b = []
859 b = []
860 for n in nodes:
860 for n in nodes:
861 t = n
861 t = n
862 while n:
862 while n:
863 p = self.changelog.parents(n)
863 p = self.changelog.parents(n)
864 if p[1] != nullid or p[0] == nullid:
864 if p[1] != nullid or p[0] == nullid:
865 b.append((t, n, p[0], p[1]))
865 b.append((t, n, p[0], p[1]))
866 break
866 break
867 n = p[0]
867 n = p[0]
868 return b
868 return b
869
869
870 def between(self, pairs):
870 def between(self, pairs):
871 r = []
871 r = []
872
872
873 for top, bottom in pairs:
873 for top, bottom in pairs:
874 n, l, i = top, [], 0
874 n, l, i = top, [], 0
875 f = 1
875 f = 1
876
876
877 while n != bottom:
877 while n != bottom:
878 p = self.changelog.parents(n)[0]
878 p = self.changelog.parents(n)[0]
879 if i == f:
879 if i == f:
880 l.append(n)
880 l.append(n)
881 f = f * 2
881 f = f * 2
882 n = p
882 n = p
883 i += 1
883 i += 1
884
884
885 r.append(l)
885 r.append(l)
886
886
887 return r
887 return r
888
888
889 def findincoming(self, remote, base=None, heads=None, force=False):
889 def findincoming(self, remote, base=None, heads=None, force=False):
890 m = self.changelog.nodemap
890 m = self.changelog.nodemap
891 search = []
891 search = []
892 fetch = {}
892 fetch = {}
893 seen = {}
893 seen = {}
894 seenbranch = {}
894 seenbranch = {}
895 if base == None:
895 if base == None:
896 base = {}
896 base = {}
897
897
898 if not heads:
898 if not heads:
899 heads = remote.heads()
899 heads = remote.heads()
900
900
901 if self.changelog.tip() == nullid:
901 if self.changelog.tip() == nullid:
902 if heads != [nullid]:
902 if heads != [nullid]:
903 return [nullid]
903 return [nullid]
904 return []
904 return []
905
905
906 # assume we're closer to the tip than the root
906 # assume we're closer to the tip than the root
907 # and start by examining the heads
907 # and start by examining the heads
908 self.ui.status(_("searching for changes\n"))
908 self.ui.status(_("searching for changes\n"))
909
909
910 unknown = []
910 unknown = []
911 for h in heads:
911 for h in heads:
912 if h not in m:
912 if h not in m:
913 unknown.append(h)
913 unknown.append(h)
914 else:
914 else:
915 base[h] = 1
915 base[h] = 1
916
916
917 if not unknown:
917 if not unknown:
918 return []
918 return []
919
919
920 rep = {}
920 rep = {}
921 reqcnt = 0
921 reqcnt = 0
922
922
923 # search through remote branches
923 # search through remote branches
924 # a 'branch' here is a linear segment of history, with four parts:
924 # a 'branch' here is a linear segment of history, with four parts:
925 # head, root, first parent, second parent
925 # head, root, first parent, second parent
926 # (a branch always has two parents (or none) by definition)
926 # (a branch always has two parents (or none) by definition)
927 unknown = remote.branches(unknown)
927 unknown = remote.branches(unknown)
928 while unknown:
928 while unknown:
929 r = []
929 r = []
930 while unknown:
930 while unknown:
931 n = unknown.pop(0)
931 n = unknown.pop(0)
932 if n[0] in seen:
932 if n[0] in seen:
933 continue
933 continue
934
934
935 self.ui.debug(_("examining %s:%s\n")
935 self.ui.debug(_("examining %s:%s\n")
936 % (short(n[0]), short(n[1])))
936 % (short(n[0]), short(n[1])))
937 if n[0] == nullid:
937 if n[0] == nullid:
938 break
938 break
939 if n in seenbranch:
939 if n in seenbranch:
940 self.ui.debug(_("branch already found\n"))
940 self.ui.debug(_("branch already found\n"))
941 continue
941 continue
942 if n[1] and n[1] in m: # do we know the base?
942 if n[1] and n[1] in m: # do we know the base?
943 self.ui.debug(_("found incomplete branch %s:%s\n")
943 self.ui.debug(_("found incomplete branch %s:%s\n")
944 % (short(n[0]), short(n[1])))
944 % (short(n[0]), short(n[1])))
945 search.append(n) # schedule branch range for scanning
945 search.append(n) # schedule branch range for scanning
946 seenbranch[n] = 1
946 seenbranch[n] = 1
947 else:
947 else:
948 if n[1] not in seen and n[1] not in fetch:
948 if n[1] not in seen and n[1] not in fetch:
949 if n[2] in m and n[3] in m:
949 if n[2] in m and n[3] in m:
950 self.ui.debug(_("found new changeset %s\n") %
950 self.ui.debug(_("found new changeset %s\n") %
951 short(n[1]))
951 short(n[1]))
952 fetch[n[1]] = 1 # earliest unknown
952 fetch[n[1]] = 1 # earliest unknown
953 base[n[2]] = 1 # latest known
953 base[n[2]] = 1 # latest known
954 continue
954 continue
955
955
956 for a in n[2:4]:
956 for a in n[2:4]:
957 if a not in rep:
957 if a not in rep:
958 r.append(a)
958 r.append(a)
959 rep[a] = 1
959 rep[a] = 1
960
960
961 seen[n[0]] = 1
961 seen[n[0]] = 1
962
962
963 if r:
963 if r:
964 reqcnt += 1
964 reqcnt += 1
965 self.ui.debug(_("request %d: %s\n") %
965 self.ui.debug(_("request %d: %s\n") %
966 (reqcnt, " ".join(map(short, r))))
966 (reqcnt, " ".join(map(short, r))))
967 for p in range(0, len(r), 10):
967 for p in range(0, len(r), 10):
968 for b in remote.branches(r[p:p+10]):
968 for b in remote.branches(r[p:p+10]):
969 self.ui.debug(_("received %s:%s\n") %
969 self.ui.debug(_("received %s:%s\n") %
970 (short(b[0]), short(b[1])))
970 (short(b[0]), short(b[1])))
971 if b[0] in m:
971 if b[0] in m:
972 self.ui.debug(_("found base node %s\n")
972 self.ui.debug(_("found base node %s\n")
973 % short(b[0]))
973 % short(b[0]))
974 base[b[0]] = 1
974 base[b[0]] = 1
975 elif b[0] not in seen:
975 elif b[0] not in seen:
976 unknown.append(b)
976 unknown.append(b)
977
977
978 # do binary search on the branches we found
978 # do binary search on the branches we found
979 while search:
979 while search:
980 n = search.pop(0)
980 n = search.pop(0)
981 reqcnt += 1
981 reqcnt += 1
982 l = remote.between([(n[0], n[1])])[0]
982 l = remote.between([(n[0], n[1])])[0]
983 l.append(n[1])
983 l.append(n[1])
984 p = n[0]
984 p = n[0]
985 f = 1
985 f = 1
986 for i in l:
986 for i in l:
987 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
987 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
988 if i in m:
988 if i in m:
989 if f <= 2:
989 if f <= 2:
990 self.ui.debug(_("found new branch changeset %s\n") %
990 self.ui.debug(_("found new branch changeset %s\n") %
991 short(p))
991 short(p))
992 fetch[p] = 1
992 fetch[p] = 1
993 base[i] = 1
993 base[i] = 1
994 else:
994 else:
995 self.ui.debug(_("narrowed branch search to %s:%s\n")
995 self.ui.debug(_("narrowed branch search to %s:%s\n")
996 % (short(p), short(i)))
996 % (short(p), short(i)))
997 search.append((p, i))
997 search.append((p, i))
998 break
998 break
999 p, f = i, f * 2
999 p, f = i, f * 2
1000
1000
1001 # sanity check our fetch list
1001 # sanity check our fetch list
1002 for f in fetch.keys():
1002 for f in fetch.keys():
1003 if f in m:
1003 if f in m:
1004 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1004 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1005
1005
1006 if base.keys() == [nullid]:
1006 if base.keys() == [nullid]:
1007 if force:
1007 if force:
1008 self.ui.warn(_("warning: repository is unrelated\n"))
1008 self.ui.warn(_("warning: repository is unrelated\n"))
1009 else:
1009 else:
1010 raise util.Abort(_("repository is unrelated"))
1010 raise util.Abort(_("repository is unrelated"))
1011
1011
1012 self.ui.note(_("found new changesets starting at ") +
1012 self.ui.note(_("found new changesets starting at ") +
1013 " ".join([short(f) for f in fetch]) + "\n")
1013 " ".join([short(f) for f in fetch]) + "\n")
1014
1014
1015 self.ui.debug(_("%d total queries\n") % reqcnt)
1015 self.ui.debug(_("%d total queries\n") % reqcnt)
1016
1016
1017 return fetch.keys()
1017 return fetch.keys()
1018
1018
1019 def findoutgoing(self, remote, base=None, heads=None, force=False):
1019 def findoutgoing(self, remote, base=None, heads=None, force=False):
1020 """Return list of nodes that are roots of subsets not in remote
1020 """Return list of nodes that are roots of subsets not in remote
1021
1021
1022 If base dict is specified, assume that these nodes and their parents
1022 If base dict is specified, assume that these nodes and their parents
1023 exist on the remote side.
1023 exist on the remote side.
1024 If a list of heads is specified, return only nodes which are heads
1024 If a list of heads is specified, return only nodes which are heads
1025 or ancestors of these heads, and return a second element which
1025 or ancestors of these heads, and return a second element which
1026 contains all remote heads which get new children.
1026 contains all remote heads which get new children.
1027 """
1027 """
1028 if base == None:
1028 if base == None:
1029 base = {}
1029 base = {}
1030 self.findincoming(remote, base, heads, force=force)
1030 self.findincoming(remote, base, heads, force=force)
1031
1031
1032 self.ui.debug(_("common changesets up to ")
1032 self.ui.debug(_("common changesets up to ")
1033 + " ".join(map(short, base.keys())) + "\n")
1033 + " ".join(map(short, base.keys())) + "\n")
1034
1034
1035 remain = dict.fromkeys(self.changelog.nodemap)
1035 remain = dict.fromkeys(self.changelog.nodemap)
1036
1036
1037 # prune everything remote has from the tree
1037 # prune everything remote has from the tree
1038 del remain[nullid]
1038 del remain[nullid]
1039 remove = base.keys()
1039 remove = base.keys()
1040 while remove:
1040 while remove:
1041 n = remove.pop(0)
1041 n = remove.pop(0)
1042 if n in remain:
1042 if n in remain:
1043 del remain[n]
1043 del remain[n]
1044 for p in self.changelog.parents(n):
1044 for p in self.changelog.parents(n):
1045 remove.append(p)
1045 remove.append(p)
1046
1046
1047 # find every node whose parents have been pruned
1047 # find every node whose parents have been pruned
1048 subset = []
1048 subset = []
1049 # find every remote head that will get new children
1049 # find every remote head that will get new children
1050 updated_heads = {}
1050 updated_heads = {}
1051 for n in remain:
1051 for n in remain:
1052 p1, p2 = self.changelog.parents(n)
1052 p1, p2 = self.changelog.parents(n)
1053 if p1 not in remain and p2 not in remain:
1053 if p1 not in remain and p2 not in remain:
1054 subset.append(n)
1054 subset.append(n)
1055 if heads:
1055 if heads:
1056 if p1 in heads:
1056 if p1 in heads:
1057 updated_heads[p1] = True
1057 updated_heads[p1] = True
1058 if p2 in heads:
1058 if p2 in heads:
1059 updated_heads[p2] = True
1059 updated_heads[p2] = True
1060
1060
1061 # this is the set of all roots we have to push
1061 # this is the set of all roots we have to push
1062 if heads:
1062 if heads:
1063 return subset, updated_heads.keys()
1063 return subset, updated_heads.keys()
1064 else:
1064 else:
1065 return subset
1065 return subset
1066
1066
1067 def pull(self, remote, heads=None, force=False):
1067 def pull(self, remote, heads=None, force=False):
1068 l = self.lock()
1068 l = self.lock()
1069
1069
1070 fetch = self.findincoming(remote, force=force)
1070 fetch = self.findincoming(remote, force=force)
1071 if fetch == [nullid]:
1071 if fetch == [nullid]:
1072 self.ui.status(_("requesting all changes\n"))
1072 self.ui.status(_("requesting all changes\n"))
1073
1073
1074 if not fetch:
1074 if not fetch:
1075 self.ui.status(_("no changes found\n"))
1075 self.ui.status(_("no changes found\n"))
1076 return 0
1076 return 0
1077
1077
1078 if heads is None:
1078 if heads is None:
1079 cg = remote.changegroup(fetch, 'pull')
1079 cg = remote.changegroup(fetch, 'pull')
1080 else:
1080 else:
1081 cg = remote.changegroupsubset(fetch, heads, 'pull')
1081 cg = remote.changegroupsubset(fetch, heads, 'pull')
1082 return self.addchangegroup(cg)
1082 return self.addchangegroup(cg, 'pull')
1083
1083
1084 def push(self, remote, force=False, revs=None):
1084 def push(self, remote, force=False, revs=None):
1085 lock = remote.lock()
1085 lock = remote.lock()
1086
1086
1087 base = {}
1087 base = {}
1088 remote_heads = remote.heads()
1088 remote_heads = remote.heads()
1089 inc = self.findincoming(remote, base, remote_heads, force=force)
1089 inc = self.findincoming(remote, base, remote_heads, force=force)
1090 if not force and inc:
1090 if not force and inc:
1091 self.ui.warn(_("abort: unsynced remote changes!\n"))
1091 self.ui.warn(_("abort: unsynced remote changes!\n"))
1092 self.ui.status(_("(did you forget to sync?"
1092 self.ui.status(_("(did you forget to sync?"
1093 " use push -f to force)\n"))
1093 " use push -f to force)\n"))
1094 return 1
1094 return 1
1095
1095
1096 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1096 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1097 if revs is not None:
1097 if revs is not None:
1098 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1098 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1099 else:
1099 else:
1100 bases, heads = update, self.changelog.heads()
1100 bases, heads = update, self.changelog.heads()
1101
1101
1102 if not bases:
1102 if not bases:
1103 self.ui.status(_("no changes found\n"))
1103 self.ui.status(_("no changes found\n"))
1104 return 1
1104 return 1
1105 elif not force:
1105 elif not force:
1106 # FIXME we don't properly detect creation of new heads
1106 # FIXME we don't properly detect creation of new heads
1107 # in the push -r case, assume the user knows what he's doing
1107 # in the push -r case, assume the user knows what he's doing
1108 if not revs and len(remote_heads) < len(heads) \
1108 if not revs and len(remote_heads) < len(heads) \
1109 and remote_heads != [nullid]:
1109 and remote_heads != [nullid]:
1110 self.ui.warn(_("abort: push creates new remote branches!\n"))
1110 self.ui.warn(_("abort: push creates new remote branches!\n"))
1111 self.ui.status(_("(did you forget to merge?"
1111 self.ui.status(_("(did you forget to merge?"
1112 " use push -f to force)\n"))
1112 " use push -f to force)\n"))
1113 return 1
1113 return 1
1114
1114
1115 if revs is None:
1115 if revs is None:
1116 cg = self.changegroup(update, 'push')
1116 cg = self.changegroup(update, 'push')
1117 else:
1117 else:
1118 cg = self.changegroupsubset(update, revs, 'push')
1118 cg = self.changegroupsubset(update, revs, 'push')
1119 return remote.addchangegroup(cg)
1119 return remote.addchangegroup(cg, 'push')
1120
1120
1121 def changegroupsubset(self, bases, heads, source):
1121 def changegroupsubset(self, bases, heads, source):
1122 """This function generates a changegroup consisting of all the nodes
1122 """This function generates a changegroup consisting of all the nodes
1123 that are descendents of any of the bases, and ancestors of any of
1123 that are descendents of any of the bases, and ancestors of any of
1124 the heads.
1124 the heads.
1125
1125
1126 It is fairly complex as determining which filenodes and which
1126 It is fairly complex as determining which filenodes and which
1127 manifest nodes need to be included for the changeset to be complete
1127 manifest nodes need to be included for the changeset to be complete
1128 is non-trivial.
1128 is non-trivial.
1129
1129
1130 Another wrinkle is doing the reverse, figuring out which changeset in
1130 Another wrinkle is doing the reverse, figuring out which changeset in
1131 the changegroup a particular filenode or manifestnode belongs to."""
1131 the changegroup a particular filenode or manifestnode belongs to."""
1132
1132
1133 self.hook('preoutgoing', throw=True, source=source)
1133 self.hook('preoutgoing', throw=True, source=source)
1134
1134
1135 # Set up some initial variables
1135 # Set up some initial variables
1136 # Make it easy to refer to self.changelog
1136 # Make it easy to refer to self.changelog
1137 cl = self.changelog
1137 cl = self.changelog
1138 # msng is short for missing - compute the list of changesets in this
1138 # msng is short for missing - compute the list of changesets in this
1139 # changegroup.
1139 # changegroup.
1140 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1140 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1141 # Some bases may turn out to be superfluous, and some heads may be
1141 # Some bases may turn out to be superfluous, and some heads may be
1142 # too. nodesbetween will return the minimal set of bases and heads
1142 # too. nodesbetween will return the minimal set of bases and heads
1143 # necessary to re-create the changegroup.
1143 # necessary to re-create the changegroup.
1144
1144
1145 # Known heads are the list of heads that it is assumed the recipient
1145 # Known heads are the list of heads that it is assumed the recipient
1146 # of this changegroup will know about.
1146 # of this changegroup will know about.
1147 knownheads = {}
1147 knownheads = {}
1148 # We assume that all parents of bases are known heads.
1148 # We assume that all parents of bases are known heads.
1149 for n in bases:
1149 for n in bases:
1150 for p in cl.parents(n):
1150 for p in cl.parents(n):
1151 if p != nullid:
1151 if p != nullid:
1152 knownheads[p] = 1
1152 knownheads[p] = 1
1153 knownheads = knownheads.keys()
1153 knownheads = knownheads.keys()
1154 if knownheads:
1154 if knownheads:
1155 # Now that we know what heads are known, we can compute which
1155 # Now that we know what heads are known, we can compute which
1156 # changesets are known. The recipient must know about all
1156 # changesets are known. The recipient must know about all
1157 # changesets required to reach the known heads from the null
1157 # changesets required to reach the known heads from the null
1158 # changeset.
1158 # changeset.
1159 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1159 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1160 junk = None
1160 junk = None
1161 # Transform the list into an ersatz set.
1161 # Transform the list into an ersatz set.
1162 has_cl_set = dict.fromkeys(has_cl_set)
1162 has_cl_set = dict.fromkeys(has_cl_set)
1163 else:
1163 else:
1164 # If there were no known heads, the recipient cannot be assumed to
1164 # If there were no known heads, the recipient cannot be assumed to
1165 # know about any changesets.
1165 # know about any changesets.
1166 has_cl_set = {}
1166 has_cl_set = {}
1167
1167
1168 # Make it easy to refer to self.manifest
1168 # Make it easy to refer to self.manifest
1169 mnfst = self.manifest
1169 mnfst = self.manifest
1170 # We don't know which manifests are missing yet
1170 # We don't know which manifests are missing yet
1171 msng_mnfst_set = {}
1171 msng_mnfst_set = {}
1172 # Nor do we know which filenodes are missing.
1172 # Nor do we know which filenodes are missing.
1173 msng_filenode_set = {}
1173 msng_filenode_set = {}
1174
1174
1175 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1175 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1176 junk = None
1176 junk = None
1177
1177
1178 # A changeset always belongs to itself, so the changenode lookup
1178 # A changeset always belongs to itself, so the changenode lookup
1179 # function for a changenode is identity.
1179 # function for a changenode is identity.
1180 def identity(x):
1180 def identity(x):
1181 return x
1181 return x
1182
1182
1183 # A function generating function. Sets up an environment for the
1183 # A function generating function. Sets up an environment for the
1184 # inner function.
1184 # inner function.
1185 def cmp_by_rev_func(revlog):
1185 def cmp_by_rev_func(revlog):
1186 # Compare two nodes by their revision number in the environment's
1186 # Compare two nodes by their revision number in the environment's
1187 # revision history. Since the revision number both represents the
1187 # revision history. Since the revision number both represents the
1188 # most efficient order to read the nodes in, and represents a
1188 # most efficient order to read the nodes in, and represents a
1189 # topological sorting of the nodes, this function is often useful.
1189 # topological sorting of the nodes, this function is often useful.
1190 def cmp_by_rev(a, b):
1190 def cmp_by_rev(a, b):
1191 return cmp(revlog.rev(a), revlog.rev(b))
1191 return cmp(revlog.rev(a), revlog.rev(b))
1192 return cmp_by_rev
1192 return cmp_by_rev
1193
1193
1194 # If we determine that a particular file or manifest node must be a
1194 # If we determine that a particular file or manifest node must be a
1195 # node that the recipient of the changegroup will already have, we can
1195 # node that the recipient of the changegroup will already have, we can
1196 # also assume the recipient will have all the parents. This function
1196 # also assume the recipient will have all the parents. This function
1197 # prunes them from the set of missing nodes.
1197 # prunes them from the set of missing nodes.
1198 def prune_parents(revlog, hasset, msngset):
1198 def prune_parents(revlog, hasset, msngset):
1199 haslst = hasset.keys()
1199 haslst = hasset.keys()
1200 haslst.sort(cmp_by_rev_func(revlog))
1200 haslst.sort(cmp_by_rev_func(revlog))
1201 for node in haslst:
1201 for node in haslst:
1202 parentlst = [p for p in revlog.parents(node) if p != nullid]
1202 parentlst = [p for p in revlog.parents(node) if p != nullid]
1203 while parentlst:
1203 while parentlst:
1204 n = parentlst.pop()
1204 n = parentlst.pop()
1205 if n not in hasset:
1205 if n not in hasset:
1206 hasset[n] = 1
1206 hasset[n] = 1
1207 p = [p for p in revlog.parents(n) if p != nullid]
1207 p = [p for p in revlog.parents(n) if p != nullid]
1208 parentlst.extend(p)
1208 parentlst.extend(p)
1209 for n in hasset:
1209 for n in hasset:
1210 msngset.pop(n, None)
1210 msngset.pop(n, None)
1211
1211
1212 # This is a function generating function used to set up an environment
1212 # This is a function generating function used to set up an environment
1213 # for the inner function to execute in.
1213 # for the inner function to execute in.
1214 def manifest_and_file_collector(changedfileset):
1214 def manifest_and_file_collector(changedfileset):
1215 # This is an information gathering function that gathers
1215 # This is an information gathering function that gathers
1216 # information from each changeset node that goes out as part of
1216 # information from each changeset node that goes out as part of
1217 # the changegroup. The information gathered is a list of which
1217 # the changegroup. The information gathered is a list of which
1218 # manifest nodes are potentially required (the recipient may
1218 # manifest nodes are potentially required (the recipient may
1219 # already have them) and total list of all files which were
1219 # already have them) and total list of all files which were
1220 # changed in any changeset in the changegroup.
1220 # changed in any changeset in the changegroup.
1221 #
1221 #
1222 # We also remember the first changenode we saw any manifest
1222 # We also remember the first changenode we saw any manifest
1223 # referenced by so we can later determine which changenode 'owns'
1223 # referenced by so we can later determine which changenode 'owns'
1224 # the manifest.
1224 # the manifest.
1225 def collect_manifests_and_files(clnode):
1225 def collect_manifests_and_files(clnode):
1226 c = cl.read(clnode)
1226 c = cl.read(clnode)
1227 for f in c[3]:
1227 for f in c[3]:
1228 # This is to make sure we only have one instance of each
1228 # This is to make sure we only have one instance of each
1229 # filename string for each filename.
1229 # filename string for each filename.
1230 changedfileset.setdefault(f, f)
1230 changedfileset.setdefault(f, f)
1231 msng_mnfst_set.setdefault(c[0], clnode)
1231 msng_mnfst_set.setdefault(c[0], clnode)
1232 return collect_manifests_and_files
1232 return collect_manifests_and_files
1233
1233
1234 # Figure out which manifest nodes (of the ones we think might be part
1234 # Figure out which manifest nodes (of the ones we think might be part
1235 # of the changegroup) the recipient must know about and remove them
1235 # of the changegroup) the recipient must know about and remove them
1236 # from the changegroup.
1236 # from the changegroup.
1237 def prune_manifests():
1237 def prune_manifests():
1238 has_mnfst_set = {}
1238 has_mnfst_set = {}
1239 for n in msng_mnfst_set:
1239 for n in msng_mnfst_set:
1240 # If a 'missing' manifest thinks it belongs to a changenode
1240 # If a 'missing' manifest thinks it belongs to a changenode
1241 # the recipient is assumed to have, obviously the recipient
1241 # the recipient is assumed to have, obviously the recipient
1242 # must have that manifest.
1242 # must have that manifest.
1243 linknode = cl.node(mnfst.linkrev(n))
1243 linknode = cl.node(mnfst.linkrev(n))
1244 if linknode in has_cl_set:
1244 if linknode in has_cl_set:
1245 has_mnfst_set[n] = 1
1245 has_mnfst_set[n] = 1
1246 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1246 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1247
1247
1248 # Use the information collected in collect_manifests_and_files to say
1248 # Use the information collected in collect_manifests_and_files to say
1249 # which changenode any manifestnode belongs to.
1249 # which changenode any manifestnode belongs to.
1250 def lookup_manifest_link(mnfstnode):
1250 def lookup_manifest_link(mnfstnode):
1251 return msng_mnfst_set[mnfstnode]
1251 return msng_mnfst_set[mnfstnode]
1252
1252
1253 # A function generating function that sets up the initial environment
1253 # A function generating function that sets up the initial environment
1254 # the inner function.
1254 # the inner function.
1255 def filenode_collector(changedfiles):
1255 def filenode_collector(changedfiles):
1256 next_rev = [0]
1256 next_rev = [0]
1257 # This gathers information from each manifestnode included in the
1257 # This gathers information from each manifestnode included in the
1258 # changegroup about which filenodes the manifest node references
1258 # changegroup about which filenodes the manifest node references
1259 # so we can include those in the changegroup too.
1259 # so we can include those in the changegroup too.
1260 #
1260 #
1261 # It also remembers which changenode each filenode belongs to. It
1261 # It also remembers which changenode each filenode belongs to. It
1262 # does this by assuming the a filenode belongs to the changenode
1262 # does this by assuming the a filenode belongs to the changenode
1263 # the first manifest that references it belongs to.
1263 # the first manifest that references it belongs to.
1264 def collect_msng_filenodes(mnfstnode):
1264 def collect_msng_filenodes(mnfstnode):
1265 r = mnfst.rev(mnfstnode)
1265 r = mnfst.rev(mnfstnode)
1266 if r == next_rev[0]:
1266 if r == next_rev[0]:
1267 # If the last rev we looked at was the one just previous,
1267 # If the last rev we looked at was the one just previous,
1268 # we only need to see a diff.
1268 # we only need to see a diff.
1269 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1269 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1270 # For each line in the delta
1270 # For each line in the delta
1271 for dline in delta.splitlines():
1271 for dline in delta.splitlines():
1272 # get the filename and filenode for that line
1272 # get the filename and filenode for that line
1273 f, fnode = dline.split('\0')
1273 f, fnode = dline.split('\0')
1274 fnode = bin(fnode[:40])
1274 fnode = bin(fnode[:40])
1275 f = changedfiles.get(f, None)
1275 f = changedfiles.get(f, None)
1276 # And if the file is in the list of files we care
1276 # And if the file is in the list of files we care
1277 # about.
1277 # about.
1278 if f is not None:
1278 if f is not None:
1279 # Get the changenode this manifest belongs to
1279 # Get the changenode this manifest belongs to
1280 clnode = msng_mnfst_set[mnfstnode]
1280 clnode = msng_mnfst_set[mnfstnode]
1281 # Create the set of filenodes for the file if
1281 # Create the set of filenodes for the file if
1282 # there isn't one already.
1282 # there isn't one already.
1283 ndset = msng_filenode_set.setdefault(f, {})
1283 ndset = msng_filenode_set.setdefault(f, {})
1284 # And set the filenode's changelog node to the
1284 # And set the filenode's changelog node to the
1285 # manifest's if it hasn't been set already.
1285 # manifest's if it hasn't been set already.
1286 ndset.setdefault(fnode, clnode)
1286 ndset.setdefault(fnode, clnode)
1287 else:
1287 else:
1288 # Otherwise we need a full manifest.
1288 # Otherwise we need a full manifest.
1289 m = mnfst.read(mnfstnode)
1289 m = mnfst.read(mnfstnode)
1290 # For every file in we care about.
1290 # For every file in we care about.
1291 for f in changedfiles:
1291 for f in changedfiles:
1292 fnode = m.get(f, None)
1292 fnode = m.get(f, None)
1293 # If it's in the manifest
1293 # If it's in the manifest
1294 if fnode is not None:
1294 if fnode is not None:
1295 # See comments above.
1295 # See comments above.
1296 clnode = msng_mnfst_set[mnfstnode]
1296 clnode = msng_mnfst_set[mnfstnode]
1297 ndset = msng_filenode_set.setdefault(f, {})
1297 ndset = msng_filenode_set.setdefault(f, {})
1298 ndset.setdefault(fnode, clnode)
1298 ndset.setdefault(fnode, clnode)
1299 # Remember the revision we hope to see next.
1299 # Remember the revision we hope to see next.
1300 next_rev[0] = r + 1
1300 next_rev[0] = r + 1
1301 return collect_msng_filenodes
1301 return collect_msng_filenodes
1302
1302
1303 # We have a list of filenodes we think we need for a file, lets remove
1303 # We have a list of filenodes we think we need for a file, lets remove
1304 # all those we now the recipient must have.
1304 # all those we now the recipient must have.
1305 def prune_filenodes(f, filerevlog):
1305 def prune_filenodes(f, filerevlog):
1306 msngset = msng_filenode_set[f]
1306 msngset = msng_filenode_set[f]
1307 hasset = {}
1307 hasset = {}
1308 # If a 'missing' filenode thinks it belongs to a changenode we
1308 # If a 'missing' filenode thinks it belongs to a changenode we
1309 # assume the recipient must have, then the recipient must have
1309 # assume the recipient must have, then the recipient must have
1310 # that filenode.
1310 # that filenode.
1311 for n in msngset:
1311 for n in msngset:
1312 clnode = cl.node(filerevlog.linkrev(n))
1312 clnode = cl.node(filerevlog.linkrev(n))
1313 if clnode in has_cl_set:
1313 if clnode in has_cl_set:
1314 hasset[n] = 1
1314 hasset[n] = 1
1315 prune_parents(filerevlog, hasset, msngset)
1315 prune_parents(filerevlog, hasset, msngset)
1316
1316
1317 # A function generator function that sets up the a context for the
1317 # A function generator function that sets up the a context for the
1318 # inner function.
1318 # inner function.
1319 def lookup_filenode_link_func(fname):
1319 def lookup_filenode_link_func(fname):
1320 msngset = msng_filenode_set[fname]
1320 msngset = msng_filenode_set[fname]
1321 # Lookup the changenode the filenode belongs to.
1321 # Lookup the changenode the filenode belongs to.
1322 def lookup_filenode_link(fnode):
1322 def lookup_filenode_link(fnode):
1323 return msngset[fnode]
1323 return msngset[fnode]
1324 return lookup_filenode_link
1324 return lookup_filenode_link
1325
1325
1326 # Now that we have all theses utility functions to help out and
1326 # Now that we have all theses utility functions to help out and
1327 # logically divide up the task, generate the group.
1327 # logically divide up the task, generate the group.
1328 def gengroup():
1328 def gengroup():
1329 # The set of changed files starts empty.
1329 # The set of changed files starts empty.
1330 changedfiles = {}
1330 changedfiles = {}
1331 # Create a changenode group generator that will call our functions
1331 # Create a changenode group generator that will call our functions
1332 # back to lookup the owning changenode and collect information.
1332 # back to lookup the owning changenode and collect information.
1333 group = cl.group(msng_cl_lst, identity,
1333 group = cl.group(msng_cl_lst, identity,
1334 manifest_and_file_collector(changedfiles))
1334 manifest_and_file_collector(changedfiles))
1335 for chnk in group:
1335 for chnk in group:
1336 yield chnk
1336 yield chnk
1337
1337
1338 # The list of manifests has been collected by the generator
1338 # The list of manifests has been collected by the generator
1339 # calling our functions back.
1339 # calling our functions back.
1340 prune_manifests()
1340 prune_manifests()
1341 msng_mnfst_lst = msng_mnfst_set.keys()
1341 msng_mnfst_lst = msng_mnfst_set.keys()
1342 # Sort the manifestnodes by revision number.
1342 # Sort the manifestnodes by revision number.
1343 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1343 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1344 # Create a generator for the manifestnodes that calls our lookup
1344 # Create a generator for the manifestnodes that calls our lookup
1345 # and data collection functions back.
1345 # and data collection functions back.
1346 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1346 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1347 filenode_collector(changedfiles))
1347 filenode_collector(changedfiles))
1348 for chnk in group:
1348 for chnk in group:
1349 yield chnk
1349 yield chnk
1350
1350
1351 # These are no longer needed, dereference and toss the memory for
1351 # These are no longer needed, dereference and toss the memory for
1352 # them.
1352 # them.
1353 msng_mnfst_lst = None
1353 msng_mnfst_lst = None
1354 msng_mnfst_set.clear()
1354 msng_mnfst_set.clear()
1355
1355
1356 changedfiles = changedfiles.keys()
1356 changedfiles = changedfiles.keys()
1357 changedfiles.sort()
1357 changedfiles.sort()
1358 # Go through all our files in order sorted by name.
1358 # Go through all our files in order sorted by name.
1359 for fname in changedfiles:
1359 for fname in changedfiles:
1360 filerevlog = self.file(fname)
1360 filerevlog = self.file(fname)
1361 # Toss out the filenodes that the recipient isn't really
1361 # Toss out the filenodes that the recipient isn't really
1362 # missing.
1362 # missing.
1363 if msng_filenode_set.has_key(fname):
1363 if msng_filenode_set.has_key(fname):
1364 prune_filenodes(fname, filerevlog)
1364 prune_filenodes(fname, filerevlog)
1365 msng_filenode_lst = msng_filenode_set[fname].keys()
1365 msng_filenode_lst = msng_filenode_set[fname].keys()
1366 else:
1366 else:
1367 msng_filenode_lst = []
1367 msng_filenode_lst = []
1368 # If any filenodes are left, generate the group for them,
1368 # If any filenodes are left, generate the group for them,
1369 # otherwise don't bother.
1369 # otherwise don't bother.
1370 if len(msng_filenode_lst) > 0:
1370 if len(msng_filenode_lst) > 0:
1371 yield changegroup.genchunk(fname)
1371 yield changegroup.genchunk(fname)
1372 # Sort the filenodes by their revision #
1372 # Sort the filenodes by their revision #
1373 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1373 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1374 # Create a group generator and only pass in a changenode
1374 # Create a group generator and only pass in a changenode
1375 # lookup function as we need to collect no information
1375 # lookup function as we need to collect no information
1376 # from filenodes.
1376 # from filenodes.
1377 group = filerevlog.group(msng_filenode_lst,
1377 group = filerevlog.group(msng_filenode_lst,
1378 lookup_filenode_link_func(fname))
1378 lookup_filenode_link_func(fname))
1379 for chnk in group:
1379 for chnk in group:
1380 yield chnk
1380 yield chnk
1381 if msng_filenode_set.has_key(fname):
1381 if msng_filenode_set.has_key(fname):
1382 # Don't need this anymore, toss it to free memory.
1382 # Don't need this anymore, toss it to free memory.
1383 del msng_filenode_set[fname]
1383 del msng_filenode_set[fname]
1384 # Signal that no more groups are left.
1384 # Signal that no more groups are left.
1385 yield changegroup.closechunk()
1385 yield changegroup.closechunk()
1386
1386
1387 if msng_cl_lst:
1387 if msng_cl_lst:
1388 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1388 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1389
1389
1390 return util.chunkbuffer(gengroup())
1390 return util.chunkbuffer(gengroup())
1391
1391
1392 def changegroup(self, basenodes, source):
1392 def changegroup(self, basenodes, source):
1393 """Generate a changegroup of all nodes that we have that a recipient
1393 """Generate a changegroup of all nodes that we have that a recipient
1394 doesn't.
1394 doesn't.
1395
1395
1396 This is much easier than the previous function as we can assume that
1396 This is much easier than the previous function as we can assume that
1397 the recipient has any changenode we aren't sending them."""
1397 the recipient has any changenode we aren't sending them."""
1398
1398
1399 self.hook('preoutgoing', throw=True, source=source)
1399 self.hook('preoutgoing', throw=True, source=source)
1400
1400
1401 cl = self.changelog
1401 cl = self.changelog
1402 nodes = cl.nodesbetween(basenodes, None)[0]
1402 nodes = cl.nodesbetween(basenodes, None)[0]
1403 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1403 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1404
1404
1405 def identity(x):
1405 def identity(x):
1406 return x
1406 return x
1407
1407
1408 def gennodelst(revlog):
1408 def gennodelst(revlog):
1409 for r in xrange(0, revlog.count()):
1409 for r in xrange(0, revlog.count()):
1410 n = revlog.node(r)
1410 n = revlog.node(r)
1411 if revlog.linkrev(n) in revset:
1411 if revlog.linkrev(n) in revset:
1412 yield n
1412 yield n
1413
1413
1414 def changed_file_collector(changedfileset):
1414 def changed_file_collector(changedfileset):
1415 def collect_changed_files(clnode):
1415 def collect_changed_files(clnode):
1416 c = cl.read(clnode)
1416 c = cl.read(clnode)
1417 for fname in c[3]:
1417 for fname in c[3]:
1418 changedfileset[fname] = 1
1418 changedfileset[fname] = 1
1419 return collect_changed_files
1419 return collect_changed_files
1420
1420
1421 def lookuprevlink_func(revlog):
1421 def lookuprevlink_func(revlog):
1422 def lookuprevlink(n):
1422 def lookuprevlink(n):
1423 return cl.node(revlog.linkrev(n))
1423 return cl.node(revlog.linkrev(n))
1424 return lookuprevlink
1424 return lookuprevlink
1425
1425
1426 def gengroup():
1426 def gengroup():
1427 # construct a list of all changed files
1427 # construct a list of all changed files
1428 changedfiles = {}
1428 changedfiles = {}
1429
1429
1430 for chnk in cl.group(nodes, identity,
1430 for chnk in cl.group(nodes, identity,
1431 changed_file_collector(changedfiles)):
1431 changed_file_collector(changedfiles)):
1432 yield chnk
1432 yield chnk
1433 changedfiles = changedfiles.keys()
1433 changedfiles = changedfiles.keys()
1434 changedfiles.sort()
1434 changedfiles.sort()
1435
1435
1436 mnfst = self.manifest
1436 mnfst = self.manifest
1437 nodeiter = gennodelst(mnfst)
1437 nodeiter = gennodelst(mnfst)
1438 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1438 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1439 yield chnk
1439 yield chnk
1440
1440
1441 for fname in changedfiles:
1441 for fname in changedfiles:
1442 filerevlog = self.file(fname)
1442 filerevlog = self.file(fname)
1443 nodeiter = gennodelst(filerevlog)
1443 nodeiter = gennodelst(filerevlog)
1444 nodeiter = list(nodeiter)
1444 nodeiter = list(nodeiter)
1445 if nodeiter:
1445 if nodeiter:
1446 yield changegroup.genchunk(fname)
1446 yield changegroup.genchunk(fname)
1447 lookup = lookuprevlink_func(filerevlog)
1447 lookup = lookuprevlink_func(filerevlog)
1448 for chnk in filerevlog.group(nodeiter, lookup):
1448 for chnk in filerevlog.group(nodeiter, lookup):
1449 yield chnk
1449 yield chnk
1450
1450
1451 yield changegroup.closechunk()
1451 yield changegroup.closechunk()
1452
1452
1453 if nodes:
1453 if nodes:
1454 self.hook('outgoing', node=hex(nodes[0]), source=source)
1454 self.hook('outgoing', node=hex(nodes[0]), source=source)
1455
1455
1456 return util.chunkbuffer(gengroup())
1456 return util.chunkbuffer(gengroup())
1457
1457
1458 def addchangegroup(self, source):
1458 def addchangegroup(self, source, srctype):
1459 """add changegroup to repo.
1459 """add changegroup to repo.
1460 returns number of heads modified or added + 1."""
1460 returns number of heads modified or added + 1."""
1461
1461
1462 def csmap(x):
1462 def csmap(x):
1463 self.ui.debug(_("add changeset %s\n") % short(x))
1463 self.ui.debug(_("add changeset %s\n") % short(x))
1464 return cl.count()
1464 return cl.count()
1465
1465
1466 def revmap(x):
1466 def revmap(x):
1467 return cl.rev(x)
1467 return cl.rev(x)
1468
1468
1469 if not source:
1469 if not source:
1470 return 0
1470 return 0
1471
1471
1472 self.hook('prechangegroup', throw=True, source=source)
1472 self.hook('prechangegroup', throw=True, source=srctype)
1473
1473
1474 changesets = files = revisions = 0
1474 changesets = files = revisions = 0
1475
1475
1476 tr = self.transaction()
1476 tr = self.transaction()
1477
1477
1478 # write changelog and manifest data to temp files so
1478 # write changelog and manifest data to temp files so
1479 # concurrent readers will not see inconsistent view
1479 # concurrent readers will not see inconsistent view
1480 cl = appendfile.appendchangelog(self.opener, self.changelog.version)
1480 cl = appendfile.appendchangelog(self.opener, self.changelog.version)
1481
1481
1482 oldheads = len(cl.heads())
1482 oldheads = len(cl.heads())
1483
1483
1484 # pull off the changeset group
1484 # pull off the changeset group
1485 self.ui.status(_("adding changesets\n"))
1485 self.ui.status(_("adding changesets\n"))
1486 co = cl.tip()
1486 co = cl.tip()
1487 chunkiter = changegroup.chunkiter(source)
1487 chunkiter = changegroup.chunkiter(source)
1488 cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
1488 cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
1489 cnr, cor = map(cl.rev, (cn, co))
1489 cnr, cor = map(cl.rev, (cn, co))
1490 if cn == nullid:
1490 if cn == nullid:
1491 cnr = cor
1491 cnr = cor
1492 changesets = cnr - cor
1492 changesets = cnr - cor
1493
1493
1494 mf = appendfile.appendmanifest(self.opener, self.manifest.version)
1494 mf = appendfile.appendmanifest(self.opener, self.manifest.version)
1495
1495
1496 # pull off the manifest group
1496 # pull off the manifest group
1497 self.ui.status(_("adding manifests\n"))
1497 self.ui.status(_("adding manifests\n"))
1498 mm = mf.tip()
1498 mm = mf.tip()
1499 chunkiter = changegroup.chunkiter(source)
1499 chunkiter = changegroup.chunkiter(source)
1500 mo = mf.addgroup(chunkiter, revmap, tr)
1500 mo = mf.addgroup(chunkiter, revmap, tr)
1501
1501
1502 # process the files
1502 # process the files
1503 self.ui.status(_("adding file changes\n"))
1503 self.ui.status(_("adding file changes\n"))
1504 while 1:
1504 while 1:
1505 f = changegroup.getchunk(source)
1505 f = changegroup.getchunk(source)
1506 if not f:
1506 if not f:
1507 break
1507 break
1508 self.ui.debug(_("adding %s revisions\n") % f)
1508 self.ui.debug(_("adding %s revisions\n") % f)
1509 fl = self.file(f)
1509 fl = self.file(f)
1510 o = fl.count()
1510 o = fl.count()
1511 chunkiter = changegroup.chunkiter(source)
1511 chunkiter = changegroup.chunkiter(source)
1512 n = fl.addgroup(chunkiter, revmap, tr)
1512 n = fl.addgroup(chunkiter, revmap, tr)
1513 revisions += fl.count() - o
1513 revisions += fl.count() - o
1514 files += 1
1514 files += 1
1515
1515
1516 # write order here is important so concurrent readers will see
1516 # write order here is important so concurrent readers will see
1517 # consistent view of repo
1517 # consistent view of repo
1518 mf.writedata()
1518 mf.writedata()
1519 cl.writedata()
1519 cl.writedata()
1520
1520
1521 # make changelog and manifest see real files again
1521 # make changelog and manifest see real files again
1522 self.changelog = changelog.changelog(self.opener, self.changelog.version)
1522 self.changelog = changelog.changelog(self.opener, self.changelog.version)
1523 self.manifest = manifest.manifest(self.opener, self.manifest.version)
1523 self.manifest = manifest.manifest(self.opener, self.manifest.version)
1524 self.changelog.checkinlinesize(tr)
1524 self.changelog.checkinlinesize(tr)
1525 self.manifest.checkinlinesize(tr)
1525 self.manifest.checkinlinesize(tr)
1526
1526
1527 newheads = len(self.changelog.heads())
1527 newheads = len(self.changelog.heads())
1528 heads = ""
1528 heads = ""
1529 if oldheads and newheads > oldheads:
1529 if oldheads and newheads > oldheads:
1530 heads = _(" (+%d heads)") % (newheads - oldheads)
1530 heads = _(" (+%d heads)") % (newheads - oldheads)
1531
1531
1532 self.ui.status(_("added %d changesets"
1532 self.ui.status(_("added %d changesets"
1533 " with %d changes to %d files%s\n")
1533 " with %d changes to %d files%s\n")
1534 % (changesets, revisions, files, heads))
1534 % (changesets, revisions, files, heads))
1535
1535
1536 self.hook('pretxnchangegroup', throw=True,
1536 self.hook('pretxnchangegroup', throw=True,
1537 node=hex(self.changelog.node(cor+1)), source=source)
1537 node=hex(self.changelog.node(cor+1)), source=srctype)
1538
1538
1539 tr.close()
1539 tr.close()
1540
1540
1541 if changesets > 0:
1541 if changesets > 0:
1542 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1542 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1543 source=source)
1543 source=srctype)
1544
1544
1545 for i in range(cor + 1, cnr + 1):
1545 for i in range(cor + 1, cnr + 1):
1546 self.hook("incoming", node=hex(self.changelog.node(i)),
1546 self.hook("incoming", node=hex(self.changelog.node(i)),
1547 source=source)
1547 source=srctype)
1548
1548
1549 return newheads - oldheads + 1
1549 return newheads - oldheads + 1
1550
1550
1551 def update(self, node, allow=False, force=False, choose=None,
1551 def update(self, node, allow=False, force=False, choose=None,
1552 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
1552 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
1553 pl = self.dirstate.parents()
1553 pl = self.dirstate.parents()
1554 if not force and pl[1] != nullid:
1554 if not force and pl[1] != nullid:
1555 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1555 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1556 return 1
1556 return 1
1557
1557
1558 err = False
1558 err = False
1559
1559
1560 p1, p2 = pl[0], node
1560 p1, p2 = pl[0], node
1561 pa = self.changelog.ancestor(p1, p2)
1561 pa = self.changelog.ancestor(p1, p2)
1562 m1n = self.changelog.read(p1)[0]
1562 m1n = self.changelog.read(p1)[0]
1563 m2n = self.changelog.read(p2)[0]
1563 m2n = self.changelog.read(p2)[0]
1564 man = self.manifest.ancestor(m1n, m2n)
1564 man = self.manifest.ancestor(m1n, m2n)
1565 m1 = self.manifest.read(m1n)
1565 m1 = self.manifest.read(m1n)
1566 mf1 = self.manifest.readflags(m1n)
1566 mf1 = self.manifest.readflags(m1n)
1567 m2 = self.manifest.read(m2n).copy()
1567 m2 = self.manifest.read(m2n).copy()
1568 mf2 = self.manifest.readflags(m2n)
1568 mf2 = self.manifest.readflags(m2n)
1569 ma = self.manifest.read(man)
1569 ma = self.manifest.read(man)
1570 mfa = self.manifest.readflags(man)
1570 mfa = self.manifest.readflags(man)
1571
1571
1572 modified, added, removed, deleted, unknown = self.changes()
1572 modified, added, removed, deleted, unknown = self.changes()
1573
1573
1574 # is this a jump, or a merge? i.e. is there a linear path
1574 # is this a jump, or a merge? i.e. is there a linear path
1575 # from p1 to p2?
1575 # from p1 to p2?
1576 linear_path = (pa == p1 or pa == p2)
1576 linear_path = (pa == p1 or pa == p2)
1577
1577
1578 if allow and linear_path:
1578 if allow and linear_path:
1579 raise util.Abort(_("there is nothing to merge, "
1579 raise util.Abort(_("there is nothing to merge, "
1580 "just use 'hg update'"))
1580 "just use 'hg update'"))
1581 if allow and not forcemerge:
1581 if allow and not forcemerge:
1582 if modified or added or removed:
1582 if modified or added or removed:
1583 raise util.Abort(_("outstanding uncommitted changes"))
1583 raise util.Abort(_("outstanding uncommitted changes"))
1584 if not forcemerge and not force:
1584 if not forcemerge and not force:
1585 for f in unknown:
1585 for f in unknown:
1586 if f in m2:
1586 if f in m2:
1587 t1 = self.wread(f)
1587 t1 = self.wread(f)
1588 t2 = self.file(f).read(m2[f])
1588 t2 = self.file(f).read(m2[f])
1589 if cmp(t1, t2) != 0:
1589 if cmp(t1, t2) != 0:
1590 raise util.Abort(_("'%s' already exists in the working"
1590 raise util.Abort(_("'%s' already exists in the working"
1591 " dir and differs from remote") % f)
1591 " dir and differs from remote") % f)
1592
1592
1593 # resolve the manifest to determine which files
1593 # resolve the manifest to determine which files
1594 # we care about merging
1594 # we care about merging
1595 self.ui.note(_("resolving manifests\n"))
1595 self.ui.note(_("resolving manifests\n"))
1596 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1596 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1597 (force, allow, moddirstate, linear_path))
1597 (force, allow, moddirstate, linear_path))
1598 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1598 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1599 (short(man), short(m1n), short(m2n)))
1599 (short(man), short(m1n), short(m2n)))
1600
1600
1601 merge = {}
1601 merge = {}
1602 get = {}
1602 get = {}
1603 remove = []
1603 remove = []
1604
1604
1605 # construct a working dir manifest
1605 # construct a working dir manifest
1606 mw = m1.copy()
1606 mw = m1.copy()
1607 mfw = mf1.copy()
1607 mfw = mf1.copy()
1608 umap = dict.fromkeys(unknown)
1608 umap = dict.fromkeys(unknown)
1609
1609
1610 for f in added + modified + unknown:
1610 for f in added + modified + unknown:
1611 mw[f] = ""
1611 mw[f] = ""
1612 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1612 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1613
1613
1614 if moddirstate and not wlock:
1614 if moddirstate and not wlock:
1615 wlock = self.wlock()
1615 wlock = self.wlock()
1616
1616
1617 for f in deleted + removed:
1617 for f in deleted + removed:
1618 if f in mw:
1618 if f in mw:
1619 del mw[f]
1619 del mw[f]
1620
1620
1621 # If we're jumping between revisions (as opposed to merging),
1621 # If we're jumping between revisions (as opposed to merging),
1622 # and if neither the working directory nor the target rev has
1622 # and if neither the working directory nor the target rev has
1623 # the file, then we need to remove it from the dirstate, to
1623 # the file, then we need to remove it from the dirstate, to
1624 # prevent the dirstate from listing the file when it is no
1624 # prevent the dirstate from listing the file when it is no
1625 # longer in the manifest.
1625 # longer in the manifest.
1626 if moddirstate and linear_path and f not in m2:
1626 if moddirstate and linear_path and f not in m2:
1627 self.dirstate.forget((f,))
1627 self.dirstate.forget((f,))
1628
1628
1629 # Compare manifests
1629 # Compare manifests
1630 for f, n in mw.iteritems():
1630 for f, n in mw.iteritems():
1631 if choose and not choose(f):
1631 if choose and not choose(f):
1632 continue
1632 continue
1633 if f in m2:
1633 if f in m2:
1634 s = 0
1634 s = 0
1635
1635
1636 # is the wfile new since m1, and match m2?
1636 # is the wfile new since m1, and match m2?
1637 if f not in m1:
1637 if f not in m1:
1638 t1 = self.wread(f)
1638 t1 = self.wread(f)
1639 t2 = self.file(f).read(m2[f])
1639 t2 = self.file(f).read(m2[f])
1640 if cmp(t1, t2) == 0:
1640 if cmp(t1, t2) == 0:
1641 n = m2[f]
1641 n = m2[f]
1642 del t1, t2
1642 del t1, t2
1643
1643
1644 # are files different?
1644 # are files different?
1645 if n != m2[f]:
1645 if n != m2[f]:
1646 a = ma.get(f, nullid)
1646 a = ma.get(f, nullid)
1647 # are both different from the ancestor?
1647 # are both different from the ancestor?
1648 if n != a and m2[f] != a:
1648 if n != a and m2[f] != a:
1649 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1649 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1650 # merge executable bits
1650 # merge executable bits
1651 # "if we changed or they changed, change in merge"
1651 # "if we changed or they changed, change in merge"
1652 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1652 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1653 mode = ((a^b) | (a^c)) ^ a
1653 mode = ((a^b) | (a^c)) ^ a
1654 merge[f] = (m1.get(f, nullid), m2[f], mode)
1654 merge[f] = (m1.get(f, nullid), m2[f], mode)
1655 s = 1
1655 s = 1
1656 # are we clobbering?
1656 # are we clobbering?
1657 # is remote's version newer?
1657 # is remote's version newer?
1658 # or are we going back in time?
1658 # or are we going back in time?
1659 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1659 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1660 self.ui.debug(_(" remote %s is newer, get\n") % f)
1660 self.ui.debug(_(" remote %s is newer, get\n") % f)
1661 get[f] = m2[f]
1661 get[f] = m2[f]
1662 s = 1
1662 s = 1
1663 elif f in umap or f in added:
1663 elif f in umap or f in added:
1664 # this unknown file is the same as the checkout
1664 # this unknown file is the same as the checkout
1665 # we need to reset the dirstate if the file was added
1665 # we need to reset the dirstate if the file was added
1666 get[f] = m2[f]
1666 get[f] = m2[f]
1667
1667
1668 if not s and mfw[f] != mf2[f]:
1668 if not s and mfw[f] != mf2[f]:
1669 if force:
1669 if force:
1670 self.ui.debug(_(" updating permissions for %s\n") % f)
1670 self.ui.debug(_(" updating permissions for %s\n") % f)
1671 util.set_exec(self.wjoin(f), mf2[f])
1671 util.set_exec(self.wjoin(f), mf2[f])
1672 else:
1672 else:
1673 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1673 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1674 mode = ((a^b) | (a^c)) ^ a
1674 mode = ((a^b) | (a^c)) ^ a
1675 if mode != b:
1675 if mode != b:
1676 self.ui.debug(_(" updating permissions for %s\n")
1676 self.ui.debug(_(" updating permissions for %s\n")
1677 % f)
1677 % f)
1678 util.set_exec(self.wjoin(f), mode)
1678 util.set_exec(self.wjoin(f), mode)
1679 del m2[f]
1679 del m2[f]
1680 elif f in ma:
1680 elif f in ma:
1681 if n != ma[f]:
1681 if n != ma[f]:
1682 r = _("d")
1682 r = _("d")
1683 if not force and (linear_path or allow):
1683 if not force and (linear_path or allow):
1684 r = self.ui.prompt(
1684 r = self.ui.prompt(
1685 (_(" local changed %s which remote deleted\n") % f) +
1685 (_(" local changed %s which remote deleted\n") % f) +
1686 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1686 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1687 if r == _("d"):
1687 if r == _("d"):
1688 remove.append(f)
1688 remove.append(f)
1689 else:
1689 else:
1690 self.ui.debug(_("other deleted %s\n") % f)
1690 self.ui.debug(_("other deleted %s\n") % f)
1691 remove.append(f) # other deleted it
1691 remove.append(f) # other deleted it
1692 else:
1692 else:
1693 # file is created on branch or in working directory
1693 # file is created on branch or in working directory
1694 if force and f not in umap:
1694 if force and f not in umap:
1695 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1695 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1696 remove.append(f)
1696 remove.append(f)
1697 elif n == m1.get(f, nullid): # same as parent
1697 elif n == m1.get(f, nullid): # same as parent
1698 if p2 == pa: # going backwards?
1698 if p2 == pa: # going backwards?
1699 self.ui.debug(_("remote deleted %s\n") % f)
1699 self.ui.debug(_("remote deleted %s\n") % f)
1700 remove.append(f)
1700 remove.append(f)
1701 else:
1701 else:
1702 self.ui.debug(_("local modified %s, keeping\n") % f)
1702 self.ui.debug(_("local modified %s, keeping\n") % f)
1703 else:
1703 else:
1704 self.ui.debug(_("working dir created %s, keeping\n") % f)
1704 self.ui.debug(_("working dir created %s, keeping\n") % f)
1705
1705
1706 for f, n in m2.iteritems():
1706 for f, n in m2.iteritems():
1707 if choose and not choose(f):
1707 if choose and not choose(f):
1708 continue
1708 continue
1709 if f[0] == "/":
1709 if f[0] == "/":
1710 continue
1710 continue
1711 if f in ma and n != ma[f]:
1711 if f in ma and n != ma[f]:
1712 r = _("k")
1712 r = _("k")
1713 if not force and (linear_path or allow):
1713 if not force and (linear_path or allow):
1714 r = self.ui.prompt(
1714 r = self.ui.prompt(
1715 (_("remote changed %s which local deleted\n") % f) +
1715 (_("remote changed %s which local deleted\n") % f) +
1716 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1716 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1717 if r == _("k"):
1717 if r == _("k"):
1718 get[f] = n
1718 get[f] = n
1719 elif f not in ma:
1719 elif f not in ma:
1720 self.ui.debug(_("remote created %s\n") % f)
1720 self.ui.debug(_("remote created %s\n") % f)
1721 get[f] = n
1721 get[f] = n
1722 else:
1722 else:
1723 if force or p2 == pa: # going backwards?
1723 if force or p2 == pa: # going backwards?
1724 self.ui.debug(_("local deleted %s, recreating\n") % f)
1724 self.ui.debug(_("local deleted %s, recreating\n") % f)
1725 get[f] = n
1725 get[f] = n
1726 else:
1726 else:
1727 self.ui.debug(_("local deleted %s\n") % f)
1727 self.ui.debug(_("local deleted %s\n") % f)
1728
1728
1729 del mw, m1, m2, ma
1729 del mw, m1, m2, ma
1730
1730
1731 if force:
1731 if force:
1732 for f in merge:
1732 for f in merge:
1733 get[f] = merge[f][1]
1733 get[f] = merge[f][1]
1734 merge = {}
1734 merge = {}
1735
1735
1736 if linear_path or force:
1736 if linear_path or force:
1737 # we don't need to do any magic, just jump to the new rev
1737 # we don't need to do any magic, just jump to the new rev
1738 branch_merge = False
1738 branch_merge = False
1739 p1, p2 = p2, nullid
1739 p1, p2 = p2, nullid
1740 else:
1740 else:
1741 if not allow:
1741 if not allow:
1742 self.ui.status(_("this update spans a branch"
1742 self.ui.status(_("this update spans a branch"
1743 " affecting the following files:\n"))
1743 " affecting the following files:\n"))
1744 fl = merge.keys() + get.keys()
1744 fl = merge.keys() + get.keys()
1745 fl.sort()
1745 fl.sort()
1746 for f in fl:
1746 for f in fl:
1747 cf = ""
1747 cf = ""
1748 if f in merge:
1748 if f in merge:
1749 cf = _(" (resolve)")
1749 cf = _(" (resolve)")
1750 self.ui.status(" %s%s\n" % (f, cf))
1750 self.ui.status(" %s%s\n" % (f, cf))
1751 self.ui.warn(_("aborting update spanning branches!\n"))
1751 self.ui.warn(_("aborting update spanning branches!\n"))
1752 self.ui.status(_("(use 'hg merge' to merge across branches"
1752 self.ui.status(_("(use 'hg merge' to merge across branches"
1753 " or 'hg update -C' to lose changes)\n"))
1753 " or 'hg update -C' to lose changes)\n"))
1754 return 1
1754 return 1
1755 branch_merge = True
1755 branch_merge = True
1756
1756
1757 # get the files we don't need to change
1757 # get the files we don't need to change
1758 files = get.keys()
1758 files = get.keys()
1759 files.sort()
1759 files.sort()
1760 for f in files:
1760 for f in files:
1761 if f[0] == "/":
1761 if f[0] == "/":
1762 continue
1762 continue
1763 self.ui.note(_("getting %s\n") % f)
1763 self.ui.note(_("getting %s\n") % f)
1764 t = self.file(f).read(get[f])
1764 t = self.file(f).read(get[f])
1765 self.wwrite(f, t)
1765 self.wwrite(f, t)
1766 util.set_exec(self.wjoin(f), mf2[f])
1766 util.set_exec(self.wjoin(f), mf2[f])
1767 if moddirstate:
1767 if moddirstate:
1768 if branch_merge:
1768 if branch_merge:
1769 self.dirstate.update([f], 'n', st_mtime=-1)
1769 self.dirstate.update([f], 'n', st_mtime=-1)
1770 else:
1770 else:
1771 self.dirstate.update([f], 'n')
1771 self.dirstate.update([f], 'n')
1772
1772
1773 # merge the tricky bits
1773 # merge the tricky bits
1774 failedmerge = []
1774 failedmerge = []
1775 files = merge.keys()
1775 files = merge.keys()
1776 files.sort()
1776 files.sort()
1777 xp1 = hex(p1)
1777 xp1 = hex(p1)
1778 xp2 = hex(p2)
1778 xp2 = hex(p2)
1779 for f in files:
1779 for f in files:
1780 self.ui.status(_("merging %s\n") % f)
1780 self.ui.status(_("merging %s\n") % f)
1781 my, other, flag = merge[f]
1781 my, other, flag = merge[f]
1782 ret = self.merge3(f, my, other, xp1, xp2)
1782 ret = self.merge3(f, my, other, xp1, xp2)
1783 if ret:
1783 if ret:
1784 err = True
1784 err = True
1785 failedmerge.append(f)
1785 failedmerge.append(f)
1786 util.set_exec(self.wjoin(f), flag)
1786 util.set_exec(self.wjoin(f), flag)
1787 if moddirstate:
1787 if moddirstate:
1788 if branch_merge:
1788 if branch_merge:
1789 # We've done a branch merge, mark this file as merged
1789 # We've done a branch merge, mark this file as merged
1790 # so that we properly record the merger later
1790 # so that we properly record the merger later
1791 self.dirstate.update([f], 'm')
1791 self.dirstate.update([f], 'm')
1792 else:
1792 else:
1793 # We've update-merged a locally modified file, so
1793 # We've update-merged a locally modified file, so
1794 # we set the dirstate to emulate a normal checkout
1794 # we set the dirstate to emulate a normal checkout
1795 # of that file some time in the past. Thus our
1795 # of that file some time in the past. Thus our
1796 # merge will appear as a normal local file
1796 # merge will appear as a normal local file
1797 # modification.
1797 # modification.
1798 f_len = len(self.file(f).read(other))
1798 f_len = len(self.file(f).read(other))
1799 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1799 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1800
1800
1801 remove.sort()
1801 remove.sort()
1802 for f in remove:
1802 for f in remove:
1803 self.ui.note(_("removing %s\n") % f)
1803 self.ui.note(_("removing %s\n") % f)
1804 util.audit_path(f)
1804 util.audit_path(f)
1805 try:
1805 try:
1806 util.unlink(self.wjoin(f))
1806 util.unlink(self.wjoin(f))
1807 except OSError, inst:
1807 except OSError, inst:
1808 if inst.errno != errno.ENOENT:
1808 if inst.errno != errno.ENOENT:
1809 self.ui.warn(_("update failed to remove %s: %s!\n") %
1809 self.ui.warn(_("update failed to remove %s: %s!\n") %
1810 (f, inst.strerror))
1810 (f, inst.strerror))
1811 if moddirstate:
1811 if moddirstate:
1812 if branch_merge:
1812 if branch_merge:
1813 self.dirstate.update(remove, 'r')
1813 self.dirstate.update(remove, 'r')
1814 else:
1814 else:
1815 self.dirstate.forget(remove)
1815 self.dirstate.forget(remove)
1816
1816
1817 if moddirstate:
1817 if moddirstate:
1818 self.dirstate.setparents(p1, p2)
1818 self.dirstate.setparents(p1, p2)
1819
1819
1820 if show_stats:
1820 if show_stats:
1821 stats = ((len(get), _("updated")),
1821 stats = ((len(get), _("updated")),
1822 (len(merge) - len(failedmerge), _("merged")),
1822 (len(merge) - len(failedmerge), _("merged")),
1823 (len(remove), _("removed")),
1823 (len(remove), _("removed")),
1824 (len(failedmerge), _("unresolved")))
1824 (len(failedmerge), _("unresolved")))
1825 note = ", ".join([_("%d files %s") % s for s in stats])
1825 note = ", ".join([_("%d files %s") % s for s in stats])
1826 self.ui.status("%s\n" % note)
1826 self.ui.status("%s\n" % note)
1827 if moddirstate:
1827 if moddirstate:
1828 if branch_merge:
1828 if branch_merge:
1829 if failedmerge:
1829 if failedmerge:
1830 self.ui.status(_("There are unresolved merges,"
1830 self.ui.status(_("There are unresolved merges,"
1831 " you can redo the full merge using:\n"
1831 " you can redo the full merge using:\n"
1832 " hg update -C %s\n"
1832 " hg update -C %s\n"
1833 " hg merge %s\n"
1833 " hg merge %s\n"
1834 % (self.changelog.rev(p1),
1834 % (self.changelog.rev(p1),
1835 self.changelog.rev(p2))))
1835 self.changelog.rev(p2))))
1836 else:
1836 else:
1837 self.ui.status(_("(branch merge, don't forget to commit)\n"))
1837 self.ui.status(_("(branch merge, don't forget to commit)\n"))
1838 elif failedmerge:
1838 elif failedmerge:
1839 self.ui.status(_("There are unresolved merges with"
1839 self.ui.status(_("There are unresolved merges with"
1840 " locally modified files.\n"))
1840 " locally modified files.\n"))
1841
1841
1842 return err
1842 return err
1843
1843
1844 def merge3(self, fn, my, other, p1, p2):
1844 def merge3(self, fn, my, other, p1, p2):
1845 """perform a 3-way merge in the working directory"""
1845 """perform a 3-way merge in the working directory"""
1846
1846
1847 def temp(prefix, node):
1847 def temp(prefix, node):
1848 pre = "%s~%s." % (os.path.basename(fn), prefix)
1848 pre = "%s~%s." % (os.path.basename(fn), prefix)
1849 (fd, name) = tempfile.mkstemp(prefix=pre)
1849 (fd, name) = tempfile.mkstemp(prefix=pre)
1850 f = os.fdopen(fd, "wb")
1850 f = os.fdopen(fd, "wb")
1851 self.wwrite(fn, fl.read(node), f)
1851 self.wwrite(fn, fl.read(node), f)
1852 f.close()
1852 f.close()
1853 return name
1853 return name
1854
1854
1855 fl = self.file(fn)
1855 fl = self.file(fn)
1856 base = fl.ancestor(my, other)
1856 base = fl.ancestor(my, other)
1857 a = self.wjoin(fn)
1857 a = self.wjoin(fn)
1858 b = temp("base", base)
1858 b = temp("base", base)
1859 c = temp("other", other)
1859 c = temp("other", other)
1860
1860
1861 self.ui.note(_("resolving %s\n") % fn)
1861 self.ui.note(_("resolving %s\n") % fn)
1862 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1862 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1863 (fn, short(my), short(other), short(base)))
1863 (fn, short(my), short(other), short(base)))
1864
1864
1865 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1865 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1866 or "hgmerge")
1866 or "hgmerge")
1867 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1867 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1868 environ={'HG_FILE': fn,
1868 environ={'HG_FILE': fn,
1869 'HG_MY_NODE': p1,
1869 'HG_MY_NODE': p1,
1870 'HG_OTHER_NODE': p2,
1870 'HG_OTHER_NODE': p2,
1871 'HG_FILE_MY_NODE': hex(my),
1871 'HG_FILE_MY_NODE': hex(my),
1872 'HG_FILE_OTHER_NODE': hex(other),
1872 'HG_FILE_OTHER_NODE': hex(other),
1873 'HG_FILE_BASE_NODE': hex(base)})
1873 'HG_FILE_BASE_NODE': hex(base)})
1874 if r:
1874 if r:
1875 self.ui.warn(_("merging %s failed!\n") % fn)
1875 self.ui.warn(_("merging %s failed!\n") % fn)
1876
1876
1877 os.unlink(b)
1877 os.unlink(b)
1878 os.unlink(c)
1878 os.unlink(c)
1879 return r
1879 return r
1880
1880
1881 def verify(self):
1881 def verify(self):
1882 filelinkrevs = {}
1882 filelinkrevs = {}
1883 filenodes = {}
1883 filenodes = {}
1884 changesets = revisions = files = 0
1884 changesets = revisions = files = 0
1885 errors = [0]
1885 errors = [0]
1886 warnings = [0]
1886 warnings = [0]
1887 neededmanifests = {}
1887 neededmanifests = {}
1888
1888
1889 def err(msg):
1889 def err(msg):
1890 self.ui.warn(msg + "\n")
1890 self.ui.warn(msg + "\n")
1891 errors[0] += 1
1891 errors[0] += 1
1892
1892
1893 def warn(msg):
1893 def warn(msg):
1894 self.ui.warn(msg + "\n")
1894 self.ui.warn(msg + "\n")
1895 warnings[0] += 1
1895 warnings[0] += 1
1896
1896
1897 def checksize(obj, name):
1897 def checksize(obj, name):
1898 d = obj.checksize()
1898 d = obj.checksize()
1899 if d[0]:
1899 if d[0]:
1900 err(_("%s data length off by %d bytes") % (name, d[0]))
1900 err(_("%s data length off by %d bytes") % (name, d[0]))
1901 if d[1]:
1901 if d[1]:
1902 err(_("%s index contains %d extra bytes") % (name, d[1]))
1902 err(_("%s index contains %d extra bytes") % (name, d[1]))
1903
1903
1904 def checkversion(obj, name):
1904 def checkversion(obj, name):
1905 if obj.version != revlog.REVLOGV0:
1905 if obj.version != revlog.REVLOGV0:
1906 if not revlogv1:
1906 if not revlogv1:
1907 warn(_("warning: `%s' uses revlog format 1") % name)
1907 warn(_("warning: `%s' uses revlog format 1") % name)
1908 elif revlogv1:
1908 elif revlogv1:
1909 warn(_("warning: `%s' uses revlog format 0") % name)
1909 warn(_("warning: `%s' uses revlog format 0") % name)
1910
1910
1911 revlogv1 = self.revlogversion != revlog.REVLOGV0
1911 revlogv1 = self.revlogversion != revlog.REVLOGV0
1912 if self.ui.verbose or revlogv1 != self.revlogv1:
1912 if self.ui.verbose or revlogv1 != self.revlogv1:
1913 self.ui.status(_("repository uses revlog format %d\n") %
1913 self.ui.status(_("repository uses revlog format %d\n") %
1914 (revlogv1 and 1 or 0))
1914 (revlogv1 and 1 or 0))
1915
1915
1916 seen = {}
1916 seen = {}
1917 self.ui.status(_("checking changesets\n"))
1917 self.ui.status(_("checking changesets\n"))
1918 checksize(self.changelog, "changelog")
1918 checksize(self.changelog, "changelog")
1919
1919
1920 for i in range(self.changelog.count()):
1920 for i in range(self.changelog.count()):
1921 changesets += 1
1921 changesets += 1
1922 n = self.changelog.node(i)
1922 n = self.changelog.node(i)
1923 l = self.changelog.linkrev(n)
1923 l = self.changelog.linkrev(n)
1924 if l != i:
1924 if l != i:
1925 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1925 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1926 if n in seen:
1926 if n in seen:
1927 err(_("duplicate changeset at revision %d") % i)
1927 err(_("duplicate changeset at revision %d") % i)
1928 seen[n] = 1
1928 seen[n] = 1
1929
1929
1930 for p in self.changelog.parents(n):
1930 for p in self.changelog.parents(n):
1931 if p not in self.changelog.nodemap:
1931 if p not in self.changelog.nodemap:
1932 err(_("changeset %s has unknown parent %s") %
1932 err(_("changeset %s has unknown parent %s") %
1933 (short(n), short(p)))
1933 (short(n), short(p)))
1934 try:
1934 try:
1935 changes = self.changelog.read(n)
1935 changes = self.changelog.read(n)
1936 except KeyboardInterrupt:
1936 except KeyboardInterrupt:
1937 self.ui.warn(_("interrupted"))
1937 self.ui.warn(_("interrupted"))
1938 raise
1938 raise
1939 except Exception, inst:
1939 except Exception, inst:
1940 err(_("unpacking changeset %s: %s") % (short(n), inst))
1940 err(_("unpacking changeset %s: %s") % (short(n), inst))
1941 continue
1941 continue
1942
1942
1943 neededmanifests[changes[0]] = n
1943 neededmanifests[changes[0]] = n
1944
1944
1945 for f in changes[3]:
1945 for f in changes[3]:
1946 filelinkrevs.setdefault(f, []).append(i)
1946 filelinkrevs.setdefault(f, []).append(i)
1947
1947
1948 seen = {}
1948 seen = {}
1949 self.ui.status(_("checking manifests\n"))
1949 self.ui.status(_("checking manifests\n"))
1950 checkversion(self.manifest, "manifest")
1950 checkversion(self.manifest, "manifest")
1951 checksize(self.manifest, "manifest")
1951 checksize(self.manifest, "manifest")
1952
1952
1953 for i in range(self.manifest.count()):
1953 for i in range(self.manifest.count()):
1954 n = self.manifest.node(i)
1954 n = self.manifest.node(i)
1955 l = self.manifest.linkrev(n)
1955 l = self.manifest.linkrev(n)
1956
1956
1957 if l < 0 or l >= self.changelog.count():
1957 if l < 0 or l >= self.changelog.count():
1958 err(_("bad manifest link (%d) at revision %d") % (l, i))
1958 err(_("bad manifest link (%d) at revision %d") % (l, i))
1959
1959
1960 if n in neededmanifests:
1960 if n in neededmanifests:
1961 del neededmanifests[n]
1961 del neededmanifests[n]
1962
1962
1963 if n in seen:
1963 if n in seen:
1964 err(_("duplicate manifest at revision %d") % i)
1964 err(_("duplicate manifest at revision %d") % i)
1965
1965
1966 seen[n] = 1
1966 seen[n] = 1
1967
1967
1968 for p in self.manifest.parents(n):
1968 for p in self.manifest.parents(n):
1969 if p not in self.manifest.nodemap:
1969 if p not in self.manifest.nodemap:
1970 err(_("manifest %s has unknown parent %s") %
1970 err(_("manifest %s has unknown parent %s") %
1971 (short(n), short(p)))
1971 (short(n), short(p)))
1972
1972
1973 try:
1973 try:
1974 delta = mdiff.patchtext(self.manifest.delta(n))
1974 delta = mdiff.patchtext(self.manifest.delta(n))
1975 except KeyboardInterrupt:
1975 except KeyboardInterrupt:
1976 self.ui.warn(_("interrupted"))
1976 self.ui.warn(_("interrupted"))
1977 raise
1977 raise
1978 except Exception, inst:
1978 except Exception, inst:
1979 err(_("unpacking manifest %s: %s") % (short(n), inst))
1979 err(_("unpacking manifest %s: %s") % (short(n), inst))
1980 continue
1980 continue
1981
1981
1982 try:
1982 try:
1983 ff = [ l.split('\0') for l in delta.splitlines() ]
1983 ff = [ l.split('\0') for l in delta.splitlines() ]
1984 for f, fn in ff:
1984 for f, fn in ff:
1985 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1985 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1986 except (ValueError, TypeError), inst:
1986 except (ValueError, TypeError), inst:
1987 err(_("broken delta in manifest %s: %s") % (short(n), inst))
1987 err(_("broken delta in manifest %s: %s") % (short(n), inst))
1988
1988
1989 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1989 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1990
1990
1991 for m, c in neededmanifests.items():
1991 for m, c in neededmanifests.items():
1992 err(_("Changeset %s refers to unknown manifest %s") %
1992 err(_("Changeset %s refers to unknown manifest %s") %
1993 (short(m), short(c)))
1993 (short(m), short(c)))
1994 del neededmanifests
1994 del neededmanifests
1995
1995
1996 for f in filenodes:
1996 for f in filenodes:
1997 if f not in filelinkrevs:
1997 if f not in filelinkrevs:
1998 err(_("file %s in manifest but not in changesets") % f)
1998 err(_("file %s in manifest but not in changesets") % f)
1999
1999
2000 for f in filelinkrevs:
2000 for f in filelinkrevs:
2001 if f not in filenodes:
2001 if f not in filenodes:
2002 err(_("file %s in changeset but not in manifest") % f)
2002 err(_("file %s in changeset but not in manifest") % f)
2003
2003
2004 self.ui.status(_("checking files\n"))
2004 self.ui.status(_("checking files\n"))
2005 ff = filenodes.keys()
2005 ff = filenodes.keys()
2006 ff.sort()
2006 ff.sort()
2007 for f in ff:
2007 for f in ff:
2008 if f == "/dev/null":
2008 if f == "/dev/null":
2009 continue
2009 continue
2010 files += 1
2010 files += 1
2011 if not f:
2011 if not f:
2012 err(_("file without name in manifest %s") % short(n))
2012 err(_("file without name in manifest %s") % short(n))
2013 continue
2013 continue
2014 fl = self.file(f)
2014 fl = self.file(f)
2015 checkversion(fl, f)
2015 checkversion(fl, f)
2016 checksize(fl, f)
2016 checksize(fl, f)
2017
2017
2018 nodes = {nullid: 1}
2018 nodes = {nullid: 1}
2019 seen = {}
2019 seen = {}
2020 for i in range(fl.count()):
2020 for i in range(fl.count()):
2021 revisions += 1
2021 revisions += 1
2022 n = fl.node(i)
2022 n = fl.node(i)
2023
2023
2024 if n in seen:
2024 if n in seen:
2025 err(_("%s: duplicate revision %d") % (f, i))
2025 err(_("%s: duplicate revision %d") % (f, i))
2026 if n not in filenodes[f]:
2026 if n not in filenodes[f]:
2027 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
2027 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
2028 else:
2028 else:
2029 del filenodes[f][n]
2029 del filenodes[f][n]
2030
2030
2031 flr = fl.linkrev(n)
2031 flr = fl.linkrev(n)
2032 if flr not in filelinkrevs.get(f, []):
2032 if flr not in filelinkrevs.get(f, []):
2033 err(_("%s:%s points to unexpected changeset %d")
2033 err(_("%s:%s points to unexpected changeset %d")
2034 % (f, short(n), flr))
2034 % (f, short(n), flr))
2035 else:
2035 else:
2036 filelinkrevs[f].remove(flr)
2036 filelinkrevs[f].remove(flr)
2037
2037
2038 # verify contents
2038 # verify contents
2039 try:
2039 try:
2040 t = fl.read(n)
2040 t = fl.read(n)
2041 except KeyboardInterrupt:
2041 except KeyboardInterrupt:
2042 self.ui.warn(_("interrupted"))
2042 self.ui.warn(_("interrupted"))
2043 raise
2043 raise
2044 except Exception, inst:
2044 except Exception, inst:
2045 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
2045 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
2046
2046
2047 # verify parents
2047 # verify parents
2048 (p1, p2) = fl.parents(n)
2048 (p1, p2) = fl.parents(n)
2049 if p1 not in nodes:
2049 if p1 not in nodes:
2050 err(_("file %s:%s unknown parent 1 %s") %
2050 err(_("file %s:%s unknown parent 1 %s") %
2051 (f, short(n), short(p1)))
2051 (f, short(n), short(p1)))
2052 if p2 not in nodes:
2052 if p2 not in nodes:
2053 err(_("file %s:%s unknown parent 2 %s") %
2053 err(_("file %s:%s unknown parent 2 %s") %
2054 (f, short(n), short(p1)))
2054 (f, short(n), short(p1)))
2055 nodes[n] = 1
2055 nodes[n] = 1
2056
2056
2057 # cross-check
2057 # cross-check
2058 for node in filenodes[f]:
2058 for node in filenodes[f]:
2059 err(_("node %s in manifests not in %s") % (hex(node), f))
2059 err(_("node %s in manifests not in %s") % (hex(node), f))
2060
2060
2061 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
2061 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
2062 (files, changesets, revisions))
2062 (files, changesets, revisions))
2063
2063
2064 if warnings[0]:
2064 if warnings[0]:
2065 self.ui.warn(_("%d warnings encountered!\n") % warnings[0])
2065 self.ui.warn(_("%d warnings encountered!\n") % warnings[0])
2066 if errors[0]:
2066 if errors[0]:
2067 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
2067 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
2068 return 1
2068 return 1
2069
2069
2070 # used to avoid circular references so destructors work
2070 # used to avoid circular references so destructors work
2071 def aftertrans(base):
2071 def aftertrans(base):
2072 p = base
2072 p = base
2073 def a():
2073 def a():
2074 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
2074 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
2075 util.rename(os.path.join(p, "journal.dirstate"),
2075 util.rename(os.path.join(p, "journal.dirstate"),
2076 os.path.join(p, "undo.dirstate"))
2076 os.path.join(p, "undo.dirstate"))
2077 return a
2077 return a
2078
2078
@@ -1,155 +1,155 b''
1 # sshrepo.py - ssh repository proxy class for mercurial
1 # sshrepo.py - ssh repository proxy class for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from remoterepo import *
9 from remoterepo import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 from demandload import *
11 from demandload import *
12 demandload(globals(), "hg os re stat util")
12 demandload(globals(), "hg os re stat util")
13
13
14 class sshrepository(remoterepository):
14 class sshrepository(remoterepository):
15 def __init__(self, ui, path):
15 def __init__(self, ui, path):
16 self.url = path
16 self.url = path
17 self.ui = ui
17 self.ui = ui
18
18
19 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
19 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
20 if not m:
20 if not m:
21 raise hg.RepoError(_("couldn't parse destination %s") % path)
21 raise hg.RepoError(_("couldn't parse destination %s") % path)
22
22
23 self.user = m.group(2)
23 self.user = m.group(2)
24 self.host = m.group(3)
24 self.host = m.group(3)
25 self.port = m.group(5)
25 self.port = m.group(5)
26 self.path = m.group(7) or "."
26 self.path = m.group(7) or "."
27
27
28 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
28 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
29 args = self.port and ("%s -p %s") % (args, self.port) or args
29 args = self.port and ("%s -p %s") % (args, self.port) or args
30
30
31 sshcmd = self.ui.config("ui", "ssh", "ssh")
31 sshcmd = self.ui.config("ui", "ssh", "ssh")
32 remotecmd = self.ui.config("ui", "remotecmd", "hg")
32 remotecmd = self.ui.config("ui", "remotecmd", "hg")
33 cmd = '%s %s "%s -R %s serve --stdio"'
33 cmd = '%s %s "%s -R %s serve --stdio"'
34 cmd = cmd % (sshcmd, args, remotecmd, self.path)
34 cmd = cmd % (sshcmd, args, remotecmd, self.path)
35
35
36 ui.note('running %s\n' % cmd)
36 ui.note('running %s\n' % cmd)
37 self.pipeo, self.pipei, self.pipee = os.popen3(cmd, 'b')
37 self.pipeo, self.pipei, self.pipee = os.popen3(cmd, 'b')
38
38
39 # skip any noise generated by remote shell
39 # skip any noise generated by remote shell
40 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
40 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
41 l1 = ""
41 l1 = ""
42 l2 = "dummy"
42 l2 = "dummy"
43 max_noise = 500
43 max_noise = 500
44 while l2 and max_noise:
44 while l2 and max_noise:
45 l2 = r.readline()
45 l2 = r.readline()
46 self.readerr()
46 self.readerr()
47 if l1 == "1\n" and l2 == "\n":
47 if l1 == "1\n" and l2 == "\n":
48 break
48 break
49 if l1:
49 if l1:
50 ui.debug(_("remote: "), l1)
50 ui.debug(_("remote: "), l1)
51 l1 = l2
51 l1 = l2
52 max_noise -= 1
52 max_noise -= 1
53 else:
53 else:
54 if l1:
54 if l1:
55 ui.debug(_("remote: "), l1)
55 ui.debug(_("remote: "), l1)
56 raise hg.RepoError(_("no response from remote hg"))
56 raise hg.RepoError(_("no response from remote hg"))
57
57
58 def readerr(self):
58 def readerr(self):
59 while 1:
59 while 1:
60 size = util.fstat(self.pipee).st_size
60 size = util.fstat(self.pipee).st_size
61 if size == 0: break
61 if size == 0: break
62 l = self.pipee.readline()
62 l = self.pipee.readline()
63 if not l: break
63 if not l: break
64 self.ui.status(_("remote: "), l)
64 self.ui.status(_("remote: "), l)
65
65
66 def __del__(self):
66 def __del__(self):
67 try:
67 try:
68 self.pipeo.close()
68 self.pipeo.close()
69 self.pipei.close()
69 self.pipei.close()
70 # read the error descriptor until EOF
70 # read the error descriptor until EOF
71 for l in self.pipee:
71 for l in self.pipee:
72 self.ui.status(_("remote: "), l)
72 self.ui.status(_("remote: "), l)
73 self.pipee.close()
73 self.pipee.close()
74 except:
74 except:
75 pass
75 pass
76
76
77 def dev(self):
77 def dev(self):
78 return -1
78 return -1
79
79
80 def do_cmd(self, cmd, **args):
80 def do_cmd(self, cmd, **args):
81 self.ui.debug(_("sending %s command\n") % cmd)
81 self.ui.debug(_("sending %s command\n") % cmd)
82 self.pipeo.write("%s\n" % cmd)
82 self.pipeo.write("%s\n" % cmd)
83 for k, v in args.items():
83 for k, v in args.items():
84 self.pipeo.write("%s %d\n" % (k, len(v)))
84 self.pipeo.write("%s %d\n" % (k, len(v)))
85 self.pipeo.write(v)
85 self.pipeo.write(v)
86 self.pipeo.flush()
86 self.pipeo.flush()
87
87
88 return self.pipei
88 return self.pipei
89
89
90 def call(self, cmd, **args):
90 def call(self, cmd, **args):
91 r = self.do_cmd(cmd, **args)
91 r = self.do_cmd(cmd, **args)
92 l = r.readline()
92 l = r.readline()
93 self.readerr()
93 self.readerr()
94 try:
94 try:
95 l = int(l)
95 l = int(l)
96 except:
96 except:
97 raise hg.RepoError(_("unexpected response '%s'") % l)
97 raise hg.RepoError(_("unexpected response '%s'") % l)
98 return r.read(l)
98 return r.read(l)
99
99
100 def lock(self):
100 def lock(self):
101 self.call("lock")
101 self.call("lock")
102 return remotelock(self)
102 return remotelock(self)
103
103
104 def unlock(self):
104 def unlock(self):
105 self.call("unlock")
105 self.call("unlock")
106
106
107 def heads(self):
107 def heads(self):
108 d = self.call("heads")
108 d = self.call("heads")
109 try:
109 try:
110 return map(bin, d[:-1].split(" "))
110 return map(bin, d[:-1].split(" "))
111 except:
111 except:
112 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
112 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
113
113
114 def branches(self, nodes):
114 def branches(self, nodes):
115 n = " ".join(map(hex, nodes))
115 n = " ".join(map(hex, nodes))
116 d = self.call("branches", nodes=n)
116 d = self.call("branches", nodes=n)
117 try:
117 try:
118 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
118 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
119 return br
119 return br
120 except:
120 except:
121 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
121 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
122
122
123 def between(self, pairs):
123 def between(self, pairs):
124 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
124 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
125 d = self.call("between", pairs=n)
125 d = self.call("between", pairs=n)
126 try:
126 try:
127 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
127 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
128 return p
128 return p
129 except:
129 except:
130 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
130 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
131
131
132 def changegroup(self, nodes, kind):
132 def changegroup(self, nodes, kind):
133 n = " ".join(map(hex, nodes))
133 n = " ".join(map(hex, nodes))
134 f = self.do_cmd("changegroup", roots=n)
134 f = self.do_cmd("changegroup", roots=n)
135 return self.pipei
135 return self.pipei
136
136
137 def addchangegroup(self, cg):
137 def addchangegroup(self, cg, source):
138 d = self.call("addchangegroup")
138 d = self.call("addchangegroup")
139 if d:
139 if d:
140 raise hg.RepoError(_("push refused: %s"), d)
140 raise hg.RepoError(_("push refused: %s"), d)
141
141
142 while 1:
142 while 1:
143 d = cg.read(4096)
143 d = cg.read(4096)
144 if not d: break
144 if not d: break
145 self.pipeo.write(d)
145 self.pipeo.write(d)
146 self.readerr()
146 self.readerr()
147
147
148 self.pipeo.flush()
148 self.pipeo.flush()
149
149
150 self.readerr()
150 self.readerr()
151 l = int(self.pipei.readline())
151 l = int(self.pipei.readline())
152 r = self.pipei.read(l)
152 r = self.pipei.read(l)
153 if not r:
153 if not r:
154 return 1
154 return 1
155 return int(r)
155 return int(r)
General Comments 0
You need to be logged in to leave comments. Login now