##// END OF EJS Templates
py3: handle opts correctly for `hg add`...
Pulkit Goyal -
r32147:a77e61b4 default
parent child Browse files
Show More
@@ -1,3486 +1,3486 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14 import tempfile
14 import tempfile
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 bin,
18 bin,
19 hex,
19 hex,
20 nullid,
20 nullid,
21 nullrev,
21 nullrev,
22 short,
22 short,
23 )
23 )
24
24
25 from . import (
25 from . import (
26 bookmarks,
26 bookmarks,
27 changelog,
27 changelog,
28 copies,
28 copies,
29 crecord as crecordmod,
29 crecord as crecordmod,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 graphmod,
33 graphmod,
34 lock as lockmod,
34 lock as lockmod,
35 match as matchmod,
35 match as matchmod,
36 obsolete,
36 obsolete,
37 patch,
37 patch,
38 pathutil,
38 pathutil,
39 phases,
39 phases,
40 pycompat,
40 pycompat,
41 repair,
41 repair,
42 revlog,
42 revlog,
43 revset,
43 revset,
44 scmutil,
44 scmutil,
45 smartset,
45 smartset,
46 templatekw,
46 templatekw,
47 templater,
47 templater,
48 util,
48 util,
49 vfs as vfsmod,
49 vfs as vfsmod,
50 )
50 )
51 stringio = util.stringio
51 stringio = util.stringio
52
52
53 # special string such that everything below this line will be ingored in the
53 # special string such that everything below this line will be ingored in the
54 # editor text
54 # editor text
55 _linebelow = "^HG: ------------------------ >8 ------------------------$"
55 _linebelow = "^HG: ------------------------ >8 ------------------------$"
56
56
57 def ishunk(x):
57 def ishunk(x):
58 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
58 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
59 return isinstance(x, hunkclasses)
59 return isinstance(x, hunkclasses)
60
60
61 def newandmodified(chunks, originalchunks):
61 def newandmodified(chunks, originalchunks):
62 newlyaddedandmodifiedfiles = set()
62 newlyaddedandmodifiedfiles = set()
63 for chunk in chunks:
63 for chunk in chunks:
64 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
64 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
65 originalchunks:
65 originalchunks:
66 newlyaddedandmodifiedfiles.add(chunk.header.filename())
66 newlyaddedandmodifiedfiles.add(chunk.header.filename())
67 return newlyaddedandmodifiedfiles
67 return newlyaddedandmodifiedfiles
68
68
69 def parsealiases(cmd):
69 def parsealiases(cmd):
70 return cmd.lstrip("^").split("|")
70 return cmd.lstrip("^").split("|")
71
71
72 def setupwrapcolorwrite(ui):
72 def setupwrapcolorwrite(ui):
73 # wrap ui.write so diff output can be labeled/colorized
73 # wrap ui.write so diff output can be labeled/colorized
74 def wrapwrite(orig, *args, **kw):
74 def wrapwrite(orig, *args, **kw):
75 label = kw.pop('label', '')
75 label = kw.pop('label', '')
76 for chunk, l in patch.difflabel(lambda: args):
76 for chunk, l in patch.difflabel(lambda: args):
77 orig(chunk, label=label + l)
77 orig(chunk, label=label + l)
78
78
79 oldwrite = ui.write
79 oldwrite = ui.write
80 def wrap(*args, **kwargs):
80 def wrap(*args, **kwargs):
81 return wrapwrite(oldwrite, *args, **kwargs)
81 return wrapwrite(oldwrite, *args, **kwargs)
82 setattr(ui, 'write', wrap)
82 setattr(ui, 'write', wrap)
83 return oldwrite
83 return oldwrite
84
84
85 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
85 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
86 if usecurses:
86 if usecurses:
87 if testfile:
87 if testfile:
88 recordfn = crecordmod.testdecorator(testfile,
88 recordfn = crecordmod.testdecorator(testfile,
89 crecordmod.testchunkselector)
89 crecordmod.testchunkselector)
90 else:
90 else:
91 recordfn = crecordmod.chunkselector
91 recordfn = crecordmod.chunkselector
92
92
93 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
93 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
94
94
95 else:
95 else:
96 return patch.filterpatch(ui, originalhunks, operation)
96 return patch.filterpatch(ui, originalhunks, operation)
97
97
98 def recordfilter(ui, originalhunks, operation=None):
98 def recordfilter(ui, originalhunks, operation=None):
99 """ Prompts the user to filter the originalhunks and return a list of
99 """ Prompts the user to filter the originalhunks and return a list of
100 selected hunks.
100 selected hunks.
101 *operation* is used for to build ui messages to indicate the user what
101 *operation* is used for to build ui messages to indicate the user what
102 kind of filtering they are doing: reverting, committing, shelving, etc.
102 kind of filtering they are doing: reverting, committing, shelving, etc.
103 (see patch.filterpatch).
103 (see patch.filterpatch).
104 """
104 """
105 usecurses = crecordmod.checkcurses(ui)
105 usecurses = crecordmod.checkcurses(ui)
106 testfile = ui.config('experimental', 'crecordtest', None)
106 testfile = ui.config('experimental', 'crecordtest', None)
107 oldwrite = setupwrapcolorwrite(ui)
107 oldwrite = setupwrapcolorwrite(ui)
108 try:
108 try:
109 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
109 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
110 testfile, operation)
110 testfile, operation)
111 finally:
111 finally:
112 ui.write = oldwrite
112 ui.write = oldwrite
113 return newchunks, newopts
113 return newchunks, newopts
114
114
115 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
115 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
116 filterfn, *pats, **opts):
116 filterfn, *pats, **opts):
117 from . import merge as mergemod
117 from . import merge as mergemod
118 opts = pycompat.byteskwargs(opts)
118 opts = pycompat.byteskwargs(opts)
119 if not ui.interactive():
119 if not ui.interactive():
120 if cmdsuggest:
120 if cmdsuggest:
121 msg = _('running non-interactively, use %s instead') % cmdsuggest
121 msg = _('running non-interactively, use %s instead') % cmdsuggest
122 else:
122 else:
123 msg = _('running non-interactively')
123 msg = _('running non-interactively')
124 raise error.Abort(msg)
124 raise error.Abort(msg)
125
125
126 # make sure username is set before going interactive
126 # make sure username is set before going interactive
127 if not opts.get('user'):
127 if not opts.get('user'):
128 ui.username() # raise exception, username not provided
128 ui.username() # raise exception, username not provided
129
129
130 def recordfunc(ui, repo, message, match, opts):
130 def recordfunc(ui, repo, message, match, opts):
131 """This is generic record driver.
131 """This is generic record driver.
132
132
133 Its job is to interactively filter local changes, and
133 Its job is to interactively filter local changes, and
134 accordingly prepare working directory into a state in which the
134 accordingly prepare working directory into a state in which the
135 job can be delegated to a non-interactive commit command such as
135 job can be delegated to a non-interactive commit command such as
136 'commit' or 'qrefresh'.
136 'commit' or 'qrefresh'.
137
137
138 After the actual job is done by non-interactive command, the
138 After the actual job is done by non-interactive command, the
139 working directory is restored to its original state.
139 working directory is restored to its original state.
140
140
141 In the end we'll record interesting changes, and everything else
141 In the end we'll record interesting changes, and everything else
142 will be left in place, so the user can continue working.
142 will be left in place, so the user can continue working.
143 """
143 """
144
144
145 checkunfinished(repo, commit=True)
145 checkunfinished(repo, commit=True)
146 wctx = repo[None]
146 wctx = repo[None]
147 merge = len(wctx.parents()) > 1
147 merge = len(wctx.parents()) > 1
148 if merge:
148 if merge:
149 raise error.Abort(_('cannot partially commit a merge '
149 raise error.Abort(_('cannot partially commit a merge '
150 '(use "hg commit" instead)'))
150 '(use "hg commit" instead)'))
151
151
152 def fail(f, msg):
152 def fail(f, msg):
153 raise error.Abort('%s: %s' % (f, msg))
153 raise error.Abort('%s: %s' % (f, msg))
154
154
155 force = opts.get('force')
155 force = opts.get('force')
156 if not force:
156 if not force:
157 vdirs = []
157 vdirs = []
158 match.explicitdir = vdirs.append
158 match.explicitdir = vdirs.append
159 match.bad = fail
159 match.bad = fail
160
160
161 status = repo.status(match=match)
161 status = repo.status(match=match)
162 if not force:
162 if not force:
163 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
163 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
164 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
164 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
165 diffopts.nodates = True
165 diffopts.nodates = True
166 diffopts.git = True
166 diffopts.git = True
167 diffopts.showfunc = True
167 diffopts.showfunc = True
168 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
168 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
169 originalchunks = patch.parsepatch(originaldiff)
169 originalchunks = patch.parsepatch(originaldiff)
170
170
171 # 1. filter patch, since we are intending to apply subset of it
171 # 1. filter patch, since we are intending to apply subset of it
172 try:
172 try:
173 chunks, newopts = filterfn(ui, originalchunks)
173 chunks, newopts = filterfn(ui, originalchunks)
174 except patch.PatchError as err:
174 except patch.PatchError as err:
175 raise error.Abort(_('error parsing patch: %s') % err)
175 raise error.Abort(_('error parsing patch: %s') % err)
176 opts.update(newopts)
176 opts.update(newopts)
177
177
178 # We need to keep a backup of files that have been newly added and
178 # We need to keep a backup of files that have been newly added and
179 # modified during the recording process because there is a previous
179 # modified during the recording process because there is a previous
180 # version without the edit in the workdir
180 # version without the edit in the workdir
181 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
181 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
182 contenders = set()
182 contenders = set()
183 for h in chunks:
183 for h in chunks:
184 try:
184 try:
185 contenders.update(set(h.files()))
185 contenders.update(set(h.files()))
186 except AttributeError:
186 except AttributeError:
187 pass
187 pass
188
188
189 changed = status.modified + status.added + status.removed
189 changed = status.modified + status.added + status.removed
190 newfiles = [f for f in changed if f in contenders]
190 newfiles = [f for f in changed if f in contenders]
191 if not newfiles:
191 if not newfiles:
192 ui.status(_('no changes to record\n'))
192 ui.status(_('no changes to record\n'))
193 return 0
193 return 0
194
194
195 modified = set(status.modified)
195 modified = set(status.modified)
196
196
197 # 2. backup changed files, so we can restore them in the end
197 # 2. backup changed files, so we can restore them in the end
198
198
199 if backupall:
199 if backupall:
200 tobackup = changed
200 tobackup = changed
201 else:
201 else:
202 tobackup = [f for f in newfiles if f in modified or f in \
202 tobackup = [f for f in newfiles if f in modified or f in \
203 newlyaddedandmodifiedfiles]
203 newlyaddedandmodifiedfiles]
204 backups = {}
204 backups = {}
205 if tobackup:
205 if tobackup:
206 backupdir = repo.vfs.join('record-backups')
206 backupdir = repo.vfs.join('record-backups')
207 try:
207 try:
208 os.mkdir(backupdir)
208 os.mkdir(backupdir)
209 except OSError as err:
209 except OSError as err:
210 if err.errno != errno.EEXIST:
210 if err.errno != errno.EEXIST:
211 raise
211 raise
212 try:
212 try:
213 # backup continues
213 # backup continues
214 for f in tobackup:
214 for f in tobackup:
215 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
215 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
216 dir=backupdir)
216 dir=backupdir)
217 os.close(fd)
217 os.close(fd)
218 ui.debug('backup %r as %r\n' % (f, tmpname))
218 ui.debug('backup %r as %r\n' % (f, tmpname))
219 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
219 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
220 backups[f] = tmpname
220 backups[f] = tmpname
221
221
222 fp = stringio()
222 fp = stringio()
223 for c in chunks:
223 for c in chunks:
224 fname = c.filename()
224 fname = c.filename()
225 if fname in backups:
225 if fname in backups:
226 c.write(fp)
226 c.write(fp)
227 dopatch = fp.tell()
227 dopatch = fp.tell()
228 fp.seek(0)
228 fp.seek(0)
229
229
230 # 2.5 optionally review / modify patch in text editor
230 # 2.5 optionally review / modify patch in text editor
231 if opts.get('review', False):
231 if opts.get('review', False):
232 patchtext = (crecordmod.diffhelptext
232 patchtext = (crecordmod.diffhelptext
233 + crecordmod.patchhelptext
233 + crecordmod.patchhelptext
234 + fp.read())
234 + fp.read())
235 reviewedpatch = ui.edit(patchtext, "",
235 reviewedpatch = ui.edit(patchtext, "",
236 extra={"suffix": ".diff"},
236 extra={"suffix": ".diff"},
237 repopath=repo.path)
237 repopath=repo.path)
238 fp.truncate(0)
238 fp.truncate(0)
239 fp.write(reviewedpatch)
239 fp.write(reviewedpatch)
240 fp.seek(0)
240 fp.seek(0)
241
241
242 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
242 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
243 # 3a. apply filtered patch to clean repo (clean)
243 # 3a. apply filtered patch to clean repo (clean)
244 if backups:
244 if backups:
245 # Equivalent to hg.revert
245 # Equivalent to hg.revert
246 m = scmutil.matchfiles(repo, backups.keys())
246 m = scmutil.matchfiles(repo, backups.keys())
247 mergemod.update(repo, repo.dirstate.p1(),
247 mergemod.update(repo, repo.dirstate.p1(),
248 False, True, matcher=m)
248 False, True, matcher=m)
249
249
250 # 3b. (apply)
250 # 3b. (apply)
251 if dopatch:
251 if dopatch:
252 try:
252 try:
253 ui.debug('applying patch\n')
253 ui.debug('applying patch\n')
254 ui.debug(fp.getvalue())
254 ui.debug(fp.getvalue())
255 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
255 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
256 except patch.PatchError as err:
256 except patch.PatchError as err:
257 raise error.Abort(str(err))
257 raise error.Abort(str(err))
258 del fp
258 del fp
259
259
260 # 4. We prepared working directory according to filtered
260 # 4. We prepared working directory according to filtered
261 # patch. Now is the time to delegate the job to
261 # patch. Now is the time to delegate the job to
262 # commit/qrefresh or the like!
262 # commit/qrefresh or the like!
263
263
264 # Make all of the pathnames absolute.
264 # Make all of the pathnames absolute.
265 newfiles = [repo.wjoin(nf) for nf in newfiles]
265 newfiles = [repo.wjoin(nf) for nf in newfiles]
266 return commitfunc(ui, repo, *newfiles, **opts)
266 return commitfunc(ui, repo, *newfiles, **opts)
267 finally:
267 finally:
268 # 5. finally restore backed-up files
268 # 5. finally restore backed-up files
269 try:
269 try:
270 dirstate = repo.dirstate
270 dirstate = repo.dirstate
271 for realname, tmpname in backups.iteritems():
271 for realname, tmpname in backups.iteritems():
272 ui.debug('restoring %r to %r\n' % (tmpname, realname))
272 ui.debug('restoring %r to %r\n' % (tmpname, realname))
273
273
274 if dirstate[realname] == 'n':
274 if dirstate[realname] == 'n':
275 # without normallookup, restoring timestamp
275 # without normallookup, restoring timestamp
276 # may cause partially committed files
276 # may cause partially committed files
277 # to be treated as unmodified
277 # to be treated as unmodified
278 dirstate.normallookup(realname)
278 dirstate.normallookup(realname)
279
279
280 # copystat=True here and above are a hack to trick any
280 # copystat=True here and above are a hack to trick any
281 # editors that have f open that we haven't modified them.
281 # editors that have f open that we haven't modified them.
282 #
282 #
283 # Also note that this racy as an editor could notice the
283 # Also note that this racy as an editor could notice the
284 # file's mtime before we've finished writing it.
284 # file's mtime before we've finished writing it.
285 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
285 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
286 os.unlink(tmpname)
286 os.unlink(tmpname)
287 if tobackup:
287 if tobackup:
288 os.rmdir(backupdir)
288 os.rmdir(backupdir)
289 except OSError:
289 except OSError:
290 pass
290 pass
291
291
292 def recordinwlock(ui, repo, message, match, opts):
292 def recordinwlock(ui, repo, message, match, opts):
293 with repo.wlock():
293 with repo.wlock():
294 return recordfunc(ui, repo, message, match, opts)
294 return recordfunc(ui, repo, message, match, opts)
295
295
296 return commit(ui, repo, recordinwlock, pats, opts)
296 return commit(ui, repo, recordinwlock, pats, opts)
297
297
298 def findpossible(cmd, table, strict=False):
298 def findpossible(cmd, table, strict=False):
299 """
299 """
300 Return cmd -> (aliases, command table entry)
300 Return cmd -> (aliases, command table entry)
301 for each matching command.
301 for each matching command.
302 Return debug commands (or their aliases) only if no normal command matches.
302 Return debug commands (or their aliases) only if no normal command matches.
303 """
303 """
304 choice = {}
304 choice = {}
305 debugchoice = {}
305 debugchoice = {}
306
306
307 if cmd in table:
307 if cmd in table:
308 # short-circuit exact matches, "log" alias beats "^log|history"
308 # short-circuit exact matches, "log" alias beats "^log|history"
309 keys = [cmd]
309 keys = [cmd]
310 else:
310 else:
311 keys = table.keys()
311 keys = table.keys()
312
312
313 allcmds = []
313 allcmds = []
314 for e in keys:
314 for e in keys:
315 aliases = parsealiases(e)
315 aliases = parsealiases(e)
316 allcmds.extend(aliases)
316 allcmds.extend(aliases)
317 found = None
317 found = None
318 if cmd in aliases:
318 if cmd in aliases:
319 found = cmd
319 found = cmd
320 elif not strict:
320 elif not strict:
321 for a in aliases:
321 for a in aliases:
322 if a.startswith(cmd):
322 if a.startswith(cmd):
323 found = a
323 found = a
324 break
324 break
325 if found is not None:
325 if found is not None:
326 if aliases[0].startswith("debug") or found.startswith("debug"):
326 if aliases[0].startswith("debug") or found.startswith("debug"):
327 debugchoice[found] = (aliases, table[e])
327 debugchoice[found] = (aliases, table[e])
328 else:
328 else:
329 choice[found] = (aliases, table[e])
329 choice[found] = (aliases, table[e])
330
330
331 if not choice and debugchoice:
331 if not choice and debugchoice:
332 choice = debugchoice
332 choice = debugchoice
333
333
334 return choice, allcmds
334 return choice, allcmds
335
335
336 def findcmd(cmd, table, strict=True):
336 def findcmd(cmd, table, strict=True):
337 """Return (aliases, command table entry) for command string."""
337 """Return (aliases, command table entry) for command string."""
338 choice, allcmds = findpossible(cmd, table, strict)
338 choice, allcmds = findpossible(cmd, table, strict)
339
339
340 if cmd in choice:
340 if cmd in choice:
341 return choice[cmd]
341 return choice[cmd]
342
342
343 if len(choice) > 1:
343 if len(choice) > 1:
344 clist = choice.keys()
344 clist = choice.keys()
345 clist.sort()
345 clist.sort()
346 raise error.AmbiguousCommand(cmd, clist)
346 raise error.AmbiguousCommand(cmd, clist)
347
347
348 if choice:
348 if choice:
349 return choice.values()[0]
349 return choice.values()[0]
350
350
351 raise error.UnknownCommand(cmd, allcmds)
351 raise error.UnknownCommand(cmd, allcmds)
352
352
353 def findrepo(p):
353 def findrepo(p):
354 while not os.path.isdir(os.path.join(p, ".hg")):
354 while not os.path.isdir(os.path.join(p, ".hg")):
355 oldp, p = p, os.path.dirname(p)
355 oldp, p = p, os.path.dirname(p)
356 if p == oldp:
356 if p == oldp:
357 return None
357 return None
358
358
359 return p
359 return p
360
360
361 def bailifchanged(repo, merge=True, hint=None):
361 def bailifchanged(repo, merge=True, hint=None):
362 """ enforce the precondition that working directory must be clean.
362 """ enforce the precondition that working directory must be clean.
363
363
364 'merge' can be set to false if a pending uncommitted merge should be
364 'merge' can be set to false if a pending uncommitted merge should be
365 ignored (such as when 'update --check' runs).
365 ignored (such as when 'update --check' runs).
366
366
367 'hint' is the usual hint given to Abort exception.
367 'hint' is the usual hint given to Abort exception.
368 """
368 """
369
369
370 if merge and repo.dirstate.p2() != nullid:
370 if merge and repo.dirstate.p2() != nullid:
371 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
371 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
372 modified, added, removed, deleted = repo.status()[:4]
372 modified, added, removed, deleted = repo.status()[:4]
373 if modified or added or removed or deleted:
373 if modified or added or removed or deleted:
374 raise error.Abort(_('uncommitted changes'), hint=hint)
374 raise error.Abort(_('uncommitted changes'), hint=hint)
375 ctx = repo[None]
375 ctx = repo[None]
376 for s in sorted(ctx.substate):
376 for s in sorted(ctx.substate):
377 ctx.sub(s).bailifchanged(hint=hint)
377 ctx.sub(s).bailifchanged(hint=hint)
378
378
379 def logmessage(ui, opts):
379 def logmessage(ui, opts):
380 """ get the log message according to -m and -l option """
380 """ get the log message according to -m and -l option """
381 message = opts.get('message')
381 message = opts.get('message')
382 logfile = opts.get('logfile')
382 logfile = opts.get('logfile')
383
383
384 if message and logfile:
384 if message and logfile:
385 raise error.Abort(_('options --message and --logfile are mutually '
385 raise error.Abort(_('options --message and --logfile are mutually '
386 'exclusive'))
386 'exclusive'))
387 if not message and logfile:
387 if not message and logfile:
388 try:
388 try:
389 if logfile == '-':
389 if logfile == '-':
390 message = ui.fin.read()
390 message = ui.fin.read()
391 else:
391 else:
392 message = '\n'.join(util.readfile(logfile).splitlines())
392 message = '\n'.join(util.readfile(logfile).splitlines())
393 except IOError as inst:
393 except IOError as inst:
394 raise error.Abort(_("can't read commit message '%s': %s") %
394 raise error.Abort(_("can't read commit message '%s': %s") %
395 (logfile, inst.strerror))
395 (logfile, inst.strerror))
396 return message
396 return message
397
397
398 def mergeeditform(ctxorbool, baseformname):
398 def mergeeditform(ctxorbool, baseformname):
399 """return appropriate editform name (referencing a committemplate)
399 """return appropriate editform name (referencing a committemplate)
400
400
401 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
401 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
402 merging is committed.
402 merging is committed.
403
403
404 This returns baseformname with '.merge' appended if it is a merge,
404 This returns baseformname with '.merge' appended if it is a merge,
405 otherwise '.normal' is appended.
405 otherwise '.normal' is appended.
406 """
406 """
407 if isinstance(ctxorbool, bool):
407 if isinstance(ctxorbool, bool):
408 if ctxorbool:
408 if ctxorbool:
409 return baseformname + ".merge"
409 return baseformname + ".merge"
410 elif 1 < len(ctxorbool.parents()):
410 elif 1 < len(ctxorbool.parents()):
411 return baseformname + ".merge"
411 return baseformname + ".merge"
412
412
413 return baseformname + ".normal"
413 return baseformname + ".normal"
414
414
415 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
415 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
416 editform='', **opts):
416 editform='', **opts):
417 """get appropriate commit message editor according to '--edit' option
417 """get appropriate commit message editor according to '--edit' option
418
418
419 'finishdesc' is a function to be called with edited commit message
419 'finishdesc' is a function to be called with edited commit message
420 (= 'description' of the new changeset) just after editing, but
420 (= 'description' of the new changeset) just after editing, but
421 before checking empty-ness. It should return actual text to be
421 before checking empty-ness. It should return actual text to be
422 stored into history. This allows to change description before
422 stored into history. This allows to change description before
423 storing.
423 storing.
424
424
425 'extramsg' is a extra message to be shown in the editor instead of
425 'extramsg' is a extra message to be shown in the editor instead of
426 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
426 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
427 is automatically added.
427 is automatically added.
428
428
429 'editform' is a dot-separated list of names, to distinguish
429 'editform' is a dot-separated list of names, to distinguish
430 the purpose of commit text editing.
430 the purpose of commit text editing.
431
431
432 'getcommiteditor' returns 'commitforceeditor' regardless of
432 'getcommiteditor' returns 'commitforceeditor' regardless of
433 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
433 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
434 they are specific for usage in MQ.
434 they are specific for usage in MQ.
435 """
435 """
436 if edit or finishdesc or extramsg:
436 if edit or finishdesc or extramsg:
437 return lambda r, c, s: commitforceeditor(r, c, s,
437 return lambda r, c, s: commitforceeditor(r, c, s,
438 finishdesc=finishdesc,
438 finishdesc=finishdesc,
439 extramsg=extramsg,
439 extramsg=extramsg,
440 editform=editform)
440 editform=editform)
441 elif editform:
441 elif editform:
442 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
442 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
443 else:
443 else:
444 return commiteditor
444 return commiteditor
445
445
446 def loglimit(opts):
446 def loglimit(opts):
447 """get the log limit according to option -l/--limit"""
447 """get the log limit according to option -l/--limit"""
448 limit = opts.get('limit')
448 limit = opts.get('limit')
449 if limit:
449 if limit:
450 try:
450 try:
451 limit = int(limit)
451 limit = int(limit)
452 except ValueError:
452 except ValueError:
453 raise error.Abort(_('limit must be a positive integer'))
453 raise error.Abort(_('limit must be a positive integer'))
454 if limit <= 0:
454 if limit <= 0:
455 raise error.Abort(_('limit must be positive'))
455 raise error.Abort(_('limit must be positive'))
456 else:
456 else:
457 limit = None
457 limit = None
458 return limit
458 return limit
459
459
460 def makefilename(repo, pat, node, desc=None,
460 def makefilename(repo, pat, node, desc=None,
461 total=None, seqno=None, revwidth=None, pathname=None):
461 total=None, seqno=None, revwidth=None, pathname=None):
462 node_expander = {
462 node_expander = {
463 'H': lambda: hex(node),
463 'H': lambda: hex(node),
464 'R': lambda: str(repo.changelog.rev(node)),
464 'R': lambda: str(repo.changelog.rev(node)),
465 'h': lambda: short(node),
465 'h': lambda: short(node),
466 'm': lambda: re.sub('[^\w]', '_', str(desc))
466 'm': lambda: re.sub('[^\w]', '_', str(desc))
467 }
467 }
468 expander = {
468 expander = {
469 '%': lambda: '%',
469 '%': lambda: '%',
470 'b': lambda: os.path.basename(repo.root),
470 'b': lambda: os.path.basename(repo.root),
471 }
471 }
472
472
473 try:
473 try:
474 if node:
474 if node:
475 expander.update(node_expander)
475 expander.update(node_expander)
476 if node:
476 if node:
477 expander['r'] = (lambda:
477 expander['r'] = (lambda:
478 str(repo.changelog.rev(node)).zfill(revwidth or 0))
478 str(repo.changelog.rev(node)).zfill(revwidth or 0))
479 if total is not None:
479 if total is not None:
480 expander['N'] = lambda: str(total)
480 expander['N'] = lambda: str(total)
481 if seqno is not None:
481 if seqno is not None:
482 expander['n'] = lambda: str(seqno)
482 expander['n'] = lambda: str(seqno)
483 if total is not None and seqno is not None:
483 if total is not None and seqno is not None:
484 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
484 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
485 if pathname is not None:
485 if pathname is not None:
486 expander['s'] = lambda: os.path.basename(pathname)
486 expander['s'] = lambda: os.path.basename(pathname)
487 expander['d'] = lambda: os.path.dirname(pathname) or '.'
487 expander['d'] = lambda: os.path.dirname(pathname) or '.'
488 expander['p'] = lambda: pathname
488 expander['p'] = lambda: pathname
489
489
490 newname = []
490 newname = []
491 patlen = len(pat)
491 patlen = len(pat)
492 i = 0
492 i = 0
493 while i < patlen:
493 while i < patlen:
494 c = pat[i]
494 c = pat[i]
495 if c == '%':
495 if c == '%':
496 i += 1
496 i += 1
497 c = pat[i]
497 c = pat[i]
498 c = expander[c]()
498 c = expander[c]()
499 newname.append(c)
499 newname.append(c)
500 i += 1
500 i += 1
501 return ''.join(newname)
501 return ''.join(newname)
502 except KeyError as inst:
502 except KeyError as inst:
503 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
503 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
504 inst.args[0])
504 inst.args[0])
505
505
506 class _unclosablefile(object):
506 class _unclosablefile(object):
507 def __init__(self, fp):
507 def __init__(self, fp):
508 self._fp = fp
508 self._fp = fp
509
509
510 def close(self):
510 def close(self):
511 pass
511 pass
512
512
513 def __iter__(self):
513 def __iter__(self):
514 return iter(self._fp)
514 return iter(self._fp)
515
515
516 def __getattr__(self, attr):
516 def __getattr__(self, attr):
517 return getattr(self._fp, attr)
517 return getattr(self._fp, attr)
518
518
519 def __enter__(self):
519 def __enter__(self):
520 return self
520 return self
521
521
522 def __exit__(self, exc_type, exc_value, exc_tb):
522 def __exit__(self, exc_type, exc_value, exc_tb):
523 pass
523 pass
524
524
525 def makefileobj(repo, pat, node=None, desc=None, total=None,
525 def makefileobj(repo, pat, node=None, desc=None, total=None,
526 seqno=None, revwidth=None, mode='wb', modemap=None,
526 seqno=None, revwidth=None, mode='wb', modemap=None,
527 pathname=None):
527 pathname=None):
528
528
529 writable = mode not in ('r', 'rb')
529 writable = mode not in ('r', 'rb')
530
530
531 if not pat or pat == '-':
531 if not pat or pat == '-':
532 if writable:
532 if writable:
533 fp = repo.ui.fout
533 fp = repo.ui.fout
534 else:
534 else:
535 fp = repo.ui.fin
535 fp = repo.ui.fin
536 return _unclosablefile(fp)
536 return _unclosablefile(fp)
537 if util.safehasattr(pat, 'write') and writable:
537 if util.safehasattr(pat, 'write') and writable:
538 return pat
538 return pat
539 if util.safehasattr(pat, 'read') and 'r' in mode:
539 if util.safehasattr(pat, 'read') and 'r' in mode:
540 return pat
540 return pat
541 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
541 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
542 if modemap is not None:
542 if modemap is not None:
543 mode = modemap.get(fn, mode)
543 mode = modemap.get(fn, mode)
544 if mode == 'wb':
544 if mode == 'wb':
545 modemap[fn] = 'ab'
545 modemap[fn] = 'ab'
546 return open(fn, mode)
546 return open(fn, mode)
547
547
548 def openrevlog(repo, cmd, file_, opts):
548 def openrevlog(repo, cmd, file_, opts):
549 """opens the changelog, manifest, a filelog or a given revlog"""
549 """opens the changelog, manifest, a filelog or a given revlog"""
550 cl = opts['changelog']
550 cl = opts['changelog']
551 mf = opts['manifest']
551 mf = opts['manifest']
552 dir = opts['dir']
552 dir = opts['dir']
553 msg = None
553 msg = None
554 if cl and mf:
554 if cl and mf:
555 msg = _('cannot specify --changelog and --manifest at the same time')
555 msg = _('cannot specify --changelog and --manifest at the same time')
556 elif cl and dir:
556 elif cl and dir:
557 msg = _('cannot specify --changelog and --dir at the same time')
557 msg = _('cannot specify --changelog and --dir at the same time')
558 elif cl or mf or dir:
558 elif cl or mf or dir:
559 if file_:
559 if file_:
560 msg = _('cannot specify filename with --changelog or --manifest')
560 msg = _('cannot specify filename with --changelog or --manifest')
561 elif not repo:
561 elif not repo:
562 msg = _('cannot specify --changelog or --manifest or --dir '
562 msg = _('cannot specify --changelog or --manifest or --dir '
563 'without a repository')
563 'without a repository')
564 if msg:
564 if msg:
565 raise error.Abort(msg)
565 raise error.Abort(msg)
566
566
567 r = None
567 r = None
568 if repo:
568 if repo:
569 if cl:
569 if cl:
570 r = repo.unfiltered().changelog
570 r = repo.unfiltered().changelog
571 elif dir:
571 elif dir:
572 if 'treemanifest' not in repo.requirements:
572 if 'treemanifest' not in repo.requirements:
573 raise error.Abort(_("--dir can only be used on repos with "
573 raise error.Abort(_("--dir can only be used on repos with "
574 "treemanifest enabled"))
574 "treemanifest enabled"))
575 dirlog = repo.manifestlog._revlog.dirlog(dir)
575 dirlog = repo.manifestlog._revlog.dirlog(dir)
576 if len(dirlog):
576 if len(dirlog):
577 r = dirlog
577 r = dirlog
578 elif mf:
578 elif mf:
579 r = repo.manifestlog._revlog
579 r = repo.manifestlog._revlog
580 elif file_:
580 elif file_:
581 filelog = repo.file(file_)
581 filelog = repo.file(file_)
582 if len(filelog):
582 if len(filelog):
583 r = filelog
583 r = filelog
584 if not r:
584 if not r:
585 if not file_:
585 if not file_:
586 raise error.CommandError(cmd, _('invalid arguments'))
586 raise error.CommandError(cmd, _('invalid arguments'))
587 if not os.path.isfile(file_):
587 if not os.path.isfile(file_):
588 raise error.Abort(_("revlog '%s' not found") % file_)
588 raise error.Abort(_("revlog '%s' not found") % file_)
589 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
589 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
590 file_[:-2] + ".i")
590 file_[:-2] + ".i")
591 return r
591 return r
592
592
593 def copy(ui, repo, pats, opts, rename=False):
593 def copy(ui, repo, pats, opts, rename=False):
594 # called with the repo lock held
594 # called with the repo lock held
595 #
595 #
596 # hgsep => pathname that uses "/" to separate directories
596 # hgsep => pathname that uses "/" to separate directories
597 # ossep => pathname that uses os.sep to separate directories
597 # ossep => pathname that uses os.sep to separate directories
598 cwd = repo.getcwd()
598 cwd = repo.getcwd()
599 targets = {}
599 targets = {}
600 after = opts.get("after")
600 after = opts.get("after")
601 dryrun = opts.get("dry_run")
601 dryrun = opts.get("dry_run")
602 wctx = repo[None]
602 wctx = repo[None]
603
603
604 def walkpat(pat):
604 def walkpat(pat):
605 srcs = []
605 srcs = []
606 if after:
606 if after:
607 badstates = '?'
607 badstates = '?'
608 else:
608 else:
609 badstates = '?r'
609 badstates = '?r'
610 m = scmutil.match(repo[None], [pat], opts, globbed=True)
610 m = scmutil.match(repo[None], [pat], opts, globbed=True)
611 for abs in repo.walk(m):
611 for abs in repo.walk(m):
612 state = repo.dirstate[abs]
612 state = repo.dirstate[abs]
613 rel = m.rel(abs)
613 rel = m.rel(abs)
614 exact = m.exact(abs)
614 exact = m.exact(abs)
615 if state in badstates:
615 if state in badstates:
616 if exact and state == '?':
616 if exact and state == '?':
617 ui.warn(_('%s: not copying - file is not managed\n') % rel)
617 ui.warn(_('%s: not copying - file is not managed\n') % rel)
618 if exact and state == 'r':
618 if exact and state == 'r':
619 ui.warn(_('%s: not copying - file has been marked for'
619 ui.warn(_('%s: not copying - file has been marked for'
620 ' remove\n') % rel)
620 ' remove\n') % rel)
621 continue
621 continue
622 # abs: hgsep
622 # abs: hgsep
623 # rel: ossep
623 # rel: ossep
624 srcs.append((abs, rel, exact))
624 srcs.append((abs, rel, exact))
625 return srcs
625 return srcs
626
626
627 # abssrc: hgsep
627 # abssrc: hgsep
628 # relsrc: ossep
628 # relsrc: ossep
629 # otarget: ossep
629 # otarget: ossep
630 def copyfile(abssrc, relsrc, otarget, exact):
630 def copyfile(abssrc, relsrc, otarget, exact):
631 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
631 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
632 if '/' in abstarget:
632 if '/' in abstarget:
633 # We cannot normalize abstarget itself, this would prevent
633 # We cannot normalize abstarget itself, this would prevent
634 # case only renames, like a => A.
634 # case only renames, like a => A.
635 abspath, absname = abstarget.rsplit('/', 1)
635 abspath, absname = abstarget.rsplit('/', 1)
636 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
636 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
637 reltarget = repo.pathto(abstarget, cwd)
637 reltarget = repo.pathto(abstarget, cwd)
638 target = repo.wjoin(abstarget)
638 target = repo.wjoin(abstarget)
639 src = repo.wjoin(abssrc)
639 src = repo.wjoin(abssrc)
640 state = repo.dirstate[abstarget]
640 state = repo.dirstate[abstarget]
641
641
642 scmutil.checkportable(ui, abstarget)
642 scmutil.checkportable(ui, abstarget)
643
643
644 # check for collisions
644 # check for collisions
645 prevsrc = targets.get(abstarget)
645 prevsrc = targets.get(abstarget)
646 if prevsrc is not None:
646 if prevsrc is not None:
647 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
647 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
648 (reltarget, repo.pathto(abssrc, cwd),
648 (reltarget, repo.pathto(abssrc, cwd),
649 repo.pathto(prevsrc, cwd)))
649 repo.pathto(prevsrc, cwd)))
650 return
650 return
651
651
652 # check for overwrites
652 # check for overwrites
653 exists = os.path.lexists(target)
653 exists = os.path.lexists(target)
654 samefile = False
654 samefile = False
655 if exists and abssrc != abstarget:
655 if exists and abssrc != abstarget:
656 if (repo.dirstate.normalize(abssrc) ==
656 if (repo.dirstate.normalize(abssrc) ==
657 repo.dirstate.normalize(abstarget)):
657 repo.dirstate.normalize(abstarget)):
658 if not rename:
658 if not rename:
659 ui.warn(_("%s: can't copy - same file\n") % reltarget)
659 ui.warn(_("%s: can't copy - same file\n") % reltarget)
660 return
660 return
661 exists = False
661 exists = False
662 samefile = True
662 samefile = True
663
663
664 if not after and exists or after and state in 'mn':
664 if not after and exists or after and state in 'mn':
665 if not opts['force']:
665 if not opts['force']:
666 if state in 'mn':
666 if state in 'mn':
667 msg = _('%s: not overwriting - file already committed\n')
667 msg = _('%s: not overwriting - file already committed\n')
668 if after:
668 if after:
669 flags = '--after --force'
669 flags = '--after --force'
670 else:
670 else:
671 flags = '--force'
671 flags = '--force'
672 if rename:
672 if rename:
673 hint = _('(hg rename %s to replace the file by '
673 hint = _('(hg rename %s to replace the file by '
674 'recording a rename)\n') % flags
674 'recording a rename)\n') % flags
675 else:
675 else:
676 hint = _('(hg copy %s to replace the file by '
676 hint = _('(hg copy %s to replace the file by '
677 'recording a copy)\n') % flags
677 'recording a copy)\n') % flags
678 else:
678 else:
679 msg = _('%s: not overwriting - file exists\n')
679 msg = _('%s: not overwriting - file exists\n')
680 if rename:
680 if rename:
681 hint = _('(hg rename --after to record the rename)\n')
681 hint = _('(hg rename --after to record the rename)\n')
682 else:
682 else:
683 hint = _('(hg copy --after to record the copy)\n')
683 hint = _('(hg copy --after to record the copy)\n')
684 ui.warn(msg % reltarget)
684 ui.warn(msg % reltarget)
685 ui.warn(hint)
685 ui.warn(hint)
686 return
686 return
687
687
688 if after:
688 if after:
689 if not exists:
689 if not exists:
690 if rename:
690 if rename:
691 ui.warn(_('%s: not recording move - %s does not exist\n') %
691 ui.warn(_('%s: not recording move - %s does not exist\n') %
692 (relsrc, reltarget))
692 (relsrc, reltarget))
693 else:
693 else:
694 ui.warn(_('%s: not recording copy - %s does not exist\n') %
694 ui.warn(_('%s: not recording copy - %s does not exist\n') %
695 (relsrc, reltarget))
695 (relsrc, reltarget))
696 return
696 return
697 elif not dryrun:
697 elif not dryrun:
698 try:
698 try:
699 if exists:
699 if exists:
700 os.unlink(target)
700 os.unlink(target)
701 targetdir = os.path.dirname(target) or '.'
701 targetdir = os.path.dirname(target) or '.'
702 if not os.path.isdir(targetdir):
702 if not os.path.isdir(targetdir):
703 os.makedirs(targetdir)
703 os.makedirs(targetdir)
704 if samefile:
704 if samefile:
705 tmp = target + "~hgrename"
705 tmp = target + "~hgrename"
706 os.rename(src, tmp)
706 os.rename(src, tmp)
707 os.rename(tmp, target)
707 os.rename(tmp, target)
708 else:
708 else:
709 util.copyfile(src, target)
709 util.copyfile(src, target)
710 srcexists = True
710 srcexists = True
711 except IOError as inst:
711 except IOError as inst:
712 if inst.errno == errno.ENOENT:
712 if inst.errno == errno.ENOENT:
713 ui.warn(_('%s: deleted in working directory\n') % relsrc)
713 ui.warn(_('%s: deleted in working directory\n') % relsrc)
714 srcexists = False
714 srcexists = False
715 else:
715 else:
716 ui.warn(_('%s: cannot copy - %s\n') %
716 ui.warn(_('%s: cannot copy - %s\n') %
717 (relsrc, inst.strerror))
717 (relsrc, inst.strerror))
718 return True # report a failure
718 return True # report a failure
719
719
720 if ui.verbose or not exact:
720 if ui.verbose or not exact:
721 if rename:
721 if rename:
722 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
722 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
723 else:
723 else:
724 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
724 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
725
725
726 targets[abstarget] = abssrc
726 targets[abstarget] = abssrc
727
727
728 # fix up dirstate
728 # fix up dirstate
729 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
729 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
730 dryrun=dryrun, cwd=cwd)
730 dryrun=dryrun, cwd=cwd)
731 if rename and not dryrun:
731 if rename and not dryrun:
732 if not after and srcexists and not samefile:
732 if not after and srcexists and not samefile:
733 repo.wvfs.unlinkpath(abssrc)
733 repo.wvfs.unlinkpath(abssrc)
734 wctx.forget([abssrc])
734 wctx.forget([abssrc])
735
735
736 # pat: ossep
736 # pat: ossep
737 # dest ossep
737 # dest ossep
738 # srcs: list of (hgsep, hgsep, ossep, bool)
738 # srcs: list of (hgsep, hgsep, ossep, bool)
739 # return: function that takes hgsep and returns ossep
739 # return: function that takes hgsep and returns ossep
740 def targetpathfn(pat, dest, srcs):
740 def targetpathfn(pat, dest, srcs):
741 if os.path.isdir(pat):
741 if os.path.isdir(pat):
742 abspfx = pathutil.canonpath(repo.root, cwd, pat)
742 abspfx = pathutil.canonpath(repo.root, cwd, pat)
743 abspfx = util.localpath(abspfx)
743 abspfx = util.localpath(abspfx)
744 if destdirexists:
744 if destdirexists:
745 striplen = len(os.path.split(abspfx)[0])
745 striplen = len(os.path.split(abspfx)[0])
746 else:
746 else:
747 striplen = len(abspfx)
747 striplen = len(abspfx)
748 if striplen:
748 if striplen:
749 striplen += len(pycompat.ossep)
749 striplen += len(pycompat.ossep)
750 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
750 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
751 elif destdirexists:
751 elif destdirexists:
752 res = lambda p: os.path.join(dest,
752 res = lambda p: os.path.join(dest,
753 os.path.basename(util.localpath(p)))
753 os.path.basename(util.localpath(p)))
754 else:
754 else:
755 res = lambda p: dest
755 res = lambda p: dest
756 return res
756 return res
757
757
758 # pat: ossep
758 # pat: ossep
759 # dest ossep
759 # dest ossep
760 # srcs: list of (hgsep, hgsep, ossep, bool)
760 # srcs: list of (hgsep, hgsep, ossep, bool)
761 # return: function that takes hgsep and returns ossep
761 # return: function that takes hgsep and returns ossep
762 def targetpathafterfn(pat, dest, srcs):
762 def targetpathafterfn(pat, dest, srcs):
763 if matchmod.patkind(pat):
763 if matchmod.patkind(pat):
764 # a mercurial pattern
764 # a mercurial pattern
765 res = lambda p: os.path.join(dest,
765 res = lambda p: os.path.join(dest,
766 os.path.basename(util.localpath(p)))
766 os.path.basename(util.localpath(p)))
767 else:
767 else:
768 abspfx = pathutil.canonpath(repo.root, cwd, pat)
768 abspfx = pathutil.canonpath(repo.root, cwd, pat)
769 if len(abspfx) < len(srcs[0][0]):
769 if len(abspfx) < len(srcs[0][0]):
770 # A directory. Either the target path contains the last
770 # A directory. Either the target path contains the last
771 # component of the source path or it does not.
771 # component of the source path or it does not.
772 def evalpath(striplen):
772 def evalpath(striplen):
773 score = 0
773 score = 0
774 for s in srcs:
774 for s in srcs:
775 t = os.path.join(dest, util.localpath(s[0])[striplen:])
775 t = os.path.join(dest, util.localpath(s[0])[striplen:])
776 if os.path.lexists(t):
776 if os.path.lexists(t):
777 score += 1
777 score += 1
778 return score
778 return score
779
779
780 abspfx = util.localpath(abspfx)
780 abspfx = util.localpath(abspfx)
781 striplen = len(abspfx)
781 striplen = len(abspfx)
782 if striplen:
782 if striplen:
783 striplen += len(pycompat.ossep)
783 striplen += len(pycompat.ossep)
784 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
784 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
785 score = evalpath(striplen)
785 score = evalpath(striplen)
786 striplen1 = len(os.path.split(abspfx)[0])
786 striplen1 = len(os.path.split(abspfx)[0])
787 if striplen1:
787 if striplen1:
788 striplen1 += len(pycompat.ossep)
788 striplen1 += len(pycompat.ossep)
789 if evalpath(striplen1) > score:
789 if evalpath(striplen1) > score:
790 striplen = striplen1
790 striplen = striplen1
791 res = lambda p: os.path.join(dest,
791 res = lambda p: os.path.join(dest,
792 util.localpath(p)[striplen:])
792 util.localpath(p)[striplen:])
793 else:
793 else:
794 # a file
794 # a file
795 if destdirexists:
795 if destdirexists:
796 res = lambda p: os.path.join(dest,
796 res = lambda p: os.path.join(dest,
797 os.path.basename(util.localpath(p)))
797 os.path.basename(util.localpath(p)))
798 else:
798 else:
799 res = lambda p: dest
799 res = lambda p: dest
800 return res
800 return res
801
801
802 pats = scmutil.expandpats(pats)
802 pats = scmutil.expandpats(pats)
803 if not pats:
803 if not pats:
804 raise error.Abort(_('no source or destination specified'))
804 raise error.Abort(_('no source or destination specified'))
805 if len(pats) == 1:
805 if len(pats) == 1:
806 raise error.Abort(_('no destination specified'))
806 raise error.Abort(_('no destination specified'))
807 dest = pats.pop()
807 dest = pats.pop()
808 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
808 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
809 if not destdirexists:
809 if not destdirexists:
810 if len(pats) > 1 or matchmod.patkind(pats[0]):
810 if len(pats) > 1 or matchmod.patkind(pats[0]):
811 raise error.Abort(_('with multiple sources, destination must be an '
811 raise error.Abort(_('with multiple sources, destination must be an '
812 'existing directory'))
812 'existing directory'))
813 if util.endswithsep(dest):
813 if util.endswithsep(dest):
814 raise error.Abort(_('destination %s is not a directory') % dest)
814 raise error.Abort(_('destination %s is not a directory') % dest)
815
815
816 tfn = targetpathfn
816 tfn = targetpathfn
817 if after:
817 if after:
818 tfn = targetpathafterfn
818 tfn = targetpathafterfn
819 copylist = []
819 copylist = []
820 for pat in pats:
820 for pat in pats:
821 srcs = walkpat(pat)
821 srcs = walkpat(pat)
822 if not srcs:
822 if not srcs:
823 continue
823 continue
824 copylist.append((tfn(pat, dest, srcs), srcs))
824 copylist.append((tfn(pat, dest, srcs), srcs))
825 if not copylist:
825 if not copylist:
826 raise error.Abort(_('no files to copy'))
826 raise error.Abort(_('no files to copy'))
827
827
828 errors = 0
828 errors = 0
829 for targetpath, srcs in copylist:
829 for targetpath, srcs in copylist:
830 for abssrc, relsrc, exact in srcs:
830 for abssrc, relsrc, exact in srcs:
831 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
831 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
832 errors += 1
832 errors += 1
833
833
834 if errors:
834 if errors:
835 ui.warn(_('(consider using --after)\n'))
835 ui.warn(_('(consider using --after)\n'))
836
836
837 return errors != 0
837 return errors != 0
838
838
839 ## facility to let extension process additional data into an import patch
839 ## facility to let extension process additional data into an import patch
840 # list of identifier to be executed in order
840 # list of identifier to be executed in order
841 extrapreimport = [] # run before commit
841 extrapreimport = [] # run before commit
842 extrapostimport = [] # run after commit
842 extrapostimport = [] # run after commit
843 # mapping from identifier to actual import function
843 # mapping from identifier to actual import function
844 #
844 #
845 # 'preimport' are run before the commit is made and are provided the following
845 # 'preimport' are run before the commit is made and are provided the following
846 # arguments:
846 # arguments:
847 # - repo: the localrepository instance,
847 # - repo: the localrepository instance,
848 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
848 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
849 # - extra: the future extra dictionary of the changeset, please mutate it,
849 # - extra: the future extra dictionary of the changeset, please mutate it,
850 # - opts: the import options.
850 # - opts: the import options.
851 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
851 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
852 # mutation of in memory commit and more. Feel free to rework the code to get
852 # mutation of in memory commit and more. Feel free to rework the code to get
853 # there.
853 # there.
854 extrapreimportmap = {}
854 extrapreimportmap = {}
855 # 'postimport' are run after the commit is made and are provided the following
855 # 'postimport' are run after the commit is made and are provided the following
856 # argument:
856 # argument:
857 # - ctx: the changectx created by import.
857 # - ctx: the changectx created by import.
858 extrapostimportmap = {}
858 extrapostimportmap = {}
859
859
860 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
860 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
861 """Utility function used by commands.import to import a single patch
861 """Utility function used by commands.import to import a single patch
862
862
863 This function is explicitly defined here to help the evolve extension to
863 This function is explicitly defined here to help the evolve extension to
864 wrap this part of the import logic.
864 wrap this part of the import logic.
865
865
866 The API is currently a bit ugly because it a simple code translation from
866 The API is currently a bit ugly because it a simple code translation from
867 the import command. Feel free to make it better.
867 the import command. Feel free to make it better.
868
868
869 :hunk: a patch (as a binary string)
869 :hunk: a patch (as a binary string)
870 :parents: nodes that will be parent of the created commit
870 :parents: nodes that will be parent of the created commit
871 :opts: the full dict of option passed to the import command
871 :opts: the full dict of option passed to the import command
872 :msgs: list to save commit message to.
872 :msgs: list to save commit message to.
873 (used in case we need to save it when failing)
873 (used in case we need to save it when failing)
874 :updatefunc: a function that update a repo to a given node
874 :updatefunc: a function that update a repo to a given node
875 updatefunc(<repo>, <node>)
875 updatefunc(<repo>, <node>)
876 """
876 """
877 # avoid cycle context -> subrepo -> cmdutil
877 # avoid cycle context -> subrepo -> cmdutil
878 from . import context
878 from . import context
879 extractdata = patch.extract(ui, hunk)
879 extractdata = patch.extract(ui, hunk)
880 tmpname = extractdata.get('filename')
880 tmpname = extractdata.get('filename')
881 message = extractdata.get('message')
881 message = extractdata.get('message')
882 user = opts.get('user') or extractdata.get('user')
882 user = opts.get('user') or extractdata.get('user')
883 date = opts.get('date') or extractdata.get('date')
883 date = opts.get('date') or extractdata.get('date')
884 branch = extractdata.get('branch')
884 branch = extractdata.get('branch')
885 nodeid = extractdata.get('nodeid')
885 nodeid = extractdata.get('nodeid')
886 p1 = extractdata.get('p1')
886 p1 = extractdata.get('p1')
887 p2 = extractdata.get('p2')
887 p2 = extractdata.get('p2')
888
888
889 nocommit = opts.get('no_commit')
889 nocommit = opts.get('no_commit')
890 importbranch = opts.get('import_branch')
890 importbranch = opts.get('import_branch')
891 update = not opts.get('bypass')
891 update = not opts.get('bypass')
892 strip = opts["strip"]
892 strip = opts["strip"]
893 prefix = opts["prefix"]
893 prefix = opts["prefix"]
894 sim = float(opts.get('similarity') or 0)
894 sim = float(opts.get('similarity') or 0)
895 if not tmpname:
895 if not tmpname:
896 return (None, None, False)
896 return (None, None, False)
897
897
898 rejects = False
898 rejects = False
899
899
900 try:
900 try:
901 cmdline_message = logmessage(ui, opts)
901 cmdline_message = logmessage(ui, opts)
902 if cmdline_message:
902 if cmdline_message:
903 # pickup the cmdline msg
903 # pickup the cmdline msg
904 message = cmdline_message
904 message = cmdline_message
905 elif message:
905 elif message:
906 # pickup the patch msg
906 # pickup the patch msg
907 message = message.strip()
907 message = message.strip()
908 else:
908 else:
909 # launch the editor
909 # launch the editor
910 message = None
910 message = None
911 ui.debug('message:\n%s\n' % message)
911 ui.debug('message:\n%s\n' % message)
912
912
913 if len(parents) == 1:
913 if len(parents) == 1:
914 parents.append(repo[nullid])
914 parents.append(repo[nullid])
915 if opts.get('exact'):
915 if opts.get('exact'):
916 if not nodeid or not p1:
916 if not nodeid or not p1:
917 raise error.Abort(_('not a Mercurial patch'))
917 raise error.Abort(_('not a Mercurial patch'))
918 p1 = repo[p1]
918 p1 = repo[p1]
919 p2 = repo[p2 or nullid]
919 p2 = repo[p2 or nullid]
920 elif p2:
920 elif p2:
921 try:
921 try:
922 p1 = repo[p1]
922 p1 = repo[p1]
923 p2 = repo[p2]
923 p2 = repo[p2]
924 # Without any options, consider p2 only if the
924 # Without any options, consider p2 only if the
925 # patch is being applied on top of the recorded
925 # patch is being applied on top of the recorded
926 # first parent.
926 # first parent.
927 if p1 != parents[0]:
927 if p1 != parents[0]:
928 p1 = parents[0]
928 p1 = parents[0]
929 p2 = repo[nullid]
929 p2 = repo[nullid]
930 except error.RepoError:
930 except error.RepoError:
931 p1, p2 = parents
931 p1, p2 = parents
932 if p2.node() == nullid:
932 if p2.node() == nullid:
933 ui.warn(_("warning: import the patch as a normal revision\n"
933 ui.warn(_("warning: import the patch as a normal revision\n"
934 "(use --exact to import the patch as a merge)\n"))
934 "(use --exact to import the patch as a merge)\n"))
935 else:
935 else:
936 p1, p2 = parents
936 p1, p2 = parents
937
937
938 n = None
938 n = None
939 if update:
939 if update:
940 if p1 != parents[0]:
940 if p1 != parents[0]:
941 updatefunc(repo, p1.node())
941 updatefunc(repo, p1.node())
942 if p2 != parents[1]:
942 if p2 != parents[1]:
943 repo.setparents(p1.node(), p2.node())
943 repo.setparents(p1.node(), p2.node())
944
944
945 if opts.get('exact') or importbranch:
945 if opts.get('exact') or importbranch:
946 repo.dirstate.setbranch(branch or 'default')
946 repo.dirstate.setbranch(branch or 'default')
947
947
948 partial = opts.get('partial', False)
948 partial = opts.get('partial', False)
949 files = set()
949 files = set()
950 try:
950 try:
951 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
951 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
952 files=files, eolmode=None, similarity=sim / 100.0)
952 files=files, eolmode=None, similarity=sim / 100.0)
953 except patch.PatchError as e:
953 except patch.PatchError as e:
954 if not partial:
954 if not partial:
955 raise error.Abort(str(e))
955 raise error.Abort(str(e))
956 if partial:
956 if partial:
957 rejects = True
957 rejects = True
958
958
959 files = list(files)
959 files = list(files)
960 if nocommit:
960 if nocommit:
961 if message:
961 if message:
962 msgs.append(message)
962 msgs.append(message)
963 else:
963 else:
964 if opts.get('exact') or p2:
964 if opts.get('exact') or p2:
965 # If you got here, you either use --force and know what
965 # If you got here, you either use --force and know what
966 # you are doing or used --exact or a merge patch while
966 # you are doing or used --exact or a merge patch while
967 # being updated to its first parent.
967 # being updated to its first parent.
968 m = None
968 m = None
969 else:
969 else:
970 m = scmutil.matchfiles(repo, files or [])
970 m = scmutil.matchfiles(repo, files or [])
971 editform = mergeeditform(repo[None], 'import.normal')
971 editform = mergeeditform(repo[None], 'import.normal')
972 if opts.get('exact'):
972 if opts.get('exact'):
973 editor = None
973 editor = None
974 else:
974 else:
975 editor = getcommiteditor(editform=editform, **opts)
975 editor = getcommiteditor(editform=editform, **opts)
976 extra = {}
976 extra = {}
977 for idfunc in extrapreimport:
977 for idfunc in extrapreimport:
978 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
978 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
979 overrides = {}
979 overrides = {}
980 if partial:
980 if partial:
981 overrides[('ui', 'allowemptycommit')] = True
981 overrides[('ui', 'allowemptycommit')] = True
982 with repo.ui.configoverride(overrides, 'import'):
982 with repo.ui.configoverride(overrides, 'import'):
983 n = repo.commit(message, user,
983 n = repo.commit(message, user,
984 date, match=m,
984 date, match=m,
985 editor=editor, extra=extra)
985 editor=editor, extra=extra)
986 for idfunc in extrapostimport:
986 for idfunc in extrapostimport:
987 extrapostimportmap[idfunc](repo[n])
987 extrapostimportmap[idfunc](repo[n])
988 else:
988 else:
989 if opts.get('exact') or importbranch:
989 if opts.get('exact') or importbranch:
990 branch = branch or 'default'
990 branch = branch or 'default'
991 else:
991 else:
992 branch = p1.branch()
992 branch = p1.branch()
993 store = patch.filestore()
993 store = patch.filestore()
994 try:
994 try:
995 files = set()
995 files = set()
996 try:
996 try:
997 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
997 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
998 files, eolmode=None)
998 files, eolmode=None)
999 except patch.PatchError as e:
999 except patch.PatchError as e:
1000 raise error.Abort(str(e))
1000 raise error.Abort(str(e))
1001 if opts.get('exact'):
1001 if opts.get('exact'):
1002 editor = None
1002 editor = None
1003 else:
1003 else:
1004 editor = getcommiteditor(editform='import.bypass')
1004 editor = getcommiteditor(editform='import.bypass')
1005 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1005 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1006 message,
1006 message,
1007 user,
1007 user,
1008 date,
1008 date,
1009 branch, files, store,
1009 branch, files, store,
1010 editor=editor)
1010 editor=editor)
1011 n = memctx.commit()
1011 n = memctx.commit()
1012 finally:
1012 finally:
1013 store.close()
1013 store.close()
1014 if opts.get('exact') and nocommit:
1014 if opts.get('exact') and nocommit:
1015 # --exact with --no-commit is still useful in that it does merge
1015 # --exact with --no-commit is still useful in that it does merge
1016 # and branch bits
1016 # and branch bits
1017 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1017 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1018 elif opts.get('exact') and hex(n) != nodeid:
1018 elif opts.get('exact') and hex(n) != nodeid:
1019 raise error.Abort(_('patch is damaged or loses information'))
1019 raise error.Abort(_('patch is damaged or loses information'))
1020 msg = _('applied to working directory')
1020 msg = _('applied to working directory')
1021 if n:
1021 if n:
1022 # i18n: refers to a short changeset id
1022 # i18n: refers to a short changeset id
1023 msg = _('created %s') % short(n)
1023 msg = _('created %s') % short(n)
1024 return (msg, n, rejects)
1024 return (msg, n, rejects)
1025 finally:
1025 finally:
1026 os.unlink(tmpname)
1026 os.unlink(tmpname)
1027
1027
1028 # facility to let extensions include additional data in an exported patch
1028 # facility to let extensions include additional data in an exported patch
1029 # list of identifiers to be executed in order
1029 # list of identifiers to be executed in order
1030 extraexport = []
1030 extraexport = []
1031 # mapping from identifier to actual export function
1031 # mapping from identifier to actual export function
1032 # function as to return a string to be added to the header or None
1032 # function as to return a string to be added to the header or None
1033 # it is given two arguments (sequencenumber, changectx)
1033 # it is given two arguments (sequencenumber, changectx)
1034 extraexportmap = {}
1034 extraexportmap = {}
1035
1035
1036 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1036 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1037 opts=None, match=None):
1037 opts=None, match=None):
1038 '''export changesets as hg patches.'''
1038 '''export changesets as hg patches.'''
1039
1039
1040 total = len(revs)
1040 total = len(revs)
1041 revwidth = max([len(str(rev)) for rev in revs])
1041 revwidth = max([len(str(rev)) for rev in revs])
1042 filemode = {}
1042 filemode = {}
1043
1043
1044 def single(rev, seqno, fp):
1044 def single(rev, seqno, fp):
1045 ctx = repo[rev]
1045 ctx = repo[rev]
1046 node = ctx.node()
1046 node = ctx.node()
1047 parents = [p.node() for p in ctx.parents() if p]
1047 parents = [p.node() for p in ctx.parents() if p]
1048 branch = ctx.branch()
1048 branch = ctx.branch()
1049 if switch_parent:
1049 if switch_parent:
1050 parents.reverse()
1050 parents.reverse()
1051
1051
1052 if parents:
1052 if parents:
1053 prev = parents[0]
1053 prev = parents[0]
1054 else:
1054 else:
1055 prev = nullid
1055 prev = nullid
1056
1056
1057 shouldclose = False
1057 shouldclose = False
1058 if not fp and len(template) > 0:
1058 if not fp and len(template) > 0:
1059 desc_lines = ctx.description().rstrip().split('\n')
1059 desc_lines = ctx.description().rstrip().split('\n')
1060 desc = desc_lines[0] #Commit always has a first line.
1060 desc = desc_lines[0] #Commit always has a first line.
1061 fp = makefileobj(repo, template, node, desc=desc, total=total,
1061 fp = makefileobj(repo, template, node, desc=desc, total=total,
1062 seqno=seqno, revwidth=revwidth, mode='wb',
1062 seqno=seqno, revwidth=revwidth, mode='wb',
1063 modemap=filemode)
1063 modemap=filemode)
1064 shouldclose = True
1064 shouldclose = True
1065 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1065 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1066 repo.ui.note("%s\n" % fp.name)
1066 repo.ui.note("%s\n" % fp.name)
1067
1067
1068 if not fp:
1068 if not fp:
1069 write = repo.ui.write
1069 write = repo.ui.write
1070 else:
1070 else:
1071 def write(s, **kw):
1071 def write(s, **kw):
1072 fp.write(s)
1072 fp.write(s)
1073
1073
1074 write("# HG changeset patch\n")
1074 write("# HG changeset patch\n")
1075 write("# User %s\n" % ctx.user())
1075 write("# User %s\n" % ctx.user())
1076 write("# Date %d %d\n" % ctx.date())
1076 write("# Date %d %d\n" % ctx.date())
1077 write("# %s\n" % util.datestr(ctx.date()))
1077 write("# %s\n" % util.datestr(ctx.date()))
1078 if branch and branch != 'default':
1078 if branch and branch != 'default':
1079 write("# Branch %s\n" % branch)
1079 write("# Branch %s\n" % branch)
1080 write("# Node ID %s\n" % hex(node))
1080 write("# Node ID %s\n" % hex(node))
1081 write("# Parent %s\n" % hex(prev))
1081 write("# Parent %s\n" % hex(prev))
1082 if len(parents) > 1:
1082 if len(parents) > 1:
1083 write("# Parent %s\n" % hex(parents[1]))
1083 write("# Parent %s\n" % hex(parents[1]))
1084
1084
1085 for headerid in extraexport:
1085 for headerid in extraexport:
1086 header = extraexportmap[headerid](seqno, ctx)
1086 header = extraexportmap[headerid](seqno, ctx)
1087 if header is not None:
1087 if header is not None:
1088 write('# %s\n' % header)
1088 write('# %s\n' % header)
1089 write(ctx.description().rstrip())
1089 write(ctx.description().rstrip())
1090 write("\n\n")
1090 write("\n\n")
1091
1091
1092 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1092 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1093 write(chunk, label=label)
1093 write(chunk, label=label)
1094
1094
1095 if shouldclose:
1095 if shouldclose:
1096 fp.close()
1096 fp.close()
1097
1097
1098 for seqno, rev in enumerate(revs):
1098 for seqno, rev in enumerate(revs):
1099 single(rev, seqno + 1, fp)
1099 single(rev, seqno + 1, fp)
1100
1100
1101 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1101 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1102 changes=None, stat=False, fp=None, prefix='',
1102 changes=None, stat=False, fp=None, prefix='',
1103 root='', listsubrepos=False):
1103 root='', listsubrepos=False):
1104 '''show diff or diffstat.'''
1104 '''show diff or diffstat.'''
1105 if fp is None:
1105 if fp is None:
1106 write = ui.write
1106 write = ui.write
1107 else:
1107 else:
1108 def write(s, **kw):
1108 def write(s, **kw):
1109 fp.write(s)
1109 fp.write(s)
1110
1110
1111 if root:
1111 if root:
1112 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1112 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1113 else:
1113 else:
1114 relroot = ''
1114 relroot = ''
1115 if relroot != '':
1115 if relroot != '':
1116 # XXX relative roots currently don't work if the root is within a
1116 # XXX relative roots currently don't work if the root is within a
1117 # subrepo
1117 # subrepo
1118 uirelroot = match.uipath(relroot)
1118 uirelroot = match.uipath(relroot)
1119 relroot += '/'
1119 relroot += '/'
1120 for matchroot in match.files():
1120 for matchroot in match.files():
1121 if not matchroot.startswith(relroot):
1121 if not matchroot.startswith(relroot):
1122 ui.warn(_('warning: %s not inside relative root %s\n') % (
1122 ui.warn(_('warning: %s not inside relative root %s\n') % (
1123 match.uipath(matchroot), uirelroot))
1123 match.uipath(matchroot), uirelroot))
1124
1124
1125 if stat:
1125 if stat:
1126 diffopts = diffopts.copy(context=0)
1126 diffopts = diffopts.copy(context=0)
1127 width = 80
1127 width = 80
1128 if not ui.plain():
1128 if not ui.plain():
1129 width = ui.termwidth()
1129 width = ui.termwidth()
1130 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1130 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1131 prefix=prefix, relroot=relroot)
1131 prefix=prefix, relroot=relroot)
1132 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1132 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1133 width=width):
1133 width=width):
1134 write(chunk, label=label)
1134 write(chunk, label=label)
1135 else:
1135 else:
1136 for chunk, label in patch.diffui(repo, node1, node2, match,
1136 for chunk, label in patch.diffui(repo, node1, node2, match,
1137 changes, diffopts, prefix=prefix,
1137 changes, diffopts, prefix=prefix,
1138 relroot=relroot):
1138 relroot=relroot):
1139 write(chunk, label=label)
1139 write(chunk, label=label)
1140
1140
1141 if listsubrepos:
1141 if listsubrepos:
1142 ctx1 = repo[node1]
1142 ctx1 = repo[node1]
1143 ctx2 = repo[node2]
1143 ctx2 = repo[node2]
1144 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1144 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1145 tempnode2 = node2
1145 tempnode2 = node2
1146 try:
1146 try:
1147 if node2 is not None:
1147 if node2 is not None:
1148 tempnode2 = ctx2.substate[subpath][1]
1148 tempnode2 = ctx2.substate[subpath][1]
1149 except KeyError:
1149 except KeyError:
1150 # A subrepo that existed in node1 was deleted between node1 and
1150 # A subrepo that existed in node1 was deleted between node1 and
1151 # node2 (inclusive). Thus, ctx2's substate won't contain that
1151 # node2 (inclusive). Thus, ctx2's substate won't contain that
1152 # subpath. The best we can do is to ignore it.
1152 # subpath. The best we can do is to ignore it.
1153 tempnode2 = None
1153 tempnode2 = None
1154 submatch = matchmod.subdirmatcher(subpath, match)
1154 submatch = matchmod.subdirmatcher(subpath, match)
1155 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1155 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1156 stat=stat, fp=fp, prefix=prefix)
1156 stat=stat, fp=fp, prefix=prefix)
1157
1157
1158 def _changesetlabels(ctx):
1158 def _changesetlabels(ctx):
1159 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1159 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1160 if ctx.obsolete():
1160 if ctx.obsolete():
1161 labels.append('changeset.obsolete')
1161 labels.append('changeset.obsolete')
1162 if ctx.troubled():
1162 if ctx.troubled():
1163 labels.append('changeset.troubled')
1163 labels.append('changeset.troubled')
1164 for trouble in ctx.troubles():
1164 for trouble in ctx.troubles():
1165 labels.append('trouble.%s' % trouble)
1165 labels.append('trouble.%s' % trouble)
1166 return ' '.join(labels)
1166 return ' '.join(labels)
1167
1167
1168 class changeset_printer(object):
1168 class changeset_printer(object):
1169 '''show changeset information when templating not requested.'''
1169 '''show changeset information when templating not requested.'''
1170
1170
1171 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1171 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1172 self.ui = ui
1172 self.ui = ui
1173 self.repo = repo
1173 self.repo = repo
1174 self.buffered = buffered
1174 self.buffered = buffered
1175 self.matchfn = matchfn
1175 self.matchfn = matchfn
1176 self.diffopts = diffopts
1176 self.diffopts = diffopts
1177 self.header = {}
1177 self.header = {}
1178 self.hunk = {}
1178 self.hunk = {}
1179 self.lastheader = None
1179 self.lastheader = None
1180 self.footer = None
1180 self.footer = None
1181
1181
1182 def flush(self, ctx):
1182 def flush(self, ctx):
1183 rev = ctx.rev()
1183 rev = ctx.rev()
1184 if rev in self.header:
1184 if rev in self.header:
1185 h = self.header[rev]
1185 h = self.header[rev]
1186 if h != self.lastheader:
1186 if h != self.lastheader:
1187 self.lastheader = h
1187 self.lastheader = h
1188 self.ui.write(h)
1188 self.ui.write(h)
1189 del self.header[rev]
1189 del self.header[rev]
1190 if rev in self.hunk:
1190 if rev in self.hunk:
1191 self.ui.write(self.hunk[rev])
1191 self.ui.write(self.hunk[rev])
1192 del self.hunk[rev]
1192 del self.hunk[rev]
1193 return 1
1193 return 1
1194 return 0
1194 return 0
1195
1195
1196 def close(self):
1196 def close(self):
1197 if self.footer:
1197 if self.footer:
1198 self.ui.write(self.footer)
1198 self.ui.write(self.footer)
1199
1199
1200 def show(self, ctx, copies=None, matchfn=None, **props):
1200 def show(self, ctx, copies=None, matchfn=None, **props):
1201 if self.buffered:
1201 if self.buffered:
1202 self.ui.pushbuffer(labeled=True)
1202 self.ui.pushbuffer(labeled=True)
1203 self._show(ctx, copies, matchfn, props)
1203 self._show(ctx, copies, matchfn, props)
1204 self.hunk[ctx.rev()] = self.ui.popbuffer()
1204 self.hunk[ctx.rev()] = self.ui.popbuffer()
1205 else:
1205 else:
1206 self._show(ctx, copies, matchfn, props)
1206 self._show(ctx, copies, matchfn, props)
1207
1207
1208 def _show(self, ctx, copies, matchfn, props):
1208 def _show(self, ctx, copies, matchfn, props):
1209 '''show a single changeset or file revision'''
1209 '''show a single changeset or file revision'''
1210 changenode = ctx.node()
1210 changenode = ctx.node()
1211 rev = ctx.rev()
1211 rev = ctx.rev()
1212 if self.ui.debugflag:
1212 if self.ui.debugflag:
1213 hexfunc = hex
1213 hexfunc = hex
1214 else:
1214 else:
1215 hexfunc = short
1215 hexfunc = short
1216 # as of now, wctx.node() and wctx.rev() return None, but we want to
1216 # as of now, wctx.node() and wctx.rev() return None, but we want to
1217 # show the same values as {node} and {rev} templatekw
1217 # show the same values as {node} and {rev} templatekw
1218 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1218 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1219
1219
1220 if self.ui.quiet:
1220 if self.ui.quiet:
1221 self.ui.write("%d:%s\n" % revnode, label='log.node')
1221 self.ui.write("%d:%s\n" % revnode, label='log.node')
1222 return
1222 return
1223
1223
1224 date = util.datestr(ctx.date())
1224 date = util.datestr(ctx.date())
1225
1225
1226 # i18n: column positioning for "hg log"
1226 # i18n: column positioning for "hg log"
1227 self.ui.write(_("changeset: %d:%s\n") % revnode,
1227 self.ui.write(_("changeset: %d:%s\n") % revnode,
1228 label=_changesetlabels(ctx))
1228 label=_changesetlabels(ctx))
1229
1229
1230 # branches are shown first before any other names due to backwards
1230 # branches are shown first before any other names due to backwards
1231 # compatibility
1231 # compatibility
1232 branch = ctx.branch()
1232 branch = ctx.branch()
1233 # don't show the default branch name
1233 # don't show the default branch name
1234 if branch != 'default':
1234 if branch != 'default':
1235 # i18n: column positioning for "hg log"
1235 # i18n: column positioning for "hg log"
1236 self.ui.write(_("branch: %s\n") % branch,
1236 self.ui.write(_("branch: %s\n") % branch,
1237 label='log.branch')
1237 label='log.branch')
1238
1238
1239 for nsname, ns in self.repo.names.iteritems():
1239 for nsname, ns in self.repo.names.iteritems():
1240 # branches has special logic already handled above, so here we just
1240 # branches has special logic already handled above, so here we just
1241 # skip it
1241 # skip it
1242 if nsname == 'branches':
1242 if nsname == 'branches':
1243 continue
1243 continue
1244 # we will use the templatename as the color name since those two
1244 # we will use the templatename as the color name since those two
1245 # should be the same
1245 # should be the same
1246 for name in ns.names(self.repo, changenode):
1246 for name in ns.names(self.repo, changenode):
1247 self.ui.write(ns.logfmt % name,
1247 self.ui.write(ns.logfmt % name,
1248 label='log.%s' % ns.colorname)
1248 label='log.%s' % ns.colorname)
1249 if self.ui.debugflag:
1249 if self.ui.debugflag:
1250 # i18n: column positioning for "hg log"
1250 # i18n: column positioning for "hg log"
1251 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1251 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1252 label='log.phase')
1252 label='log.phase')
1253 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1253 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1254 label = 'log.parent changeset.%s' % pctx.phasestr()
1254 label = 'log.parent changeset.%s' % pctx.phasestr()
1255 # i18n: column positioning for "hg log"
1255 # i18n: column positioning for "hg log"
1256 self.ui.write(_("parent: %d:%s\n")
1256 self.ui.write(_("parent: %d:%s\n")
1257 % (pctx.rev(), hexfunc(pctx.node())),
1257 % (pctx.rev(), hexfunc(pctx.node())),
1258 label=label)
1258 label=label)
1259
1259
1260 if self.ui.debugflag and rev is not None:
1260 if self.ui.debugflag and rev is not None:
1261 mnode = ctx.manifestnode()
1261 mnode = ctx.manifestnode()
1262 # i18n: column positioning for "hg log"
1262 # i18n: column positioning for "hg log"
1263 self.ui.write(_("manifest: %d:%s\n") %
1263 self.ui.write(_("manifest: %d:%s\n") %
1264 (self.repo.manifestlog._revlog.rev(mnode),
1264 (self.repo.manifestlog._revlog.rev(mnode),
1265 hex(mnode)),
1265 hex(mnode)),
1266 label='ui.debug log.manifest')
1266 label='ui.debug log.manifest')
1267 # i18n: column positioning for "hg log"
1267 # i18n: column positioning for "hg log"
1268 self.ui.write(_("user: %s\n") % ctx.user(),
1268 self.ui.write(_("user: %s\n") % ctx.user(),
1269 label='log.user')
1269 label='log.user')
1270 # i18n: column positioning for "hg log"
1270 # i18n: column positioning for "hg log"
1271 self.ui.write(_("date: %s\n") % date,
1271 self.ui.write(_("date: %s\n") % date,
1272 label='log.date')
1272 label='log.date')
1273
1273
1274 if ctx.troubled():
1274 if ctx.troubled():
1275 # i18n: column positioning for "hg log"
1275 # i18n: column positioning for "hg log"
1276 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1276 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1277 label='log.trouble')
1277 label='log.trouble')
1278
1278
1279 if self.ui.debugflag:
1279 if self.ui.debugflag:
1280 files = ctx.p1().status(ctx)[:3]
1280 files = ctx.p1().status(ctx)[:3]
1281 for key, value in zip([# i18n: column positioning for "hg log"
1281 for key, value in zip([# i18n: column positioning for "hg log"
1282 _("files:"),
1282 _("files:"),
1283 # i18n: column positioning for "hg log"
1283 # i18n: column positioning for "hg log"
1284 _("files+:"),
1284 _("files+:"),
1285 # i18n: column positioning for "hg log"
1285 # i18n: column positioning for "hg log"
1286 _("files-:")], files):
1286 _("files-:")], files):
1287 if value:
1287 if value:
1288 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1288 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1289 label='ui.debug log.files')
1289 label='ui.debug log.files')
1290 elif ctx.files() and self.ui.verbose:
1290 elif ctx.files() and self.ui.verbose:
1291 # i18n: column positioning for "hg log"
1291 # i18n: column positioning for "hg log"
1292 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1292 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1293 label='ui.note log.files')
1293 label='ui.note log.files')
1294 if copies and self.ui.verbose:
1294 if copies and self.ui.verbose:
1295 copies = ['%s (%s)' % c for c in copies]
1295 copies = ['%s (%s)' % c for c in copies]
1296 # i18n: column positioning for "hg log"
1296 # i18n: column positioning for "hg log"
1297 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1297 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1298 label='ui.note log.copies')
1298 label='ui.note log.copies')
1299
1299
1300 extra = ctx.extra()
1300 extra = ctx.extra()
1301 if extra and self.ui.debugflag:
1301 if extra and self.ui.debugflag:
1302 for key, value in sorted(extra.items()):
1302 for key, value in sorted(extra.items()):
1303 # i18n: column positioning for "hg log"
1303 # i18n: column positioning for "hg log"
1304 self.ui.write(_("extra: %s=%s\n")
1304 self.ui.write(_("extra: %s=%s\n")
1305 % (key, util.escapestr(value)),
1305 % (key, util.escapestr(value)),
1306 label='ui.debug log.extra')
1306 label='ui.debug log.extra')
1307
1307
1308 description = ctx.description().strip()
1308 description = ctx.description().strip()
1309 if description:
1309 if description:
1310 if self.ui.verbose:
1310 if self.ui.verbose:
1311 self.ui.write(_("description:\n"),
1311 self.ui.write(_("description:\n"),
1312 label='ui.note log.description')
1312 label='ui.note log.description')
1313 self.ui.write(description,
1313 self.ui.write(description,
1314 label='ui.note log.description')
1314 label='ui.note log.description')
1315 self.ui.write("\n\n")
1315 self.ui.write("\n\n")
1316 else:
1316 else:
1317 # i18n: column positioning for "hg log"
1317 # i18n: column positioning for "hg log"
1318 self.ui.write(_("summary: %s\n") %
1318 self.ui.write(_("summary: %s\n") %
1319 description.splitlines()[0],
1319 description.splitlines()[0],
1320 label='log.summary')
1320 label='log.summary')
1321 self.ui.write("\n")
1321 self.ui.write("\n")
1322
1322
1323 self.showpatch(ctx, matchfn)
1323 self.showpatch(ctx, matchfn)
1324
1324
1325 def showpatch(self, ctx, matchfn):
1325 def showpatch(self, ctx, matchfn):
1326 if not matchfn:
1326 if not matchfn:
1327 matchfn = self.matchfn
1327 matchfn = self.matchfn
1328 if matchfn:
1328 if matchfn:
1329 stat = self.diffopts.get('stat')
1329 stat = self.diffopts.get('stat')
1330 diff = self.diffopts.get('patch')
1330 diff = self.diffopts.get('patch')
1331 diffopts = patch.diffallopts(self.ui, self.diffopts)
1331 diffopts = patch.diffallopts(self.ui, self.diffopts)
1332 node = ctx.node()
1332 node = ctx.node()
1333 prev = ctx.p1().node()
1333 prev = ctx.p1().node()
1334 if stat:
1334 if stat:
1335 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1335 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1336 match=matchfn, stat=True)
1336 match=matchfn, stat=True)
1337 if diff:
1337 if diff:
1338 if stat:
1338 if stat:
1339 self.ui.write("\n")
1339 self.ui.write("\n")
1340 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1340 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1341 match=matchfn, stat=False)
1341 match=matchfn, stat=False)
1342 self.ui.write("\n")
1342 self.ui.write("\n")
1343
1343
1344 class jsonchangeset(changeset_printer):
1344 class jsonchangeset(changeset_printer):
1345 '''format changeset information.'''
1345 '''format changeset information.'''
1346
1346
1347 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1347 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1348 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1348 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1349 self.cache = {}
1349 self.cache = {}
1350 self._first = True
1350 self._first = True
1351
1351
1352 def close(self):
1352 def close(self):
1353 if not self._first:
1353 if not self._first:
1354 self.ui.write("\n]\n")
1354 self.ui.write("\n]\n")
1355 else:
1355 else:
1356 self.ui.write("[]\n")
1356 self.ui.write("[]\n")
1357
1357
1358 def _show(self, ctx, copies, matchfn, props):
1358 def _show(self, ctx, copies, matchfn, props):
1359 '''show a single changeset or file revision'''
1359 '''show a single changeset or file revision'''
1360 rev = ctx.rev()
1360 rev = ctx.rev()
1361 if rev is None:
1361 if rev is None:
1362 jrev = jnode = 'null'
1362 jrev = jnode = 'null'
1363 else:
1363 else:
1364 jrev = str(rev)
1364 jrev = str(rev)
1365 jnode = '"%s"' % hex(ctx.node())
1365 jnode = '"%s"' % hex(ctx.node())
1366 j = encoding.jsonescape
1366 j = encoding.jsonescape
1367
1367
1368 if self._first:
1368 if self._first:
1369 self.ui.write("[\n {")
1369 self.ui.write("[\n {")
1370 self._first = False
1370 self._first = False
1371 else:
1371 else:
1372 self.ui.write(",\n {")
1372 self.ui.write(",\n {")
1373
1373
1374 if self.ui.quiet:
1374 if self.ui.quiet:
1375 self.ui.write(('\n "rev": %s') % jrev)
1375 self.ui.write(('\n "rev": %s') % jrev)
1376 self.ui.write((',\n "node": %s') % jnode)
1376 self.ui.write((',\n "node": %s') % jnode)
1377 self.ui.write('\n }')
1377 self.ui.write('\n }')
1378 return
1378 return
1379
1379
1380 self.ui.write(('\n "rev": %s') % jrev)
1380 self.ui.write(('\n "rev": %s') % jrev)
1381 self.ui.write((',\n "node": %s') % jnode)
1381 self.ui.write((',\n "node": %s') % jnode)
1382 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1382 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1383 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1383 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1384 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1384 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1385 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1385 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1386 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1386 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1387
1387
1388 self.ui.write((',\n "bookmarks": [%s]') %
1388 self.ui.write((',\n "bookmarks": [%s]') %
1389 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1389 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1390 self.ui.write((',\n "tags": [%s]') %
1390 self.ui.write((',\n "tags": [%s]') %
1391 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1391 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1392 self.ui.write((',\n "parents": [%s]') %
1392 self.ui.write((',\n "parents": [%s]') %
1393 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1393 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1394
1394
1395 if self.ui.debugflag:
1395 if self.ui.debugflag:
1396 if rev is None:
1396 if rev is None:
1397 jmanifestnode = 'null'
1397 jmanifestnode = 'null'
1398 else:
1398 else:
1399 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1399 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1400 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1400 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1401
1401
1402 self.ui.write((',\n "extra": {%s}') %
1402 self.ui.write((',\n "extra": {%s}') %
1403 ", ".join('"%s": "%s"' % (j(k), j(v))
1403 ", ".join('"%s": "%s"' % (j(k), j(v))
1404 for k, v in ctx.extra().items()))
1404 for k, v in ctx.extra().items()))
1405
1405
1406 files = ctx.p1().status(ctx)
1406 files = ctx.p1().status(ctx)
1407 self.ui.write((',\n "modified": [%s]') %
1407 self.ui.write((',\n "modified": [%s]') %
1408 ", ".join('"%s"' % j(f) for f in files[0]))
1408 ", ".join('"%s"' % j(f) for f in files[0]))
1409 self.ui.write((',\n "added": [%s]') %
1409 self.ui.write((',\n "added": [%s]') %
1410 ", ".join('"%s"' % j(f) for f in files[1]))
1410 ", ".join('"%s"' % j(f) for f in files[1]))
1411 self.ui.write((',\n "removed": [%s]') %
1411 self.ui.write((',\n "removed": [%s]') %
1412 ", ".join('"%s"' % j(f) for f in files[2]))
1412 ", ".join('"%s"' % j(f) for f in files[2]))
1413
1413
1414 elif self.ui.verbose:
1414 elif self.ui.verbose:
1415 self.ui.write((',\n "files": [%s]') %
1415 self.ui.write((',\n "files": [%s]') %
1416 ", ".join('"%s"' % j(f) for f in ctx.files()))
1416 ", ".join('"%s"' % j(f) for f in ctx.files()))
1417
1417
1418 if copies:
1418 if copies:
1419 self.ui.write((',\n "copies": {%s}') %
1419 self.ui.write((',\n "copies": {%s}') %
1420 ", ".join('"%s": "%s"' % (j(k), j(v))
1420 ", ".join('"%s": "%s"' % (j(k), j(v))
1421 for k, v in copies))
1421 for k, v in copies))
1422
1422
1423 matchfn = self.matchfn
1423 matchfn = self.matchfn
1424 if matchfn:
1424 if matchfn:
1425 stat = self.diffopts.get('stat')
1425 stat = self.diffopts.get('stat')
1426 diff = self.diffopts.get('patch')
1426 diff = self.diffopts.get('patch')
1427 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1427 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1428 node, prev = ctx.node(), ctx.p1().node()
1428 node, prev = ctx.node(), ctx.p1().node()
1429 if stat:
1429 if stat:
1430 self.ui.pushbuffer()
1430 self.ui.pushbuffer()
1431 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1431 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1432 match=matchfn, stat=True)
1432 match=matchfn, stat=True)
1433 self.ui.write((',\n "diffstat": "%s"')
1433 self.ui.write((',\n "diffstat": "%s"')
1434 % j(self.ui.popbuffer()))
1434 % j(self.ui.popbuffer()))
1435 if diff:
1435 if diff:
1436 self.ui.pushbuffer()
1436 self.ui.pushbuffer()
1437 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1437 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1438 match=matchfn, stat=False)
1438 match=matchfn, stat=False)
1439 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1439 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1440
1440
1441 self.ui.write("\n }")
1441 self.ui.write("\n }")
1442
1442
1443 class changeset_templater(changeset_printer):
1443 class changeset_templater(changeset_printer):
1444 '''format changeset information.'''
1444 '''format changeset information.'''
1445
1445
1446 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1446 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1447 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1447 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1448 assert not (tmpl and mapfile)
1448 assert not (tmpl and mapfile)
1449 defaulttempl = templatekw.defaulttempl
1449 defaulttempl = templatekw.defaulttempl
1450 if mapfile:
1450 if mapfile:
1451 self.t = templater.templater.frommapfile(mapfile,
1451 self.t = templater.templater.frommapfile(mapfile,
1452 cache=defaulttempl)
1452 cache=defaulttempl)
1453 else:
1453 else:
1454 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1454 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1455 cache=defaulttempl)
1455 cache=defaulttempl)
1456
1456
1457 self._counter = itertools.count()
1457 self._counter = itertools.count()
1458 self.cache = {}
1458 self.cache = {}
1459
1459
1460 # find correct templates for current mode
1460 # find correct templates for current mode
1461 tmplmodes = [
1461 tmplmodes = [
1462 (True, None),
1462 (True, None),
1463 (self.ui.verbose, 'verbose'),
1463 (self.ui.verbose, 'verbose'),
1464 (self.ui.quiet, 'quiet'),
1464 (self.ui.quiet, 'quiet'),
1465 (self.ui.debugflag, 'debug'),
1465 (self.ui.debugflag, 'debug'),
1466 ]
1466 ]
1467
1467
1468 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1468 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1469 'docheader': '', 'docfooter': ''}
1469 'docheader': '', 'docfooter': ''}
1470 for mode, postfix in tmplmodes:
1470 for mode, postfix in tmplmodes:
1471 for t in self._parts:
1471 for t in self._parts:
1472 cur = t
1472 cur = t
1473 if postfix:
1473 if postfix:
1474 cur += "_" + postfix
1474 cur += "_" + postfix
1475 if mode and cur in self.t:
1475 if mode and cur in self.t:
1476 self._parts[t] = cur
1476 self._parts[t] = cur
1477
1477
1478 if self._parts['docheader']:
1478 if self._parts['docheader']:
1479 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1479 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1480
1480
1481 def close(self):
1481 def close(self):
1482 if self._parts['docfooter']:
1482 if self._parts['docfooter']:
1483 if not self.footer:
1483 if not self.footer:
1484 self.footer = ""
1484 self.footer = ""
1485 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1485 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1486 return super(changeset_templater, self).close()
1486 return super(changeset_templater, self).close()
1487
1487
1488 def _show(self, ctx, copies, matchfn, props):
1488 def _show(self, ctx, copies, matchfn, props):
1489 '''show a single changeset or file revision'''
1489 '''show a single changeset or file revision'''
1490 props = props.copy()
1490 props = props.copy()
1491 props.update(templatekw.keywords)
1491 props.update(templatekw.keywords)
1492 props['templ'] = self.t
1492 props['templ'] = self.t
1493 props['ctx'] = ctx
1493 props['ctx'] = ctx
1494 props['repo'] = self.repo
1494 props['repo'] = self.repo
1495 props['ui'] = self.repo.ui
1495 props['ui'] = self.repo.ui
1496 props['index'] = next(self._counter)
1496 props['index'] = next(self._counter)
1497 props['revcache'] = {'copies': copies}
1497 props['revcache'] = {'copies': copies}
1498 props['cache'] = self.cache
1498 props['cache'] = self.cache
1499
1499
1500 # write header
1500 # write header
1501 if self._parts['header']:
1501 if self._parts['header']:
1502 h = templater.stringify(self.t(self._parts['header'], **props))
1502 h = templater.stringify(self.t(self._parts['header'], **props))
1503 if self.buffered:
1503 if self.buffered:
1504 self.header[ctx.rev()] = h
1504 self.header[ctx.rev()] = h
1505 else:
1505 else:
1506 if self.lastheader != h:
1506 if self.lastheader != h:
1507 self.lastheader = h
1507 self.lastheader = h
1508 self.ui.write(h)
1508 self.ui.write(h)
1509
1509
1510 # write changeset metadata, then patch if requested
1510 # write changeset metadata, then patch if requested
1511 key = self._parts['changeset']
1511 key = self._parts['changeset']
1512 self.ui.write(templater.stringify(self.t(key, **props)))
1512 self.ui.write(templater.stringify(self.t(key, **props)))
1513 self.showpatch(ctx, matchfn)
1513 self.showpatch(ctx, matchfn)
1514
1514
1515 if self._parts['footer']:
1515 if self._parts['footer']:
1516 if not self.footer:
1516 if not self.footer:
1517 self.footer = templater.stringify(
1517 self.footer = templater.stringify(
1518 self.t(self._parts['footer'], **props))
1518 self.t(self._parts['footer'], **props))
1519
1519
1520 def gettemplate(ui, tmpl, style):
1520 def gettemplate(ui, tmpl, style):
1521 """
1521 """
1522 Find the template matching the given template spec or style.
1522 Find the template matching the given template spec or style.
1523 """
1523 """
1524
1524
1525 # ui settings
1525 # ui settings
1526 if not tmpl and not style: # template are stronger than style
1526 if not tmpl and not style: # template are stronger than style
1527 tmpl = ui.config('ui', 'logtemplate')
1527 tmpl = ui.config('ui', 'logtemplate')
1528 if tmpl:
1528 if tmpl:
1529 return templater.unquotestring(tmpl), None
1529 return templater.unquotestring(tmpl), None
1530 else:
1530 else:
1531 style = util.expandpath(ui.config('ui', 'style', ''))
1531 style = util.expandpath(ui.config('ui', 'style', ''))
1532
1532
1533 if not tmpl and style:
1533 if not tmpl and style:
1534 mapfile = style
1534 mapfile = style
1535 if not os.path.split(mapfile)[0]:
1535 if not os.path.split(mapfile)[0]:
1536 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1536 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1537 or templater.templatepath(mapfile))
1537 or templater.templatepath(mapfile))
1538 if mapname:
1538 if mapname:
1539 mapfile = mapname
1539 mapfile = mapname
1540 return None, mapfile
1540 return None, mapfile
1541
1541
1542 if not tmpl:
1542 if not tmpl:
1543 return None, None
1543 return None, None
1544
1544
1545 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1545 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1546
1546
1547 def show_changeset(ui, repo, opts, buffered=False):
1547 def show_changeset(ui, repo, opts, buffered=False):
1548 """show one changeset using template or regular display.
1548 """show one changeset using template or regular display.
1549
1549
1550 Display format will be the first non-empty hit of:
1550 Display format will be the first non-empty hit of:
1551 1. option 'template'
1551 1. option 'template'
1552 2. option 'style'
1552 2. option 'style'
1553 3. [ui] setting 'logtemplate'
1553 3. [ui] setting 'logtemplate'
1554 4. [ui] setting 'style'
1554 4. [ui] setting 'style'
1555 If all of these values are either the unset or the empty string,
1555 If all of these values are either the unset or the empty string,
1556 regular display via changeset_printer() is done.
1556 regular display via changeset_printer() is done.
1557 """
1557 """
1558 # options
1558 # options
1559 matchfn = None
1559 matchfn = None
1560 if opts.get('patch') or opts.get('stat'):
1560 if opts.get('patch') or opts.get('stat'):
1561 matchfn = scmutil.matchall(repo)
1561 matchfn = scmutil.matchall(repo)
1562
1562
1563 if opts.get('template') == 'json':
1563 if opts.get('template') == 'json':
1564 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1564 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1565
1565
1566 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1566 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1567
1567
1568 if not tmpl and not mapfile:
1568 if not tmpl and not mapfile:
1569 return changeset_printer(ui, repo, matchfn, opts, buffered)
1569 return changeset_printer(ui, repo, matchfn, opts, buffered)
1570
1570
1571 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1571 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1572
1572
1573 def showmarker(fm, marker, index=None):
1573 def showmarker(fm, marker, index=None):
1574 """utility function to display obsolescence marker in a readable way
1574 """utility function to display obsolescence marker in a readable way
1575
1575
1576 To be used by debug function."""
1576 To be used by debug function."""
1577 if index is not None:
1577 if index is not None:
1578 fm.write('index', '%i ', index)
1578 fm.write('index', '%i ', index)
1579 fm.write('precnode', '%s ', hex(marker.precnode()))
1579 fm.write('precnode', '%s ', hex(marker.precnode()))
1580 succs = marker.succnodes()
1580 succs = marker.succnodes()
1581 fm.condwrite(succs, 'succnodes', '%s ',
1581 fm.condwrite(succs, 'succnodes', '%s ',
1582 fm.formatlist(map(hex, succs), name='node'))
1582 fm.formatlist(map(hex, succs), name='node'))
1583 fm.write('flag', '%X ', marker.flags())
1583 fm.write('flag', '%X ', marker.flags())
1584 parents = marker.parentnodes()
1584 parents = marker.parentnodes()
1585 if parents is not None:
1585 if parents is not None:
1586 fm.write('parentnodes', '{%s} ',
1586 fm.write('parentnodes', '{%s} ',
1587 fm.formatlist(map(hex, parents), name='node', sep=', '))
1587 fm.formatlist(map(hex, parents), name='node', sep=', '))
1588 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1588 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1589 meta = marker.metadata().copy()
1589 meta = marker.metadata().copy()
1590 meta.pop('date', None)
1590 meta.pop('date', None)
1591 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1591 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1592 fm.plain('\n')
1592 fm.plain('\n')
1593
1593
1594 def finddate(ui, repo, date):
1594 def finddate(ui, repo, date):
1595 """Find the tipmost changeset that matches the given date spec"""
1595 """Find the tipmost changeset that matches the given date spec"""
1596
1596
1597 df = util.matchdate(date)
1597 df = util.matchdate(date)
1598 m = scmutil.matchall(repo)
1598 m = scmutil.matchall(repo)
1599 results = {}
1599 results = {}
1600
1600
1601 def prep(ctx, fns):
1601 def prep(ctx, fns):
1602 d = ctx.date()
1602 d = ctx.date()
1603 if df(d[0]):
1603 if df(d[0]):
1604 results[ctx.rev()] = d
1604 results[ctx.rev()] = d
1605
1605
1606 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1606 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1607 rev = ctx.rev()
1607 rev = ctx.rev()
1608 if rev in results:
1608 if rev in results:
1609 ui.status(_("found revision %s from %s\n") %
1609 ui.status(_("found revision %s from %s\n") %
1610 (rev, util.datestr(results[rev])))
1610 (rev, util.datestr(results[rev])))
1611 return str(rev)
1611 return str(rev)
1612
1612
1613 raise error.Abort(_("revision matching date not found"))
1613 raise error.Abort(_("revision matching date not found"))
1614
1614
1615 def increasingwindows(windowsize=8, sizelimit=512):
1615 def increasingwindows(windowsize=8, sizelimit=512):
1616 while True:
1616 while True:
1617 yield windowsize
1617 yield windowsize
1618 if windowsize < sizelimit:
1618 if windowsize < sizelimit:
1619 windowsize *= 2
1619 windowsize *= 2
1620
1620
1621 class FileWalkError(Exception):
1621 class FileWalkError(Exception):
1622 pass
1622 pass
1623
1623
1624 def walkfilerevs(repo, match, follow, revs, fncache):
1624 def walkfilerevs(repo, match, follow, revs, fncache):
1625 '''Walks the file history for the matched files.
1625 '''Walks the file history for the matched files.
1626
1626
1627 Returns the changeset revs that are involved in the file history.
1627 Returns the changeset revs that are involved in the file history.
1628
1628
1629 Throws FileWalkError if the file history can't be walked using
1629 Throws FileWalkError if the file history can't be walked using
1630 filelogs alone.
1630 filelogs alone.
1631 '''
1631 '''
1632 wanted = set()
1632 wanted = set()
1633 copies = []
1633 copies = []
1634 minrev, maxrev = min(revs), max(revs)
1634 minrev, maxrev = min(revs), max(revs)
1635 def filerevgen(filelog, last):
1635 def filerevgen(filelog, last):
1636 """
1636 """
1637 Only files, no patterns. Check the history of each file.
1637 Only files, no patterns. Check the history of each file.
1638
1638
1639 Examines filelog entries within minrev, maxrev linkrev range
1639 Examines filelog entries within minrev, maxrev linkrev range
1640 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1640 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1641 tuples in backwards order
1641 tuples in backwards order
1642 """
1642 """
1643 cl_count = len(repo)
1643 cl_count = len(repo)
1644 revs = []
1644 revs = []
1645 for j in xrange(0, last + 1):
1645 for j in xrange(0, last + 1):
1646 linkrev = filelog.linkrev(j)
1646 linkrev = filelog.linkrev(j)
1647 if linkrev < minrev:
1647 if linkrev < minrev:
1648 continue
1648 continue
1649 # only yield rev for which we have the changelog, it can
1649 # only yield rev for which we have the changelog, it can
1650 # happen while doing "hg log" during a pull or commit
1650 # happen while doing "hg log" during a pull or commit
1651 if linkrev >= cl_count:
1651 if linkrev >= cl_count:
1652 break
1652 break
1653
1653
1654 parentlinkrevs = []
1654 parentlinkrevs = []
1655 for p in filelog.parentrevs(j):
1655 for p in filelog.parentrevs(j):
1656 if p != nullrev:
1656 if p != nullrev:
1657 parentlinkrevs.append(filelog.linkrev(p))
1657 parentlinkrevs.append(filelog.linkrev(p))
1658 n = filelog.node(j)
1658 n = filelog.node(j)
1659 revs.append((linkrev, parentlinkrevs,
1659 revs.append((linkrev, parentlinkrevs,
1660 follow and filelog.renamed(n)))
1660 follow and filelog.renamed(n)))
1661
1661
1662 return reversed(revs)
1662 return reversed(revs)
1663 def iterfiles():
1663 def iterfiles():
1664 pctx = repo['.']
1664 pctx = repo['.']
1665 for filename in match.files():
1665 for filename in match.files():
1666 if follow:
1666 if follow:
1667 if filename not in pctx:
1667 if filename not in pctx:
1668 raise error.Abort(_('cannot follow file not in parent '
1668 raise error.Abort(_('cannot follow file not in parent '
1669 'revision: "%s"') % filename)
1669 'revision: "%s"') % filename)
1670 yield filename, pctx[filename].filenode()
1670 yield filename, pctx[filename].filenode()
1671 else:
1671 else:
1672 yield filename, None
1672 yield filename, None
1673 for filename_node in copies:
1673 for filename_node in copies:
1674 yield filename_node
1674 yield filename_node
1675
1675
1676 for file_, node in iterfiles():
1676 for file_, node in iterfiles():
1677 filelog = repo.file(file_)
1677 filelog = repo.file(file_)
1678 if not len(filelog):
1678 if not len(filelog):
1679 if node is None:
1679 if node is None:
1680 # A zero count may be a directory or deleted file, so
1680 # A zero count may be a directory or deleted file, so
1681 # try to find matching entries on the slow path.
1681 # try to find matching entries on the slow path.
1682 if follow:
1682 if follow:
1683 raise error.Abort(
1683 raise error.Abort(
1684 _('cannot follow nonexistent file: "%s"') % file_)
1684 _('cannot follow nonexistent file: "%s"') % file_)
1685 raise FileWalkError("Cannot walk via filelog")
1685 raise FileWalkError("Cannot walk via filelog")
1686 else:
1686 else:
1687 continue
1687 continue
1688
1688
1689 if node is None:
1689 if node is None:
1690 last = len(filelog) - 1
1690 last = len(filelog) - 1
1691 else:
1691 else:
1692 last = filelog.rev(node)
1692 last = filelog.rev(node)
1693
1693
1694 # keep track of all ancestors of the file
1694 # keep track of all ancestors of the file
1695 ancestors = set([filelog.linkrev(last)])
1695 ancestors = set([filelog.linkrev(last)])
1696
1696
1697 # iterate from latest to oldest revision
1697 # iterate from latest to oldest revision
1698 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1698 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1699 if not follow:
1699 if not follow:
1700 if rev > maxrev:
1700 if rev > maxrev:
1701 continue
1701 continue
1702 else:
1702 else:
1703 # Note that last might not be the first interesting
1703 # Note that last might not be the first interesting
1704 # rev to us:
1704 # rev to us:
1705 # if the file has been changed after maxrev, we'll
1705 # if the file has been changed after maxrev, we'll
1706 # have linkrev(last) > maxrev, and we still need
1706 # have linkrev(last) > maxrev, and we still need
1707 # to explore the file graph
1707 # to explore the file graph
1708 if rev not in ancestors:
1708 if rev not in ancestors:
1709 continue
1709 continue
1710 # XXX insert 1327 fix here
1710 # XXX insert 1327 fix here
1711 if flparentlinkrevs:
1711 if flparentlinkrevs:
1712 ancestors.update(flparentlinkrevs)
1712 ancestors.update(flparentlinkrevs)
1713
1713
1714 fncache.setdefault(rev, []).append(file_)
1714 fncache.setdefault(rev, []).append(file_)
1715 wanted.add(rev)
1715 wanted.add(rev)
1716 if copied:
1716 if copied:
1717 copies.append(copied)
1717 copies.append(copied)
1718
1718
1719 return wanted
1719 return wanted
1720
1720
1721 class _followfilter(object):
1721 class _followfilter(object):
1722 def __init__(self, repo, onlyfirst=False):
1722 def __init__(self, repo, onlyfirst=False):
1723 self.repo = repo
1723 self.repo = repo
1724 self.startrev = nullrev
1724 self.startrev = nullrev
1725 self.roots = set()
1725 self.roots = set()
1726 self.onlyfirst = onlyfirst
1726 self.onlyfirst = onlyfirst
1727
1727
1728 def match(self, rev):
1728 def match(self, rev):
1729 def realparents(rev):
1729 def realparents(rev):
1730 if self.onlyfirst:
1730 if self.onlyfirst:
1731 return self.repo.changelog.parentrevs(rev)[0:1]
1731 return self.repo.changelog.parentrevs(rev)[0:1]
1732 else:
1732 else:
1733 return filter(lambda x: x != nullrev,
1733 return filter(lambda x: x != nullrev,
1734 self.repo.changelog.parentrevs(rev))
1734 self.repo.changelog.parentrevs(rev))
1735
1735
1736 if self.startrev == nullrev:
1736 if self.startrev == nullrev:
1737 self.startrev = rev
1737 self.startrev = rev
1738 return True
1738 return True
1739
1739
1740 if rev > self.startrev:
1740 if rev > self.startrev:
1741 # forward: all descendants
1741 # forward: all descendants
1742 if not self.roots:
1742 if not self.roots:
1743 self.roots.add(self.startrev)
1743 self.roots.add(self.startrev)
1744 for parent in realparents(rev):
1744 for parent in realparents(rev):
1745 if parent in self.roots:
1745 if parent in self.roots:
1746 self.roots.add(rev)
1746 self.roots.add(rev)
1747 return True
1747 return True
1748 else:
1748 else:
1749 # backwards: all parents
1749 # backwards: all parents
1750 if not self.roots:
1750 if not self.roots:
1751 self.roots.update(realparents(self.startrev))
1751 self.roots.update(realparents(self.startrev))
1752 if rev in self.roots:
1752 if rev in self.roots:
1753 self.roots.remove(rev)
1753 self.roots.remove(rev)
1754 self.roots.update(realparents(rev))
1754 self.roots.update(realparents(rev))
1755 return True
1755 return True
1756
1756
1757 return False
1757 return False
1758
1758
1759 def walkchangerevs(repo, match, opts, prepare):
1759 def walkchangerevs(repo, match, opts, prepare):
1760 '''Iterate over files and the revs in which they changed.
1760 '''Iterate over files and the revs in which they changed.
1761
1761
1762 Callers most commonly need to iterate backwards over the history
1762 Callers most commonly need to iterate backwards over the history
1763 in which they are interested. Doing so has awful (quadratic-looking)
1763 in which they are interested. Doing so has awful (quadratic-looking)
1764 performance, so we use iterators in a "windowed" way.
1764 performance, so we use iterators in a "windowed" way.
1765
1765
1766 We walk a window of revisions in the desired order. Within the
1766 We walk a window of revisions in the desired order. Within the
1767 window, we first walk forwards to gather data, then in the desired
1767 window, we first walk forwards to gather data, then in the desired
1768 order (usually backwards) to display it.
1768 order (usually backwards) to display it.
1769
1769
1770 This function returns an iterator yielding contexts. Before
1770 This function returns an iterator yielding contexts. Before
1771 yielding each context, the iterator will first call the prepare
1771 yielding each context, the iterator will first call the prepare
1772 function on each context in the window in forward order.'''
1772 function on each context in the window in forward order.'''
1773
1773
1774 follow = opts.get('follow') or opts.get('follow_first')
1774 follow = opts.get('follow') or opts.get('follow_first')
1775 revs = _logrevs(repo, opts)
1775 revs = _logrevs(repo, opts)
1776 if not revs:
1776 if not revs:
1777 return []
1777 return []
1778 wanted = set()
1778 wanted = set()
1779 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1779 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1780 opts.get('removed'))
1780 opts.get('removed'))
1781 fncache = {}
1781 fncache = {}
1782 change = repo.changectx
1782 change = repo.changectx
1783
1783
1784 # First step is to fill wanted, the set of revisions that we want to yield.
1784 # First step is to fill wanted, the set of revisions that we want to yield.
1785 # When it does not induce extra cost, we also fill fncache for revisions in
1785 # When it does not induce extra cost, we also fill fncache for revisions in
1786 # wanted: a cache of filenames that were changed (ctx.files()) and that
1786 # wanted: a cache of filenames that were changed (ctx.files()) and that
1787 # match the file filtering conditions.
1787 # match the file filtering conditions.
1788
1788
1789 if match.always():
1789 if match.always():
1790 # No files, no patterns. Display all revs.
1790 # No files, no patterns. Display all revs.
1791 wanted = revs
1791 wanted = revs
1792 elif not slowpath:
1792 elif not slowpath:
1793 # We only have to read through the filelog to find wanted revisions
1793 # We only have to read through the filelog to find wanted revisions
1794
1794
1795 try:
1795 try:
1796 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1796 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1797 except FileWalkError:
1797 except FileWalkError:
1798 slowpath = True
1798 slowpath = True
1799
1799
1800 # We decided to fall back to the slowpath because at least one
1800 # We decided to fall back to the slowpath because at least one
1801 # of the paths was not a file. Check to see if at least one of them
1801 # of the paths was not a file. Check to see if at least one of them
1802 # existed in history, otherwise simply return
1802 # existed in history, otherwise simply return
1803 for path in match.files():
1803 for path in match.files():
1804 if path == '.' or path in repo.store:
1804 if path == '.' or path in repo.store:
1805 break
1805 break
1806 else:
1806 else:
1807 return []
1807 return []
1808
1808
1809 if slowpath:
1809 if slowpath:
1810 # We have to read the changelog to match filenames against
1810 # We have to read the changelog to match filenames against
1811 # changed files
1811 # changed files
1812
1812
1813 if follow:
1813 if follow:
1814 raise error.Abort(_('can only follow copies/renames for explicit '
1814 raise error.Abort(_('can only follow copies/renames for explicit '
1815 'filenames'))
1815 'filenames'))
1816
1816
1817 # The slow path checks files modified in every changeset.
1817 # The slow path checks files modified in every changeset.
1818 # This is really slow on large repos, so compute the set lazily.
1818 # This is really slow on large repos, so compute the set lazily.
1819 class lazywantedset(object):
1819 class lazywantedset(object):
1820 def __init__(self):
1820 def __init__(self):
1821 self.set = set()
1821 self.set = set()
1822 self.revs = set(revs)
1822 self.revs = set(revs)
1823
1823
1824 # No need to worry about locality here because it will be accessed
1824 # No need to worry about locality here because it will be accessed
1825 # in the same order as the increasing window below.
1825 # in the same order as the increasing window below.
1826 def __contains__(self, value):
1826 def __contains__(self, value):
1827 if value in self.set:
1827 if value in self.set:
1828 return True
1828 return True
1829 elif not value in self.revs:
1829 elif not value in self.revs:
1830 return False
1830 return False
1831 else:
1831 else:
1832 self.revs.discard(value)
1832 self.revs.discard(value)
1833 ctx = change(value)
1833 ctx = change(value)
1834 matches = filter(match, ctx.files())
1834 matches = filter(match, ctx.files())
1835 if matches:
1835 if matches:
1836 fncache[value] = matches
1836 fncache[value] = matches
1837 self.set.add(value)
1837 self.set.add(value)
1838 return True
1838 return True
1839 return False
1839 return False
1840
1840
1841 def discard(self, value):
1841 def discard(self, value):
1842 self.revs.discard(value)
1842 self.revs.discard(value)
1843 self.set.discard(value)
1843 self.set.discard(value)
1844
1844
1845 wanted = lazywantedset()
1845 wanted = lazywantedset()
1846
1846
1847 # it might be worthwhile to do this in the iterator if the rev range
1847 # it might be worthwhile to do this in the iterator if the rev range
1848 # is descending and the prune args are all within that range
1848 # is descending and the prune args are all within that range
1849 for rev in opts.get('prune', ()):
1849 for rev in opts.get('prune', ()):
1850 rev = repo[rev].rev()
1850 rev = repo[rev].rev()
1851 ff = _followfilter(repo)
1851 ff = _followfilter(repo)
1852 stop = min(revs[0], revs[-1])
1852 stop = min(revs[0], revs[-1])
1853 for x in xrange(rev, stop - 1, -1):
1853 for x in xrange(rev, stop - 1, -1):
1854 if ff.match(x):
1854 if ff.match(x):
1855 wanted = wanted - [x]
1855 wanted = wanted - [x]
1856
1856
1857 # Now that wanted is correctly initialized, we can iterate over the
1857 # Now that wanted is correctly initialized, we can iterate over the
1858 # revision range, yielding only revisions in wanted.
1858 # revision range, yielding only revisions in wanted.
1859 def iterate():
1859 def iterate():
1860 if follow and match.always():
1860 if follow and match.always():
1861 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1861 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1862 def want(rev):
1862 def want(rev):
1863 return ff.match(rev) and rev in wanted
1863 return ff.match(rev) and rev in wanted
1864 else:
1864 else:
1865 def want(rev):
1865 def want(rev):
1866 return rev in wanted
1866 return rev in wanted
1867
1867
1868 it = iter(revs)
1868 it = iter(revs)
1869 stopiteration = False
1869 stopiteration = False
1870 for windowsize in increasingwindows():
1870 for windowsize in increasingwindows():
1871 nrevs = []
1871 nrevs = []
1872 for i in xrange(windowsize):
1872 for i in xrange(windowsize):
1873 rev = next(it, None)
1873 rev = next(it, None)
1874 if rev is None:
1874 if rev is None:
1875 stopiteration = True
1875 stopiteration = True
1876 break
1876 break
1877 elif want(rev):
1877 elif want(rev):
1878 nrevs.append(rev)
1878 nrevs.append(rev)
1879 for rev in sorted(nrevs):
1879 for rev in sorted(nrevs):
1880 fns = fncache.get(rev)
1880 fns = fncache.get(rev)
1881 ctx = change(rev)
1881 ctx = change(rev)
1882 if not fns:
1882 if not fns:
1883 def fns_generator():
1883 def fns_generator():
1884 for f in ctx.files():
1884 for f in ctx.files():
1885 if match(f):
1885 if match(f):
1886 yield f
1886 yield f
1887 fns = fns_generator()
1887 fns = fns_generator()
1888 prepare(ctx, fns)
1888 prepare(ctx, fns)
1889 for rev in nrevs:
1889 for rev in nrevs:
1890 yield change(rev)
1890 yield change(rev)
1891
1891
1892 if stopiteration:
1892 if stopiteration:
1893 break
1893 break
1894
1894
1895 return iterate()
1895 return iterate()
1896
1896
1897 def _makefollowlogfilematcher(repo, files, followfirst):
1897 def _makefollowlogfilematcher(repo, files, followfirst):
1898 # When displaying a revision with --patch --follow FILE, we have
1898 # When displaying a revision with --patch --follow FILE, we have
1899 # to know which file of the revision must be diffed. With
1899 # to know which file of the revision must be diffed. With
1900 # --follow, we want the names of the ancestors of FILE in the
1900 # --follow, we want the names of the ancestors of FILE in the
1901 # revision, stored in "fcache". "fcache" is populated by
1901 # revision, stored in "fcache". "fcache" is populated by
1902 # reproducing the graph traversal already done by --follow revset
1902 # reproducing the graph traversal already done by --follow revset
1903 # and relating revs to file names (which is not "correct" but
1903 # and relating revs to file names (which is not "correct" but
1904 # good enough).
1904 # good enough).
1905 fcache = {}
1905 fcache = {}
1906 fcacheready = [False]
1906 fcacheready = [False]
1907 pctx = repo['.']
1907 pctx = repo['.']
1908
1908
1909 def populate():
1909 def populate():
1910 for fn in files:
1910 for fn in files:
1911 fctx = pctx[fn]
1911 fctx = pctx[fn]
1912 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1912 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1913 for c in fctx.ancestors(followfirst=followfirst):
1913 for c in fctx.ancestors(followfirst=followfirst):
1914 fcache.setdefault(c.rev(), set()).add(c.path())
1914 fcache.setdefault(c.rev(), set()).add(c.path())
1915
1915
1916 def filematcher(rev):
1916 def filematcher(rev):
1917 if not fcacheready[0]:
1917 if not fcacheready[0]:
1918 # Lazy initialization
1918 # Lazy initialization
1919 fcacheready[0] = True
1919 fcacheready[0] = True
1920 populate()
1920 populate()
1921 return scmutil.matchfiles(repo, fcache.get(rev, []))
1921 return scmutil.matchfiles(repo, fcache.get(rev, []))
1922
1922
1923 return filematcher
1923 return filematcher
1924
1924
1925 def _makenofollowlogfilematcher(repo, pats, opts):
1925 def _makenofollowlogfilematcher(repo, pats, opts):
1926 '''hook for extensions to override the filematcher for non-follow cases'''
1926 '''hook for extensions to override the filematcher for non-follow cases'''
1927 return None
1927 return None
1928
1928
1929 def _makelogrevset(repo, pats, opts, revs):
1929 def _makelogrevset(repo, pats, opts, revs):
1930 """Return (expr, filematcher) where expr is a revset string built
1930 """Return (expr, filematcher) where expr is a revset string built
1931 from log options and file patterns or None. If --stat or --patch
1931 from log options and file patterns or None. If --stat or --patch
1932 are not passed filematcher is None. Otherwise it is a callable
1932 are not passed filematcher is None. Otherwise it is a callable
1933 taking a revision number and returning a match objects filtering
1933 taking a revision number and returning a match objects filtering
1934 the files to be detailed when displaying the revision.
1934 the files to be detailed when displaying the revision.
1935 """
1935 """
1936 opt2revset = {
1936 opt2revset = {
1937 'no_merges': ('not merge()', None),
1937 'no_merges': ('not merge()', None),
1938 'only_merges': ('merge()', None),
1938 'only_merges': ('merge()', None),
1939 '_ancestors': ('ancestors(%(val)s)', None),
1939 '_ancestors': ('ancestors(%(val)s)', None),
1940 '_fancestors': ('_firstancestors(%(val)s)', None),
1940 '_fancestors': ('_firstancestors(%(val)s)', None),
1941 '_descendants': ('descendants(%(val)s)', None),
1941 '_descendants': ('descendants(%(val)s)', None),
1942 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1942 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1943 '_matchfiles': ('_matchfiles(%(val)s)', None),
1943 '_matchfiles': ('_matchfiles(%(val)s)', None),
1944 'date': ('date(%(val)r)', None),
1944 'date': ('date(%(val)r)', None),
1945 'branch': ('branch(%(val)r)', ' or '),
1945 'branch': ('branch(%(val)r)', ' or '),
1946 '_patslog': ('filelog(%(val)r)', ' or '),
1946 '_patslog': ('filelog(%(val)r)', ' or '),
1947 '_patsfollow': ('follow(%(val)r)', ' or '),
1947 '_patsfollow': ('follow(%(val)r)', ' or '),
1948 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1948 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1949 'keyword': ('keyword(%(val)r)', ' or '),
1949 'keyword': ('keyword(%(val)r)', ' or '),
1950 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1950 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1951 'user': ('user(%(val)r)', ' or '),
1951 'user': ('user(%(val)r)', ' or '),
1952 }
1952 }
1953
1953
1954 opts = dict(opts)
1954 opts = dict(opts)
1955 # follow or not follow?
1955 # follow or not follow?
1956 follow = opts.get('follow') or opts.get('follow_first')
1956 follow = opts.get('follow') or opts.get('follow_first')
1957 if opts.get('follow_first'):
1957 if opts.get('follow_first'):
1958 followfirst = 1
1958 followfirst = 1
1959 else:
1959 else:
1960 followfirst = 0
1960 followfirst = 0
1961 # --follow with FILE behavior depends on revs...
1961 # --follow with FILE behavior depends on revs...
1962 it = iter(revs)
1962 it = iter(revs)
1963 startrev = next(it)
1963 startrev = next(it)
1964 followdescendants = startrev < next(it, startrev)
1964 followdescendants = startrev < next(it, startrev)
1965
1965
1966 # branch and only_branch are really aliases and must be handled at
1966 # branch and only_branch are really aliases and must be handled at
1967 # the same time
1967 # the same time
1968 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1968 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1969 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1969 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1970 # pats/include/exclude are passed to match.match() directly in
1970 # pats/include/exclude are passed to match.match() directly in
1971 # _matchfiles() revset but walkchangerevs() builds its matcher with
1971 # _matchfiles() revset but walkchangerevs() builds its matcher with
1972 # scmutil.match(). The difference is input pats are globbed on
1972 # scmutil.match(). The difference is input pats are globbed on
1973 # platforms without shell expansion (windows).
1973 # platforms without shell expansion (windows).
1974 wctx = repo[None]
1974 wctx = repo[None]
1975 match, pats = scmutil.matchandpats(wctx, pats, opts)
1975 match, pats = scmutil.matchandpats(wctx, pats, opts)
1976 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1976 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1977 opts.get('removed'))
1977 opts.get('removed'))
1978 if not slowpath:
1978 if not slowpath:
1979 for f in match.files():
1979 for f in match.files():
1980 if follow and f not in wctx:
1980 if follow and f not in wctx:
1981 # If the file exists, it may be a directory, so let it
1981 # If the file exists, it may be a directory, so let it
1982 # take the slow path.
1982 # take the slow path.
1983 if os.path.exists(repo.wjoin(f)):
1983 if os.path.exists(repo.wjoin(f)):
1984 slowpath = True
1984 slowpath = True
1985 continue
1985 continue
1986 else:
1986 else:
1987 raise error.Abort(_('cannot follow file not in parent '
1987 raise error.Abort(_('cannot follow file not in parent '
1988 'revision: "%s"') % f)
1988 'revision: "%s"') % f)
1989 filelog = repo.file(f)
1989 filelog = repo.file(f)
1990 if not filelog:
1990 if not filelog:
1991 # A zero count may be a directory or deleted file, so
1991 # A zero count may be a directory or deleted file, so
1992 # try to find matching entries on the slow path.
1992 # try to find matching entries on the slow path.
1993 if follow:
1993 if follow:
1994 raise error.Abort(
1994 raise error.Abort(
1995 _('cannot follow nonexistent file: "%s"') % f)
1995 _('cannot follow nonexistent file: "%s"') % f)
1996 slowpath = True
1996 slowpath = True
1997
1997
1998 # We decided to fall back to the slowpath because at least one
1998 # We decided to fall back to the slowpath because at least one
1999 # of the paths was not a file. Check to see if at least one of them
1999 # of the paths was not a file. Check to see if at least one of them
2000 # existed in history - in that case, we'll continue down the
2000 # existed in history - in that case, we'll continue down the
2001 # slowpath; otherwise, we can turn off the slowpath
2001 # slowpath; otherwise, we can turn off the slowpath
2002 if slowpath:
2002 if slowpath:
2003 for path in match.files():
2003 for path in match.files():
2004 if path == '.' or path in repo.store:
2004 if path == '.' or path in repo.store:
2005 break
2005 break
2006 else:
2006 else:
2007 slowpath = False
2007 slowpath = False
2008
2008
2009 fpats = ('_patsfollow', '_patsfollowfirst')
2009 fpats = ('_patsfollow', '_patsfollowfirst')
2010 fnopats = (('_ancestors', '_fancestors'),
2010 fnopats = (('_ancestors', '_fancestors'),
2011 ('_descendants', '_fdescendants'))
2011 ('_descendants', '_fdescendants'))
2012 if slowpath:
2012 if slowpath:
2013 # See walkchangerevs() slow path.
2013 # See walkchangerevs() slow path.
2014 #
2014 #
2015 # pats/include/exclude cannot be represented as separate
2015 # pats/include/exclude cannot be represented as separate
2016 # revset expressions as their filtering logic applies at file
2016 # revset expressions as their filtering logic applies at file
2017 # level. For instance "-I a -X a" matches a revision touching
2017 # level. For instance "-I a -X a" matches a revision touching
2018 # "a" and "b" while "file(a) and not file(b)" does
2018 # "a" and "b" while "file(a) and not file(b)" does
2019 # not. Besides, filesets are evaluated against the working
2019 # not. Besides, filesets are evaluated against the working
2020 # directory.
2020 # directory.
2021 matchargs = ['r:', 'd:relpath']
2021 matchargs = ['r:', 'd:relpath']
2022 for p in pats:
2022 for p in pats:
2023 matchargs.append('p:' + p)
2023 matchargs.append('p:' + p)
2024 for p in opts.get('include', []):
2024 for p in opts.get('include', []):
2025 matchargs.append('i:' + p)
2025 matchargs.append('i:' + p)
2026 for p in opts.get('exclude', []):
2026 for p in opts.get('exclude', []):
2027 matchargs.append('x:' + p)
2027 matchargs.append('x:' + p)
2028 matchargs = ','.join(('%r' % p) for p in matchargs)
2028 matchargs = ','.join(('%r' % p) for p in matchargs)
2029 opts['_matchfiles'] = matchargs
2029 opts['_matchfiles'] = matchargs
2030 if follow:
2030 if follow:
2031 opts[fnopats[0][followfirst]] = '.'
2031 opts[fnopats[0][followfirst]] = '.'
2032 else:
2032 else:
2033 if follow:
2033 if follow:
2034 if pats:
2034 if pats:
2035 # follow() revset interprets its file argument as a
2035 # follow() revset interprets its file argument as a
2036 # manifest entry, so use match.files(), not pats.
2036 # manifest entry, so use match.files(), not pats.
2037 opts[fpats[followfirst]] = list(match.files())
2037 opts[fpats[followfirst]] = list(match.files())
2038 else:
2038 else:
2039 op = fnopats[followdescendants][followfirst]
2039 op = fnopats[followdescendants][followfirst]
2040 opts[op] = 'rev(%d)' % startrev
2040 opts[op] = 'rev(%d)' % startrev
2041 else:
2041 else:
2042 opts['_patslog'] = list(pats)
2042 opts['_patslog'] = list(pats)
2043
2043
2044 filematcher = None
2044 filematcher = None
2045 if opts.get('patch') or opts.get('stat'):
2045 if opts.get('patch') or opts.get('stat'):
2046 # When following files, track renames via a special matcher.
2046 # When following files, track renames via a special matcher.
2047 # If we're forced to take the slowpath it means we're following
2047 # If we're forced to take the slowpath it means we're following
2048 # at least one pattern/directory, so don't bother with rename tracking.
2048 # at least one pattern/directory, so don't bother with rename tracking.
2049 if follow and not match.always() and not slowpath:
2049 if follow and not match.always() and not slowpath:
2050 # _makefollowlogfilematcher expects its files argument to be
2050 # _makefollowlogfilematcher expects its files argument to be
2051 # relative to the repo root, so use match.files(), not pats.
2051 # relative to the repo root, so use match.files(), not pats.
2052 filematcher = _makefollowlogfilematcher(repo, match.files(),
2052 filematcher = _makefollowlogfilematcher(repo, match.files(),
2053 followfirst)
2053 followfirst)
2054 else:
2054 else:
2055 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2055 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2056 if filematcher is None:
2056 if filematcher is None:
2057 filematcher = lambda rev: match
2057 filematcher = lambda rev: match
2058
2058
2059 expr = []
2059 expr = []
2060 for op, val in sorted(opts.iteritems()):
2060 for op, val in sorted(opts.iteritems()):
2061 if not val:
2061 if not val:
2062 continue
2062 continue
2063 if op not in opt2revset:
2063 if op not in opt2revset:
2064 continue
2064 continue
2065 revop, andor = opt2revset[op]
2065 revop, andor = opt2revset[op]
2066 if '%(val)' not in revop:
2066 if '%(val)' not in revop:
2067 expr.append(revop)
2067 expr.append(revop)
2068 else:
2068 else:
2069 if not isinstance(val, list):
2069 if not isinstance(val, list):
2070 e = revop % {'val': val}
2070 e = revop % {'val': val}
2071 else:
2071 else:
2072 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2072 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2073 expr.append(e)
2073 expr.append(e)
2074
2074
2075 if expr:
2075 if expr:
2076 expr = '(' + ' and '.join(expr) + ')'
2076 expr = '(' + ' and '.join(expr) + ')'
2077 else:
2077 else:
2078 expr = None
2078 expr = None
2079 return expr, filematcher
2079 return expr, filematcher
2080
2080
2081 def _logrevs(repo, opts):
2081 def _logrevs(repo, opts):
2082 # Default --rev value depends on --follow but --follow behavior
2082 # Default --rev value depends on --follow but --follow behavior
2083 # depends on revisions resolved from --rev...
2083 # depends on revisions resolved from --rev...
2084 follow = opts.get('follow') or opts.get('follow_first')
2084 follow = opts.get('follow') or opts.get('follow_first')
2085 if opts.get('rev'):
2085 if opts.get('rev'):
2086 revs = scmutil.revrange(repo, opts['rev'])
2086 revs = scmutil.revrange(repo, opts['rev'])
2087 elif follow and repo.dirstate.p1() == nullid:
2087 elif follow and repo.dirstate.p1() == nullid:
2088 revs = smartset.baseset()
2088 revs = smartset.baseset()
2089 elif follow:
2089 elif follow:
2090 revs = repo.revs('reverse(:.)')
2090 revs = repo.revs('reverse(:.)')
2091 else:
2091 else:
2092 revs = smartset.spanset(repo)
2092 revs = smartset.spanset(repo)
2093 revs.reverse()
2093 revs.reverse()
2094 return revs
2094 return revs
2095
2095
2096 def getgraphlogrevs(repo, pats, opts):
2096 def getgraphlogrevs(repo, pats, opts):
2097 """Return (revs, expr, filematcher) where revs is an iterable of
2097 """Return (revs, expr, filematcher) where revs is an iterable of
2098 revision numbers, expr is a revset string built from log options
2098 revision numbers, expr is a revset string built from log options
2099 and file patterns or None, and used to filter 'revs'. If --stat or
2099 and file patterns or None, and used to filter 'revs'. If --stat or
2100 --patch are not passed filematcher is None. Otherwise it is a
2100 --patch are not passed filematcher is None. Otherwise it is a
2101 callable taking a revision number and returning a match objects
2101 callable taking a revision number and returning a match objects
2102 filtering the files to be detailed when displaying the revision.
2102 filtering the files to be detailed when displaying the revision.
2103 """
2103 """
2104 limit = loglimit(opts)
2104 limit = loglimit(opts)
2105 revs = _logrevs(repo, opts)
2105 revs = _logrevs(repo, opts)
2106 if not revs:
2106 if not revs:
2107 return smartset.baseset(), None, None
2107 return smartset.baseset(), None, None
2108 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2108 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2109 if opts.get('rev'):
2109 if opts.get('rev'):
2110 # User-specified revs might be unsorted, but don't sort before
2110 # User-specified revs might be unsorted, but don't sort before
2111 # _makelogrevset because it might depend on the order of revs
2111 # _makelogrevset because it might depend on the order of revs
2112 if not (revs.isdescending() or revs.istopo()):
2112 if not (revs.isdescending() or revs.istopo()):
2113 revs.sort(reverse=True)
2113 revs.sort(reverse=True)
2114 if expr:
2114 if expr:
2115 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2115 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2116 revs = matcher(repo, revs)
2116 revs = matcher(repo, revs)
2117 if limit is not None:
2117 if limit is not None:
2118 limitedrevs = []
2118 limitedrevs = []
2119 for idx, rev in enumerate(revs):
2119 for idx, rev in enumerate(revs):
2120 if idx >= limit:
2120 if idx >= limit:
2121 break
2121 break
2122 limitedrevs.append(rev)
2122 limitedrevs.append(rev)
2123 revs = smartset.baseset(limitedrevs)
2123 revs = smartset.baseset(limitedrevs)
2124
2124
2125 return revs, expr, filematcher
2125 return revs, expr, filematcher
2126
2126
2127 def getlogrevs(repo, pats, opts):
2127 def getlogrevs(repo, pats, opts):
2128 """Return (revs, expr, filematcher) where revs is an iterable of
2128 """Return (revs, expr, filematcher) where revs is an iterable of
2129 revision numbers, expr is a revset string built from log options
2129 revision numbers, expr is a revset string built from log options
2130 and file patterns or None, and used to filter 'revs'. If --stat or
2130 and file patterns or None, and used to filter 'revs'. If --stat or
2131 --patch are not passed filematcher is None. Otherwise it is a
2131 --patch are not passed filematcher is None. Otherwise it is a
2132 callable taking a revision number and returning a match objects
2132 callable taking a revision number and returning a match objects
2133 filtering the files to be detailed when displaying the revision.
2133 filtering the files to be detailed when displaying the revision.
2134 """
2134 """
2135 limit = loglimit(opts)
2135 limit = loglimit(opts)
2136 revs = _logrevs(repo, opts)
2136 revs = _logrevs(repo, opts)
2137 if not revs:
2137 if not revs:
2138 return smartset.baseset([]), None, None
2138 return smartset.baseset([]), None, None
2139 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2139 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2140 if expr:
2140 if expr:
2141 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2141 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2142 revs = matcher(repo, revs)
2142 revs = matcher(repo, revs)
2143 if limit is not None:
2143 if limit is not None:
2144 limitedrevs = []
2144 limitedrevs = []
2145 for idx, r in enumerate(revs):
2145 for idx, r in enumerate(revs):
2146 if limit <= idx:
2146 if limit <= idx:
2147 break
2147 break
2148 limitedrevs.append(r)
2148 limitedrevs.append(r)
2149 revs = smartset.baseset(limitedrevs)
2149 revs = smartset.baseset(limitedrevs)
2150
2150
2151 return revs, expr, filematcher
2151 return revs, expr, filematcher
2152
2152
2153 def _graphnodeformatter(ui, displayer):
2153 def _graphnodeformatter(ui, displayer):
2154 spec = ui.config('ui', 'graphnodetemplate')
2154 spec = ui.config('ui', 'graphnodetemplate')
2155 if not spec:
2155 if not spec:
2156 return templatekw.showgraphnode # fast path for "{graphnode}"
2156 return templatekw.showgraphnode # fast path for "{graphnode}"
2157
2157
2158 spec = templater.unquotestring(spec)
2158 spec = templater.unquotestring(spec)
2159 templ = formatter.gettemplater(ui, 'graphnode', spec)
2159 templ = formatter.gettemplater(ui, 'graphnode', spec)
2160 cache = {}
2160 cache = {}
2161 if isinstance(displayer, changeset_templater):
2161 if isinstance(displayer, changeset_templater):
2162 cache = displayer.cache # reuse cache of slow templates
2162 cache = displayer.cache # reuse cache of slow templates
2163 props = templatekw.keywords.copy()
2163 props = templatekw.keywords.copy()
2164 props['templ'] = templ
2164 props['templ'] = templ
2165 props['cache'] = cache
2165 props['cache'] = cache
2166 def formatnode(repo, ctx):
2166 def formatnode(repo, ctx):
2167 props['ctx'] = ctx
2167 props['ctx'] = ctx
2168 props['repo'] = repo
2168 props['repo'] = repo
2169 props['ui'] = repo.ui
2169 props['ui'] = repo.ui
2170 props['revcache'] = {}
2170 props['revcache'] = {}
2171 return templater.stringify(templ('graphnode', **props))
2171 return templater.stringify(templ('graphnode', **props))
2172 return formatnode
2172 return formatnode
2173
2173
2174 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2174 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2175 filematcher=None):
2175 filematcher=None):
2176 formatnode = _graphnodeformatter(ui, displayer)
2176 formatnode = _graphnodeformatter(ui, displayer)
2177 state = graphmod.asciistate()
2177 state = graphmod.asciistate()
2178 styles = state['styles']
2178 styles = state['styles']
2179
2179
2180 # only set graph styling if HGPLAIN is not set.
2180 # only set graph styling if HGPLAIN is not set.
2181 if ui.plain('graph'):
2181 if ui.plain('graph'):
2182 # set all edge styles to |, the default pre-3.8 behaviour
2182 # set all edge styles to |, the default pre-3.8 behaviour
2183 styles.update(dict.fromkeys(styles, '|'))
2183 styles.update(dict.fromkeys(styles, '|'))
2184 else:
2184 else:
2185 edgetypes = {
2185 edgetypes = {
2186 'parent': graphmod.PARENT,
2186 'parent': graphmod.PARENT,
2187 'grandparent': graphmod.GRANDPARENT,
2187 'grandparent': graphmod.GRANDPARENT,
2188 'missing': graphmod.MISSINGPARENT
2188 'missing': graphmod.MISSINGPARENT
2189 }
2189 }
2190 for name, key in edgetypes.items():
2190 for name, key in edgetypes.items():
2191 # experimental config: experimental.graphstyle.*
2191 # experimental config: experimental.graphstyle.*
2192 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2192 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2193 styles[key])
2193 styles[key])
2194 if not styles[key]:
2194 if not styles[key]:
2195 styles[key] = None
2195 styles[key] = None
2196
2196
2197 # experimental config: experimental.graphshorten
2197 # experimental config: experimental.graphshorten
2198 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2198 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2199
2199
2200 for rev, type, ctx, parents in dag:
2200 for rev, type, ctx, parents in dag:
2201 char = formatnode(repo, ctx)
2201 char = formatnode(repo, ctx)
2202 copies = None
2202 copies = None
2203 if getrenamed and ctx.rev():
2203 if getrenamed and ctx.rev():
2204 copies = []
2204 copies = []
2205 for fn in ctx.files():
2205 for fn in ctx.files():
2206 rename = getrenamed(fn, ctx.rev())
2206 rename = getrenamed(fn, ctx.rev())
2207 if rename:
2207 if rename:
2208 copies.append((fn, rename[0]))
2208 copies.append((fn, rename[0]))
2209 revmatchfn = None
2209 revmatchfn = None
2210 if filematcher is not None:
2210 if filematcher is not None:
2211 revmatchfn = filematcher(ctx.rev())
2211 revmatchfn = filematcher(ctx.rev())
2212 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2212 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2213 lines = displayer.hunk.pop(rev).split('\n')
2213 lines = displayer.hunk.pop(rev).split('\n')
2214 if not lines[-1]:
2214 if not lines[-1]:
2215 del lines[-1]
2215 del lines[-1]
2216 displayer.flush(ctx)
2216 displayer.flush(ctx)
2217 edges = edgefn(type, char, lines, state, rev, parents)
2217 edges = edgefn(type, char, lines, state, rev, parents)
2218 for type, char, lines, coldata in edges:
2218 for type, char, lines, coldata in edges:
2219 graphmod.ascii(ui, state, type, char, lines, coldata)
2219 graphmod.ascii(ui, state, type, char, lines, coldata)
2220 displayer.close()
2220 displayer.close()
2221
2221
2222 def graphlog(ui, repo, pats, opts):
2222 def graphlog(ui, repo, pats, opts):
2223 # Parameters are identical to log command ones
2223 # Parameters are identical to log command ones
2224 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2224 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2225 revdag = graphmod.dagwalker(repo, revs)
2225 revdag = graphmod.dagwalker(repo, revs)
2226
2226
2227 getrenamed = None
2227 getrenamed = None
2228 if opts.get('copies'):
2228 if opts.get('copies'):
2229 endrev = None
2229 endrev = None
2230 if opts.get('rev'):
2230 if opts.get('rev'):
2231 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2231 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2232 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2232 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2233
2233
2234 ui.pager('log')
2234 ui.pager('log')
2235 displayer = show_changeset(ui, repo, opts, buffered=True)
2235 displayer = show_changeset(ui, repo, opts, buffered=True)
2236 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2236 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2237 filematcher)
2237 filematcher)
2238
2238
2239 def checkunsupportedgraphflags(pats, opts):
2239 def checkunsupportedgraphflags(pats, opts):
2240 for op in ["newest_first"]:
2240 for op in ["newest_first"]:
2241 if op in opts and opts[op]:
2241 if op in opts and opts[op]:
2242 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2242 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2243 % op.replace("_", "-"))
2243 % op.replace("_", "-"))
2244
2244
2245 def graphrevs(repo, nodes, opts):
2245 def graphrevs(repo, nodes, opts):
2246 limit = loglimit(opts)
2246 limit = loglimit(opts)
2247 nodes.reverse()
2247 nodes.reverse()
2248 if limit is not None:
2248 if limit is not None:
2249 nodes = nodes[:limit]
2249 nodes = nodes[:limit]
2250 return graphmod.nodes(repo, nodes)
2250 return graphmod.nodes(repo, nodes)
2251
2251
2252 def add(ui, repo, match, prefix, explicitonly, **opts):
2252 def add(ui, repo, match, prefix, explicitonly, **opts):
2253 join = lambda f: os.path.join(prefix, f)
2253 join = lambda f: os.path.join(prefix, f)
2254 bad = []
2254 bad = []
2255
2255
2256 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2256 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2257 names = []
2257 names = []
2258 wctx = repo[None]
2258 wctx = repo[None]
2259 cca = None
2259 cca = None
2260 abort, warn = scmutil.checkportabilityalert(ui)
2260 abort, warn = scmutil.checkportabilityalert(ui)
2261 if abort or warn:
2261 if abort or warn:
2262 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2262 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2263
2263
2264 badmatch = matchmod.badmatch(match, badfn)
2264 badmatch = matchmod.badmatch(match, badfn)
2265 dirstate = repo.dirstate
2265 dirstate = repo.dirstate
2266 # We don't want to just call wctx.walk here, since it would return a lot of
2266 # We don't want to just call wctx.walk here, since it would return a lot of
2267 # clean files, which we aren't interested in and takes time.
2267 # clean files, which we aren't interested in and takes time.
2268 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2268 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2269 True, False, full=False)):
2269 True, False, full=False)):
2270 exact = match.exact(f)
2270 exact = match.exact(f)
2271 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2271 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2272 if cca:
2272 if cca:
2273 cca(f)
2273 cca(f)
2274 names.append(f)
2274 names.append(f)
2275 if ui.verbose or not exact:
2275 if ui.verbose or not exact:
2276 ui.status(_('adding %s\n') % match.rel(f))
2276 ui.status(_('adding %s\n') % match.rel(f))
2277
2277
2278 for subpath in sorted(wctx.substate):
2278 for subpath in sorted(wctx.substate):
2279 sub = wctx.sub(subpath)
2279 sub = wctx.sub(subpath)
2280 try:
2280 try:
2281 submatch = matchmod.subdirmatcher(subpath, match)
2281 submatch = matchmod.subdirmatcher(subpath, match)
2282 if opts.get('subrepos'):
2282 if opts.get(r'subrepos'):
2283 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2283 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2284 else:
2284 else:
2285 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2285 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2286 except error.LookupError:
2286 except error.LookupError:
2287 ui.status(_("skipping missing subrepository: %s\n")
2287 ui.status(_("skipping missing subrepository: %s\n")
2288 % join(subpath))
2288 % join(subpath))
2289
2289
2290 if not opts.get('dry_run'):
2290 if not opts.get(r'dry_run'):
2291 rejected = wctx.add(names, prefix)
2291 rejected = wctx.add(names, prefix)
2292 bad.extend(f for f in rejected if f in match.files())
2292 bad.extend(f for f in rejected if f in match.files())
2293 return bad
2293 return bad
2294
2294
2295 def addwebdirpath(repo, serverpath, webconf):
2295 def addwebdirpath(repo, serverpath, webconf):
2296 webconf[serverpath] = repo.root
2296 webconf[serverpath] = repo.root
2297 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2297 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2298
2298
2299 for r in repo.revs('filelog("path:.hgsub")'):
2299 for r in repo.revs('filelog("path:.hgsub")'):
2300 ctx = repo[r]
2300 ctx = repo[r]
2301 for subpath in ctx.substate:
2301 for subpath in ctx.substate:
2302 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2302 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2303
2303
2304 def forget(ui, repo, match, prefix, explicitonly):
2304 def forget(ui, repo, match, prefix, explicitonly):
2305 join = lambda f: os.path.join(prefix, f)
2305 join = lambda f: os.path.join(prefix, f)
2306 bad = []
2306 bad = []
2307 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2307 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2308 wctx = repo[None]
2308 wctx = repo[None]
2309 forgot = []
2309 forgot = []
2310
2310
2311 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2311 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2312 forget = sorted(s[0] + s[1] + s[3] + s[6])
2312 forget = sorted(s[0] + s[1] + s[3] + s[6])
2313 if explicitonly:
2313 if explicitonly:
2314 forget = [f for f in forget if match.exact(f)]
2314 forget = [f for f in forget if match.exact(f)]
2315
2315
2316 for subpath in sorted(wctx.substate):
2316 for subpath in sorted(wctx.substate):
2317 sub = wctx.sub(subpath)
2317 sub = wctx.sub(subpath)
2318 try:
2318 try:
2319 submatch = matchmod.subdirmatcher(subpath, match)
2319 submatch = matchmod.subdirmatcher(subpath, match)
2320 subbad, subforgot = sub.forget(submatch, prefix)
2320 subbad, subforgot = sub.forget(submatch, prefix)
2321 bad.extend([subpath + '/' + f for f in subbad])
2321 bad.extend([subpath + '/' + f for f in subbad])
2322 forgot.extend([subpath + '/' + f for f in subforgot])
2322 forgot.extend([subpath + '/' + f for f in subforgot])
2323 except error.LookupError:
2323 except error.LookupError:
2324 ui.status(_("skipping missing subrepository: %s\n")
2324 ui.status(_("skipping missing subrepository: %s\n")
2325 % join(subpath))
2325 % join(subpath))
2326
2326
2327 if not explicitonly:
2327 if not explicitonly:
2328 for f in match.files():
2328 for f in match.files():
2329 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2329 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2330 if f not in forgot:
2330 if f not in forgot:
2331 if repo.wvfs.exists(f):
2331 if repo.wvfs.exists(f):
2332 # Don't complain if the exact case match wasn't given.
2332 # Don't complain if the exact case match wasn't given.
2333 # But don't do this until after checking 'forgot', so
2333 # But don't do this until after checking 'forgot', so
2334 # that subrepo files aren't normalized, and this op is
2334 # that subrepo files aren't normalized, and this op is
2335 # purely from data cached by the status walk above.
2335 # purely from data cached by the status walk above.
2336 if repo.dirstate.normalize(f) in repo.dirstate:
2336 if repo.dirstate.normalize(f) in repo.dirstate:
2337 continue
2337 continue
2338 ui.warn(_('not removing %s: '
2338 ui.warn(_('not removing %s: '
2339 'file is already untracked\n')
2339 'file is already untracked\n')
2340 % match.rel(f))
2340 % match.rel(f))
2341 bad.append(f)
2341 bad.append(f)
2342
2342
2343 for f in forget:
2343 for f in forget:
2344 if ui.verbose or not match.exact(f):
2344 if ui.verbose or not match.exact(f):
2345 ui.status(_('removing %s\n') % match.rel(f))
2345 ui.status(_('removing %s\n') % match.rel(f))
2346
2346
2347 rejected = wctx.forget(forget, prefix)
2347 rejected = wctx.forget(forget, prefix)
2348 bad.extend(f for f in rejected if f in match.files())
2348 bad.extend(f for f in rejected if f in match.files())
2349 forgot.extend(f for f in forget if f not in rejected)
2349 forgot.extend(f for f in forget if f not in rejected)
2350 return bad, forgot
2350 return bad, forgot
2351
2351
2352 def files(ui, ctx, m, fm, fmt, subrepos):
2352 def files(ui, ctx, m, fm, fmt, subrepos):
2353 rev = ctx.rev()
2353 rev = ctx.rev()
2354 ret = 1
2354 ret = 1
2355 ds = ctx.repo().dirstate
2355 ds = ctx.repo().dirstate
2356
2356
2357 for f in ctx.matches(m):
2357 for f in ctx.matches(m):
2358 if rev is None and ds[f] == 'r':
2358 if rev is None and ds[f] == 'r':
2359 continue
2359 continue
2360 fm.startitem()
2360 fm.startitem()
2361 if ui.verbose:
2361 if ui.verbose:
2362 fc = ctx[f]
2362 fc = ctx[f]
2363 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2363 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2364 fm.data(abspath=f)
2364 fm.data(abspath=f)
2365 fm.write('path', fmt, m.rel(f))
2365 fm.write('path', fmt, m.rel(f))
2366 ret = 0
2366 ret = 0
2367
2367
2368 for subpath in sorted(ctx.substate):
2368 for subpath in sorted(ctx.substate):
2369 submatch = matchmod.subdirmatcher(subpath, m)
2369 submatch = matchmod.subdirmatcher(subpath, m)
2370 if (subrepos or m.exact(subpath) or any(submatch.files())):
2370 if (subrepos or m.exact(subpath) or any(submatch.files())):
2371 sub = ctx.sub(subpath)
2371 sub = ctx.sub(subpath)
2372 try:
2372 try:
2373 recurse = m.exact(subpath) or subrepos
2373 recurse = m.exact(subpath) or subrepos
2374 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2374 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2375 ret = 0
2375 ret = 0
2376 except error.LookupError:
2376 except error.LookupError:
2377 ui.status(_("skipping missing subrepository: %s\n")
2377 ui.status(_("skipping missing subrepository: %s\n")
2378 % m.abs(subpath))
2378 % m.abs(subpath))
2379
2379
2380 return ret
2380 return ret
2381
2381
2382 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2382 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2383 join = lambda f: os.path.join(prefix, f)
2383 join = lambda f: os.path.join(prefix, f)
2384 ret = 0
2384 ret = 0
2385 s = repo.status(match=m, clean=True)
2385 s = repo.status(match=m, clean=True)
2386 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2386 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2387
2387
2388 wctx = repo[None]
2388 wctx = repo[None]
2389
2389
2390 if warnings is None:
2390 if warnings is None:
2391 warnings = []
2391 warnings = []
2392 warn = True
2392 warn = True
2393 else:
2393 else:
2394 warn = False
2394 warn = False
2395
2395
2396 subs = sorted(wctx.substate)
2396 subs = sorted(wctx.substate)
2397 total = len(subs)
2397 total = len(subs)
2398 count = 0
2398 count = 0
2399 for subpath in subs:
2399 for subpath in subs:
2400 count += 1
2400 count += 1
2401 submatch = matchmod.subdirmatcher(subpath, m)
2401 submatch = matchmod.subdirmatcher(subpath, m)
2402 if subrepos or m.exact(subpath) or any(submatch.files()):
2402 if subrepos or m.exact(subpath) or any(submatch.files()):
2403 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2403 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2404 sub = wctx.sub(subpath)
2404 sub = wctx.sub(subpath)
2405 try:
2405 try:
2406 if sub.removefiles(submatch, prefix, after, force, subrepos,
2406 if sub.removefiles(submatch, prefix, after, force, subrepos,
2407 warnings):
2407 warnings):
2408 ret = 1
2408 ret = 1
2409 except error.LookupError:
2409 except error.LookupError:
2410 warnings.append(_("skipping missing subrepository: %s\n")
2410 warnings.append(_("skipping missing subrepository: %s\n")
2411 % join(subpath))
2411 % join(subpath))
2412 ui.progress(_('searching'), None)
2412 ui.progress(_('searching'), None)
2413
2413
2414 # warn about failure to delete explicit files/dirs
2414 # warn about failure to delete explicit files/dirs
2415 deleteddirs = util.dirs(deleted)
2415 deleteddirs = util.dirs(deleted)
2416 files = m.files()
2416 files = m.files()
2417 total = len(files)
2417 total = len(files)
2418 count = 0
2418 count = 0
2419 for f in files:
2419 for f in files:
2420 def insubrepo():
2420 def insubrepo():
2421 for subpath in wctx.substate:
2421 for subpath in wctx.substate:
2422 if f.startswith(subpath + '/'):
2422 if f.startswith(subpath + '/'):
2423 return True
2423 return True
2424 return False
2424 return False
2425
2425
2426 count += 1
2426 count += 1
2427 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2427 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2428 isdir = f in deleteddirs or wctx.hasdir(f)
2428 isdir = f in deleteddirs or wctx.hasdir(f)
2429 if (f in repo.dirstate or isdir or f == '.'
2429 if (f in repo.dirstate or isdir or f == '.'
2430 or insubrepo() or f in subs):
2430 or insubrepo() or f in subs):
2431 continue
2431 continue
2432
2432
2433 if repo.wvfs.exists(f):
2433 if repo.wvfs.exists(f):
2434 if repo.wvfs.isdir(f):
2434 if repo.wvfs.isdir(f):
2435 warnings.append(_('not removing %s: no tracked files\n')
2435 warnings.append(_('not removing %s: no tracked files\n')
2436 % m.rel(f))
2436 % m.rel(f))
2437 else:
2437 else:
2438 warnings.append(_('not removing %s: file is untracked\n')
2438 warnings.append(_('not removing %s: file is untracked\n')
2439 % m.rel(f))
2439 % m.rel(f))
2440 # missing files will generate a warning elsewhere
2440 # missing files will generate a warning elsewhere
2441 ret = 1
2441 ret = 1
2442 ui.progress(_('deleting'), None)
2442 ui.progress(_('deleting'), None)
2443
2443
2444 if force:
2444 if force:
2445 list = modified + deleted + clean + added
2445 list = modified + deleted + clean + added
2446 elif after:
2446 elif after:
2447 list = deleted
2447 list = deleted
2448 remaining = modified + added + clean
2448 remaining = modified + added + clean
2449 total = len(remaining)
2449 total = len(remaining)
2450 count = 0
2450 count = 0
2451 for f in remaining:
2451 for f in remaining:
2452 count += 1
2452 count += 1
2453 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2453 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2454 warnings.append(_('not removing %s: file still exists\n')
2454 warnings.append(_('not removing %s: file still exists\n')
2455 % m.rel(f))
2455 % m.rel(f))
2456 ret = 1
2456 ret = 1
2457 ui.progress(_('skipping'), None)
2457 ui.progress(_('skipping'), None)
2458 else:
2458 else:
2459 list = deleted + clean
2459 list = deleted + clean
2460 total = len(modified) + len(added)
2460 total = len(modified) + len(added)
2461 count = 0
2461 count = 0
2462 for f in modified:
2462 for f in modified:
2463 count += 1
2463 count += 1
2464 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2464 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2465 warnings.append(_('not removing %s: file is modified (use -f'
2465 warnings.append(_('not removing %s: file is modified (use -f'
2466 ' to force removal)\n') % m.rel(f))
2466 ' to force removal)\n') % m.rel(f))
2467 ret = 1
2467 ret = 1
2468 for f in added:
2468 for f in added:
2469 count += 1
2469 count += 1
2470 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2470 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2471 warnings.append(_("not removing %s: file has been marked for add"
2471 warnings.append(_("not removing %s: file has been marked for add"
2472 " (use 'hg forget' to undo add)\n") % m.rel(f))
2472 " (use 'hg forget' to undo add)\n") % m.rel(f))
2473 ret = 1
2473 ret = 1
2474 ui.progress(_('skipping'), None)
2474 ui.progress(_('skipping'), None)
2475
2475
2476 list = sorted(list)
2476 list = sorted(list)
2477 total = len(list)
2477 total = len(list)
2478 count = 0
2478 count = 0
2479 for f in list:
2479 for f in list:
2480 count += 1
2480 count += 1
2481 if ui.verbose or not m.exact(f):
2481 if ui.verbose or not m.exact(f):
2482 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2482 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2483 ui.status(_('removing %s\n') % m.rel(f))
2483 ui.status(_('removing %s\n') % m.rel(f))
2484 ui.progress(_('deleting'), None)
2484 ui.progress(_('deleting'), None)
2485
2485
2486 with repo.wlock():
2486 with repo.wlock():
2487 if not after:
2487 if not after:
2488 for f in list:
2488 for f in list:
2489 if f in added:
2489 if f in added:
2490 continue # we never unlink added files on remove
2490 continue # we never unlink added files on remove
2491 repo.wvfs.unlinkpath(f, ignoremissing=True)
2491 repo.wvfs.unlinkpath(f, ignoremissing=True)
2492 repo[None].forget(list)
2492 repo[None].forget(list)
2493
2493
2494 if warn:
2494 if warn:
2495 for warning in warnings:
2495 for warning in warnings:
2496 ui.warn(warning)
2496 ui.warn(warning)
2497
2497
2498 return ret
2498 return ret
2499
2499
2500 def cat(ui, repo, ctx, matcher, prefix, **opts):
2500 def cat(ui, repo, ctx, matcher, prefix, **opts):
2501 err = 1
2501 err = 1
2502
2502
2503 def write(path):
2503 def write(path):
2504 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2504 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2505 pathname=os.path.join(prefix, path))
2505 pathname=os.path.join(prefix, path))
2506 data = ctx[path].data()
2506 data = ctx[path].data()
2507 if opts.get('decode'):
2507 if opts.get('decode'):
2508 data = repo.wwritedata(path, data)
2508 data = repo.wwritedata(path, data)
2509 fp.write(data)
2509 fp.write(data)
2510 fp.close()
2510 fp.close()
2511
2511
2512 # Automation often uses hg cat on single files, so special case it
2512 # Automation often uses hg cat on single files, so special case it
2513 # for performance to avoid the cost of parsing the manifest.
2513 # for performance to avoid the cost of parsing the manifest.
2514 if len(matcher.files()) == 1 and not matcher.anypats():
2514 if len(matcher.files()) == 1 and not matcher.anypats():
2515 file = matcher.files()[0]
2515 file = matcher.files()[0]
2516 mfl = repo.manifestlog
2516 mfl = repo.manifestlog
2517 mfnode = ctx.manifestnode()
2517 mfnode = ctx.manifestnode()
2518 try:
2518 try:
2519 if mfnode and mfl[mfnode].find(file)[0]:
2519 if mfnode and mfl[mfnode].find(file)[0]:
2520 write(file)
2520 write(file)
2521 return 0
2521 return 0
2522 except KeyError:
2522 except KeyError:
2523 pass
2523 pass
2524
2524
2525 for abs in ctx.walk(matcher):
2525 for abs in ctx.walk(matcher):
2526 write(abs)
2526 write(abs)
2527 err = 0
2527 err = 0
2528
2528
2529 for subpath in sorted(ctx.substate):
2529 for subpath in sorted(ctx.substate):
2530 sub = ctx.sub(subpath)
2530 sub = ctx.sub(subpath)
2531 try:
2531 try:
2532 submatch = matchmod.subdirmatcher(subpath, matcher)
2532 submatch = matchmod.subdirmatcher(subpath, matcher)
2533
2533
2534 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2534 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2535 **opts):
2535 **opts):
2536 err = 0
2536 err = 0
2537 except error.RepoLookupError:
2537 except error.RepoLookupError:
2538 ui.status(_("skipping missing subrepository: %s\n")
2538 ui.status(_("skipping missing subrepository: %s\n")
2539 % os.path.join(prefix, subpath))
2539 % os.path.join(prefix, subpath))
2540
2540
2541 return err
2541 return err
2542
2542
2543 def commit(ui, repo, commitfunc, pats, opts):
2543 def commit(ui, repo, commitfunc, pats, opts):
2544 '''commit the specified files or all outstanding changes'''
2544 '''commit the specified files or all outstanding changes'''
2545 date = opts.get('date')
2545 date = opts.get('date')
2546 if date:
2546 if date:
2547 opts['date'] = util.parsedate(date)
2547 opts['date'] = util.parsedate(date)
2548 message = logmessage(ui, opts)
2548 message = logmessage(ui, opts)
2549 matcher = scmutil.match(repo[None], pats, opts)
2549 matcher = scmutil.match(repo[None], pats, opts)
2550
2550
2551 # extract addremove carefully -- this function can be called from a command
2551 # extract addremove carefully -- this function can be called from a command
2552 # that doesn't support addremove
2552 # that doesn't support addremove
2553 if opts.get('addremove'):
2553 if opts.get('addremove'):
2554 if scmutil.addremove(repo, matcher, "", opts) != 0:
2554 if scmutil.addremove(repo, matcher, "", opts) != 0:
2555 raise error.Abort(
2555 raise error.Abort(
2556 _("failed to mark all new/missing files as added/removed"))
2556 _("failed to mark all new/missing files as added/removed"))
2557
2557
2558 return commitfunc(ui, repo, message, matcher, opts)
2558 return commitfunc(ui, repo, message, matcher, opts)
2559
2559
2560 def samefile(f, ctx1, ctx2):
2560 def samefile(f, ctx1, ctx2):
2561 if f in ctx1.manifest():
2561 if f in ctx1.manifest():
2562 a = ctx1.filectx(f)
2562 a = ctx1.filectx(f)
2563 if f in ctx2.manifest():
2563 if f in ctx2.manifest():
2564 b = ctx2.filectx(f)
2564 b = ctx2.filectx(f)
2565 return (not a.cmp(b)
2565 return (not a.cmp(b)
2566 and a.flags() == b.flags())
2566 and a.flags() == b.flags())
2567 else:
2567 else:
2568 return False
2568 return False
2569 else:
2569 else:
2570 return f not in ctx2.manifest()
2570 return f not in ctx2.manifest()
2571
2571
2572 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2572 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2573 # avoid cycle context -> subrepo -> cmdutil
2573 # avoid cycle context -> subrepo -> cmdutil
2574 from . import context
2574 from . import context
2575
2575
2576 # amend will reuse the existing user if not specified, but the obsolete
2576 # amend will reuse the existing user if not specified, but the obsolete
2577 # marker creation requires that the current user's name is specified.
2577 # marker creation requires that the current user's name is specified.
2578 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2578 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2579 ui.username() # raise exception if username not set
2579 ui.username() # raise exception if username not set
2580
2580
2581 ui.note(_('amending changeset %s\n') % old)
2581 ui.note(_('amending changeset %s\n') % old)
2582 base = old.p1()
2582 base = old.p1()
2583 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2583 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2584
2584
2585 wlock = lock = newid = None
2585 wlock = lock = newid = None
2586 try:
2586 try:
2587 wlock = repo.wlock()
2587 wlock = repo.wlock()
2588 lock = repo.lock()
2588 lock = repo.lock()
2589 with repo.transaction('amend') as tr:
2589 with repo.transaction('amend') as tr:
2590 # See if we got a message from -m or -l, if not, open the editor
2590 # See if we got a message from -m or -l, if not, open the editor
2591 # with the message of the changeset to amend
2591 # with the message of the changeset to amend
2592 message = logmessage(ui, opts)
2592 message = logmessage(ui, opts)
2593 # ensure logfile does not conflict with later enforcement of the
2593 # ensure logfile does not conflict with later enforcement of the
2594 # message. potential logfile content has been processed by
2594 # message. potential logfile content has been processed by
2595 # `logmessage` anyway.
2595 # `logmessage` anyway.
2596 opts.pop('logfile')
2596 opts.pop('logfile')
2597 # First, do a regular commit to record all changes in the working
2597 # First, do a regular commit to record all changes in the working
2598 # directory (if there are any)
2598 # directory (if there are any)
2599 ui.callhooks = False
2599 ui.callhooks = False
2600 activebookmark = repo._bookmarks.active
2600 activebookmark = repo._bookmarks.active
2601 try:
2601 try:
2602 repo._bookmarks.active = None
2602 repo._bookmarks.active = None
2603 opts['message'] = 'temporary amend commit for %s' % old
2603 opts['message'] = 'temporary amend commit for %s' % old
2604 node = commit(ui, repo, commitfunc, pats, opts)
2604 node = commit(ui, repo, commitfunc, pats, opts)
2605 finally:
2605 finally:
2606 repo._bookmarks.active = activebookmark
2606 repo._bookmarks.active = activebookmark
2607 repo._bookmarks.recordchange(tr)
2607 repo._bookmarks.recordchange(tr)
2608 ui.callhooks = True
2608 ui.callhooks = True
2609 ctx = repo[node]
2609 ctx = repo[node]
2610
2610
2611 # Participating changesets:
2611 # Participating changesets:
2612 #
2612 #
2613 # node/ctx o - new (intermediate) commit that contains changes
2613 # node/ctx o - new (intermediate) commit that contains changes
2614 # | from working dir to go into amending commit
2614 # | from working dir to go into amending commit
2615 # | (or a workingctx if there were no changes)
2615 # | (or a workingctx if there were no changes)
2616 # |
2616 # |
2617 # old o - changeset to amend
2617 # old o - changeset to amend
2618 # |
2618 # |
2619 # base o - parent of amending changeset
2619 # base o - parent of amending changeset
2620
2620
2621 # Update extra dict from amended commit (e.g. to preserve graft
2621 # Update extra dict from amended commit (e.g. to preserve graft
2622 # source)
2622 # source)
2623 extra.update(old.extra())
2623 extra.update(old.extra())
2624
2624
2625 # Also update it from the intermediate commit or from the wctx
2625 # Also update it from the intermediate commit or from the wctx
2626 extra.update(ctx.extra())
2626 extra.update(ctx.extra())
2627
2627
2628 if len(old.parents()) > 1:
2628 if len(old.parents()) > 1:
2629 # ctx.files() isn't reliable for merges, so fall back to the
2629 # ctx.files() isn't reliable for merges, so fall back to the
2630 # slower repo.status() method
2630 # slower repo.status() method
2631 files = set([fn for st in repo.status(base, old)[:3]
2631 files = set([fn for st in repo.status(base, old)[:3]
2632 for fn in st])
2632 for fn in st])
2633 else:
2633 else:
2634 files = set(old.files())
2634 files = set(old.files())
2635
2635
2636 # Second, we use either the commit we just did, or if there were no
2636 # Second, we use either the commit we just did, or if there were no
2637 # changes the parent of the working directory as the version of the
2637 # changes the parent of the working directory as the version of the
2638 # files in the final amend commit
2638 # files in the final amend commit
2639 if node:
2639 if node:
2640 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2640 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2641
2641
2642 user = ctx.user()
2642 user = ctx.user()
2643 date = ctx.date()
2643 date = ctx.date()
2644 # Recompute copies (avoid recording a -> b -> a)
2644 # Recompute copies (avoid recording a -> b -> a)
2645 copied = copies.pathcopies(base, ctx)
2645 copied = copies.pathcopies(base, ctx)
2646 if old.p2:
2646 if old.p2:
2647 copied.update(copies.pathcopies(old.p2(), ctx))
2647 copied.update(copies.pathcopies(old.p2(), ctx))
2648
2648
2649 # Prune files which were reverted by the updates: if old
2649 # Prune files which were reverted by the updates: if old
2650 # introduced file X and our intermediate commit, node,
2650 # introduced file X and our intermediate commit, node,
2651 # renamed that file, then those two files are the same and
2651 # renamed that file, then those two files are the same and
2652 # we can discard X from our list of files. Likewise if X
2652 # we can discard X from our list of files. Likewise if X
2653 # was deleted, it's no longer relevant
2653 # was deleted, it's no longer relevant
2654 files.update(ctx.files())
2654 files.update(ctx.files())
2655 files = [f for f in files if not samefile(f, ctx, base)]
2655 files = [f for f in files if not samefile(f, ctx, base)]
2656
2656
2657 def filectxfn(repo, ctx_, path):
2657 def filectxfn(repo, ctx_, path):
2658 try:
2658 try:
2659 fctx = ctx[path]
2659 fctx = ctx[path]
2660 flags = fctx.flags()
2660 flags = fctx.flags()
2661 mctx = context.memfilectx(repo,
2661 mctx = context.memfilectx(repo,
2662 fctx.path(), fctx.data(),
2662 fctx.path(), fctx.data(),
2663 islink='l' in flags,
2663 islink='l' in flags,
2664 isexec='x' in flags,
2664 isexec='x' in flags,
2665 copied=copied.get(path))
2665 copied=copied.get(path))
2666 return mctx
2666 return mctx
2667 except KeyError:
2667 except KeyError:
2668 return None
2668 return None
2669 else:
2669 else:
2670 ui.note(_('copying changeset %s to %s\n') % (old, base))
2670 ui.note(_('copying changeset %s to %s\n') % (old, base))
2671
2671
2672 # Use version of files as in the old cset
2672 # Use version of files as in the old cset
2673 def filectxfn(repo, ctx_, path):
2673 def filectxfn(repo, ctx_, path):
2674 try:
2674 try:
2675 return old.filectx(path)
2675 return old.filectx(path)
2676 except KeyError:
2676 except KeyError:
2677 return None
2677 return None
2678
2678
2679 user = opts.get('user') or old.user()
2679 user = opts.get('user') or old.user()
2680 date = opts.get('date') or old.date()
2680 date = opts.get('date') or old.date()
2681 editform = mergeeditform(old, 'commit.amend')
2681 editform = mergeeditform(old, 'commit.amend')
2682 editor = getcommiteditor(editform=editform, **opts)
2682 editor = getcommiteditor(editform=editform, **opts)
2683 if not message:
2683 if not message:
2684 editor = getcommiteditor(edit=True, editform=editform)
2684 editor = getcommiteditor(edit=True, editform=editform)
2685 message = old.description()
2685 message = old.description()
2686
2686
2687 pureextra = extra.copy()
2687 pureextra = extra.copy()
2688 extra['amend_source'] = old.hex()
2688 extra['amend_source'] = old.hex()
2689
2689
2690 new = context.memctx(repo,
2690 new = context.memctx(repo,
2691 parents=[base.node(), old.p2().node()],
2691 parents=[base.node(), old.p2().node()],
2692 text=message,
2692 text=message,
2693 files=files,
2693 files=files,
2694 filectxfn=filectxfn,
2694 filectxfn=filectxfn,
2695 user=user,
2695 user=user,
2696 date=date,
2696 date=date,
2697 extra=extra,
2697 extra=extra,
2698 editor=editor)
2698 editor=editor)
2699
2699
2700 newdesc = changelog.stripdesc(new.description())
2700 newdesc = changelog.stripdesc(new.description())
2701 if ((not node)
2701 if ((not node)
2702 and newdesc == old.description()
2702 and newdesc == old.description()
2703 and user == old.user()
2703 and user == old.user()
2704 and date == old.date()
2704 and date == old.date()
2705 and pureextra == old.extra()):
2705 and pureextra == old.extra()):
2706 # nothing changed. continuing here would create a new node
2706 # nothing changed. continuing here would create a new node
2707 # anyway because of the amend_source noise.
2707 # anyway because of the amend_source noise.
2708 #
2708 #
2709 # This not what we expect from amend.
2709 # This not what we expect from amend.
2710 return old.node()
2710 return old.node()
2711
2711
2712 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2712 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2713 try:
2713 try:
2714 if opts.get('secret'):
2714 if opts.get('secret'):
2715 commitphase = 'secret'
2715 commitphase = 'secret'
2716 else:
2716 else:
2717 commitphase = old.phase()
2717 commitphase = old.phase()
2718 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2718 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2719 newid = repo.commitctx(new)
2719 newid = repo.commitctx(new)
2720 finally:
2720 finally:
2721 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2721 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2722 if newid != old.node():
2722 if newid != old.node():
2723 # Reroute the working copy parent to the new changeset
2723 # Reroute the working copy parent to the new changeset
2724 repo.setparents(newid, nullid)
2724 repo.setparents(newid, nullid)
2725
2725
2726 # Move bookmarks from old parent to amend commit
2726 # Move bookmarks from old parent to amend commit
2727 bms = repo.nodebookmarks(old.node())
2727 bms = repo.nodebookmarks(old.node())
2728 if bms:
2728 if bms:
2729 marks = repo._bookmarks
2729 marks = repo._bookmarks
2730 for bm in bms:
2730 for bm in bms:
2731 ui.debug('moving bookmarks %r from %s to %s\n' %
2731 ui.debug('moving bookmarks %r from %s to %s\n' %
2732 (marks, old.hex(), hex(newid)))
2732 (marks, old.hex(), hex(newid)))
2733 marks[bm] = newid
2733 marks[bm] = newid
2734 marks.recordchange(tr)
2734 marks.recordchange(tr)
2735 #commit the whole amend process
2735 #commit the whole amend process
2736 if createmarkers:
2736 if createmarkers:
2737 # mark the new changeset as successor of the rewritten one
2737 # mark the new changeset as successor of the rewritten one
2738 new = repo[newid]
2738 new = repo[newid]
2739 obs = [(old, (new,))]
2739 obs = [(old, (new,))]
2740 if node:
2740 if node:
2741 obs.append((ctx, ()))
2741 obs.append((ctx, ()))
2742
2742
2743 obsolete.createmarkers(repo, obs)
2743 obsolete.createmarkers(repo, obs)
2744 if not createmarkers and newid != old.node():
2744 if not createmarkers and newid != old.node():
2745 # Strip the intermediate commit (if there was one) and the amended
2745 # Strip the intermediate commit (if there was one) and the amended
2746 # commit
2746 # commit
2747 if node:
2747 if node:
2748 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2748 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2749 ui.note(_('stripping amended changeset %s\n') % old)
2749 ui.note(_('stripping amended changeset %s\n') % old)
2750 repair.strip(ui, repo, old.node(), topic='amend-backup')
2750 repair.strip(ui, repo, old.node(), topic='amend-backup')
2751 finally:
2751 finally:
2752 lockmod.release(lock, wlock)
2752 lockmod.release(lock, wlock)
2753 return newid
2753 return newid
2754
2754
2755 def commiteditor(repo, ctx, subs, editform=''):
2755 def commiteditor(repo, ctx, subs, editform=''):
2756 if ctx.description():
2756 if ctx.description():
2757 return ctx.description()
2757 return ctx.description()
2758 return commitforceeditor(repo, ctx, subs, editform=editform,
2758 return commitforceeditor(repo, ctx, subs, editform=editform,
2759 unchangedmessagedetection=True)
2759 unchangedmessagedetection=True)
2760
2760
2761 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2761 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2762 editform='', unchangedmessagedetection=False):
2762 editform='', unchangedmessagedetection=False):
2763 if not extramsg:
2763 if not extramsg:
2764 extramsg = _("Leave message empty to abort commit.")
2764 extramsg = _("Leave message empty to abort commit.")
2765
2765
2766 forms = [e for e in editform.split('.') if e]
2766 forms = [e for e in editform.split('.') if e]
2767 forms.insert(0, 'changeset')
2767 forms.insert(0, 'changeset')
2768 templatetext = None
2768 templatetext = None
2769 while forms:
2769 while forms:
2770 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2770 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2771 if tmpl:
2771 if tmpl:
2772 tmpl = templater.unquotestring(tmpl)
2772 tmpl = templater.unquotestring(tmpl)
2773 templatetext = committext = buildcommittemplate(
2773 templatetext = committext = buildcommittemplate(
2774 repo, ctx, subs, extramsg, tmpl)
2774 repo, ctx, subs, extramsg, tmpl)
2775 break
2775 break
2776 forms.pop()
2776 forms.pop()
2777 else:
2777 else:
2778 committext = buildcommittext(repo, ctx, subs, extramsg)
2778 committext = buildcommittext(repo, ctx, subs, extramsg)
2779
2779
2780 # run editor in the repository root
2780 # run editor in the repository root
2781 olddir = pycompat.getcwd()
2781 olddir = pycompat.getcwd()
2782 os.chdir(repo.root)
2782 os.chdir(repo.root)
2783
2783
2784 # make in-memory changes visible to external process
2784 # make in-memory changes visible to external process
2785 tr = repo.currenttransaction()
2785 tr = repo.currenttransaction()
2786 repo.dirstate.write(tr)
2786 repo.dirstate.write(tr)
2787 pending = tr and tr.writepending() and repo.root
2787 pending = tr and tr.writepending() and repo.root
2788
2788
2789 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2789 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2790 editform=editform, pending=pending,
2790 editform=editform, pending=pending,
2791 repopath=repo.path)
2791 repopath=repo.path)
2792 text = editortext
2792 text = editortext
2793
2793
2794 # strip away anything below this special string (used for editors that want
2794 # strip away anything below this special string (used for editors that want
2795 # to display the diff)
2795 # to display the diff)
2796 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2796 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2797 if stripbelow:
2797 if stripbelow:
2798 text = text[:stripbelow.start()]
2798 text = text[:stripbelow.start()]
2799
2799
2800 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2800 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2801 os.chdir(olddir)
2801 os.chdir(olddir)
2802
2802
2803 if finishdesc:
2803 if finishdesc:
2804 text = finishdesc(text)
2804 text = finishdesc(text)
2805 if not text.strip():
2805 if not text.strip():
2806 raise error.Abort(_("empty commit message"))
2806 raise error.Abort(_("empty commit message"))
2807 if unchangedmessagedetection and editortext == templatetext:
2807 if unchangedmessagedetection and editortext == templatetext:
2808 raise error.Abort(_("commit message unchanged"))
2808 raise error.Abort(_("commit message unchanged"))
2809
2809
2810 return text
2810 return text
2811
2811
2812 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2812 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2813 ui = repo.ui
2813 ui = repo.ui
2814 tmpl, mapfile = gettemplate(ui, tmpl, None)
2814 tmpl, mapfile = gettemplate(ui, tmpl, None)
2815
2815
2816 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2816 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2817
2817
2818 for k, v in repo.ui.configitems('committemplate'):
2818 for k, v in repo.ui.configitems('committemplate'):
2819 if k != 'changeset':
2819 if k != 'changeset':
2820 t.t.cache[k] = v
2820 t.t.cache[k] = v
2821
2821
2822 if not extramsg:
2822 if not extramsg:
2823 extramsg = '' # ensure that extramsg is string
2823 extramsg = '' # ensure that extramsg is string
2824
2824
2825 ui.pushbuffer()
2825 ui.pushbuffer()
2826 t.show(ctx, extramsg=extramsg)
2826 t.show(ctx, extramsg=extramsg)
2827 return ui.popbuffer()
2827 return ui.popbuffer()
2828
2828
2829 def hgprefix(msg):
2829 def hgprefix(msg):
2830 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2830 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2831
2831
2832 def buildcommittext(repo, ctx, subs, extramsg):
2832 def buildcommittext(repo, ctx, subs, extramsg):
2833 edittext = []
2833 edittext = []
2834 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2834 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2835 if ctx.description():
2835 if ctx.description():
2836 edittext.append(ctx.description())
2836 edittext.append(ctx.description())
2837 edittext.append("")
2837 edittext.append("")
2838 edittext.append("") # Empty line between message and comments.
2838 edittext.append("") # Empty line between message and comments.
2839 edittext.append(hgprefix(_("Enter commit message."
2839 edittext.append(hgprefix(_("Enter commit message."
2840 " Lines beginning with 'HG:' are removed.")))
2840 " Lines beginning with 'HG:' are removed.")))
2841 edittext.append(hgprefix(extramsg))
2841 edittext.append(hgprefix(extramsg))
2842 edittext.append("HG: --")
2842 edittext.append("HG: --")
2843 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2843 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2844 if ctx.p2():
2844 if ctx.p2():
2845 edittext.append(hgprefix(_("branch merge")))
2845 edittext.append(hgprefix(_("branch merge")))
2846 if ctx.branch():
2846 if ctx.branch():
2847 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2847 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2848 if bookmarks.isactivewdirparent(repo):
2848 if bookmarks.isactivewdirparent(repo):
2849 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2849 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2850 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2850 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2851 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2851 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2852 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2852 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2853 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2853 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2854 if not added and not modified and not removed:
2854 if not added and not modified and not removed:
2855 edittext.append(hgprefix(_("no files changed")))
2855 edittext.append(hgprefix(_("no files changed")))
2856 edittext.append("")
2856 edittext.append("")
2857
2857
2858 return "\n".join(edittext)
2858 return "\n".join(edittext)
2859
2859
2860 def commitstatus(repo, node, branch, bheads=None, opts=None):
2860 def commitstatus(repo, node, branch, bheads=None, opts=None):
2861 if opts is None:
2861 if opts is None:
2862 opts = {}
2862 opts = {}
2863 ctx = repo[node]
2863 ctx = repo[node]
2864 parents = ctx.parents()
2864 parents = ctx.parents()
2865
2865
2866 if (not opts.get('amend') and bheads and node not in bheads and not
2866 if (not opts.get('amend') and bheads and node not in bheads and not
2867 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2867 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2868 repo.ui.status(_('created new head\n'))
2868 repo.ui.status(_('created new head\n'))
2869 # The message is not printed for initial roots. For the other
2869 # The message is not printed for initial roots. For the other
2870 # changesets, it is printed in the following situations:
2870 # changesets, it is printed in the following situations:
2871 #
2871 #
2872 # Par column: for the 2 parents with ...
2872 # Par column: for the 2 parents with ...
2873 # N: null or no parent
2873 # N: null or no parent
2874 # B: parent is on another named branch
2874 # B: parent is on another named branch
2875 # C: parent is a regular non head changeset
2875 # C: parent is a regular non head changeset
2876 # H: parent was a branch head of the current branch
2876 # H: parent was a branch head of the current branch
2877 # Msg column: whether we print "created new head" message
2877 # Msg column: whether we print "created new head" message
2878 # In the following, it is assumed that there already exists some
2878 # In the following, it is assumed that there already exists some
2879 # initial branch heads of the current branch, otherwise nothing is
2879 # initial branch heads of the current branch, otherwise nothing is
2880 # printed anyway.
2880 # printed anyway.
2881 #
2881 #
2882 # Par Msg Comment
2882 # Par Msg Comment
2883 # N N y additional topo root
2883 # N N y additional topo root
2884 #
2884 #
2885 # B N y additional branch root
2885 # B N y additional branch root
2886 # C N y additional topo head
2886 # C N y additional topo head
2887 # H N n usual case
2887 # H N n usual case
2888 #
2888 #
2889 # B B y weird additional branch root
2889 # B B y weird additional branch root
2890 # C B y branch merge
2890 # C B y branch merge
2891 # H B n merge with named branch
2891 # H B n merge with named branch
2892 #
2892 #
2893 # C C y additional head from merge
2893 # C C y additional head from merge
2894 # C H n merge with a head
2894 # C H n merge with a head
2895 #
2895 #
2896 # H H n head merge: head count decreases
2896 # H H n head merge: head count decreases
2897
2897
2898 if not opts.get('close_branch'):
2898 if not opts.get('close_branch'):
2899 for r in parents:
2899 for r in parents:
2900 if r.closesbranch() and r.branch() == branch:
2900 if r.closesbranch() and r.branch() == branch:
2901 repo.ui.status(_('reopening closed branch head %d\n') % r)
2901 repo.ui.status(_('reopening closed branch head %d\n') % r)
2902
2902
2903 if repo.ui.debugflag:
2903 if repo.ui.debugflag:
2904 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2904 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2905 elif repo.ui.verbose:
2905 elif repo.ui.verbose:
2906 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2906 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2907
2907
2908 def postcommitstatus(repo, pats, opts):
2908 def postcommitstatus(repo, pats, opts):
2909 return repo.status(match=scmutil.match(repo[None], pats, opts))
2909 return repo.status(match=scmutil.match(repo[None], pats, opts))
2910
2910
2911 def revert(ui, repo, ctx, parents, *pats, **opts):
2911 def revert(ui, repo, ctx, parents, *pats, **opts):
2912 parent, p2 = parents
2912 parent, p2 = parents
2913 node = ctx.node()
2913 node = ctx.node()
2914
2914
2915 mf = ctx.manifest()
2915 mf = ctx.manifest()
2916 if node == p2:
2916 if node == p2:
2917 parent = p2
2917 parent = p2
2918
2918
2919 # need all matching names in dirstate and manifest of target rev,
2919 # need all matching names in dirstate and manifest of target rev,
2920 # so have to walk both. do not print errors if files exist in one
2920 # so have to walk both. do not print errors if files exist in one
2921 # but not other. in both cases, filesets should be evaluated against
2921 # but not other. in both cases, filesets should be evaluated against
2922 # workingctx to get consistent result (issue4497). this means 'set:**'
2922 # workingctx to get consistent result (issue4497). this means 'set:**'
2923 # cannot be used to select missing files from target rev.
2923 # cannot be used to select missing files from target rev.
2924
2924
2925 # `names` is a mapping for all elements in working copy and target revision
2925 # `names` is a mapping for all elements in working copy and target revision
2926 # The mapping is in the form:
2926 # The mapping is in the form:
2927 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2927 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2928 names = {}
2928 names = {}
2929
2929
2930 with repo.wlock():
2930 with repo.wlock():
2931 ## filling of the `names` mapping
2931 ## filling of the `names` mapping
2932 # walk dirstate to fill `names`
2932 # walk dirstate to fill `names`
2933
2933
2934 interactive = opts.get('interactive', False)
2934 interactive = opts.get('interactive', False)
2935 wctx = repo[None]
2935 wctx = repo[None]
2936 m = scmutil.match(wctx, pats, opts)
2936 m = scmutil.match(wctx, pats, opts)
2937
2937
2938 # we'll need this later
2938 # we'll need this later
2939 targetsubs = sorted(s for s in wctx.substate if m(s))
2939 targetsubs = sorted(s for s in wctx.substate if m(s))
2940
2940
2941 if not m.always():
2941 if not m.always():
2942 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2942 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2943 names[abs] = m.rel(abs), m.exact(abs)
2943 names[abs] = m.rel(abs), m.exact(abs)
2944
2944
2945 # walk target manifest to fill `names`
2945 # walk target manifest to fill `names`
2946
2946
2947 def badfn(path, msg):
2947 def badfn(path, msg):
2948 if path in names:
2948 if path in names:
2949 return
2949 return
2950 if path in ctx.substate:
2950 if path in ctx.substate:
2951 return
2951 return
2952 path_ = path + '/'
2952 path_ = path + '/'
2953 for f in names:
2953 for f in names:
2954 if f.startswith(path_):
2954 if f.startswith(path_):
2955 return
2955 return
2956 ui.warn("%s: %s\n" % (m.rel(path), msg))
2956 ui.warn("%s: %s\n" % (m.rel(path), msg))
2957
2957
2958 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2958 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2959 if abs not in names:
2959 if abs not in names:
2960 names[abs] = m.rel(abs), m.exact(abs)
2960 names[abs] = m.rel(abs), m.exact(abs)
2961
2961
2962 # Find status of all file in `names`.
2962 # Find status of all file in `names`.
2963 m = scmutil.matchfiles(repo, names)
2963 m = scmutil.matchfiles(repo, names)
2964
2964
2965 changes = repo.status(node1=node, match=m,
2965 changes = repo.status(node1=node, match=m,
2966 unknown=True, ignored=True, clean=True)
2966 unknown=True, ignored=True, clean=True)
2967 else:
2967 else:
2968 changes = repo.status(node1=node, match=m)
2968 changes = repo.status(node1=node, match=m)
2969 for kind in changes:
2969 for kind in changes:
2970 for abs in kind:
2970 for abs in kind:
2971 names[abs] = m.rel(abs), m.exact(abs)
2971 names[abs] = m.rel(abs), m.exact(abs)
2972
2972
2973 m = scmutil.matchfiles(repo, names)
2973 m = scmutil.matchfiles(repo, names)
2974
2974
2975 modified = set(changes.modified)
2975 modified = set(changes.modified)
2976 added = set(changes.added)
2976 added = set(changes.added)
2977 removed = set(changes.removed)
2977 removed = set(changes.removed)
2978 _deleted = set(changes.deleted)
2978 _deleted = set(changes.deleted)
2979 unknown = set(changes.unknown)
2979 unknown = set(changes.unknown)
2980 unknown.update(changes.ignored)
2980 unknown.update(changes.ignored)
2981 clean = set(changes.clean)
2981 clean = set(changes.clean)
2982 modadded = set()
2982 modadded = set()
2983
2983
2984 # We need to account for the state of the file in the dirstate,
2984 # We need to account for the state of the file in the dirstate,
2985 # even when we revert against something else than parent. This will
2985 # even when we revert against something else than parent. This will
2986 # slightly alter the behavior of revert (doing back up or not, delete
2986 # slightly alter the behavior of revert (doing back up or not, delete
2987 # or just forget etc).
2987 # or just forget etc).
2988 if parent == node:
2988 if parent == node:
2989 dsmodified = modified
2989 dsmodified = modified
2990 dsadded = added
2990 dsadded = added
2991 dsremoved = removed
2991 dsremoved = removed
2992 # store all local modifications, useful later for rename detection
2992 # store all local modifications, useful later for rename detection
2993 localchanges = dsmodified | dsadded
2993 localchanges = dsmodified | dsadded
2994 modified, added, removed = set(), set(), set()
2994 modified, added, removed = set(), set(), set()
2995 else:
2995 else:
2996 changes = repo.status(node1=parent, match=m)
2996 changes = repo.status(node1=parent, match=m)
2997 dsmodified = set(changes.modified)
2997 dsmodified = set(changes.modified)
2998 dsadded = set(changes.added)
2998 dsadded = set(changes.added)
2999 dsremoved = set(changes.removed)
2999 dsremoved = set(changes.removed)
3000 # store all local modifications, useful later for rename detection
3000 # store all local modifications, useful later for rename detection
3001 localchanges = dsmodified | dsadded
3001 localchanges = dsmodified | dsadded
3002
3002
3003 # only take into account for removes between wc and target
3003 # only take into account for removes between wc and target
3004 clean |= dsremoved - removed
3004 clean |= dsremoved - removed
3005 dsremoved &= removed
3005 dsremoved &= removed
3006 # distinct between dirstate remove and other
3006 # distinct between dirstate remove and other
3007 removed -= dsremoved
3007 removed -= dsremoved
3008
3008
3009 modadded = added & dsmodified
3009 modadded = added & dsmodified
3010 added -= modadded
3010 added -= modadded
3011
3011
3012 # tell newly modified apart.
3012 # tell newly modified apart.
3013 dsmodified &= modified
3013 dsmodified &= modified
3014 dsmodified |= modified & dsadded # dirstate added may need backup
3014 dsmodified |= modified & dsadded # dirstate added may need backup
3015 modified -= dsmodified
3015 modified -= dsmodified
3016
3016
3017 # We need to wait for some post-processing to update this set
3017 # We need to wait for some post-processing to update this set
3018 # before making the distinction. The dirstate will be used for
3018 # before making the distinction. The dirstate will be used for
3019 # that purpose.
3019 # that purpose.
3020 dsadded = added
3020 dsadded = added
3021
3021
3022 # in case of merge, files that are actually added can be reported as
3022 # in case of merge, files that are actually added can be reported as
3023 # modified, we need to post process the result
3023 # modified, we need to post process the result
3024 if p2 != nullid:
3024 if p2 != nullid:
3025 mergeadd = set(dsmodified)
3025 mergeadd = set(dsmodified)
3026 for path in dsmodified:
3026 for path in dsmodified:
3027 if path in mf:
3027 if path in mf:
3028 mergeadd.remove(path)
3028 mergeadd.remove(path)
3029 dsadded |= mergeadd
3029 dsadded |= mergeadd
3030 dsmodified -= mergeadd
3030 dsmodified -= mergeadd
3031
3031
3032 # if f is a rename, update `names` to also revert the source
3032 # if f is a rename, update `names` to also revert the source
3033 cwd = repo.getcwd()
3033 cwd = repo.getcwd()
3034 for f in localchanges:
3034 for f in localchanges:
3035 src = repo.dirstate.copied(f)
3035 src = repo.dirstate.copied(f)
3036 # XXX should we check for rename down to target node?
3036 # XXX should we check for rename down to target node?
3037 if src and src not in names and repo.dirstate[src] == 'r':
3037 if src and src not in names and repo.dirstate[src] == 'r':
3038 dsremoved.add(src)
3038 dsremoved.add(src)
3039 names[src] = (repo.pathto(src, cwd), True)
3039 names[src] = (repo.pathto(src, cwd), True)
3040
3040
3041 # determine the exact nature of the deleted changesets
3041 # determine the exact nature of the deleted changesets
3042 deladded = set(_deleted)
3042 deladded = set(_deleted)
3043 for path in _deleted:
3043 for path in _deleted:
3044 if path in mf:
3044 if path in mf:
3045 deladded.remove(path)
3045 deladded.remove(path)
3046 deleted = _deleted - deladded
3046 deleted = _deleted - deladded
3047
3047
3048 # distinguish between file to forget and the other
3048 # distinguish between file to forget and the other
3049 added = set()
3049 added = set()
3050 for abs in dsadded:
3050 for abs in dsadded:
3051 if repo.dirstate[abs] != 'a':
3051 if repo.dirstate[abs] != 'a':
3052 added.add(abs)
3052 added.add(abs)
3053 dsadded -= added
3053 dsadded -= added
3054
3054
3055 for abs in deladded:
3055 for abs in deladded:
3056 if repo.dirstate[abs] == 'a':
3056 if repo.dirstate[abs] == 'a':
3057 dsadded.add(abs)
3057 dsadded.add(abs)
3058 deladded -= dsadded
3058 deladded -= dsadded
3059
3059
3060 # For files marked as removed, we check if an unknown file is present at
3060 # For files marked as removed, we check if an unknown file is present at
3061 # the same path. If a such file exists it may need to be backed up.
3061 # the same path. If a such file exists it may need to be backed up.
3062 # Making the distinction at this stage helps have simpler backup
3062 # Making the distinction at this stage helps have simpler backup
3063 # logic.
3063 # logic.
3064 removunk = set()
3064 removunk = set()
3065 for abs in removed:
3065 for abs in removed:
3066 target = repo.wjoin(abs)
3066 target = repo.wjoin(abs)
3067 if os.path.lexists(target):
3067 if os.path.lexists(target):
3068 removunk.add(abs)
3068 removunk.add(abs)
3069 removed -= removunk
3069 removed -= removunk
3070
3070
3071 dsremovunk = set()
3071 dsremovunk = set()
3072 for abs in dsremoved:
3072 for abs in dsremoved:
3073 target = repo.wjoin(abs)
3073 target = repo.wjoin(abs)
3074 if os.path.lexists(target):
3074 if os.path.lexists(target):
3075 dsremovunk.add(abs)
3075 dsremovunk.add(abs)
3076 dsremoved -= dsremovunk
3076 dsremoved -= dsremovunk
3077
3077
3078 # action to be actually performed by revert
3078 # action to be actually performed by revert
3079 # (<list of file>, message>) tuple
3079 # (<list of file>, message>) tuple
3080 actions = {'revert': ([], _('reverting %s\n')),
3080 actions = {'revert': ([], _('reverting %s\n')),
3081 'add': ([], _('adding %s\n')),
3081 'add': ([], _('adding %s\n')),
3082 'remove': ([], _('removing %s\n')),
3082 'remove': ([], _('removing %s\n')),
3083 'drop': ([], _('removing %s\n')),
3083 'drop': ([], _('removing %s\n')),
3084 'forget': ([], _('forgetting %s\n')),
3084 'forget': ([], _('forgetting %s\n')),
3085 'undelete': ([], _('undeleting %s\n')),
3085 'undelete': ([], _('undeleting %s\n')),
3086 'noop': (None, _('no changes needed to %s\n')),
3086 'noop': (None, _('no changes needed to %s\n')),
3087 'unknown': (None, _('file not managed: %s\n')),
3087 'unknown': (None, _('file not managed: %s\n')),
3088 }
3088 }
3089
3089
3090 # "constant" that convey the backup strategy.
3090 # "constant" that convey the backup strategy.
3091 # All set to `discard` if `no-backup` is set do avoid checking
3091 # All set to `discard` if `no-backup` is set do avoid checking
3092 # no_backup lower in the code.
3092 # no_backup lower in the code.
3093 # These values are ordered for comparison purposes
3093 # These values are ordered for comparison purposes
3094 backupinteractive = 3 # do backup if interactively modified
3094 backupinteractive = 3 # do backup if interactively modified
3095 backup = 2 # unconditionally do backup
3095 backup = 2 # unconditionally do backup
3096 check = 1 # check if the existing file differs from target
3096 check = 1 # check if the existing file differs from target
3097 discard = 0 # never do backup
3097 discard = 0 # never do backup
3098 if opts.get('no_backup'):
3098 if opts.get('no_backup'):
3099 backupinteractive = backup = check = discard
3099 backupinteractive = backup = check = discard
3100 if interactive:
3100 if interactive:
3101 dsmodifiedbackup = backupinteractive
3101 dsmodifiedbackup = backupinteractive
3102 else:
3102 else:
3103 dsmodifiedbackup = backup
3103 dsmodifiedbackup = backup
3104 tobackup = set()
3104 tobackup = set()
3105
3105
3106 backupanddel = actions['remove']
3106 backupanddel = actions['remove']
3107 if not opts.get('no_backup'):
3107 if not opts.get('no_backup'):
3108 backupanddel = actions['drop']
3108 backupanddel = actions['drop']
3109
3109
3110 disptable = (
3110 disptable = (
3111 # dispatch table:
3111 # dispatch table:
3112 # file state
3112 # file state
3113 # action
3113 # action
3114 # make backup
3114 # make backup
3115
3115
3116 ## Sets that results that will change file on disk
3116 ## Sets that results that will change file on disk
3117 # Modified compared to target, no local change
3117 # Modified compared to target, no local change
3118 (modified, actions['revert'], discard),
3118 (modified, actions['revert'], discard),
3119 # Modified compared to target, but local file is deleted
3119 # Modified compared to target, but local file is deleted
3120 (deleted, actions['revert'], discard),
3120 (deleted, actions['revert'], discard),
3121 # Modified compared to target, local change
3121 # Modified compared to target, local change
3122 (dsmodified, actions['revert'], dsmodifiedbackup),
3122 (dsmodified, actions['revert'], dsmodifiedbackup),
3123 # Added since target
3123 # Added since target
3124 (added, actions['remove'], discard),
3124 (added, actions['remove'], discard),
3125 # Added in working directory
3125 # Added in working directory
3126 (dsadded, actions['forget'], discard),
3126 (dsadded, actions['forget'], discard),
3127 # Added since target, have local modification
3127 # Added since target, have local modification
3128 (modadded, backupanddel, backup),
3128 (modadded, backupanddel, backup),
3129 # Added since target but file is missing in working directory
3129 # Added since target but file is missing in working directory
3130 (deladded, actions['drop'], discard),
3130 (deladded, actions['drop'], discard),
3131 # Removed since target, before working copy parent
3131 # Removed since target, before working copy parent
3132 (removed, actions['add'], discard),
3132 (removed, actions['add'], discard),
3133 # Same as `removed` but an unknown file exists at the same path
3133 # Same as `removed` but an unknown file exists at the same path
3134 (removunk, actions['add'], check),
3134 (removunk, actions['add'], check),
3135 # Removed since targe, marked as such in working copy parent
3135 # Removed since targe, marked as such in working copy parent
3136 (dsremoved, actions['undelete'], discard),
3136 (dsremoved, actions['undelete'], discard),
3137 # Same as `dsremoved` but an unknown file exists at the same path
3137 # Same as `dsremoved` but an unknown file exists at the same path
3138 (dsremovunk, actions['undelete'], check),
3138 (dsremovunk, actions['undelete'], check),
3139 ## the following sets does not result in any file changes
3139 ## the following sets does not result in any file changes
3140 # File with no modification
3140 # File with no modification
3141 (clean, actions['noop'], discard),
3141 (clean, actions['noop'], discard),
3142 # Existing file, not tracked anywhere
3142 # Existing file, not tracked anywhere
3143 (unknown, actions['unknown'], discard),
3143 (unknown, actions['unknown'], discard),
3144 )
3144 )
3145
3145
3146 for abs, (rel, exact) in sorted(names.items()):
3146 for abs, (rel, exact) in sorted(names.items()):
3147 # target file to be touch on disk (relative to cwd)
3147 # target file to be touch on disk (relative to cwd)
3148 target = repo.wjoin(abs)
3148 target = repo.wjoin(abs)
3149 # search the entry in the dispatch table.
3149 # search the entry in the dispatch table.
3150 # if the file is in any of these sets, it was touched in the working
3150 # if the file is in any of these sets, it was touched in the working
3151 # directory parent and we are sure it needs to be reverted.
3151 # directory parent and we are sure it needs to be reverted.
3152 for table, (xlist, msg), dobackup in disptable:
3152 for table, (xlist, msg), dobackup in disptable:
3153 if abs not in table:
3153 if abs not in table:
3154 continue
3154 continue
3155 if xlist is not None:
3155 if xlist is not None:
3156 xlist.append(abs)
3156 xlist.append(abs)
3157 if dobackup:
3157 if dobackup:
3158 # If in interactive mode, don't automatically create
3158 # If in interactive mode, don't automatically create
3159 # .orig files (issue4793)
3159 # .orig files (issue4793)
3160 if dobackup == backupinteractive:
3160 if dobackup == backupinteractive:
3161 tobackup.add(abs)
3161 tobackup.add(abs)
3162 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3162 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3163 bakname = scmutil.origpath(ui, repo, rel)
3163 bakname = scmutil.origpath(ui, repo, rel)
3164 ui.note(_('saving current version of %s as %s\n') %
3164 ui.note(_('saving current version of %s as %s\n') %
3165 (rel, bakname))
3165 (rel, bakname))
3166 if not opts.get('dry_run'):
3166 if not opts.get('dry_run'):
3167 if interactive:
3167 if interactive:
3168 util.copyfile(target, bakname)
3168 util.copyfile(target, bakname)
3169 else:
3169 else:
3170 util.rename(target, bakname)
3170 util.rename(target, bakname)
3171 if ui.verbose or not exact:
3171 if ui.verbose or not exact:
3172 if not isinstance(msg, basestring):
3172 if not isinstance(msg, basestring):
3173 msg = msg(abs)
3173 msg = msg(abs)
3174 ui.status(msg % rel)
3174 ui.status(msg % rel)
3175 elif exact:
3175 elif exact:
3176 ui.warn(msg % rel)
3176 ui.warn(msg % rel)
3177 break
3177 break
3178
3178
3179 if not opts.get('dry_run'):
3179 if not opts.get('dry_run'):
3180 needdata = ('revert', 'add', 'undelete')
3180 needdata = ('revert', 'add', 'undelete')
3181 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3181 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3182 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3182 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3183
3183
3184 if targetsubs:
3184 if targetsubs:
3185 # Revert the subrepos on the revert list
3185 # Revert the subrepos on the revert list
3186 for sub in targetsubs:
3186 for sub in targetsubs:
3187 try:
3187 try:
3188 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3188 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3189 except KeyError:
3189 except KeyError:
3190 raise error.Abort("subrepository '%s' does not exist in %s!"
3190 raise error.Abort("subrepository '%s' does not exist in %s!"
3191 % (sub, short(ctx.node())))
3191 % (sub, short(ctx.node())))
3192
3192
3193 def _revertprefetch(repo, ctx, *files):
3193 def _revertprefetch(repo, ctx, *files):
3194 """Let extension changing the storage layer prefetch content"""
3194 """Let extension changing the storage layer prefetch content"""
3195 pass
3195 pass
3196
3196
3197 def _performrevert(repo, parents, ctx, actions, interactive=False,
3197 def _performrevert(repo, parents, ctx, actions, interactive=False,
3198 tobackup=None):
3198 tobackup=None):
3199 """function that actually perform all the actions computed for revert
3199 """function that actually perform all the actions computed for revert
3200
3200
3201 This is an independent function to let extension to plug in and react to
3201 This is an independent function to let extension to plug in and react to
3202 the imminent revert.
3202 the imminent revert.
3203
3203
3204 Make sure you have the working directory locked when calling this function.
3204 Make sure you have the working directory locked when calling this function.
3205 """
3205 """
3206 parent, p2 = parents
3206 parent, p2 = parents
3207 node = ctx.node()
3207 node = ctx.node()
3208 excluded_files = []
3208 excluded_files = []
3209 matcher_opts = {"exclude": excluded_files}
3209 matcher_opts = {"exclude": excluded_files}
3210
3210
3211 def checkout(f):
3211 def checkout(f):
3212 fc = ctx[f]
3212 fc = ctx[f]
3213 repo.wwrite(f, fc.data(), fc.flags())
3213 repo.wwrite(f, fc.data(), fc.flags())
3214
3214
3215 def doremove(f):
3215 def doremove(f):
3216 try:
3216 try:
3217 repo.wvfs.unlinkpath(f)
3217 repo.wvfs.unlinkpath(f)
3218 except OSError:
3218 except OSError:
3219 pass
3219 pass
3220 repo.dirstate.remove(f)
3220 repo.dirstate.remove(f)
3221
3221
3222 audit_path = pathutil.pathauditor(repo.root)
3222 audit_path = pathutil.pathauditor(repo.root)
3223 for f in actions['forget'][0]:
3223 for f in actions['forget'][0]:
3224 if interactive:
3224 if interactive:
3225 choice = repo.ui.promptchoice(
3225 choice = repo.ui.promptchoice(
3226 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3226 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3227 if choice == 0:
3227 if choice == 0:
3228 repo.dirstate.drop(f)
3228 repo.dirstate.drop(f)
3229 else:
3229 else:
3230 excluded_files.append(repo.wjoin(f))
3230 excluded_files.append(repo.wjoin(f))
3231 else:
3231 else:
3232 repo.dirstate.drop(f)
3232 repo.dirstate.drop(f)
3233 for f in actions['remove'][0]:
3233 for f in actions['remove'][0]:
3234 audit_path(f)
3234 audit_path(f)
3235 if interactive:
3235 if interactive:
3236 choice = repo.ui.promptchoice(
3236 choice = repo.ui.promptchoice(
3237 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3237 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3238 if choice == 0:
3238 if choice == 0:
3239 doremove(f)
3239 doremove(f)
3240 else:
3240 else:
3241 excluded_files.append(repo.wjoin(f))
3241 excluded_files.append(repo.wjoin(f))
3242 else:
3242 else:
3243 doremove(f)
3243 doremove(f)
3244 for f in actions['drop'][0]:
3244 for f in actions['drop'][0]:
3245 audit_path(f)
3245 audit_path(f)
3246 repo.dirstate.remove(f)
3246 repo.dirstate.remove(f)
3247
3247
3248 normal = None
3248 normal = None
3249 if node == parent:
3249 if node == parent:
3250 # We're reverting to our parent. If possible, we'd like status
3250 # We're reverting to our parent. If possible, we'd like status
3251 # to report the file as clean. We have to use normallookup for
3251 # to report the file as clean. We have to use normallookup for
3252 # merges to avoid losing information about merged/dirty files.
3252 # merges to avoid losing information about merged/dirty files.
3253 if p2 != nullid:
3253 if p2 != nullid:
3254 normal = repo.dirstate.normallookup
3254 normal = repo.dirstate.normallookup
3255 else:
3255 else:
3256 normal = repo.dirstate.normal
3256 normal = repo.dirstate.normal
3257
3257
3258 newlyaddedandmodifiedfiles = set()
3258 newlyaddedandmodifiedfiles = set()
3259 if interactive:
3259 if interactive:
3260 # Prompt the user for changes to revert
3260 # Prompt the user for changes to revert
3261 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3261 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3262 m = scmutil.match(ctx, torevert, matcher_opts)
3262 m = scmutil.match(ctx, torevert, matcher_opts)
3263 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3263 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3264 diffopts.nodates = True
3264 diffopts.nodates = True
3265 diffopts.git = True
3265 diffopts.git = True
3266 operation = 'discard'
3266 operation = 'discard'
3267 reversehunks = True
3267 reversehunks = True
3268 if node != parent:
3268 if node != parent:
3269 operation = 'revert'
3269 operation = 'revert'
3270 reversehunks = repo.ui.configbool('experimental',
3270 reversehunks = repo.ui.configbool('experimental',
3271 'revertalternateinteractivemode',
3271 'revertalternateinteractivemode',
3272 True)
3272 True)
3273 if reversehunks:
3273 if reversehunks:
3274 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3274 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3275 else:
3275 else:
3276 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3276 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3277 originalchunks = patch.parsepatch(diff)
3277 originalchunks = patch.parsepatch(diff)
3278
3278
3279 try:
3279 try:
3280
3280
3281 chunks, opts = recordfilter(repo.ui, originalchunks,
3281 chunks, opts = recordfilter(repo.ui, originalchunks,
3282 operation=operation)
3282 operation=operation)
3283 if reversehunks:
3283 if reversehunks:
3284 chunks = patch.reversehunks(chunks)
3284 chunks = patch.reversehunks(chunks)
3285
3285
3286 except patch.PatchError as err:
3286 except patch.PatchError as err:
3287 raise error.Abort(_('error parsing patch: %s') % err)
3287 raise error.Abort(_('error parsing patch: %s') % err)
3288
3288
3289 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3289 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3290 if tobackup is None:
3290 if tobackup is None:
3291 tobackup = set()
3291 tobackup = set()
3292 # Apply changes
3292 # Apply changes
3293 fp = stringio()
3293 fp = stringio()
3294 for c in chunks:
3294 for c in chunks:
3295 # Create a backup file only if this hunk should be backed up
3295 # Create a backup file only if this hunk should be backed up
3296 if ishunk(c) and c.header.filename() in tobackup:
3296 if ishunk(c) and c.header.filename() in tobackup:
3297 abs = c.header.filename()
3297 abs = c.header.filename()
3298 target = repo.wjoin(abs)
3298 target = repo.wjoin(abs)
3299 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3299 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3300 util.copyfile(target, bakname)
3300 util.copyfile(target, bakname)
3301 tobackup.remove(abs)
3301 tobackup.remove(abs)
3302 c.write(fp)
3302 c.write(fp)
3303 dopatch = fp.tell()
3303 dopatch = fp.tell()
3304 fp.seek(0)
3304 fp.seek(0)
3305 if dopatch:
3305 if dopatch:
3306 try:
3306 try:
3307 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3307 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3308 except patch.PatchError as err:
3308 except patch.PatchError as err:
3309 raise error.Abort(str(err))
3309 raise error.Abort(str(err))
3310 del fp
3310 del fp
3311 else:
3311 else:
3312 for f in actions['revert'][0]:
3312 for f in actions['revert'][0]:
3313 checkout(f)
3313 checkout(f)
3314 if normal:
3314 if normal:
3315 normal(f)
3315 normal(f)
3316
3316
3317 for f in actions['add'][0]:
3317 for f in actions['add'][0]:
3318 # Don't checkout modified files, they are already created by the diff
3318 # Don't checkout modified files, they are already created by the diff
3319 if f not in newlyaddedandmodifiedfiles:
3319 if f not in newlyaddedandmodifiedfiles:
3320 checkout(f)
3320 checkout(f)
3321 repo.dirstate.add(f)
3321 repo.dirstate.add(f)
3322
3322
3323 normal = repo.dirstate.normallookup
3323 normal = repo.dirstate.normallookup
3324 if node == parent and p2 == nullid:
3324 if node == parent and p2 == nullid:
3325 normal = repo.dirstate.normal
3325 normal = repo.dirstate.normal
3326 for f in actions['undelete'][0]:
3326 for f in actions['undelete'][0]:
3327 checkout(f)
3327 checkout(f)
3328 normal(f)
3328 normal(f)
3329
3329
3330 copied = copies.pathcopies(repo[parent], ctx)
3330 copied = copies.pathcopies(repo[parent], ctx)
3331
3331
3332 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3332 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3333 if f in copied:
3333 if f in copied:
3334 repo.dirstate.copy(copied[f], f)
3334 repo.dirstate.copy(copied[f], f)
3335
3335
3336 def command(table):
3336 def command(table):
3337 """Returns a function object to be used as a decorator for making commands.
3337 """Returns a function object to be used as a decorator for making commands.
3338
3338
3339 This function receives a command table as its argument. The table should
3339 This function receives a command table as its argument. The table should
3340 be a dict.
3340 be a dict.
3341
3341
3342 The returned function can be used as a decorator for adding commands
3342 The returned function can be used as a decorator for adding commands
3343 to that command table. This function accepts multiple arguments to define
3343 to that command table. This function accepts multiple arguments to define
3344 a command.
3344 a command.
3345
3345
3346 The first argument is the command name.
3346 The first argument is the command name.
3347
3347
3348 The options argument is an iterable of tuples defining command arguments.
3348 The options argument is an iterable of tuples defining command arguments.
3349 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3349 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3350
3350
3351 The synopsis argument defines a short, one line summary of how to use the
3351 The synopsis argument defines a short, one line summary of how to use the
3352 command. This shows up in the help output.
3352 command. This shows up in the help output.
3353
3353
3354 The norepo argument defines whether the command does not require a
3354 The norepo argument defines whether the command does not require a
3355 local repository. Most commands operate against a repository, thus the
3355 local repository. Most commands operate against a repository, thus the
3356 default is False.
3356 default is False.
3357
3357
3358 The optionalrepo argument defines whether the command optionally requires
3358 The optionalrepo argument defines whether the command optionally requires
3359 a local repository.
3359 a local repository.
3360
3360
3361 The inferrepo argument defines whether to try to find a repository from the
3361 The inferrepo argument defines whether to try to find a repository from the
3362 command line arguments. If True, arguments will be examined for potential
3362 command line arguments. If True, arguments will be examined for potential
3363 repository locations. See ``findrepo()``. If a repository is found, it
3363 repository locations. See ``findrepo()``. If a repository is found, it
3364 will be used.
3364 will be used.
3365 """
3365 """
3366 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3366 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3367 inferrepo=False):
3367 inferrepo=False):
3368 def decorator(func):
3368 def decorator(func):
3369 func.norepo = norepo
3369 func.norepo = norepo
3370 func.optionalrepo = optionalrepo
3370 func.optionalrepo = optionalrepo
3371 func.inferrepo = inferrepo
3371 func.inferrepo = inferrepo
3372 if synopsis:
3372 if synopsis:
3373 table[name] = func, list(options), synopsis
3373 table[name] = func, list(options), synopsis
3374 else:
3374 else:
3375 table[name] = func, list(options)
3375 table[name] = func, list(options)
3376 return func
3376 return func
3377 return decorator
3377 return decorator
3378
3378
3379 return cmd
3379 return cmd
3380
3380
3381 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3381 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3382 # commands.outgoing. "missing" is "missing" of the result of
3382 # commands.outgoing. "missing" is "missing" of the result of
3383 # "findcommonoutgoing()"
3383 # "findcommonoutgoing()"
3384 outgoinghooks = util.hooks()
3384 outgoinghooks = util.hooks()
3385
3385
3386 # a list of (ui, repo) functions called by commands.summary
3386 # a list of (ui, repo) functions called by commands.summary
3387 summaryhooks = util.hooks()
3387 summaryhooks = util.hooks()
3388
3388
3389 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3389 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3390 #
3390 #
3391 # functions should return tuple of booleans below, if 'changes' is None:
3391 # functions should return tuple of booleans below, if 'changes' is None:
3392 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3392 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3393 #
3393 #
3394 # otherwise, 'changes' is a tuple of tuples below:
3394 # otherwise, 'changes' is a tuple of tuples below:
3395 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3395 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3396 # - (desturl, destbranch, destpeer, outgoing)
3396 # - (desturl, destbranch, destpeer, outgoing)
3397 summaryremotehooks = util.hooks()
3397 summaryremotehooks = util.hooks()
3398
3398
3399 # A list of state files kept by multistep operations like graft.
3399 # A list of state files kept by multistep operations like graft.
3400 # Since graft cannot be aborted, it is considered 'clearable' by update.
3400 # Since graft cannot be aborted, it is considered 'clearable' by update.
3401 # note: bisect is intentionally excluded
3401 # note: bisect is intentionally excluded
3402 # (state file, clearable, allowcommit, error, hint)
3402 # (state file, clearable, allowcommit, error, hint)
3403 unfinishedstates = [
3403 unfinishedstates = [
3404 ('graftstate', True, False, _('graft in progress'),
3404 ('graftstate', True, False, _('graft in progress'),
3405 _("use 'hg graft --continue' or 'hg update' to abort")),
3405 _("use 'hg graft --continue' or 'hg update' to abort")),
3406 ('updatestate', True, False, _('last update was interrupted'),
3406 ('updatestate', True, False, _('last update was interrupted'),
3407 _("use 'hg update' to get a consistent checkout"))
3407 _("use 'hg update' to get a consistent checkout"))
3408 ]
3408 ]
3409
3409
3410 def checkunfinished(repo, commit=False):
3410 def checkunfinished(repo, commit=False):
3411 '''Look for an unfinished multistep operation, like graft, and abort
3411 '''Look for an unfinished multistep operation, like graft, and abort
3412 if found. It's probably good to check this right before
3412 if found. It's probably good to check this right before
3413 bailifchanged().
3413 bailifchanged().
3414 '''
3414 '''
3415 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3415 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3416 if commit and allowcommit:
3416 if commit and allowcommit:
3417 continue
3417 continue
3418 if repo.vfs.exists(f):
3418 if repo.vfs.exists(f):
3419 raise error.Abort(msg, hint=hint)
3419 raise error.Abort(msg, hint=hint)
3420
3420
3421 def clearunfinished(repo):
3421 def clearunfinished(repo):
3422 '''Check for unfinished operations (as above), and clear the ones
3422 '''Check for unfinished operations (as above), and clear the ones
3423 that are clearable.
3423 that are clearable.
3424 '''
3424 '''
3425 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3425 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3426 if not clearable and repo.vfs.exists(f):
3426 if not clearable and repo.vfs.exists(f):
3427 raise error.Abort(msg, hint=hint)
3427 raise error.Abort(msg, hint=hint)
3428 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3428 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3429 if clearable and repo.vfs.exists(f):
3429 if clearable and repo.vfs.exists(f):
3430 util.unlink(repo.vfs.join(f))
3430 util.unlink(repo.vfs.join(f))
3431
3431
3432 afterresolvedstates = [
3432 afterresolvedstates = [
3433 ('graftstate',
3433 ('graftstate',
3434 _('hg graft --continue')),
3434 _('hg graft --continue')),
3435 ]
3435 ]
3436
3436
3437 def howtocontinue(repo):
3437 def howtocontinue(repo):
3438 '''Check for an unfinished operation and return the command to finish
3438 '''Check for an unfinished operation and return the command to finish
3439 it.
3439 it.
3440
3440
3441 afterresolvedstates tuples define a .hg/{file} and the corresponding
3441 afterresolvedstates tuples define a .hg/{file} and the corresponding
3442 command needed to finish it.
3442 command needed to finish it.
3443
3443
3444 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3444 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3445 a boolean.
3445 a boolean.
3446 '''
3446 '''
3447 contmsg = _("continue: %s")
3447 contmsg = _("continue: %s")
3448 for f, msg in afterresolvedstates:
3448 for f, msg in afterresolvedstates:
3449 if repo.vfs.exists(f):
3449 if repo.vfs.exists(f):
3450 return contmsg % msg, True
3450 return contmsg % msg, True
3451 workingctx = repo[None]
3451 workingctx = repo[None]
3452 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3452 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3453 for s in workingctx.substate)
3453 for s in workingctx.substate)
3454 if dirty:
3454 if dirty:
3455 return contmsg % _("hg commit"), False
3455 return contmsg % _("hg commit"), False
3456 return None, None
3456 return None, None
3457
3457
3458 def checkafterresolved(repo):
3458 def checkafterresolved(repo):
3459 '''Inform the user about the next action after completing hg resolve
3459 '''Inform the user about the next action after completing hg resolve
3460
3460
3461 If there's a matching afterresolvedstates, howtocontinue will yield
3461 If there's a matching afterresolvedstates, howtocontinue will yield
3462 repo.ui.warn as the reporter.
3462 repo.ui.warn as the reporter.
3463
3463
3464 Otherwise, it will yield repo.ui.note.
3464 Otherwise, it will yield repo.ui.note.
3465 '''
3465 '''
3466 msg, warning = howtocontinue(repo)
3466 msg, warning = howtocontinue(repo)
3467 if msg is not None:
3467 if msg is not None:
3468 if warning:
3468 if warning:
3469 repo.ui.warn("%s\n" % msg)
3469 repo.ui.warn("%s\n" % msg)
3470 else:
3470 else:
3471 repo.ui.note("%s\n" % msg)
3471 repo.ui.note("%s\n" % msg)
3472
3472
3473 def wrongtooltocontinue(repo, task):
3473 def wrongtooltocontinue(repo, task):
3474 '''Raise an abort suggesting how to properly continue if there is an
3474 '''Raise an abort suggesting how to properly continue if there is an
3475 active task.
3475 active task.
3476
3476
3477 Uses howtocontinue() to find the active task.
3477 Uses howtocontinue() to find the active task.
3478
3478
3479 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3479 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3480 a hint.
3480 a hint.
3481 '''
3481 '''
3482 after = howtocontinue(repo)
3482 after = howtocontinue(repo)
3483 hint = None
3483 hint = None
3484 if after[1]:
3484 if after[1]:
3485 hint = after[0]
3485 hint = after[0]
3486 raise error.Abort(_('no %s in progress') % task, hint=hint)
3486 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,5520 +1,5519 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22 from . import (
22 from . import (
23 archival,
23 archival,
24 bookmarks,
24 bookmarks,
25 bundle2,
25 bundle2,
26 changegroup,
26 changegroup,
27 cmdutil,
27 cmdutil,
28 copies,
28 copies,
29 destutil,
29 destutil,
30 dirstateguard,
30 dirstateguard,
31 discovery,
31 discovery,
32 encoding,
32 encoding,
33 error,
33 error,
34 exchange,
34 exchange,
35 extensions,
35 extensions,
36 graphmod,
36 graphmod,
37 hbisect,
37 hbisect,
38 help,
38 help,
39 hg,
39 hg,
40 lock as lockmod,
40 lock as lockmod,
41 merge as mergemod,
41 merge as mergemod,
42 obsolete,
42 obsolete,
43 patch,
43 patch,
44 phases,
44 phases,
45 pycompat,
45 pycompat,
46 rcutil,
46 rcutil,
47 revsetlang,
47 revsetlang,
48 scmutil,
48 scmutil,
49 server,
49 server,
50 sshserver,
50 sshserver,
51 streamclone,
51 streamclone,
52 tags as tagsmod,
52 tags as tagsmod,
53 templatekw,
53 templatekw,
54 ui as uimod,
54 ui as uimod,
55 util,
55 util,
56 )
56 )
57
57
58 release = lockmod.release
58 release = lockmod.release
59
59
60 table = {}
60 table = {}
61
61
62 command = cmdutil.command(table)
62 command = cmdutil.command(table)
63
63
64 # label constants
64 # label constants
65 # until 3.5, bookmarks.current was the advertised name, not
65 # until 3.5, bookmarks.current was the advertised name, not
66 # bookmarks.active, so we must use both to avoid breaking old
66 # bookmarks.active, so we must use both to avoid breaking old
67 # custom styles
67 # custom styles
68 activebookmarklabel = 'bookmarks.active bookmarks.current'
68 activebookmarklabel = 'bookmarks.active bookmarks.current'
69
69
70 # common command options
70 # common command options
71
71
72 globalopts = [
72 globalopts = [
73 ('R', 'repository', '',
73 ('R', 'repository', '',
74 _('repository root directory or name of overlay bundle file'),
74 _('repository root directory or name of overlay bundle file'),
75 _('REPO')),
75 _('REPO')),
76 ('', 'cwd', '',
76 ('', 'cwd', '',
77 _('change working directory'), _('DIR')),
77 _('change working directory'), _('DIR')),
78 ('y', 'noninteractive', None,
78 ('y', 'noninteractive', None,
79 _('do not prompt, automatically pick the first choice for all prompts')),
79 _('do not prompt, automatically pick the first choice for all prompts')),
80 ('q', 'quiet', None, _('suppress output')),
80 ('q', 'quiet', None, _('suppress output')),
81 ('v', 'verbose', None, _('enable additional output')),
81 ('v', 'verbose', None, _('enable additional output')),
82 ('', 'color', '',
82 ('', 'color', '',
83 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
83 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
84 # and should not be translated
84 # and should not be translated
85 _("when to colorize (boolean, always, auto, never, or debug)"),
85 _("when to colorize (boolean, always, auto, never, or debug)"),
86 _('TYPE')),
86 _('TYPE')),
87 ('', 'config', [],
87 ('', 'config', [],
88 _('set/override config option (use \'section.name=value\')'),
88 _('set/override config option (use \'section.name=value\')'),
89 _('CONFIG')),
89 _('CONFIG')),
90 ('', 'debug', None, _('enable debugging output')),
90 ('', 'debug', None, _('enable debugging output')),
91 ('', 'debugger', None, _('start debugger')),
91 ('', 'debugger', None, _('start debugger')),
92 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
92 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
93 _('ENCODE')),
93 _('ENCODE')),
94 ('', 'encodingmode', encoding.encodingmode,
94 ('', 'encodingmode', encoding.encodingmode,
95 _('set the charset encoding mode'), _('MODE')),
95 _('set the charset encoding mode'), _('MODE')),
96 ('', 'traceback', None, _('always print a traceback on exception')),
96 ('', 'traceback', None, _('always print a traceback on exception')),
97 ('', 'time', None, _('time how long the command takes')),
97 ('', 'time', None, _('time how long the command takes')),
98 ('', 'profile', None, _('print command execution profile')),
98 ('', 'profile', None, _('print command execution profile')),
99 ('', 'version', None, _('output version information and exit')),
99 ('', 'version', None, _('output version information and exit')),
100 ('h', 'help', None, _('display help and exit')),
100 ('h', 'help', None, _('display help and exit')),
101 ('', 'hidden', False, _('consider hidden changesets')),
101 ('', 'hidden', False, _('consider hidden changesets')),
102 ('', 'pager', 'auto',
102 ('', 'pager', 'auto',
103 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
103 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
104 ]
104 ]
105
105
106 dryrunopts = [('n', 'dry-run', None,
106 dryrunopts = [('n', 'dry-run', None,
107 _('do not perform actions, just print output'))]
107 _('do not perform actions, just print output'))]
108
108
109 remoteopts = [
109 remoteopts = [
110 ('e', 'ssh', '',
110 ('e', 'ssh', '',
111 _('specify ssh command to use'), _('CMD')),
111 _('specify ssh command to use'), _('CMD')),
112 ('', 'remotecmd', '',
112 ('', 'remotecmd', '',
113 _('specify hg command to run on the remote side'), _('CMD')),
113 _('specify hg command to run on the remote side'), _('CMD')),
114 ('', 'insecure', None,
114 ('', 'insecure', None,
115 _('do not verify server certificate (ignoring web.cacerts config)')),
115 _('do not verify server certificate (ignoring web.cacerts config)')),
116 ]
116 ]
117
117
118 walkopts = [
118 walkopts = [
119 ('I', 'include', [],
119 ('I', 'include', [],
120 _('include names matching the given patterns'), _('PATTERN')),
120 _('include names matching the given patterns'), _('PATTERN')),
121 ('X', 'exclude', [],
121 ('X', 'exclude', [],
122 _('exclude names matching the given patterns'), _('PATTERN')),
122 _('exclude names matching the given patterns'), _('PATTERN')),
123 ]
123 ]
124
124
125 commitopts = [
125 commitopts = [
126 ('m', 'message', '',
126 ('m', 'message', '',
127 _('use text as commit message'), _('TEXT')),
127 _('use text as commit message'), _('TEXT')),
128 ('l', 'logfile', '',
128 ('l', 'logfile', '',
129 _('read commit message from file'), _('FILE')),
129 _('read commit message from file'), _('FILE')),
130 ]
130 ]
131
131
132 commitopts2 = [
132 commitopts2 = [
133 ('d', 'date', '',
133 ('d', 'date', '',
134 _('record the specified date as commit date'), _('DATE')),
134 _('record the specified date as commit date'), _('DATE')),
135 ('u', 'user', '',
135 ('u', 'user', '',
136 _('record the specified user as committer'), _('USER')),
136 _('record the specified user as committer'), _('USER')),
137 ]
137 ]
138
138
139 # hidden for now
139 # hidden for now
140 formatteropts = [
140 formatteropts = [
141 ('T', 'template', '',
141 ('T', 'template', '',
142 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
142 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
143 ]
143 ]
144
144
145 templateopts = [
145 templateopts = [
146 ('', 'style', '',
146 ('', 'style', '',
147 _('display using template map file (DEPRECATED)'), _('STYLE')),
147 _('display using template map file (DEPRECATED)'), _('STYLE')),
148 ('T', 'template', '',
148 ('T', 'template', '',
149 _('display with template'), _('TEMPLATE')),
149 _('display with template'), _('TEMPLATE')),
150 ]
150 ]
151
151
152 logopts = [
152 logopts = [
153 ('p', 'patch', None, _('show patch')),
153 ('p', 'patch', None, _('show patch')),
154 ('g', 'git', None, _('use git extended diff format')),
154 ('g', 'git', None, _('use git extended diff format')),
155 ('l', 'limit', '',
155 ('l', 'limit', '',
156 _('limit number of changes displayed'), _('NUM')),
156 _('limit number of changes displayed'), _('NUM')),
157 ('M', 'no-merges', None, _('do not show merges')),
157 ('M', 'no-merges', None, _('do not show merges')),
158 ('', 'stat', None, _('output diffstat-style summary of changes')),
158 ('', 'stat', None, _('output diffstat-style summary of changes')),
159 ('G', 'graph', None, _("show the revision DAG")),
159 ('G', 'graph', None, _("show the revision DAG")),
160 ] + templateopts
160 ] + templateopts
161
161
162 diffopts = [
162 diffopts = [
163 ('a', 'text', None, _('treat all files as text')),
163 ('a', 'text', None, _('treat all files as text')),
164 ('g', 'git', None, _('use git extended diff format')),
164 ('g', 'git', None, _('use git extended diff format')),
165 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
165 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
166 ('', 'nodates', None, _('omit dates from diff headers'))
166 ('', 'nodates', None, _('omit dates from diff headers'))
167 ]
167 ]
168
168
169 diffwsopts = [
169 diffwsopts = [
170 ('w', 'ignore-all-space', None,
170 ('w', 'ignore-all-space', None,
171 _('ignore white space when comparing lines')),
171 _('ignore white space when comparing lines')),
172 ('b', 'ignore-space-change', None,
172 ('b', 'ignore-space-change', None,
173 _('ignore changes in the amount of white space')),
173 _('ignore changes in the amount of white space')),
174 ('B', 'ignore-blank-lines', None,
174 ('B', 'ignore-blank-lines', None,
175 _('ignore changes whose lines are all blank')),
175 _('ignore changes whose lines are all blank')),
176 ]
176 ]
177
177
178 diffopts2 = [
178 diffopts2 = [
179 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
179 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
180 ('p', 'show-function', None, _('show which function each change is in')),
180 ('p', 'show-function', None, _('show which function each change is in')),
181 ('', 'reverse', None, _('produce a diff that undoes the changes')),
181 ('', 'reverse', None, _('produce a diff that undoes the changes')),
182 ] + diffwsopts + [
182 ] + diffwsopts + [
183 ('U', 'unified', '',
183 ('U', 'unified', '',
184 _('number of lines of context to show'), _('NUM')),
184 _('number of lines of context to show'), _('NUM')),
185 ('', 'stat', None, _('output diffstat-style summary of changes')),
185 ('', 'stat', None, _('output diffstat-style summary of changes')),
186 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
186 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
187 ]
187 ]
188
188
189 mergetoolopts = [
189 mergetoolopts = [
190 ('t', 'tool', '', _('specify merge tool')),
190 ('t', 'tool', '', _('specify merge tool')),
191 ]
191 ]
192
192
193 similarityopts = [
193 similarityopts = [
194 ('s', 'similarity', '',
194 ('s', 'similarity', '',
195 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
195 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
196 ]
196 ]
197
197
198 subrepoopts = [
198 subrepoopts = [
199 ('S', 'subrepos', None,
199 ('S', 'subrepos', None,
200 _('recurse into subrepositories'))
200 _('recurse into subrepositories'))
201 ]
201 ]
202
202
203 debugrevlogopts = [
203 debugrevlogopts = [
204 ('c', 'changelog', False, _('open changelog')),
204 ('c', 'changelog', False, _('open changelog')),
205 ('m', 'manifest', False, _('open manifest')),
205 ('m', 'manifest', False, _('open manifest')),
206 ('', 'dir', '', _('open directory manifest')),
206 ('', 'dir', '', _('open directory manifest')),
207 ]
207 ]
208
208
209 # Commands start here, listed alphabetically
209 # Commands start here, listed alphabetically
210
210
211 @command('^add',
211 @command('^add',
212 walkopts + subrepoopts + dryrunopts,
212 walkopts + subrepoopts + dryrunopts,
213 _('[OPTION]... [FILE]...'),
213 _('[OPTION]... [FILE]...'),
214 inferrepo=True)
214 inferrepo=True)
215 def add(ui, repo, *pats, **opts):
215 def add(ui, repo, *pats, **opts):
216 """add the specified files on the next commit
216 """add the specified files on the next commit
217
217
218 Schedule files to be version controlled and added to the
218 Schedule files to be version controlled and added to the
219 repository.
219 repository.
220
220
221 The files will be added to the repository at the next commit. To
221 The files will be added to the repository at the next commit. To
222 undo an add before that, see :hg:`forget`.
222 undo an add before that, see :hg:`forget`.
223
223
224 If no names are given, add all files to the repository (except
224 If no names are given, add all files to the repository (except
225 files matching ``.hgignore``).
225 files matching ``.hgignore``).
226
226
227 .. container:: verbose
227 .. container:: verbose
228
228
229 Examples:
229 Examples:
230
230
231 - New (unknown) files are added
231 - New (unknown) files are added
232 automatically by :hg:`add`::
232 automatically by :hg:`add`::
233
233
234 $ ls
234 $ ls
235 foo.c
235 foo.c
236 $ hg status
236 $ hg status
237 ? foo.c
237 ? foo.c
238 $ hg add
238 $ hg add
239 adding foo.c
239 adding foo.c
240 $ hg status
240 $ hg status
241 A foo.c
241 A foo.c
242
242
243 - Specific files to be added can be specified::
243 - Specific files to be added can be specified::
244
244
245 $ ls
245 $ ls
246 bar.c foo.c
246 bar.c foo.c
247 $ hg status
247 $ hg status
248 ? bar.c
248 ? bar.c
249 ? foo.c
249 ? foo.c
250 $ hg add bar.c
250 $ hg add bar.c
251 $ hg status
251 $ hg status
252 A bar.c
252 A bar.c
253 ? foo.c
253 ? foo.c
254
254
255 Returns 0 if all files are successfully added.
255 Returns 0 if all files are successfully added.
256 """
256 """
257
257
258 opts = pycompat.byteskwargs(opts)
258 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
259 m = scmutil.match(repo[None], pats, opts)
260 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
259 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
261 return rejected and 1 or 0
260 return rejected and 1 or 0
262
261
263 @command('addremove',
262 @command('addremove',
264 similarityopts + subrepoopts + walkopts + dryrunopts,
263 similarityopts + subrepoopts + walkopts + dryrunopts,
265 _('[OPTION]... [FILE]...'),
264 _('[OPTION]... [FILE]...'),
266 inferrepo=True)
265 inferrepo=True)
267 def addremove(ui, repo, *pats, **opts):
266 def addremove(ui, repo, *pats, **opts):
268 """add all new files, delete all missing files
267 """add all new files, delete all missing files
269
268
270 Add all new files and remove all missing files from the
269 Add all new files and remove all missing files from the
271 repository.
270 repository.
272
271
273 Unless names are given, new files are ignored if they match any of
272 Unless names are given, new files are ignored if they match any of
274 the patterns in ``.hgignore``. As with add, these changes take
273 the patterns in ``.hgignore``. As with add, these changes take
275 effect at the next commit.
274 effect at the next commit.
276
275
277 Use the -s/--similarity option to detect renamed files. This
276 Use the -s/--similarity option to detect renamed files. This
278 option takes a percentage between 0 (disabled) and 100 (files must
277 option takes a percentage between 0 (disabled) and 100 (files must
279 be identical) as its parameter. With a parameter greater than 0,
278 be identical) as its parameter. With a parameter greater than 0,
280 this compares every removed file with every added file and records
279 this compares every removed file with every added file and records
281 those similar enough as renames. Detecting renamed files this way
280 those similar enough as renames. Detecting renamed files this way
282 can be expensive. After using this option, :hg:`status -C` can be
281 can be expensive. After using this option, :hg:`status -C` can be
283 used to check which files were identified as moved or renamed. If
282 used to check which files were identified as moved or renamed. If
284 not specified, -s/--similarity defaults to 100 and only renames of
283 not specified, -s/--similarity defaults to 100 and only renames of
285 identical files are detected.
284 identical files are detected.
286
285
287 .. container:: verbose
286 .. container:: verbose
288
287
289 Examples:
288 Examples:
290
289
291 - A number of files (bar.c and foo.c) are new,
290 - A number of files (bar.c and foo.c) are new,
292 while foobar.c has been removed (without using :hg:`remove`)
291 while foobar.c has been removed (without using :hg:`remove`)
293 from the repository::
292 from the repository::
294
293
295 $ ls
294 $ ls
296 bar.c foo.c
295 bar.c foo.c
297 $ hg status
296 $ hg status
298 ! foobar.c
297 ! foobar.c
299 ? bar.c
298 ? bar.c
300 ? foo.c
299 ? foo.c
301 $ hg addremove
300 $ hg addremove
302 adding bar.c
301 adding bar.c
303 adding foo.c
302 adding foo.c
304 removing foobar.c
303 removing foobar.c
305 $ hg status
304 $ hg status
306 A bar.c
305 A bar.c
307 A foo.c
306 A foo.c
308 R foobar.c
307 R foobar.c
309
308
310 - A file foobar.c was moved to foo.c without using :hg:`rename`.
309 - A file foobar.c was moved to foo.c without using :hg:`rename`.
311 Afterwards, it was edited slightly::
310 Afterwards, it was edited slightly::
312
311
313 $ ls
312 $ ls
314 foo.c
313 foo.c
315 $ hg status
314 $ hg status
316 ! foobar.c
315 ! foobar.c
317 ? foo.c
316 ? foo.c
318 $ hg addremove --similarity 90
317 $ hg addremove --similarity 90
319 removing foobar.c
318 removing foobar.c
320 adding foo.c
319 adding foo.c
321 recording removal of foobar.c as rename to foo.c (94% similar)
320 recording removal of foobar.c as rename to foo.c (94% similar)
322 $ hg status -C
321 $ hg status -C
323 A foo.c
322 A foo.c
324 foobar.c
323 foobar.c
325 R foobar.c
324 R foobar.c
326
325
327 Returns 0 if all files are successfully added.
326 Returns 0 if all files are successfully added.
328 """
327 """
329 opts = pycompat.byteskwargs(opts)
328 opts = pycompat.byteskwargs(opts)
330 try:
329 try:
331 sim = float(opts.get('similarity') or 100)
330 sim = float(opts.get('similarity') or 100)
332 except ValueError:
331 except ValueError:
333 raise error.Abort(_('similarity must be a number'))
332 raise error.Abort(_('similarity must be a number'))
334 if sim < 0 or sim > 100:
333 if sim < 0 or sim > 100:
335 raise error.Abort(_('similarity must be between 0 and 100'))
334 raise error.Abort(_('similarity must be between 0 and 100'))
336 matcher = scmutil.match(repo[None], pats, opts)
335 matcher = scmutil.match(repo[None], pats, opts)
337 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
336 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
338
337
339 @command('^annotate|blame',
338 @command('^annotate|blame',
340 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
339 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
341 ('', 'follow', None,
340 ('', 'follow', None,
342 _('follow copies/renames and list the filename (DEPRECATED)')),
341 _('follow copies/renames and list the filename (DEPRECATED)')),
343 ('', 'no-follow', None, _("don't follow copies and renames")),
342 ('', 'no-follow', None, _("don't follow copies and renames")),
344 ('a', 'text', None, _('treat all files as text')),
343 ('a', 'text', None, _('treat all files as text')),
345 ('u', 'user', None, _('list the author (long with -v)')),
344 ('u', 'user', None, _('list the author (long with -v)')),
346 ('f', 'file', None, _('list the filename')),
345 ('f', 'file', None, _('list the filename')),
347 ('d', 'date', None, _('list the date (short with -q)')),
346 ('d', 'date', None, _('list the date (short with -q)')),
348 ('n', 'number', None, _('list the revision number (default)')),
347 ('n', 'number', None, _('list the revision number (default)')),
349 ('c', 'changeset', None, _('list the changeset')),
348 ('c', 'changeset', None, _('list the changeset')),
350 ('l', 'line-number', None, _('show line number at the first appearance'))
349 ('l', 'line-number', None, _('show line number at the first appearance'))
351 ] + diffwsopts + walkopts + formatteropts,
350 ] + diffwsopts + walkopts + formatteropts,
352 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
351 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
353 inferrepo=True)
352 inferrepo=True)
354 def annotate(ui, repo, *pats, **opts):
353 def annotate(ui, repo, *pats, **opts):
355 """show changeset information by line for each file
354 """show changeset information by line for each file
356
355
357 List changes in files, showing the revision id responsible for
356 List changes in files, showing the revision id responsible for
358 each line.
357 each line.
359
358
360 This command is useful for discovering when a change was made and
359 This command is useful for discovering when a change was made and
361 by whom.
360 by whom.
362
361
363 If you include --file, --user, or --date, the revision number is
362 If you include --file, --user, or --date, the revision number is
364 suppressed unless you also include --number.
363 suppressed unless you also include --number.
365
364
366 Without the -a/--text option, annotate will avoid processing files
365 Without the -a/--text option, annotate will avoid processing files
367 it detects as binary. With -a, annotate will annotate the file
366 it detects as binary. With -a, annotate will annotate the file
368 anyway, although the results will probably be neither useful
367 anyway, although the results will probably be neither useful
369 nor desirable.
368 nor desirable.
370
369
371 Returns 0 on success.
370 Returns 0 on success.
372 """
371 """
373 opts = pycompat.byteskwargs(opts)
372 opts = pycompat.byteskwargs(opts)
374 if not pats:
373 if not pats:
375 raise error.Abort(_('at least one filename or pattern is required'))
374 raise error.Abort(_('at least one filename or pattern is required'))
376
375
377 if opts.get('follow'):
376 if opts.get('follow'):
378 # --follow is deprecated and now just an alias for -f/--file
377 # --follow is deprecated and now just an alias for -f/--file
379 # to mimic the behavior of Mercurial before version 1.5
378 # to mimic the behavior of Mercurial before version 1.5
380 opts['file'] = True
379 opts['file'] = True
381
380
382 ctx = scmutil.revsingle(repo, opts.get('rev'))
381 ctx = scmutil.revsingle(repo, opts.get('rev'))
383
382
384 fm = ui.formatter('annotate', opts)
383 fm = ui.formatter('annotate', opts)
385 if ui.quiet:
384 if ui.quiet:
386 datefunc = util.shortdate
385 datefunc = util.shortdate
387 else:
386 else:
388 datefunc = util.datestr
387 datefunc = util.datestr
389 if ctx.rev() is None:
388 if ctx.rev() is None:
390 def hexfn(node):
389 def hexfn(node):
391 if node is None:
390 if node is None:
392 return None
391 return None
393 else:
392 else:
394 return fm.hexfunc(node)
393 return fm.hexfunc(node)
395 if opts.get('changeset'):
394 if opts.get('changeset'):
396 # omit "+" suffix which is appended to node hex
395 # omit "+" suffix which is appended to node hex
397 def formatrev(rev):
396 def formatrev(rev):
398 if rev is None:
397 if rev is None:
399 return '%d' % ctx.p1().rev()
398 return '%d' % ctx.p1().rev()
400 else:
399 else:
401 return '%d' % rev
400 return '%d' % rev
402 else:
401 else:
403 def formatrev(rev):
402 def formatrev(rev):
404 if rev is None:
403 if rev is None:
405 return '%d+' % ctx.p1().rev()
404 return '%d+' % ctx.p1().rev()
406 else:
405 else:
407 return '%d ' % rev
406 return '%d ' % rev
408 def formathex(hex):
407 def formathex(hex):
409 if hex is None:
408 if hex is None:
410 return '%s+' % fm.hexfunc(ctx.p1().node())
409 return '%s+' % fm.hexfunc(ctx.p1().node())
411 else:
410 else:
412 return '%s ' % hex
411 return '%s ' % hex
413 else:
412 else:
414 hexfn = fm.hexfunc
413 hexfn = fm.hexfunc
415 formatrev = formathex = str
414 formatrev = formathex = str
416
415
417 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
416 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
418 ('number', ' ', lambda x: x[0].rev(), formatrev),
417 ('number', ' ', lambda x: x[0].rev(), formatrev),
419 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
418 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
420 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
419 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
421 ('file', ' ', lambda x: x[0].path(), str),
420 ('file', ' ', lambda x: x[0].path(), str),
422 ('line_number', ':', lambda x: x[1], str),
421 ('line_number', ':', lambda x: x[1], str),
423 ]
422 ]
424 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
423 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
425
424
426 if (not opts.get('user') and not opts.get('changeset')
425 if (not opts.get('user') and not opts.get('changeset')
427 and not opts.get('date') and not opts.get('file')):
426 and not opts.get('date') and not opts.get('file')):
428 opts['number'] = True
427 opts['number'] = True
429
428
430 linenumber = opts.get('line_number') is not None
429 linenumber = opts.get('line_number') is not None
431 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
430 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
432 raise error.Abort(_('at least one of -n/-c is required for -l'))
431 raise error.Abort(_('at least one of -n/-c is required for -l'))
433
432
434 ui.pager('annotate')
433 ui.pager('annotate')
435
434
436 if fm.isplain():
435 if fm.isplain():
437 def makefunc(get, fmt):
436 def makefunc(get, fmt):
438 return lambda x: fmt(get(x))
437 return lambda x: fmt(get(x))
439 else:
438 else:
440 def makefunc(get, fmt):
439 def makefunc(get, fmt):
441 return get
440 return get
442 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
441 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
443 if opts.get(op)]
442 if opts.get(op)]
444 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
443 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
445 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
444 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
446 if opts.get(op))
445 if opts.get(op))
447
446
448 def bad(x, y):
447 def bad(x, y):
449 raise error.Abort("%s: %s" % (x, y))
448 raise error.Abort("%s: %s" % (x, y))
450
449
451 m = scmutil.match(ctx, pats, opts, badfn=bad)
450 m = scmutil.match(ctx, pats, opts, badfn=bad)
452
451
453 follow = not opts.get('no_follow')
452 follow = not opts.get('no_follow')
454 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
453 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
455 whitespace=True)
454 whitespace=True)
456 for abs in ctx.walk(m):
455 for abs in ctx.walk(m):
457 fctx = ctx[abs]
456 fctx = ctx[abs]
458 if not opts.get('text') and fctx.isbinary():
457 if not opts.get('text') and fctx.isbinary():
459 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
458 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
460 continue
459 continue
461
460
462 lines = fctx.annotate(follow=follow, linenumber=linenumber,
461 lines = fctx.annotate(follow=follow, linenumber=linenumber,
463 diffopts=diffopts)
462 diffopts=diffopts)
464 if not lines:
463 if not lines:
465 continue
464 continue
466 formats = []
465 formats = []
467 pieces = []
466 pieces = []
468
467
469 for f, sep in funcmap:
468 for f, sep in funcmap:
470 l = [f(n) for n, dummy in lines]
469 l = [f(n) for n, dummy in lines]
471 if fm.isplain():
470 if fm.isplain():
472 sizes = [encoding.colwidth(x) for x in l]
471 sizes = [encoding.colwidth(x) for x in l]
473 ml = max(sizes)
472 ml = max(sizes)
474 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
473 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
475 else:
474 else:
476 formats.append(['%s' for x in l])
475 formats.append(['%s' for x in l])
477 pieces.append(l)
476 pieces.append(l)
478
477
479 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
478 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
480 fm.startitem()
479 fm.startitem()
481 fm.write(fields, "".join(f), *p)
480 fm.write(fields, "".join(f), *p)
482 fm.write('line', ": %s", l[1])
481 fm.write('line', ": %s", l[1])
483
482
484 if not lines[-1][1].endswith('\n'):
483 if not lines[-1][1].endswith('\n'):
485 fm.plain('\n')
484 fm.plain('\n')
486
485
487 fm.end()
486 fm.end()
488
487
489 @command('archive',
488 @command('archive',
490 [('', 'no-decode', None, _('do not pass files through decoders')),
489 [('', 'no-decode', None, _('do not pass files through decoders')),
491 ('p', 'prefix', '', _('directory prefix for files in archive'),
490 ('p', 'prefix', '', _('directory prefix for files in archive'),
492 _('PREFIX')),
491 _('PREFIX')),
493 ('r', 'rev', '', _('revision to distribute'), _('REV')),
492 ('r', 'rev', '', _('revision to distribute'), _('REV')),
494 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
493 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
495 ] + subrepoopts + walkopts,
494 ] + subrepoopts + walkopts,
496 _('[OPTION]... DEST'))
495 _('[OPTION]... DEST'))
497 def archive(ui, repo, dest, **opts):
496 def archive(ui, repo, dest, **opts):
498 '''create an unversioned archive of a repository revision
497 '''create an unversioned archive of a repository revision
499
498
500 By default, the revision used is the parent of the working
499 By default, the revision used is the parent of the working
501 directory; use -r/--rev to specify a different revision.
500 directory; use -r/--rev to specify a different revision.
502
501
503 The archive type is automatically detected based on file
502 The archive type is automatically detected based on file
504 extension (to override, use -t/--type).
503 extension (to override, use -t/--type).
505
504
506 .. container:: verbose
505 .. container:: verbose
507
506
508 Examples:
507 Examples:
509
508
510 - create a zip file containing the 1.0 release::
509 - create a zip file containing the 1.0 release::
511
510
512 hg archive -r 1.0 project-1.0.zip
511 hg archive -r 1.0 project-1.0.zip
513
512
514 - create a tarball excluding .hg files::
513 - create a tarball excluding .hg files::
515
514
516 hg archive project.tar.gz -X ".hg*"
515 hg archive project.tar.gz -X ".hg*"
517
516
518 Valid types are:
517 Valid types are:
519
518
520 :``files``: a directory full of files (default)
519 :``files``: a directory full of files (default)
521 :``tar``: tar archive, uncompressed
520 :``tar``: tar archive, uncompressed
522 :``tbz2``: tar archive, compressed using bzip2
521 :``tbz2``: tar archive, compressed using bzip2
523 :``tgz``: tar archive, compressed using gzip
522 :``tgz``: tar archive, compressed using gzip
524 :``uzip``: zip archive, uncompressed
523 :``uzip``: zip archive, uncompressed
525 :``zip``: zip archive, compressed using deflate
524 :``zip``: zip archive, compressed using deflate
526
525
527 The exact name of the destination archive or directory is given
526 The exact name of the destination archive or directory is given
528 using a format string; see :hg:`help export` for details.
527 using a format string; see :hg:`help export` for details.
529
528
530 Each member added to an archive file has a directory prefix
529 Each member added to an archive file has a directory prefix
531 prepended. Use -p/--prefix to specify a format string for the
530 prepended. Use -p/--prefix to specify a format string for the
532 prefix. The default is the basename of the archive, with suffixes
531 prefix. The default is the basename of the archive, with suffixes
533 removed.
532 removed.
534
533
535 Returns 0 on success.
534 Returns 0 on success.
536 '''
535 '''
537
536
538 opts = pycompat.byteskwargs(opts)
537 opts = pycompat.byteskwargs(opts)
539 ctx = scmutil.revsingle(repo, opts.get('rev'))
538 ctx = scmutil.revsingle(repo, opts.get('rev'))
540 if not ctx:
539 if not ctx:
541 raise error.Abort(_('no working directory: please specify a revision'))
540 raise error.Abort(_('no working directory: please specify a revision'))
542 node = ctx.node()
541 node = ctx.node()
543 dest = cmdutil.makefilename(repo, dest, node)
542 dest = cmdutil.makefilename(repo, dest, node)
544 if os.path.realpath(dest) == repo.root:
543 if os.path.realpath(dest) == repo.root:
545 raise error.Abort(_('repository root cannot be destination'))
544 raise error.Abort(_('repository root cannot be destination'))
546
545
547 kind = opts.get('type') or archival.guesskind(dest) or 'files'
546 kind = opts.get('type') or archival.guesskind(dest) or 'files'
548 prefix = opts.get('prefix')
547 prefix = opts.get('prefix')
549
548
550 if dest == '-':
549 if dest == '-':
551 if kind == 'files':
550 if kind == 'files':
552 raise error.Abort(_('cannot archive plain files to stdout'))
551 raise error.Abort(_('cannot archive plain files to stdout'))
553 dest = cmdutil.makefileobj(repo, dest)
552 dest = cmdutil.makefileobj(repo, dest)
554 if not prefix:
553 if not prefix:
555 prefix = os.path.basename(repo.root) + '-%h'
554 prefix = os.path.basename(repo.root) + '-%h'
556
555
557 prefix = cmdutil.makefilename(repo, prefix, node)
556 prefix = cmdutil.makefilename(repo, prefix, node)
558 matchfn = scmutil.match(ctx, [], opts)
557 matchfn = scmutil.match(ctx, [], opts)
559 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
558 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
560 matchfn, prefix, subrepos=opts.get('subrepos'))
559 matchfn, prefix, subrepos=opts.get('subrepos'))
561
560
562 @command('backout',
561 @command('backout',
563 [('', 'merge', None, _('merge with old dirstate parent after backout')),
562 [('', 'merge', None, _('merge with old dirstate parent after backout')),
564 ('', 'commit', None,
563 ('', 'commit', None,
565 _('commit if no conflicts were encountered (DEPRECATED)')),
564 _('commit if no conflicts were encountered (DEPRECATED)')),
566 ('', 'no-commit', None, _('do not commit')),
565 ('', 'no-commit', None, _('do not commit')),
567 ('', 'parent', '',
566 ('', 'parent', '',
568 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
567 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
569 ('r', 'rev', '', _('revision to backout'), _('REV')),
568 ('r', 'rev', '', _('revision to backout'), _('REV')),
570 ('e', 'edit', False, _('invoke editor on commit messages')),
569 ('e', 'edit', False, _('invoke editor on commit messages')),
571 ] + mergetoolopts + walkopts + commitopts + commitopts2,
570 ] + mergetoolopts + walkopts + commitopts + commitopts2,
572 _('[OPTION]... [-r] REV'))
571 _('[OPTION]... [-r] REV'))
573 def backout(ui, repo, node=None, rev=None, **opts):
572 def backout(ui, repo, node=None, rev=None, **opts):
574 '''reverse effect of earlier changeset
573 '''reverse effect of earlier changeset
575
574
576 Prepare a new changeset with the effect of REV undone in the
575 Prepare a new changeset with the effect of REV undone in the
577 current working directory. If no conflicts were encountered,
576 current working directory. If no conflicts were encountered,
578 it will be committed immediately.
577 it will be committed immediately.
579
578
580 If REV is the parent of the working directory, then this new changeset
579 If REV is the parent of the working directory, then this new changeset
581 is committed automatically (unless --no-commit is specified).
580 is committed automatically (unless --no-commit is specified).
582
581
583 .. note::
582 .. note::
584
583
585 :hg:`backout` cannot be used to fix either an unwanted or
584 :hg:`backout` cannot be used to fix either an unwanted or
586 incorrect merge.
585 incorrect merge.
587
586
588 .. container:: verbose
587 .. container:: verbose
589
588
590 Examples:
589 Examples:
591
590
592 - Reverse the effect of the parent of the working directory.
591 - Reverse the effect of the parent of the working directory.
593 This backout will be committed immediately::
592 This backout will be committed immediately::
594
593
595 hg backout -r .
594 hg backout -r .
596
595
597 - Reverse the effect of previous bad revision 23::
596 - Reverse the effect of previous bad revision 23::
598
597
599 hg backout -r 23
598 hg backout -r 23
600
599
601 - Reverse the effect of previous bad revision 23 and
600 - Reverse the effect of previous bad revision 23 and
602 leave changes uncommitted::
601 leave changes uncommitted::
603
602
604 hg backout -r 23 --no-commit
603 hg backout -r 23 --no-commit
605 hg commit -m "Backout revision 23"
604 hg commit -m "Backout revision 23"
606
605
607 By default, the pending changeset will have one parent,
606 By default, the pending changeset will have one parent,
608 maintaining a linear history. With --merge, the pending
607 maintaining a linear history. With --merge, the pending
609 changeset will instead have two parents: the old parent of the
608 changeset will instead have two parents: the old parent of the
610 working directory and a new child of REV that simply undoes REV.
609 working directory and a new child of REV that simply undoes REV.
611
610
612 Before version 1.7, the behavior without --merge was equivalent
611 Before version 1.7, the behavior without --merge was equivalent
613 to specifying --merge followed by :hg:`update --clean .` to
612 to specifying --merge followed by :hg:`update --clean .` to
614 cancel the merge and leave the child of REV as a head to be
613 cancel the merge and leave the child of REV as a head to be
615 merged separately.
614 merged separately.
616
615
617 See :hg:`help dates` for a list of formats valid for -d/--date.
616 See :hg:`help dates` for a list of formats valid for -d/--date.
618
617
619 See :hg:`help revert` for a way to restore files to the state
618 See :hg:`help revert` for a way to restore files to the state
620 of another revision.
619 of another revision.
621
620
622 Returns 0 on success, 1 if nothing to backout or there are unresolved
621 Returns 0 on success, 1 if nothing to backout or there are unresolved
623 files.
622 files.
624 '''
623 '''
625 wlock = lock = None
624 wlock = lock = None
626 try:
625 try:
627 wlock = repo.wlock()
626 wlock = repo.wlock()
628 lock = repo.lock()
627 lock = repo.lock()
629 return _dobackout(ui, repo, node, rev, **opts)
628 return _dobackout(ui, repo, node, rev, **opts)
630 finally:
629 finally:
631 release(lock, wlock)
630 release(lock, wlock)
632
631
633 def _dobackout(ui, repo, node=None, rev=None, **opts):
632 def _dobackout(ui, repo, node=None, rev=None, **opts):
634 opts = pycompat.byteskwargs(opts)
633 opts = pycompat.byteskwargs(opts)
635 if opts.get('commit') and opts.get('no_commit'):
634 if opts.get('commit') and opts.get('no_commit'):
636 raise error.Abort(_("cannot use --commit with --no-commit"))
635 raise error.Abort(_("cannot use --commit with --no-commit"))
637 if opts.get('merge') and opts.get('no_commit'):
636 if opts.get('merge') and opts.get('no_commit'):
638 raise error.Abort(_("cannot use --merge with --no-commit"))
637 raise error.Abort(_("cannot use --merge with --no-commit"))
639
638
640 if rev and node:
639 if rev and node:
641 raise error.Abort(_("please specify just one revision"))
640 raise error.Abort(_("please specify just one revision"))
642
641
643 if not rev:
642 if not rev:
644 rev = node
643 rev = node
645
644
646 if not rev:
645 if not rev:
647 raise error.Abort(_("please specify a revision to backout"))
646 raise error.Abort(_("please specify a revision to backout"))
648
647
649 date = opts.get('date')
648 date = opts.get('date')
650 if date:
649 if date:
651 opts['date'] = util.parsedate(date)
650 opts['date'] = util.parsedate(date)
652
651
653 cmdutil.checkunfinished(repo)
652 cmdutil.checkunfinished(repo)
654 cmdutil.bailifchanged(repo)
653 cmdutil.bailifchanged(repo)
655 node = scmutil.revsingle(repo, rev).node()
654 node = scmutil.revsingle(repo, rev).node()
656
655
657 op1, op2 = repo.dirstate.parents()
656 op1, op2 = repo.dirstate.parents()
658 if not repo.changelog.isancestor(node, op1):
657 if not repo.changelog.isancestor(node, op1):
659 raise error.Abort(_('cannot backout change that is not an ancestor'))
658 raise error.Abort(_('cannot backout change that is not an ancestor'))
660
659
661 p1, p2 = repo.changelog.parents(node)
660 p1, p2 = repo.changelog.parents(node)
662 if p1 == nullid:
661 if p1 == nullid:
663 raise error.Abort(_('cannot backout a change with no parents'))
662 raise error.Abort(_('cannot backout a change with no parents'))
664 if p2 != nullid:
663 if p2 != nullid:
665 if not opts.get('parent'):
664 if not opts.get('parent'):
666 raise error.Abort(_('cannot backout a merge changeset'))
665 raise error.Abort(_('cannot backout a merge changeset'))
667 p = repo.lookup(opts['parent'])
666 p = repo.lookup(opts['parent'])
668 if p not in (p1, p2):
667 if p not in (p1, p2):
669 raise error.Abort(_('%s is not a parent of %s') %
668 raise error.Abort(_('%s is not a parent of %s') %
670 (short(p), short(node)))
669 (short(p), short(node)))
671 parent = p
670 parent = p
672 else:
671 else:
673 if opts.get('parent'):
672 if opts.get('parent'):
674 raise error.Abort(_('cannot use --parent on non-merge changeset'))
673 raise error.Abort(_('cannot use --parent on non-merge changeset'))
675 parent = p1
674 parent = p1
676
675
677 # the backout should appear on the same branch
676 # the backout should appear on the same branch
678 branch = repo.dirstate.branch()
677 branch = repo.dirstate.branch()
679 bheads = repo.branchheads(branch)
678 bheads = repo.branchheads(branch)
680 rctx = scmutil.revsingle(repo, hex(parent))
679 rctx = scmutil.revsingle(repo, hex(parent))
681 if not opts.get('merge') and op1 != node:
680 if not opts.get('merge') and op1 != node:
682 dsguard = dirstateguard.dirstateguard(repo, 'backout')
681 dsguard = dirstateguard.dirstateguard(repo, 'backout')
683 try:
682 try:
684 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
683 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
685 'backout')
684 'backout')
686 stats = mergemod.update(repo, parent, True, True, node, False)
685 stats = mergemod.update(repo, parent, True, True, node, False)
687 repo.setparents(op1, op2)
686 repo.setparents(op1, op2)
688 dsguard.close()
687 dsguard.close()
689 hg._showstats(repo, stats)
688 hg._showstats(repo, stats)
690 if stats[3]:
689 if stats[3]:
691 repo.ui.status(_("use 'hg resolve' to retry unresolved "
690 repo.ui.status(_("use 'hg resolve' to retry unresolved "
692 "file merges\n"))
691 "file merges\n"))
693 return 1
692 return 1
694 finally:
693 finally:
695 ui.setconfig('ui', 'forcemerge', '', '')
694 ui.setconfig('ui', 'forcemerge', '', '')
696 lockmod.release(dsguard)
695 lockmod.release(dsguard)
697 else:
696 else:
698 hg.clean(repo, node, show_stats=False)
697 hg.clean(repo, node, show_stats=False)
699 repo.dirstate.setbranch(branch)
698 repo.dirstate.setbranch(branch)
700 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
699 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
701
700
702 if opts.get('no_commit'):
701 if opts.get('no_commit'):
703 msg = _("changeset %s backed out, "
702 msg = _("changeset %s backed out, "
704 "don't forget to commit.\n")
703 "don't forget to commit.\n")
705 ui.status(msg % short(node))
704 ui.status(msg % short(node))
706 return 0
705 return 0
707
706
708 def commitfunc(ui, repo, message, match, opts):
707 def commitfunc(ui, repo, message, match, opts):
709 editform = 'backout'
708 editform = 'backout'
710 e = cmdutil.getcommiteditor(editform=editform, **opts)
709 e = cmdutil.getcommiteditor(editform=editform, **opts)
711 if not message:
710 if not message:
712 # we don't translate commit messages
711 # we don't translate commit messages
713 message = "Backed out changeset %s" % short(node)
712 message = "Backed out changeset %s" % short(node)
714 e = cmdutil.getcommiteditor(edit=True, editform=editform)
713 e = cmdutil.getcommiteditor(edit=True, editform=editform)
715 return repo.commit(message, opts.get('user'), opts.get('date'),
714 return repo.commit(message, opts.get('user'), opts.get('date'),
716 match, editor=e)
715 match, editor=e)
717 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
716 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
718 if not newnode:
717 if not newnode:
719 ui.status(_("nothing changed\n"))
718 ui.status(_("nothing changed\n"))
720 return 1
719 return 1
721 cmdutil.commitstatus(repo, newnode, branch, bheads)
720 cmdutil.commitstatus(repo, newnode, branch, bheads)
722
721
723 def nice(node):
722 def nice(node):
724 return '%d:%s' % (repo.changelog.rev(node), short(node))
723 return '%d:%s' % (repo.changelog.rev(node), short(node))
725 ui.status(_('changeset %s backs out changeset %s\n') %
724 ui.status(_('changeset %s backs out changeset %s\n') %
726 (nice(repo.changelog.tip()), nice(node)))
725 (nice(repo.changelog.tip()), nice(node)))
727 if opts.get('merge') and op1 != node:
726 if opts.get('merge') and op1 != node:
728 hg.clean(repo, op1, show_stats=False)
727 hg.clean(repo, op1, show_stats=False)
729 ui.status(_('merging with changeset %s\n')
728 ui.status(_('merging with changeset %s\n')
730 % nice(repo.changelog.tip()))
729 % nice(repo.changelog.tip()))
731 try:
730 try:
732 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
731 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
733 'backout')
732 'backout')
734 return hg.merge(repo, hex(repo.changelog.tip()))
733 return hg.merge(repo, hex(repo.changelog.tip()))
735 finally:
734 finally:
736 ui.setconfig('ui', 'forcemerge', '', '')
735 ui.setconfig('ui', 'forcemerge', '', '')
737 return 0
736 return 0
738
737
739 @command('bisect',
738 @command('bisect',
740 [('r', 'reset', False, _('reset bisect state')),
739 [('r', 'reset', False, _('reset bisect state')),
741 ('g', 'good', False, _('mark changeset good')),
740 ('g', 'good', False, _('mark changeset good')),
742 ('b', 'bad', False, _('mark changeset bad')),
741 ('b', 'bad', False, _('mark changeset bad')),
743 ('s', 'skip', False, _('skip testing changeset')),
742 ('s', 'skip', False, _('skip testing changeset')),
744 ('e', 'extend', False, _('extend the bisect range')),
743 ('e', 'extend', False, _('extend the bisect range')),
745 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
744 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
746 ('U', 'noupdate', False, _('do not update to target'))],
745 ('U', 'noupdate', False, _('do not update to target'))],
747 _("[-gbsr] [-U] [-c CMD] [REV]"))
746 _("[-gbsr] [-U] [-c CMD] [REV]"))
748 def bisect(ui, repo, rev=None, extra=None, command=None,
747 def bisect(ui, repo, rev=None, extra=None, command=None,
749 reset=None, good=None, bad=None, skip=None, extend=None,
748 reset=None, good=None, bad=None, skip=None, extend=None,
750 noupdate=None):
749 noupdate=None):
751 """subdivision search of changesets
750 """subdivision search of changesets
752
751
753 This command helps to find changesets which introduce problems. To
752 This command helps to find changesets which introduce problems. To
754 use, mark the earliest changeset you know exhibits the problem as
753 use, mark the earliest changeset you know exhibits the problem as
755 bad, then mark the latest changeset which is free from the problem
754 bad, then mark the latest changeset which is free from the problem
756 as good. Bisect will update your working directory to a revision
755 as good. Bisect will update your working directory to a revision
757 for testing (unless the -U/--noupdate option is specified). Once
756 for testing (unless the -U/--noupdate option is specified). Once
758 you have performed tests, mark the working directory as good or
757 you have performed tests, mark the working directory as good or
759 bad, and bisect will either update to another candidate changeset
758 bad, and bisect will either update to another candidate changeset
760 or announce that it has found the bad revision.
759 or announce that it has found the bad revision.
761
760
762 As a shortcut, you can also use the revision argument to mark a
761 As a shortcut, you can also use the revision argument to mark a
763 revision as good or bad without checking it out first.
762 revision as good or bad without checking it out first.
764
763
765 If you supply a command, it will be used for automatic bisection.
764 If you supply a command, it will be used for automatic bisection.
766 The environment variable HG_NODE will contain the ID of the
765 The environment variable HG_NODE will contain the ID of the
767 changeset being tested. The exit status of the command will be
766 changeset being tested. The exit status of the command will be
768 used to mark revisions as good or bad: status 0 means good, 125
767 used to mark revisions as good or bad: status 0 means good, 125
769 means to skip the revision, 127 (command not found) will abort the
768 means to skip the revision, 127 (command not found) will abort the
770 bisection, and any other non-zero exit status means the revision
769 bisection, and any other non-zero exit status means the revision
771 is bad.
770 is bad.
772
771
773 .. container:: verbose
772 .. container:: verbose
774
773
775 Some examples:
774 Some examples:
776
775
777 - start a bisection with known bad revision 34, and good revision 12::
776 - start a bisection with known bad revision 34, and good revision 12::
778
777
779 hg bisect --bad 34
778 hg bisect --bad 34
780 hg bisect --good 12
779 hg bisect --good 12
781
780
782 - advance the current bisection by marking current revision as good or
781 - advance the current bisection by marking current revision as good or
783 bad::
782 bad::
784
783
785 hg bisect --good
784 hg bisect --good
786 hg bisect --bad
785 hg bisect --bad
787
786
788 - mark the current revision, or a known revision, to be skipped (e.g. if
787 - mark the current revision, or a known revision, to be skipped (e.g. if
789 that revision is not usable because of another issue)::
788 that revision is not usable because of another issue)::
790
789
791 hg bisect --skip
790 hg bisect --skip
792 hg bisect --skip 23
791 hg bisect --skip 23
793
792
794 - skip all revisions that do not touch directories ``foo`` or ``bar``::
793 - skip all revisions that do not touch directories ``foo`` or ``bar``::
795
794
796 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
795 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
797
796
798 - forget the current bisection::
797 - forget the current bisection::
799
798
800 hg bisect --reset
799 hg bisect --reset
801
800
802 - use 'make && make tests' to automatically find the first broken
801 - use 'make && make tests' to automatically find the first broken
803 revision::
802 revision::
804
803
805 hg bisect --reset
804 hg bisect --reset
806 hg bisect --bad 34
805 hg bisect --bad 34
807 hg bisect --good 12
806 hg bisect --good 12
808 hg bisect --command "make && make tests"
807 hg bisect --command "make && make tests"
809
808
810 - see all changesets whose states are already known in the current
809 - see all changesets whose states are already known in the current
811 bisection::
810 bisection::
812
811
813 hg log -r "bisect(pruned)"
812 hg log -r "bisect(pruned)"
814
813
815 - see the changeset currently being bisected (especially useful
814 - see the changeset currently being bisected (especially useful
816 if running with -U/--noupdate)::
815 if running with -U/--noupdate)::
817
816
818 hg log -r "bisect(current)"
817 hg log -r "bisect(current)"
819
818
820 - see all changesets that took part in the current bisection::
819 - see all changesets that took part in the current bisection::
821
820
822 hg log -r "bisect(range)"
821 hg log -r "bisect(range)"
823
822
824 - you can even get a nice graph::
823 - you can even get a nice graph::
825
824
826 hg log --graph -r "bisect(range)"
825 hg log --graph -r "bisect(range)"
827
826
828 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
827 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
829
828
830 Returns 0 on success.
829 Returns 0 on success.
831 """
830 """
832 # backward compatibility
831 # backward compatibility
833 if rev in "good bad reset init".split():
832 if rev in "good bad reset init".split():
834 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
833 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
835 cmd, rev, extra = rev, extra, None
834 cmd, rev, extra = rev, extra, None
836 if cmd == "good":
835 if cmd == "good":
837 good = True
836 good = True
838 elif cmd == "bad":
837 elif cmd == "bad":
839 bad = True
838 bad = True
840 else:
839 else:
841 reset = True
840 reset = True
842 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
841 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
843 raise error.Abort(_('incompatible arguments'))
842 raise error.Abort(_('incompatible arguments'))
844
843
845 if reset:
844 if reset:
846 hbisect.resetstate(repo)
845 hbisect.resetstate(repo)
847 return
846 return
848
847
849 state = hbisect.load_state(repo)
848 state = hbisect.load_state(repo)
850
849
851 # update state
850 # update state
852 if good or bad or skip:
851 if good or bad or skip:
853 if rev:
852 if rev:
854 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
853 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
855 else:
854 else:
856 nodes = [repo.lookup('.')]
855 nodes = [repo.lookup('.')]
857 if good:
856 if good:
858 state['good'] += nodes
857 state['good'] += nodes
859 elif bad:
858 elif bad:
860 state['bad'] += nodes
859 state['bad'] += nodes
861 elif skip:
860 elif skip:
862 state['skip'] += nodes
861 state['skip'] += nodes
863 hbisect.save_state(repo, state)
862 hbisect.save_state(repo, state)
864 if not (state['good'] and state['bad']):
863 if not (state['good'] and state['bad']):
865 return
864 return
866
865
867 def mayupdate(repo, node, show_stats=True):
866 def mayupdate(repo, node, show_stats=True):
868 """common used update sequence"""
867 """common used update sequence"""
869 if noupdate:
868 if noupdate:
870 return
869 return
871 cmdutil.checkunfinished(repo)
870 cmdutil.checkunfinished(repo)
872 cmdutil.bailifchanged(repo)
871 cmdutil.bailifchanged(repo)
873 return hg.clean(repo, node, show_stats=show_stats)
872 return hg.clean(repo, node, show_stats=show_stats)
874
873
875 displayer = cmdutil.show_changeset(ui, repo, {})
874 displayer = cmdutil.show_changeset(ui, repo, {})
876
875
877 if command:
876 if command:
878 changesets = 1
877 changesets = 1
879 if noupdate:
878 if noupdate:
880 try:
879 try:
881 node = state['current'][0]
880 node = state['current'][0]
882 except LookupError:
881 except LookupError:
883 raise error.Abort(_('current bisect revision is unknown - '
882 raise error.Abort(_('current bisect revision is unknown - '
884 'start a new bisect to fix'))
883 'start a new bisect to fix'))
885 else:
884 else:
886 node, p2 = repo.dirstate.parents()
885 node, p2 = repo.dirstate.parents()
887 if p2 != nullid:
886 if p2 != nullid:
888 raise error.Abort(_('current bisect revision is a merge'))
887 raise error.Abort(_('current bisect revision is a merge'))
889 if rev:
888 if rev:
890 node = repo[scmutil.revsingle(repo, rev, node)].node()
889 node = repo[scmutil.revsingle(repo, rev, node)].node()
891 try:
890 try:
892 while changesets:
891 while changesets:
893 # update state
892 # update state
894 state['current'] = [node]
893 state['current'] = [node]
895 hbisect.save_state(repo, state)
894 hbisect.save_state(repo, state)
896 status = ui.system(command, environ={'HG_NODE': hex(node)},
895 status = ui.system(command, environ={'HG_NODE': hex(node)},
897 blockedtag='bisect_check')
896 blockedtag='bisect_check')
898 if status == 125:
897 if status == 125:
899 transition = "skip"
898 transition = "skip"
900 elif status == 0:
899 elif status == 0:
901 transition = "good"
900 transition = "good"
902 # status < 0 means process was killed
901 # status < 0 means process was killed
903 elif status == 127:
902 elif status == 127:
904 raise error.Abort(_("failed to execute %s") % command)
903 raise error.Abort(_("failed to execute %s") % command)
905 elif status < 0:
904 elif status < 0:
906 raise error.Abort(_("%s killed") % command)
905 raise error.Abort(_("%s killed") % command)
907 else:
906 else:
908 transition = "bad"
907 transition = "bad"
909 state[transition].append(node)
908 state[transition].append(node)
910 ctx = repo[node]
909 ctx = repo[node]
911 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
910 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
912 hbisect.checkstate(state)
911 hbisect.checkstate(state)
913 # bisect
912 # bisect
914 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
913 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
915 # update to next check
914 # update to next check
916 node = nodes[0]
915 node = nodes[0]
917 mayupdate(repo, node, show_stats=False)
916 mayupdate(repo, node, show_stats=False)
918 finally:
917 finally:
919 state['current'] = [node]
918 state['current'] = [node]
920 hbisect.save_state(repo, state)
919 hbisect.save_state(repo, state)
921 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
920 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
922 return
921 return
923
922
924 hbisect.checkstate(state)
923 hbisect.checkstate(state)
925
924
926 # actually bisect
925 # actually bisect
927 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
926 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
928 if extend:
927 if extend:
929 if not changesets:
928 if not changesets:
930 extendnode = hbisect.extendrange(repo, state, nodes, good)
929 extendnode = hbisect.extendrange(repo, state, nodes, good)
931 if extendnode is not None:
930 if extendnode is not None:
932 ui.write(_("Extending search to changeset %d:%s\n")
931 ui.write(_("Extending search to changeset %d:%s\n")
933 % (extendnode.rev(), extendnode))
932 % (extendnode.rev(), extendnode))
934 state['current'] = [extendnode.node()]
933 state['current'] = [extendnode.node()]
935 hbisect.save_state(repo, state)
934 hbisect.save_state(repo, state)
936 return mayupdate(repo, extendnode.node())
935 return mayupdate(repo, extendnode.node())
937 raise error.Abort(_("nothing to extend"))
936 raise error.Abort(_("nothing to extend"))
938
937
939 if changesets == 0:
938 if changesets == 0:
940 hbisect.printresult(ui, repo, state, displayer, nodes, good)
939 hbisect.printresult(ui, repo, state, displayer, nodes, good)
941 else:
940 else:
942 assert len(nodes) == 1 # only a single node can be tested next
941 assert len(nodes) == 1 # only a single node can be tested next
943 node = nodes[0]
942 node = nodes[0]
944 # compute the approximate number of remaining tests
943 # compute the approximate number of remaining tests
945 tests, size = 0, 2
944 tests, size = 0, 2
946 while size <= changesets:
945 while size <= changesets:
947 tests, size = tests + 1, size * 2
946 tests, size = tests + 1, size * 2
948 rev = repo.changelog.rev(node)
947 rev = repo.changelog.rev(node)
949 ui.write(_("Testing changeset %d:%s "
948 ui.write(_("Testing changeset %d:%s "
950 "(%d changesets remaining, ~%d tests)\n")
949 "(%d changesets remaining, ~%d tests)\n")
951 % (rev, short(node), changesets, tests))
950 % (rev, short(node), changesets, tests))
952 state['current'] = [node]
951 state['current'] = [node]
953 hbisect.save_state(repo, state)
952 hbisect.save_state(repo, state)
954 return mayupdate(repo, node)
953 return mayupdate(repo, node)
955
954
956 @command('bookmarks|bookmark',
955 @command('bookmarks|bookmark',
957 [('f', 'force', False, _('force')),
956 [('f', 'force', False, _('force')),
958 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
957 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
959 ('d', 'delete', False, _('delete a given bookmark')),
958 ('d', 'delete', False, _('delete a given bookmark')),
960 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
959 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
961 ('i', 'inactive', False, _('mark a bookmark inactive')),
960 ('i', 'inactive', False, _('mark a bookmark inactive')),
962 ] + formatteropts,
961 ] + formatteropts,
963 _('hg bookmarks [OPTIONS]... [NAME]...'))
962 _('hg bookmarks [OPTIONS]... [NAME]...'))
964 def bookmark(ui, repo, *names, **opts):
963 def bookmark(ui, repo, *names, **opts):
965 '''create a new bookmark or list existing bookmarks
964 '''create a new bookmark or list existing bookmarks
966
965
967 Bookmarks are labels on changesets to help track lines of development.
966 Bookmarks are labels on changesets to help track lines of development.
968 Bookmarks are unversioned and can be moved, renamed and deleted.
967 Bookmarks are unversioned and can be moved, renamed and deleted.
969 Deleting or moving a bookmark has no effect on the associated changesets.
968 Deleting or moving a bookmark has no effect on the associated changesets.
970
969
971 Creating or updating to a bookmark causes it to be marked as 'active'.
970 Creating or updating to a bookmark causes it to be marked as 'active'.
972 The active bookmark is indicated with a '*'.
971 The active bookmark is indicated with a '*'.
973 When a commit is made, the active bookmark will advance to the new commit.
972 When a commit is made, the active bookmark will advance to the new commit.
974 A plain :hg:`update` will also advance an active bookmark, if possible.
973 A plain :hg:`update` will also advance an active bookmark, if possible.
975 Updating away from a bookmark will cause it to be deactivated.
974 Updating away from a bookmark will cause it to be deactivated.
976
975
977 Bookmarks can be pushed and pulled between repositories (see
976 Bookmarks can be pushed and pulled between repositories (see
978 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
977 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
979 diverged, a new 'divergent bookmark' of the form 'name@path' will
978 diverged, a new 'divergent bookmark' of the form 'name@path' will
980 be created. Using :hg:`merge` will resolve the divergence.
979 be created. Using :hg:`merge` will resolve the divergence.
981
980
982 A bookmark named '@' has the special property that :hg:`clone` will
981 A bookmark named '@' has the special property that :hg:`clone` will
983 check it out by default if it exists.
982 check it out by default if it exists.
984
983
985 .. container:: verbose
984 .. container:: verbose
986
985
987 Examples:
986 Examples:
988
987
989 - create an active bookmark for a new line of development::
988 - create an active bookmark for a new line of development::
990
989
991 hg book new-feature
990 hg book new-feature
992
991
993 - create an inactive bookmark as a place marker::
992 - create an inactive bookmark as a place marker::
994
993
995 hg book -i reviewed
994 hg book -i reviewed
996
995
997 - create an inactive bookmark on another changeset::
996 - create an inactive bookmark on another changeset::
998
997
999 hg book -r .^ tested
998 hg book -r .^ tested
1000
999
1001 - rename bookmark turkey to dinner::
1000 - rename bookmark turkey to dinner::
1002
1001
1003 hg book -m turkey dinner
1002 hg book -m turkey dinner
1004
1003
1005 - move the '@' bookmark from another branch::
1004 - move the '@' bookmark from another branch::
1006
1005
1007 hg book -f @
1006 hg book -f @
1008 '''
1007 '''
1009 opts = pycompat.byteskwargs(opts)
1008 opts = pycompat.byteskwargs(opts)
1010 force = opts.get('force')
1009 force = opts.get('force')
1011 rev = opts.get('rev')
1010 rev = opts.get('rev')
1012 delete = opts.get('delete')
1011 delete = opts.get('delete')
1013 rename = opts.get('rename')
1012 rename = opts.get('rename')
1014 inactive = opts.get('inactive')
1013 inactive = opts.get('inactive')
1015
1014
1016 def checkformat(mark):
1015 def checkformat(mark):
1017 mark = mark.strip()
1016 mark = mark.strip()
1018 if not mark:
1017 if not mark:
1019 raise error.Abort(_("bookmark names cannot consist entirely of "
1018 raise error.Abort(_("bookmark names cannot consist entirely of "
1020 "whitespace"))
1019 "whitespace"))
1021 scmutil.checknewlabel(repo, mark, 'bookmark')
1020 scmutil.checknewlabel(repo, mark, 'bookmark')
1022 return mark
1021 return mark
1023
1022
1024 def checkconflict(repo, mark, cur, force=False, target=None):
1023 def checkconflict(repo, mark, cur, force=False, target=None):
1025 if mark in marks and not force:
1024 if mark in marks and not force:
1026 if target:
1025 if target:
1027 if marks[mark] == target and target == cur:
1026 if marks[mark] == target and target == cur:
1028 # re-activating a bookmark
1027 # re-activating a bookmark
1029 return
1028 return
1030 anc = repo.changelog.ancestors([repo[target].rev()])
1029 anc = repo.changelog.ancestors([repo[target].rev()])
1031 bmctx = repo[marks[mark]]
1030 bmctx = repo[marks[mark]]
1032 divs = [repo[b].node() for b in marks
1031 divs = [repo[b].node() for b in marks
1033 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1032 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
1034
1033
1035 # allow resolving a single divergent bookmark even if moving
1034 # allow resolving a single divergent bookmark even if moving
1036 # the bookmark across branches when a revision is specified
1035 # the bookmark across branches when a revision is specified
1037 # that contains a divergent bookmark
1036 # that contains a divergent bookmark
1038 if bmctx.rev() not in anc and target in divs:
1037 if bmctx.rev() not in anc and target in divs:
1039 bookmarks.deletedivergent(repo, [target], mark)
1038 bookmarks.deletedivergent(repo, [target], mark)
1040 return
1039 return
1041
1040
1042 deletefrom = [b for b in divs
1041 deletefrom = [b for b in divs
1043 if repo[b].rev() in anc or b == target]
1042 if repo[b].rev() in anc or b == target]
1044 bookmarks.deletedivergent(repo, deletefrom, mark)
1043 bookmarks.deletedivergent(repo, deletefrom, mark)
1045 if bookmarks.validdest(repo, bmctx, repo[target]):
1044 if bookmarks.validdest(repo, bmctx, repo[target]):
1046 ui.status(_("moving bookmark '%s' forward from %s\n") %
1045 ui.status(_("moving bookmark '%s' forward from %s\n") %
1047 (mark, short(bmctx.node())))
1046 (mark, short(bmctx.node())))
1048 return
1047 return
1049 raise error.Abort(_("bookmark '%s' already exists "
1048 raise error.Abort(_("bookmark '%s' already exists "
1050 "(use -f to force)") % mark)
1049 "(use -f to force)") % mark)
1051 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1050 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
1052 and not force):
1051 and not force):
1053 raise error.Abort(
1052 raise error.Abort(
1054 _("a bookmark cannot have the name of an existing branch"))
1053 _("a bookmark cannot have the name of an existing branch"))
1055
1054
1056 if delete and rename:
1055 if delete and rename:
1057 raise error.Abort(_("--delete and --rename are incompatible"))
1056 raise error.Abort(_("--delete and --rename are incompatible"))
1058 if delete and rev:
1057 if delete and rev:
1059 raise error.Abort(_("--rev is incompatible with --delete"))
1058 raise error.Abort(_("--rev is incompatible with --delete"))
1060 if rename and rev:
1059 if rename and rev:
1061 raise error.Abort(_("--rev is incompatible with --rename"))
1060 raise error.Abort(_("--rev is incompatible with --rename"))
1062 if not names and (delete or rev):
1061 if not names and (delete or rev):
1063 raise error.Abort(_("bookmark name required"))
1062 raise error.Abort(_("bookmark name required"))
1064
1063
1065 if delete or rename or names or inactive:
1064 if delete or rename or names or inactive:
1066 wlock = lock = tr = None
1065 wlock = lock = tr = None
1067 try:
1066 try:
1068 wlock = repo.wlock()
1067 wlock = repo.wlock()
1069 lock = repo.lock()
1068 lock = repo.lock()
1070 cur = repo.changectx('.').node()
1069 cur = repo.changectx('.').node()
1071 marks = repo._bookmarks
1070 marks = repo._bookmarks
1072 if delete:
1071 if delete:
1073 tr = repo.transaction('bookmark')
1072 tr = repo.transaction('bookmark')
1074 for mark in names:
1073 for mark in names:
1075 if mark not in marks:
1074 if mark not in marks:
1076 raise error.Abort(_("bookmark '%s' does not exist") %
1075 raise error.Abort(_("bookmark '%s' does not exist") %
1077 mark)
1076 mark)
1078 if mark == repo._activebookmark:
1077 if mark == repo._activebookmark:
1079 bookmarks.deactivate(repo)
1078 bookmarks.deactivate(repo)
1080 del marks[mark]
1079 del marks[mark]
1081
1080
1082 elif rename:
1081 elif rename:
1083 tr = repo.transaction('bookmark')
1082 tr = repo.transaction('bookmark')
1084 if not names:
1083 if not names:
1085 raise error.Abort(_("new bookmark name required"))
1084 raise error.Abort(_("new bookmark name required"))
1086 elif len(names) > 1:
1085 elif len(names) > 1:
1087 raise error.Abort(_("only one new bookmark name allowed"))
1086 raise error.Abort(_("only one new bookmark name allowed"))
1088 mark = checkformat(names[0])
1087 mark = checkformat(names[0])
1089 if rename not in marks:
1088 if rename not in marks:
1090 raise error.Abort(_("bookmark '%s' does not exist")
1089 raise error.Abort(_("bookmark '%s' does not exist")
1091 % rename)
1090 % rename)
1092 checkconflict(repo, mark, cur, force)
1091 checkconflict(repo, mark, cur, force)
1093 marks[mark] = marks[rename]
1092 marks[mark] = marks[rename]
1094 if repo._activebookmark == rename and not inactive:
1093 if repo._activebookmark == rename and not inactive:
1095 bookmarks.activate(repo, mark)
1094 bookmarks.activate(repo, mark)
1096 del marks[rename]
1095 del marks[rename]
1097 elif names:
1096 elif names:
1098 tr = repo.transaction('bookmark')
1097 tr = repo.transaction('bookmark')
1099 newact = None
1098 newact = None
1100 for mark in names:
1099 for mark in names:
1101 mark = checkformat(mark)
1100 mark = checkformat(mark)
1102 if newact is None:
1101 if newact is None:
1103 newact = mark
1102 newact = mark
1104 if inactive and mark == repo._activebookmark:
1103 if inactive and mark == repo._activebookmark:
1105 bookmarks.deactivate(repo)
1104 bookmarks.deactivate(repo)
1106 return
1105 return
1107 tgt = cur
1106 tgt = cur
1108 if rev:
1107 if rev:
1109 tgt = scmutil.revsingle(repo, rev).node()
1108 tgt = scmutil.revsingle(repo, rev).node()
1110 checkconflict(repo, mark, cur, force, tgt)
1109 checkconflict(repo, mark, cur, force, tgt)
1111 marks[mark] = tgt
1110 marks[mark] = tgt
1112 if not inactive and cur == marks[newact] and not rev:
1111 if not inactive and cur == marks[newact] and not rev:
1113 bookmarks.activate(repo, newact)
1112 bookmarks.activate(repo, newact)
1114 elif cur != tgt and newact == repo._activebookmark:
1113 elif cur != tgt and newact == repo._activebookmark:
1115 bookmarks.deactivate(repo)
1114 bookmarks.deactivate(repo)
1116 elif inactive:
1115 elif inactive:
1117 if len(marks) == 0:
1116 if len(marks) == 0:
1118 ui.status(_("no bookmarks set\n"))
1117 ui.status(_("no bookmarks set\n"))
1119 elif not repo._activebookmark:
1118 elif not repo._activebookmark:
1120 ui.status(_("no active bookmark\n"))
1119 ui.status(_("no active bookmark\n"))
1121 else:
1120 else:
1122 bookmarks.deactivate(repo)
1121 bookmarks.deactivate(repo)
1123 if tr is not None:
1122 if tr is not None:
1124 marks.recordchange(tr)
1123 marks.recordchange(tr)
1125 tr.close()
1124 tr.close()
1126 finally:
1125 finally:
1127 lockmod.release(tr, lock, wlock)
1126 lockmod.release(tr, lock, wlock)
1128 else: # show bookmarks
1127 else: # show bookmarks
1129 fm = ui.formatter('bookmarks', opts)
1128 fm = ui.formatter('bookmarks', opts)
1130 hexfn = fm.hexfunc
1129 hexfn = fm.hexfunc
1131 marks = repo._bookmarks
1130 marks = repo._bookmarks
1132 if len(marks) == 0 and fm.isplain():
1131 if len(marks) == 0 and fm.isplain():
1133 ui.status(_("no bookmarks set\n"))
1132 ui.status(_("no bookmarks set\n"))
1134 for bmark, n in sorted(marks.iteritems()):
1133 for bmark, n in sorted(marks.iteritems()):
1135 active = repo._activebookmark
1134 active = repo._activebookmark
1136 if bmark == active:
1135 if bmark == active:
1137 prefix, label = '*', activebookmarklabel
1136 prefix, label = '*', activebookmarklabel
1138 else:
1137 else:
1139 prefix, label = ' ', ''
1138 prefix, label = ' ', ''
1140
1139
1141 fm.startitem()
1140 fm.startitem()
1142 if not ui.quiet:
1141 if not ui.quiet:
1143 fm.plain(' %s ' % prefix, label=label)
1142 fm.plain(' %s ' % prefix, label=label)
1144 fm.write('bookmark', '%s', bmark, label=label)
1143 fm.write('bookmark', '%s', bmark, label=label)
1145 pad = " " * (25 - encoding.colwidth(bmark))
1144 pad = " " * (25 - encoding.colwidth(bmark))
1146 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1145 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1147 repo.changelog.rev(n), hexfn(n), label=label)
1146 repo.changelog.rev(n), hexfn(n), label=label)
1148 fm.data(active=(bmark == active))
1147 fm.data(active=(bmark == active))
1149 fm.plain('\n')
1148 fm.plain('\n')
1150 fm.end()
1149 fm.end()
1151
1150
1152 @command('branch',
1151 @command('branch',
1153 [('f', 'force', None,
1152 [('f', 'force', None,
1154 _('set branch name even if it shadows an existing branch')),
1153 _('set branch name even if it shadows an existing branch')),
1155 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1154 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1156 _('[-fC] [NAME]'))
1155 _('[-fC] [NAME]'))
1157 def branch(ui, repo, label=None, **opts):
1156 def branch(ui, repo, label=None, **opts):
1158 """set or show the current branch name
1157 """set or show the current branch name
1159
1158
1160 .. note::
1159 .. note::
1161
1160
1162 Branch names are permanent and global. Use :hg:`bookmark` to create a
1161 Branch names are permanent and global. Use :hg:`bookmark` to create a
1163 light-weight bookmark instead. See :hg:`help glossary` for more
1162 light-weight bookmark instead. See :hg:`help glossary` for more
1164 information about named branches and bookmarks.
1163 information about named branches and bookmarks.
1165
1164
1166 With no argument, show the current branch name. With one argument,
1165 With no argument, show the current branch name. With one argument,
1167 set the working directory branch name (the branch will not exist
1166 set the working directory branch name (the branch will not exist
1168 in the repository until the next commit). Standard practice
1167 in the repository until the next commit). Standard practice
1169 recommends that primary development take place on the 'default'
1168 recommends that primary development take place on the 'default'
1170 branch.
1169 branch.
1171
1170
1172 Unless -f/--force is specified, branch will not let you set a
1171 Unless -f/--force is specified, branch will not let you set a
1173 branch name that already exists.
1172 branch name that already exists.
1174
1173
1175 Use -C/--clean to reset the working directory branch to that of
1174 Use -C/--clean to reset the working directory branch to that of
1176 the parent of the working directory, negating a previous branch
1175 the parent of the working directory, negating a previous branch
1177 change.
1176 change.
1178
1177
1179 Use the command :hg:`update` to switch to an existing branch. Use
1178 Use the command :hg:`update` to switch to an existing branch. Use
1180 :hg:`commit --close-branch` to mark this branch head as closed.
1179 :hg:`commit --close-branch` to mark this branch head as closed.
1181 When all heads of a branch are closed, the branch will be
1180 When all heads of a branch are closed, the branch will be
1182 considered closed.
1181 considered closed.
1183
1182
1184 Returns 0 on success.
1183 Returns 0 on success.
1185 """
1184 """
1186 opts = pycompat.byteskwargs(opts)
1185 opts = pycompat.byteskwargs(opts)
1187 if label:
1186 if label:
1188 label = label.strip()
1187 label = label.strip()
1189
1188
1190 if not opts.get('clean') and not label:
1189 if not opts.get('clean') and not label:
1191 ui.write("%s\n" % repo.dirstate.branch())
1190 ui.write("%s\n" % repo.dirstate.branch())
1192 return
1191 return
1193
1192
1194 with repo.wlock():
1193 with repo.wlock():
1195 if opts.get('clean'):
1194 if opts.get('clean'):
1196 label = repo[None].p1().branch()
1195 label = repo[None].p1().branch()
1197 repo.dirstate.setbranch(label)
1196 repo.dirstate.setbranch(label)
1198 ui.status(_('reset working directory to branch %s\n') % label)
1197 ui.status(_('reset working directory to branch %s\n') % label)
1199 elif label:
1198 elif label:
1200 if not opts.get('force') and label in repo.branchmap():
1199 if not opts.get('force') and label in repo.branchmap():
1201 if label not in [p.branch() for p in repo[None].parents()]:
1200 if label not in [p.branch() for p in repo[None].parents()]:
1202 raise error.Abort(_('a branch of the same name already'
1201 raise error.Abort(_('a branch of the same name already'
1203 ' exists'),
1202 ' exists'),
1204 # i18n: "it" refers to an existing branch
1203 # i18n: "it" refers to an existing branch
1205 hint=_("use 'hg update' to switch to it"))
1204 hint=_("use 'hg update' to switch to it"))
1206 scmutil.checknewlabel(repo, label, 'branch')
1205 scmutil.checknewlabel(repo, label, 'branch')
1207 repo.dirstate.setbranch(label)
1206 repo.dirstate.setbranch(label)
1208 ui.status(_('marked working directory as branch %s\n') % label)
1207 ui.status(_('marked working directory as branch %s\n') % label)
1209
1208
1210 # find any open named branches aside from default
1209 # find any open named branches aside from default
1211 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1210 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1212 if n != "default" and not c]
1211 if n != "default" and not c]
1213 if not others:
1212 if not others:
1214 ui.status(_('(branches are permanent and global, '
1213 ui.status(_('(branches are permanent and global, '
1215 'did you want a bookmark?)\n'))
1214 'did you want a bookmark?)\n'))
1216
1215
1217 @command('branches',
1216 @command('branches',
1218 [('a', 'active', False,
1217 [('a', 'active', False,
1219 _('show only branches that have unmerged heads (DEPRECATED)')),
1218 _('show only branches that have unmerged heads (DEPRECATED)')),
1220 ('c', 'closed', False, _('show normal and closed branches')),
1219 ('c', 'closed', False, _('show normal and closed branches')),
1221 ] + formatteropts,
1220 ] + formatteropts,
1222 _('[-c]'))
1221 _('[-c]'))
1223 def branches(ui, repo, active=False, closed=False, **opts):
1222 def branches(ui, repo, active=False, closed=False, **opts):
1224 """list repository named branches
1223 """list repository named branches
1225
1224
1226 List the repository's named branches, indicating which ones are
1225 List the repository's named branches, indicating which ones are
1227 inactive. If -c/--closed is specified, also list branches which have
1226 inactive. If -c/--closed is specified, also list branches which have
1228 been marked closed (see :hg:`commit --close-branch`).
1227 been marked closed (see :hg:`commit --close-branch`).
1229
1228
1230 Use the command :hg:`update` to switch to an existing branch.
1229 Use the command :hg:`update` to switch to an existing branch.
1231
1230
1232 Returns 0.
1231 Returns 0.
1233 """
1232 """
1234
1233
1235 opts = pycompat.byteskwargs(opts)
1234 opts = pycompat.byteskwargs(opts)
1236 ui.pager('branches')
1235 ui.pager('branches')
1237 fm = ui.formatter('branches', opts)
1236 fm = ui.formatter('branches', opts)
1238 hexfunc = fm.hexfunc
1237 hexfunc = fm.hexfunc
1239
1238
1240 allheads = set(repo.heads())
1239 allheads = set(repo.heads())
1241 branches = []
1240 branches = []
1242 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1241 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1243 isactive = not isclosed and bool(set(heads) & allheads)
1242 isactive = not isclosed and bool(set(heads) & allheads)
1244 branches.append((tag, repo[tip], isactive, not isclosed))
1243 branches.append((tag, repo[tip], isactive, not isclosed))
1245 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1244 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1246 reverse=True)
1245 reverse=True)
1247
1246
1248 for tag, ctx, isactive, isopen in branches:
1247 for tag, ctx, isactive, isopen in branches:
1249 if active and not isactive:
1248 if active and not isactive:
1250 continue
1249 continue
1251 if isactive:
1250 if isactive:
1252 label = 'branches.active'
1251 label = 'branches.active'
1253 notice = ''
1252 notice = ''
1254 elif not isopen:
1253 elif not isopen:
1255 if not closed:
1254 if not closed:
1256 continue
1255 continue
1257 label = 'branches.closed'
1256 label = 'branches.closed'
1258 notice = _(' (closed)')
1257 notice = _(' (closed)')
1259 else:
1258 else:
1260 label = 'branches.inactive'
1259 label = 'branches.inactive'
1261 notice = _(' (inactive)')
1260 notice = _(' (inactive)')
1262 current = (tag == repo.dirstate.branch())
1261 current = (tag == repo.dirstate.branch())
1263 if current:
1262 if current:
1264 label = 'branches.current'
1263 label = 'branches.current'
1265
1264
1266 fm.startitem()
1265 fm.startitem()
1267 fm.write('branch', '%s', tag, label=label)
1266 fm.write('branch', '%s', tag, label=label)
1268 rev = ctx.rev()
1267 rev = ctx.rev()
1269 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1268 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1270 fmt = ' ' * padsize + ' %d:%s'
1269 fmt = ' ' * padsize + ' %d:%s'
1271 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1270 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1272 label='log.changeset changeset.%s' % ctx.phasestr())
1271 label='log.changeset changeset.%s' % ctx.phasestr())
1273 fm.context(ctx=ctx)
1272 fm.context(ctx=ctx)
1274 fm.data(active=isactive, closed=not isopen, current=current)
1273 fm.data(active=isactive, closed=not isopen, current=current)
1275 if not ui.quiet:
1274 if not ui.quiet:
1276 fm.plain(notice)
1275 fm.plain(notice)
1277 fm.plain('\n')
1276 fm.plain('\n')
1278 fm.end()
1277 fm.end()
1279
1278
1280 @command('bundle',
1279 @command('bundle',
1281 [('f', 'force', None, _('run even when the destination is unrelated')),
1280 [('f', 'force', None, _('run even when the destination is unrelated')),
1282 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1281 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1283 _('REV')),
1282 _('REV')),
1284 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1283 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1285 _('BRANCH')),
1284 _('BRANCH')),
1286 ('', 'base', [],
1285 ('', 'base', [],
1287 _('a base changeset assumed to be available at the destination'),
1286 _('a base changeset assumed to be available at the destination'),
1288 _('REV')),
1287 _('REV')),
1289 ('a', 'all', None, _('bundle all changesets in the repository')),
1288 ('a', 'all', None, _('bundle all changesets in the repository')),
1290 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1289 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1291 ] + remoteopts,
1290 ] + remoteopts,
1292 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1291 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1293 def bundle(ui, repo, fname, dest=None, **opts):
1292 def bundle(ui, repo, fname, dest=None, **opts):
1294 """create a bundle file
1293 """create a bundle file
1295
1294
1296 Generate a bundle file containing data to be added to a repository.
1295 Generate a bundle file containing data to be added to a repository.
1297
1296
1298 To create a bundle containing all changesets, use -a/--all
1297 To create a bundle containing all changesets, use -a/--all
1299 (or --base null). Otherwise, hg assumes the destination will have
1298 (or --base null). Otherwise, hg assumes the destination will have
1300 all the nodes you specify with --base parameters. Otherwise, hg
1299 all the nodes you specify with --base parameters. Otherwise, hg
1301 will assume the repository has all the nodes in destination, or
1300 will assume the repository has all the nodes in destination, or
1302 default-push/default if no destination is specified.
1301 default-push/default if no destination is specified.
1303
1302
1304 You can change bundle format with the -t/--type option. See
1303 You can change bundle format with the -t/--type option. See
1305 :hg:`help bundlespec` for documentation on this format. By default,
1304 :hg:`help bundlespec` for documentation on this format. By default,
1306 the most appropriate format is used and compression defaults to
1305 the most appropriate format is used and compression defaults to
1307 bzip2.
1306 bzip2.
1308
1307
1309 The bundle file can then be transferred using conventional means
1308 The bundle file can then be transferred using conventional means
1310 and applied to another repository with the unbundle or pull
1309 and applied to another repository with the unbundle or pull
1311 command. This is useful when direct push and pull are not
1310 command. This is useful when direct push and pull are not
1312 available or when exporting an entire repository is undesirable.
1311 available or when exporting an entire repository is undesirable.
1313
1312
1314 Applying bundles preserves all changeset contents including
1313 Applying bundles preserves all changeset contents including
1315 permissions, copy/rename information, and revision history.
1314 permissions, copy/rename information, and revision history.
1316
1315
1317 Returns 0 on success, 1 if no changes found.
1316 Returns 0 on success, 1 if no changes found.
1318 """
1317 """
1319 opts = pycompat.byteskwargs(opts)
1318 opts = pycompat.byteskwargs(opts)
1320 revs = None
1319 revs = None
1321 if 'rev' in opts:
1320 if 'rev' in opts:
1322 revstrings = opts['rev']
1321 revstrings = opts['rev']
1323 revs = scmutil.revrange(repo, revstrings)
1322 revs = scmutil.revrange(repo, revstrings)
1324 if revstrings and not revs:
1323 if revstrings and not revs:
1325 raise error.Abort(_('no commits to bundle'))
1324 raise error.Abort(_('no commits to bundle'))
1326
1325
1327 bundletype = opts.get('type', 'bzip2').lower()
1326 bundletype = opts.get('type', 'bzip2').lower()
1328 try:
1327 try:
1329 bcompression, cgversion, params = exchange.parsebundlespec(
1328 bcompression, cgversion, params = exchange.parsebundlespec(
1330 repo, bundletype, strict=False)
1329 repo, bundletype, strict=False)
1331 except error.UnsupportedBundleSpecification as e:
1330 except error.UnsupportedBundleSpecification as e:
1332 raise error.Abort(str(e),
1331 raise error.Abort(str(e),
1333 hint=_("see 'hg help bundlespec' for supported "
1332 hint=_("see 'hg help bundlespec' for supported "
1334 "values for --type"))
1333 "values for --type"))
1335
1334
1336 # Packed bundles are a pseudo bundle format for now.
1335 # Packed bundles are a pseudo bundle format for now.
1337 if cgversion == 's1':
1336 if cgversion == 's1':
1338 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1337 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1339 hint=_("use 'hg debugcreatestreamclonebundle'"))
1338 hint=_("use 'hg debugcreatestreamclonebundle'"))
1340
1339
1341 if opts.get('all'):
1340 if opts.get('all'):
1342 if dest:
1341 if dest:
1343 raise error.Abort(_("--all is incompatible with specifying "
1342 raise error.Abort(_("--all is incompatible with specifying "
1344 "a destination"))
1343 "a destination"))
1345 if opts.get('base'):
1344 if opts.get('base'):
1346 ui.warn(_("ignoring --base because --all was specified\n"))
1345 ui.warn(_("ignoring --base because --all was specified\n"))
1347 base = ['null']
1346 base = ['null']
1348 else:
1347 else:
1349 base = scmutil.revrange(repo, opts.get('base'))
1348 base = scmutil.revrange(repo, opts.get('base'))
1350 # TODO: get desired bundlecaps from command line.
1349 # TODO: get desired bundlecaps from command line.
1351 bundlecaps = None
1350 bundlecaps = None
1352 if cgversion not in changegroup.supportedoutgoingversions(repo):
1351 if cgversion not in changegroup.supportedoutgoingversions(repo):
1353 raise error.Abort(_("repository does not support bundle version %s") %
1352 raise error.Abort(_("repository does not support bundle version %s") %
1354 cgversion)
1353 cgversion)
1355
1354
1356 if base:
1355 if base:
1357 if dest:
1356 if dest:
1358 raise error.Abort(_("--base is incompatible with specifying "
1357 raise error.Abort(_("--base is incompatible with specifying "
1359 "a destination"))
1358 "a destination"))
1360 common = [repo.lookup(rev) for rev in base]
1359 common = [repo.lookup(rev) for rev in base]
1361 heads = revs and map(repo.lookup, revs) or None
1360 heads = revs and map(repo.lookup, revs) or None
1362 outgoing = discovery.outgoing(repo, common, heads)
1361 outgoing = discovery.outgoing(repo, common, heads)
1363 cg = changegroup.getchangegroup(repo, 'bundle', outgoing,
1362 cg = changegroup.getchangegroup(repo, 'bundle', outgoing,
1364 bundlecaps=bundlecaps,
1363 bundlecaps=bundlecaps,
1365 version=cgversion)
1364 version=cgversion)
1366 outgoing = None
1365 outgoing = None
1367 else:
1366 else:
1368 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1367 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1369 dest, branches = hg.parseurl(dest, opts.get('branch'))
1368 dest, branches = hg.parseurl(dest, opts.get('branch'))
1370 other = hg.peer(repo, opts, dest)
1369 other = hg.peer(repo, opts, dest)
1371 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1370 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1372 heads = revs and map(repo.lookup, revs) or revs
1371 heads = revs and map(repo.lookup, revs) or revs
1373 outgoing = discovery.findcommonoutgoing(repo, other,
1372 outgoing = discovery.findcommonoutgoing(repo, other,
1374 onlyheads=heads,
1373 onlyheads=heads,
1375 force=opts.get('force'),
1374 force=opts.get('force'),
1376 portable=True)
1375 portable=True)
1377 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1376 cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
1378 bundlecaps, version=cgversion)
1377 bundlecaps, version=cgversion)
1379 if not cg:
1378 if not cg:
1380 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1379 scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
1381 return 1
1380 return 1
1382
1381
1383 if cgversion == '01': #bundle1
1382 if cgversion == '01': #bundle1
1384 if bcompression is None:
1383 if bcompression is None:
1385 bcompression = 'UN'
1384 bcompression = 'UN'
1386 bversion = 'HG10' + bcompression
1385 bversion = 'HG10' + bcompression
1387 bcompression = None
1386 bcompression = None
1388 elif cgversion in ('02', '03'):
1387 elif cgversion in ('02', '03'):
1389 bversion = 'HG20'
1388 bversion = 'HG20'
1390 else:
1389 else:
1391 raise error.ProgrammingError(
1390 raise error.ProgrammingError(
1392 'bundle: unexpected changegroup version %s' % cgversion)
1391 'bundle: unexpected changegroup version %s' % cgversion)
1393
1392
1394 # TODO compression options should be derived from bundlespec parsing.
1393 # TODO compression options should be derived from bundlespec parsing.
1395 # This is a temporary hack to allow adjusting bundle compression
1394 # This is a temporary hack to allow adjusting bundle compression
1396 # level without a) formalizing the bundlespec changes to declare it
1395 # level without a) formalizing the bundlespec changes to declare it
1397 # b) introducing a command flag.
1396 # b) introducing a command flag.
1398 compopts = {}
1397 compopts = {}
1399 complevel = ui.configint('experimental', 'bundlecomplevel')
1398 complevel = ui.configint('experimental', 'bundlecomplevel')
1400 if complevel is not None:
1399 if complevel is not None:
1401 compopts['level'] = complevel
1400 compopts['level'] = complevel
1402
1401
1403 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression,
1402 bundle2.writebundle(ui, cg, fname, bversion, compression=bcompression,
1404 compopts=compopts)
1403 compopts=compopts)
1405
1404
1406 @command('cat',
1405 @command('cat',
1407 [('o', 'output', '',
1406 [('o', 'output', '',
1408 _('print output to file with formatted name'), _('FORMAT')),
1407 _('print output to file with formatted name'), _('FORMAT')),
1409 ('r', 'rev', '', _('print the given revision'), _('REV')),
1408 ('r', 'rev', '', _('print the given revision'), _('REV')),
1410 ('', 'decode', None, _('apply any matching decode filter')),
1409 ('', 'decode', None, _('apply any matching decode filter')),
1411 ] + walkopts,
1410 ] + walkopts,
1412 _('[OPTION]... FILE...'),
1411 _('[OPTION]... FILE...'),
1413 inferrepo=True)
1412 inferrepo=True)
1414 def cat(ui, repo, file1, *pats, **opts):
1413 def cat(ui, repo, file1, *pats, **opts):
1415 """output the current or given revision of files
1414 """output the current or given revision of files
1416
1415
1417 Print the specified files as they were at the given revision. If
1416 Print the specified files as they were at the given revision. If
1418 no revision is given, the parent of the working directory is used.
1417 no revision is given, the parent of the working directory is used.
1419
1418
1420 Output may be to a file, in which case the name of the file is
1419 Output may be to a file, in which case the name of the file is
1421 given using a format string. The formatting rules as follows:
1420 given using a format string. The formatting rules as follows:
1422
1421
1423 :``%%``: literal "%" character
1422 :``%%``: literal "%" character
1424 :``%s``: basename of file being printed
1423 :``%s``: basename of file being printed
1425 :``%d``: dirname of file being printed, or '.' if in repository root
1424 :``%d``: dirname of file being printed, or '.' if in repository root
1426 :``%p``: root-relative path name of file being printed
1425 :``%p``: root-relative path name of file being printed
1427 :``%H``: changeset hash (40 hexadecimal digits)
1426 :``%H``: changeset hash (40 hexadecimal digits)
1428 :``%R``: changeset revision number
1427 :``%R``: changeset revision number
1429 :``%h``: short-form changeset hash (12 hexadecimal digits)
1428 :``%h``: short-form changeset hash (12 hexadecimal digits)
1430 :``%r``: zero-padded changeset revision number
1429 :``%r``: zero-padded changeset revision number
1431 :``%b``: basename of the exporting repository
1430 :``%b``: basename of the exporting repository
1432
1431
1433 Returns 0 on success.
1432 Returns 0 on success.
1434 """
1433 """
1435 ctx = scmutil.revsingle(repo, opts.get('rev'))
1434 ctx = scmutil.revsingle(repo, opts.get('rev'))
1436 m = scmutil.match(ctx, (file1,) + pats, opts)
1435 m = scmutil.match(ctx, (file1,) + pats, opts)
1437
1436
1438 ui.pager('cat')
1437 ui.pager('cat')
1439 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1438 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1440
1439
1441 @command('^clone',
1440 @command('^clone',
1442 [('U', 'noupdate', None, _('the clone will include an empty working '
1441 [('U', 'noupdate', None, _('the clone will include an empty working '
1443 'directory (only a repository)')),
1442 'directory (only a repository)')),
1444 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1443 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1445 _('REV')),
1444 _('REV')),
1446 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1445 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1447 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1446 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1448 ('', 'pull', None, _('use pull protocol to copy metadata')),
1447 ('', 'pull', None, _('use pull protocol to copy metadata')),
1449 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1448 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1450 ] + remoteopts,
1449 ] + remoteopts,
1451 _('[OPTION]... SOURCE [DEST]'),
1450 _('[OPTION]... SOURCE [DEST]'),
1452 norepo=True)
1451 norepo=True)
1453 def clone(ui, source, dest=None, **opts):
1452 def clone(ui, source, dest=None, **opts):
1454 """make a copy of an existing repository
1453 """make a copy of an existing repository
1455
1454
1456 Create a copy of an existing repository in a new directory.
1455 Create a copy of an existing repository in a new directory.
1457
1456
1458 If no destination directory name is specified, it defaults to the
1457 If no destination directory name is specified, it defaults to the
1459 basename of the source.
1458 basename of the source.
1460
1459
1461 The location of the source is added to the new repository's
1460 The location of the source is added to the new repository's
1462 ``.hg/hgrc`` file, as the default to be used for future pulls.
1461 ``.hg/hgrc`` file, as the default to be used for future pulls.
1463
1462
1464 Only local paths and ``ssh://`` URLs are supported as
1463 Only local paths and ``ssh://`` URLs are supported as
1465 destinations. For ``ssh://`` destinations, no working directory or
1464 destinations. For ``ssh://`` destinations, no working directory or
1466 ``.hg/hgrc`` will be created on the remote side.
1465 ``.hg/hgrc`` will be created on the remote side.
1467
1466
1468 If the source repository has a bookmark called '@' set, that
1467 If the source repository has a bookmark called '@' set, that
1469 revision will be checked out in the new repository by default.
1468 revision will be checked out in the new repository by default.
1470
1469
1471 To check out a particular version, use -u/--update, or
1470 To check out a particular version, use -u/--update, or
1472 -U/--noupdate to create a clone with no working directory.
1471 -U/--noupdate to create a clone with no working directory.
1473
1472
1474 To pull only a subset of changesets, specify one or more revisions
1473 To pull only a subset of changesets, specify one or more revisions
1475 identifiers with -r/--rev or branches with -b/--branch. The
1474 identifiers with -r/--rev or branches with -b/--branch. The
1476 resulting clone will contain only the specified changesets and
1475 resulting clone will contain only the specified changesets and
1477 their ancestors. These options (or 'clone src#rev dest') imply
1476 their ancestors. These options (or 'clone src#rev dest') imply
1478 --pull, even for local source repositories.
1477 --pull, even for local source repositories.
1479
1478
1480 .. note::
1479 .. note::
1481
1480
1482 Specifying a tag will include the tagged changeset but not the
1481 Specifying a tag will include the tagged changeset but not the
1483 changeset containing the tag.
1482 changeset containing the tag.
1484
1483
1485 .. container:: verbose
1484 .. container:: verbose
1486
1485
1487 For efficiency, hardlinks are used for cloning whenever the
1486 For efficiency, hardlinks are used for cloning whenever the
1488 source and destination are on the same filesystem (note this
1487 source and destination are on the same filesystem (note this
1489 applies only to the repository data, not to the working
1488 applies only to the repository data, not to the working
1490 directory). Some filesystems, such as AFS, implement hardlinking
1489 directory). Some filesystems, such as AFS, implement hardlinking
1491 incorrectly, but do not report errors. In these cases, use the
1490 incorrectly, but do not report errors. In these cases, use the
1492 --pull option to avoid hardlinking.
1491 --pull option to avoid hardlinking.
1493
1492
1494 In some cases, you can clone repositories and the working
1493 In some cases, you can clone repositories and the working
1495 directory using full hardlinks with ::
1494 directory using full hardlinks with ::
1496
1495
1497 $ cp -al REPO REPOCLONE
1496 $ cp -al REPO REPOCLONE
1498
1497
1499 This is the fastest way to clone, but it is not always safe. The
1498 This is the fastest way to clone, but it is not always safe. The
1500 operation is not atomic (making sure REPO is not modified during
1499 operation is not atomic (making sure REPO is not modified during
1501 the operation is up to you) and you have to make sure your
1500 the operation is up to you) and you have to make sure your
1502 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1501 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1503 so). Also, this is not compatible with certain extensions that
1502 so). Also, this is not compatible with certain extensions that
1504 place their metadata under the .hg directory, such as mq.
1503 place their metadata under the .hg directory, such as mq.
1505
1504
1506 Mercurial will update the working directory to the first applicable
1505 Mercurial will update the working directory to the first applicable
1507 revision from this list:
1506 revision from this list:
1508
1507
1509 a) null if -U or the source repository has no changesets
1508 a) null if -U or the source repository has no changesets
1510 b) if -u . and the source repository is local, the first parent of
1509 b) if -u . and the source repository is local, the first parent of
1511 the source repository's working directory
1510 the source repository's working directory
1512 c) the changeset specified with -u (if a branch name, this means the
1511 c) the changeset specified with -u (if a branch name, this means the
1513 latest head of that branch)
1512 latest head of that branch)
1514 d) the changeset specified with -r
1513 d) the changeset specified with -r
1515 e) the tipmost head specified with -b
1514 e) the tipmost head specified with -b
1516 f) the tipmost head specified with the url#branch source syntax
1515 f) the tipmost head specified with the url#branch source syntax
1517 g) the revision marked with the '@' bookmark, if present
1516 g) the revision marked with the '@' bookmark, if present
1518 h) the tipmost head of the default branch
1517 h) the tipmost head of the default branch
1519 i) tip
1518 i) tip
1520
1519
1521 When cloning from servers that support it, Mercurial may fetch
1520 When cloning from servers that support it, Mercurial may fetch
1522 pre-generated data from a server-advertised URL. When this is done,
1521 pre-generated data from a server-advertised URL. When this is done,
1523 hooks operating on incoming changesets and changegroups may fire twice,
1522 hooks operating on incoming changesets and changegroups may fire twice,
1524 once for the bundle fetched from the URL and another for any additional
1523 once for the bundle fetched from the URL and another for any additional
1525 data not fetched from this URL. In addition, if an error occurs, the
1524 data not fetched from this URL. In addition, if an error occurs, the
1526 repository may be rolled back to a partial clone. This behavior may
1525 repository may be rolled back to a partial clone. This behavior may
1527 change in future releases. See :hg:`help -e clonebundles` for more.
1526 change in future releases. See :hg:`help -e clonebundles` for more.
1528
1527
1529 Examples:
1528 Examples:
1530
1529
1531 - clone a remote repository to a new directory named hg/::
1530 - clone a remote repository to a new directory named hg/::
1532
1531
1533 hg clone https://www.mercurial-scm.org/repo/hg/
1532 hg clone https://www.mercurial-scm.org/repo/hg/
1534
1533
1535 - create a lightweight local clone::
1534 - create a lightweight local clone::
1536
1535
1537 hg clone project/ project-feature/
1536 hg clone project/ project-feature/
1538
1537
1539 - clone from an absolute path on an ssh server (note double-slash)::
1538 - clone from an absolute path on an ssh server (note double-slash)::
1540
1539
1541 hg clone ssh://user@server//home/projects/alpha/
1540 hg clone ssh://user@server//home/projects/alpha/
1542
1541
1543 - do a high-speed clone over a LAN while checking out a
1542 - do a high-speed clone over a LAN while checking out a
1544 specified version::
1543 specified version::
1545
1544
1546 hg clone --uncompressed http://server/repo -u 1.5
1545 hg clone --uncompressed http://server/repo -u 1.5
1547
1546
1548 - create a repository without changesets after a particular revision::
1547 - create a repository without changesets after a particular revision::
1549
1548
1550 hg clone -r 04e544 experimental/ good/
1549 hg clone -r 04e544 experimental/ good/
1551
1550
1552 - clone (and track) a particular named branch::
1551 - clone (and track) a particular named branch::
1553
1552
1554 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1553 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1555
1554
1556 See :hg:`help urls` for details on specifying URLs.
1555 See :hg:`help urls` for details on specifying URLs.
1557
1556
1558 Returns 0 on success.
1557 Returns 0 on success.
1559 """
1558 """
1560 opts = pycompat.byteskwargs(opts)
1559 opts = pycompat.byteskwargs(opts)
1561 if opts.get('noupdate') and opts.get('updaterev'):
1560 if opts.get('noupdate') and opts.get('updaterev'):
1562 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1561 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1563
1562
1564 r = hg.clone(ui, opts, source, dest,
1563 r = hg.clone(ui, opts, source, dest,
1565 pull=opts.get('pull'),
1564 pull=opts.get('pull'),
1566 stream=opts.get('uncompressed'),
1565 stream=opts.get('uncompressed'),
1567 rev=opts.get('rev'),
1566 rev=opts.get('rev'),
1568 update=opts.get('updaterev') or not opts.get('noupdate'),
1567 update=opts.get('updaterev') or not opts.get('noupdate'),
1569 branch=opts.get('branch'),
1568 branch=opts.get('branch'),
1570 shareopts=opts.get('shareopts'))
1569 shareopts=opts.get('shareopts'))
1571
1570
1572 return r is None
1571 return r is None
1573
1572
1574 @command('^commit|ci',
1573 @command('^commit|ci',
1575 [('A', 'addremove', None,
1574 [('A', 'addremove', None,
1576 _('mark new/missing files as added/removed before committing')),
1575 _('mark new/missing files as added/removed before committing')),
1577 ('', 'close-branch', None,
1576 ('', 'close-branch', None,
1578 _('mark a branch head as closed')),
1577 _('mark a branch head as closed')),
1579 ('', 'amend', None, _('amend the parent of the working directory')),
1578 ('', 'amend', None, _('amend the parent of the working directory')),
1580 ('s', 'secret', None, _('use the secret phase for committing')),
1579 ('s', 'secret', None, _('use the secret phase for committing')),
1581 ('e', 'edit', None, _('invoke editor on commit messages')),
1580 ('e', 'edit', None, _('invoke editor on commit messages')),
1582 ('i', 'interactive', None, _('use interactive mode')),
1581 ('i', 'interactive', None, _('use interactive mode')),
1583 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1582 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1584 _('[OPTION]... [FILE]...'),
1583 _('[OPTION]... [FILE]...'),
1585 inferrepo=True)
1584 inferrepo=True)
1586 def commit(ui, repo, *pats, **opts):
1585 def commit(ui, repo, *pats, **opts):
1587 """commit the specified files or all outstanding changes
1586 """commit the specified files or all outstanding changes
1588
1587
1589 Commit changes to the given files into the repository. Unlike a
1588 Commit changes to the given files into the repository. Unlike a
1590 centralized SCM, this operation is a local operation. See
1589 centralized SCM, this operation is a local operation. See
1591 :hg:`push` for a way to actively distribute your changes.
1590 :hg:`push` for a way to actively distribute your changes.
1592
1591
1593 If a list of files is omitted, all changes reported by :hg:`status`
1592 If a list of files is omitted, all changes reported by :hg:`status`
1594 will be committed.
1593 will be committed.
1595
1594
1596 If you are committing the result of a merge, do not provide any
1595 If you are committing the result of a merge, do not provide any
1597 filenames or -I/-X filters.
1596 filenames or -I/-X filters.
1598
1597
1599 If no commit message is specified, Mercurial starts your
1598 If no commit message is specified, Mercurial starts your
1600 configured editor where you can enter a message. In case your
1599 configured editor where you can enter a message. In case your
1601 commit fails, you will find a backup of your message in
1600 commit fails, you will find a backup of your message in
1602 ``.hg/last-message.txt``.
1601 ``.hg/last-message.txt``.
1603
1602
1604 The --close-branch flag can be used to mark the current branch
1603 The --close-branch flag can be used to mark the current branch
1605 head closed. When all heads of a branch are closed, the branch
1604 head closed. When all heads of a branch are closed, the branch
1606 will be considered closed and no longer listed.
1605 will be considered closed and no longer listed.
1607
1606
1608 The --amend flag can be used to amend the parent of the
1607 The --amend flag can be used to amend the parent of the
1609 working directory with a new commit that contains the changes
1608 working directory with a new commit that contains the changes
1610 in the parent in addition to those currently reported by :hg:`status`,
1609 in the parent in addition to those currently reported by :hg:`status`,
1611 if there are any. The old commit is stored in a backup bundle in
1610 if there are any. The old commit is stored in a backup bundle in
1612 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1611 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1613 on how to restore it).
1612 on how to restore it).
1614
1613
1615 Message, user and date are taken from the amended commit unless
1614 Message, user and date are taken from the amended commit unless
1616 specified. When a message isn't specified on the command line,
1615 specified. When a message isn't specified on the command line,
1617 the editor will open with the message of the amended commit.
1616 the editor will open with the message of the amended commit.
1618
1617
1619 It is not possible to amend public changesets (see :hg:`help phases`)
1618 It is not possible to amend public changesets (see :hg:`help phases`)
1620 or changesets that have children.
1619 or changesets that have children.
1621
1620
1622 See :hg:`help dates` for a list of formats valid for -d/--date.
1621 See :hg:`help dates` for a list of formats valid for -d/--date.
1623
1622
1624 Returns 0 on success, 1 if nothing changed.
1623 Returns 0 on success, 1 if nothing changed.
1625
1624
1626 .. container:: verbose
1625 .. container:: verbose
1627
1626
1628 Examples:
1627 Examples:
1629
1628
1630 - commit all files ending in .py::
1629 - commit all files ending in .py::
1631
1630
1632 hg commit --include "set:**.py"
1631 hg commit --include "set:**.py"
1633
1632
1634 - commit all non-binary files::
1633 - commit all non-binary files::
1635
1634
1636 hg commit --exclude "set:binary()"
1635 hg commit --exclude "set:binary()"
1637
1636
1638 - amend the current commit and set the date to now::
1637 - amend the current commit and set the date to now::
1639
1638
1640 hg commit --amend --date now
1639 hg commit --amend --date now
1641 """
1640 """
1642 wlock = lock = None
1641 wlock = lock = None
1643 try:
1642 try:
1644 wlock = repo.wlock()
1643 wlock = repo.wlock()
1645 lock = repo.lock()
1644 lock = repo.lock()
1646 return _docommit(ui, repo, *pats, **opts)
1645 return _docommit(ui, repo, *pats, **opts)
1647 finally:
1646 finally:
1648 release(lock, wlock)
1647 release(lock, wlock)
1649
1648
1650 def _docommit(ui, repo, *pats, **opts):
1649 def _docommit(ui, repo, *pats, **opts):
1651 if opts.get(r'interactive'):
1650 if opts.get(r'interactive'):
1652 opts.pop(r'interactive')
1651 opts.pop(r'interactive')
1653 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1652 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1654 cmdutil.recordfilter, *pats,
1653 cmdutil.recordfilter, *pats,
1655 **opts)
1654 **opts)
1656 # ret can be 0 (no changes to record) or the value returned by
1655 # ret can be 0 (no changes to record) or the value returned by
1657 # commit(), 1 if nothing changed or None on success.
1656 # commit(), 1 if nothing changed or None on success.
1658 return 1 if ret == 0 else ret
1657 return 1 if ret == 0 else ret
1659
1658
1660 opts = pycompat.byteskwargs(opts)
1659 opts = pycompat.byteskwargs(opts)
1661 if opts.get('subrepos'):
1660 if opts.get('subrepos'):
1662 if opts.get('amend'):
1661 if opts.get('amend'):
1663 raise error.Abort(_('cannot amend with --subrepos'))
1662 raise error.Abort(_('cannot amend with --subrepos'))
1664 # Let --subrepos on the command line override config setting.
1663 # Let --subrepos on the command line override config setting.
1665 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1664 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1666
1665
1667 cmdutil.checkunfinished(repo, commit=True)
1666 cmdutil.checkunfinished(repo, commit=True)
1668
1667
1669 branch = repo[None].branch()
1668 branch = repo[None].branch()
1670 bheads = repo.branchheads(branch)
1669 bheads = repo.branchheads(branch)
1671
1670
1672 extra = {}
1671 extra = {}
1673 if opts.get('close_branch'):
1672 if opts.get('close_branch'):
1674 extra['close'] = 1
1673 extra['close'] = 1
1675
1674
1676 if not bheads:
1675 if not bheads:
1677 raise error.Abort(_('can only close branch heads'))
1676 raise error.Abort(_('can only close branch heads'))
1678 elif opts.get('amend'):
1677 elif opts.get('amend'):
1679 if repo[None].parents()[0].p1().branch() != branch and \
1678 if repo[None].parents()[0].p1().branch() != branch and \
1680 repo[None].parents()[0].p2().branch() != branch:
1679 repo[None].parents()[0].p2().branch() != branch:
1681 raise error.Abort(_('can only close branch heads'))
1680 raise error.Abort(_('can only close branch heads'))
1682
1681
1683 if opts.get('amend'):
1682 if opts.get('amend'):
1684 if ui.configbool('ui', 'commitsubrepos'):
1683 if ui.configbool('ui', 'commitsubrepos'):
1685 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1684 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1686
1685
1687 old = repo['.']
1686 old = repo['.']
1688 if not old.mutable():
1687 if not old.mutable():
1689 raise error.Abort(_('cannot amend public changesets'))
1688 raise error.Abort(_('cannot amend public changesets'))
1690 if len(repo[None].parents()) > 1:
1689 if len(repo[None].parents()) > 1:
1691 raise error.Abort(_('cannot amend while merging'))
1690 raise error.Abort(_('cannot amend while merging'))
1692 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1691 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1693 if not allowunstable and old.children():
1692 if not allowunstable and old.children():
1694 raise error.Abort(_('cannot amend changeset with children'))
1693 raise error.Abort(_('cannot amend changeset with children'))
1695
1694
1696 # Currently histedit gets confused if an amend happens while histedit
1695 # Currently histedit gets confused if an amend happens while histedit
1697 # is in progress. Since we have a checkunfinished command, we are
1696 # is in progress. Since we have a checkunfinished command, we are
1698 # temporarily honoring it.
1697 # temporarily honoring it.
1699 #
1698 #
1700 # Note: eventually this guard will be removed. Please do not expect
1699 # Note: eventually this guard will be removed. Please do not expect
1701 # this behavior to remain.
1700 # this behavior to remain.
1702 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1701 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1703 cmdutil.checkunfinished(repo)
1702 cmdutil.checkunfinished(repo)
1704
1703
1705 # commitfunc is used only for temporary amend commit by cmdutil.amend
1704 # commitfunc is used only for temporary amend commit by cmdutil.amend
1706 def commitfunc(ui, repo, message, match, opts):
1705 def commitfunc(ui, repo, message, match, opts):
1707 return repo.commit(message,
1706 return repo.commit(message,
1708 opts.get('user') or old.user(),
1707 opts.get('user') or old.user(),
1709 opts.get('date') or old.date(),
1708 opts.get('date') or old.date(),
1710 match,
1709 match,
1711 extra=extra)
1710 extra=extra)
1712
1711
1713 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1712 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1714 if node == old.node():
1713 if node == old.node():
1715 ui.status(_("nothing changed\n"))
1714 ui.status(_("nothing changed\n"))
1716 return 1
1715 return 1
1717 else:
1716 else:
1718 def commitfunc(ui, repo, message, match, opts):
1717 def commitfunc(ui, repo, message, match, opts):
1719 overrides = {}
1718 overrides = {}
1720 if opts.get('secret'):
1719 if opts.get('secret'):
1721 overrides[('phases', 'new-commit')] = 'secret'
1720 overrides[('phases', 'new-commit')] = 'secret'
1722
1721
1723 baseui = repo.baseui
1722 baseui = repo.baseui
1724 with baseui.configoverride(overrides, 'commit'):
1723 with baseui.configoverride(overrides, 'commit'):
1725 with ui.configoverride(overrides, 'commit'):
1724 with ui.configoverride(overrides, 'commit'):
1726 editform = cmdutil.mergeeditform(repo[None],
1725 editform = cmdutil.mergeeditform(repo[None],
1727 'commit.normal')
1726 'commit.normal')
1728 editor = cmdutil.getcommiteditor(
1727 editor = cmdutil.getcommiteditor(
1729 editform=editform, **pycompat.strkwargs(opts))
1728 editform=editform, **pycompat.strkwargs(opts))
1730 return repo.commit(message,
1729 return repo.commit(message,
1731 opts.get('user'),
1730 opts.get('user'),
1732 opts.get('date'),
1731 opts.get('date'),
1733 match,
1732 match,
1734 editor=editor,
1733 editor=editor,
1735 extra=extra)
1734 extra=extra)
1736
1735
1737 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1736 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1738
1737
1739 if not node:
1738 if not node:
1740 stat = cmdutil.postcommitstatus(repo, pats, opts)
1739 stat = cmdutil.postcommitstatus(repo, pats, opts)
1741 if stat[3]:
1740 if stat[3]:
1742 ui.status(_("nothing changed (%d missing files, see "
1741 ui.status(_("nothing changed (%d missing files, see "
1743 "'hg status')\n") % len(stat[3]))
1742 "'hg status')\n") % len(stat[3]))
1744 else:
1743 else:
1745 ui.status(_("nothing changed\n"))
1744 ui.status(_("nothing changed\n"))
1746 return 1
1745 return 1
1747
1746
1748 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1747 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1749
1748
1750 @command('config|showconfig|debugconfig',
1749 @command('config|showconfig|debugconfig',
1751 [('u', 'untrusted', None, _('show untrusted configuration options')),
1750 [('u', 'untrusted', None, _('show untrusted configuration options')),
1752 ('e', 'edit', None, _('edit user config')),
1751 ('e', 'edit', None, _('edit user config')),
1753 ('l', 'local', None, _('edit repository config')),
1752 ('l', 'local', None, _('edit repository config')),
1754 ('g', 'global', None, _('edit global config'))] + formatteropts,
1753 ('g', 'global', None, _('edit global config'))] + formatteropts,
1755 _('[-u] [NAME]...'),
1754 _('[-u] [NAME]...'),
1756 optionalrepo=True)
1755 optionalrepo=True)
1757 def config(ui, repo, *values, **opts):
1756 def config(ui, repo, *values, **opts):
1758 """show combined config settings from all hgrc files
1757 """show combined config settings from all hgrc files
1759
1758
1760 With no arguments, print names and values of all config items.
1759 With no arguments, print names and values of all config items.
1761
1760
1762 With one argument of the form section.name, print just the value
1761 With one argument of the form section.name, print just the value
1763 of that config item.
1762 of that config item.
1764
1763
1765 With multiple arguments, print names and values of all config
1764 With multiple arguments, print names and values of all config
1766 items with matching section names.
1765 items with matching section names.
1767
1766
1768 With --edit, start an editor on the user-level config file. With
1767 With --edit, start an editor on the user-level config file. With
1769 --global, edit the system-wide config file. With --local, edit the
1768 --global, edit the system-wide config file. With --local, edit the
1770 repository-level config file.
1769 repository-level config file.
1771
1770
1772 With --debug, the source (filename and line number) is printed
1771 With --debug, the source (filename and line number) is printed
1773 for each config item.
1772 for each config item.
1774
1773
1775 See :hg:`help config` for more information about config files.
1774 See :hg:`help config` for more information about config files.
1776
1775
1777 Returns 0 on success, 1 if NAME does not exist.
1776 Returns 0 on success, 1 if NAME does not exist.
1778
1777
1779 """
1778 """
1780
1779
1781 opts = pycompat.byteskwargs(opts)
1780 opts = pycompat.byteskwargs(opts)
1782 if opts.get('edit') or opts.get('local') or opts.get('global'):
1781 if opts.get('edit') or opts.get('local') or opts.get('global'):
1783 if opts.get('local') and opts.get('global'):
1782 if opts.get('local') and opts.get('global'):
1784 raise error.Abort(_("can't use --local and --global together"))
1783 raise error.Abort(_("can't use --local and --global together"))
1785
1784
1786 if opts.get('local'):
1785 if opts.get('local'):
1787 if not repo:
1786 if not repo:
1788 raise error.Abort(_("can't use --local outside a repository"))
1787 raise error.Abort(_("can't use --local outside a repository"))
1789 paths = [repo.vfs.join('hgrc')]
1788 paths = [repo.vfs.join('hgrc')]
1790 elif opts.get('global'):
1789 elif opts.get('global'):
1791 paths = rcutil.systemrcpath()
1790 paths = rcutil.systemrcpath()
1792 else:
1791 else:
1793 paths = rcutil.userrcpath()
1792 paths = rcutil.userrcpath()
1794
1793
1795 for f in paths:
1794 for f in paths:
1796 if os.path.exists(f):
1795 if os.path.exists(f):
1797 break
1796 break
1798 else:
1797 else:
1799 if opts.get('global'):
1798 if opts.get('global'):
1800 samplehgrc = uimod.samplehgrcs['global']
1799 samplehgrc = uimod.samplehgrcs['global']
1801 elif opts.get('local'):
1800 elif opts.get('local'):
1802 samplehgrc = uimod.samplehgrcs['local']
1801 samplehgrc = uimod.samplehgrcs['local']
1803 else:
1802 else:
1804 samplehgrc = uimod.samplehgrcs['user']
1803 samplehgrc = uimod.samplehgrcs['user']
1805
1804
1806 f = paths[0]
1805 f = paths[0]
1807 fp = open(f, "w")
1806 fp = open(f, "w")
1808 fp.write(samplehgrc)
1807 fp.write(samplehgrc)
1809 fp.close()
1808 fp.close()
1810
1809
1811 editor = ui.geteditor()
1810 editor = ui.geteditor()
1812 ui.system("%s \"%s\"" % (editor, f),
1811 ui.system("%s \"%s\"" % (editor, f),
1813 onerr=error.Abort, errprefix=_("edit failed"),
1812 onerr=error.Abort, errprefix=_("edit failed"),
1814 blockedtag='config_edit')
1813 blockedtag='config_edit')
1815 return
1814 return
1816 ui.pager('config')
1815 ui.pager('config')
1817 fm = ui.formatter('config', opts)
1816 fm = ui.formatter('config', opts)
1818 for t, f in rcutil.rccomponents():
1817 for t, f in rcutil.rccomponents():
1819 if t == 'path':
1818 if t == 'path':
1820 ui.debug('read config from: %s\n' % f)
1819 ui.debug('read config from: %s\n' % f)
1821 elif t == 'items':
1820 elif t == 'items':
1822 for section, name, value, source in f:
1821 for section, name, value, source in f:
1823 ui.debug('set config by: %s\n' % source)
1822 ui.debug('set config by: %s\n' % source)
1824 else:
1823 else:
1825 raise error.ProgrammingError('unknown rctype: %s' % t)
1824 raise error.ProgrammingError('unknown rctype: %s' % t)
1826 untrusted = bool(opts.get('untrusted'))
1825 untrusted = bool(opts.get('untrusted'))
1827 if values:
1826 if values:
1828 sections = [v for v in values if '.' not in v]
1827 sections = [v for v in values if '.' not in v]
1829 items = [v for v in values if '.' in v]
1828 items = [v for v in values if '.' in v]
1830 if len(items) > 1 or items and sections:
1829 if len(items) > 1 or items and sections:
1831 raise error.Abort(_('only one config item permitted'))
1830 raise error.Abort(_('only one config item permitted'))
1832 matched = False
1831 matched = False
1833 for section, name, value in ui.walkconfig(untrusted=untrusted):
1832 for section, name, value in ui.walkconfig(untrusted=untrusted):
1834 source = ui.configsource(section, name, untrusted)
1833 source = ui.configsource(section, name, untrusted)
1835 value = pycompat.bytestr(value)
1834 value = pycompat.bytestr(value)
1836 if fm.isplain():
1835 if fm.isplain():
1837 source = source or 'none'
1836 source = source or 'none'
1838 value = value.replace('\n', '\\n')
1837 value = value.replace('\n', '\\n')
1839 entryname = section + '.' + name
1838 entryname = section + '.' + name
1840 if values:
1839 if values:
1841 for v in values:
1840 for v in values:
1842 if v == section:
1841 if v == section:
1843 fm.startitem()
1842 fm.startitem()
1844 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1843 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1845 fm.write('name value', '%s=%s\n', entryname, value)
1844 fm.write('name value', '%s=%s\n', entryname, value)
1846 matched = True
1845 matched = True
1847 elif v == entryname:
1846 elif v == entryname:
1848 fm.startitem()
1847 fm.startitem()
1849 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1848 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1850 fm.write('value', '%s\n', value)
1849 fm.write('value', '%s\n', value)
1851 fm.data(name=entryname)
1850 fm.data(name=entryname)
1852 matched = True
1851 matched = True
1853 else:
1852 else:
1854 fm.startitem()
1853 fm.startitem()
1855 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1854 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1856 fm.write('name value', '%s=%s\n', entryname, value)
1855 fm.write('name value', '%s=%s\n', entryname, value)
1857 matched = True
1856 matched = True
1858 fm.end()
1857 fm.end()
1859 if matched:
1858 if matched:
1860 return 0
1859 return 0
1861 return 1
1860 return 1
1862
1861
1863 @command('copy|cp',
1862 @command('copy|cp',
1864 [('A', 'after', None, _('record a copy that has already occurred')),
1863 [('A', 'after', None, _('record a copy that has already occurred')),
1865 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1864 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1866 ] + walkopts + dryrunopts,
1865 ] + walkopts + dryrunopts,
1867 _('[OPTION]... [SOURCE]... DEST'))
1866 _('[OPTION]... [SOURCE]... DEST'))
1868 def copy(ui, repo, *pats, **opts):
1867 def copy(ui, repo, *pats, **opts):
1869 """mark files as copied for the next commit
1868 """mark files as copied for the next commit
1870
1869
1871 Mark dest as having copies of source files. If dest is a
1870 Mark dest as having copies of source files. If dest is a
1872 directory, copies are put in that directory. If dest is a file,
1871 directory, copies are put in that directory. If dest is a file,
1873 the source must be a single file.
1872 the source must be a single file.
1874
1873
1875 By default, this command copies the contents of files as they
1874 By default, this command copies the contents of files as they
1876 exist in the working directory. If invoked with -A/--after, the
1875 exist in the working directory. If invoked with -A/--after, the
1877 operation is recorded, but no copying is performed.
1876 operation is recorded, but no copying is performed.
1878
1877
1879 This command takes effect with the next commit. To undo a copy
1878 This command takes effect with the next commit. To undo a copy
1880 before that, see :hg:`revert`.
1879 before that, see :hg:`revert`.
1881
1880
1882 Returns 0 on success, 1 if errors are encountered.
1881 Returns 0 on success, 1 if errors are encountered.
1883 """
1882 """
1884 opts = pycompat.byteskwargs(opts)
1883 opts = pycompat.byteskwargs(opts)
1885 with repo.wlock(False):
1884 with repo.wlock(False):
1886 return cmdutil.copy(ui, repo, pats, opts)
1885 return cmdutil.copy(ui, repo, pats, opts)
1887
1886
1888 @command('^diff',
1887 @command('^diff',
1889 [('r', 'rev', [], _('revision'), _('REV')),
1888 [('r', 'rev', [], _('revision'), _('REV')),
1890 ('c', 'change', '', _('change made by revision'), _('REV'))
1889 ('c', 'change', '', _('change made by revision'), _('REV'))
1891 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1890 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1892 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1891 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1893 inferrepo=True)
1892 inferrepo=True)
1894 def diff(ui, repo, *pats, **opts):
1893 def diff(ui, repo, *pats, **opts):
1895 """diff repository (or selected files)
1894 """diff repository (or selected files)
1896
1895
1897 Show differences between revisions for the specified files.
1896 Show differences between revisions for the specified files.
1898
1897
1899 Differences between files are shown using the unified diff format.
1898 Differences between files are shown using the unified diff format.
1900
1899
1901 .. note::
1900 .. note::
1902
1901
1903 :hg:`diff` may generate unexpected results for merges, as it will
1902 :hg:`diff` may generate unexpected results for merges, as it will
1904 default to comparing against the working directory's first
1903 default to comparing against the working directory's first
1905 parent changeset if no revisions are specified.
1904 parent changeset if no revisions are specified.
1906
1905
1907 When two revision arguments are given, then changes are shown
1906 When two revision arguments are given, then changes are shown
1908 between those revisions. If only one revision is specified then
1907 between those revisions. If only one revision is specified then
1909 that revision is compared to the working directory, and, when no
1908 that revision is compared to the working directory, and, when no
1910 revisions are specified, the working directory files are compared
1909 revisions are specified, the working directory files are compared
1911 to its first parent.
1910 to its first parent.
1912
1911
1913 Alternatively you can specify -c/--change with a revision to see
1912 Alternatively you can specify -c/--change with a revision to see
1914 the changes in that changeset relative to its first parent.
1913 the changes in that changeset relative to its first parent.
1915
1914
1916 Without the -a/--text option, diff will avoid generating diffs of
1915 Without the -a/--text option, diff will avoid generating diffs of
1917 files it detects as binary. With -a, diff will generate a diff
1916 files it detects as binary. With -a, diff will generate a diff
1918 anyway, probably with undesirable results.
1917 anyway, probably with undesirable results.
1919
1918
1920 Use the -g/--git option to generate diffs in the git extended diff
1919 Use the -g/--git option to generate diffs in the git extended diff
1921 format. For more information, read :hg:`help diffs`.
1920 format. For more information, read :hg:`help diffs`.
1922
1921
1923 .. container:: verbose
1922 .. container:: verbose
1924
1923
1925 Examples:
1924 Examples:
1926
1925
1927 - compare a file in the current working directory to its parent::
1926 - compare a file in the current working directory to its parent::
1928
1927
1929 hg diff foo.c
1928 hg diff foo.c
1930
1929
1931 - compare two historical versions of a directory, with rename info::
1930 - compare two historical versions of a directory, with rename info::
1932
1931
1933 hg diff --git -r 1.0:1.2 lib/
1932 hg diff --git -r 1.0:1.2 lib/
1934
1933
1935 - get change stats relative to the last change on some date::
1934 - get change stats relative to the last change on some date::
1936
1935
1937 hg diff --stat -r "date('may 2')"
1936 hg diff --stat -r "date('may 2')"
1938
1937
1939 - diff all newly-added files that contain a keyword::
1938 - diff all newly-added files that contain a keyword::
1940
1939
1941 hg diff "set:added() and grep(GNU)"
1940 hg diff "set:added() and grep(GNU)"
1942
1941
1943 - compare a revision and its parents::
1942 - compare a revision and its parents::
1944
1943
1945 hg diff -c 9353 # compare against first parent
1944 hg diff -c 9353 # compare against first parent
1946 hg diff -r 9353^:9353 # same using revset syntax
1945 hg diff -r 9353^:9353 # same using revset syntax
1947 hg diff -r 9353^2:9353 # compare against the second parent
1946 hg diff -r 9353^2:9353 # compare against the second parent
1948
1947
1949 Returns 0 on success.
1948 Returns 0 on success.
1950 """
1949 """
1951
1950
1952 opts = pycompat.byteskwargs(opts)
1951 opts = pycompat.byteskwargs(opts)
1953 revs = opts.get('rev')
1952 revs = opts.get('rev')
1954 change = opts.get('change')
1953 change = opts.get('change')
1955 stat = opts.get('stat')
1954 stat = opts.get('stat')
1956 reverse = opts.get('reverse')
1955 reverse = opts.get('reverse')
1957
1956
1958 if revs and change:
1957 if revs and change:
1959 msg = _('cannot specify --rev and --change at the same time')
1958 msg = _('cannot specify --rev and --change at the same time')
1960 raise error.Abort(msg)
1959 raise error.Abort(msg)
1961 elif change:
1960 elif change:
1962 node2 = scmutil.revsingle(repo, change, None).node()
1961 node2 = scmutil.revsingle(repo, change, None).node()
1963 node1 = repo[node2].p1().node()
1962 node1 = repo[node2].p1().node()
1964 else:
1963 else:
1965 node1, node2 = scmutil.revpair(repo, revs)
1964 node1, node2 = scmutil.revpair(repo, revs)
1966
1965
1967 if reverse:
1966 if reverse:
1968 node1, node2 = node2, node1
1967 node1, node2 = node2, node1
1969
1968
1970 diffopts = patch.diffallopts(ui, opts)
1969 diffopts = patch.diffallopts(ui, opts)
1971 m = scmutil.match(repo[node2], pats, opts)
1970 m = scmutil.match(repo[node2], pats, opts)
1972 ui.pager('diff')
1971 ui.pager('diff')
1973 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1972 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1974 listsubrepos=opts.get('subrepos'),
1973 listsubrepos=opts.get('subrepos'),
1975 root=opts.get('root'))
1974 root=opts.get('root'))
1976
1975
1977 @command('^export',
1976 @command('^export',
1978 [('o', 'output', '',
1977 [('o', 'output', '',
1979 _('print output to file with formatted name'), _('FORMAT')),
1978 _('print output to file with formatted name'), _('FORMAT')),
1980 ('', 'switch-parent', None, _('diff against the second parent')),
1979 ('', 'switch-parent', None, _('diff against the second parent')),
1981 ('r', 'rev', [], _('revisions to export'), _('REV')),
1980 ('r', 'rev', [], _('revisions to export'), _('REV')),
1982 ] + diffopts,
1981 ] + diffopts,
1983 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1982 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1984 def export(ui, repo, *changesets, **opts):
1983 def export(ui, repo, *changesets, **opts):
1985 """dump the header and diffs for one or more changesets
1984 """dump the header and diffs for one or more changesets
1986
1985
1987 Print the changeset header and diffs for one or more revisions.
1986 Print the changeset header and diffs for one or more revisions.
1988 If no revision is given, the parent of the working directory is used.
1987 If no revision is given, the parent of the working directory is used.
1989
1988
1990 The information shown in the changeset header is: author, date,
1989 The information shown in the changeset header is: author, date,
1991 branch name (if non-default), changeset hash, parent(s) and commit
1990 branch name (if non-default), changeset hash, parent(s) and commit
1992 comment.
1991 comment.
1993
1992
1994 .. note::
1993 .. note::
1995
1994
1996 :hg:`export` may generate unexpected diff output for merge
1995 :hg:`export` may generate unexpected diff output for merge
1997 changesets, as it will compare the merge changeset against its
1996 changesets, as it will compare the merge changeset against its
1998 first parent only.
1997 first parent only.
1999
1998
2000 Output may be to a file, in which case the name of the file is
1999 Output may be to a file, in which case the name of the file is
2001 given using a format string. The formatting rules are as follows:
2000 given using a format string. The formatting rules are as follows:
2002
2001
2003 :``%%``: literal "%" character
2002 :``%%``: literal "%" character
2004 :``%H``: changeset hash (40 hexadecimal digits)
2003 :``%H``: changeset hash (40 hexadecimal digits)
2005 :``%N``: number of patches being generated
2004 :``%N``: number of patches being generated
2006 :``%R``: changeset revision number
2005 :``%R``: changeset revision number
2007 :``%b``: basename of the exporting repository
2006 :``%b``: basename of the exporting repository
2008 :``%h``: short-form changeset hash (12 hexadecimal digits)
2007 :``%h``: short-form changeset hash (12 hexadecimal digits)
2009 :``%m``: first line of the commit message (only alphanumeric characters)
2008 :``%m``: first line of the commit message (only alphanumeric characters)
2010 :``%n``: zero-padded sequence number, starting at 1
2009 :``%n``: zero-padded sequence number, starting at 1
2011 :``%r``: zero-padded changeset revision number
2010 :``%r``: zero-padded changeset revision number
2012
2011
2013 Without the -a/--text option, export will avoid generating diffs
2012 Without the -a/--text option, export will avoid generating diffs
2014 of files it detects as binary. With -a, export will generate a
2013 of files it detects as binary. With -a, export will generate a
2015 diff anyway, probably with undesirable results.
2014 diff anyway, probably with undesirable results.
2016
2015
2017 Use the -g/--git option to generate diffs in the git extended diff
2016 Use the -g/--git option to generate diffs in the git extended diff
2018 format. See :hg:`help diffs` for more information.
2017 format. See :hg:`help diffs` for more information.
2019
2018
2020 With the --switch-parent option, the diff will be against the
2019 With the --switch-parent option, the diff will be against the
2021 second parent. It can be useful to review a merge.
2020 second parent. It can be useful to review a merge.
2022
2021
2023 .. container:: verbose
2022 .. container:: verbose
2024
2023
2025 Examples:
2024 Examples:
2026
2025
2027 - use export and import to transplant a bugfix to the current
2026 - use export and import to transplant a bugfix to the current
2028 branch::
2027 branch::
2029
2028
2030 hg export -r 9353 | hg import -
2029 hg export -r 9353 | hg import -
2031
2030
2032 - export all the changesets between two revisions to a file with
2031 - export all the changesets between two revisions to a file with
2033 rename information::
2032 rename information::
2034
2033
2035 hg export --git -r 123:150 > changes.txt
2034 hg export --git -r 123:150 > changes.txt
2036
2035
2037 - split outgoing changes into a series of patches with
2036 - split outgoing changes into a series of patches with
2038 descriptive names::
2037 descriptive names::
2039
2038
2040 hg export -r "outgoing()" -o "%n-%m.patch"
2039 hg export -r "outgoing()" -o "%n-%m.patch"
2041
2040
2042 Returns 0 on success.
2041 Returns 0 on success.
2043 """
2042 """
2044 opts = pycompat.byteskwargs(opts)
2043 opts = pycompat.byteskwargs(opts)
2045 changesets += tuple(opts.get('rev', []))
2044 changesets += tuple(opts.get('rev', []))
2046 if not changesets:
2045 if not changesets:
2047 changesets = ['.']
2046 changesets = ['.']
2048 revs = scmutil.revrange(repo, changesets)
2047 revs = scmutil.revrange(repo, changesets)
2049 if not revs:
2048 if not revs:
2050 raise error.Abort(_("export requires at least one changeset"))
2049 raise error.Abort(_("export requires at least one changeset"))
2051 if len(revs) > 1:
2050 if len(revs) > 1:
2052 ui.note(_('exporting patches:\n'))
2051 ui.note(_('exporting patches:\n'))
2053 else:
2052 else:
2054 ui.note(_('exporting patch:\n'))
2053 ui.note(_('exporting patch:\n'))
2055 ui.pager('export')
2054 ui.pager('export')
2056 cmdutil.export(repo, revs, template=opts.get('output'),
2055 cmdutil.export(repo, revs, template=opts.get('output'),
2057 switch_parent=opts.get('switch_parent'),
2056 switch_parent=opts.get('switch_parent'),
2058 opts=patch.diffallopts(ui, opts))
2057 opts=patch.diffallopts(ui, opts))
2059
2058
2060 @command('files',
2059 @command('files',
2061 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2060 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2062 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2061 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2063 ] + walkopts + formatteropts + subrepoopts,
2062 ] + walkopts + formatteropts + subrepoopts,
2064 _('[OPTION]... [FILE]...'))
2063 _('[OPTION]... [FILE]...'))
2065 def files(ui, repo, *pats, **opts):
2064 def files(ui, repo, *pats, **opts):
2066 """list tracked files
2065 """list tracked files
2067
2066
2068 Print files under Mercurial control in the working directory or
2067 Print files under Mercurial control in the working directory or
2069 specified revision for given files (excluding removed files).
2068 specified revision for given files (excluding removed files).
2070 Files can be specified as filenames or filesets.
2069 Files can be specified as filenames or filesets.
2071
2070
2072 If no files are given to match, this command prints the names
2071 If no files are given to match, this command prints the names
2073 of all files under Mercurial control.
2072 of all files under Mercurial control.
2074
2073
2075 .. container:: verbose
2074 .. container:: verbose
2076
2075
2077 Examples:
2076 Examples:
2078
2077
2079 - list all files under the current directory::
2078 - list all files under the current directory::
2080
2079
2081 hg files .
2080 hg files .
2082
2081
2083 - shows sizes and flags for current revision::
2082 - shows sizes and flags for current revision::
2084
2083
2085 hg files -vr .
2084 hg files -vr .
2086
2085
2087 - list all files named README::
2086 - list all files named README::
2088
2087
2089 hg files -I "**/README"
2088 hg files -I "**/README"
2090
2089
2091 - list all binary files::
2090 - list all binary files::
2092
2091
2093 hg files "set:binary()"
2092 hg files "set:binary()"
2094
2093
2095 - find files containing a regular expression::
2094 - find files containing a regular expression::
2096
2095
2097 hg files "set:grep('bob')"
2096 hg files "set:grep('bob')"
2098
2097
2099 - search tracked file contents with xargs and grep::
2098 - search tracked file contents with xargs and grep::
2100
2099
2101 hg files -0 | xargs -0 grep foo
2100 hg files -0 | xargs -0 grep foo
2102
2101
2103 See :hg:`help patterns` and :hg:`help filesets` for more information
2102 See :hg:`help patterns` and :hg:`help filesets` for more information
2104 on specifying file patterns.
2103 on specifying file patterns.
2105
2104
2106 Returns 0 if a match is found, 1 otherwise.
2105 Returns 0 if a match is found, 1 otherwise.
2107
2106
2108 """
2107 """
2109
2108
2110 opts = pycompat.byteskwargs(opts)
2109 opts = pycompat.byteskwargs(opts)
2111 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2110 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2112
2111
2113 end = '\n'
2112 end = '\n'
2114 if opts.get('print0'):
2113 if opts.get('print0'):
2115 end = '\0'
2114 end = '\0'
2116 fmt = '%s' + end
2115 fmt = '%s' + end
2117
2116
2118 m = scmutil.match(ctx, pats, opts)
2117 m = scmutil.match(ctx, pats, opts)
2119 ui.pager('files')
2118 ui.pager('files')
2120 with ui.formatter('files', opts) as fm:
2119 with ui.formatter('files', opts) as fm:
2121 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2120 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2122
2121
2123 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2122 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2124 def forget(ui, repo, *pats, **opts):
2123 def forget(ui, repo, *pats, **opts):
2125 """forget the specified files on the next commit
2124 """forget the specified files on the next commit
2126
2125
2127 Mark the specified files so they will no longer be tracked
2126 Mark the specified files so they will no longer be tracked
2128 after the next commit.
2127 after the next commit.
2129
2128
2130 This only removes files from the current branch, not from the
2129 This only removes files from the current branch, not from the
2131 entire project history, and it does not delete them from the
2130 entire project history, and it does not delete them from the
2132 working directory.
2131 working directory.
2133
2132
2134 To delete the file from the working directory, see :hg:`remove`.
2133 To delete the file from the working directory, see :hg:`remove`.
2135
2134
2136 To undo a forget before the next commit, see :hg:`add`.
2135 To undo a forget before the next commit, see :hg:`add`.
2137
2136
2138 .. container:: verbose
2137 .. container:: verbose
2139
2138
2140 Examples:
2139 Examples:
2141
2140
2142 - forget newly-added binary files::
2141 - forget newly-added binary files::
2143
2142
2144 hg forget "set:added() and binary()"
2143 hg forget "set:added() and binary()"
2145
2144
2146 - forget files that would be excluded by .hgignore::
2145 - forget files that would be excluded by .hgignore::
2147
2146
2148 hg forget "set:hgignore()"
2147 hg forget "set:hgignore()"
2149
2148
2150 Returns 0 on success.
2149 Returns 0 on success.
2151 """
2150 """
2152
2151
2153 opts = pycompat.byteskwargs(opts)
2152 opts = pycompat.byteskwargs(opts)
2154 if not pats:
2153 if not pats:
2155 raise error.Abort(_('no files specified'))
2154 raise error.Abort(_('no files specified'))
2156
2155
2157 m = scmutil.match(repo[None], pats, opts)
2156 m = scmutil.match(repo[None], pats, opts)
2158 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2157 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2159 return rejected and 1 or 0
2158 return rejected and 1 or 0
2160
2159
2161 @command(
2160 @command(
2162 'graft',
2161 'graft',
2163 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2162 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2164 ('c', 'continue', False, _('resume interrupted graft')),
2163 ('c', 'continue', False, _('resume interrupted graft')),
2165 ('e', 'edit', False, _('invoke editor on commit messages')),
2164 ('e', 'edit', False, _('invoke editor on commit messages')),
2166 ('', 'log', None, _('append graft info to log message')),
2165 ('', 'log', None, _('append graft info to log message')),
2167 ('f', 'force', False, _('force graft')),
2166 ('f', 'force', False, _('force graft')),
2168 ('D', 'currentdate', False,
2167 ('D', 'currentdate', False,
2169 _('record the current date as commit date')),
2168 _('record the current date as commit date')),
2170 ('U', 'currentuser', False,
2169 ('U', 'currentuser', False,
2171 _('record the current user as committer'), _('DATE'))]
2170 _('record the current user as committer'), _('DATE'))]
2172 + commitopts2 + mergetoolopts + dryrunopts,
2171 + commitopts2 + mergetoolopts + dryrunopts,
2173 _('[OPTION]... [-r REV]... REV...'))
2172 _('[OPTION]... [-r REV]... REV...'))
2174 def graft(ui, repo, *revs, **opts):
2173 def graft(ui, repo, *revs, **opts):
2175 '''copy changes from other branches onto the current branch
2174 '''copy changes from other branches onto the current branch
2176
2175
2177 This command uses Mercurial's merge logic to copy individual
2176 This command uses Mercurial's merge logic to copy individual
2178 changes from other branches without merging branches in the
2177 changes from other branches without merging branches in the
2179 history graph. This is sometimes known as 'backporting' or
2178 history graph. This is sometimes known as 'backporting' or
2180 'cherry-picking'. By default, graft will copy user, date, and
2179 'cherry-picking'. By default, graft will copy user, date, and
2181 description from the source changesets.
2180 description from the source changesets.
2182
2181
2183 Changesets that are ancestors of the current revision, that have
2182 Changesets that are ancestors of the current revision, that have
2184 already been grafted, or that are merges will be skipped.
2183 already been grafted, or that are merges will be skipped.
2185
2184
2186 If --log is specified, log messages will have a comment appended
2185 If --log is specified, log messages will have a comment appended
2187 of the form::
2186 of the form::
2188
2187
2189 (grafted from CHANGESETHASH)
2188 (grafted from CHANGESETHASH)
2190
2189
2191 If --force is specified, revisions will be grafted even if they
2190 If --force is specified, revisions will be grafted even if they
2192 are already ancestors of or have been grafted to the destination.
2191 are already ancestors of or have been grafted to the destination.
2193 This is useful when the revisions have since been backed out.
2192 This is useful when the revisions have since been backed out.
2194
2193
2195 If a graft merge results in conflicts, the graft process is
2194 If a graft merge results in conflicts, the graft process is
2196 interrupted so that the current merge can be manually resolved.
2195 interrupted so that the current merge can be manually resolved.
2197 Once all conflicts are addressed, the graft process can be
2196 Once all conflicts are addressed, the graft process can be
2198 continued with the -c/--continue option.
2197 continued with the -c/--continue option.
2199
2198
2200 .. note::
2199 .. note::
2201
2200
2202 The -c/--continue option does not reapply earlier options, except
2201 The -c/--continue option does not reapply earlier options, except
2203 for --force.
2202 for --force.
2204
2203
2205 .. container:: verbose
2204 .. container:: verbose
2206
2205
2207 Examples:
2206 Examples:
2208
2207
2209 - copy a single change to the stable branch and edit its description::
2208 - copy a single change to the stable branch and edit its description::
2210
2209
2211 hg update stable
2210 hg update stable
2212 hg graft --edit 9393
2211 hg graft --edit 9393
2213
2212
2214 - graft a range of changesets with one exception, updating dates::
2213 - graft a range of changesets with one exception, updating dates::
2215
2214
2216 hg graft -D "2085::2093 and not 2091"
2215 hg graft -D "2085::2093 and not 2091"
2217
2216
2218 - continue a graft after resolving conflicts::
2217 - continue a graft after resolving conflicts::
2219
2218
2220 hg graft -c
2219 hg graft -c
2221
2220
2222 - show the source of a grafted changeset::
2221 - show the source of a grafted changeset::
2223
2222
2224 hg log --debug -r .
2223 hg log --debug -r .
2225
2224
2226 - show revisions sorted by date::
2225 - show revisions sorted by date::
2227
2226
2228 hg log -r "sort(all(), date)"
2227 hg log -r "sort(all(), date)"
2229
2228
2230 See :hg:`help revisions` for more about specifying revisions.
2229 See :hg:`help revisions` for more about specifying revisions.
2231
2230
2232 Returns 0 on successful completion.
2231 Returns 0 on successful completion.
2233 '''
2232 '''
2234 with repo.wlock():
2233 with repo.wlock():
2235 return _dograft(ui, repo, *revs, **opts)
2234 return _dograft(ui, repo, *revs, **opts)
2236
2235
2237 def _dograft(ui, repo, *revs, **opts):
2236 def _dograft(ui, repo, *revs, **opts):
2238 opts = pycompat.byteskwargs(opts)
2237 opts = pycompat.byteskwargs(opts)
2239 if revs and opts.get('rev'):
2238 if revs and opts.get('rev'):
2240 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2239 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2241 'revision ordering!\n'))
2240 'revision ordering!\n'))
2242
2241
2243 revs = list(revs)
2242 revs = list(revs)
2244 revs.extend(opts.get('rev'))
2243 revs.extend(opts.get('rev'))
2245
2244
2246 if not opts.get('user') and opts.get('currentuser'):
2245 if not opts.get('user') and opts.get('currentuser'):
2247 opts['user'] = ui.username()
2246 opts['user'] = ui.username()
2248 if not opts.get('date') and opts.get('currentdate'):
2247 if not opts.get('date') and opts.get('currentdate'):
2249 opts['date'] = "%d %d" % util.makedate()
2248 opts['date'] = "%d %d" % util.makedate()
2250
2249
2251 editor = cmdutil.getcommiteditor(editform='graft', **opts)
2250 editor = cmdutil.getcommiteditor(editform='graft', **opts)
2252
2251
2253 cont = False
2252 cont = False
2254 if opts.get('continue'):
2253 if opts.get('continue'):
2255 cont = True
2254 cont = True
2256 if revs:
2255 if revs:
2257 raise error.Abort(_("can't specify --continue and revisions"))
2256 raise error.Abort(_("can't specify --continue and revisions"))
2258 # read in unfinished revisions
2257 # read in unfinished revisions
2259 try:
2258 try:
2260 nodes = repo.vfs.read('graftstate').splitlines()
2259 nodes = repo.vfs.read('graftstate').splitlines()
2261 revs = [repo[node].rev() for node in nodes]
2260 revs = [repo[node].rev() for node in nodes]
2262 except IOError as inst:
2261 except IOError as inst:
2263 if inst.errno != errno.ENOENT:
2262 if inst.errno != errno.ENOENT:
2264 raise
2263 raise
2265 cmdutil.wrongtooltocontinue(repo, _('graft'))
2264 cmdutil.wrongtooltocontinue(repo, _('graft'))
2266 else:
2265 else:
2267 cmdutil.checkunfinished(repo)
2266 cmdutil.checkunfinished(repo)
2268 cmdutil.bailifchanged(repo)
2267 cmdutil.bailifchanged(repo)
2269 if not revs:
2268 if not revs:
2270 raise error.Abort(_('no revisions specified'))
2269 raise error.Abort(_('no revisions specified'))
2271 revs = scmutil.revrange(repo, revs)
2270 revs = scmutil.revrange(repo, revs)
2272
2271
2273 skipped = set()
2272 skipped = set()
2274 # check for merges
2273 # check for merges
2275 for rev in repo.revs('%ld and merge()', revs):
2274 for rev in repo.revs('%ld and merge()', revs):
2276 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2275 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2277 skipped.add(rev)
2276 skipped.add(rev)
2278 revs = [r for r in revs if r not in skipped]
2277 revs = [r for r in revs if r not in skipped]
2279 if not revs:
2278 if not revs:
2280 return -1
2279 return -1
2281
2280
2282 # Don't check in the --continue case, in effect retaining --force across
2281 # Don't check in the --continue case, in effect retaining --force across
2283 # --continues. That's because without --force, any revisions we decided to
2282 # --continues. That's because without --force, any revisions we decided to
2284 # skip would have been filtered out here, so they wouldn't have made their
2283 # skip would have been filtered out here, so they wouldn't have made their
2285 # way to the graftstate. With --force, any revisions we would have otherwise
2284 # way to the graftstate. With --force, any revisions we would have otherwise
2286 # skipped would not have been filtered out, and if they hadn't been applied
2285 # skipped would not have been filtered out, and if they hadn't been applied
2287 # already, they'd have been in the graftstate.
2286 # already, they'd have been in the graftstate.
2288 if not (cont or opts.get('force')):
2287 if not (cont or opts.get('force')):
2289 # check for ancestors of dest branch
2288 # check for ancestors of dest branch
2290 crev = repo['.'].rev()
2289 crev = repo['.'].rev()
2291 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2290 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2292 # XXX make this lazy in the future
2291 # XXX make this lazy in the future
2293 # don't mutate while iterating, create a copy
2292 # don't mutate while iterating, create a copy
2294 for rev in list(revs):
2293 for rev in list(revs):
2295 if rev in ancestors:
2294 if rev in ancestors:
2296 ui.warn(_('skipping ancestor revision %d:%s\n') %
2295 ui.warn(_('skipping ancestor revision %d:%s\n') %
2297 (rev, repo[rev]))
2296 (rev, repo[rev]))
2298 # XXX remove on list is slow
2297 # XXX remove on list is slow
2299 revs.remove(rev)
2298 revs.remove(rev)
2300 if not revs:
2299 if not revs:
2301 return -1
2300 return -1
2302
2301
2303 # analyze revs for earlier grafts
2302 # analyze revs for earlier grafts
2304 ids = {}
2303 ids = {}
2305 for ctx in repo.set("%ld", revs):
2304 for ctx in repo.set("%ld", revs):
2306 ids[ctx.hex()] = ctx.rev()
2305 ids[ctx.hex()] = ctx.rev()
2307 n = ctx.extra().get('source')
2306 n = ctx.extra().get('source')
2308 if n:
2307 if n:
2309 ids[n] = ctx.rev()
2308 ids[n] = ctx.rev()
2310
2309
2311 # check ancestors for earlier grafts
2310 # check ancestors for earlier grafts
2312 ui.debug('scanning for duplicate grafts\n')
2311 ui.debug('scanning for duplicate grafts\n')
2313
2312
2314 for rev in repo.changelog.findmissingrevs(revs, [crev]):
2313 for rev in repo.changelog.findmissingrevs(revs, [crev]):
2315 ctx = repo[rev]
2314 ctx = repo[rev]
2316 n = ctx.extra().get('source')
2315 n = ctx.extra().get('source')
2317 if n in ids:
2316 if n in ids:
2318 try:
2317 try:
2319 r = repo[n].rev()
2318 r = repo[n].rev()
2320 except error.RepoLookupError:
2319 except error.RepoLookupError:
2321 r = None
2320 r = None
2322 if r in revs:
2321 if r in revs:
2323 ui.warn(_('skipping revision %d:%s '
2322 ui.warn(_('skipping revision %d:%s '
2324 '(already grafted to %d:%s)\n')
2323 '(already grafted to %d:%s)\n')
2325 % (r, repo[r], rev, ctx))
2324 % (r, repo[r], rev, ctx))
2326 revs.remove(r)
2325 revs.remove(r)
2327 elif ids[n] in revs:
2326 elif ids[n] in revs:
2328 if r is None:
2327 if r is None:
2329 ui.warn(_('skipping already grafted revision %d:%s '
2328 ui.warn(_('skipping already grafted revision %d:%s '
2330 '(%d:%s also has unknown origin %s)\n')
2329 '(%d:%s also has unknown origin %s)\n')
2331 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2330 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2332 else:
2331 else:
2333 ui.warn(_('skipping already grafted revision %d:%s '
2332 ui.warn(_('skipping already grafted revision %d:%s '
2334 '(%d:%s also has origin %d:%s)\n')
2333 '(%d:%s also has origin %d:%s)\n')
2335 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2334 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2336 revs.remove(ids[n])
2335 revs.remove(ids[n])
2337 elif ctx.hex() in ids:
2336 elif ctx.hex() in ids:
2338 r = ids[ctx.hex()]
2337 r = ids[ctx.hex()]
2339 ui.warn(_('skipping already grafted revision %d:%s '
2338 ui.warn(_('skipping already grafted revision %d:%s '
2340 '(was grafted from %d:%s)\n') %
2339 '(was grafted from %d:%s)\n') %
2341 (r, repo[r], rev, ctx))
2340 (r, repo[r], rev, ctx))
2342 revs.remove(r)
2341 revs.remove(r)
2343 if not revs:
2342 if not revs:
2344 return -1
2343 return -1
2345
2344
2346 for pos, ctx in enumerate(repo.set("%ld", revs)):
2345 for pos, ctx in enumerate(repo.set("%ld", revs)):
2347 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2346 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2348 ctx.description().split('\n', 1)[0])
2347 ctx.description().split('\n', 1)[0])
2349 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2348 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2350 if names:
2349 if names:
2351 desc += ' (%s)' % ' '.join(names)
2350 desc += ' (%s)' % ' '.join(names)
2352 ui.status(_('grafting %s\n') % desc)
2351 ui.status(_('grafting %s\n') % desc)
2353 if opts.get('dry_run'):
2352 if opts.get('dry_run'):
2354 continue
2353 continue
2355
2354
2356 source = ctx.extra().get('source')
2355 source = ctx.extra().get('source')
2357 extra = {}
2356 extra = {}
2358 if source:
2357 if source:
2359 extra['source'] = source
2358 extra['source'] = source
2360 extra['intermediate-source'] = ctx.hex()
2359 extra['intermediate-source'] = ctx.hex()
2361 else:
2360 else:
2362 extra['source'] = ctx.hex()
2361 extra['source'] = ctx.hex()
2363 user = ctx.user()
2362 user = ctx.user()
2364 if opts.get('user'):
2363 if opts.get('user'):
2365 user = opts['user']
2364 user = opts['user']
2366 date = ctx.date()
2365 date = ctx.date()
2367 if opts.get('date'):
2366 if opts.get('date'):
2368 date = opts['date']
2367 date = opts['date']
2369 message = ctx.description()
2368 message = ctx.description()
2370 if opts.get('log'):
2369 if opts.get('log'):
2371 message += '\n(grafted from %s)' % ctx.hex()
2370 message += '\n(grafted from %s)' % ctx.hex()
2372
2371
2373 # we don't merge the first commit when continuing
2372 # we don't merge the first commit when continuing
2374 if not cont:
2373 if not cont:
2375 # perform the graft merge with p1(rev) as 'ancestor'
2374 # perform the graft merge with p1(rev) as 'ancestor'
2376 try:
2375 try:
2377 # ui.forcemerge is an internal variable, do not document
2376 # ui.forcemerge is an internal variable, do not document
2378 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2377 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2379 'graft')
2378 'graft')
2380 stats = mergemod.graft(repo, ctx, ctx.p1(),
2379 stats = mergemod.graft(repo, ctx, ctx.p1(),
2381 ['local', 'graft'])
2380 ['local', 'graft'])
2382 finally:
2381 finally:
2383 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2382 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2384 # report any conflicts
2383 # report any conflicts
2385 if stats and stats[3] > 0:
2384 if stats and stats[3] > 0:
2386 # write out state for --continue
2385 # write out state for --continue
2387 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2386 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2388 repo.vfs.write('graftstate', ''.join(nodelines))
2387 repo.vfs.write('graftstate', ''.join(nodelines))
2389 extra = ''
2388 extra = ''
2390 if opts.get('user'):
2389 if opts.get('user'):
2391 extra += ' --user %s' % util.shellquote(opts['user'])
2390 extra += ' --user %s' % util.shellquote(opts['user'])
2392 if opts.get('date'):
2391 if opts.get('date'):
2393 extra += ' --date %s' % util.shellquote(opts['date'])
2392 extra += ' --date %s' % util.shellquote(opts['date'])
2394 if opts.get('log'):
2393 if opts.get('log'):
2395 extra += ' --log'
2394 extra += ' --log'
2396 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2395 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2397 raise error.Abort(
2396 raise error.Abort(
2398 _("unresolved conflicts, can't continue"),
2397 _("unresolved conflicts, can't continue"),
2399 hint=hint)
2398 hint=hint)
2400 else:
2399 else:
2401 cont = False
2400 cont = False
2402
2401
2403 # commit
2402 # commit
2404 node = repo.commit(text=message, user=user,
2403 node = repo.commit(text=message, user=user,
2405 date=date, extra=extra, editor=editor)
2404 date=date, extra=extra, editor=editor)
2406 if node is None:
2405 if node is None:
2407 ui.warn(
2406 ui.warn(
2408 _('note: graft of %d:%s created no changes to commit\n') %
2407 _('note: graft of %d:%s created no changes to commit\n') %
2409 (ctx.rev(), ctx))
2408 (ctx.rev(), ctx))
2410
2409
2411 # remove state when we complete successfully
2410 # remove state when we complete successfully
2412 if not opts.get('dry_run'):
2411 if not opts.get('dry_run'):
2413 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2412 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2414
2413
2415 return 0
2414 return 0
2416
2415
2417 @command('grep',
2416 @command('grep',
2418 [('0', 'print0', None, _('end fields with NUL')),
2417 [('0', 'print0', None, _('end fields with NUL')),
2419 ('', 'all', None, _('print all revisions that match')),
2418 ('', 'all', None, _('print all revisions that match')),
2420 ('a', 'text', None, _('treat all files as text')),
2419 ('a', 'text', None, _('treat all files as text')),
2421 ('f', 'follow', None,
2420 ('f', 'follow', None,
2422 _('follow changeset history,'
2421 _('follow changeset history,'
2423 ' or file history across copies and renames')),
2422 ' or file history across copies and renames')),
2424 ('i', 'ignore-case', None, _('ignore case when matching')),
2423 ('i', 'ignore-case', None, _('ignore case when matching')),
2425 ('l', 'files-with-matches', None,
2424 ('l', 'files-with-matches', None,
2426 _('print only filenames and revisions that match')),
2425 _('print only filenames and revisions that match')),
2427 ('n', 'line-number', None, _('print matching line numbers')),
2426 ('n', 'line-number', None, _('print matching line numbers')),
2428 ('r', 'rev', [],
2427 ('r', 'rev', [],
2429 _('only search files changed within revision range'), _('REV')),
2428 _('only search files changed within revision range'), _('REV')),
2430 ('u', 'user', None, _('list the author (long with -v)')),
2429 ('u', 'user', None, _('list the author (long with -v)')),
2431 ('d', 'date', None, _('list the date (short with -q)')),
2430 ('d', 'date', None, _('list the date (short with -q)')),
2432 ] + formatteropts + walkopts,
2431 ] + formatteropts + walkopts,
2433 _('[OPTION]... PATTERN [FILE]...'),
2432 _('[OPTION]... PATTERN [FILE]...'),
2434 inferrepo=True)
2433 inferrepo=True)
2435 def grep(ui, repo, pattern, *pats, **opts):
2434 def grep(ui, repo, pattern, *pats, **opts):
2436 """search revision history for a pattern in specified files
2435 """search revision history for a pattern in specified files
2437
2436
2438 Search revision history for a regular expression in the specified
2437 Search revision history for a regular expression in the specified
2439 files or the entire project.
2438 files or the entire project.
2440
2439
2441 By default, grep prints the most recent revision number for each
2440 By default, grep prints the most recent revision number for each
2442 file in which it finds a match. To get it to print every revision
2441 file in which it finds a match. To get it to print every revision
2443 that contains a change in match status ("-" for a match that becomes
2442 that contains a change in match status ("-" for a match that becomes
2444 a non-match, or "+" for a non-match that becomes a match), use the
2443 a non-match, or "+" for a non-match that becomes a match), use the
2445 --all flag.
2444 --all flag.
2446
2445
2447 PATTERN can be any Python (roughly Perl-compatible) regular
2446 PATTERN can be any Python (roughly Perl-compatible) regular
2448 expression.
2447 expression.
2449
2448
2450 If no FILEs are specified (and -f/--follow isn't set), all files in
2449 If no FILEs are specified (and -f/--follow isn't set), all files in
2451 the repository are searched, including those that don't exist in the
2450 the repository are searched, including those that don't exist in the
2452 current branch or have been deleted in a prior changeset.
2451 current branch or have been deleted in a prior changeset.
2453
2452
2454 Returns 0 if a match is found, 1 otherwise.
2453 Returns 0 if a match is found, 1 otherwise.
2455 """
2454 """
2456 opts = pycompat.byteskwargs(opts)
2455 opts = pycompat.byteskwargs(opts)
2457 reflags = re.M
2456 reflags = re.M
2458 if opts.get('ignore_case'):
2457 if opts.get('ignore_case'):
2459 reflags |= re.I
2458 reflags |= re.I
2460 try:
2459 try:
2461 regexp = util.re.compile(pattern, reflags)
2460 regexp = util.re.compile(pattern, reflags)
2462 except re.error as inst:
2461 except re.error as inst:
2463 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2462 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2464 return 1
2463 return 1
2465 sep, eol = ':', '\n'
2464 sep, eol = ':', '\n'
2466 if opts.get('print0'):
2465 if opts.get('print0'):
2467 sep = eol = '\0'
2466 sep = eol = '\0'
2468
2467
2469 getfile = util.lrucachefunc(repo.file)
2468 getfile = util.lrucachefunc(repo.file)
2470
2469
2471 def matchlines(body):
2470 def matchlines(body):
2472 begin = 0
2471 begin = 0
2473 linenum = 0
2472 linenum = 0
2474 while begin < len(body):
2473 while begin < len(body):
2475 match = regexp.search(body, begin)
2474 match = regexp.search(body, begin)
2476 if not match:
2475 if not match:
2477 break
2476 break
2478 mstart, mend = match.span()
2477 mstart, mend = match.span()
2479 linenum += body.count('\n', begin, mstart) + 1
2478 linenum += body.count('\n', begin, mstart) + 1
2480 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2479 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2481 begin = body.find('\n', mend) + 1 or len(body) + 1
2480 begin = body.find('\n', mend) + 1 or len(body) + 1
2482 lend = begin - 1
2481 lend = begin - 1
2483 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2482 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2484
2483
2485 class linestate(object):
2484 class linestate(object):
2486 def __init__(self, line, linenum, colstart, colend):
2485 def __init__(self, line, linenum, colstart, colend):
2487 self.line = line
2486 self.line = line
2488 self.linenum = linenum
2487 self.linenum = linenum
2489 self.colstart = colstart
2488 self.colstart = colstart
2490 self.colend = colend
2489 self.colend = colend
2491
2490
2492 def __hash__(self):
2491 def __hash__(self):
2493 return hash((self.linenum, self.line))
2492 return hash((self.linenum, self.line))
2494
2493
2495 def __eq__(self, other):
2494 def __eq__(self, other):
2496 return self.line == other.line
2495 return self.line == other.line
2497
2496
2498 def findpos(self):
2497 def findpos(self):
2499 """Iterate all (start, end) indices of matches"""
2498 """Iterate all (start, end) indices of matches"""
2500 yield self.colstart, self.colend
2499 yield self.colstart, self.colend
2501 p = self.colend
2500 p = self.colend
2502 while p < len(self.line):
2501 while p < len(self.line):
2503 m = regexp.search(self.line, p)
2502 m = regexp.search(self.line, p)
2504 if not m:
2503 if not m:
2505 break
2504 break
2506 yield m.span()
2505 yield m.span()
2507 p = m.end()
2506 p = m.end()
2508
2507
2509 matches = {}
2508 matches = {}
2510 copies = {}
2509 copies = {}
2511 def grepbody(fn, rev, body):
2510 def grepbody(fn, rev, body):
2512 matches[rev].setdefault(fn, [])
2511 matches[rev].setdefault(fn, [])
2513 m = matches[rev][fn]
2512 m = matches[rev][fn]
2514 for lnum, cstart, cend, line in matchlines(body):
2513 for lnum, cstart, cend, line in matchlines(body):
2515 s = linestate(line, lnum, cstart, cend)
2514 s = linestate(line, lnum, cstart, cend)
2516 m.append(s)
2515 m.append(s)
2517
2516
2518 def difflinestates(a, b):
2517 def difflinestates(a, b):
2519 sm = difflib.SequenceMatcher(None, a, b)
2518 sm = difflib.SequenceMatcher(None, a, b)
2520 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2519 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2521 if tag == 'insert':
2520 if tag == 'insert':
2522 for i in xrange(blo, bhi):
2521 for i in xrange(blo, bhi):
2523 yield ('+', b[i])
2522 yield ('+', b[i])
2524 elif tag == 'delete':
2523 elif tag == 'delete':
2525 for i in xrange(alo, ahi):
2524 for i in xrange(alo, ahi):
2526 yield ('-', a[i])
2525 yield ('-', a[i])
2527 elif tag == 'replace':
2526 elif tag == 'replace':
2528 for i in xrange(alo, ahi):
2527 for i in xrange(alo, ahi):
2529 yield ('-', a[i])
2528 yield ('-', a[i])
2530 for i in xrange(blo, bhi):
2529 for i in xrange(blo, bhi):
2531 yield ('+', b[i])
2530 yield ('+', b[i])
2532
2531
2533 def display(fm, fn, ctx, pstates, states):
2532 def display(fm, fn, ctx, pstates, states):
2534 rev = ctx.rev()
2533 rev = ctx.rev()
2535 if fm.isplain():
2534 if fm.isplain():
2536 formatuser = ui.shortuser
2535 formatuser = ui.shortuser
2537 else:
2536 else:
2538 formatuser = str
2537 formatuser = str
2539 if ui.quiet:
2538 if ui.quiet:
2540 datefmt = '%Y-%m-%d'
2539 datefmt = '%Y-%m-%d'
2541 else:
2540 else:
2542 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2541 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2543 found = False
2542 found = False
2544 @util.cachefunc
2543 @util.cachefunc
2545 def binary():
2544 def binary():
2546 flog = getfile(fn)
2545 flog = getfile(fn)
2547 return util.binary(flog.read(ctx.filenode(fn)))
2546 return util.binary(flog.read(ctx.filenode(fn)))
2548
2547
2549 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2548 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2550 if opts.get('all'):
2549 if opts.get('all'):
2551 iter = difflinestates(pstates, states)
2550 iter = difflinestates(pstates, states)
2552 else:
2551 else:
2553 iter = [('', l) for l in states]
2552 iter = [('', l) for l in states]
2554 for change, l in iter:
2553 for change, l in iter:
2555 fm.startitem()
2554 fm.startitem()
2556 fm.data(node=fm.hexfunc(ctx.node()))
2555 fm.data(node=fm.hexfunc(ctx.node()))
2557 cols = [
2556 cols = [
2558 ('filename', fn, True),
2557 ('filename', fn, True),
2559 ('rev', rev, True),
2558 ('rev', rev, True),
2560 ('linenumber', l.linenum, opts.get('line_number')),
2559 ('linenumber', l.linenum, opts.get('line_number')),
2561 ]
2560 ]
2562 if opts.get('all'):
2561 if opts.get('all'):
2563 cols.append(('change', change, True))
2562 cols.append(('change', change, True))
2564 cols.extend([
2563 cols.extend([
2565 ('user', formatuser(ctx.user()), opts.get('user')),
2564 ('user', formatuser(ctx.user()), opts.get('user')),
2566 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2565 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2567 ])
2566 ])
2568 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2567 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2569 for name, data, cond in cols:
2568 for name, data, cond in cols:
2570 field = fieldnamemap.get(name, name)
2569 field = fieldnamemap.get(name, name)
2571 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2570 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2572 if cond and name != lastcol:
2571 if cond and name != lastcol:
2573 fm.plain(sep, label='grep.sep')
2572 fm.plain(sep, label='grep.sep')
2574 if not opts.get('files_with_matches'):
2573 if not opts.get('files_with_matches'):
2575 fm.plain(sep, label='grep.sep')
2574 fm.plain(sep, label='grep.sep')
2576 if not opts.get('text') and binary():
2575 if not opts.get('text') and binary():
2577 fm.plain(_(" Binary file matches"))
2576 fm.plain(_(" Binary file matches"))
2578 else:
2577 else:
2579 displaymatches(fm.nested('texts'), l)
2578 displaymatches(fm.nested('texts'), l)
2580 fm.plain(eol)
2579 fm.plain(eol)
2581 found = True
2580 found = True
2582 if opts.get('files_with_matches'):
2581 if opts.get('files_with_matches'):
2583 break
2582 break
2584 return found
2583 return found
2585
2584
2586 def displaymatches(fm, l):
2585 def displaymatches(fm, l):
2587 p = 0
2586 p = 0
2588 for s, e in l.findpos():
2587 for s, e in l.findpos():
2589 if p < s:
2588 if p < s:
2590 fm.startitem()
2589 fm.startitem()
2591 fm.write('text', '%s', l.line[p:s])
2590 fm.write('text', '%s', l.line[p:s])
2592 fm.data(matched=False)
2591 fm.data(matched=False)
2593 fm.startitem()
2592 fm.startitem()
2594 fm.write('text', '%s', l.line[s:e], label='grep.match')
2593 fm.write('text', '%s', l.line[s:e], label='grep.match')
2595 fm.data(matched=True)
2594 fm.data(matched=True)
2596 p = e
2595 p = e
2597 if p < len(l.line):
2596 if p < len(l.line):
2598 fm.startitem()
2597 fm.startitem()
2599 fm.write('text', '%s', l.line[p:])
2598 fm.write('text', '%s', l.line[p:])
2600 fm.data(matched=False)
2599 fm.data(matched=False)
2601 fm.end()
2600 fm.end()
2602
2601
2603 skip = {}
2602 skip = {}
2604 revfiles = {}
2603 revfiles = {}
2605 matchfn = scmutil.match(repo[None], pats, opts)
2604 matchfn = scmutil.match(repo[None], pats, opts)
2606 found = False
2605 found = False
2607 follow = opts.get('follow')
2606 follow = opts.get('follow')
2608
2607
2609 def prep(ctx, fns):
2608 def prep(ctx, fns):
2610 rev = ctx.rev()
2609 rev = ctx.rev()
2611 pctx = ctx.p1()
2610 pctx = ctx.p1()
2612 parent = pctx.rev()
2611 parent = pctx.rev()
2613 matches.setdefault(rev, {})
2612 matches.setdefault(rev, {})
2614 matches.setdefault(parent, {})
2613 matches.setdefault(parent, {})
2615 files = revfiles.setdefault(rev, [])
2614 files = revfiles.setdefault(rev, [])
2616 for fn in fns:
2615 for fn in fns:
2617 flog = getfile(fn)
2616 flog = getfile(fn)
2618 try:
2617 try:
2619 fnode = ctx.filenode(fn)
2618 fnode = ctx.filenode(fn)
2620 except error.LookupError:
2619 except error.LookupError:
2621 continue
2620 continue
2622
2621
2623 copied = flog.renamed(fnode)
2622 copied = flog.renamed(fnode)
2624 copy = follow and copied and copied[0]
2623 copy = follow and copied and copied[0]
2625 if copy:
2624 if copy:
2626 copies.setdefault(rev, {})[fn] = copy
2625 copies.setdefault(rev, {})[fn] = copy
2627 if fn in skip:
2626 if fn in skip:
2628 if copy:
2627 if copy:
2629 skip[copy] = True
2628 skip[copy] = True
2630 continue
2629 continue
2631 files.append(fn)
2630 files.append(fn)
2632
2631
2633 if fn not in matches[rev]:
2632 if fn not in matches[rev]:
2634 grepbody(fn, rev, flog.read(fnode))
2633 grepbody(fn, rev, flog.read(fnode))
2635
2634
2636 pfn = copy or fn
2635 pfn = copy or fn
2637 if pfn not in matches[parent]:
2636 if pfn not in matches[parent]:
2638 try:
2637 try:
2639 fnode = pctx.filenode(pfn)
2638 fnode = pctx.filenode(pfn)
2640 grepbody(pfn, parent, flog.read(fnode))
2639 grepbody(pfn, parent, flog.read(fnode))
2641 except error.LookupError:
2640 except error.LookupError:
2642 pass
2641 pass
2643
2642
2644 ui.pager('grep')
2643 ui.pager('grep')
2645 fm = ui.formatter('grep', opts)
2644 fm = ui.formatter('grep', opts)
2646 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2645 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2647 rev = ctx.rev()
2646 rev = ctx.rev()
2648 parent = ctx.p1().rev()
2647 parent = ctx.p1().rev()
2649 for fn in sorted(revfiles.get(rev, [])):
2648 for fn in sorted(revfiles.get(rev, [])):
2650 states = matches[rev][fn]
2649 states = matches[rev][fn]
2651 copy = copies.get(rev, {}).get(fn)
2650 copy = copies.get(rev, {}).get(fn)
2652 if fn in skip:
2651 if fn in skip:
2653 if copy:
2652 if copy:
2654 skip[copy] = True
2653 skip[copy] = True
2655 continue
2654 continue
2656 pstates = matches.get(parent, {}).get(copy or fn, [])
2655 pstates = matches.get(parent, {}).get(copy or fn, [])
2657 if pstates or states:
2656 if pstates or states:
2658 r = display(fm, fn, ctx, pstates, states)
2657 r = display(fm, fn, ctx, pstates, states)
2659 found = found or r
2658 found = found or r
2660 if r and not opts.get('all'):
2659 if r and not opts.get('all'):
2661 skip[fn] = True
2660 skip[fn] = True
2662 if copy:
2661 if copy:
2663 skip[copy] = True
2662 skip[copy] = True
2664 del matches[rev]
2663 del matches[rev]
2665 del revfiles[rev]
2664 del revfiles[rev]
2666 fm.end()
2665 fm.end()
2667
2666
2668 return not found
2667 return not found
2669
2668
2670 @command('heads',
2669 @command('heads',
2671 [('r', 'rev', '',
2670 [('r', 'rev', '',
2672 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2671 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2673 ('t', 'topo', False, _('show topological heads only')),
2672 ('t', 'topo', False, _('show topological heads only')),
2674 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2673 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2675 ('c', 'closed', False, _('show normal and closed branch heads')),
2674 ('c', 'closed', False, _('show normal and closed branch heads')),
2676 ] + templateopts,
2675 ] + templateopts,
2677 _('[-ct] [-r STARTREV] [REV]...'))
2676 _('[-ct] [-r STARTREV] [REV]...'))
2678 def heads(ui, repo, *branchrevs, **opts):
2677 def heads(ui, repo, *branchrevs, **opts):
2679 """show branch heads
2678 """show branch heads
2680
2679
2681 With no arguments, show all open branch heads in the repository.
2680 With no arguments, show all open branch heads in the repository.
2682 Branch heads are changesets that have no descendants on the
2681 Branch heads are changesets that have no descendants on the
2683 same branch. They are where development generally takes place and
2682 same branch. They are where development generally takes place and
2684 are the usual targets for update and merge operations.
2683 are the usual targets for update and merge operations.
2685
2684
2686 If one or more REVs are given, only open branch heads on the
2685 If one or more REVs are given, only open branch heads on the
2687 branches associated with the specified changesets are shown. This
2686 branches associated with the specified changesets are shown. This
2688 means that you can use :hg:`heads .` to see the heads on the
2687 means that you can use :hg:`heads .` to see the heads on the
2689 currently checked-out branch.
2688 currently checked-out branch.
2690
2689
2691 If -c/--closed is specified, also show branch heads marked closed
2690 If -c/--closed is specified, also show branch heads marked closed
2692 (see :hg:`commit --close-branch`).
2691 (see :hg:`commit --close-branch`).
2693
2692
2694 If STARTREV is specified, only those heads that are descendants of
2693 If STARTREV is specified, only those heads that are descendants of
2695 STARTREV will be displayed.
2694 STARTREV will be displayed.
2696
2695
2697 If -t/--topo is specified, named branch mechanics will be ignored and only
2696 If -t/--topo is specified, named branch mechanics will be ignored and only
2698 topological heads (changesets with no children) will be shown.
2697 topological heads (changesets with no children) will be shown.
2699
2698
2700 Returns 0 if matching heads are found, 1 if not.
2699 Returns 0 if matching heads are found, 1 if not.
2701 """
2700 """
2702
2701
2703 opts = pycompat.byteskwargs(opts)
2702 opts = pycompat.byteskwargs(opts)
2704 start = None
2703 start = None
2705 if 'rev' in opts:
2704 if 'rev' in opts:
2706 start = scmutil.revsingle(repo, opts['rev'], None).node()
2705 start = scmutil.revsingle(repo, opts['rev'], None).node()
2707
2706
2708 if opts.get('topo'):
2707 if opts.get('topo'):
2709 heads = [repo[h] for h in repo.heads(start)]
2708 heads = [repo[h] for h in repo.heads(start)]
2710 else:
2709 else:
2711 heads = []
2710 heads = []
2712 for branch in repo.branchmap():
2711 for branch in repo.branchmap():
2713 heads += repo.branchheads(branch, start, opts.get('closed'))
2712 heads += repo.branchheads(branch, start, opts.get('closed'))
2714 heads = [repo[h] for h in heads]
2713 heads = [repo[h] for h in heads]
2715
2714
2716 if branchrevs:
2715 if branchrevs:
2717 branches = set(repo[br].branch() for br in branchrevs)
2716 branches = set(repo[br].branch() for br in branchrevs)
2718 heads = [h for h in heads if h.branch() in branches]
2717 heads = [h for h in heads if h.branch() in branches]
2719
2718
2720 if opts.get('active') and branchrevs:
2719 if opts.get('active') and branchrevs:
2721 dagheads = repo.heads(start)
2720 dagheads = repo.heads(start)
2722 heads = [h for h in heads if h.node() in dagheads]
2721 heads = [h for h in heads if h.node() in dagheads]
2723
2722
2724 if branchrevs:
2723 if branchrevs:
2725 haveheads = set(h.branch() for h in heads)
2724 haveheads = set(h.branch() for h in heads)
2726 if branches - haveheads:
2725 if branches - haveheads:
2727 headless = ', '.join(b for b in branches - haveheads)
2726 headless = ', '.join(b for b in branches - haveheads)
2728 msg = _('no open branch heads found on branches %s')
2727 msg = _('no open branch heads found on branches %s')
2729 if opts.get('rev'):
2728 if opts.get('rev'):
2730 msg += _(' (started at %s)') % opts['rev']
2729 msg += _(' (started at %s)') % opts['rev']
2731 ui.warn((msg + '\n') % headless)
2730 ui.warn((msg + '\n') % headless)
2732
2731
2733 if not heads:
2732 if not heads:
2734 return 1
2733 return 1
2735
2734
2736 ui.pager('heads')
2735 ui.pager('heads')
2737 heads = sorted(heads, key=lambda x: -x.rev())
2736 heads = sorted(heads, key=lambda x: -x.rev())
2738 displayer = cmdutil.show_changeset(ui, repo, opts)
2737 displayer = cmdutil.show_changeset(ui, repo, opts)
2739 for ctx in heads:
2738 for ctx in heads:
2740 displayer.show(ctx)
2739 displayer.show(ctx)
2741 displayer.close()
2740 displayer.close()
2742
2741
2743 @command('help',
2742 @command('help',
2744 [('e', 'extension', None, _('show only help for extensions')),
2743 [('e', 'extension', None, _('show only help for extensions')),
2745 ('c', 'command', None, _('show only help for commands')),
2744 ('c', 'command', None, _('show only help for commands')),
2746 ('k', 'keyword', None, _('show topics matching keyword')),
2745 ('k', 'keyword', None, _('show topics matching keyword')),
2747 ('s', 'system', [], _('show help for specific platform(s)')),
2746 ('s', 'system', [], _('show help for specific platform(s)')),
2748 ],
2747 ],
2749 _('[-ecks] [TOPIC]'),
2748 _('[-ecks] [TOPIC]'),
2750 norepo=True)
2749 norepo=True)
2751 def help_(ui, name=None, **opts):
2750 def help_(ui, name=None, **opts):
2752 """show help for a given topic or a help overview
2751 """show help for a given topic or a help overview
2753
2752
2754 With no arguments, print a list of commands with short help messages.
2753 With no arguments, print a list of commands with short help messages.
2755
2754
2756 Given a topic, extension, or command name, print help for that
2755 Given a topic, extension, or command name, print help for that
2757 topic.
2756 topic.
2758
2757
2759 Returns 0 if successful.
2758 Returns 0 if successful.
2760 """
2759 """
2761
2760
2762 keep = opts.get(r'system') or []
2761 keep = opts.get(r'system') or []
2763 if len(keep) == 0:
2762 if len(keep) == 0:
2764 if pycompat.sysplatform.startswith('win'):
2763 if pycompat.sysplatform.startswith('win'):
2765 keep.append('windows')
2764 keep.append('windows')
2766 elif pycompat.sysplatform == 'OpenVMS':
2765 elif pycompat.sysplatform == 'OpenVMS':
2767 keep.append('vms')
2766 keep.append('vms')
2768 elif pycompat.sysplatform == 'plan9':
2767 elif pycompat.sysplatform == 'plan9':
2769 keep.append('plan9')
2768 keep.append('plan9')
2770 else:
2769 else:
2771 keep.append('unix')
2770 keep.append('unix')
2772 keep.append(pycompat.sysplatform.lower())
2771 keep.append(pycompat.sysplatform.lower())
2773 if ui.verbose:
2772 if ui.verbose:
2774 keep.append('verbose')
2773 keep.append('verbose')
2775
2774
2776 formatted = help.formattedhelp(ui, name, keep=keep, **opts)
2775 formatted = help.formattedhelp(ui, name, keep=keep, **opts)
2777 ui.pager('help')
2776 ui.pager('help')
2778 ui.write(formatted)
2777 ui.write(formatted)
2779
2778
2780
2779
2781 @command('identify|id',
2780 @command('identify|id',
2782 [('r', 'rev', '',
2781 [('r', 'rev', '',
2783 _('identify the specified revision'), _('REV')),
2782 _('identify the specified revision'), _('REV')),
2784 ('n', 'num', None, _('show local revision number')),
2783 ('n', 'num', None, _('show local revision number')),
2785 ('i', 'id', None, _('show global revision id')),
2784 ('i', 'id', None, _('show global revision id')),
2786 ('b', 'branch', None, _('show branch')),
2785 ('b', 'branch', None, _('show branch')),
2787 ('t', 'tags', None, _('show tags')),
2786 ('t', 'tags', None, _('show tags')),
2788 ('B', 'bookmarks', None, _('show bookmarks')),
2787 ('B', 'bookmarks', None, _('show bookmarks')),
2789 ] + remoteopts,
2788 ] + remoteopts,
2790 _('[-nibtB] [-r REV] [SOURCE]'),
2789 _('[-nibtB] [-r REV] [SOURCE]'),
2791 optionalrepo=True)
2790 optionalrepo=True)
2792 def identify(ui, repo, source=None, rev=None,
2791 def identify(ui, repo, source=None, rev=None,
2793 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2792 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2794 """identify the working directory or specified revision
2793 """identify the working directory or specified revision
2795
2794
2796 Print a summary identifying the repository state at REV using one or
2795 Print a summary identifying the repository state at REV using one or
2797 two parent hash identifiers, followed by a "+" if the working
2796 two parent hash identifiers, followed by a "+" if the working
2798 directory has uncommitted changes, the branch name (if not default),
2797 directory has uncommitted changes, the branch name (if not default),
2799 a list of tags, and a list of bookmarks.
2798 a list of tags, and a list of bookmarks.
2800
2799
2801 When REV is not given, print a summary of the current state of the
2800 When REV is not given, print a summary of the current state of the
2802 repository.
2801 repository.
2803
2802
2804 Specifying a path to a repository root or Mercurial bundle will
2803 Specifying a path to a repository root or Mercurial bundle will
2805 cause lookup to operate on that repository/bundle.
2804 cause lookup to operate on that repository/bundle.
2806
2805
2807 .. container:: verbose
2806 .. container:: verbose
2808
2807
2809 Examples:
2808 Examples:
2810
2809
2811 - generate a build identifier for the working directory::
2810 - generate a build identifier for the working directory::
2812
2811
2813 hg id --id > build-id.dat
2812 hg id --id > build-id.dat
2814
2813
2815 - find the revision corresponding to a tag::
2814 - find the revision corresponding to a tag::
2816
2815
2817 hg id -n -r 1.3
2816 hg id -n -r 1.3
2818
2817
2819 - check the most recent revision of a remote repository::
2818 - check the most recent revision of a remote repository::
2820
2819
2821 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2820 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2822
2821
2823 See :hg:`log` for generating more information about specific revisions,
2822 See :hg:`log` for generating more information about specific revisions,
2824 including full hash identifiers.
2823 including full hash identifiers.
2825
2824
2826 Returns 0 if successful.
2825 Returns 0 if successful.
2827 """
2826 """
2828
2827
2829 opts = pycompat.byteskwargs(opts)
2828 opts = pycompat.byteskwargs(opts)
2830 if not repo and not source:
2829 if not repo and not source:
2831 raise error.Abort(_("there is no Mercurial repository here "
2830 raise error.Abort(_("there is no Mercurial repository here "
2832 "(.hg not found)"))
2831 "(.hg not found)"))
2833
2832
2834 if ui.debugflag:
2833 if ui.debugflag:
2835 hexfunc = hex
2834 hexfunc = hex
2836 else:
2835 else:
2837 hexfunc = short
2836 hexfunc = short
2838 default = not (num or id or branch or tags or bookmarks)
2837 default = not (num or id or branch or tags or bookmarks)
2839 output = []
2838 output = []
2840 revs = []
2839 revs = []
2841
2840
2842 if source:
2841 if source:
2843 source, branches = hg.parseurl(ui.expandpath(source))
2842 source, branches = hg.parseurl(ui.expandpath(source))
2844 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2843 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2845 repo = peer.local()
2844 repo = peer.local()
2846 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2845 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2847
2846
2848 if not repo:
2847 if not repo:
2849 if num or branch or tags:
2848 if num or branch or tags:
2850 raise error.Abort(
2849 raise error.Abort(
2851 _("can't query remote revision number, branch, or tags"))
2850 _("can't query remote revision number, branch, or tags"))
2852 if not rev and revs:
2851 if not rev and revs:
2853 rev = revs[0]
2852 rev = revs[0]
2854 if not rev:
2853 if not rev:
2855 rev = "tip"
2854 rev = "tip"
2856
2855
2857 remoterev = peer.lookup(rev)
2856 remoterev = peer.lookup(rev)
2858 if default or id:
2857 if default or id:
2859 output = [hexfunc(remoterev)]
2858 output = [hexfunc(remoterev)]
2860
2859
2861 def getbms():
2860 def getbms():
2862 bms = []
2861 bms = []
2863
2862
2864 if 'bookmarks' in peer.listkeys('namespaces'):
2863 if 'bookmarks' in peer.listkeys('namespaces'):
2865 hexremoterev = hex(remoterev)
2864 hexremoterev = hex(remoterev)
2866 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2865 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2867 if bmr == hexremoterev]
2866 if bmr == hexremoterev]
2868
2867
2869 return sorted(bms)
2868 return sorted(bms)
2870
2869
2871 if bookmarks:
2870 if bookmarks:
2872 output.extend(getbms())
2871 output.extend(getbms())
2873 elif default and not ui.quiet:
2872 elif default and not ui.quiet:
2874 # multiple bookmarks for a single parent separated by '/'
2873 # multiple bookmarks for a single parent separated by '/'
2875 bm = '/'.join(getbms())
2874 bm = '/'.join(getbms())
2876 if bm:
2875 if bm:
2877 output.append(bm)
2876 output.append(bm)
2878 else:
2877 else:
2879 ctx = scmutil.revsingle(repo, rev, None)
2878 ctx = scmutil.revsingle(repo, rev, None)
2880
2879
2881 if ctx.rev() is None:
2880 if ctx.rev() is None:
2882 ctx = repo[None]
2881 ctx = repo[None]
2883 parents = ctx.parents()
2882 parents = ctx.parents()
2884 taglist = []
2883 taglist = []
2885 for p in parents:
2884 for p in parents:
2886 taglist.extend(p.tags())
2885 taglist.extend(p.tags())
2887
2886
2888 changed = ""
2887 changed = ""
2889 if default or id or num:
2888 if default or id or num:
2890 if (any(repo.status())
2889 if (any(repo.status())
2891 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2890 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2892 changed = '+'
2891 changed = '+'
2893 if default or id:
2892 if default or id:
2894 output = ["%s%s" %
2893 output = ["%s%s" %
2895 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2894 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2896 if num:
2895 if num:
2897 output.append("%s%s" %
2896 output.append("%s%s" %
2898 ('+'.join([str(p.rev()) for p in parents]), changed))
2897 ('+'.join([str(p.rev()) for p in parents]), changed))
2899 else:
2898 else:
2900 if default or id:
2899 if default or id:
2901 output = [hexfunc(ctx.node())]
2900 output = [hexfunc(ctx.node())]
2902 if num:
2901 if num:
2903 output.append(str(ctx.rev()))
2902 output.append(str(ctx.rev()))
2904 taglist = ctx.tags()
2903 taglist = ctx.tags()
2905
2904
2906 if default and not ui.quiet:
2905 if default and not ui.quiet:
2907 b = ctx.branch()
2906 b = ctx.branch()
2908 if b != 'default':
2907 if b != 'default':
2909 output.append("(%s)" % b)
2908 output.append("(%s)" % b)
2910
2909
2911 # multiple tags for a single parent separated by '/'
2910 # multiple tags for a single parent separated by '/'
2912 t = '/'.join(taglist)
2911 t = '/'.join(taglist)
2913 if t:
2912 if t:
2914 output.append(t)
2913 output.append(t)
2915
2914
2916 # multiple bookmarks for a single parent separated by '/'
2915 # multiple bookmarks for a single parent separated by '/'
2917 bm = '/'.join(ctx.bookmarks())
2916 bm = '/'.join(ctx.bookmarks())
2918 if bm:
2917 if bm:
2919 output.append(bm)
2918 output.append(bm)
2920 else:
2919 else:
2921 if branch:
2920 if branch:
2922 output.append(ctx.branch())
2921 output.append(ctx.branch())
2923
2922
2924 if tags:
2923 if tags:
2925 output.extend(taglist)
2924 output.extend(taglist)
2926
2925
2927 if bookmarks:
2926 if bookmarks:
2928 output.extend(ctx.bookmarks())
2927 output.extend(ctx.bookmarks())
2929
2928
2930 ui.write("%s\n" % ' '.join(output))
2929 ui.write("%s\n" % ' '.join(output))
2931
2930
2932 @command('import|patch',
2931 @command('import|patch',
2933 [('p', 'strip', 1,
2932 [('p', 'strip', 1,
2934 _('directory strip option for patch. This has the same '
2933 _('directory strip option for patch. This has the same '
2935 'meaning as the corresponding patch option'), _('NUM')),
2934 'meaning as the corresponding patch option'), _('NUM')),
2936 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2935 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2937 ('e', 'edit', False, _('invoke editor on commit messages')),
2936 ('e', 'edit', False, _('invoke editor on commit messages')),
2938 ('f', 'force', None,
2937 ('f', 'force', None,
2939 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2938 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2940 ('', 'no-commit', None,
2939 ('', 'no-commit', None,
2941 _("don't commit, just update the working directory")),
2940 _("don't commit, just update the working directory")),
2942 ('', 'bypass', None,
2941 ('', 'bypass', None,
2943 _("apply patch without touching the working directory")),
2942 _("apply patch without touching the working directory")),
2944 ('', 'partial', None,
2943 ('', 'partial', None,
2945 _('commit even if some hunks fail')),
2944 _('commit even if some hunks fail')),
2946 ('', 'exact', None,
2945 ('', 'exact', None,
2947 _('abort if patch would apply lossily')),
2946 _('abort if patch would apply lossily')),
2948 ('', 'prefix', '',
2947 ('', 'prefix', '',
2949 _('apply patch to subdirectory'), _('DIR')),
2948 _('apply patch to subdirectory'), _('DIR')),
2950 ('', 'import-branch', None,
2949 ('', 'import-branch', None,
2951 _('use any branch information in patch (implied by --exact)'))] +
2950 _('use any branch information in patch (implied by --exact)'))] +
2952 commitopts + commitopts2 + similarityopts,
2951 commitopts + commitopts2 + similarityopts,
2953 _('[OPTION]... PATCH...'))
2952 _('[OPTION]... PATCH...'))
2954 def import_(ui, repo, patch1=None, *patches, **opts):
2953 def import_(ui, repo, patch1=None, *patches, **opts):
2955 """import an ordered set of patches
2954 """import an ordered set of patches
2956
2955
2957 Import a list of patches and commit them individually (unless
2956 Import a list of patches and commit them individually (unless
2958 --no-commit is specified).
2957 --no-commit is specified).
2959
2958
2960 To read a patch from standard input (stdin), use "-" as the patch
2959 To read a patch from standard input (stdin), use "-" as the patch
2961 name. If a URL is specified, the patch will be downloaded from
2960 name. If a URL is specified, the patch will be downloaded from
2962 there.
2961 there.
2963
2962
2964 Import first applies changes to the working directory (unless
2963 Import first applies changes to the working directory (unless
2965 --bypass is specified), import will abort if there are outstanding
2964 --bypass is specified), import will abort if there are outstanding
2966 changes.
2965 changes.
2967
2966
2968 Use --bypass to apply and commit patches directly to the
2967 Use --bypass to apply and commit patches directly to the
2969 repository, without affecting the working directory. Without
2968 repository, without affecting the working directory. Without
2970 --exact, patches will be applied on top of the working directory
2969 --exact, patches will be applied on top of the working directory
2971 parent revision.
2970 parent revision.
2972
2971
2973 You can import a patch straight from a mail message. Even patches
2972 You can import a patch straight from a mail message. Even patches
2974 as attachments work (to use the body part, it must have type
2973 as attachments work (to use the body part, it must have type
2975 text/plain or text/x-patch). From and Subject headers of email
2974 text/plain or text/x-patch). From and Subject headers of email
2976 message are used as default committer and commit message. All
2975 message are used as default committer and commit message. All
2977 text/plain body parts before first diff are added to the commit
2976 text/plain body parts before first diff are added to the commit
2978 message.
2977 message.
2979
2978
2980 If the imported patch was generated by :hg:`export`, user and
2979 If the imported patch was generated by :hg:`export`, user and
2981 description from patch override values from message headers and
2980 description from patch override values from message headers and
2982 body. Values given on command line with -m/--message and -u/--user
2981 body. Values given on command line with -m/--message and -u/--user
2983 override these.
2982 override these.
2984
2983
2985 If --exact is specified, import will set the working directory to
2984 If --exact is specified, import will set the working directory to
2986 the parent of each patch before applying it, and will abort if the
2985 the parent of each patch before applying it, and will abort if the
2987 resulting changeset has a different ID than the one recorded in
2986 resulting changeset has a different ID than the one recorded in
2988 the patch. This will guard against various ways that portable
2987 the patch. This will guard against various ways that portable
2989 patch formats and mail systems might fail to transfer Mercurial
2988 patch formats and mail systems might fail to transfer Mercurial
2990 data or metadata. See :hg:`bundle` for lossless transmission.
2989 data or metadata. See :hg:`bundle` for lossless transmission.
2991
2990
2992 Use --partial to ensure a changeset will be created from the patch
2991 Use --partial to ensure a changeset will be created from the patch
2993 even if some hunks fail to apply. Hunks that fail to apply will be
2992 even if some hunks fail to apply. Hunks that fail to apply will be
2994 written to a <target-file>.rej file. Conflicts can then be resolved
2993 written to a <target-file>.rej file. Conflicts can then be resolved
2995 by hand before :hg:`commit --amend` is run to update the created
2994 by hand before :hg:`commit --amend` is run to update the created
2996 changeset. This flag exists to let people import patches that
2995 changeset. This flag exists to let people import patches that
2997 partially apply without losing the associated metadata (author,
2996 partially apply without losing the associated metadata (author,
2998 date, description, ...).
2997 date, description, ...).
2999
2998
3000 .. note::
2999 .. note::
3001
3000
3002 When no hunks apply cleanly, :hg:`import --partial` will create
3001 When no hunks apply cleanly, :hg:`import --partial` will create
3003 an empty changeset, importing only the patch metadata.
3002 an empty changeset, importing only the patch metadata.
3004
3003
3005 With -s/--similarity, hg will attempt to discover renames and
3004 With -s/--similarity, hg will attempt to discover renames and
3006 copies in the patch in the same way as :hg:`addremove`.
3005 copies in the patch in the same way as :hg:`addremove`.
3007
3006
3008 It is possible to use external patch programs to perform the patch
3007 It is possible to use external patch programs to perform the patch
3009 by setting the ``ui.patch`` configuration option. For the default
3008 by setting the ``ui.patch`` configuration option. For the default
3010 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3009 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3011 See :hg:`help config` for more information about configuration
3010 See :hg:`help config` for more information about configuration
3012 files and how to use these options.
3011 files and how to use these options.
3013
3012
3014 See :hg:`help dates` for a list of formats valid for -d/--date.
3013 See :hg:`help dates` for a list of formats valid for -d/--date.
3015
3014
3016 .. container:: verbose
3015 .. container:: verbose
3017
3016
3018 Examples:
3017 Examples:
3019
3018
3020 - import a traditional patch from a website and detect renames::
3019 - import a traditional patch from a website and detect renames::
3021
3020
3022 hg import -s 80 http://example.com/bugfix.patch
3021 hg import -s 80 http://example.com/bugfix.patch
3023
3022
3024 - import a changeset from an hgweb server::
3023 - import a changeset from an hgweb server::
3025
3024
3026 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3025 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3027
3026
3028 - import all the patches in an Unix-style mbox::
3027 - import all the patches in an Unix-style mbox::
3029
3028
3030 hg import incoming-patches.mbox
3029 hg import incoming-patches.mbox
3031
3030
3032 - import patches from stdin::
3031 - import patches from stdin::
3033
3032
3034 hg import -
3033 hg import -
3035
3034
3036 - attempt to exactly restore an exported changeset (not always
3035 - attempt to exactly restore an exported changeset (not always
3037 possible)::
3036 possible)::
3038
3037
3039 hg import --exact proposed-fix.patch
3038 hg import --exact proposed-fix.patch
3040
3039
3041 - use an external tool to apply a patch which is too fuzzy for
3040 - use an external tool to apply a patch which is too fuzzy for
3042 the default internal tool.
3041 the default internal tool.
3043
3042
3044 hg import --config ui.patch="patch --merge" fuzzy.patch
3043 hg import --config ui.patch="patch --merge" fuzzy.patch
3045
3044
3046 - change the default fuzzing from 2 to a less strict 7
3045 - change the default fuzzing from 2 to a less strict 7
3047
3046
3048 hg import --config ui.fuzz=7 fuzz.patch
3047 hg import --config ui.fuzz=7 fuzz.patch
3049
3048
3050 Returns 0 on success, 1 on partial success (see --partial).
3049 Returns 0 on success, 1 on partial success (see --partial).
3051 """
3050 """
3052
3051
3053 opts = pycompat.byteskwargs(opts)
3052 opts = pycompat.byteskwargs(opts)
3054 if not patch1:
3053 if not patch1:
3055 raise error.Abort(_('need at least one patch to import'))
3054 raise error.Abort(_('need at least one patch to import'))
3056
3055
3057 patches = (patch1,) + patches
3056 patches = (patch1,) + patches
3058
3057
3059 date = opts.get('date')
3058 date = opts.get('date')
3060 if date:
3059 if date:
3061 opts['date'] = util.parsedate(date)
3060 opts['date'] = util.parsedate(date)
3062
3061
3063 exact = opts.get('exact')
3062 exact = opts.get('exact')
3064 update = not opts.get('bypass')
3063 update = not opts.get('bypass')
3065 if not update and opts.get('no_commit'):
3064 if not update and opts.get('no_commit'):
3066 raise error.Abort(_('cannot use --no-commit with --bypass'))
3065 raise error.Abort(_('cannot use --no-commit with --bypass'))
3067 try:
3066 try:
3068 sim = float(opts.get('similarity') or 0)
3067 sim = float(opts.get('similarity') or 0)
3069 except ValueError:
3068 except ValueError:
3070 raise error.Abort(_('similarity must be a number'))
3069 raise error.Abort(_('similarity must be a number'))
3071 if sim < 0 or sim > 100:
3070 if sim < 0 or sim > 100:
3072 raise error.Abort(_('similarity must be between 0 and 100'))
3071 raise error.Abort(_('similarity must be between 0 and 100'))
3073 if sim and not update:
3072 if sim and not update:
3074 raise error.Abort(_('cannot use --similarity with --bypass'))
3073 raise error.Abort(_('cannot use --similarity with --bypass'))
3075 if exact:
3074 if exact:
3076 if opts.get('edit'):
3075 if opts.get('edit'):
3077 raise error.Abort(_('cannot use --exact with --edit'))
3076 raise error.Abort(_('cannot use --exact with --edit'))
3078 if opts.get('prefix'):
3077 if opts.get('prefix'):
3079 raise error.Abort(_('cannot use --exact with --prefix'))
3078 raise error.Abort(_('cannot use --exact with --prefix'))
3080
3079
3081 base = opts["base"]
3080 base = opts["base"]
3082 wlock = dsguard = lock = tr = None
3081 wlock = dsguard = lock = tr = None
3083 msgs = []
3082 msgs = []
3084 ret = 0
3083 ret = 0
3085
3084
3086
3085
3087 try:
3086 try:
3088 wlock = repo.wlock()
3087 wlock = repo.wlock()
3089
3088
3090 if update:
3089 if update:
3091 cmdutil.checkunfinished(repo)
3090 cmdutil.checkunfinished(repo)
3092 if (exact or not opts.get('force')):
3091 if (exact or not opts.get('force')):
3093 cmdutil.bailifchanged(repo)
3092 cmdutil.bailifchanged(repo)
3094
3093
3095 if not opts.get('no_commit'):
3094 if not opts.get('no_commit'):
3096 lock = repo.lock()
3095 lock = repo.lock()
3097 tr = repo.transaction('import')
3096 tr = repo.transaction('import')
3098 else:
3097 else:
3099 dsguard = dirstateguard.dirstateguard(repo, 'import')
3098 dsguard = dirstateguard.dirstateguard(repo, 'import')
3100 parents = repo[None].parents()
3099 parents = repo[None].parents()
3101 for patchurl in patches:
3100 for patchurl in patches:
3102 if patchurl == '-':
3101 if patchurl == '-':
3103 ui.status(_('applying patch from stdin\n'))
3102 ui.status(_('applying patch from stdin\n'))
3104 patchfile = ui.fin
3103 patchfile = ui.fin
3105 patchurl = 'stdin' # for error message
3104 patchurl = 'stdin' # for error message
3106 else:
3105 else:
3107 patchurl = os.path.join(base, patchurl)
3106 patchurl = os.path.join(base, patchurl)
3108 ui.status(_('applying %s\n') % patchurl)
3107 ui.status(_('applying %s\n') % patchurl)
3109 patchfile = hg.openpath(ui, patchurl)
3108 patchfile = hg.openpath(ui, patchurl)
3110
3109
3111 haspatch = False
3110 haspatch = False
3112 for hunk in patch.split(patchfile):
3111 for hunk in patch.split(patchfile):
3113 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3112 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3114 parents, opts,
3113 parents, opts,
3115 msgs, hg.clean)
3114 msgs, hg.clean)
3116 if msg:
3115 if msg:
3117 haspatch = True
3116 haspatch = True
3118 ui.note(msg + '\n')
3117 ui.note(msg + '\n')
3119 if update or exact:
3118 if update or exact:
3120 parents = repo[None].parents()
3119 parents = repo[None].parents()
3121 else:
3120 else:
3122 parents = [repo[node]]
3121 parents = [repo[node]]
3123 if rej:
3122 if rej:
3124 ui.write_err(_("patch applied partially\n"))
3123 ui.write_err(_("patch applied partially\n"))
3125 ui.write_err(_("(fix the .rej files and run "
3124 ui.write_err(_("(fix the .rej files and run "
3126 "`hg commit --amend`)\n"))
3125 "`hg commit --amend`)\n"))
3127 ret = 1
3126 ret = 1
3128 break
3127 break
3129
3128
3130 if not haspatch:
3129 if not haspatch:
3131 raise error.Abort(_('%s: no diffs found') % patchurl)
3130 raise error.Abort(_('%s: no diffs found') % patchurl)
3132
3131
3133 if tr:
3132 if tr:
3134 tr.close()
3133 tr.close()
3135 if msgs:
3134 if msgs:
3136 repo.savecommitmessage('\n* * *\n'.join(msgs))
3135 repo.savecommitmessage('\n* * *\n'.join(msgs))
3137 if dsguard:
3136 if dsguard:
3138 dsguard.close()
3137 dsguard.close()
3139 return ret
3138 return ret
3140 finally:
3139 finally:
3141 if tr:
3140 if tr:
3142 tr.release()
3141 tr.release()
3143 release(lock, dsguard, wlock)
3142 release(lock, dsguard, wlock)
3144
3143
3145 @command('incoming|in',
3144 @command('incoming|in',
3146 [('f', 'force', None,
3145 [('f', 'force', None,
3147 _('run even if remote repository is unrelated')),
3146 _('run even if remote repository is unrelated')),
3148 ('n', 'newest-first', None, _('show newest record first')),
3147 ('n', 'newest-first', None, _('show newest record first')),
3149 ('', 'bundle', '',
3148 ('', 'bundle', '',
3150 _('file to store the bundles into'), _('FILE')),
3149 _('file to store the bundles into'), _('FILE')),
3151 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3150 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3152 ('B', 'bookmarks', False, _("compare bookmarks")),
3151 ('B', 'bookmarks', False, _("compare bookmarks")),
3153 ('b', 'branch', [],
3152 ('b', 'branch', [],
3154 _('a specific branch you would like to pull'), _('BRANCH')),
3153 _('a specific branch you would like to pull'), _('BRANCH')),
3155 ] + logopts + remoteopts + subrepoopts,
3154 ] + logopts + remoteopts + subrepoopts,
3156 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3155 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3157 def incoming(ui, repo, source="default", **opts):
3156 def incoming(ui, repo, source="default", **opts):
3158 """show new changesets found in source
3157 """show new changesets found in source
3159
3158
3160 Show new changesets found in the specified path/URL or the default
3159 Show new changesets found in the specified path/URL or the default
3161 pull location. These are the changesets that would have been pulled
3160 pull location. These are the changesets that would have been pulled
3162 if a pull at the time you issued this command.
3161 if a pull at the time you issued this command.
3163
3162
3164 See pull for valid source format details.
3163 See pull for valid source format details.
3165
3164
3166 .. container:: verbose
3165 .. container:: verbose
3167
3166
3168 With -B/--bookmarks, the result of bookmark comparison between
3167 With -B/--bookmarks, the result of bookmark comparison between
3169 local and remote repositories is displayed. With -v/--verbose,
3168 local and remote repositories is displayed. With -v/--verbose,
3170 status is also displayed for each bookmark like below::
3169 status is also displayed for each bookmark like below::
3171
3170
3172 BM1 01234567890a added
3171 BM1 01234567890a added
3173 BM2 1234567890ab advanced
3172 BM2 1234567890ab advanced
3174 BM3 234567890abc diverged
3173 BM3 234567890abc diverged
3175 BM4 34567890abcd changed
3174 BM4 34567890abcd changed
3176
3175
3177 The action taken locally when pulling depends on the
3176 The action taken locally when pulling depends on the
3178 status of each bookmark:
3177 status of each bookmark:
3179
3178
3180 :``added``: pull will create it
3179 :``added``: pull will create it
3181 :``advanced``: pull will update it
3180 :``advanced``: pull will update it
3182 :``diverged``: pull will create a divergent bookmark
3181 :``diverged``: pull will create a divergent bookmark
3183 :``changed``: result depends on remote changesets
3182 :``changed``: result depends on remote changesets
3184
3183
3185 From the point of view of pulling behavior, bookmark
3184 From the point of view of pulling behavior, bookmark
3186 existing only in the remote repository are treated as ``added``,
3185 existing only in the remote repository are treated as ``added``,
3187 even if it is in fact locally deleted.
3186 even if it is in fact locally deleted.
3188
3187
3189 .. container:: verbose
3188 .. container:: verbose
3190
3189
3191 For remote repository, using --bundle avoids downloading the
3190 For remote repository, using --bundle avoids downloading the
3192 changesets twice if the incoming is followed by a pull.
3191 changesets twice if the incoming is followed by a pull.
3193
3192
3194 Examples:
3193 Examples:
3195
3194
3196 - show incoming changes with patches and full description::
3195 - show incoming changes with patches and full description::
3197
3196
3198 hg incoming -vp
3197 hg incoming -vp
3199
3198
3200 - show incoming changes excluding merges, store a bundle::
3199 - show incoming changes excluding merges, store a bundle::
3201
3200
3202 hg in -vpM --bundle incoming.hg
3201 hg in -vpM --bundle incoming.hg
3203 hg pull incoming.hg
3202 hg pull incoming.hg
3204
3203
3205 - briefly list changes inside a bundle::
3204 - briefly list changes inside a bundle::
3206
3205
3207 hg in changes.hg -T "{desc|firstline}\\n"
3206 hg in changes.hg -T "{desc|firstline}\\n"
3208
3207
3209 Returns 0 if there are incoming changes, 1 otherwise.
3208 Returns 0 if there are incoming changes, 1 otherwise.
3210 """
3209 """
3211 opts = pycompat.byteskwargs(opts)
3210 opts = pycompat.byteskwargs(opts)
3212 if opts.get('graph'):
3211 if opts.get('graph'):
3213 cmdutil.checkunsupportedgraphflags([], opts)
3212 cmdutil.checkunsupportedgraphflags([], opts)
3214 def display(other, chlist, displayer):
3213 def display(other, chlist, displayer):
3215 revdag = cmdutil.graphrevs(other, chlist, opts)
3214 revdag = cmdutil.graphrevs(other, chlist, opts)
3216 cmdutil.displaygraph(ui, repo, revdag, displayer,
3215 cmdutil.displaygraph(ui, repo, revdag, displayer,
3217 graphmod.asciiedges)
3216 graphmod.asciiedges)
3218
3217
3219 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3218 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3220 return 0
3219 return 0
3221
3220
3222 if opts.get('bundle') and opts.get('subrepos'):
3221 if opts.get('bundle') and opts.get('subrepos'):
3223 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3222 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3224
3223
3225 if opts.get('bookmarks'):
3224 if opts.get('bookmarks'):
3226 source, branches = hg.parseurl(ui.expandpath(source),
3225 source, branches = hg.parseurl(ui.expandpath(source),
3227 opts.get('branch'))
3226 opts.get('branch'))
3228 other = hg.peer(repo, opts, source)
3227 other = hg.peer(repo, opts, source)
3229 if 'bookmarks' not in other.listkeys('namespaces'):
3228 if 'bookmarks' not in other.listkeys('namespaces'):
3230 ui.warn(_("remote doesn't support bookmarks\n"))
3229 ui.warn(_("remote doesn't support bookmarks\n"))
3231 return 0
3230 return 0
3232 ui.pager('incoming')
3231 ui.pager('incoming')
3233 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3232 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3234 return bookmarks.incoming(ui, repo, other)
3233 return bookmarks.incoming(ui, repo, other)
3235
3234
3236 repo._subtoppath = ui.expandpath(source)
3235 repo._subtoppath = ui.expandpath(source)
3237 try:
3236 try:
3238 return hg.incoming(ui, repo, source, opts)
3237 return hg.incoming(ui, repo, source, opts)
3239 finally:
3238 finally:
3240 del repo._subtoppath
3239 del repo._subtoppath
3241
3240
3242
3241
3243 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3242 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3244 norepo=True)
3243 norepo=True)
3245 def init(ui, dest=".", **opts):
3244 def init(ui, dest=".", **opts):
3246 """create a new repository in the given directory
3245 """create a new repository in the given directory
3247
3246
3248 Initialize a new repository in the given directory. If the given
3247 Initialize a new repository in the given directory. If the given
3249 directory does not exist, it will be created.
3248 directory does not exist, it will be created.
3250
3249
3251 If no directory is given, the current directory is used.
3250 If no directory is given, the current directory is used.
3252
3251
3253 It is possible to specify an ``ssh://`` URL as the destination.
3252 It is possible to specify an ``ssh://`` URL as the destination.
3254 See :hg:`help urls` for more information.
3253 See :hg:`help urls` for more information.
3255
3254
3256 Returns 0 on success.
3255 Returns 0 on success.
3257 """
3256 """
3258 opts = pycompat.byteskwargs(opts)
3257 opts = pycompat.byteskwargs(opts)
3259 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3258 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3260
3259
3261 @command('locate',
3260 @command('locate',
3262 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3261 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3263 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3262 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3264 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3263 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3265 ] + walkopts,
3264 ] + walkopts,
3266 _('[OPTION]... [PATTERN]...'))
3265 _('[OPTION]... [PATTERN]...'))
3267 def locate(ui, repo, *pats, **opts):
3266 def locate(ui, repo, *pats, **opts):
3268 """locate files matching specific patterns (DEPRECATED)
3267 """locate files matching specific patterns (DEPRECATED)
3269
3268
3270 Print files under Mercurial control in the working directory whose
3269 Print files under Mercurial control in the working directory whose
3271 names match the given patterns.
3270 names match the given patterns.
3272
3271
3273 By default, this command searches all directories in the working
3272 By default, this command searches all directories in the working
3274 directory. To search just the current directory and its
3273 directory. To search just the current directory and its
3275 subdirectories, use "--include .".
3274 subdirectories, use "--include .".
3276
3275
3277 If no patterns are given to match, this command prints the names
3276 If no patterns are given to match, this command prints the names
3278 of all files under Mercurial control in the working directory.
3277 of all files under Mercurial control in the working directory.
3279
3278
3280 If you want to feed the output of this command into the "xargs"
3279 If you want to feed the output of this command into the "xargs"
3281 command, use the -0 option to both this command and "xargs". This
3280 command, use the -0 option to both this command and "xargs". This
3282 will avoid the problem of "xargs" treating single filenames that
3281 will avoid the problem of "xargs" treating single filenames that
3283 contain whitespace as multiple filenames.
3282 contain whitespace as multiple filenames.
3284
3283
3285 See :hg:`help files` for a more versatile command.
3284 See :hg:`help files` for a more versatile command.
3286
3285
3287 Returns 0 if a match is found, 1 otherwise.
3286 Returns 0 if a match is found, 1 otherwise.
3288 """
3287 """
3289 opts = pycompat.byteskwargs(opts)
3288 opts = pycompat.byteskwargs(opts)
3290 if opts.get('print0'):
3289 if opts.get('print0'):
3291 end = '\0'
3290 end = '\0'
3292 else:
3291 else:
3293 end = '\n'
3292 end = '\n'
3294 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3293 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3295
3294
3296 ret = 1
3295 ret = 1
3297 ctx = repo[rev]
3296 ctx = repo[rev]
3298 m = scmutil.match(ctx, pats, opts, default='relglob',
3297 m = scmutil.match(ctx, pats, opts, default='relglob',
3299 badfn=lambda x, y: False)
3298 badfn=lambda x, y: False)
3300
3299
3301 ui.pager('locate')
3300 ui.pager('locate')
3302 for abs in ctx.matches(m):
3301 for abs in ctx.matches(m):
3303 if opts.get('fullpath'):
3302 if opts.get('fullpath'):
3304 ui.write(repo.wjoin(abs), end)
3303 ui.write(repo.wjoin(abs), end)
3305 else:
3304 else:
3306 ui.write(((pats and m.rel(abs)) or abs), end)
3305 ui.write(((pats and m.rel(abs)) or abs), end)
3307 ret = 0
3306 ret = 0
3308
3307
3309 return ret
3308 return ret
3310
3309
3311 @command('^log|history',
3310 @command('^log|history',
3312 [('f', 'follow', None,
3311 [('f', 'follow', None,
3313 _('follow changeset history, or file history across copies and renames')),
3312 _('follow changeset history, or file history across copies and renames')),
3314 ('', 'follow-first', None,
3313 ('', 'follow-first', None,
3315 _('only follow the first parent of merge changesets (DEPRECATED)')),
3314 _('only follow the first parent of merge changesets (DEPRECATED)')),
3316 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3315 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3317 ('C', 'copies', None, _('show copied files')),
3316 ('C', 'copies', None, _('show copied files')),
3318 ('k', 'keyword', [],
3317 ('k', 'keyword', [],
3319 _('do case-insensitive search for a given text'), _('TEXT')),
3318 _('do case-insensitive search for a given text'), _('TEXT')),
3320 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3319 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3321 ('', 'removed', None, _('include revisions where files were removed')),
3320 ('', 'removed', None, _('include revisions where files were removed')),
3322 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3321 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3323 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3322 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3324 ('', 'only-branch', [],
3323 ('', 'only-branch', [],
3325 _('show only changesets within the given named branch (DEPRECATED)'),
3324 _('show only changesets within the given named branch (DEPRECATED)'),
3326 _('BRANCH')),
3325 _('BRANCH')),
3327 ('b', 'branch', [],
3326 ('b', 'branch', [],
3328 _('show changesets within the given named branch'), _('BRANCH')),
3327 _('show changesets within the given named branch'), _('BRANCH')),
3329 ('P', 'prune', [],
3328 ('P', 'prune', [],
3330 _('do not display revision or any of its ancestors'), _('REV')),
3329 _('do not display revision or any of its ancestors'), _('REV')),
3331 ] + logopts + walkopts,
3330 ] + logopts + walkopts,
3332 _('[OPTION]... [FILE]'),
3331 _('[OPTION]... [FILE]'),
3333 inferrepo=True)
3332 inferrepo=True)
3334 def log(ui, repo, *pats, **opts):
3333 def log(ui, repo, *pats, **opts):
3335 """show revision history of entire repository or files
3334 """show revision history of entire repository or files
3336
3335
3337 Print the revision history of the specified files or the entire
3336 Print the revision history of the specified files or the entire
3338 project.
3337 project.
3339
3338
3340 If no revision range is specified, the default is ``tip:0`` unless
3339 If no revision range is specified, the default is ``tip:0`` unless
3341 --follow is set, in which case the working directory parent is
3340 --follow is set, in which case the working directory parent is
3342 used as the starting revision.
3341 used as the starting revision.
3343
3342
3344 File history is shown without following rename or copy history of
3343 File history is shown without following rename or copy history of
3345 files. Use -f/--follow with a filename to follow history across
3344 files. Use -f/--follow with a filename to follow history across
3346 renames and copies. --follow without a filename will only show
3345 renames and copies. --follow without a filename will only show
3347 ancestors or descendants of the starting revision.
3346 ancestors or descendants of the starting revision.
3348
3347
3349 By default this command prints revision number and changeset id,
3348 By default this command prints revision number and changeset id,
3350 tags, non-trivial parents, user, date and time, and a summary for
3349 tags, non-trivial parents, user, date and time, and a summary for
3351 each commit. When the -v/--verbose switch is used, the list of
3350 each commit. When the -v/--verbose switch is used, the list of
3352 changed files and full commit message are shown.
3351 changed files and full commit message are shown.
3353
3352
3354 With --graph the revisions are shown as an ASCII art DAG with the most
3353 With --graph the revisions are shown as an ASCII art DAG with the most
3355 recent changeset at the top.
3354 recent changeset at the top.
3356 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3355 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3357 and '+' represents a fork where the changeset from the lines below is a
3356 and '+' represents a fork where the changeset from the lines below is a
3358 parent of the 'o' merge on the same line.
3357 parent of the 'o' merge on the same line.
3359 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3358 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3360 of a '|' indicates one or more revisions in a path are omitted.
3359 of a '|' indicates one or more revisions in a path are omitted.
3361
3360
3362 .. note::
3361 .. note::
3363
3362
3364 :hg:`log --patch` may generate unexpected diff output for merge
3363 :hg:`log --patch` may generate unexpected diff output for merge
3365 changesets, as it will only compare the merge changeset against
3364 changesets, as it will only compare the merge changeset against
3366 its first parent. Also, only files different from BOTH parents
3365 its first parent. Also, only files different from BOTH parents
3367 will appear in files:.
3366 will appear in files:.
3368
3367
3369 .. note::
3368 .. note::
3370
3369
3371 For performance reasons, :hg:`log FILE` may omit duplicate changes
3370 For performance reasons, :hg:`log FILE` may omit duplicate changes
3372 made on branches and will not show removals or mode changes. To
3371 made on branches and will not show removals or mode changes. To
3373 see all such changes, use the --removed switch.
3372 see all such changes, use the --removed switch.
3374
3373
3375 .. container:: verbose
3374 .. container:: verbose
3376
3375
3377 Some examples:
3376 Some examples:
3378
3377
3379 - changesets with full descriptions and file lists::
3378 - changesets with full descriptions and file lists::
3380
3379
3381 hg log -v
3380 hg log -v
3382
3381
3383 - changesets ancestral to the working directory::
3382 - changesets ancestral to the working directory::
3384
3383
3385 hg log -f
3384 hg log -f
3386
3385
3387 - last 10 commits on the current branch::
3386 - last 10 commits on the current branch::
3388
3387
3389 hg log -l 10 -b .
3388 hg log -l 10 -b .
3390
3389
3391 - changesets showing all modifications of a file, including removals::
3390 - changesets showing all modifications of a file, including removals::
3392
3391
3393 hg log --removed file.c
3392 hg log --removed file.c
3394
3393
3395 - all changesets that touch a directory, with diffs, excluding merges::
3394 - all changesets that touch a directory, with diffs, excluding merges::
3396
3395
3397 hg log -Mp lib/
3396 hg log -Mp lib/
3398
3397
3399 - all revision numbers that match a keyword::
3398 - all revision numbers that match a keyword::
3400
3399
3401 hg log -k bug --template "{rev}\\n"
3400 hg log -k bug --template "{rev}\\n"
3402
3401
3403 - the full hash identifier of the working directory parent::
3402 - the full hash identifier of the working directory parent::
3404
3403
3405 hg log -r . --template "{node}\\n"
3404 hg log -r . --template "{node}\\n"
3406
3405
3407 - list available log templates::
3406 - list available log templates::
3408
3407
3409 hg log -T list
3408 hg log -T list
3410
3409
3411 - check if a given changeset is included in a tagged release::
3410 - check if a given changeset is included in a tagged release::
3412
3411
3413 hg log -r "a21ccf and ancestor(1.9)"
3412 hg log -r "a21ccf and ancestor(1.9)"
3414
3413
3415 - find all changesets by some user in a date range::
3414 - find all changesets by some user in a date range::
3416
3415
3417 hg log -k alice -d "may 2008 to jul 2008"
3416 hg log -k alice -d "may 2008 to jul 2008"
3418
3417
3419 - summary of all changesets after the last tag::
3418 - summary of all changesets after the last tag::
3420
3419
3421 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3420 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3422
3421
3423 See :hg:`help dates` for a list of formats valid for -d/--date.
3422 See :hg:`help dates` for a list of formats valid for -d/--date.
3424
3423
3425 See :hg:`help revisions` for more about specifying and ordering
3424 See :hg:`help revisions` for more about specifying and ordering
3426 revisions.
3425 revisions.
3427
3426
3428 See :hg:`help templates` for more about pre-packaged styles and
3427 See :hg:`help templates` for more about pre-packaged styles and
3429 specifying custom templates.
3428 specifying custom templates.
3430
3429
3431 Returns 0 on success.
3430 Returns 0 on success.
3432
3431
3433 """
3432 """
3434 opts = pycompat.byteskwargs(opts)
3433 opts = pycompat.byteskwargs(opts)
3435 if opts.get('follow') and opts.get('rev'):
3434 if opts.get('follow') and opts.get('rev'):
3436 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3435 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3437 del opts['follow']
3436 del opts['follow']
3438
3437
3439 if opts.get('graph'):
3438 if opts.get('graph'):
3440 return cmdutil.graphlog(ui, repo, pats, opts)
3439 return cmdutil.graphlog(ui, repo, pats, opts)
3441
3440
3442 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3441 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3443 limit = cmdutil.loglimit(opts)
3442 limit = cmdutil.loglimit(opts)
3444 count = 0
3443 count = 0
3445
3444
3446 getrenamed = None
3445 getrenamed = None
3447 if opts.get('copies'):
3446 if opts.get('copies'):
3448 endrev = None
3447 endrev = None
3449 if opts.get('rev'):
3448 if opts.get('rev'):
3450 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3449 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3451 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3450 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3452
3451
3453 ui.pager('log')
3452 ui.pager('log')
3454 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3453 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3455 for rev in revs:
3454 for rev in revs:
3456 if count == limit:
3455 if count == limit:
3457 break
3456 break
3458 ctx = repo[rev]
3457 ctx = repo[rev]
3459 copies = None
3458 copies = None
3460 if getrenamed is not None and rev:
3459 if getrenamed is not None and rev:
3461 copies = []
3460 copies = []
3462 for fn in ctx.files():
3461 for fn in ctx.files():
3463 rename = getrenamed(fn, rev)
3462 rename = getrenamed(fn, rev)
3464 if rename:
3463 if rename:
3465 copies.append((fn, rename[0]))
3464 copies.append((fn, rename[0]))
3466 if filematcher:
3465 if filematcher:
3467 revmatchfn = filematcher(ctx.rev())
3466 revmatchfn = filematcher(ctx.rev())
3468 else:
3467 else:
3469 revmatchfn = None
3468 revmatchfn = None
3470 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3469 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3471 if displayer.flush(ctx):
3470 if displayer.flush(ctx):
3472 count += 1
3471 count += 1
3473
3472
3474 displayer.close()
3473 displayer.close()
3475
3474
3476 @command('manifest',
3475 @command('manifest',
3477 [('r', 'rev', '', _('revision to display'), _('REV')),
3476 [('r', 'rev', '', _('revision to display'), _('REV')),
3478 ('', 'all', False, _("list files from all revisions"))]
3477 ('', 'all', False, _("list files from all revisions"))]
3479 + formatteropts,
3478 + formatteropts,
3480 _('[-r REV]'))
3479 _('[-r REV]'))
3481 def manifest(ui, repo, node=None, rev=None, **opts):
3480 def manifest(ui, repo, node=None, rev=None, **opts):
3482 """output the current or given revision of the project manifest
3481 """output the current or given revision of the project manifest
3483
3482
3484 Print a list of version controlled files for the given revision.
3483 Print a list of version controlled files for the given revision.
3485 If no revision is given, the first parent of the working directory
3484 If no revision is given, the first parent of the working directory
3486 is used, or the null revision if no revision is checked out.
3485 is used, or the null revision if no revision is checked out.
3487
3486
3488 With -v, print file permissions, symlink and executable bits.
3487 With -v, print file permissions, symlink and executable bits.
3489 With --debug, print file revision hashes.
3488 With --debug, print file revision hashes.
3490
3489
3491 If option --all is specified, the list of all files from all revisions
3490 If option --all is specified, the list of all files from all revisions
3492 is printed. This includes deleted and renamed files.
3491 is printed. This includes deleted and renamed files.
3493
3492
3494 Returns 0 on success.
3493 Returns 0 on success.
3495 """
3494 """
3496 opts = pycompat.byteskwargs(opts)
3495 opts = pycompat.byteskwargs(opts)
3497 fm = ui.formatter('manifest', opts)
3496 fm = ui.formatter('manifest', opts)
3498
3497
3499 if opts.get('all'):
3498 if opts.get('all'):
3500 if rev or node:
3499 if rev or node:
3501 raise error.Abort(_("can't specify a revision with --all"))
3500 raise error.Abort(_("can't specify a revision with --all"))
3502
3501
3503 res = []
3502 res = []
3504 prefix = "data/"
3503 prefix = "data/"
3505 suffix = ".i"
3504 suffix = ".i"
3506 plen = len(prefix)
3505 plen = len(prefix)
3507 slen = len(suffix)
3506 slen = len(suffix)
3508 with repo.lock():
3507 with repo.lock():
3509 for fn, b, size in repo.store.datafiles():
3508 for fn, b, size in repo.store.datafiles():
3510 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3509 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3511 res.append(fn[plen:-slen])
3510 res.append(fn[plen:-slen])
3512 ui.pager('manifest')
3511 ui.pager('manifest')
3513 for f in res:
3512 for f in res:
3514 fm.startitem()
3513 fm.startitem()
3515 fm.write("path", '%s\n', f)
3514 fm.write("path", '%s\n', f)
3516 fm.end()
3515 fm.end()
3517 return
3516 return
3518
3517
3519 if rev and node:
3518 if rev and node:
3520 raise error.Abort(_("please specify just one revision"))
3519 raise error.Abort(_("please specify just one revision"))
3521
3520
3522 if not node:
3521 if not node:
3523 node = rev
3522 node = rev
3524
3523
3525 char = {'l': '@', 'x': '*', '': ''}
3524 char = {'l': '@', 'x': '*', '': ''}
3526 mode = {'l': '644', 'x': '755', '': '644'}
3525 mode = {'l': '644', 'x': '755', '': '644'}
3527 ctx = scmutil.revsingle(repo, node)
3526 ctx = scmutil.revsingle(repo, node)
3528 mf = ctx.manifest()
3527 mf = ctx.manifest()
3529 ui.pager('manifest')
3528 ui.pager('manifest')
3530 for f in ctx:
3529 for f in ctx:
3531 fm.startitem()
3530 fm.startitem()
3532 fl = ctx[f].flags()
3531 fl = ctx[f].flags()
3533 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3532 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3534 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3533 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3535 fm.write('path', '%s\n', f)
3534 fm.write('path', '%s\n', f)
3536 fm.end()
3535 fm.end()
3537
3536
3538 @command('^merge',
3537 @command('^merge',
3539 [('f', 'force', None,
3538 [('f', 'force', None,
3540 _('force a merge including outstanding changes (DEPRECATED)')),
3539 _('force a merge including outstanding changes (DEPRECATED)')),
3541 ('r', 'rev', '', _('revision to merge'), _('REV')),
3540 ('r', 'rev', '', _('revision to merge'), _('REV')),
3542 ('P', 'preview', None,
3541 ('P', 'preview', None,
3543 _('review revisions to merge (no merge is performed)'))
3542 _('review revisions to merge (no merge is performed)'))
3544 ] + mergetoolopts,
3543 ] + mergetoolopts,
3545 _('[-P] [[-r] REV]'))
3544 _('[-P] [[-r] REV]'))
3546 def merge(ui, repo, node=None, **opts):
3545 def merge(ui, repo, node=None, **opts):
3547 """merge another revision into working directory
3546 """merge another revision into working directory
3548
3547
3549 The current working directory is updated with all changes made in
3548 The current working directory is updated with all changes made in
3550 the requested revision since the last common predecessor revision.
3549 the requested revision since the last common predecessor revision.
3551
3550
3552 Files that changed between either parent are marked as changed for
3551 Files that changed between either parent are marked as changed for
3553 the next commit and a commit must be performed before any further
3552 the next commit and a commit must be performed before any further
3554 updates to the repository are allowed. The next commit will have
3553 updates to the repository are allowed. The next commit will have
3555 two parents.
3554 two parents.
3556
3555
3557 ``--tool`` can be used to specify the merge tool used for file
3556 ``--tool`` can be used to specify the merge tool used for file
3558 merges. It overrides the HGMERGE environment variable and your
3557 merges. It overrides the HGMERGE environment variable and your
3559 configuration files. See :hg:`help merge-tools` for options.
3558 configuration files. See :hg:`help merge-tools` for options.
3560
3559
3561 If no revision is specified, the working directory's parent is a
3560 If no revision is specified, the working directory's parent is a
3562 head revision, and the current branch contains exactly one other
3561 head revision, and the current branch contains exactly one other
3563 head, the other head is merged with by default. Otherwise, an
3562 head, the other head is merged with by default. Otherwise, an
3564 explicit revision with which to merge with must be provided.
3563 explicit revision with which to merge with must be provided.
3565
3564
3566 See :hg:`help resolve` for information on handling file conflicts.
3565 See :hg:`help resolve` for information on handling file conflicts.
3567
3566
3568 To undo an uncommitted merge, use :hg:`update --clean .` which
3567 To undo an uncommitted merge, use :hg:`update --clean .` which
3569 will check out a clean copy of the original merge parent, losing
3568 will check out a clean copy of the original merge parent, losing
3570 all changes.
3569 all changes.
3571
3570
3572 Returns 0 on success, 1 if there are unresolved files.
3571 Returns 0 on success, 1 if there are unresolved files.
3573 """
3572 """
3574
3573
3575 opts = pycompat.byteskwargs(opts)
3574 opts = pycompat.byteskwargs(opts)
3576 if opts.get('rev') and node:
3575 if opts.get('rev') and node:
3577 raise error.Abort(_("please specify just one revision"))
3576 raise error.Abort(_("please specify just one revision"))
3578 if not node:
3577 if not node:
3579 node = opts.get('rev')
3578 node = opts.get('rev')
3580
3579
3581 if node:
3580 if node:
3582 node = scmutil.revsingle(repo, node).node()
3581 node = scmutil.revsingle(repo, node).node()
3583
3582
3584 if not node:
3583 if not node:
3585 node = repo[destutil.destmerge(repo)].node()
3584 node = repo[destutil.destmerge(repo)].node()
3586
3585
3587 if opts.get('preview'):
3586 if opts.get('preview'):
3588 # find nodes that are ancestors of p2 but not of p1
3587 # find nodes that are ancestors of p2 but not of p1
3589 p1 = repo.lookup('.')
3588 p1 = repo.lookup('.')
3590 p2 = repo.lookup(node)
3589 p2 = repo.lookup(node)
3591 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3590 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3592
3591
3593 displayer = cmdutil.show_changeset(ui, repo, opts)
3592 displayer = cmdutil.show_changeset(ui, repo, opts)
3594 for node in nodes:
3593 for node in nodes:
3595 displayer.show(repo[node])
3594 displayer.show(repo[node])
3596 displayer.close()
3595 displayer.close()
3597 return 0
3596 return 0
3598
3597
3599 try:
3598 try:
3600 # ui.forcemerge is an internal variable, do not document
3599 # ui.forcemerge is an internal variable, do not document
3601 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3600 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3602 force = opts.get('force')
3601 force = opts.get('force')
3603 labels = ['working copy', 'merge rev']
3602 labels = ['working copy', 'merge rev']
3604 return hg.merge(repo, node, force=force, mergeforce=force,
3603 return hg.merge(repo, node, force=force, mergeforce=force,
3605 labels=labels)
3604 labels=labels)
3606 finally:
3605 finally:
3607 ui.setconfig('ui', 'forcemerge', '', 'merge')
3606 ui.setconfig('ui', 'forcemerge', '', 'merge')
3608
3607
3609 @command('outgoing|out',
3608 @command('outgoing|out',
3610 [('f', 'force', None, _('run even when the destination is unrelated')),
3609 [('f', 'force', None, _('run even when the destination is unrelated')),
3611 ('r', 'rev', [],
3610 ('r', 'rev', [],
3612 _('a changeset intended to be included in the destination'), _('REV')),
3611 _('a changeset intended to be included in the destination'), _('REV')),
3613 ('n', 'newest-first', None, _('show newest record first')),
3612 ('n', 'newest-first', None, _('show newest record first')),
3614 ('B', 'bookmarks', False, _('compare bookmarks')),
3613 ('B', 'bookmarks', False, _('compare bookmarks')),
3615 ('b', 'branch', [], _('a specific branch you would like to push'),
3614 ('b', 'branch', [], _('a specific branch you would like to push'),
3616 _('BRANCH')),
3615 _('BRANCH')),
3617 ] + logopts + remoteopts + subrepoopts,
3616 ] + logopts + remoteopts + subrepoopts,
3618 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3617 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3619 def outgoing(ui, repo, dest=None, **opts):
3618 def outgoing(ui, repo, dest=None, **opts):
3620 """show changesets not found in the destination
3619 """show changesets not found in the destination
3621
3620
3622 Show changesets not found in the specified destination repository
3621 Show changesets not found in the specified destination repository
3623 or the default push location. These are the changesets that would
3622 or the default push location. These are the changesets that would
3624 be pushed if a push was requested.
3623 be pushed if a push was requested.
3625
3624
3626 See pull for details of valid destination formats.
3625 See pull for details of valid destination formats.
3627
3626
3628 .. container:: verbose
3627 .. container:: verbose
3629
3628
3630 With -B/--bookmarks, the result of bookmark comparison between
3629 With -B/--bookmarks, the result of bookmark comparison between
3631 local and remote repositories is displayed. With -v/--verbose,
3630 local and remote repositories is displayed. With -v/--verbose,
3632 status is also displayed for each bookmark like below::
3631 status is also displayed for each bookmark like below::
3633
3632
3634 BM1 01234567890a added
3633 BM1 01234567890a added
3635 BM2 deleted
3634 BM2 deleted
3636 BM3 234567890abc advanced
3635 BM3 234567890abc advanced
3637 BM4 34567890abcd diverged
3636 BM4 34567890abcd diverged
3638 BM5 4567890abcde changed
3637 BM5 4567890abcde changed
3639
3638
3640 The action taken when pushing depends on the
3639 The action taken when pushing depends on the
3641 status of each bookmark:
3640 status of each bookmark:
3642
3641
3643 :``added``: push with ``-B`` will create it
3642 :``added``: push with ``-B`` will create it
3644 :``deleted``: push with ``-B`` will delete it
3643 :``deleted``: push with ``-B`` will delete it
3645 :``advanced``: push will update it
3644 :``advanced``: push will update it
3646 :``diverged``: push with ``-B`` will update it
3645 :``diverged``: push with ``-B`` will update it
3647 :``changed``: push with ``-B`` will update it
3646 :``changed``: push with ``-B`` will update it
3648
3647
3649 From the point of view of pushing behavior, bookmarks
3648 From the point of view of pushing behavior, bookmarks
3650 existing only in the remote repository are treated as
3649 existing only in the remote repository are treated as
3651 ``deleted``, even if it is in fact added remotely.
3650 ``deleted``, even if it is in fact added remotely.
3652
3651
3653 Returns 0 if there are outgoing changes, 1 otherwise.
3652 Returns 0 if there are outgoing changes, 1 otherwise.
3654 """
3653 """
3655 opts = pycompat.byteskwargs(opts)
3654 opts = pycompat.byteskwargs(opts)
3656 if opts.get('graph'):
3655 if opts.get('graph'):
3657 cmdutil.checkunsupportedgraphflags([], opts)
3656 cmdutil.checkunsupportedgraphflags([], opts)
3658 o, other = hg._outgoing(ui, repo, dest, opts)
3657 o, other = hg._outgoing(ui, repo, dest, opts)
3659 if not o:
3658 if not o:
3660 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3659 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3661 return
3660 return
3662
3661
3663 revdag = cmdutil.graphrevs(repo, o, opts)
3662 revdag = cmdutil.graphrevs(repo, o, opts)
3664 ui.pager('outgoing')
3663 ui.pager('outgoing')
3665 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3664 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3666 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3665 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3667 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3666 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3668 return 0
3667 return 0
3669
3668
3670 if opts.get('bookmarks'):
3669 if opts.get('bookmarks'):
3671 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3670 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3672 dest, branches = hg.parseurl(dest, opts.get('branch'))
3671 dest, branches = hg.parseurl(dest, opts.get('branch'))
3673 other = hg.peer(repo, opts, dest)
3672 other = hg.peer(repo, opts, dest)
3674 if 'bookmarks' not in other.listkeys('namespaces'):
3673 if 'bookmarks' not in other.listkeys('namespaces'):
3675 ui.warn(_("remote doesn't support bookmarks\n"))
3674 ui.warn(_("remote doesn't support bookmarks\n"))
3676 return 0
3675 return 0
3677 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3676 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3678 ui.pager('outgoing')
3677 ui.pager('outgoing')
3679 return bookmarks.outgoing(ui, repo, other)
3678 return bookmarks.outgoing(ui, repo, other)
3680
3679
3681 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3680 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3682 try:
3681 try:
3683 return hg.outgoing(ui, repo, dest, opts)
3682 return hg.outgoing(ui, repo, dest, opts)
3684 finally:
3683 finally:
3685 del repo._subtoppath
3684 del repo._subtoppath
3686
3685
3687 @command('parents',
3686 @command('parents',
3688 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3687 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3689 ] + templateopts,
3688 ] + templateopts,
3690 _('[-r REV] [FILE]'),
3689 _('[-r REV] [FILE]'),
3691 inferrepo=True)
3690 inferrepo=True)
3692 def parents(ui, repo, file_=None, **opts):
3691 def parents(ui, repo, file_=None, **opts):
3693 """show the parents of the working directory or revision (DEPRECATED)
3692 """show the parents of the working directory or revision (DEPRECATED)
3694
3693
3695 Print the working directory's parent revisions. If a revision is
3694 Print the working directory's parent revisions. If a revision is
3696 given via -r/--rev, the parent of that revision will be printed.
3695 given via -r/--rev, the parent of that revision will be printed.
3697 If a file argument is given, the revision in which the file was
3696 If a file argument is given, the revision in which the file was
3698 last changed (before the working directory revision or the
3697 last changed (before the working directory revision or the
3699 argument to --rev if given) is printed.
3698 argument to --rev if given) is printed.
3700
3699
3701 This command is equivalent to::
3700 This command is equivalent to::
3702
3701
3703 hg log -r "p1()+p2()" or
3702 hg log -r "p1()+p2()" or
3704 hg log -r "p1(REV)+p2(REV)" or
3703 hg log -r "p1(REV)+p2(REV)" or
3705 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3704 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3706 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3705 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3707
3706
3708 See :hg:`summary` and :hg:`help revsets` for related information.
3707 See :hg:`summary` and :hg:`help revsets` for related information.
3709
3708
3710 Returns 0 on success.
3709 Returns 0 on success.
3711 """
3710 """
3712
3711
3713 opts = pycompat.byteskwargs(opts)
3712 opts = pycompat.byteskwargs(opts)
3714 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3713 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3715
3714
3716 if file_:
3715 if file_:
3717 m = scmutil.match(ctx, (file_,), opts)
3716 m = scmutil.match(ctx, (file_,), opts)
3718 if m.anypats() or len(m.files()) != 1:
3717 if m.anypats() or len(m.files()) != 1:
3719 raise error.Abort(_('can only specify an explicit filename'))
3718 raise error.Abort(_('can only specify an explicit filename'))
3720 file_ = m.files()[0]
3719 file_ = m.files()[0]
3721 filenodes = []
3720 filenodes = []
3722 for cp in ctx.parents():
3721 for cp in ctx.parents():
3723 if not cp:
3722 if not cp:
3724 continue
3723 continue
3725 try:
3724 try:
3726 filenodes.append(cp.filenode(file_))
3725 filenodes.append(cp.filenode(file_))
3727 except error.LookupError:
3726 except error.LookupError:
3728 pass
3727 pass
3729 if not filenodes:
3728 if not filenodes:
3730 raise error.Abort(_("'%s' not found in manifest!") % file_)
3729 raise error.Abort(_("'%s' not found in manifest!") % file_)
3731 p = []
3730 p = []
3732 for fn in filenodes:
3731 for fn in filenodes:
3733 fctx = repo.filectx(file_, fileid=fn)
3732 fctx = repo.filectx(file_, fileid=fn)
3734 p.append(fctx.node())
3733 p.append(fctx.node())
3735 else:
3734 else:
3736 p = [cp.node() for cp in ctx.parents()]
3735 p = [cp.node() for cp in ctx.parents()]
3737
3736
3738 displayer = cmdutil.show_changeset(ui, repo, opts)
3737 displayer = cmdutil.show_changeset(ui, repo, opts)
3739 for n in p:
3738 for n in p:
3740 if n != nullid:
3739 if n != nullid:
3741 displayer.show(repo[n])
3740 displayer.show(repo[n])
3742 displayer.close()
3741 displayer.close()
3743
3742
3744 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3743 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3745 def paths(ui, repo, search=None, **opts):
3744 def paths(ui, repo, search=None, **opts):
3746 """show aliases for remote repositories
3745 """show aliases for remote repositories
3747
3746
3748 Show definition of symbolic path name NAME. If no name is given,
3747 Show definition of symbolic path name NAME. If no name is given,
3749 show definition of all available names.
3748 show definition of all available names.
3750
3749
3751 Option -q/--quiet suppresses all output when searching for NAME
3750 Option -q/--quiet suppresses all output when searching for NAME
3752 and shows only the path names when listing all definitions.
3751 and shows only the path names when listing all definitions.
3753
3752
3754 Path names are defined in the [paths] section of your
3753 Path names are defined in the [paths] section of your
3755 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3754 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3756 repository, ``.hg/hgrc`` is used, too.
3755 repository, ``.hg/hgrc`` is used, too.
3757
3756
3758 The path names ``default`` and ``default-push`` have a special
3757 The path names ``default`` and ``default-push`` have a special
3759 meaning. When performing a push or pull operation, they are used
3758 meaning. When performing a push or pull operation, they are used
3760 as fallbacks if no location is specified on the command-line.
3759 as fallbacks if no location is specified on the command-line.
3761 When ``default-push`` is set, it will be used for push and
3760 When ``default-push`` is set, it will be used for push and
3762 ``default`` will be used for pull; otherwise ``default`` is used
3761 ``default`` will be used for pull; otherwise ``default`` is used
3763 as the fallback for both. When cloning a repository, the clone
3762 as the fallback for both. When cloning a repository, the clone
3764 source is written as ``default`` in ``.hg/hgrc``.
3763 source is written as ``default`` in ``.hg/hgrc``.
3765
3764
3766 .. note::
3765 .. note::
3767
3766
3768 ``default`` and ``default-push`` apply to all inbound (e.g.
3767 ``default`` and ``default-push`` apply to all inbound (e.g.
3769 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3768 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3770 and :hg:`bundle`) operations.
3769 and :hg:`bundle`) operations.
3771
3770
3772 See :hg:`help urls` for more information.
3771 See :hg:`help urls` for more information.
3773
3772
3774 Returns 0 on success.
3773 Returns 0 on success.
3775 """
3774 """
3776
3775
3777 opts = pycompat.byteskwargs(opts)
3776 opts = pycompat.byteskwargs(opts)
3778 ui.pager('paths')
3777 ui.pager('paths')
3779 if search:
3778 if search:
3780 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3779 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3781 if name == search]
3780 if name == search]
3782 else:
3781 else:
3783 pathitems = sorted(ui.paths.iteritems())
3782 pathitems = sorted(ui.paths.iteritems())
3784
3783
3785 fm = ui.formatter('paths', opts)
3784 fm = ui.formatter('paths', opts)
3786 if fm.isplain():
3785 if fm.isplain():
3787 hidepassword = util.hidepassword
3786 hidepassword = util.hidepassword
3788 else:
3787 else:
3789 hidepassword = str
3788 hidepassword = str
3790 if ui.quiet:
3789 if ui.quiet:
3791 namefmt = '%s\n'
3790 namefmt = '%s\n'
3792 else:
3791 else:
3793 namefmt = '%s = '
3792 namefmt = '%s = '
3794 showsubopts = not search and not ui.quiet
3793 showsubopts = not search and not ui.quiet
3795
3794
3796 for name, path in pathitems:
3795 for name, path in pathitems:
3797 fm.startitem()
3796 fm.startitem()
3798 fm.condwrite(not search, 'name', namefmt, name)
3797 fm.condwrite(not search, 'name', namefmt, name)
3799 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3798 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3800 for subopt, value in sorted(path.suboptions.items()):
3799 for subopt, value in sorted(path.suboptions.items()):
3801 assert subopt not in ('name', 'url')
3800 assert subopt not in ('name', 'url')
3802 if showsubopts:
3801 if showsubopts:
3803 fm.plain('%s:%s = ' % (name, subopt))
3802 fm.plain('%s:%s = ' % (name, subopt))
3804 fm.condwrite(showsubopts, subopt, '%s\n', value)
3803 fm.condwrite(showsubopts, subopt, '%s\n', value)
3805
3804
3806 fm.end()
3805 fm.end()
3807
3806
3808 if search and not pathitems:
3807 if search and not pathitems:
3809 if not ui.quiet:
3808 if not ui.quiet:
3810 ui.warn(_("not found!\n"))
3809 ui.warn(_("not found!\n"))
3811 return 1
3810 return 1
3812 else:
3811 else:
3813 return 0
3812 return 0
3814
3813
3815 @command('phase',
3814 @command('phase',
3816 [('p', 'public', False, _('set changeset phase to public')),
3815 [('p', 'public', False, _('set changeset phase to public')),
3817 ('d', 'draft', False, _('set changeset phase to draft')),
3816 ('d', 'draft', False, _('set changeset phase to draft')),
3818 ('s', 'secret', False, _('set changeset phase to secret')),
3817 ('s', 'secret', False, _('set changeset phase to secret')),
3819 ('f', 'force', False, _('allow to move boundary backward')),
3818 ('f', 'force', False, _('allow to move boundary backward')),
3820 ('r', 'rev', [], _('target revision'), _('REV')),
3819 ('r', 'rev', [], _('target revision'), _('REV')),
3821 ],
3820 ],
3822 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3821 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3823 def phase(ui, repo, *revs, **opts):
3822 def phase(ui, repo, *revs, **opts):
3824 """set or show the current phase name
3823 """set or show the current phase name
3825
3824
3826 With no argument, show the phase name of the current revision(s).
3825 With no argument, show the phase name of the current revision(s).
3827
3826
3828 With one of -p/--public, -d/--draft or -s/--secret, change the
3827 With one of -p/--public, -d/--draft or -s/--secret, change the
3829 phase value of the specified revisions.
3828 phase value of the specified revisions.
3830
3829
3831 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3830 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3832 lower phase to an higher phase. Phases are ordered as follows::
3831 lower phase to an higher phase. Phases are ordered as follows::
3833
3832
3834 public < draft < secret
3833 public < draft < secret
3835
3834
3836 Returns 0 on success, 1 if some phases could not be changed.
3835 Returns 0 on success, 1 if some phases could not be changed.
3837
3836
3838 (For more information about the phases concept, see :hg:`help phases`.)
3837 (For more information about the phases concept, see :hg:`help phases`.)
3839 """
3838 """
3840 opts = pycompat.byteskwargs(opts)
3839 opts = pycompat.byteskwargs(opts)
3841 # search for a unique phase argument
3840 # search for a unique phase argument
3842 targetphase = None
3841 targetphase = None
3843 for idx, name in enumerate(phases.phasenames):
3842 for idx, name in enumerate(phases.phasenames):
3844 if opts[name]:
3843 if opts[name]:
3845 if targetphase is not None:
3844 if targetphase is not None:
3846 raise error.Abort(_('only one phase can be specified'))
3845 raise error.Abort(_('only one phase can be specified'))
3847 targetphase = idx
3846 targetphase = idx
3848
3847
3849 # look for specified revision
3848 # look for specified revision
3850 revs = list(revs)
3849 revs = list(revs)
3851 revs.extend(opts['rev'])
3850 revs.extend(opts['rev'])
3852 if not revs:
3851 if not revs:
3853 # display both parents as the second parent phase can influence
3852 # display both parents as the second parent phase can influence
3854 # the phase of a merge commit
3853 # the phase of a merge commit
3855 revs = [c.rev() for c in repo[None].parents()]
3854 revs = [c.rev() for c in repo[None].parents()]
3856
3855
3857 revs = scmutil.revrange(repo, revs)
3856 revs = scmutil.revrange(repo, revs)
3858
3857
3859 lock = None
3858 lock = None
3860 ret = 0
3859 ret = 0
3861 if targetphase is None:
3860 if targetphase is None:
3862 # display
3861 # display
3863 for r in revs:
3862 for r in revs:
3864 ctx = repo[r]
3863 ctx = repo[r]
3865 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3864 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3866 else:
3865 else:
3867 tr = None
3866 tr = None
3868 lock = repo.lock()
3867 lock = repo.lock()
3869 try:
3868 try:
3870 tr = repo.transaction("phase")
3869 tr = repo.transaction("phase")
3871 # set phase
3870 # set phase
3872 if not revs:
3871 if not revs:
3873 raise error.Abort(_('empty revision set'))
3872 raise error.Abort(_('empty revision set'))
3874 nodes = [repo[r].node() for r in revs]
3873 nodes = [repo[r].node() for r in revs]
3875 # moving revision from public to draft may hide them
3874 # moving revision from public to draft may hide them
3876 # We have to check result on an unfiltered repository
3875 # We have to check result on an unfiltered repository
3877 unfi = repo.unfiltered()
3876 unfi = repo.unfiltered()
3878 getphase = unfi._phasecache.phase
3877 getphase = unfi._phasecache.phase
3879 olddata = [getphase(unfi, r) for r in unfi]
3878 olddata = [getphase(unfi, r) for r in unfi]
3880 phases.advanceboundary(repo, tr, targetphase, nodes)
3879 phases.advanceboundary(repo, tr, targetphase, nodes)
3881 if opts['force']:
3880 if opts['force']:
3882 phases.retractboundary(repo, tr, targetphase, nodes)
3881 phases.retractboundary(repo, tr, targetphase, nodes)
3883 tr.close()
3882 tr.close()
3884 finally:
3883 finally:
3885 if tr is not None:
3884 if tr is not None:
3886 tr.release()
3885 tr.release()
3887 lock.release()
3886 lock.release()
3888 getphase = unfi._phasecache.phase
3887 getphase = unfi._phasecache.phase
3889 newdata = [getphase(unfi, r) for r in unfi]
3888 newdata = [getphase(unfi, r) for r in unfi]
3890 changes = sum(newdata[r] != olddata[r] for r in unfi)
3889 changes = sum(newdata[r] != olddata[r] for r in unfi)
3891 cl = unfi.changelog
3890 cl = unfi.changelog
3892 rejected = [n for n in nodes
3891 rejected = [n for n in nodes
3893 if newdata[cl.rev(n)] < targetphase]
3892 if newdata[cl.rev(n)] < targetphase]
3894 if rejected:
3893 if rejected:
3895 ui.warn(_('cannot move %i changesets to a higher '
3894 ui.warn(_('cannot move %i changesets to a higher '
3896 'phase, use --force\n') % len(rejected))
3895 'phase, use --force\n') % len(rejected))
3897 ret = 1
3896 ret = 1
3898 if changes:
3897 if changes:
3899 msg = _('phase changed for %i changesets\n') % changes
3898 msg = _('phase changed for %i changesets\n') % changes
3900 if ret:
3899 if ret:
3901 ui.status(msg)
3900 ui.status(msg)
3902 else:
3901 else:
3903 ui.note(msg)
3902 ui.note(msg)
3904 else:
3903 else:
3905 ui.warn(_('no phases changed\n'))
3904 ui.warn(_('no phases changed\n'))
3906 return ret
3905 return ret
3907
3906
3908 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3907 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3909 """Run after a changegroup has been added via pull/unbundle
3908 """Run after a changegroup has been added via pull/unbundle
3910
3909
3911 This takes arguments below:
3910 This takes arguments below:
3912
3911
3913 :modheads: change of heads by pull/unbundle
3912 :modheads: change of heads by pull/unbundle
3914 :optupdate: updating working directory is needed or not
3913 :optupdate: updating working directory is needed or not
3915 :checkout: update destination revision (or None to default destination)
3914 :checkout: update destination revision (or None to default destination)
3916 :brev: a name, which might be a bookmark to be activated after updating
3915 :brev: a name, which might be a bookmark to be activated after updating
3917 """
3916 """
3918 if modheads == 0:
3917 if modheads == 0:
3919 return
3918 return
3920 if optupdate:
3919 if optupdate:
3921 try:
3920 try:
3922 return hg.updatetotally(ui, repo, checkout, brev)
3921 return hg.updatetotally(ui, repo, checkout, brev)
3923 except error.UpdateAbort as inst:
3922 except error.UpdateAbort as inst:
3924 msg = _("not updating: %s") % str(inst)
3923 msg = _("not updating: %s") % str(inst)
3925 hint = inst.hint
3924 hint = inst.hint
3926 raise error.UpdateAbort(msg, hint=hint)
3925 raise error.UpdateAbort(msg, hint=hint)
3927 if modheads > 1:
3926 if modheads > 1:
3928 currentbranchheads = len(repo.branchheads())
3927 currentbranchheads = len(repo.branchheads())
3929 if currentbranchheads == modheads:
3928 if currentbranchheads == modheads:
3930 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3929 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3931 elif currentbranchheads > 1:
3930 elif currentbranchheads > 1:
3932 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3931 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3933 "merge)\n"))
3932 "merge)\n"))
3934 else:
3933 else:
3935 ui.status(_("(run 'hg heads' to see heads)\n"))
3934 ui.status(_("(run 'hg heads' to see heads)\n"))
3936 else:
3935 else:
3937 ui.status(_("(run 'hg update' to get a working copy)\n"))
3936 ui.status(_("(run 'hg update' to get a working copy)\n"))
3938
3937
3939 @command('^pull',
3938 @command('^pull',
3940 [('u', 'update', None,
3939 [('u', 'update', None,
3941 _('update to new branch head if changesets were pulled')),
3940 _('update to new branch head if changesets were pulled')),
3942 ('f', 'force', None, _('run even when remote repository is unrelated')),
3941 ('f', 'force', None, _('run even when remote repository is unrelated')),
3943 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3942 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3944 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3943 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3945 ('b', 'branch', [], _('a specific branch you would like to pull'),
3944 ('b', 'branch', [], _('a specific branch you would like to pull'),
3946 _('BRANCH')),
3945 _('BRANCH')),
3947 ] + remoteopts,
3946 ] + remoteopts,
3948 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3947 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3949 def pull(ui, repo, source="default", **opts):
3948 def pull(ui, repo, source="default", **opts):
3950 """pull changes from the specified source
3949 """pull changes from the specified source
3951
3950
3952 Pull changes from a remote repository to a local one.
3951 Pull changes from a remote repository to a local one.
3953
3952
3954 This finds all changes from the repository at the specified path
3953 This finds all changes from the repository at the specified path
3955 or URL and adds them to a local repository (the current one unless
3954 or URL and adds them to a local repository (the current one unless
3956 -R is specified). By default, this does not update the copy of the
3955 -R is specified). By default, this does not update the copy of the
3957 project in the working directory.
3956 project in the working directory.
3958
3957
3959 Use :hg:`incoming` if you want to see what would have been added
3958 Use :hg:`incoming` if you want to see what would have been added
3960 by a pull at the time you issued this command. If you then decide
3959 by a pull at the time you issued this command. If you then decide
3961 to add those changes to the repository, you should use :hg:`pull
3960 to add those changes to the repository, you should use :hg:`pull
3962 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3961 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3963
3962
3964 If SOURCE is omitted, the 'default' path will be used.
3963 If SOURCE is omitted, the 'default' path will be used.
3965 See :hg:`help urls` for more information.
3964 See :hg:`help urls` for more information.
3966
3965
3967 Specifying bookmark as ``.`` is equivalent to specifying the active
3966 Specifying bookmark as ``.`` is equivalent to specifying the active
3968 bookmark's name.
3967 bookmark's name.
3969
3968
3970 Returns 0 on success, 1 if an update had unresolved files.
3969 Returns 0 on success, 1 if an update had unresolved files.
3971 """
3970 """
3972
3971
3973 opts = pycompat.byteskwargs(opts)
3972 opts = pycompat.byteskwargs(opts)
3974 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3973 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3975 msg = _('update destination required by configuration')
3974 msg = _('update destination required by configuration')
3976 hint = _('use hg pull followed by hg update DEST')
3975 hint = _('use hg pull followed by hg update DEST')
3977 raise error.Abort(msg, hint=hint)
3976 raise error.Abort(msg, hint=hint)
3978
3977
3979 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3978 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3980 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3979 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3981 other = hg.peer(repo, opts, source)
3980 other = hg.peer(repo, opts, source)
3982 try:
3981 try:
3983 revs, checkout = hg.addbranchrevs(repo, other, branches,
3982 revs, checkout = hg.addbranchrevs(repo, other, branches,
3984 opts.get('rev'))
3983 opts.get('rev'))
3985
3984
3986
3985
3987 pullopargs = {}
3986 pullopargs = {}
3988 if opts.get('bookmark'):
3987 if opts.get('bookmark'):
3989 if not revs:
3988 if not revs:
3990 revs = []
3989 revs = []
3991 # The list of bookmark used here is not the one used to actually
3990 # The list of bookmark used here is not the one used to actually
3992 # update the bookmark name. This can result in the revision pulled
3991 # update the bookmark name. This can result in the revision pulled
3993 # not ending up with the name of the bookmark because of a race
3992 # not ending up with the name of the bookmark because of a race
3994 # condition on the server. (See issue 4689 for details)
3993 # condition on the server. (See issue 4689 for details)
3995 remotebookmarks = other.listkeys('bookmarks')
3994 remotebookmarks = other.listkeys('bookmarks')
3996 pullopargs['remotebookmarks'] = remotebookmarks
3995 pullopargs['remotebookmarks'] = remotebookmarks
3997 for b in opts['bookmark']:
3996 for b in opts['bookmark']:
3998 b = repo._bookmarks.expandname(b)
3997 b = repo._bookmarks.expandname(b)
3999 if b not in remotebookmarks:
3998 if b not in remotebookmarks:
4000 raise error.Abort(_('remote bookmark %s not found!') % b)
3999 raise error.Abort(_('remote bookmark %s not found!') % b)
4001 revs.append(remotebookmarks[b])
4000 revs.append(remotebookmarks[b])
4002
4001
4003 if revs:
4002 if revs:
4004 try:
4003 try:
4005 # When 'rev' is a bookmark name, we cannot guarantee that it
4004 # When 'rev' is a bookmark name, we cannot guarantee that it
4006 # will be updated with that name because of a race condition
4005 # will be updated with that name because of a race condition
4007 # server side. (See issue 4689 for details)
4006 # server side. (See issue 4689 for details)
4008 oldrevs = revs
4007 oldrevs = revs
4009 revs = [] # actually, nodes
4008 revs = [] # actually, nodes
4010 for r in oldrevs:
4009 for r in oldrevs:
4011 node = other.lookup(r)
4010 node = other.lookup(r)
4012 revs.append(node)
4011 revs.append(node)
4013 if r == checkout:
4012 if r == checkout:
4014 checkout = node
4013 checkout = node
4015 except error.CapabilityError:
4014 except error.CapabilityError:
4016 err = _("other repository doesn't support revision lookup, "
4015 err = _("other repository doesn't support revision lookup, "
4017 "so a rev cannot be specified.")
4016 "so a rev cannot be specified.")
4018 raise error.Abort(err)
4017 raise error.Abort(err)
4019
4018
4020 pullopargs.update(opts.get('opargs', {}))
4019 pullopargs.update(opts.get('opargs', {}))
4021 modheads = exchange.pull(repo, other, heads=revs,
4020 modheads = exchange.pull(repo, other, heads=revs,
4022 force=opts.get('force'),
4021 force=opts.get('force'),
4023 bookmarks=opts.get('bookmark', ()),
4022 bookmarks=opts.get('bookmark', ()),
4024 opargs=pullopargs).cgresult
4023 opargs=pullopargs).cgresult
4025
4024
4026 # brev is a name, which might be a bookmark to be activated at
4025 # brev is a name, which might be a bookmark to be activated at
4027 # the end of the update. In other words, it is an explicit
4026 # the end of the update. In other words, it is an explicit
4028 # destination of the update
4027 # destination of the update
4029 brev = None
4028 brev = None
4030
4029
4031 if checkout:
4030 if checkout:
4032 checkout = str(repo.changelog.rev(checkout))
4031 checkout = str(repo.changelog.rev(checkout))
4033
4032
4034 # order below depends on implementation of
4033 # order below depends on implementation of
4035 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4034 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4036 # because 'checkout' is determined without it.
4035 # because 'checkout' is determined without it.
4037 if opts.get('rev'):
4036 if opts.get('rev'):
4038 brev = opts['rev'][0]
4037 brev = opts['rev'][0]
4039 elif opts.get('branch'):
4038 elif opts.get('branch'):
4040 brev = opts['branch'][0]
4039 brev = opts['branch'][0]
4041 else:
4040 else:
4042 brev = branches[0]
4041 brev = branches[0]
4043 repo._subtoppath = source
4042 repo._subtoppath = source
4044 try:
4043 try:
4045 ret = postincoming(ui, repo, modheads, opts.get('update'),
4044 ret = postincoming(ui, repo, modheads, opts.get('update'),
4046 checkout, brev)
4045 checkout, brev)
4047
4046
4048 finally:
4047 finally:
4049 del repo._subtoppath
4048 del repo._subtoppath
4050
4049
4051 finally:
4050 finally:
4052 other.close()
4051 other.close()
4053 return ret
4052 return ret
4054
4053
4055 @command('^push',
4054 @command('^push',
4056 [('f', 'force', None, _('force push')),
4055 [('f', 'force', None, _('force push')),
4057 ('r', 'rev', [],
4056 ('r', 'rev', [],
4058 _('a changeset intended to be included in the destination'),
4057 _('a changeset intended to be included in the destination'),
4059 _('REV')),
4058 _('REV')),
4060 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4059 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4061 ('b', 'branch', [],
4060 ('b', 'branch', [],
4062 _('a specific branch you would like to push'), _('BRANCH')),
4061 _('a specific branch you would like to push'), _('BRANCH')),
4063 ('', 'new-branch', False, _('allow pushing a new branch')),
4062 ('', 'new-branch', False, _('allow pushing a new branch')),
4064 ] + remoteopts,
4063 ] + remoteopts,
4065 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4064 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4066 def push(ui, repo, dest=None, **opts):
4065 def push(ui, repo, dest=None, **opts):
4067 """push changes to the specified destination
4066 """push changes to the specified destination
4068
4067
4069 Push changesets from the local repository to the specified
4068 Push changesets from the local repository to the specified
4070 destination.
4069 destination.
4071
4070
4072 This operation is symmetrical to pull: it is identical to a pull
4071 This operation is symmetrical to pull: it is identical to a pull
4073 in the destination repository from the current one.
4072 in the destination repository from the current one.
4074
4073
4075 By default, push will not allow creation of new heads at the
4074 By default, push will not allow creation of new heads at the
4076 destination, since multiple heads would make it unclear which head
4075 destination, since multiple heads would make it unclear which head
4077 to use. In this situation, it is recommended to pull and merge
4076 to use. In this situation, it is recommended to pull and merge
4078 before pushing.
4077 before pushing.
4079
4078
4080 Use --new-branch if you want to allow push to create a new named
4079 Use --new-branch if you want to allow push to create a new named
4081 branch that is not present at the destination. This allows you to
4080 branch that is not present at the destination. This allows you to
4082 only create a new branch without forcing other changes.
4081 only create a new branch without forcing other changes.
4083
4082
4084 .. note::
4083 .. note::
4085
4084
4086 Extra care should be taken with the -f/--force option,
4085 Extra care should be taken with the -f/--force option,
4087 which will push all new heads on all branches, an action which will
4086 which will push all new heads on all branches, an action which will
4088 almost always cause confusion for collaborators.
4087 almost always cause confusion for collaborators.
4089
4088
4090 If -r/--rev is used, the specified revision and all its ancestors
4089 If -r/--rev is used, the specified revision and all its ancestors
4091 will be pushed to the remote repository.
4090 will be pushed to the remote repository.
4092
4091
4093 If -B/--bookmark is used, the specified bookmarked revision, its
4092 If -B/--bookmark is used, the specified bookmarked revision, its
4094 ancestors, and the bookmark will be pushed to the remote
4093 ancestors, and the bookmark will be pushed to the remote
4095 repository. Specifying ``.`` is equivalent to specifying the active
4094 repository. Specifying ``.`` is equivalent to specifying the active
4096 bookmark's name.
4095 bookmark's name.
4097
4096
4098 Please see :hg:`help urls` for important details about ``ssh://``
4097 Please see :hg:`help urls` for important details about ``ssh://``
4099 URLs. If DESTINATION is omitted, a default path will be used.
4098 URLs. If DESTINATION is omitted, a default path will be used.
4100
4099
4101 Returns 0 if push was successful, 1 if nothing to push.
4100 Returns 0 if push was successful, 1 if nothing to push.
4102 """
4101 """
4103
4102
4104 opts = pycompat.byteskwargs(opts)
4103 opts = pycompat.byteskwargs(opts)
4105 if opts.get('bookmark'):
4104 if opts.get('bookmark'):
4106 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4105 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4107 for b in opts['bookmark']:
4106 for b in opts['bookmark']:
4108 # translate -B options to -r so changesets get pushed
4107 # translate -B options to -r so changesets get pushed
4109 b = repo._bookmarks.expandname(b)
4108 b = repo._bookmarks.expandname(b)
4110 if b in repo._bookmarks:
4109 if b in repo._bookmarks:
4111 opts.setdefault('rev', []).append(b)
4110 opts.setdefault('rev', []).append(b)
4112 else:
4111 else:
4113 # if we try to push a deleted bookmark, translate it to null
4112 # if we try to push a deleted bookmark, translate it to null
4114 # this lets simultaneous -r, -b options continue working
4113 # this lets simultaneous -r, -b options continue working
4115 opts.setdefault('rev', []).append("null")
4114 opts.setdefault('rev', []).append("null")
4116
4115
4117 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4116 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4118 if not path:
4117 if not path:
4119 raise error.Abort(_('default repository not configured!'),
4118 raise error.Abort(_('default repository not configured!'),
4120 hint=_("see 'hg help config.paths'"))
4119 hint=_("see 'hg help config.paths'"))
4121 dest = path.pushloc or path.loc
4120 dest = path.pushloc or path.loc
4122 branches = (path.branch, opts.get('branch') or [])
4121 branches = (path.branch, opts.get('branch') or [])
4123 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4122 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4124 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4123 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4125 other = hg.peer(repo, opts, dest)
4124 other = hg.peer(repo, opts, dest)
4126
4125
4127 if revs:
4126 if revs:
4128 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4127 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4129 if not revs:
4128 if not revs:
4130 raise error.Abort(_("specified revisions evaluate to an empty set"),
4129 raise error.Abort(_("specified revisions evaluate to an empty set"),
4131 hint=_("use different revision arguments"))
4130 hint=_("use different revision arguments"))
4132 elif path.pushrev:
4131 elif path.pushrev:
4133 # It doesn't make any sense to specify ancestor revisions. So limit
4132 # It doesn't make any sense to specify ancestor revisions. So limit
4134 # to DAG heads to make discovery simpler.
4133 # to DAG heads to make discovery simpler.
4135 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4134 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4136 revs = scmutil.revrange(repo, [expr])
4135 revs = scmutil.revrange(repo, [expr])
4137 revs = [repo[rev].node() for rev in revs]
4136 revs = [repo[rev].node() for rev in revs]
4138 if not revs:
4137 if not revs:
4139 raise error.Abort(_('default push revset for path evaluates to an '
4138 raise error.Abort(_('default push revset for path evaluates to an '
4140 'empty set'))
4139 'empty set'))
4141
4140
4142 repo._subtoppath = dest
4141 repo._subtoppath = dest
4143 try:
4142 try:
4144 # push subrepos depth-first for coherent ordering
4143 # push subrepos depth-first for coherent ordering
4145 c = repo['']
4144 c = repo['']
4146 subs = c.substate # only repos that are committed
4145 subs = c.substate # only repos that are committed
4147 for s in sorted(subs):
4146 for s in sorted(subs):
4148 result = c.sub(s).push(opts)
4147 result = c.sub(s).push(opts)
4149 if result == 0:
4148 if result == 0:
4150 return not result
4149 return not result
4151 finally:
4150 finally:
4152 del repo._subtoppath
4151 del repo._subtoppath
4153 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4152 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4154 newbranch=opts.get('new_branch'),
4153 newbranch=opts.get('new_branch'),
4155 bookmarks=opts.get('bookmark', ()),
4154 bookmarks=opts.get('bookmark', ()),
4156 opargs=opts.get('opargs'))
4155 opargs=opts.get('opargs'))
4157
4156
4158 result = not pushop.cgresult
4157 result = not pushop.cgresult
4159
4158
4160 if pushop.bkresult is not None:
4159 if pushop.bkresult is not None:
4161 if pushop.bkresult == 2:
4160 if pushop.bkresult == 2:
4162 result = 2
4161 result = 2
4163 elif not result and pushop.bkresult:
4162 elif not result and pushop.bkresult:
4164 result = 2
4163 result = 2
4165
4164
4166 return result
4165 return result
4167
4166
4168 @command('recover', [])
4167 @command('recover', [])
4169 def recover(ui, repo):
4168 def recover(ui, repo):
4170 """roll back an interrupted transaction
4169 """roll back an interrupted transaction
4171
4170
4172 Recover from an interrupted commit or pull.
4171 Recover from an interrupted commit or pull.
4173
4172
4174 This command tries to fix the repository status after an
4173 This command tries to fix the repository status after an
4175 interrupted operation. It should only be necessary when Mercurial
4174 interrupted operation. It should only be necessary when Mercurial
4176 suggests it.
4175 suggests it.
4177
4176
4178 Returns 0 if successful, 1 if nothing to recover or verify fails.
4177 Returns 0 if successful, 1 if nothing to recover or verify fails.
4179 """
4178 """
4180 if repo.recover():
4179 if repo.recover():
4181 return hg.verify(repo)
4180 return hg.verify(repo)
4182 return 1
4181 return 1
4183
4182
4184 @command('^remove|rm',
4183 @command('^remove|rm',
4185 [('A', 'after', None, _('record delete for missing files')),
4184 [('A', 'after', None, _('record delete for missing files')),
4186 ('f', 'force', None,
4185 ('f', 'force', None,
4187 _('forget added files, delete modified files')),
4186 _('forget added files, delete modified files')),
4188 ] + subrepoopts + walkopts,
4187 ] + subrepoopts + walkopts,
4189 _('[OPTION]... FILE...'),
4188 _('[OPTION]... FILE...'),
4190 inferrepo=True)
4189 inferrepo=True)
4191 def remove(ui, repo, *pats, **opts):
4190 def remove(ui, repo, *pats, **opts):
4192 """remove the specified files on the next commit
4191 """remove the specified files on the next commit
4193
4192
4194 Schedule the indicated files for removal from the current branch.
4193 Schedule the indicated files for removal from the current branch.
4195
4194
4196 This command schedules the files to be removed at the next commit.
4195 This command schedules the files to be removed at the next commit.
4197 To undo a remove before that, see :hg:`revert`. To undo added
4196 To undo a remove before that, see :hg:`revert`. To undo added
4198 files, see :hg:`forget`.
4197 files, see :hg:`forget`.
4199
4198
4200 .. container:: verbose
4199 .. container:: verbose
4201
4200
4202 -A/--after can be used to remove only files that have already
4201 -A/--after can be used to remove only files that have already
4203 been deleted, -f/--force can be used to force deletion, and -Af
4202 been deleted, -f/--force can be used to force deletion, and -Af
4204 can be used to remove files from the next revision without
4203 can be used to remove files from the next revision without
4205 deleting them from the working directory.
4204 deleting them from the working directory.
4206
4205
4207 The following table details the behavior of remove for different
4206 The following table details the behavior of remove for different
4208 file states (columns) and option combinations (rows). The file
4207 file states (columns) and option combinations (rows). The file
4209 states are Added [A], Clean [C], Modified [M] and Missing [!]
4208 states are Added [A], Clean [C], Modified [M] and Missing [!]
4210 (as reported by :hg:`status`). The actions are Warn, Remove
4209 (as reported by :hg:`status`). The actions are Warn, Remove
4211 (from branch) and Delete (from disk):
4210 (from branch) and Delete (from disk):
4212
4211
4213 ========= == == == ==
4212 ========= == == == ==
4214 opt/state A C M !
4213 opt/state A C M !
4215 ========= == == == ==
4214 ========= == == == ==
4216 none W RD W R
4215 none W RD W R
4217 -f R RD RD R
4216 -f R RD RD R
4218 -A W W W R
4217 -A W W W R
4219 -Af R R R R
4218 -Af R R R R
4220 ========= == == == ==
4219 ========= == == == ==
4221
4220
4222 .. note::
4221 .. note::
4223
4222
4224 :hg:`remove` never deletes files in Added [A] state from the
4223 :hg:`remove` never deletes files in Added [A] state from the
4225 working directory, not even if ``--force`` is specified.
4224 working directory, not even if ``--force`` is specified.
4226
4225
4227 Returns 0 on success, 1 if any warnings encountered.
4226 Returns 0 on success, 1 if any warnings encountered.
4228 """
4227 """
4229
4228
4230 opts = pycompat.byteskwargs(opts)
4229 opts = pycompat.byteskwargs(opts)
4231 after, force = opts.get('after'), opts.get('force')
4230 after, force = opts.get('after'), opts.get('force')
4232 if not pats and not after:
4231 if not pats and not after:
4233 raise error.Abort(_('no files specified'))
4232 raise error.Abort(_('no files specified'))
4234
4233
4235 m = scmutil.match(repo[None], pats, opts)
4234 m = scmutil.match(repo[None], pats, opts)
4236 subrepos = opts.get('subrepos')
4235 subrepos = opts.get('subrepos')
4237 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4236 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4238
4237
4239 @command('rename|move|mv',
4238 @command('rename|move|mv',
4240 [('A', 'after', None, _('record a rename that has already occurred')),
4239 [('A', 'after', None, _('record a rename that has already occurred')),
4241 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4240 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4242 ] + walkopts + dryrunopts,
4241 ] + walkopts + dryrunopts,
4243 _('[OPTION]... SOURCE... DEST'))
4242 _('[OPTION]... SOURCE... DEST'))
4244 def rename(ui, repo, *pats, **opts):
4243 def rename(ui, repo, *pats, **opts):
4245 """rename files; equivalent of copy + remove
4244 """rename files; equivalent of copy + remove
4246
4245
4247 Mark dest as copies of sources; mark sources for deletion. If dest
4246 Mark dest as copies of sources; mark sources for deletion. If dest
4248 is a directory, copies are put in that directory. If dest is a
4247 is a directory, copies are put in that directory. If dest is a
4249 file, there can only be one source.
4248 file, there can only be one source.
4250
4249
4251 By default, this command copies the contents of files as they
4250 By default, this command copies the contents of files as they
4252 exist in the working directory. If invoked with -A/--after, the
4251 exist in the working directory. If invoked with -A/--after, the
4253 operation is recorded, but no copying is performed.
4252 operation is recorded, but no copying is performed.
4254
4253
4255 This command takes effect at the next commit. To undo a rename
4254 This command takes effect at the next commit. To undo a rename
4256 before that, see :hg:`revert`.
4255 before that, see :hg:`revert`.
4257
4256
4258 Returns 0 on success, 1 if errors are encountered.
4257 Returns 0 on success, 1 if errors are encountered.
4259 """
4258 """
4260 opts = pycompat.byteskwargs(opts)
4259 opts = pycompat.byteskwargs(opts)
4261 with repo.wlock(False):
4260 with repo.wlock(False):
4262 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4261 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4263
4262
4264 @command('resolve',
4263 @command('resolve',
4265 [('a', 'all', None, _('select all unresolved files')),
4264 [('a', 'all', None, _('select all unresolved files')),
4266 ('l', 'list', None, _('list state of files needing merge')),
4265 ('l', 'list', None, _('list state of files needing merge')),
4267 ('m', 'mark', None, _('mark files as resolved')),
4266 ('m', 'mark', None, _('mark files as resolved')),
4268 ('u', 'unmark', None, _('mark files as unresolved')),
4267 ('u', 'unmark', None, _('mark files as unresolved')),
4269 ('n', 'no-status', None, _('hide status prefix'))]
4268 ('n', 'no-status', None, _('hide status prefix'))]
4270 + mergetoolopts + walkopts + formatteropts,
4269 + mergetoolopts + walkopts + formatteropts,
4271 _('[OPTION]... [FILE]...'),
4270 _('[OPTION]... [FILE]...'),
4272 inferrepo=True)
4271 inferrepo=True)
4273 def resolve(ui, repo, *pats, **opts):
4272 def resolve(ui, repo, *pats, **opts):
4274 """redo merges or set/view the merge status of files
4273 """redo merges or set/view the merge status of files
4275
4274
4276 Merges with unresolved conflicts are often the result of
4275 Merges with unresolved conflicts are often the result of
4277 non-interactive merging using the ``internal:merge`` configuration
4276 non-interactive merging using the ``internal:merge`` configuration
4278 setting, or a command-line merge tool like ``diff3``. The resolve
4277 setting, or a command-line merge tool like ``diff3``. The resolve
4279 command is used to manage the files involved in a merge, after
4278 command is used to manage the files involved in a merge, after
4280 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4279 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4281 working directory must have two parents). See :hg:`help
4280 working directory must have two parents). See :hg:`help
4282 merge-tools` for information on configuring merge tools.
4281 merge-tools` for information on configuring merge tools.
4283
4282
4284 The resolve command can be used in the following ways:
4283 The resolve command can be used in the following ways:
4285
4284
4286 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4285 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4287 files, discarding any previous merge attempts. Re-merging is not
4286 files, discarding any previous merge attempts. Re-merging is not
4288 performed for files already marked as resolved. Use ``--all/-a``
4287 performed for files already marked as resolved. Use ``--all/-a``
4289 to select all unresolved files. ``--tool`` can be used to specify
4288 to select all unresolved files. ``--tool`` can be used to specify
4290 the merge tool used for the given files. It overrides the HGMERGE
4289 the merge tool used for the given files. It overrides the HGMERGE
4291 environment variable and your configuration files. Previous file
4290 environment variable and your configuration files. Previous file
4292 contents are saved with a ``.orig`` suffix.
4291 contents are saved with a ``.orig`` suffix.
4293
4292
4294 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4293 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4295 (e.g. after having manually fixed-up the files). The default is
4294 (e.g. after having manually fixed-up the files). The default is
4296 to mark all unresolved files.
4295 to mark all unresolved files.
4297
4296
4298 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4297 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4299 default is to mark all resolved files.
4298 default is to mark all resolved files.
4300
4299
4301 - :hg:`resolve -l`: list files which had or still have conflicts.
4300 - :hg:`resolve -l`: list files which had or still have conflicts.
4302 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4301 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4303 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4302 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4304 the list. See :hg:`help filesets` for details.
4303 the list. See :hg:`help filesets` for details.
4305
4304
4306 .. note::
4305 .. note::
4307
4306
4308 Mercurial will not let you commit files with unresolved merge
4307 Mercurial will not let you commit files with unresolved merge
4309 conflicts. You must use :hg:`resolve -m ...` before you can
4308 conflicts. You must use :hg:`resolve -m ...` before you can
4310 commit after a conflicting merge.
4309 commit after a conflicting merge.
4311
4310
4312 Returns 0 on success, 1 if any files fail a resolve attempt.
4311 Returns 0 on success, 1 if any files fail a resolve attempt.
4313 """
4312 """
4314
4313
4315 opts = pycompat.byteskwargs(opts)
4314 opts = pycompat.byteskwargs(opts)
4316 flaglist = 'all mark unmark list no_status'.split()
4315 flaglist = 'all mark unmark list no_status'.split()
4317 all, mark, unmark, show, nostatus = \
4316 all, mark, unmark, show, nostatus = \
4318 [opts.get(o) for o in flaglist]
4317 [opts.get(o) for o in flaglist]
4319
4318
4320 if (show and (mark or unmark)) or (mark and unmark):
4319 if (show and (mark or unmark)) or (mark and unmark):
4321 raise error.Abort(_("too many options specified"))
4320 raise error.Abort(_("too many options specified"))
4322 if pats and all:
4321 if pats and all:
4323 raise error.Abort(_("can't specify --all and patterns"))
4322 raise error.Abort(_("can't specify --all and patterns"))
4324 if not (all or pats or show or mark or unmark):
4323 if not (all or pats or show or mark or unmark):
4325 raise error.Abort(_('no files or directories specified'),
4324 raise error.Abort(_('no files or directories specified'),
4326 hint=('use --all to re-merge all unresolved files'))
4325 hint=('use --all to re-merge all unresolved files'))
4327
4326
4328 if show:
4327 if show:
4329 ui.pager('resolve')
4328 ui.pager('resolve')
4330 fm = ui.formatter('resolve', opts)
4329 fm = ui.formatter('resolve', opts)
4331 ms = mergemod.mergestate.read(repo)
4330 ms = mergemod.mergestate.read(repo)
4332 m = scmutil.match(repo[None], pats, opts)
4331 m = scmutil.match(repo[None], pats, opts)
4333 for f in ms:
4332 for f in ms:
4334 if not m(f):
4333 if not m(f):
4335 continue
4334 continue
4336 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4335 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4337 'd': 'driverresolved'}[ms[f]]
4336 'd': 'driverresolved'}[ms[f]]
4338 fm.startitem()
4337 fm.startitem()
4339 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4338 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4340 fm.write('path', '%s\n', f, label=l)
4339 fm.write('path', '%s\n', f, label=l)
4341 fm.end()
4340 fm.end()
4342 return 0
4341 return 0
4343
4342
4344 with repo.wlock():
4343 with repo.wlock():
4345 ms = mergemod.mergestate.read(repo)
4344 ms = mergemod.mergestate.read(repo)
4346
4345
4347 if not (ms.active() or repo.dirstate.p2() != nullid):
4346 if not (ms.active() or repo.dirstate.p2() != nullid):
4348 raise error.Abort(
4347 raise error.Abort(
4349 _('resolve command not applicable when not merging'))
4348 _('resolve command not applicable when not merging'))
4350
4349
4351 wctx = repo[None]
4350 wctx = repo[None]
4352
4351
4353 if ms.mergedriver and ms.mdstate() == 'u':
4352 if ms.mergedriver and ms.mdstate() == 'u':
4354 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4353 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4355 ms.commit()
4354 ms.commit()
4356 # allow mark and unmark to go through
4355 # allow mark and unmark to go through
4357 if not mark and not unmark and not proceed:
4356 if not mark and not unmark and not proceed:
4358 return 1
4357 return 1
4359
4358
4360 m = scmutil.match(wctx, pats, opts)
4359 m = scmutil.match(wctx, pats, opts)
4361 ret = 0
4360 ret = 0
4362 didwork = False
4361 didwork = False
4363 runconclude = False
4362 runconclude = False
4364
4363
4365 tocomplete = []
4364 tocomplete = []
4366 for f in ms:
4365 for f in ms:
4367 if not m(f):
4366 if not m(f):
4368 continue
4367 continue
4369
4368
4370 didwork = True
4369 didwork = True
4371
4370
4372 # don't let driver-resolved files be marked, and run the conclude
4371 # don't let driver-resolved files be marked, and run the conclude
4373 # step if asked to resolve
4372 # step if asked to resolve
4374 if ms[f] == "d":
4373 if ms[f] == "d":
4375 exact = m.exact(f)
4374 exact = m.exact(f)
4376 if mark:
4375 if mark:
4377 if exact:
4376 if exact:
4378 ui.warn(_('not marking %s as it is driver-resolved\n')
4377 ui.warn(_('not marking %s as it is driver-resolved\n')
4379 % f)
4378 % f)
4380 elif unmark:
4379 elif unmark:
4381 if exact:
4380 if exact:
4382 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4381 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4383 % f)
4382 % f)
4384 else:
4383 else:
4385 runconclude = True
4384 runconclude = True
4386 continue
4385 continue
4387
4386
4388 if mark:
4387 if mark:
4389 ms.mark(f, "r")
4388 ms.mark(f, "r")
4390 elif unmark:
4389 elif unmark:
4391 ms.mark(f, "u")
4390 ms.mark(f, "u")
4392 else:
4391 else:
4393 # backup pre-resolve (merge uses .orig for its own purposes)
4392 # backup pre-resolve (merge uses .orig for its own purposes)
4394 a = repo.wjoin(f)
4393 a = repo.wjoin(f)
4395 try:
4394 try:
4396 util.copyfile(a, a + ".resolve")
4395 util.copyfile(a, a + ".resolve")
4397 except (IOError, OSError) as inst:
4396 except (IOError, OSError) as inst:
4398 if inst.errno != errno.ENOENT:
4397 if inst.errno != errno.ENOENT:
4399 raise
4398 raise
4400
4399
4401 try:
4400 try:
4402 # preresolve file
4401 # preresolve file
4403 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4404 'resolve')
4403 'resolve')
4405 complete, r = ms.preresolve(f, wctx)
4404 complete, r = ms.preresolve(f, wctx)
4406 if not complete:
4405 if not complete:
4407 tocomplete.append(f)
4406 tocomplete.append(f)
4408 elif r:
4407 elif r:
4409 ret = 1
4408 ret = 1
4410 finally:
4409 finally:
4411 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4410 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4412 ms.commit()
4411 ms.commit()
4413
4412
4414 # replace filemerge's .orig file with our resolve file, but only
4413 # replace filemerge's .orig file with our resolve file, but only
4415 # for merges that are complete
4414 # for merges that are complete
4416 if complete:
4415 if complete:
4417 try:
4416 try:
4418 util.rename(a + ".resolve",
4417 util.rename(a + ".resolve",
4419 scmutil.origpath(ui, repo, a))
4418 scmutil.origpath(ui, repo, a))
4420 except OSError as inst:
4419 except OSError as inst:
4421 if inst.errno != errno.ENOENT:
4420 if inst.errno != errno.ENOENT:
4422 raise
4421 raise
4423
4422
4424 for f in tocomplete:
4423 for f in tocomplete:
4425 try:
4424 try:
4426 # resolve file
4425 # resolve file
4427 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4426 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4428 'resolve')
4427 'resolve')
4429 r = ms.resolve(f, wctx)
4428 r = ms.resolve(f, wctx)
4430 if r:
4429 if r:
4431 ret = 1
4430 ret = 1
4432 finally:
4431 finally:
4433 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4432 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4434 ms.commit()
4433 ms.commit()
4435
4434
4436 # replace filemerge's .orig file with our resolve file
4435 # replace filemerge's .orig file with our resolve file
4437 a = repo.wjoin(f)
4436 a = repo.wjoin(f)
4438 try:
4437 try:
4439 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4438 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4440 except OSError as inst:
4439 except OSError as inst:
4441 if inst.errno != errno.ENOENT:
4440 if inst.errno != errno.ENOENT:
4442 raise
4441 raise
4443
4442
4444 ms.commit()
4443 ms.commit()
4445 ms.recordactions()
4444 ms.recordactions()
4446
4445
4447 if not didwork and pats:
4446 if not didwork and pats:
4448 hint = None
4447 hint = None
4449 if not any([p for p in pats if p.find(':') >= 0]):
4448 if not any([p for p in pats if p.find(':') >= 0]):
4450 pats = ['path:%s' % p for p in pats]
4449 pats = ['path:%s' % p for p in pats]
4451 m = scmutil.match(wctx, pats, opts)
4450 m = scmutil.match(wctx, pats, opts)
4452 for f in ms:
4451 for f in ms:
4453 if not m(f):
4452 if not m(f):
4454 continue
4453 continue
4455 flags = ''.join(['-%s ' % o[0] for o in flaglist
4454 flags = ''.join(['-%s ' % o[0] for o in flaglist
4456 if opts.get(o)])
4455 if opts.get(o)])
4457 hint = _("(try: hg resolve %s%s)\n") % (
4456 hint = _("(try: hg resolve %s%s)\n") % (
4458 flags,
4457 flags,
4459 ' '.join(pats))
4458 ' '.join(pats))
4460 break
4459 break
4461 ui.warn(_("arguments do not match paths that need resolving\n"))
4460 ui.warn(_("arguments do not match paths that need resolving\n"))
4462 if hint:
4461 if hint:
4463 ui.warn(hint)
4462 ui.warn(hint)
4464 elif ms.mergedriver and ms.mdstate() != 's':
4463 elif ms.mergedriver and ms.mdstate() != 's':
4465 # run conclude step when either a driver-resolved file is requested
4464 # run conclude step when either a driver-resolved file is requested
4466 # or there are no driver-resolved files
4465 # or there are no driver-resolved files
4467 # we can't use 'ret' to determine whether any files are unresolved
4466 # we can't use 'ret' to determine whether any files are unresolved
4468 # because we might not have tried to resolve some
4467 # because we might not have tried to resolve some
4469 if ((runconclude or not list(ms.driverresolved()))
4468 if ((runconclude or not list(ms.driverresolved()))
4470 and not list(ms.unresolved())):
4469 and not list(ms.unresolved())):
4471 proceed = mergemod.driverconclude(repo, ms, wctx)
4470 proceed = mergemod.driverconclude(repo, ms, wctx)
4472 ms.commit()
4471 ms.commit()
4473 if not proceed:
4472 if not proceed:
4474 return 1
4473 return 1
4475
4474
4476 # Nudge users into finishing an unfinished operation
4475 # Nudge users into finishing an unfinished operation
4477 unresolvedf = list(ms.unresolved())
4476 unresolvedf = list(ms.unresolved())
4478 driverresolvedf = list(ms.driverresolved())
4477 driverresolvedf = list(ms.driverresolved())
4479 if not unresolvedf and not driverresolvedf:
4478 if not unresolvedf and not driverresolvedf:
4480 ui.status(_('(no more unresolved files)\n'))
4479 ui.status(_('(no more unresolved files)\n'))
4481 cmdutil.checkafterresolved(repo)
4480 cmdutil.checkafterresolved(repo)
4482 elif not unresolvedf:
4481 elif not unresolvedf:
4483 ui.status(_('(no more unresolved files -- '
4482 ui.status(_('(no more unresolved files -- '
4484 'run "hg resolve --all" to conclude)\n'))
4483 'run "hg resolve --all" to conclude)\n'))
4485
4484
4486 return ret
4485 return ret
4487
4486
4488 @command('revert',
4487 @command('revert',
4489 [('a', 'all', None, _('revert all changes when no arguments given')),
4488 [('a', 'all', None, _('revert all changes when no arguments given')),
4490 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4489 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4491 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4490 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4492 ('C', 'no-backup', None, _('do not save backup copies of files')),
4491 ('C', 'no-backup', None, _('do not save backup copies of files')),
4493 ('i', 'interactive', None,
4492 ('i', 'interactive', None,
4494 _('interactively select the changes (EXPERIMENTAL)')),
4493 _('interactively select the changes (EXPERIMENTAL)')),
4495 ] + walkopts + dryrunopts,
4494 ] + walkopts + dryrunopts,
4496 _('[OPTION]... [-r REV] [NAME]...'))
4495 _('[OPTION]... [-r REV] [NAME]...'))
4497 def revert(ui, repo, *pats, **opts):
4496 def revert(ui, repo, *pats, **opts):
4498 """restore files to their checkout state
4497 """restore files to their checkout state
4499
4498
4500 .. note::
4499 .. note::
4501
4500
4502 To check out earlier revisions, you should use :hg:`update REV`.
4501 To check out earlier revisions, you should use :hg:`update REV`.
4503 To cancel an uncommitted merge (and lose your changes),
4502 To cancel an uncommitted merge (and lose your changes),
4504 use :hg:`update --clean .`.
4503 use :hg:`update --clean .`.
4505
4504
4506 With no revision specified, revert the specified files or directories
4505 With no revision specified, revert the specified files or directories
4507 to the contents they had in the parent of the working directory.
4506 to the contents they had in the parent of the working directory.
4508 This restores the contents of files to an unmodified
4507 This restores the contents of files to an unmodified
4509 state and unschedules adds, removes, copies, and renames. If the
4508 state and unschedules adds, removes, copies, and renames. If the
4510 working directory has two parents, you must explicitly specify a
4509 working directory has two parents, you must explicitly specify a
4511 revision.
4510 revision.
4512
4511
4513 Using the -r/--rev or -d/--date options, revert the given files or
4512 Using the -r/--rev or -d/--date options, revert the given files or
4514 directories to their states as of a specific revision. Because
4513 directories to their states as of a specific revision. Because
4515 revert does not change the working directory parents, this will
4514 revert does not change the working directory parents, this will
4516 cause these files to appear modified. This can be helpful to "back
4515 cause these files to appear modified. This can be helpful to "back
4517 out" some or all of an earlier change. See :hg:`backout` for a
4516 out" some or all of an earlier change. See :hg:`backout` for a
4518 related method.
4517 related method.
4519
4518
4520 Modified files are saved with a .orig suffix before reverting.
4519 Modified files are saved with a .orig suffix before reverting.
4521 To disable these backups, use --no-backup. It is possible to store
4520 To disable these backups, use --no-backup. It is possible to store
4522 the backup files in a custom directory relative to the root of the
4521 the backup files in a custom directory relative to the root of the
4523 repository by setting the ``ui.origbackuppath`` configuration
4522 repository by setting the ``ui.origbackuppath`` configuration
4524 option.
4523 option.
4525
4524
4526 See :hg:`help dates` for a list of formats valid for -d/--date.
4525 See :hg:`help dates` for a list of formats valid for -d/--date.
4527
4526
4528 See :hg:`help backout` for a way to reverse the effect of an
4527 See :hg:`help backout` for a way to reverse the effect of an
4529 earlier changeset.
4528 earlier changeset.
4530
4529
4531 Returns 0 on success.
4530 Returns 0 on success.
4532 """
4531 """
4533
4532
4534 if opts.get("date"):
4533 if opts.get("date"):
4535 if opts.get("rev"):
4534 if opts.get("rev"):
4536 raise error.Abort(_("you can't specify a revision and a date"))
4535 raise error.Abort(_("you can't specify a revision and a date"))
4537 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4536 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4538
4537
4539 parent, p2 = repo.dirstate.parents()
4538 parent, p2 = repo.dirstate.parents()
4540 if not opts.get('rev') and p2 != nullid:
4539 if not opts.get('rev') and p2 != nullid:
4541 # revert after merge is a trap for new users (issue2915)
4540 # revert after merge is a trap for new users (issue2915)
4542 raise error.Abort(_('uncommitted merge with no revision specified'),
4541 raise error.Abort(_('uncommitted merge with no revision specified'),
4543 hint=_("use 'hg update' or see 'hg help revert'"))
4542 hint=_("use 'hg update' or see 'hg help revert'"))
4544
4543
4545 ctx = scmutil.revsingle(repo, opts.get('rev'))
4544 ctx = scmutil.revsingle(repo, opts.get('rev'))
4546
4545
4547 if (not (pats or opts.get('include') or opts.get('exclude') or
4546 if (not (pats or opts.get('include') or opts.get('exclude') or
4548 opts.get('all') or opts.get('interactive'))):
4547 opts.get('all') or opts.get('interactive'))):
4549 msg = _("no files or directories specified")
4548 msg = _("no files or directories specified")
4550 if p2 != nullid:
4549 if p2 != nullid:
4551 hint = _("uncommitted merge, use --all to discard all changes,"
4550 hint = _("uncommitted merge, use --all to discard all changes,"
4552 " or 'hg update -C .' to abort the merge")
4551 " or 'hg update -C .' to abort the merge")
4553 raise error.Abort(msg, hint=hint)
4552 raise error.Abort(msg, hint=hint)
4554 dirty = any(repo.status())
4553 dirty = any(repo.status())
4555 node = ctx.node()
4554 node = ctx.node()
4556 if node != parent:
4555 if node != parent:
4557 if dirty:
4556 if dirty:
4558 hint = _("uncommitted changes, use --all to discard all"
4557 hint = _("uncommitted changes, use --all to discard all"
4559 " changes, or 'hg update %s' to update") % ctx.rev()
4558 " changes, or 'hg update %s' to update") % ctx.rev()
4560 else:
4559 else:
4561 hint = _("use --all to revert all files,"
4560 hint = _("use --all to revert all files,"
4562 " or 'hg update %s' to update") % ctx.rev()
4561 " or 'hg update %s' to update") % ctx.rev()
4563 elif dirty:
4562 elif dirty:
4564 hint = _("uncommitted changes, use --all to discard all changes")
4563 hint = _("uncommitted changes, use --all to discard all changes")
4565 else:
4564 else:
4566 hint = _("use --all to revert all files")
4565 hint = _("use --all to revert all files")
4567 raise error.Abort(msg, hint=hint)
4566 raise error.Abort(msg, hint=hint)
4568
4567
4569 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4568 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4570
4569
4571 @command('rollback', dryrunopts +
4570 @command('rollback', dryrunopts +
4572 [('f', 'force', False, _('ignore safety measures'))])
4571 [('f', 'force', False, _('ignore safety measures'))])
4573 def rollback(ui, repo, **opts):
4572 def rollback(ui, repo, **opts):
4574 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4573 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4575
4574
4576 Please use :hg:`commit --amend` instead of rollback to correct
4575 Please use :hg:`commit --amend` instead of rollback to correct
4577 mistakes in the last commit.
4576 mistakes in the last commit.
4578
4577
4579 This command should be used with care. There is only one level of
4578 This command should be used with care. There is only one level of
4580 rollback, and there is no way to undo a rollback. It will also
4579 rollback, and there is no way to undo a rollback. It will also
4581 restore the dirstate at the time of the last transaction, losing
4580 restore the dirstate at the time of the last transaction, losing
4582 any dirstate changes since that time. This command does not alter
4581 any dirstate changes since that time. This command does not alter
4583 the working directory.
4582 the working directory.
4584
4583
4585 Transactions are used to encapsulate the effects of all commands
4584 Transactions are used to encapsulate the effects of all commands
4586 that create new changesets or propagate existing changesets into a
4585 that create new changesets or propagate existing changesets into a
4587 repository.
4586 repository.
4588
4587
4589 .. container:: verbose
4588 .. container:: verbose
4590
4589
4591 For example, the following commands are transactional, and their
4590 For example, the following commands are transactional, and their
4592 effects can be rolled back:
4591 effects can be rolled back:
4593
4592
4594 - commit
4593 - commit
4595 - import
4594 - import
4596 - pull
4595 - pull
4597 - push (with this repository as the destination)
4596 - push (with this repository as the destination)
4598 - unbundle
4597 - unbundle
4599
4598
4600 To avoid permanent data loss, rollback will refuse to rollback a
4599 To avoid permanent data loss, rollback will refuse to rollback a
4601 commit transaction if it isn't checked out. Use --force to
4600 commit transaction if it isn't checked out. Use --force to
4602 override this protection.
4601 override this protection.
4603
4602
4604 The rollback command can be entirely disabled by setting the
4603 The rollback command can be entirely disabled by setting the
4605 ``ui.rollback`` configuration setting to false. If you're here
4604 ``ui.rollback`` configuration setting to false. If you're here
4606 because you want to use rollback and it's disabled, you can
4605 because you want to use rollback and it's disabled, you can
4607 re-enable the command by setting ``ui.rollback`` to true.
4606 re-enable the command by setting ``ui.rollback`` to true.
4608
4607
4609 This command is not intended for use on public repositories. Once
4608 This command is not intended for use on public repositories. Once
4610 changes are visible for pull by other users, rolling a transaction
4609 changes are visible for pull by other users, rolling a transaction
4611 back locally is ineffective (someone else may already have pulled
4610 back locally is ineffective (someone else may already have pulled
4612 the changes). Furthermore, a race is possible with readers of the
4611 the changes). Furthermore, a race is possible with readers of the
4613 repository; for example an in-progress pull from the repository
4612 repository; for example an in-progress pull from the repository
4614 may fail if a rollback is performed.
4613 may fail if a rollback is performed.
4615
4614
4616 Returns 0 on success, 1 if no rollback data is available.
4615 Returns 0 on success, 1 if no rollback data is available.
4617 """
4616 """
4618 if not ui.configbool('ui', 'rollback', True):
4617 if not ui.configbool('ui', 'rollback', True):
4619 raise error.Abort(_('rollback is disabled because it is unsafe'),
4618 raise error.Abort(_('rollback is disabled because it is unsafe'),
4620 hint=('see `hg help -v rollback` for information'))
4619 hint=('see `hg help -v rollback` for information'))
4621 return repo.rollback(dryrun=opts.get(r'dry_run'),
4620 return repo.rollback(dryrun=opts.get(r'dry_run'),
4622 force=opts.get(r'force'))
4621 force=opts.get(r'force'))
4623
4622
4624 @command('root', [])
4623 @command('root', [])
4625 def root(ui, repo):
4624 def root(ui, repo):
4626 """print the root (top) of the current working directory
4625 """print the root (top) of the current working directory
4627
4626
4628 Print the root directory of the current repository.
4627 Print the root directory of the current repository.
4629
4628
4630 Returns 0 on success.
4629 Returns 0 on success.
4631 """
4630 """
4632 ui.write(repo.root + "\n")
4631 ui.write(repo.root + "\n")
4633
4632
4634 @command('^serve',
4633 @command('^serve',
4635 [('A', 'accesslog', '', _('name of access log file to write to'),
4634 [('A', 'accesslog', '', _('name of access log file to write to'),
4636 _('FILE')),
4635 _('FILE')),
4637 ('d', 'daemon', None, _('run server in background')),
4636 ('d', 'daemon', None, _('run server in background')),
4638 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4637 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4639 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4638 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4640 # use string type, then we can check if something was passed
4639 # use string type, then we can check if something was passed
4641 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4640 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4642 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4641 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4643 _('ADDR')),
4642 _('ADDR')),
4644 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4643 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4645 _('PREFIX')),
4644 _('PREFIX')),
4646 ('n', 'name', '',
4645 ('n', 'name', '',
4647 _('name to show in web pages (default: working directory)'), _('NAME')),
4646 _('name to show in web pages (default: working directory)'), _('NAME')),
4648 ('', 'web-conf', '',
4647 ('', 'web-conf', '',
4649 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4648 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4650 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4649 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4651 _('FILE')),
4650 _('FILE')),
4652 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4651 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4653 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4652 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4654 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4653 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4655 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4654 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4656 ('', 'style', '', _('template style to use'), _('STYLE')),
4655 ('', 'style', '', _('template style to use'), _('STYLE')),
4657 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4656 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4658 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4657 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4659 + subrepoopts,
4658 + subrepoopts,
4660 _('[OPTION]...'),
4659 _('[OPTION]...'),
4661 optionalrepo=True)
4660 optionalrepo=True)
4662 def serve(ui, repo, **opts):
4661 def serve(ui, repo, **opts):
4663 """start stand-alone webserver
4662 """start stand-alone webserver
4664
4663
4665 Start a local HTTP repository browser and pull server. You can use
4664 Start a local HTTP repository browser and pull server. You can use
4666 this for ad-hoc sharing and browsing of repositories. It is
4665 this for ad-hoc sharing and browsing of repositories. It is
4667 recommended to use a real web server to serve a repository for
4666 recommended to use a real web server to serve a repository for
4668 longer periods of time.
4667 longer periods of time.
4669
4668
4670 Please note that the server does not implement access control.
4669 Please note that the server does not implement access control.
4671 This means that, by default, anybody can read from the server and
4670 This means that, by default, anybody can read from the server and
4672 nobody can write to it by default. Set the ``web.allow_push``
4671 nobody can write to it by default. Set the ``web.allow_push``
4673 option to ``*`` to allow everybody to push to the server. You
4672 option to ``*`` to allow everybody to push to the server. You
4674 should use a real web server if you need to authenticate users.
4673 should use a real web server if you need to authenticate users.
4675
4674
4676 By default, the server logs accesses to stdout and errors to
4675 By default, the server logs accesses to stdout and errors to
4677 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4676 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4678 files.
4677 files.
4679
4678
4680 To have the server choose a free port number to listen on, specify
4679 To have the server choose a free port number to listen on, specify
4681 a port number of 0; in this case, the server will print the port
4680 a port number of 0; in this case, the server will print the port
4682 number it uses.
4681 number it uses.
4683
4682
4684 Returns 0 on success.
4683 Returns 0 on success.
4685 """
4684 """
4686
4685
4687 opts = pycompat.byteskwargs(opts)
4686 opts = pycompat.byteskwargs(opts)
4688 if opts["stdio"] and opts["cmdserver"]:
4687 if opts["stdio"] and opts["cmdserver"]:
4689 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4688 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4690
4689
4691 if opts["stdio"]:
4690 if opts["stdio"]:
4692 if repo is None:
4691 if repo is None:
4693 raise error.RepoError(_("there is no Mercurial repository here"
4692 raise error.RepoError(_("there is no Mercurial repository here"
4694 " (.hg not found)"))
4693 " (.hg not found)"))
4695 s = sshserver.sshserver(ui, repo)
4694 s = sshserver.sshserver(ui, repo)
4696 s.serve_forever()
4695 s.serve_forever()
4697
4696
4698 service = server.createservice(ui, repo, opts)
4697 service = server.createservice(ui, repo, opts)
4699 return server.runservice(opts, initfn=service.init, runfn=service.run)
4698 return server.runservice(opts, initfn=service.init, runfn=service.run)
4700
4699
4701 @command('^status|st',
4700 @command('^status|st',
4702 [('A', 'all', None, _('show status of all files')),
4701 [('A', 'all', None, _('show status of all files')),
4703 ('m', 'modified', None, _('show only modified files')),
4702 ('m', 'modified', None, _('show only modified files')),
4704 ('a', 'added', None, _('show only added files')),
4703 ('a', 'added', None, _('show only added files')),
4705 ('r', 'removed', None, _('show only removed files')),
4704 ('r', 'removed', None, _('show only removed files')),
4706 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4705 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4707 ('c', 'clean', None, _('show only files without changes')),
4706 ('c', 'clean', None, _('show only files without changes')),
4708 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4707 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4709 ('i', 'ignored', None, _('show only ignored files')),
4708 ('i', 'ignored', None, _('show only ignored files')),
4710 ('n', 'no-status', None, _('hide status prefix')),
4709 ('n', 'no-status', None, _('hide status prefix')),
4711 ('C', 'copies', None, _('show source of copied files')),
4710 ('C', 'copies', None, _('show source of copied files')),
4712 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4711 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4713 ('', 'rev', [], _('show difference from revision'), _('REV')),
4712 ('', 'rev', [], _('show difference from revision'), _('REV')),
4714 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4713 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4715 ] + walkopts + subrepoopts + formatteropts,
4714 ] + walkopts + subrepoopts + formatteropts,
4716 _('[OPTION]... [FILE]...'),
4715 _('[OPTION]... [FILE]...'),
4717 inferrepo=True)
4716 inferrepo=True)
4718 def status(ui, repo, *pats, **opts):
4717 def status(ui, repo, *pats, **opts):
4719 """show changed files in the working directory
4718 """show changed files in the working directory
4720
4719
4721 Show status of files in the repository. If names are given, only
4720 Show status of files in the repository. If names are given, only
4722 files that match are shown. Files that are clean or ignored or
4721 files that match are shown. Files that are clean or ignored or
4723 the source of a copy/move operation, are not listed unless
4722 the source of a copy/move operation, are not listed unless
4724 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4723 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4725 Unless options described with "show only ..." are given, the
4724 Unless options described with "show only ..." are given, the
4726 options -mardu are used.
4725 options -mardu are used.
4727
4726
4728 Option -q/--quiet hides untracked (unknown and ignored) files
4727 Option -q/--quiet hides untracked (unknown and ignored) files
4729 unless explicitly requested with -u/--unknown or -i/--ignored.
4728 unless explicitly requested with -u/--unknown or -i/--ignored.
4730
4729
4731 .. note::
4730 .. note::
4732
4731
4733 :hg:`status` may appear to disagree with diff if permissions have
4732 :hg:`status` may appear to disagree with diff if permissions have
4734 changed or a merge has occurred. The standard diff format does
4733 changed or a merge has occurred. The standard diff format does
4735 not report permission changes and diff only reports changes
4734 not report permission changes and diff only reports changes
4736 relative to one merge parent.
4735 relative to one merge parent.
4737
4736
4738 If one revision is given, it is used as the base revision.
4737 If one revision is given, it is used as the base revision.
4739 If two revisions are given, the differences between them are
4738 If two revisions are given, the differences between them are
4740 shown. The --change option can also be used as a shortcut to list
4739 shown. The --change option can also be used as a shortcut to list
4741 the changed files of a revision from its first parent.
4740 the changed files of a revision from its first parent.
4742
4741
4743 The codes used to show the status of files are::
4742 The codes used to show the status of files are::
4744
4743
4745 M = modified
4744 M = modified
4746 A = added
4745 A = added
4747 R = removed
4746 R = removed
4748 C = clean
4747 C = clean
4749 ! = missing (deleted by non-hg command, but still tracked)
4748 ! = missing (deleted by non-hg command, but still tracked)
4750 ? = not tracked
4749 ? = not tracked
4751 I = ignored
4750 I = ignored
4752 = origin of the previous file (with --copies)
4751 = origin of the previous file (with --copies)
4753
4752
4754 .. container:: verbose
4753 .. container:: verbose
4755
4754
4756 Examples:
4755 Examples:
4757
4756
4758 - show changes in the working directory relative to a
4757 - show changes in the working directory relative to a
4759 changeset::
4758 changeset::
4760
4759
4761 hg status --rev 9353
4760 hg status --rev 9353
4762
4761
4763 - show changes in the working directory relative to the
4762 - show changes in the working directory relative to the
4764 current directory (see :hg:`help patterns` for more information)::
4763 current directory (see :hg:`help patterns` for more information)::
4765
4764
4766 hg status re:
4765 hg status re:
4767
4766
4768 - show all changes including copies in an existing changeset::
4767 - show all changes including copies in an existing changeset::
4769
4768
4770 hg status --copies --change 9353
4769 hg status --copies --change 9353
4771
4770
4772 - get a NUL separated list of added files, suitable for xargs::
4771 - get a NUL separated list of added files, suitable for xargs::
4773
4772
4774 hg status -an0
4773 hg status -an0
4775
4774
4776 Returns 0 on success.
4775 Returns 0 on success.
4777 """
4776 """
4778
4777
4779 opts = pycompat.byteskwargs(opts)
4778 opts = pycompat.byteskwargs(opts)
4780 revs = opts.get('rev')
4779 revs = opts.get('rev')
4781 change = opts.get('change')
4780 change = opts.get('change')
4782
4781
4783 if revs and change:
4782 if revs and change:
4784 msg = _('cannot specify --rev and --change at the same time')
4783 msg = _('cannot specify --rev and --change at the same time')
4785 raise error.Abort(msg)
4784 raise error.Abort(msg)
4786 elif change:
4785 elif change:
4787 node2 = scmutil.revsingle(repo, change, None).node()
4786 node2 = scmutil.revsingle(repo, change, None).node()
4788 node1 = repo[node2].p1().node()
4787 node1 = repo[node2].p1().node()
4789 else:
4788 else:
4790 node1, node2 = scmutil.revpair(repo, revs)
4789 node1, node2 = scmutil.revpair(repo, revs)
4791
4790
4792 if pats or ui.configbool('commands', 'status.relative'):
4791 if pats or ui.configbool('commands', 'status.relative'):
4793 cwd = repo.getcwd()
4792 cwd = repo.getcwd()
4794 else:
4793 else:
4795 cwd = ''
4794 cwd = ''
4796
4795
4797 if opts.get('print0'):
4796 if opts.get('print0'):
4798 end = '\0'
4797 end = '\0'
4799 else:
4798 else:
4800 end = '\n'
4799 end = '\n'
4801 copy = {}
4800 copy = {}
4802 states = 'modified added removed deleted unknown ignored clean'.split()
4801 states = 'modified added removed deleted unknown ignored clean'.split()
4803 show = [k for k in states if opts.get(k)]
4802 show = [k for k in states if opts.get(k)]
4804 if opts.get('all'):
4803 if opts.get('all'):
4805 show += ui.quiet and (states[:4] + ['clean']) or states
4804 show += ui.quiet and (states[:4] + ['clean']) or states
4806 if not show:
4805 if not show:
4807 if ui.quiet:
4806 if ui.quiet:
4808 show = states[:4]
4807 show = states[:4]
4809 else:
4808 else:
4810 show = states[:5]
4809 show = states[:5]
4811
4810
4812 m = scmutil.match(repo[node2], pats, opts)
4811 m = scmutil.match(repo[node2], pats, opts)
4813 stat = repo.status(node1, node2, m,
4812 stat = repo.status(node1, node2, m,
4814 'ignored' in show, 'clean' in show, 'unknown' in show,
4813 'ignored' in show, 'clean' in show, 'unknown' in show,
4815 opts.get('subrepos'))
4814 opts.get('subrepos'))
4816 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4815 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4817
4816
4818 if (opts.get('all') or opts.get('copies')
4817 if (opts.get('all') or opts.get('copies')
4819 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4818 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4820 copy = copies.pathcopies(repo[node1], repo[node2], m)
4819 copy = copies.pathcopies(repo[node1], repo[node2], m)
4821
4820
4822 ui.pager('status')
4821 ui.pager('status')
4823 fm = ui.formatter('status', opts)
4822 fm = ui.formatter('status', opts)
4824 fmt = '%s' + end
4823 fmt = '%s' + end
4825 showchar = not opts.get('no_status')
4824 showchar = not opts.get('no_status')
4826
4825
4827 for state, char, files in changestates:
4826 for state, char, files in changestates:
4828 if state in show:
4827 if state in show:
4829 label = 'status.' + state
4828 label = 'status.' + state
4830 for f in files:
4829 for f in files:
4831 fm.startitem()
4830 fm.startitem()
4832 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4831 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4833 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4832 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4834 if f in copy:
4833 if f in copy:
4835 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4834 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4836 label='status.copied')
4835 label='status.copied')
4837 fm.end()
4836 fm.end()
4838
4837
4839 @command('^summary|sum',
4838 @command('^summary|sum',
4840 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4839 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4841 def summary(ui, repo, **opts):
4840 def summary(ui, repo, **opts):
4842 """summarize working directory state
4841 """summarize working directory state
4843
4842
4844 This generates a brief summary of the working directory state,
4843 This generates a brief summary of the working directory state,
4845 including parents, branch, commit status, phase and available updates.
4844 including parents, branch, commit status, phase and available updates.
4846
4845
4847 With the --remote option, this will check the default paths for
4846 With the --remote option, this will check the default paths for
4848 incoming and outgoing changes. This can be time-consuming.
4847 incoming and outgoing changes. This can be time-consuming.
4849
4848
4850 Returns 0 on success.
4849 Returns 0 on success.
4851 """
4850 """
4852
4851
4853 opts = pycompat.byteskwargs(opts)
4852 opts = pycompat.byteskwargs(opts)
4854 ui.pager('summary')
4853 ui.pager('summary')
4855 ctx = repo[None]
4854 ctx = repo[None]
4856 parents = ctx.parents()
4855 parents = ctx.parents()
4857 pnode = parents[0].node()
4856 pnode = parents[0].node()
4858 marks = []
4857 marks = []
4859
4858
4860 ms = None
4859 ms = None
4861 try:
4860 try:
4862 ms = mergemod.mergestate.read(repo)
4861 ms = mergemod.mergestate.read(repo)
4863 except error.UnsupportedMergeRecords as e:
4862 except error.UnsupportedMergeRecords as e:
4864 s = ' '.join(e.recordtypes)
4863 s = ' '.join(e.recordtypes)
4865 ui.warn(
4864 ui.warn(
4866 _('warning: merge state has unsupported record types: %s\n') % s)
4865 _('warning: merge state has unsupported record types: %s\n') % s)
4867 unresolved = 0
4866 unresolved = 0
4868 else:
4867 else:
4869 unresolved = [f for f in ms if ms[f] == 'u']
4868 unresolved = [f for f in ms if ms[f] == 'u']
4870
4869
4871 for p in parents:
4870 for p in parents:
4872 # label with log.changeset (instead of log.parent) since this
4871 # label with log.changeset (instead of log.parent) since this
4873 # shows a working directory parent *changeset*:
4872 # shows a working directory parent *changeset*:
4874 # i18n: column positioning for "hg summary"
4873 # i18n: column positioning for "hg summary"
4875 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4874 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4876 label=cmdutil._changesetlabels(p))
4875 label=cmdutil._changesetlabels(p))
4877 ui.write(' '.join(p.tags()), label='log.tag')
4876 ui.write(' '.join(p.tags()), label='log.tag')
4878 if p.bookmarks():
4877 if p.bookmarks():
4879 marks.extend(p.bookmarks())
4878 marks.extend(p.bookmarks())
4880 if p.rev() == -1:
4879 if p.rev() == -1:
4881 if not len(repo):
4880 if not len(repo):
4882 ui.write(_(' (empty repository)'))
4881 ui.write(_(' (empty repository)'))
4883 else:
4882 else:
4884 ui.write(_(' (no revision checked out)'))
4883 ui.write(_(' (no revision checked out)'))
4885 if p.obsolete():
4884 if p.obsolete():
4886 ui.write(_(' (obsolete)'))
4885 ui.write(_(' (obsolete)'))
4887 if p.troubled():
4886 if p.troubled():
4888 ui.write(' ('
4887 ui.write(' ('
4889 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4888 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4890 for trouble in p.troubles())
4889 for trouble in p.troubles())
4891 + ')')
4890 + ')')
4892 ui.write('\n')
4891 ui.write('\n')
4893 if p.description():
4892 if p.description():
4894 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4893 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4895 label='log.summary')
4894 label='log.summary')
4896
4895
4897 branch = ctx.branch()
4896 branch = ctx.branch()
4898 bheads = repo.branchheads(branch)
4897 bheads = repo.branchheads(branch)
4899 # i18n: column positioning for "hg summary"
4898 # i18n: column positioning for "hg summary"
4900 m = _('branch: %s\n') % branch
4899 m = _('branch: %s\n') % branch
4901 if branch != 'default':
4900 if branch != 'default':
4902 ui.write(m, label='log.branch')
4901 ui.write(m, label='log.branch')
4903 else:
4902 else:
4904 ui.status(m, label='log.branch')
4903 ui.status(m, label='log.branch')
4905
4904
4906 if marks:
4905 if marks:
4907 active = repo._activebookmark
4906 active = repo._activebookmark
4908 # i18n: column positioning for "hg summary"
4907 # i18n: column positioning for "hg summary"
4909 ui.write(_('bookmarks:'), label='log.bookmark')
4908 ui.write(_('bookmarks:'), label='log.bookmark')
4910 if active is not None:
4909 if active is not None:
4911 if active in marks:
4910 if active in marks:
4912 ui.write(' *' + active, label=activebookmarklabel)
4911 ui.write(' *' + active, label=activebookmarklabel)
4913 marks.remove(active)
4912 marks.remove(active)
4914 else:
4913 else:
4915 ui.write(' [%s]' % active, label=activebookmarklabel)
4914 ui.write(' [%s]' % active, label=activebookmarklabel)
4916 for m in marks:
4915 for m in marks:
4917 ui.write(' ' + m, label='log.bookmark')
4916 ui.write(' ' + m, label='log.bookmark')
4918 ui.write('\n', label='log.bookmark')
4917 ui.write('\n', label='log.bookmark')
4919
4918
4920 status = repo.status(unknown=True)
4919 status = repo.status(unknown=True)
4921
4920
4922 c = repo.dirstate.copies()
4921 c = repo.dirstate.copies()
4923 copied, renamed = [], []
4922 copied, renamed = [], []
4924 for d, s in c.iteritems():
4923 for d, s in c.iteritems():
4925 if s in status.removed:
4924 if s in status.removed:
4926 status.removed.remove(s)
4925 status.removed.remove(s)
4927 renamed.append(d)
4926 renamed.append(d)
4928 else:
4927 else:
4929 copied.append(d)
4928 copied.append(d)
4930 if d in status.added:
4929 if d in status.added:
4931 status.added.remove(d)
4930 status.added.remove(d)
4932
4931
4933 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4932 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4934
4933
4935 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4934 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4936 (ui.label(_('%d added'), 'status.added'), status.added),
4935 (ui.label(_('%d added'), 'status.added'), status.added),
4937 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4936 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4938 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4937 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4939 (ui.label(_('%d copied'), 'status.copied'), copied),
4938 (ui.label(_('%d copied'), 'status.copied'), copied),
4940 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4939 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4941 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4940 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4942 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4941 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4943 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4942 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4944 t = []
4943 t = []
4945 for l, s in labels:
4944 for l, s in labels:
4946 if s:
4945 if s:
4947 t.append(l % len(s))
4946 t.append(l % len(s))
4948
4947
4949 t = ', '.join(t)
4948 t = ', '.join(t)
4950 cleanworkdir = False
4949 cleanworkdir = False
4951
4950
4952 if repo.vfs.exists('graftstate'):
4951 if repo.vfs.exists('graftstate'):
4953 t += _(' (graft in progress)')
4952 t += _(' (graft in progress)')
4954 if repo.vfs.exists('updatestate'):
4953 if repo.vfs.exists('updatestate'):
4955 t += _(' (interrupted update)')
4954 t += _(' (interrupted update)')
4956 elif len(parents) > 1:
4955 elif len(parents) > 1:
4957 t += _(' (merge)')
4956 t += _(' (merge)')
4958 elif branch != parents[0].branch():
4957 elif branch != parents[0].branch():
4959 t += _(' (new branch)')
4958 t += _(' (new branch)')
4960 elif (parents[0].closesbranch() and
4959 elif (parents[0].closesbranch() and
4961 pnode in repo.branchheads(branch, closed=True)):
4960 pnode in repo.branchheads(branch, closed=True)):
4962 t += _(' (head closed)')
4961 t += _(' (head closed)')
4963 elif not (status.modified or status.added or status.removed or renamed or
4962 elif not (status.modified or status.added or status.removed or renamed or
4964 copied or subs):
4963 copied or subs):
4965 t += _(' (clean)')
4964 t += _(' (clean)')
4966 cleanworkdir = True
4965 cleanworkdir = True
4967 elif pnode not in bheads:
4966 elif pnode not in bheads:
4968 t += _(' (new branch head)')
4967 t += _(' (new branch head)')
4969
4968
4970 if parents:
4969 if parents:
4971 pendingphase = max(p.phase() for p in parents)
4970 pendingphase = max(p.phase() for p in parents)
4972 else:
4971 else:
4973 pendingphase = phases.public
4972 pendingphase = phases.public
4974
4973
4975 if pendingphase > phases.newcommitphase(ui):
4974 if pendingphase > phases.newcommitphase(ui):
4976 t += ' (%s)' % phases.phasenames[pendingphase]
4975 t += ' (%s)' % phases.phasenames[pendingphase]
4977
4976
4978 if cleanworkdir:
4977 if cleanworkdir:
4979 # i18n: column positioning for "hg summary"
4978 # i18n: column positioning for "hg summary"
4980 ui.status(_('commit: %s\n') % t.strip())
4979 ui.status(_('commit: %s\n') % t.strip())
4981 else:
4980 else:
4982 # i18n: column positioning for "hg summary"
4981 # i18n: column positioning for "hg summary"
4983 ui.write(_('commit: %s\n') % t.strip())
4982 ui.write(_('commit: %s\n') % t.strip())
4984
4983
4985 # all ancestors of branch heads - all ancestors of parent = new csets
4984 # all ancestors of branch heads - all ancestors of parent = new csets
4986 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4985 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4987 bheads))
4986 bheads))
4988
4987
4989 if new == 0:
4988 if new == 0:
4990 # i18n: column positioning for "hg summary"
4989 # i18n: column positioning for "hg summary"
4991 ui.status(_('update: (current)\n'))
4990 ui.status(_('update: (current)\n'))
4992 elif pnode not in bheads:
4991 elif pnode not in bheads:
4993 # i18n: column positioning for "hg summary"
4992 # i18n: column positioning for "hg summary"
4994 ui.write(_('update: %d new changesets (update)\n') % new)
4993 ui.write(_('update: %d new changesets (update)\n') % new)
4995 else:
4994 else:
4996 # i18n: column positioning for "hg summary"
4995 # i18n: column positioning for "hg summary"
4997 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4996 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4998 (new, len(bheads)))
4997 (new, len(bheads)))
4999
4998
5000 t = []
4999 t = []
5001 draft = len(repo.revs('draft()'))
5000 draft = len(repo.revs('draft()'))
5002 if draft:
5001 if draft:
5003 t.append(_('%d draft') % draft)
5002 t.append(_('%d draft') % draft)
5004 secret = len(repo.revs('secret()'))
5003 secret = len(repo.revs('secret()'))
5005 if secret:
5004 if secret:
5006 t.append(_('%d secret') % secret)
5005 t.append(_('%d secret') % secret)
5007
5006
5008 if draft or secret:
5007 if draft or secret:
5009 ui.status(_('phases: %s\n') % ', '.join(t))
5008 ui.status(_('phases: %s\n') % ', '.join(t))
5010
5009
5011 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5010 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5012 for trouble in ("unstable", "divergent", "bumped"):
5011 for trouble in ("unstable", "divergent", "bumped"):
5013 numtrouble = len(repo.revs(trouble + "()"))
5012 numtrouble = len(repo.revs(trouble + "()"))
5014 # We write all the possibilities to ease translation
5013 # We write all the possibilities to ease translation
5015 troublemsg = {
5014 troublemsg = {
5016 "unstable": _("unstable: %d changesets"),
5015 "unstable": _("unstable: %d changesets"),
5017 "divergent": _("divergent: %d changesets"),
5016 "divergent": _("divergent: %d changesets"),
5018 "bumped": _("bumped: %d changesets"),
5017 "bumped": _("bumped: %d changesets"),
5019 }
5018 }
5020 if numtrouble > 0:
5019 if numtrouble > 0:
5021 ui.status(troublemsg[trouble] % numtrouble + "\n")
5020 ui.status(troublemsg[trouble] % numtrouble + "\n")
5022
5021
5023 cmdutil.summaryhooks(ui, repo)
5022 cmdutil.summaryhooks(ui, repo)
5024
5023
5025 if opts.get('remote'):
5024 if opts.get('remote'):
5026 needsincoming, needsoutgoing = True, True
5025 needsincoming, needsoutgoing = True, True
5027 else:
5026 else:
5028 needsincoming, needsoutgoing = False, False
5027 needsincoming, needsoutgoing = False, False
5029 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5028 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5030 if i:
5029 if i:
5031 needsincoming = True
5030 needsincoming = True
5032 if o:
5031 if o:
5033 needsoutgoing = True
5032 needsoutgoing = True
5034 if not needsincoming and not needsoutgoing:
5033 if not needsincoming and not needsoutgoing:
5035 return
5034 return
5036
5035
5037 def getincoming():
5036 def getincoming():
5038 source, branches = hg.parseurl(ui.expandpath('default'))
5037 source, branches = hg.parseurl(ui.expandpath('default'))
5039 sbranch = branches[0]
5038 sbranch = branches[0]
5040 try:
5039 try:
5041 other = hg.peer(repo, {}, source)
5040 other = hg.peer(repo, {}, source)
5042 except error.RepoError:
5041 except error.RepoError:
5043 if opts.get('remote'):
5042 if opts.get('remote'):
5044 raise
5043 raise
5045 return source, sbranch, None, None, None
5044 return source, sbranch, None, None, None
5046 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5045 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5047 if revs:
5046 if revs:
5048 revs = [other.lookup(rev) for rev in revs]
5047 revs = [other.lookup(rev) for rev in revs]
5049 ui.debug('comparing with %s\n' % util.hidepassword(source))
5048 ui.debug('comparing with %s\n' % util.hidepassword(source))
5050 repo.ui.pushbuffer()
5049 repo.ui.pushbuffer()
5051 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5050 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5052 repo.ui.popbuffer()
5051 repo.ui.popbuffer()
5053 return source, sbranch, other, commoninc, commoninc[1]
5052 return source, sbranch, other, commoninc, commoninc[1]
5054
5053
5055 if needsincoming:
5054 if needsincoming:
5056 source, sbranch, sother, commoninc, incoming = getincoming()
5055 source, sbranch, sother, commoninc, incoming = getincoming()
5057 else:
5056 else:
5058 source = sbranch = sother = commoninc = incoming = None
5057 source = sbranch = sother = commoninc = incoming = None
5059
5058
5060 def getoutgoing():
5059 def getoutgoing():
5061 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5060 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5062 dbranch = branches[0]
5061 dbranch = branches[0]
5063 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5062 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5064 if source != dest:
5063 if source != dest:
5065 try:
5064 try:
5066 dother = hg.peer(repo, {}, dest)
5065 dother = hg.peer(repo, {}, dest)
5067 except error.RepoError:
5066 except error.RepoError:
5068 if opts.get('remote'):
5067 if opts.get('remote'):
5069 raise
5068 raise
5070 return dest, dbranch, None, None
5069 return dest, dbranch, None, None
5071 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5070 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5072 elif sother is None:
5071 elif sother is None:
5073 # there is no explicit destination peer, but source one is invalid
5072 # there is no explicit destination peer, but source one is invalid
5074 return dest, dbranch, None, None
5073 return dest, dbranch, None, None
5075 else:
5074 else:
5076 dother = sother
5075 dother = sother
5077 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5076 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5078 common = None
5077 common = None
5079 else:
5078 else:
5080 common = commoninc
5079 common = commoninc
5081 if revs:
5080 if revs:
5082 revs = [repo.lookup(rev) for rev in revs]
5081 revs = [repo.lookup(rev) for rev in revs]
5083 repo.ui.pushbuffer()
5082 repo.ui.pushbuffer()
5084 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5083 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5085 commoninc=common)
5084 commoninc=common)
5086 repo.ui.popbuffer()
5085 repo.ui.popbuffer()
5087 return dest, dbranch, dother, outgoing
5086 return dest, dbranch, dother, outgoing
5088
5087
5089 if needsoutgoing:
5088 if needsoutgoing:
5090 dest, dbranch, dother, outgoing = getoutgoing()
5089 dest, dbranch, dother, outgoing = getoutgoing()
5091 else:
5090 else:
5092 dest = dbranch = dother = outgoing = None
5091 dest = dbranch = dother = outgoing = None
5093
5092
5094 if opts.get('remote'):
5093 if opts.get('remote'):
5095 t = []
5094 t = []
5096 if incoming:
5095 if incoming:
5097 t.append(_('1 or more incoming'))
5096 t.append(_('1 or more incoming'))
5098 o = outgoing.missing
5097 o = outgoing.missing
5099 if o:
5098 if o:
5100 t.append(_('%d outgoing') % len(o))
5099 t.append(_('%d outgoing') % len(o))
5101 other = dother or sother
5100 other = dother or sother
5102 if 'bookmarks' in other.listkeys('namespaces'):
5101 if 'bookmarks' in other.listkeys('namespaces'):
5103 counts = bookmarks.summary(repo, other)
5102 counts = bookmarks.summary(repo, other)
5104 if counts[0] > 0:
5103 if counts[0] > 0:
5105 t.append(_('%d incoming bookmarks') % counts[0])
5104 t.append(_('%d incoming bookmarks') % counts[0])
5106 if counts[1] > 0:
5105 if counts[1] > 0:
5107 t.append(_('%d outgoing bookmarks') % counts[1])
5106 t.append(_('%d outgoing bookmarks') % counts[1])
5108
5107
5109 if t:
5108 if t:
5110 # i18n: column positioning for "hg summary"
5109 # i18n: column positioning for "hg summary"
5111 ui.write(_('remote: %s\n') % (', '.join(t)))
5110 ui.write(_('remote: %s\n') % (', '.join(t)))
5112 else:
5111 else:
5113 # i18n: column positioning for "hg summary"
5112 # i18n: column positioning for "hg summary"
5114 ui.status(_('remote: (synced)\n'))
5113 ui.status(_('remote: (synced)\n'))
5115
5114
5116 cmdutil.summaryremotehooks(ui, repo, opts,
5115 cmdutil.summaryremotehooks(ui, repo, opts,
5117 ((source, sbranch, sother, commoninc),
5116 ((source, sbranch, sother, commoninc),
5118 (dest, dbranch, dother, outgoing)))
5117 (dest, dbranch, dother, outgoing)))
5119
5118
5120 @command('tag',
5119 @command('tag',
5121 [('f', 'force', None, _('force tag')),
5120 [('f', 'force', None, _('force tag')),
5122 ('l', 'local', None, _('make the tag local')),
5121 ('l', 'local', None, _('make the tag local')),
5123 ('r', 'rev', '', _('revision to tag'), _('REV')),
5122 ('r', 'rev', '', _('revision to tag'), _('REV')),
5124 ('', 'remove', None, _('remove a tag')),
5123 ('', 'remove', None, _('remove a tag')),
5125 # -l/--local is already there, commitopts cannot be used
5124 # -l/--local is already there, commitopts cannot be used
5126 ('e', 'edit', None, _('invoke editor on commit messages')),
5125 ('e', 'edit', None, _('invoke editor on commit messages')),
5127 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5126 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5128 ] + commitopts2,
5127 ] + commitopts2,
5129 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5128 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5130 def tag(ui, repo, name1, *names, **opts):
5129 def tag(ui, repo, name1, *names, **opts):
5131 """add one or more tags for the current or given revision
5130 """add one or more tags for the current or given revision
5132
5131
5133 Name a particular revision using <name>.
5132 Name a particular revision using <name>.
5134
5133
5135 Tags are used to name particular revisions of the repository and are
5134 Tags are used to name particular revisions of the repository and are
5136 very useful to compare different revisions, to go back to significant
5135 very useful to compare different revisions, to go back to significant
5137 earlier versions or to mark branch points as releases, etc. Changing
5136 earlier versions or to mark branch points as releases, etc. Changing
5138 an existing tag is normally disallowed; use -f/--force to override.
5137 an existing tag is normally disallowed; use -f/--force to override.
5139
5138
5140 If no revision is given, the parent of the working directory is
5139 If no revision is given, the parent of the working directory is
5141 used.
5140 used.
5142
5141
5143 To facilitate version control, distribution, and merging of tags,
5142 To facilitate version control, distribution, and merging of tags,
5144 they are stored as a file named ".hgtags" which is managed similarly
5143 they are stored as a file named ".hgtags" which is managed similarly
5145 to other project files and can be hand-edited if necessary. This
5144 to other project files and can be hand-edited if necessary. This
5146 also means that tagging creates a new commit. The file
5145 also means that tagging creates a new commit. The file
5147 ".hg/localtags" is used for local tags (not shared among
5146 ".hg/localtags" is used for local tags (not shared among
5148 repositories).
5147 repositories).
5149
5148
5150 Tag commits are usually made at the head of a branch. If the parent
5149 Tag commits are usually made at the head of a branch. If the parent
5151 of the working directory is not a branch head, :hg:`tag` aborts; use
5150 of the working directory is not a branch head, :hg:`tag` aborts; use
5152 -f/--force to force the tag commit to be based on a non-head
5151 -f/--force to force the tag commit to be based on a non-head
5153 changeset.
5152 changeset.
5154
5153
5155 See :hg:`help dates` for a list of formats valid for -d/--date.
5154 See :hg:`help dates` for a list of formats valid for -d/--date.
5156
5155
5157 Since tag names have priority over branch names during revision
5156 Since tag names have priority over branch names during revision
5158 lookup, using an existing branch name as a tag name is discouraged.
5157 lookup, using an existing branch name as a tag name is discouraged.
5159
5158
5160 Returns 0 on success.
5159 Returns 0 on success.
5161 """
5160 """
5162 opts = pycompat.byteskwargs(opts)
5161 opts = pycompat.byteskwargs(opts)
5163 wlock = lock = None
5162 wlock = lock = None
5164 try:
5163 try:
5165 wlock = repo.wlock()
5164 wlock = repo.wlock()
5166 lock = repo.lock()
5165 lock = repo.lock()
5167 rev_ = "."
5166 rev_ = "."
5168 names = [t.strip() for t in (name1,) + names]
5167 names = [t.strip() for t in (name1,) + names]
5169 if len(names) != len(set(names)):
5168 if len(names) != len(set(names)):
5170 raise error.Abort(_('tag names must be unique'))
5169 raise error.Abort(_('tag names must be unique'))
5171 for n in names:
5170 for n in names:
5172 scmutil.checknewlabel(repo, n, 'tag')
5171 scmutil.checknewlabel(repo, n, 'tag')
5173 if not n:
5172 if not n:
5174 raise error.Abort(_('tag names cannot consist entirely of '
5173 raise error.Abort(_('tag names cannot consist entirely of '
5175 'whitespace'))
5174 'whitespace'))
5176 if opts.get('rev') and opts.get('remove'):
5175 if opts.get('rev') and opts.get('remove'):
5177 raise error.Abort(_("--rev and --remove are incompatible"))
5176 raise error.Abort(_("--rev and --remove are incompatible"))
5178 if opts.get('rev'):
5177 if opts.get('rev'):
5179 rev_ = opts['rev']
5178 rev_ = opts['rev']
5180 message = opts.get('message')
5179 message = opts.get('message')
5181 if opts.get('remove'):
5180 if opts.get('remove'):
5182 if opts.get('local'):
5181 if opts.get('local'):
5183 expectedtype = 'local'
5182 expectedtype = 'local'
5184 else:
5183 else:
5185 expectedtype = 'global'
5184 expectedtype = 'global'
5186
5185
5187 for n in names:
5186 for n in names:
5188 if not repo.tagtype(n):
5187 if not repo.tagtype(n):
5189 raise error.Abort(_("tag '%s' does not exist") % n)
5188 raise error.Abort(_("tag '%s' does not exist") % n)
5190 if repo.tagtype(n) != expectedtype:
5189 if repo.tagtype(n) != expectedtype:
5191 if expectedtype == 'global':
5190 if expectedtype == 'global':
5192 raise error.Abort(_("tag '%s' is not a global tag") % n)
5191 raise error.Abort(_("tag '%s' is not a global tag") % n)
5193 else:
5192 else:
5194 raise error.Abort(_("tag '%s' is not a local tag") % n)
5193 raise error.Abort(_("tag '%s' is not a local tag") % n)
5195 rev_ = 'null'
5194 rev_ = 'null'
5196 if not message:
5195 if not message:
5197 # we don't translate commit messages
5196 # we don't translate commit messages
5198 message = 'Removed tag %s' % ', '.join(names)
5197 message = 'Removed tag %s' % ', '.join(names)
5199 elif not opts.get('force'):
5198 elif not opts.get('force'):
5200 for n in names:
5199 for n in names:
5201 if n in repo.tags():
5200 if n in repo.tags():
5202 raise error.Abort(_("tag '%s' already exists "
5201 raise error.Abort(_("tag '%s' already exists "
5203 "(use -f to force)") % n)
5202 "(use -f to force)") % n)
5204 if not opts.get('local'):
5203 if not opts.get('local'):
5205 p1, p2 = repo.dirstate.parents()
5204 p1, p2 = repo.dirstate.parents()
5206 if p2 != nullid:
5205 if p2 != nullid:
5207 raise error.Abort(_('uncommitted merge'))
5206 raise error.Abort(_('uncommitted merge'))
5208 bheads = repo.branchheads()
5207 bheads = repo.branchheads()
5209 if not opts.get('force') and bheads and p1 not in bheads:
5208 if not opts.get('force') and bheads and p1 not in bheads:
5210 raise error.Abort(_('working directory is not at a branch head '
5209 raise error.Abort(_('working directory is not at a branch head '
5211 '(use -f to force)'))
5210 '(use -f to force)'))
5212 r = scmutil.revsingle(repo, rev_).node()
5211 r = scmutil.revsingle(repo, rev_).node()
5213
5212
5214 if not message:
5213 if not message:
5215 # we don't translate commit messages
5214 # we don't translate commit messages
5216 message = ('Added tag %s for changeset %s' %
5215 message = ('Added tag %s for changeset %s' %
5217 (', '.join(names), short(r)))
5216 (', '.join(names), short(r)))
5218
5217
5219 date = opts.get('date')
5218 date = opts.get('date')
5220 if date:
5219 if date:
5221 date = util.parsedate(date)
5220 date = util.parsedate(date)
5222
5221
5223 if opts.get('remove'):
5222 if opts.get('remove'):
5224 editform = 'tag.remove'
5223 editform = 'tag.remove'
5225 else:
5224 else:
5226 editform = 'tag.add'
5225 editform = 'tag.add'
5227 editor = cmdutil.getcommiteditor(editform=editform, **opts)
5226 editor = cmdutil.getcommiteditor(editform=editform, **opts)
5228
5227
5229 # don't allow tagging the null rev
5228 # don't allow tagging the null rev
5230 if (not opts.get('remove') and
5229 if (not opts.get('remove') and
5231 scmutil.revsingle(repo, rev_).rev() == nullrev):
5230 scmutil.revsingle(repo, rev_).rev() == nullrev):
5232 raise error.Abort(_("cannot tag null revision"))
5231 raise error.Abort(_("cannot tag null revision"))
5233
5232
5234 tagsmod.tag(repo, names, r, message, opts.get('local'),
5233 tagsmod.tag(repo, names, r, message, opts.get('local'),
5235 opts.get('user'), date, editor=editor)
5234 opts.get('user'), date, editor=editor)
5236 finally:
5235 finally:
5237 release(lock, wlock)
5236 release(lock, wlock)
5238
5237
5239 @command('tags', formatteropts, '')
5238 @command('tags', formatteropts, '')
5240 def tags(ui, repo, **opts):
5239 def tags(ui, repo, **opts):
5241 """list repository tags
5240 """list repository tags
5242
5241
5243 This lists both regular and local tags. When the -v/--verbose
5242 This lists both regular and local tags. When the -v/--verbose
5244 switch is used, a third column "local" is printed for local tags.
5243 switch is used, a third column "local" is printed for local tags.
5245 When the -q/--quiet switch is used, only the tag name is printed.
5244 When the -q/--quiet switch is used, only the tag name is printed.
5246
5245
5247 Returns 0 on success.
5246 Returns 0 on success.
5248 """
5247 """
5249
5248
5250 opts = pycompat.byteskwargs(opts)
5249 opts = pycompat.byteskwargs(opts)
5251 ui.pager('tags')
5250 ui.pager('tags')
5252 fm = ui.formatter('tags', opts)
5251 fm = ui.formatter('tags', opts)
5253 hexfunc = fm.hexfunc
5252 hexfunc = fm.hexfunc
5254 tagtype = ""
5253 tagtype = ""
5255
5254
5256 for t, n in reversed(repo.tagslist()):
5255 for t, n in reversed(repo.tagslist()):
5257 hn = hexfunc(n)
5256 hn = hexfunc(n)
5258 label = 'tags.normal'
5257 label = 'tags.normal'
5259 tagtype = ''
5258 tagtype = ''
5260 if repo.tagtype(t) == 'local':
5259 if repo.tagtype(t) == 'local':
5261 label = 'tags.local'
5260 label = 'tags.local'
5262 tagtype = 'local'
5261 tagtype = 'local'
5263
5262
5264 fm.startitem()
5263 fm.startitem()
5265 fm.write('tag', '%s', t, label=label)
5264 fm.write('tag', '%s', t, label=label)
5266 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5265 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5267 fm.condwrite(not ui.quiet, 'rev node', fmt,
5266 fm.condwrite(not ui.quiet, 'rev node', fmt,
5268 repo.changelog.rev(n), hn, label=label)
5267 repo.changelog.rev(n), hn, label=label)
5269 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5268 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5270 tagtype, label=label)
5269 tagtype, label=label)
5271 fm.plain('\n')
5270 fm.plain('\n')
5272 fm.end()
5271 fm.end()
5273
5272
5274 @command('tip',
5273 @command('tip',
5275 [('p', 'patch', None, _('show patch')),
5274 [('p', 'patch', None, _('show patch')),
5276 ('g', 'git', None, _('use git extended diff format')),
5275 ('g', 'git', None, _('use git extended diff format')),
5277 ] + templateopts,
5276 ] + templateopts,
5278 _('[-p] [-g]'))
5277 _('[-p] [-g]'))
5279 def tip(ui, repo, **opts):
5278 def tip(ui, repo, **opts):
5280 """show the tip revision (DEPRECATED)
5279 """show the tip revision (DEPRECATED)
5281
5280
5282 The tip revision (usually just called the tip) is the changeset
5281 The tip revision (usually just called the tip) is the changeset
5283 most recently added to the repository (and therefore the most
5282 most recently added to the repository (and therefore the most
5284 recently changed head).
5283 recently changed head).
5285
5284
5286 If you have just made a commit, that commit will be the tip. If
5285 If you have just made a commit, that commit will be the tip. If
5287 you have just pulled changes from another repository, the tip of
5286 you have just pulled changes from another repository, the tip of
5288 that repository becomes the current tip. The "tip" tag is special
5287 that repository becomes the current tip. The "tip" tag is special
5289 and cannot be renamed or assigned to a different changeset.
5288 and cannot be renamed or assigned to a different changeset.
5290
5289
5291 This command is deprecated, please use :hg:`heads` instead.
5290 This command is deprecated, please use :hg:`heads` instead.
5292
5291
5293 Returns 0 on success.
5292 Returns 0 on success.
5294 """
5293 """
5295 opts = pycompat.byteskwargs(opts)
5294 opts = pycompat.byteskwargs(opts)
5296 displayer = cmdutil.show_changeset(ui, repo, opts)
5295 displayer = cmdutil.show_changeset(ui, repo, opts)
5297 displayer.show(repo['tip'])
5296 displayer.show(repo['tip'])
5298 displayer.close()
5297 displayer.close()
5299
5298
5300 @command('unbundle',
5299 @command('unbundle',
5301 [('u', 'update', None,
5300 [('u', 'update', None,
5302 _('update to new branch head if changesets were unbundled'))],
5301 _('update to new branch head if changesets were unbundled'))],
5303 _('[-u] FILE...'))
5302 _('[-u] FILE...'))
5304 def unbundle(ui, repo, fname1, *fnames, **opts):
5303 def unbundle(ui, repo, fname1, *fnames, **opts):
5305 """apply one or more bundle files
5304 """apply one or more bundle files
5306
5305
5307 Apply one or more bundle files generated by :hg:`bundle`.
5306 Apply one or more bundle files generated by :hg:`bundle`.
5308
5307
5309 Returns 0 on success, 1 if an update has unresolved files.
5308 Returns 0 on success, 1 if an update has unresolved files.
5310 """
5309 """
5311 fnames = (fname1,) + fnames
5310 fnames = (fname1,) + fnames
5312
5311
5313 with repo.lock():
5312 with repo.lock():
5314 for fname in fnames:
5313 for fname in fnames:
5315 f = hg.openpath(ui, fname)
5314 f = hg.openpath(ui, fname)
5316 gen = exchange.readbundle(ui, f, fname)
5315 gen = exchange.readbundle(ui, f, fname)
5317 if isinstance(gen, bundle2.unbundle20):
5316 if isinstance(gen, bundle2.unbundle20):
5318 tr = repo.transaction('unbundle')
5317 tr = repo.transaction('unbundle')
5319 try:
5318 try:
5320 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5319 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5321 url='bundle:' + fname)
5320 url='bundle:' + fname)
5322 tr.close()
5321 tr.close()
5323 except error.BundleUnknownFeatureError as exc:
5322 except error.BundleUnknownFeatureError as exc:
5324 raise error.Abort(_('%s: unknown bundle feature, %s')
5323 raise error.Abort(_('%s: unknown bundle feature, %s')
5325 % (fname, exc),
5324 % (fname, exc),
5326 hint=_("see https://mercurial-scm.org/"
5325 hint=_("see https://mercurial-scm.org/"
5327 "wiki/BundleFeature for more "
5326 "wiki/BundleFeature for more "
5328 "information"))
5327 "information"))
5329 finally:
5328 finally:
5330 if tr:
5329 if tr:
5331 tr.release()
5330 tr.release()
5332 changes = [r.get('return', 0)
5331 changes = [r.get('return', 0)
5333 for r in op.records['changegroup']]
5332 for r in op.records['changegroup']]
5334 modheads = changegroup.combineresults(changes)
5333 modheads = changegroup.combineresults(changes)
5335 elif isinstance(gen, streamclone.streamcloneapplier):
5334 elif isinstance(gen, streamclone.streamcloneapplier):
5336 raise error.Abort(
5335 raise error.Abort(
5337 _('packed bundles cannot be applied with '
5336 _('packed bundles cannot be applied with '
5338 '"hg unbundle"'),
5337 '"hg unbundle"'),
5339 hint=_('use "hg debugapplystreamclonebundle"'))
5338 hint=_('use "hg debugapplystreamclonebundle"'))
5340 else:
5339 else:
5341 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5340 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5342
5341
5343 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5342 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5344
5343
5345 @command('^update|up|checkout|co',
5344 @command('^update|up|checkout|co',
5346 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5345 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5347 ('c', 'check', None, _('require clean working directory')),
5346 ('c', 'check', None, _('require clean working directory')),
5348 ('m', 'merge', None, _('merge uncommitted changes')),
5347 ('m', 'merge', None, _('merge uncommitted changes')),
5349 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5348 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5350 ('r', 'rev', '', _('revision'), _('REV'))
5349 ('r', 'rev', '', _('revision'), _('REV'))
5351 ] + mergetoolopts,
5350 ] + mergetoolopts,
5352 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5351 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5353 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5352 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5354 merge=None, tool=None):
5353 merge=None, tool=None):
5355 """update working directory (or switch revisions)
5354 """update working directory (or switch revisions)
5356
5355
5357 Update the repository's working directory to the specified
5356 Update the repository's working directory to the specified
5358 changeset. If no changeset is specified, update to the tip of the
5357 changeset. If no changeset is specified, update to the tip of the
5359 current named branch and move the active bookmark (see :hg:`help
5358 current named branch and move the active bookmark (see :hg:`help
5360 bookmarks`).
5359 bookmarks`).
5361
5360
5362 Update sets the working directory's parent revision to the specified
5361 Update sets the working directory's parent revision to the specified
5363 changeset (see :hg:`help parents`).
5362 changeset (see :hg:`help parents`).
5364
5363
5365 If the changeset is not a descendant or ancestor of the working
5364 If the changeset is not a descendant or ancestor of the working
5366 directory's parent and there are uncommitted changes, the update is
5365 directory's parent and there are uncommitted changes, the update is
5367 aborted. With the -c/--check option, the working directory is checked
5366 aborted. With the -c/--check option, the working directory is checked
5368 for uncommitted changes; if none are found, the working directory is
5367 for uncommitted changes; if none are found, the working directory is
5369 updated to the specified changeset.
5368 updated to the specified changeset.
5370
5369
5371 .. container:: verbose
5370 .. container:: verbose
5372
5371
5373 The -C/--clean, -c/--check, and -m/--merge options control what
5372 The -C/--clean, -c/--check, and -m/--merge options control what
5374 happens if the working directory contains uncommitted changes.
5373 happens if the working directory contains uncommitted changes.
5375 At most of one of them can be specified.
5374 At most of one of them can be specified.
5376
5375
5377 1. If no option is specified, and if
5376 1. If no option is specified, and if
5378 the requested changeset is an ancestor or descendant of
5377 the requested changeset is an ancestor or descendant of
5379 the working directory's parent, the uncommitted changes
5378 the working directory's parent, the uncommitted changes
5380 are merged into the requested changeset and the merged
5379 are merged into the requested changeset and the merged
5381 result is left uncommitted. If the requested changeset is
5380 result is left uncommitted. If the requested changeset is
5382 not an ancestor or descendant (that is, it is on another
5381 not an ancestor or descendant (that is, it is on another
5383 branch), the update is aborted and the uncommitted changes
5382 branch), the update is aborted and the uncommitted changes
5384 are preserved.
5383 are preserved.
5385
5384
5386 2. With the -m/--merge option, the update is allowed even if the
5385 2. With the -m/--merge option, the update is allowed even if the
5387 requested changeset is not an ancestor or descendant of
5386 requested changeset is not an ancestor or descendant of
5388 the working directory's parent.
5387 the working directory's parent.
5389
5388
5390 3. With the -c/--check option, the update is aborted and the
5389 3. With the -c/--check option, the update is aborted and the
5391 uncommitted changes are preserved.
5390 uncommitted changes are preserved.
5392
5391
5393 4. With the -C/--clean option, uncommitted changes are discarded and
5392 4. With the -C/--clean option, uncommitted changes are discarded and
5394 the working directory is updated to the requested changeset.
5393 the working directory is updated to the requested changeset.
5395
5394
5396 To cancel an uncommitted merge (and lose your changes), use
5395 To cancel an uncommitted merge (and lose your changes), use
5397 :hg:`update --clean .`.
5396 :hg:`update --clean .`.
5398
5397
5399 Use null as the changeset to remove the working directory (like
5398 Use null as the changeset to remove the working directory (like
5400 :hg:`clone -U`).
5399 :hg:`clone -U`).
5401
5400
5402 If you want to revert just one file to an older revision, use
5401 If you want to revert just one file to an older revision, use
5403 :hg:`revert [-r REV] NAME`.
5402 :hg:`revert [-r REV] NAME`.
5404
5403
5405 See :hg:`help dates` for a list of formats valid for -d/--date.
5404 See :hg:`help dates` for a list of formats valid for -d/--date.
5406
5405
5407 Returns 0 on success, 1 if there are unresolved files.
5406 Returns 0 on success, 1 if there are unresolved files.
5408 """
5407 """
5409 if rev and node:
5408 if rev and node:
5410 raise error.Abort(_("please specify just one revision"))
5409 raise error.Abort(_("please specify just one revision"))
5411
5410
5412 if ui.configbool('commands', 'update.requiredest'):
5411 if ui.configbool('commands', 'update.requiredest'):
5413 if not node and not rev and not date:
5412 if not node and not rev and not date:
5414 raise error.Abort(_('you must specify a destination'),
5413 raise error.Abort(_('you must specify a destination'),
5415 hint=_('for example: hg update ".::"'))
5414 hint=_('for example: hg update ".::"'))
5416
5415
5417 if rev is None or rev == '':
5416 if rev is None or rev == '':
5418 rev = node
5417 rev = node
5419
5418
5420 if date and rev is not None:
5419 if date and rev is not None:
5421 raise error.Abort(_("you can't specify a revision and a date"))
5420 raise error.Abort(_("you can't specify a revision and a date"))
5422
5421
5423 if len([x for x in (clean, check, merge) if x]) > 1:
5422 if len([x for x in (clean, check, merge) if x]) > 1:
5424 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5423 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5425 "or -m/merge"))
5424 "or -m/merge"))
5426
5425
5427 updatecheck = None
5426 updatecheck = None
5428 if check:
5427 if check:
5429 updatecheck = 'abort'
5428 updatecheck = 'abort'
5430 elif merge:
5429 elif merge:
5431 updatecheck = 'none'
5430 updatecheck = 'none'
5432
5431
5433 with repo.wlock():
5432 with repo.wlock():
5434 cmdutil.clearunfinished(repo)
5433 cmdutil.clearunfinished(repo)
5435
5434
5436 if date:
5435 if date:
5437 rev = cmdutil.finddate(ui, repo, date)
5436 rev = cmdutil.finddate(ui, repo, date)
5438
5437
5439 # if we defined a bookmark, we have to remember the original name
5438 # if we defined a bookmark, we have to remember the original name
5440 brev = rev
5439 brev = rev
5441 rev = scmutil.revsingle(repo, rev, rev).rev()
5440 rev = scmutil.revsingle(repo, rev, rev).rev()
5442
5441
5443 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5442 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5444
5443
5445 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5444 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5446 updatecheck=updatecheck)
5445 updatecheck=updatecheck)
5447
5446
5448 @command('verify', [])
5447 @command('verify', [])
5449 def verify(ui, repo):
5448 def verify(ui, repo):
5450 """verify the integrity of the repository
5449 """verify the integrity of the repository
5451
5450
5452 Verify the integrity of the current repository.
5451 Verify the integrity of the current repository.
5453
5452
5454 This will perform an extensive check of the repository's
5453 This will perform an extensive check of the repository's
5455 integrity, validating the hashes and checksums of each entry in
5454 integrity, validating the hashes and checksums of each entry in
5456 the changelog, manifest, and tracked files, as well as the
5455 the changelog, manifest, and tracked files, as well as the
5457 integrity of their crosslinks and indices.
5456 integrity of their crosslinks and indices.
5458
5457
5459 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5458 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5460 for more information about recovery from corruption of the
5459 for more information about recovery from corruption of the
5461 repository.
5460 repository.
5462
5461
5463 Returns 0 on success, 1 if errors are encountered.
5462 Returns 0 on success, 1 if errors are encountered.
5464 """
5463 """
5465 return hg.verify(repo)
5464 return hg.verify(repo)
5466
5465
5467 @command('version', [] + formatteropts, norepo=True)
5466 @command('version', [] + formatteropts, norepo=True)
5468 def version_(ui, **opts):
5467 def version_(ui, **opts):
5469 """output version and copyright information"""
5468 """output version and copyright information"""
5470 opts = pycompat.byteskwargs(opts)
5469 opts = pycompat.byteskwargs(opts)
5471 if ui.verbose:
5470 if ui.verbose:
5472 ui.pager('version')
5471 ui.pager('version')
5473 fm = ui.formatter("version", opts)
5472 fm = ui.formatter("version", opts)
5474 fm.startitem()
5473 fm.startitem()
5475 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5474 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5476 util.version())
5475 util.version())
5477 license = _(
5476 license = _(
5478 "(see https://mercurial-scm.org for more information)\n"
5477 "(see https://mercurial-scm.org for more information)\n"
5479 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5478 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5480 "This is free software; see the source for copying conditions. "
5479 "This is free software; see the source for copying conditions. "
5481 "There is NO\nwarranty; "
5480 "There is NO\nwarranty; "
5482 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5481 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5483 )
5482 )
5484 if not ui.quiet:
5483 if not ui.quiet:
5485 fm.plain(license)
5484 fm.plain(license)
5486
5485
5487 if ui.verbose:
5486 if ui.verbose:
5488 fm.plain(_("\nEnabled extensions:\n\n"))
5487 fm.plain(_("\nEnabled extensions:\n\n"))
5489 # format names and versions into columns
5488 # format names and versions into columns
5490 names = []
5489 names = []
5491 vers = []
5490 vers = []
5492 isinternals = []
5491 isinternals = []
5493 for name, module in extensions.extensions():
5492 for name, module in extensions.extensions():
5494 names.append(name)
5493 names.append(name)
5495 vers.append(extensions.moduleversion(module) or None)
5494 vers.append(extensions.moduleversion(module) or None)
5496 isinternals.append(extensions.ismoduleinternal(module))
5495 isinternals.append(extensions.ismoduleinternal(module))
5497 fn = fm.nested("extensions")
5496 fn = fm.nested("extensions")
5498 if names:
5497 if names:
5499 namefmt = " %%-%ds " % max(len(n) for n in names)
5498 namefmt = " %%-%ds " % max(len(n) for n in names)
5500 places = [_("external"), _("internal")]
5499 places = [_("external"), _("internal")]
5501 for n, v, p in zip(names, vers, isinternals):
5500 for n, v, p in zip(names, vers, isinternals):
5502 fn.startitem()
5501 fn.startitem()
5503 fn.condwrite(ui.verbose, "name", namefmt, n)
5502 fn.condwrite(ui.verbose, "name", namefmt, n)
5504 if ui.verbose:
5503 if ui.verbose:
5505 fn.plain("%s " % places[p])
5504 fn.plain("%s " % places[p])
5506 fn.data(bundled=p)
5505 fn.data(bundled=p)
5507 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5506 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5508 if ui.verbose:
5507 if ui.verbose:
5509 fn.plain("\n")
5508 fn.plain("\n")
5510 fn.end()
5509 fn.end()
5511 fm.end()
5510 fm.end()
5512
5511
5513 def loadcmdtable(ui, name, cmdtable):
5512 def loadcmdtable(ui, name, cmdtable):
5514 """Load command functions from specified cmdtable
5513 """Load command functions from specified cmdtable
5515 """
5514 """
5516 overrides = [cmd for cmd in cmdtable if cmd in table]
5515 overrides = [cmd for cmd in cmdtable if cmd in table]
5517 if overrides:
5516 if overrides:
5518 ui.warn(_("extension '%s' overrides commands: %s\n")
5517 ui.warn(_("extension '%s' overrides commands: %s\n")
5519 % (name, " ".join(overrides)))
5518 % (name, " ".join(overrides)))
5520 table.update(cmdtable)
5519 table.update(cmdtable)
@@ -1,1987 +1,1987 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy
10 import copy
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import posixpath
14 import posixpath
15 import re
15 import re
16 import stat
16 import stat
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import tarfile
19 import tarfile
20 import xml.dom.minidom
20 import xml.dom.minidom
21
21
22
22
23 from .i18n import _
23 from .i18n import _
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 config,
26 config,
27 encoding,
27 encoding,
28 error,
28 error,
29 exchange,
29 exchange,
30 filemerge,
30 filemerge,
31 match as matchmod,
31 match as matchmod,
32 node,
32 node,
33 pathutil,
33 pathutil,
34 phases,
34 phases,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 vfs as vfsmod,
38 vfs as vfsmod,
39 )
39 )
40
40
41 hg = None
41 hg = None
42 propertycache = util.propertycache
42 propertycache = util.propertycache
43
43
44 nullstate = ('', '', 'empty')
44 nullstate = ('', '', 'empty')
45
45
46 def _expandedabspath(path):
46 def _expandedabspath(path):
47 '''
47 '''
48 get a path or url and if it is a path expand it and return an absolute path
48 get a path or url and if it is a path expand it and return an absolute path
49 '''
49 '''
50 expandedpath = util.urllocalpath(util.expandpath(path))
50 expandedpath = util.urllocalpath(util.expandpath(path))
51 u = util.url(expandedpath)
51 u = util.url(expandedpath)
52 if not u.scheme:
52 if not u.scheme:
53 path = util.normpath(os.path.abspath(u.path))
53 path = util.normpath(os.path.abspath(u.path))
54 return path
54 return path
55
55
56 def _getstorehashcachename(remotepath):
56 def _getstorehashcachename(remotepath):
57 '''get a unique filename for the store hash cache of a remote repository'''
57 '''get a unique filename for the store hash cache of a remote repository'''
58 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
58 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
59
59
60 class SubrepoAbort(error.Abort):
60 class SubrepoAbort(error.Abort):
61 """Exception class used to avoid handling a subrepo error more than once"""
61 """Exception class used to avoid handling a subrepo error more than once"""
62 def __init__(self, *args, **kw):
62 def __init__(self, *args, **kw):
63 self.subrepo = kw.pop('subrepo', None)
63 self.subrepo = kw.pop('subrepo', None)
64 self.cause = kw.pop('cause', None)
64 self.cause = kw.pop('cause', None)
65 error.Abort.__init__(self, *args, **kw)
65 error.Abort.__init__(self, *args, **kw)
66
66
67 def annotatesubrepoerror(func):
67 def annotatesubrepoerror(func):
68 def decoratedmethod(self, *args, **kargs):
68 def decoratedmethod(self, *args, **kargs):
69 try:
69 try:
70 res = func(self, *args, **kargs)
70 res = func(self, *args, **kargs)
71 except SubrepoAbort as ex:
71 except SubrepoAbort as ex:
72 # This exception has already been handled
72 # This exception has already been handled
73 raise ex
73 raise ex
74 except error.Abort as ex:
74 except error.Abort as ex:
75 subrepo = subrelpath(self)
75 subrepo = subrelpath(self)
76 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
76 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
77 # avoid handling this exception by raising a SubrepoAbort exception
77 # avoid handling this exception by raising a SubrepoAbort exception
78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
79 cause=sys.exc_info())
79 cause=sys.exc_info())
80 return res
80 return res
81 return decoratedmethod
81 return decoratedmethod
82
82
83 def state(ctx, ui):
83 def state(ctx, ui):
84 """return a state dict, mapping subrepo paths configured in .hgsub
84 """return a state dict, mapping subrepo paths configured in .hgsub
85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
86 (key in types dict))
86 (key in types dict))
87 """
87 """
88 p = config.config()
88 p = config.config()
89 repo = ctx.repo()
89 repo = ctx.repo()
90 def read(f, sections=None, remap=None):
90 def read(f, sections=None, remap=None):
91 if f in ctx:
91 if f in ctx:
92 try:
92 try:
93 data = ctx[f].data()
93 data = ctx[f].data()
94 except IOError as err:
94 except IOError as err:
95 if err.errno != errno.ENOENT:
95 if err.errno != errno.ENOENT:
96 raise
96 raise
97 # handle missing subrepo spec files as removed
97 # handle missing subrepo spec files as removed
98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
99 repo.pathto(f))
99 repo.pathto(f))
100 return
100 return
101 p.parse(f, data, sections, remap, read)
101 p.parse(f, data, sections, remap, read)
102 else:
102 else:
103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
104 repo.pathto(f))
104 repo.pathto(f))
105 if '.hgsub' in ctx:
105 if '.hgsub' in ctx:
106 read('.hgsub')
106 read('.hgsub')
107
107
108 for path, src in ui.configitems('subpaths'):
108 for path, src in ui.configitems('subpaths'):
109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
110
110
111 rev = {}
111 rev = {}
112 if '.hgsubstate' in ctx:
112 if '.hgsubstate' in ctx:
113 try:
113 try:
114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
115 l = l.lstrip()
115 l = l.lstrip()
116 if not l:
116 if not l:
117 continue
117 continue
118 try:
118 try:
119 revision, path = l.split(" ", 1)
119 revision, path = l.split(" ", 1)
120 except ValueError:
120 except ValueError:
121 raise error.Abort(_("invalid subrepository revision "
121 raise error.Abort(_("invalid subrepository revision "
122 "specifier in \'%s\' line %d")
122 "specifier in \'%s\' line %d")
123 % (repo.pathto('.hgsubstate'), (i + 1)))
123 % (repo.pathto('.hgsubstate'), (i + 1)))
124 rev[path] = revision
124 rev[path] = revision
125 except IOError as err:
125 except IOError as err:
126 if err.errno != errno.ENOENT:
126 if err.errno != errno.ENOENT:
127 raise
127 raise
128
128
129 def remap(src):
129 def remap(src):
130 for pattern, repl in p.items('subpaths'):
130 for pattern, repl in p.items('subpaths'):
131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
132 # does a string decode.
132 # does a string decode.
133 repl = util.escapestr(repl)
133 repl = util.escapestr(repl)
134 # However, we still want to allow back references to go
134 # However, we still want to allow back references to go
135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
136 # extra escapes are needed because re.sub string decodes.
136 # extra escapes are needed because re.sub string decodes.
137 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
137 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
138 try:
138 try:
139 src = re.sub(pattern, repl, src, 1)
139 src = re.sub(pattern, repl, src, 1)
140 except re.error as e:
140 except re.error as e:
141 raise error.Abort(_("bad subrepository pattern in %s: %s")
141 raise error.Abort(_("bad subrepository pattern in %s: %s")
142 % (p.source('subpaths', pattern), e))
142 % (p.source('subpaths', pattern), e))
143 return src
143 return src
144
144
145 state = {}
145 state = {}
146 for path, src in p[''].items():
146 for path, src in p[''].items():
147 kind = 'hg'
147 kind = 'hg'
148 if src.startswith('['):
148 if src.startswith('['):
149 if ']' not in src:
149 if ']' not in src:
150 raise error.Abort(_('missing ] in subrepo source'))
150 raise error.Abort(_('missing ] in subrepo source'))
151 kind, src = src.split(']', 1)
151 kind, src = src.split(']', 1)
152 kind = kind[1:]
152 kind = kind[1:]
153 src = src.lstrip() # strip any extra whitespace after ']'
153 src = src.lstrip() # strip any extra whitespace after ']'
154
154
155 if not util.url(src).isabs():
155 if not util.url(src).isabs():
156 parent = _abssource(repo, abort=False)
156 parent = _abssource(repo, abort=False)
157 if parent:
157 if parent:
158 parent = util.url(parent)
158 parent = util.url(parent)
159 parent.path = posixpath.join(parent.path or '', src)
159 parent.path = posixpath.join(parent.path or '', src)
160 parent.path = posixpath.normpath(parent.path)
160 parent.path = posixpath.normpath(parent.path)
161 joined = str(parent)
161 joined = str(parent)
162 # Remap the full joined path and use it if it changes,
162 # Remap the full joined path and use it if it changes,
163 # else remap the original source.
163 # else remap the original source.
164 remapped = remap(joined)
164 remapped = remap(joined)
165 if remapped == joined:
165 if remapped == joined:
166 src = remap(src)
166 src = remap(src)
167 else:
167 else:
168 src = remapped
168 src = remapped
169
169
170 src = remap(src)
170 src = remap(src)
171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
172
172
173 return state
173 return state
174
174
175 def writestate(repo, state):
175 def writestate(repo, state):
176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
178 if state[s][1] != nullstate[1]]
178 if state[s][1] != nullstate[1]]
179 repo.wwrite('.hgsubstate', ''.join(lines), '')
179 repo.wwrite('.hgsubstate', ''.join(lines), '')
180
180
181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
182 """delegated from merge.applyupdates: merging of .hgsubstate file
182 """delegated from merge.applyupdates: merging of .hgsubstate file
183 in working context, merging context and ancestor context"""
183 in working context, merging context and ancestor context"""
184 if mctx == actx: # backwards?
184 if mctx == actx: # backwards?
185 actx = wctx.p1()
185 actx = wctx.p1()
186 s1 = wctx.substate
186 s1 = wctx.substate
187 s2 = mctx.substate
187 s2 = mctx.substate
188 sa = actx.substate
188 sa = actx.substate
189 sm = {}
189 sm = {}
190
190
191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
192
192
193 def debug(s, msg, r=""):
193 def debug(s, msg, r=""):
194 if r:
194 if r:
195 r = "%s:%s:%s" % r
195 r = "%s:%s:%s" % r
196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
197
197
198 promptssrc = filemerge.partextras(labels)
198 promptssrc = filemerge.partextras(labels)
199 for s, l in sorted(s1.iteritems()):
199 for s, l in sorted(s1.iteritems()):
200 prompts = None
200 prompts = None
201 a = sa.get(s, nullstate)
201 a = sa.get(s, nullstate)
202 ld = l # local state with possible dirty flag for compares
202 ld = l # local state with possible dirty flag for compares
203 if wctx.sub(s).dirty():
203 if wctx.sub(s).dirty():
204 ld = (l[0], l[1] + "+")
204 ld = (l[0], l[1] + "+")
205 if wctx == actx: # overwrite
205 if wctx == actx: # overwrite
206 a = ld
206 a = ld
207
207
208 prompts = promptssrc.copy()
208 prompts = promptssrc.copy()
209 prompts['s'] = s
209 prompts['s'] = s
210 if s in s2:
210 if s in s2:
211 r = s2[s]
211 r = s2[s]
212 if ld == r or r == a: # no change or local is newer
212 if ld == r or r == a: # no change or local is newer
213 sm[s] = l
213 sm[s] = l
214 continue
214 continue
215 elif ld == a: # other side changed
215 elif ld == a: # other side changed
216 debug(s, "other changed, get", r)
216 debug(s, "other changed, get", r)
217 wctx.sub(s).get(r, overwrite)
217 wctx.sub(s).get(r, overwrite)
218 sm[s] = r
218 sm[s] = r
219 elif ld[0] != r[0]: # sources differ
219 elif ld[0] != r[0]: # sources differ
220 prompts['lo'] = l[0]
220 prompts['lo'] = l[0]
221 prompts['ro'] = r[0]
221 prompts['ro'] = r[0]
222 if repo.ui.promptchoice(
222 if repo.ui.promptchoice(
223 _(' subrepository sources for %(s)s differ\n'
223 _(' subrepository sources for %(s)s differ\n'
224 'use (l)ocal%(l)s source (%(lo)s)'
224 'use (l)ocal%(l)s source (%(lo)s)'
225 ' or (r)emote%(o)s source (%(ro)s)?'
225 ' or (r)emote%(o)s source (%(ro)s)?'
226 '$$ &Local $$ &Remote') % prompts, 0):
226 '$$ &Local $$ &Remote') % prompts, 0):
227 debug(s, "prompt changed, get", r)
227 debug(s, "prompt changed, get", r)
228 wctx.sub(s).get(r, overwrite)
228 wctx.sub(s).get(r, overwrite)
229 sm[s] = r
229 sm[s] = r
230 elif ld[1] == a[1]: # local side is unchanged
230 elif ld[1] == a[1]: # local side is unchanged
231 debug(s, "other side changed, get", r)
231 debug(s, "other side changed, get", r)
232 wctx.sub(s).get(r, overwrite)
232 wctx.sub(s).get(r, overwrite)
233 sm[s] = r
233 sm[s] = r
234 else:
234 else:
235 debug(s, "both sides changed")
235 debug(s, "both sides changed")
236 srepo = wctx.sub(s)
236 srepo = wctx.sub(s)
237 prompts['sl'] = srepo.shortid(l[1])
237 prompts['sl'] = srepo.shortid(l[1])
238 prompts['sr'] = srepo.shortid(r[1])
238 prompts['sr'] = srepo.shortid(r[1])
239 option = repo.ui.promptchoice(
239 option = repo.ui.promptchoice(
240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
241 'remote revision: %(sr)s)\n'
241 'remote revision: %(sr)s)\n'
242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
243 '$$ &Merge $$ &Local $$ &Remote')
243 '$$ &Merge $$ &Local $$ &Remote')
244 % prompts, 0)
244 % prompts, 0)
245 if option == 0:
245 if option == 0:
246 wctx.sub(s).merge(r)
246 wctx.sub(s).merge(r)
247 sm[s] = l
247 sm[s] = l
248 debug(s, "merge with", r)
248 debug(s, "merge with", r)
249 elif option == 1:
249 elif option == 1:
250 sm[s] = l
250 sm[s] = l
251 debug(s, "keep local subrepo revision", l)
251 debug(s, "keep local subrepo revision", l)
252 else:
252 else:
253 wctx.sub(s).get(r, overwrite)
253 wctx.sub(s).get(r, overwrite)
254 sm[s] = r
254 sm[s] = r
255 debug(s, "get remote subrepo revision", r)
255 debug(s, "get remote subrepo revision", r)
256 elif ld == a: # remote removed, local unchanged
256 elif ld == a: # remote removed, local unchanged
257 debug(s, "remote removed, remove")
257 debug(s, "remote removed, remove")
258 wctx.sub(s).remove()
258 wctx.sub(s).remove()
259 elif a == nullstate: # not present in remote or ancestor
259 elif a == nullstate: # not present in remote or ancestor
260 debug(s, "local added, keep")
260 debug(s, "local added, keep")
261 sm[s] = l
261 sm[s] = l
262 continue
262 continue
263 else:
263 else:
264 if repo.ui.promptchoice(
264 if repo.ui.promptchoice(
265 _(' local%(l)s changed subrepository %(s)s'
265 _(' local%(l)s changed subrepository %(s)s'
266 ' which remote%(o)s removed\n'
266 ' which remote%(o)s removed\n'
267 'use (c)hanged version or (d)elete?'
267 'use (c)hanged version or (d)elete?'
268 '$$ &Changed $$ &Delete') % prompts, 0):
268 '$$ &Changed $$ &Delete') % prompts, 0):
269 debug(s, "prompt remove")
269 debug(s, "prompt remove")
270 wctx.sub(s).remove()
270 wctx.sub(s).remove()
271
271
272 for s, r in sorted(s2.items()):
272 for s, r in sorted(s2.items()):
273 prompts = None
273 prompts = None
274 if s in s1:
274 if s in s1:
275 continue
275 continue
276 elif s not in sa:
276 elif s not in sa:
277 debug(s, "remote added, get", r)
277 debug(s, "remote added, get", r)
278 mctx.sub(s).get(r)
278 mctx.sub(s).get(r)
279 sm[s] = r
279 sm[s] = r
280 elif r != sa[s]:
280 elif r != sa[s]:
281 prompts = promptssrc.copy()
281 prompts = promptssrc.copy()
282 prompts['s'] = s
282 prompts['s'] = s
283 if repo.ui.promptchoice(
283 if repo.ui.promptchoice(
284 _(' remote%(o)s changed subrepository %(s)s'
284 _(' remote%(o)s changed subrepository %(s)s'
285 ' which local%(l)s removed\n'
285 ' which local%(l)s removed\n'
286 'use (c)hanged version or (d)elete?'
286 'use (c)hanged version or (d)elete?'
287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
288 debug(s, "prompt recreate", r)
288 debug(s, "prompt recreate", r)
289 mctx.sub(s).get(r)
289 mctx.sub(s).get(r)
290 sm[s] = r
290 sm[s] = r
291
291
292 # record merged .hgsubstate
292 # record merged .hgsubstate
293 writestate(repo, sm)
293 writestate(repo, sm)
294 return sm
294 return sm
295
295
296 def _updateprompt(ui, sub, dirty, local, remote):
296 def _updateprompt(ui, sub, dirty, local, remote):
297 if dirty:
297 if dirty:
298 msg = (_(' subrepository sources for %s differ\n'
298 msg = (_(' subrepository sources for %s differ\n'
299 'use (l)ocal source (%s) or (r)emote source (%s)?'
299 'use (l)ocal source (%s) or (r)emote source (%s)?'
300 '$$ &Local $$ &Remote')
300 '$$ &Local $$ &Remote')
301 % (subrelpath(sub), local, remote))
301 % (subrelpath(sub), local, remote))
302 else:
302 else:
303 msg = (_(' subrepository sources for %s differ (in checked out '
303 msg = (_(' subrepository sources for %s differ (in checked out '
304 'version)\n'
304 'version)\n'
305 'use (l)ocal source (%s) or (r)emote source (%s)?'
305 'use (l)ocal source (%s) or (r)emote source (%s)?'
306 '$$ &Local $$ &Remote')
306 '$$ &Local $$ &Remote')
307 % (subrelpath(sub), local, remote))
307 % (subrelpath(sub), local, remote))
308 return ui.promptchoice(msg, 0)
308 return ui.promptchoice(msg, 0)
309
309
310 def reporelpath(repo):
310 def reporelpath(repo):
311 """return path to this (sub)repo as seen from outermost repo"""
311 """return path to this (sub)repo as seen from outermost repo"""
312 parent = repo
312 parent = repo
313 while util.safehasattr(parent, '_subparent'):
313 while util.safehasattr(parent, '_subparent'):
314 parent = parent._subparent
314 parent = parent._subparent
315 return repo.root[len(pathutil.normasprefix(parent.root)):]
315 return repo.root[len(pathutil.normasprefix(parent.root)):]
316
316
317 def subrelpath(sub):
317 def subrelpath(sub):
318 """return path to this subrepo as seen from outermost repo"""
318 """return path to this subrepo as seen from outermost repo"""
319 return sub._relpath
319 return sub._relpath
320
320
321 def _abssource(repo, push=False, abort=True):
321 def _abssource(repo, push=False, abort=True):
322 """return pull/push path of repo - either based on parent repo .hgsub info
322 """return pull/push path of repo - either based on parent repo .hgsub info
323 or on the top repo config. Abort or return None if no source found."""
323 or on the top repo config. Abort or return None if no source found."""
324 if util.safehasattr(repo, '_subparent'):
324 if util.safehasattr(repo, '_subparent'):
325 source = util.url(repo._subsource)
325 source = util.url(repo._subsource)
326 if source.isabs():
326 if source.isabs():
327 return str(source)
327 return str(source)
328 source.path = posixpath.normpath(source.path)
328 source.path = posixpath.normpath(source.path)
329 parent = _abssource(repo._subparent, push, abort=False)
329 parent = _abssource(repo._subparent, push, abort=False)
330 if parent:
330 if parent:
331 parent = util.url(util.pconvert(parent))
331 parent = util.url(util.pconvert(parent))
332 parent.path = posixpath.join(parent.path or '', source.path)
332 parent.path = posixpath.join(parent.path or '', source.path)
333 parent.path = posixpath.normpath(parent.path)
333 parent.path = posixpath.normpath(parent.path)
334 return str(parent)
334 return str(parent)
335 else: # recursion reached top repo
335 else: # recursion reached top repo
336 if util.safehasattr(repo, '_subtoppath'):
336 if util.safehasattr(repo, '_subtoppath'):
337 return repo._subtoppath
337 return repo._subtoppath
338 if push and repo.ui.config('paths', 'default-push'):
338 if push and repo.ui.config('paths', 'default-push'):
339 return repo.ui.config('paths', 'default-push')
339 return repo.ui.config('paths', 'default-push')
340 if repo.ui.config('paths', 'default'):
340 if repo.ui.config('paths', 'default'):
341 return repo.ui.config('paths', 'default')
341 return repo.ui.config('paths', 'default')
342 if repo.shared():
342 if repo.shared():
343 # chop off the .hg component to get the default path form
343 # chop off the .hg component to get the default path form
344 return os.path.dirname(repo.sharedpath)
344 return os.path.dirname(repo.sharedpath)
345 if abort:
345 if abort:
346 raise error.Abort(_("default path for subrepository not found"))
346 raise error.Abort(_("default path for subrepository not found"))
347
347
348 def _sanitize(ui, vfs, ignore):
348 def _sanitize(ui, vfs, ignore):
349 for dirname, dirs, names in vfs.walk():
349 for dirname, dirs, names in vfs.walk():
350 for i, d in enumerate(dirs):
350 for i, d in enumerate(dirs):
351 if d.lower() == ignore:
351 if d.lower() == ignore:
352 del dirs[i]
352 del dirs[i]
353 break
353 break
354 if vfs.basename(dirname).lower() != '.hg':
354 if vfs.basename(dirname).lower() != '.hg':
355 continue
355 continue
356 for f in names:
356 for f in names:
357 if f.lower() == 'hgrc':
357 if f.lower() == 'hgrc':
358 ui.warn(_("warning: removing potentially hostile 'hgrc' "
358 ui.warn(_("warning: removing potentially hostile 'hgrc' "
359 "in '%s'\n") % vfs.join(dirname))
359 "in '%s'\n") % vfs.join(dirname))
360 vfs.unlink(vfs.reljoin(dirname, f))
360 vfs.unlink(vfs.reljoin(dirname, f))
361
361
362 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
362 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
363 """return instance of the right subrepo class for subrepo in path"""
363 """return instance of the right subrepo class for subrepo in path"""
364 # subrepo inherently violates our import layering rules
364 # subrepo inherently violates our import layering rules
365 # because it wants to make repo objects from deep inside the stack
365 # because it wants to make repo objects from deep inside the stack
366 # so we manually delay the circular imports to not break
366 # so we manually delay the circular imports to not break
367 # scripts that don't use our demand-loading
367 # scripts that don't use our demand-loading
368 global hg
368 global hg
369 from . import hg as h
369 from . import hg as h
370 hg = h
370 hg = h
371
371
372 pathutil.pathauditor(ctx.repo().root)(path)
372 pathutil.pathauditor(ctx.repo().root)(path)
373 state = ctx.substate[path]
373 state = ctx.substate[path]
374 if state[2] not in types:
374 if state[2] not in types:
375 raise error.Abort(_('unknown subrepo type %s') % state[2])
375 raise error.Abort(_('unknown subrepo type %s') % state[2])
376 if allowwdir:
376 if allowwdir:
377 state = (state[0], ctx.subrev(path), state[2])
377 state = (state[0], ctx.subrev(path), state[2])
378 return types[state[2]](ctx, path, state[:2], allowcreate)
378 return types[state[2]](ctx, path, state[:2], allowcreate)
379
379
380 def nullsubrepo(ctx, path, pctx):
380 def nullsubrepo(ctx, path, pctx):
381 """return an empty subrepo in pctx for the extant subrepo in ctx"""
381 """return an empty subrepo in pctx for the extant subrepo in ctx"""
382 # subrepo inherently violates our import layering rules
382 # subrepo inherently violates our import layering rules
383 # because it wants to make repo objects from deep inside the stack
383 # because it wants to make repo objects from deep inside the stack
384 # so we manually delay the circular imports to not break
384 # so we manually delay the circular imports to not break
385 # scripts that don't use our demand-loading
385 # scripts that don't use our demand-loading
386 global hg
386 global hg
387 from . import hg as h
387 from . import hg as h
388 hg = h
388 hg = h
389
389
390 pathutil.pathauditor(ctx.repo().root)(path)
390 pathutil.pathauditor(ctx.repo().root)(path)
391 state = ctx.substate[path]
391 state = ctx.substate[path]
392 if state[2] not in types:
392 if state[2] not in types:
393 raise error.Abort(_('unknown subrepo type %s') % state[2])
393 raise error.Abort(_('unknown subrepo type %s') % state[2])
394 subrev = ''
394 subrev = ''
395 if state[2] == 'hg':
395 if state[2] == 'hg':
396 subrev = "0" * 40
396 subrev = "0" * 40
397 return types[state[2]](pctx, path, (state[0], subrev), True)
397 return types[state[2]](pctx, path, (state[0], subrev), True)
398
398
399 def newcommitphase(ui, ctx):
399 def newcommitphase(ui, ctx):
400 commitphase = phases.newcommitphase(ui)
400 commitphase = phases.newcommitphase(ui)
401 substate = getattr(ctx, "substate", None)
401 substate = getattr(ctx, "substate", None)
402 if not substate:
402 if not substate:
403 return commitphase
403 return commitphase
404 check = ui.config('phases', 'checksubrepos', 'follow')
404 check = ui.config('phases', 'checksubrepos', 'follow')
405 if check not in ('ignore', 'follow', 'abort'):
405 if check not in ('ignore', 'follow', 'abort'):
406 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
406 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
407 % (check))
407 % (check))
408 if check == 'ignore':
408 if check == 'ignore':
409 return commitphase
409 return commitphase
410 maxphase = phases.public
410 maxphase = phases.public
411 maxsub = None
411 maxsub = None
412 for s in sorted(substate):
412 for s in sorted(substate):
413 sub = ctx.sub(s)
413 sub = ctx.sub(s)
414 subphase = sub.phase(substate[s][1])
414 subphase = sub.phase(substate[s][1])
415 if maxphase < subphase:
415 if maxphase < subphase:
416 maxphase = subphase
416 maxphase = subphase
417 maxsub = s
417 maxsub = s
418 if commitphase < maxphase:
418 if commitphase < maxphase:
419 if check == 'abort':
419 if check == 'abort':
420 raise error.Abort(_("can't commit in %s phase"
420 raise error.Abort(_("can't commit in %s phase"
421 " conflicting %s from subrepository %s") %
421 " conflicting %s from subrepository %s") %
422 (phases.phasenames[commitphase],
422 (phases.phasenames[commitphase],
423 phases.phasenames[maxphase], maxsub))
423 phases.phasenames[maxphase], maxsub))
424 ui.warn(_("warning: changes are committed in"
424 ui.warn(_("warning: changes are committed in"
425 " %s phase from subrepository %s\n") %
425 " %s phase from subrepository %s\n") %
426 (phases.phasenames[maxphase], maxsub))
426 (phases.phasenames[maxphase], maxsub))
427 return maxphase
427 return maxphase
428 return commitphase
428 return commitphase
429
429
430 # subrepo classes need to implement the following abstract class:
430 # subrepo classes need to implement the following abstract class:
431
431
432 class abstractsubrepo(object):
432 class abstractsubrepo(object):
433
433
434 def __init__(self, ctx, path):
434 def __init__(self, ctx, path):
435 """Initialize abstractsubrepo part
435 """Initialize abstractsubrepo part
436
436
437 ``ctx`` is the context referring this subrepository in the
437 ``ctx`` is the context referring this subrepository in the
438 parent repository.
438 parent repository.
439
439
440 ``path`` is the path to this subrepository as seen from
440 ``path`` is the path to this subrepository as seen from
441 innermost repository.
441 innermost repository.
442 """
442 """
443 self.ui = ctx.repo().ui
443 self.ui = ctx.repo().ui
444 self._ctx = ctx
444 self._ctx = ctx
445 self._path = path
445 self._path = path
446
446
447 def addwebdirpath(self, serverpath, webconf):
447 def addwebdirpath(self, serverpath, webconf):
448 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
448 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
449
449
450 ``serverpath`` is the path component of the URL for this repo.
450 ``serverpath`` is the path component of the URL for this repo.
451
451
452 ``webconf`` is the dictionary of hgwebdir entries.
452 ``webconf`` is the dictionary of hgwebdir entries.
453 """
453 """
454 pass
454 pass
455
455
456 def storeclean(self, path):
456 def storeclean(self, path):
457 """
457 """
458 returns true if the repository has not changed since it was last
458 returns true if the repository has not changed since it was last
459 cloned from or pushed to a given repository.
459 cloned from or pushed to a given repository.
460 """
460 """
461 return False
461 return False
462
462
463 def dirty(self, ignoreupdate=False):
463 def dirty(self, ignoreupdate=False):
464 """returns true if the dirstate of the subrepo is dirty or does not
464 """returns true if the dirstate of the subrepo is dirty or does not
465 match current stored state. If ignoreupdate is true, only check
465 match current stored state. If ignoreupdate is true, only check
466 whether the subrepo has uncommitted changes in its dirstate.
466 whether the subrepo has uncommitted changes in its dirstate.
467 """
467 """
468 raise NotImplementedError
468 raise NotImplementedError
469
469
470 def dirtyreason(self, ignoreupdate=False):
470 def dirtyreason(self, ignoreupdate=False):
471 """return reason string if it is ``dirty()``
471 """return reason string if it is ``dirty()``
472
472
473 Returned string should have enough information for the message
473 Returned string should have enough information for the message
474 of exception.
474 of exception.
475
475
476 This returns None, otherwise.
476 This returns None, otherwise.
477 """
477 """
478 if self.dirty(ignoreupdate=ignoreupdate):
478 if self.dirty(ignoreupdate=ignoreupdate):
479 return _("uncommitted changes in subrepository '%s'"
479 return _("uncommitted changes in subrepository '%s'"
480 ) % subrelpath(self)
480 ) % subrelpath(self)
481
481
482 def bailifchanged(self, ignoreupdate=False, hint=None):
482 def bailifchanged(self, ignoreupdate=False, hint=None):
483 """raise Abort if subrepository is ``dirty()``
483 """raise Abort if subrepository is ``dirty()``
484 """
484 """
485 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
485 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
486 if dirtyreason:
486 if dirtyreason:
487 raise error.Abort(dirtyreason, hint=hint)
487 raise error.Abort(dirtyreason, hint=hint)
488
488
489 def basestate(self):
489 def basestate(self):
490 """current working directory base state, disregarding .hgsubstate
490 """current working directory base state, disregarding .hgsubstate
491 state and working directory modifications"""
491 state and working directory modifications"""
492 raise NotImplementedError
492 raise NotImplementedError
493
493
494 def checknested(self, path):
494 def checknested(self, path):
495 """check if path is a subrepository within this repository"""
495 """check if path is a subrepository within this repository"""
496 return False
496 return False
497
497
498 def commit(self, text, user, date):
498 def commit(self, text, user, date):
499 """commit the current changes to the subrepo with the given
499 """commit the current changes to the subrepo with the given
500 log message. Use given user and date if possible. Return the
500 log message. Use given user and date if possible. Return the
501 new state of the subrepo.
501 new state of the subrepo.
502 """
502 """
503 raise NotImplementedError
503 raise NotImplementedError
504
504
505 def phase(self, state):
505 def phase(self, state):
506 """returns phase of specified state in the subrepository.
506 """returns phase of specified state in the subrepository.
507 """
507 """
508 return phases.public
508 return phases.public
509
509
510 def remove(self):
510 def remove(self):
511 """remove the subrepo
511 """remove the subrepo
512
512
513 (should verify the dirstate is not dirty first)
513 (should verify the dirstate is not dirty first)
514 """
514 """
515 raise NotImplementedError
515 raise NotImplementedError
516
516
517 def get(self, state, overwrite=False):
517 def get(self, state, overwrite=False):
518 """run whatever commands are needed to put the subrepo into
518 """run whatever commands are needed to put the subrepo into
519 this state
519 this state
520 """
520 """
521 raise NotImplementedError
521 raise NotImplementedError
522
522
523 def merge(self, state):
523 def merge(self, state):
524 """merge currently-saved state with the new state."""
524 """merge currently-saved state with the new state."""
525 raise NotImplementedError
525 raise NotImplementedError
526
526
527 def push(self, opts):
527 def push(self, opts):
528 """perform whatever action is analogous to 'hg push'
528 """perform whatever action is analogous to 'hg push'
529
529
530 This may be a no-op on some systems.
530 This may be a no-op on some systems.
531 """
531 """
532 raise NotImplementedError
532 raise NotImplementedError
533
533
534 def add(self, ui, match, prefix, explicitonly, **opts):
534 def add(self, ui, match, prefix, explicitonly, **opts):
535 return []
535 return []
536
536
537 def addremove(self, matcher, prefix, opts, dry_run, similarity):
537 def addremove(self, matcher, prefix, opts, dry_run, similarity):
538 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
538 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
539 return 1
539 return 1
540
540
541 def cat(self, match, prefix, **opts):
541 def cat(self, match, prefix, **opts):
542 return 1
542 return 1
543
543
544 def status(self, rev2, **opts):
544 def status(self, rev2, **opts):
545 return scmutil.status([], [], [], [], [], [], [])
545 return scmutil.status([], [], [], [], [], [], [])
546
546
547 def diff(self, ui, diffopts, node2, match, prefix, **opts):
547 def diff(self, ui, diffopts, node2, match, prefix, **opts):
548 pass
548 pass
549
549
550 def outgoing(self, ui, dest, opts):
550 def outgoing(self, ui, dest, opts):
551 return 1
551 return 1
552
552
553 def incoming(self, ui, source, opts):
553 def incoming(self, ui, source, opts):
554 return 1
554 return 1
555
555
556 def files(self):
556 def files(self):
557 """return filename iterator"""
557 """return filename iterator"""
558 raise NotImplementedError
558 raise NotImplementedError
559
559
560 def filedata(self, name, decode):
560 def filedata(self, name, decode):
561 """return file data, optionally passed through repo decoders"""
561 """return file data, optionally passed through repo decoders"""
562 raise NotImplementedError
562 raise NotImplementedError
563
563
564 def fileflags(self, name):
564 def fileflags(self, name):
565 """return file flags"""
565 """return file flags"""
566 return ''
566 return ''
567
567
568 def getfileset(self, expr):
568 def getfileset(self, expr):
569 """Resolve the fileset expression for this repo"""
569 """Resolve the fileset expression for this repo"""
570 return set()
570 return set()
571
571
572 def printfiles(self, ui, m, fm, fmt, subrepos):
572 def printfiles(self, ui, m, fm, fmt, subrepos):
573 """handle the files command for this subrepo"""
573 """handle the files command for this subrepo"""
574 return 1
574 return 1
575
575
576 def archive(self, archiver, prefix, match=None, decode=True):
576 def archive(self, archiver, prefix, match=None, decode=True):
577 if match is not None:
577 if match is not None:
578 files = [f for f in self.files() if match(f)]
578 files = [f for f in self.files() if match(f)]
579 else:
579 else:
580 files = self.files()
580 files = self.files()
581 total = len(files)
581 total = len(files)
582 relpath = subrelpath(self)
582 relpath = subrelpath(self)
583 self.ui.progress(_('archiving (%s)') % relpath, 0,
583 self.ui.progress(_('archiving (%s)') % relpath, 0,
584 unit=_('files'), total=total)
584 unit=_('files'), total=total)
585 for i, name in enumerate(files):
585 for i, name in enumerate(files):
586 flags = self.fileflags(name)
586 flags = self.fileflags(name)
587 mode = 'x' in flags and 0o755 or 0o644
587 mode = 'x' in flags and 0o755 or 0o644
588 symlink = 'l' in flags
588 symlink = 'l' in flags
589 archiver.addfile(prefix + self._path + '/' + name,
589 archiver.addfile(prefix + self._path + '/' + name,
590 mode, symlink, self.filedata(name, decode))
590 mode, symlink, self.filedata(name, decode))
591 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
591 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
592 unit=_('files'), total=total)
592 unit=_('files'), total=total)
593 self.ui.progress(_('archiving (%s)') % relpath, None)
593 self.ui.progress(_('archiving (%s)') % relpath, None)
594 return total
594 return total
595
595
596 def walk(self, match):
596 def walk(self, match):
597 '''
597 '''
598 walk recursively through the directory tree, finding all files
598 walk recursively through the directory tree, finding all files
599 matched by the match function
599 matched by the match function
600 '''
600 '''
601 pass
601 pass
602
602
603 def forget(self, match, prefix):
603 def forget(self, match, prefix):
604 return ([], [])
604 return ([], [])
605
605
606 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
606 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
607 """remove the matched files from the subrepository and the filesystem,
607 """remove the matched files from the subrepository and the filesystem,
608 possibly by force and/or after the file has been removed from the
608 possibly by force and/or after the file has been removed from the
609 filesystem. Return 0 on success, 1 on any warning.
609 filesystem. Return 0 on success, 1 on any warning.
610 """
610 """
611 warnings.append(_("warning: removefiles not implemented (%s)")
611 warnings.append(_("warning: removefiles not implemented (%s)")
612 % self._path)
612 % self._path)
613 return 1
613 return 1
614
614
615 def revert(self, substate, *pats, **opts):
615 def revert(self, substate, *pats, **opts):
616 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
616 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
617 % (substate[0], substate[2]))
617 % (substate[0], substate[2]))
618 return []
618 return []
619
619
620 def shortid(self, revid):
620 def shortid(self, revid):
621 return revid
621 return revid
622
622
623 def verify(self):
623 def verify(self):
624 '''verify the integrity of the repository. Return 0 on success or
624 '''verify the integrity of the repository. Return 0 on success or
625 warning, 1 on any error.
625 warning, 1 on any error.
626 '''
626 '''
627 return 0
627 return 0
628
628
629 @propertycache
629 @propertycache
630 def wvfs(self):
630 def wvfs(self):
631 """return vfs to access the working directory of this subrepository
631 """return vfs to access the working directory of this subrepository
632 """
632 """
633 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
633 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
634
634
635 @propertycache
635 @propertycache
636 def _relpath(self):
636 def _relpath(self):
637 """return path to this subrepository as seen from outermost repository
637 """return path to this subrepository as seen from outermost repository
638 """
638 """
639 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
639 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
640
640
641 class hgsubrepo(abstractsubrepo):
641 class hgsubrepo(abstractsubrepo):
642 def __init__(self, ctx, path, state, allowcreate):
642 def __init__(self, ctx, path, state, allowcreate):
643 super(hgsubrepo, self).__init__(ctx, path)
643 super(hgsubrepo, self).__init__(ctx, path)
644 self._state = state
644 self._state = state
645 r = ctx.repo()
645 r = ctx.repo()
646 root = r.wjoin(path)
646 root = r.wjoin(path)
647 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
647 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
648 self._repo = hg.repository(r.baseui, root, create=create)
648 self._repo = hg.repository(r.baseui, root, create=create)
649
649
650 # Propagate the parent's --hidden option
650 # Propagate the parent's --hidden option
651 if r is r.unfiltered():
651 if r is r.unfiltered():
652 self._repo = self._repo.unfiltered()
652 self._repo = self._repo.unfiltered()
653
653
654 self.ui = self._repo.ui
654 self.ui = self._repo.ui
655 for s, k in [('ui', 'commitsubrepos')]:
655 for s, k in [('ui', 'commitsubrepos')]:
656 v = r.ui.config(s, k)
656 v = r.ui.config(s, k)
657 if v:
657 if v:
658 self.ui.setconfig(s, k, v, 'subrepo')
658 self.ui.setconfig(s, k, v, 'subrepo')
659 # internal config: ui._usedassubrepo
659 # internal config: ui._usedassubrepo
660 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
660 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
661 self._initrepo(r, state[0], create)
661 self._initrepo(r, state[0], create)
662
662
663 @annotatesubrepoerror
663 @annotatesubrepoerror
664 def addwebdirpath(self, serverpath, webconf):
664 def addwebdirpath(self, serverpath, webconf):
665 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
665 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
666
666
667 def storeclean(self, path):
667 def storeclean(self, path):
668 with self._repo.lock():
668 with self._repo.lock():
669 return self._storeclean(path)
669 return self._storeclean(path)
670
670
671 def _storeclean(self, path):
671 def _storeclean(self, path):
672 clean = True
672 clean = True
673 itercache = self._calcstorehash(path)
673 itercache = self._calcstorehash(path)
674 for filehash in self._readstorehashcache(path):
674 for filehash in self._readstorehashcache(path):
675 if filehash != next(itercache, None):
675 if filehash != next(itercache, None):
676 clean = False
676 clean = False
677 break
677 break
678 if clean:
678 if clean:
679 # if not empty:
679 # if not empty:
680 # the cached and current pull states have a different size
680 # the cached and current pull states have a different size
681 clean = next(itercache, None) is None
681 clean = next(itercache, None) is None
682 return clean
682 return clean
683
683
684 def _calcstorehash(self, remotepath):
684 def _calcstorehash(self, remotepath):
685 '''calculate a unique "store hash"
685 '''calculate a unique "store hash"
686
686
687 This method is used to to detect when there are changes that may
687 This method is used to to detect when there are changes that may
688 require a push to a given remote path.'''
688 require a push to a given remote path.'''
689 # sort the files that will be hashed in increasing (likely) file size
689 # sort the files that will be hashed in increasing (likely) file size
690 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
690 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
691 yield '# %s\n' % _expandedabspath(remotepath)
691 yield '# %s\n' % _expandedabspath(remotepath)
692 vfs = self._repo.vfs
692 vfs = self._repo.vfs
693 for relname in filelist:
693 for relname in filelist:
694 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
694 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
695 yield '%s = %s\n' % (relname, filehash)
695 yield '%s = %s\n' % (relname, filehash)
696
696
697 @propertycache
697 @propertycache
698 def _cachestorehashvfs(self):
698 def _cachestorehashvfs(self):
699 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
699 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
700
700
701 def _readstorehashcache(self, remotepath):
701 def _readstorehashcache(self, remotepath):
702 '''read the store hash cache for a given remote repository'''
702 '''read the store hash cache for a given remote repository'''
703 cachefile = _getstorehashcachename(remotepath)
703 cachefile = _getstorehashcachename(remotepath)
704 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
704 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
705
705
706 def _cachestorehash(self, remotepath):
706 def _cachestorehash(self, remotepath):
707 '''cache the current store hash
707 '''cache the current store hash
708
708
709 Each remote repo requires its own store hash cache, because a subrepo
709 Each remote repo requires its own store hash cache, because a subrepo
710 store may be "clean" versus a given remote repo, but not versus another
710 store may be "clean" versus a given remote repo, but not versus another
711 '''
711 '''
712 cachefile = _getstorehashcachename(remotepath)
712 cachefile = _getstorehashcachename(remotepath)
713 with self._repo.lock():
713 with self._repo.lock():
714 storehash = list(self._calcstorehash(remotepath))
714 storehash = list(self._calcstorehash(remotepath))
715 vfs = self._cachestorehashvfs
715 vfs = self._cachestorehashvfs
716 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
716 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
717
717
718 def _getctx(self):
718 def _getctx(self):
719 '''fetch the context for this subrepo revision, possibly a workingctx
719 '''fetch the context for this subrepo revision, possibly a workingctx
720 '''
720 '''
721 if self._ctx.rev() is None:
721 if self._ctx.rev() is None:
722 return self._repo[None] # workingctx if parent is workingctx
722 return self._repo[None] # workingctx if parent is workingctx
723 else:
723 else:
724 rev = self._state[1]
724 rev = self._state[1]
725 return self._repo[rev]
725 return self._repo[rev]
726
726
727 @annotatesubrepoerror
727 @annotatesubrepoerror
728 def _initrepo(self, parentrepo, source, create):
728 def _initrepo(self, parentrepo, source, create):
729 self._repo._subparent = parentrepo
729 self._repo._subparent = parentrepo
730 self._repo._subsource = source
730 self._repo._subsource = source
731
731
732 if create:
732 if create:
733 lines = ['[paths]\n']
733 lines = ['[paths]\n']
734
734
735 def addpathconfig(key, value):
735 def addpathconfig(key, value):
736 if value:
736 if value:
737 lines.append('%s = %s\n' % (key, value))
737 lines.append('%s = %s\n' % (key, value))
738 self.ui.setconfig('paths', key, value, 'subrepo')
738 self.ui.setconfig('paths', key, value, 'subrepo')
739
739
740 defpath = _abssource(self._repo, abort=False)
740 defpath = _abssource(self._repo, abort=False)
741 defpushpath = _abssource(self._repo, True, abort=False)
741 defpushpath = _abssource(self._repo, True, abort=False)
742 addpathconfig('default', defpath)
742 addpathconfig('default', defpath)
743 if defpath != defpushpath:
743 if defpath != defpushpath:
744 addpathconfig('default-push', defpushpath)
744 addpathconfig('default-push', defpushpath)
745
745
746 fp = self._repo.vfs("hgrc", "w", text=True)
746 fp = self._repo.vfs("hgrc", "w", text=True)
747 try:
747 try:
748 fp.write(''.join(lines))
748 fp.write(''.join(lines))
749 finally:
749 finally:
750 fp.close()
750 fp.close()
751
751
752 @annotatesubrepoerror
752 @annotatesubrepoerror
753 def add(self, ui, match, prefix, explicitonly, **opts):
753 def add(self, ui, match, prefix, explicitonly, **opts):
754 return cmdutil.add(ui, self._repo, match,
754 return cmdutil.add(ui, self._repo, match,
755 self.wvfs.reljoin(prefix, self._path),
755 self.wvfs.reljoin(prefix, self._path),
756 explicitonly, **opts)
756 explicitonly, **opts)
757
757
758 @annotatesubrepoerror
758 @annotatesubrepoerror
759 def addremove(self, m, prefix, opts, dry_run, similarity):
759 def addremove(self, m, prefix, opts, dry_run, similarity):
760 # In the same way as sub directories are processed, once in a subrepo,
760 # In the same way as sub directories are processed, once in a subrepo,
761 # always entry any of its subrepos. Don't corrupt the options that will
761 # always entry any of its subrepos. Don't corrupt the options that will
762 # be used to process sibling subrepos however.
762 # be used to process sibling subrepos however.
763 opts = copy.copy(opts)
763 opts = copy.copy(opts)
764 opts['subrepos'] = True
764 opts['subrepos'] = True
765 return scmutil.addremove(self._repo, m,
765 return scmutil.addremove(self._repo, m,
766 self.wvfs.reljoin(prefix, self._path), opts,
766 self.wvfs.reljoin(prefix, self._path), opts,
767 dry_run, similarity)
767 dry_run, similarity)
768
768
769 @annotatesubrepoerror
769 @annotatesubrepoerror
770 def cat(self, match, prefix, **opts):
770 def cat(self, match, prefix, **opts):
771 rev = self._state[1]
771 rev = self._state[1]
772 ctx = self._repo[rev]
772 ctx = self._repo[rev]
773 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
773 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
774
774
775 @annotatesubrepoerror
775 @annotatesubrepoerror
776 def status(self, rev2, **opts):
776 def status(self, rev2, **opts):
777 try:
777 try:
778 rev1 = self._state[1]
778 rev1 = self._state[1]
779 ctx1 = self._repo[rev1]
779 ctx1 = self._repo[rev1]
780 ctx2 = self._repo[rev2]
780 ctx2 = self._repo[rev2]
781 return self._repo.status(ctx1, ctx2, **opts)
781 return self._repo.status(ctx1, ctx2, **opts)
782 except error.RepoLookupError as inst:
782 except error.RepoLookupError as inst:
783 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
783 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
784 % (inst, subrelpath(self)))
784 % (inst, subrelpath(self)))
785 return scmutil.status([], [], [], [], [], [], [])
785 return scmutil.status([], [], [], [], [], [], [])
786
786
787 @annotatesubrepoerror
787 @annotatesubrepoerror
788 def diff(self, ui, diffopts, node2, match, prefix, **opts):
788 def diff(self, ui, diffopts, node2, match, prefix, **opts):
789 try:
789 try:
790 node1 = node.bin(self._state[1])
790 node1 = node.bin(self._state[1])
791 # We currently expect node2 to come from substate and be
791 # We currently expect node2 to come from substate and be
792 # in hex format
792 # in hex format
793 if node2 is not None:
793 if node2 is not None:
794 node2 = node.bin(node2)
794 node2 = node.bin(node2)
795 cmdutil.diffordiffstat(ui, self._repo, diffopts,
795 cmdutil.diffordiffstat(ui, self._repo, diffopts,
796 node1, node2, match,
796 node1, node2, match,
797 prefix=posixpath.join(prefix, self._path),
797 prefix=posixpath.join(prefix, self._path),
798 listsubrepos=True, **opts)
798 listsubrepos=True, **opts)
799 except error.RepoLookupError as inst:
799 except error.RepoLookupError as inst:
800 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
800 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
801 % (inst, subrelpath(self)))
801 % (inst, subrelpath(self)))
802
802
803 @annotatesubrepoerror
803 @annotatesubrepoerror
804 def archive(self, archiver, prefix, match=None, decode=True):
804 def archive(self, archiver, prefix, match=None, decode=True):
805 self._get(self._state + ('hg',))
805 self._get(self._state + ('hg',))
806 total = abstractsubrepo.archive(self, archiver, prefix, match)
806 total = abstractsubrepo.archive(self, archiver, prefix, match)
807 rev = self._state[1]
807 rev = self._state[1]
808 ctx = self._repo[rev]
808 ctx = self._repo[rev]
809 for subpath in ctx.substate:
809 for subpath in ctx.substate:
810 s = subrepo(ctx, subpath, True)
810 s = subrepo(ctx, subpath, True)
811 submatch = matchmod.subdirmatcher(subpath, match)
811 submatch = matchmod.subdirmatcher(subpath, match)
812 total += s.archive(archiver, prefix + self._path + '/', submatch,
812 total += s.archive(archiver, prefix + self._path + '/', submatch,
813 decode)
813 decode)
814 return total
814 return total
815
815
816 @annotatesubrepoerror
816 @annotatesubrepoerror
817 def dirty(self, ignoreupdate=False):
817 def dirty(self, ignoreupdate=False):
818 r = self._state[1]
818 r = self._state[1]
819 if r == '' and not ignoreupdate: # no state recorded
819 if r == '' and not ignoreupdate: # no state recorded
820 return True
820 return True
821 w = self._repo[None]
821 w = self._repo[None]
822 if r != w.p1().hex() and not ignoreupdate:
822 if r != w.p1().hex() and not ignoreupdate:
823 # different version checked out
823 # different version checked out
824 return True
824 return True
825 return w.dirty() # working directory changed
825 return w.dirty() # working directory changed
826
826
827 def basestate(self):
827 def basestate(self):
828 return self._repo['.'].hex()
828 return self._repo['.'].hex()
829
829
830 def checknested(self, path):
830 def checknested(self, path):
831 return self._repo._checknested(self._repo.wjoin(path))
831 return self._repo._checknested(self._repo.wjoin(path))
832
832
833 @annotatesubrepoerror
833 @annotatesubrepoerror
834 def commit(self, text, user, date):
834 def commit(self, text, user, date):
835 # don't bother committing in the subrepo if it's only been
835 # don't bother committing in the subrepo if it's only been
836 # updated
836 # updated
837 if not self.dirty(True):
837 if not self.dirty(True):
838 return self._repo['.'].hex()
838 return self._repo['.'].hex()
839 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
839 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
840 n = self._repo.commit(text, user, date)
840 n = self._repo.commit(text, user, date)
841 if not n:
841 if not n:
842 return self._repo['.'].hex() # different version checked out
842 return self._repo['.'].hex() # different version checked out
843 return node.hex(n)
843 return node.hex(n)
844
844
845 @annotatesubrepoerror
845 @annotatesubrepoerror
846 def phase(self, state):
846 def phase(self, state):
847 return self._repo[state].phase()
847 return self._repo[state].phase()
848
848
849 @annotatesubrepoerror
849 @annotatesubrepoerror
850 def remove(self):
850 def remove(self):
851 # we can't fully delete the repository as it may contain
851 # we can't fully delete the repository as it may contain
852 # local-only history
852 # local-only history
853 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
853 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
854 hg.clean(self._repo, node.nullid, False)
854 hg.clean(self._repo, node.nullid, False)
855
855
856 def _get(self, state):
856 def _get(self, state):
857 source, revision, kind = state
857 source, revision, kind = state
858 if revision in self._repo.unfiltered():
858 if revision in self._repo.unfiltered():
859 return True
859 return True
860 self._repo._subsource = source
860 self._repo._subsource = source
861 srcurl = _abssource(self._repo)
861 srcurl = _abssource(self._repo)
862 other = hg.peer(self._repo, {}, srcurl)
862 other = hg.peer(self._repo, {}, srcurl)
863 if len(self._repo) == 0:
863 if len(self._repo) == 0:
864 self.ui.status(_('cloning subrepo %s from %s\n')
864 self.ui.status(_('cloning subrepo %s from %s\n')
865 % (subrelpath(self), srcurl))
865 % (subrelpath(self), srcurl))
866 parentrepo = self._repo._subparent
866 parentrepo = self._repo._subparent
867 # use self._repo.vfs instead of self.wvfs to remove .hg only
867 # use self._repo.vfs instead of self.wvfs to remove .hg only
868 self._repo.vfs.rmtree()
868 self._repo.vfs.rmtree()
869 other, cloned = hg.clone(self._repo._subparent.baseui, {},
869 other, cloned = hg.clone(self._repo._subparent.baseui, {},
870 other, self._repo.root,
870 other, self._repo.root,
871 update=False)
871 update=False)
872 self._repo = cloned.local()
872 self._repo = cloned.local()
873 self._initrepo(parentrepo, source, create=True)
873 self._initrepo(parentrepo, source, create=True)
874 self._cachestorehash(srcurl)
874 self._cachestorehash(srcurl)
875 else:
875 else:
876 self.ui.status(_('pulling subrepo %s from %s\n')
876 self.ui.status(_('pulling subrepo %s from %s\n')
877 % (subrelpath(self), srcurl))
877 % (subrelpath(self), srcurl))
878 cleansub = self.storeclean(srcurl)
878 cleansub = self.storeclean(srcurl)
879 exchange.pull(self._repo, other)
879 exchange.pull(self._repo, other)
880 if cleansub:
880 if cleansub:
881 # keep the repo clean after pull
881 # keep the repo clean after pull
882 self._cachestorehash(srcurl)
882 self._cachestorehash(srcurl)
883 return False
883 return False
884
884
885 @annotatesubrepoerror
885 @annotatesubrepoerror
886 def get(self, state, overwrite=False):
886 def get(self, state, overwrite=False):
887 inrepo = self._get(state)
887 inrepo = self._get(state)
888 source, revision, kind = state
888 source, revision, kind = state
889 repo = self._repo
889 repo = self._repo
890 repo.ui.debug("getting subrepo %s\n" % self._path)
890 repo.ui.debug("getting subrepo %s\n" % self._path)
891 if inrepo:
891 if inrepo:
892 urepo = repo.unfiltered()
892 urepo = repo.unfiltered()
893 ctx = urepo[revision]
893 ctx = urepo[revision]
894 if ctx.hidden():
894 if ctx.hidden():
895 urepo.ui.warn(
895 urepo.ui.warn(
896 _('revision %s in subrepo %s is hidden\n') \
896 _('revision %s in subrepo %s is hidden\n') \
897 % (revision[0:12], self._path))
897 % (revision[0:12], self._path))
898 repo = urepo
898 repo = urepo
899 hg.updaterepo(repo, revision, overwrite)
899 hg.updaterepo(repo, revision, overwrite)
900
900
901 @annotatesubrepoerror
901 @annotatesubrepoerror
902 def merge(self, state):
902 def merge(self, state):
903 self._get(state)
903 self._get(state)
904 cur = self._repo['.']
904 cur = self._repo['.']
905 dst = self._repo[state[1]]
905 dst = self._repo[state[1]]
906 anc = dst.ancestor(cur)
906 anc = dst.ancestor(cur)
907
907
908 def mergefunc():
908 def mergefunc():
909 if anc == cur and dst.branch() == cur.branch():
909 if anc == cur and dst.branch() == cur.branch():
910 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
910 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
911 hg.update(self._repo, state[1])
911 hg.update(self._repo, state[1])
912 elif anc == dst:
912 elif anc == dst:
913 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
913 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
914 else:
914 else:
915 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
915 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
916 hg.merge(self._repo, state[1], remind=False)
916 hg.merge(self._repo, state[1], remind=False)
917
917
918 wctx = self._repo[None]
918 wctx = self._repo[None]
919 if self.dirty():
919 if self.dirty():
920 if anc != dst:
920 if anc != dst:
921 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
921 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
922 mergefunc()
922 mergefunc()
923 else:
923 else:
924 mergefunc()
924 mergefunc()
925 else:
925 else:
926 mergefunc()
926 mergefunc()
927
927
928 @annotatesubrepoerror
928 @annotatesubrepoerror
929 def push(self, opts):
929 def push(self, opts):
930 force = opts.get('force')
930 force = opts.get('force')
931 newbranch = opts.get('new_branch')
931 newbranch = opts.get('new_branch')
932 ssh = opts.get('ssh')
932 ssh = opts.get('ssh')
933
933
934 # push subrepos depth-first for coherent ordering
934 # push subrepos depth-first for coherent ordering
935 c = self._repo['']
935 c = self._repo['']
936 subs = c.substate # only repos that are committed
936 subs = c.substate # only repos that are committed
937 for s in sorted(subs):
937 for s in sorted(subs):
938 if c.sub(s).push(opts) == 0:
938 if c.sub(s).push(opts) == 0:
939 return False
939 return False
940
940
941 dsturl = _abssource(self._repo, True)
941 dsturl = _abssource(self._repo, True)
942 if not force:
942 if not force:
943 if self.storeclean(dsturl):
943 if self.storeclean(dsturl):
944 self.ui.status(
944 self.ui.status(
945 _('no changes made to subrepo %s since last push to %s\n')
945 _('no changes made to subrepo %s since last push to %s\n')
946 % (subrelpath(self), dsturl))
946 % (subrelpath(self), dsturl))
947 return None
947 return None
948 self.ui.status(_('pushing subrepo %s to %s\n') %
948 self.ui.status(_('pushing subrepo %s to %s\n') %
949 (subrelpath(self), dsturl))
949 (subrelpath(self), dsturl))
950 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
950 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
951 res = exchange.push(self._repo, other, force, newbranch=newbranch)
951 res = exchange.push(self._repo, other, force, newbranch=newbranch)
952
952
953 # the repo is now clean
953 # the repo is now clean
954 self._cachestorehash(dsturl)
954 self._cachestorehash(dsturl)
955 return res.cgresult
955 return res.cgresult
956
956
957 @annotatesubrepoerror
957 @annotatesubrepoerror
958 def outgoing(self, ui, dest, opts):
958 def outgoing(self, ui, dest, opts):
959 if 'rev' in opts or 'branch' in opts:
959 if 'rev' in opts or 'branch' in opts:
960 opts = copy.copy(opts)
960 opts = copy.copy(opts)
961 opts.pop('rev', None)
961 opts.pop('rev', None)
962 opts.pop('branch', None)
962 opts.pop('branch', None)
963 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
963 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
964
964
965 @annotatesubrepoerror
965 @annotatesubrepoerror
966 def incoming(self, ui, source, opts):
966 def incoming(self, ui, source, opts):
967 if 'rev' in opts or 'branch' in opts:
967 if 'rev' in opts or 'branch' in opts:
968 opts = copy.copy(opts)
968 opts = copy.copy(opts)
969 opts.pop('rev', None)
969 opts.pop('rev', None)
970 opts.pop('branch', None)
970 opts.pop('branch', None)
971 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
971 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
972
972
973 @annotatesubrepoerror
973 @annotatesubrepoerror
974 def files(self):
974 def files(self):
975 rev = self._state[1]
975 rev = self._state[1]
976 ctx = self._repo[rev]
976 ctx = self._repo[rev]
977 return ctx.manifest().keys()
977 return ctx.manifest().keys()
978
978
979 def filedata(self, name, decode):
979 def filedata(self, name, decode):
980 rev = self._state[1]
980 rev = self._state[1]
981 data = self._repo[rev][name].data()
981 data = self._repo[rev][name].data()
982 if decode:
982 if decode:
983 data = self._repo.wwritedata(name, data)
983 data = self._repo.wwritedata(name, data)
984 return data
984 return data
985
985
986 def fileflags(self, name):
986 def fileflags(self, name):
987 rev = self._state[1]
987 rev = self._state[1]
988 ctx = self._repo[rev]
988 ctx = self._repo[rev]
989 return ctx.flags(name)
989 return ctx.flags(name)
990
990
991 @annotatesubrepoerror
991 @annotatesubrepoerror
992 def printfiles(self, ui, m, fm, fmt, subrepos):
992 def printfiles(self, ui, m, fm, fmt, subrepos):
993 # If the parent context is a workingctx, use the workingctx here for
993 # If the parent context is a workingctx, use the workingctx here for
994 # consistency.
994 # consistency.
995 if self._ctx.rev() is None:
995 if self._ctx.rev() is None:
996 ctx = self._repo[None]
996 ctx = self._repo[None]
997 else:
997 else:
998 rev = self._state[1]
998 rev = self._state[1]
999 ctx = self._repo[rev]
999 ctx = self._repo[rev]
1000 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1000 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1001
1001
1002 @annotatesubrepoerror
1002 @annotatesubrepoerror
1003 def getfileset(self, expr):
1003 def getfileset(self, expr):
1004 if self._ctx.rev() is None:
1004 if self._ctx.rev() is None:
1005 ctx = self._repo[None]
1005 ctx = self._repo[None]
1006 else:
1006 else:
1007 rev = self._state[1]
1007 rev = self._state[1]
1008 ctx = self._repo[rev]
1008 ctx = self._repo[rev]
1009
1009
1010 files = ctx.getfileset(expr)
1010 files = ctx.getfileset(expr)
1011
1011
1012 for subpath in ctx.substate:
1012 for subpath in ctx.substate:
1013 sub = ctx.sub(subpath)
1013 sub = ctx.sub(subpath)
1014
1014
1015 try:
1015 try:
1016 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1016 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1017 except error.LookupError:
1017 except error.LookupError:
1018 self.ui.status(_("skipping missing subrepository: %s\n")
1018 self.ui.status(_("skipping missing subrepository: %s\n")
1019 % self.wvfs.reljoin(reporelpath(self), subpath))
1019 % self.wvfs.reljoin(reporelpath(self), subpath))
1020 return files
1020 return files
1021
1021
1022 def walk(self, match):
1022 def walk(self, match):
1023 ctx = self._repo[None]
1023 ctx = self._repo[None]
1024 return ctx.walk(match)
1024 return ctx.walk(match)
1025
1025
1026 @annotatesubrepoerror
1026 @annotatesubrepoerror
1027 def forget(self, match, prefix):
1027 def forget(self, match, prefix):
1028 return cmdutil.forget(self.ui, self._repo, match,
1028 return cmdutil.forget(self.ui, self._repo, match,
1029 self.wvfs.reljoin(prefix, self._path), True)
1029 self.wvfs.reljoin(prefix, self._path), True)
1030
1030
1031 @annotatesubrepoerror
1031 @annotatesubrepoerror
1032 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1032 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1033 return cmdutil.remove(self.ui, self._repo, matcher,
1033 return cmdutil.remove(self.ui, self._repo, matcher,
1034 self.wvfs.reljoin(prefix, self._path),
1034 self.wvfs.reljoin(prefix, self._path),
1035 after, force, subrepos)
1035 after, force, subrepos)
1036
1036
1037 @annotatesubrepoerror
1037 @annotatesubrepoerror
1038 def revert(self, substate, *pats, **opts):
1038 def revert(self, substate, *pats, **opts):
1039 # reverting a subrepo is a 2 step process:
1039 # reverting a subrepo is a 2 step process:
1040 # 1. if the no_backup is not set, revert all modified
1040 # 1. if the no_backup is not set, revert all modified
1041 # files inside the subrepo
1041 # files inside the subrepo
1042 # 2. update the subrepo to the revision specified in
1042 # 2. update the subrepo to the revision specified in
1043 # the corresponding substate dictionary
1043 # the corresponding substate dictionary
1044 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1044 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1045 if not opts.get('no_backup'):
1045 if not opts.get('no_backup'):
1046 # Revert all files on the subrepo, creating backups
1046 # Revert all files on the subrepo, creating backups
1047 # Note that this will not recursively revert subrepos
1047 # Note that this will not recursively revert subrepos
1048 # We could do it if there was a set:subrepos() predicate
1048 # We could do it if there was a set:subrepos() predicate
1049 opts = opts.copy()
1049 opts = opts.copy()
1050 opts['date'] = None
1050 opts['date'] = None
1051 opts['rev'] = substate[1]
1051 opts['rev'] = substate[1]
1052
1052
1053 self.filerevert(*pats, **opts)
1053 self.filerevert(*pats, **opts)
1054
1054
1055 # Update the repo to the revision specified in the given substate
1055 # Update the repo to the revision specified in the given substate
1056 if not opts.get('dry_run'):
1056 if not opts.get('dry_run'):
1057 self.get(substate, overwrite=True)
1057 self.get(substate, overwrite=True)
1058
1058
1059 def filerevert(self, *pats, **opts):
1059 def filerevert(self, *pats, **opts):
1060 ctx = self._repo[opts['rev']]
1060 ctx = self._repo[opts['rev']]
1061 parents = self._repo.dirstate.parents()
1061 parents = self._repo.dirstate.parents()
1062 if opts.get('all'):
1062 if opts.get('all'):
1063 pats = ['set:modified()']
1063 pats = ['set:modified()']
1064 else:
1064 else:
1065 pats = []
1065 pats = []
1066 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1066 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1067
1067
1068 def shortid(self, revid):
1068 def shortid(self, revid):
1069 return revid[:12]
1069 return revid[:12]
1070
1070
1071 def verify(self):
1071 def verify(self):
1072 try:
1072 try:
1073 rev = self._state[1]
1073 rev = self._state[1]
1074 ctx = self._repo.unfiltered()[rev]
1074 ctx = self._repo.unfiltered()[rev]
1075 if ctx.hidden():
1075 if ctx.hidden():
1076 # Since hidden revisions aren't pushed/pulled, it seems worth an
1076 # Since hidden revisions aren't pushed/pulled, it seems worth an
1077 # explicit warning.
1077 # explicit warning.
1078 ui = self._repo.ui
1078 ui = self._repo.ui
1079 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1079 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1080 (self._relpath, node.short(self._ctx.node())))
1080 (self._relpath, node.short(self._ctx.node())))
1081 return 0
1081 return 0
1082 except error.RepoLookupError:
1082 except error.RepoLookupError:
1083 # A missing subrepo revision may be a case of needing to pull it, so
1083 # A missing subrepo revision may be a case of needing to pull it, so
1084 # don't treat this as an error.
1084 # don't treat this as an error.
1085 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1085 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1086 (self._relpath, node.short(self._ctx.node())))
1086 (self._relpath, node.short(self._ctx.node())))
1087 return 0
1087 return 0
1088
1088
1089 @propertycache
1089 @propertycache
1090 def wvfs(self):
1090 def wvfs(self):
1091 """return own wvfs for efficiency and consistency
1091 """return own wvfs for efficiency and consistency
1092 """
1092 """
1093 return self._repo.wvfs
1093 return self._repo.wvfs
1094
1094
1095 @propertycache
1095 @propertycache
1096 def _relpath(self):
1096 def _relpath(self):
1097 """return path to this subrepository as seen from outermost repository
1097 """return path to this subrepository as seen from outermost repository
1098 """
1098 """
1099 # Keep consistent dir separators by avoiding vfs.join(self._path)
1099 # Keep consistent dir separators by avoiding vfs.join(self._path)
1100 return reporelpath(self._repo)
1100 return reporelpath(self._repo)
1101
1101
1102 class svnsubrepo(abstractsubrepo):
1102 class svnsubrepo(abstractsubrepo):
1103 def __init__(self, ctx, path, state, allowcreate):
1103 def __init__(self, ctx, path, state, allowcreate):
1104 super(svnsubrepo, self).__init__(ctx, path)
1104 super(svnsubrepo, self).__init__(ctx, path)
1105 self._state = state
1105 self._state = state
1106 self._exe = util.findexe('svn')
1106 self._exe = util.findexe('svn')
1107 if not self._exe:
1107 if not self._exe:
1108 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1108 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1109 % self._path)
1109 % self._path)
1110
1110
1111 def _svncommand(self, commands, filename='', failok=False):
1111 def _svncommand(self, commands, filename='', failok=False):
1112 cmd = [self._exe]
1112 cmd = [self._exe]
1113 extrakw = {}
1113 extrakw = {}
1114 if not self.ui.interactive():
1114 if not self.ui.interactive():
1115 # Making stdin be a pipe should prevent svn from behaving
1115 # Making stdin be a pipe should prevent svn from behaving
1116 # interactively even if we can't pass --non-interactive.
1116 # interactively even if we can't pass --non-interactive.
1117 extrakw['stdin'] = subprocess.PIPE
1117 extrakw['stdin'] = subprocess.PIPE
1118 # Starting in svn 1.5 --non-interactive is a global flag
1118 # Starting in svn 1.5 --non-interactive is a global flag
1119 # instead of being per-command, but we need to support 1.4 so
1119 # instead of being per-command, but we need to support 1.4 so
1120 # we have to be intelligent about what commands take
1120 # we have to be intelligent about what commands take
1121 # --non-interactive.
1121 # --non-interactive.
1122 if commands[0] in ('update', 'checkout', 'commit'):
1122 if commands[0] in ('update', 'checkout', 'commit'):
1123 cmd.append('--non-interactive')
1123 cmd.append('--non-interactive')
1124 cmd.extend(commands)
1124 cmd.extend(commands)
1125 if filename is not None:
1125 if filename is not None:
1126 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1126 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1127 self._path, filename)
1127 self._path, filename)
1128 cmd.append(path)
1128 cmd.append(path)
1129 env = dict(encoding.environ)
1129 env = dict(encoding.environ)
1130 # Avoid localized output, preserve current locale for everything else.
1130 # Avoid localized output, preserve current locale for everything else.
1131 lc_all = env.get('LC_ALL')
1131 lc_all = env.get('LC_ALL')
1132 if lc_all:
1132 if lc_all:
1133 env['LANG'] = lc_all
1133 env['LANG'] = lc_all
1134 del env['LC_ALL']
1134 del env['LC_ALL']
1135 env['LC_MESSAGES'] = 'C'
1135 env['LC_MESSAGES'] = 'C'
1136 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1136 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1137 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1137 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1138 universal_newlines=True, env=env, **extrakw)
1138 universal_newlines=True, env=env, **extrakw)
1139 stdout, stderr = p.communicate()
1139 stdout, stderr = p.communicate()
1140 stderr = stderr.strip()
1140 stderr = stderr.strip()
1141 if not failok:
1141 if not failok:
1142 if p.returncode:
1142 if p.returncode:
1143 raise error.Abort(stderr or 'exited with code %d'
1143 raise error.Abort(stderr or 'exited with code %d'
1144 % p.returncode)
1144 % p.returncode)
1145 if stderr:
1145 if stderr:
1146 self.ui.warn(stderr + '\n')
1146 self.ui.warn(stderr + '\n')
1147 return stdout, stderr
1147 return stdout, stderr
1148
1148
1149 @propertycache
1149 @propertycache
1150 def _svnversion(self):
1150 def _svnversion(self):
1151 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1151 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1152 m = re.search(r'^(\d+)\.(\d+)', output)
1152 m = re.search(r'^(\d+)\.(\d+)', output)
1153 if not m:
1153 if not m:
1154 raise error.Abort(_('cannot retrieve svn tool version'))
1154 raise error.Abort(_('cannot retrieve svn tool version'))
1155 return (int(m.group(1)), int(m.group(2)))
1155 return (int(m.group(1)), int(m.group(2)))
1156
1156
1157 def _wcrevs(self):
1157 def _wcrevs(self):
1158 # Get the working directory revision as well as the last
1158 # Get the working directory revision as well as the last
1159 # commit revision so we can compare the subrepo state with
1159 # commit revision so we can compare the subrepo state with
1160 # both. We used to store the working directory one.
1160 # both. We used to store the working directory one.
1161 output, err = self._svncommand(['info', '--xml'])
1161 output, err = self._svncommand(['info', '--xml'])
1162 doc = xml.dom.minidom.parseString(output)
1162 doc = xml.dom.minidom.parseString(output)
1163 entries = doc.getElementsByTagName('entry')
1163 entries = doc.getElementsByTagName('entry')
1164 lastrev, rev = '0', '0'
1164 lastrev, rev = '0', '0'
1165 if entries:
1165 if entries:
1166 rev = str(entries[0].getAttribute('revision')) or '0'
1166 rev = str(entries[0].getAttribute('revision')) or '0'
1167 commits = entries[0].getElementsByTagName('commit')
1167 commits = entries[0].getElementsByTagName('commit')
1168 if commits:
1168 if commits:
1169 lastrev = str(commits[0].getAttribute('revision')) or '0'
1169 lastrev = str(commits[0].getAttribute('revision')) or '0'
1170 return (lastrev, rev)
1170 return (lastrev, rev)
1171
1171
1172 def _wcrev(self):
1172 def _wcrev(self):
1173 return self._wcrevs()[0]
1173 return self._wcrevs()[0]
1174
1174
1175 def _wcchanged(self):
1175 def _wcchanged(self):
1176 """Return (changes, extchanges, missing) where changes is True
1176 """Return (changes, extchanges, missing) where changes is True
1177 if the working directory was changed, extchanges is
1177 if the working directory was changed, extchanges is
1178 True if any of these changes concern an external entry and missing
1178 True if any of these changes concern an external entry and missing
1179 is True if any change is a missing entry.
1179 is True if any change is a missing entry.
1180 """
1180 """
1181 output, err = self._svncommand(['status', '--xml'])
1181 output, err = self._svncommand(['status', '--xml'])
1182 externals, changes, missing = [], [], []
1182 externals, changes, missing = [], [], []
1183 doc = xml.dom.minidom.parseString(output)
1183 doc = xml.dom.minidom.parseString(output)
1184 for e in doc.getElementsByTagName('entry'):
1184 for e in doc.getElementsByTagName('entry'):
1185 s = e.getElementsByTagName('wc-status')
1185 s = e.getElementsByTagName('wc-status')
1186 if not s:
1186 if not s:
1187 continue
1187 continue
1188 item = s[0].getAttribute('item')
1188 item = s[0].getAttribute('item')
1189 props = s[0].getAttribute('props')
1189 props = s[0].getAttribute('props')
1190 path = e.getAttribute('path')
1190 path = e.getAttribute('path')
1191 if item == 'external':
1191 if item == 'external':
1192 externals.append(path)
1192 externals.append(path)
1193 elif item == 'missing':
1193 elif item == 'missing':
1194 missing.append(path)
1194 missing.append(path)
1195 if (item not in ('', 'normal', 'unversioned', 'external')
1195 if (item not in ('', 'normal', 'unversioned', 'external')
1196 or props not in ('', 'none', 'normal')):
1196 or props not in ('', 'none', 'normal')):
1197 changes.append(path)
1197 changes.append(path)
1198 for path in changes:
1198 for path in changes:
1199 for ext in externals:
1199 for ext in externals:
1200 if path == ext or path.startswith(ext + pycompat.ossep):
1200 if path == ext or path.startswith(ext + pycompat.ossep):
1201 return True, True, bool(missing)
1201 return True, True, bool(missing)
1202 return bool(changes), False, bool(missing)
1202 return bool(changes), False, bool(missing)
1203
1203
1204 def dirty(self, ignoreupdate=False):
1204 def dirty(self, ignoreupdate=False):
1205 if not self._wcchanged()[0]:
1205 if not self._wcchanged()[0]:
1206 if self._state[1] in self._wcrevs() or ignoreupdate:
1206 if self._state[1] in self._wcrevs() or ignoreupdate:
1207 return False
1207 return False
1208 return True
1208 return True
1209
1209
1210 def basestate(self):
1210 def basestate(self):
1211 lastrev, rev = self._wcrevs()
1211 lastrev, rev = self._wcrevs()
1212 if lastrev != rev:
1212 if lastrev != rev:
1213 # Last committed rev is not the same than rev. We would
1213 # Last committed rev is not the same than rev. We would
1214 # like to take lastrev but we do not know if the subrepo
1214 # like to take lastrev but we do not know if the subrepo
1215 # URL exists at lastrev. Test it and fallback to rev it
1215 # URL exists at lastrev. Test it and fallback to rev it
1216 # is not there.
1216 # is not there.
1217 try:
1217 try:
1218 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1218 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1219 return lastrev
1219 return lastrev
1220 except error.Abort:
1220 except error.Abort:
1221 pass
1221 pass
1222 return rev
1222 return rev
1223
1223
1224 @annotatesubrepoerror
1224 @annotatesubrepoerror
1225 def commit(self, text, user, date):
1225 def commit(self, text, user, date):
1226 # user and date are out of our hands since svn is centralized
1226 # user and date are out of our hands since svn is centralized
1227 changed, extchanged, missing = self._wcchanged()
1227 changed, extchanged, missing = self._wcchanged()
1228 if not changed:
1228 if not changed:
1229 return self.basestate()
1229 return self.basestate()
1230 if extchanged:
1230 if extchanged:
1231 # Do not try to commit externals
1231 # Do not try to commit externals
1232 raise error.Abort(_('cannot commit svn externals'))
1232 raise error.Abort(_('cannot commit svn externals'))
1233 if missing:
1233 if missing:
1234 # svn can commit with missing entries but aborting like hg
1234 # svn can commit with missing entries but aborting like hg
1235 # seems a better approach.
1235 # seems a better approach.
1236 raise error.Abort(_('cannot commit missing svn entries'))
1236 raise error.Abort(_('cannot commit missing svn entries'))
1237 commitinfo, err = self._svncommand(['commit', '-m', text])
1237 commitinfo, err = self._svncommand(['commit', '-m', text])
1238 self.ui.status(commitinfo)
1238 self.ui.status(commitinfo)
1239 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1239 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1240 if not newrev:
1240 if not newrev:
1241 if not commitinfo.strip():
1241 if not commitinfo.strip():
1242 # Sometimes, our definition of "changed" differs from
1242 # Sometimes, our definition of "changed" differs from
1243 # svn one. For instance, svn ignores missing files
1243 # svn one. For instance, svn ignores missing files
1244 # when committing. If there are only missing files, no
1244 # when committing. If there are only missing files, no
1245 # commit is made, no output and no error code.
1245 # commit is made, no output and no error code.
1246 raise error.Abort(_('failed to commit svn changes'))
1246 raise error.Abort(_('failed to commit svn changes'))
1247 raise error.Abort(commitinfo.splitlines()[-1])
1247 raise error.Abort(commitinfo.splitlines()[-1])
1248 newrev = newrev.groups()[0]
1248 newrev = newrev.groups()[0]
1249 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1249 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1250 return newrev
1250 return newrev
1251
1251
1252 @annotatesubrepoerror
1252 @annotatesubrepoerror
1253 def remove(self):
1253 def remove(self):
1254 if self.dirty():
1254 if self.dirty():
1255 self.ui.warn(_('not removing repo %s because '
1255 self.ui.warn(_('not removing repo %s because '
1256 'it has changes.\n') % self._path)
1256 'it has changes.\n') % self._path)
1257 return
1257 return
1258 self.ui.note(_('removing subrepo %s\n') % self._path)
1258 self.ui.note(_('removing subrepo %s\n') % self._path)
1259
1259
1260 self.wvfs.rmtree(forcibly=True)
1260 self.wvfs.rmtree(forcibly=True)
1261 try:
1261 try:
1262 pwvfs = self._ctx.repo().wvfs
1262 pwvfs = self._ctx.repo().wvfs
1263 pwvfs.removedirs(pwvfs.dirname(self._path))
1263 pwvfs.removedirs(pwvfs.dirname(self._path))
1264 except OSError:
1264 except OSError:
1265 pass
1265 pass
1266
1266
1267 @annotatesubrepoerror
1267 @annotatesubrepoerror
1268 def get(self, state, overwrite=False):
1268 def get(self, state, overwrite=False):
1269 if overwrite:
1269 if overwrite:
1270 self._svncommand(['revert', '--recursive'])
1270 self._svncommand(['revert', '--recursive'])
1271 args = ['checkout']
1271 args = ['checkout']
1272 if self._svnversion >= (1, 5):
1272 if self._svnversion >= (1, 5):
1273 args.append('--force')
1273 args.append('--force')
1274 # The revision must be specified at the end of the URL to properly
1274 # The revision must be specified at the end of the URL to properly
1275 # update to a directory which has since been deleted and recreated.
1275 # update to a directory which has since been deleted and recreated.
1276 args.append('%s@%s' % (state[0], state[1]))
1276 args.append('%s@%s' % (state[0], state[1]))
1277 status, err = self._svncommand(args, failok=True)
1277 status, err = self._svncommand(args, failok=True)
1278 _sanitize(self.ui, self.wvfs, '.svn')
1278 _sanitize(self.ui, self.wvfs, '.svn')
1279 if not re.search('Checked out revision [0-9]+.', status):
1279 if not re.search('Checked out revision [0-9]+.', status):
1280 if ('is already a working copy for a different URL' in err
1280 if ('is already a working copy for a different URL' in err
1281 and (self._wcchanged()[:2] == (False, False))):
1281 and (self._wcchanged()[:2] == (False, False))):
1282 # obstructed but clean working copy, so just blow it away.
1282 # obstructed but clean working copy, so just blow it away.
1283 self.remove()
1283 self.remove()
1284 self.get(state, overwrite=False)
1284 self.get(state, overwrite=False)
1285 return
1285 return
1286 raise error.Abort((status or err).splitlines()[-1])
1286 raise error.Abort((status or err).splitlines()[-1])
1287 self.ui.status(status)
1287 self.ui.status(status)
1288
1288
1289 @annotatesubrepoerror
1289 @annotatesubrepoerror
1290 def merge(self, state):
1290 def merge(self, state):
1291 old = self._state[1]
1291 old = self._state[1]
1292 new = state[1]
1292 new = state[1]
1293 wcrev = self._wcrev()
1293 wcrev = self._wcrev()
1294 if new != wcrev:
1294 if new != wcrev:
1295 dirty = old == wcrev or self._wcchanged()[0]
1295 dirty = old == wcrev or self._wcchanged()[0]
1296 if _updateprompt(self.ui, self, dirty, wcrev, new):
1296 if _updateprompt(self.ui, self, dirty, wcrev, new):
1297 self.get(state, False)
1297 self.get(state, False)
1298
1298
1299 def push(self, opts):
1299 def push(self, opts):
1300 # push is a no-op for SVN
1300 # push is a no-op for SVN
1301 return True
1301 return True
1302
1302
1303 @annotatesubrepoerror
1303 @annotatesubrepoerror
1304 def files(self):
1304 def files(self):
1305 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1305 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1306 doc = xml.dom.minidom.parseString(output)
1306 doc = xml.dom.minidom.parseString(output)
1307 paths = []
1307 paths = []
1308 for e in doc.getElementsByTagName('entry'):
1308 for e in doc.getElementsByTagName('entry'):
1309 kind = str(e.getAttribute('kind'))
1309 kind = str(e.getAttribute('kind'))
1310 if kind != 'file':
1310 if kind != 'file':
1311 continue
1311 continue
1312 name = ''.join(c.data for c
1312 name = ''.join(c.data for c
1313 in e.getElementsByTagName('name')[0].childNodes
1313 in e.getElementsByTagName('name')[0].childNodes
1314 if c.nodeType == c.TEXT_NODE)
1314 if c.nodeType == c.TEXT_NODE)
1315 paths.append(name.encode('utf-8'))
1315 paths.append(name.encode('utf-8'))
1316 return paths
1316 return paths
1317
1317
1318 def filedata(self, name, decode):
1318 def filedata(self, name, decode):
1319 return self._svncommand(['cat'], name)[0]
1319 return self._svncommand(['cat'], name)[0]
1320
1320
1321
1321
1322 class gitsubrepo(abstractsubrepo):
1322 class gitsubrepo(abstractsubrepo):
1323 def __init__(self, ctx, path, state, allowcreate):
1323 def __init__(self, ctx, path, state, allowcreate):
1324 super(gitsubrepo, self).__init__(ctx, path)
1324 super(gitsubrepo, self).__init__(ctx, path)
1325 self._state = state
1325 self._state = state
1326 self._abspath = ctx.repo().wjoin(path)
1326 self._abspath = ctx.repo().wjoin(path)
1327 self._subparent = ctx.repo()
1327 self._subparent = ctx.repo()
1328 self._ensuregit()
1328 self._ensuregit()
1329
1329
1330 def _ensuregit(self):
1330 def _ensuregit(self):
1331 try:
1331 try:
1332 self._gitexecutable = 'git'
1332 self._gitexecutable = 'git'
1333 out, err = self._gitnodir(['--version'])
1333 out, err = self._gitnodir(['--version'])
1334 except OSError as e:
1334 except OSError as e:
1335 genericerror = _("error executing git for subrepo '%s': %s")
1335 genericerror = _("error executing git for subrepo '%s': %s")
1336 notfoundhint = _("check git is installed and in your PATH")
1336 notfoundhint = _("check git is installed and in your PATH")
1337 if e.errno != errno.ENOENT:
1337 if e.errno != errno.ENOENT:
1338 raise error.Abort(genericerror % (self._path, e.strerror))
1338 raise error.Abort(genericerror % (self._path, e.strerror))
1339 elif pycompat.osname == 'nt':
1339 elif pycompat.osname == 'nt':
1340 try:
1340 try:
1341 self._gitexecutable = 'git.cmd'
1341 self._gitexecutable = 'git.cmd'
1342 out, err = self._gitnodir(['--version'])
1342 out, err = self._gitnodir(['--version'])
1343 except OSError as e2:
1343 except OSError as e2:
1344 if e2.errno == errno.ENOENT:
1344 if e2.errno == errno.ENOENT:
1345 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1345 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1346 " for subrepo '%s'") % self._path,
1346 " for subrepo '%s'") % self._path,
1347 hint=notfoundhint)
1347 hint=notfoundhint)
1348 else:
1348 else:
1349 raise error.Abort(genericerror % (self._path,
1349 raise error.Abort(genericerror % (self._path,
1350 e2.strerror))
1350 e2.strerror))
1351 else:
1351 else:
1352 raise error.Abort(_("couldn't find git for subrepo '%s'")
1352 raise error.Abort(_("couldn't find git for subrepo '%s'")
1353 % self._path, hint=notfoundhint)
1353 % self._path, hint=notfoundhint)
1354 versionstatus = self._checkversion(out)
1354 versionstatus = self._checkversion(out)
1355 if versionstatus == 'unknown':
1355 if versionstatus == 'unknown':
1356 self.ui.warn(_('cannot retrieve git version\n'))
1356 self.ui.warn(_('cannot retrieve git version\n'))
1357 elif versionstatus == 'abort':
1357 elif versionstatus == 'abort':
1358 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1358 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1359 elif versionstatus == 'warning':
1359 elif versionstatus == 'warning':
1360 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1360 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1361
1361
1362 @staticmethod
1362 @staticmethod
1363 def _gitversion(out):
1363 def _gitversion(out):
1364 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1364 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1365 if m:
1365 if m:
1366 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1366 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1367
1367
1368 m = re.search(r'^git version (\d+)\.(\d+)', out)
1368 m = re.search(r'^git version (\d+)\.(\d+)', out)
1369 if m:
1369 if m:
1370 return (int(m.group(1)), int(m.group(2)), 0)
1370 return (int(m.group(1)), int(m.group(2)), 0)
1371
1371
1372 return -1
1372 return -1
1373
1373
1374 @staticmethod
1374 @staticmethod
1375 def _checkversion(out):
1375 def _checkversion(out):
1376 '''ensure git version is new enough
1376 '''ensure git version is new enough
1377
1377
1378 >>> _checkversion = gitsubrepo._checkversion
1378 >>> _checkversion = gitsubrepo._checkversion
1379 >>> _checkversion('git version 1.6.0')
1379 >>> _checkversion('git version 1.6.0')
1380 'ok'
1380 'ok'
1381 >>> _checkversion('git version 1.8.5')
1381 >>> _checkversion('git version 1.8.5')
1382 'ok'
1382 'ok'
1383 >>> _checkversion('git version 1.4.0')
1383 >>> _checkversion('git version 1.4.0')
1384 'abort'
1384 'abort'
1385 >>> _checkversion('git version 1.5.0')
1385 >>> _checkversion('git version 1.5.0')
1386 'warning'
1386 'warning'
1387 >>> _checkversion('git version 1.9-rc0')
1387 >>> _checkversion('git version 1.9-rc0')
1388 'ok'
1388 'ok'
1389 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1389 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1390 'ok'
1390 'ok'
1391 >>> _checkversion('git version 1.9.0.GIT')
1391 >>> _checkversion('git version 1.9.0.GIT')
1392 'ok'
1392 'ok'
1393 >>> _checkversion('git version 12345')
1393 >>> _checkversion('git version 12345')
1394 'unknown'
1394 'unknown'
1395 >>> _checkversion('no')
1395 >>> _checkversion('no')
1396 'unknown'
1396 'unknown'
1397 '''
1397 '''
1398 version = gitsubrepo._gitversion(out)
1398 version = gitsubrepo._gitversion(out)
1399 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1399 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1400 # despite the docstring comment. For now, error on 1.4.0, warn on
1400 # despite the docstring comment. For now, error on 1.4.0, warn on
1401 # 1.5.0 but attempt to continue.
1401 # 1.5.0 but attempt to continue.
1402 if version == -1:
1402 if version == -1:
1403 return 'unknown'
1403 return 'unknown'
1404 if version < (1, 5, 0):
1404 if version < (1, 5, 0):
1405 return 'abort'
1405 return 'abort'
1406 elif version < (1, 6, 0):
1406 elif version < (1, 6, 0):
1407 return 'warning'
1407 return 'warning'
1408 return 'ok'
1408 return 'ok'
1409
1409
1410 def _gitcommand(self, commands, env=None, stream=False):
1410 def _gitcommand(self, commands, env=None, stream=False):
1411 return self._gitdir(commands, env=env, stream=stream)[0]
1411 return self._gitdir(commands, env=env, stream=stream)[0]
1412
1412
1413 def _gitdir(self, commands, env=None, stream=False):
1413 def _gitdir(self, commands, env=None, stream=False):
1414 return self._gitnodir(commands, env=env, stream=stream,
1414 return self._gitnodir(commands, env=env, stream=stream,
1415 cwd=self._abspath)
1415 cwd=self._abspath)
1416
1416
1417 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1417 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1418 """Calls the git command
1418 """Calls the git command
1419
1419
1420 The methods tries to call the git command. versions prior to 1.6.0
1420 The methods tries to call the git command. versions prior to 1.6.0
1421 are not supported and very probably fail.
1421 are not supported and very probably fail.
1422 """
1422 """
1423 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1423 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1424 if env is None:
1424 if env is None:
1425 env = encoding.environ.copy()
1425 env = encoding.environ.copy()
1426 # disable localization for Git output (issue5176)
1426 # disable localization for Git output (issue5176)
1427 env['LC_ALL'] = 'C'
1427 env['LC_ALL'] = 'C'
1428 # fix for Git CVE-2015-7545
1428 # fix for Git CVE-2015-7545
1429 if 'GIT_ALLOW_PROTOCOL' not in env:
1429 if 'GIT_ALLOW_PROTOCOL' not in env:
1430 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1430 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1431 # unless ui.quiet is set, print git's stderr,
1431 # unless ui.quiet is set, print git's stderr,
1432 # which is mostly progress and useful info
1432 # which is mostly progress and useful info
1433 errpipe = None
1433 errpipe = None
1434 if self.ui.quiet:
1434 if self.ui.quiet:
1435 errpipe = open(os.devnull, 'w')
1435 errpipe = open(os.devnull, 'w')
1436 if self.ui._colormode and len(commands) and commands[0] == "diff":
1436 if self.ui._colormode and len(commands) and commands[0] == "diff":
1437 # insert the argument in the front,
1437 # insert the argument in the front,
1438 # the end of git diff arguments is used for paths
1438 # the end of git diff arguments is used for paths
1439 commands.insert(1, '--color')
1439 commands.insert(1, '--color')
1440 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1440 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1441 cwd=cwd, env=env, close_fds=util.closefds,
1441 cwd=cwd, env=env, close_fds=util.closefds,
1442 stdout=subprocess.PIPE, stderr=errpipe)
1442 stdout=subprocess.PIPE, stderr=errpipe)
1443 if stream:
1443 if stream:
1444 return p.stdout, None
1444 return p.stdout, None
1445
1445
1446 retdata = p.stdout.read().strip()
1446 retdata = p.stdout.read().strip()
1447 # wait for the child to exit to avoid race condition.
1447 # wait for the child to exit to avoid race condition.
1448 p.wait()
1448 p.wait()
1449
1449
1450 if p.returncode != 0 and p.returncode != 1:
1450 if p.returncode != 0 and p.returncode != 1:
1451 # there are certain error codes that are ok
1451 # there are certain error codes that are ok
1452 command = commands[0]
1452 command = commands[0]
1453 if command in ('cat-file', 'symbolic-ref'):
1453 if command in ('cat-file', 'symbolic-ref'):
1454 return retdata, p.returncode
1454 return retdata, p.returncode
1455 # for all others, abort
1455 # for all others, abort
1456 raise error.Abort(_('git %s error %d in %s') %
1456 raise error.Abort(_('git %s error %d in %s') %
1457 (command, p.returncode, self._relpath))
1457 (command, p.returncode, self._relpath))
1458
1458
1459 return retdata, p.returncode
1459 return retdata, p.returncode
1460
1460
1461 def _gitmissing(self):
1461 def _gitmissing(self):
1462 return not self.wvfs.exists('.git')
1462 return not self.wvfs.exists('.git')
1463
1463
1464 def _gitstate(self):
1464 def _gitstate(self):
1465 return self._gitcommand(['rev-parse', 'HEAD'])
1465 return self._gitcommand(['rev-parse', 'HEAD'])
1466
1466
1467 def _gitcurrentbranch(self):
1467 def _gitcurrentbranch(self):
1468 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1468 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1469 if err:
1469 if err:
1470 current = None
1470 current = None
1471 return current
1471 return current
1472
1472
1473 def _gitremote(self, remote):
1473 def _gitremote(self, remote):
1474 out = self._gitcommand(['remote', 'show', '-n', remote])
1474 out = self._gitcommand(['remote', 'show', '-n', remote])
1475 line = out.split('\n')[1]
1475 line = out.split('\n')[1]
1476 i = line.index('URL: ') + len('URL: ')
1476 i = line.index('URL: ') + len('URL: ')
1477 return line[i:]
1477 return line[i:]
1478
1478
1479 def _githavelocally(self, revision):
1479 def _githavelocally(self, revision):
1480 out, code = self._gitdir(['cat-file', '-e', revision])
1480 out, code = self._gitdir(['cat-file', '-e', revision])
1481 return code == 0
1481 return code == 0
1482
1482
1483 def _gitisancestor(self, r1, r2):
1483 def _gitisancestor(self, r1, r2):
1484 base = self._gitcommand(['merge-base', r1, r2])
1484 base = self._gitcommand(['merge-base', r1, r2])
1485 return base == r1
1485 return base == r1
1486
1486
1487 def _gitisbare(self):
1487 def _gitisbare(self):
1488 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1488 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1489
1489
1490 def _gitupdatestat(self):
1490 def _gitupdatestat(self):
1491 """This must be run before git diff-index.
1491 """This must be run before git diff-index.
1492 diff-index only looks at changes to file stat;
1492 diff-index only looks at changes to file stat;
1493 this command looks at file contents and updates the stat."""
1493 this command looks at file contents and updates the stat."""
1494 self._gitcommand(['update-index', '-q', '--refresh'])
1494 self._gitcommand(['update-index', '-q', '--refresh'])
1495
1495
1496 def _gitbranchmap(self):
1496 def _gitbranchmap(self):
1497 '''returns 2 things:
1497 '''returns 2 things:
1498 a map from git branch to revision
1498 a map from git branch to revision
1499 a map from revision to branches'''
1499 a map from revision to branches'''
1500 branch2rev = {}
1500 branch2rev = {}
1501 rev2branch = {}
1501 rev2branch = {}
1502
1502
1503 out = self._gitcommand(['for-each-ref', '--format',
1503 out = self._gitcommand(['for-each-ref', '--format',
1504 '%(objectname) %(refname)'])
1504 '%(objectname) %(refname)'])
1505 for line in out.split('\n'):
1505 for line in out.split('\n'):
1506 revision, ref = line.split(' ')
1506 revision, ref = line.split(' ')
1507 if (not ref.startswith('refs/heads/') and
1507 if (not ref.startswith('refs/heads/') and
1508 not ref.startswith('refs/remotes/')):
1508 not ref.startswith('refs/remotes/')):
1509 continue
1509 continue
1510 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1510 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1511 continue # ignore remote/HEAD redirects
1511 continue # ignore remote/HEAD redirects
1512 branch2rev[ref] = revision
1512 branch2rev[ref] = revision
1513 rev2branch.setdefault(revision, []).append(ref)
1513 rev2branch.setdefault(revision, []).append(ref)
1514 return branch2rev, rev2branch
1514 return branch2rev, rev2branch
1515
1515
1516 def _gittracking(self, branches):
1516 def _gittracking(self, branches):
1517 'return map of remote branch to local tracking branch'
1517 'return map of remote branch to local tracking branch'
1518 # assumes no more than one local tracking branch for each remote
1518 # assumes no more than one local tracking branch for each remote
1519 tracking = {}
1519 tracking = {}
1520 for b in branches:
1520 for b in branches:
1521 if b.startswith('refs/remotes/'):
1521 if b.startswith('refs/remotes/'):
1522 continue
1522 continue
1523 bname = b.split('/', 2)[2]
1523 bname = b.split('/', 2)[2]
1524 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1524 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1525 if remote:
1525 if remote:
1526 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1526 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1527 tracking['refs/remotes/%s/%s' %
1527 tracking['refs/remotes/%s/%s' %
1528 (remote, ref.split('/', 2)[2])] = b
1528 (remote, ref.split('/', 2)[2])] = b
1529 return tracking
1529 return tracking
1530
1530
1531 def _abssource(self, source):
1531 def _abssource(self, source):
1532 if '://' not in source:
1532 if '://' not in source:
1533 # recognize the scp syntax as an absolute source
1533 # recognize the scp syntax as an absolute source
1534 colon = source.find(':')
1534 colon = source.find(':')
1535 if colon != -1 and '/' not in source[:colon]:
1535 if colon != -1 and '/' not in source[:colon]:
1536 return source
1536 return source
1537 self._subsource = source
1537 self._subsource = source
1538 return _abssource(self)
1538 return _abssource(self)
1539
1539
1540 def _fetch(self, source, revision):
1540 def _fetch(self, source, revision):
1541 if self._gitmissing():
1541 if self._gitmissing():
1542 source = self._abssource(source)
1542 source = self._abssource(source)
1543 self.ui.status(_('cloning subrepo %s from %s\n') %
1543 self.ui.status(_('cloning subrepo %s from %s\n') %
1544 (self._relpath, source))
1544 (self._relpath, source))
1545 self._gitnodir(['clone', source, self._abspath])
1545 self._gitnodir(['clone', source, self._abspath])
1546 if self._githavelocally(revision):
1546 if self._githavelocally(revision):
1547 return
1547 return
1548 self.ui.status(_('pulling subrepo %s from %s\n') %
1548 self.ui.status(_('pulling subrepo %s from %s\n') %
1549 (self._relpath, self._gitremote('origin')))
1549 (self._relpath, self._gitremote('origin')))
1550 # try only origin: the originally cloned repo
1550 # try only origin: the originally cloned repo
1551 self._gitcommand(['fetch'])
1551 self._gitcommand(['fetch'])
1552 if not self._githavelocally(revision):
1552 if not self._githavelocally(revision):
1553 raise error.Abort(_("revision %s does not exist in subrepo %s\n") %
1553 raise error.Abort(_("revision %s does not exist in subrepo %s\n") %
1554 (revision, self._relpath))
1554 (revision, self._relpath))
1555
1555
1556 @annotatesubrepoerror
1556 @annotatesubrepoerror
1557 def dirty(self, ignoreupdate=False):
1557 def dirty(self, ignoreupdate=False):
1558 if self._gitmissing():
1558 if self._gitmissing():
1559 return self._state[1] != ''
1559 return self._state[1] != ''
1560 if self._gitisbare():
1560 if self._gitisbare():
1561 return True
1561 return True
1562 if not ignoreupdate and self._state[1] != self._gitstate():
1562 if not ignoreupdate and self._state[1] != self._gitstate():
1563 # different version checked out
1563 # different version checked out
1564 return True
1564 return True
1565 # check for staged changes or modified files; ignore untracked files
1565 # check for staged changes or modified files; ignore untracked files
1566 self._gitupdatestat()
1566 self._gitupdatestat()
1567 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1567 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1568 return code == 1
1568 return code == 1
1569
1569
1570 def basestate(self):
1570 def basestate(self):
1571 return self._gitstate()
1571 return self._gitstate()
1572
1572
1573 @annotatesubrepoerror
1573 @annotatesubrepoerror
1574 def get(self, state, overwrite=False):
1574 def get(self, state, overwrite=False):
1575 source, revision, kind = state
1575 source, revision, kind = state
1576 if not revision:
1576 if not revision:
1577 self.remove()
1577 self.remove()
1578 return
1578 return
1579 self._fetch(source, revision)
1579 self._fetch(source, revision)
1580 # if the repo was set to be bare, unbare it
1580 # if the repo was set to be bare, unbare it
1581 if self._gitisbare():
1581 if self._gitisbare():
1582 self._gitcommand(['config', 'core.bare', 'false'])
1582 self._gitcommand(['config', 'core.bare', 'false'])
1583 if self._gitstate() == revision:
1583 if self._gitstate() == revision:
1584 self._gitcommand(['reset', '--hard', 'HEAD'])
1584 self._gitcommand(['reset', '--hard', 'HEAD'])
1585 return
1585 return
1586 elif self._gitstate() == revision:
1586 elif self._gitstate() == revision:
1587 if overwrite:
1587 if overwrite:
1588 # first reset the index to unmark new files for commit, because
1588 # first reset the index to unmark new files for commit, because
1589 # reset --hard will otherwise throw away files added for commit,
1589 # reset --hard will otherwise throw away files added for commit,
1590 # not just unmark them.
1590 # not just unmark them.
1591 self._gitcommand(['reset', 'HEAD'])
1591 self._gitcommand(['reset', 'HEAD'])
1592 self._gitcommand(['reset', '--hard', 'HEAD'])
1592 self._gitcommand(['reset', '--hard', 'HEAD'])
1593 return
1593 return
1594 branch2rev, rev2branch = self._gitbranchmap()
1594 branch2rev, rev2branch = self._gitbranchmap()
1595
1595
1596 def checkout(args):
1596 def checkout(args):
1597 cmd = ['checkout']
1597 cmd = ['checkout']
1598 if overwrite:
1598 if overwrite:
1599 # first reset the index to unmark new files for commit, because
1599 # first reset the index to unmark new files for commit, because
1600 # the -f option will otherwise throw away files added for
1600 # the -f option will otherwise throw away files added for
1601 # commit, not just unmark them.
1601 # commit, not just unmark them.
1602 self._gitcommand(['reset', 'HEAD'])
1602 self._gitcommand(['reset', 'HEAD'])
1603 cmd.append('-f')
1603 cmd.append('-f')
1604 self._gitcommand(cmd + args)
1604 self._gitcommand(cmd + args)
1605 _sanitize(self.ui, self.wvfs, '.git')
1605 _sanitize(self.ui, self.wvfs, '.git')
1606
1606
1607 def rawcheckout():
1607 def rawcheckout():
1608 # no branch to checkout, check it out with no branch
1608 # no branch to checkout, check it out with no branch
1609 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1609 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1610 self._relpath)
1610 self._relpath)
1611 self.ui.warn(_('check out a git branch if you intend '
1611 self.ui.warn(_('check out a git branch if you intend '
1612 'to make changes\n'))
1612 'to make changes\n'))
1613 checkout(['-q', revision])
1613 checkout(['-q', revision])
1614
1614
1615 if revision not in rev2branch:
1615 if revision not in rev2branch:
1616 rawcheckout()
1616 rawcheckout()
1617 return
1617 return
1618 branches = rev2branch[revision]
1618 branches = rev2branch[revision]
1619 firstlocalbranch = None
1619 firstlocalbranch = None
1620 for b in branches:
1620 for b in branches:
1621 if b == 'refs/heads/master':
1621 if b == 'refs/heads/master':
1622 # master trumps all other branches
1622 # master trumps all other branches
1623 checkout(['refs/heads/master'])
1623 checkout(['refs/heads/master'])
1624 return
1624 return
1625 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1625 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1626 firstlocalbranch = b
1626 firstlocalbranch = b
1627 if firstlocalbranch:
1627 if firstlocalbranch:
1628 checkout([firstlocalbranch])
1628 checkout([firstlocalbranch])
1629 return
1629 return
1630
1630
1631 tracking = self._gittracking(branch2rev.keys())
1631 tracking = self._gittracking(branch2rev.keys())
1632 # choose a remote branch already tracked if possible
1632 # choose a remote branch already tracked if possible
1633 remote = branches[0]
1633 remote = branches[0]
1634 if remote not in tracking:
1634 if remote not in tracking:
1635 for b in branches:
1635 for b in branches:
1636 if b in tracking:
1636 if b in tracking:
1637 remote = b
1637 remote = b
1638 break
1638 break
1639
1639
1640 if remote not in tracking:
1640 if remote not in tracking:
1641 # create a new local tracking branch
1641 # create a new local tracking branch
1642 local = remote.split('/', 3)[3]
1642 local = remote.split('/', 3)[3]
1643 checkout(['-b', local, remote])
1643 checkout(['-b', local, remote])
1644 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1644 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1645 # When updating to a tracked remote branch,
1645 # When updating to a tracked remote branch,
1646 # if the local tracking branch is downstream of it,
1646 # if the local tracking branch is downstream of it,
1647 # a normal `git pull` would have performed a "fast-forward merge"
1647 # a normal `git pull` would have performed a "fast-forward merge"
1648 # which is equivalent to updating the local branch to the remote.
1648 # which is equivalent to updating the local branch to the remote.
1649 # Since we are only looking at branching at update, we need to
1649 # Since we are only looking at branching at update, we need to
1650 # detect this situation and perform this action lazily.
1650 # detect this situation and perform this action lazily.
1651 if tracking[remote] != self._gitcurrentbranch():
1651 if tracking[remote] != self._gitcurrentbranch():
1652 checkout([tracking[remote]])
1652 checkout([tracking[remote]])
1653 self._gitcommand(['merge', '--ff', remote])
1653 self._gitcommand(['merge', '--ff', remote])
1654 _sanitize(self.ui, self.wvfs, '.git')
1654 _sanitize(self.ui, self.wvfs, '.git')
1655 else:
1655 else:
1656 # a real merge would be required, just checkout the revision
1656 # a real merge would be required, just checkout the revision
1657 rawcheckout()
1657 rawcheckout()
1658
1658
1659 @annotatesubrepoerror
1659 @annotatesubrepoerror
1660 def commit(self, text, user, date):
1660 def commit(self, text, user, date):
1661 if self._gitmissing():
1661 if self._gitmissing():
1662 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1662 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1663 cmd = ['commit', '-a', '-m', text]
1663 cmd = ['commit', '-a', '-m', text]
1664 env = encoding.environ.copy()
1664 env = encoding.environ.copy()
1665 if user:
1665 if user:
1666 cmd += ['--author', user]
1666 cmd += ['--author', user]
1667 if date:
1667 if date:
1668 # git's date parser silently ignores when seconds < 1e9
1668 # git's date parser silently ignores when seconds < 1e9
1669 # convert to ISO8601
1669 # convert to ISO8601
1670 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1670 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1671 '%Y-%m-%dT%H:%M:%S %1%2')
1671 '%Y-%m-%dT%H:%M:%S %1%2')
1672 self._gitcommand(cmd, env=env)
1672 self._gitcommand(cmd, env=env)
1673 # make sure commit works otherwise HEAD might not exist under certain
1673 # make sure commit works otherwise HEAD might not exist under certain
1674 # circumstances
1674 # circumstances
1675 return self._gitstate()
1675 return self._gitstate()
1676
1676
1677 @annotatesubrepoerror
1677 @annotatesubrepoerror
1678 def merge(self, state):
1678 def merge(self, state):
1679 source, revision, kind = state
1679 source, revision, kind = state
1680 self._fetch(source, revision)
1680 self._fetch(source, revision)
1681 base = self._gitcommand(['merge-base', revision, self._state[1]])
1681 base = self._gitcommand(['merge-base', revision, self._state[1]])
1682 self._gitupdatestat()
1682 self._gitupdatestat()
1683 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1683 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1684
1684
1685 def mergefunc():
1685 def mergefunc():
1686 if base == revision:
1686 if base == revision:
1687 self.get(state) # fast forward merge
1687 self.get(state) # fast forward merge
1688 elif base != self._state[1]:
1688 elif base != self._state[1]:
1689 self._gitcommand(['merge', '--no-commit', revision])
1689 self._gitcommand(['merge', '--no-commit', revision])
1690 _sanitize(self.ui, self.wvfs, '.git')
1690 _sanitize(self.ui, self.wvfs, '.git')
1691
1691
1692 if self.dirty():
1692 if self.dirty():
1693 if self._gitstate() != revision:
1693 if self._gitstate() != revision:
1694 dirty = self._gitstate() == self._state[1] or code != 0
1694 dirty = self._gitstate() == self._state[1] or code != 0
1695 if _updateprompt(self.ui, self, dirty,
1695 if _updateprompt(self.ui, self, dirty,
1696 self._state[1][:7], revision[:7]):
1696 self._state[1][:7], revision[:7]):
1697 mergefunc()
1697 mergefunc()
1698 else:
1698 else:
1699 mergefunc()
1699 mergefunc()
1700
1700
1701 @annotatesubrepoerror
1701 @annotatesubrepoerror
1702 def push(self, opts):
1702 def push(self, opts):
1703 force = opts.get('force')
1703 force = opts.get('force')
1704
1704
1705 if not self._state[1]:
1705 if not self._state[1]:
1706 return True
1706 return True
1707 if self._gitmissing():
1707 if self._gitmissing():
1708 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1708 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1709 # if a branch in origin contains the revision, nothing to do
1709 # if a branch in origin contains the revision, nothing to do
1710 branch2rev, rev2branch = self._gitbranchmap()
1710 branch2rev, rev2branch = self._gitbranchmap()
1711 if self._state[1] in rev2branch:
1711 if self._state[1] in rev2branch:
1712 for b in rev2branch[self._state[1]]:
1712 for b in rev2branch[self._state[1]]:
1713 if b.startswith('refs/remotes/origin/'):
1713 if b.startswith('refs/remotes/origin/'):
1714 return True
1714 return True
1715 for b, revision in branch2rev.iteritems():
1715 for b, revision in branch2rev.iteritems():
1716 if b.startswith('refs/remotes/origin/'):
1716 if b.startswith('refs/remotes/origin/'):
1717 if self._gitisancestor(self._state[1], revision):
1717 if self._gitisancestor(self._state[1], revision):
1718 return True
1718 return True
1719 # otherwise, try to push the currently checked out branch
1719 # otherwise, try to push the currently checked out branch
1720 cmd = ['push']
1720 cmd = ['push']
1721 if force:
1721 if force:
1722 cmd.append('--force')
1722 cmd.append('--force')
1723
1723
1724 current = self._gitcurrentbranch()
1724 current = self._gitcurrentbranch()
1725 if current:
1725 if current:
1726 # determine if the current branch is even useful
1726 # determine if the current branch is even useful
1727 if not self._gitisancestor(self._state[1], current):
1727 if not self._gitisancestor(self._state[1], current):
1728 self.ui.warn(_('unrelated git branch checked out '
1728 self.ui.warn(_('unrelated git branch checked out '
1729 'in subrepo %s\n') % self._relpath)
1729 'in subrepo %s\n') % self._relpath)
1730 return False
1730 return False
1731 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1731 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1732 (current.split('/', 2)[2], self._relpath))
1732 (current.split('/', 2)[2], self._relpath))
1733 ret = self._gitdir(cmd + ['origin', current])
1733 ret = self._gitdir(cmd + ['origin', current])
1734 return ret[1] == 0
1734 return ret[1] == 0
1735 else:
1735 else:
1736 self.ui.warn(_('no branch checked out in subrepo %s\n'
1736 self.ui.warn(_('no branch checked out in subrepo %s\n'
1737 'cannot push revision %s\n') %
1737 'cannot push revision %s\n') %
1738 (self._relpath, self._state[1]))
1738 (self._relpath, self._state[1]))
1739 return False
1739 return False
1740
1740
1741 @annotatesubrepoerror
1741 @annotatesubrepoerror
1742 def add(self, ui, match, prefix, explicitonly, **opts):
1742 def add(self, ui, match, prefix, explicitonly, **opts):
1743 if self._gitmissing():
1743 if self._gitmissing():
1744 return []
1744 return []
1745
1745
1746 (modified, added, removed,
1746 (modified, added, removed,
1747 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1747 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1748 clean=True)
1748 clean=True)
1749
1749
1750 tracked = set()
1750 tracked = set()
1751 # dirstates 'amn' warn, 'r' is added again
1751 # dirstates 'amn' warn, 'r' is added again
1752 for l in (modified, added, deleted, clean):
1752 for l in (modified, added, deleted, clean):
1753 tracked.update(l)
1753 tracked.update(l)
1754
1754
1755 # Unknown files not of interest will be rejected by the matcher
1755 # Unknown files not of interest will be rejected by the matcher
1756 files = unknown
1756 files = unknown
1757 files.extend(match.files())
1757 files.extend(match.files())
1758
1758
1759 rejected = []
1759 rejected = []
1760
1760
1761 files = [f for f in sorted(set(files)) if match(f)]
1761 files = [f for f in sorted(set(files)) if match(f)]
1762 for f in files:
1762 for f in files:
1763 exact = match.exact(f)
1763 exact = match.exact(f)
1764 command = ["add"]
1764 command = ["add"]
1765 if exact:
1765 if exact:
1766 command.append("-f") #should be added, even if ignored
1766 command.append("-f") #should be added, even if ignored
1767 if ui.verbose or not exact:
1767 if ui.verbose or not exact:
1768 ui.status(_('adding %s\n') % match.rel(f))
1768 ui.status(_('adding %s\n') % match.rel(f))
1769
1769
1770 if f in tracked: # hg prints 'adding' even if already tracked
1770 if f in tracked: # hg prints 'adding' even if already tracked
1771 if exact:
1771 if exact:
1772 rejected.append(f)
1772 rejected.append(f)
1773 continue
1773 continue
1774 if not opts.get('dry_run'):
1774 if not opts.get(r'dry_run'):
1775 self._gitcommand(command + [f])
1775 self._gitcommand(command + [f])
1776
1776
1777 for f in rejected:
1777 for f in rejected:
1778 ui.warn(_("%s already tracked!\n") % match.abs(f))
1778 ui.warn(_("%s already tracked!\n") % match.abs(f))
1779
1779
1780 return rejected
1780 return rejected
1781
1781
1782 @annotatesubrepoerror
1782 @annotatesubrepoerror
1783 def remove(self):
1783 def remove(self):
1784 if self._gitmissing():
1784 if self._gitmissing():
1785 return
1785 return
1786 if self.dirty():
1786 if self.dirty():
1787 self.ui.warn(_('not removing repo %s because '
1787 self.ui.warn(_('not removing repo %s because '
1788 'it has changes.\n') % self._relpath)
1788 'it has changes.\n') % self._relpath)
1789 return
1789 return
1790 # we can't fully delete the repository as it may contain
1790 # we can't fully delete the repository as it may contain
1791 # local-only history
1791 # local-only history
1792 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1792 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1793 self._gitcommand(['config', 'core.bare', 'true'])
1793 self._gitcommand(['config', 'core.bare', 'true'])
1794 for f, kind in self.wvfs.readdir():
1794 for f, kind in self.wvfs.readdir():
1795 if f == '.git':
1795 if f == '.git':
1796 continue
1796 continue
1797 if kind == stat.S_IFDIR:
1797 if kind == stat.S_IFDIR:
1798 self.wvfs.rmtree(f)
1798 self.wvfs.rmtree(f)
1799 else:
1799 else:
1800 self.wvfs.unlink(f)
1800 self.wvfs.unlink(f)
1801
1801
1802 def archive(self, archiver, prefix, match=None, decode=True):
1802 def archive(self, archiver, prefix, match=None, decode=True):
1803 total = 0
1803 total = 0
1804 source, revision = self._state
1804 source, revision = self._state
1805 if not revision:
1805 if not revision:
1806 return total
1806 return total
1807 self._fetch(source, revision)
1807 self._fetch(source, revision)
1808
1808
1809 # Parse git's native archive command.
1809 # Parse git's native archive command.
1810 # This should be much faster than manually traversing the trees
1810 # This should be much faster than manually traversing the trees
1811 # and objects with many subprocess calls.
1811 # and objects with many subprocess calls.
1812 tarstream = self._gitcommand(['archive', revision], stream=True)
1812 tarstream = self._gitcommand(['archive', revision], stream=True)
1813 tar = tarfile.open(fileobj=tarstream, mode='r|')
1813 tar = tarfile.open(fileobj=tarstream, mode='r|')
1814 relpath = subrelpath(self)
1814 relpath = subrelpath(self)
1815 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1815 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1816 for i, info in enumerate(tar):
1816 for i, info in enumerate(tar):
1817 if info.isdir():
1817 if info.isdir():
1818 continue
1818 continue
1819 if match and not match(info.name):
1819 if match and not match(info.name):
1820 continue
1820 continue
1821 if info.issym():
1821 if info.issym():
1822 data = info.linkname
1822 data = info.linkname
1823 else:
1823 else:
1824 data = tar.extractfile(info).read()
1824 data = tar.extractfile(info).read()
1825 archiver.addfile(prefix + self._path + '/' + info.name,
1825 archiver.addfile(prefix + self._path + '/' + info.name,
1826 info.mode, info.issym(), data)
1826 info.mode, info.issym(), data)
1827 total += 1
1827 total += 1
1828 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1828 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1829 unit=_('files'))
1829 unit=_('files'))
1830 self.ui.progress(_('archiving (%s)') % relpath, None)
1830 self.ui.progress(_('archiving (%s)') % relpath, None)
1831 return total
1831 return total
1832
1832
1833
1833
1834 @annotatesubrepoerror
1834 @annotatesubrepoerror
1835 def cat(self, match, prefix, **opts):
1835 def cat(self, match, prefix, **opts):
1836 rev = self._state[1]
1836 rev = self._state[1]
1837 if match.anypats():
1837 if match.anypats():
1838 return 1 #No support for include/exclude yet
1838 return 1 #No support for include/exclude yet
1839
1839
1840 if not match.files():
1840 if not match.files():
1841 return 1
1841 return 1
1842
1842
1843 for f in match.files():
1843 for f in match.files():
1844 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1844 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1845 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1845 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1846 self._ctx.node(),
1846 self._ctx.node(),
1847 pathname=self.wvfs.reljoin(prefix, f))
1847 pathname=self.wvfs.reljoin(prefix, f))
1848 fp.write(output)
1848 fp.write(output)
1849 fp.close()
1849 fp.close()
1850 return 0
1850 return 0
1851
1851
1852
1852
1853 @annotatesubrepoerror
1853 @annotatesubrepoerror
1854 def status(self, rev2, **opts):
1854 def status(self, rev2, **opts):
1855 rev1 = self._state[1]
1855 rev1 = self._state[1]
1856 if self._gitmissing() or not rev1:
1856 if self._gitmissing() or not rev1:
1857 # if the repo is missing, return no results
1857 # if the repo is missing, return no results
1858 return scmutil.status([], [], [], [], [], [], [])
1858 return scmutil.status([], [], [], [], [], [], [])
1859 modified, added, removed = [], [], []
1859 modified, added, removed = [], [], []
1860 self._gitupdatestat()
1860 self._gitupdatestat()
1861 if rev2:
1861 if rev2:
1862 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1862 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1863 else:
1863 else:
1864 command = ['diff-index', '--no-renames', rev1]
1864 command = ['diff-index', '--no-renames', rev1]
1865 out = self._gitcommand(command)
1865 out = self._gitcommand(command)
1866 for line in out.split('\n'):
1866 for line in out.split('\n'):
1867 tab = line.find('\t')
1867 tab = line.find('\t')
1868 if tab == -1:
1868 if tab == -1:
1869 continue
1869 continue
1870 status, f = line[tab - 1], line[tab + 1:]
1870 status, f = line[tab - 1], line[tab + 1:]
1871 if status == 'M':
1871 if status == 'M':
1872 modified.append(f)
1872 modified.append(f)
1873 elif status == 'A':
1873 elif status == 'A':
1874 added.append(f)
1874 added.append(f)
1875 elif status == 'D':
1875 elif status == 'D':
1876 removed.append(f)
1876 removed.append(f)
1877
1877
1878 deleted, unknown, ignored, clean = [], [], [], []
1878 deleted, unknown, ignored, clean = [], [], [], []
1879
1879
1880 command = ['status', '--porcelain', '-z']
1880 command = ['status', '--porcelain', '-z']
1881 if opts.get('unknown'):
1881 if opts.get('unknown'):
1882 command += ['--untracked-files=all']
1882 command += ['--untracked-files=all']
1883 if opts.get('ignored'):
1883 if opts.get('ignored'):
1884 command += ['--ignored']
1884 command += ['--ignored']
1885 out = self._gitcommand(command)
1885 out = self._gitcommand(command)
1886
1886
1887 changedfiles = set()
1887 changedfiles = set()
1888 changedfiles.update(modified)
1888 changedfiles.update(modified)
1889 changedfiles.update(added)
1889 changedfiles.update(added)
1890 changedfiles.update(removed)
1890 changedfiles.update(removed)
1891 for line in out.split('\0'):
1891 for line in out.split('\0'):
1892 if not line:
1892 if not line:
1893 continue
1893 continue
1894 st = line[0:2]
1894 st = line[0:2]
1895 #moves and copies show 2 files on one line
1895 #moves and copies show 2 files on one line
1896 if line.find('\0') >= 0:
1896 if line.find('\0') >= 0:
1897 filename1, filename2 = line[3:].split('\0')
1897 filename1, filename2 = line[3:].split('\0')
1898 else:
1898 else:
1899 filename1 = line[3:]
1899 filename1 = line[3:]
1900 filename2 = None
1900 filename2 = None
1901
1901
1902 changedfiles.add(filename1)
1902 changedfiles.add(filename1)
1903 if filename2:
1903 if filename2:
1904 changedfiles.add(filename2)
1904 changedfiles.add(filename2)
1905
1905
1906 if st == '??':
1906 if st == '??':
1907 unknown.append(filename1)
1907 unknown.append(filename1)
1908 elif st == '!!':
1908 elif st == '!!':
1909 ignored.append(filename1)
1909 ignored.append(filename1)
1910
1910
1911 if opts.get('clean'):
1911 if opts.get('clean'):
1912 out = self._gitcommand(['ls-files'])
1912 out = self._gitcommand(['ls-files'])
1913 for f in out.split('\n'):
1913 for f in out.split('\n'):
1914 if not f in changedfiles:
1914 if not f in changedfiles:
1915 clean.append(f)
1915 clean.append(f)
1916
1916
1917 return scmutil.status(modified, added, removed, deleted,
1917 return scmutil.status(modified, added, removed, deleted,
1918 unknown, ignored, clean)
1918 unknown, ignored, clean)
1919
1919
1920 @annotatesubrepoerror
1920 @annotatesubrepoerror
1921 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1921 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1922 node1 = self._state[1]
1922 node1 = self._state[1]
1923 cmd = ['diff', '--no-renames']
1923 cmd = ['diff', '--no-renames']
1924 if opts['stat']:
1924 if opts['stat']:
1925 cmd.append('--stat')
1925 cmd.append('--stat')
1926 else:
1926 else:
1927 # for Git, this also implies '-p'
1927 # for Git, this also implies '-p'
1928 cmd.append('-U%d' % diffopts.context)
1928 cmd.append('-U%d' % diffopts.context)
1929
1929
1930 gitprefix = self.wvfs.reljoin(prefix, self._path)
1930 gitprefix = self.wvfs.reljoin(prefix, self._path)
1931
1931
1932 if diffopts.noprefix:
1932 if diffopts.noprefix:
1933 cmd.extend(['--src-prefix=%s/' % gitprefix,
1933 cmd.extend(['--src-prefix=%s/' % gitprefix,
1934 '--dst-prefix=%s/' % gitprefix])
1934 '--dst-prefix=%s/' % gitprefix])
1935 else:
1935 else:
1936 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1936 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1937 '--dst-prefix=b/%s/' % gitprefix])
1937 '--dst-prefix=b/%s/' % gitprefix])
1938
1938
1939 if diffopts.ignorews:
1939 if diffopts.ignorews:
1940 cmd.append('--ignore-all-space')
1940 cmd.append('--ignore-all-space')
1941 if diffopts.ignorewsamount:
1941 if diffopts.ignorewsamount:
1942 cmd.append('--ignore-space-change')
1942 cmd.append('--ignore-space-change')
1943 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1943 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1944 and diffopts.ignoreblanklines:
1944 and diffopts.ignoreblanklines:
1945 cmd.append('--ignore-blank-lines')
1945 cmd.append('--ignore-blank-lines')
1946
1946
1947 cmd.append(node1)
1947 cmd.append(node1)
1948 if node2:
1948 if node2:
1949 cmd.append(node2)
1949 cmd.append(node2)
1950
1950
1951 output = ""
1951 output = ""
1952 if match.always():
1952 if match.always():
1953 output += self._gitcommand(cmd) + '\n'
1953 output += self._gitcommand(cmd) + '\n'
1954 else:
1954 else:
1955 st = self.status(node2)[:3]
1955 st = self.status(node2)[:3]
1956 files = [f for sublist in st for f in sublist]
1956 files = [f for sublist in st for f in sublist]
1957 for f in files:
1957 for f in files:
1958 if match(f):
1958 if match(f):
1959 output += self._gitcommand(cmd + ['--', f]) + '\n'
1959 output += self._gitcommand(cmd + ['--', f]) + '\n'
1960
1960
1961 if output.strip():
1961 if output.strip():
1962 ui.write(output)
1962 ui.write(output)
1963
1963
1964 @annotatesubrepoerror
1964 @annotatesubrepoerror
1965 def revert(self, substate, *pats, **opts):
1965 def revert(self, substate, *pats, **opts):
1966 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1966 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1967 if not opts.get('no_backup'):
1967 if not opts.get('no_backup'):
1968 status = self.status(None)
1968 status = self.status(None)
1969 names = status.modified
1969 names = status.modified
1970 for name in names:
1970 for name in names:
1971 bakname = scmutil.origpath(self.ui, self._subparent, name)
1971 bakname = scmutil.origpath(self.ui, self._subparent, name)
1972 self.ui.note(_('saving current version of %s as %s\n') %
1972 self.ui.note(_('saving current version of %s as %s\n') %
1973 (name, bakname))
1973 (name, bakname))
1974 self.wvfs.rename(name, bakname)
1974 self.wvfs.rename(name, bakname)
1975
1975
1976 if not opts.get('dry_run'):
1976 if not opts.get('dry_run'):
1977 self.get(substate, overwrite=True)
1977 self.get(substate, overwrite=True)
1978 return []
1978 return []
1979
1979
1980 def shortid(self, revid):
1980 def shortid(self, revid):
1981 return revid[:7]
1981 return revid[:7]
1982
1982
1983 types = {
1983 types = {
1984 'hg': hgsubrepo,
1984 'hg': hgsubrepo,
1985 'svn': svnsubrepo,
1985 'svn': svnsubrepo,
1986 'git': gitsubrepo,
1986 'git': gitsubrepo,
1987 }
1987 }
General Comments 0
You need to be logged in to leave comments. Login now