##// 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 1 # notify.py - email notifications for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # hook extension to email notifications to people when changesets are
9 9 # committed to a repo they subscribe to.
10 10 #
11 11 # default mode is to print messages to stdout, for testing and
12 12 # configuring.
13 13 #
14 14 # to use, configure notify extension and enable in hgrc like this:
15 15 #
16 16 # [extensions]
17 17 # hgext.notify =
18 18 #
19 19 # [hooks]
20 20 # # one email for each incoming changeset
21 21 # incoming.notify = python:hgext.notify.hook
22 22 # # batch emails when many changesets incoming at one time
23 23 # changegroup.notify = python:hgext.notify.hook
24 24 #
25 25 # [notify]
26 26 # # config items go in here
27 27 #
28 28 # config items:
29 29 #
30 30 # REQUIRED:
31 31 # config = /path/to/file # file containing subscriptions
32 32 #
33 33 # OPTIONAL:
34 34 # test = True # print messages to stdout for testing
35 35 # strip = 3 # number of slashes to strip for url paths
36 36 # domain = example.com # domain to use if committer missing domain
37 37 # style = ... # style file to use when formatting email
38 38 # template = ... # template to use when formatting email
39 39 # incoming = ... # template to use when run as incoming hook
40 40 # changegroup = ... # template when run as changegroup hook
41 41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 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 45 # [email]
44 46 # from = user@host.com # email address to send as if none given
45 47 # [web]
46 48 # baseurl = http://hgserver/... # root of hg web site for browsing commits
47 49 #
48 50 # notify config file has same format as regular hgrc. it has two
49 51 # sections so you can express subscriptions in whatever way is handier
50 52 # for you.
51 53 #
52 54 # [usersubs]
53 55 # # key is subscriber email, value is ","-separated list of glob patterns
54 56 # user@host = pattern
55 57 #
56 58 # [reposubs]
57 59 # # key is glob pattern, value is ","-separated list of subscriber emails
58 60 # pattern = user@host
59 61 #
60 62 # glob patterns are matched against path to repo root.
61 63 #
62 64 # if you like, you can put notify config file in repo that users can
63 65 # push changes to, they can manage their own subscriptions.
64 66
65 67 from mercurial.demandload import *
66 68 from mercurial.i18n import gettext as _
67 69 from mercurial.node import *
68 70 demandload(globals(), 'email.Parser mercurial:commands,templater,util')
69 71 demandload(globals(), 'fnmatch socket time')
70 72
71 73 # template for single changeset can include email headers.
72 74 single_template = '''
73 75 Subject: changeset in {webroot}: {desc|firstline|strip}
74 76 From: {author}
75 77
76 78 changeset {node|short} in {root}
77 79 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
78 80 description:
79 81 \t{desc|tabindent|strip}
80 82 '''.lstrip()
81 83
82 84 # template for multiple changesets should not contain email headers,
83 85 # because only first set of headers will be used and result will look
84 86 # strange.
85 87 multiple_template = '''
86 88 changeset {node|short} in {root}
87 89 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
88 90 summary: {desc|firstline}
89 91 '''
90 92
91 93 deftemplates = {
92 94 'changegroup': multiple_template,
93 95 }
94 96
95 97 class notifier(object):
96 98 '''email notification class.'''
97 99
98 100 def __init__(self, ui, repo, hooktype):
99 101 self.ui = ui
100 102 self.ui.readconfig(self.ui.config('notify', 'config'))
101 103 self.repo = repo
102 104 self.stripcount = int(self.ui.config('notify', 'strip', 0))
103 105 self.root = self.strip(self.repo.root)
104 106 self.domain = self.ui.config('notify', 'domain')
105 107 self.sio = templater.stringio()
106 108 self.subs = self.subscribers()
107 109
108 110 mapfile = self.ui.config('notify', 'style')
109 111 template = (self.ui.config('notify', hooktype) or
110 112 self.ui.config('notify', 'template'))
111 113 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
112 114 self.sio)
113 115 if not mapfile and not template:
114 116 template = deftemplates.get(hooktype) or single_template
115 117 if template:
116 118 template = templater.parsestring(template, quoted=False)
117 119 self.t.use_template(template)
118 120
119 121 def strip(self, path):
120 122 '''strip leading slashes from local path, turn into web-safe path.'''
121 123
122 124 path = util.pconvert(path)
123 125 count = self.stripcount
124 126 while path and count >= 0:
125 127 c = path.find('/')
126 128 if c == -1:
127 129 break
128 130 path = path[c+1:]
129 131 count -= 1
130 132 return path
131 133
132 134 def fixmail(self, addr):
133 135 '''try to clean up email addresses.'''
134 136
135 137 addr = templater.email(addr.strip())
136 138 a = addr.find('@localhost')
137 139 if a != -1:
138 140 addr = addr[:a]
139 141 if '@' not in addr:
140 142 return addr + '@' + self.domain
141 143 return addr
142 144
143 145 def subscribers(self):
144 146 '''return list of email addresses of subscribers to this repo.'''
145 147
146 148 subs = {}
147 149 for user, pats in self.ui.configitems('usersubs'):
148 150 for pat in pats.split(','):
149 151 if fnmatch.fnmatch(self.repo.root, pat.strip()):
150 152 subs[self.fixmail(user)] = 1
151 153 for pat, users in self.ui.configitems('reposubs'):
152 154 if fnmatch.fnmatch(self.repo.root, pat):
153 155 for user in users.split(','):
154 156 subs[self.fixmail(user)] = 1
155 157 subs = subs.keys()
156 158 subs.sort()
157 159 return subs
158 160
159 161 def url(self, path=None):
160 162 return self.ui.config('web', 'baseurl') + (path or self.root)
161 163
162 164 def node(self, node):
163 165 '''format one changeset.'''
164 166
165 167 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
166 168 baseurl=self.ui.config('web', 'baseurl'),
167 169 root=self.repo.root,
168 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 177 def send(self, node, count):
171 178 '''send message.'''
172 179
173 180 p = email.Parser.Parser()
174 181 self.sio.seek(0)
175 182 msg = p.parse(self.sio)
176 183
177 184 def fix_subject():
178 185 '''try to make subject line exist and be useful.'''
179 186
180 187 subject = msg['Subject']
181 188 if not subject:
182 189 if count > 1:
183 190 subject = _('%s: %d new changesets') % (self.root, count)
184 191 else:
185 192 changes = self.repo.changelog.read(node)
186 193 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
187 194 subject = '%s: %s' % (self.root, s)
188 195 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
189 196 if maxsubject and len(subject) > maxsubject:
190 197 subject = subject[:maxsubject-3] + '...'
191 198 del msg['Subject']
192 199 msg['Subject'] = subject
193 200
194 201 def fix_sender():
195 202 '''try to make message have proper sender.'''
196 203
197 204 sender = msg['From']
198 205 if not sender:
199 206 sender = self.ui.config('email', 'from') or self.ui.username()
200 207 if '@' not in sender or '@localhost' in sender:
201 208 sender = self.fixmail(sender)
202 209 del msg['From']
203 210 msg['From'] = sender
204 211
205 212 fix_subject()
206 213 fix_sender()
207 214
208 215 msg['X-Hg-Notification'] = 'changeset ' + short(node)
209 216 if not msg['Message-Id']:
210 217 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
211 218 (short(node), int(time.time()),
212 219 hash(self.repo.root), socket.getfqdn()))
213 msg['To'] = self.subs
220 msg['To'] = ', '.join(self.subs)
214 221
215 222 msgtext = msg.as_string(0)
216 223 if self.ui.configbool('notify', 'test', True):
217 224 self.ui.write(msgtext)
218 225 if not msgtext.endswith('\n'):
219 226 self.ui.write('\n')
220 227 else:
221 228 mail = self.ui.sendmail()
222 229 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
223 230
224 231 def diff(self, node):
225 232 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
226 233 if maxdiff == 0:
227 234 return
228 235 fp = templater.stringio()
229 236 prev = self.repo.changelog.parents(node)[0]
230 237 commands.dodiff(fp, self.ui, self.repo, prev,
231 238 self.repo.changelog.tip())
232 239 difflines = fp.getvalue().splitlines(1)
233 240 if maxdiff > 0 and len(difflines) > maxdiff:
234 241 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
235 242 (len(difflines), maxdiff))
236 243 difflines = difflines[:maxdiff]
237 244 elif difflines:
238 245 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
239 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 249 '''send email notifications to interested subscribers.
243 250
244 251 if used as changegroup hook, send one email for all changesets in
245 252 changegroup. else send one email per changeset.'''
246 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 256 node = bin(node)
249 257 if hooktype == 'changegroup':
250 258 start = repo.changelog.rev(node)
251 259 end = repo.changelog.count()
252 260 count = end - start
253 261 for rev in xrange(start, end):
254 262 n.node(repo.changelog.node(rev))
255 263 else:
256 264 count = 1
257 265 n.node(node)
258 266 n.diff(node)
259 267 n.send(node, count)
@@ -1,3430 +1,3430 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 11 demandload(globals(), "os re sys signal shutil imp urllib pdb")
12 12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
13 13 demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
14 14 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
15 15 demandload(globals(), "archival changegroup")
16 16
17 17 class UnknownCommand(Exception):
18 18 """Exception raised if command is not in the command table."""
19 19 class AmbiguousCommand(Exception):
20 20 """Exception raised if command shortcut matches more than one command."""
21 21
22 22 def bail_if_changed(repo):
23 23 modified, added, removed, deleted, unknown = repo.changes()
24 24 if modified or added or removed or deleted:
25 25 raise util.Abort(_("outstanding uncommitted changes"))
26 26
27 27 def filterfiles(filters, files):
28 28 l = [x for x in files if x in filters]
29 29
30 30 for t in filters:
31 31 if t and t[-1] != "/":
32 32 t += "/"
33 33 l += [x for x in files if x.startswith(t)]
34 34 return l
35 35
36 36 def relpath(repo, args):
37 37 cwd = repo.getcwd()
38 38 if cwd:
39 39 return [util.normpath(os.path.join(cwd, x)) for x in args]
40 40 return args
41 41
42 42 def matchpats(repo, pats=[], opts={}, head=''):
43 43 cwd = repo.getcwd()
44 44 if not pats and cwd:
45 45 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
46 46 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
47 47 cwd = ''
48 48 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
49 49 opts.get('exclude'), head)
50 50
51 51 def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
52 52 files, matchfn, anypats = matchpats(repo, pats, opts, head)
53 53 exact = dict(zip(files, files))
54 54 def walk():
55 55 for src, fn in repo.walk(node=node, files=files, match=matchfn,
56 56 badmatch=badmatch):
57 57 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
58 58 return files, matchfn, walk()
59 59
60 60 def walk(repo, pats, opts, node=None, head='', badmatch=None):
61 61 files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
62 62 for r in results:
63 63 yield r
64 64
65 65 def walkchangerevs(ui, repo, pats, opts):
66 66 '''Iterate over files and the revs they changed in.
67 67
68 68 Callers most commonly need to iterate backwards over the history
69 69 it is interested in. Doing so has awful (quadratic-looking)
70 70 performance, so we use iterators in a "windowed" way.
71 71
72 72 We walk a window of revisions in the desired order. Within the
73 73 window, we first walk forwards to gather data, then in the desired
74 74 order (usually backwards) to display it.
75 75
76 76 This function returns an (iterator, getchange, matchfn) tuple. The
77 77 getchange function returns the changelog entry for a numeric
78 78 revision. The iterator yields 3-tuples. They will be of one of
79 79 the following forms:
80 80
81 81 "window", incrementing, lastrev: stepping through a window,
82 82 positive if walking forwards through revs, last rev in the
83 83 sequence iterated over - use to reset state for the current window
84 84
85 85 "add", rev, fns: out-of-order traversal of the given file names
86 86 fns, which changed during revision rev - use to gather data for
87 87 possible display
88 88
89 89 "iter", rev, None: in-order traversal of the revs earlier iterated
90 90 over with "add" - use to display data'''
91 91
92 92 def increasing_windows(start, end, windowsize=8, sizelimit=512):
93 93 if start < end:
94 94 while start < end:
95 95 yield start, min(windowsize, end-start)
96 96 start += windowsize
97 97 if windowsize < sizelimit:
98 98 windowsize *= 2
99 99 else:
100 100 while start > end:
101 101 yield start, min(windowsize, start-end-1)
102 102 start -= windowsize
103 103 if windowsize < sizelimit:
104 104 windowsize *= 2
105 105
106 106
107 107 files, matchfn, anypats = matchpats(repo, pats, opts)
108 108
109 109 if repo.changelog.count() == 0:
110 110 return [], False, matchfn
111 111
112 112 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
113 113 wanted = {}
114 114 slowpath = anypats
115 115 fncache = {}
116 116
117 117 chcache = {}
118 118 def getchange(rev):
119 119 ch = chcache.get(rev)
120 120 if ch is None:
121 121 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
122 122 return ch
123 123
124 124 if not slowpath and not files:
125 125 # No files, no patterns. Display all revs.
126 126 wanted = dict(zip(revs, revs))
127 127 if not slowpath:
128 128 # Only files, no patterns. Check the history of each file.
129 129 def filerevgen(filelog):
130 130 for i, window in increasing_windows(filelog.count()-1, -1):
131 131 revs = []
132 132 for j in xrange(i - window, i + 1):
133 133 revs.append(filelog.linkrev(filelog.node(j)))
134 134 revs.reverse()
135 135 for rev in revs:
136 136 yield rev
137 137
138 138 minrev, maxrev = min(revs), max(revs)
139 139 for file_ in files:
140 140 filelog = repo.file(file_)
141 141 # A zero count may be a directory or deleted file, so
142 142 # try to find matching entries on the slow path.
143 143 if filelog.count() == 0:
144 144 slowpath = True
145 145 break
146 146 for rev in filerevgen(filelog):
147 147 if rev <= maxrev:
148 148 if rev < minrev:
149 149 break
150 150 fncache.setdefault(rev, [])
151 151 fncache[rev].append(file_)
152 152 wanted[rev] = 1
153 153 if slowpath:
154 154 # The slow path checks files modified in every changeset.
155 155 def changerevgen():
156 156 for i, window in increasing_windows(repo.changelog.count()-1, -1):
157 157 for j in xrange(i - window, i + 1):
158 158 yield j, getchange(j)[3]
159 159
160 160 for rev, changefiles in changerevgen():
161 161 matches = filter(matchfn, changefiles)
162 162 if matches:
163 163 fncache[rev] = matches
164 164 wanted[rev] = 1
165 165
166 166 def iterate():
167 167 for i, window in increasing_windows(0, len(revs)):
168 168 yield 'window', revs[0] < revs[-1], revs[-1]
169 169 nrevs = [rev for rev in revs[i:i+window]
170 170 if rev in wanted]
171 171 srevs = list(nrevs)
172 172 srevs.sort()
173 173 for rev in srevs:
174 174 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
175 175 yield 'add', rev, fns
176 176 for rev in nrevs:
177 177 yield 'iter', rev, None
178 178 return iterate(), getchange, matchfn
179 179
180 180 revrangesep = ':'
181 181
182 182 def revrange(ui, repo, revs, revlog=None):
183 183 """Yield revision as strings from a list of revision specifications."""
184 184 if revlog is None:
185 185 revlog = repo.changelog
186 186 revcount = revlog.count()
187 187 def fix(val, defval):
188 188 if not val:
189 189 return defval
190 190 try:
191 191 num = int(val)
192 192 if str(num) != val:
193 193 raise ValueError
194 194 if num < 0:
195 195 num += revcount
196 196 if num < 0:
197 197 num = 0
198 198 elif num >= revcount:
199 199 raise ValueError
200 200 except ValueError:
201 201 try:
202 202 num = repo.changelog.rev(repo.lookup(val))
203 203 except KeyError:
204 204 try:
205 205 num = revlog.rev(revlog.lookup(val))
206 206 except KeyError:
207 207 raise util.Abort(_('invalid revision identifier %s'), val)
208 208 return num
209 209 seen = {}
210 210 for spec in revs:
211 211 if spec.find(revrangesep) >= 0:
212 212 start, end = spec.split(revrangesep, 1)
213 213 start = fix(start, 0)
214 214 end = fix(end, revcount - 1)
215 215 step = start > end and -1 or 1
216 216 for rev in xrange(start, end+step, step):
217 217 if rev in seen:
218 218 continue
219 219 seen[rev] = 1
220 220 yield str(rev)
221 221 else:
222 222 rev = fix(spec, None)
223 223 if rev in seen:
224 224 continue
225 225 seen[rev] = 1
226 226 yield str(rev)
227 227
228 228 def make_filename(repo, r, pat, node=None,
229 229 total=None, seqno=None, revwidth=None, pathname=None):
230 230 node_expander = {
231 231 'H': lambda: hex(node),
232 232 'R': lambda: str(r.rev(node)),
233 233 'h': lambda: short(node),
234 234 }
235 235 expander = {
236 236 '%': lambda: '%',
237 237 'b': lambda: os.path.basename(repo.root),
238 238 }
239 239
240 240 try:
241 241 if node:
242 242 expander.update(node_expander)
243 243 if node and revwidth is not None:
244 244 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
245 245 if total is not None:
246 246 expander['N'] = lambda: str(total)
247 247 if seqno is not None:
248 248 expander['n'] = lambda: str(seqno)
249 249 if total is not None and seqno is not None:
250 250 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
251 251 if pathname is not None:
252 252 expander['s'] = lambda: os.path.basename(pathname)
253 253 expander['d'] = lambda: os.path.dirname(pathname) or '.'
254 254 expander['p'] = lambda: pathname
255 255
256 256 newname = []
257 257 patlen = len(pat)
258 258 i = 0
259 259 while i < patlen:
260 260 c = pat[i]
261 261 if c == '%':
262 262 i += 1
263 263 c = pat[i]
264 264 c = expander[c]()
265 265 newname.append(c)
266 266 i += 1
267 267 return ''.join(newname)
268 268 except KeyError, inst:
269 269 raise util.Abort(_("invalid format spec '%%%s' in output file name"),
270 270 inst.args[0])
271 271
272 272 def make_file(repo, r, pat, node=None,
273 273 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
274 274 if not pat or pat == '-':
275 275 return 'w' in mode and sys.stdout or sys.stdin
276 276 if hasattr(pat, 'write') and 'w' in mode:
277 277 return pat
278 278 if hasattr(pat, 'read') and 'r' in mode:
279 279 return pat
280 280 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
281 281 pathname),
282 282 mode)
283 283
284 284 def write_bundle(cg, filename=None, compress=True):
285 285 """Write a bundle file and return its filename.
286 286
287 287 Existing files will not be overwritten.
288 288 If no filename is specified, a temporary file is created.
289 289 bz2 compression can be turned off.
290 290 The bundle file will be deleted in case of errors.
291 291 """
292 292 class nocompress(object):
293 293 def compress(self, x):
294 294 return x
295 295 def flush(self):
296 296 return ""
297 297
298 298 fh = None
299 299 cleanup = None
300 300 try:
301 301 if filename:
302 302 if os.path.exists(filename):
303 303 raise util.Abort(_("file '%s' already exists"), filename)
304 304 fh = open(filename, "wb")
305 305 else:
306 306 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
307 307 fh = os.fdopen(fd, "wb")
308 308 cleanup = filename
309 309
310 310 if compress:
311 311 fh.write("HG10")
312 312 z = bz2.BZ2Compressor(9)
313 313 else:
314 314 fh.write("HG10UN")
315 315 z = nocompress()
316 316 # parse the changegroup data, otherwise we will block
317 317 # in case of sshrepo because we don't know the end of the stream
318 318
319 319 # an empty chunkiter is the end of the changegroup
320 320 empty = False
321 321 while not empty:
322 322 empty = True
323 323 for chunk in changegroup.chunkiter(cg):
324 324 empty = False
325 325 fh.write(z.compress(changegroup.genchunk(chunk)))
326 326 fh.write(z.compress(changegroup.closechunk()))
327 327 fh.write(z.flush())
328 328 cleanup = None
329 329 return filename
330 330 finally:
331 331 if fh is not None:
332 332 fh.close()
333 333 if cleanup is not None:
334 334 os.unlink(cleanup)
335 335
336 336 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
337 337 changes=None, text=False, opts={}):
338 338 if not node1:
339 339 node1 = repo.dirstate.parents()[0]
340 340 # reading the data for node1 early allows it to play nicely
341 341 # with repo.changes and the revlog cache.
342 342 change = repo.changelog.read(node1)
343 343 mmap = repo.manifest.read(change[0])
344 344 date1 = util.datestr(change[2])
345 345
346 346 if not changes:
347 347 changes = repo.changes(node1, node2, files, match=match)
348 348 modified, added, removed, deleted, unknown = changes
349 349 if files:
350 350 modified, added, removed = map(lambda x: filterfiles(files, x),
351 351 (modified, added, removed))
352 352
353 353 if not modified and not added and not removed:
354 354 return
355 355
356 356 if node2:
357 357 change = repo.changelog.read(node2)
358 358 mmap2 = repo.manifest.read(change[0])
359 359 date2 = util.datestr(change[2])
360 360 def read(f):
361 361 return repo.file(f).read(mmap2[f])
362 362 else:
363 363 date2 = util.datestr()
364 364 def read(f):
365 365 return repo.wread(f)
366 366
367 367 if ui.quiet:
368 368 r = None
369 369 else:
370 370 hexfunc = ui.verbose and hex or short
371 371 r = [hexfunc(node) for node in [node1, node2] if node]
372 372
373 373 diffopts = ui.diffopts()
374 374 showfunc = opts.get('show_function') or diffopts['showfunc']
375 375 ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
376 376 for f in modified:
377 377 to = None
378 378 if f in mmap:
379 379 to = repo.file(f).read(mmap[f])
380 380 tn = read(f)
381 381 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
382 382 showfunc=showfunc, ignorews=ignorews))
383 383 for f in added:
384 384 to = None
385 385 tn = read(f)
386 386 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
387 387 showfunc=showfunc, ignorews=ignorews))
388 388 for f in removed:
389 389 to = repo.file(f).read(mmap[f])
390 390 tn = None
391 391 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text,
392 392 showfunc=showfunc, ignorews=ignorews))
393 393
394 394 def trimuser(ui, name, rev, revcache):
395 395 """trim the name of the user who committed a change"""
396 396 user = revcache.get(rev)
397 397 if user is None:
398 398 user = revcache[rev] = ui.shortuser(name)
399 399 return user
400 400
401 401 class changeset_printer(object):
402 402 '''show changeset information when templating not requested.'''
403 403
404 404 def __init__(self, ui, repo):
405 405 self.ui = ui
406 406 self.repo = repo
407 407
408 408 def show(self, rev=0, changenode=None, brinfo=None):
409 409 '''show a single changeset or file revision'''
410 410 log = self.repo.changelog
411 411 if changenode is None:
412 412 changenode = log.node(rev)
413 413 elif not rev:
414 414 rev = log.rev(changenode)
415 415
416 416 if self.ui.quiet:
417 417 self.ui.write("%d:%s\n" % (rev, short(changenode)))
418 418 return
419 419
420 420 changes = log.read(changenode)
421 421 date = util.datestr(changes[2])
422 422
423 423 parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
424 424 for p in log.parents(changenode)
425 425 if self.ui.debugflag or p != nullid]
426 426 if (not self.ui.debugflag and len(parents) == 1 and
427 427 parents[0][0] == rev-1):
428 428 parents = []
429 429
430 430 if self.ui.verbose:
431 431 self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode)))
432 432 else:
433 433 self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode)))
434 434
435 435 for tag in self.repo.nodetags(changenode):
436 436 self.ui.status(_("tag: %s\n") % tag)
437 437 for parent in parents:
438 438 self.ui.write(_("parent: %d:%s\n") % parent)
439 439
440 440 if brinfo and changenode in brinfo:
441 441 br = brinfo[changenode]
442 442 self.ui.write(_("branch: %s\n") % " ".join(br))
443 443
444 444 self.ui.debug(_("manifest: %d:%s\n") %
445 445 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
446 446 self.ui.status(_("user: %s\n") % changes[1])
447 447 self.ui.status(_("date: %s\n") % date)
448 448
449 449 if self.ui.debugflag:
450 450 files = self.repo.changes(log.parents(changenode)[0], changenode)
451 451 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
452 452 files):
453 453 if value:
454 454 self.ui.note("%-12s %s\n" % (key, " ".join(value)))
455 455 else:
456 456 self.ui.note(_("files: %s\n") % " ".join(changes[3]))
457 457
458 458 description = changes[4].strip()
459 459 if description:
460 460 if self.ui.verbose:
461 461 self.ui.status(_("description:\n"))
462 462 self.ui.status(description)
463 463 self.ui.status("\n\n")
464 464 else:
465 465 self.ui.status(_("summary: %s\n") %
466 466 description.splitlines()[0])
467 467 self.ui.status("\n")
468 468
469 469 def show_changeset(ui, repo, opts):
470 470 '''show one changeset. uses template or regular display. caller
471 471 can pass in 'style' and 'template' options in opts.'''
472 472
473 473 tmpl = opts.get('template')
474 474 if tmpl:
475 475 tmpl = templater.parsestring(tmpl, quoted=False)
476 476 else:
477 477 tmpl = ui.config('ui', 'logtemplate')
478 478 if tmpl: tmpl = templater.parsestring(tmpl)
479 479 mapfile = opts.get('style') or ui.config('ui', 'style')
480 480 if tmpl or mapfile:
481 481 if mapfile:
482 482 if not os.path.isfile(mapfile):
483 483 mapname = templater.templatepath('map-cmdline.' + mapfile)
484 484 if not mapname: mapname = templater.templatepath(mapfile)
485 485 if mapname: mapfile = mapname
486 486 try:
487 487 t = templater.changeset_templater(ui, repo, mapfile)
488 488 except SyntaxError, inst:
489 489 raise util.Abort(inst.args[0])
490 490 if tmpl: t.use_template(tmpl)
491 491 return t
492 492 return changeset_printer(ui, repo)
493 493
494 494 def show_version(ui):
495 495 """output version and copyright information"""
496 496 ui.write(_("Mercurial Distributed SCM (version %s)\n")
497 497 % version.get_version())
498 498 ui.status(_(
499 499 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
500 500 "This is free software; see the source for copying conditions. "
501 501 "There is NO\nwarranty; "
502 502 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
503 503 ))
504 504
505 505 def help_(ui, cmd=None, with_version=False):
506 506 """show help for a given command or all commands"""
507 507 option_lists = []
508 508 if cmd and cmd != 'shortlist':
509 509 if with_version:
510 510 show_version(ui)
511 511 ui.write('\n')
512 512 aliases, i = find(cmd)
513 513 # synopsis
514 514 ui.write("%s\n\n" % i[2])
515 515
516 516 # description
517 517 doc = i[0].__doc__
518 518 if not doc:
519 519 doc = _("(No help text available)")
520 520 if ui.quiet:
521 521 doc = doc.splitlines(0)[0]
522 522 ui.write("%s\n" % doc.rstrip())
523 523
524 524 if not ui.quiet:
525 525 # aliases
526 526 if len(aliases) > 1:
527 527 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
528 528
529 529 # options
530 530 if i[1]:
531 531 option_lists.append(("options", i[1]))
532 532
533 533 else:
534 534 # program name
535 535 if ui.verbose or with_version:
536 536 show_version(ui)
537 537 else:
538 538 ui.status(_("Mercurial Distributed SCM\n"))
539 539 ui.status('\n')
540 540
541 541 # list of commands
542 542 if cmd == "shortlist":
543 543 ui.status(_('basic commands (use "hg help" '
544 544 'for the full list or option "-v" for details):\n\n'))
545 545 elif ui.verbose:
546 546 ui.status(_('list of commands:\n\n'))
547 547 else:
548 548 ui.status(_('list of commands (use "hg help -v" '
549 549 'to show aliases and global options):\n\n'))
550 550
551 551 h = {}
552 552 cmds = {}
553 553 for c, e in table.items():
554 554 f = c.split("|")[0]
555 555 if cmd == "shortlist" and not f.startswith("^"):
556 556 continue
557 557 f = f.lstrip("^")
558 558 if not ui.debugflag and f.startswith("debug"):
559 559 continue
560 560 doc = e[0].__doc__
561 561 if not doc:
562 562 doc = _("(No help text available)")
563 563 h[f] = doc.splitlines(0)[0].rstrip()
564 564 cmds[f] = c.lstrip("^")
565 565
566 566 fns = h.keys()
567 567 fns.sort()
568 568 m = max(map(len, fns))
569 569 for f in fns:
570 570 if ui.verbose:
571 571 commands = cmds[f].replace("|",", ")
572 572 ui.write(" %s:\n %s\n"%(commands, h[f]))
573 573 else:
574 574 ui.write(' %-*s %s\n' % (m, f, h[f]))
575 575
576 576 # global options
577 577 if ui.verbose:
578 578 option_lists.append(("global options", globalopts))
579 579
580 580 # list all option lists
581 581 opt_output = []
582 582 for title, options in option_lists:
583 583 opt_output.append(("\n%s:\n" % title, None))
584 584 for shortopt, longopt, default, desc in options:
585 585 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
586 586 longopt and " --%s" % longopt),
587 587 "%s%s" % (desc,
588 588 default
589 589 and _(" (default: %s)") % default
590 590 or "")))
591 591
592 592 if opt_output:
593 593 opts_len = max([len(line[0]) for line in opt_output if line[1]])
594 594 for first, second in opt_output:
595 595 if second:
596 596 ui.write(" %-*s %s\n" % (opts_len, first, second))
597 597 else:
598 598 ui.write("%s\n" % first)
599 599
600 600 # Commands start here, listed alphabetically
601 601
602 602 def add(ui, repo, *pats, **opts):
603 603 """add the specified files on the next commit
604 604
605 605 Schedule files to be version controlled and added to the repository.
606 606
607 607 The files will be added to the repository at the next commit.
608 608
609 609 If no names are given, add all files in the repository.
610 610 """
611 611
612 612 names = []
613 613 for src, abs, rel, exact in walk(repo, pats, opts):
614 614 if exact:
615 615 if ui.verbose:
616 616 ui.status(_('adding %s\n') % rel)
617 617 names.append(abs)
618 618 elif repo.dirstate.state(abs) == '?':
619 619 ui.status(_('adding %s\n') % rel)
620 620 names.append(abs)
621 621 repo.add(names)
622 622
623 623 def addremove(ui, repo, *pats, **opts):
624 624 """add all new files, delete all missing files (DEPRECATED)
625 625
626 626 (DEPRECATED)
627 627 Add all new files and remove all missing files from the repository.
628 628
629 629 New files are ignored if they match any of the patterns in .hgignore. As
630 630 with add, these changes take effect at the next commit.
631 631
632 632 This command is now deprecated and will be removed in a future
633 633 release. Please use add and remove --after instead.
634 634 """
635 635 ui.warn(_('(the addremove command is deprecated; use add and remove '
636 636 '--after instead)\n'))
637 637 return addremove_lock(ui, repo, pats, opts)
638 638
639 639 def addremove_lock(ui, repo, pats, opts, wlock=None):
640 640 add, remove = [], []
641 641 for src, abs, rel, exact in walk(repo, pats, opts):
642 642 if src == 'f' and repo.dirstate.state(abs) == '?':
643 643 add.append(abs)
644 644 if ui.verbose or not exact:
645 645 ui.status(_('adding %s\n') % ((pats and rel) or abs))
646 646 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
647 647 remove.append(abs)
648 648 if ui.verbose or not exact:
649 649 ui.status(_('removing %s\n') % ((pats and rel) or abs))
650 650 repo.add(add, wlock=wlock)
651 651 repo.remove(remove, wlock=wlock)
652 652
653 653 def annotate(ui, repo, *pats, **opts):
654 654 """show changeset information per file line
655 655
656 656 List changes in files, showing the revision id responsible for each line
657 657
658 658 This command is useful to discover who did a change or when a change took
659 659 place.
660 660
661 661 Without the -a option, annotate will avoid processing files it
662 662 detects as binary. With -a, annotate will generate an annotation
663 663 anyway, probably with undesirable results.
664 664 """
665 665 def getnode(rev):
666 666 return short(repo.changelog.node(rev))
667 667
668 668 ucache = {}
669 669 def getname(rev):
670 670 cl = repo.changelog.read(repo.changelog.node(rev))
671 671 return trimuser(ui, cl[1], rev, ucache)
672 672
673 673 dcache = {}
674 674 def getdate(rev):
675 675 datestr = dcache.get(rev)
676 676 if datestr is None:
677 677 cl = repo.changelog.read(repo.changelog.node(rev))
678 678 datestr = dcache[rev] = util.datestr(cl[2])
679 679 return datestr
680 680
681 681 if not pats:
682 682 raise util.Abort(_('at least one file name or pattern required'))
683 683
684 684 opmap = [['user', getname], ['number', str], ['changeset', getnode],
685 685 ['date', getdate]]
686 686 if not opts['user'] and not opts['changeset'] and not opts['date']:
687 687 opts['number'] = 1
688 688
689 689 if opts['rev']:
690 690 node = repo.changelog.lookup(opts['rev'])
691 691 else:
692 692 node = repo.dirstate.parents()[0]
693 693 change = repo.changelog.read(node)
694 694 mmap = repo.manifest.read(change[0])
695 695
696 696 for src, abs, rel, exact in walk(repo, pats, opts, node=node):
697 697 f = repo.file(abs)
698 698 if not opts['text'] and util.binary(f.read(mmap[abs])):
699 699 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
700 700 continue
701 701
702 702 lines = f.annotate(mmap[abs])
703 703 pieces = []
704 704
705 705 for o, f in opmap:
706 706 if opts[o]:
707 707 l = [f(n) for n, dummy in lines]
708 708 if l:
709 709 m = max(map(len, l))
710 710 pieces.append(["%*s" % (m, x) for x in l])
711 711
712 712 if pieces:
713 713 for p, l in zip(zip(*pieces), lines):
714 714 ui.write("%s: %s" % (" ".join(p), l[1]))
715 715
716 716 def archive(ui, repo, dest, **opts):
717 717 '''create unversioned archive of a repository revision
718 718
719 719 By default, the revision used is the parent of the working
720 720 directory; use "-r" to specify a different revision.
721 721
722 722 To specify the type of archive to create, use "-t". Valid
723 723 types are:
724 724
725 725 "files" (default): a directory full of files
726 726 "tar": tar archive, uncompressed
727 727 "tbz2": tar archive, compressed using bzip2
728 728 "tgz": tar archive, compressed using gzip
729 729 "uzip": zip archive, uncompressed
730 730 "zip": zip archive, compressed using deflate
731 731
732 732 The exact name of the destination archive or directory is given
733 733 using a format string; see "hg help export" for details.
734 734
735 735 Each member added to an archive file has a directory prefix
736 736 prepended. Use "-p" to specify a format string for the prefix.
737 737 The default is the basename of the archive, with suffixes removed.
738 738 '''
739 739
740 740 if opts['rev']:
741 741 node = repo.lookup(opts['rev'])
742 742 else:
743 743 node, p2 = repo.dirstate.parents()
744 744 if p2 != nullid:
745 745 raise util.Abort(_('uncommitted merge - please provide a '
746 746 'specific revision'))
747 747
748 748 dest = make_filename(repo, repo.changelog, dest, node)
749 749 prefix = make_filename(repo, repo.changelog, opts['prefix'], node)
750 750 if os.path.realpath(dest) == repo.root:
751 751 raise util.Abort(_('repository root cannot be destination'))
752 752 dummy, matchfn, dummy = matchpats(repo, [], opts)
753 753 archival.archive(repo, dest, node, opts.get('type') or 'files',
754 754 not opts['no_decode'], matchfn, prefix)
755 755
756 756 def backout(ui, repo, rev, **opts):
757 757 '''reverse effect of earlier changeset
758 758
759 759 Commit the backed out changes as a new changeset.
760 760
761 761 If you back out a changeset other than the tip, a new head is
762 762 created. The --merge option remembers the parent of the working
763 763 directory before starting the backout, then merges the new head
764 764 with it afterwards, to save you from doing this by hand. The
765 765 result of this merge is not committed, as for a normal merge.'''
766 766
767 767 bail_if_changed(repo)
768 768 op1, op2 = repo.dirstate.parents()
769 769 if op2 != nullid:
770 770 raise util.Abort(_('outstanding uncommitted merge'))
771 771 node = repo.lookup(rev)
772 772 parent, p2 = repo.changelog.parents(node)
773 773 if parent == nullid:
774 774 raise util.Abort(_('cannot back out a change with no parents'))
775 775 if p2 != nullid:
776 776 raise util.Abort(_('cannot back out a merge'))
777 777 repo.update(node, force=True, show_stats=False)
778 778 revert_opts = opts.copy()
779 779 revert_opts['rev'] = hex(parent)
780 780 revert(ui, repo, **revert_opts)
781 781 commit_opts = opts.copy()
782 782 commit_opts['addremove'] = False
783 783 if not commit_opts['message'] and not commit_opts['logfile']:
784 784 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
785 785 commit(ui, repo, **commit_opts)
786 786 def nice(node):
787 787 return '%d:%s' % (repo.changelog.rev(node), short(node))
788 788 ui.status(_('changeset %s backs out changeset %s\n') %
789 789 (nice(repo.changelog.tip()), nice(node)))
790 790 if opts['merge'] and op1 != node:
791 791 ui.status(_('merging with changeset %s\n') % nice(op1))
792 792 update(ui, repo, hex(op1), **opts)
793 793
794 794 def bundle(ui, repo, fname, dest="default-push", **opts):
795 795 """create a changegroup file
796 796
797 797 Generate a compressed changegroup file collecting all changesets
798 798 not found in the other repository.
799 799
800 800 This file can then be transferred using conventional means and
801 801 applied to another repository with the unbundle command. This is
802 802 useful when native push and pull are not available or when
803 803 exporting an entire repository is undesirable. The standard file
804 804 extension is ".hg".
805 805
806 806 Unlike import/export, this exactly preserves all changeset
807 807 contents including permissions, rename data, and revision history.
808 808 """
809 809 dest = ui.expandpath(dest)
810 810 other = hg.repository(ui, dest)
811 811 o = repo.findoutgoing(other, force=opts['force'])
812 812 cg = repo.changegroup(o, 'bundle')
813 813 write_bundle(cg, fname)
814 814
815 815 def cat(ui, repo, file1, *pats, **opts):
816 816 """output the latest or given revisions of files
817 817
818 818 Print the specified files as they were at the given revision.
819 819 If no revision is given then the tip is used.
820 820
821 821 Output may be to a file, in which case the name of the file is
822 822 given using a format string. The formatting rules are the same as
823 823 for the export command, with the following additions:
824 824
825 825 %s basename of file being printed
826 826 %d dirname of file being printed, or '.' if in repo root
827 827 %p root-relative path name of file being printed
828 828 """
829 829 mf = {}
830 830 rev = opts['rev']
831 831 if rev:
832 832 node = repo.lookup(rev)
833 833 else:
834 834 node = repo.changelog.tip()
835 835 change = repo.changelog.read(node)
836 836 mf = repo.manifest.read(change[0])
837 837 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node):
838 838 r = repo.file(abs)
839 839 n = mf[abs]
840 840 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
841 841 fp.write(r.read(n))
842 842
843 843 def clone(ui, source, dest=None, **opts):
844 844 """make a copy of an existing repository
845 845
846 846 Create a copy of an existing repository in a new directory.
847 847
848 848 If no destination directory name is specified, it defaults to the
849 849 basename of the source.
850 850
851 851 The location of the source is added to the new repository's
852 852 .hg/hgrc file, as the default to be used for future pulls.
853 853
854 854 For efficiency, hardlinks are used for cloning whenever the source
855 855 and destination are on the same filesystem. Some filesystems,
856 856 such as AFS, implement hardlinking incorrectly, but do not report
857 857 errors. In these cases, use the --pull option to avoid
858 858 hardlinking.
859 859
860 860 See pull for valid source format details.
861 861 """
862 862 if dest is None:
863 863 dest = os.path.basename(os.path.normpath(source))
864 864
865 865 if os.path.exists(dest):
866 866 raise util.Abort(_("destination '%s' already exists"), dest)
867 867
868 868 dest = os.path.realpath(dest)
869 869
870 870 class Dircleanup(object):
871 871 def __init__(self, dir_):
872 872 self.rmtree = shutil.rmtree
873 873 self.dir_ = dir_
874 874 os.mkdir(dir_)
875 875 def close(self):
876 876 self.dir_ = None
877 877 def __del__(self):
878 878 if self.dir_:
879 879 self.rmtree(self.dir_, True)
880 880
881 881 if opts['ssh']:
882 882 ui.setconfig("ui", "ssh", opts['ssh'])
883 883 if opts['remotecmd']:
884 884 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
885 885
886 886 source = ui.expandpath(source)
887 887
888 888 d = Dircleanup(dest)
889 889 abspath = source
890 890 other = hg.repository(ui, source)
891 891
892 892 copy = False
893 893 if other.dev() != -1:
894 894 abspath = os.path.abspath(source)
895 895 if not opts['pull'] and not opts['rev']:
896 896 copy = True
897 897
898 898 if copy:
899 899 try:
900 900 # we use a lock here because if we race with commit, we
901 901 # can end up with extra data in the cloned revlogs that's
902 902 # not pointed to by changesets, thus causing verify to
903 903 # fail
904 904 l1 = other.lock()
905 905 except lock.LockException:
906 906 copy = False
907 907
908 908 if copy:
909 909 # we lock here to avoid premature writing to the target
910 910 os.mkdir(os.path.join(dest, ".hg"))
911 911 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
912 912
913 913 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
914 914 for f in files.split():
915 915 src = os.path.join(source, ".hg", f)
916 916 dst = os.path.join(dest, ".hg", f)
917 917 try:
918 918 util.copyfiles(src, dst)
919 919 except OSError, inst:
920 920 if inst.errno != errno.ENOENT:
921 921 raise
922 922
923 923 repo = hg.repository(ui, dest)
924 924
925 925 else:
926 926 revs = None
927 927 if opts['rev']:
928 928 if not other.local():
929 929 error = _("clone -r not supported yet for remote repositories.")
930 930 raise util.Abort(error)
931 931 else:
932 932 revs = [other.lookup(rev) for rev in opts['rev']]
933 933 repo = hg.repository(ui, dest, create=1)
934 934 repo.pull(other, heads = revs)
935 935
936 936 f = repo.opener("hgrc", "w", text=True)
937 937 f.write("[paths]\n")
938 938 f.write("default = %s\n" % abspath)
939 939 f.close()
940 940
941 941 if not opts['noupdate']:
942 942 update(repo.ui, repo)
943 943
944 944 d.close()
945 945
946 946 def commit(ui, repo, *pats, **opts):
947 947 """commit the specified files or all outstanding changes
948 948
949 949 Commit changes to the given files into the repository.
950 950
951 951 If a list of files is omitted, all changes reported by "hg status"
952 952 will be committed.
953 953
954 954 If no commit message is specified, the editor configured in your hgrc
955 955 or in the EDITOR environment variable is started to enter a message.
956 956 """
957 957 message = opts['message']
958 958 logfile = opts['logfile']
959 959
960 960 if message and logfile:
961 961 raise util.Abort(_('options --message and --logfile are mutually '
962 962 'exclusive'))
963 963 if not message and logfile:
964 964 try:
965 965 if logfile == '-':
966 966 message = sys.stdin.read()
967 967 else:
968 968 message = open(logfile).read()
969 969 except IOError, inst:
970 970 raise util.Abort(_("can't read commit message '%s': %s") %
971 971 (logfile, inst.strerror))
972 972
973 973 if opts['addremove']:
974 974 addremove_lock(ui, repo, pats, opts)
975 975 fns, match, anypats = matchpats(repo, pats, opts)
976 976 if pats:
977 977 modified, added, removed, deleted, unknown = (
978 978 repo.changes(files=fns, match=match))
979 979 files = modified + added + removed
980 980 else:
981 981 files = []
982 982 try:
983 983 repo.commit(files, message, opts['user'], opts['date'], match)
984 984 except ValueError, inst:
985 985 raise util.Abort(str(inst))
986 986
987 987 def docopy(ui, repo, pats, opts, wlock):
988 988 # called with the repo lock held
989 989 cwd = repo.getcwd()
990 990 errors = 0
991 991 copied = []
992 992 targets = {}
993 993
994 994 def okaytocopy(abs, rel, exact):
995 995 reasons = {'?': _('is not managed'),
996 996 'a': _('has been marked for add'),
997 997 'r': _('has been marked for remove')}
998 998 state = repo.dirstate.state(abs)
999 999 reason = reasons.get(state)
1000 1000 if reason:
1001 1001 if state == 'a':
1002 1002 origsrc = repo.dirstate.copied(abs)
1003 1003 if origsrc is not None:
1004 1004 return origsrc
1005 1005 if exact:
1006 1006 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
1007 1007 else:
1008 1008 return abs
1009 1009
1010 1010 def copy(origsrc, abssrc, relsrc, target, exact):
1011 1011 abstarget = util.canonpath(repo.root, cwd, target)
1012 1012 reltarget = util.pathto(cwd, abstarget)
1013 1013 prevsrc = targets.get(abstarget)
1014 1014 if prevsrc is not None:
1015 1015 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1016 1016 (reltarget, abssrc, prevsrc))
1017 1017 return
1018 1018 if (not opts['after'] and os.path.exists(reltarget) or
1019 1019 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
1020 1020 if not opts['force']:
1021 1021 ui.warn(_('%s: not overwriting - file exists\n') %
1022 1022 reltarget)
1023 1023 return
1024 1024 if not opts['after']:
1025 1025 os.unlink(reltarget)
1026 1026 if opts['after']:
1027 1027 if not os.path.exists(reltarget):
1028 1028 return
1029 1029 else:
1030 1030 targetdir = os.path.dirname(reltarget) or '.'
1031 1031 if not os.path.isdir(targetdir):
1032 1032 os.makedirs(targetdir)
1033 1033 try:
1034 1034 restore = repo.dirstate.state(abstarget) == 'r'
1035 1035 if restore:
1036 1036 repo.undelete([abstarget], wlock)
1037 1037 try:
1038 1038 shutil.copyfile(relsrc, reltarget)
1039 1039 shutil.copymode(relsrc, reltarget)
1040 1040 restore = False
1041 1041 finally:
1042 1042 if restore:
1043 1043 repo.remove([abstarget], wlock)
1044 1044 except shutil.Error, inst:
1045 1045 raise util.Abort(str(inst))
1046 1046 except IOError, inst:
1047 1047 if inst.errno == errno.ENOENT:
1048 1048 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1049 1049 else:
1050 1050 ui.warn(_('%s: cannot copy - %s\n') %
1051 1051 (relsrc, inst.strerror))
1052 1052 errors += 1
1053 1053 return
1054 1054 if ui.verbose or not exact:
1055 1055 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1056 1056 targets[abstarget] = abssrc
1057 1057 if abstarget != origsrc:
1058 1058 repo.copy(origsrc, abstarget, wlock)
1059 1059 copied.append((abssrc, relsrc, exact))
1060 1060
1061 1061 def targetpathfn(pat, dest, srcs):
1062 1062 if os.path.isdir(pat):
1063 1063 abspfx = util.canonpath(repo.root, cwd, pat)
1064 1064 if destdirexists:
1065 1065 striplen = len(os.path.split(abspfx)[0])
1066 1066 else:
1067 1067 striplen = len(abspfx)
1068 1068 if striplen:
1069 1069 striplen += len(os.sep)
1070 1070 res = lambda p: os.path.join(dest, p[striplen:])
1071 1071 elif destdirexists:
1072 1072 res = lambda p: os.path.join(dest, os.path.basename(p))
1073 1073 else:
1074 1074 res = lambda p: dest
1075 1075 return res
1076 1076
1077 1077 def targetpathafterfn(pat, dest, srcs):
1078 1078 if util.patkind(pat, None)[0]:
1079 1079 # a mercurial pattern
1080 1080 res = lambda p: os.path.join(dest, os.path.basename(p))
1081 1081 else:
1082 1082 abspfx = util.canonpath(repo.root, cwd, pat)
1083 1083 if len(abspfx) < len(srcs[0][0]):
1084 1084 # A directory. Either the target path contains the last
1085 1085 # component of the source path or it does not.
1086 1086 def evalpath(striplen):
1087 1087 score = 0
1088 1088 for s in srcs:
1089 1089 t = os.path.join(dest, s[0][striplen:])
1090 1090 if os.path.exists(t):
1091 1091 score += 1
1092 1092 return score
1093 1093
1094 1094 striplen = len(abspfx)
1095 1095 if striplen:
1096 1096 striplen += len(os.sep)
1097 1097 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1098 1098 score = evalpath(striplen)
1099 1099 striplen1 = len(os.path.split(abspfx)[0])
1100 1100 if striplen1:
1101 1101 striplen1 += len(os.sep)
1102 1102 if evalpath(striplen1) > score:
1103 1103 striplen = striplen1
1104 1104 res = lambda p: os.path.join(dest, p[striplen:])
1105 1105 else:
1106 1106 # a file
1107 1107 if destdirexists:
1108 1108 res = lambda p: os.path.join(dest, os.path.basename(p))
1109 1109 else:
1110 1110 res = lambda p: dest
1111 1111 return res
1112 1112
1113 1113
1114 1114 pats = list(pats)
1115 1115 if not pats:
1116 1116 raise util.Abort(_('no source or destination specified'))
1117 1117 if len(pats) == 1:
1118 1118 raise util.Abort(_('no destination specified'))
1119 1119 dest = pats.pop()
1120 1120 destdirexists = os.path.isdir(dest)
1121 1121 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1122 1122 raise util.Abort(_('with multiple sources, destination must be an '
1123 1123 'existing directory'))
1124 1124 if opts['after']:
1125 1125 tfn = targetpathafterfn
1126 1126 else:
1127 1127 tfn = targetpathfn
1128 1128 copylist = []
1129 1129 for pat in pats:
1130 1130 srcs = []
1131 1131 for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
1132 1132 origsrc = okaytocopy(abssrc, relsrc, exact)
1133 1133 if origsrc:
1134 1134 srcs.append((origsrc, abssrc, relsrc, exact))
1135 1135 if not srcs:
1136 1136 continue
1137 1137 copylist.append((tfn(pat, dest, srcs), srcs))
1138 1138 if not copylist:
1139 1139 raise util.Abort(_('no files to copy'))
1140 1140
1141 1141 for targetpath, srcs in copylist:
1142 1142 for origsrc, abssrc, relsrc, exact in srcs:
1143 1143 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1144 1144
1145 1145 if errors:
1146 1146 ui.warn(_('(consider using --after)\n'))
1147 1147 return errors, copied
1148 1148
1149 1149 def copy(ui, repo, *pats, **opts):
1150 1150 """mark files as copied for the next commit
1151 1151
1152 1152 Mark dest as having copies of source files. If dest is a
1153 1153 directory, copies are put in that directory. If dest is a file,
1154 1154 there can only be one source.
1155 1155
1156 1156 By default, this command copies the contents of files as they
1157 1157 stand in the working directory. If invoked with --after, the
1158 1158 operation is recorded, but no copying is performed.
1159 1159
1160 1160 This command takes effect in the next commit.
1161 1161
1162 1162 NOTE: This command should be treated as experimental. While it
1163 1163 should properly record copied files, this information is not yet
1164 1164 fully used by merge, nor fully reported by log.
1165 1165 """
1166 1166 wlock = repo.wlock(0)
1167 1167 errs, copied = docopy(ui, repo, pats, opts, wlock)
1168 1168 return errs
1169 1169
1170 1170 def debugancestor(ui, index, rev1, rev2):
1171 1171 """find the ancestor revision of two revisions in a given index"""
1172 1172 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1173 1173 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1174 1174 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1175 1175
1176 1176 def debugcomplete(ui, cmd='', **opts):
1177 1177 """returns the completion list associated with the given command"""
1178 1178
1179 1179 if opts['options']:
1180 1180 options = []
1181 1181 otables = [globalopts]
1182 1182 if cmd:
1183 1183 aliases, entry = find(cmd)
1184 1184 otables.append(entry[1])
1185 1185 for t in otables:
1186 1186 for o in t:
1187 1187 if o[0]:
1188 1188 options.append('-%s' % o[0])
1189 1189 options.append('--%s' % o[1])
1190 1190 ui.write("%s\n" % "\n".join(options))
1191 1191 return
1192 1192
1193 1193 clist = findpossible(cmd).keys()
1194 1194 clist.sort()
1195 1195 ui.write("%s\n" % "\n".join(clist))
1196 1196
1197 1197 def debugrebuildstate(ui, repo, rev=None):
1198 1198 """rebuild the dirstate as it would look like for the given revision"""
1199 1199 if not rev:
1200 1200 rev = repo.changelog.tip()
1201 1201 else:
1202 1202 rev = repo.lookup(rev)
1203 1203 change = repo.changelog.read(rev)
1204 1204 n = change[0]
1205 1205 files = repo.manifest.readflags(n)
1206 1206 wlock = repo.wlock()
1207 1207 repo.dirstate.rebuild(rev, files.iteritems())
1208 1208
1209 1209 def debugcheckstate(ui, repo):
1210 1210 """validate the correctness of the current dirstate"""
1211 1211 parent1, parent2 = repo.dirstate.parents()
1212 1212 repo.dirstate.read()
1213 1213 dc = repo.dirstate.map
1214 1214 keys = dc.keys()
1215 1215 keys.sort()
1216 1216 m1n = repo.changelog.read(parent1)[0]
1217 1217 m2n = repo.changelog.read(parent2)[0]
1218 1218 m1 = repo.manifest.read(m1n)
1219 1219 m2 = repo.manifest.read(m2n)
1220 1220 errors = 0
1221 1221 for f in dc:
1222 1222 state = repo.dirstate.state(f)
1223 1223 if state in "nr" and f not in m1:
1224 1224 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1225 1225 errors += 1
1226 1226 if state in "a" and f in m1:
1227 1227 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1228 1228 errors += 1
1229 1229 if state in "m" and f not in m1 and f not in m2:
1230 1230 ui.warn(_("%s in state %s, but not in either manifest\n") %
1231 1231 (f, state))
1232 1232 errors += 1
1233 1233 for f in m1:
1234 1234 state = repo.dirstate.state(f)
1235 1235 if state not in "nrm":
1236 1236 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1237 1237 errors += 1
1238 1238 if errors:
1239 1239 error = _(".hg/dirstate inconsistent with current parent's manifest")
1240 1240 raise util.Abort(error)
1241 1241
1242 1242 def debugconfig(ui, repo):
1243 1243 """show combined config settings from all hgrc files"""
1244 1244 for section, name, value in ui.walkconfig():
1245 1245 ui.write('%s.%s=%s\n' % (section, name, value))
1246 1246
1247 1247 def debugsetparents(ui, repo, rev1, rev2=None):
1248 1248 """manually set the parents of the current working directory
1249 1249
1250 1250 This is useful for writing repository conversion tools, but should
1251 1251 be used with care.
1252 1252 """
1253 1253
1254 1254 if not rev2:
1255 1255 rev2 = hex(nullid)
1256 1256
1257 1257 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1258 1258
1259 1259 def debugstate(ui, repo):
1260 1260 """show the contents of the current dirstate"""
1261 1261 repo.dirstate.read()
1262 1262 dc = repo.dirstate.map
1263 1263 keys = dc.keys()
1264 1264 keys.sort()
1265 1265 for file_ in keys:
1266 1266 ui.write("%c %3o %10d %s %s\n"
1267 1267 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1268 1268 time.strftime("%x %X",
1269 1269 time.localtime(dc[file_][3])), file_))
1270 1270 for f in repo.dirstate.copies:
1271 1271 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
1272 1272
1273 1273 def debugdata(ui, file_, rev):
1274 1274 """dump the contents of an data file revision"""
1275 1275 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1276 1276 file_[:-2] + ".i", file_, 0)
1277 1277 try:
1278 1278 ui.write(r.revision(r.lookup(rev)))
1279 1279 except KeyError:
1280 1280 raise util.Abort(_('invalid revision identifier %s'), rev)
1281 1281
1282 1282 def debugindex(ui, file_):
1283 1283 """dump the contents of an index file"""
1284 1284 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1285 1285 ui.write(" rev offset length base linkrev" +
1286 1286 " nodeid p1 p2\n")
1287 1287 for i in range(r.count()):
1288 1288 node = r.node(i)
1289 1289 pp = r.parents(node)
1290 1290 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1291 1291 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1292 1292 short(node), short(pp[0]), short(pp[1])))
1293 1293
1294 1294 def debugindexdot(ui, file_):
1295 1295 """dump an index DAG as a .dot file"""
1296 1296 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1297 1297 ui.write("digraph G {\n")
1298 1298 for i in range(r.count()):
1299 1299 e = r.index[i]
1300 1300 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
1301 1301 if e[5] != nullid:
1302 1302 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
1303 1303 ui.write("}\n")
1304 1304
1305 1305 def debugrename(ui, repo, file, rev=None):
1306 1306 """dump rename information"""
1307 1307 r = repo.file(relpath(repo, [file])[0])
1308 1308 if rev:
1309 1309 try:
1310 1310 # assume all revision numbers are for changesets
1311 1311 n = repo.lookup(rev)
1312 1312 change = repo.changelog.read(n)
1313 1313 m = repo.manifest.read(change[0])
1314 1314 n = m[relpath(repo, [file])[0]]
1315 1315 except (hg.RepoError, KeyError):
1316 1316 n = r.lookup(rev)
1317 1317 else:
1318 1318 n = r.tip()
1319 1319 m = r.renamed(n)
1320 1320 if m:
1321 1321 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1322 1322 else:
1323 1323 ui.write(_("not renamed\n"))
1324 1324
1325 1325 def debugwalk(ui, repo, *pats, **opts):
1326 1326 """show how files match on given patterns"""
1327 1327 items = list(walk(repo, pats, opts))
1328 1328 if not items:
1329 1329 return
1330 1330 fmt = '%%s %%-%ds %%-%ds %%s' % (
1331 1331 max([len(abs) for (src, abs, rel, exact) in items]),
1332 1332 max([len(rel) for (src, abs, rel, exact) in items]))
1333 1333 for src, abs, rel, exact in items:
1334 1334 line = fmt % (src, abs, rel, exact and 'exact' or '')
1335 1335 ui.write("%s\n" % line.rstrip())
1336 1336
1337 1337 def diff(ui, repo, *pats, **opts):
1338 1338 """diff repository (or selected files)
1339 1339
1340 1340 Show differences between revisions for the specified files.
1341 1341
1342 1342 Differences between files are shown using the unified diff format.
1343 1343
1344 1344 When two revision arguments are given, then changes are shown
1345 1345 between those revisions. If only one revision is specified then
1346 1346 that revision is compared to the working directory, and, when no
1347 1347 revisions are specified, the working directory files are compared
1348 1348 to its parent.
1349 1349
1350 1350 Without the -a option, diff will avoid generating diffs of files
1351 1351 it detects as binary. With -a, diff will generate a diff anyway,
1352 1352 probably with undesirable results.
1353 1353 """
1354 1354 node1, node2 = None, None
1355 1355 revs = [repo.lookup(x) for x in opts['rev']]
1356 1356
1357 1357 if len(revs) > 0:
1358 1358 node1 = revs[0]
1359 1359 if len(revs) > 1:
1360 1360 node2 = revs[1]
1361 1361 if len(revs) > 2:
1362 1362 raise util.Abort(_("too many revisions to diff"))
1363 1363
1364 1364 fns, matchfn, anypats = matchpats(repo, pats, opts)
1365 1365
1366 1366 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
1367 1367 text=opts['text'], opts=opts)
1368 1368
1369 1369 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
1370 1370 node = repo.lookup(changeset)
1371 1371 parents = [p for p in repo.changelog.parents(node) if p != nullid]
1372 1372 if opts['switch_parent']:
1373 1373 parents.reverse()
1374 1374 prev = (parents and parents[0]) or nullid
1375 1375 change = repo.changelog.read(node)
1376 1376
1377 1377 fp = make_file(repo, repo.changelog, opts['output'],
1378 1378 node=node, total=total, seqno=seqno,
1379 1379 revwidth=revwidth)
1380 1380 if fp != sys.stdout:
1381 1381 ui.note("%s\n" % fp.name)
1382 1382
1383 1383 fp.write("# HG changeset patch\n")
1384 1384 fp.write("# User %s\n" % change[1])
1385 1385 fp.write("# Node ID %s\n" % hex(node))
1386 1386 fp.write("# Parent %s\n" % hex(prev))
1387 1387 if len(parents) > 1:
1388 1388 fp.write("# Parent %s\n" % hex(parents[1]))
1389 1389 fp.write(change[4].rstrip())
1390 1390 fp.write("\n\n")
1391 1391
1392 1392 dodiff(fp, ui, repo, prev, node, text=opts['text'])
1393 1393 if fp != sys.stdout:
1394 1394 fp.close()
1395 1395
1396 1396 def export(ui, repo, *changesets, **opts):
1397 1397 """dump the header and diffs for one or more changesets
1398 1398
1399 1399 Print the changeset header and diffs for one or more revisions.
1400 1400
1401 1401 The information shown in the changeset header is: author,
1402 1402 changeset hash, parent and commit comment.
1403 1403
1404 1404 Output may be to a file, in which case the name of the file is
1405 1405 given using a format string. The formatting rules are as follows:
1406 1406
1407 1407 %% literal "%" character
1408 1408 %H changeset hash (40 bytes of hexadecimal)
1409 1409 %N number of patches being generated
1410 1410 %R changeset revision number
1411 1411 %b basename of the exporting repository
1412 1412 %h short-form changeset hash (12 bytes of hexadecimal)
1413 1413 %n zero-padded sequence number, starting at 1
1414 1414 %r zero-padded changeset revision number
1415 1415
1416 1416 Without the -a option, export will avoid generating diffs of files
1417 1417 it detects as binary. With -a, export will generate a diff anyway,
1418 1418 probably with undesirable results.
1419 1419
1420 1420 With the --switch-parent option, the diff will be against the second
1421 1421 parent. It can be useful to review a merge.
1422 1422 """
1423 1423 if not changesets:
1424 1424 raise util.Abort(_("export requires at least one changeset"))
1425 1425 seqno = 0
1426 1426 revs = list(revrange(ui, repo, changesets))
1427 1427 total = len(revs)
1428 1428 revwidth = max(map(len, revs))
1429 1429 msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
1430 1430 ui.note(msg)
1431 1431 for cset in revs:
1432 1432 seqno += 1
1433 1433 doexport(ui, repo, cset, seqno, total, revwidth, opts)
1434 1434
1435 1435 def forget(ui, repo, *pats, **opts):
1436 1436 """don't add the specified files on the next commit (DEPRECATED)
1437 1437
1438 1438 (DEPRECATED)
1439 1439 Undo an 'hg add' scheduled for the next commit.
1440 1440
1441 1441 This command is now deprecated and will be removed in a future
1442 1442 release. Please use revert instead.
1443 1443 """
1444 1444 ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
1445 1445 forget = []
1446 1446 for src, abs, rel, exact in walk(repo, pats, opts):
1447 1447 if repo.dirstate.state(abs) == 'a':
1448 1448 forget.append(abs)
1449 1449 if ui.verbose or not exact:
1450 1450 ui.status(_('forgetting %s\n') % ((pats and rel) or abs))
1451 1451 repo.forget(forget)
1452 1452
1453 1453 def grep(ui, repo, pattern, *pats, **opts):
1454 1454 """search for a pattern in specified files and revisions
1455 1455
1456 1456 Search revisions of files for a regular expression.
1457 1457
1458 1458 This command behaves differently than Unix grep. It only accepts
1459 1459 Python/Perl regexps. It searches repository history, not the
1460 1460 working directory. It always prints the revision number in which
1461 1461 a match appears.
1462 1462
1463 1463 By default, grep only prints output for the first revision of a
1464 1464 file in which it finds a match. To get it to print every revision
1465 1465 that contains a change in match status ("-" for a match that
1466 1466 becomes a non-match, or "+" for a non-match that becomes a match),
1467 1467 use the --all flag.
1468 1468 """
1469 1469 reflags = 0
1470 1470 if opts['ignore_case']:
1471 1471 reflags |= re.I
1472 1472 regexp = re.compile(pattern, reflags)
1473 1473 sep, eol = ':', '\n'
1474 1474 if opts['print0']:
1475 1475 sep = eol = '\0'
1476 1476
1477 1477 fcache = {}
1478 1478 def getfile(fn):
1479 1479 if fn not in fcache:
1480 1480 fcache[fn] = repo.file(fn)
1481 1481 return fcache[fn]
1482 1482
1483 1483 def matchlines(body):
1484 1484 begin = 0
1485 1485 linenum = 0
1486 1486 while True:
1487 1487 match = regexp.search(body, begin)
1488 1488 if not match:
1489 1489 break
1490 1490 mstart, mend = match.span()
1491 1491 linenum += body.count('\n', begin, mstart) + 1
1492 1492 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1493 1493 lend = body.find('\n', mend)
1494 1494 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1495 1495 begin = lend + 1
1496 1496
1497 1497 class linestate(object):
1498 1498 def __init__(self, line, linenum, colstart, colend):
1499 1499 self.line = line
1500 1500 self.linenum = linenum
1501 1501 self.colstart = colstart
1502 1502 self.colend = colend
1503 1503 def __eq__(self, other):
1504 1504 return self.line == other.line
1505 1505 def __hash__(self):
1506 1506 return hash(self.line)
1507 1507
1508 1508 matches = {}
1509 1509 def grepbody(fn, rev, body):
1510 1510 matches[rev].setdefault(fn, {})
1511 1511 m = matches[rev][fn]
1512 1512 for lnum, cstart, cend, line in matchlines(body):
1513 1513 s = linestate(line, lnum, cstart, cend)
1514 1514 m[s] = s
1515 1515
1516 1516 # FIXME: prev isn't used, why ?
1517 1517 prev = {}
1518 1518 ucache = {}
1519 1519 def display(fn, rev, states, prevstates):
1520 1520 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1521 1521 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1522 1522 counts = {'-': 0, '+': 0}
1523 1523 filerevmatches = {}
1524 1524 for l in diff:
1525 1525 if incrementing or not opts['all']:
1526 1526 change = ((l in prevstates) and '-') or '+'
1527 1527 r = rev
1528 1528 else:
1529 1529 change = ((l in states) and '-') or '+'
1530 1530 r = prev[fn]
1531 1531 cols = [fn, str(rev)]
1532 1532 if opts['line_number']:
1533 1533 cols.append(str(l.linenum))
1534 1534 if opts['all']:
1535 1535 cols.append(change)
1536 1536 if opts['user']:
1537 1537 cols.append(trimuser(ui, getchange(rev)[1], rev,
1538 1538 ucache))
1539 1539 if opts['files_with_matches']:
1540 1540 c = (fn, rev)
1541 1541 if c in filerevmatches:
1542 1542 continue
1543 1543 filerevmatches[c] = 1
1544 1544 else:
1545 1545 cols.append(l.line)
1546 1546 ui.write(sep.join(cols), eol)
1547 1547 counts[change] += 1
1548 1548 return counts['+'], counts['-']
1549 1549
1550 1550 fstate = {}
1551 1551 skip = {}
1552 1552 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1553 1553 count = 0
1554 1554 incrementing = False
1555 1555 for st, rev, fns in changeiter:
1556 1556 if st == 'window':
1557 1557 incrementing = rev
1558 1558 matches.clear()
1559 1559 elif st == 'add':
1560 1560 change = repo.changelog.read(repo.lookup(str(rev)))
1561 1561 mf = repo.manifest.read(change[0])
1562 1562 matches[rev] = {}
1563 1563 for fn in fns:
1564 1564 if fn in skip:
1565 1565 continue
1566 1566 fstate.setdefault(fn, {})
1567 1567 try:
1568 1568 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1569 1569 except KeyError:
1570 1570 pass
1571 1571 elif st == 'iter':
1572 1572 states = matches[rev].items()
1573 1573 states.sort()
1574 1574 for fn, m in states:
1575 1575 if fn in skip:
1576 1576 continue
1577 1577 if incrementing or not opts['all'] or fstate[fn]:
1578 1578 pos, neg = display(fn, rev, m, fstate[fn])
1579 1579 count += pos + neg
1580 1580 if pos and not opts['all']:
1581 1581 skip[fn] = True
1582 1582 fstate[fn] = m
1583 1583 prev[fn] = rev
1584 1584
1585 1585 if not incrementing:
1586 1586 fstate = fstate.items()
1587 1587 fstate.sort()
1588 1588 for fn, state in fstate:
1589 1589 if fn in skip:
1590 1590 continue
1591 1591 display(fn, rev, {}, state)
1592 1592 return (count == 0 and 1) or 0
1593 1593
1594 1594 def heads(ui, repo, **opts):
1595 1595 """show current repository heads
1596 1596
1597 1597 Show all repository head changesets.
1598 1598
1599 1599 Repository "heads" are changesets that don't have children
1600 1600 changesets. They are where development generally takes place and
1601 1601 are the usual targets for update and merge operations.
1602 1602 """
1603 1603 if opts['rev']:
1604 1604 heads = repo.heads(repo.lookup(opts['rev']))
1605 1605 else:
1606 1606 heads = repo.heads()
1607 1607 br = None
1608 1608 if opts['branches']:
1609 1609 br = repo.branchlookup(heads)
1610 1610 displayer = show_changeset(ui, repo, opts)
1611 1611 for n in heads:
1612 1612 displayer.show(changenode=n, brinfo=br)
1613 1613
1614 1614 def identify(ui, repo):
1615 1615 """print information about the working copy
1616 1616
1617 1617 Print a short summary of the current state of the repo.
1618 1618
1619 1619 This summary identifies the repository state using one or two parent
1620 1620 hash identifiers, followed by a "+" if there are uncommitted changes
1621 1621 in the working directory, followed by a list of tags for this revision.
1622 1622 """
1623 1623 parents = [p for p in repo.dirstate.parents() if p != nullid]
1624 1624 if not parents:
1625 1625 ui.write(_("unknown\n"))
1626 1626 return
1627 1627
1628 1628 hexfunc = ui.verbose and hex or short
1629 1629 modified, added, removed, deleted, unknown = repo.changes()
1630 1630 output = ["%s%s" %
1631 1631 ('+'.join([hexfunc(parent) for parent in parents]),
1632 1632 (modified or added or removed or deleted) and "+" or "")]
1633 1633
1634 1634 if not ui.quiet:
1635 1635 # multiple tags for a single parent separated by '/'
1636 1636 parenttags = ['/'.join(tags)
1637 1637 for tags in map(repo.nodetags, parents) if tags]
1638 1638 # tags for multiple parents separated by ' + '
1639 1639 if parenttags:
1640 1640 output.append(' + '.join(parenttags))
1641 1641
1642 1642 ui.write("%s\n" % ' '.join(output))
1643 1643
1644 1644 def import_(ui, repo, patch1, *patches, **opts):
1645 1645 """import an ordered set of patches
1646 1646
1647 1647 Import a list of patches and commit them individually.
1648 1648
1649 1649 If there are outstanding changes in the working directory, import
1650 1650 will abort unless given the -f flag.
1651 1651
1652 1652 If a patch looks like a mail message (its first line starts with
1653 1653 "From " or looks like an RFC822 header), it will not be applied
1654 1654 unless the -f option is used. The importer neither parses nor
1655 1655 discards mail headers, so use -f only to override the "mailness"
1656 1656 safety check, not to import a real mail message.
1657 1657 """
1658 1658 patches = (patch1,) + patches
1659 1659
1660 1660 if not opts['force']:
1661 1661 bail_if_changed(repo)
1662 1662
1663 1663 d = opts["base"]
1664 1664 strip = opts["strip"]
1665 1665
1666 1666 mailre = re.compile(r'(?:From |[\w-]+:)')
1667 1667
1668 1668 # attempt to detect the start of a patch
1669 1669 # (this heuristic is borrowed from quilt)
1670 1670 diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' +
1671 1671 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
1672 1672 '(---|\*\*\*)[ \t])')
1673 1673
1674 1674 for patch in patches:
1675 1675 ui.status(_("applying %s\n") % patch)
1676 1676 pf = os.path.join(d, patch)
1677 1677
1678 1678 message = []
1679 1679 user = None
1680 1680 hgpatch = False
1681 1681 for line in file(pf):
1682 1682 line = line.rstrip()
1683 1683 if (not message and not hgpatch and
1684 1684 mailre.match(line) and not opts['force']):
1685 1685 if len(line) > 35:
1686 1686 line = line[:32] + '...'
1687 1687 raise util.Abort(_('first line looks like a '
1688 1688 'mail header: ') + line)
1689 1689 if diffre.match(line):
1690 1690 break
1691 1691 elif hgpatch:
1692 1692 # parse values when importing the result of an hg export
1693 1693 if line.startswith("# User "):
1694 1694 user = line[7:]
1695 1695 ui.debug(_('User: %s\n') % user)
1696 1696 elif not line.startswith("# ") and line:
1697 1697 message.append(line)
1698 1698 hgpatch = False
1699 1699 elif line == '# HG changeset patch':
1700 1700 hgpatch = True
1701 1701 message = [] # We may have collected garbage
1702 1702 else:
1703 1703 message.append(line)
1704 1704
1705 1705 # make sure message isn't empty
1706 1706 if not message:
1707 1707 message = _("imported patch %s\n") % patch
1708 1708 else:
1709 1709 message = "%s\n" % '\n'.join(message)
1710 1710 ui.debug(_('message:\n%s\n') % message)
1711 1711
1712 1712 files = util.patch(strip, pf, ui)
1713 1713
1714 1714 if len(files) > 0:
1715 1715 addremove_lock(ui, repo, files, {})
1716 1716 repo.commit(files, message, user)
1717 1717
1718 1718 def incoming(ui, repo, source="default", **opts):
1719 1719 """show new changesets found in source
1720 1720
1721 1721 Show new changesets found in the specified path/URL or the default
1722 1722 pull location. These are the changesets that would be pulled if a pull
1723 1723 was requested.
1724 1724
1725 1725 For remote repository, using --bundle avoids downloading the changesets
1726 1726 twice if the incoming is followed by a pull.
1727 1727
1728 1728 See pull for valid source format details.
1729 1729 """
1730 1730 source = ui.expandpath(source)
1731 1731 if opts['ssh']:
1732 1732 ui.setconfig("ui", "ssh", opts['ssh'])
1733 1733 if opts['remotecmd']:
1734 1734 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1735 1735
1736 1736 other = hg.repository(ui, source)
1737 1737 incoming = repo.findincoming(other, force=opts["force"])
1738 1738 if not incoming:
1739 1739 ui.status(_("no changes found\n"))
1740 1740 return
1741 1741
1742 1742 cleanup = None
1743 1743 try:
1744 1744 fname = opts["bundle"]
1745 1745 if fname or not other.local():
1746 1746 # create a bundle (uncompressed if other repo is not local)
1747 1747 cg = other.changegroup(incoming, "incoming")
1748 1748 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1749 1749 # keep written bundle?
1750 1750 if opts["bundle"]:
1751 1751 cleanup = None
1752 1752 if not other.local():
1753 1753 # use the created uncompressed bundlerepo
1754 1754 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1755 1755
1756 1756 o = other.changelog.nodesbetween(incoming)[0]
1757 1757 if opts['newest_first']:
1758 1758 o.reverse()
1759 1759 displayer = show_changeset(ui, other, opts)
1760 1760 for n in o:
1761 1761 parents = [p for p in other.changelog.parents(n) if p != nullid]
1762 1762 if opts['no_merges'] and len(parents) == 2:
1763 1763 continue
1764 1764 displayer.show(changenode=n)
1765 1765 if opts['patch']:
1766 1766 prev = (parents and parents[0]) or nullid
1767 1767 dodiff(ui, ui, other, prev, n)
1768 1768 ui.write("\n")
1769 1769 finally:
1770 1770 if hasattr(other, 'close'):
1771 1771 other.close()
1772 1772 if cleanup:
1773 1773 os.unlink(cleanup)
1774 1774
1775 1775 def init(ui, dest="."):
1776 1776 """create a new repository in the given directory
1777 1777
1778 1778 Initialize a new repository in the given directory. If the given
1779 1779 directory does not exist, it is created.
1780 1780
1781 1781 If no directory is given, the current directory is used.
1782 1782 """
1783 1783 if not os.path.exists(dest):
1784 1784 os.mkdir(dest)
1785 1785 hg.repository(ui, dest, create=1)
1786 1786
1787 1787 def locate(ui, repo, *pats, **opts):
1788 1788 """locate files matching specific patterns
1789 1789
1790 1790 Print all files under Mercurial control whose names match the
1791 1791 given patterns.
1792 1792
1793 1793 This command searches the current directory and its
1794 1794 subdirectories. To search an entire repository, move to the root
1795 1795 of the repository.
1796 1796
1797 1797 If no patterns are given to match, this command prints all file
1798 1798 names.
1799 1799
1800 1800 If you want to feed the output of this command into the "xargs"
1801 1801 command, use the "-0" option to both this command and "xargs".
1802 1802 This will avoid the problem of "xargs" treating single filenames
1803 1803 that contain white space as multiple filenames.
1804 1804 """
1805 1805 end = opts['print0'] and '\0' or '\n'
1806 1806 rev = opts['rev']
1807 1807 if rev:
1808 1808 node = repo.lookup(rev)
1809 1809 else:
1810 1810 node = None
1811 1811
1812 1812 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
1813 1813 head='(?:.*/|)'):
1814 1814 if not node and repo.dirstate.state(abs) == '?':
1815 1815 continue
1816 1816 if opts['fullpath']:
1817 1817 ui.write(os.path.join(repo.root, abs), end)
1818 1818 else:
1819 1819 ui.write(((pats and rel) or abs), end)
1820 1820
1821 1821 def log(ui, repo, *pats, **opts):
1822 1822 """show revision history of entire repository or files
1823 1823
1824 1824 Print the revision history of the specified files or the entire project.
1825 1825
1826 1826 By default this command outputs: changeset id and hash, tags,
1827 1827 non-trivial parents, user, date and time, and a summary for each
1828 1828 commit. When the -v/--verbose switch is used, the list of changed
1829 1829 files and full commit message is shown.
1830 1830 """
1831 1831 class dui(object):
1832 1832 # Implement and delegate some ui protocol. Save hunks of
1833 1833 # output for later display in the desired order.
1834 1834 def __init__(self, ui):
1835 1835 self.ui = ui
1836 1836 self.hunk = {}
1837 1837 self.header = {}
1838 1838 def bump(self, rev):
1839 1839 self.rev = rev
1840 1840 self.hunk[rev] = []
1841 1841 self.header[rev] = []
1842 1842 def note(self, *args):
1843 1843 if self.verbose:
1844 1844 self.write(*args)
1845 1845 def status(self, *args):
1846 1846 if not self.quiet:
1847 1847 self.write(*args)
1848 1848 def write(self, *args):
1849 1849 self.hunk[self.rev].append(args)
1850 1850 def write_header(self, *args):
1851 1851 self.header[self.rev].append(args)
1852 1852 def debug(self, *args):
1853 1853 if self.debugflag:
1854 1854 self.write(*args)
1855 1855 def __getattr__(self, key):
1856 1856 return getattr(self.ui, key)
1857 1857
1858 1858 changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
1859 1859
1860 1860 if opts['limit']:
1861 1861 try:
1862 1862 limit = int(opts['limit'])
1863 1863 except ValueError:
1864 1864 raise util.Abort(_('limit must be a positive integer'))
1865 1865 if limit <= 0: raise util.Abort(_('limit must be positive'))
1866 1866 else:
1867 1867 limit = sys.maxint
1868 1868 count = 0
1869 1869
1870 1870 displayer = show_changeset(ui, repo, opts)
1871 1871 for st, rev, fns in changeiter:
1872 1872 if st == 'window':
1873 1873 du = dui(ui)
1874 1874 displayer.ui = du
1875 1875 elif st == 'add':
1876 1876 du.bump(rev)
1877 1877 changenode = repo.changelog.node(rev)
1878 1878 parents = [p for p in repo.changelog.parents(changenode)
1879 1879 if p != nullid]
1880 1880 if opts['no_merges'] and len(parents) == 2:
1881 1881 continue
1882 1882 if opts['only_merges'] and len(parents) != 2:
1883 1883 continue
1884 1884
1885 1885 if opts['keyword']:
1886 1886 changes = getchange(rev)
1887 1887 miss = 0
1888 1888 for k in [kw.lower() for kw in opts['keyword']]:
1889 1889 if not (k in changes[1].lower() or
1890 1890 k in changes[4].lower() or
1891 1891 k in " ".join(changes[3][:20]).lower()):
1892 1892 miss = 1
1893 1893 break
1894 1894 if miss:
1895 1895 continue
1896 1896
1897 1897 br = None
1898 1898 if opts['branches']:
1899 1899 br = repo.branchlookup([repo.changelog.node(rev)])
1900 1900
1901 1901 displayer.show(rev, brinfo=br)
1902 1902 if opts['patch']:
1903 1903 prev = (parents and parents[0]) or nullid
1904 1904 dodiff(du, du, repo, prev, changenode, match=matchfn)
1905 1905 du.write("\n\n")
1906 1906 elif st == 'iter':
1907 1907 if count == limit: break
1908 1908 if du.header[rev]:
1909 1909 for args in du.header[rev]:
1910 1910 ui.write_header(*args)
1911 1911 if du.hunk[rev]:
1912 1912 count += 1
1913 1913 for args in du.hunk[rev]:
1914 1914 ui.write(*args)
1915 1915
1916 1916 def manifest(ui, repo, rev=None):
1917 1917 """output the latest or given revision of the project manifest
1918 1918
1919 1919 Print a list of version controlled files for the given revision.
1920 1920
1921 1921 The manifest is the list of files being version controlled. If no revision
1922 1922 is given then the tip is used.
1923 1923 """
1924 1924 if rev:
1925 1925 try:
1926 1926 # assume all revision numbers are for changesets
1927 1927 n = repo.lookup(rev)
1928 1928 change = repo.changelog.read(n)
1929 1929 n = change[0]
1930 1930 except hg.RepoError:
1931 1931 n = repo.manifest.lookup(rev)
1932 1932 else:
1933 1933 n = repo.manifest.tip()
1934 1934 m = repo.manifest.read(n)
1935 1935 mf = repo.manifest.readflags(n)
1936 1936 files = m.keys()
1937 1937 files.sort()
1938 1938
1939 1939 for f in files:
1940 1940 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1941 1941
1942 1942 def merge(ui, repo, node=None, **opts):
1943 1943 """Merge working directory with another revision
1944 1944
1945 1945 Merge the contents of the current working directory and the
1946 1946 requested revision. Files that changed between either parent are
1947 1947 marked as changed for the next commit and a commit must be
1948 1948 performed before any further updates are allowed.
1949 1949 """
1950 1950 return update(ui, repo, node=node, merge=True, **opts)
1951 1951
1952 1952 def outgoing(ui, repo, dest="default-push", **opts):
1953 1953 """show changesets not found in destination
1954 1954
1955 1955 Show changesets not found in the specified destination repository or
1956 1956 the default push location. These are the changesets that would be pushed
1957 1957 if a push was requested.
1958 1958
1959 1959 See pull for valid destination format details.
1960 1960 """
1961 1961 dest = ui.expandpath(dest)
1962 1962 if opts['ssh']:
1963 1963 ui.setconfig("ui", "ssh", opts['ssh'])
1964 1964 if opts['remotecmd']:
1965 1965 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1966 1966
1967 1967 other = hg.repository(ui, dest)
1968 1968 o = repo.findoutgoing(other, force=opts['force'])
1969 1969 if not o:
1970 1970 ui.status(_("no changes found\n"))
1971 1971 return
1972 1972 o = repo.changelog.nodesbetween(o)[0]
1973 1973 if opts['newest_first']:
1974 1974 o.reverse()
1975 1975 displayer = show_changeset(ui, repo, opts)
1976 1976 for n in o:
1977 1977 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1978 1978 if opts['no_merges'] and len(parents) == 2:
1979 1979 continue
1980 1980 displayer.show(changenode=n)
1981 1981 if opts['patch']:
1982 1982 prev = (parents and parents[0]) or nullid
1983 1983 dodiff(ui, ui, repo, prev, n)
1984 1984 ui.write("\n")
1985 1985
1986 1986 def parents(ui, repo, rev=None, branches=None, **opts):
1987 1987 """show the parents of the working dir or revision
1988 1988
1989 1989 Print the working directory's parent revisions.
1990 1990 """
1991 1991 if rev:
1992 1992 p = repo.changelog.parents(repo.lookup(rev))
1993 1993 else:
1994 1994 p = repo.dirstate.parents()
1995 1995
1996 1996 br = None
1997 1997 if branches is not None:
1998 1998 br = repo.branchlookup(p)
1999 1999 displayer = show_changeset(ui, repo, opts)
2000 2000 for n in p:
2001 2001 if n != nullid:
2002 2002 displayer.show(changenode=n, brinfo=br)
2003 2003
2004 2004 def paths(ui, repo, search=None):
2005 2005 """show definition of symbolic path names
2006 2006
2007 2007 Show definition of symbolic path name NAME. If no name is given, show
2008 2008 definition of available names.
2009 2009
2010 2010 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2011 2011 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2012 2012 """
2013 2013 if search:
2014 2014 for name, path in ui.configitems("paths"):
2015 2015 if name == search:
2016 2016 ui.write("%s\n" % path)
2017 2017 return
2018 2018 ui.warn(_("not found!\n"))
2019 2019 return 1
2020 2020 else:
2021 2021 for name, path in ui.configitems("paths"):
2022 2022 ui.write("%s = %s\n" % (name, path))
2023 2023
2024 2024 def postincoming(ui, repo, modheads, optupdate):
2025 2025 if modheads == 0:
2026 2026 return
2027 2027 if optupdate:
2028 2028 if modheads == 1:
2029 2029 return update(ui, repo)
2030 2030 else:
2031 2031 ui.status(_("not updating, since new heads added\n"))
2032 2032 if modheads > 1:
2033 2033 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2034 2034 else:
2035 2035 ui.status(_("(run 'hg update' to get a working copy)\n"))
2036 2036
2037 2037 def pull(ui, repo, source="default", **opts):
2038 2038 """pull changes from the specified source
2039 2039
2040 2040 Pull changes from a remote repository to a local one.
2041 2041
2042 2042 This finds all changes from the repository at the specified path
2043 2043 or URL and adds them to the local repository. By default, this
2044 2044 does not update the copy of the project in the working directory.
2045 2045
2046 2046 Valid URLs are of the form:
2047 2047
2048 2048 local/filesystem/path
2049 2049 http://[user@]host[:port][/path]
2050 2050 https://[user@]host[:port][/path]
2051 2051 ssh://[user@]host[:port][/path]
2052 2052
2053 2053 Some notes about using SSH with Mercurial:
2054 2054 - SSH requires an accessible shell account on the destination machine
2055 2055 and a copy of hg in the remote path or specified with as remotecmd.
2056 2056 - /path is relative to the remote user's home directory by default.
2057 2057 Use two slashes at the start of a path to specify an absolute path.
2058 2058 - Mercurial doesn't use its own compression via SSH; the right thing
2059 2059 to do is to configure it in your ~/.ssh/ssh_config, e.g.:
2060 2060 Host *.mylocalnetwork.example.com
2061 2061 Compression off
2062 2062 Host *
2063 2063 Compression on
2064 2064 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2065 2065 with the --ssh command line option.
2066 2066 """
2067 2067 source = ui.expandpath(source)
2068 2068 ui.status(_('pulling from %s\n') % (source))
2069 2069
2070 2070 if opts['ssh']:
2071 2071 ui.setconfig("ui", "ssh", opts['ssh'])
2072 2072 if opts['remotecmd']:
2073 2073 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2074 2074
2075 2075 other = hg.repository(ui, source)
2076 2076 revs = None
2077 2077 if opts['rev'] and not other.local():
2078 2078 raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
2079 2079 elif opts['rev']:
2080 2080 revs = [other.lookup(rev) for rev in opts['rev']]
2081 2081 modheads = repo.pull(other, heads=revs, force=opts['force'])
2082 2082 return postincoming(ui, repo, modheads, opts['update'])
2083 2083
2084 2084 def push(ui, repo, dest="default-push", **opts):
2085 2085 """push changes to the specified destination
2086 2086
2087 2087 Push changes from the local repository to the given destination.
2088 2088
2089 2089 This is the symmetrical operation for pull. It helps to move
2090 2090 changes from the current repository to a different one. If the
2091 2091 destination is local this is identical to a pull in that directory
2092 2092 from the current one.
2093 2093
2094 2094 By default, push will refuse to run if it detects the result would
2095 2095 increase the number of remote heads. This generally indicates the
2096 2096 the client has forgotten to sync and merge before pushing.
2097 2097
2098 2098 Valid URLs are of the form:
2099 2099
2100 2100 local/filesystem/path
2101 2101 ssh://[user@]host[:port][/path]
2102 2102
2103 2103 Look at the help text for the pull command for important details
2104 2104 about ssh:// URLs.
2105 2105 """
2106 2106 dest = ui.expandpath(dest)
2107 2107 ui.status('pushing to %s\n' % (dest))
2108 2108
2109 2109 if opts['ssh']:
2110 2110 ui.setconfig("ui", "ssh", opts['ssh'])
2111 2111 if opts['remotecmd']:
2112 2112 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
2113 2113
2114 2114 other = hg.repository(ui, dest)
2115 2115 revs = None
2116 2116 if opts['rev']:
2117 2117 revs = [repo.lookup(rev) for rev in opts['rev']]
2118 2118 r = repo.push(other, opts['force'], revs=revs)
2119 2119 return r == 0
2120 2120
2121 2121 def rawcommit(ui, repo, *flist, **rc):
2122 2122 """raw commit interface (DEPRECATED)
2123 2123
2124 2124 (DEPRECATED)
2125 2125 Lowlevel commit, for use in helper scripts.
2126 2126
2127 2127 This command is not intended to be used by normal users, as it is
2128 2128 primarily useful for importing from other SCMs.
2129 2129
2130 2130 This command is now deprecated and will be removed in a future
2131 2131 release, please use debugsetparents and commit instead.
2132 2132 """
2133 2133
2134 2134 ui.warn(_("(the rawcommit command is deprecated)\n"))
2135 2135
2136 2136 message = rc['message']
2137 2137 if not message and rc['logfile']:
2138 2138 try:
2139 2139 message = open(rc['logfile']).read()
2140 2140 except IOError:
2141 2141 pass
2142 2142 if not message and not rc['logfile']:
2143 2143 raise util.Abort(_("missing commit message"))
2144 2144
2145 2145 files = relpath(repo, list(flist))
2146 2146 if rc['files']:
2147 2147 files += open(rc['files']).read().splitlines()
2148 2148
2149 2149 rc['parent'] = map(repo.lookup, rc['parent'])
2150 2150
2151 2151 try:
2152 2152 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2153 2153 except ValueError, inst:
2154 2154 raise util.Abort(str(inst))
2155 2155
2156 2156 def recover(ui, repo):
2157 2157 """roll back an interrupted transaction
2158 2158
2159 2159 Recover from an interrupted commit or pull.
2160 2160
2161 2161 This command tries to fix the repository status after an interrupted
2162 2162 operation. It should only be necessary when Mercurial suggests it.
2163 2163 """
2164 2164 if repo.recover():
2165 2165 return repo.verify()
2166 2166 return 1
2167 2167
2168 2168 def remove(ui, repo, *pats, **opts):
2169 2169 """remove the specified files on the next commit
2170 2170
2171 2171 Schedule the indicated files for removal from the repository.
2172 2172
2173 2173 This command schedules the files to be removed at the next commit.
2174 2174 This only removes files from the current branch, not from the
2175 2175 entire project history. If the files still exist in the working
2176 2176 directory, they will be deleted from it. If invoked with --after,
2177 2177 files that have been manually deleted are marked as removed.
2178 2178 """
2179 2179 names = []
2180 2180 if not opts['after'] and not pats:
2181 2181 raise util.Abort(_('no files specified'))
2182 2182 def okaytoremove(abs, rel, exact):
2183 2183 modified, added, removed, deleted, unknown = repo.changes(files=[abs])
2184 2184 reason = None
2185 2185 if not deleted and opts['after']:
2186 2186 reason = _('is still present')
2187 2187 elif modified and not opts['force']:
2188 2188 reason = _('is modified')
2189 2189 elif added:
2190 2190 reason = _('has been marked for add')
2191 2191 elif unknown:
2192 2192 reason = _('is not managed')
2193 2193 elif removed:
2194 2194 return False
2195 2195 if reason:
2196 2196 if exact:
2197 2197 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2198 2198 else:
2199 2199 return True
2200 2200 for src, abs, rel, exact in walk(repo, pats, opts):
2201 2201 if okaytoremove(abs, rel, exact):
2202 2202 if ui.verbose or not exact:
2203 2203 ui.status(_('removing %s\n') % rel)
2204 2204 names.append(abs)
2205 2205 repo.remove(names, unlink=not opts['after'])
2206 2206
2207 2207 def rename(ui, repo, *pats, **opts):
2208 2208 """rename files; equivalent of copy + remove
2209 2209
2210 2210 Mark dest as copies of sources; mark sources for deletion. If
2211 2211 dest is a directory, copies are put in that directory. If dest is
2212 2212 a file, there can only be one source.
2213 2213
2214 2214 By default, this command copies the contents of files as they
2215 2215 stand in the working directory. If invoked with --after, the
2216 2216 operation is recorded, but no copying is performed.
2217 2217
2218 2218 This command takes effect in the next commit.
2219 2219
2220 2220 NOTE: This command should be treated as experimental. While it
2221 2221 should properly record rename files, this information is not yet
2222 2222 fully used by merge, nor fully reported by log.
2223 2223 """
2224 2224 wlock = repo.wlock(0)
2225 2225 errs, copied = docopy(ui, repo, pats, opts, wlock)
2226 2226 names = []
2227 2227 for abs, rel, exact in copied:
2228 2228 if ui.verbose or not exact:
2229 2229 ui.status(_('removing %s\n') % rel)
2230 2230 names.append(abs)
2231 2231 repo.remove(names, True, wlock)
2232 2232 return errs
2233 2233
2234 2234 def revert(ui, repo, *pats, **opts):
2235 2235 """revert files or dirs to their states as of some revision
2236 2236
2237 2237 With no revision specified, revert the named files or directories
2238 2238 to the contents they had in the parent of the working directory.
2239 2239 This restores the contents of the affected files to an unmodified
2240 2240 state. If the working directory has two parents, you must
2241 2241 explicitly specify the revision to revert to.
2242 2242
2243 2243 Modified files are saved with a .orig suffix before reverting.
2244 2244 To disable these backups, use --no-backup.
2245 2245
2246 2246 Using the -r option, revert the given files or directories to
2247 2247 their contents as of a specific revision. This can be helpful to"roll
2248 2248 back" some or all of a change that should not have been committed.
2249 2249
2250 2250 Revert modifies the working directory. It does not commit any
2251 2251 changes, or change the parent of the working directory. If you
2252 2252 revert to a revision other than the parent of the working
2253 2253 directory, the reverted files will thus appear modified
2254 2254 afterwards.
2255 2255
2256 2256 If a file has been deleted, it is recreated. If the executable
2257 2257 mode of a file was changed, it is reset.
2258 2258
2259 2259 If names are given, all files matching the names are reverted.
2260 2260
2261 2261 If no arguments are given, all files in the repository are reverted.
2262 2262 """
2263 2263 parent, p2 = repo.dirstate.parents()
2264 2264 if opts['rev']:
2265 2265 node = repo.lookup(opts['rev'])
2266 2266 elif p2 != nullid:
2267 2267 raise util.Abort(_('working dir has two parents; '
2268 2268 'you must specify the revision to revert to'))
2269 2269 else:
2270 2270 node = parent
2271 2271 mf = repo.manifest.read(repo.changelog.read(node)[0])
2272 2272
2273 2273 wlock = repo.wlock()
2274 2274
2275 2275 # need all matching names in dirstate and manifest of target rev,
2276 2276 # so have to walk both. do not print errors if files exist in one
2277 2277 # but not other.
2278 2278
2279 2279 names = {}
2280 2280 target_only = {}
2281 2281
2282 2282 # walk dirstate.
2283 2283
2284 2284 for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
2285 2285 names[abs] = (rel, exact)
2286 2286 if src == 'b':
2287 2287 target_only[abs] = True
2288 2288
2289 2289 # walk target manifest.
2290 2290
2291 2291 for src, abs, rel, exact in walk(repo, pats, opts, node=node,
2292 2292 badmatch=names.has_key):
2293 2293 if abs in names: continue
2294 2294 names[abs] = (rel, exact)
2295 2295 target_only[abs] = True
2296 2296
2297 2297 changes = repo.changes(match=names.has_key, wlock=wlock)
2298 2298 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2299 2299
2300 2300 revert = ([], _('reverting %s\n'))
2301 2301 add = ([], _('adding %s\n'))
2302 2302 remove = ([], _('removing %s\n'))
2303 2303 forget = ([], _('forgetting %s\n'))
2304 2304 undelete = ([], _('undeleting %s\n'))
2305 2305 update = {}
2306 2306
2307 2307 disptable = (
2308 2308 # dispatch table:
2309 2309 # file state
2310 2310 # action if in target manifest
2311 2311 # action if not in target manifest
2312 2312 # make backup if in target manifest
2313 2313 # make backup if not in target manifest
2314 2314 (modified, revert, remove, True, True),
2315 2315 (added, revert, forget, True, False),
2316 2316 (removed, undelete, None, False, False),
2317 2317 (deleted, revert, remove, False, False),
2318 2318 (unknown, add, None, True, False),
2319 2319 (target_only, add, None, False, False),
2320 2320 )
2321 2321
2322 2322 entries = names.items()
2323 2323 entries.sort()
2324 2324
2325 2325 for abs, (rel, exact) in entries:
2326 2326 in_mf = abs in mf
2327 2327 def handle(xlist, dobackup):
2328 2328 xlist[0].append(abs)
2329 2329 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2330 2330 bakname = "%s.orig" % rel
2331 2331 ui.note(_('saving current version of %s as %s\n') %
2332 2332 (rel, bakname))
2333 2333 shutil.copyfile(rel, bakname)
2334 2334 shutil.copymode(rel, bakname)
2335 2335 if ui.verbose or not exact:
2336 2336 ui.status(xlist[1] % rel)
2337 2337 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2338 2338 if abs not in table: continue
2339 2339 # file has changed in dirstate
2340 2340 if in_mf:
2341 2341 handle(hitlist, backuphit)
2342 2342 elif misslist is not None:
2343 2343 handle(misslist, backupmiss)
2344 2344 else:
2345 2345 if exact: ui.warn(_('file not managed: %s\n' % rel))
2346 2346 break
2347 2347 else:
2348 2348 # file has not changed in dirstate
2349 2349 if node == parent:
2350 2350 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2351 2351 continue
2352 2352 if not in_mf:
2353 2353 handle(remove, False)
2354 2354 update[abs] = True
2355 2355
2356 2356 repo.dirstate.forget(forget[0])
2357 2357 r = repo.update(node, False, True, update.has_key, False, wlock=wlock,
2358 2358 show_stats=False)
2359 2359 repo.dirstate.update(add[0], 'a')
2360 2360 repo.dirstate.update(undelete[0], 'n')
2361 2361 repo.dirstate.update(remove[0], 'r')
2362 2362 return r
2363 2363
2364 2364 def rollback(ui, repo):
2365 2365 """roll back the last transaction in this repository
2366 2366
2367 2367 Roll back the last transaction in this repository, restoring the
2368 2368 project to its state prior to the transaction.
2369 2369
2370 2370 Transactions are used to encapsulate the effects of all commands
2371 2371 that create new changesets or propagate existing changesets into a
2372 2372 repository. For example, the following commands are transactional,
2373 2373 and their effects can be rolled back:
2374 2374
2375 2375 commit
2376 2376 import
2377 2377 pull
2378 2378 push (with this repository as destination)
2379 2379 unbundle
2380 2380
2381 2381 This command should be used with care. There is only one level of
2382 2382 rollback, and there is no way to undo a rollback.
2383 2383
2384 2384 This command is not intended for use on public repositories. Once
2385 2385 changes are visible for pull by other users, rolling a transaction
2386 2386 back locally is ineffective (someone else may already have pulled
2387 2387 the changes). Furthermore, a race is possible with readers of the
2388 2388 repository; for example an in-progress pull from the repository
2389 2389 may fail if a rollback is performed.
2390 2390 """
2391 2391 repo.undo()
2392 2392
2393 2393 def root(ui, repo):
2394 2394 """print the root (top) of the current working dir
2395 2395
2396 2396 Print the root directory of the current repository.
2397 2397 """
2398 2398 ui.write(repo.root + "\n")
2399 2399
2400 2400 def serve(ui, repo, **opts):
2401 2401 """export the repository via HTTP
2402 2402
2403 2403 Start a local HTTP repository browser and pull server.
2404 2404
2405 2405 By default, the server logs accesses to stdout and errors to
2406 2406 stderr. Use the "-A" and "-E" options to log to files.
2407 2407 """
2408 2408
2409 2409 if opts["stdio"]:
2410 2410 if repo is None:
2411 2411 raise hg.RepoError(_('no repo found'))
2412 2412 fin, fout = sys.stdin, sys.stdout
2413 2413 sys.stdout = sys.stderr
2414 2414
2415 2415 # Prevent insertion/deletion of CRs
2416 2416 util.set_binary(fin)
2417 2417 util.set_binary(fout)
2418 2418
2419 2419 def getarg():
2420 2420 argline = fin.readline()[:-1]
2421 2421 arg, l = argline.split()
2422 2422 val = fin.read(int(l))
2423 2423 return arg, val
2424 2424 def respond(v):
2425 2425 fout.write("%d\n" % len(v))
2426 2426 fout.write(v)
2427 2427 fout.flush()
2428 2428
2429 2429 lock = None
2430 2430
2431 2431 while 1:
2432 2432 cmd = fin.readline()[:-1]
2433 2433 if cmd == '':
2434 2434 return
2435 2435 if cmd == "heads":
2436 2436 h = repo.heads()
2437 2437 respond(" ".join(map(hex, h)) + "\n")
2438 2438 if cmd == "lock":
2439 2439 lock = repo.lock()
2440 2440 respond("")
2441 2441 if cmd == "unlock":
2442 2442 if lock:
2443 2443 lock.release()
2444 2444 lock = None
2445 2445 respond("")
2446 2446 elif cmd == "branches":
2447 2447 arg, nodes = getarg()
2448 2448 nodes = map(bin, nodes.split(" "))
2449 2449 r = []
2450 2450 for b in repo.branches(nodes):
2451 2451 r.append(" ".join(map(hex, b)) + "\n")
2452 2452 respond("".join(r))
2453 2453 elif cmd == "between":
2454 2454 arg, pairs = getarg()
2455 2455 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
2456 2456 r = []
2457 2457 for b in repo.between(pairs):
2458 2458 r.append(" ".join(map(hex, b)) + "\n")
2459 2459 respond("".join(r))
2460 2460 elif cmd == "changegroup":
2461 2461 nodes = []
2462 2462 arg, roots = getarg()
2463 2463 nodes = map(bin, roots.split(" "))
2464 2464
2465 2465 cg = repo.changegroup(nodes, 'serve')
2466 2466 while 1:
2467 2467 d = cg.read(4096)
2468 2468 if not d:
2469 2469 break
2470 2470 fout.write(d)
2471 2471
2472 2472 fout.flush()
2473 2473
2474 2474 elif cmd == "addchangegroup":
2475 2475 if not lock:
2476 2476 respond("not locked")
2477 2477 continue
2478 2478 respond("")
2479 2479
2480 r = repo.addchangegroup(fin)
2480 r = repo.addchangegroup(fin, 'serve')
2481 2481 respond(str(r))
2482 2482
2483 2483 optlist = ("name templates style address port ipv6"
2484 2484 " accesslog errorlog webdir_conf")
2485 2485 for o in optlist.split():
2486 2486 if opts[o]:
2487 2487 ui.setconfig("web", o, opts[o])
2488 2488
2489 2489 if repo is None and not ui.config("web", "webdir_conf"):
2490 2490 raise hg.RepoError(_('no repo found'))
2491 2491
2492 2492 if opts['daemon'] and not opts['daemon_pipefds']:
2493 2493 rfd, wfd = os.pipe()
2494 2494 args = sys.argv[:]
2495 2495 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2496 2496 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2497 2497 args[0], args)
2498 2498 os.close(wfd)
2499 2499 os.read(rfd, 1)
2500 2500 os._exit(0)
2501 2501
2502 2502 try:
2503 2503 httpd = hgweb.create_server(ui, repo)
2504 2504 except socket.error, inst:
2505 2505 raise util.Abort(_('cannot start server: ') + inst.args[1])
2506 2506
2507 2507 if ui.verbose:
2508 2508 addr, port = httpd.socket.getsockname()
2509 2509 if addr == '0.0.0.0':
2510 2510 addr = socket.gethostname()
2511 2511 else:
2512 2512 try:
2513 2513 addr = socket.gethostbyaddr(addr)[0]
2514 2514 except socket.error:
2515 2515 pass
2516 2516 if port != 80:
2517 2517 ui.status(_('listening at http://%s:%d/\n') % (addr, port))
2518 2518 else:
2519 2519 ui.status(_('listening at http://%s/\n') % addr)
2520 2520
2521 2521 if opts['pid_file']:
2522 2522 fp = open(opts['pid_file'], 'w')
2523 2523 fp.write(str(os.getpid()))
2524 2524 fp.close()
2525 2525
2526 2526 if opts['daemon_pipefds']:
2527 2527 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2528 2528 os.close(rfd)
2529 2529 os.write(wfd, 'y')
2530 2530 os.close(wfd)
2531 2531 sys.stdout.flush()
2532 2532 sys.stderr.flush()
2533 2533 fd = os.open(util.nulldev, os.O_RDWR)
2534 2534 if fd != 0: os.dup2(fd, 0)
2535 2535 if fd != 1: os.dup2(fd, 1)
2536 2536 if fd != 2: os.dup2(fd, 2)
2537 2537 if fd not in (0, 1, 2): os.close(fd)
2538 2538
2539 2539 httpd.serve_forever()
2540 2540
2541 2541 def status(ui, repo, *pats, **opts):
2542 2542 """show changed files in the working directory
2543 2543
2544 2544 Show changed files in the repository. If names are
2545 2545 given, only files that match are shown.
2546 2546
2547 2547 The codes used to show the status of files are:
2548 2548 M = modified
2549 2549 A = added
2550 2550 R = removed
2551 2551 ! = deleted, but still tracked
2552 2552 ? = not tracked
2553 2553 I = ignored (not shown by default)
2554 2554 """
2555 2555
2556 2556 show_ignored = opts['ignored'] and True or False
2557 2557 files, matchfn, anypats = matchpats(repo, pats, opts)
2558 2558 cwd = (pats and repo.getcwd()) or ''
2559 2559 modified, added, removed, deleted, unknown, ignored = [
2560 2560 [util.pathto(cwd, x) for x in n]
2561 2561 for n in repo.changes(files=files, match=matchfn,
2562 2562 show_ignored=show_ignored)]
2563 2563
2564 2564 changetypes = [('modified', 'M', modified),
2565 2565 ('added', 'A', added),
2566 2566 ('removed', 'R', removed),
2567 2567 ('deleted', '!', deleted),
2568 2568 ('unknown', '?', unknown),
2569 2569 ('ignored', 'I', ignored)]
2570 2570
2571 2571 end = opts['print0'] and '\0' or '\n'
2572 2572
2573 2573 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
2574 2574 or changetypes):
2575 2575 if opts['no_status']:
2576 2576 format = "%%s%s" % end
2577 2577 else:
2578 2578 format = "%s %%s%s" % (char, end)
2579 2579
2580 2580 for f in changes:
2581 2581 ui.write(format % f)
2582 2582
2583 2583 def tag(ui, repo, name, rev_=None, **opts):
2584 2584 """add a tag for the current tip or a given revision
2585 2585
2586 2586 Name a particular revision using <name>.
2587 2587
2588 2588 Tags are used to name particular revisions of the repository and are
2589 2589 very useful to compare different revision, to go back to significant
2590 2590 earlier versions or to mark branch points as releases, etc.
2591 2591
2592 2592 If no revision is given, the tip is used.
2593 2593
2594 2594 To facilitate version control, distribution, and merging of tags,
2595 2595 they are stored as a file named ".hgtags" which is managed
2596 2596 similarly to other project files and can be hand-edited if
2597 2597 necessary. The file '.hg/localtags' is used for local tags (not
2598 2598 shared among repositories).
2599 2599 """
2600 2600 if name == "tip":
2601 2601 raise util.Abort(_("the name 'tip' is reserved"))
2602 2602 if rev_ is not None:
2603 2603 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2604 2604 "please use 'hg tag [-r REV] NAME' instead\n"))
2605 2605 if opts['rev']:
2606 2606 raise util.Abort(_("use only one form to specify the revision"))
2607 2607 if opts['rev']:
2608 2608 rev_ = opts['rev']
2609 2609 if rev_:
2610 2610 r = hex(repo.lookup(rev_))
2611 2611 else:
2612 2612 r = hex(repo.changelog.tip())
2613 2613
2614 2614 disallowed = (revrangesep, '\r', '\n')
2615 2615 for c in disallowed:
2616 2616 if name.find(c) >= 0:
2617 2617 raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
2618 2618
2619 2619 repo.hook('pretag', throw=True, node=r, tag=name,
2620 2620 local=int(not not opts['local']))
2621 2621
2622 2622 if opts['local']:
2623 2623 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
2624 2624 repo.hook('tag', node=r, tag=name, local=1)
2625 2625 return
2626 2626
2627 2627 for x in repo.changes():
2628 2628 if ".hgtags" in x:
2629 2629 raise util.Abort(_("working copy of .hgtags is changed "
2630 2630 "(please commit .hgtags manually)"))
2631 2631
2632 2632 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
2633 2633 if repo.dirstate.state(".hgtags") == '?':
2634 2634 repo.add([".hgtags"])
2635 2635
2636 2636 message = (opts['message'] or
2637 2637 _("Added tag %s for changeset %s") % (name, r))
2638 2638 try:
2639 2639 repo.commit([".hgtags"], message, opts['user'], opts['date'])
2640 2640 repo.hook('tag', node=r, tag=name, local=0)
2641 2641 except ValueError, inst:
2642 2642 raise util.Abort(str(inst))
2643 2643
2644 2644 def tags(ui, repo):
2645 2645 """list repository tags
2646 2646
2647 2647 List the repository tags.
2648 2648
2649 2649 This lists both regular and local tags.
2650 2650 """
2651 2651
2652 2652 l = repo.tagslist()
2653 2653 l.reverse()
2654 2654 for t, n in l:
2655 2655 try:
2656 2656 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
2657 2657 except KeyError:
2658 2658 r = " ?:?"
2659 2659 if ui.quiet:
2660 2660 ui.write("%s\n" % t)
2661 2661 else:
2662 2662 ui.write("%-30s %s\n" % (t, r))
2663 2663
2664 2664 def tip(ui, repo, **opts):
2665 2665 """show the tip revision
2666 2666
2667 2667 Show the tip revision.
2668 2668 """
2669 2669 n = repo.changelog.tip()
2670 2670 br = None
2671 2671 if opts['branches']:
2672 2672 br = repo.branchlookup([n])
2673 2673 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2674 2674 if opts['patch']:
2675 2675 dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
2676 2676
2677 2677 def unbundle(ui, repo, fname, **opts):
2678 2678 """apply a changegroup file
2679 2679
2680 2680 Apply a compressed changegroup file generated by the bundle
2681 2681 command.
2682 2682 """
2683 2683 f = urllib.urlopen(fname)
2684 2684
2685 2685 header = f.read(6)
2686 2686 if not header.startswith("HG"):
2687 2687 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2688 2688 elif not header.startswith("HG10"):
2689 2689 raise util.Abort(_("%s: unknown bundle version") % fname)
2690 2690 elif header == "HG10BZ":
2691 2691 def generator(f):
2692 2692 zd = bz2.BZ2Decompressor()
2693 2693 zd.decompress("BZ")
2694 2694 for chunk in f:
2695 2695 yield zd.decompress(chunk)
2696 2696 elif header == "HG10UN":
2697 2697 def generator(f):
2698 2698 for chunk in f:
2699 2699 yield chunk
2700 2700 else:
2701 2701 raise util.Abort(_("%s: unknown bundle compression type")
2702 2702 % fname)
2703 2703 gen = generator(util.filechunkiter(f, 4096))
2704 modheads = repo.addchangegroup(util.chunkbuffer(gen))
2704 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle')
2705 2705 return postincoming(ui, repo, modheads, opts['update'])
2706 2706
2707 2707 def undo(ui, repo):
2708 2708 """undo the last commit or pull (DEPRECATED)
2709 2709
2710 2710 (DEPRECATED)
2711 2711 This command is now deprecated and will be removed in a future
2712 2712 release. Please use the rollback command instead. For usage
2713 2713 instructions, see the rollback command.
2714 2714 """
2715 2715 repo.undo()
2716 2716
2717 2717 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2718 2718 branch=None, **opts):
2719 2719 """update or merge working directory
2720 2720
2721 2721 Update the working directory to the specified revision.
2722 2722
2723 2723 If there are no outstanding changes in the working directory and
2724 2724 there is a linear relationship between the current version and the
2725 2725 requested version, the result is the requested version.
2726 2726
2727 2727 Otherwise the result is a merge between the contents of the
2728 2728 current working directory and the requested version. Files that
2729 2729 changed between either parent are marked as changed for the next
2730 2730 commit and a commit must be performed before any further updates
2731 2731 are allowed.
2732 2732
2733 2733 By default, update will refuse to run if doing so would require
2734 2734 merging or discarding local changes.
2735 2735 """
2736 2736 if branch:
2737 2737 br = repo.branchlookup(branch=branch)
2738 2738 found = []
2739 2739 for x in br:
2740 2740 if branch in br[x]:
2741 2741 found.append(x)
2742 2742 if len(found) > 1:
2743 2743 ui.warn(_("Found multiple heads for %s\n") % branch)
2744 2744 for x in found:
2745 2745 show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
2746 2746 return 1
2747 2747 if len(found) == 1:
2748 2748 node = found[0]
2749 2749 ui.warn(_("Using head %s for branch %s\n") % (short(node), branch))
2750 2750 else:
2751 2751 ui.warn(_("branch %s not found\n") % (branch))
2752 2752 return 1
2753 2753 else:
2754 2754 node = node and repo.lookup(node) or repo.changelog.tip()
2755 2755 return repo.update(node, allow=merge, force=clean, forcemerge=force)
2756 2756
2757 2757 def verify(ui, repo):
2758 2758 """verify the integrity of the repository
2759 2759
2760 2760 Verify the integrity of the current repository.
2761 2761
2762 2762 This will perform an extensive check of the repository's
2763 2763 integrity, validating the hashes and checksums of each entry in
2764 2764 the changelog, manifest, and tracked files, as well as the
2765 2765 integrity of their crosslinks and indices.
2766 2766 """
2767 2767 return repo.verify()
2768 2768
2769 2769 # Command options and aliases are listed here, alphabetically
2770 2770
2771 2771 table = {
2772 2772 "^add":
2773 2773 (add,
2774 2774 [('I', 'include', [], _('include names matching the given patterns')),
2775 2775 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2776 2776 _('hg add [OPTION]... [FILE]...')),
2777 2777 "debugaddremove|addremove":
2778 2778 (addremove,
2779 2779 [('I', 'include', [], _('include names matching the given patterns')),
2780 2780 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2781 2781 _('hg addremove [OPTION]... [FILE]...')),
2782 2782 "^annotate":
2783 2783 (annotate,
2784 2784 [('r', 'rev', '', _('annotate the specified revision')),
2785 2785 ('a', 'text', None, _('treat all files as text')),
2786 2786 ('u', 'user', None, _('list the author')),
2787 2787 ('d', 'date', None, _('list the date')),
2788 2788 ('n', 'number', None, _('list the revision number (default)')),
2789 2789 ('c', 'changeset', None, _('list the changeset')),
2790 2790 ('I', 'include', [], _('include names matching the given patterns')),
2791 2791 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2792 2792 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2793 2793 "archive":
2794 2794 (archive,
2795 2795 [('', 'no-decode', None, _('do not pass files through decoders')),
2796 2796 ('p', 'prefix', '', _('directory prefix for files in archive')),
2797 2797 ('r', 'rev', '', _('revision to distribute')),
2798 2798 ('t', 'type', '', _('type of distribution to create')),
2799 2799 ('I', 'include', [], _('include names matching the given patterns')),
2800 2800 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2801 2801 _('hg archive [OPTION]... DEST')),
2802 2802 "backout":
2803 2803 (backout,
2804 2804 [('', 'merge', None,
2805 2805 _('merge with old dirstate parent after backout')),
2806 2806 ('m', 'message', '', _('use <text> as commit message')),
2807 2807 ('l', 'logfile', '', _('read commit message from <file>')),
2808 2808 ('d', 'date', '', _('record datecode as commit date')),
2809 2809 ('u', 'user', '', _('record user as committer')),
2810 2810 ('I', 'include', [], _('include names matching the given patterns')),
2811 2811 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2812 2812 _('hg backout [OPTION]... REV')),
2813 2813 "bundle":
2814 2814 (bundle,
2815 2815 [('f', 'force', None,
2816 2816 _('run even when remote repository is unrelated'))],
2817 2817 _('hg bundle FILE DEST')),
2818 2818 "cat":
2819 2819 (cat,
2820 2820 [('o', 'output', '', _('print output to file with formatted name')),
2821 2821 ('r', 'rev', '', _('print the given revision')),
2822 2822 ('I', 'include', [], _('include names matching the given patterns')),
2823 2823 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2824 2824 _('hg cat [OPTION]... FILE...')),
2825 2825 "^clone":
2826 2826 (clone,
2827 2827 [('U', 'noupdate', None, _('do not update the new working directory')),
2828 2828 ('r', 'rev', [],
2829 2829 _('a changeset you would like to have after cloning')),
2830 2830 ('', 'pull', None, _('use pull protocol to copy metadata')),
2831 2831 ('e', 'ssh', '', _('specify ssh command to use')),
2832 2832 ('', 'remotecmd', '',
2833 2833 _('specify hg command to run on the remote side'))],
2834 2834 _('hg clone [OPTION]... SOURCE [DEST]')),
2835 2835 "^commit|ci":
2836 2836 (commit,
2837 2837 [('A', 'addremove', None,
2838 2838 _('mark new/missing files as added/removed before committing')),
2839 2839 ('m', 'message', '', _('use <text> as commit message')),
2840 2840 ('l', 'logfile', '', _('read the commit message from <file>')),
2841 2841 ('d', 'date', '', _('record datecode as commit date')),
2842 2842 ('u', 'user', '', _('record user as commiter')),
2843 2843 ('I', 'include', [], _('include names matching the given patterns')),
2844 2844 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2845 2845 _('hg commit [OPTION]... [FILE]...')),
2846 2846 "copy|cp":
2847 2847 (copy,
2848 2848 [('A', 'after', None, _('record a copy that has already occurred')),
2849 2849 ('f', 'force', None,
2850 2850 _('forcibly copy over an existing managed file')),
2851 2851 ('I', 'include', [], _('include names matching the given patterns')),
2852 2852 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2853 2853 _('hg copy [OPTION]... [SOURCE]... DEST')),
2854 2854 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2855 2855 "debugcomplete":
2856 2856 (debugcomplete,
2857 2857 [('o', 'options', None, _('show the command options'))],
2858 2858 _('debugcomplete [-o] CMD')),
2859 2859 "debugrebuildstate":
2860 2860 (debugrebuildstate,
2861 2861 [('r', 'rev', '', _('revision to rebuild to'))],
2862 2862 _('debugrebuildstate [-r REV] [REV]')),
2863 2863 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2864 2864 "debugconfig": (debugconfig, [], _('debugconfig')),
2865 2865 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2866 2866 "debugstate": (debugstate, [], _('debugstate')),
2867 2867 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2868 2868 "debugindex": (debugindex, [], _('debugindex FILE')),
2869 2869 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2870 2870 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2871 2871 "debugwalk":
2872 2872 (debugwalk,
2873 2873 [('I', 'include', [], _('include names matching the given patterns')),
2874 2874 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2875 2875 _('debugwalk [OPTION]... [FILE]...')),
2876 2876 "^diff":
2877 2877 (diff,
2878 2878 [('r', 'rev', [], _('revision')),
2879 2879 ('a', 'text', None, _('treat all files as text')),
2880 2880 ('p', 'show-function', None,
2881 2881 _('show which function each change is in')),
2882 2882 ('w', 'ignore-all-space', None,
2883 2883 _('ignore white space when comparing lines')),
2884 2884 ('I', 'include', [], _('include names matching the given patterns')),
2885 2885 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2886 2886 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2887 2887 "^export":
2888 2888 (export,
2889 2889 [('o', 'output', '', _('print output to file with formatted name')),
2890 2890 ('a', 'text', None, _('treat all files as text')),
2891 2891 ('', 'switch-parent', None, _('diff against the second parent'))],
2892 2892 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2893 2893 "debugforget|forget":
2894 2894 (forget,
2895 2895 [('I', 'include', [], _('include names matching the given patterns')),
2896 2896 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2897 2897 _('hg forget [OPTION]... FILE...')),
2898 2898 "grep":
2899 2899 (grep,
2900 2900 [('0', 'print0', None, _('end fields with NUL')),
2901 2901 ('', 'all', None, _('print all revisions that match')),
2902 2902 ('i', 'ignore-case', None, _('ignore case when matching')),
2903 2903 ('l', 'files-with-matches', None,
2904 2904 _('print only filenames and revs that match')),
2905 2905 ('n', 'line-number', None, _('print matching line numbers')),
2906 2906 ('r', 'rev', [], _('search in given revision range')),
2907 2907 ('u', 'user', None, _('print user who committed change')),
2908 2908 ('I', 'include', [], _('include names matching the given patterns')),
2909 2909 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2910 2910 _('hg grep [OPTION]... PATTERN [FILE]...')),
2911 2911 "heads":
2912 2912 (heads,
2913 2913 [('b', 'branches', None, _('show branches')),
2914 2914 ('', 'style', '', _('display using template map file')),
2915 2915 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2916 2916 ('', 'template', '', _('display with template'))],
2917 2917 _('hg heads [-b] [-r <rev>]')),
2918 2918 "help": (help_, [], _('hg help [COMMAND]')),
2919 2919 "identify|id": (identify, [], _('hg identify')),
2920 2920 "import|patch":
2921 2921 (import_,
2922 2922 [('p', 'strip', 1,
2923 2923 _('directory strip option for patch. This has the same\n'
2924 2924 'meaning as the corresponding patch option')),
2925 2925 ('b', 'base', '', _('base path')),
2926 2926 ('f', 'force', None,
2927 2927 _('skip check for outstanding uncommitted changes'))],
2928 2928 _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
2929 2929 "incoming|in": (incoming,
2930 2930 [('M', 'no-merges', None, _('do not show merges')),
2931 2931 ('f', 'force', None,
2932 2932 _('run even when remote repository is unrelated')),
2933 2933 ('', 'style', '', _('display using template map file')),
2934 2934 ('n', 'newest-first', None, _('show newest record first')),
2935 2935 ('', 'bundle', '', _('file to store the bundles into')),
2936 2936 ('p', 'patch', None, _('show patch')),
2937 2937 ('', 'template', '', _('display with template')),
2938 2938 ('e', 'ssh', '', _('specify ssh command to use')),
2939 2939 ('', 'remotecmd', '',
2940 2940 _('specify hg command to run on the remote side'))],
2941 2941 _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')),
2942 2942 "^init": (init, [], _('hg init [DEST]')),
2943 2943 "locate":
2944 2944 (locate,
2945 2945 [('r', 'rev', '', _('search the repository as it stood at rev')),
2946 2946 ('0', 'print0', None,
2947 2947 _('end filenames with NUL, for use with xargs')),
2948 2948 ('f', 'fullpath', None,
2949 2949 _('print complete paths from the filesystem root')),
2950 2950 ('I', 'include', [], _('include names matching the given patterns')),
2951 2951 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2952 2952 _('hg locate [OPTION]... [PATTERN]...')),
2953 2953 "^log|history":
2954 2954 (log,
2955 2955 [('b', 'branches', None, _('show branches')),
2956 2956 ('k', 'keyword', [], _('search for a keyword')),
2957 2957 ('l', 'limit', '', _('limit number of changes displayed')),
2958 2958 ('r', 'rev', [], _('show the specified revision or range')),
2959 2959 ('M', 'no-merges', None, _('do not show merges')),
2960 2960 ('', 'style', '', _('display using template map file')),
2961 2961 ('m', 'only-merges', None, _('show only merges')),
2962 2962 ('p', 'patch', None, _('show patch')),
2963 2963 ('', 'template', '', _('display with template')),
2964 2964 ('I', 'include', [], _('include names matching the given patterns')),
2965 2965 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
2966 2966 _('hg log [OPTION]... [FILE]')),
2967 2967 "manifest": (manifest, [], _('hg manifest [REV]')),
2968 2968 "merge":
2969 2969 (merge,
2970 2970 [('b', 'branch', '', _('merge with head of a specific branch')),
2971 2971 ('f', 'force', None, _('force a merge with outstanding changes'))],
2972 2972 _('hg merge [-b TAG] [-f] [REV]')),
2973 2973 "outgoing|out": (outgoing,
2974 2974 [('M', 'no-merges', None, _('do not show merges')),
2975 2975 ('f', 'force', None,
2976 2976 _('run even when remote repository is unrelated')),
2977 2977 ('p', 'patch', None, _('show patch')),
2978 2978 ('', 'style', '', _('display using template map file')),
2979 2979 ('n', 'newest-first', None, _('show newest record first')),
2980 2980 ('', 'template', '', _('display with template')),
2981 2981 ('e', 'ssh', '', _('specify ssh command to use')),
2982 2982 ('', 'remotecmd', '',
2983 2983 _('specify hg command to run on the remote side'))],
2984 2984 _('hg outgoing [-M] [-p] [-n] [DEST]')),
2985 2985 "^parents":
2986 2986 (parents,
2987 2987 [('b', 'branches', None, _('show branches')),
2988 2988 ('', 'style', '', _('display using template map file')),
2989 2989 ('', 'template', '', _('display with template'))],
2990 2990 _('hg parents [-b] [REV]')),
2991 2991 "paths": (paths, [], _('hg paths [NAME]')),
2992 2992 "^pull":
2993 2993 (pull,
2994 2994 [('u', 'update', None,
2995 2995 _('update the working directory to tip after pull')),
2996 2996 ('e', 'ssh', '', _('specify ssh command to use')),
2997 2997 ('f', 'force', None,
2998 2998 _('run even when remote repository is unrelated')),
2999 2999 ('r', 'rev', [], _('a specific revision you would like to pull')),
3000 3000 ('', 'remotecmd', '',
3001 3001 _('specify hg command to run on the remote side'))],
3002 3002 _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
3003 3003 "^push":
3004 3004 (push,
3005 3005 [('f', 'force', None, _('force push')),
3006 3006 ('e', 'ssh', '', _('specify ssh command to use')),
3007 3007 ('r', 'rev', [], _('a specific revision you would like to push')),
3008 3008 ('', 'remotecmd', '',
3009 3009 _('specify hg command to run on the remote side'))],
3010 3010 _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
3011 3011 "debugrawcommit|rawcommit":
3012 3012 (rawcommit,
3013 3013 [('p', 'parent', [], _('parent')),
3014 3014 ('d', 'date', '', _('date code')),
3015 3015 ('u', 'user', '', _('user')),
3016 3016 ('F', 'files', '', _('file list')),
3017 3017 ('m', 'message', '', _('commit message')),
3018 3018 ('l', 'logfile', '', _('commit message file'))],
3019 3019 _('hg debugrawcommit [OPTION]... [FILE]...')),
3020 3020 "recover": (recover, [], _('hg recover')),
3021 3021 "^remove|rm":
3022 3022 (remove,
3023 3023 [('', 'after', None, _('record remove that has already occurred')),
3024 3024 ('f', 'force', None, _('remove file even if modified')),
3025 3025 ('I', 'include', [], _('include names matching the given patterns')),
3026 3026 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3027 3027 _('hg remove [OPTION]... FILE...')),
3028 3028 "rename|mv":
3029 3029 (rename,
3030 3030 [('A', 'after', None, _('record a rename that has already occurred')),
3031 3031 ('f', 'force', None,
3032 3032 _('forcibly copy over an existing managed file')),
3033 3033 ('I', 'include', [], _('include names matching the given patterns')),
3034 3034 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3035 3035 _('hg rename [OPTION]... SOURCE... DEST')),
3036 3036 "^revert":
3037 3037 (revert,
3038 3038 [('r', 'rev', '', _('revision to revert to')),
3039 3039 ('', 'no-backup', None, _('do not save backup copies of files')),
3040 3040 ('I', 'include', [], _('include names matching given patterns')),
3041 3041 ('X', 'exclude', [], _('exclude names matching given patterns'))],
3042 3042 _('hg revert [-r REV] [NAME]...')),
3043 3043 "rollback": (rollback, [], _('hg rollback')),
3044 3044 "root": (root, [], _('hg root')),
3045 3045 "^serve":
3046 3046 (serve,
3047 3047 [('A', 'accesslog', '', _('name of access log file to write to')),
3048 3048 ('d', 'daemon', None, _('run server in background')),
3049 3049 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3050 3050 ('E', 'errorlog', '', _('name of error log file to write to')),
3051 3051 ('p', 'port', 0, _('port to use (default: 8000)')),
3052 3052 ('a', 'address', '', _('address to use')),
3053 3053 ('n', 'name', '',
3054 3054 _('name to show in web pages (default: working dir)')),
3055 3055 ('', 'webdir-conf', '', _('name of the webdir config file'
3056 3056 ' (serve more than one repo)')),
3057 3057 ('', 'pid-file', '', _('name of file to write process ID to')),
3058 3058 ('', 'stdio', None, _('for remote clients')),
3059 3059 ('t', 'templates', '', _('web templates to use')),
3060 3060 ('', 'style', '', _('template style to use')),
3061 3061 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3062 3062 _('hg serve [OPTION]...')),
3063 3063 "^status|st":
3064 3064 (status,
3065 3065 [('m', 'modified', None, _('show only modified files')),
3066 3066 ('a', 'added', None, _('show only added files')),
3067 3067 ('r', 'removed', None, _('show only removed files')),
3068 3068 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3069 3069 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3070 3070 ('i', 'ignored', None, _('show ignored files')),
3071 3071 ('n', 'no-status', None, _('hide status prefix')),
3072 3072 ('0', 'print0', None,
3073 3073 _('end filenames with NUL, for use with xargs')),
3074 3074 ('I', 'include', [], _('include names matching the given patterns')),
3075 3075 ('X', 'exclude', [], _('exclude names matching the given patterns'))],
3076 3076 _('hg status [OPTION]... [FILE]...')),
3077 3077 "tag":
3078 3078 (tag,
3079 3079 [('l', 'local', None, _('make the tag local')),
3080 3080 ('m', 'message', '', _('message for tag commit log entry')),
3081 3081 ('d', 'date', '', _('record datecode as commit date')),
3082 3082 ('u', 'user', '', _('record user as commiter')),
3083 3083 ('r', 'rev', '', _('revision to tag'))],
3084 3084 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3085 3085 "tags": (tags, [], _('hg tags')),
3086 3086 "tip":
3087 3087 (tip,
3088 3088 [('b', 'branches', None, _('show branches')),
3089 3089 ('', 'style', '', _('display using template map file')),
3090 3090 ('p', 'patch', None, _('show patch')),
3091 3091 ('', 'template', '', _('display with template'))],
3092 3092 _('hg tip [-b] [-p]')),
3093 3093 "unbundle":
3094 3094 (unbundle,
3095 3095 [('u', 'update', None,
3096 3096 _('update the working directory to tip after unbundle'))],
3097 3097 _('hg unbundle [-u] FILE')),
3098 3098 "undo": (undo, [], _('hg undo')),
3099 3099 "^update|up|checkout|co":
3100 3100 (update,
3101 3101 [('b', 'branch', '', _('checkout the head of a specific branch')),
3102 3102 ('m', 'merge', None, _('allow merging of branches')),
3103 3103 ('C', 'clean', None, _('overwrite locally modified files')),
3104 3104 ('f', 'force', None, _('force a merge with outstanding changes'))],
3105 3105 _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
3106 3106 "verify": (verify, [], _('hg verify')),
3107 3107 "version": (show_version, [], _('hg version')),
3108 3108 }
3109 3109
3110 3110 globalopts = [
3111 3111 ('R', 'repository', '',
3112 3112 _('repository root directory or symbolic path name')),
3113 3113 ('', 'cwd', '', _('change working directory')),
3114 3114 ('y', 'noninteractive', None,
3115 3115 _('do not prompt, assume \'yes\' for any required answers')),
3116 3116 ('q', 'quiet', None, _('suppress output')),
3117 3117 ('v', 'verbose', None, _('enable additional output')),
3118 3118 ('', 'debug', None, _('enable debugging output')),
3119 3119 ('', 'debugger', None, _('start debugger')),
3120 3120 ('', 'traceback', None, _('print traceback on exception')),
3121 3121 ('', 'time', None, _('time how long the command takes')),
3122 3122 ('', 'profile', None, _('print command execution profile')),
3123 3123 ('', 'version', None, _('output version information and exit')),
3124 3124 ('h', 'help', None, _('display help and exit')),
3125 3125 ]
3126 3126
3127 3127 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3128 3128 " debugindex debugindexdot")
3129 3129 optionalrepo = ("paths serve debugconfig")
3130 3130
3131 3131 def findpossible(cmd):
3132 3132 """
3133 3133 Return cmd -> (aliases, command table entry)
3134 3134 for each matching command.
3135 3135 Return debug commands (or their aliases) only if no normal command matches.
3136 3136 """
3137 3137 choice = {}
3138 3138 debugchoice = {}
3139 3139 for e in table.keys():
3140 3140 aliases = e.lstrip("^").split("|")
3141 3141 found = None
3142 3142 if cmd in aliases:
3143 3143 found = cmd
3144 3144 else:
3145 3145 for a in aliases:
3146 3146 if a.startswith(cmd):
3147 3147 found = a
3148 3148 break
3149 3149 if found is not None:
3150 3150 if aliases[0].startswith("debug"):
3151 3151 debugchoice[found] = (aliases, table[e])
3152 3152 else:
3153 3153 choice[found] = (aliases, table[e])
3154 3154
3155 3155 if not choice and debugchoice:
3156 3156 choice = debugchoice
3157 3157
3158 3158 return choice
3159 3159
3160 3160 def find(cmd):
3161 3161 """Return (aliases, command table entry) for command string."""
3162 3162 choice = findpossible(cmd)
3163 3163
3164 3164 if choice.has_key(cmd):
3165 3165 return choice[cmd]
3166 3166
3167 3167 if len(choice) > 1:
3168 3168 clist = choice.keys()
3169 3169 clist.sort()
3170 3170 raise AmbiguousCommand(cmd, clist)
3171 3171
3172 3172 if choice:
3173 3173 return choice.values()[0]
3174 3174
3175 3175 raise UnknownCommand(cmd)
3176 3176
3177 3177 def catchterm(*args):
3178 3178 raise util.SignalInterrupt
3179 3179
3180 3180 def run():
3181 3181 sys.exit(dispatch(sys.argv[1:]))
3182 3182
3183 3183 class ParseError(Exception):
3184 3184 """Exception raised on errors in parsing the command line."""
3185 3185
3186 3186 def parse(ui, args):
3187 3187 options = {}
3188 3188 cmdoptions = {}
3189 3189
3190 3190 try:
3191 3191 args = fancyopts.fancyopts(args, globalopts, options)
3192 3192 except fancyopts.getopt.GetoptError, inst:
3193 3193 raise ParseError(None, inst)
3194 3194
3195 3195 if args:
3196 3196 cmd, args = args[0], args[1:]
3197 3197 aliases, i = find(cmd)
3198 3198 cmd = aliases[0]
3199 3199 defaults = ui.config("defaults", cmd)
3200 3200 if defaults:
3201 3201 args = defaults.split() + args
3202 3202 c = list(i[1])
3203 3203 else:
3204 3204 cmd = None
3205 3205 c = []
3206 3206
3207 3207 # combine global options into local
3208 3208 for o in globalopts:
3209 3209 c.append((o[0], o[1], options[o[1]], o[3]))
3210 3210
3211 3211 try:
3212 3212 args = fancyopts.fancyopts(args, c, cmdoptions)
3213 3213 except fancyopts.getopt.GetoptError, inst:
3214 3214 raise ParseError(cmd, inst)
3215 3215
3216 3216 # separate global options back out
3217 3217 for o in globalopts:
3218 3218 n = o[1]
3219 3219 options[n] = cmdoptions[n]
3220 3220 del cmdoptions[n]
3221 3221
3222 3222 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3223 3223
3224 3224 def dispatch(args):
3225 3225 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3226 3226 num = getattr(signal, name, None)
3227 3227 if num: signal.signal(num, catchterm)
3228 3228
3229 3229 try:
3230 3230 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3231 3231 except util.Abort, inst:
3232 3232 sys.stderr.write(_("abort: %s\n") % inst)
3233 3233 return -1
3234 3234
3235 3235 external = []
3236 3236 for x in u.extensions():
3237 3237 try:
3238 3238 if x[1]:
3239 3239 mod = imp.load_source(x[0], x[1])
3240 3240 else:
3241 3241 def importh(name):
3242 3242 mod = __import__(name)
3243 3243 components = name.split('.')
3244 3244 for comp in components[1:]:
3245 3245 mod = getattr(mod, comp)
3246 3246 return mod
3247 3247 try:
3248 3248 mod = importh("hgext." + x[0])
3249 3249 except ImportError:
3250 3250 mod = importh(x[0])
3251 3251 external.append(mod)
3252 3252 except Exception, inst:
3253 3253 u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
3254 3254 if u.traceback:
3255 3255 traceback.print_exc()
3256 3256 return 1
3257 3257 continue
3258 3258
3259 3259 for x in external:
3260 3260 cmdtable = getattr(x, 'cmdtable', {})
3261 3261 for t in cmdtable:
3262 3262 if t in table:
3263 3263 u.warn(_("module %s overrides %s\n") % (x.__name__, t))
3264 3264 table.update(cmdtable)
3265 3265
3266 3266 try:
3267 3267 cmd, func, args, options, cmdoptions = parse(u, args)
3268 3268 if options["time"]:
3269 3269 def get_times():
3270 3270 t = os.times()
3271 3271 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3272 3272 t = (t[0], t[1], t[2], t[3], time.clock())
3273 3273 return t
3274 3274 s = get_times()
3275 3275 def print_time():
3276 3276 t = get_times()
3277 3277 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3278 3278 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3279 3279 atexit.register(print_time)
3280 3280
3281 3281 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3282 3282 not options["noninteractive"], options["traceback"])
3283 3283
3284 3284 # enter the debugger before command execution
3285 3285 if options['debugger']:
3286 3286 pdb.set_trace()
3287 3287
3288 3288 try:
3289 3289 if options['cwd']:
3290 3290 try:
3291 3291 os.chdir(options['cwd'])
3292 3292 except OSError, inst:
3293 3293 raise util.Abort('%s: %s' %
3294 3294 (options['cwd'], inst.strerror))
3295 3295
3296 3296 path = u.expandpath(options["repository"]) or ""
3297 3297 repo = path and hg.repository(u, path=path) or None
3298 3298
3299 3299 if options['help']:
3300 3300 return help_(u, cmd, options['version'])
3301 3301 elif options['version']:
3302 3302 return show_version(u)
3303 3303 elif not cmd:
3304 3304 return help_(u, 'shortlist')
3305 3305
3306 3306 if cmd not in norepo.split():
3307 3307 try:
3308 3308 if not repo:
3309 3309 repo = hg.repository(u, path=path)
3310 3310 u = repo.ui
3311 3311 for x in external:
3312 3312 if hasattr(x, 'reposetup'):
3313 3313 x.reposetup(u, repo)
3314 3314 except hg.RepoError:
3315 3315 if cmd not in optionalrepo.split():
3316 3316 raise
3317 3317 d = lambda: func(u, repo, *args, **cmdoptions)
3318 3318 else:
3319 3319 d = lambda: func(u, *args, **cmdoptions)
3320 3320
3321 3321 try:
3322 3322 if options['profile']:
3323 3323 import hotshot, hotshot.stats
3324 3324 prof = hotshot.Profile("hg.prof")
3325 3325 try:
3326 3326 try:
3327 3327 return prof.runcall(d)
3328 3328 except:
3329 3329 try:
3330 3330 u.warn(_('exception raised - generating '
3331 3331 'profile anyway\n'))
3332 3332 except:
3333 3333 pass
3334 3334 raise
3335 3335 finally:
3336 3336 prof.close()
3337 3337 stats = hotshot.stats.load("hg.prof")
3338 3338 stats.strip_dirs()
3339 3339 stats.sort_stats('time', 'calls')
3340 3340 stats.print_stats(40)
3341 3341 else:
3342 3342 return d()
3343 3343 finally:
3344 3344 u.flush()
3345 3345 except:
3346 3346 # enter the debugger when we hit an exception
3347 3347 if options['debugger']:
3348 3348 pdb.post_mortem(sys.exc_info()[2])
3349 3349 if u.traceback:
3350 3350 traceback.print_exc()
3351 3351 raise
3352 3352 except ParseError, inst:
3353 3353 if inst.args[0]:
3354 3354 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3355 3355 help_(u, inst.args[0])
3356 3356 else:
3357 3357 u.warn(_("hg: %s\n") % inst.args[1])
3358 3358 help_(u, 'shortlist')
3359 3359 except AmbiguousCommand, inst:
3360 3360 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3361 3361 (inst.args[0], " ".join(inst.args[1])))
3362 3362 except UnknownCommand, inst:
3363 3363 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3364 3364 help_(u, 'shortlist')
3365 3365 except hg.RepoError, inst:
3366 3366 u.warn(_("abort: %s!\n") % inst)
3367 3367 except lock.LockHeld, inst:
3368 3368 if inst.errno == errno.ETIMEDOUT:
3369 3369 reason = _('timed out waiting for lock held by %s') % inst.locker
3370 3370 else:
3371 3371 reason = _('lock held by %s') % inst.locker
3372 3372 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3373 3373 except lock.LockUnavailable, inst:
3374 3374 u.warn(_("abort: could not lock %s: %s\n") %
3375 3375 (inst.desc or inst.filename, inst.strerror))
3376 3376 except revlog.RevlogError, inst:
3377 3377 u.warn(_("abort: "), inst, "!\n")
3378 3378 except util.SignalInterrupt:
3379 3379 u.warn(_("killed!\n"))
3380 3380 except KeyboardInterrupt:
3381 3381 try:
3382 3382 u.warn(_("interrupted!\n"))
3383 3383 except IOError, inst:
3384 3384 if inst.errno == errno.EPIPE:
3385 3385 if u.debugflag:
3386 3386 u.warn(_("\nbroken pipe\n"))
3387 3387 else:
3388 3388 raise
3389 3389 except IOError, inst:
3390 3390 if hasattr(inst, "code"):
3391 3391 u.warn(_("abort: %s\n") % inst)
3392 3392 elif hasattr(inst, "reason"):
3393 3393 u.warn(_("abort: error: %s\n") % inst.reason[1])
3394 3394 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3395 3395 if u.debugflag:
3396 3396 u.warn(_("broken pipe\n"))
3397 3397 elif getattr(inst, "strerror", None):
3398 3398 if getattr(inst, "filename", None):
3399 3399 u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
3400 3400 else:
3401 3401 u.warn(_("abort: %s\n") % inst.strerror)
3402 3402 else:
3403 3403 raise
3404 3404 except OSError, inst:
3405 3405 if hasattr(inst, "filename"):
3406 3406 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3407 3407 else:
3408 3408 u.warn(_("abort: %s\n") % inst.strerror)
3409 3409 except util.Abort, inst:
3410 3410 u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
3411 3411 except TypeError, inst:
3412 3412 # was this an argument error?
3413 3413 tb = traceback.extract_tb(sys.exc_info()[2])
3414 3414 if len(tb) > 2: # no
3415 3415 raise
3416 3416 u.debug(inst, "\n")
3417 3417 u.warn(_("%s: invalid arguments\n") % cmd)
3418 3418 help_(u, cmd)
3419 3419 except SystemExit, inst:
3420 3420 # Commands shouldn't sys.exit directly, but give a return code.
3421 3421 # Just in case catch this and and pass exit code to caller.
3422 3422 return inst.code
3423 3423 except:
3424 3424 u.warn(_("** unknown exception encountered, details follow\n"))
3425 3425 u.warn(_("** report bug details to mercurial@selenic.com\n"))
3426 3426 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3427 3427 % version.get_version())
3428 3428 raise
3429 3429
3430 3430 return -1
@@ -1,2078 +1,2078 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, util
9 9 import filelog, manifest, changelog, dirstate, repo
10 10 from node import *
11 11 from i18n import gettext as _
12 12 from demandload import *
13 13 demandload(globals(), "appendfile changegroup")
14 14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
15 15 demandload(globals(), "revlog traceback")
16 16
17 17 class localrepository(object):
18 18 def __del__(self):
19 19 self.transhandle = None
20 20 def __init__(self, parentui, path=None, create=0):
21 21 if not path:
22 22 p = os.getcwd()
23 23 while not os.path.isdir(os.path.join(p, ".hg")):
24 24 oldp = p
25 25 p = os.path.dirname(p)
26 26 if p == oldp:
27 27 raise repo.RepoError(_("no repo found"))
28 28 path = p
29 29 self.path = os.path.join(path, ".hg")
30 30
31 31 if not create and not os.path.isdir(self.path):
32 32 raise repo.RepoError(_("repository %s not found") % path)
33 33
34 34 self.root = os.path.abspath(path)
35 35 self.origroot = path
36 36 self.ui = ui.ui(parentui=parentui)
37 37 self.opener = util.opener(self.path)
38 38 self.wopener = util.opener(self.root)
39 39
40 40 try:
41 41 self.ui.readconfig(self.join("hgrc"), self.root)
42 42 except IOError:
43 43 pass
44 44
45 45 v = self.ui.revlogopts
46 46 self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
47 47 self.revlogv1 = self.revlogversion != revlog.REVLOGV0
48 48 fl = v.get('flags', None)
49 49 flags = 0
50 50 if fl != None:
51 51 for x in fl.split():
52 52 flags |= revlog.flagstr(x)
53 53 elif self.revlogv1:
54 54 flags = revlog.REVLOG_DEFAULT_FLAGS
55 55
56 56 v = self.revlogversion | flags
57 57 self.manifest = manifest.manifest(self.opener, v)
58 58 self.changelog = changelog.changelog(self.opener, v)
59 59
60 60 # the changelog might not have the inline index flag
61 61 # on. If the format of the changelog is the same as found in
62 62 # .hgrc, apply any flags found in the .hgrc as well.
63 63 # Otherwise, just version from the changelog
64 64 v = self.changelog.version
65 65 if v == self.revlogversion:
66 66 v |= flags
67 67 self.revlogversion = v
68 68
69 69 self.tagscache = None
70 70 self.nodetagscache = None
71 71 self.encodepats = None
72 72 self.decodepats = None
73 73 self.transhandle = None
74 74
75 75 if create:
76 76 os.mkdir(self.path)
77 77 os.mkdir(self.join("data"))
78 78
79 79 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
80 80
81 81 def hook(self, name, throw=False, **args):
82 82 def callhook(hname, funcname):
83 83 '''call python hook. hook is callable object, looked up as
84 84 name in python module. if callable returns "true", hook
85 85 fails, else passes. if hook raises exception, treated as
86 86 hook failure. exception propagates if throw is "true".
87 87
88 88 reason for "true" meaning "hook failed" is so that
89 89 unmodified commands (e.g. mercurial.commands.update) can
90 90 be run as hooks without wrappers to convert return values.'''
91 91
92 92 self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
93 93 d = funcname.rfind('.')
94 94 if d == -1:
95 95 raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
96 96 % (hname, funcname))
97 97 modname = funcname[:d]
98 98 try:
99 99 obj = __import__(modname)
100 100 except ImportError:
101 101 raise util.Abort(_('%s hook is invalid '
102 102 '(import of "%s" failed)') %
103 103 (hname, modname))
104 104 try:
105 105 for p in funcname.split('.')[1:]:
106 106 obj = getattr(obj, p)
107 107 except AttributeError, err:
108 108 raise util.Abort(_('%s hook is invalid '
109 109 '("%s" is not defined)') %
110 110 (hname, funcname))
111 111 if not callable(obj):
112 112 raise util.Abort(_('%s hook is invalid '
113 113 '("%s" is not callable)') %
114 114 (hname, funcname))
115 115 try:
116 116 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
117 117 except (KeyboardInterrupt, util.SignalInterrupt):
118 118 raise
119 119 except Exception, exc:
120 120 if isinstance(exc, util.Abort):
121 121 self.ui.warn(_('error: %s hook failed: %s\n') %
122 122 (hname, exc.args[0] % exc.args[1:]))
123 123 else:
124 124 self.ui.warn(_('error: %s hook raised an exception: '
125 125 '%s\n') % (hname, exc))
126 126 if throw:
127 127 raise
128 128 if self.ui.traceback:
129 129 traceback.print_exc()
130 130 return True
131 131 if r:
132 132 if throw:
133 133 raise util.Abort(_('%s hook failed') % hname)
134 134 self.ui.warn(_('warning: %s hook failed\n') % hname)
135 135 return r
136 136
137 137 def runhook(name, cmd):
138 138 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
139 139 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
140 140 [(k.upper(), v) for k, v in args.iteritems()])
141 141 r = util.system(cmd, environ=env, cwd=self.root)
142 142 if r:
143 143 desc, r = util.explain_exit(r)
144 144 if throw:
145 145 raise util.Abort(_('%s hook %s') % (name, desc))
146 146 self.ui.warn(_('warning: %s hook %s\n') % (name, desc))
147 147 return r
148 148
149 149 r = False
150 150 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
151 151 if hname.split(".", 1)[0] == name and cmd]
152 152 hooks.sort()
153 153 for hname, cmd in hooks:
154 154 if cmd.startswith('python:'):
155 155 r = callhook(hname, cmd[7:].strip()) or r
156 156 else:
157 157 r = runhook(hname, cmd) or r
158 158 return r
159 159
160 160 def tags(self):
161 161 '''return a mapping of tag to node'''
162 162 if not self.tagscache:
163 163 self.tagscache = {}
164 164
165 165 def parsetag(line, context):
166 166 if not line:
167 167 return
168 168 s = l.split(" ", 1)
169 169 if len(s) != 2:
170 170 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
171 171 return
172 172 node, key = s
173 173 try:
174 174 bin_n = bin(node)
175 175 except TypeError:
176 176 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
177 177 return
178 178 if bin_n not in self.changelog.nodemap:
179 179 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
180 180 return
181 181 self.tagscache[key.strip()] = bin_n
182 182
183 183 # read each head of the tags file, ending with the tip
184 184 # and add each tag found to the map, with "newer" ones
185 185 # taking precedence
186 186 fl = self.file(".hgtags")
187 187 h = fl.heads()
188 188 h.reverse()
189 189 for r in h:
190 190 count = 0
191 191 for l in fl.read(r).splitlines():
192 192 count += 1
193 193 parsetag(l, ".hgtags:%d" % count)
194 194
195 195 try:
196 196 f = self.opener("localtags")
197 197 count = 0
198 198 for l in f:
199 199 count += 1
200 200 parsetag(l, "localtags:%d" % count)
201 201 except IOError:
202 202 pass
203 203
204 204 self.tagscache['tip'] = self.changelog.tip()
205 205
206 206 return self.tagscache
207 207
208 208 def tagslist(self):
209 209 '''return a list of tags ordered by revision'''
210 210 l = []
211 211 for t, n in self.tags().items():
212 212 try:
213 213 r = self.changelog.rev(n)
214 214 except:
215 215 r = -2 # sort to the beginning of the list if unknown
216 216 l.append((r, t, n))
217 217 l.sort()
218 218 return [(t, n) for r, t, n in l]
219 219
220 220 def nodetags(self, node):
221 221 '''return the tags associated with a node'''
222 222 if not self.nodetagscache:
223 223 self.nodetagscache = {}
224 224 for t, n in self.tags().items():
225 225 self.nodetagscache.setdefault(n, []).append(t)
226 226 return self.nodetagscache.get(node, [])
227 227
228 228 def lookup(self, key):
229 229 try:
230 230 return self.tags()[key]
231 231 except KeyError:
232 232 try:
233 233 return self.changelog.lookup(key)
234 234 except:
235 235 raise repo.RepoError(_("unknown revision '%s'") % key)
236 236
237 237 def dev(self):
238 238 return os.stat(self.path).st_dev
239 239
240 240 def local(self):
241 241 return True
242 242
243 243 def join(self, f):
244 244 return os.path.join(self.path, f)
245 245
246 246 def wjoin(self, f):
247 247 return os.path.join(self.root, f)
248 248
249 249 def file(self, f):
250 250 if f[0] == '/':
251 251 f = f[1:]
252 252 return filelog.filelog(self.opener, f, self.revlogversion)
253 253
254 254 def getcwd(self):
255 255 return self.dirstate.getcwd()
256 256
257 257 def wfile(self, f, mode='r'):
258 258 return self.wopener(f, mode)
259 259
260 260 def wread(self, filename):
261 261 if self.encodepats == None:
262 262 l = []
263 263 for pat, cmd in self.ui.configitems("encode"):
264 264 mf = util.matcher(self.root, "", [pat], [], [])[1]
265 265 l.append((mf, cmd))
266 266 self.encodepats = l
267 267
268 268 data = self.wopener(filename, 'r').read()
269 269
270 270 for mf, cmd in self.encodepats:
271 271 if mf(filename):
272 272 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
273 273 data = util.filter(data, cmd)
274 274 break
275 275
276 276 return data
277 277
278 278 def wwrite(self, filename, data, fd=None):
279 279 if self.decodepats == None:
280 280 l = []
281 281 for pat, cmd in self.ui.configitems("decode"):
282 282 mf = util.matcher(self.root, "", [pat], [], [])[1]
283 283 l.append((mf, cmd))
284 284 self.decodepats = l
285 285
286 286 for mf, cmd in self.decodepats:
287 287 if mf(filename):
288 288 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
289 289 data = util.filter(data, cmd)
290 290 break
291 291
292 292 if fd:
293 293 return fd.write(data)
294 294 return self.wopener(filename, 'w').write(data)
295 295
296 296 def transaction(self):
297 297 tr = self.transhandle
298 298 if tr != None and tr.running():
299 299 return tr.nest()
300 300
301 301 # save dirstate for undo
302 302 try:
303 303 ds = self.opener("dirstate").read()
304 304 except IOError:
305 305 ds = ""
306 306 self.opener("journal.dirstate", "w").write(ds)
307 307
308 308 tr = transaction.transaction(self.ui.warn, self.opener,
309 309 self.join("journal"),
310 310 aftertrans(self.path))
311 311 self.transhandle = tr
312 312 return tr
313 313
314 314 def recover(self):
315 315 l = self.lock()
316 316 if os.path.exists(self.join("journal")):
317 317 self.ui.status(_("rolling back interrupted transaction\n"))
318 318 transaction.rollback(self.opener, self.join("journal"))
319 319 self.reload()
320 320 return True
321 321 else:
322 322 self.ui.warn(_("no interrupted transaction available\n"))
323 323 return False
324 324
325 325 def undo(self, wlock=None):
326 326 if not wlock:
327 327 wlock = self.wlock()
328 328 l = self.lock()
329 329 if os.path.exists(self.join("undo")):
330 330 self.ui.status(_("rolling back last transaction\n"))
331 331 transaction.rollback(self.opener, self.join("undo"))
332 332 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
333 333 self.reload()
334 334 self.wreload()
335 335 else:
336 336 self.ui.warn(_("no undo information available\n"))
337 337
338 338 def wreload(self):
339 339 self.dirstate.read()
340 340
341 341 def reload(self):
342 342 self.changelog.load()
343 343 self.manifest.load()
344 344 self.tagscache = None
345 345 self.nodetagscache = None
346 346
347 347 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
348 348 desc=None):
349 349 try:
350 350 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
351 351 except lock.LockHeld, inst:
352 352 if not wait:
353 353 raise
354 354 self.ui.warn(_("waiting for lock on %s held by %s\n") %
355 355 (desc, inst.args[0]))
356 356 # default to 600 seconds timeout
357 357 l = lock.lock(self.join(lockname),
358 358 int(self.ui.config("ui", "timeout") or 600),
359 359 releasefn, desc=desc)
360 360 if acquirefn:
361 361 acquirefn()
362 362 return l
363 363
364 364 def lock(self, wait=1):
365 365 return self.do_lock("lock", wait, acquirefn=self.reload,
366 366 desc=_('repository %s') % self.origroot)
367 367
368 368 def wlock(self, wait=1):
369 369 return self.do_lock("wlock", wait, self.dirstate.write,
370 370 self.wreload,
371 371 desc=_('working directory of %s') % self.origroot)
372 372
373 373 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
374 374 "determine whether a new filenode is needed"
375 375 fp1 = manifest1.get(filename, nullid)
376 376 fp2 = manifest2.get(filename, nullid)
377 377
378 378 if fp2 != nullid:
379 379 # is one parent an ancestor of the other?
380 380 fpa = filelog.ancestor(fp1, fp2)
381 381 if fpa == fp1:
382 382 fp1, fp2 = fp2, nullid
383 383 elif fpa == fp2:
384 384 fp2 = nullid
385 385
386 386 # is the file unmodified from the parent? report existing entry
387 387 if fp2 == nullid and text == filelog.read(fp1):
388 388 return (fp1, None, None)
389 389
390 390 return (None, fp1, fp2)
391 391
392 392 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
393 393 orig_parent = self.dirstate.parents()[0] or nullid
394 394 p1 = p1 or self.dirstate.parents()[0] or nullid
395 395 p2 = p2 or self.dirstate.parents()[1] or nullid
396 396 c1 = self.changelog.read(p1)
397 397 c2 = self.changelog.read(p2)
398 398 m1 = self.manifest.read(c1[0])
399 399 mf1 = self.manifest.readflags(c1[0])
400 400 m2 = self.manifest.read(c2[0])
401 401 changed = []
402 402
403 403 if orig_parent == p1:
404 404 update_dirstate = 1
405 405 else:
406 406 update_dirstate = 0
407 407
408 408 if not wlock:
409 409 wlock = self.wlock()
410 410 l = self.lock()
411 411 tr = self.transaction()
412 412 mm = m1.copy()
413 413 mfm = mf1.copy()
414 414 linkrev = self.changelog.count()
415 415 for f in files:
416 416 try:
417 417 t = self.wread(f)
418 418 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
419 419 r = self.file(f)
420 420 mfm[f] = tm
421 421
422 422 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
423 423 if entry:
424 424 mm[f] = entry
425 425 continue
426 426
427 427 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
428 428 changed.append(f)
429 429 if update_dirstate:
430 430 self.dirstate.update([f], "n")
431 431 except IOError:
432 432 try:
433 433 del mm[f]
434 434 del mfm[f]
435 435 if update_dirstate:
436 436 self.dirstate.forget([f])
437 437 except:
438 438 # deleted from p2?
439 439 pass
440 440
441 441 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
442 442 user = user or self.ui.username()
443 443 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
444 444 tr.close()
445 445 if update_dirstate:
446 446 self.dirstate.setparents(n, nullid)
447 447
448 448 def commit(self, files=None, text="", user=None, date=None,
449 449 match=util.always, force=False, lock=None, wlock=None):
450 450 commit = []
451 451 remove = []
452 452 changed = []
453 453
454 454 if files:
455 455 for f in files:
456 456 s = self.dirstate.state(f)
457 457 if s in 'nmai':
458 458 commit.append(f)
459 459 elif s == 'r':
460 460 remove.append(f)
461 461 else:
462 462 self.ui.warn(_("%s not tracked!\n") % f)
463 463 else:
464 464 modified, added, removed, deleted, unknown = self.changes(match=match)
465 465 commit = modified + added
466 466 remove = removed
467 467
468 468 p1, p2 = self.dirstate.parents()
469 469 c1 = self.changelog.read(p1)
470 470 c2 = self.changelog.read(p2)
471 471 m1 = self.manifest.read(c1[0])
472 472 mf1 = self.manifest.readflags(c1[0])
473 473 m2 = self.manifest.read(c2[0])
474 474
475 475 if not commit and not remove and not force and p2 == nullid:
476 476 self.ui.status(_("nothing changed\n"))
477 477 return None
478 478
479 479 xp1 = hex(p1)
480 480 if p2 == nullid: xp2 = ''
481 481 else: xp2 = hex(p2)
482 482
483 483 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
484 484
485 485 if not wlock:
486 486 wlock = self.wlock()
487 487 if not lock:
488 488 lock = self.lock()
489 489 tr = self.transaction()
490 490
491 491 # check in files
492 492 new = {}
493 493 linkrev = self.changelog.count()
494 494 commit.sort()
495 495 for f in commit:
496 496 self.ui.note(f + "\n")
497 497 try:
498 498 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
499 499 t = self.wread(f)
500 500 except IOError:
501 501 self.ui.warn(_("trouble committing %s!\n") % f)
502 502 raise
503 503
504 504 r = self.file(f)
505 505
506 506 meta = {}
507 507 cp = self.dirstate.copied(f)
508 508 if cp:
509 509 meta["copy"] = cp
510 510 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
511 511 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
512 512 fp1, fp2 = nullid, nullid
513 513 else:
514 514 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
515 515 if entry:
516 516 new[f] = entry
517 517 continue
518 518
519 519 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
520 520 # remember what we've added so that we can later calculate
521 521 # the files to pull from a set of changesets
522 522 changed.append(f)
523 523
524 524 # update manifest
525 525 m1 = m1.copy()
526 526 m1.update(new)
527 527 for f in remove:
528 528 if f in m1:
529 529 del m1[f]
530 530 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
531 531 (new, remove))
532 532
533 533 # add changeset
534 534 new = new.keys()
535 535 new.sort()
536 536
537 537 user = user or self.ui.username()
538 538 if not text:
539 539 edittext = [""]
540 540 if p2 != nullid:
541 541 edittext.append("HG: branch merge")
542 542 edittext.extend(["HG: changed %s" % f for f in changed])
543 543 edittext.extend(["HG: removed %s" % f for f in remove])
544 544 if not changed and not remove:
545 545 edittext.append("HG: no files changed")
546 546 edittext.append("")
547 547 # run editor in the repository root
548 548 olddir = os.getcwd()
549 549 os.chdir(self.root)
550 550 edittext = self.ui.edit("\n".join(edittext), user)
551 551 os.chdir(olddir)
552 552 if not edittext.rstrip():
553 553 return None
554 554 text = edittext
555 555
556 556 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
557 557 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
558 558 parent2=xp2)
559 559 tr.close()
560 560
561 561 self.dirstate.setparents(n)
562 562 self.dirstate.update(new, "n")
563 563 self.dirstate.forget(remove)
564 564
565 565 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
566 566 return n
567 567
568 568 def walk(self, node=None, files=[], match=util.always, badmatch=None):
569 569 if node:
570 570 fdict = dict.fromkeys(files)
571 571 for fn in self.manifest.read(self.changelog.read(node)[0]):
572 572 fdict.pop(fn, None)
573 573 if match(fn):
574 574 yield 'm', fn
575 575 for fn in fdict:
576 576 if badmatch and badmatch(fn):
577 577 if match(fn):
578 578 yield 'b', fn
579 579 else:
580 580 self.ui.warn(_('%s: No such file in rev %s\n') % (
581 581 util.pathto(self.getcwd(), fn), short(node)))
582 582 else:
583 583 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
584 584 yield src, fn
585 585
586 586 def changes(self, node1=None, node2=None, files=[], match=util.always,
587 587 wlock=None, show_ignored=None):
588 588 """return changes between two nodes or node and working directory
589 589
590 590 If node1 is None, use the first dirstate parent instead.
591 591 If node2 is None, compare node1 with working directory.
592 592 """
593 593
594 594 def fcmp(fn, mf):
595 595 t1 = self.wread(fn)
596 596 t2 = self.file(fn).read(mf.get(fn, nullid))
597 597 return cmp(t1, t2)
598 598
599 599 def mfmatches(node):
600 600 change = self.changelog.read(node)
601 601 mf = dict(self.manifest.read(change[0]))
602 602 for fn in mf.keys():
603 603 if not match(fn):
604 604 del mf[fn]
605 605 return mf
606 606
607 607 if node1:
608 608 # read the manifest from node1 before the manifest from node2,
609 609 # so that we'll hit the manifest cache if we're going through
610 610 # all the revisions in parent->child order.
611 611 mf1 = mfmatches(node1)
612 612
613 613 # are we comparing the working directory?
614 614 if not node2:
615 615 if not wlock:
616 616 try:
617 617 wlock = self.wlock(wait=0)
618 618 except lock.LockException:
619 619 wlock = None
620 620 lookup, modified, added, removed, deleted, unknown, ignored = (
621 621 self.dirstate.changes(files, match, show_ignored))
622 622
623 623 # are we comparing working dir against its parent?
624 624 if not node1:
625 625 if lookup:
626 626 # do a full compare of any files that might have changed
627 627 mf2 = mfmatches(self.dirstate.parents()[0])
628 628 for f in lookup:
629 629 if fcmp(f, mf2):
630 630 modified.append(f)
631 631 elif wlock is not None:
632 632 self.dirstate.update([f], "n")
633 633 else:
634 634 # we are comparing working dir against non-parent
635 635 # generate a pseudo-manifest for the working dir
636 636 mf2 = mfmatches(self.dirstate.parents()[0])
637 637 for f in lookup + modified + added:
638 638 mf2[f] = ""
639 639 for f in removed:
640 640 if f in mf2:
641 641 del mf2[f]
642 642 else:
643 643 # we are comparing two revisions
644 644 deleted, unknown, ignored = [], [], []
645 645 mf2 = mfmatches(node2)
646 646
647 647 if node1:
648 648 # flush lists from dirstate before comparing manifests
649 649 modified, added = [], []
650 650
651 651 for fn in mf2:
652 652 if mf1.has_key(fn):
653 653 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
654 654 modified.append(fn)
655 655 del mf1[fn]
656 656 else:
657 657 added.append(fn)
658 658
659 659 removed = mf1.keys()
660 660
661 661 # sort and return results:
662 662 for l in modified, added, removed, deleted, unknown, ignored:
663 663 l.sort()
664 664 if show_ignored is None:
665 665 return (modified, added, removed, deleted, unknown)
666 666 else:
667 667 return (modified, added, removed, deleted, unknown, ignored)
668 668
669 669 def add(self, list, wlock=None):
670 670 if not wlock:
671 671 wlock = self.wlock()
672 672 for f in list:
673 673 p = self.wjoin(f)
674 674 if not os.path.exists(p):
675 675 self.ui.warn(_("%s does not exist!\n") % f)
676 676 elif not os.path.isfile(p):
677 677 self.ui.warn(_("%s not added: only files supported currently\n")
678 678 % f)
679 679 elif self.dirstate.state(f) in 'an':
680 680 self.ui.warn(_("%s already tracked!\n") % f)
681 681 else:
682 682 self.dirstate.update([f], "a")
683 683
684 684 def forget(self, list, wlock=None):
685 685 if not wlock:
686 686 wlock = self.wlock()
687 687 for f in list:
688 688 if self.dirstate.state(f) not in 'ai':
689 689 self.ui.warn(_("%s not added!\n") % f)
690 690 else:
691 691 self.dirstate.forget([f])
692 692
693 693 def remove(self, list, unlink=False, wlock=None):
694 694 if unlink:
695 695 for f in list:
696 696 try:
697 697 util.unlink(self.wjoin(f))
698 698 except OSError, inst:
699 699 if inst.errno != errno.ENOENT:
700 700 raise
701 701 if not wlock:
702 702 wlock = self.wlock()
703 703 for f in list:
704 704 p = self.wjoin(f)
705 705 if os.path.exists(p):
706 706 self.ui.warn(_("%s still exists!\n") % f)
707 707 elif self.dirstate.state(f) == 'a':
708 708 self.dirstate.forget([f])
709 709 elif f not in self.dirstate:
710 710 self.ui.warn(_("%s not tracked!\n") % f)
711 711 else:
712 712 self.dirstate.update([f], "r")
713 713
714 714 def undelete(self, list, wlock=None):
715 715 p = self.dirstate.parents()[0]
716 716 mn = self.changelog.read(p)[0]
717 717 mf = self.manifest.readflags(mn)
718 718 m = self.manifest.read(mn)
719 719 if not wlock:
720 720 wlock = self.wlock()
721 721 for f in list:
722 722 if self.dirstate.state(f) not in "r":
723 723 self.ui.warn("%s not removed!\n" % f)
724 724 else:
725 725 t = self.file(f).read(m[f])
726 726 self.wwrite(f, t)
727 727 util.set_exec(self.wjoin(f), mf[f])
728 728 self.dirstate.update([f], "n")
729 729
730 730 def copy(self, source, dest, wlock=None):
731 731 p = self.wjoin(dest)
732 732 if not os.path.exists(p):
733 733 self.ui.warn(_("%s does not exist!\n") % dest)
734 734 elif not os.path.isfile(p):
735 735 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
736 736 else:
737 737 if not wlock:
738 738 wlock = self.wlock()
739 739 if self.dirstate.state(dest) == '?':
740 740 self.dirstate.update([dest], "a")
741 741 self.dirstate.copy(source, dest)
742 742
743 743 def heads(self, start=None):
744 744 heads = self.changelog.heads(start)
745 745 # sort the output in rev descending order
746 746 heads = [(-self.changelog.rev(h), h) for h in heads]
747 747 heads.sort()
748 748 return [n for (r, n) in heads]
749 749
750 750 # branchlookup returns a dict giving a list of branches for
751 751 # each head. A branch is defined as the tag of a node or
752 752 # the branch of the node's parents. If a node has multiple
753 753 # branch tags, tags are eliminated if they are visible from other
754 754 # branch tags.
755 755 #
756 756 # So, for this graph: a->b->c->d->e
757 757 # \ /
758 758 # aa -----/
759 759 # a has tag 2.6.12
760 760 # d has tag 2.6.13
761 761 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
762 762 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
763 763 # from the list.
764 764 #
765 765 # It is possible that more than one head will have the same branch tag.
766 766 # callers need to check the result for multiple heads under the same
767 767 # branch tag if that is a problem for them (ie checkout of a specific
768 768 # branch).
769 769 #
770 770 # passing in a specific branch will limit the depth of the search
771 771 # through the parents. It won't limit the branches returned in the
772 772 # result though.
773 773 def branchlookup(self, heads=None, branch=None):
774 774 if not heads:
775 775 heads = self.heads()
776 776 headt = [ h for h in heads ]
777 777 chlog = self.changelog
778 778 branches = {}
779 779 merges = []
780 780 seenmerge = {}
781 781
782 782 # traverse the tree once for each head, recording in the branches
783 783 # dict which tags are visible from this head. The branches
784 784 # dict also records which tags are visible from each tag
785 785 # while we traverse.
786 786 while headt or merges:
787 787 if merges:
788 788 n, found = merges.pop()
789 789 visit = [n]
790 790 else:
791 791 h = headt.pop()
792 792 visit = [h]
793 793 found = [h]
794 794 seen = {}
795 795 while visit:
796 796 n = visit.pop()
797 797 if n in seen:
798 798 continue
799 799 pp = chlog.parents(n)
800 800 tags = self.nodetags(n)
801 801 if tags:
802 802 for x in tags:
803 803 if x == 'tip':
804 804 continue
805 805 for f in found:
806 806 branches.setdefault(f, {})[n] = 1
807 807 branches.setdefault(n, {})[n] = 1
808 808 break
809 809 if n not in found:
810 810 found.append(n)
811 811 if branch in tags:
812 812 continue
813 813 seen[n] = 1
814 814 if pp[1] != nullid and n not in seenmerge:
815 815 merges.append((pp[1], [x for x in found]))
816 816 seenmerge[n] = 1
817 817 if pp[0] != nullid:
818 818 visit.append(pp[0])
819 819 # traverse the branches dict, eliminating branch tags from each
820 820 # head that are visible from another branch tag for that head.
821 821 out = {}
822 822 viscache = {}
823 823 for h in heads:
824 824 def visible(node):
825 825 if node in viscache:
826 826 return viscache[node]
827 827 ret = {}
828 828 visit = [node]
829 829 while visit:
830 830 x = visit.pop()
831 831 if x in viscache:
832 832 ret.update(viscache[x])
833 833 elif x not in ret:
834 834 ret[x] = 1
835 835 if x in branches:
836 836 visit[len(visit):] = branches[x].keys()
837 837 viscache[node] = ret
838 838 return ret
839 839 if h not in branches:
840 840 continue
841 841 # O(n^2), but somewhat limited. This only searches the
842 842 # tags visible from a specific head, not all the tags in the
843 843 # whole repo.
844 844 for b in branches[h]:
845 845 vis = False
846 846 for bb in branches[h].keys():
847 847 if b != bb:
848 848 if b in visible(bb):
849 849 vis = True
850 850 break
851 851 if not vis:
852 852 l = out.setdefault(h, [])
853 853 l[len(l):] = self.nodetags(b)
854 854 return out
855 855
856 856 def branches(self, nodes):
857 857 if not nodes:
858 858 nodes = [self.changelog.tip()]
859 859 b = []
860 860 for n in nodes:
861 861 t = n
862 862 while n:
863 863 p = self.changelog.parents(n)
864 864 if p[1] != nullid or p[0] == nullid:
865 865 b.append((t, n, p[0], p[1]))
866 866 break
867 867 n = p[0]
868 868 return b
869 869
870 870 def between(self, pairs):
871 871 r = []
872 872
873 873 for top, bottom in pairs:
874 874 n, l, i = top, [], 0
875 875 f = 1
876 876
877 877 while n != bottom:
878 878 p = self.changelog.parents(n)[0]
879 879 if i == f:
880 880 l.append(n)
881 881 f = f * 2
882 882 n = p
883 883 i += 1
884 884
885 885 r.append(l)
886 886
887 887 return r
888 888
889 889 def findincoming(self, remote, base=None, heads=None, force=False):
890 890 m = self.changelog.nodemap
891 891 search = []
892 892 fetch = {}
893 893 seen = {}
894 894 seenbranch = {}
895 895 if base == None:
896 896 base = {}
897 897
898 898 if not heads:
899 899 heads = remote.heads()
900 900
901 901 if self.changelog.tip() == nullid:
902 902 if heads != [nullid]:
903 903 return [nullid]
904 904 return []
905 905
906 906 # assume we're closer to the tip than the root
907 907 # and start by examining the heads
908 908 self.ui.status(_("searching for changes\n"))
909 909
910 910 unknown = []
911 911 for h in heads:
912 912 if h not in m:
913 913 unknown.append(h)
914 914 else:
915 915 base[h] = 1
916 916
917 917 if not unknown:
918 918 return []
919 919
920 920 rep = {}
921 921 reqcnt = 0
922 922
923 923 # search through remote branches
924 924 # a 'branch' here is a linear segment of history, with four parts:
925 925 # head, root, first parent, second parent
926 926 # (a branch always has two parents (or none) by definition)
927 927 unknown = remote.branches(unknown)
928 928 while unknown:
929 929 r = []
930 930 while unknown:
931 931 n = unknown.pop(0)
932 932 if n[0] in seen:
933 933 continue
934 934
935 935 self.ui.debug(_("examining %s:%s\n")
936 936 % (short(n[0]), short(n[1])))
937 937 if n[0] == nullid:
938 938 break
939 939 if n in seenbranch:
940 940 self.ui.debug(_("branch already found\n"))
941 941 continue
942 942 if n[1] and n[1] in m: # do we know the base?
943 943 self.ui.debug(_("found incomplete branch %s:%s\n")
944 944 % (short(n[0]), short(n[1])))
945 945 search.append(n) # schedule branch range for scanning
946 946 seenbranch[n] = 1
947 947 else:
948 948 if n[1] not in seen and n[1] not in fetch:
949 949 if n[2] in m and n[3] in m:
950 950 self.ui.debug(_("found new changeset %s\n") %
951 951 short(n[1]))
952 952 fetch[n[1]] = 1 # earliest unknown
953 953 base[n[2]] = 1 # latest known
954 954 continue
955 955
956 956 for a in n[2:4]:
957 957 if a not in rep:
958 958 r.append(a)
959 959 rep[a] = 1
960 960
961 961 seen[n[0]] = 1
962 962
963 963 if r:
964 964 reqcnt += 1
965 965 self.ui.debug(_("request %d: %s\n") %
966 966 (reqcnt, " ".join(map(short, r))))
967 967 for p in range(0, len(r), 10):
968 968 for b in remote.branches(r[p:p+10]):
969 969 self.ui.debug(_("received %s:%s\n") %
970 970 (short(b[0]), short(b[1])))
971 971 if b[0] in m:
972 972 self.ui.debug(_("found base node %s\n")
973 973 % short(b[0]))
974 974 base[b[0]] = 1
975 975 elif b[0] not in seen:
976 976 unknown.append(b)
977 977
978 978 # do binary search on the branches we found
979 979 while search:
980 980 n = search.pop(0)
981 981 reqcnt += 1
982 982 l = remote.between([(n[0], n[1])])[0]
983 983 l.append(n[1])
984 984 p = n[0]
985 985 f = 1
986 986 for i in l:
987 987 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
988 988 if i in m:
989 989 if f <= 2:
990 990 self.ui.debug(_("found new branch changeset %s\n") %
991 991 short(p))
992 992 fetch[p] = 1
993 993 base[i] = 1
994 994 else:
995 995 self.ui.debug(_("narrowed branch search to %s:%s\n")
996 996 % (short(p), short(i)))
997 997 search.append((p, i))
998 998 break
999 999 p, f = i, f * 2
1000 1000
1001 1001 # sanity check our fetch list
1002 1002 for f in fetch.keys():
1003 1003 if f in m:
1004 1004 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
1005 1005
1006 1006 if base.keys() == [nullid]:
1007 1007 if force:
1008 1008 self.ui.warn(_("warning: repository is unrelated\n"))
1009 1009 else:
1010 1010 raise util.Abort(_("repository is unrelated"))
1011 1011
1012 1012 self.ui.note(_("found new changesets starting at ") +
1013 1013 " ".join([short(f) for f in fetch]) + "\n")
1014 1014
1015 1015 self.ui.debug(_("%d total queries\n") % reqcnt)
1016 1016
1017 1017 return fetch.keys()
1018 1018
1019 1019 def findoutgoing(self, remote, base=None, heads=None, force=False):
1020 1020 """Return list of nodes that are roots of subsets not in remote
1021 1021
1022 1022 If base dict is specified, assume that these nodes and their parents
1023 1023 exist on the remote side.
1024 1024 If a list of heads is specified, return only nodes which are heads
1025 1025 or ancestors of these heads, and return a second element which
1026 1026 contains all remote heads which get new children.
1027 1027 """
1028 1028 if base == None:
1029 1029 base = {}
1030 1030 self.findincoming(remote, base, heads, force=force)
1031 1031
1032 1032 self.ui.debug(_("common changesets up to ")
1033 1033 + " ".join(map(short, base.keys())) + "\n")
1034 1034
1035 1035 remain = dict.fromkeys(self.changelog.nodemap)
1036 1036
1037 1037 # prune everything remote has from the tree
1038 1038 del remain[nullid]
1039 1039 remove = base.keys()
1040 1040 while remove:
1041 1041 n = remove.pop(0)
1042 1042 if n in remain:
1043 1043 del remain[n]
1044 1044 for p in self.changelog.parents(n):
1045 1045 remove.append(p)
1046 1046
1047 1047 # find every node whose parents have been pruned
1048 1048 subset = []
1049 1049 # find every remote head that will get new children
1050 1050 updated_heads = {}
1051 1051 for n in remain:
1052 1052 p1, p2 = self.changelog.parents(n)
1053 1053 if p1 not in remain and p2 not in remain:
1054 1054 subset.append(n)
1055 1055 if heads:
1056 1056 if p1 in heads:
1057 1057 updated_heads[p1] = True
1058 1058 if p2 in heads:
1059 1059 updated_heads[p2] = True
1060 1060
1061 1061 # this is the set of all roots we have to push
1062 1062 if heads:
1063 1063 return subset, updated_heads.keys()
1064 1064 else:
1065 1065 return subset
1066 1066
1067 1067 def pull(self, remote, heads=None, force=False):
1068 1068 l = self.lock()
1069 1069
1070 1070 fetch = self.findincoming(remote, force=force)
1071 1071 if fetch == [nullid]:
1072 1072 self.ui.status(_("requesting all changes\n"))
1073 1073
1074 1074 if not fetch:
1075 1075 self.ui.status(_("no changes found\n"))
1076 1076 return 0
1077 1077
1078 1078 if heads is None:
1079 1079 cg = remote.changegroup(fetch, 'pull')
1080 1080 else:
1081 1081 cg = remote.changegroupsubset(fetch, heads, 'pull')
1082 return self.addchangegroup(cg)
1082 return self.addchangegroup(cg, 'pull')
1083 1083
1084 1084 def push(self, remote, force=False, revs=None):
1085 1085 lock = remote.lock()
1086 1086
1087 1087 base = {}
1088 1088 remote_heads = remote.heads()
1089 1089 inc = self.findincoming(remote, base, remote_heads, force=force)
1090 1090 if not force and inc:
1091 1091 self.ui.warn(_("abort: unsynced remote changes!\n"))
1092 1092 self.ui.status(_("(did you forget to sync?"
1093 1093 " use push -f to force)\n"))
1094 1094 return 1
1095 1095
1096 1096 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1097 1097 if revs is not None:
1098 1098 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1099 1099 else:
1100 1100 bases, heads = update, self.changelog.heads()
1101 1101
1102 1102 if not bases:
1103 1103 self.ui.status(_("no changes found\n"))
1104 1104 return 1
1105 1105 elif not force:
1106 1106 # FIXME we don't properly detect creation of new heads
1107 1107 # in the push -r case, assume the user knows what he's doing
1108 1108 if not revs and len(remote_heads) < len(heads) \
1109 1109 and remote_heads != [nullid]:
1110 1110 self.ui.warn(_("abort: push creates new remote branches!\n"))
1111 1111 self.ui.status(_("(did you forget to merge?"
1112 1112 " use push -f to force)\n"))
1113 1113 return 1
1114 1114
1115 1115 if revs is None:
1116 1116 cg = self.changegroup(update, 'push')
1117 1117 else:
1118 1118 cg = self.changegroupsubset(update, revs, 'push')
1119 return remote.addchangegroup(cg)
1119 return remote.addchangegroup(cg, 'push')
1120 1120
1121 1121 def changegroupsubset(self, bases, heads, source):
1122 1122 """This function generates a changegroup consisting of all the nodes
1123 1123 that are descendents of any of the bases, and ancestors of any of
1124 1124 the heads.
1125 1125
1126 1126 It is fairly complex as determining which filenodes and which
1127 1127 manifest nodes need to be included for the changeset to be complete
1128 1128 is non-trivial.
1129 1129
1130 1130 Another wrinkle is doing the reverse, figuring out which changeset in
1131 1131 the changegroup a particular filenode or manifestnode belongs to."""
1132 1132
1133 1133 self.hook('preoutgoing', throw=True, source=source)
1134 1134
1135 1135 # Set up some initial variables
1136 1136 # Make it easy to refer to self.changelog
1137 1137 cl = self.changelog
1138 1138 # msng is short for missing - compute the list of changesets in this
1139 1139 # changegroup.
1140 1140 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1141 1141 # Some bases may turn out to be superfluous, and some heads may be
1142 1142 # too. nodesbetween will return the minimal set of bases and heads
1143 1143 # necessary to re-create the changegroup.
1144 1144
1145 1145 # Known heads are the list of heads that it is assumed the recipient
1146 1146 # of this changegroup will know about.
1147 1147 knownheads = {}
1148 1148 # We assume that all parents of bases are known heads.
1149 1149 for n in bases:
1150 1150 for p in cl.parents(n):
1151 1151 if p != nullid:
1152 1152 knownheads[p] = 1
1153 1153 knownheads = knownheads.keys()
1154 1154 if knownheads:
1155 1155 # Now that we know what heads are known, we can compute which
1156 1156 # changesets are known. The recipient must know about all
1157 1157 # changesets required to reach the known heads from the null
1158 1158 # changeset.
1159 1159 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1160 1160 junk = None
1161 1161 # Transform the list into an ersatz set.
1162 1162 has_cl_set = dict.fromkeys(has_cl_set)
1163 1163 else:
1164 1164 # If there were no known heads, the recipient cannot be assumed to
1165 1165 # know about any changesets.
1166 1166 has_cl_set = {}
1167 1167
1168 1168 # Make it easy to refer to self.manifest
1169 1169 mnfst = self.manifest
1170 1170 # We don't know which manifests are missing yet
1171 1171 msng_mnfst_set = {}
1172 1172 # Nor do we know which filenodes are missing.
1173 1173 msng_filenode_set = {}
1174 1174
1175 1175 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1176 1176 junk = None
1177 1177
1178 1178 # A changeset always belongs to itself, so the changenode lookup
1179 1179 # function for a changenode is identity.
1180 1180 def identity(x):
1181 1181 return x
1182 1182
1183 1183 # A function generating function. Sets up an environment for the
1184 1184 # inner function.
1185 1185 def cmp_by_rev_func(revlog):
1186 1186 # Compare two nodes by their revision number in the environment's
1187 1187 # revision history. Since the revision number both represents the
1188 1188 # most efficient order to read the nodes in, and represents a
1189 1189 # topological sorting of the nodes, this function is often useful.
1190 1190 def cmp_by_rev(a, b):
1191 1191 return cmp(revlog.rev(a), revlog.rev(b))
1192 1192 return cmp_by_rev
1193 1193
1194 1194 # If we determine that a particular file or manifest node must be a
1195 1195 # node that the recipient of the changegroup will already have, we can
1196 1196 # also assume the recipient will have all the parents. This function
1197 1197 # prunes them from the set of missing nodes.
1198 1198 def prune_parents(revlog, hasset, msngset):
1199 1199 haslst = hasset.keys()
1200 1200 haslst.sort(cmp_by_rev_func(revlog))
1201 1201 for node in haslst:
1202 1202 parentlst = [p for p in revlog.parents(node) if p != nullid]
1203 1203 while parentlst:
1204 1204 n = parentlst.pop()
1205 1205 if n not in hasset:
1206 1206 hasset[n] = 1
1207 1207 p = [p for p in revlog.parents(n) if p != nullid]
1208 1208 parentlst.extend(p)
1209 1209 for n in hasset:
1210 1210 msngset.pop(n, None)
1211 1211
1212 1212 # This is a function generating function used to set up an environment
1213 1213 # for the inner function to execute in.
1214 1214 def manifest_and_file_collector(changedfileset):
1215 1215 # This is an information gathering function that gathers
1216 1216 # information from each changeset node that goes out as part of
1217 1217 # the changegroup. The information gathered is a list of which
1218 1218 # manifest nodes are potentially required (the recipient may
1219 1219 # already have them) and total list of all files which were
1220 1220 # changed in any changeset in the changegroup.
1221 1221 #
1222 1222 # We also remember the first changenode we saw any manifest
1223 1223 # referenced by so we can later determine which changenode 'owns'
1224 1224 # the manifest.
1225 1225 def collect_manifests_and_files(clnode):
1226 1226 c = cl.read(clnode)
1227 1227 for f in c[3]:
1228 1228 # This is to make sure we only have one instance of each
1229 1229 # filename string for each filename.
1230 1230 changedfileset.setdefault(f, f)
1231 1231 msng_mnfst_set.setdefault(c[0], clnode)
1232 1232 return collect_manifests_and_files
1233 1233
1234 1234 # Figure out which manifest nodes (of the ones we think might be part
1235 1235 # of the changegroup) the recipient must know about and remove them
1236 1236 # from the changegroup.
1237 1237 def prune_manifests():
1238 1238 has_mnfst_set = {}
1239 1239 for n in msng_mnfst_set:
1240 1240 # If a 'missing' manifest thinks it belongs to a changenode
1241 1241 # the recipient is assumed to have, obviously the recipient
1242 1242 # must have that manifest.
1243 1243 linknode = cl.node(mnfst.linkrev(n))
1244 1244 if linknode in has_cl_set:
1245 1245 has_mnfst_set[n] = 1
1246 1246 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1247 1247
1248 1248 # Use the information collected in collect_manifests_and_files to say
1249 1249 # which changenode any manifestnode belongs to.
1250 1250 def lookup_manifest_link(mnfstnode):
1251 1251 return msng_mnfst_set[mnfstnode]
1252 1252
1253 1253 # A function generating function that sets up the initial environment
1254 1254 # the inner function.
1255 1255 def filenode_collector(changedfiles):
1256 1256 next_rev = [0]
1257 1257 # This gathers information from each manifestnode included in the
1258 1258 # changegroup about which filenodes the manifest node references
1259 1259 # so we can include those in the changegroup too.
1260 1260 #
1261 1261 # It also remembers which changenode each filenode belongs to. It
1262 1262 # does this by assuming the a filenode belongs to the changenode
1263 1263 # the first manifest that references it belongs to.
1264 1264 def collect_msng_filenodes(mnfstnode):
1265 1265 r = mnfst.rev(mnfstnode)
1266 1266 if r == next_rev[0]:
1267 1267 # If the last rev we looked at was the one just previous,
1268 1268 # we only need to see a diff.
1269 1269 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1270 1270 # For each line in the delta
1271 1271 for dline in delta.splitlines():
1272 1272 # get the filename and filenode for that line
1273 1273 f, fnode = dline.split('\0')
1274 1274 fnode = bin(fnode[:40])
1275 1275 f = changedfiles.get(f, None)
1276 1276 # And if the file is in the list of files we care
1277 1277 # about.
1278 1278 if f is not None:
1279 1279 # Get the changenode this manifest belongs to
1280 1280 clnode = msng_mnfst_set[mnfstnode]
1281 1281 # Create the set of filenodes for the file if
1282 1282 # there isn't one already.
1283 1283 ndset = msng_filenode_set.setdefault(f, {})
1284 1284 # And set the filenode's changelog node to the
1285 1285 # manifest's if it hasn't been set already.
1286 1286 ndset.setdefault(fnode, clnode)
1287 1287 else:
1288 1288 # Otherwise we need a full manifest.
1289 1289 m = mnfst.read(mnfstnode)
1290 1290 # For every file in we care about.
1291 1291 for f in changedfiles:
1292 1292 fnode = m.get(f, None)
1293 1293 # If it's in the manifest
1294 1294 if fnode is not None:
1295 1295 # See comments above.
1296 1296 clnode = msng_mnfst_set[mnfstnode]
1297 1297 ndset = msng_filenode_set.setdefault(f, {})
1298 1298 ndset.setdefault(fnode, clnode)
1299 1299 # Remember the revision we hope to see next.
1300 1300 next_rev[0] = r + 1
1301 1301 return collect_msng_filenodes
1302 1302
1303 1303 # We have a list of filenodes we think we need for a file, lets remove
1304 1304 # all those we now the recipient must have.
1305 1305 def prune_filenodes(f, filerevlog):
1306 1306 msngset = msng_filenode_set[f]
1307 1307 hasset = {}
1308 1308 # If a 'missing' filenode thinks it belongs to a changenode we
1309 1309 # assume the recipient must have, then the recipient must have
1310 1310 # that filenode.
1311 1311 for n in msngset:
1312 1312 clnode = cl.node(filerevlog.linkrev(n))
1313 1313 if clnode in has_cl_set:
1314 1314 hasset[n] = 1
1315 1315 prune_parents(filerevlog, hasset, msngset)
1316 1316
1317 1317 # A function generator function that sets up the a context for the
1318 1318 # inner function.
1319 1319 def lookup_filenode_link_func(fname):
1320 1320 msngset = msng_filenode_set[fname]
1321 1321 # Lookup the changenode the filenode belongs to.
1322 1322 def lookup_filenode_link(fnode):
1323 1323 return msngset[fnode]
1324 1324 return lookup_filenode_link
1325 1325
1326 1326 # Now that we have all theses utility functions to help out and
1327 1327 # logically divide up the task, generate the group.
1328 1328 def gengroup():
1329 1329 # The set of changed files starts empty.
1330 1330 changedfiles = {}
1331 1331 # Create a changenode group generator that will call our functions
1332 1332 # back to lookup the owning changenode and collect information.
1333 1333 group = cl.group(msng_cl_lst, identity,
1334 1334 manifest_and_file_collector(changedfiles))
1335 1335 for chnk in group:
1336 1336 yield chnk
1337 1337
1338 1338 # The list of manifests has been collected by the generator
1339 1339 # calling our functions back.
1340 1340 prune_manifests()
1341 1341 msng_mnfst_lst = msng_mnfst_set.keys()
1342 1342 # Sort the manifestnodes by revision number.
1343 1343 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1344 1344 # Create a generator for the manifestnodes that calls our lookup
1345 1345 # and data collection functions back.
1346 1346 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1347 1347 filenode_collector(changedfiles))
1348 1348 for chnk in group:
1349 1349 yield chnk
1350 1350
1351 1351 # These are no longer needed, dereference and toss the memory for
1352 1352 # them.
1353 1353 msng_mnfst_lst = None
1354 1354 msng_mnfst_set.clear()
1355 1355
1356 1356 changedfiles = changedfiles.keys()
1357 1357 changedfiles.sort()
1358 1358 # Go through all our files in order sorted by name.
1359 1359 for fname in changedfiles:
1360 1360 filerevlog = self.file(fname)
1361 1361 # Toss out the filenodes that the recipient isn't really
1362 1362 # missing.
1363 1363 if msng_filenode_set.has_key(fname):
1364 1364 prune_filenodes(fname, filerevlog)
1365 1365 msng_filenode_lst = msng_filenode_set[fname].keys()
1366 1366 else:
1367 1367 msng_filenode_lst = []
1368 1368 # If any filenodes are left, generate the group for them,
1369 1369 # otherwise don't bother.
1370 1370 if len(msng_filenode_lst) > 0:
1371 1371 yield changegroup.genchunk(fname)
1372 1372 # Sort the filenodes by their revision #
1373 1373 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1374 1374 # Create a group generator and only pass in a changenode
1375 1375 # lookup function as we need to collect no information
1376 1376 # from filenodes.
1377 1377 group = filerevlog.group(msng_filenode_lst,
1378 1378 lookup_filenode_link_func(fname))
1379 1379 for chnk in group:
1380 1380 yield chnk
1381 1381 if msng_filenode_set.has_key(fname):
1382 1382 # Don't need this anymore, toss it to free memory.
1383 1383 del msng_filenode_set[fname]
1384 1384 # Signal that no more groups are left.
1385 1385 yield changegroup.closechunk()
1386 1386
1387 1387 if msng_cl_lst:
1388 1388 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1389 1389
1390 1390 return util.chunkbuffer(gengroup())
1391 1391
1392 1392 def changegroup(self, basenodes, source):
1393 1393 """Generate a changegroup of all nodes that we have that a recipient
1394 1394 doesn't.
1395 1395
1396 1396 This is much easier than the previous function as we can assume that
1397 1397 the recipient has any changenode we aren't sending them."""
1398 1398
1399 1399 self.hook('preoutgoing', throw=True, source=source)
1400 1400
1401 1401 cl = self.changelog
1402 1402 nodes = cl.nodesbetween(basenodes, None)[0]
1403 1403 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1404 1404
1405 1405 def identity(x):
1406 1406 return x
1407 1407
1408 1408 def gennodelst(revlog):
1409 1409 for r in xrange(0, revlog.count()):
1410 1410 n = revlog.node(r)
1411 1411 if revlog.linkrev(n) in revset:
1412 1412 yield n
1413 1413
1414 1414 def changed_file_collector(changedfileset):
1415 1415 def collect_changed_files(clnode):
1416 1416 c = cl.read(clnode)
1417 1417 for fname in c[3]:
1418 1418 changedfileset[fname] = 1
1419 1419 return collect_changed_files
1420 1420
1421 1421 def lookuprevlink_func(revlog):
1422 1422 def lookuprevlink(n):
1423 1423 return cl.node(revlog.linkrev(n))
1424 1424 return lookuprevlink
1425 1425
1426 1426 def gengroup():
1427 1427 # construct a list of all changed files
1428 1428 changedfiles = {}
1429 1429
1430 1430 for chnk in cl.group(nodes, identity,
1431 1431 changed_file_collector(changedfiles)):
1432 1432 yield chnk
1433 1433 changedfiles = changedfiles.keys()
1434 1434 changedfiles.sort()
1435 1435
1436 1436 mnfst = self.manifest
1437 1437 nodeiter = gennodelst(mnfst)
1438 1438 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1439 1439 yield chnk
1440 1440
1441 1441 for fname in changedfiles:
1442 1442 filerevlog = self.file(fname)
1443 1443 nodeiter = gennodelst(filerevlog)
1444 1444 nodeiter = list(nodeiter)
1445 1445 if nodeiter:
1446 1446 yield changegroup.genchunk(fname)
1447 1447 lookup = lookuprevlink_func(filerevlog)
1448 1448 for chnk in filerevlog.group(nodeiter, lookup):
1449 1449 yield chnk
1450 1450
1451 1451 yield changegroup.closechunk()
1452 1452
1453 1453 if nodes:
1454 1454 self.hook('outgoing', node=hex(nodes[0]), source=source)
1455 1455
1456 1456 return util.chunkbuffer(gengroup())
1457 1457
1458 def addchangegroup(self, source):
1458 def addchangegroup(self, source, srctype):
1459 1459 """add changegroup to repo.
1460 1460 returns number of heads modified or added + 1."""
1461 1461
1462 1462 def csmap(x):
1463 1463 self.ui.debug(_("add changeset %s\n") % short(x))
1464 1464 return cl.count()
1465 1465
1466 1466 def revmap(x):
1467 1467 return cl.rev(x)
1468 1468
1469 1469 if not source:
1470 1470 return 0
1471 1471
1472 self.hook('prechangegroup', throw=True, source=source)
1472 self.hook('prechangegroup', throw=True, source=srctype)
1473 1473
1474 1474 changesets = files = revisions = 0
1475 1475
1476 1476 tr = self.transaction()
1477 1477
1478 1478 # write changelog and manifest data to temp files so
1479 1479 # concurrent readers will not see inconsistent view
1480 1480 cl = appendfile.appendchangelog(self.opener, self.changelog.version)
1481 1481
1482 1482 oldheads = len(cl.heads())
1483 1483
1484 1484 # pull off the changeset group
1485 1485 self.ui.status(_("adding changesets\n"))
1486 1486 co = cl.tip()
1487 1487 chunkiter = changegroup.chunkiter(source)
1488 1488 cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
1489 1489 cnr, cor = map(cl.rev, (cn, co))
1490 1490 if cn == nullid:
1491 1491 cnr = cor
1492 1492 changesets = cnr - cor
1493 1493
1494 1494 mf = appendfile.appendmanifest(self.opener, self.manifest.version)
1495 1495
1496 1496 # pull off the manifest group
1497 1497 self.ui.status(_("adding manifests\n"))
1498 1498 mm = mf.tip()
1499 1499 chunkiter = changegroup.chunkiter(source)
1500 1500 mo = mf.addgroup(chunkiter, revmap, tr)
1501 1501
1502 1502 # process the files
1503 1503 self.ui.status(_("adding file changes\n"))
1504 1504 while 1:
1505 1505 f = changegroup.getchunk(source)
1506 1506 if not f:
1507 1507 break
1508 1508 self.ui.debug(_("adding %s revisions\n") % f)
1509 1509 fl = self.file(f)
1510 1510 o = fl.count()
1511 1511 chunkiter = changegroup.chunkiter(source)
1512 1512 n = fl.addgroup(chunkiter, revmap, tr)
1513 1513 revisions += fl.count() - o
1514 1514 files += 1
1515 1515
1516 1516 # write order here is important so concurrent readers will see
1517 1517 # consistent view of repo
1518 1518 mf.writedata()
1519 1519 cl.writedata()
1520 1520
1521 1521 # make changelog and manifest see real files again
1522 1522 self.changelog = changelog.changelog(self.opener, self.changelog.version)
1523 1523 self.manifest = manifest.manifest(self.opener, self.manifest.version)
1524 1524 self.changelog.checkinlinesize(tr)
1525 1525 self.manifest.checkinlinesize(tr)
1526 1526
1527 1527 newheads = len(self.changelog.heads())
1528 1528 heads = ""
1529 1529 if oldheads and newheads > oldheads:
1530 1530 heads = _(" (+%d heads)") % (newheads - oldheads)
1531 1531
1532 1532 self.ui.status(_("added %d changesets"
1533 1533 " with %d changes to %d files%s\n")
1534 1534 % (changesets, revisions, files, heads))
1535 1535
1536 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 1539 tr.close()
1540 1540
1541 1541 if changesets > 0:
1542 1542 self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
1543 source=source)
1543 source=srctype)
1544 1544
1545 1545 for i in range(cor + 1, cnr + 1):
1546 1546 self.hook("incoming", node=hex(self.changelog.node(i)),
1547 source=source)
1547 source=srctype)
1548 1548
1549 1549 return newheads - oldheads + 1
1550 1550
1551 1551 def update(self, node, allow=False, force=False, choose=None,
1552 1552 moddirstate=True, forcemerge=False, wlock=None, show_stats=True):
1553 1553 pl = self.dirstate.parents()
1554 1554 if not force and pl[1] != nullid:
1555 1555 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1556 1556 return 1
1557 1557
1558 1558 err = False
1559 1559
1560 1560 p1, p2 = pl[0], node
1561 1561 pa = self.changelog.ancestor(p1, p2)
1562 1562 m1n = self.changelog.read(p1)[0]
1563 1563 m2n = self.changelog.read(p2)[0]
1564 1564 man = self.manifest.ancestor(m1n, m2n)
1565 1565 m1 = self.manifest.read(m1n)
1566 1566 mf1 = self.manifest.readflags(m1n)
1567 1567 m2 = self.manifest.read(m2n).copy()
1568 1568 mf2 = self.manifest.readflags(m2n)
1569 1569 ma = self.manifest.read(man)
1570 1570 mfa = self.manifest.readflags(man)
1571 1571
1572 1572 modified, added, removed, deleted, unknown = self.changes()
1573 1573
1574 1574 # is this a jump, or a merge? i.e. is there a linear path
1575 1575 # from p1 to p2?
1576 1576 linear_path = (pa == p1 or pa == p2)
1577 1577
1578 1578 if allow and linear_path:
1579 1579 raise util.Abort(_("there is nothing to merge, "
1580 1580 "just use 'hg update'"))
1581 1581 if allow and not forcemerge:
1582 1582 if modified or added or removed:
1583 1583 raise util.Abort(_("outstanding uncommitted changes"))
1584 1584 if not forcemerge and not force:
1585 1585 for f in unknown:
1586 1586 if f in m2:
1587 1587 t1 = self.wread(f)
1588 1588 t2 = self.file(f).read(m2[f])
1589 1589 if cmp(t1, t2) != 0:
1590 1590 raise util.Abort(_("'%s' already exists in the working"
1591 1591 " dir and differs from remote") % f)
1592 1592
1593 1593 # resolve the manifest to determine which files
1594 1594 # we care about merging
1595 1595 self.ui.note(_("resolving manifests\n"))
1596 1596 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1597 1597 (force, allow, moddirstate, linear_path))
1598 1598 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1599 1599 (short(man), short(m1n), short(m2n)))
1600 1600
1601 1601 merge = {}
1602 1602 get = {}
1603 1603 remove = []
1604 1604
1605 1605 # construct a working dir manifest
1606 1606 mw = m1.copy()
1607 1607 mfw = mf1.copy()
1608 1608 umap = dict.fromkeys(unknown)
1609 1609
1610 1610 for f in added + modified + unknown:
1611 1611 mw[f] = ""
1612 1612 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1613 1613
1614 1614 if moddirstate and not wlock:
1615 1615 wlock = self.wlock()
1616 1616
1617 1617 for f in deleted + removed:
1618 1618 if f in mw:
1619 1619 del mw[f]
1620 1620
1621 1621 # If we're jumping between revisions (as opposed to merging),
1622 1622 # and if neither the working directory nor the target rev has
1623 1623 # the file, then we need to remove it from the dirstate, to
1624 1624 # prevent the dirstate from listing the file when it is no
1625 1625 # longer in the manifest.
1626 1626 if moddirstate and linear_path and f not in m2:
1627 1627 self.dirstate.forget((f,))
1628 1628
1629 1629 # Compare manifests
1630 1630 for f, n in mw.iteritems():
1631 1631 if choose and not choose(f):
1632 1632 continue
1633 1633 if f in m2:
1634 1634 s = 0
1635 1635
1636 1636 # is the wfile new since m1, and match m2?
1637 1637 if f not in m1:
1638 1638 t1 = self.wread(f)
1639 1639 t2 = self.file(f).read(m2[f])
1640 1640 if cmp(t1, t2) == 0:
1641 1641 n = m2[f]
1642 1642 del t1, t2
1643 1643
1644 1644 # are files different?
1645 1645 if n != m2[f]:
1646 1646 a = ma.get(f, nullid)
1647 1647 # are both different from the ancestor?
1648 1648 if n != a and m2[f] != a:
1649 1649 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1650 1650 # merge executable bits
1651 1651 # "if we changed or they changed, change in merge"
1652 1652 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1653 1653 mode = ((a^b) | (a^c)) ^ a
1654 1654 merge[f] = (m1.get(f, nullid), m2[f], mode)
1655 1655 s = 1
1656 1656 # are we clobbering?
1657 1657 # is remote's version newer?
1658 1658 # or are we going back in time?
1659 1659 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1660 1660 self.ui.debug(_(" remote %s is newer, get\n") % f)
1661 1661 get[f] = m2[f]
1662 1662 s = 1
1663 1663 elif f in umap or f in added:
1664 1664 # this unknown file is the same as the checkout
1665 1665 # we need to reset the dirstate if the file was added
1666 1666 get[f] = m2[f]
1667 1667
1668 1668 if not s and mfw[f] != mf2[f]:
1669 1669 if force:
1670 1670 self.ui.debug(_(" updating permissions for %s\n") % f)
1671 1671 util.set_exec(self.wjoin(f), mf2[f])
1672 1672 else:
1673 1673 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1674 1674 mode = ((a^b) | (a^c)) ^ a
1675 1675 if mode != b:
1676 1676 self.ui.debug(_(" updating permissions for %s\n")
1677 1677 % f)
1678 1678 util.set_exec(self.wjoin(f), mode)
1679 1679 del m2[f]
1680 1680 elif f in ma:
1681 1681 if n != ma[f]:
1682 1682 r = _("d")
1683 1683 if not force and (linear_path or allow):
1684 1684 r = self.ui.prompt(
1685 1685 (_(" local changed %s which remote deleted\n") % f) +
1686 1686 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1687 1687 if r == _("d"):
1688 1688 remove.append(f)
1689 1689 else:
1690 1690 self.ui.debug(_("other deleted %s\n") % f)
1691 1691 remove.append(f) # other deleted it
1692 1692 else:
1693 1693 # file is created on branch or in working directory
1694 1694 if force and f not in umap:
1695 1695 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1696 1696 remove.append(f)
1697 1697 elif n == m1.get(f, nullid): # same as parent
1698 1698 if p2 == pa: # going backwards?
1699 1699 self.ui.debug(_("remote deleted %s\n") % f)
1700 1700 remove.append(f)
1701 1701 else:
1702 1702 self.ui.debug(_("local modified %s, keeping\n") % f)
1703 1703 else:
1704 1704 self.ui.debug(_("working dir created %s, keeping\n") % f)
1705 1705
1706 1706 for f, n in m2.iteritems():
1707 1707 if choose and not choose(f):
1708 1708 continue
1709 1709 if f[0] == "/":
1710 1710 continue
1711 1711 if f in ma and n != ma[f]:
1712 1712 r = _("k")
1713 1713 if not force and (linear_path or allow):
1714 1714 r = self.ui.prompt(
1715 1715 (_("remote changed %s which local deleted\n") % f) +
1716 1716 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1717 1717 if r == _("k"):
1718 1718 get[f] = n
1719 1719 elif f not in ma:
1720 1720 self.ui.debug(_("remote created %s\n") % f)
1721 1721 get[f] = n
1722 1722 else:
1723 1723 if force or p2 == pa: # going backwards?
1724 1724 self.ui.debug(_("local deleted %s, recreating\n") % f)
1725 1725 get[f] = n
1726 1726 else:
1727 1727 self.ui.debug(_("local deleted %s\n") % f)
1728 1728
1729 1729 del mw, m1, m2, ma
1730 1730
1731 1731 if force:
1732 1732 for f in merge:
1733 1733 get[f] = merge[f][1]
1734 1734 merge = {}
1735 1735
1736 1736 if linear_path or force:
1737 1737 # we don't need to do any magic, just jump to the new rev
1738 1738 branch_merge = False
1739 1739 p1, p2 = p2, nullid
1740 1740 else:
1741 1741 if not allow:
1742 1742 self.ui.status(_("this update spans a branch"
1743 1743 " affecting the following files:\n"))
1744 1744 fl = merge.keys() + get.keys()
1745 1745 fl.sort()
1746 1746 for f in fl:
1747 1747 cf = ""
1748 1748 if f in merge:
1749 1749 cf = _(" (resolve)")
1750 1750 self.ui.status(" %s%s\n" % (f, cf))
1751 1751 self.ui.warn(_("aborting update spanning branches!\n"))
1752 1752 self.ui.status(_("(use 'hg merge' to merge across branches"
1753 1753 " or 'hg update -C' to lose changes)\n"))
1754 1754 return 1
1755 1755 branch_merge = True
1756 1756
1757 1757 # get the files we don't need to change
1758 1758 files = get.keys()
1759 1759 files.sort()
1760 1760 for f in files:
1761 1761 if f[0] == "/":
1762 1762 continue
1763 1763 self.ui.note(_("getting %s\n") % f)
1764 1764 t = self.file(f).read(get[f])
1765 1765 self.wwrite(f, t)
1766 1766 util.set_exec(self.wjoin(f), mf2[f])
1767 1767 if moddirstate:
1768 1768 if branch_merge:
1769 1769 self.dirstate.update([f], 'n', st_mtime=-1)
1770 1770 else:
1771 1771 self.dirstate.update([f], 'n')
1772 1772
1773 1773 # merge the tricky bits
1774 1774 failedmerge = []
1775 1775 files = merge.keys()
1776 1776 files.sort()
1777 1777 xp1 = hex(p1)
1778 1778 xp2 = hex(p2)
1779 1779 for f in files:
1780 1780 self.ui.status(_("merging %s\n") % f)
1781 1781 my, other, flag = merge[f]
1782 1782 ret = self.merge3(f, my, other, xp1, xp2)
1783 1783 if ret:
1784 1784 err = True
1785 1785 failedmerge.append(f)
1786 1786 util.set_exec(self.wjoin(f), flag)
1787 1787 if moddirstate:
1788 1788 if branch_merge:
1789 1789 # We've done a branch merge, mark this file as merged
1790 1790 # so that we properly record the merger later
1791 1791 self.dirstate.update([f], 'm')
1792 1792 else:
1793 1793 # We've update-merged a locally modified file, so
1794 1794 # we set the dirstate to emulate a normal checkout
1795 1795 # of that file some time in the past. Thus our
1796 1796 # merge will appear as a normal local file
1797 1797 # modification.
1798 1798 f_len = len(self.file(f).read(other))
1799 1799 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1800 1800
1801 1801 remove.sort()
1802 1802 for f in remove:
1803 1803 self.ui.note(_("removing %s\n") % f)
1804 1804 util.audit_path(f)
1805 1805 try:
1806 1806 util.unlink(self.wjoin(f))
1807 1807 except OSError, inst:
1808 1808 if inst.errno != errno.ENOENT:
1809 1809 self.ui.warn(_("update failed to remove %s: %s!\n") %
1810 1810 (f, inst.strerror))
1811 1811 if moddirstate:
1812 1812 if branch_merge:
1813 1813 self.dirstate.update(remove, 'r')
1814 1814 else:
1815 1815 self.dirstate.forget(remove)
1816 1816
1817 1817 if moddirstate:
1818 1818 self.dirstate.setparents(p1, p2)
1819 1819
1820 1820 if show_stats:
1821 1821 stats = ((len(get), _("updated")),
1822 1822 (len(merge) - len(failedmerge), _("merged")),
1823 1823 (len(remove), _("removed")),
1824 1824 (len(failedmerge), _("unresolved")))
1825 1825 note = ", ".join([_("%d files %s") % s for s in stats])
1826 1826 self.ui.status("%s\n" % note)
1827 1827 if moddirstate:
1828 1828 if branch_merge:
1829 1829 if failedmerge:
1830 1830 self.ui.status(_("There are unresolved merges,"
1831 1831 " you can redo the full merge using:\n"
1832 1832 " hg update -C %s\n"
1833 1833 " hg merge %s\n"
1834 1834 % (self.changelog.rev(p1),
1835 1835 self.changelog.rev(p2))))
1836 1836 else:
1837 1837 self.ui.status(_("(branch merge, don't forget to commit)\n"))
1838 1838 elif failedmerge:
1839 1839 self.ui.status(_("There are unresolved merges with"
1840 1840 " locally modified files.\n"))
1841 1841
1842 1842 return err
1843 1843
1844 1844 def merge3(self, fn, my, other, p1, p2):
1845 1845 """perform a 3-way merge in the working directory"""
1846 1846
1847 1847 def temp(prefix, node):
1848 1848 pre = "%s~%s." % (os.path.basename(fn), prefix)
1849 1849 (fd, name) = tempfile.mkstemp(prefix=pre)
1850 1850 f = os.fdopen(fd, "wb")
1851 1851 self.wwrite(fn, fl.read(node), f)
1852 1852 f.close()
1853 1853 return name
1854 1854
1855 1855 fl = self.file(fn)
1856 1856 base = fl.ancestor(my, other)
1857 1857 a = self.wjoin(fn)
1858 1858 b = temp("base", base)
1859 1859 c = temp("other", other)
1860 1860
1861 1861 self.ui.note(_("resolving %s\n") % fn)
1862 1862 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1863 1863 (fn, short(my), short(other), short(base)))
1864 1864
1865 1865 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1866 1866 or "hgmerge")
1867 1867 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1868 1868 environ={'HG_FILE': fn,
1869 1869 'HG_MY_NODE': p1,
1870 1870 'HG_OTHER_NODE': p2,
1871 1871 'HG_FILE_MY_NODE': hex(my),
1872 1872 'HG_FILE_OTHER_NODE': hex(other),
1873 1873 'HG_FILE_BASE_NODE': hex(base)})
1874 1874 if r:
1875 1875 self.ui.warn(_("merging %s failed!\n") % fn)
1876 1876
1877 1877 os.unlink(b)
1878 1878 os.unlink(c)
1879 1879 return r
1880 1880
1881 1881 def verify(self):
1882 1882 filelinkrevs = {}
1883 1883 filenodes = {}
1884 1884 changesets = revisions = files = 0
1885 1885 errors = [0]
1886 1886 warnings = [0]
1887 1887 neededmanifests = {}
1888 1888
1889 1889 def err(msg):
1890 1890 self.ui.warn(msg + "\n")
1891 1891 errors[0] += 1
1892 1892
1893 1893 def warn(msg):
1894 1894 self.ui.warn(msg + "\n")
1895 1895 warnings[0] += 1
1896 1896
1897 1897 def checksize(obj, name):
1898 1898 d = obj.checksize()
1899 1899 if d[0]:
1900 1900 err(_("%s data length off by %d bytes") % (name, d[0]))
1901 1901 if d[1]:
1902 1902 err(_("%s index contains %d extra bytes") % (name, d[1]))
1903 1903
1904 1904 def checkversion(obj, name):
1905 1905 if obj.version != revlog.REVLOGV0:
1906 1906 if not revlogv1:
1907 1907 warn(_("warning: `%s' uses revlog format 1") % name)
1908 1908 elif revlogv1:
1909 1909 warn(_("warning: `%s' uses revlog format 0") % name)
1910 1910
1911 1911 revlogv1 = self.revlogversion != revlog.REVLOGV0
1912 1912 if self.ui.verbose or revlogv1 != self.revlogv1:
1913 1913 self.ui.status(_("repository uses revlog format %d\n") %
1914 1914 (revlogv1 and 1 or 0))
1915 1915
1916 1916 seen = {}
1917 1917 self.ui.status(_("checking changesets\n"))
1918 1918 checksize(self.changelog, "changelog")
1919 1919
1920 1920 for i in range(self.changelog.count()):
1921 1921 changesets += 1
1922 1922 n = self.changelog.node(i)
1923 1923 l = self.changelog.linkrev(n)
1924 1924 if l != i:
1925 1925 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1926 1926 if n in seen:
1927 1927 err(_("duplicate changeset at revision %d") % i)
1928 1928 seen[n] = 1
1929 1929
1930 1930 for p in self.changelog.parents(n):
1931 1931 if p not in self.changelog.nodemap:
1932 1932 err(_("changeset %s has unknown parent %s") %
1933 1933 (short(n), short(p)))
1934 1934 try:
1935 1935 changes = self.changelog.read(n)
1936 1936 except KeyboardInterrupt:
1937 1937 self.ui.warn(_("interrupted"))
1938 1938 raise
1939 1939 except Exception, inst:
1940 1940 err(_("unpacking changeset %s: %s") % (short(n), inst))
1941 1941 continue
1942 1942
1943 1943 neededmanifests[changes[0]] = n
1944 1944
1945 1945 for f in changes[3]:
1946 1946 filelinkrevs.setdefault(f, []).append(i)
1947 1947
1948 1948 seen = {}
1949 1949 self.ui.status(_("checking manifests\n"))
1950 1950 checkversion(self.manifest, "manifest")
1951 1951 checksize(self.manifest, "manifest")
1952 1952
1953 1953 for i in range(self.manifest.count()):
1954 1954 n = self.manifest.node(i)
1955 1955 l = self.manifest.linkrev(n)
1956 1956
1957 1957 if l < 0 or l >= self.changelog.count():
1958 1958 err(_("bad manifest link (%d) at revision %d") % (l, i))
1959 1959
1960 1960 if n in neededmanifests:
1961 1961 del neededmanifests[n]
1962 1962
1963 1963 if n in seen:
1964 1964 err(_("duplicate manifest at revision %d") % i)
1965 1965
1966 1966 seen[n] = 1
1967 1967
1968 1968 for p in self.manifest.parents(n):
1969 1969 if p not in self.manifest.nodemap:
1970 1970 err(_("manifest %s has unknown parent %s") %
1971 1971 (short(n), short(p)))
1972 1972
1973 1973 try:
1974 1974 delta = mdiff.patchtext(self.manifest.delta(n))
1975 1975 except KeyboardInterrupt:
1976 1976 self.ui.warn(_("interrupted"))
1977 1977 raise
1978 1978 except Exception, inst:
1979 1979 err(_("unpacking manifest %s: %s") % (short(n), inst))
1980 1980 continue
1981 1981
1982 1982 try:
1983 1983 ff = [ l.split('\0') for l in delta.splitlines() ]
1984 1984 for f, fn in ff:
1985 1985 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1986 1986 except (ValueError, TypeError), inst:
1987 1987 err(_("broken delta in manifest %s: %s") % (short(n), inst))
1988 1988
1989 1989 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1990 1990
1991 1991 for m, c in neededmanifests.items():
1992 1992 err(_("Changeset %s refers to unknown manifest %s") %
1993 1993 (short(m), short(c)))
1994 1994 del neededmanifests
1995 1995
1996 1996 for f in filenodes:
1997 1997 if f not in filelinkrevs:
1998 1998 err(_("file %s in manifest but not in changesets") % f)
1999 1999
2000 2000 for f in filelinkrevs:
2001 2001 if f not in filenodes:
2002 2002 err(_("file %s in changeset but not in manifest") % f)
2003 2003
2004 2004 self.ui.status(_("checking files\n"))
2005 2005 ff = filenodes.keys()
2006 2006 ff.sort()
2007 2007 for f in ff:
2008 2008 if f == "/dev/null":
2009 2009 continue
2010 2010 files += 1
2011 2011 if not f:
2012 2012 err(_("file without name in manifest %s") % short(n))
2013 2013 continue
2014 2014 fl = self.file(f)
2015 2015 checkversion(fl, f)
2016 2016 checksize(fl, f)
2017 2017
2018 2018 nodes = {nullid: 1}
2019 2019 seen = {}
2020 2020 for i in range(fl.count()):
2021 2021 revisions += 1
2022 2022 n = fl.node(i)
2023 2023
2024 2024 if n in seen:
2025 2025 err(_("%s: duplicate revision %d") % (f, i))
2026 2026 if n not in filenodes[f]:
2027 2027 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
2028 2028 else:
2029 2029 del filenodes[f][n]
2030 2030
2031 2031 flr = fl.linkrev(n)
2032 2032 if flr not in filelinkrevs.get(f, []):
2033 2033 err(_("%s:%s points to unexpected changeset %d")
2034 2034 % (f, short(n), flr))
2035 2035 else:
2036 2036 filelinkrevs[f].remove(flr)
2037 2037
2038 2038 # verify contents
2039 2039 try:
2040 2040 t = fl.read(n)
2041 2041 except KeyboardInterrupt:
2042 2042 self.ui.warn(_("interrupted"))
2043 2043 raise
2044 2044 except Exception, inst:
2045 2045 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
2046 2046
2047 2047 # verify parents
2048 2048 (p1, p2) = fl.parents(n)
2049 2049 if p1 not in nodes:
2050 2050 err(_("file %s:%s unknown parent 1 %s") %
2051 2051 (f, short(n), short(p1)))
2052 2052 if p2 not in nodes:
2053 2053 err(_("file %s:%s unknown parent 2 %s") %
2054 2054 (f, short(n), short(p1)))
2055 2055 nodes[n] = 1
2056 2056
2057 2057 # cross-check
2058 2058 for node in filenodes[f]:
2059 2059 err(_("node %s in manifests not in %s") % (hex(node), f))
2060 2060
2061 2061 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
2062 2062 (files, changesets, revisions))
2063 2063
2064 2064 if warnings[0]:
2065 2065 self.ui.warn(_("%d warnings encountered!\n") % warnings[0])
2066 2066 if errors[0]:
2067 2067 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
2068 2068 return 1
2069 2069
2070 2070 # used to avoid circular references so destructors work
2071 2071 def aftertrans(base):
2072 2072 p = base
2073 2073 def a():
2074 2074 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
2075 2075 util.rename(os.path.join(p, "journal.dirstate"),
2076 2076 os.path.join(p, "undo.dirstate"))
2077 2077 return a
2078 2078
@@ -1,155 +1,155 b''
1 1 # sshrepo.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from remoterepo import *
10 10 from i18n import gettext as _
11 11 from demandload import *
12 12 demandload(globals(), "hg os re stat util")
13 13
14 14 class sshrepository(remoterepository):
15 15 def __init__(self, ui, path):
16 16 self.url = path
17 17 self.ui = ui
18 18
19 19 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
20 20 if not m:
21 21 raise hg.RepoError(_("couldn't parse destination %s") % path)
22 22
23 23 self.user = m.group(2)
24 24 self.host = m.group(3)
25 25 self.port = m.group(5)
26 26 self.path = m.group(7) or "."
27 27
28 28 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
29 29 args = self.port and ("%s -p %s") % (args, self.port) or args
30 30
31 31 sshcmd = self.ui.config("ui", "ssh", "ssh")
32 32 remotecmd = self.ui.config("ui", "remotecmd", "hg")
33 33 cmd = '%s %s "%s -R %s serve --stdio"'
34 34 cmd = cmd % (sshcmd, args, remotecmd, self.path)
35 35
36 36 ui.note('running %s\n' % cmd)
37 37 self.pipeo, self.pipei, self.pipee = os.popen3(cmd, 'b')
38 38
39 39 # skip any noise generated by remote shell
40 40 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
41 41 l1 = ""
42 42 l2 = "dummy"
43 43 max_noise = 500
44 44 while l2 and max_noise:
45 45 l2 = r.readline()
46 46 self.readerr()
47 47 if l1 == "1\n" and l2 == "\n":
48 48 break
49 49 if l1:
50 50 ui.debug(_("remote: "), l1)
51 51 l1 = l2
52 52 max_noise -= 1
53 53 else:
54 54 if l1:
55 55 ui.debug(_("remote: "), l1)
56 56 raise hg.RepoError(_("no response from remote hg"))
57 57
58 58 def readerr(self):
59 59 while 1:
60 60 size = util.fstat(self.pipee).st_size
61 61 if size == 0: break
62 62 l = self.pipee.readline()
63 63 if not l: break
64 64 self.ui.status(_("remote: "), l)
65 65
66 66 def __del__(self):
67 67 try:
68 68 self.pipeo.close()
69 69 self.pipei.close()
70 70 # read the error descriptor until EOF
71 71 for l in self.pipee:
72 72 self.ui.status(_("remote: "), l)
73 73 self.pipee.close()
74 74 except:
75 75 pass
76 76
77 77 def dev(self):
78 78 return -1
79 79
80 80 def do_cmd(self, cmd, **args):
81 81 self.ui.debug(_("sending %s command\n") % cmd)
82 82 self.pipeo.write("%s\n" % cmd)
83 83 for k, v in args.items():
84 84 self.pipeo.write("%s %d\n" % (k, len(v)))
85 85 self.pipeo.write(v)
86 86 self.pipeo.flush()
87 87
88 88 return self.pipei
89 89
90 90 def call(self, cmd, **args):
91 91 r = self.do_cmd(cmd, **args)
92 92 l = r.readline()
93 93 self.readerr()
94 94 try:
95 95 l = int(l)
96 96 except:
97 97 raise hg.RepoError(_("unexpected response '%s'") % l)
98 98 return r.read(l)
99 99
100 100 def lock(self):
101 101 self.call("lock")
102 102 return remotelock(self)
103 103
104 104 def unlock(self):
105 105 self.call("unlock")
106 106
107 107 def heads(self):
108 108 d = self.call("heads")
109 109 try:
110 110 return map(bin, d[:-1].split(" "))
111 111 except:
112 112 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
113 113
114 114 def branches(self, nodes):
115 115 n = " ".join(map(hex, nodes))
116 116 d = self.call("branches", nodes=n)
117 117 try:
118 118 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
119 119 return br
120 120 except:
121 121 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
122 122
123 123 def between(self, pairs):
124 124 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
125 125 d = self.call("between", pairs=n)
126 126 try:
127 127 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
128 128 return p
129 129 except:
130 130 raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
131 131
132 132 def changegroup(self, nodes, kind):
133 133 n = " ".join(map(hex, nodes))
134 134 f = self.do_cmd("changegroup", roots=n)
135 135 return self.pipei
136 136
137 def addchangegroup(self, cg):
137 def addchangegroup(self, cg, source):
138 138 d = self.call("addchangegroup")
139 139 if d:
140 140 raise hg.RepoError(_("push refused: %s"), d)
141 141
142 142 while 1:
143 143 d = cg.read(4096)
144 144 if not d: break
145 145 self.pipeo.write(d)
146 146 self.readerr()
147 147
148 148 self.pipeo.flush()
149 149
150 150 self.readerr()
151 151 l = int(self.pipei.readline())
152 152 r = self.pipei.read(l)
153 153 if not r:
154 154 return 1
155 155 return int(r)
General Comments 0
You need to be logged in to leave comments. Login now